1 | :- module( json, [ json_parse/2, json_parse/3, | |
2 | json_parse_file/2 | |
3 | ] ). | |
4 | % based on https://github.com/yoroto/pl-json | |
5 | % have added line counting and error reporting, reading directly from file | |
6 | % fixed a few issues with whitespace and avoid backtracking | |
7 | % return strings using string(.) wrapper to avoid ambiguity with arrays of integers | |
8 | ||
9 | % parse a JSON file | |
10 | json_parse_file(File, Json) :- | |
11 | read_file(File,Codes), | |
12 | %format('Read:~n~s~n',[Codes]), | |
13 | json_parse(Codes,Json). | |
14 | ||
15 | read_file(Filename,Codes) :- | |
16 | open(Filename,read,S,[encoding('UTF-8')]), | |
17 | read_codes(S,Codes), | |
18 | close(S). | |
19 | read_codes(S,Codes) :- | |
20 | get_code(S,Code), | |
21 | ( Code < 0 -> | |
22 | Codes = [] | |
23 | ; otherwise -> | |
24 | Codes = [Code|Rest], | |
25 | read_codes(S,Rest)). | |
26 | ||
27 | % -------------- | |
28 | ||
29 | json_parse( Chars, Json ) :- | |
30 | json_parse( Chars, Json, [] ). | |
31 | ||
32 | json_parse( Chars, Json, _Options ) :- | |
33 | reset_line_nr, | |
34 | json(Json, Chars, _). | |
35 | ||
36 | json(Json) --> ws,!,json(Json). | |
37 | json(Json) --> | |
38 | json1(Json), | |
39 | !, | |
40 | spaces. | |
41 | json(Json) --> {var(Json)},check_string("?"), {fail}. % generate error message | |
42 | %json(Json) --> {ground(Json)}, json(JSon2), {format('### Mismatch:~n ~w~n~w~n',[Json,Json2]),fail}. | |
43 | ||
44 | % a version that does not print an error message | |
45 | try_json(Json) --> ws,!,try_json(Json). | |
46 | try_json(Json) --> | |
47 | json1(Json), | |
48 | !, | |
49 | spaces. | |
50 | ||
51 | json1(null) --> "null",!. | |
52 | json1(true) --> "true",!. | |
53 | json1(false) --> "false",!. | |
54 | ||
55 | json1(Number) --> number(Number),!. | |
56 | ||
57 | json1(string(Codes)) --> | |
58 | """",!, | |
59 | %{print_info(start_string)}, | |
60 | string(Codes). %, {atom_codes(String,Codes)}. | |
61 | ||
62 | json1(Array) --> | |
63 | "[",!, | |
64 | %{print_info(start_array)}, | |
65 | array(Array), | |
66 | check_string("]"). | |
67 | ||
68 | json1(obj(Pairs)) --> | |
69 | "{",!, | |
70 | %{print_info(start_object)}, | |
71 | spaces, | |
72 | pairs(Pairs), | |
73 | spaces, | |
74 | check_string("}"). | |
75 | ||
76 | number(Number) --> | |
77 | nm_token(NmCodes), | |
78 | { number_codes(Number, NmCodes) }. | |
79 | ||
80 | nm_token([H|T]) --> | |
81 | [H], | |
82 | { minus(H);digit_table(H) }, | |
83 | nm_token1(T). | |
84 | ||
85 | nm_token1([0'\x2E\|T]) --> | |
86 | ".",!, | |
87 | nm_frac(T). | |
88 | ||
89 | nm_token1([H|T]) --> | |
90 | [H], | |
91 | { digit_table(H) }, !, | |
92 | nm_token1(T). | |
93 | ||
94 | nm_token1([]) --> []. | |
95 | ||
96 | ||
97 | nm_frac([0'\x45\,H|T]) --> | |
98 | ("e";"E"),!, | |
99 | [H], {minus(H);plus(H)}, | |
100 | nm_exp(T). | |
101 | ||
102 | nm_frac([H|T]) --> | |
103 | [H], | |
104 | { digit_table(H) }, !, | |
105 | nm_frac(T). | |
106 | ||
107 | nm_frac([]) --> []. | |
108 | ||
109 | nm_exp([H|T]) --> | |
110 | [H], | |
111 | { digit_table(H) }, !, | |
112 | nm_exp1(T). | |
113 | ||
114 | nm_exp1([H|T]) --> | |
115 | [H], | |
116 | { digit_table(H) }, !, | |
117 | nm_exp1(T). | |
118 | ||
119 | nm_exp1([]) --> []. | |
120 | ||
121 | string(X) --> string2(X). | |
122 | ||
123 | string2([]) --> [0'\x22\], | |
124 | !. | |
125 | string2([EscapedChar|T]) --> | |
126 | [0'\x5C\],!, | |
127 | escape_char(EscapedChar), | |
128 | string2(T). | |
129 | string2([H|T]) --> | |
130 | [H], | |
131 | string2(T). | |
132 | ||
133 | escape_char( 0'\x22\ ) --> [0'\x22\]. | |
134 | escape_char( 0'\x5C\ ) --> [0'\x5C\]. | |
135 | escape_char( 0'\x2F\ ) --> [0'\x2F\]. | |
136 | escape_char( 0'\x08\ ) --> [0'\x62\]. %b | |
137 | escape_char( 0'\x0C\ ) --> [0'\x66\]. %f | |
138 | escape_char( 0'\x0A\ ) --> [0'\x6E\]. %n | |
139 | escape_char( 0'\x0D\ ) --> [0'\x72\]. %r | |
140 | escape_char( 0'\x09\ ) --> [0'\x74\]. %t | |
141 | ||
142 | escape_char( Code ) --> | |
143 | "u", | |
144 | hex_digit_char( H1 ), | |
145 | hex_digit_char( H2 ), | |
146 | hex_digit_char( H3 ), | |
147 | hex_digit_char( H4 ), | |
148 | { Code is (((H1 << 4 + H2) << 4 + H3) << 4 + H4) }. | |
149 | ||
150 | hex_digit_char( 0 ) --> "0". | |
151 | hex_digit_char( 1 ) --> "1". | |
152 | hex_digit_char( 2 ) --> "2". | |
153 | hex_digit_char( 3 ) --> "3". | |
154 | hex_digit_char( 4 ) --> "4". | |
155 | hex_digit_char( 5 ) --> "5". | |
156 | hex_digit_char( 6 ) --> "6". | |
157 | hex_digit_char( 7 ) --> "7". | |
158 | hex_digit_char( 8 ) --> "8". | |
159 | hex_digit_char( 9 ) --> "9". | |
160 | hex_digit_char( 10 ) --> "A". | |
161 | hex_digit_char( 11 ) --> "B". | |
162 | hex_digit_char( 12 ) --> "C". | |
163 | hex_digit_char( 13 ) --> "D". | |
164 | hex_digit_char( 14 ) --> "E". | |
165 | hex_digit_char( 15 ) --> "F". | |
166 | hex_digit_char( 10 ) --> "a". | |
167 | hex_digit_char( 11 ) --> "b". | |
168 | hex_digit_char( 12 ) --> "c". | |
169 | hex_digit_char( 13 ) --> "d". | |
170 | hex_digit_char( 14 ) --> "e". | |
171 | hex_digit_char( 15 ) --> "f". | |
172 | ||
173 | array([H|T]) --> | |
174 | try_json(H), !,{print_info(first_array)}, | |
175 | array1(T). | |
176 | array([]) --> [], {print_info(end_array)}. | |
177 | ||
178 | array1([H|T]) --> | |
179 | ",", !, | |
180 | json(H),!, {print_info(next_array)}, | |
181 | array1(T). | |
182 | array1([]) --> [], {print_info(empty_array)}. | |
183 | ||
184 | pair(pair(Name, Value),Optional) --> | |
185 | spaces, | |
186 | ({Optional=optional} -> opt_pair_name(Codes) ; pair_name(Codes)), | |
187 | check_string(":"), | |
188 | { atom_codes(Name, Codes) }, | |
189 | {print_info(pair_value_for(Name))}, | |
190 | json(Value), | |
191 | {print_info(found_value_vor(Name,Value))}. | |
192 | ||
193 | opt_pair_name(Name) --> """", string(Name), spaces. | |
194 | pair_name(Name) --> check_string(""""), string(Name), spaces. | |
195 | ||
196 | ||
197 | pairs(List) --> ws,!,pairs(List). | |
198 | pairs([H|T]) --> | |
199 | pair(H,optional), !, | |
200 | pairs1(T). | |
201 | pairs([]) --> [], {print_info(empty_pairs)}. | |
202 | ||
203 | pairs1(List) --> ws,!,pairs1(List). | |
204 | pairs1([H|T]) --> | |
205 | ",", !, | |
206 | pair(H,required),!, | |
207 | pairs1(T). | |
208 | pairs1([]) --> [], {print_info(end_pairs)}. | |
209 | ||
210 | check_string(List) --> ws,!,check_string(List). | |
211 | check_string([Char]) --> [Char],!. | |
212 | check_string(String, [Char|_],_) :- | |
213 | cur_line(LineNr), | |
214 | format(user_error,'! Error on line ~w: expecting ~s obtained ~s~n',[LineNr,String,[Char]]), | |
215 | %trace, | |
216 | fail. | |
217 | ||
218 | minus( 0'- ). | |
219 | plus( 0'+ ). | |
220 | digit_table( 0'0 ). | |
221 | digit_table( 0'1 ). | |
222 | digit_table( 0'2 ). | |
223 | digit_table( 0'3 ). | |
224 | digit_table( 0'4 ). | |
225 | digit_table( 0'5 ). | |
226 | digit_table( 0'6 ). | |
227 | digit_table( 0'7 ). | |
228 | digit_table( 0'8 ). | |
229 | digit_table( 0'9 ). | |
230 | ||
231 | ||
232 | spaces --> ws,!,spaces. | |
233 | spaces --> []. | |
234 | ||
235 | % whitespace | |
236 | ws --> new_line,!. | |
237 | ws --> " "; "\t" ; [10] ; [13]. | |
238 | ||
239 | new_line --> "\n",{inc_line_nr}. | |
240 | ||
241 | % use a fact to keep track of line numbers | |
242 | :- dynamic cur_line/1. | |
243 | cur_line(1). | |
244 | inc_line_nr :- retract(cur_line(N)), N1 is N+1, assert(cur_line(N1)). | |
245 | reset_line_nr :- retract(cur_line(_)), assert(cur_line(1)). | |
246 | ||
247 | print_info(_) :- !. | |
248 | print_info(Error) :- print_error(Error). | |
249 | print_error(Error) :- | |
250 | cur_line(LineNr), | |
251 | current_output(X), | |
252 | set_output(user_error), | |
253 | nl, | |
254 | write('! Line: '),write_term(LineNr,[]),nl, | |
255 | (var(Error) -> print_message(error,'_') | |
256 | ; write('! '),write_term(Error,[max_depth(20),numbervars(true)]),nl), | |
257 | %% flush_output(user_error), %% | |
258 | set_output(X). |