491 lines
11 KiB
C
491 lines
11 KiB
C
/* JSON Parser
|
|
* ZZJSON - Copyright (C) 2008-2009 by Ivo van Poorten
|
|
* License: GNU Lesser General Public License version 2.1
|
|
*/
|
|
|
|
#include "zzjson.h"
|
|
#include <ctype.h>
|
|
#include <string.h>
|
|
#include <math.h>
|
|
#include <stdio.h>
|
|
|
|
#define GETC() config->getchar(config->ihandle)
|
|
#define UNGETC(c) config->ungetchar(c, config->ihandle)
|
|
#define SKIPWS() skipws(config)
|
|
#ifdef CONFIG_NO_ERROR_MESSAGES
|
|
#define ERROR(x...)
|
|
#else
|
|
#define ERROR(x...) config->error(config->ehandle, ##x)
|
|
#endif
|
|
#define MEMERROR() ERROR("out of memory")
|
|
|
|
#define ALLOW_EXTRA_COMMA (config->strictness & ZZJSON_ALLOW_EXTRA_COMMA)
|
|
#define ALLOW_ILLEGAL_ESCAPE (config->strictness & ZZJSON_ALLOW_ILLEGAL_ESCAPE)
|
|
#define ALLOW_CONTROL_CHARS (config->strictness & ZZJSON_ALLOW_CONTROL_CHARS)
|
|
#define ALLOW_GARBAGE_AT_END (config->strictness & ZZJSON_ALLOW_GARBAGE_AT_END)
|
|
#define ALLOW_COMMENTS (config->strictness & ZZJSON_ALLOW_COMMENTS)
|
|
|
|
static ZZJSON *parse_array(ZZJSON_CONFIG *config);
|
|
static ZZJSON *parse_object(ZZJSON_CONFIG *config);
|
|
|
|
static void skipws(ZZJSON_CONFIG *config) {
|
|
int d, c = GETC();
|
|
morews:
|
|
while (isspace(c)) c = GETC();
|
|
if (!ALLOW_COMMENTS) goto endws;
|
|
if (c != '/') goto endws;
|
|
d = GETC();
|
|
if (d != '*') goto endws; /* pushing back c will generate a parse error */
|
|
c = GETC();
|
|
morecomments:
|
|
while (c != '*') {
|
|
if (c == EOF) goto endws;
|
|
c = GETC();
|
|
}
|
|
c = GETC();
|
|
if (c != '/') goto morecomments;
|
|
c = GETC();
|
|
if (isspace(c) || c == '/') goto morews;
|
|
endws:
|
|
UNGETC(c);
|
|
}
|
|
|
|
static char *parse_string(ZZJSON_CONFIG *config) {
|
|
unsigned int len = 16, pos = 0;
|
|
int c;
|
|
char *str = NULL;
|
|
|
|
SKIPWS();
|
|
c = GETC();
|
|
if (c != '"') {
|
|
ERROR("string: expected \" at the start");
|
|
return NULL;
|
|
}
|
|
|
|
str = config->malloc(len);
|
|
if (!str) {
|
|
MEMERROR();
|
|
return NULL;
|
|
}
|
|
c = GETC();
|
|
while (c > 0 && c != '"') {
|
|
if (!ALLOW_CONTROL_CHARS && c >= 0 && c <= 31) {
|
|
ERROR("string: control characters not allowed");
|
|
goto errout;
|
|
}
|
|
if (c == '\\') {
|
|
c = GETC();
|
|
switch (c) {
|
|
case 'b': c = '\b'; break;
|
|
case 'f': c = '\f'; break;
|
|
case 'n': c = '\n'; break;
|
|
case 'r': c = '\r'; break;
|
|
case 't': c = '\t'; break;
|
|
case 'u': {
|
|
UNGETC(c); /* ignore \uHHHH, copy verbatim */
|
|
c = '\\';
|
|
break;
|
|
}
|
|
case '\\': case '/': case '"':
|
|
break;
|
|
default:
|
|
if (!ALLOW_ILLEGAL_ESCAPE) {
|
|
ERROR("string: illegal escape character");
|
|
goto errout;
|
|
}
|
|
}
|
|
}
|
|
str[pos++] = c;
|
|
if (pos == len-1) {
|
|
void *tmp = str;
|
|
len *= 2;
|
|
str = config->realloc(str, len);
|
|
if (!str) {
|
|
MEMERROR();
|
|
str = tmp;
|
|
goto errout;
|
|
}
|
|
}
|
|
c = GETC();
|
|
}
|
|
if (c != '"') {
|
|
ERROR("string: expected \" at the end");
|
|
goto errout;
|
|
}
|
|
str[pos] = 0;
|
|
return str;
|
|
|
|
errout:
|
|
config->free(str);
|
|
return NULL;
|
|
}
|
|
|
|
static ZZJSON *parse_string2(ZZJSON_CONFIG *config) {
|
|
ZZJSON *zzjson = NULL;
|
|
char *str;
|
|
|
|
str = parse_string(config);
|
|
if (str) {
|
|
zzjson = config->calloc(1, sizeof(ZZJSON));
|
|
if (!zzjson) {
|
|
MEMERROR();
|
|
config->free(str);
|
|
return NULL;
|
|
}
|
|
zzjson->type = ZZJSON_STRING;
|
|
zzjson->value.string.string = str;
|
|
}
|
|
return zzjson;
|
|
}
|
|
|
|
static ZZJSON *parse_number(ZZJSON_CONFIG *config) {
|
|
ZZJSON *zzjson;
|
|
unsigned long long ival = 0, expo = 0;
|
|
double dval = 0.0, frac = 0.0, fracshft = 10.0;
|
|
int c, dbl = 0, sign = 1, signexpo = 1;
|
|
|
|
SKIPWS();
|
|
c = GETC();
|
|
if (c == '-') {
|
|
sign = -1;
|
|
c = GETC();
|
|
}
|
|
if (c == '0') {
|
|
c = GETC();
|
|
goto skip;
|
|
}
|
|
|
|
if (!isdigit(c)) {
|
|
ERROR("number: digit expected");
|
|
return NULL;
|
|
}
|
|
|
|
while (isdigit(c)) {
|
|
ival *= 10;
|
|
ival += c - '0';
|
|
c = GETC();
|
|
}
|
|
|
|
skip:
|
|
if (c != '.') goto skipfrac;
|
|
|
|
dbl = 1;
|
|
|
|
c = GETC();
|
|
if (!isdigit(c)) {
|
|
ERROR("number: digit expected");
|
|
return NULL;
|
|
}
|
|
|
|
while (isdigit(c)) {
|
|
frac += (double)(c - '0') / fracshft;
|
|
fracshft *= 10.0;
|
|
c = GETC();
|
|
}
|
|
|
|
skipfrac:
|
|
if (c != 'e' && c != 'E') goto skipexpo;
|
|
|
|
dbl = 1;
|
|
|
|
c = GETC();
|
|
if (c == '+')
|
|
c = GETC();
|
|
else if (c == '-') {
|
|
signexpo = -1;
|
|
c = GETC();
|
|
}
|
|
|
|
if (!isdigit(c)) {
|
|
ERROR("number: digit expected");
|
|
return NULL;
|
|
}
|
|
|
|
while (isdigit(c)) {
|
|
expo *= 10;
|
|
expo += c - '0';
|
|
c = GETC();
|
|
}
|
|
|
|
skipexpo:
|
|
UNGETC(c);
|
|
|
|
if (dbl) {
|
|
dval = sign * (long long) ival;
|
|
dval += sign * frac;
|
|
dval *= pow(10.0, (double) signexpo * expo);
|
|
}
|
|
|
|
zzjson = config->calloc(1, sizeof(ZZJSON));
|
|
if (!zzjson) {
|
|
MEMERROR();
|
|
return NULL;
|
|
}
|
|
if (dbl) {
|
|
zzjson->type = ZZJSON_NUMBER_DOUBLE;
|
|
zzjson->value.number.val.dval = dval;
|
|
} else {
|
|
zzjson->type = sign < 0 ? ZZJSON_NUMBER_NEGINT : ZZJSON_NUMBER_POSINT;
|
|
zzjson->value.number.val.ival = ival;
|
|
}
|
|
|
|
return zzjson;
|
|
}
|
|
|
|
static ZZJSON *parse_literal(ZZJSON_CONFIG *config, char *s, ZZJSON_TYPE t) {
|
|
char b[strlen(s)+1];
|
|
unsigned int i;
|
|
|
|
for (i=0; i<strlen(s); i++) b[i] = GETC();
|
|
b[i] = 0;
|
|
|
|
if (!strcmp(b,s)) {
|
|
ZZJSON *zzjson;
|
|
zzjson = config->calloc(1, sizeof(ZZJSON));
|
|
if (!zzjson) {
|
|
MEMERROR();
|
|
return NULL;
|
|
}
|
|
zzjson->type = t;
|
|
return zzjson;
|
|
}
|
|
ERROR("literal: expected %s", s);
|
|
return NULL;
|
|
}
|
|
|
|
static ZZJSON *parse_true(ZZJSON_CONFIG *config) {
|
|
return parse_literal(config, (char *)"true", ZZJSON_TRUE);
|
|
}
|
|
|
|
static ZZJSON *parse_false(ZZJSON_CONFIG *config) {
|
|
return parse_literal(config, (char *)"false", ZZJSON_FALSE);
|
|
}
|
|
|
|
static ZZJSON *parse_null(ZZJSON_CONFIG *config) {
|
|
return parse_literal(config, (char *)"null", ZZJSON_NULL);
|
|
}
|
|
|
|
static ZZJSON *parse_value(ZZJSON_CONFIG *config) {
|
|
ZZJSON *retval = NULL;
|
|
int c;
|
|
|
|
SKIPWS();
|
|
c = GETC();
|
|
UNGETC(c);
|
|
switch (c) {
|
|
case '"': retval = parse_string2(config); break;
|
|
case '0': case '1': case '2': case '3': case '4': case '5':
|
|
case '6': case '7': case '8': case '9': case '-':
|
|
retval = parse_number(config); break;
|
|
case '{': retval = parse_object(config); break;
|
|
case '[': retval = parse_array(config); break;
|
|
case 't': retval = parse_true(config); break;
|
|
case 'f': retval = parse_false(config); break;
|
|
case 'n': retval = parse_null(config); break;
|
|
}
|
|
|
|
if (!retval) {
|
|
ERROR("value: invalid value");
|
|
return retval;
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
static ZZJSON *parse_array(ZZJSON_CONFIG *config) {
|
|
ZZJSON *retval = NULL, **next = &retval;
|
|
int c;
|
|
|
|
SKIPWS();
|
|
c = GETC();
|
|
if (c != '[') {
|
|
ERROR("array: expected '['");
|
|
return NULL;
|
|
}
|
|
|
|
SKIPWS();
|
|
c = GETC();
|
|
while (c > 0 && c != ']') {
|
|
ZZJSON *zzjson = NULL, *val = NULL;
|
|
|
|
UNGETC(c);
|
|
|
|
SKIPWS();
|
|
val = parse_value(config);
|
|
if (!val) {
|
|
ERROR("array: value expected");
|
|
goto errout;
|
|
}
|
|
|
|
SKIPWS();
|
|
c = GETC();
|
|
if (c != ',' && c != ']') {
|
|
ERROR("array: expected ',' or ']'");
|
|
errout_with_val:
|
|
zzjson_free(config, val);
|
|
goto errout;
|
|
}
|
|
if (c == ',') {
|
|
SKIPWS();
|
|
c = GETC();
|
|
if (c == ']' && !ALLOW_EXTRA_COMMA) {
|
|
ERROR("array: expected value after ','");
|
|
goto errout_with_val;
|
|
}
|
|
}
|
|
UNGETC(c);
|
|
|
|
zzjson = config->calloc(1, sizeof(ZZJSON));
|
|
if (!zzjson) {
|
|
MEMERROR();
|
|
zzjson_free(config, val);
|
|
goto errout_with_val;
|
|
}
|
|
zzjson->type = ZZJSON_ARRAY;
|
|
zzjson->value.array.val = val;
|
|
*next = zzjson;
|
|
next = &zzjson->next;
|
|
|
|
c = GETC();
|
|
}
|
|
|
|
if (c != ']') {
|
|
ERROR("array: expected ']'");
|
|
goto errout;
|
|
}
|
|
|
|
if (!retval) { /* empty array, [ ] */
|
|
retval = config->calloc(1, sizeof(ZZJSON));
|
|
if (!retval) {
|
|
MEMERROR();
|
|
return NULL;
|
|
}
|
|
retval->type = ZZJSON_ARRAY;
|
|
}
|
|
|
|
return retval;
|
|
|
|
errout:
|
|
zzjson_free(config, retval);
|
|
return NULL;
|
|
}
|
|
|
|
static ZZJSON *parse_object(ZZJSON_CONFIG *config) {
|
|
ZZJSON *retval = NULL;
|
|
int c;
|
|
ZZJSON **next = &retval;
|
|
|
|
SKIPWS();
|
|
c = GETC();
|
|
if (c != '{') {
|
|
ERROR("object: expected '{'");
|
|
return NULL;
|
|
}
|
|
|
|
SKIPWS();
|
|
c = GETC();
|
|
while (c > 0 && c != '}') {
|
|
ZZJSON *zzjson = NULL, *val = NULL;
|
|
char *str;
|
|
|
|
UNGETC(c);
|
|
|
|
str = parse_string(config);
|
|
if (!str) {
|
|
ERROR("object: expected string");
|
|
errout_with_str:
|
|
config->free(str);
|
|
goto errout;
|
|
}
|
|
|
|
SKIPWS();
|
|
c = GETC();
|
|
if (c != ':') {
|
|
ERROR("object: expected ':'");
|
|
goto errout_with_str;
|
|
}
|
|
|
|
SKIPWS();
|
|
val = parse_value(config);
|
|
if (!val) {
|
|
ERROR("object: value expected");
|
|
goto errout_with_str;
|
|
}
|
|
|
|
SKIPWS();
|
|
c = GETC();
|
|
if (c != ',' && c != '}') {
|
|
ERROR("object: expected ',' or '}'");
|
|
errout_with_str_and_val:
|
|
zzjson_free(config, val);
|
|
goto errout_with_str;
|
|
}
|
|
if (c == ',') {
|
|
SKIPWS();
|
|
c = GETC();
|
|
if (c == '}' && !ALLOW_EXTRA_COMMA) {
|
|
ERROR("object: expected pair after ','");
|
|
goto errout_with_str_and_val;
|
|
}
|
|
}
|
|
UNGETC(c);
|
|
|
|
zzjson = config->calloc(1, sizeof(ZZJSON));
|
|
if (!zzjson) {
|
|
MEMERROR();
|
|
goto errout_with_str_and_val;
|
|
}
|
|
zzjson->type = ZZJSON_OBJECT;
|
|
zzjson->value.object.label = str;
|
|
zzjson->value.object.val = val;
|
|
*next = zzjson;
|
|
next = &zzjson->next;
|
|
|
|
c = GETC();
|
|
}
|
|
|
|
if (c != '}') {
|
|
ERROR("object: expected '}'");
|
|
goto errout;
|
|
}
|
|
|
|
if (!retval) { /* empty object, { } */
|
|
retval = config->calloc(1, sizeof(ZZJSON));
|
|
if (!retval) {
|
|
MEMERROR();
|
|
return NULL;
|
|
}
|
|
retval->type = ZZJSON_OBJECT;
|
|
}
|
|
|
|
return retval;
|
|
|
|
errout:
|
|
zzjson_free(config, retval);
|
|
return NULL;
|
|
}
|
|
|
|
ZZJSON *zzjson_parse(ZZJSON_CONFIG *config) {
|
|
ZZJSON *retval;
|
|
int c;
|
|
|
|
SKIPWS();
|
|
c = GETC();
|
|
UNGETC(c);
|
|
if (c == '[') retval = parse_array(config);
|
|
else if (c == '{') retval = parse_object(config);
|
|
else { ERROR("expected '[' or '{'"); return NULL; }
|
|
|
|
if (!retval) return NULL;
|
|
|
|
SKIPWS();
|
|
c = GETC();
|
|
if (c >= 0 && !ALLOW_GARBAGE_AT_END) {
|
|
ERROR("parse: garbage at end of file");
|
|
zzjson_free(config, retval);
|
|
return NULL;
|
|
}
|
|
|
|
return retval;
|
|
}
|