buldthensnip/json.c
2012-12-04 21:48:48 +13:00

529 lines
9.4 KiB
C

/*
This file is part of Iceball.
Iceball is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Iceball is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Iceball. If not, see <http://www.gnu.org/licenses/>.
*/
// TODO: finish this!
// TODO: write a JSON writer!
// syntax is as per the stuff down the right hand side of http://json.org/
// and is reproduced purely for reference
// there's also train track syntax on that page, too
// note, this assumes 8-bit ASCII.
// UTF-8 is supported BUT you have to parse it yourself;
// having said that, '\u' chars should hopefully encode to UTF-8.
// one exception to strict JSON conformance:
// this DOES allow the last element of an array or object to have a trailing comma.
// the JSON writer (TODO!), however, will not abuse that feature of this parser.
// furthermore, the parser WILL warn you if you do this.
#include "common.h"
int json_line_count = 1;
int json_parse_value(lua_State *L, const char **p);
void json_skip_whitespace(const char **p)
{
int lastwasr = 0;
while(**p == ' ' || **p == '\t' || **p == '\n' || **p == '\r')
{
if((**p == '\n' && !lastwasr) || **p == '\r')
{
json_line_count++;
//printf("%i\n",json_line_count);
}
lastwasr = (**p == '\r');
(*p)++;
}
}
int json_parse_hex4(lua_State *L, const char **p, int *uchr)
{
int i;
*uchr = 0;
for(i = 0; i < 4; i++)
{
*uchr <<= 4;
if(**p >= '0' && **p <= '9')
*uchr += (**p - '0');
else if(**p >= 'a' && **p <= 'f')
*uchr += (**p - 'a') + 10;
else if(**p >= 'A' && **p <= 'F')
*uchr += (**p - 'A') + 10;
else {
fprintf(stderr, "%i: expected hex digit\n", json_line_count);
return 1;
}
}
return 0;
}
// string
// ""
// " chars "
// chars
// char
// char chars
// char
// any-Unicode-character-
// except-"-or-\-or-
// control-character
// \"
// \\ (let's avoid a C warning by placing this note here...)
// \/
// \b
// \f
// \n
// \r
// \t
// \u four-hex-digits
int json_parse_string(lua_State *L, const char **p)
{
if(*((*p)++) != '\"')
{
fprintf(stderr, "%i: expected '\"'\n", json_line_count);
return 1;
}
int sbuf_pos = 0;
int sbuf_len = 64;
char *sbuf = malloc(sbuf_len);
// TODO: throughout this code, check if sbuf is NULL
int uchr = 0;
int lastwasr = 0;
while(**p != '\"')
{
if(**p == '\\')
{
switch(*(++(*p)))
{
case '\"':
uchr = '\"';
break;
case '\\':
uchr = '\\';
break;
case '/':
uchr = '/';
break;
case 'b':
uchr = '\b';
break;
case 'f':
uchr = '\f';
break;
case 'n':
uchr = '\n';
if(!lastwasr)
json_line_count++;
break;
case 'r':
uchr = '\r';
lastwasr = 2;
json_line_count++;
break;
case 't':
uchr = '\t';
break;
case 'u':
if(json_parse_hex4(L, p, &uchr))
{
free(sbuf);
return 1;
}
break;
default:
fprintf(stderr, "%i: invalid token after '\\'\n", json_line_count);
free(sbuf);
return 1;
}
} else if(**p == '\0') {
fprintf(stderr, "%i: unexpected NUL\n", json_line_count);
free(sbuf);
return 1;
} else {
uchr = (int)(unsigned char)(*((*p)++));
if(uchr == 10)
{
if(!lastwasr)
json_line_count++;
} else if(uchr == 13) {
lastwasr = 2;
json_line_count++;
}
}
if(lastwasr > 0)
lastwasr--;
if(sbuf_pos+4 >= sbuf_len)
{
sbuf_len <<= 1;
sbuf = realloc(sbuf, sbuf_len);
//printf("%i %016llX\n", sbuf_len, sbuf);
}
if(uchr >= 0x01 && uchr <= 0x7F)
{
// 0xxxxxxx
sbuf[sbuf_pos++] = uchr;
} else if(uchr <= 0x7FF) {
// 110xxxxx
// 10xxxxxx
sbuf[sbuf_pos++] = 0xC0 | (uchr>>6);
sbuf[sbuf_pos++] = 0x80 | ((uchr)&0x3F);
} else if(uchr <= 0xFFFF) {
// 1110xxxx
// 10xxxxxx
// 10xxxxxx
sbuf[sbuf_pos++] = 0xE0 | (uchr>>12);
sbuf[sbuf_pos++] = 0x80 | ((uchr>>6)&0x3F);
sbuf[sbuf_pos++] = 0x80 | ((uchr)&0x3F);
}
}
(*p)++;
lua_pushlstring(L, sbuf, sbuf_pos);
free(sbuf);
json_skip_whitespace(p);
return 0;
}
// number
// int
// int frac
// int exp
// int frac exp
// int
// digit
// digit1-9 digits
// - digit
// - digit1-9 digits
// frac
// . digits
// exp
// e digits
// digits
// digit
// digit digits
// e
// e
// e+
// e-
// E
// E+
// E-
int json_parse_number(lua_State *L, const char **p)
{
double n = 0.0;
double sign = 1.0;
if(**p == '-')
{
sign = -1.0;
(*p)++;
}
if(**p < '0' || **p > '9')
{
fprintf(stderr, "%i: expected digit\n", json_line_count);
return 1;
}
if(**p == '0')
{
(*p)++;
} else {
while(**p >= '0' && **p <= '9')
n = n*10.0 + (*((*p)++) - '0');
}
if(**p == '.')
{
(*p)++;
if(**p < '0' || **p > '9')
{
fprintf(stderr, "%i: expected digit\n", json_line_count);
return 1;
}
double sub = 0.1;
while(**p >= '0' && **p <= '9')
{
n += (*((*p)++) - '0')*sub;
sub *= 0.1;
}
}
if(**p == 'e' || **p == 'E')
{
(*p)++;
int esign = 1;
int e = 0;
if(**p == '+')
(*p)++;
else if(**p == '-')
{
esign = -1;
(*p)++;
}
if(**p < '0' || **p > '9')
{
fprintf(stderr, "%i: expected digit\n", json_line_count);
return 1;
}
while(**p >= '0' && **p <= '9')
e = e*10 + (*((*p)++) - '0');
if(esign < 0)
{
while(e > 0)
{
n *= 0.1;
e--;
}
} else {
while(e > 0)
{
n *= 10.0;
e--;
}
}
}
json_skip_whitespace(p);
lua_pushnumber(L, n);
return 0;
}
// array
// []
// [ elements ]
// elements
// value
// value , elements
int json_parse_array(lua_State *L, const char **p)
{
int idx = 1;
if(*((*p)++) != '[')
{
fprintf(stderr, "%i: expected '\"'\n", json_line_count);
return 1;
}
json_skip_whitespace(p);
lua_newtable(L);
while(**p != ']')
{
lua_pushinteger(L, idx++);
if(json_parse_value(L, p))
{
lua_pop(L, 2);
return 1;
}
lua_settable(L, -3);
if(**p == ',')
{
(*p)++;
json_skip_whitespace(p);
if(**p == ']')
fprintf(stderr, "%i: warning: trailing ',' in array; not compliant!\n"
, json_line_count);
} else if(**p != ']') {
fprintf(stderr, "%i: expected ',' or ']'\n", json_line_count);
lua_pop(L, 1);
return 1;
}
}
(*p)++;
return 0;
}
int json_parse_object(lua_State *L, const char **p);
// value
// string
// number
// object
// array
// true
// false
// null
int json_parse_value(lua_State *L, const char **p)
{
if(**p == 't')
{
if((*p)[1] != 'r' || (*p)[2] != 'u' || (*p)[3] != 'e')
{
fprintf(stderr, "%i: expected \"true\"\n", json_line_count);
return 1;
}
*p += 4;
lua_pushboolean(L, 1);
} else if(**p == 'f') {
if((*p)[1] != 'a' || (*p)[2] != 'l' || (*p)[3] != 's' || (*p)[4] != 'e')
{
fprintf(stderr, "%i: expected \"false\"\n", json_line_count);
return 1;
}
*p += 5;
lua_pushboolean(L, 0);
} else if(**p == 'n') {
if((*p)[1] != 'u' || (*p)[2] != 'l' || (*p)[3] != 'l')
{
fprintf(stderr, "%i: expected \"null\"\n", json_line_count);
return 1;
}
*p += 4;
lua_pushnil(L);
} else if(**p == '{') {
if(json_parse_object(L, p))
return 1;
} else if(**p == '[') {
if(json_parse_array(L, p))
return 1;
} else if(**p == '"') {
if(json_parse_string(L, p))
return 1;
} else if((**p >= '0' && **p <= '9') || **p == '-') {
if(json_parse_number(L, p))
return 1;
} else {
fprintf(stderr, "%i: expected value\n", json_line_count);
return 1;
}
json_skip_whitespace(p);
return 0;
}
// object
// {}
// { members }
// members
// pair
// pair , members
// pair
// string : value
int json_parse_object(lua_State *L, const char **p)
{
if(*((*p)++) != '{')
{
fprintf(stderr, "%i: expected '\"'\n", json_line_count);
return 1;
}
json_skip_whitespace(p);
lua_newtable(L);
if(**p == '}')
{
(*p)++;
json_skip_whitespace(p);
return 0;
}
while(**p == '"')
{
json_parse_string(L, p);
if(*((*p)++) != ':')
{
fprintf(stderr, "%i: expected ':'\n", json_line_count);
lua_pop(L, 1);
return 1;
}
json_skip_whitespace(p);
if(json_parse_value(L, p))
{
lua_pop(L, 2);
return 1;
}
lua_settable(L, -3);
if(**p == ',')
{
(*p)++;
json_skip_whitespace(p);
if(**p == '}')
fprintf(stderr, "%i: warning: trailing ',' in object; not compliant!\n"
, json_line_count);
}
if(**p == '}')
{
(*p)++;
json_skip_whitespace(p);
//printf("%s\n", luaL_typename(L, -1));
return 0;
}
}
printf("bail\n");
lua_pop(L, 1);
fprintf(stderr, "%i: expected '\"' or '}'\n", json_line_count);
return 1;
}
int json_parse(lua_State *L, const char *p)
{
json_line_count = 1;
json_skip_whitespace(&p);
if(json_parse_object(L, &p))
return 1;
if(*p != '\0')
{
lua_pop(L, 1);
return 1;
}
return 0;
}
int json_load(lua_State *L, const char *fname)
{
int flen;
char *buf = net_fetch_file(fname, &flen);
if(buf == NULL)
return 1;
int ret = json_parse(L, buf);
free(buf);
return ret;
}