webmcp

view libraries/json/json.c @ 136:3cf5fcf2bd5f

Some code cleanup and documentation of JSON library
author jbe
date Mon Jul 28 17:16:34 2014 +0200 (2014-07-28)
parents 663722e35330
children f490b78827d6
line source
1 #include <lua.h>
2 #include <lauxlib.h>
3 #include <stdlib.h>
4 #include <string.h>
6 #define JSON_UPVAL_NULLMARK lua_upvalueindex(1)
7 #define JSON_UPVAL_SHADOWTBL lua_upvalueindex(2)
8 #define JSON_UPVAL_TYPES lua_upvalueindex(3)
9 #define JSON_UPVAL_METATABLE lua_upvalueindex(4)
10 #define JSON_UPVAL_PAIRS_ITERFUNC lua_upvalueindex(5)
11 #define JSON_UPVAL_IPAIRS_ITERFUNC lua_upvalueindex(6)
13 // marks a table as JSON object or JSON array:
14 // (returns its modified argument or a new table if argument is nil)
15 static int json_mark(lua_State *L, const char *marker) {
16 // if argument is nil, then create new table:
17 if (lua_isnoneornil(L, 1)) {
18 lua_settop(L, 0);
19 lua_newtable(L);
20 // skip testing of existing shadow table:
21 goto json_object_create_shadow_table;
22 }
23 lua_pushvalue(L, 1);
24 lua_rawget(L, JSON_UPVAL_SHADOWTBL);
25 if (lua_isnil(L, -1)) {
26 json_object_create_shadow_table:
27 // set shadow table:
28 lua_pushvalue(L, 1);
29 lua_newtable(L);
30 lua_rawset(L, JSON_UPVAL_SHADOWTBL);
31 }
32 // set metatable:
33 lua_pushvalue(L, JSON_UPVAL_METATABLE);
34 lua_setmetatable(L, 1);
35 // mark table as JSON object or as JSON array
36 lua_pushvalue(L, 1);
37 lua_pushstring(L, marker);
38 lua_rawset(L, JSON_UPVAL_TYPES);
39 return 1;
40 }
42 // marks a table as JSON object:
43 // (returns its modified argument or a new table if argument is nil)
44 static int json_object(lua_State *L) {
45 return json_mark(L, "object");
46 }
48 // marks a table as JSON array:
49 // (returns its modified argument or a new table if argument is nil)
50 static int json_array(lua_State *L) {
51 return json_mark(L, "array");
52 }
54 #define JSON_STATE_VALUE 0
55 #define JSON_STATE_OBJECT_KEY 1
56 #define JSON_STATE_OBJECT_KEY_TERMINATOR 2
57 #define JSON_STATE_OBJECT_VALUE 3
58 #define JSON_STATE_OBJECT_SEPARATOR 4
59 #define JSON_STATE_ARRAY_VALUE 5
60 #define JSON_STATE_ARRAY_SEPARATOR 6
61 #define JSON_STATE_END 7
63 // decodes a JSON document:
64 static int json_import(lua_State *L) {
65 const char *str; // string to parse
66 size_t total; // total length of string to parse
67 size_t pos = 0; // current position in string to parse
68 size_t level = 0; // nested levels of objects/arrays currently being processed
69 int mode = JSON_STATE_VALUE; // state of parser
70 char c; // variable to store a single character to be processed
71 luaL_Buffer luabuf; // Lua buffer to decode (possibly escaped) strings
72 char *cbuf; // C buffer to decode (possibly escaped) strings
73 size_t writepos; // write position of decoded strings in C buffer
74 // require string as first argument:
75 str = luaL_checklstring(L, 1, &total);
76 // if string contains a NULL byte, this is a syntax error
77 if (strlen(str) != total) goto json_import_syntax_error;
78 // main loop of parser:
79 json_import_loop:
80 // skip whitespace and store next character in variable 'c':
81 while (c = str[pos], c == ' ' || c == '\n' || c == '\r' || c == '\t' || c == '\f') pos++;
82 // switch statement to handle certain (single) characters:
83 switch (c) {
84 // handle end of JSON document:
85 case 0:
86 // if end of JSON document was expected, then return top element of stack as result:
87 if (mode == JSON_STATE_END) return 1;
88 // otherwise, the JSON document was malformed:
89 json_import_unexpected_eof:
90 lua_pushnil(L);
91 if (level == 0) lua_pushliteral(L, "Empty string");
92 else lua_pushliteral(L, "Unexpected end of JSON document");
93 return 2;
94 // new JSON object:
95 case '{':
96 // if a JSON object is not expected here, then return an error:
97 if (mode != JSON_STATE_VALUE && mode != JSON_STATE_OBJECT_VALUE && mode != JSON_STATE_ARRAY_VALUE)
98 goto json_import_syntax_error;
99 // consume input character:
100 pos++;
101 // create JSON object on stack:
102 lua_newtable(L);
103 // set metatable of JSON object:
104 lua_pushvalue(L, JSON_UPVAL_METATABLE);
105 lua_setmetatable(L, -2);
106 // mark JSON object as JSON object (as opposed to JSON array):
107 lua_pushvalue(L, -1);
108 lua_pushliteral(L, "object");
109 lua_rawset(L, JSON_UPVAL_TYPES);
110 // create internal shadow table on stack:
111 lua_newtable(L);
112 // register internal shadow table:
113 lua_pushvalue(L, -2);
114 lua_pushvalue(L, -2);
115 lua_rawset(L, JSON_UPVAL_SHADOWTBL);
116 // increment level:
117 level++;
118 // expect object key (or end of object) and continue with loop:
119 mode = JSON_STATE_OBJECT_KEY;
120 goto json_import_loop;
121 // new JSON array:
122 case '[':
123 // if a JSON array is not expected here, then return an error:
124 if (mode != JSON_STATE_VALUE && mode != JSON_STATE_OBJECT_VALUE && mode != JSON_STATE_ARRAY_VALUE)
125 goto json_import_syntax_error;
126 // consume input character:
127 pos++;
128 // create JSON array on stack:
129 lua_newtable(L);
130 // set metatable of JSON array:
131 lua_pushvalue(L, JSON_UPVAL_METATABLE);
132 lua_setmetatable(L, -2);
133 // mark JSON array as JSON array (as opposed to JSON object):
134 lua_pushvalue(L, -1);
135 lua_pushliteral(L, "array");
136 lua_rawset(L, JSON_UPVAL_TYPES);
137 // create internal shadow table on stack:
138 lua_newtable(L);
139 // register internal shadow table:
140 lua_pushvalue(L, -2);
141 lua_pushvalue(L, -2);
142 lua_rawset(L, JSON_UPVAL_SHADOWTBL);
143 // increment level:
144 level++;
145 // expect array value (or end of array) and continue with loop:
146 mode = JSON_STATE_ARRAY_VALUE;
147 goto json_import_loop;
148 // end of JSON object:
149 case '}':
150 // if end of JSON object is not expected here, then return an error:
151 if (mode != JSON_STATE_OBJECT_KEY && mode != JSON_STATE_OBJECT_SEPARATOR)
152 goto json_import_syntax_error;
153 // jump to common code for end of JSON object and JSON array:
154 goto json_import_close;
155 // end of JSON array:
156 case ']':
157 // if end of JSON array is not expected here, then return an error:
158 if (mode != JSON_STATE_ARRAY_VALUE && mode != JSON_STATE_ARRAY_SEPARATOR)
159 goto json_import_syntax_error;
160 // continue with common code for end of JSON object and JSON array:
161 // common code for end of JSON object or JSON array:
162 json_import_close:
163 // consume input character:
164 pos++;
165 // pop shadow table:
166 lua_pop(L, 1);
167 // check if nested:
168 if (--level) {
169 // if nested, then check if outer(!) structure is an array or object:
170 lua_pushvalue(L, c == '}' ? -4 : -3);
171 lua_rawget(L, JSON_UPVAL_TYPES);
172 if (lua_tostring(L, -1)[0] == 'a') {
173 // select array value processing:
174 mode = JSON_STATE_ARRAY_VALUE;
175 } else {
176 // select object value processing:
177 mode = JSON_STATE_OBJECT_VALUE;
178 }
179 // pop JSON type from stack (from rawget JSON_UPVAL_TYPES):
180 lua_pop(L, 1);
181 // store value in outer structure:
182 goto json_import_process_value;
183 }
184 // if not nested, then expect end of JSON document and continue with loop:
185 mode = JSON_STATE_END;
186 goto json_import_loop;
187 // key terminator:
188 case ':':
189 // if key terminator is not expected here, then return an error:
190 if (mode != JSON_STATE_OBJECT_KEY_TERMINATOR)
191 goto json_import_syntax_error;
192 // consume input character:
193 pos++;
194 // set state of parser and continue with loop:
195 mode = JSON_STATE_OBJECT_VALUE;
196 goto json_import_loop;
197 // value terminator (NOTE: trailing comma at end of value or key-value list is tolerated by this parser)
198 case ',':
199 // change parser state accordingly:
200 if (mode == JSON_STATE_OBJECT_SEPARATOR) {
201 mode = JSON_STATE_OBJECT_KEY;
202 } else if (mode == JSON_STATE_ARRAY_SEPARATOR) {
203 mode = JSON_STATE_ARRAY_VALUE;
204 } else {
205 // if value terminator is not expected here, then return an error:
206 goto json_import_syntax_error;
207 }
208 // consume input character:
209 pos++;
210 // continue with loop:
211 goto json_import_loop;
212 // string literal:
213 case '"':
214 // prepare buffer to decode string (with maximum possible length) and set write position to zero:
215 cbuf = luaL_buffinitsize(L, &luabuf, total-pos);
216 writepos = 0;
217 // consume quote character:
218 pos++;
219 // read next character until encountering end quote:
220 while ((c = str[pos++]) != '"') {
221 if (c == 0) {
222 // handle unexpected end-of-string:
223 goto json_import_unexpected_eof;
224 } else if (c < 32 || c == 127) {
225 // do not allow ASCII control characters:
226 // NOTE: illegal UTF-8 sequences and extended control characters are not sanitized
227 // by this parser to allow different encodings than Unicode
228 lua_pushnil(L);
229 lua_pushliteral(L, "Unexpected control character in JSON string");
230 return 2;
231 } else if (c == '\\') {
232 // read next char after backslash escape:
233 c = str[pos++];
234 switch (c) {
235 // unexpected end-of-string:
236 case 0:
237 goto json_import_unexpected_eof;
238 // unescaping of quotation mark, slash, and backslash:
239 case '"':
240 case '/':
241 case '\\':
242 cbuf[writepos++] = c;
243 break;
244 // unescaping of backspace:
245 case 'b':
246 cbuf[writepos++] = '\b';
247 break;
248 // unescaping of form-feed:
249 case 'f':
250 cbuf[writepos++] = '\f';
251 break;
252 // unescaping of new-line:
253 case 'n':
254 cbuf[writepos++] = '\n';
255 break;
256 // unescaping of carriage-return:
257 case 'r':
258 cbuf[writepos++] = '\r';
259 break;
260 // unescaping of tabulator:
261 case 't':
262 cbuf[writepos++] = '\t';
263 break;
264 // unescaping of UTF-16 characters
265 case 'u':
266 lua_pushnil(L);
267 lua_pushliteral(L, "JSON unicode escape sequences are not implemented yet"); // TODO
268 return 2;
269 // unexpected escape sequence:
270 default:
271 lua_pushnil(L);
272 lua_pushliteral(L, "Unexpected string escape sequence in JSON document");
273 return 2;
274 }
275 } else {
276 // normal character:
277 cbuf[writepos++] = c;
278 }
279 }
280 // process buffer to Lua string:
281 luaL_pushresultsize(&luabuf, writepos);
282 // continue with processing of decoded string:
283 goto json_import_process_value;
284 }
285 // process values whose type is is not deducible from a single character:
286 if ((c >= '0' && c <= '9') || c == '-' || c == '+') {
287 // numbers:
288 char *endptr;
289 double numval;
290 numval = strtod(str+pos, &endptr);
291 if (endptr == str+pos) goto json_import_syntax_error;
292 pos += endptr - (str+pos);
293 lua_pushnumber(L, numval);
294 } else if (!strncmp(str+pos, "true", 4)) {
295 // consume 4 input characters for "true":
296 pos += 4;
297 // put Lua true value on stack:
298 lua_pushboolean(L, 1);
299 } else if (!strncmp(str+pos, "false", 5)) {
300 // consume 5 input characters for "false":
301 pos += 5;
302 // put Lua false value on stack:
303 lua_pushboolean(L, 0);
304 } else if (!strncmp(str+pos, "null", 4)) {
305 // consume 4 input characters for "null":
306 pos += 4;
307 // put special null-marker on stack:
308 lua_pushvalue(L, JSON_UPVAL_NULLMARK);
309 } else {
310 // all other cases are a syntax error:
311 goto json_import_syntax_error;
312 }
313 // process a decoded value or key value pair (expected on top of Lua stack):
314 json_import_process_value:
315 switch (mode) {
316 // an object key has been read:
317 case JSON_STATE_OBJECT_KEY:
318 // if an object key is not a string, then this is a syntax error:
319 if (lua_type(L, -1) != LUA_TSTRING) goto json_import_syntax_error;
320 // expect key terminator and continue with loop:
321 mode = JSON_STATE_OBJECT_KEY_TERMINATOR;
322 goto json_import_loop;
323 // a key value pair has been read:
324 case JSON_STATE_OBJECT_VALUE:
325 // store key value pair in outer shadow table:
326 lua_rawset(L, -3);
327 // expect value terminator (or end of object) and continue with loop:
328 mode = JSON_STATE_OBJECT_SEPARATOR;
329 goto json_import_loop;
330 // an array value has been read:
331 case JSON_STATE_ARRAY_VALUE:
332 // store value in outer shadow table:
333 lua_rawseti(L, -2, lua_rawlen(L, -2) + 1);
334 // expect value terminator (or end of object) and continue with loop:
335 mode = JSON_STATE_ARRAY_SEPARATOR;
336 goto json_import_loop;
337 // a single value has been read:
338 case JSON_STATE_VALUE:
339 // leave value on top of stack, expect end of JSON document, and continue with loop:
340 mode = JSON_STATE_END;
341 goto json_import_loop;
342 }
343 // syntax error handling (only reachable by goto statement):
344 json_import_syntax_error:
345 lua_pushnil(L);
346 lua_pushliteral(L, "Syntax error in JSON document");
347 return 2;
348 }
350 #define JSON_PATH_GET 1
351 #define JSON_PATH_TYPE 2
352 #define JSON_PATH_ISNULL 3
354 // gets a value, its type, or information
355 static int json_path(lua_State *L, int mode) {
356 int argc;
357 int idx = 2;
358 argc = lua_gettop(L);
359 lua_pushvalue(L, 1);
360 while (idx <= argc) {
361 if (lua_isnil(L, -1)) {
362 if (mode == JSON_PATH_ISNULL) lua_pushboolean(L, 0);
363 return 1;
364 }
365 lua_pushvalue(L, -1);
366 lua_rawget(L, JSON_UPVAL_SHADOWTBL);
367 if (lua_isnil(L, -1)) {
368 lua_pop(L, 1);
369 if (lua_type(L, -1) == LUA_TTABLE) {
370 lua_pushvalue(L, idx++);
371 lua_gettable(L, -2);
372 } else {
373 lua_pushnil(L);
374 }
375 } else {
376 lua_replace(L, -2);
377 lua_pushvalue(L, idx++);
378 lua_rawget(L, -2);
379 }
380 lua_replace(L, -2);
381 }
382 switch (mode) {
383 case JSON_PATH_GET:
384 if (lua_rawequal(L, -1, JSON_UPVAL_NULLMARK)) lua_pushnil(L);
385 return 1;
386 case JSON_PATH_TYPE:
387 if (lua_rawequal(L, -1, JSON_UPVAL_NULLMARK)) {
388 lua_pushliteral(L, "null");
389 return 1;
390 }
391 lua_pushvalue(L, -1);
392 lua_rawget(L, JSON_UPVAL_TYPES);
393 if (lua_isnil(L, -1)) lua_pushstring(L, lua_typename(L, lua_type(L, -2)));
394 return 1;
395 case JSON_PATH_ISNULL:
396 lua_pushboolean(L, lua_rawequal(L, -1, JSON_UPVAL_NULLMARK));
397 return 1;
398 }
399 return 0;
400 }
402 static int json_get(lua_State *L) {
403 return json_path(L, JSON_PATH_GET);
404 }
406 static int json_type(lua_State *L) {
407 return json_path(L, JSON_PATH_TYPE);
408 }
410 static int json_isnull(lua_State *L) {
411 return json_path(L, JSON_PATH_ISNULL);
412 }
414 static int json_setnull(lua_State *L) {
415 lua_settop(L, 2);
416 lua_pushvalue(L, JSON_UPVAL_METATABLE);
417 lua_setmetatable(L, 1);
418 lua_pushvalue(L, 1);
419 lua_rawget(L, JSON_UPVAL_SHADOWTBL);
420 if (lua_isnil(L, -1)) {
421 lua_newtable(L);
422 lua_pushvalue(L, 1);
423 lua_pushvalue(L, -2);
424 lua_rawset(L, JSON_UPVAL_SHADOWTBL);
425 }
426 lua_pushvalue(L, 2);
427 lua_pushvalue(L, JSON_UPVAL_NULLMARK);
428 lua_rawset(L, -3);
429 return 0;
430 }
432 static int json_len(lua_State *L) {
433 lua_settop(L, 1);
434 lua_pushvalue(L, 1);
435 lua_rawget(L, JSON_UPVAL_SHADOWTBL);
436 if (lua_isnil(L, -1)) lua_pop(L, 1);
437 lua_pushinteger(L, lua_rawlen(L, -1));
438 return 1;
439 }
441 static int json_index(lua_State *L) {
442 lua_settop(L, 2);
443 lua_pushvalue(L, 1);
444 lua_rawget(L, JSON_UPVAL_SHADOWTBL);
445 if (lua_isnil(L, -1)) return 1;
446 lua_pushvalue(L, 2);
447 lua_rawget(L, -2);
448 if (lua_rawequal(L, -1, JSON_UPVAL_NULLMARK)) lua_pushnil(L);
449 return 1;
450 }
452 static int json_newindex(lua_State *L) {
453 lua_settop(L, 3);
454 lua_pushvalue(L, 1);
455 lua_rawget(L, JSON_UPVAL_SHADOWTBL);
456 if (lua_isnil(L, -1)) return luaL_error(L, "Shadow table not found");
457 lua_replace(L, 1);
458 lua_rawset(L, 1);
459 return 1;
460 }
462 static int json_pairs_iterfunc(lua_State *L) {
463 lua_settop(L, 2);
464 lua_pushvalue(L, 1);
465 lua_rawget(L, JSON_UPVAL_SHADOWTBL);
466 if (lua_isnil(L, -1)) return luaL_error(L, "Shadow table not found");
467 lua_pushvalue(L, 2);
468 if (!lua_next(L, -2)) return 0;
469 if (lua_rawequal(L, -1, JSON_UPVAL_NULLMARK)) {
470 lua_pop(L, 1);
471 lua_pushnil(L);
472 }
473 return 2;
474 }
476 static int json_pairs(lua_State *L) {
477 lua_pushvalue(L, JSON_UPVAL_PAIRS_ITERFUNC);
478 lua_pushvalue(L, 1);
479 lua_pushnil(L);
480 return 3;
481 }
483 static int json_ipairs_iterfunc(lua_State *L) {
484 int idx;
485 lua_settop(L, 2);
486 idx = lua_tointeger(L, 2) + 1;
487 lua_pushvalue(L, 1);
488 lua_rawget(L, JSON_UPVAL_SHADOWTBL);
489 if (lua_isnil(L, -1)) return luaL_error(L, "Shadow table not found");
490 lua_rawgeti(L, -1, idx);
491 if (lua_isnil(L, -1)) return 0;
492 lua_pushinteger(L, idx);
493 if (lua_rawequal(L, -2, JSON_UPVAL_NULLMARK)) lua_pushnil(L);
494 else lua_pushvalue(L, -2);
495 return 2;
496 }
498 static int json_ipairs(lua_State *L) {
499 lua_pushvalue(L, JSON_UPVAL_IPAIRS_ITERFUNC);
500 lua_pushvalue(L, 1);
501 lua_pushinteger(L, 0);
502 return 3;
503 }
505 static const struct luaL_Reg json_module_functions[] = {
506 {"object", json_object},
507 {"array", json_array},
508 {"import", json_import},
509 {"get", json_get},
510 {"type", json_type},
511 {"isnull", json_isnull},
512 {"setnull", json_setnull},
513 {NULL, NULL}
514 };
516 static const struct luaL_Reg json_metatable_functions[] = {
517 {"__len", json_len},
518 {"__index", json_index},
519 {"__newindex", json_newindex},
520 {"__pairs", json_pairs},
521 {"__ipairs", json_ipairs},
522 {NULL, NULL}
523 };
525 int luaopen_json(lua_State *L) {
526 lua_settop(L, 0);
527 lua_newtable(L); // 1: library table on stack position
528 lua_newtable(L); // 2: table used as JSON NULL value in internal shadow tables
529 lua_newtable(L); // 3: ephemeron table to store shadow tables for each JSON object/array to allow NULL values returned as nil
530 lua_newtable(L); // 4: ephemeron table to store the type of the JSON object/array
531 lua_newtable(L); // 5: metatable for ephemeron tables
532 lua_pushliteral(L, "__mode");
533 lua_pushliteral(L, "k");
534 lua_rawset(L, 5);
535 lua_pushvalue(L, 5); // 6: cloned metatable reference
536 lua_setmetatable(L, 3);
537 lua_setmetatable(L, 4);
538 lua_newtable(L); // 5: metatable for JSON objects and JSON arrays
539 lua_pushvalue(L, 2);
540 lua_pushvalue(L, 3);
541 lua_pushvalue(L, 4);
542 lua_pushvalue(L, 5);
543 lua_pushcclosure(L, json_pairs_iterfunc, 4); // 6: iteration function for pairs
544 lua_pushvalue(L, 2);
545 lua_pushvalue(L, 3);
546 lua_pushvalue(L, 4);
547 lua_pushvalue(L, 5);
548 lua_pushcclosure(L, json_ipairs_iterfunc, 4); // 7: iteration function for ipairs
549 lua_pushvalue(L, 5);
550 lua_pushvalue(L, 2);
551 lua_pushvalue(L, 3);
552 lua_pushvalue(L, 4);
553 lua_pushvalue(L, 5);
554 lua_pushvalue(L, 6);
555 lua_pushvalue(L, 7);
556 luaL_setfuncs(L, json_metatable_functions, 6);
557 lua_setfield(L, 1, "metatable");
558 luaL_setfuncs(L, json_module_functions, 6);
559 return 1;
560 }

Impressum / About Us