| rev | line source | 
| jbe@121 | 1 #include <lua.h> | 
| jbe@121 | 2 #include <lauxlib.h> | 
| jbe@122 | 3 #include <stdlib.h> | 
| jbe@121 | 4 #include <string.h> | 
| jbe@121 | 5 | 
| jbe@144 | 6 // maximum number of nested JSON values (objects and arrays): | 
| jbe@142 | 7 #define JSON_MAXDEPTH 100 | 
| jbe@142 | 8 | 
| jbe@144 | 9 // macros for usage of Lua registry: | 
| jbe@144 | 10 #define JSON_REGENT char | 
| jbe@145 | 11 #define JSON_REGPOINTER void * | 
| jbe@145 | 12 #define json_pushlightref(L, x) (lua_pushlightuserdata((L), &json_reference.x)) | 
| jbe@145 | 13 #define json_regpointer(x) (&json_registry.x) | 
| jbe@145 | 14 #define json_regfetchpointer(L, x) (lua_pushlightuserdata((L), (x)), lua_rawget((L), LUA_REGISTRYINDEX)) | 
| jbe@145 | 15 #define json_regfetch(L, x) (json_regfetchpointer(L, json_regpointer(x))) | 
| jbe@145 | 16 #define json_regstore(L, x) (lua_pushlightuserdata(L, json_regpointer(x)), lua_pushvalue(L, -2), lua_rawset(L, LUA_REGISTRYINDEX)); | 
| jbe@145 | 17 | 
| jbe@145 | 18 // generate dummy memory addresses that represent Lua objects | 
| jbe@145 | 19 // directly via lightuserdata values (not using the Lua registry): | 
| jbe@145 | 20 static struct { | 
| jbe@145 | 21   JSON_REGENT nullmark;  // magic value to indicate JSON null value | 
| jbe@145 | 22 } json_reference; | 
| jbe@145 | 23 | 
| jbe@138 | 24 | 
| jbe@144 | 25 // generate dummy memory addresses that represent Lua objects | 
| jbe@145 | 26 // via lightuserdata keys and LUA_REGISTRYINDEX: | 
| jbe@144 | 27 static struct { | 
| jbe@145 | 28   JSON_REGENT shadowtbl;  // ephemeron table that maps tables to their corresponding shadow table | 
| jbe@145 | 29   JSON_REGENT unknownmt;  // metatable for tables that may be either JSON objects or JSON arrays | 
| jbe@145 | 30   JSON_REGENT objectmt;   // metatable for JSON objects | 
| jbe@145 | 31   JSON_REGENT arraymt;    // metatable for JSON arrays | 
| jbe@144 | 32 } json_registry; | 
| jbe@138 | 33 | 
| jbe@145 | 34 // marks a Lua table as JSON object or JSON array: | 
| jbe@136 | 35 // (returns its modified argument or a new table if argument is nil) | 
| jbe@145 | 36 static int json_mark(lua_State *L, JSON_REGPOINTER mt) { | 
| jbe@145 | 37   // check if argument is nil | 
| jbe@136 | 38   if (lua_isnoneornil(L, 1)) { | 
| jbe@145 | 39     // create new table at stack position 1: | 
| jbe@136 | 40     lua_settop(L, 0); | 
| jbe@136 | 41     lua_newtable(L); | 
| jbe@145 | 42     // create shadow table (leaving previously created table on stack position 1): | 
| jbe@144 | 43     json_regfetch(L, shadowtbl); | 
| jbe@136 | 44     lua_pushvalue(L, 1); | 
| jbe@136 | 45     lua_newtable(L); | 
| jbe@143 | 46     lua_rawset(L, -3); | 
| jbe@143 | 47   } else { | 
| jbe@145 | 48     // push shadow table on top of stack: | 
| jbe@144 | 49     json_regfetch(L, shadowtbl); | 
| jbe@143 | 50     lua_pushvalue(L, 1); | 
| jbe@143 | 51     lua_rawget(L, -2); | 
| jbe@145 | 52     // if shadow table does not exist: | 
| jbe@143 | 53     if (lua_isnil(L, -1)) { | 
| jbe@145 | 54       // create shadow table and leave it on top of stack: | 
| jbe@143 | 55       lua_newtable(L); | 
| jbe@143 | 56       lua_pushvalue(L, 1); | 
| jbe@143 | 57       lua_pushvalue(L, -2); | 
| jbe@143 | 58       lua_rawset(L, -5); | 
| jbe@143 | 59     } | 
| jbe@145 | 60     // move elements from original table to shadow table (that's expected on top of stack): | 
| jbe@143 | 61     for(lua_pushnil(L); lua_next(L, 1); lua_pop(L, 1)) { | 
| jbe@143 | 62       lua_pushvalue(L, -2); | 
| jbe@143 | 63       lua_pushnil(L); | 
| jbe@143 | 64       lua_rawset(L, 1); | 
| jbe@143 | 65       lua_pushvalue(L, -2); | 
| jbe@143 | 66       lua_pushvalue(L, -2); | 
| jbe@143 | 67       lua_rawset(L, -5); | 
| jbe@143 | 68     } | 
| jbe@136 | 69   } | 
| jbe@138 | 70   // discard everything but table to return: | 
| jbe@138 | 71   lua_settop(L, 1); | 
| jbe@136 | 72   // set metatable: | 
| jbe@145 | 73   json_regfetchpointer(L, mt); | 
| jbe@136 | 74   lua_setmetatable(L, 1); | 
| jbe@138 | 75   // return table: | 
| jbe@136 | 76   return 1; | 
| jbe@136 | 77 } | 
| jbe@136 | 78 | 
| jbe@136 | 79 // marks a table as JSON object: | 
| jbe@136 | 80 // (returns its modified argument or a new table if argument is nil) | 
| jbe@136 | 81 static int json_object(lua_State *L) { | 
| jbe@145 | 82   return json_mark(L, json_regpointer(objectmt)); | 
| jbe@136 | 83 } | 
| jbe@136 | 84 | 
| jbe@136 | 85 // marks a table as JSON array: | 
| jbe@136 | 86 // (returns its modified argument or a new table if argument is nil) | 
| jbe@136 | 87 static int json_array(lua_State *L) { | 
| jbe@145 | 88   return json_mark(L, json_regpointer(arraymt)); | 
| jbe@136 | 89 } | 
| jbe@136 | 90 | 
| jbe@145 | 91 // internal states of JSON parser: | 
| jbe@124 | 92 #define JSON_STATE_VALUE 0 | 
| jbe@124 | 93 #define JSON_STATE_OBJECT_KEY 1 | 
| jbe@124 | 94 #define JSON_STATE_OBJECT_KEY_TERMINATOR 2 | 
| jbe@124 | 95 #define JSON_STATE_OBJECT_VALUE 3 | 
| jbe@124 | 96 #define JSON_STATE_OBJECT_SEPARATOR 4 | 
| jbe@124 | 97 #define JSON_STATE_ARRAY_VALUE 5 | 
| jbe@124 | 98 #define JSON_STATE_ARRAY_SEPARATOR 6 | 
| jbe@124 | 99 #define JSON_STATE_END 7 | 
| jbe@121 | 100 | 
| jbe@145 | 101 // special Lua stack indicies for json_import function: | 
| jbe@138 | 102 #define json_import_objectmt_idx 2 | 
| jbe@138 | 103 #define json_import_arraymt_idx 3 | 
| jbe@138 | 104 #define json_import_shadowtbl_idx 4 | 
| jbe@138 | 105 #define json_import_nullmark_idx 5 | 
| jbe@138 | 106 | 
| jbe@136 | 107 // decodes a JSON document: | 
| jbe@121 | 108 static int json_import(lua_State *L) { | 
| jbe@136 | 109   const char *str;   // string to parse | 
| jbe@136 | 110   size_t total;      // total length of string to parse | 
| jbe@136 | 111   size_t pos = 0;    // current position in string to parse | 
| jbe@136 | 112   size_t level = 0;  // nested levels of objects/arrays currently being processed | 
| jbe@145 | 113   int mode = JSON_STATE_VALUE;  // state of parser (i.e. "what's expected next?") | 
| jbe@136 | 114   char c;              // variable to store a single character to be processed | 
| jbe@145 | 115   luaL_Buffer luabuf;  // Lua buffer to decode JSON string values | 
| jbe@145 | 116   char *cbuf;          // C buffer to decode JSON string values | 
| jbe@136 | 117   size_t writepos;     // write position of decoded strings in C buffer | 
| jbe@145 | 118   // stack shall only contain one function argument: | 
| jbe@138 | 119   lua_settop(L, 1); | 
| jbe@145 | 120   // push objectmt on stack position 2: | 
| jbe@144 | 121   json_regfetch(L, objectmt); | 
| jbe@145 | 122   // push arraymt on stack position 3: | 
| jbe@144 | 123   json_regfetch(L, arraymt); | 
| jbe@145 | 124   // push shadowtbl on stack position 4: | 
| jbe@144 | 125   json_regfetch(L, shadowtbl); | 
| jbe@145 | 126   // push nullmark on stack position 5: | 
| jbe@145 | 127   json_pushlightref(L, nullmark); | 
| jbe@136 | 128   // require string as first argument: | 
| jbe@136 | 129   str = luaL_checklstring(L, 1, &total); | 
| jbe@136 | 130   // if string contains a NULL byte, this is a syntax error | 
| jbe@136 | 131   if (strlen(str) != total) goto json_import_syntax_error; | 
| jbe@136 | 132   // main loop of parser: | 
| jbe@136 | 133   json_import_loop: | 
| jbe@136 | 134   // skip whitespace and store next character in variable 'c': | 
| jbe@121 | 135   while (c = str[pos], c == ' ' || c == '\n' || c == '\r' || c == '\t' || c == '\f') pos++; | 
| jbe@136 | 136   // switch statement to handle certain (single) characters: | 
| jbe@121 | 137   switch (c) { | 
| jbe@136 | 138   // handle end of JSON document: | 
| jbe@121 | 139   case 0: | 
| jbe@136 | 140     // if end of JSON document was expected, then return top element of stack as result: | 
| jbe@124 | 141     if (mode == JSON_STATE_END) return 1; | 
| jbe@136 | 142     // otherwise, the JSON document was malformed: | 
| jbe@121 | 143     json_import_unexpected_eof: | 
| jbe@121 | 144     lua_pushnil(L); | 
| jbe@121 | 145     if (level == 0) lua_pushliteral(L, "Empty string"); | 
| jbe@121 | 146     else lua_pushliteral(L, "Unexpected end of JSON document"); | 
| jbe@121 | 147     return 2; | 
| jbe@136 | 148   // new JSON object: | 
| jbe@121 | 149   case '{': | 
| jbe@136 | 150     // if a JSON object is not expected here, then return an error: | 
| jbe@124 | 151     if (mode != JSON_STATE_VALUE && mode != JSON_STATE_OBJECT_VALUE && mode != JSON_STATE_ARRAY_VALUE) | 
| jbe@121 | 152       goto json_import_syntax_error; | 
| jbe@136 | 153     // create JSON object on stack: | 
| jbe@136 | 154     lua_newtable(L); | 
| jbe@136 | 155     // set metatable of JSON object: | 
| jbe@138 | 156     lua_pushvalue(L, json_import_objectmt_idx); | 
| jbe@125 | 157     lua_setmetatable(L, -2); | 
| jbe@136 | 158     // create internal shadow table on stack: | 
| jbe@136 | 159     lua_newtable(L); | 
| jbe@138 | 160     // register internal shadow table (and cleanup stack afterwards): | 
| jbe@123 | 161     lua_pushvalue(L, -2); | 
| jbe@123 | 162     lua_pushvalue(L, -2); | 
| jbe@138 | 163     lua_rawset(L, json_import_shadowtbl_idx); | 
| jbe@136 | 164     // expect object key (or end of object) and continue with loop: | 
| jbe@136 | 165     mode = JSON_STATE_OBJECT_KEY; | 
| jbe@142 | 166     goto json_import_open; | 
| jbe@136 | 167   // new JSON array: | 
| jbe@121 | 168   case '[': | 
| jbe@136 | 169     // if a JSON array is not expected here, then return an error: | 
| jbe@124 | 170     if (mode != JSON_STATE_VALUE && mode != JSON_STATE_OBJECT_VALUE && mode != JSON_STATE_ARRAY_VALUE) | 
| jbe@121 | 171       goto json_import_syntax_error; | 
| jbe@136 | 172     // create JSON array on stack: | 
| jbe@136 | 173     lua_newtable(L); | 
| jbe@136 | 174     // set metatable of JSON array: | 
| jbe@138 | 175     lua_pushvalue(L, json_import_arraymt_idx); | 
| jbe@125 | 176     lua_setmetatable(L, -2); | 
| jbe@136 | 177     // create internal shadow table on stack: | 
| jbe@136 | 178     lua_newtable(L); | 
| jbe@138 | 179     // register internal shadow table (and cleanup stack afterwards): | 
| jbe@123 | 180     lua_pushvalue(L, -2); | 
| jbe@123 | 181     lua_pushvalue(L, -2); | 
| jbe@138 | 182     lua_rawset(L, json_import_shadowtbl_idx); | 
| jbe@140 | 183     // add nil as key (needed to keep stack balance) and as magic to detect arrays: | 
| jbe@140 | 184     lua_pushnil(L); | 
| jbe@142 | 185     // expect array value (or end of array) and continue with loop: | 
| jbe@142 | 186     mode = JSON_STATE_ARRAY_VALUE; | 
| jbe@142 | 187     // continue with common code for opening JSON object and JSON array: | 
| jbe@142 | 188   // commn code for opening JSON object or JSON array: | 
| jbe@142 | 189   json_import_open: | 
| jbe@142 | 190     // limit nested levels: | 
| jbe@142 | 191     if (level >= JSON_MAXDEPTH) { | 
| jbe@142 | 192       lua_pushnil(L); | 
| jbe@142 | 193       lua_pushliteral(L, "Too many nested JSON levels"); | 
| jbe@142 | 194       return 2; | 
| jbe@142 | 195     } | 
| jbe@142 | 196     // additional buffer overflow protection: | 
| jbe@142 | 197     if (!lua_checkstack(L, LUA_MINSTACK)) | 
| jbe@142 | 198       return luaL_error(L, "Caught stack overflow in JSON import function (too many nested levels and stack size too small)"); | 
| jbe@136 | 199     // increment level: | 
| jbe@121 | 200     level++; | 
| jbe@142 | 201     // consume input character: | 
| jbe@142 | 202     pos++; | 
| jbe@121 | 203     goto json_import_loop; | 
| jbe@136 | 204   // end of JSON object: | 
| jbe@121 | 205   case '}': | 
| jbe@136 | 206     // if end of JSON object is not expected here, then return an error: | 
| jbe@124 | 207     if (mode != JSON_STATE_OBJECT_KEY && mode != JSON_STATE_OBJECT_SEPARATOR) | 
| jbe@121 | 208       goto json_import_syntax_error; | 
| jbe@136 | 209     // jump to common code for end of JSON object and JSON array: | 
| jbe@121 | 210     goto json_import_close; | 
| jbe@136 | 211   // end of JSON array: | 
| jbe@121 | 212   case ']': | 
| jbe@136 | 213     // if end of JSON array is not expected here, then return an error: | 
| jbe@124 | 214     if (mode != JSON_STATE_ARRAY_VALUE && mode != JSON_STATE_ARRAY_SEPARATOR) | 
| jbe@121 | 215       goto json_import_syntax_error; | 
| jbe@140 | 216     // pop nil key/magic: | 
| jbe@140 | 217     lua_pop(L, 1); | 
| jbe@136 | 218     // continue with common code for end of JSON object and JSON array: | 
| jbe@136 | 219   // common code for end of JSON object or JSON array: | 
| jbe@121 | 220   json_import_close: | 
| jbe@136 | 221     // consume input character: | 
| jbe@121 | 222     pos++; | 
| jbe@136 | 223     // pop shadow table: | 
| jbe@136 | 224     lua_pop(L, 1); | 
| jbe@136 | 225     // check if nested: | 
| jbe@121 | 226     if (--level) { | 
| jbe@136 | 227       // if nested, then check if outer(!) structure is an array or object: | 
| jbe@140 | 228       if (lua_isnil(L, -2)) { | 
| jbe@136 | 229         // select array value processing: | 
| jbe@124 | 230         mode = JSON_STATE_ARRAY_VALUE; | 
| jbe@121 | 231       } else { | 
| jbe@136 | 232         // select object value processing: | 
| jbe@124 | 233         mode = JSON_STATE_OBJECT_VALUE; | 
| jbe@121 | 234       } | 
| jbe@136 | 235       // store value in outer structure: | 
| jbe@121 | 236       goto json_import_process_value; | 
| jbe@121 | 237     } | 
| jbe@136 | 238     // if not nested, then expect end of JSON document and continue with loop: | 
| jbe@136 | 239     mode = JSON_STATE_END; | 
| jbe@121 | 240     goto json_import_loop; | 
| jbe@136 | 241   // key terminator: | 
| jbe@121 | 242   case ':': | 
| jbe@136 | 243     // if key terminator is not expected here, then return an error: | 
| jbe@124 | 244     if (mode != JSON_STATE_OBJECT_KEY_TERMINATOR) | 
| jbe@121 | 245       goto json_import_syntax_error; | 
| jbe@136 | 246     // consume input character: | 
| jbe@121 | 247     pos++; | 
| jbe@136 | 248     // set state of parser and continue with loop: | 
| jbe@124 | 249     mode = JSON_STATE_OBJECT_VALUE; | 
| jbe@121 | 250     goto json_import_loop; | 
| jbe@136 | 251   // value terminator (NOTE: trailing comma at end of value or key-value list is tolerated by this parser) | 
| jbe@121 | 252   case ',': | 
| jbe@136 | 253     // change parser state accordingly: | 
| jbe@124 | 254     if (mode == JSON_STATE_OBJECT_SEPARATOR) { | 
| jbe@124 | 255       mode = JSON_STATE_OBJECT_KEY; | 
| jbe@124 | 256     } else if (mode == JSON_STATE_ARRAY_SEPARATOR) { | 
| jbe@124 | 257       mode = JSON_STATE_ARRAY_VALUE; | 
| jbe@121 | 258     } else { | 
| jbe@136 | 259        // if value terminator is not expected here, then return an error: | 
| jbe@136 | 260        goto json_import_syntax_error; | 
| jbe@121 | 261     } | 
| jbe@136 | 262     // consume input character: | 
| jbe@121 | 263     pos++; | 
| jbe@136 | 264     // continue with loop: | 
| jbe@121 | 265     goto json_import_loop; | 
| jbe@136 | 266   // string literal: | 
| jbe@121 | 267   case '"': | 
| jbe@136 | 268     // prepare buffer to decode string (with maximum possible length) and set write position to zero: | 
| jbe@121 | 269     cbuf = luaL_buffinitsize(L, &luabuf, total-pos); | 
| jbe@121 | 270     writepos = 0; | 
| jbe@136 | 271     // consume quote character: | 
| jbe@121 | 272     pos++; | 
| jbe@136 | 273     // read next character until encountering end quote: | 
| jbe@121 | 274     while ((c = str[pos++]) != '"') { | 
| jbe@121 | 275       if (c == 0) { | 
| jbe@136 | 276         // handle unexpected end-of-string: | 
| jbe@121 | 277         goto json_import_unexpected_eof; | 
| jbe@121 | 278       } else if (c < 32 || c == 127) { | 
| jbe@136 | 279         // do not allow ASCII control characters: | 
| jbe@136 | 280         // NOTE: illegal UTF-8 sequences and extended control characters are not sanitized | 
| jbe@136 | 281         //       by this parser to allow different encodings than Unicode | 
| jbe@121 | 282         lua_pushnil(L); | 
| jbe@121 | 283         lua_pushliteral(L, "Unexpected control character in JSON string"); | 
| jbe@121 | 284         return 2; | 
| jbe@121 | 285       } else if (c == '\\') { | 
| jbe@136 | 286         // read next char after backslash escape: | 
| jbe@121 | 287         c = str[pos++]; | 
| jbe@121 | 288         switch (c) { | 
| jbe@136 | 289         // unexpected end-of-string: | 
| jbe@121 | 290         case 0: | 
| jbe@121 | 291           goto json_import_unexpected_eof; | 
| jbe@136 | 292         // unescaping of quotation mark, slash, and backslash: | 
| jbe@121 | 293         case '"': | 
| jbe@121 | 294         case '/': | 
| jbe@121 | 295         case '\\': | 
| jbe@121 | 296           cbuf[writepos++] = c; | 
| jbe@121 | 297           break; | 
| jbe@136 | 298         // unescaping of backspace: | 
| jbe@121 | 299         case 'b': | 
| jbe@121 | 300           cbuf[writepos++] = '\b'; | 
| jbe@121 | 301           break; | 
| jbe@136 | 302         // unescaping of form-feed: | 
| jbe@121 | 303         case 'f': | 
| jbe@121 | 304           cbuf[writepos++] = '\f'; | 
| jbe@121 | 305           break; | 
| jbe@136 | 306         // unescaping of new-line: | 
| jbe@121 | 307         case 'n': | 
| jbe@121 | 308           cbuf[writepos++] = '\n'; | 
| jbe@121 | 309           break; | 
| jbe@136 | 310         // unescaping of carriage-return: | 
| jbe@121 | 311         case 'r': | 
| jbe@121 | 312           cbuf[writepos++] = '\r'; | 
| jbe@121 | 313           break; | 
| jbe@136 | 314         // unescaping of tabulator: | 
| jbe@121 | 315         case 't': | 
| jbe@121 | 316           cbuf[writepos++] = '\t'; | 
| jbe@121 | 317           break; | 
| jbe@136 | 318         // unescaping of UTF-16 characters | 
| jbe@121 | 319         case 'u': | 
| jbe@121 | 320           lua_pushnil(L); | 
| jbe@121 | 321           lua_pushliteral(L, "JSON unicode escape sequences are not implemented yet");  // TODO | 
| jbe@121 | 322           return 2; | 
| jbe@136 | 323         // unexpected escape sequence: | 
| jbe@121 | 324         default: | 
| jbe@121 | 325           lua_pushnil(L); | 
| jbe@121 | 326           lua_pushliteral(L, "Unexpected string escape sequence in JSON document"); | 
| jbe@121 | 327           return 2; | 
| jbe@121 | 328         } | 
| jbe@121 | 329       } else { | 
| jbe@136 | 330         // normal character: | 
| jbe@121 | 331         cbuf[writepos++] = c; | 
| jbe@121 | 332       } | 
| jbe@121 | 333     } | 
| jbe@136 | 334     // process buffer to Lua string: | 
| jbe@121 | 335     luaL_pushresultsize(&luabuf, writepos); | 
| jbe@136 | 336     // continue with processing of decoded string: | 
| jbe@121 | 337     goto json_import_process_value; | 
| jbe@121 | 338   } | 
| jbe@136 | 339   // process values whose type is is not deducible from a single character: | 
| jbe@136 | 340   if ((c >= '0' && c <= '9') || c == '-' || c == '+') { | 
| jbe@136 | 341     // numbers: | 
| jbe@122 | 342     char *endptr; | 
| jbe@122 | 343     double numval; | 
| jbe@122 | 344     numval = strtod(str+pos, &endptr); | 
| jbe@122 | 345     if (endptr == str+pos) goto json_import_syntax_error; | 
| jbe@122 | 346     pos += endptr - (str+pos); | 
| jbe@122 | 347     lua_pushnumber(L, numval); | 
| jbe@122 | 348   } else if (!strncmp(str+pos, "true", 4)) { | 
| jbe@136 | 349     // consume 4 input characters for "true": | 
| jbe@121 | 350     pos += 4; | 
| jbe@136 | 351     // put Lua true value on stack: | 
| jbe@136 | 352     lua_pushboolean(L, 1); | 
| jbe@121 | 353   } else if (!strncmp(str+pos, "false", 5)) { | 
| jbe@136 | 354     // consume 5 input characters for "false": | 
| jbe@121 | 355     pos += 5; | 
| jbe@136 | 356     // put Lua false value on stack: | 
| jbe@136 | 357     lua_pushboolean(L, 0); | 
| jbe@121 | 358   } else if (!strncmp(str+pos, "null", 4)) { | 
| jbe@136 | 359     // consume 4 input characters for "null": | 
| jbe@136 | 360     pos += 4; | 
| jbe@136 | 361     // put special null-marker on stack: | 
| jbe@138 | 362     lua_pushvalue(L, json_import_nullmark_idx); | 
| jbe@121 | 363   } else { | 
| jbe@136 | 364     // all other cases are a syntax error: | 
| jbe@121 | 365     goto json_import_syntax_error; | 
| jbe@121 | 366   } | 
| jbe@136 | 367   // process a decoded value or key value pair (expected on top of Lua stack): | 
| jbe@136 | 368   json_import_process_value: | 
| jbe@121 | 369   switch (mode) { | 
| jbe@136 | 370   // an object key has been read: | 
| jbe@124 | 371   case JSON_STATE_OBJECT_KEY: | 
| jbe@136 | 372     // if an object key is not a string, then this is a syntax error: | 
| jbe@121 | 373     if (lua_type(L, -1) != LUA_TSTRING) goto json_import_syntax_error; | 
| jbe@136 | 374     // expect key terminator and continue with loop: | 
| jbe@124 | 375     mode = JSON_STATE_OBJECT_KEY_TERMINATOR; | 
| jbe@121 | 376     goto json_import_loop; | 
| jbe@136 | 377   // a key value pair has been read: | 
| jbe@124 | 378   case JSON_STATE_OBJECT_VALUE: | 
| jbe@136 | 379     // store key value pair in outer shadow table: | 
| jbe@130 | 380     lua_rawset(L, -3); | 
| jbe@136 | 381     // expect value terminator (or end of object) and continue with loop: | 
| jbe@124 | 382     mode = JSON_STATE_OBJECT_SEPARATOR; | 
| jbe@121 | 383     goto json_import_loop; | 
| jbe@136 | 384   // an array value has been read: | 
| jbe@124 | 385   case JSON_STATE_ARRAY_VALUE: | 
| jbe@136 | 386     // store value in outer shadow table: | 
| jbe@140 | 387     lua_rawseti(L, -3, lua_rawlen(L, -3) + 1); | 
| jbe@136 | 388     // expect value terminator (or end of object) and continue with loop: | 
| jbe@124 | 389     mode = JSON_STATE_ARRAY_SEPARATOR; | 
| jbe@121 | 390     goto json_import_loop; | 
| jbe@136 | 391   // a single value has been read: | 
| jbe@124 | 392   case JSON_STATE_VALUE: | 
| jbe@136 | 393     // leave value on top of stack, expect end of JSON document, and continue with loop: | 
| jbe@124 | 394     mode = JSON_STATE_END; | 
| jbe@121 | 395     goto json_import_loop; | 
| jbe@121 | 396   } | 
| jbe@136 | 397   // syntax error handling (only reachable by goto statement): | 
| jbe@136 | 398   json_import_syntax_error: | 
| jbe@121 | 399   lua_pushnil(L); | 
| jbe@121 | 400   lua_pushliteral(L, "Syntax error in JSON document"); | 
| jbe@121 | 401   return 2; | 
| jbe@121 | 402 } | 
| jbe@121 | 403 | 
| jbe@138 | 404 #define json_path_shadowtbl_idx 1 | 
| jbe@138 | 405 #define json_path_nullmark_idx 2 | 
| jbe@138 | 406 #define json_path_idxshift 2 | 
| jbe@138 | 407 | 
| jbe@137 | 408 // gets a value or its type from a JSON document (first argument) | 
| jbe@137 | 409 // optionally using a path (variable number of keys after first argument): | 
| jbe@137 | 410 static int json_path(lua_State *L, int type_mode) { | 
| jbe@138 | 411   int stacktop; | 
| jbe@138 | 412   int idx = 2 + json_path_idxshift; | 
| jbe@138 | 413   // insert json_shadowtbl on stack at position 1: | 
| jbe@144 | 414   json_regfetch(L, shadowtbl); | 
| jbe@138 | 415   lua_insert(L, 1); | 
| jbe@138 | 416   // insert json_nullmark on stack at position 2: | 
| jbe@145 | 417   json_pushlightref(L, nullmark); | 
| jbe@138 | 418   lua_insert(L, 2); | 
| jbe@137 | 419   // store number of arguments: | 
| jbe@138 | 420   stacktop = lua_gettop(L); | 
| jbe@137 | 421   // follow path, starting with first argument as "current value": | 
| jbe@138 | 422   lua_pushvalue(L, 1 + json_path_idxshift); | 
| jbe@137 | 423   // process each "path key": | 
| jbe@138 | 424   while (idx <= stacktop) { | 
| jbe@137 | 425     // if "current value" is nil, then the path cannot be walked and nil is returned: | 
| jbe@137 | 426     if (lua_isnil(L, -1)) return 1; | 
| jbe@137 | 427     // try to get shadow table of "current value": | 
| jbe@130 | 428     lua_pushvalue(L, -1); | 
| jbe@138 | 429     lua_rawget(L, json_path_shadowtbl_idx); | 
| jbe@126 | 430     if (lua_isnil(L, -1)) { | 
| jbe@137 | 431       // if no shadow table is found, | 
| jbe@137 | 432       // drop nil from stack: | 
| jbe@126 | 433       lua_pop(L, 1); | 
| jbe@130 | 434       if (lua_type(L, -1) == LUA_TTABLE) { | 
| jbe@137 | 435         // if "current value" is a table, | 
| jbe@137 | 436         // get "next value" using the "path key": | 
| jbe@130 | 437         lua_pushvalue(L, idx++); | 
| jbe@130 | 438         lua_gettable(L, -2); | 
| jbe@130 | 439       } else { | 
| jbe@137 | 440         // if "current value" is not a table, | 
| jbe@137 | 441         // then the path cannot be walked and nil is returned: | 
| jbe@130 | 442         lua_pushnil(L); | 
| jbe@137 | 443         return 1; | 
| jbe@130 | 444       } | 
| jbe@130 | 445     } else { | 
| jbe@137 | 446       // if a shadow table is found, | 
| jbe@137 | 447       // set "current value" to its shadow table: | 
| jbe@130 | 448       lua_replace(L, -2); | 
| jbe@137 | 449       // get "next value" using the "path key": | 
| jbe@130 | 450       lua_pushvalue(L, idx++); | 
| jbe@130 | 451       lua_rawget(L, -2); | 
| jbe@126 | 452     } | 
| jbe@137 | 453     // the "next value" replaces the "current value": | 
| jbe@130 | 454     lua_replace(L, -2); | 
| jbe@126 | 455   } | 
| jbe@137 | 456   if (!type_mode) { | 
| jbe@137 | 457     // if a value (and not its type) was requested, | 
| jbe@137 | 458     // check if value is the null-marker, and store nil on top of Lua stack in that case: | 
| jbe@138 | 459     if (lua_rawequal(L, -1, json_path_nullmark_idx)) lua_pushnil(L); | 
| jbe@137 | 460   } else { | 
| jbe@137 | 461     // if the type was requested, | 
| jbe@137 | 462     // check if value is the null-marker: | 
| jbe@138 | 463     if (lua_rawequal(L, -1, json_path_nullmark_idx)) { | 
| jbe@137 | 464       // if yes, store string "null" on top of Lua stack: | 
| jbe@130 | 465       lua_pushliteral(L, "null"); | 
| jbe@137 | 466     } else { | 
| jbe@137 | 467       // otherwise, | 
| jbe@138 | 468       // check if metatable indicates "object" or "array": | 
| jbe@138 | 469       if (lua_getmetatable(L, -1)) { | 
| jbe@144 | 470         json_regfetch(L, objectmt); | 
| jbe@138 | 471         if (lua_rawequal(L, -2, -1)) { | 
| jbe@138 | 472           // return string "object": | 
| jbe@138 | 473           lua_pushliteral(L, "object"); | 
| jbe@138 | 474           return 1; | 
| jbe@138 | 475         } | 
| jbe@144 | 476         json_regfetch(L, arraymt); | 
| jbe@138 | 477         if (lua_rawequal(L, -3, -1)) { | 
| jbe@138 | 478           // return string "array": | 
| jbe@138 | 479           lua_pushliteral(L, "array"); | 
| jbe@138 | 480           return 1; | 
| jbe@138 | 481         } | 
| jbe@138 | 482         lua_pop(L, 3); | 
| jbe@138 | 483       } | 
| jbe@138 | 484       // otherwise, get the Lua type: | 
| jbe@138 | 485       lua_pushstring(L, lua_typename(L, lua_type(L, -1))); | 
| jbe@126 | 486     } | 
| jbe@126 | 487   } | 
| jbe@137 | 488   // return the top most value on the Lua stack: | 
| jbe@137 | 489   return 1; | 
| jbe@130 | 490 } | 
| jbe@130 | 491 | 
| jbe@137 | 492 // gets a value from a JSON document (first argument) | 
| jbe@137 | 493 // optionally using a path (variable number of keys after first argument): | 
| jbe@130 | 494 static int json_get(lua_State *L) { | 
| jbe@137 | 495   return json_path(L, 0); | 
| jbe@130 | 496 } | 
| jbe@130 | 497 | 
| jbe@137 | 498 // gets a value's type from a JSON document (first argument) | 
| jbe@137 | 499 // optionally using a path (variable number of keys after first argument): | 
| jbe@130 | 500 static int json_type(lua_State *L) { | 
| jbe@137 | 501   return json_path(L, 1); | 
| jbe@130 | 502 } | 
| jbe@130 | 503 | 
| jbe@137 | 504 // checks if a value in a JSON document (first argument) is null: | 
| jbe@130 | 505 static int json_isnull(lua_State *L) { | 
| jbe@137 | 506   const char *jsontype; | 
| jbe@138 | 507   lua_pushcfunction(L, json_type); | 
| jbe@137 | 508   lua_insert(L, 1); | 
| jbe@137 | 509   lua_call(L, lua_gettop(L) - 1, 1); | 
| jbe@137 | 510   jsontype = lua_tostring(L, -1); | 
| jbe@137 | 511   if (jsontype && !strcmp(jsontype, "null")) lua_pushboolean(L, 1); | 
| jbe@137 | 512   else lua_pushboolean(L, 0); | 
| jbe@137 | 513   return 1; | 
| jbe@130 | 514 } | 
| jbe@130 | 515 | 
| jbe@138 | 516 #define json_setnull_unknownmt_idx 3 | 
| jbe@138 | 517 #define json_setnull_objectmt_idx 4 | 
| jbe@138 | 518 #define json_setnull_arraymt_idx 5 | 
| jbe@138 | 519 #define json_setnull_shadowtbl_idx 6 | 
| jbe@138 | 520 | 
| jbe@131 | 521 static int json_setnull(lua_State *L) { | 
| jbe@138 | 522   // truncate stack to two elements: | 
| jbe@131 | 523   lua_settop(L, 2); | 
| jbe@138 | 524   // push json_unknownmt to stack position 3: | 
| jbe@144 | 525   json_regfetch(L, unknownmt); | 
| jbe@138 | 526   // push json_objectmt to stack position 4: | 
| jbe@144 | 527   json_regfetch(L, objectmt); | 
| jbe@138 | 528   // push json_arraymt to stack position 5: | 
| jbe@144 | 529   json_regfetch(L, arraymt); | 
| jbe@138 | 530   // push json_shadowtbl to stack position 6: | 
| jbe@144 | 531   json_regfetch(L, shadowtbl); | 
| jbe@138 | 532   // | 
| jbe@138 | 533   lua_getmetatable(L, 1); | 
| jbe@138 | 534   if ( | 
| jbe@138 | 535     !lua_rawequal(L, -1, json_setnull_unknownmt_idx) && | 
| jbe@138 | 536     !lua_rawequal(L, -1, json_setnull_objectmt_idx) && | 
| jbe@138 | 537     !lua_rawequal(L, -1, json_setnull_arraymt_idx) | 
| jbe@138 | 538   ) { | 
| jbe@138 | 539     lua_pushvalue(L, json_setnull_unknownmt_idx); | 
| jbe@138 | 540     lua_setmetatable(L, 1); | 
| jbe@138 | 541   } | 
| jbe@131 | 542   lua_pushvalue(L, 1); | 
| jbe@138 | 543   lua_rawget(L, json_setnull_shadowtbl_idx); | 
| jbe@131 | 544   if (lua_isnil(L, -1)) { | 
| jbe@131 | 545     lua_newtable(L); | 
| jbe@131 | 546     lua_pushvalue(L, 1); | 
| jbe@131 | 547     lua_pushvalue(L, -2); | 
| jbe@138 | 548     lua_rawset(L, json_setnull_shadowtbl_idx); | 
| jbe@131 | 549   } | 
| jbe@131 | 550   lua_pushvalue(L, 2); | 
| jbe@145 | 551   json_pushlightref(L, nullmark); | 
| jbe@131 | 552   lua_rawset(L, -3); | 
| jbe@131 | 553   return 0; | 
| jbe@131 | 554 } | 
| jbe@131 | 555 | 
| jbe@130 | 556 static int json_len(lua_State *L) { | 
| jbe@130 | 557   lua_settop(L, 1); | 
| jbe@144 | 558   json_regfetch(L, shadowtbl); | 
| jbe@130 | 559   lua_pushvalue(L, 1); | 
| jbe@138 | 560   lua_rawget(L, -2); | 
| jbe@138 | 561   lua_pushinteger(L, lua_rawlen(L, lua_isnil(L, -1) ? 1 : -1)); | 
| jbe@123 | 562   return 1; | 
| jbe@123 | 563 } | 
| jbe@123 | 564 | 
| jbe@141 | 565 #define json_index_nullmark_idx 3 | 
| jbe@141 | 566 #define json_index_shadowtbl_idx 4 | 
| jbe@141 | 567 | 
| jbe@130 | 568 static int json_index(lua_State *L) { | 
| jbe@130 | 569   lua_settop(L, 2); | 
| jbe@145 | 570   json_pushlightref(L, nullmark);  // on stack position 3 | 
| jbe@144 | 571   json_regfetch(L, shadowtbl); | 
| jbe@130 | 572   lua_pushvalue(L, 1); | 
| jbe@141 | 573   lua_rawget(L, json_index_shadowtbl_idx); | 
| jbe@139 | 574   if (lua_isnil(L, -1)) return luaL_error(L, "Shadow table not found"); | 
| jbe@130 | 575   lua_pushvalue(L, 2); | 
| jbe@130 | 576   lua_rawget(L, -2); | 
| jbe@141 | 577   if (lua_rawequal(L, -1, json_index_nullmark_idx)) lua_pushnil(L); | 
| jbe@127 | 578   return 1; | 
| jbe@127 | 579 } | 
| jbe@127 | 580 | 
| jbe@130 | 581 static int json_newindex(lua_State *L) { | 
| jbe@130 | 582   lua_settop(L, 3); | 
| jbe@144 | 583   json_regfetch(L, shadowtbl); | 
| jbe@123 | 584   lua_pushvalue(L, 1); | 
| jbe@143 | 585   lua_rawget(L, -2); | 
| jbe@130 | 586   if (lua_isnil(L, -1)) return luaL_error(L, "Shadow table not found"); | 
| jbe@130 | 587   lua_replace(L, 1); | 
| jbe@139 | 588   lua_settop(L, 3); | 
| jbe@130 | 589   lua_rawset(L, 1); | 
| jbe@121 | 590   return 1; | 
| jbe@121 | 591 } | 
| jbe@121 | 592 | 
| jbe@139 | 593 #define json_pairs_iterfunc_nullmark_idx 3 | 
| jbe@139 | 594 #define json_pairs_iterfunc_shadowtbl_idx 4 | 
| jbe@139 | 595 | 
| jbe@135 | 596 static int json_pairs_iterfunc(lua_State *L) { | 
| jbe@135 | 597   lua_settop(L, 2); | 
| jbe@145 | 598   json_pushlightref(L, nullmark);  // on stack position 3 | 
| jbe@144 | 599   json_regfetch(L, shadowtbl); | 
| jbe@135 | 600   lua_pushvalue(L, 1); | 
| jbe@139 | 601   lua_rawget(L, json_pairs_iterfunc_shadowtbl_idx); | 
| jbe@135 | 602   if (lua_isnil(L, -1)) return luaL_error(L, "Shadow table not found"); | 
| jbe@135 | 603   lua_pushvalue(L, 2); | 
| jbe@135 | 604   if (!lua_next(L, -2)) return 0; | 
| jbe@139 | 605   if (lua_rawequal(L, -1, json_pairs_iterfunc_nullmark_idx)) { | 
| jbe@135 | 606     lua_pop(L, 1); | 
| jbe@135 | 607     lua_pushnil(L); | 
| jbe@135 | 608   } | 
| jbe@135 | 609   return 2; | 
| jbe@135 | 610 } | 
| jbe@135 | 611 | 
| jbe@135 | 612 static int json_pairs(lua_State *L) { | 
| jbe@139 | 613   lua_pushcfunction(L, json_pairs_iterfunc); | 
| jbe@135 | 614   lua_pushvalue(L, 1); | 
| jbe@135 | 615   lua_pushnil(L); | 
| jbe@135 | 616   return 3; | 
| jbe@135 | 617 } | 
| jbe@135 | 618 | 
| jbe@139 | 619 #define json_ipairs_iterfunc_nullmark_idx 3 | 
| jbe@139 | 620 #define json_ipairs_iterfunc_shadowtbl_idx 4 | 
| jbe@139 | 621 | 
| jbe@134 | 622 static int json_ipairs_iterfunc(lua_State *L) { | 
| jbe@134 | 623   int idx; | 
| jbe@134 | 624   lua_settop(L, 2); | 
| jbe@145 | 625   json_pushlightref(L, nullmark);  // on stack position 3 | 
| jbe@144 | 626   json_regfetch(L, shadowtbl); | 
| jbe@134 | 627   idx = lua_tointeger(L, 2) + 1; | 
| jbe@134 | 628   lua_pushvalue(L, 1); | 
| jbe@139 | 629   lua_rawget(L, json_ipairs_iterfunc_shadowtbl_idx); | 
| jbe@134 | 630   if (lua_isnil(L, -1)) return luaL_error(L, "Shadow table not found"); | 
| jbe@134 | 631   lua_rawgeti(L, -1, idx); | 
| jbe@134 | 632   if (lua_isnil(L, -1)) return 0; | 
| jbe@134 | 633   lua_pushinteger(L, idx); | 
| jbe@139 | 634   if (lua_rawequal(L, -2, json_ipairs_iterfunc_nullmark_idx)) lua_pushnil(L); | 
| jbe@134 | 635   else lua_pushvalue(L, -2); | 
| jbe@134 | 636   return 2; | 
| jbe@134 | 637 } | 
| jbe@134 | 638 | 
| jbe@134 | 639 static int json_ipairs(lua_State *L) { | 
| jbe@139 | 640   lua_pushcfunction(L, json_ipairs_iterfunc); | 
| jbe@134 | 641   lua_pushvalue(L, 1); | 
| jbe@134 | 642   lua_pushinteger(L, 0); | 
| jbe@134 | 643   return 3; | 
| jbe@134 | 644 } | 
| jbe@134 | 645 | 
| jbe@121 | 646 static const struct luaL_Reg json_module_functions[] = { | 
| jbe@133 | 647   {"object", json_object}, | 
| jbe@133 | 648   {"array", json_array}, | 
| jbe@121 | 649   {"import", json_import}, | 
| jbe@130 | 650   {"get", json_get}, | 
| jbe@127 | 651   {"type", json_type}, | 
| jbe@123 | 652   {"isnull", json_isnull}, | 
| jbe@131 | 653   {"setnull", json_setnull}, | 
| jbe@121 | 654   {NULL, NULL} | 
| jbe@121 | 655 }; | 
| jbe@121 | 656 | 
| jbe@126 | 657 static const struct luaL_Reg json_metatable_functions[] = { | 
| jbe@130 | 658   {"__len", json_len}, | 
| jbe@130 | 659   {"__index", json_index}, | 
| jbe@130 | 660   {"__newindex", json_newindex}, | 
| jbe@135 | 661   {"__pairs", json_pairs}, | 
| jbe@134 | 662   {"__ipairs", json_ipairs}, | 
| jbe@126 | 663   {NULL, NULL} | 
| jbe@126 | 664 }; | 
| jbe@126 | 665 | 
| jbe@121 | 666 int luaopen_json(lua_State *L) { | 
| jbe@126 | 667   lua_settop(L, 0); | 
| jbe@138 | 668   lua_newtable(L);  // library | 
| jbe@138 | 669   lua_newtable(L); | 
| jbe@138 | 670   luaL_setfuncs(L, json_metatable_functions, 0); | 
| jbe@144 | 671   json_regstore(L, unknownmt); | 
| jbe@138 | 672   lua_setfield(L, 1, "ambiguous_mt"); | 
| jbe@138 | 673   lua_newtable(L); | 
| jbe@138 | 674   luaL_setfuncs(L, json_metatable_functions, 0); | 
| jbe@144 | 675   json_regstore(L, objectmt); | 
| jbe@138 | 676   lua_setfield(L, 1, "object_mt"); | 
| jbe@138 | 677   lua_newtable(L); | 
| jbe@138 | 678   luaL_setfuncs(L, json_metatable_functions, 0); | 
| jbe@144 | 679   json_regstore(L, arraymt); | 
| jbe@138 | 680   lua_setfield(L, 1, "array_mt"); | 
| jbe@138 | 681   lua_newtable(L);  // ephemeron table to store shadow tables for each JSON object/array to allow NULL values returned as nil | 
| jbe@138 | 682   lua_newtable(L);  // metatable for ephemeron table | 
| jbe@121 | 683   lua_pushliteral(L, "__mode"); | 
| jbe@121 | 684   lua_pushliteral(L, "k"); | 
| jbe@138 | 685   lua_rawset(L, -3); | 
| jbe@138 | 686   lua_setmetatable(L, -2); | 
| jbe@144 | 687   json_regstore(L, shadowtbl); | 
| jbe@138 | 688   lua_settop(L, 1); | 
| jbe@138 | 689   luaL_setfuncs(L, json_module_functions, 0); | 
| jbe@121 | 690   return 1; | 
| jbe@121 | 691 } |