/src/mupdf/thirdparty/mujs/json.c
Line | Count | Source |
1 | | #include "jsi.h" |
2 | | #include "utf.h" |
3 | | |
4 | | int js_isnumberobject(js_State *J, int idx) |
5 | 0 | { |
6 | 0 | return js_isobject(J, idx) && js_toobject(J, idx)->type == JS_CNUMBER; |
7 | 0 | } |
8 | | |
9 | | int js_isstringobject(js_State *J, int idx) |
10 | 0 | { |
11 | 0 | return js_isobject(J, idx) && js_toobject(J, idx)->type == JS_CSTRING; |
12 | 0 | } |
13 | | |
14 | | int js_isbooleanobject(js_State *J, int idx) |
15 | 0 | { |
16 | 0 | return js_isobject(J, idx) && js_toobject(J, idx)->type == JS_CBOOLEAN; |
17 | 0 | } |
18 | | |
19 | | int js_isdateobject(js_State *J, int idx) |
20 | 0 | { |
21 | 0 | return js_isobject(J, idx) && js_toobject(J, idx)->type == JS_CDATE; |
22 | 0 | } |
23 | | |
24 | | static void jsonnext(js_State *J) |
25 | 0 | { |
26 | 0 | J->lookahead = jsY_lexjson(J); |
27 | 0 | } |
28 | | |
29 | | static int jsonaccept(js_State *J, int t) |
30 | 0 | { |
31 | 0 | if (J->lookahead == t) { |
32 | 0 | jsonnext(J); |
33 | 0 | return 1; |
34 | 0 | } |
35 | 0 | return 0; |
36 | 0 | } |
37 | | |
38 | | static void jsonexpect(js_State *J, int t) |
39 | 0 | { |
40 | 0 | if (!jsonaccept(J, t)) |
41 | 0 | js_syntaxerror(J, "JSON: unexpected token: %s (expected %s)", |
42 | 0 | jsY_tokenstring(J->lookahead), jsY_tokenstring(t)); |
43 | 0 | } |
44 | | |
45 | | static void jsonvalue(js_State *J) |
46 | 0 | { |
47 | 0 | int i; |
48 | |
|
49 | 0 | switch (J->lookahead) { |
50 | 0 | case TK_STRING: |
51 | 0 | js_pushstring(J, J->text); |
52 | 0 | jsonnext(J); |
53 | 0 | break; |
54 | | |
55 | 0 | case TK_NUMBER: |
56 | 0 | js_pushnumber(J, J->number); |
57 | 0 | jsonnext(J); |
58 | 0 | break; |
59 | | |
60 | 0 | case '{': |
61 | 0 | js_newobject(J); |
62 | 0 | jsonnext(J); |
63 | 0 | if (jsonaccept(J, '}')) |
64 | 0 | return; |
65 | 0 | do { |
66 | 0 | if (J->lookahead != TK_STRING) |
67 | 0 | js_syntaxerror(J, "JSON: unexpected token: %s (expected string)", jsY_tokenstring(J->lookahead)); |
68 | 0 | js_pushstring(J, J->text); |
69 | 0 | jsonnext(J); |
70 | 0 | jsonexpect(J, ':'); |
71 | 0 | jsonvalue(J); |
72 | 0 | js_setproperty(J, -3, js_tostring(J, -2)); |
73 | 0 | js_pop(J, 1); |
74 | 0 | } while (jsonaccept(J, ',')); |
75 | 0 | jsonexpect(J, '}'); |
76 | 0 | break; |
77 | | |
78 | 0 | case '[': |
79 | 0 | js_newarray(J); |
80 | 0 | jsonnext(J); |
81 | 0 | i = 0; |
82 | 0 | if (jsonaccept(J, ']')) |
83 | 0 | return; |
84 | 0 | do { |
85 | 0 | jsonvalue(J); |
86 | 0 | js_setindex(J, -2, i++); |
87 | 0 | } while (jsonaccept(J, ',')); |
88 | 0 | jsonexpect(J, ']'); |
89 | 0 | break; |
90 | | |
91 | 0 | case TK_TRUE: |
92 | 0 | js_pushboolean(J, 1); |
93 | 0 | jsonnext(J); |
94 | 0 | break; |
95 | | |
96 | 0 | case TK_FALSE: |
97 | 0 | js_pushboolean(J, 0); |
98 | 0 | jsonnext(J); |
99 | 0 | break; |
100 | | |
101 | 0 | case TK_NULL: |
102 | 0 | js_pushnull(J); |
103 | 0 | jsonnext(J); |
104 | 0 | break; |
105 | | |
106 | 0 | default: |
107 | 0 | js_syntaxerror(J, "JSON: unexpected token: %s", jsY_tokenstring(J->lookahead)); |
108 | 0 | } |
109 | 0 | } |
110 | | |
111 | | static void jsonrevive(js_State *J, const char *name) |
112 | 0 | { |
113 | 0 | const char *key; |
114 | 0 | char buf[32]; |
115 | | |
116 | | /* revive is in 2 */ |
117 | | /* holder is in -1 */ |
118 | |
|
119 | 0 | js_getproperty(J, -1, name); /* get value from holder */ |
120 | |
|
121 | 0 | if (js_isobject(J, -1)) { |
122 | 0 | if (js_isarray(J, -1)) { |
123 | 0 | int i = 0; |
124 | 0 | int n = js_getlength(J, -1); |
125 | 0 | for (i = 0; i < n; ++i) { |
126 | 0 | jsonrevive(J, js_itoa(buf, i)); |
127 | 0 | if (js_isundefined(J, -1)) { |
128 | 0 | js_pop(J, 1); |
129 | 0 | js_delproperty(J, -1, buf); |
130 | 0 | } else { |
131 | 0 | js_setproperty(J, -2, buf); |
132 | 0 | } |
133 | 0 | } |
134 | 0 | } else { |
135 | 0 | js_pushiterator(J, -1, 1); |
136 | 0 | while ((key = js_nextiterator(J, -1))) { |
137 | 0 | js_rot2(J); |
138 | 0 | jsonrevive(J, key); |
139 | 0 | if (js_isundefined(J, -1)) { |
140 | 0 | js_pop(J, 1); |
141 | 0 | js_delproperty(J, -1, key); |
142 | 0 | } else { |
143 | 0 | js_setproperty(J, -2, key); |
144 | 0 | } |
145 | 0 | js_rot2(J); |
146 | 0 | } |
147 | 0 | js_pop(J, 1); |
148 | 0 | } |
149 | 0 | } |
150 | |
|
151 | 0 | js_copy(J, 2); /* reviver function */ |
152 | 0 | js_copy(J, -3); /* holder as this */ |
153 | 0 | js_pushstring(J, name); /* name */ |
154 | 0 | js_copy(J, -4); /* value */ |
155 | 0 | js_call(J, 2); |
156 | 0 | js_rot2pop1(J); /* pop old value, leave new value on stack */ |
157 | 0 | } |
158 | | |
159 | | static void JSON_parse(js_State *J) |
160 | 0 | { |
161 | 0 | const char *source = js_tostring(J, 1); |
162 | 0 | jsY_initlex(J, "JSON", source); |
163 | 0 | jsonnext(J); |
164 | |
|
165 | 0 | if (js_iscallable(J, 2)) { |
166 | 0 | js_newobject(J); |
167 | 0 | jsonvalue(J); |
168 | 0 | js_defproperty(J, -2, "", 0); |
169 | 0 | jsonrevive(J, ""); |
170 | 0 | } else { |
171 | 0 | jsonvalue(J); |
172 | 0 | } |
173 | 0 | } |
174 | | |
175 | | static void fmtnum(js_State *J, js_Buffer **sb, double n) |
176 | 0 | { |
177 | 0 | if (isnan(n)) js_puts(J, sb, "null"); |
178 | 0 | else if (isinf(n)) js_puts(J, sb, "null"); |
179 | 0 | else if (n == 0) js_puts(J, sb, "0"); |
180 | 0 | else { |
181 | 0 | char buf[40]; |
182 | 0 | js_puts(J, sb, jsV_numbertostring(J, buf, n)); |
183 | 0 | } |
184 | 0 | } |
185 | | |
186 | | static void fmtstr(js_State *J, js_Buffer **sb, const char *s) |
187 | 0 | { |
188 | 0 | static const char *HEX = "0123456789abcdef"; |
189 | 0 | int i, n; |
190 | 0 | Rune c; |
191 | 0 | js_putc(J, sb, '"'); |
192 | 0 | while (*s) { |
193 | 0 | n = chartorune(&c, s); |
194 | 0 | switch (c) { |
195 | 0 | case '"': js_puts(J, sb, "\\\""); break; |
196 | 0 | case '\\': js_puts(J, sb, "\\\\"); break; |
197 | 0 | case '\b': js_puts(J, sb, "\\b"); break; |
198 | 0 | case '\f': js_puts(J, sb, "\\f"); break; |
199 | 0 | case '\n': js_puts(J, sb, "\\n"); break; |
200 | 0 | case '\r': js_puts(J, sb, "\\r"); break; |
201 | 0 | case '\t': js_puts(J, sb, "\\t"); break; |
202 | 0 | default: |
203 | 0 | if (c < ' ' || (c >= 0xd800 && c <= 0xdfff)) { |
204 | 0 | js_putc(J, sb, '\\'); |
205 | 0 | js_putc(J, sb, 'u'); |
206 | 0 | js_putc(J, sb, HEX[(c>>12)&15]); |
207 | 0 | js_putc(J, sb, HEX[(c>>8)&15]); |
208 | 0 | js_putc(J, sb, HEX[(c>>4)&15]); |
209 | 0 | js_putc(J, sb, HEX[c&15]); |
210 | 0 | } else if (c < 128) { |
211 | 0 | js_putc(J, sb, c); |
212 | 0 | } else { |
213 | 0 | for (i = 0; i < n; ++i) |
214 | 0 | js_putc(J, sb, s[i]); |
215 | 0 | } |
216 | 0 | break; |
217 | 0 | } |
218 | 0 | s += n; |
219 | 0 | } |
220 | 0 | js_putc(J, sb, '"'); |
221 | 0 | } |
222 | | |
223 | | static void fmtindent(js_State *J, js_Buffer **sb, const char *gap, int level) |
224 | 0 | { |
225 | 0 | js_putc(J, sb, '\n'); |
226 | 0 | while (level--) |
227 | 0 | js_puts(J, sb, gap); |
228 | 0 | } |
229 | | |
230 | | static int fmtvalue(js_State *J, js_Buffer **sb, const char *key, const char *gap, int level); |
231 | | |
232 | | static int filterprop(js_State *J, const char *key) |
233 | 0 | { |
234 | 0 | int i, n, found; |
235 | | /* replacer/property-list is in stack slot 2 */ |
236 | 0 | if (js_isarray(J, 2)) { |
237 | 0 | found = 0; |
238 | 0 | n = js_getlength(J, 2); |
239 | 0 | for (i = 0; i < n && !found; ++i) { |
240 | 0 | js_getindex(J, 2, i); |
241 | 0 | if (js_isstring(J, -1) || js_isnumber(J, -1) || |
242 | 0 | js_isstringobject(J, -1) || js_isnumberobject(J, -1)) |
243 | 0 | found = !strcmp(key, js_tostring(J, -1)); |
244 | 0 | js_pop(J, 1); |
245 | 0 | } |
246 | 0 | return found; |
247 | 0 | } |
248 | 0 | return 1; |
249 | 0 | } |
250 | | |
251 | | static void fmtobject(js_State *J, js_Buffer **sb, js_Object *obj, const char *gap, int level) |
252 | 0 | { |
253 | 0 | const char *key; |
254 | 0 | int save; |
255 | 0 | int i, n; |
256 | |
|
257 | 0 | n = js_gettop(J) - 1; |
258 | 0 | for (i = 4; i < n; ++i) |
259 | 0 | if (js_isobject(J, i)) |
260 | 0 | if (js_toobject(J, i) == js_toobject(J, -1)) |
261 | 0 | js_typeerror(J, "cyclic object value"); |
262 | | |
263 | 0 | n = 0; |
264 | 0 | js_putc(J, sb, '{'); |
265 | 0 | js_pushiterator(J, -1, 1); |
266 | 0 | while ((key = js_nextiterator(J, -1))) { |
267 | 0 | if (filterprop(J, key)) { |
268 | 0 | save = (*sb)->n; |
269 | 0 | if (n) js_putc(J, sb, ','); |
270 | 0 | if (gap) fmtindent(J, sb, gap, level + 1); |
271 | 0 | fmtstr(J, sb, key); |
272 | 0 | js_putc(J, sb, ':'); |
273 | 0 | if (gap) |
274 | 0 | js_putc(J, sb, ' '); |
275 | 0 | js_rot2(J); |
276 | 0 | if (!fmtvalue(J, sb, key, gap, level + 1)) |
277 | 0 | (*sb)->n = save; |
278 | 0 | else |
279 | 0 | ++n; |
280 | 0 | js_rot2(J); |
281 | 0 | } |
282 | 0 | } |
283 | 0 | js_pop(J, 1); |
284 | 0 | if (gap && n) fmtindent(J, sb, gap, level); |
285 | 0 | js_putc(J, sb, '}'); |
286 | 0 | } |
287 | | |
288 | | static void fmtarray(js_State *J, js_Buffer **sb, const char *gap, int level) |
289 | 0 | { |
290 | 0 | int n, i; |
291 | 0 | char buf[32]; |
292 | |
|
293 | 0 | n = js_gettop(J) - 1; |
294 | 0 | for (i = 4; i < n; ++i) |
295 | 0 | if (js_isobject(J, i)) |
296 | 0 | if (js_toobject(J, i) == js_toobject(J, -1)) |
297 | 0 | js_typeerror(J, "cyclic object value"); |
298 | | |
299 | 0 | js_putc(J, sb, '['); |
300 | 0 | n = js_getlength(J, -1); |
301 | 0 | for (i = 0; i < n; ++i) { |
302 | 0 | if (i) js_putc(J, sb, ','); |
303 | 0 | if (gap) fmtindent(J, sb, gap, level + 1); |
304 | 0 | if (!fmtvalue(J, sb, js_itoa(buf, i), gap, level + 1)) |
305 | 0 | js_puts(J, sb, "null"); |
306 | 0 | } |
307 | 0 | if (gap && n) fmtindent(J, sb, gap, level); |
308 | 0 | js_putc(J, sb, ']'); |
309 | 0 | } |
310 | | |
311 | | static int fmtvalue(js_State *J, js_Buffer **sb, const char *key, const char *gap, int level) |
312 | 0 | { |
313 | | /* replacer/property-list is in 2 */ |
314 | | /* holder is in -1 */ |
315 | |
|
316 | 0 | js_getproperty(J, -1, key); |
317 | |
|
318 | 0 | if (js_isobject(J, -1)) { |
319 | 0 | if (js_hasproperty(J, -1, "toJSON")) { |
320 | 0 | if (js_iscallable(J, -1)) { |
321 | 0 | js_copy(J, -2); |
322 | 0 | js_pushstring(J, key); |
323 | 0 | js_call(J, 1); |
324 | 0 | js_rot2pop1(J); |
325 | 0 | } else { |
326 | 0 | js_pop(J, 1); |
327 | 0 | } |
328 | 0 | } |
329 | 0 | } |
330 | |
|
331 | 0 | if (js_iscallable(J, 2)) { |
332 | 0 | js_copy(J, 2); /* replacer function */ |
333 | 0 | js_copy(J, -3); /* holder as this */ |
334 | 0 | js_pushstring(J, key); /* name */ |
335 | 0 | js_copy(J, -4); /* old value */ |
336 | 0 | js_call(J, 2); |
337 | 0 | js_rot2pop1(J); /* pop old value, leave new value on stack */ |
338 | 0 | } |
339 | |
|
340 | 0 | if (js_isobject(J, -1) && !js_iscallable(J, -1)) { |
341 | 0 | js_Object *obj = js_toobject(J, -1); |
342 | 0 | switch (obj->type) { |
343 | 0 | case JS_CNUMBER: fmtnum(J, sb, obj->u.number); break; |
344 | 0 | case JS_CSTRING: fmtstr(J, sb, obj->u.s.string); break; |
345 | 0 | case JS_CBOOLEAN: js_puts(J, sb, obj->u.boolean ? "true" : "false"); break; |
346 | 0 | case JS_CARRAY: fmtarray(J, sb, gap, level); break; |
347 | 0 | default: fmtobject(J, sb, obj, gap, level); break; |
348 | 0 | } |
349 | 0 | } |
350 | 0 | else if (js_isboolean(J, -1)) |
351 | 0 | js_puts(J, sb, js_toboolean(J, -1) ? "true" : "false"); |
352 | 0 | else if (js_isnumber(J, -1)) |
353 | 0 | fmtnum(J, sb, js_tonumber(J, -1)); |
354 | 0 | else if (js_isstring(J, -1)) |
355 | 0 | fmtstr(J, sb, js_tostring(J, -1)); |
356 | 0 | else if (js_isnull(J, -1)) |
357 | 0 | js_puts(J, sb, "null"); |
358 | 0 | else { |
359 | 0 | js_pop(J, 1); |
360 | 0 | return 0; |
361 | 0 | } |
362 | | |
363 | 0 | js_pop(J, 1); |
364 | 0 | return 1; |
365 | 0 | } |
366 | | |
367 | | static void JSON_stringify(js_State *J) |
368 | 0 | { |
369 | 0 | js_Buffer *sb = NULL; |
370 | 0 | char buf[12]; |
371 | | /* NOTE: volatile to silence GCC warning about longjmp clobbering a variable */ |
372 | 0 | const char * volatile gap; |
373 | 0 | const char *s; |
374 | 0 | int n; |
375 | |
|
376 | 0 | gap = NULL; |
377 | |
|
378 | 0 | if (js_isnumber(J, 3) || js_isnumberobject(J, 3)) { |
379 | 0 | n = js_tointeger(J, 3); |
380 | 0 | if (n < 0) n = 0; |
381 | 0 | if (n > 10) n = 10; |
382 | 0 | memset(buf, ' ', n); |
383 | 0 | buf[n] = 0; |
384 | 0 | if (n > 0) gap = buf; |
385 | 0 | } else if (js_isstring(J, 3) || js_isstringobject(J, 3)) { |
386 | 0 | s = js_tostring(J, 3); |
387 | 0 | n = strlen(s); |
388 | 0 | if (n > 10) n = 10; |
389 | 0 | memcpy(buf, s, n); |
390 | 0 | buf[n] = 0; |
391 | 0 | if (n > 0) gap = buf; |
392 | 0 | } |
393 | |
|
394 | 0 | if (js_try(J)) { |
395 | 0 | js_free(J, sb); |
396 | 0 | js_throw(J); |
397 | 0 | } |
398 | | |
399 | 0 | js_newobject(J); /* wrapper */ |
400 | 0 | js_copy(J, 1); |
401 | 0 | js_defproperty(J, -2, "", 0); |
402 | 0 | if (!fmtvalue(J, &sb, "", gap, 0)) { |
403 | 0 | js_pushundefined(J); |
404 | 0 | } else { |
405 | 0 | js_putc(J, &sb, 0); |
406 | 0 | js_pushstring(J, sb ? sb->s : ""); |
407 | 0 | js_rot2pop1(J); |
408 | 0 | } |
409 | |
|
410 | 0 | js_endtry(J); |
411 | 0 | js_free(J, sb); |
412 | 0 | } |
413 | | |
414 | | void jsB_initjson(js_State *J) |
415 | 0 | { |
416 | 0 | js_pushobject(J, jsV_newobject(J, JS_CJSON, J->Object_prototype)); |
417 | 0 | { |
418 | 0 | jsB_propf(J, "JSON.parse", JSON_parse, 2); |
419 | 0 | jsB_propf(J, "JSON.stringify", JSON_stringify, 3); |
420 | 0 | } |
421 | 0 | js_defglobal(J, "JSON", JS_DONTENUM); |
422 | 0 | } |