jbe@121: #include jbe@121: #include jbe@122: #include jbe@121: #include jbe@121: jbe@144: // maximum number of nested JSON values (objects and arrays): jbe@142: #define JSON_MAXDEPTH 100 jbe@142: jbe@144: // macros for usage of Lua registry: jbe@144: #define JSON_REGENT char jbe@145: #define JSON_REGPOINTER void * jbe@145: #define json_pushlightref(L, x) (lua_pushlightuserdata((L), &json_reference.x)) jbe@145: #define json_regpointer(x) (&json_registry.x) jbe@145: #define json_regfetchpointer(L, x) (lua_pushlightuserdata((L), (x)), lua_rawget((L), LUA_REGISTRYINDEX)) jbe@145: #define json_regfetch(L, x) (json_regfetchpointer(L, json_regpointer(x))) jbe@145: #define json_regstore(L, x) (lua_pushlightuserdata(L, json_regpointer(x)), lua_pushvalue(L, -2), lua_rawset(L, LUA_REGISTRYINDEX)); jbe@145: jbe@146: // generate dummy memory addresses that represent non-modifiable lightuserdata (dummy) objects: jbe@145: static struct { jbe@146: JSON_REGENT nullmark; // magic value to indicate JSON null value in shadow table jbe@145: } json_reference; jbe@145: jbe@138: 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 unknownmt; // metatable for tables that may be either JSON objects or JSON arrays 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@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: #define json_import_nullmark_idx 5 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@145: // stack shall only contain one function argument: jbe@138: lua_settop(L, 1); jbe@145: // push objectmt on stack position 2: jbe@144: json_regfetch(L, objectmt); jbe@145: // push arraymt on stack position 3: jbe@144: json_regfetch(L, arraymt); jbe@145: // push shadowtbl on stack position 4: jbe@144: json_regfetch(L, shadowtbl); jbe@145: // push nullmark on stack position 5: jbe@145: json_pushlightref(L, nullmark); 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@136: // put Lua true value on 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@136: // put Lua false value on 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@136: // put special null-marker on stack: jbe@138: lua_pushvalue(L, json_import_nullmark_idx); 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@136: // store value in outer shadow table: jbe@140: lua_rawseti(L, -3, lua_rawlen(L, -3) + 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@138: #define json_path_nullmark_idx 2 jbe@146: jbe@146: // stack offset of arguments to json_path function: jbe@138: #define json_path_idxshift 2 jbe@138: jbe@146: // gets a value or its type from a JSON document (passed as first argument) jbe@146: // optionally 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@146: // insert json_shadowtbl on stack at position 1 (shifting the arguments): jbe@144: json_regfetch(L, shadowtbl); jbe@138: lua_insert(L, 1); jbe@146: // insert json_nullmark on stack at position 2 (shifting the arguments): jbe@145: json_pushlightref(L, nullmark); jbe@138: lua_insert(L, 2); 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@138: if (lua_rawequal(L, -1, json_path_nullmark_idx)) lua_pushnil(L); jbe@137: } else { jbe@137: // if the type was requested, jbe@137: // check if value is the null-marker: jbe@138: if (lua_rawequal(L, -1, json_path_nullmark_idx)) { 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@137: // gets a value from a JSON document (first argument) jbe@137: // optionally using a path (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@137: // gets a value's type from a JSON document (first argument) jbe@137: // optionally 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@137: // checks if a value in a JSON document (first argument) is null: jbe@130: static int json_isnull(lua_State *L) { jbe@137: const char *jsontype; jbe@138: lua_pushcfunction(L, json_type); jbe@137: lua_insert(L, 1); jbe@137: lua_call(L, lua_gettop(L) - 1, 1); jbe@137: jsontype = lua_tostring(L, -1); jbe@137: if (jsontype && !strcmp(jsontype, "null")) lua_pushboolean(L, 1); jbe@137: else lua_pushboolean(L, 0); jbe@137: return 1; jbe@130: } jbe@130: jbe@146: // special Lua stack indicies for json_setnull function: jbe@138: #define json_setnull_unknownmt_idx 3 jbe@138: #define json_setnull_objectmt_idx 4 jbe@138: #define json_setnull_arraymt_idx 5 jbe@138: #define json_setnull_shadowtbl_idx 6 jbe@138: jbe@131: static int json_setnull(lua_State *L) { jbe@138: // truncate stack to two elements: jbe@131: lua_settop(L, 2); jbe@138: // push json_unknownmt to stack position 3: jbe@144: json_regfetch(L, unknownmt); jbe@138: // push json_objectmt to stack position 4: jbe@144: json_regfetch(L, objectmt); jbe@138: // push json_arraymt to stack position 5: jbe@144: json_regfetch(L, arraymt); jbe@138: // push json_shadowtbl to stack position 6: jbe@144: json_regfetch(L, shadowtbl); jbe@138: // jbe@138: lua_getmetatable(L, 1); jbe@138: if ( jbe@138: !lua_rawequal(L, -1, json_setnull_unknownmt_idx) && jbe@138: !lua_rawequal(L, -1, json_setnull_objectmt_idx) && jbe@138: !lua_rawequal(L, -1, json_setnull_arraymt_idx) jbe@138: ) { jbe@138: lua_pushvalue(L, json_setnull_unknownmt_idx); jbe@138: lua_setmetatable(L, 1); jbe@138: } jbe@131: lua_pushvalue(L, 1); jbe@138: lua_rawget(L, json_setnull_shadowtbl_idx); jbe@131: if (lua_isnil(L, -1)) { jbe@131: lua_newtable(L); jbe@131: lua_pushvalue(L, 1); jbe@131: lua_pushvalue(L, -2); jbe@138: lua_rawset(L, json_setnull_shadowtbl_idx); jbe@131: } jbe@131: lua_pushvalue(L, 2); jbe@145: json_pushlightref(L, nullmark); jbe@131: lua_rawset(L, -3); jbe@131: return 0; jbe@131: } jbe@131: jbe@130: static int json_len(lua_State *L) { jbe@130: lua_settop(L, 1); jbe@144: json_regfetch(L, shadowtbl); jbe@130: lua_pushvalue(L, 1); jbe@138: lua_rawget(L, -2); jbe@138: lua_pushinteger(L, lua_rawlen(L, lua_isnil(L, -1) ? 1 : -1)); jbe@123: return 1; jbe@123: } jbe@123: jbe@146: // special Lua stack indicies for json_index function: jbe@141: #define json_index_nullmark_idx 3 jbe@141: #define json_index_shadowtbl_idx 4 jbe@141: jbe@130: static int json_index(lua_State *L) { jbe@130: lua_settop(L, 2); jbe@145: json_pushlightref(L, nullmark); // on stack position 3 jbe@144: json_regfetch(L, shadowtbl); jbe@130: lua_pushvalue(L, 1); jbe@141: lua_rawget(L, json_index_shadowtbl_idx); jbe@139: if (lua_isnil(L, -1)) return luaL_error(L, "Shadow table not found"); jbe@130: lua_pushvalue(L, 2); jbe@130: lua_rawget(L, -2); jbe@141: if (lua_rawequal(L, -1, json_index_nullmark_idx)) lua_pushnil(L); jbe@127: return 1; jbe@127: } jbe@127: jbe@130: static int json_newindex(lua_State *L) { jbe@130: lua_settop(L, 3); jbe@144: json_regfetch(L, shadowtbl); jbe@123: lua_pushvalue(L, 1); jbe@143: lua_rawget(L, -2); jbe@130: if (lua_isnil(L, -1)) return luaL_error(L, "Shadow table not found"); jbe@130: lua_replace(L, 1); jbe@139: lua_settop(L, 3); jbe@130: lua_rawset(L, 1); jbe@121: return 1; jbe@121: } jbe@121: jbe@146: // special Lua stack indicies for json_pairs_iterfunc function: jbe@139: #define json_pairs_iterfunc_nullmark_idx 3 jbe@139: #define json_pairs_iterfunc_shadowtbl_idx 4 jbe@139: jbe@135: static int json_pairs_iterfunc(lua_State *L) { jbe@135: lua_settop(L, 2); jbe@145: json_pushlightref(L, nullmark); // on stack position 3 jbe@144: json_regfetch(L, shadowtbl); jbe@135: lua_pushvalue(L, 1); jbe@139: lua_rawget(L, json_pairs_iterfunc_shadowtbl_idx); jbe@135: if (lua_isnil(L, -1)) return luaL_error(L, "Shadow table not found"); jbe@135: lua_pushvalue(L, 2); jbe@135: if (!lua_next(L, -2)) return 0; jbe@139: if (lua_rawequal(L, -1, json_pairs_iterfunc_nullmark_idx)) { jbe@135: lua_pop(L, 1); jbe@135: lua_pushnil(L); jbe@135: } jbe@135: return 2; jbe@135: } jbe@135: jbe@135: static int json_pairs(lua_State *L) { 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@146: // special Lua stack indicies for json_ipairs_iterfunc function: jbe@139: #define json_ipairs_iterfunc_nullmark_idx 3 jbe@139: #define json_ipairs_iterfunc_shadowtbl_idx 4 jbe@139: jbe@134: static int json_ipairs_iterfunc(lua_State *L) { jbe@134: int idx; jbe@134: lua_settop(L, 2); jbe@145: json_pushlightref(L, nullmark); // on stack position 3 jbe@144: json_regfetch(L, shadowtbl); jbe@134: idx = lua_tointeger(L, 2) + 1; jbe@134: lua_pushvalue(L, 1); jbe@139: lua_rawget(L, json_ipairs_iterfunc_shadowtbl_idx); jbe@134: if (lua_isnil(L, -1)) return luaL_error(L, "Shadow table not found"); jbe@134: lua_rawgeti(L, -1, idx); jbe@134: if (lua_isnil(L, -1)) return 0; jbe@134: lua_pushinteger(L, idx); jbe@139: if (lua_rawequal(L, -2, json_ipairs_iterfunc_nullmark_idx)) lua_pushnil(L); jbe@134: else lua_pushvalue(L, -2); jbe@134: return 2; jbe@134: } jbe@134: jbe@134: static int json_ipairs(lua_State *L) { 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@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@130: {"get", json_get}, jbe@127: {"type", json_type}, jbe@123: {"isnull", json_isnull}, jbe@131: {"setnull", json_setnull}, jbe@121: {NULL, NULL} jbe@121: }; jbe@121: 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@126: {NULL, NULL} jbe@126: }; jbe@126: jbe@121: int luaopen_json(lua_State *L) { jbe@126: lua_settop(L, 0); jbe@138: lua_newtable(L); // library jbe@138: lua_newtable(L); jbe@138: luaL_setfuncs(L, json_metatable_functions, 0); jbe@144: json_regstore(L, unknownmt); jbe@138: lua_setfield(L, 1, "ambiguous_mt"); jbe@138: lua_newtable(L); jbe@138: luaL_setfuncs(L, json_metatable_functions, 0); jbe@144: json_regstore(L, objectmt); jbe@138: lua_setfield(L, 1, "object_mt"); jbe@138: lua_newtable(L); jbe@138: luaL_setfuncs(L, json_metatable_functions, 0); jbe@144: json_regstore(L, arraymt); jbe@138: lua_setfield(L, 1, "array_mt"); jbe@138: lua_newtable(L); // ephemeron table to store shadow tables for each JSON object/array to allow NULL values returned as nil 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@138: lua_settop(L, 1); jbe@138: luaL_setfuncs(L, json_module_functions, 0); jbe@121: return 1; jbe@121: }