webmcp
changeset 136:3cf5fcf2bd5f
Some code cleanup and documentation of JSON library
author | jbe |
---|---|
date | Mon Jul 28 17:16:34 2014 +0200 (2014-07-28) |
parents | 663722e35330 |
children | f490b78827d6 |
files | libraries/json/json.c |
line diff
1.1 --- a/libraries/json/json.c Mon Jul 28 03:00:40 2014 +0200 1.2 +++ b/libraries/json/json.c Mon Jul 28 17:16:34 2014 +0200 1.3 @@ -10,6 +10,47 @@ 1.4 #define JSON_UPVAL_PAIRS_ITERFUNC lua_upvalueindex(5) 1.5 #define JSON_UPVAL_IPAIRS_ITERFUNC lua_upvalueindex(6) 1.6 1.7 +// marks a table as JSON object or JSON array: 1.8 +// (returns its modified argument or a new table if argument is nil) 1.9 +static int json_mark(lua_State *L, const char *marker) { 1.10 + // if argument is nil, then create new table: 1.11 + if (lua_isnoneornil(L, 1)) { 1.12 + lua_settop(L, 0); 1.13 + lua_newtable(L); 1.14 + // skip testing of existing shadow table: 1.15 + goto json_object_create_shadow_table; 1.16 + } 1.17 + lua_pushvalue(L, 1); 1.18 + lua_rawget(L, JSON_UPVAL_SHADOWTBL); 1.19 + if (lua_isnil(L, -1)) { 1.20 + json_object_create_shadow_table: 1.21 + // set shadow table: 1.22 + lua_pushvalue(L, 1); 1.23 + lua_newtable(L); 1.24 + lua_rawset(L, JSON_UPVAL_SHADOWTBL); 1.25 + } 1.26 + // set metatable: 1.27 + lua_pushvalue(L, JSON_UPVAL_METATABLE); 1.28 + lua_setmetatable(L, 1); 1.29 + // mark table as JSON object or as JSON array 1.30 + lua_pushvalue(L, 1); 1.31 + lua_pushstring(L, marker); 1.32 + lua_rawset(L, JSON_UPVAL_TYPES); 1.33 + return 1; 1.34 +} 1.35 + 1.36 +// marks a table as JSON object: 1.37 +// (returns its modified argument or a new table if argument is nil) 1.38 +static int json_object(lua_State *L) { 1.39 + return json_mark(L, "object"); 1.40 +} 1.41 + 1.42 +// marks a table as JSON array: 1.43 +// (returns its modified argument or a new table if argument is nil) 1.44 +static int json_array(lua_State *L) { 1.45 + return json_mark(L, "array"); 1.46 +} 1.47 + 1.48 #define JSON_STATE_VALUE 0 1.49 #define JSON_STATE_OBJECT_KEY 1 1.50 #define JSON_STATE_OBJECT_KEY_TERMINATOR 2 1.51 @@ -19,190 +60,231 @@ 1.52 #define JSON_STATE_ARRAY_SEPARATOR 6 1.53 #define JSON_STATE_END 7 1.54 1.55 -static int json_object(lua_State *L) { 1.56 - lua_settop(L, 1); 1.57 - if (lua_isnil(L, 1)) { 1.58 - lua_settop(L, 0); 1.59 - lua_newtable(L); 1.60 - } 1.61 - lua_pushvalue(L, JSON_UPVAL_METATABLE); 1.62 - lua_setmetatable(L, 1); 1.63 - lua_pushvalue(L, 1); 1.64 - lua_newtable(L); // internal shadow table 1.65 - lua_rawset(L, JSON_UPVAL_SHADOWTBL); 1.66 - lua_pushvalue(L, 1); 1.67 - lua_pushliteral(L, "object"); 1.68 - lua_rawset(L, JSON_UPVAL_TYPES); 1.69 - return 1; 1.70 -} 1.71 - 1.72 -static int json_array(lua_State *L) { 1.73 - lua_settop(L, 1); 1.74 - if (lua_isnil(L, 1)) { 1.75 - lua_settop(L, 0); 1.76 - lua_newtable(L); 1.77 - } 1.78 - lua_pushvalue(L, JSON_UPVAL_METATABLE); 1.79 - lua_setmetatable(L, 1); 1.80 - lua_pushvalue(L, 1); 1.81 - lua_newtable(L); // internal shadow table 1.82 - lua_rawset(L, JSON_UPVAL_SHADOWTBL); 1.83 - lua_pushvalue(L, 1); 1.84 - lua_pushliteral(L, "array"); 1.85 - lua_rawset(L, JSON_UPVAL_TYPES); 1.86 - return 1; 1.87 -} 1.88 - 1.89 +// decodes a JSON document: 1.90 static int json_import(lua_State *L) { 1.91 - const char *str; 1.92 - size_t total; 1.93 - size_t pos = 0; 1.94 - size_t level = 0; 1.95 - int mode = JSON_STATE_VALUE; 1.96 - char c; 1.97 - luaL_Buffer luabuf; 1.98 - char *cbuf; 1.99 - size_t writepos; 1.100 - lua_settop(L, 1); 1.101 - str = lua_tostring(L, 1); 1.102 - total = strlen(str); 1.103 -json_import_loop: 1.104 + const char *str; // string to parse 1.105 + size_t total; // total length of string to parse 1.106 + size_t pos = 0; // current position in string to parse 1.107 + size_t level = 0; // nested levels of objects/arrays currently being processed 1.108 + int mode = JSON_STATE_VALUE; // state of parser 1.109 + char c; // variable to store a single character to be processed 1.110 + luaL_Buffer luabuf; // Lua buffer to decode (possibly escaped) strings 1.111 + char *cbuf; // C buffer to decode (possibly escaped) strings 1.112 + size_t writepos; // write position of decoded strings in C buffer 1.113 + // require string as first argument: 1.114 + str = luaL_checklstring(L, 1, &total); 1.115 + // if string contains a NULL byte, this is a syntax error 1.116 + if (strlen(str) != total) goto json_import_syntax_error; 1.117 + // main loop of parser: 1.118 + json_import_loop: 1.119 + // skip whitespace and store next character in variable 'c': 1.120 while (c = str[pos], c == ' ' || c == '\n' || c == '\r' || c == '\t' || c == '\f') pos++; 1.121 + // switch statement to handle certain (single) characters: 1.122 switch (c) { 1.123 + // handle end of JSON document: 1.124 case 0: 1.125 + // if end of JSON document was expected, then return top element of stack as result: 1.126 if (mode == JSON_STATE_END) return 1; 1.127 + // otherwise, the JSON document was malformed: 1.128 json_import_unexpected_eof: 1.129 lua_pushnil(L); 1.130 if (level == 0) lua_pushliteral(L, "Empty string"); 1.131 else lua_pushliteral(L, "Unexpected end of JSON document"); 1.132 return 2; 1.133 + // new JSON object: 1.134 case '{': 1.135 + // if a JSON object is not expected here, then return an error: 1.136 if (mode != JSON_STATE_VALUE && mode != JSON_STATE_OBJECT_VALUE && mode != JSON_STATE_ARRAY_VALUE) 1.137 goto json_import_syntax_error; 1.138 + // consume input character: 1.139 pos++; 1.140 - lua_newtable(L); // the external JSON object representation 1.141 + // create JSON object on stack: 1.142 + lua_newtable(L); 1.143 + // set metatable of JSON object: 1.144 lua_pushvalue(L, JSON_UPVAL_METATABLE); 1.145 lua_setmetatable(L, -2); 1.146 + // mark JSON object as JSON object (as opposed to JSON array): 1.147 lua_pushvalue(L, -1); 1.148 lua_pushliteral(L, "object"); 1.149 lua_rawset(L, JSON_UPVAL_TYPES); 1.150 - lua_newtable(L); // the internal shadow table 1.151 + // create internal shadow table on stack: 1.152 + lua_newtable(L); 1.153 + // register internal shadow table: 1.154 lua_pushvalue(L, -2); 1.155 lua_pushvalue(L, -2); 1.156 lua_rawset(L, JSON_UPVAL_SHADOWTBL); 1.157 - mode = JSON_STATE_OBJECT_KEY; 1.158 + // increment level: 1.159 level++; 1.160 + // expect object key (or end of object) and continue with loop: 1.161 + mode = JSON_STATE_OBJECT_KEY; 1.162 goto json_import_loop; 1.163 + // new JSON array: 1.164 case '[': 1.165 + // if a JSON array is not expected here, then return an error: 1.166 if (mode != JSON_STATE_VALUE && mode != JSON_STATE_OBJECT_VALUE && mode != JSON_STATE_ARRAY_VALUE) 1.167 goto json_import_syntax_error; 1.168 + // consume input character: 1.169 pos++; 1.170 - lua_newtable(L); // the external JSON array representation 1.171 + // create JSON array on stack: 1.172 + lua_newtable(L); 1.173 + // set metatable of JSON array: 1.174 lua_pushvalue(L, JSON_UPVAL_METATABLE); 1.175 lua_setmetatable(L, -2); 1.176 + // mark JSON array as JSON array (as opposed to JSON object): 1.177 lua_pushvalue(L, -1); 1.178 lua_pushliteral(L, "array"); 1.179 lua_rawset(L, JSON_UPVAL_TYPES); 1.180 - lua_newtable(L); // the internal shadow table 1.181 + // create internal shadow table on stack: 1.182 + lua_newtable(L); 1.183 + // register internal shadow table: 1.184 lua_pushvalue(L, -2); 1.185 lua_pushvalue(L, -2); 1.186 lua_rawset(L, JSON_UPVAL_SHADOWTBL); 1.187 - lua_pushinteger(L, 0); // magic integer to indicate an array 1.188 - mode = JSON_STATE_ARRAY_VALUE; 1.189 + // increment level: 1.190 level++; 1.191 + // expect array value (or end of array) and continue with loop: 1.192 + mode = JSON_STATE_ARRAY_VALUE; 1.193 goto json_import_loop; 1.194 + // end of JSON object: 1.195 case '}': 1.196 + // if end of JSON object is not expected here, then return an error: 1.197 if (mode != JSON_STATE_OBJECT_KEY && mode != JSON_STATE_OBJECT_SEPARATOR) 1.198 goto json_import_syntax_error; 1.199 + // jump to common code for end of JSON object and JSON array: 1.200 goto json_import_close; 1.201 + // end of JSON array: 1.202 case ']': 1.203 + // if end of JSON array is not expected here, then return an error: 1.204 if (mode != JSON_STATE_ARRAY_VALUE && mode != JSON_STATE_ARRAY_SEPARATOR) 1.205 goto json_import_syntax_error; 1.206 - lua_pop(L, 1); // pop magic integer 1.207 + // continue with common code for end of JSON object and JSON array: 1.208 + // common code for end of JSON object or JSON array: 1.209 json_import_close: 1.210 + // consume input character: 1.211 pos++; 1.212 - lua_pop(L, 1); // pop shadow table 1.213 + // pop shadow table: 1.214 + lua_pop(L, 1); 1.215 + // check if nested: 1.216 if (--level) { 1.217 - if (lua_type(L, -2) == LUA_TNUMBER) { 1.218 + // if nested, then check if outer(!) structure is an array or object: 1.219 + lua_pushvalue(L, c == '}' ? -4 : -3); 1.220 + lua_rawget(L, JSON_UPVAL_TYPES); 1.221 + if (lua_tostring(L, -1)[0] == 'a') { 1.222 + // select array value processing: 1.223 mode = JSON_STATE_ARRAY_VALUE; 1.224 } else { 1.225 + // select object value processing: 1.226 mode = JSON_STATE_OBJECT_VALUE; 1.227 } 1.228 + // pop JSON type from stack (from rawget JSON_UPVAL_TYPES): 1.229 + lua_pop(L, 1); 1.230 + // store value in outer structure: 1.231 goto json_import_process_value; 1.232 - } else { 1.233 - mode = JSON_STATE_END; 1.234 } 1.235 + // if not nested, then expect end of JSON document and continue with loop: 1.236 + mode = JSON_STATE_END; 1.237 goto json_import_loop; 1.238 + // key terminator: 1.239 case ':': 1.240 + // if key terminator is not expected here, then return an error: 1.241 if (mode != JSON_STATE_OBJECT_KEY_TERMINATOR) 1.242 goto json_import_syntax_error; 1.243 + // consume input character: 1.244 pos++; 1.245 + // set state of parser and continue with loop: 1.246 mode = JSON_STATE_OBJECT_VALUE; 1.247 goto json_import_loop; 1.248 + // value terminator (NOTE: trailing comma at end of value or key-value list is tolerated by this parser) 1.249 case ',': 1.250 + // change parser state accordingly: 1.251 if (mode == JSON_STATE_OBJECT_SEPARATOR) { 1.252 mode = JSON_STATE_OBJECT_KEY; 1.253 } else if (mode == JSON_STATE_ARRAY_SEPARATOR) { 1.254 mode = JSON_STATE_ARRAY_VALUE; 1.255 } else { 1.256 - goto json_import_syntax_error; 1.257 + // if value terminator is not expected here, then return an error: 1.258 + goto json_import_syntax_error; 1.259 } 1.260 + // consume input character: 1.261 pos++; 1.262 + // continue with loop: 1.263 goto json_import_loop; 1.264 + // string literal: 1.265 case '"': 1.266 + // prepare buffer to decode string (with maximum possible length) and set write position to zero: 1.267 cbuf = luaL_buffinitsize(L, &luabuf, total-pos); 1.268 writepos = 0; 1.269 + // consume quote character: 1.270 pos++; 1.271 + // read next character until encountering end quote: 1.272 while ((c = str[pos++]) != '"') { 1.273 if (c == 0) { 1.274 + // handle unexpected end-of-string: 1.275 goto json_import_unexpected_eof; 1.276 } else if (c < 32 || c == 127) { 1.277 + // do not allow ASCII control characters: 1.278 + // NOTE: illegal UTF-8 sequences and extended control characters are not sanitized 1.279 + // by this parser to allow different encodings than Unicode 1.280 lua_pushnil(L); 1.281 lua_pushliteral(L, "Unexpected control character in JSON string"); 1.282 return 2; 1.283 } else if (c == '\\') { 1.284 + // read next char after backslash escape: 1.285 c = str[pos++]; 1.286 switch (c) { 1.287 + // unexpected end-of-string: 1.288 case 0: 1.289 goto json_import_unexpected_eof; 1.290 + // unescaping of quotation mark, slash, and backslash: 1.291 case '"': 1.292 case '/': 1.293 case '\\': 1.294 cbuf[writepos++] = c; 1.295 break; 1.296 + // unescaping of backspace: 1.297 case 'b': 1.298 cbuf[writepos++] = '\b'; 1.299 break; 1.300 + // unescaping of form-feed: 1.301 case 'f': 1.302 cbuf[writepos++] = '\f'; 1.303 break; 1.304 + // unescaping of new-line: 1.305 case 'n': 1.306 cbuf[writepos++] = '\n'; 1.307 break; 1.308 + // unescaping of carriage-return: 1.309 case 'r': 1.310 cbuf[writepos++] = '\r'; 1.311 break; 1.312 + // unescaping of tabulator: 1.313 case 't': 1.314 cbuf[writepos++] = '\t'; 1.315 break; 1.316 + // unescaping of UTF-16 characters 1.317 case 'u': 1.318 lua_pushnil(L); 1.319 lua_pushliteral(L, "JSON unicode escape sequences are not implemented yet"); // TODO 1.320 return 2; 1.321 + // unexpected escape sequence: 1.322 default: 1.323 lua_pushnil(L); 1.324 lua_pushliteral(L, "Unexpected string escape sequence in JSON document"); 1.325 return 2; 1.326 } 1.327 } else { 1.328 + // normal character: 1.329 cbuf[writepos++] = c; 1.330 } 1.331 } 1.332 - if (!c) goto json_import_unexpected_eof; 1.333 + // process buffer to Lua string: 1.334 luaL_pushresultsize(&luabuf, writepos); 1.335 + // continue with processing of decoded string: 1.336 goto json_import_process_value; 1.337 } 1.338 - if (c == '-' || (c >= '0' && c <= '9')) { 1.339 + // process values whose type is is not deducible from a single character: 1.340 + if ((c >= '0' && c <= '9') || c == '-' || c == '+') { 1.341 + // numbers: 1.342 char *endptr; 1.343 double numval; 1.344 numval = strtod(str+pos, &endptr); 1.345 @@ -210,36 +292,56 @@ 1.346 pos += endptr - (str+pos); 1.347 lua_pushnumber(L, numval); 1.348 } else if (!strncmp(str+pos, "true", 4)) { 1.349 - lua_pushboolean(L, 1); 1.350 + // consume 4 input characters for "true": 1.351 pos += 4; 1.352 + // put Lua true value on stack: 1.353 + lua_pushboolean(L, 1); 1.354 } else if (!strncmp(str+pos, "false", 5)) { 1.355 - lua_pushboolean(L, 0); 1.356 + // consume 5 input characters for "false": 1.357 pos += 5; 1.358 + // put Lua false value on stack: 1.359 + lua_pushboolean(L, 0); 1.360 } else if (!strncmp(str+pos, "null", 4)) { 1.361 + // consume 4 input characters for "null": 1.362 + pos += 4; 1.363 + // put special null-marker on stack: 1.364 lua_pushvalue(L, JSON_UPVAL_NULLMARK); 1.365 - pos += 4; 1.366 } else { 1.367 + // all other cases are a syntax error: 1.368 goto json_import_syntax_error; 1.369 } 1.370 -json_import_process_value: 1.371 + // process a decoded value or key value pair (expected on top of Lua stack): 1.372 + json_import_process_value: 1.373 switch (mode) { 1.374 + // an object key has been read: 1.375 case JSON_STATE_OBJECT_KEY: 1.376 + // if an object key is not a string, then this is a syntax error: 1.377 if (lua_type(L, -1) != LUA_TSTRING) goto json_import_syntax_error; 1.378 + // expect key terminator and continue with loop: 1.379 mode = JSON_STATE_OBJECT_KEY_TERMINATOR; 1.380 goto json_import_loop; 1.381 + // a key value pair has been read: 1.382 case JSON_STATE_OBJECT_VALUE: 1.383 + // store key value pair in outer shadow table: 1.384 lua_rawset(L, -3); 1.385 + // expect value terminator (or end of object) and continue with loop: 1.386 mode = JSON_STATE_OBJECT_SEPARATOR; 1.387 goto json_import_loop; 1.388 + // an array value has been read: 1.389 case JSON_STATE_ARRAY_VALUE: 1.390 - lua_rawseti(L, -3, lua_rawlen(L, -3) + 1); 1.391 + // store value in outer shadow table: 1.392 + lua_rawseti(L, -2, lua_rawlen(L, -2) + 1); 1.393 + // expect value terminator (or end of object) and continue with loop: 1.394 mode = JSON_STATE_ARRAY_SEPARATOR; 1.395 goto json_import_loop; 1.396 + // a single value has been read: 1.397 case JSON_STATE_VALUE: 1.398 + // leave value on top of stack, expect end of JSON document, and continue with loop: 1.399 mode = JSON_STATE_END; 1.400 goto json_import_loop; 1.401 } 1.402 -json_import_syntax_error: 1.403 + // syntax error handling (only reachable by goto statement): 1.404 + json_import_syntax_error: 1.405 lua_pushnil(L); 1.406 lua_pushliteral(L, "Syntax error in JSON document"); 1.407 return 2; 1.408 @@ -249,6 +351,7 @@ 1.409 #define JSON_PATH_TYPE 2 1.410 #define JSON_PATH_ISNULL 3 1.411 1.412 +// gets a value, its type, or information 1.413 static int json_path(lua_State *L, int mode) { 1.414 int argc; 1.415 int idx = 2;