mirror of
https://github.com/allaunthefox/Research-Stack.git
synced 2026-08-08 18:05:47 +00:00
304 lines
7.5 KiB
C
304 lines
7.5 KiB
C
/*
|
|
* Tardygrada — Minimal JSON Parser Implementation
|
|
* Zero-copy: tokens point into the original buffer.
|
|
*/
|
|
|
|
#include "json.h"
|
|
#include <string.h>
|
|
|
|
static void skip_ws(tardy_json_parser_t *p)
|
|
{
|
|
while (p->pos < p->src_len) {
|
|
char c = p->src[p->pos];
|
|
if (c == ' ' || c == '\t' || c == '\n' || c == '\r')
|
|
p->pos++;
|
|
else
|
|
break;
|
|
}
|
|
}
|
|
|
|
static int parse_string(tardy_json_parser_t *p)
|
|
{
|
|
if (p->count >= TARDY_JSON_MAX_TOKENS || p->src[p->pos] != '"')
|
|
return -1;
|
|
|
|
p->pos++; /* skip opening quote */
|
|
int start = p->pos;
|
|
|
|
while (p->pos < p->src_len) {
|
|
if (p->src[p->pos] == '\\') {
|
|
p->pos += 2; /* skip escaped char */
|
|
continue;
|
|
}
|
|
if (p->src[p->pos] == '"')
|
|
break;
|
|
p->pos++;
|
|
}
|
|
|
|
if (p->pos >= p->src_len)
|
|
return -1;
|
|
|
|
int idx = p->count++;
|
|
p->tokens[idx].type = TARDY_JSON_STRING;
|
|
p->tokens[idx].start = p->src + start;
|
|
p->tokens[idx].len = p->pos - start;
|
|
p->tokens[idx].children = 0;
|
|
|
|
p->pos++; /* skip closing quote */
|
|
return idx;
|
|
}
|
|
|
|
static int parse_value(tardy_json_parser_t *p);
|
|
|
|
static int parse_number(tardy_json_parser_t *p)
|
|
{
|
|
if (p->count >= TARDY_JSON_MAX_TOKENS)
|
|
return -1;
|
|
|
|
int start = p->pos;
|
|
if (p->src[p->pos] == '-')
|
|
p->pos++;
|
|
|
|
while (p->pos < p->src_len &&
|
|
p->src[p->pos] >= '0' && p->src[p->pos] <= '9')
|
|
p->pos++;
|
|
|
|
if (p->pos < p->src_len && p->src[p->pos] == '.') {
|
|
p->pos++;
|
|
while (p->pos < p->src_len &&
|
|
p->src[p->pos] >= '0' && p->src[p->pos] <= '9')
|
|
p->pos++;
|
|
}
|
|
|
|
int idx = p->count++;
|
|
p->tokens[idx].type = TARDY_JSON_NUMBER;
|
|
p->tokens[idx].start = p->src + start;
|
|
p->tokens[idx].len = p->pos - start;
|
|
p->tokens[idx].children = 0;
|
|
return idx;
|
|
}
|
|
|
|
static int parse_object(tardy_json_parser_t *p)
|
|
{
|
|
if (p->count >= TARDY_JSON_MAX_TOKENS || p->src[p->pos] != '{')
|
|
return -1;
|
|
|
|
int idx = p->count++;
|
|
p->tokens[idx].type = TARDY_JSON_OBJECT;
|
|
p->tokens[idx].start = p->src + p->pos;
|
|
p->tokens[idx].children = 0;
|
|
|
|
p->pos++; /* skip { */
|
|
skip_ws(p);
|
|
|
|
while (p->pos < p->src_len && p->src[p->pos] != '}') {
|
|
/* Parse key */
|
|
skip_ws(p);
|
|
if (parse_string(p) < 0)
|
|
return -1;
|
|
|
|
/* Skip colon */
|
|
skip_ws(p);
|
|
if (p->pos >= p->src_len || p->src[p->pos] != ':')
|
|
return -1;
|
|
p->pos++;
|
|
|
|
/* Parse value */
|
|
skip_ws(p);
|
|
if (parse_value(p) < 0)
|
|
return -1;
|
|
|
|
p->tokens[idx].children += 2; /* key + value */
|
|
|
|
skip_ws(p);
|
|
if (p->pos < p->src_len && p->src[p->pos] == ',')
|
|
p->pos++;
|
|
}
|
|
|
|
if (p->pos >= p->src_len)
|
|
return -1;
|
|
p->pos++; /* skip } */
|
|
|
|
p->tokens[idx].len = (int)(p->src + p->pos - p->tokens[idx].start);
|
|
return idx;
|
|
}
|
|
|
|
static int parse_array(tardy_json_parser_t *p)
|
|
{
|
|
if (p->count >= TARDY_JSON_MAX_TOKENS || p->src[p->pos] != '[')
|
|
return -1;
|
|
|
|
int idx = p->count++;
|
|
p->tokens[idx].type = TARDY_JSON_ARRAY;
|
|
p->tokens[idx].start = p->src + p->pos;
|
|
p->tokens[idx].children = 0;
|
|
|
|
p->pos++; /* skip [ */
|
|
skip_ws(p);
|
|
|
|
while (p->pos < p->src_len && p->src[p->pos] != ']') {
|
|
skip_ws(p);
|
|
if (parse_value(p) < 0)
|
|
return -1;
|
|
p->tokens[idx].children++;
|
|
|
|
skip_ws(p);
|
|
if (p->pos < p->src_len && p->src[p->pos] == ',')
|
|
p->pos++;
|
|
}
|
|
|
|
if (p->pos >= p->src_len)
|
|
return -1;
|
|
p->pos++; /* skip ] */
|
|
|
|
p->tokens[idx].len = (int)(p->src + p->pos - p->tokens[idx].start);
|
|
return idx;
|
|
}
|
|
|
|
static int parse_value(tardy_json_parser_t *p)
|
|
{
|
|
skip_ws(p);
|
|
if (p->pos >= p->src_len)
|
|
return -1;
|
|
|
|
char c = p->src[p->pos];
|
|
|
|
if (c == '"')
|
|
return parse_string(p);
|
|
if (c == '{')
|
|
return parse_object(p);
|
|
if (c == '[')
|
|
return parse_array(p);
|
|
if (c == '-' || (c >= '0' && c <= '9'))
|
|
return parse_number(p);
|
|
|
|
/* true, false, null */
|
|
if (p->count >= TARDY_JSON_MAX_TOKENS)
|
|
return -1;
|
|
|
|
int idx = p->count++;
|
|
if (p->src_len - p->pos >= 4 && memcmp(p->src + p->pos, "true", 4) == 0) {
|
|
p->tokens[idx].type = TARDY_JSON_BOOL;
|
|
p->tokens[idx].start = p->src + p->pos;
|
|
p->tokens[idx].len = 4;
|
|
p->pos += 4;
|
|
} else if (p->src_len - p->pos >= 5 &&
|
|
memcmp(p->src + p->pos, "false", 5) == 0) {
|
|
p->tokens[idx].type = TARDY_JSON_BOOL;
|
|
p->tokens[idx].start = p->src + p->pos;
|
|
p->tokens[idx].len = 5;
|
|
p->pos += 5;
|
|
} else if (p->src_len - p->pos >= 4 &&
|
|
memcmp(p->src + p->pos, "null", 4) == 0) {
|
|
p->tokens[idx].type = TARDY_JSON_NULL;
|
|
p->tokens[idx].start = p->src + p->pos;
|
|
p->tokens[idx].len = 4;
|
|
p->pos += 4;
|
|
} else {
|
|
p->count--;
|
|
return -1;
|
|
}
|
|
|
|
p->tokens[idx].children = 0;
|
|
return idx;
|
|
}
|
|
|
|
int tardy_json_parse(tardy_json_parser_t *p, const char *json, int len)
|
|
{
|
|
memset(p, 0, sizeof(tardy_json_parser_t));
|
|
p->src = json;
|
|
p->src_len = len;
|
|
p->pos = 0;
|
|
p->count = 0;
|
|
|
|
if (parse_value(p) < 0)
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
int tardy_json_find(const tardy_json_parser_t *p, int object_tok,
|
|
const char *key)
|
|
{
|
|
if (object_tok < 0 || object_tok >= p->count)
|
|
return -1;
|
|
if (p->tokens[object_tok].type != TARDY_JSON_OBJECT)
|
|
return -1;
|
|
|
|
int keylen = (int)strlen(key);
|
|
int i = object_tok + 1;
|
|
int pairs = p->tokens[object_tok].children / 2;
|
|
|
|
for (int pair = 0; pair < pairs; pair++) {
|
|
if (i >= p->count)
|
|
break;
|
|
/* Check key */
|
|
if (p->tokens[i].type == TARDY_JSON_STRING &&
|
|
p->tokens[i].len == keylen &&
|
|
memcmp(p->tokens[i].start, key, keylen) == 0) {
|
|
return i + 1; /* return value token */
|
|
}
|
|
/* Skip key + value (and value's children) */
|
|
i++; /* skip key */
|
|
/* Skip value subtree */
|
|
int skip = 1;
|
|
int j = i;
|
|
while (skip > 0 && j < p->count) {
|
|
if (p->tokens[j].type == TARDY_JSON_OBJECT ||
|
|
p->tokens[j].type == TARDY_JSON_ARRAY)
|
|
skip += p->tokens[j].children;
|
|
skip--;
|
|
j++;
|
|
}
|
|
i = j;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
int tardy_json_str(const tardy_json_parser_t *p, int tok,
|
|
char *buf, int buf_size)
|
|
{
|
|
if (tok < 0 || tok >= p->count)
|
|
return -1;
|
|
if (p->tokens[tok].type != TARDY_JSON_STRING)
|
|
return -1;
|
|
|
|
int copy = p->tokens[tok].len;
|
|
if (copy >= buf_size)
|
|
copy = buf_size - 1;
|
|
memcpy(buf, p->tokens[tok].start, copy);
|
|
buf[copy] = '\0';
|
|
return copy;
|
|
}
|
|
|
|
long tardy_json_int(const tardy_json_parser_t *p, int tok)
|
|
{
|
|
if (tok < 0 || tok >= p->count)
|
|
return 0;
|
|
|
|
long val = 0;
|
|
int neg = 0;
|
|
const char *s = p->tokens[tok].start;
|
|
int len = p->tokens[tok].len;
|
|
int i = 0;
|
|
|
|
if (len > 0 && s[0] == '-') {
|
|
neg = 1;
|
|
i = 1;
|
|
}
|
|
for (; i < len; i++) {
|
|
if (s[i] < '0' || s[i] > '9')
|
|
break;
|
|
val = val * 10 + (s[i] - '0');
|
|
}
|
|
return neg ? -val : val;
|
|
}
|
|
|
|
bool tardy_json_eq(const tardy_json_parser_t *p, int tok,
|
|
const char *str)
|
|
{
|
|
if (tok < 0 || tok >= p->count)
|
|
return false;
|
|
int slen = (int)strlen(str);
|
|
return p->tokens[tok].len == slen &&
|
|
memcmp(p->tokens[tok].start, str, slen) == 0;
|
|
}
|