/** * @brief JSON parser. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2018-2021 K. Lange */ #include #include #include #include #include #include #include typedef struct JSON_Value Value; /* Internal usage */ struct JSON_Context { const char * string; int c; const char * error; }; void json_free(Value * v) { if (v->type == JSON_TYPE_STRING) { free(v->string); } if (v->type == JSON_TYPE_OBJECT) { hashmap_free(v->object); free(v->object); } if (v->type == JSON_TYPE_ARRAY) { foreach(node, v->array) { json_free(node->value); } list_free(v->array); free(v->array); } free(v); } static Value * value(struct JSON_Context * ctx); static int peek(struct JSON_Context * ctx) { return ctx->string[ctx->c]; } static void advance(struct JSON_Context * ctx) { ctx->c++; } static void whitespace(struct JSON_Context * ctx) { while (1) { int ch = peek(ctx); if (ch == ' ' || ch == '\r' || ch == '\n' || ch == '\t') { advance(ctx); } else { break; } } } static Value * string(struct JSON_Context * ctx) { if (peek(ctx) != '"') return NULL; advance(ctx); int size = 4; char * tmp = malloc(4); tmp[0] = 0; int used = 0; #define add(c) do { \ if (used + 1 == size) { \ size *= 2; \ tmp = realloc(tmp, size); \ } \ tmp[used] = c; \ tmp[used+1] = 0; \ used++; \ } while (0) while (1) { int ch = peek(ctx); if (ch == 0) goto string_error; if (ch == '"') { break; } else if (ch == '\\') { advance(ctx); ch = peek(ctx); if (ch == '"') add('"'); else if (ch == '\\') add('\\'); else if (ch == '/') add('/'); else if (ch == 'b') add('\b'); else if (ch == 'f') add('\f'); else if (ch == 'n') add('\n'); else if (ch == 'r') add('\r'); else if (ch == 't') add('\t'); else if (ch == 'u') { /* Parse hex */ advance(ctx); char hex_digits[5]; if (!isxdigit(peek(ctx))) goto string_error; hex_digits[0] = peek(ctx); advance(ctx); if (!isxdigit(peek(ctx))) goto string_error; hex_digits[1] = peek(ctx); advance(ctx); if (!isxdigit(peek(ctx))) goto string_error; hex_digits[2] = peek(ctx); advance(ctx); if (!isxdigit(peek(ctx))) goto string_error; hex_digits[3] = peek(ctx); /* will be advanced later */ hex_digits[4] = 0; uint32_t val = strtoul(hex_digits, NULL, 16); if (val < 0x0080) { add(val); } else if (val < 0x0800) { add(0xC0 | (val >> 6)); add(0x80 | (val & 0x3F)); } else { add(0xE0 | (val >> 12)); add(0x80 | ((val >> 6) & 0x3F)); add(0x80 | (val & 0x3F)); } } else { goto string_error; } advance(ctx); } else { add(ch); advance(ctx); } } if (peek(ctx) != '"') { ctx->error = "Unexpected EOF?"; goto string_error; } advance(ctx); Value * out = malloc(sizeof(Value)); out->type = JSON_TYPE_STRING; out->string = strdup(tmp); free(tmp); return out; string_error: free(tmp); return NULL; } static Value * object(struct JSON_Context * ctx) { if (peek(ctx) != '{') { ctx->error = "Expected { (internal error)"; return NULL; } advance(ctx); Value * out; hashmap_t * output = hashmap_create(10); output->hash_val_free = (void (*)(void *))json_free; whitespace(ctx); if (peek(ctx) == '}') { advance(ctx); goto _object_done; } while (1) { whitespace(ctx); Value * s = string(ctx); if (!s) { ctx->error = "Expected string"; break; } whitespace(ctx); if (peek(ctx) != ':') { ctx->error = "Expected :"; break; } advance(ctx); Value * v = value(ctx); hashmap_set(output, s->string, v); json_free(s); if (peek(ctx) == '}') { advance(ctx); goto _object_done; } if (peek(ctx) != ',') { ctx->error = "Expected , or {"; break; } advance(ctx); } hashmap_free(output); return NULL; _object_done: out = malloc(sizeof(Value)); out->type = JSON_TYPE_OBJECT; out->object = output; return out; } static Value * boolean(struct JSON_Context * ctx) { int value = -1; if (peek(ctx) == 't') { advance(ctx); if (peek(ctx) != 'r') { ctx->error = "Invalid literal while parsing bool"; return NULL; } advance(ctx); if (peek(ctx) != 'u') { ctx->error = "Invalid literal while parsing bool"; return NULL; } advance(ctx); if (peek(ctx) != 'e') { ctx->error = "Invalid literal while parsing bool"; return NULL; } advance(ctx); value = 1; } else if (peek(ctx) == 'f') { advance(ctx); if (peek(ctx) != 'a') { ctx->error = "Invalid literal while parsing bool"; return NULL; } advance(ctx); if (peek(ctx) != 'l') { ctx->error = "Invalid literal while parsing bool"; return NULL; } advance(ctx); if (peek(ctx) != 's') { ctx->error = "Invalid literal while parsing bool"; return NULL; } advance(ctx); if (peek(ctx) != 'e') { ctx->error = "Invalid literal while parsing bool"; return NULL; } advance(ctx); value = 0; } else { ctx->error = "Invalid literal while parsing bool"; return NULL; } Value * out = malloc(sizeof(Value)); out->type = JSON_TYPE_BOOL; out->boolean = value; return out; } static Value * null(struct JSON_Context * ctx) { if (peek(ctx) != 'n') { ctx->error = "Invalid literal while parsing null"; return NULL; } advance(ctx); if (peek(ctx) != 'u') { ctx->error = "Invalid literal while parsing null"; return NULL; } advance(ctx); if (peek(ctx) != 'l') { ctx->error = "Invalid literal while parsing null"; return NULL; } advance(ctx); if (peek(ctx) != 'l') { ctx->error = "Invalid literal while parsing null"; return NULL; } advance(ctx); Value * out = malloc(sizeof(Value)); out->type = JSON_TYPE_NULL; return out; } static Value * number(struct JSON_Context * ctx) { double value = 0; int sign = 1; if (peek(ctx) == '-') { /* Negative */ sign = -1; advance(ctx); } if (peek(ctx) == '0') { advance(ctx); } else if (isdigit(peek(ctx))) { /* Read any digit */ value = peek(ctx) - '0'; advance(ctx); while (isdigit(peek(ctx))) { value *= 10; value += peek(ctx) - '0'; advance(ctx); } } else { ctx->error = "Expected digit"; return NULL; } if (peek(ctx) == '.') { /* Read fractional part */ advance(ctx); double multiplier = 0.1; /* read at least one digit */ if (!isdigit(peek(ctx))) { ctx->error = "Expected digit"; return NULL; } while (isdigit(peek(ctx))) { value += multiplier * (peek(ctx) - '0'); multiplier *= 0.1; advance(ctx); } } if (peek(ctx) == 'e' || peek(ctx) == 'E') { /* Read exponent */ int exp_sign = 1; advance(ctx); if (peek(ctx) == '+') advance(ctx); else if (peek(ctx) == '-') { exp_sign = -1; advance(ctx); } /* read digits */ if (!isdigit(peek(ctx))) { ctx->error = "Expected digit"; return NULL; } double exp = peek(ctx) - '0'; advance(ctx); while (isdigit(peek(ctx))) { exp *= 10; exp += peek(ctx) - '0'; advance(ctx); } value = value * pow(10.0,exp * exp_sign); } Value * out = malloc(sizeof(Value)); out->type = JSON_TYPE_NUMBER; out->number = value * sign; return out; } static Value * array(struct JSON_Context * ctx) { if (peek(ctx) != '[') return NULL; advance(ctx); whitespace(ctx); list_t * output = list_create(); Value * out; if (peek(ctx) == ']') { advance(ctx); goto _array_done; } while (1) { Value * next = value(ctx); if (!next) break; list_insert(output, next); if (peek(ctx) == ']') { advance(ctx); goto _array_done; } if (peek(ctx) != ',') { ctx->error = "Expected ,"; break; } advance(ctx); } /* uh oh */ foreach(node, output) { json_free(node->value); } list_free(output); free(output); return NULL; _array_done: out = malloc(sizeof(Value)); out->type = JSON_TYPE_ARRAY; out->array = output; return out; } #define WHITE(c) { \ Value * out = c; \ whitespace(ctx); \ return out; \ } static Value * value(struct JSON_Context * ctx) { whitespace(ctx); if (peek(ctx) == '"') WHITE(string(ctx)) else if (peek(ctx) == '{') WHITE(object(ctx)) else if (peek(ctx) == '[') WHITE(array(ctx)) else if (peek(ctx) == '-' || isdigit(peek(ctx))) WHITE(number(ctx)) else if (peek(ctx) == 't') WHITE(boolean(ctx)) else if (peek(ctx) == 'f') WHITE(boolean(ctx)) else if (peek(ctx) == 'n') WHITE(null(ctx)) ctx->error = "Unexpected value"; return NULL; } Value * json_parse(const char * str) { struct JSON_Context ctx; ctx.string = str; ctx.c = 0; ctx.error = NULL; Value * out = value(&ctx); #if 0 if (!out) { fprintf(stderr, "JSON parse error at %d (%c)\n", ctx.c, ctx.string[ctx.c]); fprintf(stderr, "%s\n", ctx.error); fprintf(stderr, "%s\n", ctx.string); for (int i = 0; i < ctx.c; ++i) { fprintf(stderr, " "); } fprintf(stderr, "^\n"); } #endif return out; } Value * json_parse_file(const char * filename) { FILE * f = fopen(filename, "r"); if (!f) return NULL; fseek(f, 0, SEEK_END); size_t size = ftell(f); fseek(f, 0, SEEK_SET); char * tmp = malloc(size + 1); fread(tmp, size, 1, f); tmp[size] = 0; fclose(f); Value * out = json_parse(tmp); free(tmp); return out; }