From 421300c27123ca647772dc6d9bd0cabf7f5f9c99 Mon Sep 17 00:00:00 2001 From: Martin Kagamino Lehoux Date: Wed, 23 Jul 2025 19:39:13 +0200 Subject: refactor: src & pragma once --- .gitignore | 1 + CMakeLists.txt | 23 +---- Makefile | 2 +- README.md | 2 - base_test.c | 2 +- generate.fish | 2 +- include/json.h | 34 +++++++ include/log.h | 10 ++ include/memory.h | 16 ++++ include/str.h | 18 ++++ include/testing.h | 24 +++++ include/types.h | 14 +++ include/vec.h | 19 ++++ json.c | 265 ---------------------------------------------------- json.h | 36 ------- log.c | 13 --- log.h | 10 -- main.c | 6 +- memory.c | 30 ------ memory.h | 17 ---- src/json.c | 263 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/log.c | 12 +++ src/memory.c | 30 ++++++ src/str.c | 32 +++++++ src/vec.c | 29 ++++++ string.c | 33 ------- string.h | 20 ---- test_json.c | 207 ---------------------------------------- test_string.c | 19 ---- test_vec.c | 25 ----- testing.h | 24 ----- tests/test_json.c | 207 ++++++++++++++++++++++++++++++++++++++++ tests/test_string.c | 19 ++++ tests/test_vec.c | 25 +++++ types.h | 14 --- vec.c | 29 ------ vec.h | 21 ----- 37 files changed, 764 insertions(+), 789 deletions(-) create mode 100644 include/json.h create mode 100644 include/log.h create mode 100644 include/memory.h create mode 100644 include/str.h create mode 100644 include/testing.h create mode 100644 include/types.h create mode 100644 include/vec.h delete mode 100644 json.c delete mode 100644 json.h delete mode 100644 log.c delete mode 100644 log.h delete mode 100644 memory.c delete mode 100644 memory.h create mode 100644 src/json.c create mode 100644 src/log.c create mode 100644 src/memory.c create mode 100644 src/str.c create mode 100644 src/vec.c delete mode 100644 string.c delete mode 100644 string.h delete mode 100644 test_json.c delete mode 100644 test_string.c delete mode 100644 test_vec.c delete mode 100644 testing.h create mode 100644 tests/test_json.c create mode 100644 tests/test_string.c create mode 100644 tests/test_vec.c delete mode 100644 types.h delete mode 100644 vec.c delete mode 100644 vec.h diff --git a/.gitignore b/.gitignore index c7a86e2..2ec9e4f 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ Debug/ Release/ test.c +out.json diff --git a/CMakeLists.txt b/CMakeLists.txt index c022fc3..a5fd18e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,26 +2,13 @@ cmake_minimum_required(VERSION 3.30.5) project(kagamic VERSION 0.1 LANGUAGES C) set(CMAKE_C_STANDARD 17) -add_executable(main - main.c - string.h string.c - memory.h memory.c - vec.h vec.c - json.h json.c - log.h log.c -) +file(GLOB SOURCES "src/*.c") +file(GLOB TEST_SOURCES "test/*.c") + +add_executable(main main.c ${SOURCES}) target_link_libraries(main m) target_compile_options(main PRIVATE -Wall -pedantic -Wextra) - -add_executable(test - test.c - testing.h - string.h string.c - memory.h memory.c - vec.h vec.c - json.h json.c - log.h log.c -) +add_executable(test test.c ${SOURCES} ${TEST_SOURCES}) target_link_libraries(test m) target_compile_options(test PRIVATE -Wall -pedantic -Wextra) diff --git a/Makefile b/Makefile index 234128c..2239241 100644 --- a/Makefile +++ b/Makefile @@ -16,4 +16,4 @@ test: ./Debug/test; format: - clang-format -i *.c *.h + clang-format -i src/*.c include/*.h tests/*.c *.c diff --git a/README.md b/README.md index d9cdf05..18c997c 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,6 @@ - Benchmark 100MB file - json.loads: 526 ms ± 77 ms per loop (mean ± std. dev. of 7 runs, 1 loop each) - self: 348 ms (mean of 7 runs, 1 loop each). 2079 MB/s -- More modern include pragma (once) -- File structure - Static functions for private? - `valgrind --leak-check=full ./your_program` - assert with error msg? diff --git a/base_test.c b/base_test.c index 6d095f8..a06aab4 100644 --- a/base_test.c +++ b/base_test.c @@ -1,4 +1,4 @@ -#include "types.h" +#include "include/types.h" #define test(func) \ switch (func()) { \ diff --git a/generate.fish b/generate.fish index b0e5308..96f8542 100644 --- a/generate.fish +++ b/generate.fish @@ -1,5 +1,5 @@ echo "" > test.c; -set fs (ls test_*.c); +set fs (ls tests/test_*.c); for f in $fs; echo "#include \"$f\"" >> test.c; end; diff --git a/include/json.h b/include/json.h new file mode 100644 index 0000000..749a3e0 --- /dev/null +++ b/include/json.h @@ -0,0 +1,34 @@ +#pragma once +#include "../include/memory.h" +#include "../include/str.h" +#include "../include/vec.h" + +typedef struct { + Vec keys; // Vec + Vec values; // Vec + size len; +} JSONObject; +typedef enum { + JSON_NULL, + JSON_BOOL, + JSON_INTEGER, + JSON_FLOAT, + JSON_STRING, + JSON_ARRAY, + JSON_OBJECT +} JSONType; +typedef struct { + JSONType type; + union { + bool boolean; + i32 integer; + f32 floating; + Vec array; // Vec + Str string; + JSONObject object; + } as; +} JSON; +JSON *JSONObject_get(JSONObject obj, Str key); +i32 JSON_fprint(FILE *w, JSON json); +JSON JSON_parse(Arena *a, byte *src); +JSON JSON_Int(Arena *a, i32 val); diff --git a/include/log.h b/include/log.h new file mode 100644 index 0000000..1458f03 --- /dev/null +++ b/include/log.h @@ -0,0 +1,10 @@ + +#include "types.h" + +typedef enum { INFO } LogLevel; + +typedef struct { + LogLevel level; +} Logger; + +void info(Logger l, const byte *name, const byte *str, ...); diff --git a/include/memory.h b/include/memory.h new file mode 100644 index 0000000..d202e97 --- /dev/null +++ b/include/memory.h @@ -0,0 +1,16 @@ +#pragma once +#include + +#include "types.h" + +typedef struct { + size cap; + byte *beg; + byte *end; +} Arena; + +Arena Arena_new(size cap); +#define new(a, T, n) (T *)alloc(a, n, sizeof(T), _Alignof(T)) +void *alloc(Arena *a, size count, size tsize, size align); +size Arena_get_used(Arena a); +void Arena_reset(Arena *a); diff --git a/include/str.h b/include/str.h new file mode 100644 index 0000000..300d5ea --- /dev/null +++ b/include/str.h @@ -0,0 +1,18 @@ +#pragma once +#include +#include +#include + +#include "../include/memory.h" +#include "../include/types.h" + +#define S(s) \ + (Str) { s, lengthof(s) } +typedef struct { + byte *data; + size len; +} Str; +i32 Str_equals(Str a, Str b); +u64 Str_hash64(Str s); +Str Str_copy(Arena *a, byte *data, size len); +i32 Str_fprint(FILE *w, Str s); diff --git a/include/testing.h b/include/testing.h new file mode 100644 index 0000000..9e0c022 --- /dev/null +++ b/include/testing.h @@ -0,0 +1,24 @@ +#include + +#define t_assert(fact) \ + if (!(fact)) { \ + 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", \ + (i32)a, #a, (i32)b, #b, __FILE__, __LINE__, __func__); \ + return 1; \ + } + +#define t_assert_fequal(a, b) \ + if ((a - b) > 1e-6 || (b - a) > 1e-6) { \ + fprintf(stderr, \ + "Assertion failed: %f (%s) != %f (%s) at %s:%d in %s()\n", a, \ + #a, b, #b, __FILE__, __LINE__, __func__); \ + return 1; \ + } diff --git a/include/types.h b/include/types.h new file mode 100644 index 0000000..bacedf8 --- /dev/null +++ b/include/types.h @@ -0,0 +1,14 @@ +#include +#include +#include + +typedef ptrdiff_t size; +typedef char byte; +typedef uintptr_t uptr; +typedef int32_t i32; +typedef uint64_t u64; +typedef float f32; + +#define sizeof(x) (size)sizeof(x) +#define countof(a) (sizeof(a) / sizeof(*(a))) +#define lengthof(s) (countof(s) - 1) diff --git a/include/vec.h b/include/vec.h new file mode 100644 index 0000000..bf6729b --- /dev/null +++ b/include/vec.h @@ -0,0 +1,19 @@ +#pragma once +#include +#include + +#include "../include/memory.h" +#include "../include/types.h" + +typedef struct { + void *data; + size len; + size cap; + size tsize; + size align; +} Vec; +#define Vec_new(a, T, cap) _Vec_new(a, sizeof(T), cap, _Alignof(T)) +Vec _Vec_new(Arena *a, size tsize, size cap, size align); +void Vec_push(Arena *a, Vec *v, void *data); +#define Vec_get(v, T, p) (T *)_Vec_get(v, p) +void *_Vec_get(Vec v, size pos); diff --git a/json.c b/json.c deleted file mode 100644 index b02c413..0000000 --- a/json.c +++ /dev/null @@ -1,265 +0,0 @@ -#include "json.h" - -#include -#include -#include -#include -#include -#include - -#include "json.h" -#include "string.h" -#include "types.h" - -JSONObject *JSONObject_new(Arena *a) { - JSONObject *object = new(a, JSONObject, 1); - object->len = 0; - object->keys = Vec_new(a, Str, 2); - object->values = Vec_new(a, JSON, 2); - return object; -} - -JSON JSON_integer(i32 value) { - JSON json = {0}; - json.type = JSON_INTEGER; - json.as.integer = value; - return json; -} - -JSON JSON_float(f32 value) { - JSON json = {0}; - json.type = JSON_FLOAT; - json.as.floating = value; - return json; -} - -JSON JSON_array(Vec items) { - JSON json = {0}; - json.type = JSON_ARRAY; - json.as.array = items; - return json; -} - -JSON JSON_string(Str value) { - JSON json = {0}; - json.type = JSON_STRING; - json.as.string = value; - return json; -} - -JSON JSON_object(size len, Vec keys, Vec values) { - assert(len == keys.len && len == values.len); - JSON json = {0}; - json.type = JSON_OBJECT; - json.as.object.len = len; - json.as.object.keys = keys; - json.as.object.values = values; - return json; -} - -JSON JSON_boolean(bool value) { - JSON json = {0}; - json.type = JSON_BOOL; - json.as.boolean = value; - return json; -} - -JSON JSON_null() { - JSON json = {0}; - json.type = JSON_NULL; - return json; -} - -i32 JSON_fprint(FILE *w, JSON json) { - switch (json.type) { - case JSON_STRING: - fprintf(w, "\""); - Str_fprint(w, json.as.string); - return fprintf(w, "\""); - case JSON_INTEGER: - return fprintf(w, "%d", json.as.integer); - case JSON_FLOAT: - return fprintf(w, "%f", json.as.floating); - case JSON_NULL: - return fprintf(w, "null"); - case JSON_BOOL: - return fprintf(w, "%s", json.as.boolean ? "true" : "false"); - case JSON_ARRAY: - fprintf(w, "["); - for (size i = 0; i < json.as.array.len; i++) { - JSON value = *Vec_get(json.as.array, JSON, i); - JSON_fprint(w, value); - if (i < json.as.array.len - 1) fprintf(w, ", "); - } - fprintf(w, "]"); - return 0; - case JSON_OBJECT: - fprintf(w, "{"); - for (size i = 0; i < json.as.object.len; i++) { - Str key = *Vec_get(json.as.object.keys, Str, i); - fprintf(w, "\""); - Str_fprint(w, key); - fprintf(w, "\": "); - JSON value = *Vec_get(json.as.object.values, JSON, i); - JSON_fprint(w, value); - if (i < json.as.object.len - 1) fprintf(w, ", "); - } - fprintf(w, "}"); - return 0; - } - assert(false); -} - -typedef struct { - JSON value; - uptr pos; -} parse_result; - -parse_result parse_any(Arena *a, byte *src, uptr pos); - -parse_result parse_number(byte *src, uptr start_pos) { - parse_result result = {0}; - result.pos = start_pos; - i32 integer = 0; - while (isdigit(src[result.pos])) { - integer = integer * 10 + (src[result.pos] - '0'); - result.pos++; - } - if (src[result.pos] != '.') { - result.value = JSON_integer(integer); - return result; - } - result.pos++; - f32 fractional_part = 0.0; - f32 decimal_place = 0.1; - while (isdigit(src[result.pos])) { - fractional_part += (src[result.pos] - '0') * decimal_place; - decimal_place *= 0.1; - result.pos++; - } - result.value = JSON_float((f32)integer + fractional_part); - return result; -} - -uptr absorb_whitespaces(byte *src, uptr start_pos) { - uptr pos = start_pos; - while (isspace(src[pos])) { - pos++; - } - return pos; -} - -parse_result parse_array(Arena *a, byte *src, uptr start_pos) { - assert(src[start_pos] == '['); - parse_result result = {0}; - result.pos = start_pos + 1; - if (src[result.pos] == ']') { - result.pos++; - return result; - } - Vec items = Vec_new(a, JSON, 2); - for (;;) { - result.pos = absorb_whitespaces(src, result.pos); - parse_result item_result = parse_any(a, src, result.pos); - Vec_push(a, &items, &item_result.value); - result.pos = absorb_whitespaces(src, item_result.pos); - if (src[result.pos] == ',') { - result.pos++; - } else { - break; - } - }; - assert(src[result.pos] == ']'); - result.pos++; - result.value = JSON_array(items); - return result; -} - -parse_result parse_string(Arena *a, byte *src, uptr start_pos) { - assert(src[start_pos] == '"'); - parse_result result = {0}; - result.pos = start_pos + 1; - - while (src[result.pos] != '"' && src[result.pos - 1] != '\\') { - result.pos++; - } - assert(src[result.pos] == '"'); - result.value = JSON_string( - Str_copy(a, &src[start_pos + 1], result.pos - start_pos - 1)); - result.pos++; - - return result; -} - -parse_result parse_object(Arena *a, byte *src, uptr start_pos) { - assert(src[start_pos] == '{'); - parse_result result = {0}; - result.pos = absorb_whitespaces(src, start_pos + 1); - size len = 0; - Vec keys = Vec_new(a, Str, 2); - Vec values = Vec_new(a, JSON, 2); - if (src[result.pos] == '}') { - result.pos++; - result.value = JSON_object(len, keys, values); - return result; - } - for (;;) { - assert(src[result.pos] == '"'); - parse_result key_result = parse_string(a, src, result.pos); - result.pos = absorb_whitespaces(src, key_result.pos); - assert(src[result.pos] == ':'); - result.pos = absorb_whitespaces(src, result.pos + 1); - parse_result value_result = parse_any(a, src, result.pos); - len++; - assert(key_result.value.type == JSON_STRING); - Vec_push(a, &keys, &key_result.value.as.string); - Vec_push(a, &values, &value_result.value); - result.pos = absorb_whitespaces(src, value_result.pos); - if (src[result.pos] != ',') { - break; - } - result.pos = absorb_whitespaces(src, result.pos + 1); - } - assert(src[result.pos] == '}'); - result.pos++; - result.value = JSON_object(len, keys, values); - return result; -} - -parse_result parse_any(Arena *a, byte *src, uptr pos) { - pos = absorb_whitespaces(src, pos); - if (isdigit(src[pos])) return parse_number(src, pos); - if (src[pos] == '[') return parse_array(a, src, pos); - if (src[pos] == '"') return parse_string(a, src, pos); - if (src[pos] == '{') return parse_object(a, src, pos); - if (memcmp(src + pos, "true", 4) == 0) { - parse_result result = {0}; - result.value = JSON_boolean(true); - result.pos = pos + 4; - return result; - } - if (memcmp(src + pos, "false", 5) == 0) { - parse_result result = {0}; - result.value = JSON_boolean(false); - result.pos = pos + 5; - return result; - } - if (memcmp(src + pos, "null", 4) == 0) { - parse_result result = {0}; - result.value = JSON_null(); - result.pos = pos + 4; - return result; - } - assert(false); -} - -JSON JSON_parse(Arena *a, byte *src) { return parse_any(a, src, 0).value; } - -JSON *JSONObject_get(JSONObject obj, Str key) { - for (size i = 0; i < obj.len; i++) { - if (Str_equals(*Vec_get(obj.keys, Str, i), key)) { - return Vec_get(obj.values, JSON, i); - } - } - assert(false); -} diff --git a/json.h b/json.h deleted file mode 100644 index 60e853c..0000000 --- a/json.h +++ /dev/null @@ -1,36 +0,0 @@ -#ifndef JSON_H -#define JSON_H -#include "memory.h" -#include "string.h" -#include "vec.h" - -typedef struct { - Vec keys; // Vec - Vec values; // Vec - size len; -} JSONObject; -typedef enum { - JSON_NULL, - JSON_BOOL, - JSON_INTEGER, - JSON_FLOAT, - JSON_STRING, - JSON_ARRAY, - JSON_OBJECT -} JSONType; -typedef struct { - JSONType type; - union { - bool boolean; - i32 integer; - f32 floating; - Vec array; // Vec - Str string; - JSONObject object; - } as; -} JSON; -JSON *JSONObject_get(JSONObject obj, Str key); -i32 JSON_fprint(FILE *w, JSON json); -JSON JSON_parse(Arena *a, byte *src); -JSON JSON_Int(Arena *a, i32 val); -#endif diff --git a/log.c b/log.c deleted file mode 100644 index 9cff1a0..0000000 --- a/log.c +++ /dev/null @@ -1,13 +0,0 @@ -#include "log.h" - -#include - -#include "types.h" - -void info(Logger l, const byte *name, const byte *str, ...) { - if (l.level >= INFO) { - printf("[%s] ", name); - printf(str); - printf("\n"); - } -} diff --git a/log.h b/log.h deleted file mode 100644 index 1458f03..0000000 --- a/log.h +++ /dev/null @@ -1,10 +0,0 @@ - -#include "types.h" - -typedef enum { INFO } LogLevel; - -typedef struct { - LogLevel level; -} Logger; - -void info(Logger l, const byte *name, const byte *str, ...); diff --git a/main.c b/main.c index 19e8c20..01df170 100644 --- a/main.c +++ b/main.c @@ -3,9 +3,9 @@ #include #include -#include "json.h" -#include "memory.h" -#include "types.h" +#include "include/json.h" +#include "include/memory.h" +#include "include/types.h" // Load entire file into memory byte *load_file(const char *filename, size *file_size) { diff --git a/memory.c b/memory.c deleted file mode 100644 index f60f044..0000000 --- a/memory.c +++ /dev/null @@ -1,30 +0,0 @@ -#include -#include -#include -#include - -#include "memory.h" -#include "types.h" - -Arena Arena_new(size cap) { - Arena a = {0}; - a.cap = cap; - a.beg = malloc(cap); - a.end = a.beg ? a.beg + cap : 0; - return a; -} - -// Magic to allow optimisation __attribute((malloc, alloc_size(2, 4), -// alloc_align(3))) -void *alloc(Arena *a, size count, size tsize, size align) { - size padding = -(uptr)a->beg & (align - 1); - byte *beg = a->beg + padding; - size alloc_size = count * tsize; - assert(beg + alloc_size < a->end); - a->beg = beg + alloc_size; - return memset(beg, 0, count * tsize); -} - -void Arena_reset(Arena *a) { a->beg = a->end - a->cap; } - -size Arena_get_used(Arena a) { return a.cap - (a.end - a.beg); } diff --git a/memory.h b/memory.h deleted file mode 100644 index 1a33452..0000000 --- a/memory.h +++ /dev/null @@ -1,17 +0,0 @@ -#include - -#ifndef MEMORY_H -#include "types.h" -#define MEMORY_H -typedef struct { - size cap; - byte *beg; - byte *end; -} Arena; - -Arena Arena_new(size cap); -#define new(a, T, n) (T *)alloc(a, n, sizeof(T), _Alignof(T)) -void *alloc(Arena *a, size count, size tsize, size align); -size Arena_get_used(Arena a); -void Arena_reset(Arena *a); -#endif diff --git a/src/json.c b/src/json.c new file mode 100644 index 0000000..6f00da8 --- /dev/null +++ b/src/json.c @@ -0,0 +1,263 @@ +#include +#include +#include +#include +#include +#include + +#include "../include/json.h" +#include "../include/str.h" +#include "../include/types.h" + +JSONObject *JSONObject_new(Arena *a) { + JSONObject *object = new(a, JSONObject, 1); + object->len = 0; + object->keys = Vec_new(a, Str, 2); + object->values = Vec_new(a, JSON, 2); + return object; +} + +JSON JSON_integer(i32 value) { + JSON json = {0}; + json.type = JSON_INTEGER; + json.as.integer = value; + return json; +} + +JSON JSON_float(f32 value) { + JSON json = {0}; + json.type = JSON_FLOAT; + json.as.floating = value; + return json; +} + +JSON JSON_array(Vec items) { + JSON json = {0}; + json.type = JSON_ARRAY; + json.as.array = items; + return json; +} + +JSON JSON_string(Str value) { + JSON json = {0}; + json.type = JSON_STRING; + json.as.string = value; + return json; +} + +JSON JSON_object(size len, Vec keys, Vec values) { + assert(len == keys.len && len == values.len); + JSON json = {0}; + json.type = JSON_OBJECT; + json.as.object.len = len; + json.as.object.keys = keys; + json.as.object.values = values; + return json; +} + +JSON JSON_boolean(bool value) { + JSON json = {0}; + json.type = JSON_BOOL; + json.as.boolean = value; + return json; +} + +JSON JSON_null() { + JSON json = {0}; + json.type = JSON_NULL; + return json; +} + +i32 JSON_fprint(FILE *w, JSON json) { + switch (json.type) { + case JSON_STRING: + fprintf(w, "\""); + Str_fprint(w, json.as.string); + return fprintf(w, "\""); + case JSON_INTEGER: + return fprintf(w, "%d", json.as.integer); + case JSON_FLOAT: + return fprintf(w, "%f", json.as.floating); + case JSON_NULL: + return fprintf(w, "null"); + case JSON_BOOL: + return fprintf(w, "%s", json.as.boolean ? "true" : "false"); + case JSON_ARRAY: + fprintf(w, "["); + for (size i = 0; i < json.as.array.len; i++) { + JSON value = *Vec_get(json.as.array, JSON, i); + JSON_fprint(w, value); + if (i < json.as.array.len - 1) fprintf(w, ", "); + } + fprintf(w, "]"); + return 0; + case JSON_OBJECT: + fprintf(w, "{"); + for (size i = 0; i < json.as.object.len; i++) { + Str key = *Vec_get(json.as.object.keys, Str, i); + fprintf(w, "\""); + Str_fprint(w, key); + fprintf(w, "\": "); + JSON value = *Vec_get(json.as.object.values, JSON, i); + JSON_fprint(w, value); + if (i < json.as.object.len - 1) fprintf(w, ", "); + } + fprintf(w, "}"); + return 0; + } + assert(false); +} + +typedef struct { + JSON value; + uptr pos; +} parse_result; + +parse_result parse_any(Arena *a, byte *src, uptr pos); + +parse_result parse_number(byte *src, uptr start_pos) { + parse_result result = {0}; + result.pos = start_pos; + i32 integer = 0; + while (isdigit(src[result.pos])) { + integer = integer * 10 + (src[result.pos] - '0'); + result.pos++; + } + if (src[result.pos] != '.') { + result.value = JSON_integer(integer); + return result; + } + result.pos++; + f32 fractional_part = 0.0; + f32 decimal_place = 0.1; + while (isdigit(src[result.pos])) { + fractional_part += (src[result.pos] - '0') * decimal_place; + decimal_place *= 0.1; + result.pos++; + } + result.value = JSON_float((f32)integer + fractional_part); + return result; +} + +uptr absorb_whitespaces(byte *src, uptr start_pos) { + uptr pos = start_pos; + while (isspace(src[pos])) { + pos++; + } + return pos; +} + +parse_result parse_array(Arena *a, byte *src, uptr start_pos) { + assert(src[start_pos] == '['); + parse_result result = {0}; + result.pos = start_pos + 1; + if (src[result.pos] == ']') { + result.pos++; + return result; + } + Vec items = Vec_new(a, JSON, 2); + for (;;) { + result.pos = absorb_whitespaces(src, result.pos); + parse_result item_result = parse_any(a, src, result.pos); + Vec_push(a, &items, &item_result.value); + result.pos = absorb_whitespaces(src, item_result.pos); + if (src[result.pos] == ',') { + result.pos++; + } else { + break; + } + }; + assert(src[result.pos] == ']'); + result.pos++; + result.value = JSON_array(items); + return result; +} + +parse_result parse_string(Arena *a, byte *src, uptr start_pos) { + assert(src[start_pos] == '"'); + parse_result result = {0}; + result.pos = start_pos + 1; + + while (src[result.pos] != '"' && src[result.pos - 1] != '\\') { + result.pos++; + } + assert(src[result.pos] == '"'); + result.value = JSON_string( + Str_copy(a, &src[start_pos + 1], result.pos - start_pos - 1)); + result.pos++; + + return result; +} + +parse_result parse_object(Arena *a, byte *src, uptr start_pos) { + assert(src[start_pos] == '{'); + parse_result result = {0}; + result.pos = absorb_whitespaces(src, start_pos + 1); + size len = 0; + Vec keys = Vec_new(a, Str, 2); + Vec values = Vec_new(a, JSON, 2); + if (src[result.pos] == '}') { + result.pos++; + result.value = JSON_object(len, keys, values); + return result; + } + for (;;) { + assert(src[result.pos] == '"'); + parse_result key_result = parse_string(a, src, result.pos); + result.pos = absorb_whitespaces(src, key_result.pos); + assert(src[result.pos] == ':'); + result.pos = absorb_whitespaces(src, result.pos + 1); + parse_result value_result = parse_any(a, src, result.pos); + len++; + assert(key_result.value.type == JSON_STRING); + Vec_push(a, &keys, &key_result.value.as.string); + Vec_push(a, &values, &value_result.value); + result.pos = absorb_whitespaces(src, value_result.pos); + if (src[result.pos] != ',') { + break; + } + result.pos = absorb_whitespaces(src, result.pos + 1); + } + assert(src[result.pos] == '}'); + result.pos++; + result.value = JSON_object(len, keys, values); + return result; +} + +parse_result parse_any(Arena *a, byte *src, uptr pos) { + pos = absorb_whitespaces(src, pos); + if (isdigit(src[pos])) return parse_number(src, pos); + if (src[pos] == '[') return parse_array(a, src, pos); + if (src[pos] == '"') return parse_string(a, src, pos); + if (src[pos] == '{') return parse_object(a, src, pos); + if (memcmp(src + pos, "true", 4) == 0) { + parse_result result = {0}; + result.value = JSON_boolean(true); + result.pos = pos + 4; + return result; + } + if (memcmp(src + pos, "false", 5) == 0) { + parse_result result = {0}; + result.value = JSON_boolean(false); + result.pos = pos + 5; + return result; + } + if (memcmp(src + pos, "null", 4) == 0) { + parse_result result = {0}; + result.value = JSON_null(); + result.pos = pos + 4; + return result; + } + assert(false); +} + +JSON JSON_parse(Arena *a, byte *src) { return parse_any(a, src, 0).value; } + +JSON *JSONObject_get(JSONObject obj, Str key) { + for (size i = 0; i < obj.len; i++) { + if (Str_equals(*Vec_get(obj.keys, Str, i), key)) { + return Vec_get(obj.values, JSON, i); + } + } + assert(false); +} diff --git a/src/log.c b/src/log.c new file mode 100644 index 0000000..c1d1717 --- /dev/null +++ b/src/log.c @@ -0,0 +1,12 @@ +#include + +#include "../include/log.h" +#include "../include/types.h" + +void info(Logger l, const byte *name, const byte *str, ...) { + if (l.level >= INFO) { + printf("[%s] ", name); + printf("%s", str); + printf("\n"); + } +} diff --git a/src/memory.c b/src/memory.c new file mode 100644 index 0000000..63acb39 --- /dev/null +++ b/src/memory.c @@ -0,0 +1,30 @@ +#include +#include +#include +#include + +#include "../include/memory.h" +#include "../include/types.h" + +Arena Arena_new(size cap) { + Arena a = {0}; + a.cap = cap; + a.beg = malloc(cap); + a.end = a.beg ? a.beg + cap : 0; + return a; +} + +// Magic to allow optimisation __attribute((malloc, alloc_size(2, 4), +// alloc_align(3))) +void *alloc(Arena *a, size count, size tsize, size align) { + size padding = -(uptr)a->beg & (align - 1); + byte *beg = a->beg + padding; + size alloc_size = count * tsize; + assert(beg + alloc_size < a->end); + a->beg = beg + alloc_size; + return memset(beg, 0, count * tsize); +} + +void Arena_reset(Arena *a) { a->beg = a->end - a->cap; } + +size Arena_get_used(Arena a) { return a.cap - (a.end - a.beg); } diff --git a/src/str.c b/src/str.c new file mode 100644 index 0000000..eccacd8 --- /dev/null +++ b/src/str.c @@ -0,0 +1,32 @@ +#include +#include +#include + +#include "../include/memory.h" +#include "../include/str.h" + +// Returns 1 if strings are equal, 0 otherwise +i32 Str_equals(Str a, Str b) { + return (a.len == b.len) && (!a.len || !memcmp(a.data, b.data, a.len)); +} + +u64 Str_hash64(Str s) { + u64 h = 0x100; + for (size i = 0; i < s.len; i++) { + h ^= s.data[i] & 255; + h *= 1111111111111111111; + } + return h; +} + +Str Str_copy(Arena *a, byte *data, size len) { + Str str = {0}; + str.len = len; + str.data = new(a, byte, len); + if (str.len) memcpy(str.data, data, len); + return str; +} + +i32 Str_fprint(FILE *w, Str s) { + return fprintf(w, "%.*s", (i32)s.len, s.data); +} diff --git a/src/vec.c b/src/vec.c new file mode 100644 index 0000000..a5c2fb4 --- /dev/null +++ b/src/vec.c @@ -0,0 +1,29 @@ +#include + +#include "../include/assert.h" +#include "../include/types.h" +#include "../include/vec.h" + +Vec _Vec_new(Arena *a, size tsize, size cap, size align) { + assert(cap > 0); + Vec v = {0}; + v.tsize = tsize; + v.cap = cap; + v.align = align; + v.len = 0; + v.data = alloc(a, cap, tsize, align); + return v; +} + +void Vec_push(Arena *a, Vec *v, void *data) { + if (v->len == v->cap) { + void *cpy = alloc(a, v->cap * 2, v->tsize, v->align); + memcpy(cpy, v->data, v->len * v->tsize); + v->cap *= 2; + v->data = cpy; + } + memcpy(v->data + v->len * v->tsize, data, v->tsize); + v->len++; +} + +void *_Vec_get(Vec v, size pos) { return v.data + v.tsize * pos; } diff --git a/string.c b/string.c deleted file mode 100644 index 6246a17..0000000 --- a/string.c +++ /dev/null @@ -1,33 +0,0 @@ -#include "string.h" - -#include -#include -#include - -#include "memory.h" - -// Returns 1 if strings are equal, 0 otherwise -i32 Str_equals(Str a, Str b) { - return (a.len == b.len) && (!a.len || !memcmp(a.data, b.data, a.len)); -} - -u64 Str_hash64(Str s) { - u64 h = 0x100; - for (size i = 0; i < s.len; i++) { - h ^= s.data[i] & 255; - h *= 1111111111111111111; - } - return h; -} - -Str Str_copy(Arena *a, byte *data, size len) { - Str str = {0}; - str.len = len; - str.data = new(a, byte, len); - if (str.len) memcpy(str.data, data, len); - return str; -} - -i32 Str_fprint(FILE *w, Str s) { - return fprintf(w, "%.*s", (i32)s.len, s.data); -} diff --git a/string.h b/string.h deleted file mode 100644 index a4c1e1a..0000000 --- a/string.h +++ /dev/null @@ -1,20 +0,0 @@ -#include -#include -#include - -#include "memory.h" -#include "types.h" - -#ifndef STRING_H -#define STRING_H -#define S(s) \ - (Str) { s, lengthof(s) } -typedef struct { - byte *data; - size len; -} Str; -i32 Str_equals(Str a, Str b); -u64 Str_hash64(Str s); -Str Str_copy(Arena *a, byte *data, size len); -i32 Str_fprint(FILE *w, Str s); -#endif diff --git a/test_json.c b/test_json.c deleted file mode 100644 index a37996c..0000000 --- a/test_json.c +++ /dev/null @@ -1,207 +0,0 @@ -#include "json.h" -#include "testing.h" - -i32 test_json_integer() { - Arena perm = Arena_new(128e3); - - JSON json_int = JSON_parse(&perm, "123"); - - t_assert(json_int.as.integer == 123); - t_assert_equal(Arena_get_used(perm), 0); - return 0; -} - -i32 test_json_int_array() { - Arena perm = Arena_new(128e3); - - JSON json_int_array = JSON_parse(&perm, "[1,2,3]"); - - t_assert(json_int_array.type == JSON_ARRAY); - t_assert_equal(json_int_array.as.array.len, 3); - int ints[3]; - int sum = 0; - for (int i = 0; i < json_int_array.as.array.len; i++) { - JSON *item = Vec_get(json_int_array.as.array, JSON, i); - ints[i] = item->as.integer; - sum += item->as.integer; - } - t_assert(ints[0] == 1); - t_assert(ints[1] == 2); - t_assert(ints[2] == 3); - t_assert(sum == 6); - t_assert_equal(Arena_get_used(perm), 576); - - return 0; -} - -i32 test_json_empty_array() { - Arena perm = Arena_new(128e3); - - JSON json_empty_array = JSON_parse(&perm, "[]"); - - t_assert(json_empty_array.as.array.len == 0); - t_assert_equal(Arena_get_used(perm), 0); // TODO: weird - - return 0; -} - -i32 test_json_nested_array() { - Arena perm = Arena_new(128e3); - - JSON doc = JSON_parse(&perm, "[[1,\"abc\"]]"); - - JSON sub_array = *Vec_get(doc.as.array, JSON, 0); - JSON val_1 = *Vec_get(sub_array.as.array, JSON, 0); - t_assert(val_1.as.integer == 1); - JSON val_2 = *Vec_get(sub_array.as.array, JSON, 1); - Str str = val_2.as.string; - Str expected = S("abc"); - t_assert(Str_equals(str, expected)); - t_assert_equal(Arena_get_used(perm), 387); - - return 0; -} - -i32 test_json_string() { - Arena perm = Arena_new(128e3); - - JSON js = JSON_parse(&perm, "\"abc\""); - - t_assert(Str_equals(js.as.string, S("abc"))); - t_assert_equal(Arena_get_used(perm), 3); - - return 0; -} - -i32 test_json_empty_object() { - Arena perm = Arena_new(128e3); - - JSON doc = JSON_parse(&perm, "{}"); - - t_assert_equal(doc.as.object.len, 0); - t_assert_equal(Arena_get_used(perm), 224); - - return 0; -} - -i32 test_json_object() { - Arena perm = Arena_new(128e3); - - JSON doc = JSON_parse(&perm, "{\"key\": \"value\"}"); - - t_assert_equal(doc.as.object.len, 1); - t_assert(Str_equals(*Vec_get(doc.as.object.keys, Str, 0), S("key"))); - JSON value = *JSONObject_get(doc.as.object, S("key")); - t_assert(Str_equals(value.as.string, S("value"))); - t_assert_equal(Arena_get_used(perm), 232); - - return 0; -} - -i32 test_json_boolean() { - Arena perm = Arena_new(128e3); - - JSON doc = JSON_parse(&perm, "{\"a\": true, \"b\": false}"); - - JSON a = *JSONObject_get(doc.as.object, S("a")); - t_assert(a.as.boolean == true); - JSON b = *JSONObject_get(doc.as.object, S("b")); - t_assert(b.as.boolean == false); - t_assert_equal(Arena_get_used(perm), 226); - - return 0; -} - -i32 test_json_float() { - Arena perm = Arena_new(128e3); - - JSON doc = JSON_parse(&perm, "[1.2, 2.3]"); - t_assert_equal(doc.as.array.len, 2); - JSON first = *Vec_get(doc.as.array, JSON, 0); - t_assert_fequal(first.as.floating, 1.2); - JSON second = *Vec_get(doc.as.array, JSON, 1); - t_assert_fequal(second.as.floating, 2.3); - t_assert_equal(Arena_get_used(perm), 192); - - return 0; -} - -i32 test_json_null() { - Arena perm = Arena_new(128e3); - - JSON doc = JSON_parse(&perm, "[null]"); - t_assert_equal(doc.as.array.len, 1); - JSON first = *Vec_get(doc.as.array, JSON, 0); - t_assert(first.type == JSON_NULL); - t_assert_equal(Arena_get_used(perm), 192); - - return 0; -} - -i32 test_json_multiline() { - Arena perm = Arena_new(128e3); - - JSON doc = JSON_parse( - &perm, "{\n" - " \"generated_at\": \"2025-07-22T10:36:30.974926\",\n" - " \"target_size_mb\": 1,\n" - " \"generator_version\": \"1.0\"\n" - "}"); - JSON target_size_mb = *JSONObject_get(doc.as.object, S("target_size_mb")); - t_assert(target_size_mb.as.integer == 1); - JSON generated_at = *JSONObject_get(doc.as.object, S("generated_at")); - t_assert( - Str_equals(generated_at.as.string, S("2025-07-22T10:36:30.974926"))); - - return 0; -} - -i32 test_json_nested_object() { - Arena perm = Arena_new(128e3); - - JSON doc = JSON_parse(&perm, "{\"1\":{\"2\":{}}}"); - - JSON lvl_1 = *JSONObject_get(doc.as.object, S("1")); - t_assert(lvl_1.type == JSON_OBJECT); - JSON lvl_2 = *JSONObject_get(lvl_1.as.object, S("2")); - t_assert(lvl_2.type == JSON_OBJECT); - return 0; -} - -i32 test_json_complex_multiline() { - Arena perm = Arena_new(128e3); - JSON doc = - JSON_parse(&perm, "{\n" - " \"id\": 819228,\n" - " \"username\": \"rCrRv4PRnUNuAy\",\n" - " \"email\": \"xzbwrzj@zwod.com\",\n" - " \"first_name\": \"4WH7g7PsOEm5\",\n" - " \"last_name\": \"6ulndxUgjbIayl\",\n" - " \"age\": 30,\n" - " \"phone\": \"+1-529-712-3931\",\n" - " \"is_active\": false,\n" - " \"balance\": 9347.49,\n" - " \"tags\": [\n" - " \"TWvZi0\",\n" - " \"pM0TPv9X\"\n" - " ],\n" - " \"metadata\": {\n" - " \"last_login\": \"2024-06-13T21:22:32Z\",\n" - " \"login_count\": 245,\n" - " \"preferences\": {\n" - " \"theme\": \"auto\",\n" - " \"notifications\": true,\n" - " \"language\": \"en\"\n" - " }\n" - " }\n" - " }"); - - JSON id = *JSONObject_get(doc.as.object, S("id")); - t_assert(id.as.integer == 819228); - JSON metadata = *JSONObject_get(doc.as.object, S("metadata")); - JSON preferences = *JSONObject_get(metadata.as.object, S("preferences")); - JSON theme = *JSONObject_get(preferences.as.object, S("theme")); - t_assert(Str_equals(theme.as.string, S("auto"))); - - return 0; -} diff --git a/test_string.c b/test_string.c deleted file mode 100644 index 0d6dd71..0000000 --- a/test_string.c +++ /dev/null @@ -1,19 +0,0 @@ -#include "string.h" -#include "testing.h" - -i32 test_string_literal_equality() { - t_assert(Str_equals(S("Bonjour"), S("Bonjour"))); - return 0; -} - -i32 test_string_copy() { - Arena perm = Arena_new(128e3); - Str src = S("Hello"); - - Str cpy = Str_copy(&perm, src.data, src.len); - - t_assert(Str_equals(src, cpy)); - t_assert_equal(Arena_get_used(perm), 5); - - return 0; -} diff --git a/test_vec.c b/test_vec.c deleted file mode 100644 index 180ba41..0000000 --- a/test_vec.c +++ /dev/null @@ -1,25 +0,0 @@ -#include "testing.h" -#include "vec.h" - -i32 test_int_vec() { - Arena perm = Arena_new(128e3); - - Vec vec = Vec_new(&perm, i32, 2); - i32 *tmp = new(&perm, i32, 1); - *tmp = 1; - Vec_push(&perm, &vec, tmp); - *tmp = 2; - Vec_push(&perm, &vec, tmp); - - t_assert_equal(*Vec_get(vec, i32, 0), 1); - t_assert_equal(*Vec_get(vec, i32, 1), 2); - t_assert_equal(vec.cap, 2); - - *tmp = 3; - Vec_push(&perm, &vec, tmp); - - t_assert_equal(*Vec_get(vec, i32, 2), 3); - t_assert_equal(vec.cap, 4); - - return 0; -} diff --git a/testing.h b/testing.h deleted file mode 100644 index 9e0c022..0000000 --- a/testing.h +++ /dev/null @@ -1,24 +0,0 @@ -#include - -#define t_assert(fact) \ - if (!(fact)) { \ - 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", \ - (i32)a, #a, (i32)b, #b, __FILE__, __LINE__, __func__); \ - return 1; \ - } - -#define t_assert_fequal(a, b) \ - if ((a - b) > 1e-6 || (b - a) > 1e-6) { \ - fprintf(stderr, \ - "Assertion failed: %f (%s) != %f (%s) at %s:%d in %s()\n", a, \ - #a, b, #b, __FILE__, __LINE__, __func__); \ - return 1; \ - } diff --git a/tests/test_json.c b/tests/test_json.c new file mode 100644 index 0000000..4ef3d5d --- /dev/null +++ b/tests/test_json.c @@ -0,0 +1,207 @@ +#include "../include/json.h" +#include "../include/testing.h" + +i32 test_json_integer() { + Arena perm = Arena_new(128e3); + + JSON json_int = JSON_parse(&perm, "123"); + + t_assert(json_int.as.integer == 123); + t_assert_equal(Arena_get_used(perm), 0); + return 0; +} + +i32 test_json_int_array() { + Arena perm = Arena_new(128e3); + + JSON json_int_array = JSON_parse(&perm, "[1,2,3]"); + + t_assert(json_int_array.type == JSON_ARRAY); + t_assert_equal(json_int_array.as.array.len, 3); + int ints[3]; + int sum = 0; + for (int i = 0; i < json_int_array.as.array.len; i++) { + JSON *item = Vec_get(json_int_array.as.array, JSON, i); + ints[i] = item->as.integer; + sum += item->as.integer; + } + t_assert(ints[0] == 1); + t_assert(ints[1] == 2); + t_assert(ints[2] == 3); + t_assert(sum == 6); + t_assert_equal(Arena_get_used(perm), 576); + + return 0; +} + +i32 test_json_empty_array() { + Arena perm = Arena_new(128e3); + + JSON json_empty_array = JSON_parse(&perm, "[]"); + + t_assert(json_empty_array.as.array.len == 0); + t_assert_equal(Arena_get_used(perm), 0); // TODO: weird + + return 0; +} + +i32 test_json_nested_array() { + Arena perm = Arena_new(128e3); + + JSON doc = JSON_parse(&perm, "[[1,\"abc\"]]"); + + JSON sub_array = *Vec_get(doc.as.array, JSON, 0); + JSON val_1 = *Vec_get(sub_array.as.array, JSON, 0); + t_assert(val_1.as.integer == 1); + JSON val_2 = *Vec_get(sub_array.as.array, JSON, 1); + Str str = val_2.as.string; + Str expected = S("abc"); + t_assert(Str_equals(str, expected)); + t_assert_equal(Arena_get_used(perm), 387); + + return 0; +} + +i32 test_json_string() { + Arena perm = Arena_new(128e3); + + JSON js = JSON_parse(&perm, "\"abc\""); + + t_assert(Str_equals(js.as.string, S("abc"))); + t_assert_equal(Arena_get_used(perm), 3); + + return 0; +} + +i32 test_json_empty_object() { + Arena perm = Arena_new(128e3); + + JSON doc = JSON_parse(&perm, "{}"); + + t_assert_equal(doc.as.object.len, 0); + t_assert_equal(Arena_get_used(perm), 224); + + return 0; +} + +i32 test_json_object() { + Arena perm = Arena_new(128e3); + + JSON doc = JSON_parse(&perm, "{\"key\": \"value\"}"); + + t_assert_equal(doc.as.object.len, 1); + t_assert(Str_equals(*Vec_get(doc.as.object.keys, Str, 0), S("key"))); + JSON value = *JSONObject_get(doc.as.object, S("key")); + t_assert(Str_equals(value.as.string, S("value"))); + t_assert_equal(Arena_get_used(perm), 232); + + return 0; +} + +i32 test_json_boolean() { + Arena perm = Arena_new(128e3); + + JSON doc = JSON_parse(&perm, "{\"a\": true, \"b\": false}"); + + JSON a = *JSONObject_get(doc.as.object, S("a")); + t_assert(a.as.boolean == true); + JSON b = *JSONObject_get(doc.as.object, S("b")); + t_assert(b.as.boolean == false); + t_assert_equal(Arena_get_used(perm), 226); + + return 0; +} + +i32 test_json_float() { + Arena perm = Arena_new(128e3); + + JSON doc = JSON_parse(&perm, "[1.2, 2.3]"); + t_assert_equal(doc.as.array.len, 2); + JSON first = *Vec_get(doc.as.array, JSON, 0); + t_assert_fequal(first.as.floating, 1.2); + JSON second = *Vec_get(doc.as.array, JSON, 1); + t_assert_fequal(second.as.floating, 2.3); + t_assert_equal(Arena_get_used(perm), 192); + + return 0; +} + +i32 test_json_null() { + Arena perm = Arena_new(128e3); + + JSON doc = JSON_parse(&perm, "[null]"); + t_assert_equal(doc.as.array.len, 1); + JSON first = *Vec_get(doc.as.array, JSON, 0); + t_assert(first.type == JSON_NULL); + t_assert_equal(Arena_get_used(perm), 192); + + return 0; +} + +i32 test_json_multiline() { + Arena perm = Arena_new(128e3); + + JSON doc = JSON_parse( + &perm, "{\n" + " \"generated_at\": \"2025-07-22T10:36:30.974926\",\n" + " \"target_size_mb\": 1,\n" + " \"generator_version\": \"1.0\"\n" + "}"); + JSON target_size_mb = *JSONObject_get(doc.as.object, S("target_size_mb")); + t_assert(target_size_mb.as.integer == 1); + JSON generated_at = *JSONObject_get(doc.as.object, S("generated_at")); + t_assert( + Str_equals(generated_at.as.string, S("2025-07-22T10:36:30.974926"))); + + return 0; +} + +i32 test_json_nested_object() { + Arena perm = Arena_new(128e3); + + JSON doc = JSON_parse(&perm, "{\"1\":{\"2\":{}}}"); + + JSON lvl_1 = *JSONObject_get(doc.as.object, S("1")); + t_assert(lvl_1.type == JSON_OBJECT); + JSON lvl_2 = *JSONObject_get(lvl_1.as.object, S("2")); + t_assert(lvl_2.type == JSON_OBJECT); + return 0; +} + +i32 test_json_complex_multiline() { + Arena perm = Arena_new(128e3); + JSON doc = + JSON_parse(&perm, "{\n" + " \"id\": 819228,\n" + " \"username\": \"rCrRv4PRnUNuAy\",\n" + " \"email\": \"xzbwrzj@zwod.com\",\n" + " \"first_name\": \"4WH7g7PsOEm5\",\n" + " \"last_name\": \"6ulndxUgjbIayl\",\n" + " \"age\": 30,\n" + " \"phone\": \"+1-529-712-3931\",\n" + " \"is_active\": false,\n" + " \"balance\": 9347.49,\n" + " \"tags\": [\n" + " \"TWvZi0\",\n" + " \"pM0TPv9X\"\n" + " ],\n" + " \"metadata\": {\n" + " \"last_login\": \"2024-06-13T21:22:32Z\",\n" + " \"login_count\": 245,\n" + " \"preferences\": {\n" + " \"theme\": \"auto\",\n" + " \"notifications\": true,\n" + " \"language\": \"en\"\n" + " }\n" + " }\n" + " }"); + + JSON id = *JSONObject_get(doc.as.object, S("id")); + t_assert(id.as.integer == 819228); + JSON metadata = *JSONObject_get(doc.as.object, S("metadata")); + JSON preferences = *JSONObject_get(metadata.as.object, S("preferences")); + JSON theme = *JSONObject_get(preferences.as.object, S("theme")); + t_assert(Str_equals(theme.as.string, S("auto"))); + + return 0; +} diff --git a/tests/test_string.c b/tests/test_string.c new file mode 100644 index 0000000..1baab6a --- /dev/null +++ b/tests/test_string.c @@ -0,0 +1,19 @@ +#include "../include/string.h" +#include "../include/testing.h" + +i32 test_string_literal_equality() { + t_assert(Str_equals(S("Bonjour"), S("Bonjour"))); + return 0; +} + +i32 test_string_copy() { + Arena perm = Arena_new(128e3); + Str src = S("Hello"); + + Str cpy = Str_copy(&perm, src.data, src.len); + + t_assert(Str_equals(src, cpy)); + t_assert_equal(Arena_get_used(perm), 5); + + return 0; +} diff --git a/tests/test_vec.c b/tests/test_vec.c new file mode 100644 index 0000000..68cb0ce --- /dev/null +++ b/tests/test_vec.c @@ -0,0 +1,25 @@ +#include "../include/testing.h" +#include "../include/vec.h" + +i32 test_int_vec() { + Arena perm = Arena_new(128e3); + + Vec vec = Vec_new(&perm, i32, 2); + i32 *tmp = new(&perm, i32, 1); + *tmp = 1; + Vec_push(&perm, &vec, tmp); + *tmp = 2; + Vec_push(&perm, &vec, tmp); + + t_assert_equal(*Vec_get(vec, i32, 0), 1); + t_assert_equal(*Vec_get(vec, i32, 1), 2); + t_assert_equal(vec.cap, 2); + + *tmp = 3; + Vec_push(&perm, &vec, tmp); + + t_assert_equal(*Vec_get(vec, i32, 2), 3); + t_assert_equal(vec.cap, 4); + + return 0; +} diff --git a/types.h b/types.h deleted file mode 100644 index bacedf8..0000000 --- a/types.h +++ /dev/null @@ -1,14 +0,0 @@ -#include -#include -#include - -typedef ptrdiff_t size; -typedef char byte; -typedef uintptr_t uptr; -typedef int32_t i32; -typedef uint64_t u64; -typedef float f32; - -#define sizeof(x) (size)sizeof(x) -#define countof(a) (sizeof(a) / sizeof(*(a))) -#define lengthof(s) (countof(s) - 1) diff --git a/vec.c b/vec.c deleted file mode 100644 index 3dfbf31..0000000 --- a/vec.c +++ /dev/null @@ -1,29 +0,0 @@ -#include - -#include "assert.h" -#include "types.h" -#include "vec.h" - -Vec _Vec_new(Arena *a, size tsize, size cap, size align) { - assert(cap > 0); - Vec v = {0}; - v.tsize = tsize; - v.cap = cap; - v.align = align; - v.len = 0; - v.data = alloc(a, cap, tsize, align); - return v; -} - -void Vec_push(Arena *a, Vec *v, void *data) { - if (v->len == v->cap) { - void *cpy = alloc(a, v->cap * 2, v->tsize, v->align); - memcpy(cpy, v->data, v->len * v->tsize); - v->cap *= 2; - v->data = cpy; - } - memcpy(v->data + v->len * v->tsize, data, v->tsize); - v->len++; -} - -void *_Vec_get(Vec v, size pos) { return v.data + v.tsize * pos; } diff --git a/vec.h b/vec.h deleted file mode 100644 index 1d1999b..0000000 --- a/vec.h +++ /dev/null @@ -1,21 +0,0 @@ -#include -#include - -#include "memory.h" -#include "types.h" - -#ifndef VEC_H -#define VEC_H -typedef struct { - void *data; - size len; - size cap; - size tsize; - size align; -} Vec; -#define Vec_new(a, T, cap) _Vec_new(a, sizeof(T), cap, _Alignof(T)) -Vec _Vec_new(Arena *a, size tsize, size cap, size align); -void Vec_push(Arena *a, Vec *v, void *data); -#define Vec_get(v, T, p) (T *)_Vec_get(v, p) -void *_Vec_get(Vec v, size pos); -#endif -- cgit v1.3