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@182: // NOTE: json_import can store 2^32 / 3 levels on stack swap (using jbe@183: // also negative indicies after integer wraparound), and jbe@183: // json_export can store even more levels, so 1024^3 = jbe@183: // 1073741824 is a safe value and allows practically unlimited jbe@183: // levels for JSON documents <= 2 GiB. jbe@182: #define JSON_MAXDEPTH (1024*1024*1024) jbe@142: jbe@193: // define type JSON_LIGHTUSERDATA and jbe@193: // generate dummy memory addresses for lightuserdata values: jbe@193: #define JSON_LIGHTUSERDATA char jbe@193: static struct { jbe@193: JSON_LIGHTUSERDATA nullmark; // lightuserdata value represents a NULL value jbe@193: JSON_LIGHTUSERDATA shadowtbl; // lightuserdata key for shadow table jbe@193: } json_lightuserdata; jbe@193: jbe@193: // macros for special nullmark value: jbe@193: #define json_isnullmark(L, i) (lua_touserdata((L), (i)) == &json_lightuserdata.nullmark) jbe@193: #define json_pushnullmark(L) lua_pushlightuserdata((L), &json_lightuserdata.nullmark) jbe@193: jbe@193: // macros for getting and setting shadow tables jbe@193: #define json_setshadow(L, i) lua_rawsetp((L), (i), &json_lightuserdata.shadowtbl) jbe@193: #define json_getshadow(L, i) lua_rawgetp((L), (i), &json_lightuserdata.shadowtbl) jbe@193: #define json_createproxy(L) lua_createtable((L), 0, 1) jbe@193: jbe@193: // generate additional dummy memory addresses that represent Lua objects jbe@193: // via lightuserdata keys and LUA_REGISTRYINDEX: jbe@193: static struct { jbe@193: JSON_LIGHTUSERDATA objectmt; // metatable for JSON objects jbe@193: JSON_LIGHTUSERDATA arraymt; // metatable for JSON arrays jbe@193: } json_registry; jbe@155: jbe@144: // macros for usage of Lua registry: 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@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@169: #define json_convert_source_idx 1 jbe@169: #define json_convert_iterator_idx 2 jbe@169: #define json_convert_output_idx 3 jbe@169: #define json_convert_shadow_idx 4 jbe@172: #define json_convert_iterfun_idx 5 jbe@172: #define json_convert_itertbl_idx 6 jbe@169: jbe@175: // converts a Lua table (or any other iterable value) to a JSON object or JSON array: jbe@169: // (does never modify the argument, returns an empty object or array if argument is nil) jbe@169: static int json_convert(lua_State *L, int array) { jbe@171: int arrayidx = 0; jbe@171: // determine is argument is given: jbe@169: if (lua_isnoneornil(L, json_convert_source_idx)) { jbe@171: // if no argument is given (or if argument is nil), jbe@193: // create proxy table with shadow table, and leave proxy table on top of stack: jbe@193: json_createproxy(L); jbe@169: lua_newtable(L); jbe@193: json_setshadow(L, -2); jbe@169: } else { jbe@171: // if an argument was given, jbe@193: // stack shall contain only one function argument: jbe@171: lua_settop(L, 1); jbe@193: // check if there is an iterator function in its metatable: jbe@193: if (luaL_getmetafield(L, json_convert_source_idx, array ? "__ipairs" : "__pairs")) { jbe@193: // if there is an iterator function, jbe@193: // leave it on stack position 2 and verify its type: jbe@193: if (lua_type(L, json_convert_iterator_idx) != LUA_TFUNCTION) jbe@169: return luaL_error(L, "%s metamethod is not a function", array ? "__ipairs" : "__pairs"); jbe@169: } else { jbe@193: // if there is no iterator function, jbe@193: // verify the type of the argument itself: jbe@193: luaL_checktype(L, json_convert_source_idx, LUA_TTABLE); jbe@193: // push nil onto stack position 2: jbe@169: lua_pushnil(L); jbe@143: } jbe@171: // create result table on stack position 3: jbe@193: json_createproxy(L); jbe@169: // create shadow table on stack position 4: jbe@169: lua_newtable(L); jbe@193: lua_pushvalue(L, -1); jbe@193: json_setshadow(L, -3); jbe@171: // check if iterator function exists: jbe@169: if (lua_isnil(L, json_convert_iterator_idx)) { jbe@171: // if there is no iterator function, jbe@171: // distinguish between objects and arrays: jbe@171: if (array == 0) { jbe@171: // for an object, copy all string key value pairs to shadow table: jbe@171: for (lua_pushnil(L); lua_next(L, json_convert_source_idx); lua_pop(L, 1)) { jbe@171: if (lua_type(L, -2) == LUA_TSTRING) { jbe@171: lua_pushvalue(L, -2); jbe@171: lua_pushvalue(L, -2); jbe@171: lua_rawset(L, json_convert_shadow_idx); jbe@171: } jbe@171: } jbe@171: } else { jbe@171: // for an array, copy consecutive integer value pairs to shadow table: jbe@171: while (1) { jbe@175: // throw error if array would exceed INT_MAX elements: jbe@171: // TODO: Lua 5.3 may support more elements jbe@171: if (arrayidx == INT_MAX) { jbe@171: lua_pushnumber(L, (size_t)INT_MAX+1); jbe@171: lua_rawget(L, json_convert_source_idx); jbe@171: if (lua_isnil(L, -1)) break; jbe@171: return luaL_error(L, "Array exceeded length of %d elements", INT_MAX); jbe@171: } jbe@175: // get next array entry: jbe@171: arrayidx++; jbe@171: lua_rawgeti(L, json_convert_source_idx, arrayidx); jbe@175: // break if value is nil: jbe@171: if (lua_isnil(L, -1)) break; jbe@175: // store value in shadow table: jbe@171: lua_rawseti(L, json_convert_shadow_idx, arrayidx); jbe@171: } jbe@169: } jbe@169: } else { jbe@172: // if there is an iterator function, jbe@172: // call iterator function with source value (first argument) jbe@172: // and store 3 result values on stack positions 5 through 7: jbe@172: lua_pushvalue(L, json_convert_iterator_idx); jbe@172: lua_pushvalue(L, 1); jbe@172: lua_call(L, 1, 3); jbe@175: // iterate through key value pairs and store some of them in shadow table jbe@174: // while replacing nil values with null-marker: jbe@172: while (1) { jbe@175: // call iterfun function: jbe@172: lua_pushvalue(L, json_convert_iterfun_idx); jbe@172: lua_pushvalue(L, json_convert_itertbl_idx); jbe@172: lua_pushvalue(L, -3); jbe@172: lua_remove(L, -4); jbe@172: lua_call(L, 2, 2); jbe@175: // break iteration loop if key is nil: jbe@172: if (lua_isnil(L, -2)) break; jbe@175: // store key value pair only if key type is correct: jbe@172: if (lua_type(L, -2) == (array ? LUA_TNUMBER : LUA_TSTRING)) { jbe@175: // if key type is correct, jbe@175: // push key onto stack: jbe@172: lua_pushvalue(L, -2); jbe@175: // if value is nil, push null-marker onto stack (as value): jbe@174: if (lua_isnil(L, -2)) json_pushnullmark(L); jbe@175: // else push value onto stack: jbe@174: else lua_pushvalue(L, -2); jbe@175: // set key value pair in shadow table: jbe@172: lua_rawset(L, json_convert_shadow_idx); jbe@172: } jbe@175: // pop value from stack, but leave key on stack: jbe@172: lua_pop(L, 1); jbe@172: } jbe@143: } jbe@171: // let result table be on top of stack: jbe@169: lua_settop(L, json_convert_output_idx); jbe@136: } jbe@171: // set metatable (for result table on top of stack): jbe@171: if (array == 0) json_regfetch(L, objectmt); jbe@171: else json_regfetch(L, arraymt); jbe@169: lua_setmetatable(L, -2); jbe@171: // return table on top of stack: jbe@136: return 1; jbe@136: } jbe@136: jbe@175: // converts a Lua table (or any other iterable value) to a JSON object: jbe@175: // (does never modify the argument, returns an empty object or array if argument is nil) jbe@136: static int json_object(lua_State *L) { jbe@169: return json_convert(L, 0); jbe@136: } jbe@136: jbe@175: // converts a Lua table (or any other iterable value) to a JSON array: jbe@175: // (does never modify the argument, returns an empty object or array if argument is nil) jbe@136: static int json_array(lua_State *L) { jbe@169: return json_convert(L, 1); 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@193: #define json_import_stackswap_idx 4 jbe@138: jbe@168: // macros for hex decoding: jbe@168: #define json_utf16_surrogate(x) ((x) >= 0xD800 && (x) <= 0xDFFF) jbe@168: #define json_utf16_lead(x) ((x) >= 0xD800 && (x) <= 0xDBFF) jbe@168: #define json_utf16_tail(x) ((x) >= 0xDC00 && (x) <= 0xDFFF) jbe@167: #define json_import_readhex(x) \ jbe@167: do { \ jbe@167: x = 0; \ jbe@167: for (i=0; i<4; i++) { \ jbe@167: x <<= 4; \ jbe@167: c = str[pos++]; \ jbe@167: if (c >= '0' && c <= '9') x += c - '0'; \ jbe@167: else if (c >= 'A' && c <= 'F') x += c - 'A' + 10; \ jbe@167: else if (c >= 'a' && c <= 'f') x += c - 'a' + 10; \ jbe@167: else if (c == 0) goto json_import_unexpected_eof; \ jbe@167: else goto json_import_unexpected_escape; \ jbe@167: } \ jbe@167: } while (0) jbe@167: jbe@136: // decodes a JSON document: jbe@121: static int json_import(lua_State *L) { jbe@181: int stackswapidx = 0; // elements in stack swap table jbe@181: int i; // loop variable jbe@181: const char *str; // string to parse jbe@181: size_t total; // total length of string to parse jbe@181: size_t pos = 0; // current position in string to parse jbe@181: 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@181: unsigned char c; // variable to store a single character to be processed (unsigned!) jbe@181: luaL_Buffer luabuf; // Lua buffer to decode JSON string values jbe@181: char *cbuf; // C buffer to decode JSON string values jbe@181: size_t outlen; // maximum length or write position of C buffer jbe@181: long codepoint; // decoded UTF-16 character or higher codepoint jbe@181: long utf16tail; // second decoded UTF-16 character (surrogate tail) jbe@181: size_t arraylen; // variable to temporarily store the array length jbe@166: // require string as argument and convert to C string with length information: jbe@166: str = luaL_checklstring(L, 1, &total); jbe@166: // if string contains a NULL byte, this is a syntax error jbe@166: if (strlen(str) != total) goto json_import_syntax_error; 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@181: // push table for stack swapping onto stack position 5: jbe@181: // (needed to avoid Lua stack overflows) jbe@181: lua_newtable(L); 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@170: // NOTE: variable c needs to be unsigned in the following code 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@167: if (level == 0) { jbe@167: lua_pushnil(L); jbe@167: lua_pushliteral(L, "Empty string"); jbe@167: } else { jbe@167: json_import_unexpected_eof: jbe@167: lua_pushnil(L); jbe@167: lua_pushliteral(L, "Unexpected end of JSON document"); jbe@167: } jbe@121: return 2; jbe@181: // new JSON object or JSON array: jbe@121: case '{': jbe@181: case '[': jbe@181: // if an encountered JSON object is not expected here, then return an error: jbe@146: if ( jbe@181: c == '{' && jbe@181: mode != JSON_STATE_VALUE && jbe@181: mode != JSON_STATE_OBJECT_VALUE && jbe@181: mode != JSON_STATE_ARRAY_VALUE jbe@181: ) goto json_import_syntax_error; jbe@181: // if an encountered JSON array is not expected here, then return an error: jbe@181: if ( jbe@181: c == '[' && 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@181: // consume input character: jbe@181: pos++; jbe@181: // limit nested levels: jbe@181: if (level >= JSON_MAXDEPTH) { jbe@181: lua_pushnil(L); jbe@181: lua_pushfstring(L, "More than %d nested JSON levels", JSON_MAXDEPTH); jbe@181: return 2; jbe@181: } jbe@181: // swap Lua stack entries for previous level to swap table: jbe@181: // (avoids depth limitations due to Lua stack size) jbe@181: if (level) { jbe@181: lua_rawseti(L, json_import_stackswap_idx, ++stackswapidx); jbe@181: lua_rawseti(L, json_import_stackswap_idx, ++stackswapidx); jbe@181: lua_rawseti(L, json_import_stackswap_idx, ++stackswapidx); jbe@181: } jbe@181: // increment level: jbe@181: level++; jbe@181: // create JSON object or JSON array on stack: jbe@136: lua_newtable(L); jbe@181: // set metatable of JSON object or JSON array: jbe@181: lua_pushvalue(L, c == '{' ? json_import_objectmt_idx : 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@193: lua_pushvalue(L, -1); jbe@193: json_setshadow(L, -3); jbe@181: // distinguish between JSON objects and JSON arrays: jbe@181: if (c == '{') { jbe@181: // if JSON object, jbe@181: // expect object key (or end of object) to follow: jbe@181: mode = JSON_STATE_OBJECT_KEY; jbe@181: } else { jbe@181: // if JSON array, jbe@181: // expect array value (or end of array) to follow: jbe@181: mode = JSON_STATE_ARRAY_VALUE; jbe@181: // add nil as key (needed to keep stack balance) and as magic to detect arrays: jbe@181: if (c == '[') lua_pushnil(L); jbe@142: } 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@181: // restore previous stack elements from stack swap: jbe@181: lua_rawgeti(L, json_import_stackswap_idx, stackswapidx--); jbe@181: lua_insert(L, -2); jbe@181: lua_rawgeti(L, json_import_stackswap_idx, stackswapidx--); jbe@181: lua_insert(L, -2); jbe@181: lua_rawgeti(L, json_import_stackswap_idx, stackswapidx--); jbe@181: lua_insert(L, -2); 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@162: // find last character in input string: jbe@162: outlen = pos; jbe@162: while ((c = str[outlen]) != '"') { jbe@161: // consume one character: jbe@162: outlen++; jbe@161: // handle unexpected end of JSON document: jbe@161: if (c == 0) goto json_import_unexpected_eof; jbe@161: // consume one extra character when encountering an escaped quote: jbe@162: else if (c == '\\' && str[outlen] == '"') outlen++; jbe@161: } jbe@162: // determine buffer length: jbe@162: outlen -= pos; jbe@161: // check if string is non empty: jbe@162: if (outlen) { jbe@161: // prepare buffer to decode string (with maximum possible length) and set write position to zero: jbe@162: cbuf = luaL_buffinitsize(L, &luabuf, outlen); jbe@162: outlen = 0; jbe@161: // loop through the characters until encountering end quote: jbe@161: while ((c = str[pos++]) != '"') { jbe@162: // NOTE: unexpected end cannot happen anymore jbe@162: if (c < 32 || c == 127) { jbe@161: // do not allow ASCII control characters: jbe@161: // NOTE: illegal UTF-8 sequences and extended control characters are not sanitized jbe@161: // by this parser to allow different encodings than Unicode jbe@161: lua_pushnil(L); jbe@161: lua_pushliteral(L, "Unexpected control character in JSON string"); jbe@161: return 2; jbe@161: } else if (c == '\\') { jbe@161: // read next char after backslash escape: jbe@161: c = str[pos++]; jbe@161: switch (c) { jbe@161: // unexpected end-of-string: jbe@161: case 0: jbe@161: goto json_import_unexpected_eof; jbe@161: // unescaping of quotation mark, slash, and backslash: jbe@161: case '"': jbe@161: case '/': jbe@161: case '\\': jbe@162: cbuf[outlen++] = c; jbe@161: break; jbe@161: // unescaping of backspace: jbe@162: case 'b': cbuf[outlen++] = '\b'; break; jbe@161: // unescaping of form-feed: jbe@162: case 'f': cbuf[outlen++] = '\f'; break; jbe@161: // unescaping of new-line: jbe@162: case 'n': cbuf[outlen++] = '\n'; break; jbe@161: // unescaping of carriage-return: jbe@162: case 'r': cbuf[outlen++] = '\r'; break; jbe@161: // unescaping of tabulator: jbe@162: case 't': cbuf[outlen++] = '\t'; break; jbe@161: // unescaping of UTF-16 characters jbe@161: case 'u': jbe@167: // decode 4 hex nibbles: jbe@167: json_import_readhex(codepoint); jbe@167: // handle surrogate character: jbe@167: if (json_utf16_surrogate(codepoint)) { jbe@167: // check if first surrogate is in valid range: jbe@167: if (json_utf16_lead(codepoint)) { jbe@167: // require second surrogate: jbe@167: if ((c = str[pos++]) != '\\' || (c = str[pos++]) != 'u') { jbe@167: if (c == 0) goto json_import_unexpected_eof; jbe@167: else goto json_import_wrong_surrogate; jbe@167: } jbe@167: // read 4 hex nibbles of second surrogate character: jbe@167: json_import_readhex(utf16tail); jbe@167: // check if second surrogate is in valid range: jbe@167: if (!json_utf16_tail(utf16tail)) goto json_import_wrong_surrogate; jbe@167: // calculate codepoint: jbe@167: codepoint = 0x10000 + (utf16tail - 0xDC00) + (codepoint - 0xD800) * 0x400; jbe@167: } else { jbe@167: // throw error for wrong surrogates: jbe@167: json_import_wrong_surrogate: jbe@167: lua_pushnil(L); jbe@167: lua_pushliteral(L, "Illegal UTF-16 surrogate in JSON string escape sequence"); jbe@167: return 2; jbe@167: } jbe@167: } jbe@167: // encode as UTF-8: jbe@167: if (codepoint < 0x80) { jbe@167: cbuf[outlen++] = (char)codepoint; jbe@167: } else if (codepoint < 0x800) { jbe@167: cbuf[outlen++] = (char)(0xc0 | (codepoint >> 6)); jbe@167: cbuf[outlen++] = (char)(0x80 | (codepoint & 0x3f)); jbe@167: } else if (codepoint < 0x10000) { jbe@167: cbuf[outlen++] = (char)(0xe0 | (codepoint >> 12)); jbe@167: cbuf[outlen++] = (char)(0x80 | ((codepoint >> 6) & 0x3f)); jbe@167: cbuf[outlen++] = (char)(0x80 | (codepoint & 0x3f)); jbe@167: } else { jbe@167: cbuf[outlen++] = (char)(0xf0 | (codepoint >> 18)); jbe@167: cbuf[outlen++] = (char)(0x80 | ((codepoint >> 12) & 0x3f)); jbe@167: cbuf[outlen++] = (char)(0x80 | ((codepoint >> 6) & 0x3f)); jbe@167: cbuf[outlen++] = (char)(0x80 | (codepoint & 0x3f)); jbe@167: } jbe@167: break; jbe@161: // unexpected escape sequence: jbe@161: default: jbe@167: json_import_unexpected_escape: jbe@161: lua_pushnil(L); jbe@161: lua_pushliteral(L, "Unexpected string escape sequence in JSON document"); jbe@161: return 2; jbe@161: } jbe@161: } else { jbe@161: // normal character: jbe@162: cbuf[outlen++] = c; jbe@121: } jbe@121: } jbe@161: // process buffer to Lua string: jbe@162: luaL_pushresultsize(&luabuf, outlen); jbe@161: } else { jbe@161: // if JSON string is empty, jbe@161: // push empty Lua string: jbe@161: lua_pushliteral(L, ""); jbe@167: // consume closing quote: jbe@167: pos++; jbe@121: } 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@167: double numval; jbe@122: char *endptr; 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@194: // push special null-marker onto stack: jbe@194: json_pushnullmark(L); 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: // gets a value or its type from a JSON document (passed as first argument) jbe@175: // using a path (passed as variable number of keys after the first argument): jbe@137: static int json_path(lua_State *L, int type_mode) { jbe@193: int stacktop; // number of arguments jbe@193: int idx = 2; // stack index of current argument to process jbe@173: // require at least one argument: jbe@173: luaL_checkany(L, 1); jbe@193: // store stack index of top of stack (number of arguments): jbe@138: stacktop = lua_gettop(L); jbe@146: // use first argument as "current value" (stored on top of stack): jbe@193: lua_pushvalue(L, 1); 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@193: json_getshadow(L, -1); jbe@126: if (lua_isnil(L, -1)) { jbe@137: // if no shadow table is found, jbe@193: if (lua_type(L, -2) == LUA_TTABLE) { jbe@146: // and if "current value" is a table, jbe@193: // pop 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@175: // using a path (passed as variable number of keys after the 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@175: // using a path (passed as variable number of keys after first the argument): jbe@130: static int json_type(lua_State *L) { jbe@137: return json_path(L, 1); jbe@130: } jbe@130: jbe@173: // special Lua stack indicies for json_set function: jbe@193: #define json_set_objectmt_idx 1 jbe@193: #define json_set_arraymt_idx 2 jbe@173: jbe@173: // stack offset of arguments to json_set function: jbe@193: #define json_set_idxshift 2 jbe@173: jbe@173: // sets a value (passed as second argument) in a JSON document (passed as first argument) jbe@175: // using a path (passed as variable number of keys starting at third argument): jbe@173: static int json_set(lua_State *L) { jbe@193: int stacktop; // stack index of top of stack (after shifting) jbe@193: int idx; // stack index of current argument to process jbe@199: // require at least three arguments: jbe@173: luaL_checkany(L, 1); jbe@173: luaL_checkany(L, 2); jbe@199: luaL_checkany(L, 3); jbe@193: // insert objectmt into stack at position 1 (shifting the arguments): jbe@173: json_regfetch(L, objectmt); jbe@193: lua_insert(L, 1); jbe@193: // insert arraymt into stack at position 2 (shifting the arguments): jbe@193: json_regfetch(L, arraymt); jbe@173: lua_insert(L, 2); jbe@173: // store stack index of top of stack: jbe@173: stacktop = lua_gettop(L); jbe@173: // use nil as initial "parent value": jbe@173: lua_pushnil(L); jbe@173: // use first argument as "current value": jbe@173: lua_pushvalue(L, 1 + json_set_idxshift); jbe@173: // set all necessary values in path: jbe@173: for (idx = 3 + json_set_idxshift; idx<=stacktop; idx++) { jbe@173: // push metatable of "current value" onto stack: jbe@173: if (!lua_getmetatable(L, -1)) lua_pushnil(L); jbe@173: // distinguish according to type of path key: jbe@173: switch (lua_type(L, idx)) { jbe@173: case LUA_TSTRING: jbe@173: // if path key is a string, jbe@173: // check if "current value" is a JSON object (or table without metatable): jbe@173: if ( jbe@173: lua_rawequal(L, -1, json_set_objectmt_idx) || jbe@173: (lua_isnil(L, -1) && lua_type(L, -2) == LUA_TTABLE) jbe@173: ) { jbe@173: // if "current value" is acceptable, jbe@173: // pop metatable and leave "current value" on top of stack: jbe@173: lua_pop(L, 1); jbe@173: } else { jbe@173: // if "current value" is not acceptable: jbe@173: // pop metatable and "current value": jbe@173: lua_pop(L, 2); jbe@173: // throw error if parent element does not exist: jbe@173: if (lua_isnil(L, -1)) return luaL_error(L, "Root element is not a JSON object"); jbe@173: // push new JSON object as "current value" onto stack: jbe@193: json_createproxy(L); jbe@173: // create and register shadow table: jbe@173: lua_newtable(L); jbe@193: json_setshadow(L, -2); jbe@173: // set metatable of JSON object: jbe@173: lua_pushvalue(L, json_set_objectmt_idx); jbe@173: lua_setmetatable(L, -2); jbe@173: // set entry in "parent value": jbe@173: lua_pushvalue(L, idx-1); jbe@173: lua_pushvalue(L, -2); jbe@173: lua_settable(L, -4); jbe@173: } jbe@173: break; jbe@173: case LUA_TNUMBER: jbe@173: // if path key is a number, jbe@173: // check if "current value" is a JSON array (or table without metatable): jbe@173: if ( jbe@173: lua_rawequal(L, -1, json_set_arraymt_idx) || jbe@173: (lua_isnil(L, -1) && lua_type(L, -2) == LUA_TTABLE) jbe@173: ) { jbe@173: // if "current value" is acceptable, jbe@173: // pop metatable and leave "current value" on top of stack: jbe@173: lua_pop(L, 1); jbe@173: } else { jbe@173: // if "current value" is not acceptable: jbe@173: // pop metatable and "current value": jbe@173: lua_pop(L, 2); jbe@173: // throw error if parent element does not exist: jbe@173: if (lua_isnil(L, -1)) return luaL_error(L, "Root element is not a JSON array"); jbe@173: // push new JSON array as "current value" onto stack: jbe@193: json_createproxy(L); jbe@173: // create and register shadow table: jbe@173: lua_newtable(L); jbe@193: json_setshadow(L, -2); jbe@173: // set metatable of JSON array: jbe@173: lua_pushvalue(L, json_set_arraymt_idx); jbe@173: lua_setmetatable(L, -2); jbe@173: // set entry in "parent value": jbe@173: lua_pushvalue(L, idx-1); jbe@173: lua_pushvalue(L, -2); jbe@173: lua_settable(L, -4); jbe@173: } jbe@173: break; jbe@173: default: jbe@173: return luaL_error(L, "Invalid path key of type %s", lua_typename(L, lua_type(L, idx))); jbe@173: } jbe@173: // check if last path element is being processed: jbe@173: if (idx == stacktop) { jbe@173: // if the last path element is being processed, jbe@173: // set last path value in "current value" container: jbe@173: lua_pushvalue(L, idx); jbe@173: lua_pushvalue(L, 2 + json_set_idxshift); jbe@173: lua_settable(L, -3); jbe@173: } else { jbe@173: // if the processed path element is not the last, jbe@173: // use old "current value" as new "parent value" jbe@173: lua_remove(L, -2); jbe@173: // push new "current value" onto stack by performing a lookup: jbe@173: lua_pushvalue(L, idx); jbe@173: lua_gettable(L, -2); jbe@173: } jbe@173: } jbe@173: // return first argument for convenience: jbe@173: lua_settop(L, 1 + json_set_idxshift); jbe@173: return 1; jbe@173: } jbe@173: 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@193: // push shadow table or nil onto stack: jbe@193: json_getshadow(L, 1); jbe@193: // pop nil from stack if no shadow table has been found: jbe@193: if (lua_isnil(L, -1)) lua_pop(L, 1); jbe@193: // return length of argument or shadow table: jbe@193: lua_pushnumber(L, lua_rawlen(L, -1)); jbe@123: return 1; jbe@123: } jbe@123: jbe@175: // __index metamethod for JSON objects and JSON arrays: jbe@130: static int json_index(lua_State *L) { jbe@148: // stack shall contain two function arguments: jbe@130: lua_settop(L, 2); jbe@193: // replace first argument with its shadow table jbe@193: // or throw error if no shadow table is found: jbe@193: json_getshadow(L, 1); jbe@139: if (lua_isnil(L, -1)) return luaL_error(L, "Shadow table not found"); jbe@193: lua_replace(L, 1); jbe@148: // use key passed as second argument to lookup value in shadow table: jbe@193: lua_rawget(L, 1); jbe@148: // if value is null-marker, then push nil onto stack: jbe@193: if (json_isnullmark(L, 2)) lua_pushnil(L); jbe@148: // return either looked up value, or nil jbe@127: return 1; jbe@127: } jbe@127: jbe@175: // __newindex metamethod for JSON objects and JSON arrays: jbe@130: static int json_newindex(lua_State *L) { jbe@148: // stack shall contain three function arguments: jbe@130: lua_settop(L, 3); jbe@193: // replace first argument with its shadow table jbe@193: // or throw error if no shadow table is found: jbe@193: json_getshadow(L, 1); jbe@130: if (lua_isnil(L, -1)) return luaL_error(L, "Shadow table not found"); jbe@130: lua_replace(L, 1); jbe@193: // second and third argument to write to shadow table: jbe@130: lua_rawset(L, 1); jbe@148: // return nothing: jbe@148: return 0; jbe@121: } jbe@121: jbe@175: // function returned as first value by json_pairs function: 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@200: // get next key value pair from shadow table (argument 1) using previous key (argument 2) jbe@149: // and return nothing if there is no next pair: jbe@193: if (!lua_next(L, 1)) 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@175: // iterates through all key value pairs (including JSON null values represented as Lua nil): jbe@135: static int json_pairs(lua_State *L) { jbe@172: // require one argument to function jbe@172: luaL_checkany(L, 1); jbe@200: // return triple of function json_pairs_iterfunc, shadow table of first argument, and nil: jbe@139: lua_pushcfunction(L, json_pairs_iterfunc); jbe@200: json_getshadow(L, 1); jbe@200: if (lua_isnil(L, -1)) return luaL_error(L, "Shadow table not found"); jbe@135: lua_pushnil(L); jbe@135: return 3; jbe@135: } jbe@135: jbe@175: // function returned as first value by json_ipairs function: 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@200: // do integer lookup in shadow table and store result on stack position 3: jbe@200: lua_rawgeti(L, 1, idx); jbe@149: // return nothing if there was no value: jbe@200: if (lua_isnil(L, 3)) 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@200: if (json_isnullmark(L, 3)) lua_pushnil(L); jbe@200: else lua_pushvalue(L, 3); jbe@134: return 2; jbe@134: } jbe@134: jbe@149: // returns a triple such that 'for idx, value in ipairs(ary) do ... end' jbe@175: // iterates through all values (including JSON null values represented as Lua nil): jbe@134: static int json_ipairs(lua_State *L) { jbe@172: // require one argument to function jbe@172: luaL_checkany(L, 1); jbe@200: // return triple of function json_ipairs_iterfunc, shadow table of first argument, and zero: jbe@139: lua_pushcfunction(L, json_ipairs_iterfunc); jbe@200: json_getshadow(L, 1); jbe@200: if (lua_isnil(L, -1)) return luaL_error(L, "Shadow table not found"); jbe@134: lua_pushinteger(L, 0); jbe@134: return 3; jbe@134: } jbe@134: jbe@175: // datatype representing a table key: jbe@175: // (used for sorting) jbe@163: typedef struct { jbe@163: size_t length; jbe@163: const char *data; jbe@163: } json_key_t; jbe@163: jbe@175: // comparation function for table keys to be passed to qsort function: jbe@163: static int json_key_cmp(json_key_t *key1, json_key_t *key2) { jbe@163: size_t pos = 0; jbe@163: unsigned char c1, c2; jbe@163: while (1) { jbe@163: if (key1->length > pos) { jbe@163: if (key2->length > pos) { jbe@163: c1 = key1->data[pos]; jbe@163: c2 = key2->data[pos]; jbe@163: if (c1 < c2) return -1; jbe@163: else if (c1 > c2) return 1; jbe@163: } else { jbe@163: return 1; jbe@163: } jbe@163: } else { jbe@163: if (key2->length > pos) { jbe@163: return -1; jbe@163: } else { jbe@163: return 0; jbe@163: } jbe@163: } jbe@163: pos++; jbe@163: } jbe@163: } jbe@163: jbe@175: // constants for type detection of ambiguous tables: jbe@154: #define JSON_TABLETYPE_UNKNOWN 0 jbe@154: #define JSON_TABLETYPE_OBJECT 1 jbe@154: #define JSON_TABLETYPE_ARRAY 2 jbe@154: jbe@183: typedef struct { jbe@183: int type; jbe@183: int pos; jbe@183: int count; jbe@183: json_key_t keys[1]; // or more jbe@183: } json_container_t; jbe@183: jbe@183: // special Lua stack indicies for json_export function: jbe@183: #define json_export_value_idx 1 jbe@183: #define json_export_indentstring_idx 2 jbe@183: #define json_export_objectmt_idx 3 jbe@183: #define json_export_arraymt_idx 4 jbe@193: #define json_export_stackswap_idx 5 jbe@193: #define json_export_luacontainer_idx 6 jbe@193: #define json_export_ccontainer_idx 7 jbe@193: #define json_export_buffer_idx 8 jbe@164: jbe@183: // encodes a JSON document (passed as first argument) jbe@187: // optionally using indentation (indentation string or true passed as second argument) jbe@183: static int json_export(lua_State *L) { jbe@183: int pretty; // pretty printing on? (i.e. printing with indentation) jbe@183: luaL_Buffer buf; // Lua buffer containing result string jbe@183: lua_Number num; // number to encode jbe@190: char numstr[21]; // encoded number (sign, zero, point, 17 significant digits, and terminating NULL byte) jbe@183: const char *str; // string to encode jbe@183: size_t strlen; // length of string to encode jbe@183: size_t strpos ; // position in string or position of current key jbe@183: unsigned char c; // character to encode (unsigned!) jbe@183: char hexcode[7]; // store for unicode hex escape sequence jbe@183: // NOTE: 7 bytes due to backslash, character 'u', 4 hex digits, and terminating NULL byte jbe@183: int tabletype; // table type: unknown, JSON object, or JSON array jbe@183: size_t keycount = 0; // number of string keys in object jbe@185: json_key_t *key; // pointer to C structure containing a string key jbe@183: int level = 0; // current depth level jbe@183: int i; // iteration variable for level dependent repetitions jbe@183: int stackswapidx = 0; // elements in stack swap table jbe@183: int containerkey = 0; // temporarily set to 1, if a container key is being encoded jbe@183: json_container_t *container = NULL; // pointer to current C struct for container information jbe@183: // stack shall contain two function arguments: jbe@183: lua_settop(L, 2); jbe@188: // check if pretty printing (with indentation) is desired: jbe@188: if (lua_toboolean(L, json_export_indentstring_idx)) { jbe@188: // if yes, jbe@188: // set pretty variable to 1: jbe@188: pretty = 1; jbe@188: // check if second argument is a boolean (true): jbe@188: if (lua_isboolean(L, json_export_indentstring_idx)) { jbe@188: // if yes, jbe@188: // use default indentation if indentation argument is boolean true: jbe@188: lua_pushliteral(L, " "); jbe@188: lua_replace(L, json_export_indentstring_idx); jbe@188: } else { jbe@188: // if no, jbe@188: // require second argument to be a string: jbe@188: luaL_checktype(L, json_export_indentstring_idx, LUA_TSTRING); jbe@188: } jbe@188: } else { jbe@188: // if no, jbe@188: // set pretty variable to 0: jbe@188: pretty = 0; jbe@157: } jbe@183: // push objectmt onto stack position 3: jbe@183: json_regfetch(L, objectmt); jbe@183: // push arraymt onto stack position 4: jbe@183: json_regfetch(L, arraymt); jbe@193: // push table for stack swapping onto stack position 5: jbe@183: lua_newtable(L); jbe@193: // create placeholders on stack positions 6 through 7: jbe@187: lua_settop(L, json_export_buffer_idx); jbe@183: // create Lua string buffer: jbe@183: luaL_buffinit(L, &buf); jbe@183: // loop: jbe@183: while (1) { jbe@183: // if value to encode is the null-marker, then treat it the same as nil: jbe@183: if (json_isnullmark(L, json_export_value_idx)) { jbe@183: lua_pushnil(L); jbe@183: lua_replace(L, json_export_value_idx); jbe@164: } jbe@183: // distinguish between different Lua types: jbe@183: switch (lua_type(L, json_export_value_idx)) { jbe@183: // value to encode is nil: jbe@183: case LUA_TNIL: jbe@183: // add string "null" to output buffer: jbe@183: luaL_addstring(&buf, "null"); jbe@183: break; jbe@183: // value to encode is of type number: jbe@183: case LUA_TNUMBER: jbe@183: // convert value to double precision number: jbe@183: num = lua_tonumber(L, json_export_value_idx); jbe@183: // throw error if number is not-a-number: jbe@183: if (isnan(num)) return luaL_error(L, "JSON export not possible for NaN value"); jbe@183: // throw error if number is positive or negative infinity: jbe@183: if (isinf(num)) return luaL_error(L, "JSON export not possible for infinite numbers"); jbe@191: // determine necessary precision to represent double precision floating point number: jbe@192: sprintf(numstr, "%.16g", num); jbe@191: if (strtod(numstr, NULL) != num) sprintf(numstr, "%.17g", num); jbe@191: // add string encoding of the number to the output buffer: jbe@189: luaL_addstring(&buf, numstr); jbe@183: break; jbe@183: // value to encode is of type boolean: jbe@183: case LUA_TBOOLEAN: jbe@183: // add string "true" or "false" according to boolean value: jbe@183: luaL_addstring(&buf, lua_toboolean(L, json_export_value_idx) ? "true" : "false"); jbe@183: break; jbe@183: // value to encode is of type string: jbe@183: case LUA_TSTRING: jbe@183: // add quoted and escaped string to output buffer: jbe@183: str = lua_tolstring(L, json_export_value_idx, &strlen); jbe@183: luaL_addchar(&buf, '"'); jbe@183: strpos = 0; jbe@183: while (strpos < strlen) { jbe@183: c = str[strpos++]; jbe@183: if (c == '"') luaL_addstring(&buf, "\\\""); jbe@183: else if (c == '\\') luaL_addstring(&buf, "\\\\"); jbe@183: else if (c == 127) luaL_addstring(&buf, "\\u007F"); jbe@183: else if (c >= 32) luaL_addchar(&buf, c); jbe@183: else if (c == '\b') luaL_addstring(&buf, "\\b"); jbe@183: else if (c == '\f') luaL_addstring(&buf, "\\f"); jbe@183: else if (c == '\n') luaL_addstring(&buf, "\\n"); jbe@183: else if (c == '\r') luaL_addstring(&buf, "\\r"); jbe@183: else if (c == '\t') luaL_addstring(&buf, "\\t"); jbe@183: else if (c == '\v') luaL_addstring(&buf, "\\v"); jbe@183: else { jbe@183: sprintf(hexcode, "\\u%04X", c); jbe@183: luaL_addstring(&buf, hexcode); jbe@154: } jbe@154: } jbe@183: luaL_addchar(&buf, '"'); jbe@183: break; jbe@183: // value to encode is of type table (this includes JSON objects and JSON arrays): jbe@183: case LUA_TTABLE: jbe@183: // use table's metatable to try to determine type of table: jbe@183: tabletype = JSON_TABLETYPE_UNKNOWN; jbe@188: if (lua_getmetatable(L, json_export_value_idx)) { jbe@183: if (lua_rawequal(L, -1, json_export_objectmt_idx)) { jbe@183: tabletype = JSON_TABLETYPE_OBJECT; jbe@183: } else { jbe@183: if (lua_rawequal(L, -1, json_export_arraymt_idx)) { jbe@183: tabletype = JSON_TABLETYPE_ARRAY; jbe@183: } else { jbe@183: return luaL_error(L, "JSON export not possible for tables with nonsupported metatable"); jbe@183: } jbe@183: } jbe@183: // reset stack (pop metatable from stack): jbe@183: lua_pop(L, 1); jbe@183: } jbe@193: // replace table with its shadow table if existent: jbe@193: json_getshadow(L, json_export_value_idx); jbe@183: if (lua_isnil(L, -1)) lua_pop(L, 1); jbe@193: else lua_replace(L, json_export_value_idx); jbe@187: // check if type of table is still undetermined jbe@187: // and optionally calculate number of string keys (keycount) jbe@187: // or set keycount to zero: jbe@186: keycount = 0; jbe@183: if (tabletype == JSON_TABLETYPE_UNKNOWN) { jbe@187: // if type of table is undetermined, jbe@187: // iterate over all keys: jbe@188: for (lua_pushnil(L); lua_next(L, json_export_value_idx); lua_pop(L, 1)) { jbe@183: switch (lua_type(L, -2)) { jbe@183: case LUA_TSTRING: jbe@183: // for string keys, jbe@183: // increase keycount (may avoid another iteration): jbe@183: keycount++; jbe@183: // if type of table was unknown, then type of table is a JSON object now: jbe@183: if (tabletype == JSON_TABLETYPE_UNKNOWN) tabletype = JSON_TABLETYPE_OBJECT; jbe@183: // if type of table was a JSON array, then the type of table is ambiguous now jbe@183: // and an error is thrown: jbe@183: else if (tabletype == JSON_TABLETYPE_ARRAY) goto json_export_tabletype_error; jbe@183: break; jbe@183: case LUA_TNUMBER: jbe@183: // for numeric keys, jbe@183: // if type of table was unknown, then type of table is a JSON array now: jbe@183: if (tabletype == JSON_TABLETYPE_UNKNOWN) tabletype = JSON_TABLETYPE_ARRAY; jbe@183: // if type of table was a JSON object, then the type of table is ambiguous now jbe@183: // and an error is thrown: jbe@183: else if (tabletype == JSON_TABLETYPE_OBJECT) goto json_export_tabletype_error; jbe@183: break; jbe@183: } jbe@164: } jbe@163: } jbe@183: // raise error if too many nested levels: jbe@183: if (level >= JSON_MAXDEPTH) { jbe@183: return luaL_error(L, "More than %d nested JSON levels", JSON_MAXDEPTH); jbe@163: } jbe@183: // store previous container information (if existent) on stack swap jbe@183: // and increase level variable: jbe@183: if (level++) { jbe@183: lua_pushvalue(L, json_export_luacontainer_idx); jbe@183: lua_rawseti(L, json_export_stackswap_idx, ++stackswapidx); jbe@183: lua_pushvalue(L, json_export_ccontainer_idx); jbe@183: lua_rawseti(L, json_export_stackswap_idx, ++stackswapidx); jbe@183: } jbe@188: // use value as current container: jbe@188: lua_pushvalue(L, json_export_value_idx); jbe@188: lua_replace(L, json_export_luacontainer_idx); jbe@183: // distinguish between JSON objects and JSON arrays: jbe@183: switch (tabletype) { jbe@183: // JSON object: jbe@183: case JSON_TABLETYPE_OBJECT: jbe@183: // calculate count of string keys unless it has been calculated before: jbe@183: if (!keycount) { jbe@183: for (lua_pushnil(L); lua_next(L, json_export_luacontainer_idx); lua_pop(L, 1)) { jbe@183: if (lua_type(L, -2) == LUA_TSTRING) keycount++; jbe@164: } jbe@164: } jbe@186: // allocate memory for C structure containing string keys and container iteration state: jbe@186: container = lua_newuserdata(L, sizeof(json_container_t) + (keycount-1) * sizeof(json_key_t)); jbe@187: // store reference to C structure on designated stack position: jbe@186: lua_replace(L, json_export_ccontainer_idx); jbe@186: // initialize C structure for container state: jbe@186: container->type = JSON_TABLETYPE_OBJECT; jbe@186: container->count = keycount; jbe@186: container->pos = 0; jbe@187: // check if object contains any keys: jbe@183: if (keycount) { jbe@187: // if yes, jbe@187: // copy all string keys to the C structure (and reset container->pos again): jbe@183: for (lua_pushnil(L); lua_next(L, json_export_luacontainer_idx); lua_pop(L, 1)) { jbe@183: if (lua_type(L, -2) == LUA_TSTRING) { jbe@183: json_key_t *key = &container->keys[container->pos++]; jbe@183: key->data = lua_tolstring(L, -2, &key->length); jbe@183: } jbe@183: } jbe@183: container->pos = 0; jbe@183: // sort C array using quicksort: jbe@183: qsort(container->keys, keycount, sizeof(json_key_t), (void *)json_key_cmp); jbe@154: } jbe@183: // add opening bracket to output buffer: jbe@183: luaL_addchar(&buf, '{'); jbe@183: break; jbe@183: // JSON array: jbe@183: case JSON_TABLETYPE_ARRAY: jbe@187: // allocate memory for C structure for container iteration state: jbe@183: container = lua_newuserdata(L, sizeof(json_container_t) - sizeof(json_key_t)); jbe@187: // store reference to C structure on designated stack position: jbe@183: lua_replace(L, json_export_ccontainer_idx); jbe@187: // initialize C structure for container state: jbe@183: container->type = JSON_TABLETYPE_ARRAY; jbe@183: container->pos = 0; jbe@183: // add opening bracket to output buffer: jbe@183: luaL_addchar(&buf, '['); jbe@183: break; jbe@183: default: jbe@183: // throw error if table type is unknown: jbe@183: json_export_tabletype_error: jbe@183: return luaL_error(L, "JSON export not possible for ambiguous table (cannot decide whether it is an object or array)"); jbe@183: } jbe@183: break; jbe@183: default: jbe@183: // all other datatypes are considered an error: jbe@183: return luaL_error(L, "JSON export not possible for values of type \"%s\"", lua_typename(L, lua_type(L, json_export_value_idx))); jbe@183: } jbe@187: // check if a container is being processed: jbe@183: if (container) { jbe@187: // if yes, jbe@187: // execute code for container iteration: jbe@184: json_export_container: jbe@187: // distinguish between JSON objects and JSON arrays: jbe@183: switch (container->type) { jbe@187: // JSON object: jbe@183: case JSON_TABLETYPE_OBJECT: jbe@187: // finish iteration if all string keys have been processed: jbe@185: if (container->pos == container->count) goto json_export_close; jbe@187: // push current string key on top of stack: jbe@185: key = &container->keys[container->pos]; jbe@185: lua_pushlstring(L, key->data, key->length); jbe@187: // check if the key has already been exported: jbe@185: if (!containerkey) { jbe@187: // if no, jbe@187: // add a comma to the output buffer if necessary: jbe@185: if (container->pos) luaL_addchar(&buf, ','); jbe@187: // set containerkey variable to true: jbe@185: containerkey = 1; jbe@183: } else { jbe@187: // if a key has already been exported, jbe@187: // add a colon to the output buffer: jbe@185: luaL_addchar(&buf, ':'); jbe@187: // add a space to the output buffer for pretty results: jbe@185: if (pretty) luaL_addchar(&buf, ' '); jbe@187: // replace string key on top of stack with corresponding value: jbe@185: lua_rawget(L, json_export_luacontainer_idx); jbe@187: // reset containerkey variable jbe@185: containerkey = 0; jbe@187: // increase number of processed key value pairs: jbe@185: container->pos++; jbe@163: } jbe@187: // store key or value on top of stack in designated stack position: jbe@185: lua_replace(L, json_export_value_idx); jbe@183: break; jbe@187: // JSON array: jbe@183: case JSON_TABLETYPE_ARRAY: jbe@187: // store next value in designated stack position: jbe@185: lua_rawgeti(L, json_export_luacontainer_idx, container->pos+1); jbe@183: lua_replace(L, json_export_value_idx); jbe@187: // finish iteration if value is nil: jbe@185: if (lua_isnil(L, json_export_value_idx)) goto json_export_close; jbe@187: // add a comma to the output buffer if necessary: jbe@185: if (container->pos) luaL_addchar(&buf, ','); jbe@187: // increase number of processed values: jbe@185: container->pos++; jbe@183: break; jbe@187: // common code for closing JSON objects or JSON arrays: jbe@183: json_export_close: jbe@187: // decrement level variable: jbe@185: level--; jbe@187: // handle indentation for pretty results: jbe@187: if (pretty && container->pos) { jbe@187: luaL_addchar(&buf, '\n'); jbe@187: for (i=0; itype == JSON_TABLETYPE_OBJECT ? '}' : ']'); jbe@187: // finish export if last level has been closed: jbe@185: if (!level) goto json_export_finish; jbe@187: // otherwise, jbe@187: // recall previous container information from stack swap jbe@187: // and set C pointer to corresponding C struct: jbe@185: lua_rawgeti(L, json_export_stackswap_idx, stackswapidx--); jbe@185: lua_replace(L, json_export_ccontainer_idx); jbe@185: container = lua_touserdata(L, json_export_ccontainer_idx); jbe@185: lua_rawgeti(L, json_export_stackswap_idx, stackswapidx--); jbe@185: lua_replace(L, json_export_luacontainer_idx); jbe@187: // repeat code for container iteration: jbe@185: goto json_export_container; jbe@164: } jbe@184: // handle indentation for pretty results: jbe@185: if (pretty && (containerkey || container->type == JSON_TABLETYPE_ARRAY)) { jbe@185: luaL_addchar(&buf, '\n'); jbe@185: for (i=0; i