# HG changeset patch # User jbe # Date 1406560594 -7200 # Node ID 3cf5fcf2bd5f4376d95dbe1df0ba023199f95c44 # Parent 663722e35330f2360bcbdb5f1307824793709908 Some code cleanup and documentation of JSON library diff -r 663722e35330 -r 3cf5fcf2bd5f libraries/json/json.c --- a/libraries/json/json.c Mon Jul 28 03:00:40 2014 +0200 +++ b/libraries/json/json.c Mon Jul 28 17:16:34 2014 +0200 @@ -10,6 +10,47 @@ #define JSON_UPVAL_PAIRS_ITERFUNC lua_upvalueindex(5) #define JSON_UPVAL_IPAIRS_ITERFUNC lua_upvalueindex(6) +// marks a table as JSON object or JSON array: +// (returns its modified argument or a new table if argument is nil) +static int json_mark(lua_State *L, const char *marker) { + // if argument is nil, then create new table: + if (lua_isnoneornil(L, 1)) { + lua_settop(L, 0); + lua_newtable(L); + // skip testing of existing shadow table: + goto json_object_create_shadow_table; + } + lua_pushvalue(L, 1); + lua_rawget(L, JSON_UPVAL_SHADOWTBL); + if (lua_isnil(L, -1)) { + json_object_create_shadow_table: + // set shadow table: + lua_pushvalue(L, 1); + lua_newtable(L); + lua_rawset(L, JSON_UPVAL_SHADOWTBL); + } + // set metatable: + lua_pushvalue(L, JSON_UPVAL_METATABLE); + lua_setmetatable(L, 1); + // mark table as JSON object or as JSON array + lua_pushvalue(L, 1); + lua_pushstring(L, marker); + lua_rawset(L, JSON_UPVAL_TYPES); + return 1; +} + +// marks a table as JSON object: +// (returns its modified argument or a new table if argument is nil) +static int json_object(lua_State *L) { + return json_mark(L, "object"); +} + +// marks a table as JSON array: +// (returns its modified argument or a new table if argument is nil) +static int json_array(lua_State *L) { + return json_mark(L, "array"); +} + #define JSON_STATE_VALUE 0 #define JSON_STATE_OBJECT_KEY 1 #define JSON_STATE_OBJECT_KEY_TERMINATOR 2 @@ -19,190 +60,231 @@ #define JSON_STATE_ARRAY_SEPARATOR 6 #define JSON_STATE_END 7 -static int json_object(lua_State *L) { - lua_settop(L, 1); - if (lua_isnil(L, 1)) { - lua_settop(L, 0); - lua_newtable(L); - } - lua_pushvalue(L, JSON_UPVAL_METATABLE); - lua_setmetatable(L, 1); - lua_pushvalue(L, 1); - lua_newtable(L); // internal shadow table - lua_rawset(L, JSON_UPVAL_SHADOWTBL); - lua_pushvalue(L, 1); - lua_pushliteral(L, "object"); - lua_rawset(L, JSON_UPVAL_TYPES); - return 1; -} - -static int json_array(lua_State *L) { - lua_settop(L, 1); - if (lua_isnil(L, 1)) { - lua_settop(L, 0); - lua_newtable(L); - } - lua_pushvalue(L, JSON_UPVAL_METATABLE); - lua_setmetatable(L, 1); - lua_pushvalue(L, 1); - lua_newtable(L); // internal shadow table - lua_rawset(L, JSON_UPVAL_SHADOWTBL); - lua_pushvalue(L, 1); - lua_pushliteral(L, "array"); - lua_rawset(L, JSON_UPVAL_TYPES); - return 1; -} - +// decodes a JSON document: static int json_import(lua_State *L) { - const char *str; - size_t total; - size_t pos = 0; - size_t level = 0; - int mode = JSON_STATE_VALUE; - char c; - luaL_Buffer luabuf; - char *cbuf; - size_t writepos; - lua_settop(L, 1); - str = lua_tostring(L, 1); - total = strlen(str); -json_import_loop: + const char *str; // string to parse + size_t total; // total length of string to parse + size_t pos = 0; // current position in string to parse + size_t level = 0; // nested levels of objects/arrays currently being processed + int mode = JSON_STATE_VALUE; // state of parser + char c; // variable to store a single character to be processed + luaL_Buffer luabuf; // Lua buffer to decode (possibly escaped) strings + char *cbuf; // C buffer to decode (possibly escaped) strings + size_t writepos; // write position of decoded strings in C buffer + // require string as first argument: + str = luaL_checklstring(L, 1, &total); + // if string contains a NULL byte, this is a syntax error + if (strlen(str) != total) goto json_import_syntax_error; + // main loop of parser: + json_import_loop: + // skip whitespace and store next character in variable 'c': while (c = str[pos], c == ' ' || c == '\n' || c == '\r' || c == '\t' || c == '\f') pos++; + // switch statement to handle certain (single) characters: switch (c) { + // handle end of JSON document: case 0: + // if end of JSON document was expected, then return top element of stack as result: if (mode == JSON_STATE_END) return 1; + // otherwise, the JSON document was malformed: json_import_unexpected_eof: lua_pushnil(L); if (level == 0) lua_pushliteral(L, "Empty string"); else lua_pushliteral(L, "Unexpected end of JSON document"); return 2; + // new JSON object: case '{': + // if a JSON object is not expected here, then return an error: if (mode != JSON_STATE_VALUE && mode != JSON_STATE_OBJECT_VALUE && mode != JSON_STATE_ARRAY_VALUE) goto json_import_syntax_error; + // consume input character: pos++; - lua_newtable(L); // the external JSON object representation + // create JSON object on stack: + lua_newtable(L); + // set metatable of JSON object: lua_pushvalue(L, JSON_UPVAL_METATABLE); lua_setmetatable(L, -2); + // mark JSON object as JSON object (as opposed to JSON array): lua_pushvalue(L, -1); lua_pushliteral(L, "object"); lua_rawset(L, JSON_UPVAL_TYPES); - lua_newtable(L); // the internal shadow table + // create internal shadow table on stack: + lua_newtable(L); + // register internal shadow table: lua_pushvalue(L, -2); lua_pushvalue(L, -2); lua_rawset(L, JSON_UPVAL_SHADOWTBL); - mode = JSON_STATE_OBJECT_KEY; + // increment level: level++; + // expect object key (or end of object) and continue with loop: + mode = JSON_STATE_OBJECT_KEY; goto json_import_loop; + // new JSON array: case '[': + // if a JSON array is not expected here, then return an error: if (mode != JSON_STATE_VALUE && mode != JSON_STATE_OBJECT_VALUE && mode != JSON_STATE_ARRAY_VALUE) goto json_import_syntax_error; + // consume input character: pos++; - lua_newtable(L); // the external JSON array representation + // create JSON array on stack: + lua_newtable(L); + // set metatable of JSON array: lua_pushvalue(L, JSON_UPVAL_METATABLE); lua_setmetatable(L, -2); + // mark JSON array as JSON array (as opposed to JSON object): lua_pushvalue(L, -1); lua_pushliteral(L, "array"); lua_rawset(L, JSON_UPVAL_TYPES); - lua_newtable(L); // the internal shadow table + // create internal shadow table on stack: + lua_newtable(L); + // register internal shadow table: lua_pushvalue(L, -2); lua_pushvalue(L, -2); lua_rawset(L, JSON_UPVAL_SHADOWTBL); - lua_pushinteger(L, 0); // magic integer to indicate an array - mode = JSON_STATE_ARRAY_VALUE; + // increment level: level++; + // expect array value (or end of array) and continue with loop: + mode = JSON_STATE_ARRAY_VALUE; goto json_import_loop; + // end of JSON object: case '}': + // if end of JSON object is not expected here, then return an error: if (mode != JSON_STATE_OBJECT_KEY && mode != JSON_STATE_OBJECT_SEPARATOR) goto json_import_syntax_error; + // jump to common code for end of JSON object and JSON array: goto json_import_close; + // end of JSON array: case ']': + // if end of JSON array is not expected here, then return an error: if (mode != JSON_STATE_ARRAY_VALUE && mode != JSON_STATE_ARRAY_SEPARATOR) goto json_import_syntax_error; - lua_pop(L, 1); // pop magic integer + // continue with common code for end of JSON object and JSON array: + // common code for end of JSON object or JSON array: json_import_close: + // consume input character: pos++; - lua_pop(L, 1); // pop shadow table + // pop shadow table: + lua_pop(L, 1); + // check if nested: if (--level) { - if (lua_type(L, -2) == LUA_TNUMBER) { + // if nested, then check if outer(!) structure is an array or object: + lua_pushvalue(L, c == '}' ? -4 : -3); + lua_rawget(L, JSON_UPVAL_TYPES); + if (lua_tostring(L, -1)[0] == 'a') { + // select array value processing: mode = JSON_STATE_ARRAY_VALUE; } else { + // select object value processing: mode = JSON_STATE_OBJECT_VALUE; } + // pop JSON type from stack (from rawget JSON_UPVAL_TYPES): + lua_pop(L, 1); + // store value in outer structure: goto json_import_process_value; - } else { - mode = JSON_STATE_END; } + // if not nested, then expect end of JSON document and continue with loop: + mode = JSON_STATE_END; goto json_import_loop; + // key terminator: case ':': + // if key terminator is not expected here, then return an error: if (mode != JSON_STATE_OBJECT_KEY_TERMINATOR) goto json_import_syntax_error; + // consume input character: pos++; + // set state of parser and continue with loop: mode = JSON_STATE_OBJECT_VALUE; goto json_import_loop; + // value terminator (NOTE: trailing comma at end of value or key-value list is tolerated by this parser) case ',': + // change parser state accordingly: if (mode == JSON_STATE_OBJECT_SEPARATOR) { mode = JSON_STATE_OBJECT_KEY; } else if (mode == JSON_STATE_ARRAY_SEPARATOR) { mode = JSON_STATE_ARRAY_VALUE; } else { - goto json_import_syntax_error; + // if value terminator is not expected here, then return an error: + goto json_import_syntax_error; } + // consume input character: pos++; + // continue with loop: goto json_import_loop; + // string literal: case '"': + // prepare buffer to decode string (with maximum possible length) and set write position to zero: cbuf = luaL_buffinitsize(L, &luabuf, total-pos); writepos = 0; + // consume quote character: pos++; + // read next character until encountering end quote: while ((c = str[pos++]) != '"') { if (c == 0) { + // handle unexpected end-of-string: goto json_import_unexpected_eof; } else if (c < 32 || c == 127) { + // do not allow ASCII control characters: + // NOTE: illegal UTF-8 sequences and extended control characters are not sanitized + // by this parser to allow different encodings than Unicode lua_pushnil(L); lua_pushliteral(L, "Unexpected control character in JSON string"); return 2; } else if (c == '\\') { + // read next char after backslash escape: c = str[pos++]; switch (c) { + // unexpected end-of-string: case 0: goto json_import_unexpected_eof; + // unescaping of quotation mark, slash, and backslash: case '"': case '/': case '\\': cbuf[writepos++] = c; break; + // unescaping of backspace: case 'b': cbuf[writepos++] = '\b'; break; + // unescaping of form-feed: case 'f': cbuf[writepos++] = '\f'; break; + // unescaping of new-line: case 'n': cbuf[writepos++] = '\n'; break; + // unescaping of carriage-return: case 'r': cbuf[writepos++] = '\r'; break; + // unescaping of tabulator: case 't': cbuf[writepos++] = '\t'; break; + // unescaping of UTF-16 characters case 'u': lua_pushnil(L); lua_pushliteral(L, "JSON unicode escape sequences are not implemented yet"); // TODO return 2; + // unexpected escape sequence: default: lua_pushnil(L); lua_pushliteral(L, "Unexpected string escape sequence in JSON document"); return 2; } } else { + // normal character: cbuf[writepos++] = c; } } - if (!c) goto json_import_unexpected_eof; + // process buffer to Lua string: luaL_pushresultsize(&luabuf, writepos); + // continue with processing of decoded string: goto json_import_process_value; } - if (c == '-' || (c >= '0' && c <= '9')) { + // process values whose type is is not deducible from a single character: + if ((c >= '0' && c <= '9') || c == '-' || c == '+') { + // numbers: char *endptr; double numval; numval = strtod(str+pos, &endptr); @@ -210,36 +292,56 @@ pos += endptr - (str+pos); lua_pushnumber(L, numval); } else if (!strncmp(str+pos, "true", 4)) { - lua_pushboolean(L, 1); + // consume 4 input characters for "true": pos += 4; + // put Lua true value on stack: + lua_pushboolean(L, 1); } else if (!strncmp(str+pos, "false", 5)) { - lua_pushboolean(L, 0); + // consume 5 input characters for "false": pos += 5; + // put Lua false value on stack: + lua_pushboolean(L, 0); } else if (!strncmp(str+pos, "null", 4)) { + // consume 4 input characters for "null": + pos += 4; + // put special null-marker on stack: lua_pushvalue(L, JSON_UPVAL_NULLMARK); - pos += 4; } else { + // all other cases are a syntax error: goto json_import_syntax_error; } -json_import_process_value: + // process a decoded value or key value pair (expected on top of Lua stack): + json_import_process_value: switch (mode) { + // an object key has been read: case JSON_STATE_OBJECT_KEY: + // if an object key is not a string, then this is a syntax error: if (lua_type(L, -1) != LUA_TSTRING) goto json_import_syntax_error; + // expect key terminator and continue with loop: mode = JSON_STATE_OBJECT_KEY_TERMINATOR; goto json_import_loop; + // a key value pair has been read: case JSON_STATE_OBJECT_VALUE: + // store key value pair in outer shadow table: lua_rawset(L, -3); + // expect value terminator (or end of object) and continue with loop: mode = JSON_STATE_OBJECT_SEPARATOR; goto json_import_loop; + // an array value has been read: case JSON_STATE_ARRAY_VALUE: - lua_rawseti(L, -3, lua_rawlen(L, -3) + 1); + // store value in outer shadow table: + lua_rawseti(L, -2, lua_rawlen(L, -2) + 1); + // expect value terminator (or end of object) and continue with loop: mode = JSON_STATE_ARRAY_SEPARATOR; goto json_import_loop; + // a single value has been read: case JSON_STATE_VALUE: + // leave value on top of stack, expect end of JSON document, and continue with loop: mode = JSON_STATE_END; goto json_import_loop; } -json_import_syntax_error: + // syntax error handling (only reachable by goto statement): + json_import_syntax_error: lua_pushnil(L); lua_pushliteral(L, "Syntax error in JSON document"); return 2; @@ -249,6 +351,7 @@ #define JSON_PATH_TYPE 2 #define JSON_PATH_ISNULL 3 +// gets a value, its type, or information static int json_path(lua_State *L, int mode) { int argc; int idx = 2;