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*/