From 88eb8c969f6abe119994c7ab307b9be58ff076a5 Mon Sep 17 00:00:00 2001 From: Martin Kagamino Lehoux Date: Mon, 21 Jul 2025 14:26:06 +0200 Subject: feat: JSON object parsing --- json.c | 51 +++++++++++++++++++++++++++++++++++++++++++++++++-- json.h | 9 ++++++++- string.c | 15 +++++++-------- string.h | 2 +- test_json.c | 36 +++++++++++++++++++++++++++++++----- testing.h | 6 ++++++ vec.c | 6 +----- 7 files changed, 103 insertions(+), 22 deletions(-) diff --git a/json.c b/json.c index 2aa3013..0ef03a2 100644 --- a/json.c +++ b/json.c @@ -3,8 +3,8 @@ #include #include #include +#include #include -#include #include "string.h" @@ -43,7 +43,7 @@ uintptr_t absorb_whitespaces(char *src, uintptr_t pos) { } typedef struct { - Vec *items; + Vec *items; // Vec uintptr_t pos; } parse_array_result; @@ -86,6 +86,40 @@ parse_string_result parse_string(Arena *a, char *src, uintptr_t pos) { return (parse_string_result){s, pos + i + 1}; } +typedef struct { + JSONObject *object; + uintptr_t pos; +} parse_object_result; + +parse_object_result parse_object(Arena *a, char *src, uintptr_t pos) { + assert(src[pos] == '{'); + JSONObject *object = new(a, JSONObject, 1); + object->len = 0; + object->keys = Vec_new(a, Str, 2); + object->values = Vec_new(a, JSON, 2); + pos = absorb_whitespaces(src, pos + 1); + if (src[pos] == '}') { + return (parse_object_result){object, pos + 1}; + } + for (;;) { + assert(src[pos] == '"'); + parse_string_result str_result = parse_string(a, src, pos); + Str key = str_result.string; + pos = absorb_whitespaces(src, str_result.pos); + assert(src[pos] == ':'); + pos = absorb_whitespaces(src, pos+1); + parse_any_result any_result = parse_any(a, src, pos); + object->len++; + Vec_push(a, &object->keys, &key); + Vec_push(a, &object->values, any_result.value); + pos = absorb_whitespaces(src, any_result.pos); + if (src[pos] != ',') {break;} + pos = absorb_whitespaces(src, pos +1); + } + assert(src[pos] == '}'); + return (parse_object_result){object, pos+1}; +} + parse_any_result parse_any(Arena *a, char *src, uintptr_t pos) { JSON *value = new (a, JSON, 1); if (isdigit(src[pos])) { @@ -100,6 +134,10 @@ parse_any_result parse_any(Arena *a, char *src, uintptr_t pos) { parse_string_result result = parse_string(a, src, pos); value->string = &result.string; pos = result.pos; + } else if (src[pos] == '{') { + parse_object_result result = parse_object(a, src, pos); + value->object = result.object; + pos = result.pos; } return (parse_any_result){value, pos}; @@ -116,3 +154,12 @@ JSON JSON_Int(Arena *a, int val) { *json.integer = val; return json; } + +JSON* JSONObject_get(JSONObject *obj, Str key) { + for (ptrdiff_t i = 0; i < obj->len; i++) { + if (Str_equals(*Vec_get(&obj->keys, Str, i), key)) { + return Vec_get(&obj->values, JSON, i); + } + } + return NULL; +} diff --git a/json.h b/json.h index 44b064e..5f2f4c0 100644 --- a/json.h +++ b/json.h @@ -4,11 +4,18 @@ #include "string.h" #include "vec.h" +typedef struct { + Vec keys; // Vec + Vec values; // Vec + ptrdiff_t len; +} JSONObject; typedef struct { int *integer; - Vec *array; + Vec *array; // Vec Str *string; + JSONObject *object; } JSON; +JSON* JSONObject_get(JSONObject *obj, Str key); JSON JSON_parse(Arena *a, char *src); JSON JSON_Int(Arena *a, int val); #endif diff --git a/string.c b/string.c index cc78719..191059e 100644 --- a/string.c +++ b/string.c @@ -1,16 +1,11 @@ #include "string.h" #include +#include #include #include "memory.h" -Str *Str_new(Arena *a, uintptr_t len) { - Str *s = new (a, Str, 1); - s->data = new (a, char, len); - return s; -} - // Returns 1 if strings are equal, 0 otherwise int Str_equals(Str a, Str b) { return (a.len == b.len) && (!a.len || !memcmp(a.data, b.data, a.len)); @@ -26,9 +21,13 @@ uint64_t Str_hash64(Str s) { } Str Str_copy(Arena *a, char *data, uintptr_t len) { - Str str = {0, len}; - str.data = new (a, char, len); + Str str = {.data = NULL, .len = len}; + str.data = new (a, char, len); if (str.len) memcpy(str.data, data, len); return str; } + +void Str_print(Str s) { + printf("%.*s", (int)s.len, s.data); +} diff --git a/string.h b/string.h index c441373..d8835f4 100644 --- a/string.h +++ b/string.h @@ -10,8 +10,8 @@ typedef struct { char *data; ptrdiff_t len; } Str; -Str *Str_new(Arena *a, uintptr_t len); int Str_equals(Str a, Str b); uint64_t Str_hash64(Str s); Str Str_copy(Arena *a, char *data, uintptr_t len); +void Str_print(Str s); #endif diff --git a/test_json.c b/test_json.c index 751b5bd..e187b62 100644 --- a/test_json.c +++ b/test_json.c @@ -1,5 +1,6 @@ #include "json.h" #include "testing.h" +#include char test_json_integer() { Arena perm = Arena_new(128e3); @@ -7,7 +8,7 @@ char test_json_integer() { JSON json_int = JSON_parse(&perm, "123"); t_assert(*json_int.integer == 123); - t_assert(Arena_get_used(perm) == 28); + t_assert_equal(Arena_get_used(perm), 36); return 0; } @@ -17,7 +18,7 @@ char test_json_int_array() { JSON json_int_array = JSON_parse(&perm, "[1,2,3]"); t_assert(json_int_array.integer == NULL); - t_assert(json_int_array.array->len == 3); + t_assert_equal(json_int_array.array->len, 3); int ints[3]; int sum = 0; for (int i = 0; i < json_int_array.array->len; i++) { @@ -29,7 +30,7 @@ char test_json_int_array() { t_assert(ints[1] == 2); t_assert(ints[2] == 3); t_assert(sum == 6); - t_assert(Arena_get_used(perm) == 264); + t_assert_equal(Arena_get_used(perm), 344); return 0; } @@ -46,7 +47,7 @@ char test_json_nested_array() { Str str = *val_2.string; Str expected = S("abc"); t_assert(Str_equals(str, expected)); - t_assert(Arena_get_used(perm) == 203); + t_assert_equal(Arena_get_used(perm), 267); return 0; } @@ -57,7 +58,32 @@ char test_json_string() { JSON js = JSON_parse(&perm, "\"abc\""); t_assert(Str_equals(*js.string, S("abc"))); - t_assert(Arena_get_used(perm) == 27); + t_assert_equal(Arena_get_used(perm), 35); return 0; } + +char test_json_empty_object() { + Arena perm = Arena_new(128e3); + + JSON doc = JSON_parse(&perm, "{}"); + + t_assert_equal(doc.object->len, 0); + t_assert_equal(Arena_get_used(perm), 216); + + return 0; +} + +char test_json_object() { + Arena perm = Arena_new(128e3); + + JSON doc = JSON_parse(&perm, "{\"key\": \"value\"}"); + + t_assert_equal(Arena_get_used(perm), 261); + t_assert_equal(doc.object->len, 1); + t_assert(Str_equals(*Vec_get(&doc.object->keys, Str, 0), S("key"))); + t_assert(Str_equals(*JSONObject_get(doc.object, S("key"))->string, S("value"))) + t_assert_equal(Arena_get_used(perm), 261); + + return 0; +} diff --git a/testing.h b/testing.h index cb40294..0b479c9 100644 --- a/testing.h +++ b/testing.h @@ -5,3 +5,9 @@ fprintf(stderr, "Assertion failed: %s at %s:%d in %s()\n", #fact, __FILE__, __LINE__, __func__); \ return 1; \ } + + # define t_assert_equal(a, b) \ + if (!(a == b)) { \ + fprintf(stderr, "Assertion failed: %d (%s) != %d (%s) at %s:%d in %s()\n", a, #a, b, #b, __FILE__, __LINE__, __func__); \ + return 1; \ + } diff --git a/vec.c b/vec.c index a8fd57a..af5f338 100644 --- a/vec.c +++ b/vec.c @@ -5,12 +5,8 @@ Vec _Vec_new(Arena *a, int size, ptrdiff_t cap, int align) { assert(cap > 0); - Vec v = {0}; + Vec v = {.size = size, .cap = cap, .len = 0, .align = align,.data = NULL}; v.data = alloc(a, cap, size, align); - v.size = size; - v.align = align; - v.cap = cap; - v.len = 0; return v; } -- cgit v1.3