aboutsummaryrefslogtreecommitdiff
path: root/src/std
diff options
context:
space:
mode:
authorMartin Kagamino Lehoux <martin@lehoux.net>2026-04-08 13:14:27 +0200
committerMartin Kagamino Lehoux <martin@lehoux.net>2026-04-08 13:15:01 +0200
commitcf534d762754860ed5b714974f99acd282d48b3b (patch)
treebf22341cedfb50ee7bcd4e15f85add2329783f31 /src/std
parent630fcb1846aed16b9ab62bcf700f668883f4521f (diff)
Restructure into src/{std,json}/
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'src/std')
-rw-r--r--src/std/assert.h15
-rw-r--r--src/std/log.c12
-rw-r--r--src/std/log.h10
-rw-r--r--src/std/memory.c33
-rw-r--r--src/std/memory.h16
-rw-r--r--src/std/str.c32
-rw-r--r--src/std/str.h30
-rw-r--r--src/std/test_string.c41
-rw-r--r--src/std/test_time.c13
-rw-r--r--src/std/test_vec.c88
-rw-r--r--src/std/testing.c28
-rw-r--r--src/std/testing.h36
-rw-r--r--src/std/time.c48
-rw-r--r--src/std/time.h20
-rw-r--r--src/std/types.h16
-rw-r--r--src/std/vec.c27
-rw-r--r--src/std/vec.h18
17 files changed, 483 insertions, 0 deletions
diff --git a/src/std/assert.h b/src/std/assert.h
new file mode 100644
index 0000000..aa62bd1
--- /dev/null
+++ b/src/std/assert.h
@@ -0,0 +1,15 @@
+#pragma once
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#define assert(condition, ...) \
+ do { \
+ if (!(condition)) { \
+ fprintf(stderr, "Assertion failed: %s:%d in %s(): ", __FILE__, \
+ __LINE__, __func__); \
+ fprintf(stderr, __VA_ARGS__); \
+ fprintf(stderr, "\n"); \
+ abort(); \
+ } \
+ } while (0)
diff --git a/src/std/log.c b/src/std/log.c
new file mode 100644
index 0000000..980c7e7
--- /dev/null
+++ b/src/std/log.c
@@ -0,0 +1,12 @@
+#include <stdio.h>
+
+#include "log.h"
+#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/std/log.h b/src/std/log.h
new file mode 100644
index 0000000..1458f03
--- /dev/null
+++ b/src/std/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/src/std/memory.c b/src/std/memory.c
new file mode 100644
index 0000000..3448819
--- /dev/null
+++ b/src/std/memory.c
@@ -0,0 +1,33 @@
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "assert.h"
+#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, "out of memory");
+ a->beg = beg + alloc_size;
+ return beg;
+}
+
+void Arena_reset(Arena *a) {
+ a->beg = a->end - a->cap;
+ memset(a->beg, 0, a->end - a->beg);
+}
+
+size Arena_get_used(Arena a) { return a.cap - (a.end - a.beg); }
diff --git a/src/std/memory.h b/src/std/memory.h
new file mode 100644
index 0000000..d202e97
--- /dev/null
+++ b/src/std/memory.h
@@ -0,0 +1,16 @@
+#pragma once
+#include <stddef.h>
+
+#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/src/std/str.c b/src/std/str.c
new file mode 100644
index 0000000..714cc59
--- /dev/null
+++ b/src/std/str.c
@@ -0,0 +1,32 @@
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "memory.h"
+#include "str.h"
+
+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;
+}
+
+StrView Str_view(byte *data, size len) {
+ StrView str = {0};
+ str.len = len;
+ str.data = data;
+ return str;
+}
+
+Str StrView_copy(Arena *a, StrView s) { return Str_copy(a, s.data, s.len); }
diff --git a/src/std/str.h b/src/std/str.h
new file mode 100644
index 0000000..526db63
--- /dev/null
+++ b/src/std/str.h
@@ -0,0 +1,30 @@
+#pragma once
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#include "memory.h"
+#include "types.h"
+
+typedef struct {
+ byte *data;
+ size len;
+} Str;
+u64 Str_hash64(Str s);
+Str Str_copy(Arena *a, byte *data, size len);
+i32 Str_fprint(FILE *w, Str s);
+
+typedef struct {
+ byte *data;
+ size len;
+} StrView;
+StrView Str_view(byte *data, size len);
+Str StrView_copy(Arena *a, StrView s);
+
+// Macros working with both types
+// Returns 1 if strings are equal, 0 otherwise
+#define Str_equals(a, b) \
+ ((a).len == (b).len) && (!(a).len || !memcmp((a).data, (b).data, (a).len))
+#define Str_fprint(w, s) fprintf(w, "%.*s", (i32)(s).len, (s).data)
+#define S(s) \
+ (Str) { s, lengthof(s) }
diff --git a/src/std/test_string.c b/src/std/test_string.c
new file mode 100644
index 0000000..d6bc716
--- /dev/null
+++ b/src/std/test_string.c
@@ -0,0 +1,41 @@
+#include "str.h"
+#include "testing.h"
+#include "types.h"
+#include <stdio.h>
+
+i32 test_string_literal_equality() {
+ t_assert_strequal(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_strequal(src, cpy);
+ t_assert_equal(Arena_get_used(perm), 5);
+
+ return 0;
+}
+
+i32 test_fast_string() {
+ bytex32 quote_mask = _mm256_set1_epi8('"');
+ byte *s = malloc(32 * sizeof(byte));
+ s = "abcdefghijklmnopqrstuvwxyzabcdef";
+
+ bytex32 chunk = _mm256_loadu_si256((bytex32 *)s);
+ bytex32 cmp = _mm256_cmpeq_epi8(chunk, quote_mask);
+ i32 mask = _mm256_movemask_epi8(cmp);
+ t_assert_equal(_mm_popcnt_u32(mask), 0);
+
+ s = "abcdefghijklm\"nopqrstuvwxyzabcde";
+
+ chunk = _mm256_loadu_si256((bytex32 *)s);
+ cmp = _mm256_cmpeq_epi8(chunk, quote_mask);
+ mask = _mm256_movemask_epi8(cmp);
+ t_assert_equal(_mm_popcnt_u32(mask), 1);
+
+ return 0;
+}
diff --git a/src/std/test_time.c b/src/std/test_time.c
new file mode 100644
index 0000000..f948fd0
--- /dev/null
+++ b/src/std/test_time.c
@@ -0,0 +1,13 @@
+#include "testing.h"
+#include "time.h"
+#include "types.h"
+
+i32 test_time_diff() {
+ Time start = Time_get();
+ Time end = Time_get();
+
+ Duration diff = Time_diff(end, start);
+ t_assert_equal(diff.s, 0);
+
+ return 0;
+}
diff --git a/src/std/test_vec.c b/src/std/test_vec.c
new file mode 100644
index 0000000..e95a1b6
--- /dev/null
+++ b/src/std/test_vec.c
@@ -0,0 +1,88 @@
+#include "testing.h"
+#include "vec.h"
+
+typedef struct {
+ i32 x;
+ i32 y;
+} Point;
+
+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;
+}
+
+i32 test_vec_push() {
+ Arena perm = Arena_new(128e3);
+ Vec v = Vec_new(&perm, i32, 4);
+
+ i32 vals[] = {10, 20, 30};
+ for (i32 i = 0; i < 3; i++) Vec_push(&perm, &v, &vals[i]);
+
+ t_assert_equal(v.len, 3);
+ t_assert_equal(*Vec_get(v, i32, 0), 10);
+ t_assert_equal(*Vec_get(v, i32, 1), 20);
+ t_assert_equal(*Vec_get(v, i32, 2), 30);
+ t_assert_equal(Arena_get_used(perm), v.cap * sizeof(i32));
+
+ return 0;
+}
+
+i32 test_vec_grow() {
+ Arena perm = Arena_new(128e3);
+ Vec v = Vec_new(&perm, i32, 2);
+
+ for (i32 i = 0; i < 8; i++) Vec_push(&perm, &v, &i);
+
+ t_assert_equal(v.len, 8);
+ t_assert_equal(Arena_get_used(perm), sizeof(i32) * (2 + 4 + 8));
+
+ return 0;
+}
+
+i32 test_vec_struct() {
+ Arena perm = Arena_new(128e3);
+ Vec v = Vec_new(&perm, Point, 2);
+
+ Point p1 = {1, 2}, p2 = {3, 4};
+ Vec_push(&perm, &v, &p1);
+ Vec_push(&perm, &v, &p2);
+
+ Point *r0 = Vec_get(v, Point, 0);
+ Point *r1 = Vec_get(v, Point, 1);
+ t_assert_equal(r0->x, 1);
+ t_assert_equal(r0->y, 2);
+ t_assert_equal(r1->x, 3);
+ t_assert_equal(r1->y, 4);
+ t_assert_equal(Arena_get_used(perm), 16);
+
+ return 0;
+}
+
+i32 test_vec_initial_len() {
+ Arena perm = Arena_new(128e3);
+ Vec v = Vec_new(&perm, i32, 4);
+
+ t_assert_equal(v.len, 0);
+ t_assert_equal(v.cap, 4);
+ t_assert_equal(Arena_get_used(perm), 16);
+
+ return 0;
+}
diff --git a/src/std/testing.c b/src/std/testing.c
new file mode 100644
index 0000000..af2d1c8
--- /dev/null
+++ b/src/std/testing.c
@@ -0,0 +1,28 @@
+#include <stdio.h>
+
+#include "assert.h"
+#include "memory.h"
+#include "str.h"
+#include "testing.h"
+#include "types.h"
+
+MemoryFile load_file(Arena *a, const char *filename) {
+ FILE *file = fopen(filename, "rb");
+ assert(file != 0, "failed to open file: %s", filename);
+ MemoryFile mem_file = {0};
+
+ // Get file size
+ fseek(file, 0, SEEK_END);
+ mem_file.size = ftell(file);
+ fseek(file, 0, SEEK_SET);
+
+ mem_file.content = new(a, byte, mem_file.size + 1);
+
+ size bytes_read = fread(mem_file.content, 1, mem_file.size, file);
+ assert(bytes_read == mem_file.size, "failed to read entire file: %s",
+ filename);
+
+ mem_file.content[mem_file.size] = '\0'; // Null terminate
+ fclose(file);
+ return mem_file;
+}
diff --git a/src/std/testing.h b/src/std/testing.h
new file mode 100644
index 0000000..388c778
--- /dev/null
+++ b/src/std/testing.h
@@ -0,0 +1,36 @@
+#pragma once
+#include <stdio.h>
+#include <string.h>
+
+#include "memory.h"
+
+typedef struct {
+ size size;
+ byte *content;
+} MemoryFile;
+MemoryFile load_file(Arena *a, const char *filename);
+
+#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; \
+ }
+
+#define t_assert_strequal(a, b) \
+ if ((a.len != b.len) || (a.len && memcmp(a.data, b.data, a.len))) { \
+ fprintf(stderr, \
+ "Assertion failed: %.*s (%s) != %.*s (%s) at %s:%d in %s()\n", \
+ (i32)a.len, a.data, #a, (i32)b.len, b.data, #b, __FILE__, \
+ __LINE__, __func__); \
+ return 1; \
+ }
diff --git a/src/std/time.c b/src/std/time.c
new file mode 100644
index 0000000..331f1d5
--- /dev/null
+++ b/src/std/time.c
@@ -0,0 +1,48 @@
+#include <time.h>
+
+#include "assert.h"
+#include "time.h"
+#include "types.h"
+
+Time Time_get() {
+ struct timespec system_time;
+ assert(clock_gettime(CLOCK_MONOTONIC, &system_time) == 0, "clock_gettime failed");
+ return (Time){system_time.tv_sec, system_time.tv_nsec};
+}
+
+f32 Time_ms(Time time) {
+ return time.s * 1e3 + time.ns / 1e6;
+}
+
+Duration Time_diff(Time end, Time start) {
+ Duration d;
+ d.s = end.s - start.s;
+ d.ns = end.ns - start.ns;
+ if (d.ns >= (u64)1e9) {
+ d.s += d.ns / (u64)1e9;
+ d.ns = d.ns % (u64)1e9;
+ }
+ return d;
+}
+
+f32 Duration_ms(Duration d) {
+ return d.s * 1e3 + d.ns / 1e6;
+}
+
+Duration Duration_from_ms(f32 ms) {
+ Duration d;
+ d.s = (u64)(ms / 1e3);
+ d.ns = (u64)((ms - d.s * 1e3) * 1e6);
+ return d;
+}
+
+Duration Duration_diff(Duration a, Duration b) {
+ Duration d;
+ d.s = a.s - b.s;
+ d.ns = a.ns - b.ns;
+ if (d.ns >= (u64)1e9) {
+ d.s += d.ns / (u64)1e9;
+ d.ns = d.ns % (u64)1e9;
+ }
+ return d;
+}
diff --git a/src/std/time.h b/src/std/time.h
new file mode 100644
index 0000000..26bbf20
--- /dev/null
+++ b/src/std/time.h
@@ -0,0 +1,20 @@
+#pragma once
+
+#include "types.h"
+
+typedef struct {
+ u64 s;
+ u64 ns;
+} Time;
+
+typedef struct {
+ u64 s;
+ u64 ns;
+} Duration;
+
+Time Time_get();
+f32 Time_ms(Time time);
+Duration Time_diff(Time end, Time start);
+f32 Duration_ms(Duration duration);
+Duration Duration_from_ms(f32 ms);
+Duration Duration_diff(Duration a, Duration b);
diff --git a/src/std/types.h b/src/std/types.h
new file mode 100644
index 0000000..6df9510
--- /dev/null
+++ b/src/std/types.h
@@ -0,0 +1,16 @@
+#include <immintrin.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+typedef ptrdiff_t size;
+typedef char byte;
+typedef uintptr_t uptr;
+typedef int32_t i32;
+typedef uint64_t u64;
+typedef float f32;
+typedef __m256i bytex32;
+
+#define sizeof(x) (size)sizeof(x)
+#define countof(a) (sizeof(a) / sizeof(*(a)))
+#define lengthof(s) (countof(s) - 1)
diff --git a/src/std/vec.c b/src/std/vec.c
new file mode 100644
index 0000000..077e64a
--- /dev/null
+++ b/src/std/vec.c
@@ -0,0 +1,27 @@
+#include <string.h>
+
+#include "assert.h"
+#include "types.h"
+#include "vec.h"
+
+Vec _Vec_new(Arena *a, size tsize, size cap, size align) {
+ assert(cap > 0, "negative cap");
+ 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++;
+}
diff --git a/src/std/vec.h b/src/std/vec.h
new file mode 100644
index 0000000..32734ae
--- /dev/null
+++ b/src/std/vec.h
@@ -0,0 +1,18 @@
+#pragma once
+#include <stddef.h>
+#include <stdint.h>
+
+#include "memory.h"
+#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, pos) (T *)(v.data + v.tsize * pos)