diff --git a/examples/.gitignore b/examples/.gitignore new file mode 100644 index 00000000..698fc63c --- /dev/null +++ b/examples/.gitignore @@ -0,0 +1,5 @@ +filtering +filtering_pos +*.cmi +*.cmx +*.o diff --git a/examples/filtering.json b/examples/filtering.json index a576b979..311db8f7 100644 --- a/examples/filtering.json +++ b/examples/filtering.json @@ -1,4 +1,4 @@ -{ +{ "id": "398eb027", "name": "John Doe", "pages": [ diff --git a/examples/filtering_broken.json b/examples/filtering_broken.json new file mode 100644 index 00000000..a4d296d0 --- /dev/null +++ b/examples/filtering_broken.json @@ -0,0 +1,22 @@ +{ + "id": "398eb027", + "name": "John Doe", + "pages": [ + { + "id": 1, + "title": "The Art of Flipping Coins", + "url": "http://example.com/398eb027/1" + }, + { "id": 2, "deleted": true }, + { + "id": 3, + "title": { "foo": "an example of broken value" }, + "url": "http://example.com/398eb027/3" + }, + { + "id": 4, + "title": "Flying Bananas", + "url": "http://example.com/398eb027/4" + } + ] +} diff --git a/examples/filtering_pos.ml b/examples/filtering_pos.ml new file mode 100644 index 00000000..c59df458 --- /dev/null +++ b/examples/filtering_pos.ml @@ -0,0 +1,51 @@ +open Yojson.SafePos.Util +open Format + +let pp_position ppf pos = + let open Yojson in + let fnamestr = + match pos.file_name with + | None -> "" + | Some(x) -> " in '" ^ x ^ "'" + in + let lnum1 = pos.start_line in + let lnum2 = pos.end_line in + if lnum1 = lnum2 then + fprintf ppf "line %d, column %d-%d%s" + lnum1 pos.start_column pos.end_column fnamestr + else + fprintf ppf "line %d column %d to line %d, column %d" + lnum1 pos.start_column lnum2 pos.end_column + +let print_with_pos pp ((pos, _) as a) = + printf "%a (%a)@," pp a pp_position pos + +let pp_object ppf _ = + fprintf ppf "" + +let extract_titles json = + let objs = + [json] + |> filter_member "pages" + |> flatten + in + List.iter (print_with_pos pp_object) objs; + objs + |> filter_member "title" + |> List.map to_string + +let main () = + printf "@["; + begin + try + let json = Yojson.SafePos.from_channel stdin in + List.iter (printf "%s@,") (extract_titles json); + with + | Yojson.SafePos.Util.Type_error(msg, json) -> + printf "! [ERROR] %s:@," msg; + printf "! "; + print_with_pos Yojson.SafePos.pretty_print json + end; + printf "@]" + +let () = main () diff --git a/examples/run-examples.sh b/examples/run-examples.sh index 4c72ed6f..2123cec4 100755 --- a/examples/run-examples.sh +++ b/examples/run-examples.sh @@ -3,3 +3,10 @@ echo "----- Example 1: filtering -----" ocamlfind ocamlopt -o filtering filtering.ml -package yojson -linkpkg ./filtering < filtering.json + +echo "----- Example 2: filtering_pos -----" +ocamlfind ocamlopt -o filtering_pos filtering_pos.ml -package yojson -linkpkg +echo "..... Example 2.1 ....." +./filtering_pos < filtering.json +echo "..... Example 2.2 ....." +./filtering_pos < filtering_broken.json diff --git a/lib/common.ml b/lib/common.ml index 935b2c8b..d3ac6f19 100644 --- a/lib/common.ml +++ b/lib/common.ml @@ -72,13 +72,6 @@ let code_of_surrogate_pair i j = let utf8_of_surrogate_pair buf i j = utf8_of_code buf (code_of_surrogate_pair i j) -let is_object_or_array x = - match x with - `List _ - | `Assoc _ -> true - | _ -> false - - type lexer_state = { buf : Bi_outbuf.t; (* Buffer used to accumulate substrings *) @@ -116,3 +109,11 @@ let init_lexer ?buf ?fname ?(lnum = 1) () = bol = 0; fname = fname } + +type position = { + file_name : string option; + start_line : int; + start_column : int; + end_line : int; + end_column : int; +} diff --git a/lib/common.mli b/lib/common.mli index 415b3630..52e5dafc 100644 --- a/lib/common.mli +++ b/lib/common.mli @@ -36,6 +36,14 @@ val init_lexer : unit -> lexer_state (** Create a fresh lexer_state record. *) +type position = { + file_name : string option; + start_line : int; + start_column : int; + end_line : int; + end_column : int; +} + (** The type for code positions. *) (**/**) (* begin undocumented section *) diff --git a/lib/jbuild b/lib/jbuild index 3f82564b..28d55429 100644 --- a/lib/jbuild +++ b/lib/jbuild @@ -13,7 +13,8 @@ write2.ml common.ml util.ml - type.ml)) + type.ml + position.ml)) (action (run cppo ${<} -o ${@})))) (rule diff --git a/lib/position.ml b/lib/position.ml new file mode 100644 index 00000000..1205f497 --- /dev/null +++ b/lib/position.ml @@ -0,0 +1,57 @@ +#ifdef POSITION + let project (_, x) = x + + let inject x = + let dummy = + { + file_name = Some "(dummy)"; + start_line = 0; + start_column = 0; + end_line = 0; + end_column = 0; + } + in + (dummy, x) + + let rec forget_positions ((_, x) : json) = + match x with + | `Null -> `Null + | `Bool b -> `Bool b + #ifdef INT + | `Int i -> `Int i + #endif + #ifdef INTLIT + | `Intlit s -> `Intlit s + #endif + #ifdef FLOAT + | `Float r -> `Float r + #endif + #ifdef FLOATLIT + | `Floatlit s -> `Floatlit s + #endif + #ifdef STRING + | `String s -> `String s + #endif + #ifdef STRINGLIT + | `Stringlit s -> `Stringlit s + #endif + | `Assoc assoc -> `Assoc (assoc |> List.map (fun (k, v) -> (k, forget_positions v))) + | `List js -> `List (js |> List.map forget_positions) + #ifdef TUPLE + | `Tuple js -> `Tuple (js |> List.map forget_positions) + #endif + #ifdef VARIANT + | `Variant (s, jopt) -> + begin + match jopt with + | None -> `Variant (s, None) + | Some(j) -> `Variant (s, Some(forget_positions j)) + end + #endif +#else + let project x = x + + let inject x = x + + let forget_positions x = x +#endif diff --git a/lib/read.mll b/lib/read.mll index 14f530fe..b0603822 100644 --- a/lib/read.mll +++ b/lib/read.mll @@ -22,9 +22,6 @@ result end - open Printf - open Lexing - (* see description in common.mli *) type lexer_state = Lexer_state.t = { buf : Bi_outbuf.t; @@ -33,6 +30,52 @@ mutable fname : string option; } + let get_raw_position v lexbuf = + let open Lexing in + let fname = v.fname in + let offs = lexbuf.lex_abs_pos in + let bol = v.bol in + let pos1 = offs + lexbuf.lex_start_pos - bol in + let pos2 = max pos1 (offs + lexbuf.lex_curr_pos - bol) in + (fname, v.lnum, pos1, pos2) + + #ifdef POSITION + let range rawpos_start rawpos_end x : json = + let open Lexing in + let (fname, lnum1, pos1, _) = rawpos_start in + let (_, lnum2, _, pos2) = rawpos_end in + let pos = + { + file_name = fname; + start_line = lnum1; + start_column = pos1; + end_line = lnum2; + end_column = pos2; + } + in + (pos, x) + + let single v lexbuf x : json = + let (fname, lnum, pos1, pos2) = get_raw_position v lexbuf in + let pos = + { + file_name = fname; + start_line = lnum; + start_column = pos1; + end_line = lnum; + end_column = pos2; + } + in + (pos, x) + #else + let range _ _ (x : json) : json = x + + let single _ _ (x : json) : json = x + #endif + + open Printf + open Lexing + let dec c = Char.code c - 48 @@ -44,10 +87,7 @@ | _ -> assert false let custom_error descr v lexbuf = - let offs = lexbuf.lex_abs_pos - 1 in - let bol = v.bol in - let pos1 = offs + lexbuf.lex_start_pos - bol - 1 in - let pos2 = max pos1 (offs + lexbuf.lex_curr_pos - bol) in + let (fname, lnum, pos1, pos2) = get_raw_position v lexbuf in let file_line = match v.fname with None -> "Line" @@ -56,11 +96,11 @@ in let bytes = if pos1 = pos2 then - sprintf "byte %i" (pos1+1) + sprintf "byte %i" pos1 else - sprintf "bytes %i-%i" (pos1+1) (pos2+1) + sprintf "bytes %i-%i" pos1 pos2 in - let msg = sprintf "%s %i, %s:\n%s" file_line v.lnum bytes descr in + let msg = sprintf "%s %i, %s:\n%s" file_line lnum bytes descr in json_error msg @@ -101,11 +141,11 @@ let make_positive_int v lexbuf = #ifdef INT - try `Int (extract_positive_int lexbuf) + try single v lexbuf (`Int (extract_positive_int lexbuf)) with Int_overflow -> #endif #ifdef INTLIT - `Intlit (lexeme lexbuf) + single v lexbuf (`Intlit (lexeme lexbuf)) #else lexer_error "Int overflow" v lexbuf #endif @@ -128,11 +168,11 @@ let make_negative_int v lexbuf = #ifdef INT - try `Int (extract_negative_int lexbuf) + try single v lexbuf (`Int (extract_negative_int lexbuf)) with Int_overflow -> #endif #ifdef INTLIT - `Intlit (lexeme lexbuf) + single v lexbuf (`Intlit (lexeme lexbuf)) #else lexer_error "Int overflow" v lexbuf #endif @@ -185,49 +225,54 @@ let optjunk32 = (eof | _ (eof | _ (eof | _ (eof | _ (eof | optjunk28))))) let junk = optjunk32 rule read_json v = parse - | "true" { `Bool true } - | "false" { `Bool false } - | "null" { `Null } + | "true" { single v lexbuf (`Bool true) } + | "false" { single v lexbuf (`Bool false) } + | "null" { single v lexbuf (`Null) } | "NaN" { #ifdef FLOAT - `Float nan + single v lexbuf (`Float nan) #elif defined FLOATLIT - `Floatlit "NaN" + single v lexbuf (`Floatlit "NaN") #endif } | "Infinity" { #ifdef FLOAT - `Float infinity + single v lexbuf (`Float infinity) #elif defined FLOATLIT - `Floatlit "Infinity" + single v lexbuf (`Floatlit "Infinity") #endif } | "-Infinity" { #ifdef FLOAT - `Float neg_infinity + single v lexbuf (`Float neg_infinity) #elif defined FLOATLIT - `Floatlit "-Infinity" + single v lexbuf (`Floatlit "-Infinity") #endif } | '"' { #ifdef STRING Bi_outbuf.clear v.buf; - `String (finish_string v lexbuf) + let pos_start = get_raw_position v lexbuf in + let (pos_end, s) = finish_string_with_position v lexbuf in + range pos_start pos_end (`String (s)) #elif defined STRINGLIT - `Stringlit (finish_stringlit v lexbuf) + let pos_start = get_raw_position v lexbuf in + let (pos_end, s) = finish_stringlit_with_position v lexbuf in + range pos_start pos_end (`Stringlit (s)) #endif } | positive_int { make_positive_int v lexbuf } | '-' positive_int { make_negative_int v lexbuf } | float { #ifdef FLOAT - `Float (float_of_string (lexeme lexbuf)) + single v lexbuf (`Float (float_of_string (lexeme lexbuf))) #elif defined FLOATLIT - `Floatlit (lexeme lexbuf) + single v lexbuf (`Floatlit (lexeme lexbuf)) #endif } | '{' { let acc = ref [] in + let pos_start = get_raw_position v lexbuf in try read_space v lexbuf; read_object_end lexbuf; @@ -248,10 +293,12 @@ rule read_json v = parse done; assert false with End_of_object -> - `Assoc (List.rev !acc) + let pos_end = get_raw_position v lexbuf in + range pos_start pos_end (`Assoc (List.rev !acc)) } | '[' { let acc = ref [] in + let pos_start = get_raw_position v lexbuf in try read_space v lexbuf; read_array_end lexbuf; @@ -264,12 +311,14 @@ rule read_json v = parse done; assert false with End_of_array -> - `List (List.rev !acc) + let pos_end = get_raw_position v lexbuf in + range pos_start pos_end (`List (List.rev !acc)) } | '(' { #ifdef TUPLE let acc = ref [] in + let pos_start = get_raw_position v lexbuf in try read_space v lexbuf; read_tuple_end lexbuf; @@ -282,7 +331,8 @@ rule read_json v = parse done; assert false with End_of_tuple -> - `Tuple (List.rev !acc) + let pos_end = get_raw_position v lexbuf in + range pos_start pos_end (`Tuple (List.rev !acc)) #else long_error "Invalid token" v lexbuf #endif @@ -290,10 +340,12 @@ rule read_json v = parse | '<' { #ifdef VARIANT + let pos_start = get_raw_position v lexbuf in read_space v lexbuf; let cons = read_ident v lexbuf in read_space v lexbuf; - `Variant (cons, finish_variant v lexbuf) + let pos_end = get_raw_position v lexbuf in + range pos_start pos_end (`Variant (cons, finish_variant v lexbuf)) #else long_error "Invalid token" v lexbuf #endif @@ -307,12 +359,12 @@ rule read_json v = parse | _ { long_error "Invalid token" v lexbuf } -and finish_string v = parse - '"' { Bi_outbuf.contents v.buf } +and finish_string_with_position v = parse + '"' { let pos_end = get_raw_position v lexbuf in (pos_end, Bi_outbuf.contents v.buf) } | '\\' { finish_escaped_char v lexbuf; - finish_string v lexbuf } + finish_string_with_position v lexbuf } | [^ '"' '\\']+ { add_lexeme v.buf lexbuf; - finish_string v lexbuf } + finish_string_with_position v lexbuf } | eof { custom_error "Unexpected end of input" v lexbuf } and map_string v f = parse @@ -360,14 +412,15 @@ and finish_surrogate_pair v x = parse for code point beyond U+FFFF" v lexbuf } | eof { custom_error "Unexpected end of input" v lexbuf } -and finish_stringlit v = parse +and finish_stringlit_with_position v = parse ( '\\' (['"' '\\' '/' 'b' 'f' 'n' 'r' 't'] | 'u' hex hex hex hex) | [^'"' '\\'] )* '"' { let len = lexbuf.lex_curr_pos - lexbuf.lex_start_pos in let s = Bytes.create (len+1) in + let pos_end = get_raw_position v lexbuf in Bytes.set s 0 '"'; Bytes.blit lexbuf.lex_buffer lexbuf.lex_start_pos s 1 len; - Bytes.to_string s + (pos_end, Bytes.to_string s) } | _ { long_error "Invalid string literal" v lexbuf } | eof { custom_error "Unexpected end of input" v lexbuf } @@ -455,7 +508,7 @@ and read_int v = parse lexer_error "Int overflow" v lexbuf } | '"' { (* Support for double-quoted "ints" *) Bi_outbuf.clear v.buf; - let s = finish_string v lexbuf in + let (_, s) = finish_string_with_position v lexbuf in try (* Any OCaml-compliant int will pass, including hexadecimal and octal notations, @@ -476,7 +529,7 @@ and read_int32 v = parse lexer_error "Int32 overflow" v lexbuf } | '"' { (* Support for double-quoted "ints" *) Bi_outbuf.clear v.buf; - let s = finish_string v lexbuf in + let (_, s) = finish_string_with_position v lexbuf in try (* Any OCaml-compliant int will pass, including hexadecimal and octal notations, @@ -497,7 +550,7 @@ and read_int64 v = parse lexer_error "Int32 overflow" v lexbuf } | '"' { (* Support for double-quoted "ints" *) Bi_outbuf.clear v.buf; - let s = finish_string v lexbuf in + let (_, s) = finish_string_with_position v lexbuf in try (* Any OCaml-compliant int will pass, including hexadecimal and octal notations, @@ -518,7 +571,7 @@ and read_number v = parse | "-Infinity" { neg_infinity } | number { float_of_string (lexeme lexbuf) } | '"' { Bi_outbuf.clear v.buf; - let s = finish_string v lexbuf in + let (_, s) = finish_string_with_position v lexbuf in try (* Any OCaml-compliant float will pass, including hexadecimal and octal notations, @@ -540,13 +593,15 @@ and read_number v = parse and read_string v = parse '"' { Bi_outbuf.clear v.buf; - finish_string v lexbuf } + let (_, s) = finish_string_with_position v lexbuf in + s } | _ { long_error "Expected '\"' but found" v lexbuf } | eof { custom_error "Unexpected end of input" v lexbuf } and read_ident v = parse '"' { Bi_outbuf.clear v.buf; - finish_string v lexbuf } + let (_, s) = finish_string_with_position v lexbuf in + s } | ident as s { s } | _ { long_error "Expected string or identifier but found" v lexbuf } @@ -1211,4 +1266,12 @@ and junk = parse to_string (from_string s) let validate_json _path _value = None + + let finish_string v lexbuf = + let (_, s) = finish_string_with_position v lexbuf in + s + + let finish_stringlit v lexbuf = + let (_, s) = finish_stringlit_with_position v lexbuf in + s } diff --git a/lib/safe.ml b/lib/safe.ml index 0db22015..cbeb4a32 100644 --- a/lib/safe.ml +++ b/lib/safe.ml @@ -1,5 +1,6 @@ -let rec to_basic : json -> Basic.json = function - `Null +let rec to_basic (x : json) : Basic.json = + match project x with + | `Null | `Bool _ | `Int _ | `Float _ diff --git a/lib/type.ml b/lib/type.ml index aa503669..d62fd238 100644 --- a/lib/type.ml +++ b/lib/type.ml @@ -1,6 +1,9 @@ (** {3 Type of the JSON tree} *) type json = +#ifdef POSITION + position * +#endif [ | `Null | `Bool of bool diff --git a/lib/util.ml b/lib/util.ml index db4574fc..6c36cc72 100644 --- a/lib/util.ml +++ b/lib/util.ml @@ -1,6 +1,7 @@ exception Type_error of string * json -let typeof = function +let typeof js = + match project js with | `Assoc _ -> "object" | `Bool _ -> "bool" | `Float _ -> "float" @@ -20,88 +21,112 @@ let ( |> ) = ( |> ) let assoc name obj = try List.assoc name obj - with Not_found -> `Null + with Not_found -> inject `Null -let member name = function +let member name js = + match project js with | `Assoc obj -> assoc name obj - | js -> typerr ("Can't get member '" ^ name ^ "' of non-object type ") js + | _ -> typerr ("Can't get member '" ^ name ^ "' of non-object type ") js -let index i = function - | `List l as js -> +let index i js = + match project js with + | `List l -> let len = List.length l in let wrapped_index = if i < 0 then len + i else i in if wrapped_index < 0 || wrapped_index >= len then raise (Undefined ("Index " ^ string_of_int i ^ " out of bounds", js)) else List.nth l wrapped_index - | js -> typerr ("Can't get index " ^ string_of_int i + | _ -> typerr ("Can't get index " ^ string_of_int i ^ " of non-array type ") js -let map f = function - | `List l -> `List (List.map f l) - | js -> typerr "Can't map function over non-array type " js - -let to_assoc = function +let map f js = +#ifdef POSITION + let (pos, x) = js in + let posf v = (pos, v) in +#else + let x = js in + let posf v = v in +#endif + match x with + | `List l -> posf (`List (List.map f l)) + | _ -> typerr "Can't map function over non-array type " js + +let to_assoc js = + match project js with | `Assoc obj -> obj - | js -> typerr "Expected object, got " js + | _ -> typerr "Expected object, got " js -let to_option f = function +let to_option f js = + match project js with | `Null -> None - | x -> Some (f x) + | _ -> Some (f js) -let to_bool = function +let to_bool js = + match project js with | `Bool b -> b - | js -> typerr "Expected bool, got " js + | _ -> typerr "Expected bool, got " js -let to_bool_option = function +let to_bool_option js = + match project js with | `Bool b -> Some b | `Null -> None - | js -> typerr "Expected bool or null, got " js + | _ -> typerr "Expected bool or null, got " js -let to_number = function +let to_number js = + match project js with | `Int i -> float i | `Float f -> f - | js -> typerr "Expected number, got " js + | _ -> typerr "Expected number, got " js -let to_number_option = function +let to_number_option js = + match project js with | `Int i -> Some (float i) | `Float f -> Some f | `Null -> None - | js -> typerr "Expected number or null, got " js + | _ -> typerr "Expected number or null, got " js -let to_float = function +let to_float js = + match project js with | `Float f -> f - | js -> typerr "Expected float, got " js + | _ -> typerr "Expected float, got " js -let to_float_option = function +let to_float_option js = + match project js with | `Float f -> Some f | `Null -> None - | js -> typerr "Expected float or null, got " js + | _ -> typerr "Expected float or null, got " js -let to_int = function +let to_int js = + match project js with | `Int i -> i - | js -> typerr "Expected int, got " js + | _ -> typerr "Expected int, got " js -let to_int_option = function +let to_int_option js = + match project js with | `Int i -> Some i | `Null -> None - | js -> typerr "Expected int or null, got " js + | _ -> typerr "Expected int or null, got " js -let to_list = function +let to_list js = + match project js with | `List l -> l - | js -> typerr "Expected array, got " js + | _ -> typerr "Expected array, got " js -let to_string = function +let to_string js = + match project js with | `String s -> s - | js -> typerr "Expected string, got " js + | _ -> typerr "Expected string, got " js -let to_string_option = function +let to_string_option js = + match project js with | `String s -> Some s | `Null -> None - | js -> typerr "Expected string or null, got " js + | _ -> typerr "Expected string or null, got " js -let convert_each f = function +let convert_each f js = + match project js with | `List l -> List.map f l - | js -> typerr "Can't convert each element of non-array type " js + | _ -> typerr "Can't convert each element of non-array type " js let rec rev_filter_map f acc l = @@ -118,8 +143,8 @@ let filter_map f l = let rec rev_flatten acc l = match l with [] -> acc - | x :: tl -> - match x with + | js :: tl -> + match project js with `List l2 -> rev_flatten (List.rev_append l2 acc) tl | _ -> rev_flatten acc tl @@ -127,8 +152,8 @@ let flatten l = List.rev (rev_flatten [] l) let filter_index i l = - filter_map ( - function + filter_map (fun js -> + match project js with `List l -> (try Some (List.nth l i) with _ -> None) @@ -136,15 +161,15 @@ let filter_index i l = ) l let filter_list l = - filter_map ( - function + filter_map (fun js -> + match project js with `List l -> Some l | _ -> None ) l let filter_member k l = - filter_map ( - function + filter_map (fun js -> + match project js with `Assoc l -> (try Some (List.assoc k l) with _ -> None) @@ -152,48 +177,86 @@ let filter_member k l = ) l let filter_assoc l = - filter_map ( - function + filter_map (fun js -> + match project js with `Assoc l -> Some l | _ -> None ) l let filter_bool l = - filter_map ( - function + filter_map (fun js -> + match project js with `Bool x -> Some x | _ -> None ) l let filter_int l = - filter_map ( - function + filter_map (fun js -> + match project js with `Int x -> Some x | _ -> None ) l let filter_float l = - filter_map ( - function + filter_map (fun js -> + match project js with `Float x -> Some x | _ -> None ) l let filter_number l = - filter_map ( - function + filter_map (fun js -> + match project js with `Int x -> Some (float x) | `Float x -> Some x | _ -> None ) l let filter_string l = - filter_map ( - function + filter_map (fun js -> + match project js with `String x -> Some x | _ -> None ) l +#ifdef POSITION +let filter_bool_with_pos l = + filter_map (fun (pos, v) -> + match v with + `Bool x -> Some (pos, x) + | _ -> None + ) l + +let filter_int_with_pos l = + filter_map (fun (pos, v) -> + match v with + `Int x -> Some (pos, x) + | _ -> None + ) l + +let filter_float_with_pos l = + filter_map (fun (pos, v) -> + match v with + `Float x -> Some (pos, x) + | _ -> None + ) l + +let filter_number_with_pos l = + filter_map (fun (pos, v) -> + match v with + `Int x -> Some (pos, float x) + | `Float x -> Some (pos, x) + | _ -> None + ) l + +let filter_string_with_pos l = + filter_map (fun (pos, v) -> + match v with + `String x -> Some (pos, x) + | _ -> None + ) l +#endif + let keys o = to_assoc o |> List.map (fun (key, _) -> key) @@ -201,6 +264,15 @@ let values o = to_assoc o |> List.map (fun (_, value) -> value) let combine (first : json) (second : json) = - match (first, second) with - | (`Assoc a, `Assoc b) -> (`Assoc (a @ b) : json) - | (a, b) -> raise (Invalid_argument "Expected two objects, check inputs") +#ifdef POSITION + let (pos, x) = first in + let (_, y) = second in + let f v = (pos, v) in +#else + let x = first in + let y = second in + let f v = v in +#endif + match (x, y) with + | (`Assoc a, `Assoc b) -> (f (`Assoc (a @ b)) : json) + | (_, _) -> raise (Invalid_argument "Expected two objects, check inputs") diff --git a/lib/util.mli b/lib/util.mli index 4d19d96a..5a832022 100644 --- a/lib/util.mli +++ b/lib/util.mli @@ -200,3 +200,20 @@ val filter_number : json list -> float list val filter_string : json list -> string list (** Expects JSON strings and unwraps them. *) + +#ifdef POSITION +val filter_bool_with_pos : json list -> (position * bool) list + (** Same as [filter_bool], but preserves the code position of the values. *) + +val filter_int_with_pos : json list -> (position * int) list + (** Same as [filter_int], but preserves the code position of the values. *) + +val filter_float_with_pos : json list -> (position * float) list + (** Same as [filter_float], but preserves the code position of the values. *) + +val filter_number_with_pos : json list -> (position * float) list + (** Same as [filter_number], but preserves the code position of the values. *) + +val filter_string_with_pos : json list -> (position * string) list + (** Same as [filter_string], but preserves the code position of the values. *) +#endif diff --git a/lib/write.ml b/lib/write.ml index 6b0749be..dc6c80ac 100644 --- a/lib/write.ml +++ b/lib/write.ml @@ -293,8 +293,8 @@ let iter2 f_elt f_sep x = function let f_sep ob = Bi_outbuf.add_char ob ',' -let rec write_json ob (x : json) = - match x with +let rec write_json ob (js : json) = + match project js with `Null -> write_null ob () | `Bool b -> write_bool ob b #ifdef INT @@ -360,8 +360,8 @@ and write_variant ob s o = #endif -let rec write_std_json ob (x : json) = - match x with +let rec write_std_json ob (js : json) = + match project js with `Null -> write_null ob () | `Bool b -> write_bool ob b #ifdef INT @@ -424,7 +424,14 @@ and write_std_variant ob s o = #endif -let to_outbuf ?(std = false) ob x = +let is_object_or_array (js : json) = + match project js with + `List _ + | `Assoc _ -> true + | _ -> false + + +let to_outbuf ?(std = false) ob (x : json) = if std then ( if not (is_object_or_array x) then json_error "Root is not an object or array" @@ -510,21 +517,28 @@ let stream_to_file ?len ?std file st = raise e -let rec sort = function +let rec sort (x : json) : json = +#ifdef POSITION + let (pos, x) = x in + let return v = (pos, v) in +#else + let return v = v in +#endif + match x with | `Assoc l -> let l = List.rev (List.rev_map (fun (k, v) -> (k, sort v)) l) in - `Assoc (List.stable_sort (fun (a, _) (b, _) -> String.compare a b) l) + return (`Assoc (List.stable_sort (fun (a, _) (b, _) -> String.compare a b) l)) | `List l -> - `List (List.rev (List.rev_map sort l)) + return (`List (List.rev (List.rev_map sort l))) #ifdef TUPLE | `Tuple l -> - `Tuple (List.rev (List.rev_map sort l)) + return (`Tuple (List.rev (List.rev_map sort l))) #endif #ifdef VARIANT | `Variant (k, Some v) as x -> let v' = sort v in - if v == v' then x + if v == v' then return x else - `Variant (k, Some v') + return (`Variant (k, Some v')) #endif - | x -> x + | x -> return x diff --git a/lib/write2.ml b/lib/write2.ml index 17d83504..d449dea2 100644 --- a/lib/write2.ml +++ b/lib/write2.ml @@ -1,11 +1,14 @@ -let pretty_format ?std (x : json) = +let pretty_format ?std (js : json) = + let x = forget_positions js in Pretty.format ?std (x :> json_max) -let pretty_print ?std out (x : json) = - Easy_format.Pretty.to_formatter out (pretty_format ?std x) +let pretty_print ?std out (js : json) = + Easy_format.Pretty.to_formatter out (pretty_format ?std js) -let pretty_to_string ?std (x : json) = +let pretty_to_string ?std (js : json) = + let x = forget_positions js in Pretty.to_string ?std (x :> json_max) -let pretty_to_channel ?std oc (x : json) = +let pretty_to_channel ?std oc (js : json) = + let x = forget_positions js in Pretty.to_channel ?std oc (x :> json_max) diff --git a/lib/yojson.cppo.ml b/lib/yojson.cppo.ml index 1a3321f4..42459e54 100644 --- a/lib/yojson.cppo.ml +++ b/lib/yojson.cppo.ml @@ -9,6 +9,7 @@ #define TUPLE #define VARIANT #include "type.ml" +#include "position.ml" type json_max = json #include "write.ml" module Pretty = @@ -31,6 +32,7 @@ struct #define FLOAT #define STRING #include "type.ml" +#include "position.ml" #include "write.ml" #include "write2.ml" #include "read.ml" @@ -52,6 +54,7 @@ struct #define TUPLE #define VARIANT #include "type.ml" +#include "position.ml" #include "safe.ml" #include "write.ml" #include "write2.ml" @@ -76,6 +79,7 @@ struct #define TUPLE #define VARIANT #include "type.ml" +#include "position.ml" #include "write.ml" #include "write2.ml" #include "read.ml" @@ -85,3 +89,31 @@ struct #undef TUPLE #undef VARIANT end + +module SafePos = +struct +#define POSITION +#define INT +#define INTLIT +#define FLOAT +#define STRING +#define TUPLE +#define VARIANT +#include "type.ml" +#include "position.ml" +#include "safe.ml" +#include "write.ml" +#include "write2.ml" +#include "read.ml" +module Util = +struct + #include "util.ml" +end +#undef POSITION +#undef INT +#undef INTLIT +#undef FLOAT +#undef STRING +#undef TUPLE +#undef VARIANT +end diff --git a/lib/yojson.cppo.mli b/lib/yojson.cppo.mli index ebafeb3f..5bce1916 100644 --- a/lib/yojson.cppo.mli +++ b/lib/yojson.cppo.mli @@ -6,7 +6,7 @@ The design goals of Yojson are the following: - Reducing inter-package dependencies by the use of polymorphic variants for the JSON tree type. - - Allowing type-aware serializers/deserializers + - Allowing type-aware serializers/deserializers to read and write directly without going through a generic JSON tree, for efficiency purposes. Readers and writers of all JSON syntaxic elements are provided @@ -15,7 +15,7 @@ - Providing optional extensions of the JSON syntax. These extensions include comments, arbitrary strings, optional quotes around field names, tuples and variants. - + @author Martin Jambon @see JSON specification *) @@ -60,10 +60,10 @@ sig (** This module supports a specific syntax for variants and tuples in addition to the standard JSON nodes. - Arbitrary integers are supported and represented as a decimal string + Arbitrary integers are supported and represented as a decimal string using [`Intlit] when they cannot be represented using OCaml's int type. - This module is recommended for intensive use + This module is recommended for intensive use or OCaml-friendly use of JSON. *) @@ -140,3 +140,41 @@ type json_max = json #undef TUPLE #undef VARIANT +(** {1 Multipurpose JSON tree type} *) + +module SafePos : +sig +(** + This module supports a specific syntax for variants and tuples + in addition to the standard JSON nodes. + Arbitrary integers are supported and represented as a decimal string + using [`Intlit] when they cannot be represented using OCaml's int type. + + This module is recommended for intensive use + or OCaml-friendly use of JSON. +*) + +#define POSITION +#define INT +#define INTLIT +#define FLOAT +#define STRING +#define TUPLE +#define VARIANT +#include "type.ml" +#include "safe.mli" +#include "write.mli" +#include "write2.mli" +#include "read.mli" +module Util : +sig + #include "util.mli" +end +#undef POSITION +#undef INT +#undef INTLIT +#undef FLOAT +#undef STRING +#undef TUPLE +#undef VARIANT +end