jbe@121: #include jbe@121: #include jbe@122: #include jbe@121: #include jbe@154: #include jbe@121: jbe@144: // maximum number of nested JSON values (objects and arrays): jbe@150: // NOTE: The Lua reference states that the stack may typically contain at least jbe@150: // "a few thousand elements". Since every nested level consumes jbe@150: // 3 elements on the Lua stack (the object/array, its shadow table, jbe@150: // a string key or a placeholder), we limit the number of nested levels jbe@150: // to 500. If a stack overflow would still happen in the import function, jbe@150: // this is detected nevertheless and an error is thrown (instead of jbe@150: // returning nil and an error string). jbe@150: #define JSON_MAXDEPTH 500 jbe@142: jbe@155: // generate dummy memory addresses that represents null values: jbe@155: char json_nullmark; jbe@155: #define json_isnullmark(L, i) (lua_touserdata((L), (i)) == &json_nullmark) jbe@155: #define json_pushnullmark(L) lua_pushlightuserdata((L), &json_nullmark) jbe@155: jbe@144: // macros for usage of Lua registry: jbe@144: #define JSON_REGENT char jbe@145: #define JSON_REGPOINTER void * jbe@145: #define json_regpointer(x) (&json_registry.x) jbe@151: #define json_regfetchpointer(L, x) lua_rawgetp((L), LUA_REGISTRYINDEX, (x)) jbe@151: #define json_regfetch(L, x) json_regfetchpointer(L, json_regpointer(x)) jbe@151: #define json_regstore(L, x) lua_rawsetp(L, LUA_REGISTRYINDEX, json_regpointer(x)) jbe@145: jbe@144: // generate dummy memory addresses that represent Lua objects jbe@145: // via lightuserdata keys and LUA_REGISTRYINDEX: jbe@144: static struct { jbe@145: JSON_REGENT shadowtbl; // ephemeron table that maps tables to their corresponding shadow table jbe@145: JSON_REGENT objectmt; // metatable for JSON objects jbe@145: JSON_REGENT arraymt; // metatable for JSON arrays jbe@144: } json_registry; jbe@138: jbe@157: // returns the string "": jbe@157: static int json_nullmark_tostring(lua_State *L) { jbe@157: lua_pushliteral(L, ""); jbe@157: return 1; jbe@157: } jbe@157: jbe@145: // marks a Lua table as JSON object or JSON array: jbe@136: // (returns its modified argument or a new table if argument is nil) jbe@145: static int json_mark(lua_State *L, JSON_REGPOINTER mt) { jbe@145: // check if argument is nil jbe@136: if (lua_isnoneornil(L, 1)) { jbe@145: // create new table at stack position 1: jbe@136: lua_settop(L, 0); jbe@136: lua_newtable(L); jbe@145: // create shadow table (leaving previously created table on stack position 1): jbe@144: json_regfetch(L, shadowtbl); jbe@136: lua_pushvalue(L, 1); jbe@136: lua_newtable(L); jbe@143: lua_rawset(L, -3); jbe@143: } else { jbe@145: // push shadow table on top of stack: jbe@144: json_regfetch(L, shadowtbl); jbe@143: lua_pushvalue(L, 1); jbe@143: lua_rawget(L, -2); jbe@145: // if shadow table does not exist: jbe@143: if (lua_isnil(L, -1)) { jbe@145: // create shadow table and leave it on top of stack: jbe@143: lua_newtable(L); jbe@143: lua_pushvalue(L, 1); jbe@143: lua_pushvalue(L, -2); jbe@143: lua_rawset(L, -5); jbe@143: } jbe@145: // move elements from original table to shadow table (that's expected on top of stack): jbe@143: for(lua_pushnil(L); lua_next(L, 1); lua_pop(L, 1)) { jbe@143: lua_pushvalue(L, -2); jbe@143: lua_pushnil(L); jbe@143: lua_rawset(L, 1); jbe@143: lua_pushvalue(L, -2); jbe@143: lua_pushvalue(L, -2); jbe@143: lua_rawset(L, -5); jbe@143: } jbe@136: } jbe@138: // discard everything but table to return: jbe@138: lua_settop(L, 1); jbe@136: // set metatable: jbe@145: json_regfetchpointer(L, mt); jbe@136: lua_setmetatable(L, 1); jbe@138: // return table: jbe@136: return 1; jbe@136: } jbe@136: jbe@136: // marks a table as JSON object: jbe@136: // (returns its modified argument or a new table if argument is nil) jbe@136: static int json_object(lua_State *L) { jbe@145: return json_mark(L, json_regpointer(objectmt)); jbe@136: } jbe@136: jbe@136: // marks a table as JSON array: jbe@136: // (returns its modified argument or a new table if argument is nil) jbe@136: static int json_array(lua_State *L) { jbe@145: return json_mark(L, json_regpointer(arraymt)); jbe@136: } jbe@136: jbe@145: // internal states of JSON parser: jbe@124: #define JSON_STATE_VALUE 0 jbe@124: #define JSON_STATE_OBJECT_KEY 1 jbe@124: #define JSON_STATE_OBJECT_KEY_TERMINATOR 2 jbe@124: #define JSON_STATE_OBJECT_VALUE 3 jbe@124: #define JSON_STATE_OBJECT_SEPARATOR 4 jbe@124: #define JSON_STATE_ARRAY_VALUE 5 jbe@124: #define JSON_STATE_ARRAY_SEPARATOR 6 jbe@124: #define JSON_STATE_END 7 jbe@121: jbe@145: // special Lua stack indicies for json_import function: jbe@138: #define json_import_objectmt_idx 2 jbe@138: #define json_import_arraymt_idx 3 jbe@138: #define json_import_shadowtbl_idx 4 jbe@138: jbe@136: // decodes a JSON document: jbe@121: static int json_import(lua_State *L) { jbe@136: const char *str; // string to parse jbe@136: size_t total; // total length of string to parse jbe@136: size_t pos = 0; // current position in string to parse jbe@136: size_t level = 0; // nested levels of objects/arrays currently being processed jbe@145: int mode = JSON_STATE_VALUE; // state of parser (i.e. "what's expected next?") jbe@136: char c; // variable to store a single character to be processed jbe@145: luaL_Buffer luabuf; // Lua buffer to decode JSON string values jbe@145: char *cbuf; // C buffer to decode JSON string values jbe@136: size_t writepos; // write position of decoded strings in C buffer jbe@152: size_t arraylen; // variable to temporarily store the array length jbe@147: // stack shall contain one function argument: jbe@138: lua_settop(L, 1); jbe@147: // push objectmt onto stack position 2: jbe@144: json_regfetch(L, objectmt); jbe@147: // push arraymt onto stack position 3: jbe@144: json_regfetch(L, arraymt); jbe@147: // push shadowtbl onto stack position 4: jbe@144: json_regfetch(L, shadowtbl); jbe@136: // require string as first argument: jbe@136: str = luaL_checklstring(L, 1, &total); jbe@136: // if string contains a NULL byte, this is a syntax error jbe@136: if (strlen(str) != total) goto json_import_syntax_error; jbe@136: // main loop of parser: jbe@136: json_import_loop: jbe@136: // skip whitespace and store next character in variable 'c': jbe@146: while (c = str[pos], jbe@146: c == ' ' || jbe@146: c == '\f' || jbe@146: c == '\n' || jbe@146: c == '\r' || jbe@146: c == '\t' || jbe@146: c == '\v' jbe@146: ) pos++; jbe@136: // switch statement to handle certain (single) characters: jbe@121: switch (c) { jbe@136: // handle end of JSON document: jbe@121: case 0: jbe@136: // if end of JSON document was expected, then return top element of stack as result: jbe@124: if (mode == JSON_STATE_END) return 1; jbe@136: // otherwise, the JSON document was malformed: jbe@121: json_import_unexpected_eof: jbe@121: lua_pushnil(L); jbe@121: if (level == 0) lua_pushliteral(L, "Empty string"); jbe@121: else lua_pushliteral(L, "Unexpected end of JSON document"); jbe@121: return 2; jbe@136: // new JSON object: jbe@121: case '{': jbe@136: // if a JSON object is not expected here, then return an error: jbe@146: if ( jbe@146: mode != JSON_STATE_VALUE && jbe@146: mode != JSON_STATE_OBJECT_VALUE && jbe@146: mode != JSON_STATE_ARRAY_VALUE jbe@146: ) goto json_import_syntax_error; jbe@136: // create JSON object on stack: jbe@136: lua_newtable(L); jbe@136: // set metatable of JSON object: jbe@138: lua_pushvalue(L, json_import_objectmt_idx); jbe@125: lua_setmetatable(L, -2); jbe@136: // create internal shadow table on stack: jbe@136: lua_newtable(L); jbe@146: // register internal shadow table: jbe@123: lua_pushvalue(L, -2); jbe@123: lua_pushvalue(L, -2); jbe@138: lua_rawset(L, json_import_shadowtbl_idx); jbe@146: // expect object key (or end of object) to follow: jbe@136: mode = JSON_STATE_OBJECT_KEY; jbe@146: // jump to common code for opening JSON object and JSON array: jbe@142: goto json_import_open; jbe@136: // new JSON array: jbe@121: case '[': jbe@136: // if a JSON array is not expected here, then return an error: jbe@146: if ( jbe@146: mode != JSON_STATE_VALUE && jbe@146: mode != JSON_STATE_OBJECT_VALUE && jbe@146: mode != JSON_STATE_ARRAY_VALUE jbe@146: ) goto json_import_syntax_error; jbe@136: // create JSON array on stack: jbe@136: lua_newtable(L); jbe@136: // set metatable of JSON array: jbe@138: lua_pushvalue(L, json_import_arraymt_idx); jbe@125: lua_setmetatable(L, -2); jbe@136: // create internal shadow table on stack: jbe@136: lua_newtable(L); jbe@146: // register internal shadow table: jbe@123: lua_pushvalue(L, -2); jbe@123: lua_pushvalue(L, -2); jbe@138: lua_rawset(L, json_import_shadowtbl_idx); jbe@140: // add nil as key (needed to keep stack balance) and as magic to detect arrays: jbe@140: lua_pushnil(L); jbe@146: // expect array value (or end of array) to follow: jbe@142: mode = JSON_STATE_ARRAY_VALUE; jbe@142: // continue with common code for opening JSON object and JSON array: jbe@146: // common code for opening JSON object or JSON array: jbe@142: json_import_open: jbe@142: // limit nested levels: jbe@142: if (level >= JSON_MAXDEPTH) { jbe@142: lua_pushnil(L); jbe@142: lua_pushliteral(L, "Too many nested JSON levels"); jbe@142: return 2; jbe@142: } jbe@142: // additional buffer overflow protection: jbe@142: if (!lua_checkstack(L, LUA_MINSTACK)) jbe@142: return luaL_error(L, "Caught stack overflow in JSON import function (too many nested levels and stack size too small)"); jbe@136: // increment level: jbe@121: level++; jbe@142: // consume input character: jbe@142: pos++; jbe@121: goto json_import_loop; jbe@136: // end of JSON object: jbe@121: case '}': jbe@136: // if end of JSON object is not expected here, then return an error: jbe@146: if ( jbe@146: mode != JSON_STATE_OBJECT_KEY && jbe@146: mode != JSON_STATE_OBJECT_SEPARATOR jbe@146: ) goto json_import_syntax_error; jbe@136: // jump to common code for end of JSON object and JSON array: jbe@121: goto json_import_close; jbe@136: // end of JSON array: jbe@121: case ']': jbe@136: // if end of JSON array is not expected here, then return an error: jbe@146: if ( jbe@146: mode != JSON_STATE_ARRAY_VALUE && jbe@146: mode != JSON_STATE_ARRAY_SEPARATOR jbe@146: ) goto json_import_syntax_error; jbe@146: // pop nil key/magic (that was needed to keep stack balance): jbe@140: lua_pop(L, 1); jbe@136: // continue with common code for end of JSON object and JSON array: jbe@136: // common code for end of JSON object or JSON array: jbe@121: json_import_close: jbe@136: // consume input character: jbe@121: pos++; jbe@136: // pop shadow table: jbe@136: lua_pop(L, 1); jbe@136: // check if nested: jbe@121: if (--level) { jbe@146: // if nested, jbe@146: // check if outer(!) structure is an array or object: jbe@140: if (lua_isnil(L, -2)) { jbe@136: // select array value processing: jbe@124: mode = JSON_STATE_ARRAY_VALUE; jbe@121: } else { jbe@136: // select object value processing: jbe@124: mode = JSON_STATE_OBJECT_VALUE; jbe@121: } jbe@136: // store value in outer structure: jbe@121: goto json_import_process_value; jbe@121: } jbe@136: // if not nested, then expect end of JSON document and continue with loop: jbe@136: mode = JSON_STATE_END; jbe@121: goto json_import_loop; jbe@136: // key terminator: jbe@121: case ':': jbe@136: // if key terminator is not expected here, then return an error: jbe@124: if (mode != JSON_STATE_OBJECT_KEY_TERMINATOR) jbe@121: goto json_import_syntax_error; jbe@136: // consume input character: jbe@121: pos++; jbe@146: // expect object value to follow: jbe@124: mode = JSON_STATE_OBJECT_VALUE; jbe@146: // continue with loop: jbe@121: goto json_import_loop; jbe@136: // value terminator (NOTE: trailing comma at end of value or key-value list is tolerated by this parser) jbe@121: case ',': jbe@146: // branch according to parser state: jbe@124: if (mode == JSON_STATE_OBJECT_SEPARATOR) { jbe@146: // expect an object key to follow: jbe@124: mode = JSON_STATE_OBJECT_KEY; jbe@124: } else if (mode == JSON_STATE_ARRAY_SEPARATOR) { jbe@146: // expect an array value to follow: jbe@124: mode = JSON_STATE_ARRAY_VALUE; jbe@121: } else { jbe@136: // if value terminator is not expected here, then return an error: jbe@136: goto json_import_syntax_error; jbe@121: } jbe@136: // consume input character: jbe@121: pos++; jbe@136: // continue with loop: jbe@121: goto json_import_loop; jbe@136: // string literal: jbe@121: case '"': jbe@146: // consume quote character: jbe@146: pos++; jbe@136: // prepare buffer to decode string (with maximum possible length) and set write position to zero: jbe@121: cbuf = luaL_buffinitsize(L, &luabuf, total-pos); jbe@121: writepos = 0; jbe@146: // loop through the characters until encountering end quote: jbe@121: while ((c = str[pos++]) != '"') { jbe@121: if (c == 0) { jbe@146: // handle unexpected end of JSON document: jbe@121: goto json_import_unexpected_eof; jbe@121: } else if (c < 32 || c == 127) { jbe@136: // do not allow ASCII control characters: jbe@136: // NOTE: illegal UTF-8 sequences and extended control characters are not sanitized jbe@136: // by this parser to allow different encodings than Unicode jbe@121: lua_pushnil(L); jbe@121: lua_pushliteral(L, "Unexpected control character in JSON string"); jbe@121: return 2; jbe@121: } else if (c == '\\') { jbe@136: // read next char after backslash escape: jbe@121: c = str[pos++]; jbe@121: switch (c) { jbe@136: // unexpected end-of-string: jbe@121: case 0: jbe@121: goto json_import_unexpected_eof; jbe@136: // unescaping of quotation mark, slash, and backslash: jbe@121: case '"': jbe@121: case '/': jbe@121: case '\\': jbe@121: cbuf[writepos++] = c; jbe@121: break; jbe@136: // unescaping of backspace: jbe@146: case 'b': cbuf[writepos++] = '\b'; break; jbe@136: // unescaping of form-feed: jbe@146: case 'f': cbuf[writepos++] = '\f'; break; jbe@136: // unescaping of new-line: jbe@146: case 'n': cbuf[writepos++] = '\n'; break; jbe@136: // unescaping of carriage-return: jbe@146: case 'r': cbuf[writepos++] = '\r'; break; jbe@136: // unescaping of tabulator: jbe@146: case 't': cbuf[writepos++] = '\t'; break; jbe@136: // unescaping of UTF-16 characters jbe@121: case 'u': jbe@121: lua_pushnil(L); jbe@121: lua_pushliteral(L, "JSON unicode escape sequences are not implemented yet"); // TODO jbe@121: return 2; jbe@136: // unexpected escape sequence: jbe@121: default: jbe@121: lua_pushnil(L); jbe@121: lua_pushliteral(L, "Unexpected string escape sequence in JSON document"); jbe@121: return 2; jbe@121: } jbe@121: } else { jbe@136: // normal character: jbe@121: cbuf[writepos++] = c; jbe@121: } jbe@121: } jbe@136: // process buffer to Lua string: jbe@121: luaL_pushresultsize(&luabuf, writepos); jbe@136: // continue with processing of decoded string: jbe@121: goto json_import_process_value; jbe@121: } jbe@136: // process values whose type is is not deducible from a single character: jbe@136: if ((c >= '0' && c <= '9') || c == '-' || c == '+') { jbe@146: // for numbers, jbe@146: // use strtod() call to parse a (double precision) floating point number: jbe@122: char *endptr; jbe@122: double numval; jbe@122: numval = strtod(str+pos, &endptr); jbe@146: // catch parsing errors: jbe@122: if (endptr == str+pos) goto json_import_syntax_error; jbe@146: // consume characters that were parsed: jbe@122: pos += endptr - (str+pos); jbe@146: // push parsed (double precision) floating point number on Lua stack: jbe@122: lua_pushnumber(L, numval); jbe@122: } else if (!strncmp(str+pos, "true", 4)) { jbe@136: // consume 4 input characters for "true": jbe@121: pos += 4; jbe@147: // put Lua true value onto stack: jbe@136: lua_pushboolean(L, 1); jbe@121: } else if (!strncmp(str+pos, "false", 5)) { jbe@136: // consume 5 input characters for "false": jbe@121: pos += 5; jbe@147: // put Lua false value onto stack: jbe@136: lua_pushboolean(L, 0); jbe@121: } else if (!strncmp(str+pos, "null", 4)) { jbe@136: // consume 4 input characters for "null": jbe@136: pos += 4; jbe@153: // different behavor for top-level and sub-levels: jbe@153: if (level) { jbe@153: // if sub-level, jbe@153: // push special null-marker onto stack: jbe@155: json_pushnullmark(L); jbe@153: } else { jbe@153: // if top-level, jbe@153: // push nil onto stack: jbe@153: lua_pushnil(L); jbe@153: } jbe@121: } else { jbe@136: // all other cases are a syntax error: jbe@121: goto json_import_syntax_error; jbe@121: } jbe@136: // process a decoded value or key value pair (expected on top of Lua stack): jbe@136: json_import_process_value: jbe@121: switch (mode) { jbe@136: // an object key has been read: jbe@124: case JSON_STATE_OBJECT_KEY: jbe@136: // if an object key is not a string, then this is a syntax error: jbe@121: if (lua_type(L, -1) != LUA_TSTRING) goto json_import_syntax_error; jbe@146: // expect key terminator to follow: jbe@124: mode = JSON_STATE_OBJECT_KEY_TERMINATOR; jbe@146: // continue with loop: jbe@121: goto json_import_loop; jbe@136: // a key value pair has been read: jbe@124: case JSON_STATE_OBJECT_VALUE: jbe@136: // store key value pair in outer shadow table: jbe@130: lua_rawset(L, -3); jbe@146: // expect value terminator (or end of object) to follow: jbe@124: mode = JSON_STATE_OBJECT_SEPARATOR; jbe@146: // continue with loop: jbe@121: goto json_import_loop; jbe@136: // an array value has been read: jbe@124: case JSON_STATE_ARRAY_VALUE: jbe@152: // get current array length: jbe@152: arraylen = lua_rawlen(L, -3); jbe@152: // throw error if array would exceed INT_MAX elements: jbe@152: // TODO: Lua 5.3 may support more elements jbe@152: if (arraylen >= INT_MAX) { jbe@152: lua_pushnil(L); jbe@152: lua_pushfstring(L, "Array exceeded length of %d elements", INT_MAX); jbe@152: } jbe@136: // store value in outer shadow table: jbe@152: lua_rawseti(L, -3, arraylen + 1); jbe@146: // expect value terminator (or end of object) to follow: jbe@124: mode = JSON_STATE_ARRAY_SEPARATOR; jbe@146: // continue with loop jbe@121: goto json_import_loop; jbe@136: // a single value has been read: jbe@124: case JSON_STATE_VALUE: jbe@136: // leave value on top of stack, expect end of JSON document, and continue with loop: jbe@124: mode = JSON_STATE_END; jbe@121: goto json_import_loop; jbe@121: } jbe@146: // syntax error handling (reachable by goto statement): jbe@136: json_import_syntax_error: jbe@121: lua_pushnil(L); jbe@121: lua_pushliteral(L, "Syntax error in JSON document"); jbe@121: return 2; jbe@121: } jbe@121: jbe@146: // special Lua stack indicies for json_path function: jbe@138: #define json_path_shadowtbl_idx 1 jbe@146: jbe@146: // stack offset of arguments to json_path function: jbe@155: #define json_path_idxshift 1 jbe@138: jbe@146: // gets a value or its type from a JSON document (passed as first argument) jbe@147: // using a path (passed as variable number of keys after first argument): jbe@137: static int json_path(lua_State *L, int type_mode) { jbe@146: int stacktop; // stack index of top of stack (after shifting) jbe@146: int idx = 2 + json_path_idxshift; // stack index of current argument to process jbe@148: // insert shadowtbl into stack at position 1 (shifting the arguments): jbe@144: json_regfetch(L, shadowtbl); jbe@138: lua_insert(L, 1); jbe@146: // store stack index of top of stack: jbe@138: stacktop = lua_gettop(L); jbe@146: // use first argument as "current value" (stored on top of stack): jbe@138: lua_pushvalue(L, 1 + json_path_idxshift); jbe@146: // process each "path key" (2nd argument and following arguments): jbe@138: while (idx <= stacktop) { jbe@146: // if "current value" (on top of stack) is nil, then the path cannot be walked and nil is returned: jbe@137: if (lua_isnil(L, -1)) return 1; jbe@137: // try to get shadow table of "current value": jbe@130: lua_pushvalue(L, -1); jbe@138: lua_rawget(L, json_path_shadowtbl_idx); jbe@126: if (lua_isnil(L, -1)) { jbe@137: // if no shadow table is found, jbe@130: if (lua_type(L, -1) == LUA_TTABLE) { jbe@146: // and if "current value" is a table, jbe@146: // drop nil from stack: jbe@146: lua_pop(L, 1); jbe@137: // get "next value" using the "path key": jbe@130: lua_pushvalue(L, idx++); jbe@130: lua_gettable(L, -2); jbe@130: } else { jbe@137: // if "current value" is not a table, jbe@146: // then the path cannot be walked and nil (already on top of stack) is returned: jbe@137: return 1; jbe@130: } jbe@130: } else { jbe@137: // if a shadow table is found, jbe@137: // set "current value" to its shadow table: jbe@130: lua_replace(L, -2); jbe@137: // get "next value" using the "path key": jbe@130: lua_pushvalue(L, idx++); jbe@130: lua_rawget(L, -2); jbe@126: } jbe@137: // the "next value" replaces the "current value": jbe@130: lua_replace(L, -2); jbe@126: } jbe@137: if (!type_mode) { jbe@137: // if a value (and not its type) was requested, jbe@137: // check if value is the null-marker, and store nil on top of Lua stack in that case: jbe@155: if (json_isnullmark(L, -1)) lua_pushnil(L); jbe@137: } else { jbe@137: // if the type was requested, jbe@137: // check if value is the null-marker: jbe@155: if (json_isnullmark(L, -1)) { jbe@137: // if yes, store string "null" on top of Lua stack: jbe@130: lua_pushliteral(L, "null"); jbe@137: } else { jbe@137: // otherwise, jbe@138: // check if metatable indicates "object" or "array": jbe@138: if (lua_getmetatable(L, -1)) { jbe@144: json_regfetch(L, objectmt); jbe@138: if (lua_rawequal(L, -2, -1)) { jbe@146: // if value has metatable for JSON objects, jbe@138: // return string "object": jbe@138: lua_pushliteral(L, "object"); jbe@138: return 1; jbe@138: } jbe@144: json_regfetch(L, arraymt); jbe@138: if (lua_rawequal(L, -3, -1)) { jbe@146: // if value has metatable for JSON arrays, jbe@146: // return string "object": jbe@138: lua_pushliteral(L, "array"); jbe@138: return 1; jbe@138: } jbe@146: // remove 3 metatables (one of the value, two for comparison) from stack: jbe@138: lua_pop(L, 3); jbe@138: } jbe@138: // otherwise, get the Lua type: jbe@138: lua_pushstring(L, lua_typename(L, lua_type(L, -1))); jbe@126: } jbe@126: } jbe@137: // return the top most value on the Lua stack: jbe@137: return 1; jbe@130: } jbe@130: jbe@147: // gets a value from a JSON document (passed as first argument) jbe@147: // using a path (passed as variable number of keys after first argument): jbe@130: static int json_get(lua_State *L) { jbe@137: return json_path(L, 0); jbe@130: } jbe@130: jbe@147: // gets a value's type from a JSON document (passed as first argument) jbe@147: // using a path (variable number of keys after first argument): jbe@130: static int json_type(lua_State *L) { jbe@137: return json_path(L, 1); jbe@130: } jbe@130: jbe@147: // returns the length of a JSON array (or zero for a table without numeric keys): jbe@130: static int json_len(lua_State *L) { jbe@147: // stack shall contain one function argument: jbe@130: lua_settop(L, 1); jbe@148: // try to get corresponding shadow table for first argument: jbe@144: json_regfetch(L, shadowtbl); jbe@130: lua_pushvalue(L, 1); jbe@138: lua_rawget(L, -2); jbe@147: // if shadow table does not exist, return length of argument, else length of shadow table: jbe@147: lua_pushnumber(L, lua_rawlen(L, lua_isnil(L, -1) ? 1 : -1)); jbe@123: return 1; jbe@123: } jbe@123: jbe@130: static int json_index(lua_State *L) { jbe@148: // stack shall contain two function arguments: jbe@130: lua_settop(L, 2); jbe@155: // get corresponding shadow table for first argument: jbe@144: json_regfetch(L, shadowtbl); jbe@130: lua_pushvalue(L, 1); jbe@155: lua_rawget(L, -2); jbe@148: // throw error if no shadow table was found: jbe@139: if (lua_isnil(L, -1)) return luaL_error(L, "Shadow table not found"); jbe@148: // use key passed as second argument to lookup value in shadow table: jbe@130: lua_pushvalue(L, 2); jbe@130: lua_rawget(L, -2); jbe@148: // if value is null-marker, then push nil onto stack: jbe@155: if (json_isnullmark(L, -1)) lua_pushnil(L); jbe@148: // return either looked up value, or nil jbe@127: return 1; jbe@127: } jbe@127: jbe@130: static int json_newindex(lua_State *L) { jbe@148: // stack shall contain three function arguments: jbe@130: lua_settop(L, 3); jbe@148: // get corresponding shadow table for first argument: jbe@144: json_regfetch(L, shadowtbl); jbe@123: lua_pushvalue(L, 1); jbe@143: lua_rawget(L, -2); jbe@148: // throw error if no shadow table was found: jbe@130: if (lua_isnil(L, -1)) return luaL_error(L, "Shadow table not found"); jbe@148: // replace first argument with shadow table: jbe@130: lua_replace(L, 1); jbe@148: // reset stack and use second and third argument to write to shadow table: jbe@139: lua_settop(L, 3); jbe@130: lua_rawset(L, 1); jbe@148: // return nothing: jbe@148: return 0; jbe@121: } jbe@121: jbe@135: static int json_pairs_iterfunc(lua_State *L) { jbe@149: // stack shall contain two function arguments: jbe@135: lua_settop(L, 2); jbe@155: // get corresponding shadow table for first argument: jbe@144: json_regfetch(L, shadowtbl); jbe@135: lua_pushvalue(L, 1); jbe@155: lua_rawget(L, -2); jbe@149: // throw error if no shadow table was found: jbe@135: if (lua_isnil(L, -1)) return luaL_error(L, "Shadow table not found"); jbe@149: // get next key value pair from shadow table (using previous key from argument 2) jbe@149: // and return nothing if there is no next pair: jbe@135: lua_pushvalue(L, 2); jbe@135: if (!lua_next(L, -2)) return 0; jbe@149: // replace null-marker with nil: jbe@155: if (json_isnullmark(L, -1)) { jbe@135: lua_pop(L, 1); jbe@135: lua_pushnil(L); jbe@135: } jbe@149: // return key and value (or key and nil, if null-marker was found): jbe@135: return 2; jbe@135: } jbe@135: jbe@149: // returns a triple such that 'for key, value in pairs(obj) do ... end' jbe@149: // iterates through all key value pairs (including JSON null keys represented as Lua nil): jbe@135: static int json_pairs(lua_State *L) { jbe@149: // return triple of function json_pairs_iterfunc, first argument, and nil: jbe@139: lua_pushcfunction(L, json_pairs_iterfunc); jbe@135: lua_pushvalue(L, 1); jbe@135: lua_pushnil(L); jbe@135: return 3; jbe@135: } jbe@135: jbe@134: static int json_ipairs_iterfunc(lua_State *L) { jbe@152: lua_Integer idx; jbe@149: // stack shall contain two function arguments: jbe@134: lua_settop(L, 2); jbe@149: // calculate new index by incrementing second argument: jbe@134: idx = lua_tointeger(L, 2) + 1; jbe@149: // get corresponding shadow table for first argument: jbe@155: json_regfetch(L, shadowtbl); jbe@134: lua_pushvalue(L, 1); jbe@155: lua_rawget(L, -2); jbe@149: // throw error if no shadow table was found: jbe@134: if (lua_isnil(L, -1)) return luaL_error(L, "Shadow table not found"); jbe@149: // do integer lookup in shadow table: jbe@134: lua_rawgeti(L, -1, idx); jbe@149: // return nothing if there was no value: jbe@134: if (lua_isnil(L, -1)) return 0; jbe@149: // return new index and jbe@149: // either the looked up value if it is not equal to the null-marker jbe@149: // or nil instead of null-marker: jbe@134: lua_pushinteger(L, idx); jbe@155: if (json_isnullmark(L, -2)) lua_pushnil(L); jbe@134: else lua_pushvalue(L, -2); jbe@134: return 2; jbe@134: } jbe@134: jbe@149: // returns a triple such that 'for idx, value in ipairs(ary) do ... end' jbe@149: // iterates through all values (including JSON null represented as Lua nil): jbe@134: static int json_ipairs(lua_State *L) { jbe@149: // return triple of function json_ipairs_iterfunc, first argument, and zero: jbe@139: lua_pushcfunction(L, json_ipairs_iterfunc); jbe@134: lua_pushvalue(L, 1); jbe@134: lua_pushinteger(L, 0); jbe@134: return 3; jbe@134: } jbe@134: jbe@154: #define JSON_TABLETYPE_UNKNOWN 0 jbe@154: #define JSON_TABLETYPE_OBJECT 1 jbe@154: #define JSON_TABLETYPE_ARRAY 2 jbe@154: jbe@154: static int json_export(lua_State *L) { jbe@154: lua_Number num; jbe@154: const char *str; jbe@154: unsigned char c; jbe@154: size_t strlen; jbe@154: size_t pos = 0; jbe@154: luaL_Buffer buf; jbe@154: char hexcode[7]; // backslash, character 'u', 4 hex digits, and terminating NULL byte jbe@154: int luatype; jbe@154: int tabletype = JSON_TABLETYPE_UNKNOWN; jbe@154: int needsep = 0; jbe@154: lua_Integer idx; jbe@154: lua_settop(L, 1); jbe@157: if (json_isnullmark(L, 1)) { jbe@157: lua_pushnil(L); jbe@157: lua_replace(L, 1); jbe@157: } jbe@154: switch (lua_type(L, 1)) { jbe@154: case LUA_TNIL: jbe@154: lua_pushliteral(L, "null"); jbe@154: return 1; jbe@154: case LUA_TNUMBER: jbe@154: num = lua_tonumber(L, 1); jbe@154: if (isnan(num)) return luaL_error(L, "JSON export not possible for NaN value"); jbe@154: if (isinf(num)) return luaL_error(L, "JSON export not possible for infinite numbers"); jbe@154: lua_tostring(L, 1); jbe@154: return 1; jbe@154: case LUA_TBOOLEAN: jbe@154: if (lua_toboolean(L, 1)) lua_pushliteral(L, "true"); jbe@154: else lua_pushliteral(L, "false"); jbe@154: return 1; jbe@154: case LUA_TSTRING: jbe@154: str = lua_tolstring(L, 1, &strlen); jbe@154: luaL_buffinit(L, &buf); jbe@154: luaL_addchar(&buf, '"'); jbe@154: while (pos < strlen) { jbe@154: c = str[pos++]; jbe@154: if (c == '"') luaL_addstring(&buf, "\\\""); jbe@154: else if (c == '\\') luaL_addstring(&buf, "\\\\"); jbe@154: else if (c == 127) luaL_addstring(&buf, "\\u007F"); jbe@154: else if (c >= 32) luaL_addchar(&buf, c); jbe@154: else if (c == '\b') luaL_addstring(&buf, "\\b"); jbe@154: else if (c == '\f') luaL_addstring(&buf, "\\f"); jbe@154: else if (c == '\n') luaL_addstring(&buf, "\\n"); jbe@154: else if (c == '\r') luaL_addstring(&buf, "\\r"); jbe@154: else if (c == '\t') luaL_addstring(&buf, "\\t"); jbe@154: else if (c == '\v') luaL_addstring(&buf, "\\v"); jbe@154: else { jbe@154: sprintf(hexcode, "\\u%04X", c); jbe@154: luaL_addstring(&buf, hexcode); jbe@154: } jbe@154: } jbe@154: luaL_addchar(&buf, '"'); jbe@154: luaL_pushresult(&buf); jbe@154: return 1; jbe@154: case LUA_TTABLE: jbe@154: if (lua_getmetatable(L, 1)) { jbe@154: json_regfetch(L, objectmt); jbe@154: if (lua_rawequal(L, -2, -1)) { jbe@154: tabletype = JSON_TABLETYPE_OBJECT; jbe@154: } else { jbe@154: json_regfetch(L, arraymt); jbe@154: if (lua_rawequal(L, -3, -1)) tabletype = JSON_TABLETYPE_ARRAY; jbe@154: } jbe@154: } jbe@154: json_regfetch(L, shadowtbl); jbe@154: lua_pushvalue(L, 1); jbe@154: lua_rawget(L, -2); jbe@154: if (!lua_isnil(L, -1)) lua_replace(L, 1); jbe@154: lua_settop(L, 1); jbe@154: if (tabletype == JSON_TABLETYPE_UNKNOWN) { jbe@154: for (lua_pushnil(L); lua_next(L, 1); lua_pop(L, 1)) { jbe@154: luatype = lua_type(L, -2); jbe@154: if (tabletype == JSON_TABLETYPE_UNKNOWN) { jbe@154: if (luatype == LUA_TSTRING) tabletype = JSON_TABLETYPE_OBJECT; jbe@154: else if (luatype == LUA_TNUMBER) tabletype = JSON_TABLETYPE_ARRAY; jbe@154: } else if ( jbe@154: (tabletype == JSON_TABLETYPE_OBJECT && luatype == LUA_TNUMBER) || jbe@154: (tabletype == JSON_TABLETYPE_ARRAY && luatype == LUA_TSTRING) jbe@154: ) { jbe@154: goto json_export_tabletype_error; jbe@154: } jbe@154: } jbe@154: } jbe@154: switch (tabletype) { jbe@154: case JSON_TABLETYPE_OBJECT: jbe@154: lua_settop(L, 3); jbe@154: luaL_buffinit(L, &buf); jbe@154: luaL_addchar(&buf, '{'); jbe@154: for (lua_pushnil(L); lua_next(L, 1); ) { jbe@154: if (lua_type(L, -2) == LUA_TSTRING) { jbe@154: lua_replace(L, 3); jbe@154: lua_replace(L, 2); jbe@154: if (needsep) luaL_addchar(&buf, ','); jbe@154: else needsep = 1; jbe@154: lua_pushcfunction(L, json_export); jbe@154: lua_pushvalue(L, 2); jbe@154: lua_call(L, 1, 1); jbe@154: luaL_addvalue(&buf); jbe@154: luaL_addchar(&buf, ':'); jbe@155: if (json_isnullmark(L, 3)) { jbe@154: luaL_addstring(&buf, "null"); jbe@154: } else { jbe@154: lua_pushcfunction(L, json_export); jbe@154: lua_pushvalue(L, 3); jbe@154: lua_call(L, 1, 1); jbe@154: luaL_addvalue(&buf); jbe@154: } jbe@154: lua_pushvalue(L, 2); jbe@154: } else { jbe@154: lua_pop(L, 1); jbe@154: } jbe@154: } jbe@154: luaL_addchar(&buf, '}'); jbe@154: luaL_pushresult(&buf); jbe@154: return 1; jbe@154: case JSON_TABLETYPE_ARRAY: jbe@154: lua_settop(L, 2); jbe@154: luaL_buffinit(L, &buf); jbe@154: luaL_addchar(&buf, '['); jbe@154: for (idx = 1; ; idx++) { jbe@154: lua_rawgeti(L, 1, idx); jbe@154: if (lua_isnil(L, -1)) { jbe@154: lua_pop(L, 1); jbe@154: break; jbe@154: } jbe@154: lua_replace(L, 2); jbe@154: if (needsep) luaL_addchar(&buf, ','); jbe@154: else needsep = 1; jbe@154: lua_pushcfunction(L, json_export); jbe@154: lua_pushvalue(L, 2); jbe@154: lua_call(L, 1, 1); jbe@154: luaL_addvalue(&buf); jbe@154: } jbe@154: luaL_addchar(&buf, ']'); jbe@154: luaL_pushresult(&buf); jbe@154: return 1; jbe@154: } jbe@154: json_export_tabletype_error: jbe@154: return luaL_error(L, "JSON export not possible for ambiguous table (cannot decide whether it is an object or array)"); jbe@154: } jbe@154: return luaL_error(L, "JSON export not possible for values of type \"%s\"", lua_typename(L, lua_type(L, 1))); jbe@154: } jbe@154: jbe@149: // functions in library module: jbe@121: static const struct luaL_Reg json_module_functions[] = { jbe@133: {"object", json_object}, jbe@133: {"array", json_array}, jbe@121: {"import", json_import}, jbe@154: {"export", json_export}, jbe@130: {"get", json_get}, jbe@127: {"type", json_type}, jbe@121: {NULL, NULL} jbe@121: }; jbe@121: jbe@149: // metamethods for JSON objects, JSON arrays, and unknown JSON collections (object or array): jbe@126: static const struct luaL_Reg json_metatable_functions[] = { jbe@130: {"__len", json_len}, jbe@130: {"__index", json_index}, jbe@130: {"__newindex", json_newindex}, jbe@135: {"__pairs", json_pairs}, jbe@134: {"__ipairs", json_ipairs}, jbe@160: {"__tostring", json_export}, jbe@126: {NULL, NULL} jbe@126: }; jbe@126: jbe@157: // metamethods for JSON null marker: jbe@157: static const struct luaL_Reg json_nullmark_metamethods[] = { jbe@157: {"__tostring", json_nullmark_tostring}, jbe@157: {NULL, NULL} jbe@157: }; jbe@157: jbe@149: // initializes json library: jbe@121: int luaopen_json(lua_State *L) { jbe@149: // empty stack: jbe@126: lua_settop(L, 0); jbe@149: // push library module onto stack position 1: jbe@149: lua_newtable(L); jbe@149: // register library functions: jbe@149: luaL_setfuncs(L, json_module_functions, 0); jbe@149: // create and store objectmt: jbe@138: lua_newtable(L); jbe@138: luaL_setfuncs(L, json_metatable_functions, 0); jbe@144: json_regstore(L, objectmt); jbe@149: // create and store arraymt: jbe@138: lua_newtable(L); jbe@138: luaL_setfuncs(L, json_metatable_functions, 0); jbe@144: json_regstore(L, arraymt); jbe@149: // create and store ephemeron table to store shadow tables for each JSON object/array jbe@149: // to allow NULL values returned as nil jbe@149: lua_newtable(L); jbe@138: lua_newtable(L); // metatable for ephemeron table jbe@121: lua_pushliteral(L, "__mode"); jbe@121: lua_pushliteral(L, "k"); jbe@138: lua_rawset(L, -3); jbe@138: lua_setmetatable(L, -2); jbe@144: json_regstore(L, shadowtbl); jbe@157: // set metatable of null marker and make it available through library module: jbe@157: json_pushnullmark(L); jbe@157: lua_newtable(L); jbe@157: luaL_setfuncs(L, json_nullmark_metamethods, 0); jbe@157: lua_setmetatable(L, -2); jbe@157: lua_setfield(L, 1, "null"); jbe@157: // return library module (that's expected on top of stack): jbe@121: return 1; jbe@121: }