Skip to content
Snippets Groups Projects
json.mll 9.27 KiB
(**************************************************************************)
(*                                                                        *)
(*  This file is part of Frama-C.                                         *)
(*                                                                        *)
(*  Copyright (C) 2007-2020                                               *)
(*    CEA (Commissariat à l'énergie atomique et aux énergies              *)
(*         alternatives)                                                  *)
(*                                                                        *)
(*  you can redistribute it and/or modify it under the terms of the GNU   *)
(*  Lesser General Public License as published by the Free Software       *)
(*  Foundation, version 2.1.                                              *)
(*                                                                        *)
(*  It is distributed in the hope that it will be useful,                 *)
(*  but WITHOUT ANY WARRANTY; without even the implied warranty of        *)
(*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *)
(*  GNU Lesser General Public License for more details.                   *)
(*                                                                        *)
(*  See the GNU Lesser General Public License version 2.1                 *)
(*  for more details (enclosed in the file licenses/LGPLv2.1).            *)
(*                                                                        *)
(**************************************************************************)

(* -------------------------------------------------------------------------- *)
(* --- Json Parser/Lexer                                                  --- *)
(* -------------------------------------------------------------------------- *)

{

type json =
       [ `Assoc of (string * json) list
       | `Bool of bool
       | `Float of float
       | `Int of int
       | `List of json list
       | `Null
       | `String of string ]

type t = json
let equal = (=)
let compare = Stdlib.compare

type token = EOF | TRUE | FALSE | NULL | KEY of char
           | STR of string | INT of string | DEC of string
}

rule token = parse
 '\n' { Lexing.new_line lexbuf ; token lexbuf }
| [ ' ' '\t' '\r' ] { token lexbuf }
| '"' { let buffer = Buffer.create 80 in
        string buffer lexbuf ;
        STR(Buffer.contents buffer) }
| '-'? [ '0'-'9' ]+ { INT(Lexing.lexeme lexbuf) }
| '-'? [ '0'-'9' ]* '.' ['0'-'9']* ( ['e' 'E'] ['-' '+']? ['0'-'9']+ )?
    { DEC(Lexing.lexeme lexbuf) }
| [ '[' ']' '{' '}' ':' ',' ] as c { KEY c }
| "true" { TRUE }
| "false" { FALSE }
| "null" { NULL }
| eof { EOF }
| _ { failwith "un-recognised token" }

and string buffer = parse
  | '"' { () }
  | "\\\\" { Buffer.add_char buffer '\\' ; string buffer lexbuf }
  | "\\n" { Buffer.add_char buffer '\n' ; string buffer lexbuf }
  | "\\t" { Buffer.add_char buffer '\t' ; string buffer lexbuf }
  | "\\r" { Buffer.add_char buffer '\r' ; string buffer lexbuf }
  | "\\\"" { Buffer.add_char buffer '"' ; string buffer lexbuf }
  | '\n' | eof { failwith "non-terminated string" }
  | _ as c { Buffer.add_char buffer c ; string buffer lexbuf }


{

type input = {
  lexbuf : Lexing.lexbuf ;
  mutable token : token ;
}

let skip input =
  if input.token <> EOF then input.token <- token input.lexbuf

(* Termination hints:
   - unless EOF, parse_value always eat a token
   - parse_array always eat a token or call parse_value with non-EOF input
   - parse_object always eat a token
   - parse_entry always eat a token or call parse_value with non-EOF input
*)
let rec parse_value input =
  match input.token with
  | EOF -> `Null
  | TRUE -> skip input ; `Bool true
  | FALSE -> skip input ; `Bool false
  | NULL -> skip input ; `Null
  | STR a -> skip input ; `String a
  | INT a -> skip input ; (try `Int(int_of_string a) with _ -> `String a)
  | DEC a -> skip input ; (try `Float(float_of_string a) with _ -> `String a)
  | KEY '[' -> skip input ; `List (parse_array [] input)
  | KEY '{' -> skip input ; `Assoc (parse_object [] input)
  | _ -> failwith "unexpected token"

and parse_array es input =
  match input.token with
  | EOF -> failwith "non-terminated array"
  | KEY ']' -> skip input ; List.rev es
  | KEY ',' -> skip input ; parse_array es input
  | _ -> let e = parse_value input in parse_array (e::es) input

and parse_object es input =
  match input.token with
  | EOF -> failwith "non-terminated record"
  | KEY '}' -> skip input ; List.rev es
  | KEY ',' -> skip input ; parse_object es input
  | STR a -> skip input ; let e = parse_entry a input in parse_object (e::es) input
  | _ -> failwith "missing name"

and parse_entry a input =
  match input.token with
  | EOF -> failwith "non-terminated record"
  | KEY ':' -> skip input ; parse_entry a input
  | _ -> a , parse_value input

let parse_file input =
  let content = parse_value input in
  if input.token <> EOF then failwith "unexpected end-of-file" ;
  content

exception Error of Filepath.Normalized.t * int * string

let error lexbuf msg =
  let open Lexing in
  let position = Lexing.lexeme_start_p lexbuf in
  let token = Lexing.lexeme lexbuf in
  let path = Filepath.Normalized.of_string position.pos_fname in
  Error(path,position.pos_lnum,
        Printf.sprintf "%s (at %S)" msg token)

let load_lexbuf lexbuf =
  try
    let token = token lexbuf in
    parse_file { lexbuf ; token }
  with Failure msg -> raise (error lexbuf msg)

let load_string text = load_lexbuf (Lexing.from_string text)
let load_channel ?file inc =
  let lexbuf = Lexing.from_channel inc in
  begin
    match file with
    | None -> ()
    | Some pos_fname ->
      let open Lexing in
      lexbuf.lex_curr_p <- { lexbuf.lex_curr_p with pos_fname }
  end ;
  load_lexbuf lexbuf

let load_file file =
  let inc = open_in file in
  try
    let content = load_channel ~file inc in
    close_in inc ; content
  with e ->
    close_in inc ; raise e

let rec pp fmt v = let open Format in
  match v with
  | `Null -> pp_print_string fmt "null"
  | `Bool b -> pp_print_bool fmt b
  | `String s -> fprintf fmt "%S" s
  | `Int a -> pp_print_int fmt a
  | `Float f -> pp_print_float fmt f
  | `List [] -> pp_print_string fmt "[]"
  | `List (e::es) ->
      Format.fprintf fmt "@[<hov 2>[ %a" pp e ;
      List.iter (fun e -> Format.fprintf fmt ",@ %a" pp e) es ;
      Format.fprintf fmt " ]@]"
  | `Assoc [] -> pp_print_string fmt "{}"
  | `Assoc (e::es) ->
      Format.fprintf fmt "@[<hov 2>{ %a" pp_entry e ;
      List.iter (fun e -> Format.fprintf fmt ",@ %a" pp_entry e) es ;
      Format.fprintf fmt " }@]"

and pp_entry fmt (a,v) = Format.fprintf fmt "@[<hov 2>%S: %a@]" a pp v

let dump_string f s =
  let quote = "\"" in
  f quote ; f (String.escaped s) ; f quote

let rec dump f = function
  | `Null -> f "null"
  | `Bool true -> f "true"
  | `Bool false -> f "false"
  | `String s -> dump_string f s
  | `Int a -> f (string_of_int a)
  | `Float x -> f (string_of_float x)
  | `List [] -> f "[]"
  | `List (e::es) ->
      f "[" ; dump f e ;
      List.iter (fun e -> f "," ; dump f e) es ;
      f "]"
  | `Assoc [] -> f "{}"
  | `Assoc (e::es) ->
      f "{" ;
      dump_entry f e ;
      List.iter (fun e -> f "," ; dump_entry f e) es ;
      f "}"

and dump_entry f (a,v) =
  dump_string f a ; f ":" ; dump f v

let pp_dump fmt v =
  dump (Format.pp_print_string fmt) v

let save_buffer ?(pretty=true) buffer v =
  if pretty then
    Format.fprintf (Format.formatter_of_buffer buffer) "@[%a@]@." pp v
  else
    (dump (Buffer.add_string buffer) v ; Buffer.add_char buffer '\n' )

let save_string ?(pretty=true) v =
  let buffer = Buffer.create 80 in
  save_buffer ~pretty buffer v ;
  Buffer.contents buffer

let save_channel ?(pretty=true) out v =
  if pretty then
    Format.fprintf (Format.formatter_of_out_channel out) "@[%a@]@." pp v
  else
    (dump (output_string out) v ; output_char out '\n' ; flush out)

let save_formatter ?(pretty=true) fmt v =
  if pretty then pp fmt v else pp_dump fmt v

let save_file ?(pretty=true) file v =
  let out = open_out file in
  try
    save_channel ~pretty out v ;
    close_out out
  with e ->
    close_out out ; raise e

let invalid name = raise (Invalid_argument ("Json." ^ name))

let bool = function
  | `Bool b -> b
  | _ -> invalid "bool"

let int = function
  | `Null -> 0
  | `Int n -> n
  | `Float f -> (try int_of_float f with _ -> invalid "int")
  | _ -> invalid "int"

let float = function
  | `Null -> 0.0
  | `Float f -> f
  | `Int n -> (try float_of_int n with _ -> invalid "float")
  | _ -> invalid "float"

let string = function
  | `Null -> ""
  | `Int n -> string_of_int n
  | `Float f -> string_of_float f
  | `String s -> s
  | _ -> invalid "string"

let list = function
  | `Null -> []
  | `List es -> es
  | _ -> invalid "list"

let array = function
  | `Null -> [| |]
  | `List es -> Array.of_list es
  | _ -> invalid "array"

let assoc = function
  | `Null -> []
  | `Assoc fs -> fs
  | _ -> invalid "assoc"
let field f = function
  | `Null -> raise Not_found
  | `Assoc fs -> List.assoc f fs
  | _ -> invalid "field"

let fold f v w = match v with
  | `Null -> w
  | `Assoc fs -> List.fold_left (fun w (e,v) -> f e v w) w fs
  | _ -> invalid "fold"

let of_bool b = `Bool b
let of_int k = `Int k
let of_string s = `String s
let of_float f = `Float f
let of_list xs = `List xs
let of_array xs = `List (Array.to_list xs)
let of_fields m = `Assoc m

}