diff --git a/json.ml b/json.ml new file mode 100644 index 00000000..76c243c1 --- /dev/null +++ b/json.ml @@ -0,0 +1,171 @@ +(* +Copyright (c) 2013, Simon Cruanes +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this +list of conditions and the following disclaimer. Redistributions in binary +form must reproduce the above copyright notice, this list of conditions and the +following disclaimer in the documentation and/or other materials provided with +the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*) + +(** {1 Very simple JSON parser/printer} *) + +type t = + | Int of int + | Float of float + | String of string + | Null + | Bool of bool + | List of t list + | Object of (string * t) list + +(** {2 Print/parse} *) + +let lex = + Genlex.make_lexer ["{"; "}"; ":"; ","; "["; "]"; "true"; "false"; "null"] + +exception EOF + +let parse chars = + let tokens = lex chars in + let open Stream in + let rec next () = + match peek tokens with + | None -> raise EOF (* end stream *) + | Some (Genlex.Kwd "{") -> + junk tokens; + let args = read_pairs [] in + (match peek tokens with + | Some (Genlex.Kwd "}") -> + junk tokens; Object args + | _ -> raise (Stream.Error "expected '}'")) + | Some (Genlex.Kwd "[") -> + junk tokens; + let args = read_list [] in + (match peek tokens with + | Some (Genlex.Kwd "]") -> + junk tokens; List args + | _ -> raise (Stream.Error "expected ']'")) + | Some (Genlex.Int i) -> junk tokens; Int i + | Some (Genlex.Float f) -> junk tokens; Float f + | Some (Genlex.Kwd "true") -> junk tokens; Bool true + | Some (Genlex.Kwd "false") -> junk tokens; Bool false + | Some (Genlex.Kwd "null") -> junk tokens; Null + | _ -> raise (Stream.Error "expected JSON value") + and read_list acc = + match peek tokens with + | Some (Genlex.Kwd "]") -> List.rev acc (* yield *) + | _ -> + let t = next () in + (match peek tokens with + | Some (Genlex.Kwd ",") -> + junk tokens; + read_list (t::acc) (* next *) + | Some (Genlex.Kwd "]") -> + read_list (t::acc) (* next *) + | Some (Genlex.Kwd "]") -> List.rev acc (* yield *) + | _ -> raise (Stream.Error "expected ','")) + and read_pairs acc = + match peek tokens with + | Some (Genlex.Kwd "}") -> List.rev acc (* yield *) + | _ -> + let k, v = pair () in + (match peek tokens with + | Some (Genlex.Kwd ",") -> + junk tokens; + read_pairs ((k,v)::acc) (* next *) + | Some (Genlex.Kwd "}") -> + read_pairs ((k,v)::acc) (* next *) + | _ -> raise (Stream.Error "expected ','")) + and pair () = + match Stream.npeek 2 tokens with + | [Genlex.String k; Genlex.Kwd ":"] -> + junk tokens; junk tokens; + let v = next () in + k, v + | _ -> raise (Stream.Error "expected pair") + in + Stream.from + (fun _ -> + try Some (next ()) + with EOF -> None) + +let parse_one chars = + Stream.peek (parse chars) + +let rec output oc t = + match t with + | Null -> output_string oc "null" + | Bool true -> output_string oc "true" + | Bool false -> output_string oc "false" + | Int i -> Printf.fprintf oc "%d" i + | Float f -> Printf.fprintf oc "%f" f + | String s -> Printf.fprintf oc "\"%s\"" (String.escaped s) + | List l -> + output_string oc "["; + List.iteri + (fun i t -> + (if i > 0 then output_string oc ", "); + output oc t) + l; + output_string oc "]" + | Object pairs -> + output_string oc "{"; + List.iteri + (fun i (k,v) -> + (if i > 0 then output_string oc ", "); + Printf.fprintf oc "\"%s\": " k; + output oc v) + pairs; + output_string oc "}" + +let rec pp fmt t = + match t with + | Null -> Format.pp_print_string fmt "null" + | Bool true -> Format.pp_print_string fmt "true" + | Bool false -> Format.pp_print_string fmt "false" + | Int i -> Format.fprintf fmt "%d" i + | Float f -> Format.fprintf fmt "%f" f + | String s -> Format.fprintf fmt "\"%s\"" (String.escaped s) + | List l -> + Format.pp_print_string fmt "["; + List.iteri + (fun i t -> + (if i > 0 then Format.pp_print_string fmt ", "); + pp fmt t) + l; + Format.pp_print_string fmt "]" + | Object pairs -> + Format.pp_print_string fmt "{"; + List.iteri + (fun i (k,v) -> + (if i > 0 then Format.pp_print_string fmt ", "); + Format.fprintf fmt "\"%s\": " k; + pp fmt v) + pairs; + Format.pp_print_string fmt "}" + +let to_string t = + let buf = Buffer.create 16 in + Format.bprintf buf "%a@?" pp t; + Buffer.contents buf + +(** {2 Utils *) + +exception TypeError of string * t + diff --git a/json.mli b/json.mli new file mode 100644 index 00000000..3c112d77 --- /dev/null +++ b/json.mli @@ -0,0 +1,62 @@ +(* +Copyright (c) 2013, Simon Cruanes +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this +list of conditions and the following disclaimer. Redistributions in binary +form must reproduce the above copyright notice, this list of conditions and the +following disclaimer in the documentation and/or other materials provided with +the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*) + +(** {1 Very simple JSON parser/printer} *) + +type t = + | Int of int + | Float of float + | String of string + | Null + | Bool of bool + | List of t list + | Object of (string * t) list + +(** {2 Print/parse} *) + +val parse : char Stream.t -> t Stream.t + +val parse_one : char Stream.t -> t option + +val output : out_channel -> t -> unit + +val pp : Format.formatter -> t -> unit + +val to_string : t -> string + +(** {2 Utils *) + +exception TypeError of string * t + +(* +val to_int : t -> int +val to_float : t -> float +val to_string : t -> string +val to_bool : t -> bool +val to_null : t -> unit +val to_list : t -> t list +val to_object : t -> (string * t) list + +*)