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