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