| 1 | | :- module( json_parser, [ |
| 2 | | json_parse/2, json_parse/3, |
| 3 | | json_parse_file/2, json_parse_file/3, |
| 4 | | json_write/2, json_write/3, |
| 5 | | json_write_stream/1, json_write_stream/2, json_write_stream/3, |
| 6 | | json_write_file/2, json_write_file/3, |
| 7 | | json_escape_string_atom/2, json_escape_string_codes/2, json_escape_string_codes/3 |
| 8 | | ]). |
| 9 | | |
| 10 | | :- set_prolog_flag(double_quotes, codes). |
| 11 | | |
| 12 | | :- use_module(library(lists)). |
| 13 | | :- use_module('../tools', []). % to setup paths |
| 14 | | :- use_module(probsrc(error_manager),[add_error/3, add_error/2, add_warning/3]). |
| 15 | | :- use_module(probsrc(self_check)). |
| 16 | | |
| 17 | | % based on https://github.com/yoroto/pl-json |
| 18 | | % have added line counting and error reporting, reading directly from file |
| 19 | | % fixed a few issues with whitespace and avoid backtracking |
| 20 | | % return strings using string(.) wrapper to avoid ambiguity with arrays of integers |
| 21 | | % fix issue with unparsed suffix being ignored (toggle-able via option rest(_)) |
| 22 | | % add options to change the representation of all json values |
| 23 | | % add compact and pretty printing of a given json term |
| 24 | | |
| 25 | | % JSON prolog term format: |
| 26 | | % literals (null/true/false) are tagged with '@' |
| 27 | | % numbers are tagged with 'number' |
| 28 | | % strings are tagged with 'string' |
| 29 | | % arrays are tagged with 'array' |
| 30 | | % objects are tagged with 'json' and contain terms with the functor '=' |
| 31 | | |
| 32 | | |
| 33 | | % parse a JSON file |
| 34 | | json_parse_file(File, Json) :- |
| 35 | | json_parse_file(File, Json, []). |
| 36 | | |
| 37 | | % parse a JSON file with custom options |
| 38 | | json_parse_file(File, Json, Options) :- |
| 39 | | catch(read_file(File,Codes), error(existence_error(_,_),_E), |
| 40 | | (add_error(json_parse_file,'JSON file does not exist:',File),fail)), |
| 41 | | (json_parse(Codes,Json,Options) -> true |
| 42 | | ; add_error(json_parse_file,'Could not parse JSON file:',File),fail |
| 43 | | ). |
| 44 | | |
| 45 | | read_file(Filename,Codes) :- |
| 46 | | open(Filename,read,S,[encoding(utf8)]), |
| 47 | | read_codes(S,Codes), |
| 48 | | close(S). |
| 49 | | read_codes(S,Codes) :- |
| 50 | | get_code(S,Code), |
| 51 | | ( Code < 0 -> |
| 52 | | Codes = [] |
| 53 | | ; |
| 54 | | Codes = [Code|Rest], |
| 55 | | read_codes(S,Rest)). |
| 56 | | |
| 57 | | % -------------- |
| 58 | | |
| 59 | | % write a JSON file |
| 60 | | json_write_file(File, Json) :- |
| 61 | | json_write_file(File, Json, []). |
| 62 | | |
| 63 | | % write a JSON file with custom options |
| 64 | | json_write_file(File, Json, Options) :- |
| 65 | | open(File,write,Stream,[encoding(utf8)]), |
| 66 | | json_write_stream(Stream,Json,Options), |
| 67 | | close(Stream). |
| 68 | | |
| 69 | | % -------------- |
| 70 | | |
| 71 | | :- dynamic |
| 72 | | position_infos/1, |
| 73 | | strings_as_atoms/1, multiline_strings/1, |
| 74 | | pretty/1, ascii/1. |
| 75 | | reset_options :- |
| 76 | | % by default we expect no rest (unparsed suffix), but callers can use rest(Var) to capture the rest to do their own handling |
| 77 | | % (rest/1 handled separately) |
| 78 | | |
| 79 | | % if true we add FromLineNr-ToLineNr as extra argument to attribute key-value terms in objects (=/3 instead of =/2, default false) |
| 80 | | retractall(position_infos(_)), assertz(position_infos(false)), |
| 81 | | |
| 82 | | % if true compatible with SICStus library (converted to atom); otherwise use codes directly (might cause problems with number arrays without tags, default true) |
| 83 | | retractall(strings_as_atoms(_)), assertz(strings_as_atoms(true)), |
| 84 | | |
| 85 | | % if true allow newlines in strings when parsing (default false) |
| 86 | | retractall(multiline_strings(_)), assertz(multiline_strings(false)), |
| 87 | | |
| 88 | | % write json strings using pretty print (include new lines and indent, default false) |
| 89 | | retractall(pretty(_)), assertz(pretty(false)), |
| 90 | | |
| 91 | | % write json strings as ascii only (default false) |
| 92 | | retractall(ascii(_)), assertz(ascii(false)). |
| 93 | | |
| 94 | | process_option(rest(_)) :- !. % handled separately |
| 95 | | process_option(position_infos(X)) :- atom(X), !, retractall(position_infos(_)), assertz(position_infos(X)). |
| 96 | | process_option(strings_as_atoms(X)) :- atom(X), !, retractall(strings_as_atoms(_)), assertz(strings_as_atoms(X)). |
| 97 | | process_option(multiline_strings(X)) :- atom(X), !, retractall(multiline_strings(_)), assertz(multiline_strings(X)). |
| 98 | | process_option(pretty(X)) :- atom(X), !, retractall(pretty(_)), assertz(pretty(X)). |
| 99 | | process_option(ascii(X)) :- atom(X), !, retractall(ascii(_)), assertz(ascii(X)). |
| 100 | | process_option(O) :- add_warning(json_parser, 'Unrecognized option:', O). |
| 101 | | |
| 102 | | % -------------- |
| 103 | | |
| 104 | | json_write_stream(Json) :- |
| 105 | | json_write_stream(current_output, Json). |
| 106 | | |
| 107 | | json_write_stream(Stream, Json) :- |
| 108 | | json_write_stream(Stream, Json, []). |
| 109 | | |
| 110 | | json_write_stream(Stream, Json, Options) :- |
| 111 | | json_write(Json, Chars, Options), |
| 112 | | format(Stream, '~s', [Chars]). |
| 113 | | |
| 114 | | json_write(Json, Chars) :- |
| 115 | | json_write(Json, Chars, []). |
| 116 | | |
| 117 | | json_write(Json, Chars, Options) :- |
| 118 | | reset_options, |
| 119 | | reset_line_nr, |
| 120 | | maplist(process_option, Options), |
| 121 | | json_write0(Json, 0, Chars, []). |
| 122 | | |
| 123 | | % -------------- |
| 124 | | |
| 125 | | :- assert_must_succeed(json_parser:json_escape_string_atom('{"a"}','{\\"a\\"}')). |
| 126 | | :- assert_must_succeed(json_parser:json_escape_string_atom('{"a \\/ b"}','{\\"a \\\\/ b\\"}')). % escape \ for JSON |
| 127 | | |
| 128 | | json_escape_string_atom(Atom, EscapedAtom) :- |
| 129 | | \+ atom(Atom), |
| 130 | | !, |
| 131 | | add_error('Cannot escape: ', json_escape_string_atom(Atom, EscapedAtom)), |
| 132 | | Atom = EscapedAtom. |
| 133 | | |
| 134 | | json_escape_string_atom(Atom, EscapedAtom) :- |
| 135 | | atom_codes(Atom, Codes), |
| 136 | | json_escape_string_codes(Codes, EscapedCodes), |
| 137 | | atom_codes(EscapedAtom, EscapedCodes). |
| 138 | | |
| 139 | | json_escape_string_codes(Codes, EscapedCodes) :- |
| 140 | | json_escape_string_codes(Codes, EscapedCodes, []). |
| 141 | | |
| 142 | | json_escape_string_codes(Codes, EscapedCodes, Options) :- |
| 143 | | reset_options, |
| 144 | | reset_line_nr, |
| 145 | | maplist(process_option, Options), |
| 146 | | json_escape_string_codes0(Codes, EscapedCodes). |
| 147 | | |
| 148 | | % -------------- |
| 149 | | |
| 150 | | indent(" "). |
| 151 | | json_write_indent(_) --> {pretty(false)}, !, "". |
| 152 | | json_write_indent(0) --> !, "". |
| 153 | | json_write_indent(N) --> !, {indent(I)}, I, {N1 is N-1}, json_write_indent(N1). |
| 154 | | |
| 155 | | json_write_newline --> {pretty(false)}, !, "". |
| 156 | | json_write_newline --> !, "\n". |
| 157 | | |
| 158 | | json_write_pretty_whitespace --> {pretty(false)}, !, "". |
| 159 | | json_write_pretty_whitespace --> !, " ". |
| 160 | | |
| 161 | | json_write0(Json, Indent) --> |
| 162 | | json_write_indent(Indent), |
| 163 | | json_write1(Json, Indent), |
| 164 | | json_write_newline. |
| 165 | | |
| 166 | | json_write1(@(Literal), _Indent) --> |
| 167 | | {valid_literal(Literal), atom_codes(Literal, LiteralCodes)}, |
| 168 | | !, |
| 169 | | LiteralCodes. |
| 170 | | json_write1(number(Number), _Indent) --> |
| 171 | | {lenient_number_codes(Number, NumberCodes)}, |
| 172 | | !, |
| 173 | | NumberCodes. |
| 174 | | json_write1(string(String), _Indent) --> |
| 175 | | {lenient_atom_codes(String, StringCodes)}, |
| 176 | | !, |
| 177 | | json_write_string(StringCodes). |
| 178 | | json_write1(json(Object), Indent) --> |
| 179 | | !, |
| 180 | | "{", |
| 181 | | {Indent1 is Indent+1}, |
| 182 | | json_write_object(Object, Indent1), |
| 183 | | "}". |
| 184 | | json_write1(array(Array), Indent) --> |
| 185 | | !, |
| 186 | | "[", |
| 187 | | {Indent1 is Indent+1}, |
| 188 | | json_write_array(Array, Indent1), |
| 189 | | "]". |
| 190 | | json_write1(Json, _Indent) --> |
| 191 | | {add_error(json_write, 'Unknown json term: ', Json), fail}. |
| 192 | | |
| 193 | | json_write_string(StringCodes) --> |
| 194 | | {json_escape_string_codes0(StringCodes, EscCodes)}, |
| 195 | | """", |
| 196 | | EscCodes, |
| 197 | | """". |
| 198 | | |
| 199 | | valid_literal(null). |
| 200 | | valid_literal(true). |
| 201 | | valid_literal(false). |
| 202 | | |
| 203 | | safe_number_codes(N, C) :- |
| 204 | | catch(number_codes(N, C), _, fail), |
| 205 | | number(C). |
| 206 | | |
| 207 | | lenient_number_codes(Value, Codes) :- number(Value), !, number_codes(Value, Codes). |
| 208 | | lenient_number_codes(Value, Codes) :- |
| 209 | | atom(Value), |
| 210 | | atom_codes(Value, VCodes), |
| 211 | | safe_number_codes(_, VCodes), |
| 212 | | !, |
| 213 | | Codes = VCodes. |
| 214 | | lenient_number_codes(Value, Codes) :- |
| 215 | | Value = [C|_], |
| 216 | | number(C), |
| 217 | | safe_number_codes(_, Value), |
| 218 | | !, |
| 219 | | Codes = Value. |
| 220 | | |
| 221 | | lenient_atom_codes(Value, Codes) :- atom(Value), !, atom_codes(Value, Codes). |
| 222 | | lenient_atom_codes(Value, Codes) :- number(Value), !, number_codes(Value, Codes). |
| 223 | | lenient_atom_codes(Value, Codes) :- Value=[], !, Codes=[]. |
| 224 | | lenient_atom_codes(Value, Codes) :- Value=[C|_], number(C), !, Codes = Value. |
| 225 | | |
| 226 | | json_escape_string_codes0([], []) :- !. |
| 227 | | json_escape_string_codes0([34|T], [92,34|ET]) :- !, json_escape_string_codes0(T, ET). % double quote |
| 228 | | json_escape_string_codes0([92|T], [92,92|ET]) :- !, json_escape_string_codes0(T, ET). % backslash |
| 229 | | % it is unnecessary to escape the forward slash here |
| 230 | | json_escape_string_codes0([8|T], [92,98|ET]) :- !, json_escape_string_codes0(T, ET). % b(ackspace) |
| 231 | | json_escape_string_codes0([12|T], [92,102|ET]) :- !, json_escape_string_codes0(T, ET). % f(orm feed) |
| 232 | | json_escape_string_codes0([10|T], [92,110|ET]) :- !, json_escape_string_codes0(T, ET). % n(ew line) |
| 233 | | json_escape_string_codes0([13|T], [92,114|ET]) :- !, json_escape_string_codes0(T, ET). % (carriage) r(eturn) |
| 234 | | json_escape_string_codes0([9|T], [92,116|ET]) :- !, json_escape_string_codes0(T, ET). % (horizontal) t(ab) |
| 235 | | json_escape_string_codes0([N|T], [92,117,H4,H3,H2,H1|ET]) :- |
| 236 | | (N >= 0, N =< 31 ; ascii(true), N >= 0x7f, N =< 0x10000), % non-ascii/non-printable (u)nicode codepoints |
| 237 | | !, |
| 238 | | to_4_hex_codes(N, H4, H3, H2, H1), |
| 239 | | json_escape_string_codes0(T, ET). |
| 240 | | json_escape_string_codes0([N|T], [92,117,H4,H3,H2,H1,92,117,H8,H7,H6,H5|ET]) :- % escape as surrogate pair |
| 241 | | ascii(true), N >= 0x10000, N =< 0x10FFFF, % maximum unicode codepoint |
| 242 | | !, |
| 243 | | LowSurrogate is ((N-65536) mod 0x400) + 0xDC00, |
| 244 | | HighSurrogate is ((N-65536) div 0x400) + 0xD800, |
| 245 | | to_4_hex_codes(HighSurrogate, H4, H3, H2, H1), |
| 246 | | to_4_hex_codes(LowSurrogate, H8, H7, H6, H5), |
| 247 | | json_escape_string_codes0(T, ET). |
| 248 | | json_escape_string_codes0([N|T], [N|ET]) :- !, json_escape_string_codes0(T, ET). % the rest |
| 249 | | |
| 250 | | to_4_hex_codes(N1, H4, H3, H2, H1) :- |
| 251 | | D1 is (N1 mod 16), N2 is N1 div 16, |
| 252 | | D2 is (N2 mod 16), N3 is N2 div 16, |
| 253 | | D3 is (N3 mod 16), N4 is N3 div 16, |
| 254 | | D4 is (N4 mod 16), |
| 255 | | to_hex_code(D1, H1), |
| 256 | | to_hex_code(D2, H2), |
| 257 | | to_hex_code(D3, H3), |
| 258 | | to_hex_code(D4, H4). |
| 259 | | |
| 260 | | to_hex_code(N, C) :- N >= 0, N < 10, !, C is 48+N. % 48 is '0' |
| 261 | | to_hex_code(N, C) :- N >= 10, N < 16, !, C is 97+(N-10). % 97 is 'a' |
| 262 | | from_hex_code(C, N) :- C >= 48, C =< 57, !, N is C-48. |
| 263 | | from_hex_code(C, N) :- C >= 97, C =< 102, !, N is (C-97)+10. |
| 264 | | from_hex_code(C, N) :- C >= 65, C =< 70, !, N is (C-65)+10. |
| 265 | | |
| 266 | | json_write_array([], _Indent) --> !, []. |
| 267 | | json_write_array([H|T], Indent) --> |
| 268 | | !, |
| 269 | | json_write_newline, |
| 270 | | json_write_indent(Indent), |
| 271 | | json_write1(H, Indent), |
| 272 | | json_write_array1(T, Indent). |
| 273 | | json_write_array1([], Indent) --> |
| 274 | | !, |
| 275 | | json_write_newline, |
| 276 | | {Indent1 is Indent-1}, |
| 277 | | json_write_indent(Indent1). |
| 278 | | json_write_array1([H|T], Indent) --> |
| 279 | | !, |
| 280 | | ",", |
| 281 | | json_write_newline, |
| 282 | | json_write_indent(Indent), |
| 283 | | json_write1(H, Indent), |
| 284 | | json_write_array1(T, Indent). |
| 285 | | |
| 286 | | json_write_pair(Pair, Indent) --> |
| 287 | | json_write_newline, |
| 288 | | json_write_indent(Indent), |
| 289 | | {Pair =.. [_, K, V | _], atom_codes(K, KCodes)}, % leniency: accept every term with at least two arguments |
| 290 | | json_write_string(KCodes), |
| 291 | | ":", |
| 292 | | json_write_pretty_whitespace, |
| 293 | | json_write1(V, Indent). |
| 294 | | |
| 295 | | json_write_object([], _Indent) --> !, []. |
| 296 | | json_write_object([H|T], Indent) --> |
| 297 | | !, |
| 298 | | json_write_pair(H, Indent), |
| 299 | | json_write_object1(T, Indent). |
| 300 | | json_write_object1([], Indent) --> |
| 301 | | !, |
| 302 | | json_write_newline, |
| 303 | | {Indent1 is Indent-1}, |
| 304 | | json_write_indent(Indent1). |
| 305 | | json_write_object1([H|T], Indent) --> |
| 306 | | !, |
| 307 | | ",", |
| 308 | | json_write_pair(H, Indent), |
| 309 | | json_write_object1(T, Indent). |
| 310 | | |
| 311 | | % -------------- |
| 312 | | |
| 313 | | json_parse(Chars, Json) :- |
| 314 | | json_parse(Chars, Json, []). |
| 315 | | |
| 316 | | json_parse(Chars, Json, Options) :- |
| 317 | | reset_options, |
| 318 | | reset_line_nr, |
| 319 | | maplist(process_option, Options), |
| 320 | ? | (member(rest(ExpectedRest), Options) -> true ; ExpectedRest = ""), |
| 321 | | json(Json, Chars, Rest), !, |
| 322 | | (ExpectedRest = Rest |
| 323 | | -> true |
| 324 | | ; (ExpectedRest = "" -> ExpectedString = "<EOF>" ; ExpectedString = ExpectedRest), |
| 325 | | generate_json_error(ExpectedString, Rest), |
| 326 | | fail |
| 327 | | ). |
| 328 | | |
| 329 | | json(Json) --> try_json(Json), !. |
| 330 | | json(_) --> !, fail_with_error("json value"). |
| 331 | | |
| 332 | | % a version that does not print an error message |
| 333 | | try_json(Json) --> ws, !, try_json(Json). |
| 334 | | try_json(Json) --> json1(Json), !, spaces. |
| 335 | | |
| 336 | | json1(@(Literal)) --> |
| 337 | ? | {valid_literal(Literal), atom_codes(Literal, LiteralCodes)}, |
| 338 | | LiteralCodes, |
| 339 | | !. |
| 340 | | json1(number(Number)) --> parse_number(Number), !. |
| 341 | | json1(string(String)) --> |
| 342 | | json_start_string_quote(Quote), !, |
| 343 | | string2(Codes, Quote), |
| 344 | | {strings_as_atoms(true) -> atom_codes(String, Codes) ; String = Codes}. |
| 345 | | json1(array(Array)) --> |
| 346 | | "[", !, |
| 347 | | array(Array). |
| 348 | | json1(json(Object)) --> |
| 349 | | "{", !, |
| 350 | | pairs(Object). |
| 351 | | |
| 352 | | parse_number(Number) --> |
| 353 | | nm_token(NmCodes), !, |
| 354 | | {number_codes(Number, NmCodes)}. |
| 355 | | |
| 356 | | nm_token([H|T]) --> |
| 357 | | [H], {minus(H)}, !, |
| 358 | | nm_int(T). |
| 359 | | nm_token(T) --> !, nm_int(T). |
| 360 | | |
| 361 | | nm_int([H|T]) --> |
| 362 | | [H], {zero(H)}, !, |
| 363 | | nm_int0(T). |
| 364 | | nm_int([H|T]) --> |
| 365 | | [H], {digit_table_1to9(H)}, !, |
| 366 | | nm_int1(T). |
| 367 | | |
| 368 | | nm_int0([H|T]) --> |
| 369 | | [H], {dot(H)}, !, |
| 370 | | nm_frac(T). |
| 371 | | nm_int0([]) --> !, "". |
| 372 | | |
| 373 | | nm_int1([H|T]) --> |
| 374 | ? | [H], {digit_table(H)}, !, |
| 375 | | nm_int1(T). |
| 376 | | nm_int1([H|T]) --> |
| 377 | | [H], {dot(H)}, !, |
| 378 | | nm_frac(T). |
| 379 | | nm_int1([]) --> !, "". |
| 380 | | |
| 381 | | nm_frac([H|T]) --> |
| 382 | | [H], {digit_table(H)}, !, |
| 383 | | nm_frac1(T). |
| 384 | | |
| 385 | | nm_frac1([H|T]) --> |
| 386 | | [H], {exp(H)}, !, |
| 387 | | nm_exp(T). |
| 388 | | nm_frac1([H|T]) --> |
| 389 | | [H], {digit_table(H)}, !, |
| 390 | | nm_frac1(T). |
| 391 | | nm_frac1([]) --> !, "". |
| 392 | | |
| 393 | | nm_exp([H|T]) --> |
| 394 | | [H], {sign(H)}, !, |
| 395 | | nm_exp0(T). |
| 396 | | nm_exp(T) --> !, nm_exp0(T). |
| 397 | | |
| 398 | | nm_exp0([H|T]) --> |
| 399 | | [H], {digit_table(H)}, !, |
| 400 | | nm_exp1(T). |
| 401 | | |
| 402 | | nm_exp1([H|T]) --> |
| 403 | | [H], {digit_table(H)}, !, |
| 404 | | nm_exp1(T). |
| 405 | | nm_exp1([]) --> !, "". |
| 406 | | |
| 407 | | % detect valid start string quote |
| 408 | | json_start_string_quote(0'\x22\) --> """", !. % regular JSON string syntax |
| 409 | | %json_start_string_quote(39) --> "'", !. % possible ProB JSON extension, so that we do not have to quote B strings in formulas |
| 410 | | |
| 411 | | % regular JSON string between double quotes |
| 412 | | %string(X) --> string2(X,0'\x22\). |
| 413 | | |
| 414 | | string2([],EndQuote) --> |
| 415 | | [EndQuote], |
| 416 | | !. |
| 417 | | string2([EscapedChar|T],EndQuote) --> |
| 418 | | [0'\x5C\],!, % \ |
| 419 | | escape_char(EscapedChar), |
| 420 | | string2(T,EndQuote). |
| 421 | | string2([C|T],EndQuote) --> |
| 422 | | [C], |
| 423 | | {C=10;C=13}, |
| 424 | | !, |
| 425 | | ({multiline_strings(true)} -> {true} ; fail_with_error("string content")), |
| 426 | | {C=10 -> inc_line_nr ; true}, |
| 427 | | string2(T,EndQuote). |
| 428 | | string2([H|T],EndQuote) --> |
| 429 | | [H], |
| 430 | | string2(T,EndQuote). |
| 431 | | |
| 432 | | escape_char( 0'\x22\ ) --> [0'\x22\], !. %" 34 decimal |
| 433 | | escape_char( 0'\x5C\ ) --> [0'\x5C\], !. %\ |
| 434 | | escape_char( 0'\x2F\ ) --> [0'\x2F\], !. %/ |
| 435 | | escape_char( 0'\x08\ ) --> [0'\x62\], !. %b |
| 436 | | escape_char( 0'\x0C\ ) --> [0'\x66\], !. %f |
| 437 | | escape_char( 0'\x0A\ ) --> [0'\x6E\], !. %n |
| 438 | | escape_char( 0'\x0D\ ) --> [0'\x72\], !. %r |
| 439 | | escape_char( 0'\x09\ ) --> [0'\x74\], !. %t |
| 440 | | |
| 441 | | % combine surrogates (same behaviour as SICStus library) |
| 442 | | escape_char( Code ) --> |
| 443 | | "u", |
| 444 | | hex_digit_char( H1 ), |
| 445 | | hex_digit_char( H2 ), |
| 446 | | hex_digit_char( H3 ), |
| 447 | | hex_digit_char( H4 ), |
| 448 | | {HighSurrogate is (((H1 << 4 + H2) << 4 + H3) << 4 + H4)}, |
| 449 | | {HighSurrogate >= 0xD800, HighSurrogate =< 0xDBFF}, |
| 450 | | "\\u", |
| 451 | | hex_digit_char( H5 ), |
| 452 | | hex_digit_char( H6 ), |
| 453 | | hex_digit_char( H7 ), |
| 454 | | hex_digit_char( H8 ), |
| 455 | | {LowSurrogate is (((H5 << 4 + H6) << 4 + H7) << 4 + H8)}, |
| 456 | | {LowSurrogate >= 0xDC00, LowSurrogate =< 0xDFFF}, |
| 457 | | !, |
| 458 | | {Code is 0x10000 + (0x400 * (HighSurrogate - 0xD800)) + (LowSurrogate - 0xDC00)}. |
| 459 | | |
| 460 | | escape_char( Code ) --> |
| 461 | | "u", |
| 462 | | !, |
| 463 | | hex_digit_char( H1 ), |
| 464 | | hex_digit_char( H2 ), |
| 465 | | hex_digit_char( H3 ), |
| 466 | | hex_digit_char( H4 ), |
| 467 | | { Code is (((H1 << 4 + H2) << 4 + H3) << 4 + H4) }. |
| 468 | | |
| 469 | | hex_digit_char( N ) --> [C], {from_hex_code(C, N)}, !. |
| 470 | | |
| 471 | ? | array(Array) --> ws, !, array(Array). |
| 472 | | array([]) --> "]", !. |
| 473 | | array([H|T]) --> |
| 474 | | json1(H), !, |
| 475 | | spaces, |
| 476 | | array1(T). |
| 477 | | array(_) --> !, fail_with_error("json value or \"]\" in array"). |
| 478 | | |
| 479 | | array1([]) --> "]", !. |
| 480 | | array1([H|T]) --> |
| 481 | | check_string(","), |
| 482 | | spaces, |
| 483 | | json1(H), !, |
| 484 | | spaces, |
| 485 | | array1(T). |
| 486 | | array1(_) --> !, fail_with_error("\",\" json value or \"]\" in array"). |
| 487 | | |
| 488 | | pair(ResPair) --> |
| 489 | | spaces, |
| 490 | | {position_infos(true) |
| 491 | | -> position_start(Pos), ResPair = '='(Name, Value, Pos) |
| 492 | | ; ResPair = '='(Name, Value)}, |
| 493 | | (json_start_string_quote(Quote) -> {true} ; fail_with_error("string quote")), |
| 494 | | string2(Codes, Quote), |
| 495 | | {atom_codes(Name, Codes)}, |
| 496 | | spaces, check_string(":"), spaces, |
| 497 | | json1(Value), |
| 498 | | {position_infos(true) -> position_end(Pos) ; true}, |
| 499 | | spaces. |
| 500 | | |
| 501 | ? | pairs(List) --> ws, !, pairs(List). |
| 502 | | pairs([]) --> "}", !. |
| 503 | | pairs([H|T]) --> |
| 504 | | pair(H), !, |
| 505 | | pairs1(T). |
| 506 | | pairs(_) --> !, fail_with_error("string \":\" json value or \"}\" in object"). |
| 507 | | |
| 508 | | pairs1([]) --> "}", !. |
| 509 | | pairs1([H|T]) --> |
| 510 | | check_string(","), |
| 511 | | pair(H), !, |
| 512 | | pairs1(T). |
| 513 | | pairs1(_) --> !, fail_with_error("\",\" string \":\" json value or \"}\" in object"). |
| 514 | | |
| 515 | | % check if the next char matches a given character or one of the given chars |
| 516 | | check_string(List) --> [Char], {member(Char, List)}, !. |
| 517 | | check_string(List) --> !, fail_with_error(List). |
| 518 | | |
| 519 | | fail_with_error(ExpectedString, Codes, _) :- !, generate_json_error(ExpectedString, Codes), fail. |
| 520 | | |
| 521 | | generate_json_error(ExpectedString, Char) :- |
| 522 | | cur_line(LineNr), |
| 523 | | number_codes(LineNr,LC), |
| 524 | | convert_chars(Char,CStr), |
| 525 | | append(["JSON error in line ",LC,": expected ",ExpectedString,", but got ",CStr],Codes), |
| 526 | | atom_codes(Msg,Codes), |
| 527 | | add_error(json_parser,Msg). |
| 528 | | |
| 529 | | convert_chars([], Res) :- !, Res = "<EOF>". |
| 530 | | convert_chars([Char|_], Res) :- !, convert_char(Char, Res). |
| 531 | | convert_chars(Char, Res) :- !, convert_char(Char, Res). |
| 532 | | |
| 533 | | convert_char(9, Res) :- !, Res = "<tab>". |
| 534 | | convert_char(10, Res) :- !, Res = "<newline>". |
| 535 | | convert_char(13, Res) :- !, Res = "<carriage return>". |
| 536 | | convert_char(Char, Res) :- number(Char), !, Res = [Char]. |
| 537 | | convert_char(_, Res) :- !, Res = "<unknown>". |
| 538 | | |
| 539 | | minus( 0'- ). |
| 540 | | plus( 0'+ ). |
| 541 | | sign( C ) :- minus( C ). |
| 542 | | sign( C ) :- plus( C ). |
| 543 | | zero( 0'0 ). |
| 544 | | digit_table( C ) :- zero( C ). |
| 545 | | digit_table( C ) :- digit_table_1to9( C ). |
| 546 | | digit_table_1to9( 0'1 ). |
| 547 | | digit_table_1to9( 0'2 ). |
| 548 | | digit_table_1to9( 0'3 ). |
| 549 | | digit_table_1to9( 0'4 ). |
| 550 | | digit_table_1to9( 0'5 ). |
| 551 | | digit_table_1to9( 0'6 ). |
| 552 | | digit_table_1to9( 0'7 ). |
| 553 | | digit_table_1to9( 0'8 ). |
| 554 | | digit_table_1to9( 0'9 ). |
| 555 | | dot( 0'. ). |
| 556 | | exp( 0'e ). |
| 557 | | exp( 0'E ). |
| 558 | | |
| 559 | | |
| 560 | ? | spaces --> ws,!,spaces. |
| 561 | | spaces --> "". |
| 562 | | |
| 563 | | % whitespace |
| 564 | | ws --> new_line, !. |
| 565 | | ws --> " "; "\t" ; "\r". |
| 566 | | |
| 567 | | new_line --> "\n",{inc_line_nr}. |
| 568 | | |
| 569 | | % use a fact to keep track of line numbers |
| 570 | | :- dynamic cur_line/1. |
| 571 | | reset_line_nr :- retractall(cur_line(_)), assertz(cur_line(1)). |
| 572 | | inc_line_nr :- cur_line(N), !, retractall(cur_line(_)), N1 is N+1, assertz(cur_line(N1)). |
| 573 | | |
| 574 | | position_start(Start-_) :- !, cur_line(Start). |
| 575 | | position_end(_-End) :- !, cur_line(End). |