jbe@121: #include jbe@121: #include jbe@122: #include jbe@121: #include jbe@121: jbe@125: #define JSON_UPVAL_METATABLE lua_upvalueindex(1) jbe@123: #define JSON_UPVAL_NULLS lua_upvalueindex(2) jbe@125: #define JSON_UPVAL_ARYLEN lua_upvalueindex(3) jbe@123: jbe@124: #define JSON_STATE_VALUE 0 jbe@124: #define JSON_STATE_OBJECT_KEY 1 jbe@124: #define JSON_STATE_OBJECT_KEY_TERMINATOR 2 jbe@124: #define JSON_STATE_OBJECT_VALUE 3 jbe@124: #define JSON_STATE_OBJECT_SEPARATOR 4 jbe@124: #define JSON_STATE_ARRAY_VALUE 5 jbe@124: #define JSON_STATE_ARRAY_SEPARATOR 6 jbe@124: #define JSON_STATE_END 7 jbe@121: jbe@121: static int json_import(lua_State *L) { jbe@121: const char *str; jbe@121: size_t total; jbe@121: size_t pos = 0; jbe@121: size_t level = 0; jbe@124: int mode = JSON_STATE_VALUE; jbe@121: char c; jbe@121: luaL_Buffer luabuf; jbe@121: char *cbuf; jbe@121: size_t writepos; jbe@121: int aryidx; jbe@121: lua_settop(L, 1); jbe@121: str = lua_tostring(L, 1); jbe@121: total = strlen(str); jbe@121: json_import_loop: jbe@121: while (c = str[pos], c == ' ' || c == '\n' || c == '\r' || c == '\t' || c == '\f') pos++; jbe@121: switch (c) { jbe@121: case 0: jbe@124: if (mode == JSON_STATE_END) return 1; jbe@121: json_import_unexpected_eof: jbe@121: lua_pushnil(L); jbe@121: if (level == 0) lua_pushliteral(L, "Empty string"); jbe@121: else lua_pushliteral(L, "Unexpected end of JSON document"); jbe@121: return 2; jbe@121: case '{': jbe@124: if (mode != JSON_STATE_VALUE && mode != JSON_STATE_OBJECT_VALUE && mode != JSON_STATE_ARRAY_VALUE) jbe@121: goto json_import_syntax_error; jbe@121: pos++; jbe@125: lua_newtable(L); // the actual JSON object jbe@125: lua_pushvalue(L, JSON_UPVAL_METATABLE); jbe@125: lua_setmetatable(L, -2); jbe@123: lua_newtable(L); // stores set of NULL values jbe@123: lua_pushvalue(L, -2); jbe@123: lua_pushvalue(L, -2); jbe@123: lua_rawset(L, JSON_UPVAL_NULLS); jbe@124: mode = JSON_STATE_OBJECT_KEY; jbe@121: level++; jbe@121: goto json_import_loop; jbe@121: case '[': jbe@124: if (mode != JSON_STATE_VALUE && mode != JSON_STATE_OBJECT_VALUE && mode != JSON_STATE_ARRAY_VALUE) jbe@121: goto json_import_syntax_error; jbe@121: pos++; jbe@125: lua_newtable(L); // the actual JSON array jbe@125: lua_pushvalue(L, JSON_UPVAL_METATABLE); jbe@125: lua_setmetatable(L, -2); jbe@123: lua_newtable(L); // stores set of NULL values jbe@123: lua_pushvalue(L, -2); jbe@123: lua_pushvalue(L, -2); jbe@123: lua_rawset(L, JSON_UPVAL_NULLS); jbe@121: lua_pushinteger(L, 0); // length of array (since it may contain nil's) jbe@124: mode = JSON_STATE_ARRAY_VALUE; jbe@121: level++; jbe@121: goto json_import_loop; jbe@121: case '}': jbe@124: if (mode != JSON_STATE_OBJECT_KEY && mode != JSON_STATE_OBJECT_SEPARATOR) jbe@121: goto json_import_syntax_error; jbe@121: goto json_import_close; jbe@121: case ']': jbe@124: if (mode != JSON_STATE_ARRAY_VALUE && mode != JSON_STATE_ARRAY_SEPARATOR) jbe@121: goto json_import_syntax_error; jbe@121: lua_pushvalue(L, -2); // use array table as key jbe@121: lua_insert(L, -2); // use length of array as value jbe@123: lua_rawset(L, JSON_UPVAL_ARYLEN); // store length in ephemeron table jbe@121: // leaves array table on top of stack jbe@121: json_import_close: jbe@121: pos++; jbe@123: lua_pop(L, 1); // pop table that stores set of NULL values jbe@121: if (--level) { jbe@121: if (lua_type(L, -2) == LUA_TNUMBER) { jbe@124: mode = JSON_STATE_ARRAY_VALUE; jbe@121: } else { jbe@124: mode = JSON_STATE_OBJECT_VALUE; jbe@121: } jbe@121: goto json_import_process_value; jbe@121: } else { jbe@124: mode = JSON_STATE_END; jbe@121: } jbe@121: goto json_import_loop; jbe@121: case ':': jbe@124: if (mode != JSON_STATE_OBJECT_KEY_TERMINATOR) jbe@121: goto json_import_syntax_error; jbe@121: pos++; jbe@124: mode = JSON_STATE_OBJECT_VALUE; jbe@121: goto json_import_loop; jbe@121: case ',': jbe@124: if (mode == JSON_STATE_OBJECT_SEPARATOR) { jbe@124: mode = JSON_STATE_OBJECT_KEY; jbe@124: } else if (mode == JSON_STATE_ARRAY_SEPARATOR) { jbe@124: mode = JSON_STATE_ARRAY_VALUE; jbe@121: } else { jbe@121: goto json_import_syntax_error; jbe@121: } jbe@121: pos++; jbe@121: goto json_import_loop; jbe@121: case '"': jbe@121: cbuf = luaL_buffinitsize(L, &luabuf, total-pos); jbe@121: writepos = 0; jbe@121: pos++; jbe@121: while ((c = str[pos++]) != '"') { jbe@121: if (c == 0) { jbe@121: goto json_import_unexpected_eof; jbe@121: } else if (c < 32 || c == 127) { jbe@121: lua_pushnil(L); jbe@121: lua_pushliteral(L, "Unexpected control character in JSON string"); jbe@121: return 2; jbe@121: } else if (c == '\\') { jbe@121: c = str[pos++]; jbe@121: switch (c) { jbe@121: case 0: jbe@121: goto json_import_unexpected_eof; jbe@121: case '"': jbe@121: case '/': jbe@121: case '\\': jbe@121: cbuf[writepos++] = c; jbe@121: break; jbe@121: case 'b': jbe@121: cbuf[writepos++] = '\b'; jbe@121: break; jbe@121: case 'f': jbe@121: cbuf[writepos++] = '\f'; jbe@121: break; jbe@121: case 'n': jbe@121: cbuf[writepos++] = '\n'; jbe@121: break; jbe@121: case 'r': jbe@121: cbuf[writepos++] = '\r'; jbe@121: break; jbe@121: case 't': jbe@121: cbuf[writepos++] = '\t'; jbe@121: break; jbe@121: case 'u': jbe@121: lua_pushnil(L); jbe@121: lua_pushliteral(L, "JSON unicode escape sequences are not implemented yet"); // TODO jbe@121: return 2; jbe@121: default: jbe@121: lua_pushnil(L); jbe@121: lua_pushliteral(L, "Unexpected string escape sequence in JSON document"); jbe@121: return 2; jbe@121: } jbe@121: } else { jbe@121: cbuf[writepos++] = c; jbe@121: } jbe@121: } jbe@121: if (!c) goto json_import_unexpected_eof; jbe@121: luaL_pushresultsize(&luabuf, writepos); jbe@121: goto json_import_process_value; jbe@121: } jbe@122: if (c == '-' || (c >= '0' && c <= '9')) { jbe@122: char *endptr; jbe@122: double numval; jbe@122: numval = strtod(str+pos, &endptr); jbe@122: if (endptr == str+pos) goto json_import_syntax_error; jbe@122: pos += endptr - (str+pos); jbe@122: lua_pushnumber(L, numval); jbe@122: } else if (!strncmp(str+pos, "true", 4)) { jbe@121: lua_pushboolean(L, 1); jbe@121: pos += 4; jbe@121: } else if (!strncmp(str+pos, "false", 5)) { jbe@121: lua_pushboolean(L, 0); jbe@121: pos += 5; jbe@121: } else if (!strncmp(str+pos, "null", 4)) { jbe@121: lua_pushnil(L); jbe@121: pos += 4; jbe@121: } else { jbe@121: goto json_import_syntax_error; jbe@121: } jbe@121: json_import_process_value: jbe@121: switch (mode) { jbe@124: case JSON_STATE_OBJECT_KEY: jbe@121: if (lua_type(L, -1) != LUA_TSTRING) goto json_import_syntax_error; jbe@124: mode = JSON_STATE_OBJECT_KEY_TERMINATOR; jbe@121: goto json_import_loop; jbe@124: case JSON_STATE_OBJECT_VALUE: jbe@123: if (lua_isnil(L, -1)) { jbe@123: lua_pushvalue(L, -2); jbe@123: lua_pushboolean(L, 1); jbe@123: lua_rawset(L, -5); jbe@123: } jbe@123: lua_rawset(L, -4); jbe@124: mode = JSON_STATE_OBJECT_SEPARATOR; jbe@121: goto json_import_loop; jbe@124: case JSON_STATE_ARRAY_VALUE: jbe@121: aryidx = lua_tointeger(L, -2) + 1; jbe@123: if (lua_isnil(L, -1)) { jbe@123: lua_pushinteger(L, aryidx); jbe@123: lua_pushboolean(L, 1); jbe@123: lua_rawset(L, -5); jbe@123: } jbe@123: lua_rawseti(L, -4, aryidx); jbe@121: lua_pop(L, 1); jbe@121: lua_pushinteger(L, aryidx); jbe@124: mode = JSON_STATE_ARRAY_SEPARATOR; jbe@121: goto json_import_loop; jbe@124: case JSON_STATE_VALUE: jbe@124: mode = JSON_STATE_END; jbe@121: goto json_import_loop; jbe@121: } jbe@121: json_import_syntax_error: jbe@121: lua_pushnil(L); jbe@121: lua_pushliteral(L, "Syntax error in JSON document"); jbe@121: return 2; jbe@121: } jbe@121: jbe@121: static int json_arylen(lua_State *L) { jbe@121: lua_settop(L, 1); jbe@123: lua_rawget(L, JSON_UPVAL_ARYLEN); jbe@123: return 1; jbe@123: } jbe@123: jbe@123: static int json_isnull(lua_State *L) { jbe@123: lua_pushvalue(L, 1); jbe@123: lua_rawget(L, JSON_UPVAL_NULLS); jbe@123: if (lua_isnil(L, -1)) goto json_isnull_false; jbe@123: lua_pushvalue(L, 2); jbe@123: lua_rawget(L, -2); jbe@123: if (!lua_isnil(L, -1)) return 1; jbe@123: json_isnull_false: jbe@123: lua_pushboolean(L, 0); jbe@121: return 1; jbe@121: } jbe@121: jbe@121: static const struct luaL_Reg json_module_functions[] = { jbe@121: {"import", json_import}, jbe@121: {"arylen", json_arylen}, jbe@123: {"isnull", json_isnull}, jbe@121: {NULL, NULL} jbe@121: }; jbe@121: jbe@121: int luaopen_json(lua_State *L) { jbe@121: lua_newtable(L); // library table jbe@125: lua_newtable(L); // metatable for JSON objects and JSON arrays jbe@125: lua_pushvalue(L, -1); jbe@125: lua_setfield(L, -3, "metatable"); jbe@125: lua_newtable(L); // ephemeron table to store a set of keys associated with JSON-null values jbe@121: lua_newtable(L); // ephemeron table to store the length of arrays (that may contain nil's) jbe@121: lua_newtable(L); // meta table for ephemeron table jbe@121: lua_pushliteral(L, "__mode"); jbe@121: lua_pushliteral(L, "k"); jbe@121: lua_rawset(L, -3); jbe@123: lua_pushvalue(L, -1); jbe@123: lua_setmetatable(L, -3); jbe@121: lua_setmetatable(L, -2); jbe@125: luaL_setfuncs(L, json_module_functions, 3); jbe@121: return 1; jbe@121: }