/* utils.c - Random C utilities to make it more bearable to work with. Made for my usecase. Tested with C23 only. Some of these come from other people licensed in the public domain, some I wrote myself. Documentation is included at the end of the file. Licensed in the public domain. Do whatever you want with it. Styleguide: - 2 space indentation, - pointers aligned to the type (int* ptr, not int *ptr), - use snake_case for functions, function-like macros, variables and types, - use ALL_CAPS for macro expansions and macro constants, - use shorthands defined below always. */ #ifndef _UTILS_C #define _UTILS_C #include #include #include #include typedef uint8_t u8; typedef uint16_t u16; typedef uint32_t u32; typedef uint64_t u64; typedef int8_t i8; typedef int16_t i16; typedef int32_t i32; typedef int64_t i64; typedef float f32; typedef double f64; typedef size_t usz; typedef ptrdiff_t isz; #define u8_max UINT8_MAX #define u16_max UINT16_MAX #define u32_max UINT32_MAX #define u64_max UINT64_MAX #define i8_min INT8_MIN #define i8_max INT8_MAX #define i16_min INT16_MIN #define i16_max INT16_MAX #define i32_min INT32_MIN #define i32_max INT32_MAX #define i64_min INT64_MIN #define i64_max INT64_MAX #define nil NULL #ifdef USE_RANDOM_UTIL #include #include #include static inline u64 random_u64(void) { u64 value = 0; usz offset = 0; while (offset < sizeof(value)) { ssize_t result = getrandom( ((u8*)&value) + offset, sizeof(value) - offset, 0 ); if (result <= 0) { if (errno == EINTR) { continue; } assert(0 && "getrandom failed"); } offset += (usz)result; } return value; } static inline i64 random_i64(void) { return (i64)random_u64(); } static inline u32 random_u32(void) { return (u32)random_u64(); } static inline i32 random_i32(void) { return (i32)random_u32(); } static inline u16 random_u16(void) { return (u16)random_u64(); } static inline i16 random_i16(void) { return (i16)random_u16(); } static inline u8 random_u8(void) { return (u8)random_u64(); } static inline i8 random_i8(void) { return (i8)random_u8(); } static inline u64 random_u64_range(u64 min, u64 max) { if (min > max) { u64 tmp = min; min = max; max = tmp; } if (min == 0 && max == u64_max) { return random_u64(); } u64 range = max - min + 1; u64 limit = u64_max - (u64_max % range); u64 value; do { value = random_u64(); } while (value >= limit); return min + (value % range); } static inline i64 random_i64_range(i64 min, i64 max) { if (min > max) { i64 tmp = min; min = max; max = tmp; } u64 range = (u64)max - (u64)min + 1; u64 limit = u64_max - (u64_max % range); u64 value; do { value = random_u64(); } while (value >= limit); return min + (i64)(value % range); } static inline u32 random_u32_range(u32 min, u32 max) { return (u32)random_u64_range(min, max); } static inline i32 random_i32_range(i32 min, i32 max) { return (i32)random_i64_range(min, max); } static inline u16 random_u16_range(u16 min, u16 max) { return (u16)random_u64_range(min, max); } static inline i16 random_i16_range(i16 min, i16 max) { return (i16)random_i64_range(min, max); } static inline u8 random_u8_range(u8 min, u8 max) { return (u8)random_u64_range(min, max); } static inline i8 random_i8_range(i8 min, i8 max) { return (i8)random_i64_range(min, max); } static inline f64 random_f64(void) { return (f64)random_u64() / ((f64)u64_max + 1.0); } static inline f32 random_f32(void) { return (f32)random_u32() / ((f32)u32_max + 1.0f); } static inline f64 random_f64_range(f64 min, f64 max) { return min + (max - min) * random_f64(); } static inline f32 random_f32_range(f32 min, f32 max) { return min + (max - min) * random_f32(); } #endif // USE_RANDOM_UTIL #ifdef USE_ALLOC_UTIL typedef struct { void** allocations; usz count; usz capacity; } alloc_tracker; static void alloc_tracker_init(alloc_tracker* tracker) { const usz initial_capacity = 16; tracker->allocations = malloc(sizeof(void*) * initial_capacity); tracker->count = 0; tracker->capacity = initial_capacity; } static bool _alloc_tracker_resize(alloc_tracker* tracker) { const usz new_capacity = (usz)(tracker->capacity * 1.5); void** new_allocations = realloc(tracker->allocations, sizeof(void*) * new_capacity); if (new_allocations) { tracker->allocations = new_allocations; tracker->capacity = new_capacity; return true; } return false; } static void* talloc(alloc_tracker* tracker, usz size) { if (tracker->count >= tracker->capacity) { if (!_alloc_tracker_resize(tracker)) { return nil; } } void* ptr = malloc(size); if (ptr) { tracker->allocations[tracker->count++] = ptr; } return ptr; } static void tfree(alloc_tracker* tracker, void* ptr) { for (usz i = 0; i < tracker->count; i++) { if (tracker->allocations[i] == ptr) { free(ptr); tracker->allocations[i] = tracker->allocations[tracker->count - 1]; tracker->count--; return; } } } static bool alloc_tracker_add_ptr(alloc_tracker* tracker, void* ptr) { if (ptr == nil || tracker == nil) { return false; } for (usz i = 0; i < tracker->count; i++) { if (tracker->allocations[i] == ptr) { return false; } } if ( tracker->count >= tracker->capacity && !_alloc_tracker_resize(tracker) ) { return false; } if (tracker->count < tracker->capacity) { tracker->allocations[tracker->count] = ptr; tracker->count += 1; } return true; } static void* trealloc(alloc_tracker* tracker, void* ptr, usz new_size) { usz found_index = 0; bool found = false; for (usz i = 0; i < tracker->count; i++) { if (tracker->allocations[i] == ptr) { found_index = i; found = true; break; } } if (!found) { return nil; } void* new_ptr = realloc(ptr, new_size); if (!new_ptr) { return nil; } tracker->allocations[found_index] = new_ptr; return new_ptr; } static void alloc_tracker_free_all(alloc_tracker* tracker) { for (usz i = 0; i < tracker->count; i++) { free(tracker->allocations[i]); } free(tracker->allocations); tracker->allocations = nil; tracker->count = 0; tracker->capacity = 0; } #ifndef ARENA_ALIGNMENT #define ARENA_ALIGNMENT 8 #endif typedef struct arena_chunk { u8* memory; usz capacity; usz offset; struct arena_chunk* next; } _arena_chunk; typedef struct { _arena_chunk* head; _arena_chunk* tail; _arena_chunk* current; usz chunk_size; } _arena_class; typedef struct { _arena_class primary; _arena_class oversized; } arena; _Static_assert( (ARENA_ALIGNMENT & (ARENA_ALIGNMENT - 1)) == 0, "ARENA_ALIGNMENT must be a power of two" ); static usz _arena_align(usz x) { usz mask = ARENA_ALIGNMENT - 1; return (x + mask) & ~mask; } static _arena_chunk* _arena_chunk_create(usz capacity) { _arena_chunk* c = (_arena_chunk*)malloc(sizeof(_arena_chunk)); if (c == nil) { return nil; } c->memory = (u8*)malloc(capacity); if (c->memory == nil) { free(c); return nil; } c->capacity = capacity; c->offset = 0; c->next = nil; return c; } static void _arena_class_init(_arena_class* cls, usz chunk_size) { cls->head = nil; cls->tail = nil; cls->current = nil; cls->chunk_size = chunk_size; } static void _arena_class_destroy(_arena_class* cls) { _arena_chunk* c = cls->head; while (c != nil) { _arena_chunk* next = c->next; free(c->memory); free(c); c = next; } cls->head = nil; cls->tail = nil; cls->current = nil; } static void _arena_class_reset(_arena_class* cls) { for (_arena_chunk* c = cls->head; c != nil; c = c->next) { c->offset = 0; } cls->current = cls->head; } static void* _arena_class_alloc(_arena_class* cls, usz size) { size = _arena_align(size); if (cls->current == nil) { cls->current = cls->head; } while ( cls->current != nil && cls->current->offset + size > cls->current->capacity ) { cls->current = cls->current->next; } if (cls->current == nil) { usz alloc_size = (size > cls->chunk_size) ? size : cls->chunk_size; _arena_chunk* chunk = _arena_chunk_create(alloc_size); if (chunk == nil) { return nil; } if (cls->head == nil) { cls->head = chunk; cls->tail = chunk; } else { cls->tail->next = chunk; cls->tail = chunk; } cls->current = chunk; } void* ptr = cls->current->memory + cls->current->offset; cls->current->offset += size; return ptr; } static bool arena_init_custom(arena* a, usz chunk_size) { if (a == nil) { return false; } _arena_class_init(&a->primary, chunk_size); _arena_class_init(&a->oversized, chunk_size); return true; } static bool arena_init(arena* a) { return arena_init_custom(a, 64 * 1024); // 64 KB } static void arena_reset(arena* a) { _arena_class_reset(&a->primary); _arena_class_destroy(&a->oversized); _arena_class_init(&a->oversized, a->primary.chunk_size); } static void arena_destroy(arena* a) { _arena_class_destroy(&a->primary); _arena_class_destroy(&a->oversized); } static void* aalloc(arena* a, usz size) { size = _arena_align(size); if (size <= a->primary.chunk_size) { return _arena_class_alloc(&a->primary, size); } return _arena_class_alloc(&a->oversized, size); } #endif // USE_ALLOC_UTIL #ifdef USE_OPTION_UTIL typedef struct { enum { OPTION_NONE, OPTION_SOME } tag; void* some; } option; static inline option option_none(void) { option opt; opt.tag = OPTION_NONE; return opt; } static inline option option_some(void* value) { option opt; opt.tag = OPTION_SOME; opt.some = value; return opt; } static inline bool opt_is_none(option opt) { return opt.tag == OPTION_NONE; } static inline bool opt_is_some(option opt) { return opt.tag == OPTION_SOME; } static inline option option_from_ptr(void* ptr) { if (ptr == nil) { return option_none(); } return option_some(ptr); } #endif // USE_OPTION_UTIL #ifdef USE_DEFER_UTIL #define _CONCAT_INTERNAL(x, y) x##y #define _CONCAT(x, y) CONCAT_INTERNAL(x, y) #define _DEFER_INTERNAL(id, code) \ void _CONCAT(_defer_func_, id)(void* _unused) { \ (void)_unused; \ code \ } \ \ __attribute__((cleanup(_CONCAT(_defer_func_, id)))) \ int _CONCAT(_defer_var_, id) = 0 #define defer(code) _DEFER_INTERNAL(__COUNTER__, code) #endif // USE_DEFER_UTIL #ifdef USE_STR_VIEW_UTIL /* Taken from tsoding's nob.h */ #include #include #include typedef struct { size_t count; const char *data; } str_view; // Forward declarations so that the functions can call each other static str_view str_view_chop_while(str_view *sv, int (*p)(int x)); static str_view str_view_chop_by_delim(str_view *sv, char delim); static str_view str_view_chop_left(str_view *sv, size_t n); static str_view str_view_chop_right(str_view *sv, size_t n); static bool str_view_chop_prefix(str_view *sv, str_view prefix); static bool str_view_chop_suffix(str_view *sv, str_view suffix); static str_view str_view_trim(str_view sv); static str_view str_view_trim_left(str_view sv); static str_view str_view_trim_right(str_view sv); static bool str_view_eq(str_view a, str_view b); static bool str_view_ends_with_cstr(str_view sv, const char *cstr); static bool str_view_ends_with(str_view sv, str_view suffix); static bool str_view_starts_with(str_view sv, str_view prefix); static str_view str_view_from_cstr(const char *cstr); static str_view str_view_from_parts(const char *data, size_t count); #define svpfmt "%.*s" #define svpfarg(sv) (int)(sv).count, (sv).data static str_view str_view_chop_while(str_view *sv, int (*p)(int x)) { size_t i = 0; while (i < sv->count && p(sv->data[i])) { i += 1; } str_view result = str_view_from_parts(sv->data, i); sv->count -= i; sv->data += i; return result; } static str_view str_view_chop_by_delim(str_view *sv, char delim) { size_t i = 0; while (i < sv->count && sv->data[i] != delim) { i += 1; } str_view result = str_view_from_parts(sv->data, i); if (i < sv->count) { sv->count -= i + 1; sv->data += i + 1; } else { sv->count -= i; sv->data += i; } return result; } static bool str_view_chop_prefix(str_view *sv, str_view prefix) { if (str_view_starts_with(*sv, prefix)) { str_view_chop_left(sv, prefix.count); return true; } return false; } static bool str_view_chop_suffix(str_view *sv, str_view suffix) { if (str_view_ends_with(*sv, suffix)) { str_view_chop_right(sv, suffix.count); return true; } return false; } static str_view str_view_chop_left(str_view *sv, size_t n) { if (n > sv->count) { n = sv->count; } str_view result = str_view_from_parts(sv->data, n); sv->data += n; sv->count -= n; return result; } static str_view str_view_chop_right(str_view *sv, size_t n) { if (n > sv->count) { n = sv->count; } str_view result = str_view_from_parts(sv->data + sv->count - n, n); sv->count -= n; return result; } static str_view str_view_from_parts(const char *data, size_t count) { str_view sv; sv.count = count; sv.data = data; return sv; } static str_view str_view_trim_left(str_view sv) { size_t i = 0; while (i < sv.count && isspace(sv.data[i])) { i += 1; } return str_view_from_parts(sv.data + i, sv.count - i); } static str_view str_view_trim_right(str_view sv) { size_t i = 0; while (i < sv.count && isspace(sv.data[sv.count - 1 - i])) { i += 1; } return str_view_from_parts(sv.data, sv.count - i); } static str_view str_view_trim(str_view sv) { return str_view_trim_right(str_view_trim_left(sv)); } static str_view str_view_from_cstr(const char *cstr) { return str_view_from_parts(cstr, strlen(cstr)); } static bool str_view_eq(str_view a, str_view b) { if (a.count != b.count) { return false; } else { return memcmp(a.data, b.data, a.count) == 0; } } static bool str_view_ends_with_cstr(str_view sv, const char *cstr) { return str_view_ends_with(sv, str_view_from_cstr(cstr)); } static bool str_view_ends_with(str_view sv, str_view suffix) { if (sv.count >= suffix.count) { str_view sv_tail = { .count = suffix.count, .data = sv.data + sv.count - suffix.count, }; return str_view_eq(sv_tail, suffix); } return false; } static bool str_view_starts_with(str_view sv, str_view expected_prefix) { if (expected_prefix.count <= sv.count) { str_view actual_prefix = str_view_from_parts(sv.data, expected_prefix.count); return str_view_eq(expected_prefix, actual_prefix); } return false; } #endif // USE_STR_VIEW_UTIL #ifdef USE_DYN_ARR_UTIL /* Inspired by tsoding. */ #include #include typedef struct { usz count; usz capacity; } _dyn_arr_header; #define DYN_ARR_INIT_CAPACITY 8 #define DYN_ARR_GROWTH_FACTOR 1.5 #define _DYN_ARR_HEADER_SIZE \ ((sizeof(_dyn_arr_header) + alignof(max_align_t) - 1) & \ ~(alignof(max_align_t) - 1)) #define _arr_hdr(arr) \ ((_dyn_arr_header*)((char*)(arr) - _DYN_ARR_HEADER_SIZE)) #define arr_len(arr) \ ((arr) ? _arr_hdr(arr)->count : 0) #define arr_free(arr) \ do { \ if (arr) { \ free(_arr_hdr(arr)); \ (arr) = nil; \ } \ } while (0) static bool _arr_push_impl( void** arr, usz elem_size, const void* value ) { if (*arr == nil) { usz cap = DYN_ARR_INIT_CAPACITY; if ( cap == 0 || cap > SIZE_MAX / elem_size ) { return 0; } usz bytes = _DYN_ARR_HEADER_SIZE + elem_size * cap; _dyn_arr_header* h = malloc(bytes); if (h == nil) { return false; } h->count = 0; h->capacity = cap; *arr = (char*)h + _DYN_ARR_HEADER_SIZE; } _dyn_arr_header* h = _arr_hdr(*arr); if (h->count >= h->capacity) { usz newcap = h->capacity * DYN_ARR_GROWTH_FACTOR; if ( newcap <= h->capacity || newcap > SIZE_MAX / elem_size ) { return 0; } usz bytes = _DYN_ARR_HEADER_SIZE + elem_size * newcap; _dyn_arr_header* newh = realloc(h, bytes); if (!newh) { return false; } newh->capacity = newcap; *arr = (char*)newh + _DYN_ARR_HEADER_SIZE; h = newh; } memcpy( (char*)(*arr) + elem_size * h->count, value, elem_size ); h->count++; return true; } #define arr_push(arr, value) \ _arr_push_impl( \ (void**)&(arr), \ sizeof(*(arr)), \ &(typeof(*(arr))){(value)} \ ) static bool c_arr_to_dyn( void** arr, const void* c_arr, usz count ) { for (usz i = 0; i < count; i++) { const void* elem_ptr = (const char*)c_arr + i * sizeof(*(arr)); if (!arr_push(arr, *(const typeof(*(arr))*)elem_ptr)) { return false; } } return true; } #endif // USE_DYN_ARR_UTIL #ifdef USE_STR_BUILDER_UTIL #include typedef struct { char* data; usz count; usz capacity; } str_builder; #define sbpfmt "%.*s" #define sbpfarg(sb) (int)(sb).count, (sb).data static bool str_builder_reserve( str_builder* sb, usz additional ) { usz required = sb->count + additional + 1; if (required <= sb->capacity) { return true; } usz new_capacity = sb->capacity > 0 ? sb->capacity : 64; while (new_capacity < required) { usz next = (usz)(new_capacity * 1.5); if (next <= new_capacity) { return false; } new_capacity = next; } char* new_data = realloc(sb->data, new_capacity); if (new_data == nil) { return false; } sb->data = new_data; sb->capacity = new_capacity; return true; } static bool str_builder_append_n( str_builder* sb, const char* data, usz size ) { if (!str_builder_reserve(sb, size)) { return false; } memcpy( sb->data + sb->count, data, size ); sb->count += size; sb->data[sb->count] = '\0'; return true; } static bool str_builder_append_cstr( str_builder* sb, const char* cstr ) { return str_builder_append_n( sb, cstr, strlen(cstr) ); } #ifdef USE_STR_VIEW_UTIL static bool str_builder_append_sv( str_builder* sb, str_view sv ) { return str_builder_append_n( sb, sv.data, sv.count ); } static str_view str_builder_view( const str_builder* sb ) { return str_view_from_parts( sb->data ? sb->data : "", sb->count ); } #endif // USE_STR_VIEW_UTIL static void str_builder_clear( str_builder* sb ) { sb->count = 0; if (sb->data != nil) { sb->data[0] = '\0'; } } static void str_builder_free( str_builder* sb ) { free(sb->data); sb->data = nil; sb->count = 0; sb->capacity = 0; } #endif // USE_STR_BUILDER_UTIL #ifdef USE_FILE_UTIL #include #ifdef USE_STR_BUILDER_UTIL static bool read_entire_file( const char* path, str_builder* sb ) { FILE* f = fopen(path, "rb"); if (f == nil) { return false; } if (fseek(f, 0, SEEK_END) != 0) { fclose(f); return false; } long size = ftell(f); if (size < 0) { fclose(f); return false; } rewind(f); str_builder_clear(sb); if ( !str_builder_reserve( sb, (usz)size ) ) { fclose(f); return false; } usz read = fread( sb->data, 1, (usz)size, f ); fclose(f); if (read != (usz)size) { return false; } sb->count = read; sb->data[sb->count] = '\0'; return true; } #endif // USE_STR_BUILDER_UTIL static bool write_entire_file_cstr( const char* path, const char* data ) { FILE* f = fopen(path, "wb"); if (f == nil) { return false; } usz size = strlen(data); usz written = fwrite( data, 1, size, f ); fclose(f); return written == size; } #ifdef USE_STR_VIEW_UTIL static bool write_entire_file_sv( const char* path, str_view sv ) { FILE* f = fopen(path, "wb"); if (f == nil) { return false; } usz written = fwrite( sv.data, 1, sv.count, f ); fclose(f); return written == sv.count; } #endif // USE_STR_VIEW_UTIL #if defined(USE_STR_BUILDER_UTIL) && defined(USE_STR_VIEW_UTIL) static bool write_entire_file_sb( const char* path, const str_builder* sb ) { return write_entire_file_sv( path, str_builder_view(sb) ); } #endif // USE_STR_BUILDER_UTIL && USE_STR_VIEW_UTIL #ifdef USE_STR_VIEW_UTIL #define _WRITE_FILE_SV_TYPES \ , str_view: write_entire_file_sv #else #define _WRITE_FILE_SV_TYPES #endif #if defined(USE_STR_BUILDER_UTIL) && defined(USE_STR_VIEW_UTIL) #define _WRITE_FILE_SB_TYPES \ , str_builder: write_entire_file_sb \ , str_builder*: write_entire_file_sb #else #define _WRITE_FILE_SB_TYPES #endif #define write_entire_file(path, data) \ _Generic((data), \ char*: write_entire_file_cstr, \ const char*: write_entire_file_cstr \ _WRITE_FILE_SV_TYPES \ _WRITE_FILE_SB_TYPES \ )(path, data) #endif // USE_FILE_UTIL #endif // _UTILS_C /* DOCUMENTATION =============== CORE SHORTHANDS =============== Description: Convenient shorthands for common types and constants. Typedefs/macro constants: u8, u16, u32, u64 - Unsigned integers of 8, 16, 32 and 64 bits. i8, i16, i32, i64 - Signed integers of 8, 16, 32 and 64 bits. f32, f64 - 32-bit and 64-bit floating point numbers. usz - Unsigned size for platform. isz - Signed size for platform (pointer difference). u8_max, u16_max, u32_max, u64_max - Maximum values for unsigned integers. i8_min, i8_max, i16_min, i16_max, i32_min, i32_max, i64_min, i64_max - Minimum and maximum values for signed integers. nil - Shorthand for NULL pointer. =============== USE_RANDOM_UTIL =============== Description: Cryptographically secure random number generation utilities built on top of getrandom(2). Functions/macros: u64 random_u64(void) - Generate a random u64. i64 random_i64(void) - Generate a random i64. u32 random_u32(void) - Generate a random u32. i32 random_i32(void) - Generate a random i32. u16 random_u16(void) - Generate a random u16. i16 random_i16(void) - Generate a random i16. u8 random_u8(void) - Generate a random u8. i8 random_i8(void) - Generate a random i8. u64 random_u64_range(u64 min, u64 max) - Generate a random u64 in the range [min, max]. i64 random_i64_range(i64 min, i64 max) - Generate a random i64 in the range [min, max]. u32 random_u32_range(u32 min, u32 max) - Generate a random u32 in the range [min, max]. i32 random_i32_range(i32 min, i32 max) - Generate a random i32 in the range [min, max]. u16 random_u16_range(u16 min, u16 max) - Generate a random u16 in the range [min, max]. i16 random_i16_range(i16 min, i16 max) - Generate a random i16 in the range [min, max]. u8 random_u8_range(u8 min, u8 max) - Generate a random u8 in the range [min, max]. i8 random_i8_range(i8 min, i8 max) - Generate a random i8 in the range [min, max]. f64 random_f64(void) - Generate a random f64 in the range [0.0, 1.0). f32 random_f32(void) - Generate a random f32 in the range [0.0f, 1.0f). f64 random_f64_range(f64 min, f64 max) - Generate a random f64 in the range [min, max). f32 random_f32_range(f32 min, f32 max) - Generate a random f32 in the range [min, max). ============== USE_ALLOC_UTIL ============== Description: Allocation utilities including an allocation tracker and a simple arena allocator. Structs: alloc_tracker { void** allocations; // Array of tracked pointers usz count; // Number of tracked allocations usz capacity; // Capacity of the allocations array } arena { // Private fields... } Macro constants: ARENA_ALIGNMENT - Alignment used for arena allocations (default 8). Functions/macros: void alloc_tracker_init(alloc_tracker* tracker) - Initialize an allocation tracker. void* talloc(alloc_tracker* tracker, usz size) Allocate memory using malloc and track the allocation. void tfree(alloc_tracker* tracker, void* ptr) Free a tracked allocation and remove it from the tracker. bool alloc_tracker_add_ptr(alloc_tracker* tracker, void* ptr) Add an externally allocated pointer to the tracker. void* trealloc(alloc_tracker* tracker, void* ptr, usz new_size) Reallocate a tracked allocation. void alloc_tracker_free_all(alloc_tracker* tracker) Free all tracked allocations and reset the tracker. bool arena_init(arena* a) Initialize an arena with the default chunk size (64 KB). bool arena_init_custom(arena* a, usz chunk_size) Initialize an arena with a custom chunk size. void arena_reset(arena* a) Reset an arena for reuse. Keeps primary chunks allocated for future allocations. Frees oversized allocations. void arena_destroy(arena* a) Destroy an arena and free all associated memory. void* aalloc(arena* a, usz size) Allocate memory from the arena. =============== USE_OPTION_UTIL =============== Description: Rust-style optional value container for nullable pointers. Structs: option { ... tag - OPTION_NONE or OPTION_SOME void* some - Stored pointer value when tag == OPTION_SOME } Functions/macros: option option_none(void) Create an empty option. option option_some(void* value) Create an option containing a pointer value. bool opt_is_none(option opt) Check whether an option is empty. bool opt_is_some(option opt) Check whether an option contains a value. option option_from_ptr(void* ptr) Convert a nullable pointer into an option. Returns OPTION_NONE when ptr == nil. ============== USE_DEFER_UTIL ============== Description: GCC-only defer utility inspired by modern languages. Executes code automatically when the current scope exits. Dependencies: GCC compiler extensions. Functions/macros: defer(code) Execute code automatically at scope exit. ================= USE_STR_VIEW_UTIL ================= Description: Lightweight non-owning string slice utilities. Structs: str_view { size_t count; // Length of the string view const char* data; // Pointer to string data } Helper macros: svpfmt printf format string for printing str_view. svpfarg(str_view sv) printf argument helper for str_view. Functions/macros: str_view str_view_chop_while(str_view* sv, int (*p)(int x)) Remove and return characters from the beginning while predicate returns true. str_view str_view_chop_by_delim(str_view* sv, char delim) Remove and return characters until delimiter is reached. str_view str_view_chop_left(str_view* sv, size_t n) Remove and return n characters from the left side. str_view str_view_chop_right(str_view* sv, size_t n) Remove and return n characters from the right side. bool str_view_chop_prefix(str_view* sv, str_view prefix) Remove a prefix if present. bool str_view_chop_suffix(str_view* sv, str_view suffix) Remove a suffix if present. str_view str_view_trim(str_view sv) Trim whitespace from both sides. str_view str_view_trim_left(str_view sv) Trim whitespace from the left side. str_view str_view_trim_right(str_view sv) Trim whitespace from the right side. bool str_view_eq(str_view a, str_view b) Compare two string views for equality. bool str_view_ends_with_cstr(str_view sv, const char* cstr) Check whether a string view ends with a C string. bool str_view_ends_with(str_view sv, str_view suffix) Check whether a string view ends with another string view. bool str_view_starts_with(str_view sv, str_view prefix) Check whether a string view starts with another string view. str_view str_view_from_cstr(const char* cstr) Create a string view from a null-terminated C string. str_view str_view_from_parts(const char* data, size_t count) Create a string view from raw pointer and length. ================ USE_DYN_ARR_UTIL ================ Description: Dynamic array utilities implemented using a hidden header stored before the array data. Macro constants: DYN_ARR_INIT_CAPACITY - Initial dynamic array capacity. DYN_ARR_GROWTH_FACTOR - Capacity growth multiplier. Functions/macros: usz arr_len(any* arr) Get the number of elements in the dynamic array. void arr_free(any* arr) Free the dynamic array and set pointer to nil. bool arr_push(any* arr, any value) Append a value to the dynamic array. Automatically grows the allocation if needed. bool c_arr_to_dyn(any* arr, any[] c_arr, usz count) Convert a C array to a dynamic array by pushing each element. Example: int[] my_array = {}; arr_push(my_array, 42); arr_push(my_array, 99); for (usz i = 0; i < arr_len(my_array); i++) { printf("%d\n", my_array[i]); } ==================== USE_STR_BUILDER_UTIL ==================== Description: Dynamically growing string builder for efficient string construction. Structs: str_builder { char* data; // String buffer usz count; // Current string length usz capacity; // Allocated buffer size } Macros: sbpfmt printf format string for printing str_builder contents. sbpfarg(str_builder sb) printf argument helper for str_builder. Functions/macros: bool str_builder_reserve(str_builder* sb, usz additional) Ensure the builder has enough capacity for additional bytes. bool str_builder_append_n(str_builder* sb, const char* data, usz size) Append raw bytes to the builder. bool str_builder_append_cstr(str_builder* sb, const char* cstr) Append a null-terminated C string. bool str_builder_append_sv(str_builder* sb, str_view sv) Append a string view. Dependency: USE_STR_VIEW_UTIL str_view str_builder_view(const str_builder* sb) Create a str_view referencing the builder contents. Dependency: USE_STR_VIEW_UTIL void str_builder_clear(str_builder* sb) Clear the builder contents while keeping allocated memory. void str_builder_free(str_builder* sb) Free the builder memory and reset the builder. ============= USE_FILE_UTIL ============= Description: Convenience utilities for reading and writing entire files. Functions/macros: bool read_entire_file(const char* path, str_builder* sb) Read an entire file into a string builder. Dependency: USE_STR_BUILDER_UTIL bool write_entire_file_cstr(const char* path, const char* data) Write a null-terminated C string to a file. bool write_entire_file_sv(const char* path, str_view sv) Write a string view to a file. Dependency: USE_STR_VIEW_UTIL bool write_entire_file_sb(const char* path, const str_builder* sb) Write a string builder contents to a file. Dependencies: USE_STR_BUILDER_UTIL USE_STR_VIEW_UTIL bool write_entire_file( const char* path, const char* | const str_builder* | str_view data ) Generic macro wrapper selecting the correct file writing implementation based on the type of data. */