| 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@126 | 6 #define JSON_UPVAL_NULLS lua_upvalueindex(1) | 
| jbe@126 | 7 #define JSON_UPVAL_METATABLE lua_upvalueindex(2) | 
| jbe@123 | 8 | 
| jbe@124 | 9 #define JSON_STATE_VALUE 0 | 
| jbe@124 | 10 #define JSON_STATE_OBJECT_KEY 1 | 
| jbe@124 | 11 #define JSON_STATE_OBJECT_KEY_TERMINATOR 2 | 
| jbe@124 | 12 #define JSON_STATE_OBJECT_VALUE 3 | 
| jbe@124 | 13 #define JSON_STATE_OBJECT_SEPARATOR 4 | 
| jbe@124 | 14 #define JSON_STATE_ARRAY_VALUE 5 | 
| jbe@124 | 15 #define JSON_STATE_ARRAY_SEPARATOR 6 | 
| jbe@124 | 16 #define JSON_STATE_END 7 | 
| jbe@121 | 17 | 
| jbe@121 | 18 static int json_import(lua_State *L) { | 
| jbe@121 | 19   const char *str; | 
| jbe@121 | 20   size_t total; | 
| jbe@121 | 21   size_t pos = 0; | 
| jbe@121 | 22   size_t level = 0; | 
| jbe@124 | 23   int mode = JSON_STATE_VALUE; | 
| jbe@121 | 24   char c; | 
| jbe@121 | 25   luaL_Buffer luabuf; | 
| jbe@121 | 26   char *cbuf; | 
| jbe@121 | 27   size_t writepos; | 
| jbe@121 | 28   int aryidx; | 
| jbe@121 | 29   lua_settop(L, 1); | 
| jbe@121 | 30   str = lua_tostring(L, 1); | 
| jbe@121 | 31   total = strlen(str); | 
| jbe@121 | 32 json_import_loop: | 
| jbe@121 | 33   while (c = str[pos], c == ' ' || c == '\n' || c == '\r' || c == '\t' || c == '\f') pos++; | 
| jbe@121 | 34   switch (c) { | 
| jbe@121 | 35   case 0: | 
| jbe@124 | 36     if (mode == JSON_STATE_END) return 1; | 
| jbe@121 | 37     json_import_unexpected_eof: | 
| jbe@121 | 38     lua_pushnil(L); | 
| jbe@121 | 39     if (level == 0) lua_pushliteral(L, "Empty string"); | 
| jbe@121 | 40     else lua_pushliteral(L, "Unexpected end of JSON document"); | 
| jbe@121 | 41     return 2; | 
| jbe@121 | 42   case '{': | 
| jbe@124 | 43     if (mode != JSON_STATE_VALUE && mode != JSON_STATE_OBJECT_VALUE && mode != JSON_STATE_ARRAY_VALUE) | 
| jbe@121 | 44       goto json_import_syntax_error; | 
| jbe@121 | 45     pos++; | 
| jbe@125 | 46     lua_newtable(L);  // the actual JSON object | 
| jbe@125 | 47     lua_pushvalue(L, JSON_UPVAL_METATABLE); | 
| jbe@125 | 48     lua_setmetatable(L, -2); | 
| jbe@123 | 49     lua_newtable(L);  // stores set of NULL values | 
| jbe@123 | 50     lua_pushvalue(L, -2); | 
| jbe@123 | 51     lua_pushvalue(L, -2); | 
| jbe@123 | 52     lua_rawset(L, JSON_UPVAL_NULLS); | 
| jbe@124 | 53     mode = JSON_STATE_OBJECT_KEY; | 
| jbe@121 | 54     level++; | 
| jbe@121 | 55     goto json_import_loop; | 
| jbe@121 | 56   case '[': | 
| jbe@124 | 57     if (mode != JSON_STATE_VALUE && mode != JSON_STATE_OBJECT_VALUE && mode != JSON_STATE_ARRAY_VALUE) | 
| jbe@121 | 58       goto json_import_syntax_error; | 
| jbe@121 | 59     pos++; | 
| jbe@125 | 60     lua_newtable(L);  // the actual JSON array | 
| jbe@125 | 61     lua_pushvalue(L, JSON_UPVAL_METATABLE); | 
| jbe@125 | 62     lua_setmetatable(L, -2); | 
| jbe@123 | 63     lua_newtable(L);  // stores set of NULL values | 
| jbe@123 | 64     lua_pushvalue(L, -2); | 
| jbe@123 | 65     lua_pushvalue(L, -2); | 
| jbe@123 | 66     lua_rawset(L, JSON_UPVAL_NULLS); | 
| jbe@121 | 67     lua_pushinteger(L, 0);  // length of array (since it may contain nil's) | 
| jbe@124 | 68     mode = JSON_STATE_ARRAY_VALUE; | 
| jbe@121 | 69     level++; | 
| jbe@121 | 70     goto json_import_loop; | 
| jbe@121 | 71   case '}': | 
| jbe@124 | 72     if (mode != JSON_STATE_OBJECT_KEY && mode != JSON_STATE_OBJECT_SEPARATOR) | 
| jbe@121 | 73       goto json_import_syntax_error; | 
| jbe@121 | 74     goto json_import_close; | 
| jbe@121 | 75   case ']': | 
| jbe@124 | 76     if (mode != JSON_STATE_ARRAY_VALUE && mode != JSON_STATE_ARRAY_SEPARATOR) | 
| jbe@121 | 77       goto json_import_syntax_error; | 
| jbe@126 | 78     lua_pop(L, 1);  // pop length information | 
| jbe@121 | 79   json_import_close: | 
| jbe@121 | 80     pos++; | 
| jbe@123 | 81     lua_pop(L, 1);  // pop table that stores set of NULL values | 
| jbe@121 | 82     if (--level) { | 
| jbe@121 | 83       if (lua_type(L, -2) == LUA_TNUMBER) { | 
| jbe@124 | 84         mode = JSON_STATE_ARRAY_VALUE; | 
| jbe@121 | 85       } else { | 
| jbe@124 | 86         mode = JSON_STATE_OBJECT_VALUE; | 
| jbe@121 | 87       } | 
| jbe@121 | 88       goto json_import_process_value; | 
| jbe@121 | 89     } else { | 
| jbe@124 | 90       mode = JSON_STATE_END; | 
| jbe@121 | 91     } | 
| jbe@121 | 92     goto json_import_loop; | 
| jbe@121 | 93   case ':': | 
| jbe@124 | 94     if (mode != JSON_STATE_OBJECT_KEY_TERMINATOR) | 
| jbe@121 | 95       goto json_import_syntax_error; | 
| jbe@121 | 96     pos++; | 
| jbe@124 | 97     mode = JSON_STATE_OBJECT_VALUE; | 
| jbe@121 | 98     goto json_import_loop; | 
| jbe@121 | 99   case ',': | 
| jbe@124 | 100     if (mode == JSON_STATE_OBJECT_SEPARATOR) { | 
| jbe@124 | 101       mode = JSON_STATE_OBJECT_KEY; | 
| jbe@124 | 102     } else if (mode == JSON_STATE_ARRAY_SEPARATOR) { | 
| jbe@124 | 103       mode = JSON_STATE_ARRAY_VALUE; | 
| jbe@121 | 104     } else { | 
| jbe@121 | 105       goto json_import_syntax_error; | 
| jbe@121 | 106     } | 
| jbe@121 | 107     pos++; | 
| jbe@121 | 108     goto json_import_loop; | 
| jbe@121 | 109   case '"': | 
| jbe@121 | 110     cbuf = luaL_buffinitsize(L, &luabuf, total-pos); | 
| jbe@121 | 111     writepos = 0; | 
| jbe@121 | 112     pos++; | 
| jbe@121 | 113     while ((c = str[pos++]) != '"') { | 
| jbe@121 | 114       if (c == 0) { | 
| jbe@121 | 115         goto json_import_unexpected_eof; | 
| jbe@121 | 116       } else if (c < 32 || c == 127) { | 
| jbe@121 | 117         lua_pushnil(L); | 
| jbe@121 | 118         lua_pushliteral(L, "Unexpected control character in JSON string"); | 
| jbe@121 | 119         return 2; | 
| jbe@121 | 120       } else if (c == '\\') { | 
| jbe@121 | 121         c = str[pos++]; | 
| jbe@121 | 122         switch (c) { | 
| jbe@121 | 123         case 0: | 
| jbe@121 | 124           goto json_import_unexpected_eof; | 
| jbe@121 | 125         case '"': | 
| jbe@121 | 126         case '/': | 
| jbe@121 | 127         case '\\': | 
| jbe@121 | 128           cbuf[writepos++] = c; | 
| jbe@121 | 129           break; | 
| jbe@121 | 130         case 'b': | 
| jbe@121 | 131           cbuf[writepos++] = '\b'; | 
| jbe@121 | 132           break; | 
| jbe@121 | 133         case 'f': | 
| jbe@121 | 134           cbuf[writepos++] = '\f'; | 
| jbe@121 | 135           break; | 
| jbe@121 | 136         case 'n': | 
| jbe@121 | 137           cbuf[writepos++] = '\n'; | 
| jbe@121 | 138           break; | 
| jbe@121 | 139         case 'r': | 
| jbe@121 | 140           cbuf[writepos++] = '\r'; | 
| jbe@121 | 141           break; | 
| jbe@121 | 142         case 't': | 
| jbe@121 | 143           cbuf[writepos++] = '\t'; | 
| jbe@121 | 144           break; | 
| jbe@121 | 145         case 'u': | 
| jbe@121 | 146           lua_pushnil(L); | 
| jbe@121 | 147           lua_pushliteral(L, "JSON unicode escape sequences are not implemented yet");  // TODO | 
| jbe@121 | 148           return 2; | 
| jbe@121 | 149         default: | 
| jbe@121 | 150           lua_pushnil(L); | 
| jbe@121 | 151           lua_pushliteral(L, "Unexpected string escape sequence in JSON document"); | 
| jbe@121 | 152           return 2; | 
| jbe@121 | 153         } | 
| jbe@121 | 154       } else { | 
| jbe@121 | 155         cbuf[writepos++] = c; | 
| jbe@121 | 156       } | 
| jbe@121 | 157     } | 
| jbe@121 | 158     if (!c) goto json_import_unexpected_eof; | 
| jbe@121 | 159     luaL_pushresultsize(&luabuf, writepos); | 
| jbe@121 | 160     goto json_import_process_value; | 
| jbe@121 | 161   } | 
| jbe@122 | 162   if (c == '-' || (c >= '0' && c <= '9')) { | 
| jbe@122 | 163     char *endptr; | 
| jbe@122 | 164     double numval; | 
| jbe@122 | 165     numval = strtod(str+pos, &endptr); | 
| jbe@122 | 166     if (endptr == str+pos) goto json_import_syntax_error; | 
| jbe@122 | 167     pos += endptr - (str+pos); | 
| jbe@122 | 168     lua_pushnumber(L, numval); | 
| jbe@122 | 169   } else if (!strncmp(str+pos, "true", 4)) { | 
| jbe@121 | 170     lua_pushboolean(L, 1); | 
| jbe@121 | 171     pos += 4; | 
| jbe@121 | 172   } else if (!strncmp(str+pos, "false", 5)) { | 
| jbe@121 | 173     lua_pushboolean(L, 0); | 
| jbe@121 | 174     pos += 5; | 
| jbe@121 | 175   } else if (!strncmp(str+pos, "null", 4)) { | 
| jbe@121 | 176     lua_pushnil(L); | 
| jbe@121 | 177     pos += 4; | 
| jbe@121 | 178   } else { | 
| jbe@121 | 179     goto json_import_syntax_error; | 
| jbe@121 | 180   } | 
| jbe@121 | 181 json_import_process_value: | 
| jbe@121 | 182   switch (mode) { | 
| jbe@124 | 183   case JSON_STATE_OBJECT_KEY: | 
| jbe@121 | 184     if (lua_type(L, -1) != LUA_TSTRING) goto json_import_syntax_error; | 
| jbe@124 | 185     mode = JSON_STATE_OBJECT_KEY_TERMINATOR; | 
| jbe@121 | 186     goto json_import_loop; | 
| jbe@124 | 187   case JSON_STATE_OBJECT_VALUE: | 
| jbe@123 | 188     if (lua_isnil(L, -1)) { | 
| jbe@123 | 189       lua_pushvalue(L, -2); | 
| jbe@123 | 190       lua_pushboolean(L, 1); | 
| jbe@123 | 191       lua_rawset(L, -5); | 
| jbe@123 | 192     } | 
| jbe@123 | 193     lua_rawset(L, -4); | 
| jbe@124 | 194     mode = JSON_STATE_OBJECT_SEPARATOR; | 
| jbe@121 | 195     goto json_import_loop; | 
| jbe@124 | 196   case JSON_STATE_ARRAY_VALUE: | 
| jbe@121 | 197     aryidx = lua_tointeger(L, -2) + 1; | 
| jbe@123 | 198     if (lua_isnil(L, -1)) { | 
| jbe@123 | 199       lua_pushinteger(L, aryidx); | 
| jbe@123 | 200       lua_pushboolean(L, 1); | 
| jbe@123 | 201       lua_rawset(L, -5); | 
| jbe@123 | 202     } | 
| jbe@123 | 203     lua_rawseti(L, -4, aryidx); | 
| jbe@121 | 204     lua_pop(L, 1); | 
| jbe@121 | 205     lua_pushinteger(L, aryidx); | 
| jbe@124 | 206     mode = JSON_STATE_ARRAY_SEPARATOR; | 
| jbe@121 | 207     goto json_import_loop; | 
| jbe@124 | 208   case JSON_STATE_VALUE: | 
| jbe@124 | 209     mode = JSON_STATE_END; | 
| jbe@121 | 210     goto json_import_loop; | 
| jbe@121 | 211   } | 
| jbe@121 | 212 json_import_syntax_error: | 
| jbe@121 | 213   lua_pushnil(L); | 
| jbe@121 | 214   lua_pushliteral(L, "Syntax error in JSON document"); | 
| jbe@121 | 215   return 2; | 
| jbe@121 | 216 } | 
| jbe@121 | 217 | 
| jbe@121 | 218 static int json_arylen(lua_State *L) { | 
| jbe@126 | 219   int lower, middle; | 
| jbe@126 | 220   int upper = 1; | 
| jbe@126 | 221   // TODO: check input for valid array! | 
| jbe@121 | 222   lua_settop(L, 1); | 
| jbe@126 | 223   lua_pushvalue(L, 1); | 
| jbe@126 | 224   lua_rawget(L, JSON_UPVAL_NULLS); | 
| jbe@126 | 225   if (lua_isnil(L, -1)) goto json_arylen_default; | 
| jbe@126 | 226   lua_pushnil(L); | 
| jbe@126 | 227   if (!lua_next(L, -2)) goto json_arylen_default; | 
| jbe@126 | 228   lua_pop(L, 2); | 
| jbe@126 | 229   while (1) { | 
| jbe@126 | 230     lua_rawgeti(L, 1, upper); | 
| jbe@126 | 231     if (lua_isnil(L, -1)) { | 
| jbe@126 | 232       // TODO: require metatable set? | 
| jbe@126 | 233       lua_pop(L, 1); | 
| jbe@126 | 234       lua_pushinteger(L, upper); | 
| jbe@126 | 235       lua_rawget(L, 2); | 
| jbe@126 | 236     } | 
| jbe@126 | 237     if (lua_isnil(L, -1)) break; | 
| jbe@126 | 238     lua_pop(L, 1); | 
| jbe@126 | 239     upper *= 2; | 
| jbe@126 | 240     // TODO: avoid integer overflow! | 
| jbe@126 | 241   } | 
| jbe@126 | 242   lua_pop(L, 1); | 
| jbe@126 | 243   lower = upper / 2; | 
| jbe@126 | 244   while (upper - lower > 1) { | 
| jbe@126 | 245     middle = (lower + upper) / 2; | 
| jbe@126 | 246     lua_rawgeti(L, 1, middle); | 
| jbe@126 | 247     if (lua_isnil(L, -1)) { | 
| jbe@126 | 248       // TODO: require metatable set? | 
| jbe@126 | 249       lua_pop(L, 1); | 
| jbe@126 | 250       lua_pushinteger(L, middle); | 
| jbe@126 | 251       lua_rawget(L, 2); | 
| jbe@126 | 252     } | 
| jbe@126 | 253     if (lua_isnil(L, -1)) upper = middle; | 
| jbe@126 | 254     else lower = middle; | 
| jbe@126 | 255     lua_pop(L, 1); | 
| jbe@126 | 256   } | 
| jbe@126 | 257   lua_pushinteger(L, lower); | 
| jbe@126 | 258   return 1; | 
| jbe@126 | 259 json_arylen_default: | 
| jbe@126 | 260   lua_pushinteger(L, lua_rawlen(L, 1)); | 
| jbe@123 | 261   return 1; | 
| jbe@123 | 262 } | 
| jbe@123 | 263 | 
| jbe@123 | 264 static int json_isnull(lua_State *L) { | 
| jbe@126 | 265   // TODO: require metatable set? | 
| jbe@123 | 266   lua_pushvalue(L, 1); | 
| jbe@123 | 267   lua_rawget(L, JSON_UPVAL_NULLS); | 
| jbe@123 | 268   if (lua_isnil(L, -1)) goto json_isnull_false; | 
| jbe@123 | 269   lua_pushvalue(L, 2); | 
| jbe@123 | 270   lua_rawget(L, -2); | 
| jbe@123 | 271   if (!lua_isnil(L, -1)) return 1; | 
| jbe@123 | 272 json_isnull_false: | 
| jbe@123 | 273   lua_pushboolean(L, 0); | 
| jbe@121 | 274   return 1; | 
| jbe@121 | 275 } | 
| jbe@121 | 276 | 
| jbe@121 | 277 static const struct luaL_Reg json_module_functions[] = { | 
| jbe@121 | 278   {"import", json_import}, | 
| jbe@121 | 279   {"arylen", json_arylen}, | 
| jbe@123 | 280   {"isnull", json_isnull}, | 
| jbe@121 | 281   {NULL, NULL} | 
| jbe@121 | 282 }; | 
| jbe@121 | 283 | 
| jbe@126 | 284 static const struct luaL_Reg json_metatable_functions[] = { | 
| jbe@126 | 285   {"__len", json_arylen}, | 
| jbe@126 | 286   {NULL, NULL} | 
| jbe@126 | 287 }; | 
| jbe@126 | 288 | 
| jbe@121 | 289 int luaopen_json(lua_State *L) { | 
| jbe@126 | 290   lua_settop(L, 0); | 
| jbe@126 | 291   lua_newtable(L);  // 1: library table on stack position | 
| jbe@126 | 292   lua_newtable(L);  // 2: ephemeron table to store a set of keys associated with JSON-null values | 
| jbe@126 | 293   lua_newtable(L);  // 3: meta table for ephemeron table | 
| jbe@121 | 294   lua_pushliteral(L, "__mode"); | 
| jbe@121 | 295   lua_pushliteral(L, "k"); | 
| jbe@126 | 296   lua_rawset(L, 3); | 
| jbe@126 | 297   lua_setmetatable(L, 2); | 
| jbe@126 | 298   lua_newtable(L);  // 3: metatable for JSON objects and JSON arrays | 
| jbe@126 | 299   lua_pushvalue(L, 3); | 
| jbe@126 | 300   lua_setfield(L, 1, "metatable"); | 
| jbe@126 | 301   lua_pushvalue(L, 2); | 
| jbe@126 | 302   lua_pushvalue(L, 3); | 
| jbe@126 | 303   luaL_setfuncs(L, json_metatable_functions, 2); | 
| jbe@126 | 304   luaL_setfuncs(L, json_module_functions, 2); | 
| jbe@121 | 305   return 1; | 
| jbe@121 | 306 } |