# HG changeset patch # User jbe # Date 1406922885 -7200 # Node ID 20e393d2e6e150d77315c4db23b93fa3b7c3145d # Parent 070edea2a92fa12eb108f499b9f4e596e3c8ddef Completed comments and minor code cleanup in JSON library; Added emergency garbage collection for memory allocation in json.export function diff -r 070edea2a92f -r 20e393d2e6e1 libraries/json/json.c --- a/libraries/json/json.c Fri Aug 01 20:29:30 2014 +0200 +++ b/libraries/json/json.c Fri Aug 01 21:54:45 2014 +0200 @@ -48,7 +48,7 @@ #define json_convert_iterfun_idx 5 #define json_convert_itertbl_idx 6 -// converts a Lua table to a JSON object or JSON array: +// converts a Lua table (or any other iterable value) to a JSON object or JSON array: // (does never modify the argument, returns an empty object or array if argument is nil) static int json_convert(lua_State *L, int array) { int arrayidx = 0; @@ -100,6 +100,7 @@ } else { // for an array, copy consecutive integer value pairs to shadow table: while (1) { + // throw error if array would exceed INT_MAX elements: // TODO: Lua 5.3 may support more elements if (arrayidx == INT_MAX) { lua_pushnumber(L, (size_t)INT_MAX+1); @@ -107,9 +108,12 @@ if (lua_isnil(L, -1)) break; return luaL_error(L, "Array exceeded length of %d elements", INT_MAX); } + // get next array entry: arrayidx++; lua_rawgeti(L, json_convert_source_idx, arrayidx); + // break if value is nil: if (lua_isnil(L, -1)) break; + // store value in shadow table: lua_rawseti(L, json_convert_shadow_idx, arrayidx); } } @@ -120,21 +124,30 @@ lua_pushvalue(L, json_convert_iterator_idx); lua_pushvalue(L, 1); lua_call(L, 1, 3); - // iterate through key value pairs and store them in shadow table + // iterate through key value pairs and store some of them in shadow table // while replacing nil values with null-marker: while (1) { + // call iterfun function: lua_pushvalue(L, json_convert_iterfun_idx); lua_pushvalue(L, json_convert_itertbl_idx); lua_pushvalue(L, -3); lua_remove(L, -4); lua_call(L, 2, 2); + // break iteration loop if key is nil: if (lua_isnil(L, -2)) break; + // store key value pair only if key type is correct: if (lua_type(L, -2) == (array ? LUA_TNUMBER : LUA_TSTRING)) { + // if key type is correct, + // push key onto stack: lua_pushvalue(L, -2); + // if value is nil, push null-marker onto stack (as value): if (lua_isnil(L, -2)) json_pushnullmark(L); + // else push value onto stack: else lua_pushvalue(L, -2); + // set key value pair in shadow table: lua_rawset(L, json_convert_shadow_idx); } + // pop value from stack, but leave key on stack: lua_pop(L, 1); } } @@ -149,10 +162,14 @@ return 1; } +// converts a Lua table (or any other iterable value) to a JSON object: +// (does never modify the argument, returns an empty object or array if argument is nil) static int json_object(lua_State *L) { return json_convert(L, 0); } +// converts a Lua table (or any other iterable value) to a JSON array: +// (does never modify the argument, returns an empty object or array if argument is nil) static int json_array(lua_State *L) { return json_convert(L, 1); } @@ -595,7 +612,7 @@ #define json_path_idxshift 1 // gets a value or its type from a JSON document (passed as first argument) -// using a path (passed as variable number of keys after first argument): +// using a path (passed as variable number of keys after the first argument): static int json_path(lua_State *L, int type_mode) { int stacktop; // stack index of top of stack (after shifting) int idx = 2 + json_path_idxshift; // stack index of current argument to process @@ -680,13 +697,13 @@ } // gets a value from a JSON document (passed as first argument) -// using a path (passed as variable number of keys after first argument): +// using a path (passed as variable number of keys after the first argument): static int json_get(lua_State *L) { return json_path(L, 0); } // gets a value's type from a JSON document (passed as first argument) -// using a path (variable number of keys after first argument): +// using a path (passed as variable number of keys after first the argument): static int json_type(lua_State *L) { return json_path(L, 1); } @@ -700,7 +717,7 @@ #define json_set_idxshift 3 // sets a value (passed as second argument) in a JSON document (passed as first argument) -// using a path (variable number of keys starting at third argument): +// using a path (passed as variable number of keys starting at third argument): static int json_set(lua_State *L) { int stacktop; // stack index of top of stack (after shifting) int idx = 3; // stack index of current argument to process @@ -827,6 +844,7 @@ return 1; } +// __index metamethod for JSON objects and JSON arrays: static int json_index(lua_State *L) { // stack shall contain two function arguments: lua_settop(L, 2); @@ -845,6 +863,7 @@ return 1; } +// __newindex metamethod for JSON objects and JSON arrays: static int json_newindex(lua_State *L) { // stack shall contain three function arguments: lua_settop(L, 3); @@ -863,6 +882,7 @@ return 0; } +// function returned as first value by json_pairs function: static int json_pairs_iterfunc(lua_State *L) { // stack shall contain two function arguments: lua_settop(L, 2); @@ -886,7 +906,7 @@ } // returns a triple such that 'for key, value in pairs(obj) do ... end' -// iterates through all key value pairs (including JSON null keys represented as Lua nil): +// iterates through all key value pairs (including JSON null values represented as Lua nil): static int json_pairs(lua_State *L) { // require one argument to function luaL_checkany(L, 1); @@ -897,6 +917,7 @@ return 3; } +// function returned as first value by json_ipairs function: static int json_ipairs_iterfunc(lua_State *L) { lua_Integer idx; // stack shall contain two function arguments: @@ -923,7 +944,7 @@ } // returns a triple such that 'for idx, value in ipairs(ary) do ... end' -// iterates through all values (including JSON null represented as Lua nil): +// iterates through all values (including JSON null values represented as Lua nil): static int json_ipairs(lua_State *L) { // require one argument to function luaL_checkany(L, 1); @@ -934,11 +955,14 @@ return 3; } +// datatype representing a table key: +// (used for sorting) typedef struct { size_t length; const char *data; } json_key_t; +// comparation function for table keys to be passed to qsort function: static int json_key_cmp(json_key_t *key1, json_key_t *key2) { size_t pos = 0; unsigned char c1, c2; @@ -963,55 +987,74 @@ } } +// constants for type detection of ambiguous tables: #define JSON_TABLETYPE_UNKNOWN 0 #define JSON_TABLETYPE_OBJECT 1 #define JSON_TABLETYPE_ARRAY 2 +// special Lua stack indicies for json_export_internal function: #define json_export_internal_indentstring_idx 1 #define json_export_internal_level_idx 2 #define json_export_internal_value_idx 3 #define json_export_internal_tmp_idx 4 +// encodes a JSON document (passed as third argument) +// optionally using indentation (indentation string passed as first argument) +// for a certain depth level (passed as second argument): static int json_export_internal(lua_State *L) { - int level; - int pretty; - int i; - lua_Number num; - const char *str; - unsigned char c; - size_t strlen; - size_t pos = 0; - luaL_Buffer buf; - char hexcode[7]; // backslash, character 'u', 4 hex digits, and terminating NULL byte - int tabletype = JSON_TABLETYPE_UNKNOWN; - int anyelement = 0; - size_t keycount = 0; - size_t keypos = 0; - json_key_t *keybuf = NULL; - lua_Integer idx; + int level; // current depth level + int pretty; // pretty printing on? (i.e. printing with indentation) + int i; // iteration variable for level dependent repetitions: + lua_Number num; // number to encode + const char *str; // string to encode + size_t strlen; // length of string to encode + unsigned char c; // character to encode (unsigned!) + size_t pos = 0; // position in string or position of current key (initialized with zero!) + luaL_Buffer buf; // Lua buffer to create strings + char hexcode[7]; // store for unicode hex escape sequence + // NOTE: 7 bytes due to backslash, character 'u', 4 hex digits, and terminating NULL byte + int tabletype = JSON_TABLETYPE_UNKNOWN; // table type: unknown, JSON object, or JSON array + int anyelement = 0; // set to 1 if at least one array element has been processed + size_t keycount = 0; // number of string keys in object + json_key_t *keybuf = NULL; // temporary buffer to store (and sort) string keys of objects + lua_Integer arrayidx; // index to iterate through arrays + // stack shall contain three function arguments: lua_settop(L, json_export_internal_value_idx); + // if value to encode is the null-marker, then treat it the same as nil: if (json_isnullmark(L, json_export_internal_value_idx)) { lua_pop(L, 1); lua_pushnil(L); } + // distinguish between different Lua types: switch (lua_type(L, json_export_internal_value_idx)) { + // value to encode is nil: case LUA_TNIL: + // return string "null": lua_pushliteral(L, "null"); return 1; + // value to encode is of type number: case LUA_TNUMBER: + // convert value to double precision number: num = lua_tonumber(L, json_export_internal_value_idx); + // throw error if number is not-a-number: if (isnan(num)) return luaL_error(L, "JSON export not possible for NaN value"); + // throw error if number is positive or negative infinity: if (isinf(num)) return luaL_error(L, "JSON export not possible for infinite numbers"); + // return Lua's string encoding of the number: lua_tostring(L, json_export_internal_value_idx); return 1; + // value to encode is of type boolean: case LUA_TBOOLEAN: + // return string "true" or "false" according to boolean value: if (lua_toboolean(L, json_export_internal_value_idx)) { lua_pushliteral(L, "true"); } else { lua_pushliteral(L, "false"); } return 1; + // value to encode is of type string: case LUA_TSTRING: + // quote, escape and return string: str = lua_tolstring(L, 3, &strlen); luaL_buffinit(L, &buf); luaL_addchar(&buf, '"'); @@ -1035,7 +1078,9 @@ luaL_addchar(&buf, '"'); luaL_pushresult(&buf); return 1; + // value to encode is of type table (this includes JSON objects and JSON arrays): case LUA_TTABLE: + // use table's metatable to try to determine type of table: if (lua_getmetatable(L, json_export_internal_value_idx)) { json_regfetch(L, objectmt); if (lua_rawequal(L, -2, -1)) { @@ -1049,54 +1094,91 @@ } } } + // replace table with its shadow table if existent, and reset stack: json_regfetch(L, shadowtbl); lua_pushvalue(L, json_export_internal_value_idx); lua_rawget(L, -2); if (!lua_isnil(L, -1)) lua_replace(L, json_export_internal_value_idx); lua_settop(L, json_export_internal_value_idx); + // check if type of table is still undetermined: if (tabletype == JSON_TABLETYPE_UNKNOWN) { + // if yes, iterate over all keys: for (lua_pushnil(L); lua_next(L, json_export_internal_value_idx); lua_pop(L, 1)) { switch (lua_type(L, -2)) { case LUA_TSTRING: + // for string keys, + // increase keycount (may avoid another iteration): keycount++; + // if type of table was unknown, then type of table is a JSON object now: if (tabletype == JSON_TABLETYPE_UNKNOWN) tabletype = JSON_TABLETYPE_OBJECT; + // if type of table was a JSON array, then the type of table is ambiguous now + // and an error is thrown: else if (tabletype == JSON_TABLETYPE_ARRAY) goto json_export_tabletype_error; break; case LUA_TNUMBER: + // for numeric keys, + // if type of table was unknown, then type of table is a JSON array now: if (tabletype == JSON_TABLETYPE_UNKNOWN) tabletype = JSON_TABLETYPE_ARRAY; + // if type of table was a JSON object, then the type of table is ambiguous now + // and an error is thrown: else if (tabletype == JSON_TABLETYPE_OBJECT) goto json_export_tabletype_error; break; } } } + // set pretty variable to 1 if pretty printing (with indentation) is desired: pretty = lua_toboolean(L, json_export_internal_indentstring_idx); + // set level variable to corresponding function argument increased by one: level = lua_tointeger(L, json_export_internal_level_idx) + 1; + // throw error if there are more levels that could be imported by this library: if (level > JSON_MAXDEPTH) { return luaL_error(L, "More than %d nested JSON levels", JSON_MAXDEPTH); } + // distinguish between JSON objects and JSON arrays: switch (tabletype) { + // JSON object: case JSON_TABLETYPE_OBJECT: + // calculate count of string keys unless it has been calculated before: if (!keycount) { for (lua_pushnil(L); lua_next(L, json_export_internal_value_idx); lua_pop(L, 1)) { if (lua_type(L, -2) == LUA_TSTRING) keycount++; } } + // create a sorted list of all string keys in memory: if (keycount) { + // allocate memory for string keys: keybuf = calloc(keycount, sizeof(json_key_t)); - if (!keybuf) return luaL_error(L, "Memory allocation failed in JSON library"); + // check if memory allocation was successful: + if (!keybuf) { + // in case of memory exhaustion, try to collect garbage: + lua_gc(L, LUA_GCCOLLECT, 0); + // try to allocate memory again: + keybuf = calloc(keycount, sizeof(json_key_t)); + // throw error if memory allocation failed again: + if (!keybuf) { + return luaL_error(L, "Memory allocation failed in JSON library"); + } + } + // copy all string keys to the C array: for (lua_pushnil(L); lua_next(L, json_export_internal_value_idx); lua_pop(L, 1)) { if (lua_type(L, -2) == LUA_TSTRING) { - json_key_t *key = keybuf + (keypos++); + json_key_t *key = keybuf + (pos++); key->data = lua_tolstring(L, -2, &key->length); } } + // sort C array using quicksort: qsort(keybuf, keycount, sizeof(json_key_t), (void *)json_key_cmp); } + // create Lua string buffer: luaL_buffinit(L, &buf); + // add opening bracket to output buffer: luaL_addchar(&buf, '{'); - for (keypos=0; keyposdata, key->length); if (lua_pcall(L, 3, 1, 0)) { + // free memory of sorted string keys on error: if (keybuf) free(keybuf); + // rethrow error: return lua_error(L); } + // add encoded key to output buffer: luaL_addvalue(&buf); + // add colon to output buffer: luaL_addchar(&buf, ':'); + // handle indentation for pretty results: if (pretty) luaL_addchar(&buf, ' '); + // recursive call to encode value: lua_pushcfunction(L, json_export_internal); lua_pushvalue(L, json_export_internal_indentstring_idx); lua_pushinteger(L, level); lua_pushlstring(L, key->data, key->length); lua_rawget(L, json_export_internal_value_idx); if (lua_pcall(L, 3, 1, 0)) { + // free memory of sorted string keys on error: if (keybuf) free(keybuf); + // rethrow error: return lua_error(L); } + // add encoded value to output buffer: luaL_addvalue(&buf); } + // decrement level variable for all following statements: + level--; + // free memory of sorted string keys: if (keybuf) free(keybuf); + // handle indentation for pretty results: if (pretty && keycount != 0) { luaL_addchar(&buf, '\n'); - for (i=0; i