webmcp

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

Impressum / About Us