summaryrefslogtreecommitdiffstats
path: root/src/json.c
diff options
context:
space:
mode:
authorMurilo Ijanc <[email protected]>2026-07-26 12:25:48 -0300
committerMurilo Ijanc <[email protected]>2026-07-26 12:25:48 -0300
commite2c6ae13af335b311da2429c903032124f926df2 (patch)
tree73776fab7f3e52124a217e2768968fc9a8a76c34 /src/json.c
downloadc-e2c6ae13af335b311da2429c903032124f926df2.tar.gz
Initial importv0.1.0
Diffstat (limited to 'src/json.c')
-rw-r--r--src/json.c550
1 files changed, 550 insertions, 0 deletions
diff --git a/src/json.c b/src/json.c
new file mode 100644
index 0000000..ee1382c
--- /dev/null
+++ b/src/json.c
@@ -0,0 +1,550 @@
+#include "json.h"
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+
+typedef struct {
+ const char *p;
+ const char *end;
+ int depth;
+} json_parser_t;
+
+static json_value_t *parse_value(json_parser_t *ps);
+
+static json_value_t *value_new(json_type_t type)
+{
+ json_value_t *v = calloc(1, sizeof(*v));
+ if (v != NULL)
+ v->type = type;
+ return v;
+}
+
+void json_free(json_value_t *value)
+{
+ size_t i;
+
+ if (value == NULL)
+ return;
+ switch (value->type) {
+ case JSON_STRING:
+ free(value->as.string);
+ break;
+ case JSON_ARRAY:
+ for (i = 0; i < value->as.array.count; i++)
+ json_free(value->as.array.items[i]);
+ free(value->as.array.items);
+ break;
+ case JSON_OBJECT:
+ for (i = 0; i < value->as.object.count; i++) {
+ free(value->as.object.keys[i]);
+ json_free(value->as.object.values[i]);
+ }
+ free(value->as.object.keys);
+ free(value->as.object.values);
+ break;
+ default:
+ break;
+ }
+ free(value);
+}
+
+static void skip_ws(json_parser_t *ps)
+{
+ while (ps->p < ps->end) {
+ char c = *ps->p;
+ if (c != ' ' && c != '\t' && c != '\n' && c != '\r')
+ return;
+ ps->p++;
+ }
+}
+
+static bool at(const json_parser_t *ps, char c)
+{
+ return ps->p < ps->end && *ps->p == c;
+}
+
+static bool literal(json_parser_t *ps, const char *word)
+{
+ size_t n = strlen(word);
+
+ if ((size_t)(ps->end - ps->p) < n)
+ return false;
+ if (memcmp(ps->p, word, n) != 0)
+ return false;
+ ps->p += n;
+ return true;
+}
+
+static int hex_digit(char c)
+{
+ if (c >= '0' && c <= '9')
+ return c - '0';
+ if (c >= 'a' && c <= 'f')
+ return c - 'a' + 10;
+ if (c >= 'A' && c <= 'F')
+ return c - 'A' + 10;
+ return -1;
+}
+
+static bool read_hex4(json_parser_t *ps, unsigned *out)
+{
+ unsigned value = 0;
+ int i;
+
+ if (ps->end - ps->p < 4)
+ return false;
+ for (i = 0; i < 4; i++) {
+ int digit = hex_digit(ps->p[i]);
+ if (digit < 0)
+ return false;
+ value = (value << 4) | (unsigned)digit;
+ }
+ ps->p += 4;
+ *out = value;
+ return true;
+}
+
+static size_t utf8_encode(unsigned cp, char *out)
+{
+ if (cp < 0x80) {
+ out[0] = (char)cp;
+ return 1;
+ }
+ if (cp < 0x800) {
+ out[0] = (char)(0xC0 | (cp >> 6));
+ out[1] = (char)(0x80 | (cp & 0x3F));
+ return 2;
+ }
+ if (cp < 0x10000) {
+ out[0] = (char)(0xE0 | (cp >> 12));
+ out[1] = (char)(0x80 | ((cp >> 6) & 0x3F));
+ out[2] = (char)(0x80 | (cp & 0x3F));
+ return 3;
+ }
+ out[0] = (char)(0xF0 | (cp >> 18));
+ out[1] = (char)(0x80 | ((cp >> 12) & 0x3F));
+ out[2] = (char)(0x80 | ((cp >> 6) & 0x3F));
+ out[3] = (char)(0x80 | (cp & 0x3F));
+ return 4;
+}
+
+typedef struct {
+ char *data;
+ size_t len;
+ size_t cap;
+} strbuf_t;
+
+static bool strbuf_push(strbuf_t *sb, const char *data, size_t n)
+{
+ if (sb->len + n + 1 > sb->cap) {
+ size_t cap = sb->cap ? sb->cap : 32;
+ char *grown;
+ while (sb->len + n + 1 > cap)
+ cap *= 2;
+ grown = realloc(sb->data, cap);
+ if (grown == NULL)
+ return false;
+ sb->data = grown;
+ sb->cap = cap;
+ }
+ memcpy(sb->data + sb->len, data, n);
+ sb->len += n;
+ sb->data[sb->len] = '\0';
+ return true;
+}
+
+static bool parse_escape(json_parser_t *ps, strbuf_t *sb)
+{
+ char encoded[4];
+ unsigned cp;
+ char c;
+
+ if (ps->p >= ps->end)
+ return false;
+ c = *ps->p++;
+ switch (c) {
+ case '"':
+ return strbuf_push(sb, "\"", 1);
+ case '\\':
+ return strbuf_push(sb, "\\", 1);
+ case '/':
+ return strbuf_push(sb, "/", 1);
+ case 'b':
+ return strbuf_push(sb, "\b", 1);
+ case 'f':
+ return strbuf_push(sb, "\f", 1);
+ case 'n':
+ return strbuf_push(sb, "\n", 1);
+ case 'r':
+ return strbuf_push(sb, "\r", 1);
+ case 't':
+ return strbuf_push(sb, "\t", 1);
+ case 'u':
+ break;
+ default:
+ return false;
+ }
+
+ if (!read_hex4(ps, &cp))
+ return false;
+ if (cp >= 0xD800 && cp <= 0xDBFF) {
+ unsigned low;
+ const char *save = ps->p;
+ if (ps->end - ps->p >= 2 && ps->p[0] == '\\' &&
+ ps->p[1] == 'u') {
+ ps->p += 2;
+ if (!read_hex4(ps, &low))
+ return false;
+ if (low >= 0xDC00 && low <= 0xDFFF) {
+ cp = 0x10000 + ((cp - 0xD800) << 10) +
+ (low - 0xDC00);
+ } else {
+ ps->p = save;
+ cp = 0xFFFD;
+ }
+ } else {
+ cp = 0xFFFD;
+ }
+ } else if (cp >= 0xDC00 && cp <= 0xDFFF) {
+ cp = 0xFFFD;
+ }
+ return strbuf_push(sb, encoded, utf8_encode(cp, encoded));
+}
+
+static char *parse_raw_string(json_parser_t *ps)
+{
+ strbuf_t sb = {NULL, 0, 0};
+
+ if (!at(ps, '"'))
+ return NULL;
+ ps->p++;
+ if (!strbuf_push(&sb, "", 0))
+ return NULL;
+
+ while (ps->p < ps->end) {
+ char c = *ps->p;
+ if (c == '"') {
+ ps->p++;
+ return sb.data;
+ }
+ if (c == '\\') {
+ ps->p++;
+ if (!parse_escape(ps, &sb))
+ break;
+ continue;
+ }
+ if ((unsigned char)c < 0x20)
+ break;
+ ps->p++;
+ if (!strbuf_push(&sb, &c, 1))
+ break;
+ }
+ free(sb.data);
+ return NULL;
+}
+
+static json_value_t *parse_string(json_parser_t *ps)
+{
+ char *text = parse_raw_string(ps);
+ json_value_t *v;
+
+ if (text == NULL)
+ return NULL;
+ v = value_new(JSON_STRING);
+ if (v == NULL) {
+ free(text);
+ return NULL;
+ }
+ v->as.string = text;
+ return v;
+}
+
+static json_value_t *parse_number(json_parser_t *ps)
+{
+ const char *start = ps->p;
+ json_value_t *v;
+ char *stop = NULL;
+ char token[64];
+ size_t n;
+ double number;
+
+ if (at(ps, '-'))
+ ps->p++;
+ if (ps->p >= ps->end)
+ return NULL;
+ if (*ps->p == '0')
+ ps->p++;
+ else if (*ps->p >= '1' && *ps->p <= '9')
+ while (ps->p < ps->end && *ps->p >= '0' && *ps->p <= '9')
+ ps->p++;
+ else
+ return NULL;
+ if (at(ps, '.')) {
+ ps->p++;
+ if (ps->p >= ps->end || *ps->p < '0' || *ps->p > '9')
+ return NULL;
+ while (ps->p < ps->end && *ps->p >= '0' && *ps->p <= '9')
+ ps->p++;
+ }
+ if (at(ps, 'e') || at(ps, 'E')) {
+ ps->p++;
+ if (at(ps, '+') || at(ps, '-'))
+ ps->p++;
+ if (ps->p >= ps->end || *ps->p < '0' || *ps->p > '9')
+ return NULL;
+ while (ps->p < ps->end && *ps->p >= '0' && *ps->p <= '9')
+ ps->p++;
+ }
+
+ n = (size_t)(ps->p - start);
+ if (n == 0 || n >= sizeof(token))
+ return NULL;
+ memcpy(token, start, n);
+ token[n] = '\0';
+
+ errno = 0;
+ number = strtod(token, &stop);
+ if (stop != token + n)
+ return NULL;
+
+ v = value_new(JSON_NUMBER);
+ if (v == NULL)
+ return NULL;
+ v->as.number = number;
+ return v;
+}
+
+static bool grow_slots(void ***slots, size_t count, size_t *cap, size_t size)
+{
+ void **grown;
+ size_t next;
+
+ if (count < *cap)
+ return true;
+ next = *cap ? *cap * 2 : 8;
+ grown = realloc(*slots, next * size);
+ if (grown == NULL)
+ return false;
+ memset((char *)grown + count * size, 0, (next - count) * size);
+ *slots = grown;
+ *cap = next;
+ return true;
+}
+
+static json_value_t *parse_array(json_parser_t *ps)
+{
+ json_value_t *v = value_new(JSON_ARRAY);
+ size_t cap = 0;
+
+ if (v == NULL)
+ return NULL;
+ ps->p++;
+ skip_ws(ps);
+ if (at(ps, ']')) {
+ ps->p++;
+ return v;
+ }
+ for (;;) {
+ json_value_t *item;
+ if (!grow_slots((void ***)&v->as.array.items, v->as.array.count,
+ &cap, sizeof(json_value_t *)))
+ break;
+ item = parse_value(ps);
+ if (item == NULL)
+ break;
+ v->as.array.items[v->as.array.count++] = item;
+ skip_ws(ps);
+ if (at(ps, ',')) {
+ ps->p++;
+ continue;
+ }
+ if (at(ps, ']')) {
+ ps->p++;
+ return v;
+ }
+ break;
+ }
+ json_free(v);
+ return NULL;
+}
+
+static json_value_t *parse_object(json_parser_t *ps)
+{
+ json_value_t *v = value_new(JSON_OBJECT);
+ size_t cap_keys = 0;
+ size_t cap_values = 0;
+
+ if (v == NULL)
+ return NULL;
+ ps->p++;
+ skip_ws(ps);
+ if (at(ps, '}')) {
+ ps->p++;
+ return v;
+ }
+ for (;;) {
+ char *key;
+ json_value_t *item;
+
+ skip_ws(ps);
+ if (!grow_slots((void ***)&v->as.object.keys,
+ v->as.object.count, &cap_keys, sizeof(char *)))
+ break;
+ if (!grow_slots((void ***)&v->as.object.values,
+ v->as.object.count, &cap_values,
+ sizeof(json_value_t *)))
+ break;
+ key = parse_raw_string(ps);
+ if (key == NULL)
+ break;
+ skip_ws(ps);
+ if (!at(ps, ':')) {
+ free(key);
+ break;
+ }
+ ps->p++;
+ item = parse_value(ps);
+ if (item == NULL) {
+ free(key);
+ break;
+ }
+ v->as.object.keys[v->as.object.count] = key;
+ v->as.object.values[v->as.object.count] = item;
+ v->as.object.count++;
+
+ skip_ws(ps);
+ if (at(ps, ',')) {
+ ps->p++;
+ continue;
+ }
+ if (at(ps, '}')) {
+ ps->p++;
+ return v;
+ }
+ break;
+ }
+ json_free(v);
+ return NULL;
+}
+
+static json_value_t *parse_value(json_parser_t *ps)
+{
+ json_value_t *v;
+
+ if (ps->depth >= JSON_MAX_DEPTH)
+ return NULL;
+ skip_ws(ps);
+ if (ps->p >= ps->end)
+ return NULL;
+
+ switch (*ps->p) {
+ case '"':
+ return parse_string(ps);
+ case '{':
+ ps->depth++;
+ v = parse_object(ps);
+ ps->depth--;
+ return v;
+ case '[':
+ ps->depth++;
+ v = parse_array(ps);
+ ps->depth--;
+ return v;
+ case 't':
+ if (!literal(ps, "true"))
+ return NULL;
+ v = value_new(JSON_BOOL);
+ if (v != NULL)
+ v->as.boolean = true;
+ return v;
+ case 'f':
+ if (!literal(ps, "false"))
+ return NULL;
+ v = value_new(JSON_BOOL);
+ if (v != NULL)
+ v->as.boolean = false;
+ return v;
+ case 'n':
+ if (!literal(ps, "null"))
+ return NULL;
+ return value_new(JSON_NULL);
+ default:
+ return parse_number(ps);
+ }
+}
+
+json_value_t *json_parse(const char *text, size_t len)
+{
+ json_parser_t ps;
+ json_value_t *root;
+
+ if (text == NULL)
+ return NULL;
+ ps.p = text;
+ ps.end = text + len;
+ ps.depth = 0;
+
+ if (len >= 3 && memcmp(text, "\xEF\xBB\xBF", 3) == 0)
+ ps.p += 3;
+
+ root = parse_value(&ps);
+ if (root == NULL)
+ return NULL;
+ skip_ws(&ps);
+ if (ps.p != ps.end) {
+ json_free(root);
+ return NULL;
+ }
+ return root;
+}
+
+const json_value_t *json_object_get(const json_value_t *object, const char *key)
+{
+ size_t i;
+
+ if (object == NULL || object->type != JSON_OBJECT || key == NULL)
+ return NULL;
+ for (i = 0; i < object->as.object.count; i++)
+ if (strcmp(object->as.object.keys[i], key) == 0)
+ return object->as.object.values[i];
+ return NULL;
+}
+
+const char *json_as_string(const json_value_t *value)
+{
+ if (value == NULL || value->type != JSON_STRING)
+ return NULL;
+ return value->as.string;
+}
+
+bool json_as_double(const json_value_t *value, double *out)
+{
+ char *stop = NULL;
+ double parsed;
+
+ if (value == NULL)
+ return false;
+ if (value->type == JSON_NUMBER) {
+ *out = value->as.number;
+ return true;
+ }
+ if (value->type != JSON_STRING || value->as.string[0] == '\0')
+ return false;
+ errno = 0;
+ parsed = strtod(value->as.string, &stop);
+ if (stop == NULL || *stop != '\0' || errno == ERANGE)
+ return false;
+ *out = parsed;
+ return true;
+}
+
+bool json_as_llong(const json_value_t *value, long long *out)
+{
+ double parsed;
+
+ if (!json_as_double(value, &parsed))
+ return false;
+ *out = (long long)parsed;
+ return true;
+}