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;

Impressum / About Us