Files
cWEB/3p/json.c
T
2025-10-19 18:12:08 +02:00

1006 lines
24 KiB
C

#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <stdarg.h>
#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 };
}