#include #include #include #include #include "json.h" //////////////////////////////////////////////////////////////////// // PARSER //////////////////////////////////////////////////////////////////// typedef struct { char* src; size_t len; size_t cur; JSON_Arena *arena; JSON_Error *error; } Context; static bool is_wspace(char c) { return c == ' ' || c == '\t' || c == '\r' || c == '\n'; } static bool is_digit(char c) { return c >= '0' && c <= '9'; } static bool is_control(char c) { return c >= 0 && c < ' '; // TODO: is this correct? } static void consume_wspace(Context *ctx) { while (ctx->cur < ctx->len && is_wspace(ctx->src[ctx->cur])) ctx->cur++; } static void *alloc(JSON_Arena *arena, size_t len, size_t align) { size_t pad = -(uintptr_t) (arena->ptr + arena->cur) & (align-1); if (arena->len - arena->cur < len + pad) return NULL; arena->cur += pad; void *ptr = arena->ptr + arena->cur; arena->cur += len; arena->last = ptr; return ptr; } static bool grow_alloc(JSON_Arena *arena, void *ptr, size_t new_len) { if (ptr == NULL || arena->last != ptr) return false; size_t old_len = (arena->ptr + arena->cur) - arena->last; if (new_len < old_len) return false; size_t increase_len = new_len - old_len; if (arena->len - arena->cur < increase_len) return false; arena->cur += increase_len; return true; } typedef struct { JSON_Arena *arena; char* ptr; size_t len; bool err; } GrowingBuffer; static GrowingBuffer gbinit(JSON_Arena *arena) { return (GrowingBuffer) { arena, NULL, 0, false }; } static void gbappends(GrowingBuffer *gb, char *str, size_t len) { if (gb->err) return; if (len == 0) return; if (gb->ptr == NULL) { gb->ptr = alloc(gb->arena, len, 1); if (gb->ptr == NULL) { gb->err = true; return; } } else { if (!grow_alloc(gb->arena, gb->ptr, gb->len + len)) { gb->err = true; return; } } memcpy(gb->ptr + gb->len, str, len); gb->len += len; } static void gbappendc(GrowingBuffer *gb, char c) { gbappends(gb, &c, 1); } static JSON *parse_any(Context *ctx); static void consume_unescaped_string_contents(Context *ctx) { while (ctx->cur < ctx->len && ctx->src[ctx->cur] != '"' && ctx->src[ctx->cur] != '\\' && !is_control(ctx->src[ctx->cur])) ctx->cur++; } static char *parse_string_raw(Context *ctx, size_t *plen) { assert(ctx->cur < ctx->len && ctx->src[ctx->cur] == '"'); ctx->cur++; GrowingBuffer gb = gbinit(ctx->arena); for (;;) { size_t off = ctx->cur; consume_unescaped_string_contents(ctx); gbappends(&gb, ctx->src + off, ctx->cur - off ); if (ctx->cur == ctx->len) return NULL; char c = ctx->src[ctx->cur++]; if (is_control(c)) return NULL; if (c == '"') break; assert(c == '\\'); if (ctx->cur == ctx->len) return NULL; c = ctx->src[ctx->cur++]; switch (c) { case '"': gbappendc(&gb, '"'); break; case '\\': gbappendc(&gb, '\\'); break; case '/': gbappendc(&gb, '/'); break; case 'b': gbappendc(&gb, '\b'); break; case 'f': gbappendc(&gb, '\f'); break; case 'n': gbappendc(&gb, '\n'); break; case 'r': gbappendc(&gb, '\r'); break; case 't': gbappendc(&gb, '\t'); break; case 'u': // TODO: not implemented yet return NULL; default: return NULL; } } if (gb.err) return NULL; *plen = gb.len; return gb.ptr; } static JSON *parse_string(Context *ctx) { size_t len; char *ptr = parse_string_raw(ctx, &len); if (ptr == NULL) return NULL; JSON *val = alloc(ctx->arena, sizeof(JSON), _Alignof(JSON)); if (val == NULL) return NULL; val->type = JSON_TYPE_STRING; val->len = len; val->sval = ptr; return val; } static bool node_with_key_exists(JSON *head, char *key, size_t key_len) { if (head == NULL) return false; JSON *cursor = head; while (cursor) { if (cursor->key_len == key_len && memcmp(cursor->key, key, key_len) == 0) return true; cursor = cursor->next; } return false; } static JSON *parse_object(Context *ctx) { assert(ctx->cur < ctx->len && ctx->src[ctx->cur] == '{'); ctx->cur++; consume_wspace(ctx); int count = 0; JSON *head = NULL; JSON **tail = &head; if (ctx->src[ctx->cur] != '}') { for (;;) { if (ctx->cur == ctx->len || ctx->src[ctx->cur] != '"') return NULL; size_t key_len; char *key = parse_string_raw(ctx, &key_len); if (key == NULL) return NULL; if (node_with_key_exists(head, key, key_len)) return NULL; consume_wspace(ctx); if (ctx->cur == ctx->len || ctx->src[ctx->cur] != ':') return NULL; ctx->cur++; consume_wspace(ctx); JSON *val = parse_any(ctx); if (val == NULL) return NULL; val->next = NULL; val->key = key; val->key_len = key_len; *tail = val; tail = &val->next; count++; consume_wspace(ctx); if (ctx->cur == ctx->len) return NULL; if (ctx->src[ctx->cur] == '}') break; if (ctx->src[ctx->cur] != ',') return NULL; ctx->cur++; consume_wspace(ctx); } } assert(ctx->cur < ctx->len && ctx->src[ctx->cur] == '}'); ctx->cur++; JSON *obj = alloc(ctx->arena, sizeof(JSON), _Alignof(JSON)); if (obj == NULL) return NULL; obj->type = JSON_TYPE_OBJECT; obj->len = (size_t) count; obj->head = head; return obj; } static JSON *parse_array(Context *ctx) { assert(ctx->cur < ctx->len && ctx->src[ctx->cur] == '['); ctx->cur++; consume_wspace(ctx); int count = 0; JSON *head = NULL; JSON **tail = &head; if (ctx->src[ctx->cur] != ']') { for (;;) { JSON *val = parse_any(ctx); if (val == NULL) return NULL; val->next = NULL; val->key = NULL; val->key_len = 0; *tail = val; tail = &val->next; count++; consume_wspace(ctx); if (ctx->cur == ctx->len) return NULL; if (ctx->src[ctx->cur] == ']') break; if (ctx->src[ctx->cur] != ',') return NULL; ctx->cur++; consume_wspace(ctx); } } assert(ctx->cur < ctx->len && ctx->src[ctx->cur] == ']'); ctx->cur++; JSON *arr = alloc(ctx->arena, sizeof(JSON), _Alignof(JSON)); if (arr == NULL) return NULL; arr->type = JSON_TYPE_ARRAY; arr->len = (size_t) count; arr->head = head; return arr; } static JSON *parse_float(Context *ctx) { bool neg = false; if (ctx->src[ctx->cur] == '-') { ctx->cur++; if (ctx->cur == ctx->len || !is_digit(ctx->src[ctx->cur])) return NULL; neg = true; } double x = 0; do { assert(ctx->cur < ctx->len && is_digit(ctx->src[ctx->cur])); int d = ctx->src[ctx->cur++] - '0'; if (neg) d = -d; x *= 10; x += d; } while (ctx->src[ctx->cur] != '.'); // Consume dot ctx->cur++; if (ctx->cur == ctx->len || !is_digit(ctx->src[ctx->cur])) return NULL; double s = 0.1; do { int d = ctx->src[ctx->cur++] - '0'; if (neg) d = -d; x += s * d; s /= 10; } while (ctx->cur < ctx->len && is_digit(ctx->src[ctx->cur])); JSON *val = alloc(ctx->arena, sizeof(JSON), _Alignof(JSON)); if (val == NULL) return NULL; val->type = JSON_TYPE_FLOAT; val->fval = x; return val; } static JSON *parse_int(Context *ctx) { assert(ctx->cur < ctx->len && (is_digit(ctx->src[ctx->cur]) || ctx->src[ctx->cur] == '-')); bool neg = false; if (ctx->src[ctx->cur] == '-') { ctx->cur++; if (ctx->cur == ctx->len || !is_digit(ctx->src[ctx->cur])) return NULL; neg = true; } int64_t x = 0; do { int d = ctx->src[ctx->cur++] - '0'; if (neg) { if (x < (INT64_MIN + d) / 10) return NULL; d = -d; } else { if (x > (INT64_MAX - d) / 10) return NULL; } x *= 10; x += d; } while (ctx->cur < ctx->len && is_digit(ctx->src[ctx->cur])); JSON *val = alloc(ctx->arena, sizeof(JSON), _Alignof(JSON)); if (val == NULL) return NULL; val->type = JSON_TYPE_INT; val->ival = x; return val; } static bool follows_float(Context *ctx) { assert(ctx->cur < ctx->len && (is_digit(ctx->src[ctx->cur]) || ctx->src[ctx->cur] == '-')); size_t peek = ctx->cur; if (peek < ctx->len && ctx->src[peek] == '-') { peek++; if (peek == ctx->len || !is_digit(ctx->src[peek])) return false; } do peek++; while (peek < ctx->len && is_digit(ctx->src[peek])); return peek < ctx->len && ctx->src[peek] == '.'; } static JSON *parse_number(Context *ctx) { if (follows_float(ctx)) return parse_float(ctx); return parse_int(ctx); } static JSON *parse_true(Context *ctx) { assert(ctx->cur < ctx->len && ctx->src[ctx->cur] == 't'); ctx->cur++; if (ctx->len - ctx->cur <= 2 || ctx->src[ctx->cur+0] != 'r' || ctx->src[ctx->cur+1] != 'u' || ctx->src[ctx->cur+2] != 'e') return NULL; ctx->cur += 3; JSON *val = alloc(ctx->arena, sizeof(JSON), _Alignof(JSON)); if (val == NULL) return NULL; val->type = JSON_TYPE_BOOL; val->bval = true; return val; } static JSON *parse_false(Context *ctx) { assert(ctx->cur < ctx->len && ctx->src[ctx->cur] == 'f'); ctx->cur++; if (ctx->len - ctx->cur <= 3 || ctx->src[ctx->cur+0] != 'a' || ctx->src[ctx->cur+1] != 'l' || ctx->src[ctx->cur+2] != 's' || ctx->src[ctx->cur+3] != 'e') return NULL; ctx->cur += 4; JSON *val = alloc(ctx->arena, sizeof(JSON), _Alignof(JSON)); if (val == NULL) return NULL; val->type = JSON_TYPE_BOOL; val->bval = false; return val; } static JSON *parse_null(Context *ctx) { assert(ctx->cur < ctx->len && ctx->src[ctx->cur] == 'n'); ctx->cur++; if (ctx->len - ctx->cur <= 2 || ctx->src[ctx->cur+0] != 'u' || ctx->src[ctx->cur+1] != 'l' || ctx->src[ctx->cur+2] != 'l') return NULL; ctx->cur += 3; JSON *val = alloc(ctx->arena, sizeof(JSON), _Alignof(JSON)); if (val == NULL) return NULL; val->type = JSON_TYPE_NULL; return val; } static JSON *parse_any(Context *ctx) { if (ctx->cur == ctx->len) return NULL; char c = ctx->src[ctx->cur]; if (c == '"') return parse_string(ctx); if (c == '{') return parse_object(ctx); if (c == '[') return parse_array(ctx); if (is_digit(c) || c == '-') return parse_number(ctx); if (c == 't') return parse_true(ctx); if (c == 'f') return parse_false(ctx); if (c == 'n') return parse_null(ctx); return NULL; } static JSON *decode(Context *ctx) { consume_wspace(ctx); JSON *x = parse_any(ctx); if (x == NULL) return NULL; x->key = NULL; x->key_len = 0; x->next = NULL; consume_wspace(ctx); if (ctx->cur < ctx->len) return NULL; return x; } JSON_Arena json_arena_init(char *ptr, size_t len) { return (JSON_Arena) { NULL, ptr, len, 0 }; } JSON *json_decode(char *src, int len, JSON_Arena *arena, JSON_Error *error) { if (src == NULL) src = ""; if (len < 0) len = strlen(src); Context ctx = { src, len, 0, arena, error }; return decode(&ctx); } //////////////////////////////////////////////////////////////////// // UTILITIES //////////////////////////////////////////////////////////////////// const char *json_type_name(JSON_Type type) { switch (type) { case JSON_TYPE_BOOL : return "bool"; case JSON_TYPE_NULL : return "null"; case JSON_TYPE_OBJECT: return "object"; case JSON_TYPE_ARRAY : return "array"; case JSON_TYPE_FLOAT : return "float"; case JSON_TYPE_INT : return "int"; case JSON_TYPE_STRING: return "string"; } return "???"; } JSON_Type json_get_type(JSON *json) { assert(json); return json->type; } JSON_String json_get_key(JSON *json) { if (json == NULL || json->key == NULL) return (JSON_String) { NULL, 0 }; return (JSON_String) { json->key, json->key_len }; } bool json_get_bool(JSON *json, bool fallback) { if (json == NULL || json->type != JSON_TYPE_BOOL) return fallback; return json->bval; } int64_t json_get_int(JSON *json, int64_t fallback) { if (json == NULL || json->type != JSON_TYPE_INT) return fallback; return json->ival; } double json_get_float(JSON *json, double fallback) { if (json == NULL || json->type != JSON_TYPE_FLOAT) return fallback; return json->fval; } JSON_String json_get_string(JSON *json) { if (json == NULL || json->type != JSON_TYPE_STRING) return (JSON_String) { NULL, 0 }; return (JSON_String) { json->sval, json->len }; } JSON *json_get_field(JSON *obj, JSON_String key) { if (obj == NULL) return NULL; if (obj->type != JSON_TYPE_OBJECT) return NULL; JSON *child = obj->head; while (child) { if (child->key_len == (size_t) key.len && !memcmp(child->key, key.ptr, key.len)) return child; child = child->next; } return NULL; } //////////////////////////////////////////////////////////////////// // PATTERN MATCHING //////////////////////////////////////////////////////////////////// typedef struct { char *src; int len; int cur; int cur_arg; JSON_MatchArgs args; JSON_Error *err; } MatchContext; static int match_any(MatchContext *ctx, JSON *json); static void report_match_error(MatchContext *ctx, char *fmt, ...) { char *dst = ctx->err->msg; int cap = (int) sizeof(ctx->err->msg); va_list args; va_start(args, fmt); int ret = vsnprintf(dst, cap, fmt, args); va_end(args); if (ret < 0) ret = 0; if (ret >= cap) ret = cap-1; dst[ret] = '\0'; } static int match_obj(MatchContext *ctx, JSON *json) { assert(json->type == JSON_TYPE_OBJECT); while (ctx->cur < ctx->len && is_wspace(ctx->src[ctx->cur])) ctx->cur++; if (ctx->cur == ctx->len) { report_match_error(ctx, "Pattern string ended inside an object"); return -1; } // Empty object? if (ctx->cur < ctx->len && ctx->src[ctx->cur] == '}') { ctx->cur++; return 0; } for (;;) { assert(ctx->cur < ctx->len && !is_wspace(ctx->src[ctx->cur])); if (ctx->src[ctx->cur] != '\'') { report_match_error(ctx, "Invalid character inside object, where key was expected"); return -1; } ctx->cur++; char key[128]; int key_len = 0; for (;;) { int off = ctx->cur; while (ctx->cur < ctx->len && ctx->src[ctx->cur] != '\'' && ctx->src[ctx->cur] != '\\') ctx->cur++; char *substr_ptr = ctx->src + off; int substr_len = ctx->cur - off; if (substr_len > 0) { if (sizeof(key) - key_len <= (size_t) substr_len) { report_match_error(ctx, "Key buffer limit reached (you can't have keys longer than %d bytes)", (int) sizeof(key)-1); return -1; } memcpy(key + key_len, substr_ptr, substr_len); key_len += substr_len; } if (ctx->cur == ctx->len) { report_match_error(ctx, "Pattern string ended inside a string"); return -1; } if (ctx->src[ctx->cur] == '\'') { ctx->cur++; break; } assert(ctx->src[ctx->cur] == '\\'); ctx->cur++; if (ctx->cur == ctx->len) { report_match_error(ctx, "Pattern string ended inside a string"); return -1; } if (sizeof(key) - key_len <= 1) { report_match_error(ctx, "Key buffer limit reached (you can't have keys longer than %d bytes)", (int) sizeof(key)-1); return -1; } switch (ctx->src[ctx->cur]) { case '\'': key[key_len++] = '\''; break; case '\\': key[key_len++] = '\\'; break; default: report_match_error(ctx, "Invalid escape character"); return -1; } } // Consume spaces to the next separator while (ctx->cur < ctx->len && is_wspace(ctx->src[ctx->cur])) ctx->cur++; if (ctx->cur == ctx->len || ctx->src[ctx->cur] != ':') { report_match_error(ctx, "Missing ':' after key"); return -1; } ctx->cur++; // Look for a field with the given key in the object JSON *child = json->head; while (child) { if (child->key_len == (size_t) key_len && !memcmp(child->key, key, key_len)) break; child = child->next; } if (child == NULL) { report_match_error(ctx, "Field '%.*s' is missing", key_len, key); return 1; } int ret = match_any(ctx, child); if (ret < 0) return ret; while (ctx->cur < ctx->len && is_wspace(ctx->src[ctx->cur])) ctx->cur++; if (ctx->cur == ctx->len) { report_match_error(ctx, "Pattern string ended inside an object"); return -1; } if (ctx->src[ctx->cur] == '}') { ctx->cur++; break; } if (ctx->src[ctx->cur] != ',') { report_match_error(ctx, "Invalid character where ',' or '}' were expected"); return -1; } ctx->cur++; while (ctx->cur < ctx->len && is_wspace(ctx->src[ctx->cur])) ctx->cur++; if (ctx->cur == ctx->len) { report_match_error(ctx, "Pattern string ended inside an object"); return -1; } } return 0; } static int match_any(MatchContext *ctx, JSON *json) { while (ctx->cur < ctx->len && is_wspace(ctx->src[ctx->cur])) ctx->cur++; if (ctx->cur == ctx->len) { report_match_error(ctx, "Pattern string ended where a value was expected"); return -1; } // The * character matches anything but doesn't extract any value if (ctx->src[ctx->cur] == '*') { ctx->cur++; return 0; } // The ? character matches anything and writes it to output if (ctx->src[ctx->cur] == '?') { ctx->cur++; if (ctx->cur_arg == ctx->args.len) { report_match_error(ctx, "Missing output arguments"); return -1; } JSON_MatchArg arg = ctx->args.ptr[ctx->cur_arg++]; switch (arg.type) { case JSON_MATCH_ARG_TYPE_BOOL: if (json->type != JSON_TYPE_BOOL) { report_match_error(ctx, "Expected boolean, got %s instead", json_type_name(json->type)); return 1; } *arg.bptr = json->bval; break; case JSON_MATCH_ARG_TYPE_INT: if (json->type != JSON_TYPE_INT) { report_match_error(ctx, "Expected integer, got %s instead", json_type_name(json->type)); return 1; } *arg.iptr = json->ival; break; case JSON_MATCH_ARG_TYPE_FLOAT: if (json->type != JSON_TYPE_FLOAT) { report_match_error(ctx, "Expected float, got %s instead", json_type_name(json->type)); return 1; } *arg.fptr = json->fval; break; case JSON_MATCH_ARG_TYPE_STRING: if (json->type != JSON_TYPE_STRING) { report_match_error(ctx, "Expected string, got %s instead", json_type_name(json->type)); return 1; } *arg.sptr = (JSON_String) { json->sval, json->len }; break; case JSON_MATCH_ARG_TYPE_ANY: *arg.anyptr = json; break; } return 0; } if (ctx->src[ctx->cur] == '{') { ctx->cur++; if (json->type != JSON_TYPE_OBJECT) { report_match_error(ctx, "Expected object, got %s instead", json_type_name(json->type)); return 1; } return match_obj(ctx, json); } char c = ctx->src[ctx->cur]; if (c >= ' ' && c <= '~') report_match_error(ctx, "Unexpected character '%c' in pattern", c); else report_match_error(ctx, "Unexpected byte %x in pattern", c); return -1; } int json_match_impl(JSON *json, JSON_Error *err, char *pattern, JSON_MatchArgs args) { MatchContext ctx = { .src = pattern, .len = strlen(pattern), .cur = 0, .cur_arg = 0, .args = args, .err = err, }; return match_any(&ctx, json); } JSON_MatchArg json_match_arg_bool (bool *x) { return (JSON_MatchArg) { .type=JSON_MATCH_ARG_TYPE_BOOL, .bptr=x }; } JSON_MatchArg json_match_arg_int (int64_t *x) { return (JSON_MatchArg) { .type=JSON_MATCH_ARG_TYPE_INT, .iptr=x }; } JSON_MatchArg json_match_arg_float (double *x) { return (JSON_MatchArg) { .type=JSON_MATCH_ARG_TYPE_FLOAT, .fptr=x }; } JSON_MatchArg json_match_arg_string(JSON_String *x) { return (JSON_MatchArg) { .type=JSON_MATCH_ARG_TYPE_STRING, .sptr=x }; } JSON_MatchArg json_match_arg_any (JSON **x) { return (JSON_MatchArg) { .type=JSON_MATCH_ARG_TYPE_ANY, .anyptr=x }; } //////////////////////////////////////////////////////////////////// // STRING ESCAPING //////////////////////////////////////////////////////////////////// JSON_String json_escape(JSON_Arena *arena, JSON_String str) { // TODO: This is just a best-effort implementation. Should handle UTF-8 precisely. char *src = str.ptr; int len = str.len; int cur = 0; char *dst = arena->ptr + arena->cur; int cap = arena->len - arena->cur; int num = 0; for (;;) { int off = cur; while (cur < len && (src[cur] >= ' ' && src[cur] <= '~' && src[cur] != '"' && src[cur] != '\\')) cur++; char *substr_ptr = src + off; int substr_len = cur - off; if (substr_len > 0) { if (substr_len > cap - num) return (JSON_String) { NULL, 0 }; memcpy(dst + num, substr_ptr, substr_len); num += substr_len; } if (cur == len) break; if (2 > cap - num) return (JSON_String) { NULL, 0 }; cur++; if (src[cur-1] == '"') { dst[num++] = '\\'; dst[num++] = '"'; } else if (src[cur-1] == '\\') { dst[num++] = '\\'; dst[num++] = '\\'; } else if (src[cur-1] == '\n') { dst[num++] = '\\'; dst[num++] = 'n'; } else if (src[cur-1] == '\r') { dst[num++] = '\\'; dst[num++] = 'r'; } else if (src[cur-1] == '\t') { dst[num++] = '\\'; dst[num++] = 't'; } else { return (JSON_String) { NULL, 0 }; } } arena->cur += num; return (JSON_String) { dst, num }; }