# HG changeset patch # User jbe # Date 1406583563 -7200 # Node ID 8a533f37003801359aaeb1161cfe486d31713fa5 # Parent f490b78827d6f53b476fa642e2af46bafebb6fa1 First part of refactoring JSON library (use LUA_REGISTRYINDEX with lightuserdata keys instead of C closures) diff -r f490b78827d6 -r 8a533f370038 libraries/json/json.c --- a/libraries/json/json.c Mon Jul 28 19:04:32 2014 +0200 +++ b/libraries/json/json.c Mon Jul 28 23:39:23 2014 +0200 @@ -11,9 +11,22 @@ #define JSON_UPVAL_PAIRS_ITERFUNC lua_upvalueindex(6) #define JSON_UPVAL_IPAIRS_ITERFUNC lua_upvalueindex(7) +#define JSON_REGENT static char +#define JSON_REGREF void * + +JSON_REGENT json_nullmark; +JSON_REGENT json_shadowtbl; +JSON_REGENT json_unknownmt; +JSON_REGENT json_objectmt; +JSON_REGENT json_arraymt; + +#define json_regfetch(L, x) (lua_pushlightuserdata((L), &(x)), lua_rawget((L), LUA_REGISTRYINDEX)) + +#define json_regstore(L, x) (lua_pushlightuserdata(L, &(x)), lua_pushvalue(L, -2), lua_rawset(L, LUA_REGISTRYINDEX)); + // marks a table as JSON object or JSON array: // (returns its modified argument or a new table if argument is nil) -static int json_mark(lua_State *L, const char *marker) { +static int json_mark(lua_State *L, JSON_REGREF mt) { // if argument is nil, then create new table: if (lua_isnoneornil(L, 1)) { lua_settop(L, 0); @@ -21,35 +34,36 @@ // skip testing of existing shadow table: goto json_object_create_shadow_table; } + // check if shadow table already exists: + json_regfetch(L, json_shadowtbl); lua_pushvalue(L, 1); - lua_rawget(L, JSON_UPVAL_SHADOWTBL); + lua_rawget(L, -2); if (lua_isnil(L, -1)) { json_object_create_shadow_table: // set shadow table: lua_pushvalue(L, 1); lua_newtable(L); - lua_rawset(L, JSON_UPVAL_SHADOWTBL); + lua_rawset(L, -4); } + // discard everything but table to return: + lua_settop(L, 1); // set metatable: - lua_pushvalue(L, JSON_UPVAL_METATABLE); + json_regfetch(L, mt); lua_setmetatable(L, 1); - // mark table as JSON object or as JSON array - lua_pushvalue(L, 1); - lua_pushstring(L, marker); - lua_rawset(L, JSON_UPVAL_TYPES); + // return table: return 1; } // marks a table as JSON object: // (returns its modified argument or a new table if argument is nil) static int json_object(lua_State *L) { - return json_mark(L, "object"); + return json_mark(L, &json_objectmt); } // marks a table as JSON array: // (returns its modified argument or a new table if argument is nil) static int json_array(lua_State *L) { - return json_mark(L, "array"); + return json_mark(L, &json_arraymt); } #define JSON_STATE_VALUE 0 @@ -61,6 +75,11 @@ #define JSON_STATE_ARRAY_SEPARATOR 6 #define JSON_STATE_END 7 +#define json_import_objectmt_idx 2 +#define json_import_arraymt_idx 3 +#define json_import_shadowtbl_idx 4 +#define json_import_nullmark_idx 5 + // decodes a JSON document: static int json_import(lua_State *L) { const char *str; // string to parse @@ -72,6 +91,16 @@ luaL_Buffer luabuf; // Lua buffer to decode (possibly escaped) strings char *cbuf; // C buffer to decode (possibly escaped) strings size_t writepos; // write position of decoded strings in C buffer + // limit stack to 1 element: + lua_settop(L, 1); + // push json_objectmt on stack position 2: + json_regfetch(L, json_objectmt); + // push json_arraymt on stack position 3: + json_regfetch(L, json_arraymt); + // push json_shadowtbl on stack position 4: + json_regfetch(L, json_shadowtbl); + // push json_nullmark on stack position 5: + json_regfetch(L, json_nullmark); // require string as first argument: str = luaL_checklstring(L, 1, &total); // if string contains a NULL byte, this is a syntax error @@ -102,18 +131,14 @@ // create JSON object on stack: lua_newtable(L); // set metatable of JSON object: - lua_pushvalue(L, JSON_UPVAL_METATABLE); + lua_pushvalue(L, json_import_objectmt_idx); lua_setmetatable(L, -2); - // mark JSON object as JSON object (as opposed to JSON array): - lua_pushvalue(L, -1); - lua_pushliteral(L, "object"); - lua_rawset(L, JSON_UPVAL_TYPES); // create internal shadow table on stack: lua_newtable(L); - // register internal shadow table: + // register internal shadow table (and cleanup stack afterwards): lua_pushvalue(L, -2); lua_pushvalue(L, -2); - lua_rawset(L, JSON_UPVAL_SHADOWTBL); + lua_rawset(L, json_import_shadowtbl_idx); // increment level: level++; // expect object key (or end of object) and continue with loop: @@ -129,18 +154,14 @@ // create JSON array on stack: lua_newtable(L); // set metatable of JSON array: - lua_pushvalue(L, JSON_UPVAL_METATABLE); + lua_pushvalue(L, json_import_arraymt_idx); lua_setmetatable(L, -2); - // mark JSON array as JSON array (as opposed to JSON object): - lua_pushvalue(L, -1); - lua_pushliteral(L, "array"); - lua_rawset(L, JSON_UPVAL_TYPES); // create internal shadow table on stack: lua_newtable(L); - // register internal shadow table: + // register internal shadow table (and cleanup stack afterwards): lua_pushvalue(L, -2); lua_pushvalue(L, -2); - lua_rawset(L, JSON_UPVAL_SHADOWTBL); + lua_rawset(L, json_import_shadowtbl_idx); // increment level: level++; // expect array value (or end of array) and continue with loop: @@ -169,15 +190,15 @@ if (--level) { // if nested, then check if outer(!) structure is an array or object: lua_pushvalue(L, c == '}' ? -4 : -3); - lua_rawget(L, JSON_UPVAL_TYPES); - if (lua_tostring(L, -1)[0] == 'a') { + lua_getmetatable(L, -1); + if (lua_touserdata(L, -1) == &json_arraymt) { // select array value processing: mode = JSON_STATE_ARRAY_VALUE; } else { // select object value processing: mode = JSON_STATE_OBJECT_VALUE; } - // pop JSON type from stack (from rawget JSON_UPVAL_TYPES): + // pop metatable from stack (that was needed for type distinction): lua_pop(L, 1); // store value in outer structure: goto json_import_process_value; @@ -306,7 +327,7 @@ // consume 4 input characters for "null": pos += 4; // put special null-marker on stack: - lua_pushvalue(L, JSON_UPVAL_NULLMARK); + lua_pushvalue(L, json_import_nullmark_idx); } else { // all other cases are a syntax error: goto json_import_syntax_error; @@ -348,22 +369,32 @@ return 2; } +#define json_path_shadowtbl_idx 1 +#define json_path_nullmark_idx 2 +#define json_path_idxshift 2 + // gets a value or its type from a JSON document (first argument) // optionally using a path (variable number of keys after first argument): static int json_path(lua_State *L, int type_mode) { - int argc; - int idx = 2; + int stacktop; + int idx = 2 + json_path_idxshift; + // insert json_shadowtbl on stack at position 1: + json_regfetch(L, json_shadowtbl); + lua_insert(L, 1); + // insert json_nullmark on stack at position 2: + json_regfetch(L, json_nullmark); + lua_insert(L, 2); // store number of arguments: - argc = lua_gettop(L); + stacktop = lua_gettop(L); // follow path, starting with first argument as "current value": - lua_pushvalue(L, 1); + lua_pushvalue(L, 1 + json_path_idxshift); // process each "path key": - while (idx <= argc) { + while (idx <= stacktop) { // if "current value" is nil, then the path cannot be walked and nil is returned: if (lua_isnil(L, -1)) return 1; // try to get shadow table of "current value": lua_pushvalue(L, -1); - lua_rawget(L, JSON_UPVAL_SHADOWTBL); + lua_rawget(L, json_path_shadowtbl_idx); if (lua_isnil(L, -1)) { // if no shadow table is found, // drop nil from stack: @@ -393,20 +424,33 @@ if (!type_mode) { // if a value (and not its type) was requested, // check if value is the null-marker, and store nil on top of Lua stack in that case: - if (lua_rawequal(L, -1, JSON_UPVAL_NULLMARK)) lua_pushnil(L); + if (lua_rawequal(L, -1, json_path_nullmark_idx)) lua_pushnil(L); } else { // if the type was requested, // check if value is the null-marker: - if (lua_rawequal(L, -1, JSON_UPVAL_NULLMARK)) { + if (lua_rawequal(L, -1, json_path_nullmark_idx)) { // if yes, store string "null" on top of Lua stack: lua_pushliteral(L, "null"); } else { // otherwise, - // try to read type ("object" or "array") from internal type table: - lua_pushvalue(L, -1); - lua_rawget(L, JSON_UPVAL_TYPES); - // if no entry is found in the internal type table, get the Lua type: - if (lua_isnil(L, -1)) lua_pushstring(L, lua_typename(L, lua_type(L, -2))); + // check if metatable indicates "object" or "array": + if (lua_getmetatable(L, -1)) { + json_regfetch(L, json_objectmt); + if (lua_rawequal(L, -2, -1)) { + // return string "object": + lua_pushliteral(L, "object"); + return 1; + } + json_regfetch(L, json_arraymt); + if (lua_rawequal(L, -3, -1)) { + // return string "array": + lua_pushliteral(L, "array"); + return 1; + } + lua_pop(L, 3); + } + // otherwise, get the Lua type: + lua_pushstring(L, lua_typename(L, lua_type(L, -1))); } } // return the top most value on the Lua stack: @@ -428,7 +472,7 @@ // checks if a value in a JSON document (first argument) is null: static int json_isnull(lua_State *L) { const char *jsontype; - lua_getfield(L, JSON_UPVAL_LIBRARY, "type"); + lua_pushcfunction(L, json_type); lua_insert(L, 1); lua_call(L, lua_gettop(L) - 1, 1); jsontype = lua_tostring(L, -1); @@ -437,33 +481,57 @@ return 1; } +#define json_setnull_unknownmt_idx 3 +#define json_setnull_objectmt_idx 4 +#define json_setnull_arraymt_idx 5 +#define json_setnull_shadowtbl_idx 6 + static int json_setnull(lua_State *L) { + // truncate stack to two elements: lua_settop(L, 2); - lua_pushvalue(L, JSON_UPVAL_METATABLE); - lua_setmetatable(L, 1); + // push json_unknownmt to stack position 3: + json_regfetch(L, json_unknownmt); + // push json_objectmt to stack position 4: + json_regfetch(L, json_objectmt); + // push json_arraymt to stack position 5: + json_regfetch(L, json_arraymt); + // push json_shadowtbl to stack position 6: + json_regfetch(L, json_shadowtbl); + // + lua_getmetatable(L, 1); + if ( + !lua_rawequal(L, -1, json_setnull_unknownmt_idx) && + !lua_rawequal(L, -1, json_setnull_objectmt_idx) && + !lua_rawequal(L, -1, json_setnull_arraymt_idx) + ) { + lua_pushvalue(L, json_setnull_unknownmt_idx); + lua_setmetatable(L, 1); + } lua_pushvalue(L, 1); - lua_rawget(L, JSON_UPVAL_SHADOWTBL); + lua_rawget(L, json_setnull_shadowtbl_idx); if (lua_isnil(L, -1)) { lua_newtable(L); lua_pushvalue(L, 1); lua_pushvalue(L, -2); - lua_rawset(L, JSON_UPVAL_SHADOWTBL); + lua_rawset(L, json_setnull_shadowtbl_idx); } lua_pushvalue(L, 2); - lua_pushvalue(L, JSON_UPVAL_NULLMARK); + json_regfetch(L, json_nullmark); lua_rawset(L, -3); return 0; } static int json_len(lua_State *L) { lua_settop(L, 1); + json_regfetch(L, json_shadowtbl); lua_pushvalue(L, 1); - lua_rawget(L, JSON_UPVAL_SHADOWTBL); - if (lua_isnil(L, -1)) lua_pop(L, 1); - lua_pushinteger(L, lua_rawlen(L, -1)); + lua_rawget(L, -2); + lua_pushinteger(L, lua_rawlen(L, lua_isnil(L, -1) ? 1 : -1)); return 1; } +/* + static int json_index(lua_State *L) { lua_settop(L, 2); lua_pushvalue(L, 1); @@ -528,6 +596,8 @@ return 3; } +*/ + static const struct luaL_Reg json_module_functions[] = { {"object", json_object}, {"array", json_array}, @@ -541,50 +611,40 @@ static const struct luaL_Reg json_metatable_functions[] = { {"__len", json_len}, + /* {"__index", json_index}, {"__newindex", json_newindex}, {"__pairs", json_pairs}, {"__ipairs", json_ipairs}, + */ {NULL, NULL} }; int luaopen_json(lua_State *L) { lua_settop(L, 0); - lua_newtable(L); // 1: library table on stack position - lua_pushvalue(L, 1); // 2: copy of library table - lua_newtable(L); // 3: table used as JSON NULL value in internal shadow tables - lua_newtable(L); // 4: ephemeron table to store shadow tables for each JSON object/array to allow NULL values returned as nil - lua_newtable(L); // 5: ephemeron table to store the type of the JSON object/array - lua_newtable(L); // 6: metatable for ephemeron tables + lua_newtable(L); // library + lua_newtable(L); + luaL_setfuncs(L, json_metatable_functions, 0); + json_regstore(L, json_unknownmt); + lua_setfield(L, 1, "ambiguous_mt"); + lua_newtable(L); + luaL_setfuncs(L, json_metatable_functions, 0); + json_regstore(L, json_objectmt); + lua_setfield(L, 1, "object_mt"); + lua_newtable(L); + luaL_setfuncs(L, json_metatable_functions, 0); + json_regstore(L, json_arraymt); + lua_setfield(L, 1, "array_mt"); + lua_newtable(L); // ephemeron table to store shadow tables for each JSON object/array to allow NULL values returned as nil + lua_newtable(L); // metatable for ephemeron table lua_pushliteral(L, "__mode"); lua_pushliteral(L, "k"); - lua_rawset(L, 6); - lua_pushvalue(L, 6); // 7: cloned metatable reference - lua_setmetatable(L, 4); - lua_setmetatable(L, 5); - lua_newtable(L); // 6: metatable for JSON objects and JSON arrays - lua_pushvalue(L, 2); - lua_pushvalue(L, 3); - lua_pushvalue(L, 4); - lua_pushvalue(L, 5); - lua_pushvalue(L, 6); - lua_pushcclosure(L, json_pairs_iterfunc, 5); // 7: iteration function for pairs - lua_pushvalue(L, 2); - lua_pushvalue(L, 3); - lua_pushvalue(L, 4); - lua_pushvalue(L, 5); - lua_pushvalue(L, 6); - lua_pushcclosure(L, json_ipairs_iterfunc, 5); // 8: iteration function for ipairs - lua_pushvalue(L, 6); - lua_pushvalue(L, 2); - lua_pushvalue(L, 3); - lua_pushvalue(L, 4); - lua_pushvalue(L, 5); - lua_pushvalue(L, 6); - lua_pushvalue(L, 7); - lua_pushvalue(L, 8); - luaL_setfuncs(L, json_metatable_functions, 7); - lua_setfield(L, 1, "metatable"); - luaL_setfuncs(L, json_module_functions, 7); + lua_rawset(L, -3); + lua_setmetatable(L, -2); + json_regstore(L, json_shadowtbl); + lua_newtable(L); + json_regstore(L, json_nullmark); + lua_settop(L, 1); + luaL_setfuncs(L, json_module_functions, 0); return 1; }