libraries/nonstd.h
raw
1// nonstd.h
2// A collection of useful functions and macros.
3// This library is licensed under the BSD 2-Clause License.
4//
5// This file provides both the interface and the implementation.
6// To instantiate the implementation,
7// #define NONSTD_IMPLEMENTATION
8// before including this file.
9
10#ifdef NONSTD_IMPLEMENTATION
11#ifndef _POSIX_C_SOURCE
12#define _POSIX_C_SOURCE 200809L
13#endif
14#endif
15
16#ifndef NONSTD_H
17#define NONSTD_H
18
19#include <stdarg.h>
20#include <stddef.h>
21#include <stdint.h>
22#include <stdio.h>
23#include <stdlib.h>
24#include <string.h>
25#include <sys/time.h>
26#include <time.h>
27#include <unistd.h>
28
29#ifndef NONSTD_DEF
30#ifdef NONSTD_STATIC
31#define NONSTD_DEF static
32#else
33#define NONSTD_DEF extern
34#endif
35#endif
36
37typedef int8_t i8;
38typedef uint8_t u8;
39typedef int16_t i16;
40typedef uint16_t u16;
41typedef int32_t i32;
42typedef uint32_t u32;
43typedef int64_t i64;
44typedef uint64_t u64;
45typedef unsigned int uint;
46typedef unsigned long ulong;
47typedef intptr_t isize;
48typedef uintptr_t usize;
49typedef char c8;
50
51#define countof(a) (sizeof(a) / sizeof((a)[0]))
52
53#define static_foreach(type, var, array) \
54 for (size_t _i_##var = 0, _n_##var = countof(array); \
55 _i_##var < _n_##var && ((var) = (array)[_i_##var], 1); ++_i_##var)
56
57#define ALLOC(type, n) ((type *)safe_malloc(sizeof(type), (n)))
58#define REALLOC(ptr, type, n) ((type *)safe_realloc((ptr), sizeof(type), (n)))
59#define FREE(ptr) \
60 do { \
61 free(ptr); \
62 ptr = NULL; \
63 } while (0)
64
65NONSTD_DEF void *safe_malloc(size_t item_size, size_t count);
66NONSTD_DEF void *safe_realloc(void *ptr, size_t item_size, size_t count);
67
68#ifndef MIN
69#define MIN(a, b) ((a) < (b) ? (a) : (b))
70#endif
71#ifndef MAX
72#define MAX(a, b) ((a) > (b) ? (a) : (b))
73#endif
74#define CLAMP(x, lo, hi) (MIN((hi), MAX((lo), (x))))
75
76// From https://github.com/tsoding/nob.h/blob/e2c9a46f01d052ab740140e74453665dc3334832/nob.h#L205-L206.
77#define UNUSED(value) (void)(value)
78#define TODO(message) \
79 do { \
80 fprintf(stderr, "%s:%d: TODO: %s\n", __FILE__, __LINE__, message); \
81 abort(); \
82 } while (0)
83#define UNREACHABLE(message) \
84 do { \
85 fprintf(stderr, "%s:%d: UNREACHABLE: %s\n", __FILE__, __LINE__, message); \
86 abort(); \
87 } while (0)
88
89#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
90#define STATIC_ASSERT(expr, msg) _Static_assert((expr), msg)
91#else
92#define STATIC_ASSERT(expr, msg) \
93 typedef char static_assertion_##msg[(expr) ? 1 : -1]
94#endif
95
96// String view - read-only, non-owning reference to a string
97typedef struct {
98 const char *data;
99 size_t length;
100} stringv;
101
102NONSTD_DEF stringv sv_from_cstr(const char *s);
103NONSTD_DEF stringv sv_from_parts(const char *data, size_t length);
104NONSTD_DEF stringv sv_slice(stringv sv, size_t start, size_t end);
105NONSTD_DEF int sv_equals(stringv a, stringv b);
106NONSTD_DEF int sv_starts_with(stringv sv, stringv prefix);
107NONSTD_DEF int sv_ends_with(stringv sv, stringv suffix);
108
109// String builder - owning, mutable, dynamically growing string buffer
110typedef struct {
111 char *data;
112 size_t length;
113 size_t capacity;
114} stringb;
115
116NONSTD_DEF void sb_init(stringb *sb, size_t initial_cap);
117NONSTD_DEF void sb_free(stringb *sb);
118NONSTD_DEF void sb_ensure(stringb *sb, size_t additional);
119NONSTD_DEF void sb_append_cstr(stringb *sb, const char *s);
120NONSTD_DEF void sb_append_sv(stringb *sb, stringv sv);
121NONSTD_DEF void sb_append_char(stringb *sb, char c);
122NONSTD_DEF stringv sb_as_sv(const stringb *sb);
123
124// Slice - generic non-owning view into an array
125// Usage: SLICE_DEF(int); slice(int) view = ...;
126#define SLICE_DEF(T) \
127 typedef struct { \
128 T *data; \
129 size_t length; \
130 } slice_##T
131
132#define slice(T) slice_##T
133
134#define make_slice(T, ptr, len) ((slice(T)){.data = (ptr), .length = (len)})
135
136#define array_as_slice(T, arr) \
137 ((slice(T)){.data = (arr).data, .length = (arr).length})
138
139// Dynamic array - generic type-safe growable array using macros
140// Usage: array(int) numbers; array_init(numbers);
141#define array(T) \
142 struct { \
143 T *data; \
144 size_t length; \
145 size_t capacity; \
146 }
147
148#define array_init(arr) \
149 do { \
150 (arr).capacity = 0; \
151 (arr).data = NULL; \
152 (arr).length = 0; \
153 } while (0)
154
155#define array_init_cap(arr, initial_cap) \
156 do { \
157 (arr).capacity = (initial_cap) ? (initial_cap) : 16; \
158 (arr).data = ALLOC(__typeof__(*(arr).data), (arr).capacity); \
159 (arr).length = 0; \
160 } while (0)
161
162#define array_free(arr) \
163 do { \
164 FREE((arr).data); \
165 (arr).length = 0; \
166 (arr).capacity = 0; \
167 } while (0)
168
169#define array_ensure(arr, additional) \
170 do { \
171 size_t _needed = (arr).length + (additional); \
172 if (_needed > (arr).capacity) { \
173 size_t _new_cap = (arr).capacity ? (arr).capacity : 16; \
174 while (_new_cap < _needed) { \
175 if (_new_cap > SIZE_MAX / 2) { \
176 _new_cap = SIZE_MAX; \
177 break; \
178 } \
179 _new_cap *= 2; \
180 } \
181 if (_new_cap < _needed) { /* Overflow or OOM */ \
182 break; \
183 } \
184 void *_new_data = \
185 safe_realloc((arr).data, sizeof(*(arr).data), _new_cap); \
186 if (_new_data) { \
187 (arr).data = _new_data; \
188 (arr).capacity = _new_cap; \
189 } \
190 } \
191 } while (0)
192
193#define array_push(arr, value) \
194 do { \
195 array_ensure((arr), 1); \
196 if ((arr).length < (arr).capacity) { \
197 (arr).data[(arr).length++] = (value); \
198 } \
199 } while (0)
200
201#define array_pop(arr) ((arr).length > 0 ? (arr).data[--(arr).length] : 0)
202
203#define array_get(arr, index) ((arr).data[index])
204
205#define array_set(arr, index, value) \
206 do { \
207 if ((index) < (arr).length) { \
208 (arr).data[index] = (value); \
209 } \
210 } while (0)
211
212#define array_insert(arr, index, value) \
213 do { \
214 if ((index) <= (arr).length) { \
215 array_ensure((arr), 1); \
216 if ((arr).length < (arr).capacity) { \
217 for (size_t _i = (arr).length; _i > (index); --_i) { \
218 (arr).data[_i] = (arr).data[_i - 1]; \
219 } \
220 (arr).data[index] = (value); \
221 (arr).length++; \
222 } \
223 } \
224 } while (0)
225
226#define array_remove(arr, index) \
227 do { \
228 if ((index) < (arr).length) { \
229 for (size_t _i = (index); _i < (arr).length - 1; ++_i) { \
230 (arr).data[_i] = (arr).data[_i + 1]; \
231 } \
232 (arr).length--; \
233 } \
234 } while (0)
235
236#define array_clear(arr) \
237 do { \
238 (arr).length = 0; \
239 } while (0)
240
241#define array_reserve(arr, new_capacity) \
242 do { \
243 if ((new_capacity) > (arr).capacity) { \
244 void *_new_data = \
245 safe_realloc((arr).data, sizeof(*(arr).data), (new_capacity)); \
246 if (_new_data) { \
247 (arr).data = _new_data; \
248 (arr).capacity = (new_capacity); \
249 } \
250 } \
251 } while (0)
252
253#define array_foreach(arr, var) \
254 for (size_t _i_##var = 0; \
255 _i_##var < (arr).length && ((var) = (arr).data[_i_##var], 1); \
256 ++_i_##var)
257
258#define array_foreach_idx(arr, var, index) \
259 for (size_t index = 0; \
260 index < (arr).length && ((var) = (arr).data[index], 1); ++index)
261
262// Arena - block-based memory allocator
263typedef struct {
264 char *ptr;
265 char *end;
266 array(char *) blocks;
267} Arena;
268
269#define ARENA_DEFAULT_BLOCK_SIZE (4096)
270
271NONSTD_DEF Arena arena_make(void);
272NONSTD_DEF void arena_grow(Arena *a, size_t min_size);
273NONSTD_DEF void *arena_alloc(Arena *a, size_t size);
274NONSTD_DEF void arena_free(Arena *a);
275
276// File I/O helpers
277NONSTD_DEF char *read_entire_file(const char *filepath, size_t *out_size);
278NONSTD_DEF int write_entire_file(const char *filepath, const void *data, size_t size);
279NONSTD_DEF stringb read_entire_file_sb(const char *filepath);
280NONSTD_DEF int write_file_sv(const char *filepath, stringv sv);
281NONSTD_DEF int write_file_sb(const char *filepath, const stringb *sb);
282
283#ifdef NONSTD_IMPLEMENTATION
284
285NONSTD_DEF void *safe_malloc(size_t item_size, size_t count) {
286 if (count != 0 && item_size > SIZE_MAX / count) {
287 return NULL;
288 }
289 return malloc(item_size * count);
290}
291
292NONSTD_DEF void *safe_realloc(void *ptr, size_t item_size, size_t count) {
293 if (count != 0 && item_size > SIZE_MAX / count) {
294 return NULL;
295 }
296 return realloc(ptr, item_size * count);
297}
298
299// String View Implementation
300
301NONSTD_DEF stringv sv_from_cstr(const char *s) {
302 return (stringv){.data = s, .length = s ? strlen(s) : 0};
303}
304
305NONSTD_DEF stringv sv_from_parts(const char *data, size_t length) {
306 return (stringv){.data = data, .length = length};
307}
308
309NONSTD_DEF stringv sv_slice(stringv sv, size_t start, size_t end) {
310 if (start > sv.length) {
311 start = sv.length;
312 }
313 if (end > sv.length) {
314 end = sv.length;
315 }
316 if (start > end) {
317 start = end;
318 }
319 return (stringv){.data = sv.data + start, .length = end - start};
320}
321
322NONSTD_DEF int sv_equals(stringv a, stringv b) {
323 return a.length == b.length && (a.length == 0 || memcmp(a.data, b.data, a.length) == 0);
324}
325
326NONSTD_DEF int sv_starts_with(stringv sv, stringv prefix) {
327 return sv.length >= prefix.length && memcmp(sv.data, prefix.data, prefix.length) == 0;
328}
329
330NONSTD_DEF int sv_ends_with(stringv sv, stringv suffix) {
331 return sv.length >= suffix.length && memcmp(sv.data + sv.length - suffix.length, suffix.data, suffix.length) == 0;
332}
333
334// String Builder Implementation
335
336NONSTD_DEF void sb_init(stringb *sb, size_t initial_cap) {
337 sb->capacity = initial_cap ? initial_cap : 16;
338 sb->data = ALLOC(char, sb->capacity);
339 sb->length = 0;
340 if (sb->data) {
341 sb->data[0] = '\0';
342 }
343}
344
345NONSTD_DEF void sb_free(stringb *sb) {
346 FREE(sb->data);
347 sb->length = 0;
348 sb->capacity = 0;
349}
350
351NONSTD_DEF void sb_ensure(stringb *sb, size_t additional) {
352 size_t needed = sb->length + additional + 1;
353 size_t new_cap = sb->capacity;
354
355 if (needed > new_cap) {
356 while (new_cap < needed) {
357 if (new_cap > SIZE_MAX / 2) {
358 new_cap = SIZE_MAX;
359 break;
360 }
361 new_cap *= 2;
362 }
363 if (new_cap < needed)
364 return; // Overflow
365
366 char *new_data = safe_realloc(sb->data, sizeof(char), new_cap);
367 if (new_data) {
368 sb->data = new_data;
369 sb->capacity = new_cap;
370 }
371 }
372}
373
374NONSTD_DEF void sb_append_cstr(stringb *sb, const char *s) {
375 if (!s) {
376 return;
377 }
378 size_t slength = strlen(s);
379 sb_ensure(sb, slength);
380 if (sb->length + slength + 1 <= sb->capacity) {
381 memcpy(sb->data + sb->length, s, slength);
382 sb->length += slength;
383 sb->data[sb->length] = '\0';
384 }
385}
386
387NONSTD_DEF void sb_append_sv(stringb *sb, stringv sv) {
388 if (!sv.data || sv.length == 0) {
389 return;
390 }
391 sb_ensure(sb, sv.length);
392 if (sb->length + sv.length + 1 <= sb->capacity) {
393 memcpy(sb->data + sb->length, sv.data, sv.length);
394 sb->length += sv.length;
395 sb->data[sb->length] = '\0';
396 }
397}
398
399NONSTD_DEF void sb_append_char(stringb *sb, char c) {
400 sb_ensure(sb, 1);
401 if (sb->length + 2 <= sb->capacity) {
402 sb->data[sb->length++] = c;
403 sb->data[sb->length] = '\0';
404 }
405}
406
407NONSTD_DEF stringv sb_as_sv(const stringb *sb) {
408 return (stringv){.data = sb->data, .length = sb->length};
409}
410
411NONSTD_DEF Arena arena_make(void) {
412 Arena a = {0};
413 array_init(a.blocks);
414 return a;
415}
416
417// Arena Implementation
418
419NONSTD_DEF void arena_grow(Arena *a, size_t min_size) {
420 size_t size = MAX(ARENA_DEFAULT_BLOCK_SIZE, min_size);
421 char *block = ALLOC(char, size);
422 a->ptr = block;
423 a->end = block + size;
424 array_push(a->blocks, block);
425}
426
427NONSTD_DEF void *arena_alloc(Arena *a, size_t size) {
428 // Align to 8 bytes basically
429 size_t align = sizeof(void *);
430 uintptr_t current = (uintptr_t)a->ptr;
431 uintptr_t aligned = (current + align - 1) & ~(align - 1);
432 uintptr_t end = (uintptr_t)a->end;
433
434 // Check for overflow (aligned wrapped around) or out of bounds (aligned >= end)
435 // or not enough space ((end - aligned) < size)
436 if (aligned < current || aligned >= end || (end - aligned) < size) {
437 arena_grow(a, size);
438 current = (uintptr_t)a->ptr;
439 aligned = (current + align - 1) & ~(align - 1);
440 end = (uintptr_t)a->end;
441 }
442
443 // Double check after grow (in case grow failed or size is just too huge)
444 if (aligned < current || aligned >= end || (end - aligned) < size) {
445 return NULL;
446 }
447
448 a->ptr = (char *)(aligned + size);
449 return (void *)aligned;
450}
451
452NONSTD_DEF void arena_free(Arena *a) {
453 char *block;
454 array_foreach(a->blocks, block) { FREE(block); }
455 array_free(a->blocks);
456 a->ptr = NULL;
457 a->end = NULL;
458}
459
460// File I/O Implementation
461
462NONSTD_DEF char *read_entire_file(const char *filepath, size_t *out_size) {
463 FILE *f = fopen(filepath, "rb");
464 if (!f) {
465 return NULL;
466 }
467
468 fseek(f, 0, SEEK_END);
469 long length = ftell(f);
470 fseek(f, 0, SEEK_SET);
471
472 if (length < 0) {
473 fclose(f);
474 return NULL;
475 }
476
477 size_t size = (size_t)length;
478 char *buffer = ALLOC(char, size + 1);
479 if (!buffer) {
480 fclose(f);
481 return NULL;
482 }
483
484 size_t read = fread(buffer, 1, size, f);
485 fclose(f);
486
487 if (read != size) {
488 FREE(buffer);
489 return NULL;
490 }
491
492 buffer[size] = '\0';
493 if (out_size) {
494 *out_size = size;
495 }
496
497 return buffer;
498}
499
500NONSTD_DEF int write_entire_file(const char *filepath, const void *data, size_t size) {
501 FILE *f = fopen(filepath, "wb");
502 if (!f) {
503 return 0;
504 }
505
506 size_t written = fwrite(data, 1, size, f);
507 fclose(f);
508
509 return written == size;
510}
511
512NONSTD_DEF stringb read_entire_file_sb(const char *filepath) {
513 size_t size = 0;
514 char *data = read_entire_file(filepath, &size);
515 stringb sb = {0};
516 if (data) {
517 sb.data = data;
518 sb.length = size;
519 sb.capacity = size + 1;
520 }
521 return sb;
522}
523
524NONSTD_DEF int write_file_sv(const char *filepath, stringv sv) {
525 return write_entire_file(filepath, sv.data, sv.length);
526}
527
528NONSTD_DEF int write_file_sb(const char *filepath, const stringb *sb) {
529 return write_entire_file(filepath, sb->data, sb->length);
530}
531
532#endif // NONSTD_IMPLEMENTATION
533
534#endif // NONSTD_H
535
536/*
537BSD 2-Clause License
538
539Copyright (c) 2026, Mitja Felicijan <mitja.felicijan@gmail.com>
540
541Redistribution and use in source and binary forms, with or without
542modification, are permitted provided that the following conditions are met:
543
5441. Redistributions of source code must retain the above copyright notice, this
545 list of conditions and the following disclaimer.
546
5472. Redistributions in binary form must reproduce the above copyright notice,
548 this list of conditions and the following disclaimer in the documentation
549 and/or other materials provided with the distribution.
550
551THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
552AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
553IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
554DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
555FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
556DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
557SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
558CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
559OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
560OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
561*/