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). |