/* 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; }