Skip to content

hesham-cant-fly/cmylib

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

58 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

cmylib

Collection of C99 single-header libraries (also compiles clean under C89, C11, C17, C23).

Credits to Ameer for suggesting the name.

To use any header, define its implementation macro before including:

  • Define *_IMPL in exactly one translation unit.
  • Include the header in all units that need the declarations.

**C89 compatibility**: All headers compile under -std=c89 -Wall -Wextra -Werror. Generic macros (powered by C11’s _Generic) are used by default; for pre-C11 compilers, each documentation section lists the suffixed direct-call fallbacks (_cstr, _char, etc.).

All of header files are in headers/ directory

Table of Contents

my_assert.h

Custom assert macro that prints file, line, and the expression on failure, then calls abort().

example

#include "my_assert.h"

int x = 42;
assert(x == 42);     // passes
assert(x == 0);      // prints "file.c:12: Assertion Failed: x == 0" and aborts

my_macro_abuse.h

Me abuse macro. me happy.

API

  • defer_switch(...) - runs some code after a matching case (poors man defer)
  • BLOCK - Allows for early break.
  • forange(n_, start_, ...) - Iterate from start_, incrementing n_ by 1, until n_ < ... is false. C89 variant (forange_c89(n_, start_, end_)) requires pre-declared size_t n_;.
  • leach(T, name, list) – iterate a linked list (walks ->next pointer). C89 variant (leach_c89(T, name, list)) requires pre-declared T *name;.
  • iarreach(index, array) – iterate an array struct by index (old style). C89 variant (iarreach_c89(index, array)) requires pre-declared size_t index;.
  • arreach(type, var, array) – iterate an array struct by value (new pointer-based style, provides a copy of each element). C89 variant (arreach_c89(type, var, array)) requires pre-declared type var; and type *var##_ptr_, *var##_end_;.

Examples

#include <stdio.h>

// defer switch
defer_switch (0) {
        printf( "world\n" );
        break;
    case 0:
        printf( "hello " );
        continue;
    case 1:
        printf( "goodbye " );
        continue;
}
defer_switch ( 1 ) {
        printf( "world\n" );
        break;
    case 0:
        printf( "hello " );
        continue;
    case 1:
        printf( "goodbye " );
        continue;
}

// forange
forange (i, 0, 10) {
	printf("%zu\n", i);
}

// leach — linked list iteration
struct Node { int value; struct Node *next; };
struct Node third  = { 3, NULL };
struct Node second = { 2, &third };
struct Node first  = { 1, &second };

leach(struct Node, n, &first) {
  printf("node: %d\n", n->value);
}
// Output: 1, 2, 3

// iarreach — indexed iteration over an array struct
struct Ints { size_t cap, len; int *items; };
struct Ints arr = { .items = (int[]){10, 20, 30}, .len = 3 };

iarreach(i, arr) {
  printf("arr[%zu] = %d\n", i, arr.items[i]);
}

// arreach — pointer-based value iteration
arreach(int, v, arr) {
  printf("v = %d\n", v);
}

my_commons.h

Utility macros and functions for common patterns.

API

  • unused(x) – suppress unused-variable warnings.
  • panic(msg) – prints file:line: Error: Panic: msg. and exits with code 69.
  • unreachable() – prints file:line: Error: reached an unreachable. and exits with code 1.
  • unimplemented() – prints file:line: Error: this is not implemented yet. and exits.

examples

#define MY_COMMONS_IMPLEMENTATION
#include "my_commons.h"

// panic / unreachable / unimplemented
int demo(void) {
  unimplemented();
}

my_termcolor.h

ANSI escape code macros for terminal color output.

MacroEffect
ANSI_CODE_RESETReset all attributes
ANSI_CODE_BOLDBold / bright foreground
ANSI_CODE_DARKDim / dark foreground
ANSI_CODE_UNDERLINEUnderline text
ANSI_CODE_BLINKBlinking text
ANSI_CODE_REVERSEReverse video (swap fg/bg)
ANSI_CODE_CONCEALEDConcealed / hidden text
ANSI_CODE_GRAYGray foreground
ANSI_CODE_GREYGrey foreground (alias for GRAY)
ANSI_CODE_REDRed foreground
ANSI_CODE_GREENGreen foreground
ANSI_CODE_YELLOWYellow foreground
ANSI_CODE_BLUEBlue foreground
ANSI_CODE_MAGENTAMagenta foreground
ANSI_CODE_CYANCyan foreground
ANSI_CODE_WHITEWhite foreground
ANSI_CODE_BG_GRAYGray background
ANSI_CODE_BG_GREYGrey background (alias for BG_GRAY)
ANSI_CODE_BG_REDRed background
ANSI_CODE_BG_GREENGreen background
ANSI_CODE_BG_YELLOWYellow background
ANSI_CODE_BG_BLUEBlue background
ANSI_CODE_BG_MAGENTAMagenta background
ANSI_CODE_BG_CYANCyan background
ANSI_CODE_BG_WHITEWhite background

example

#include "my_termcolor.h"

printf(ANSI_CODE_RED "Error" ANSI_CODE_RESET ": something broke\n");
printf(ANSI_CODE_GREEN "%-10s" ANSI_CODE_RESET " %d\n", "pass:", 42);

my_string.h

A mutable string view supporting common operations (trim, equality, starts/ends_with).

Define MY_STRING_IMPL before include in exactly one translation unit.

Struct

struct String {
  char   *data;  // null-terminated buffer
  size_t  len;   // length (not counting null)
};

API

Function / MacroDescription
string_from_chars(chs)Create a String referencing existing memory (no alloc).
string_as_cstr(self)Deprecated — use .data directly instead.
string_eq(self, target)Generic equality: cstr or String.
string_starts_with(self, other)Generic starts-with: char, cstr, or String.
string_ends_with(self, other)Generic ends-with: char, cstr, or String.
string_trim(self)Trim whitespace from both ends (in-place).
string_ltrim(self)Trim leading whitespace (in-place).
string_rtrim(self)Trim trailing whitespace (in-place).

The generic macros use C11 _Generic. For pre-C11, call the _cstr, _char, _string suffixed functions directly.

example

#define MY_STRING_IMPL
#include "my_string.h"

int main(void) {
  struct String view = string_from_chars("  hello world  ");
  string_trim(&view);
  assert(string_eq(&view, "hello world"));
  assert(string_starts_with(&view, "hello"));
  assert(string_ends_with(&view, "world"));
  printf("%s\n", view.data); // hello world
}

my_string_view.h

A non-owning, immutable string view (slice over const char *). All operations return new views — the original data is never modified.

Define MY_STRING_VIEW_IMPL before include in exactly one translation unit.

Struct

typedef struct string_view_t {
  const char *data;
  size_t      len;
} string_view_t;

API

Function / MacroDescription
sv_from_chars(chs)Create a view from a null-terminated C string.
sv_from_parts(data, len)Create a view from a data pointer and length.
sv_eq(self, other)Generic equality: const char * or string_view_t.
sv_starts_with(self, other)Generic starts-with: char, const char *, or string_view_t.
sv_ends_with(self, other)Generic ends-with: char, const char *, or string_view_t.
sv_substr(self, start, end)Extract a substring view [start, end) (clamped).
sv_trim(self)Trim leading and trailing whitespace.
sv_ltrim(self)Trim leading whitespace.
sv_rtrim(self)Trim trailing whitespace.

The generic macros use C11 _Generic. For pre-C11, call the _char, _cstr, _view suffixed functions directly.

example

#define MY_STRING_VIEW_IMPL
#include "my_string_view.h"

int main(void) {
  string_view_t v = sv_from_chars("  hello world  ");
  ASSERT(sv_eq_cstr(sv_trim(v), "hello world"));

  string_view_t w = sv_substr(v, 2, 12);
  ASSERT(sv_starts_with(w, "hello"));
  ASSERT(sv_ends_with(w, "world"));
  ASSERT(sv_eq(w, sv_from_parts("hello world", 11)));

  printf("%.*s\n", (int)w.len, w.data); // hello world
}

my_string_builder.h

A dynamic, heap-allocated, null-terminated string builder.

Define MY_STRING_BUILDER_IMPL before include in exactly one translation unit.

Configure GROW_FACTOR before inclusion to override the default (2).

Struct

struct string_builder {
  char   *data;  // null-terminated buffer
  size_t  len;   // length (not counting null)
  size_t  cap;   // allocated capacity (not counting null)
};

API

Function / MacroDescription
sb(allocator, cstr)Convenience macro, same as sb_from_chars_copy(allocator, cstr)
sb_new(allocator, cap)Allocate empty string builder with given capacity.
sb_from_chars_copy(allocator, chs)Allocate a copy of a C string.
sb_format(allocator, fmt, ...)sprintf-style formatting into a new heap-allocated builder.
sb_delete(self)Free memory and zero out the struct.
sb_build(self)Shrink buffer to fit content and return a String (non-owning view).
sb_build_view(self)Shrink buffer to fit content and return a string_view_t.
sb_reserve(self, new_cap)Ensure at least new_cap capacity.
sb_resize(self, new_size)Set length (grows if needed, zero-fills).
sb_push(self, target)Generic push: char, cstr, or another string_builder_t.
sb_pushf(self, fmt, ...)Append formatted text.
sb_set_len(self, len)Set length directly (unsafe — prefer resize).

The generic macros use C11 _Generic. For pre-C11, call the _char, _cstr, _string suffixed functions directly.

example

#define MY_STRING_BUILDER_IMPL
#include "my_string_builder.h"

int main(void) {
  // Create from format
  struct string_builder b = sb_format("Hello %s! Score: %d", "World", 99);
  printf("%s\n", b.data);  // Hello World! Score: 99

  // Push operations
  sb_push(&b, " And more.");
  sb_pushf(&b, " Pi = %.2f", 3.14);
  // b.data = "Hello World! Score: 99 And more. Pi = 3.14"

  sb_delete(&b);
}

my_array.h

This requires my_allocator.h to be in the include path

A dynamic array implemented as macros over a struct with cap, len, items fields.

Uses allocator-aware macros (arrpush, arrfree, etc.) that take an allocator as the first argument. Configure INITIAL_CAP and GROW_FACTOR before including.

API

MacroDescription
arrpush(allocator, array, item)Append an item.
arrfree(allocator, array)Free memory and zero len/cap.
arrgrow(allocator, array)Grow capacity by GROW_FACTOR.
arrinsert(allocator, array, item, pos)Insert item at position (shifts right).
arrpop(array)Decrement length (no memory free).
arrreserve(allocator, array, min_cap)Ensure at least min_cap capacity.
arrsize(array)Total byte size of the allocation.
marrlen(array)Get the current length.
marrcap(array)Get the current capacity.
marrget(array, index)Get the element at index.

example

#include "my_array.h"
#include "my_c_allocator.h"

#define MY_C_ALLOCATOR_IMPL
#include "my_c_allocator.h"

struct Ints {
  size_t cap;
  size_t len;
  int   *items;
};

int main(void) {
  struct Allocator alloc = get_c_allocator();
  struct Ints nums = {0};

  arrpush(alloc, nums, 10);
  arrpush(alloc, nums, 20);
  arrpush(alloc, nums, 30);

  for (size_t i = 0; i < nums.len; i++) {
    printf("nums[%zu] = %d\n", i, nums.items[i]);
  }

  arrfree(alloc, nums);
}

my_thin_array.h

A dynamic array where the metadata (length, capacity) is stored in a header before the pointer. The user interacts with a plain typed pointer.

Define MY_ARRAY_IMPL before include in exactly one TU.

API

Macro / FunctionDescription
thinarrinit(T)Allocate a new thin array of type T.
thinarrfree(arr)Free the array.
thinarrlen(arr)Get length.
thinarrcap(arr)Get capacity.
thinarrpush(arr, val)Append a value.
thinarrpop(arr)Pop and return the last value.
thinarrsetlen(arr, new_len)Set length (unsafe).
thinarrreserve(arr, new_cap)Reserve capacity.
thinarrheader(arr)Get the internal metadata header pointer.

example

#define MY_ARRAY_IMPL
#include "my_thin_array.h"

int main(void) {
  int *xs = thinarrinit(int);

  for (int i = 0; i < 10; i++) {
    thinarrpush(xs, i * i);
  }

  for (size_t i = 0; i < thinarrlen(xs); i++) {
    printf("xs[%zu] = %d\n", i, xs[i]);
  }

  // Use as a normal pointer
  assert(xs[0] == 0);
  assert(xs[9] == 81);

  thinarrfree(xs);
}

my_managed_array.h

This requires my_allocator.h to be in the include path

Like my_array.h but uses the struct Allocator interface instead of bare function macros. The struct must have an .allocator field of type struct Allocator.

API

MacroDescription
marrinit(T, allocator)Initialize an array struct literal.
marrpush(array, item)Append an item.
marrfree(array)Free memory.
marrgrow(array)Grow by GROW_FACTOR.
marrinsert(array, item, pos)Insert at position.
marrpop(array)Pop last item.
marrreserve(array, min_cap)Reserve at least min_cap.
arreach(array, index)Iterate over elements.
arrprint(array, fmt)Print all elements with given format.
arrlen(array) / arrcap(array) / arrget(array, index)Accessors.

example

#include "my_managed_array.h"
#include "my_c_allocator.h"
#define MY_C_ALLOCATOR_IMPL
#include "my_c_allocator.h"

struct Floats {
  struct Allocator allocator;
  size_t cap;
  size_t len;
  float *items;
};

int main(void) {
  struct Floats arr = marrinit(struct Floats, get_c_allocator());

  marrpush(arr, 1.5f);
  marrpush(arr, 2.7f);
  marrpush(arr, 3.14f);

  arreach(arr, i) {
    printf("arr[%zu] = %.2f\n", i, arr.items[i]);
  }

  marrfree(arr);
}

my_error.h

This requires my_commons.h to be in the include path

Rust-style Result(T, E) type macros. Core macros work in C89+; advanced macros (propagate, try, catch) require auto type inference (C23 or GNU extension).

API

Macro / TypeDescription
Result(T, E)Create a Result type with Ok type T and Err type E.
Ok(ResultType, value)Construct an Ok result.
Err(ResultType, value)Construct an Err result.
is_ok(result)Check if a Result is Ok.
is_err(result)Check if a Result is Err.
unwrap(result)Unwrap the Ok value (aborts on Err).
unwrap_err(result)Unwrap the Err value (aborts on Ok).
expect(result, msg)Unwrap Ok, printing msg and aborting on Err.
unwrap_or(result, default)Unwrap Ok or return a default value on Err.
propagate(expr)Return early if Err (requires auto / C23).
try(name, expr)Bind Ok value, return early on Err (auto / C23).
catch(name, expr)Capture Err value, skip Ok (auto / C23).

example

#include "my_error.h"
#include <stdio.h>

typedef Result(int, const char*) IntResult;

static IntResult divide(int a, int b) {
  if (b == 0) return Err(IntResult, "division by zero");
  return Ok(IntResult, a / b);
}

// Functions returning Result can be chained with try.
// Requires C23 for the auto keyword.
static IntResult compute(int a, int b, int c) {
  // try — propagate errors automatically
  try (x_val, divide(a, b)) {   // unwrap Ok or return Err early
    return Ok(IntResult, x_val + c);
  }

  // without this the compiler will falsely warn you
  return (IntResult){0};
}

int main(void) {
  IntResult r = compute(10, 2, 5);
  if (is_ok(r)) {
    printf("compute(10, 2, 5) = %d\n", unwrap(r)); // 10/2 + 5 = 10
  }

  IntResult r2 = compute(10, 0, 5);
  if (is_err(r2)) {
    printf("compute(10, 0, 5) = %s\n", unwrap_err(r2)); // "division by zero"
  }

  // catch — handle the error branch
  IntResult r3 = divide(10, 0);
  catch (err, r3) {
    printf("caught error: %s\n", err);
  }
}

my_flags.h

General-purpose command-line argument flag parsing library. Define flags of type boolean, integer, size_t, or string, then parse argv and let it shuffle non-flag arguments back so you can use them afterwards.

Define MY_FLAGS_IMPL before include in exactly one translation unit. Define MY_FLAG_DEF to static or inline before inclusion for internal linkage.

Types

TypeEnumDescription
FLAG_NONEflag_type_tSentinel / unset
FLAG_SIZE_Tflag_type_tParses value with strtoul, stores in size_t
FLAG_INTflag_type_tParses value with atoi, stores in int
FLAG_STRINGflag_type_tStores pointer to argv string in char *
FLAG_BOOLflag_type_tSets destination int to 1 (no value consumed)
flag_call_tfunction pointervoid (*)(void *dest, const char *value) — custom callback invoked when the flag is matched

API

FunctionDescription
def_flag(dest, type, long_name, short_name, description)Register a flag. long_name (e.g. "output") gives --output, short_name (e.g. "o") gives -o. Either can be NULL, but not both. description is used by print_help_flag.
def_flag_call(callback, dest, type, long_name, short_name, description)Like def_flag but calls callback(dest, value) when the flag is encountered during parsing, instead of using the default type-based handler.
parse_flag(&argc, &argv)Parse argv, setting destinations for recognised flags and compacting remaining (non-flag) arguments to the front of argv; updates argc. Supports --flag=value and --flag:value syntax.
print_help_flag(stream)Print a formatted help listing of all defined flags to stream.

example (def_flag)

#define MY_FLAGS_IMPL
#include "my_flags.h"

#include <stdio.h>

int main(int argc, char **argv) {
  int   boolean = 0;
  int   integer = 0;
  char *string  = NULL;

  def_flag(&boolean, FLAG_BOOL,   "bool",   "b", "Boolean flag");
  def_flag(&integer, FLAG_INT,    "int",    "i", "Integer flag");
  def_flag(&string,  FLAG_STRING, "string", "s", "String flag");

  if (argc < 2) {
    fprintf(stderr, "%s <options>\n", argv[0]);
    print_help_flag(stderr);
    return 0;
  }

  parse_flag(&argc, &argv);

  printf("bool=%d int=%d string=%s\n", boolean, integer, string);

  for (int i = 0; i < argc; i++) {
    printf("non-flag[%d] = %s\n", i, argv[i]);
  }
}

example (def_flag_call)

#define MY_FLAGS_IMPL
#include "my_flags.h"

#include <stdio.h>
#include <stdlib.h>

static void on_verbose(void *dest, const char *value) {
  (void)value;
  int *count = dest;
  *count += 1;
}

int main(int argc, char **argv) {
  int verbose_count = 0;

  // count how many times -v / --verbose is passed
  def_flag_call(on_verbose, &verbose_count, FLAG_BOOL,
                "verbose", "v", "Increase verbosity");

  parse_flag(&argc, &argv);

  printf("verbose level: %d\n", verbose_count);
}

my_hashtable.h

This requires my_allocator.h to be in the include path

A generic open-addressing hash table with Robin Hood hashing and backward-shift deletion.

Items are stored directly in hash slots (no sparse index indirection). Supports keys of any type with runtime hash and equality function pointers.

Define MY_HASHTABLE_IMPL in exactly one TU.

The hash table struct must have .items, .len, .cap fields.

Key configuration (override before including)

MacroDefaultDescription
HM_KEY_OFFSET0 (first member)Byte offset of the key for default hash/eq.
MY_HASHTABLE_CAPACITY16Initial capacity.
HM_LOAD_FACTOR_NUM/DEN7/10Grow when len*DEN >= cap*NUM.

Types

TypeDescription
hash_fn_tsize_t (*)(const void *item)
eq_fn_tint (*)(const void *a, const void *b)

API

MacroDescription
hmcreate(allocator, map)Initialize with default hash/eq (string keys).
hminit(allocator, map, hash_fn, eq_fn)Initialize with custom hash/eq (NULL for default).
hmput(allocator, map, ...)Insert or replace an item (compound literal).
hmget(map, key_item)Pointer to stored item, or NULL.
hmdel(map, key_item)Delete item by key.
hmfree(allocator, map)Free entire hash table and zero the struct.
hmcap(map)Current capacity (number of slots).
hmlen(map)Number of occupied slots.
hmforeach(map, i)Iterate over occupied slots (i is size_t).
hmoccupied(map, i)Check if slot i is occupied.

hmget / hmdel take a pointer to an item with only the key field set (other fields are ignored).

hmfree only frees the hash table’s internal memory — any heap-allocated data within items (e.g. string keys) must be freed separately.

example (string keys)

#define MY_HASHTABLE_IMPL
#include "my_hashtable.h"
#include "my_c_allocator.h"
#define MY_C_ALLOCATOR_IMPL
#include "my_c_allocator.h"

struct Person {
  char *name;
  int   age;
};

struct People {
  struct Person *items;
  size_t         len;
  size_t         cap;
};

int main(void) {
  struct Allocator alloc = get_c_allocator();
  struct People people = {0};

  hmcreate(alloc, people); // optional — hmput auto-creates
  hmput(alloc, people, (&(struct Person){ "Alice", 30 }));
  hmput(alloc, people, (&(struct Person){ "Bob",   25 }));

  struct Person *p = hmget(people, (&(struct Person){ .name = "Bob" }));
  if (p) printf("Bob is %d\n", p->age);

  hmdel(people, (&(struct Person){ .name = "Alice" }));

  size_t i;
  hmforeach(people, i)
    printf("%s: %d\n", people.items[i].name, people.items[i].age);

  hmfree(alloc, people);
}

example (integer keys)

#include "my_hashtable.h"

struct Entry { int key; double val; };
struct Map   { struct Entry *items; size_t len; size_t cap; };

static size_t hash_int(const void *item) {
  int key = *(const int *)item;
  return (size_t)(key * 2654435761U); // knuth multiplicative
}

static int eq_int(const void *a, const void *b) {
  return *(const int *)a == *(const int *)b;
}

int main(void) {
  struct Allocator alloc = get_c_allocator();
  struct Map m = {0};
  hminit(alloc, m, hash_int, eq_int);
  hmput(alloc, m, (&(struct Entry){ 42, 3.14 }));
  struct Entry *e = hmget(m, (&(struct Entry){ .key = 42 }));
  if (e) printf("val = %f\n", e->val);
  hmfree(alloc, m);
}

my_allocator.h

This requires my_assert.h to be in the include path

Abstract allocator interface (vtable pattern). All other allocators conform to this interface.

Types

struct Allocator {
  struct AllocatorInterface *vtable;
  void *data;
};

struct AllocatorInterface {
  void *(*allocate)(void *self, size_t alignment, size_t size);
  void *(*reallocate)(void *self, size_t old_size, void *ptr,
                      size_t alignment, size_t new_size);
  void  (*free)(void *self, size_t size, void *ptr);
};

Macros

MacroDescription
alloc(allocator, size)Allocate with default alignment.
align_alloc(allocator, align, size)Allocate with given alignment.
create(allocator, T, ...)Allocate and initialize a value.
destroy(allocator, ptr)Free (size=0 variant).
recreate(allocator, new_size, ptr)Reallocate with default alignment.
xdestroy(allocator, size, ptr)Free with explicit size.
=xrecreate(allocator, old_size, new_size, ptr)=Reallocate with explicit old size.
GET_ALIGNMENT(T)Get the alignment of a type.
MY_DEFAULT_ALIGNMENTDefault alignment constant.
CONCAT(a, b)Token-paste a and b (expands macros first).
CONCAT_RAW(a, b)Token-paste a and b (literal).

Default allocator convenience macros

MacroEquivalent to
new(T,...)create(default_allocator_, T, ...)
make(size)alloc(default_allocator_, size)
aligned_make(align, size)align_alloc(default_allocator_, align, size)
delete(ptr)destroy(default_allocator_, ptr)
renew(new_size, ptr)recreate(default_allocator_, new_size, ptr)

Functions

FunctionDescription
allocator_new(data, vtable)Construct an Allocator.
set_default_allocator(allocator)Set the global default allocator.
get_default_allocator()Get the global default allocator.
allocator_alloc(allocator, alignment, size)Low-level allocate.
allocator_alloc_with_value(allocator, alignment, size, value)Allocate and copy a value.
allocator_realloc(allocator, old_size, ptr, alignment, new_size)Low-level reallocate.
allocator_free(allocator, size, ptr)Low-level free.
clone_memory(allocator, ptr, len)Heap-allocate a copy of memory.
clone_string(allocator, str)Heap-allocate a copy of a C string.
nclone_string(allocator, str, len)Copy at most len chars.

example

#define MY_ALLOCATOR_IMPL
#include "my_allocator.h"

int main(void) {
  // Uses the default allocator (initially NULL — must be set up)
  // For the default to work, point it at a real allocator first:
  //   set_default_allocator(get_c_allocator());

  int *p = new(int, 42);
  printf("%d\n", *p); // 42
  delete(p);
}

my_c_allocator.h

This requires my_allocator.h to be in the include path

Wraps libc’s malloc=/=realloc=/=free into the Allocator interface.

Define MY_C_ALLOCATOR_IMPL before include.

API

FunctionDescription
get_c_allocator(void)Returns an Allocator backed by C’s malloc=/=free.

example

#define MY_ALLOCATOR_IMPL
#define MY_C_ALLOCATOR_IMPL
#include "my_allocator.h"
#include "my_c_allocator.h"

int main(void) {
  struct Allocator c_alloc = get_c_allocator();
  int *nums = alloc(c_alloc, 5 * sizeof(int));
  nums[0] = 10;
  nums[1] = 20;
  destroy(c_alloc, nums);
}

my_arena_allocator.h

This requires my_allocator.h to be in the include path

Bump/arena allocator that allocates from large blocks. Frees are no-ops; the entire arena is freed at once.

Define MY_ARENA_ALLOCATOR_IMPL before include.

Configure ARENA_REGION_DEFAULT_CAPACITY before include (default 8*1024).

API

Function / MacroDescription
arena_new(child_allocator)Create arena backed by given allocator.
arena_new_default()Create arena backed by the default allocator.
arena_free(self)Free all arena blocks.
arena_get_allocator(arena)Get an Allocator that allocates from the arena.
arena_print(out, arena)Print block usage stats.
ARENA_REGION_DEFAULT_CAPACITYDefault block size (8192, overridable).

example

#define MY_ALLOCATOR_IMPL
#define MY_C_ALLOCATOR_IMPL
#define MY_ARENA_ALLOCATOR_IMPL
#include "my_allocator.h"
#include "my_c_allocator.h"
#include "my_arena_allocator.h"

int main(void) {
  struct Arena arena = arena_new_default();
  struct Allocator a = arena_get_allocator(&arena);

  int *xs = alloc(a, 100 * sizeof(int));
  for (int i = 0; i < 100; i++) xs[i] = i;

  char *msg = alloc(a, 64);
  strcpy(msg, "arena example");

  arena_free(&arena);  // frees everything at once
}

my_temporary_allocator.h

This requires my_allocator.h to be in the include path

A bump allocator using a global fixed-size buffer (default 2048 bytes, or heap-allocated if setup_temporary_allocator is called). Resets are fast — just set used back to 0.

Define MY_TEMPORARY_ALLOCATOR_IMPL before include.

API

Function / MacroDescription
setup_temporary_allocator(size)Replace internal buffer with a larger heap allocation.
free_temporary_allocator()Free the heap buffer, revert to static buffer.
get_temporary_allocator()Get an Allocator into the temporary region.
reset_temporary_allocator()Reset used count to 0 (fast clear).

example

#define MY_ALLOCATOR_IMPL
#define MY_TEMPORARY_ALLOCATOR_IMPL
#include "my_allocator.h"
#include "my_temporary_allocator.h"

int main(void) {
  struct Allocator tmp = get_temporary_allocator();

  int *scratch = alloc(tmp, 128 * sizeof(int));
  // use scratch space...

  reset_temporary_allocator(); // ready for next use
}

my_stream.h

A stream I/O library with custom format specifiers (using {} syntax similar to Rust/Python).

Define MY_STREAM_IMPL in exactly one TU.

Types

typedef struct stream stream_t;

struct stream_interface {
  int (*close)(void *data);
  int (*read)(void *data, unsigned char *out_buf, size_t amount);
  int (*write)(void *data, const unsigned char *in, size_t count);
  int (*seek)(void *data, long int offset, int whence);
  int (*flush)(void *data);
};

typedef struct mem_stream {
  char *buffer;
  size_t len, pos;
} mem_stream_t;

// Format modifier parser state
typedef struct modifier_stream {
  const char *current;
  size_t len;
} modifier_stream_t;

// Parsed format modifier info
typedef struct standard_format_info {
  int has_len, has_base, has_preci, has_width;
  int len, base, preci, width;
  int alternate_form, zero_padded;
} standard_format_info_t;

// Custom format specifier callback
typedef int (*format_fn_t)(stream_t stream, modifier_stream_t mod, va_list args);

// A single named format specifier
typedef struct format_specifier {
  const char *specifier;
  format_fn_t format;
} format_specifier_t;

// Registry of named format specifiers
typedef struct format_specifiers {
  size_t len, cap;
  format_specifier_t *items;
} format_specifiers_t;

Format Syntax

Use {specifier} or {specifier:modifiers}.

Modifier syntax: [flags][len/base][.precision]w[width]

Available specifiers (register with define_format_specifier):

SpecifierType
cchar
sstring (with escape via #)
i, d, intint
li, ldlong int
lli, lldlong long int
uunsigned int
luunsigned long
lluunsigned long long
i8..=i64=, int8..=int64=(u)intN_t
u8..=u64=, unt8..=unt64=unsigned (u)intN_t
z, usize, iz, isizesize_t
f, lf, Lffloat, double, long double
p, ptrvoid* (printed as hex)

Built-in streams: sout, serr, stin (need setup_io_stream()).

API

Function / MacroDescription
setup_io_stream()Init sout, serr, stin and register built-in specifiers.
print(fmt, ...)Print to stdout.
println(fmt, ...)Print to stdout with newline.
eprint(fmt, ...)Print to stderr.
eprintln(fmt, ...)Print to stderr with newline.
sprint(stream, fmt, ...)Print to a stream.
sprintln(stream, fmt, ...)Print to a stream with newline.
snsprint(buf, n, fmt, ...)Print to a fixed-size buffer.
sputc(stream, ch)Write a character.
sputs(stream, s)Write a string.
sopen(path, mode)Open a file as a stream.
smemopen(buf, len)Open a memory buffer as a stream.
sclose(stream)Close a stream.
define_format_specifier(name, fn)Register a custom format specifier.
find_format_specifier(name, out)Look up a format specifier.
vprint(fmt, args)Varargs version of print.
vsprint(stream, fmt, args)Varargs version of sprint.
vsnsprint(buf, n, fmt, args)Varargs version of snsprint.
sread(stream, out, size, n)Read from a stream.
swrite(stream, in, size, n)Write to a stream.
sseek(stream, offset, whence)Seek on a stream.
sflush(stream)Flush a stream.
snputs(stream, len, s)Write a string of known length.
parse_format_info(mod, args)Parse a modifier string into standard_format_info_t.
has_modifier(mod)Check if a modifier stream has remaining characters.
peek_modifier(mod)Peek at the next modifier character without consuming.
advance_modifier(mod)Consume and return the next modifier character.
check_modifier(mod, ch)Check if the next modifier char equals ch (no consume).
match_modifier(mod, ch)If the next modifier char equals ch, consume it and return 1.

example

#define MY_STREAM_IMPL
#include "my_stream.h"

int main(void) {
  setup_io_stream();

  print("Hello, {s}!\n", "World");
  println("Value = {i:#02w16}", 42);   // binary, zero-padded width 16
  println("Pointer = {p}", &main);

  // Custom specifier
  define_format_specifier("sq", format_int); // "sq" now works like "int"

  // Memory stream
  char buf[64];
  stream_t mem = smemopen(buf, sizeof(buf));
  sprint(mem, "written to memory: {sq}", 123);
  sclose(mem);
  printf("%s\n", buf); // "written to memory: 123"
}

utf8.h

UTF-8 validation, decoding, and iteration.

Define UTF8_IMPLEMNTATION (note the typo is in the original) before include.

API

Function / MacroDescription
utf8_iter_init(iter, str)Initialize an iterator. Returns false if invalid UTF-8.
utf8_next(iter)Advance to next codepoint. Returns byte length consumed, or -1 on error.
utf8_prev(iter)Get previous codepoint.
utf8_peek(iter)Get current codepoint without advancing.
decode_utf8(str, out)Decode one codepoint, return bytes consumed.
is_valid_utf8_cstr(str)Check if a C string is valid UTF-8.
get_utf8_char_length(first_byte)Return expected byte length of the UTF-8 character.

example

#define UTF8_IMPLEMNTATION
#include "utf8.h"

int main(void) {
  const char *text = "Hello, 世界! 🌍";

  // Validation
  if (!is_valid_utf8_cstr((const unsigned char *)text)) {
    fprintf(stderr, "Invalid UTF-8\n");
    return 1;
  }

  // Iteration
  utf8_iter_t iter;
  utf8_iter_init(&iter, text);

  while (utf8_peek(iter) != 0) {
    uint32_t cp = utf8_peek(iter);
    printf("U+%04X ", cp);
    utf8_next(&iter);
  }
  printf("\n");
  // Output: U+0048 U+0065 U+006C ... U+4E16 U+754C U+0020 U+1F30D
}

About

collection of stb style header files library to make writing code in C easier

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages