webmcp

annotate libraries/json/json.c @ 130:209f8ce39c5a

Refactored JSON library to use shadow tables with null markers
author jbe
date Sun Jul 27 21:25:26 2014 +0200 (2014-07-27)
parents 453d8f8fbace
children dbe5881e4ecd
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@130 6 #define JSON_UPVAL_NULLMARK lua_upvalueindex(1)
jbe@130 7 #define JSON_UPVAL_SHADOWTBL lua_upvalueindex(2)
jbe@130 8 #define JSON_UPVAL_TYPES lua_upvalueindex(3)
jbe@130 9 #define JSON_UPVAL_METATABLE lua_upvalueindex(4)
jbe@123 10
jbe@124 11 #define JSON_STATE_VALUE 0
jbe@124 12 #define JSON_STATE_OBJECT_KEY 1
jbe@124 13 #define JSON_STATE_OBJECT_KEY_TERMINATOR 2
jbe@124 14 #define JSON_STATE_OBJECT_VALUE 3
jbe@124 15 #define JSON_STATE_OBJECT_SEPARATOR 4
jbe@124 16 #define JSON_STATE_ARRAY_VALUE 5
jbe@124 17 #define JSON_STATE_ARRAY_SEPARATOR 6
jbe@124 18 #define JSON_STATE_END 7
jbe@121 19
jbe@121 20 static int json_import(lua_State *L) {
jbe@121 21 const char *str;
jbe@121 22 size_t total;
jbe@121 23 size_t pos = 0;
jbe@121 24 size_t level = 0;
jbe@124 25 int mode = JSON_STATE_VALUE;
jbe@121 26 char c;
jbe@121 27 luaL_Buffer luabuf;
jbe@121 28 char *cbuf;
jbe@121 29 size_t writepos;
jbe@121 30 lua_settop(L, 1);
jbe@121 31 str = lua_tostring(L, 1);
jbe@121 32 total = strlen(str);
jbe@121 33 json_import_loop:
jbe@121 34 while (c = str[pos], c == ' ' || c == '\n' || c == '\r' || c == '\t' || c == '\f') pos++;
jbe@121 35 switch (c) {
jbe@121 36 case 0:
jbe@124 37 if (mode == JSON_STATE_END) return 1;
jbe@121 38 json_import_unexpected_eof:
jbe@121 39 lua_pushnil(L);
jbe@121 40 if (level == 0) lua_pushliteral(L, "Empty string");
jbe@121 41 else lua_pushliteral(L, "Unexpected end of JSON document");
jbe@121 42 return 2;
jbe@121 43 case '{':
jbe@124 44 if (mode != JSON_STATE_VALUE && mode != JSON_STATE_OBJECT_VALUE && mode != JSON_STATE_ARRAY_VALUE)
jbe@121 45 goto json_import_syntax_error;
jbe@121 46 pos++;
jbe@130 47 lua_newtable(L); // the external JSON object representation
jbe@125 48 lua_pushvalue(L, JSON_UPVAL_METATABLE);
jbe@125 49 lua_setmetatable(L, -2);
jbe@127 50 lua_pushvalue(L, -1);
jbe@127 51 lua_pushliteral(L, "object");
jbe@127 52 lua_rawset(L, JSON_UPVAL_TYPES);
jbe@130 53 lua_newtable(L); // the internal shadow table
jbe@123 54 lua_pushvalue(L, -2);
jbe@123 55 lua_pushvalue(L, -2);
jbe@130 56 lua_rawset(L, JSON_UPVAL_SHADOWTBL);
jbe@124 57 mode = JSON_STATE_OBJECT_KEY;
jbe@121 58 level++;
jbe@121 59 goto json_import_loop;
jbe@121 60 case '[':
jbe@124 61 if (mode != JSON_STATE_VALUE && mode != JSON_STATE_OBJECT_VALUE && mode != JSON_STATE_ARRAY_VALUE)
jbe@121 62 goto json_import_syntax_error;
jbe@121 63 pos++;
jbe@130 64 lua_newtable(L); // the external JSON array representation
jbe@125 65 lua_pushvalue(L, JSON_UPVAL_METATABLE);
jbe@125 66 lua_setmetatable(L, -2);
jbe@127 67 lua_pushvalue(L, -1);
jbe@127 68 lua_pushliteral(L, "array");
jbe@127 69 lua_rawset(L, JSON_UPVAL_TYPES);
jbe@130 70 lua_newtable(L); // the internal shadow table
jbe@123 71 lua_pushvalue(L, -2);
jbe@123 72 lua_pushvalue(L, -2);
jbe@130 73 lua_rawset(L, JSON_UPVAL_SHADOWTBL);
jbe@130 74 lua_pushinteger(L, 0); // magic integer to indicate an array
jbe@124 75 mode = JSON_STATE_ARRAY_VALUE;
jbe@121 76 level++;
jbe@121 77 goto json_import_loop;
jbe@121 78 case '}':
jbe@124 79 if (mode != JSON_STATE_OBJECT_KEY && mode != JSON_STATE_OBJECT_SEPARATOR)
jbe@121 80 goto json_import_syntax_error;
jbe@121 81 goto json_import_close;
jbe@121 82 case ']':
jbe@124 83 if (mode != JSON_STATE_ARRAY_VALUE && mode != JSON_STATE_ARRAY_SEPARATOR)
jbe@121 84 goto json_import_syntax_error;
jbe@130 85 lua_pop(L, 1); // pop magic integer
jbe@121 86 json_import_close:
jbe@121 87 pos++;
jbe@130 88 lua_pop(L, 1); // pop shadow table
jbe@121 89 if (--level) {
jbe@121 90 if (lua_type(L, -2) == LUA_TNUMBER) {
jbe@124 91 mode = JSON_STATE_ARRAY_VALUE;
jbe@121 92 } else {
jbe@124 93 mode = JSON_STATE_OBJECT_VALUE;
jbe@121 94 }
jbe@121 95 goto json_import_process_value;
jbe@121 96 } else {
jbe@124 97 mode = JSON_STATE_END;
jbe@121 98 }
jbe@121 99 goto json_import_loop;
jbe@121 100 case ':':
jbe@124 101 if (mode != JSON_STATE_OBJECT_KEY_TERMINATOR)
jbe@121 102 goto json_import_syntax_error;
jbe@121 103 pos++;
jbe@124 104 mode = JSON_STATE_OBJECT_VALUE;
jbe@121 105 goto json_import_loop;
jbe@121 106 case ',':
jbe@124 107 if (mode == JSON_STATE_OBJECT_SEPARATOR) {
jbe@124 108 mode = JSON_STATE_OBJECT_KEY;
jbe@124 109 } else if (mode == JSON_STATE_ARRAY_SEPARATOR) {
jbe@124 110 mode = JSON_STATE_ARRAY_VALUE;
jbe@121 111 } else {
jbe@121 112 goto json_import_syntax_error;
jbe@121 113 }
jbe@121 114 pos++;
jbe@121 115 goto json_import_loop;
jbe@121 116 case '"':
jbe@121 117 cbuf = luaL_buffinitsize(L, &luabuf, total-pos);
jbe@121 118 writepos = 0;
jbe@121 119 pos++;
jbe@121 120 while ((c = str[pos++]) != '"') {
jbe@121 121 if (c == 0) {
jbe@121 122 goto json_import_unexpected_eof;
jbe@121 123 } else if (c < 32 || c == 127) {
jbe@121 124 lua_pushnil(L);
jbe@121 125 lua_pushliteral(L, "Unexpected control character in JSON string");
jbe@121 126 return 2;
jbe@121 127 } else if (c == '\\') {
jbe@121 128 c = str[pos++];
jbe@121 129 switch (c) {
jbe@121 130 case 0:
jbe@121 131 goto json_import_unexpected_eof;
jbe@121 132 case '"':
jbe@121 133 case '/':
jbe@121 134 case '\\':
jbe@121 135 cbuf[writepos++] = c;
jbe@121 136 break;
jbe@121 137 case 'b':
jbe@121 138 cbuf[writepos++] = '\b';
jbe@121 139 break;
jbe@121 140 case 'f':
jbe@121 141 cbuf[writepos++] = '\f';
jbe@121 142 break;
jbe@121 143 case 'n':
jbe@121 144 cbuf[writepos++] = '\n';
jbe@121 145 break;
jbe@121 146 case 'r':
jbe@121 147 cbuf[writepos++] = '\r';
jbe@121 148 break;
jbe@121 149 case 't':
jbe@121 150 cbuf[writepos++] = '\t';
jbe@121 151 break;
jbe@121 152 case 'u':
jbe@121 153 lua_pushnil(L);
jbe@121 154 lua_pushliteral(L, "JSON unicode escape sequences are not implemented yet"); // TODO
jbe@121 155 return 2;
jbe@121 156 default:
jbe@121 157 lua_pushnil(L);
jbe@121 158 lua_pushliteral(L, "Unexpected string escape sequence in JSON document");
jbe@121 159 return 2;
jbe@121 160 }
jbe@121 161 } else {
jbe@121 162 cbuf[writepos++] = c;
jbe@121 163 }
jbe@121 164 }
jbe@121 165 if (!c) goto json_import_unexpected_eof;
jbe@121 166 luaL_pushresultsize(&luabuf, writepos);
jbe@121 167 goto json_import_process_value;
jbe@121 168 }
jbe@122 169 if (c == '-' || (c >= '0' && c <= '9')) {
jbe@122 170 char *endptr;
jbe@122 171 double numval;
jbe@122 172 numval = strtod(str+pos, &endptr);
jbe@122 173 if (endptr == str+pos) goto json_import_syntax_error;
jbe@122 174 pos += endptr - (str+pos);
jbe@122 175 lua_pushnumber(L, numval);
jbe@122 176 } else if (!strncmp(str+pos, "true", 4)) {
jbe@121 177 lua_pushboolean(L, 1);
jbe@121 178 pos += 4;
jbe@121 179 } else if (!strncmp(str+pos, "false", 5)) {
jbe@121 180 lua_pushboolean(L, 0);
jbe@121 181 pos += 5;
jbe@121 182 } else if (!strncmp(str+pos, "null", 4)) {
jbe@130 183 lua_pushvalue(L, JSON_UPVAL_NULLMARK);
jbe@121 184 pos += 4;
jbe@121 185 } else {
jbe@121 186 goto json_import_syntax_error;
jbe@121 187 }
jbe@121 188 json_import_process_value:
jbe@121 189 switch (mode) {
jbe@124 190 case JSON_STATE_OBJECT_KEY:
jbe@121 191 if (lua_type(L, -1) != LUA_TSTRING) goto json_import_syntax_error;
jbe@124 192 mode = JSON_STATE_OBJECT_KEY_TERMINATOR;
jbe@121 193 goto json_import_loop;
jbe@124 194 case JSON_STATE_OBJECT_VALUE:
jbe@130 195 lua_rawset(L, -3);
jbe@124 196 mode = JSON_STATE_OBJECT_SEPARATOR;
jbe@121 197 goto json_import_loop;
jbe@124 198 case JSON_STATE_ARRAY_VALUE:
jbe@130 199 lua_rawseti(L, -3, lua_rawlen(L, -3) + 1);
jbe@124 200 mode = JSON_STATE_ARRAY_SEPARATOR;
jbe@121 201 goto json_import_loop;
jbe@124 202 case JSON_STATE_VALUE:
jbe@124 203 mode = JSON_STATE_END;
jbe@121 204 goto json_import_loop;
jbe@121 205 }
jbe@121 206 json_import_syntax_error:
jbe@121 207 lua_pushnil(L);
jbe@121 208 lua_pushliteral(L, "Syntax error in JSON document");
jbe@121 209 return 2;
jbe@121 210 }
jbe@121 211
jbe@130 212 #define JSON_PATH_GET 1
jbe@130 213 #define JSON_PATH_TYPE 2
jbe@130 214 #define JSON_PATH_ISNULL 3
jbe@130 215
jbe@130 216 static int json_path(lua_State *L, int mode) {
jbe@130 217 int argc;
jbe@130 218 int idx = 2;
jbe@130 219 argc = lua_gettop(L);
jbe@126 220 lua_pushvalue(L, 1);
jbe@130 221 while (idx <= argc) {
jbe@130 222 lua_pushvalue(L, -1);
jbe@130 223 lua_rawget(L, JSON_UPVAL_SHADOWTBL);
jbe@126 224 if (lua_isnil(L, -1)) {
jbe@126 225 lua_pop(L, 1);
jbe@130 226 if (lua_type(L, -1) == LUA_TTABLE) {
jbe@130 227 lua_pushvalue(L, idx++);
jbe@130 228 lua_gettable(L, -2);
jbe@130 229 } else {
jbe@130 230 lua_pushnil(L);
jbe@130 231 }
jbe@130 232 } else {
jbe@130 233 lua_replace(L, -2);
jbe@130 234 lua_pushvalue(L, idx++);
jbe@130 235 lua_rawget(L, -2);
jbe@126 236 }
jbe@130 237 if (lua_isnil(L, -1)) {
jbe@130 238 if (mode == JSON_PATH_ISNULL) lua_pushboolean(L, 0);
jbe@130 239 return 1;
jbe@130 240 }
jbe@130 241 lua_replace(L, -2);
jbe@126 242 }
jbe@130 243 switch (mode) {
jbe@130 244 case JSON_PATH_GET:
jbe@130 245 if (lua_rawequal(L, -1, JSON_UPVAL_NULLMARK)) lua_pushnil(L);
jbe@130 246 return 1;
jbe@130 247 case JSON_PATH_TYPE:
jbe@130 248 if (lua_rawequal(L, -1, JSON_UPVAL_NULLMARK)) {
jbe@130 249 lua_pushliteral(L, "null");
jbe@130 250 return 1;
jbe@126 251 }
jbe@130 252 lua_pushvalue(L, -1);
jbe@130 253 lua_rawget(L, JSON_UPVAL_TYPES);
jbe@130 254 if (lua_isnil(L, -1)) lua_pushstring(L, lua_typename(L, lua_type(L, -2)));
jbe@130 255 return 1;
jbe@130 256 case JSON_PATH_ISNULL:
jbe@130 257 lua_pushboolean(L, lua_rawequal(L, -1, JSON_UPVAL_NULLMARK));
jbe@130 258 return 1;
jbe@126 259 }
jbe@130 260 return 0;
jbe@130 261 }
jbe@130 262
jbe@130 263 static int json_get(lua_State *L) {
jbe@130 264 return json_path(L, JSON_PATH_GET);
jbe@130 265 }
jbe@130 266
jbe@130 267 static int json_type(lua_State *L) {
jbe@130 268 return json_path(L, JSON_PATH_TYPE);
jbe@130 269 }
jbe@130 270
jbe@130 271 static int json_isnull(lua_State *L) {
jbe@130 272 return json_path(L, JSON_PATH_ISNULL);
jbe@130 273 }
jbe@130 274
jbe@130 275 static int json_len(lua_State *L) {
jbe@130 276 lua_settop(L, 1);
jbe@130 277 lua_pushvalue(L, 1);
jbe@130 278 lua_rawget(L, JSON_UPVAL_SHADOWTBL);
jbe@130 279 if (lua_isnil(L, -1)) lua_pop(L, 1);
jbe@130 280 lua_pushinteger(L, lua_rawlen(L, -1));
jbe@123 281 return 1;
jbe@123 282 }
jbe@123 283
jbe@130 284 static int json_index(lua_State *L) {
jbe@130 285 lua_settop(L, 2);
jbe@130 286 lua_pushvalue(L, 1);
jbe@130 287 lua_rawget(L, JSON_UPVAL_SHADOWTBL);
jbe@130 288 if (lua_isnil(L, -1)) return 1;
jbe@130 289 lua_pushvalue(L, 2);
jbe@130 290 lua_rawget(L, -2);
jbe@130 291 if (lua_rawequal(L, -1, JSON_UPVAL_NULLMARK)) lua_pushnil(L);
jbe@127 292 return 1;
jbe@127 293 }
jbe@127 294
jbe@130 295 static int json_newindex(lua_State *L) {
jbe@130 296 lua_settop(L, 3);
jbe@123 297 lua_pushvalue(L, 1);
jbe@130 298 lua_rawget(L, JSON_UPVAL_SHADOWTBL);
jbe@130 299 if (lua_isnil(L, -1)) return luaL_error(L, "Shadow table not found");
jbe@130 300 lua_replace(L, 1);
jbe@130 301 lua_rawset(L, 1);
jbe@121 302 return 1;
jbe@121 303 }
jbe@121 304
jbe@121 305 static const struct luaL_Reg json_module_functions[] = {
jbe@121 306 {"import", json_import},
jbe@130 307 {"get", json_get},
jbe@127 308 {"type", json_type},
jbe@123 309 {"isnull", json_isnull},
jbe@121 310 {NULL, NULL}
jbe@121 311 };
jbe@121 312
jbe@126 313 static const struct luaL_Reg json_metatable_functions[] = {
jbe@130 314 {"__len", json_len},
jbe@130 315 {"__index", json_index},
jbe@130 316 {"__newindex", json_newindex},
jbe@126 317 {NULL, NULL}
jbe@126 318 };
jbe@126 319
jbe@121 320 int luaopen_json(lua_State *L) {
jbe@126 321 lua_settop(L, 0);
jbe@126 322 lua_newtable(L); // 1: library table on stack position
jbe@130 323 lua_newtable(L); // 2: table used as JSON NULL value in internal shadow tables
jbe@130 324 lua_newtable(L); // 3: ephemeron table to store shadow tables for each JSON object/array to allow NULL values returned as nil
jbe@130 325 lua_newtable(L); // 4: ephemeron table to store the type of the JSON object/array
jbe@130 326 lua_newtable(L); // 5: metatable for ephemeron tables
jbe@121 327 lua_pushliteral(L, "__mode");
jbe@121 328 lua_pushliteral(L, "k");
jbe@130 329 lua_rawset(L, 5);
jbe@130 330 lua_pushvalue(L, 5); // 6: cloned metatable reference
jbe@127 331 lua_setmetatable(L, 3);
jbe@130 332 lua_setmetatable(L, 4);
jbe@130 333 lua_newtable(L); // 5: metatable for JSON objects and JSON arrays
jbe@130 334 lua_pushvalue(L, 5);
jbe@126 335 lua_setfield(L, 1, "metatable");
jbe@126 336 lua_pushvalue(L, 2);
jbe@126 337 lua_pushvalue(L, 3);
jbe@127 338 lua_pushvalue(L, 4);
jbe@130 339 lua_pushvalue(L, 5);
jbe@130 340 luaL_setfuncs(L, json_metatable_functions, 4);
jbe@130 341 luaL_setfuncs(L, json_module_functions, 4);
jbe@121 342 return 1;
jbe@121 343 }

Impressum / About Us