|
@@ -0,0 +1,432 @@
|
|
|
+#include <stdio.h>
|
|
|
+#include <stdint.h>
|
|
|
+#include <ctype.h>
|
|
|
+#include <math.h>
|
|
|
+
|
|
|
+#include <toaru/hashmap.h>
|
|
|
+#include <toaru/list.h>
|
|
|
+#include <toaru/json.h>
|
|
|
+
|
|
|
+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;
|
|
|
+}
|