summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMitja Felicijan <mitja.felicijan@gmail.com>2026-01-21 17:53:40 +0100
committerMitja Felicijan <mitja.felicijan@gmail.com>2026-01-21 17:53:40 +0100
commit6cb22fd7f2c20be2cf268d6bcd236252d7847763 (patch)
tree10891dcea861e5b3a9d8af3114393e650cdaae1d
downloadnonstd-6cb22fd7f2c20be2cf268d6bcd236252d7847763.tar.gz
Engage!
-rw-r--r--.clang-format4
-rw-r--r--.gitignore1
-rw-r--r--LICENSE24
-rw-r--r--Makefile19
-rw-r--r--README.md163
-rw-r--r--examples/.gitignore8
-rw-r--r--examples/Makefile56
-rw-r--r--examples/arena.c59
-rw-r--r--examples/array.c158
-rw-r--r--examples/files.c52
-rw-r--r--examples/foreach.c68
-rw-r--r--examples/slice.c79
-rw-r--r--examples/stringb.c130
-rw-r--r--examples/stringv.c97
-rw-r--r--examples/test_basic.txt1
-rw-r--r--examples/test_sb.txt1
-rw-r--r--examples/test_sv.txt1
-rw-r--r--minunit.h391
-rw-r--r--nonstd.h445
-rw-r--r--tests.c984
20 files changed, 2741 insertions, 0 deletions
diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..de3693a
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,4 @@
+UseTab: Always
+IndentWidth: 4
+TabWidth: 4
+ColumnLimit: 0
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..2b29f27
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+tests
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..31d811a
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,24 @@
+BSD 2-Clause License
+
+Copyright (c) 2026, Mitja Felicijan <mitja.felicijan@gmail.com>
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..f18cd35
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,19 @@
+CC = clang
+CFLAGS = -Wall -Wextra -std=c99 -fsanitize=address -g -O0
+TARGET = tests
+
+all: $(TARGET)
+
+$(TARGET): tests.c nonstd.h minunit.h
+ $(CC) $(CFLAGS) -o $(TARGET) tests.c
+
+test: $(TARGET)
+ ./$(TARGET)
+
+clean:
+ rm -f $(TARGET)
+
+format:
+ clang-format -i nonstd.h tests.c examples/*.c
+
+.PHONY: all test clean format
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..100d9eb
--- /dev/null
+++ b/README.md
@@ -0,0 +1,163 @@
+**nonstd** is a single-header C library providing a collection of "non-standard" but highly useful utilities, data structures, and type definitions that are often missing from the C standard library. It aims to make C programming more ergonomic and productive without the overhead of large frameworks.
+
+## Features
+
+- **Shorthand Types**: Concise integer types (`i8`, `u32`, `usize`, etc.) for better readability.
+- **Utility Macros**: Common helpers like `countof`, `MIN`, `MAX`, `CLAMP`, and `static_foreach`.
+- **String View (`stringv`)**: Efficient, non-owning, read-only string references to avoid unnecessary copies.
+- **String Builder (`stringb`)**: Growable, mutable string buffer for efficient string construction.
+- **Dynamic Array (`array`)**: Type-safe, generic growable arrays implemented via macros (similar to `std::vector` in C++).
+- **Slices (`slice`)**: Generic non-owning views into arrays.
+- **Memory Arena**: Simple and efficient block-based arena allocator for bulk memory management.
+- **File I/O**: Helper functions to read and write entire files with a single call.
+
+## Installation
+
+`nonstd` is a single-header library. To use it:
+
+1. Copy `nonstd.h` into your project's include directory.
+2. In **one** C source file, define `NONSTD_IMPLEMENTATION` before including the header to create the implementation:
+
+```c
+#define NONSTD_IMPLEMENTATION
+#include "nonstd.h"
+```
+
+3. In other files, just include it normally:
+
+```c
+#include "nonstd.h"
+```
+
+## Usage
+
+### 1. Basic Types & Macros
+
+```c
+#include "nonstd.h"
+
+void example() {
+ i32 x = 10;
+ u64 y = 2000;
+ usize size = 1024;
+
+ int numbers[] = {1, 2, 3, 4, 5};
+ printf("Count: %zu\n", countof(numbers)); // 5
+
+ int val = CLAMP(100, 0, 10); // 10
+
+ // Static foreach loop
+ int n;
+ static_foreach(int, n, numbers) {
+ printf("%d ", n);
+ }
+}
+```
+
+### 2. Dynamic Arrays
+
+Create type-safe growable arrays for any type.
+
+```c
+// Define an array of integers
+array(int) numbers;
+array_init(numbers);
+
+// Push values
+array_push(numbers, 10);
+array_push(numbers, 20);
+array_push(numbers, 30);
+
+// Iterate (index basic loop or helper macro)
+int val;
+array_foreach(numbers, val) {
+ printf("%d\n", val);
+}
+
+// Access by index
+if (numbers.length > 0) {
+ printf("First: %d\n", numbers.data[0]);
+ // or
+ printf("First: %d\n", array_get(numbers, 0));
+}
+
+// Clean up
+array_free(numbers);
+```
+
+### 3. String Views & Builders
+
+**String View (`stringv`)**:
+Ideal for parsing and passing strings around without allocation.
+
+```c
+const char* raw = "Hello World";
+stringv sv = sv_from_cstr(raw);
+stringv word = sv_slice(sv, 0, 5); // "Hello" (no allocation)
+
+if (sv_starts_with(sv, sv_from_cstr("Hello"))) {
+ // ...
+}
+```
+
+**String Builder (`stringb`)**:
+Efficiently construct strings.
+
+```c
+stringb sb;
+sb_init(&sb, 0); // 0 = default capacity
+
+sb_append_cstr(&sb, "Hello");
+sb_append_char(&sb, ' ');
+sb_append_cstr(&sb, "World");
+
+printf("%s\n", sb.data); // "Hello World"
+
+sb_free(&sb);
+```
+
+### 4. Memory Arena
+
+Efficiently allocate many small objects and free them all at once.
+
+```c
+Arena arena = arena_make();
+
+// Allocations are fast and contiguous within blocks
+void* obj1 = arena_alloc(&arena, 64);
+void* obj2 = arena_alloc(&arena, 128);
+
+// growth is automatic if a block is full
+
+// Free everything at once
+arena_free(&arena);
+```
+
+### 5. File I/O Helpers
+
+Read or write files with a single functional call.
+
+```c
+size_t size;
+char* content = read_entire_file("data.txt", &size);
+
+if (content) {
+ printf("Read %zu bytes:\n%s\n", size, content);
+ FREE(content); // Standard free (unless using arena)
+}
+
+// Or read directly into a string view or builder
+stringv file_sv = read_entire_file_sv("config.ini");
+// ... use file_sv ...
+FREE((void*)file_sv.data); // Note: read_entire_file_sv allocates the data
+```
+
+## Testing
+
+The project includes a comprehensive test suite using `minunit`.
+
+To build and run the tests:
+
+```bash
+make test
+``` \ No newline at end of file
diff --git a/examples/.gitignore b/examples/.gitignore
new file mode 100644
index 0000000..0707ee1
--- /dev/null
+++ b/examples/.gitignore
@@ -0,0 +1,8 @@
+stringv
+stringb
+foreach
+array
+slice
+arena
+files
+
diff --git a/examples/Makefile b/examples/Makefile
new file mode 100644
index 0000000..a614627
--- /dev/null
+++ b/examples/Makefile
@@ -0,0 +1,56 @@
+# Makefile for nonstd.h examples
+
+CC = clang
+CFLAGS = -Wall -Wextra -std=c99 -fsanitize=address -g -O0
+LDFLAGS =
+
+# Example targets
+EXAMPLES = foreach stringv stringb array slice arena files
+
+# Default target
+all: $(EXAMPLES)
+
+# Build individual examples
+foreach: foreach.c
+ $(CC) $(CFLAGS) -o $@ $< $(LDFLAGS)
+
+stringv: stringv.c
+ $(CC) $(CFLAGS) -o $@ $< $(LDFLAGS)
+
+stringb: stringb.c
+ $(CC) $(CFLAGS) -o $@ $< $(LDFLAGS)
+
+array: array.c
+ $(CC) $(CFLAGS) -o $@ $< $(LDFLAGS)
+
+slice: slice.c
+ $(CC) $(CFLAGS) -o $@ $< $(LDFLAGS)
+
+arena: arena.c
+ $(CC) $(CFLAGS) -o $@ $< $(LDFLAGS)
+
+files: files.c
+ $(CC) $(CFLAGS) -o $@ $< $(LDFLAGS)
+
+# Run all examples
+run: all
+ @echo "\n=== Running stringv ===\n"
+ @./stringv
+ @echo "\n=== Running stringb ===\n"
+ @./stringb
+ @echo "\n=== Running foreach ===\n"
+ @./foreach
+ @echo "\n=== Running array ===\n"
+ @./array
+ @echo "\n=== Running slice ===\n"
+ @./slice
+ @echo "\n=== Running arena ===\n"
+ @./arena
+ @echo "\n=== Running files ===\n"
+ @./files
+
+# Clean build artifacts
+clean:
+ rm -f $(EXAMPLES)
+
+.PHONY: all run clean
diff --git a/examples/arena.c b/examples/arena.c
new file mode 100644
index 0000000..32f9d77
--- /dev/null
+++ b/examples/arena.c
@@ -0,0 +1,59 @@
+#define NONSTD_IMPLEMENTATION
+#include "../nonstd.h"
+
+#include <stdio.h>
+
+typedef struct {
+ int id;
+ char *name;
+} User;
+
+int main(void) {
+ // Initialize the arena
+ Arena a = arena_make();
+
+ // Allocate some structs
+ User *u1 = arena_alloc(&a, sizeof(User));
+ u1->id = 1;
+ // Use string builder for name, but we can't easily alloc string builder
+ // internal buffer on arena yet unless we make a custom allocator for it.
+ // For now, let's just use arena_alloc for a string.
+ char *name1 = arena_alloc(&a, 10);
+ strcpy(name1, "Alice");
+ u1->name = name1;
+
+ User *u2 = arena_alloc(&a, sizeof(User));
+ u2->id = 2;
+ char *name2 = arena_alloc(&a, 10);
+ strcpy(name2, "Bob");
+ u2->name = name2;
+
+ printf("User 1: %d, %s\n", u1->id, u1->name);
+ printf("User 2: %d, %s\n", u2->id, u2->name);
+
+ // Creating a linked list using arena
+ struct Node {
+ int val;
+ struct Node *next;
+ };
+
+ struct Node *head = NULL;
+ for (int i = 0; i < 5; i++) {
+ struct Node *node = arena_alloc(&a, sizeof(struct Node));
+ node->val = i;
+ node->next = head;
+ head = node;
+ }
+
+ printf("List: ");
+ for (struct Node *n = head; n; n = n->next) {
+ printf("%d ", n->val);
+ }
+ printf("\n");
+
+ // Free everything at once
+ arena_free(&a);
+ printf("Arena freed.\n");
+
+ return 0;
+}
diff --git a/examples/array.c b/examples/array.c
new file mode 100644
index 0000000..4275fd7
--- /dev/null
+++ b/examples/array.c
@@ -0,0 +1,158 @@
+#define NONSTD_IMPLEMENTATION
+#include "../nonstd.h"
+
+#include <stdio.h>
+
+// Example struct to demonstrate arrays of complex types
+typedef struct {
+ int id;
+ const char *name;
+} Person;
+
+int main(void) {
+ // Example 1: Basic integer array
+ printf("Example 1: Basic integer array\n");
+ array(int) numbers;
+ array_init(numbers);
+
+ // Push some numbers
+ for (int i = 1; i <= 5; i++) {
+ array_push(numbers, i * 10);
+ }
+
+ printf(" Array length: %zu\n", numbers.length);
+ printf(" Array capacity: %zu\n", numbers.capacity);
+ printf(" Contents: ");
+ for (size_t i = 0; i < numbers.length; i++) {
+ printf("%d ", numbers.data[i]);
+ }
+ printf("\n\n");
+
+ // Example 2: Pop elements
+ printf("Example 2: Pop elements\n");
+ int last = array_pop(numbers);
+ printf(" Popped: %d\n", last);
+ printf(" After pop: ");
+ for (size_t i = 0; i < numbers.length; i++) {
+ printf("%d ", numbers.data[i]);
+ }
+ printf("\n\n");
+
+ // Example 3: Insert and remove
+ printf("Example 3: Insert and remove\n");
+ array_insert(numbers, 2, 999); // Insert 999 at index 2
+ printf(" After insert at index 2: ");
+ for (size_t i = 0; i < numbers.length; i++) {
+ printf("%d ", numbers.data[i]);
+ }
+ printf("\n");
+
+ array_remove(numbers, 1); // Remove element at index 1
+ printf(" After remove index 1: ");
+ for (size_t i = 0; i < numbers.length; i++) {
+ printf("%d ", numbers.data[i]);
+ }
+ printf("\n\n");
+
+ // Example 4: Get and set
+ printf("Example 4: Get and set\n");
+ int value = array_get(numbers, 0);
+ printf(" Value at index 0: %d\n", value);
+ array_set(numbers, 0, 777);
+ printf(" After setting index 0 to 777: ");
+ for (size_t i = 0; i < numbers.length; i++) {
+ printf("%d ", numbers.data[i]);
+ }
+ printf("\n\n");
+
+ array_free(numbers);
+
+ // Example 5: Array with initial capacity
+ printf("Example 5: Array with initial capacity\n");
+ array(int) preallocated;
+ array_init_cap(preallocated, 100);
+ printf(" Initial capacity: %zu\n", preallocated.capacity);
+ printf(" Initial length: %zu\n", preallocated.length);
+ array_free(preallocated);
+ printf("\n");
+
+ // Example 6: foreach iteration
+ printf("Example 6: foreach iteration\n");
+ array(int) values;
+ array_init(values);
+ for (int i = 0; i < 10; i++) {
+ array_push(values, i * i); // Push squares
+ }
+
+ printf(" Using array_foreach: ");
+ int val;
+ array_foreach(values, val) { printf("%d ", val); }
+ printf("\n");
+
+ printf(" Using array_foreach_i: ");
+ array_foreach_i(values, val, idx) { printf("[%zu]=%d ", idx, val); }
+ printf("\n\n");
+
+ array_free(values);
+
+ // Example 7: Array of strings
+ printf("Example 7: Array of strings\n");
+ array(const char *) words;
+ array_init(words);
+
+ array_push(words, "Hello");
+ array_push(words, "World");
+ array_push(words, "from");
+ array_push(words, "C");
+
+ printf(" Words: ");
+ const char *word;
+ array_foreach(words, word) { printf("%s ", word); }
+ printf("\n\n");
+
+ array_free(words);
+
+ // Example 8: Array of structs
+ printf("Example 8: Array of structs\n");
+ array(Person) people;
+ array_init(people);
+
+ array_push(people, ((Person){1, "Alice"}));
+ array_push(people, ((Person){2, "Bob"}));
+ array_push(people, ((Person){3, "Charlie"}));
+
+ printf(" People:\n");
+ Person person;
+ array_foreach(people, person) { printf(" ID: %d, Name: %s\n", person.id, person.name); }
+ printf("\n");
+
+ array_free(people);
+
+ // Example 9: Reserve capacity
+ printf("Example 9: Reserve capacity\n");
+ array(double) measurements;
+ array_init(measurements);
+ printf(" Initial capacity: %zu\n", measurements.capacity);
+
+ array_reserve(measurements, 1000);
+ printf(" After reserve(1000): %zu\n", measurements.capacity);
+
+ array_free(measurements);
+ printf("\n");
+
+ // Example 10: Clear array
+ printf("Example 10: Clear array\n");
+ array(int) temp;
+ array_init(temp);
+ for (int i = 0; i < 5; i++) {
+ array_push(temp, i);
+ }
+ printf(" Length before clear: %zu\n", temp.length);
+ array_clear(temp);
+ printf(" Length after clear: %zu\n", temp.length);
+ printf(" Capacity after clear: %zu\n", temp.capacity);
+
+ array_free(temp);
+
+ return 0;
+}
diff --git a/examples/files.c b/examples/files.c
new file mode 100644
index 0000000..663bbf9
--- /dev/null
+++ b/examples/files.c
@@ -0,0 +1,52 @@
+#define NONSTD_IMPLEMENTATION
+#include "../nonstd.h"
+
+#include <stdio.h>
+
+int main(void) {
+ // 1. Basic usage
+ const char *msg = "Hello, World!\n";
+ if (write_entire_file("test_basic.txt", msg, strlen(msg))) {
+ printf("Written test_basic.txt\n");
+ }
+
+ size_t sz;
+ char *content = read_entire_file("test_basic.txt", &sz);
+ if (content) {
+ printf("Read: %.*s", (int)sz, content);
+ FREE(content);
+ }
+
+ // 2. usage with string view
+ stringv sv = sv_from_cstr("Hello from String View!\n");
+ if (write_file_sv("test_sv.txt", sv)) {
+ printf("Written test_sv.txt\n");
+ }
+
+ stringv sv_read = read_entire_file_sv("test_sv.txt");
+ if (sv_read.data) {
+ printf("Read sv: %.*s", (int)sv_read.length, sv_read.data);
+ free((char *)sv_read.data);
+ }
+
+ // 3. usage with string builder
+ stringb sb;
+ sb_init(&sb, 0);
+ sb_append_cstr(&sb, "Hello from ");
+ sb_append_cstr(&sb, "String Builder!\n");
+
+ if (write_file_sb("test_sb.txt", &sb)) {
+ printf("Written test_sb.txt\n");
+ }
+
+ // Read into stringb
+ stringb sb2 = read_entire_file_sb("test_sb.txt");
+ if (sb2.data) {
+ printf("Read into sb: %s", sb2.data);
+ sb_free(&sb2);
+ }
+
+ sb_free(&sb);
+
+ return 0;
+}
diff --git a/examples/foreach.c b/examples/foreach.c
new file mode 100644
index 0000000..628c993
--- /dev/null
+++ b/examples/foreach.c
@@ -0,0 +1,68 @@
+#define NONSTD_IMPLEMENTATION
+#include "../nonstd.h"
+
+#include <stdio.h>
+
+int main(void) {
+ // Example 1: Iterate over an array of integers
+ int numbers[] = {10, 20, 30, 40, 50};
+ int num; // Declare the loop variable
+
+ static_foreach(int, num, numbers) { printf(" Number: %d\n", num); }
+ printf("\n");
+
+ // Example 2: Iterate over an array of floats
+ float prices[] = {9.99f, 19.99f, 29.99f, 49.99f};
+ float price; // Declare the loop variable
+
+ static_foreach(float, price, prices) { printf(" Price: $%.2f\n", price); }
+ printf("\n");
+
+ // Example 3: Iterate over an array of strings
+ const char *fruits[] = {"Apple", "Banana", "Cherry", "Date", "Elderberry"};
+ const char *fruit; // Declare the loop variable
+
+ static_foreach(const char *, fruit, fruits) { printf(" Fruit: %s\n", fruit); }
+ printf("\n");
+
+ // Example 4: Perform calculations with static_foreach
+ int values[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
+ int sum = 0;
+ int val; // Declare the loop variable
+
+ static_foreach(int, val, values) { sum += val; }
+ printf(" Sum of values: %d\n", sum);
+ printf("\n");
+
+ // Example 5: Iterate over struct array
+ typedef struct {
+ const char *name;
+ int age;
+ } Person;
+
+ Person people[] = {{"Alice", 25}, {"Bob", 30}, {"Charlie", 35}};
+ Person person; // Declare the loop variable
+
+ static_foreach(Person, person, people) { printf(" %s is %d years old\n", person.name, person.age); }
+ printf("\n");
+
+ // Example 6: Modify array elements in place
+ int nums[] = {1, 2, 3, 4, 5};
+ int n; // Declare the loop variable
+
+ printf(" Before: ");
+ static_foreach(int, n, nums) { printf("%d ", n); }
+ printf("\n");
+
+ // Note: static_foreach creates a copy of each element by default
+ // To modify elements, use a traditional for loop with array indexing
+ for (size_t i = 0; i < countof(nums); i++) {
+ nums[i] *= 2;
+ }
+
+ printf(" After doubling: ");
+ static_foreach(int, n, nums) { printf("%d ", n); }
+ printf("\n");
+
+ return 0;
+}
diff --git a/examples/slice.c b/examples/slice.c
new file mode 100644
index 0000000..a5f4945
--- /dev/null
+++ b/examples/slice.c
@@ -0,0 +1,79 @@
+#define NONSTD_IMPLEMENTATION
+#include "../nonstd.h"
+
+#include <stdio.h>
+
+SLICE_DEF(int);
+
+// Example function that accepts a slice of integers
+void print_int_slice(slice(int) s) {
+ printf(" Slice (len=%zu): [", s.length);
+ for (size_t i = 0; i < s.length; i++) {
+ printf("%d%s", s.data[i], i < s.length - 1 ? ", " : "");
+ }
+ printf("]\n");
+}
+
+int main(void) {
+ // Example 1: Slice from static array
+ printf("Example 1: Slice from static C array\n");
+ int static_nums[] = {10, 20, 30, 40, 50};
+
+ // Create full slice
+ slice(int) s1 = make_slice(int, static_nums, countof(static_nums));
+ print_int_slice(s1);
+
+ // Create partial slice (subset)
+ slice(int) s2 = make_slice(int, static_nums + 1, 3); // 20, 30, 40
+ print_int_slice(s2);
+ printf("\n");
+
+ // Example 2: Slice from dynamic array
+ printf("Example 2: Slice from dynamic array\n");
+ array(int) dyn_arr;
+ array_init(dyn_arr);
+ for (int i = 1; i <= 5; i++)
+ array_push(dyn_arr, i * 100);
+
+ // Convert entire array to slice
+ slice(int) s3 = array_as_slice(int, dyn_arr);
+ print_int_slice(s3);
+
+ // Create slice from dynamic array data manually
+ slice(int) s4 = make_slice(int, dyn_arr.data + 2, 2); // 300, 400
+ print_int_slice(s4);
+
+ array_free(dyn_arr);
+ printf(" (Dynamic array freed)\n\n");
+
+ // Example 3: Modifying data through slice
+ printf("Example 3: Modifying data through slice\n");
+ int values[] = {1, 1, 1, 1, 1};
+ slice(int) s5 = make_slice(int, values, 5);
+
+ print_int_slice(s5);
+
+ // Modify middle elements
+ s5.data[2] = 99;
+ s5.data[3] = 99;
+
+ printf(" After modification:\n");
+ print_int_slice(s5);
+ printf("\n");
+
+ // Example 4: Slice of strings
+ printf("Example 4: Slice of strings\n");
+ typedef const char *cstr;
+ SLICE_DEF(cstr);
+
+ cstr names[] = {"Alice", "Bob", "Charlie"};
+ slice(cstr) name_slice = make_slice(cstr, names, 3);
+
+ printf(" Names: ");
+ for (size_t i = 0; i < name_slice.length; i++) {
+ printf("%s ", name_slice.data[i]);
+ }
+ printf("\n");
+
+ return 0;
+}
diff --git a/examples/stringb.c b/examples/stringb.c
new file mode 100644
index 0000000..443f2ae
--- /dev/null
+++ b/examples/stringb.c
@@ -0,0 +1,130 @@
+#define NONSTD_IMPLEMENTATION
+#include "../nonstd.h"
+
+#include <stdio.h>
+
+// Helper function to print string builder content
+void print_sb(const char *label, const stringb *sb) {
+ printf("%s (len=%zu, cap=%zu): \"%s\"\n", label, sb->length, sb->capacity, sb->data);
+}
+
+int main(void) {
+ // Example 1: Basic string building
+ stringb sb1;
+ sb_init(&sb1, 0); // 0 means use default capacity (16)
+
+ sb_append_cstr(&sb1, "Hello");
+ sb_append_cstr(&sb1, ", ");
+ sb_append_cstr(&sb1, "World");
+ sb_append_char(&sb1, '!');
+
+ print_sb(" sb1", &sb1);
+ sb_free(&sb1);
+ printf("\n");
+
+ // Example 2: Building with initial capacity
+ stringb sb2;
+ sb_init(&sb2, 100); // Start with larger capacity to avoid reallocations
+
+ print_sb(" Initial", &sb2);
+
+ sb_append_cstr(&sb2, "This is a longer string that benefits from pre-allocation");
+ print_sb(" After append", &sb2);
+
+ sb_free(&sb2);
+ printf("\n");
+
+ // Example 3: Appending string views
+ stringb sb3;
+ sb_init(&sb3, 0);
+
+ const char *text = "The quick brown fox";
+ stringv word = sv_from_parts(text + 4, 5); // "quick"
+
+ sb_append_cstr(&sb3, "Extracted: ");
+ sb_append_sv(&sb3, word);
+ sb_append_cstr(&sb3, " (from a view)");
+
+ print_sb(" sb3", &sb3);
+ sb_free(&sb3);
+ printf("\n");
+
+ // Example 4: Converting builder to view for reading
+ stringb sb4;
+ sb_init(&sb4, 0);
+
+ sb_append_cstr(&sb4, "Builder content");
+
+ // Get a read-only view of the builder
+ stringv view = sb_as_sv(&sb4);
+ printf(" Builder as view (len=%zu): \"", view.length);
+ fwrite(view.data, 1, view.length, stdout);
+ printf("\"\n");
+
+ // You can use all string view operations on it
+ stringv prefix = sv_from_cstr("Builder");
+ printf(" Starts with 'Builder': %s\n", sv_starts_with(view, prefix) ? "yes" : "no");
+
+ sb_free(&sb4);
+ printf("\n");
+
+ // Example 5: Building a formatted message
+ stringb sb5;
+ sb_init(&sb5, 0);
+
+ const char *names[] = {"Alice", "Bob", "Charlie"};
+ int ages[] = {25, 30, 35};
+
+ sb_append_cstr(&sb5, "People:\n");
+ for (size_t i = 0; i < countof(names); i++) {
+ sb_append_cstr(&sb5, " - ");
+ sb_append_cstr(&sb5, names[i]);
+ sb_append_cstr(&sb5, " (age ");
+
+ // Note: For numbers, you'd typically use sprintf with a temp buffer
+ char age_buf[32];
+ snprintf(age_buf, sizeof(age_buf), "%d", ages[i]);
+ sb_append_cstr(&sb5, age_buf);
+
+ sb_append_cstr(&sb5, ")\n");
+ }
+
+ printf("%s", sb5.data);
+ sb_free(&sb5);
+ printf("\n");
+
+ // Example 6: Building CSV data
+ stringb csv;
+ sb_init(&csv, 64);
+
+ sb_append_cstr(&csv, "Name,Age,City\n");
+ sb_append_cstr(&csv, "Alice,25,NYC\n");
+ sb_append_cstr(&csv, "Bob,30,LA\n");
+ sb_append_cstr(&csv, "Charlie,35,Chicago\n");
+
+ printf(" CSV output:\n%s", csv.data);
+ sb_free(&csv);
+ printf("\n");
+
+ // Example 7: Practical use case - building a SQL query
+ stringb query;
+ sb_init(&query, 128);
+
+ sb_append_cstr(&query, "SELECT * FROM users WHERE ");
+
+ const char *conditions[] = {"age > 18", "active = 1", "country = 'US'"};
+ for (size_t i = 0; i < countof(conditions); i++) {
+ if (i > 0) {
+ sb_append_cstr(&query, " AND ");
+ }
+ sb_append_cstr(&query, conditions[i]);
+ }
+
+ sb_append_char(&query, ';');
+
+ print_sb(" Query", &query);
+ sb_free(&query);
+ printf("\n");
+
+ return 0;
+}
diff --git a/examples/stringv.c b/examples/stringv.c
new file mode 100644
index 0000000..ff5d376
--- /dev/null
+++ b/examples/stringv.c
@@ -0,0 +1,97 @@
+#define NONSTD_IMPLEMENTATION
+#include "../nonstd.h"
+
+#include <stdio.h>
+
+// Helper function to print a string view
+void print_sv(const char *label, stringv sv) {
+ printf("%s (len=%zu): \"", label, sv.length);
+ fwrite(sv.data, 1, sv.length, stdout);
+ printf("\"\n");
+}
+
+int main(void) {
+ // Example 1: Create string view from C string
+ const char *hello = "Hello, World!";
+ stringv sv1 = sv_from_cstr(hello);
+ print_sv(" sv1", sv1);
+ printf("\n");
+
+ // Example 2: Create view from pointer and length
+ const char *text = "Programming in C";
+ stringv sv2 = sv_from_parts(text, 11); // Only "Programming"
+ print_sv(" sv2", sv2);
+ printf("\n");
+
+ // Example 3: Slicing - extract substrings without copying
+ stringv original = sv_from_cstr("The quick brown fox");
+
+ stringv word1 = sv_slice(original, 0, 3); // "The"
+ stringv word2 = sv_slice(original, 4, 9); // "quick"
+ stringv word3 = sv_slice(original, 10, 15); // "brown"
+
+ print_sv(" Original", original);
+ print_sv(" Word 1", word1);
+ print_sv(" Word 2", word2);
+ print_sv(" Word 3", word3);
+ printf("\n");
+
+ // Example 4: Comparing string views
+ stringv str_a = sv_from_cstr("hello");
+ stringv str_b = sv_from_cstr("hello");
+ stringv str_c = sv_from_cstr("world");
+
+ printf(" str_a == str_b: %s\n", sv_equals(str_a, str_b) ? "true" : "false");
+ printf(" str_a == str_c: %s\n", sv_equals(str_a, str_c) ? "true" : "false");
+ printf("\n");
+
+ // Example 5: Prefix and suffix checking
+ stringv filename = sv_from_cstr("document.txt");
+ stringv prefix = sv_from_cstr("doc");
+ stringv suffix = sv_from_cstr(".txt");
+ stringv wrong_suffix = sv_from_cstr(".pdf");
+
+ print_sv(" Filename", filename);
+ printf(" Starts with 'doc': %s\n", sv_starts_with(filename, prefix) ? "yes" : "no");
+ printf(" Ends with '.txt': %s\n", sv_ends_with(filename, suffix) ? "yes" : "no");
+ printf(" Ends with '.pdf': %s\n", sv_ends_with(filename, wrong_suffix) ? "yes" : "no");
+ printf("\n");
+
+ // Example 6: Parsing a path (practical use case)
+ const char *path = "/home/user/documents/report.pdf";
+ stringv path_sv = sv_from_cstr(path);
+
+ // Find the last '/' to extract filename
+ size_t last_slash = 0;
+ for (size_t i = 0; i < path_sv.length; i++) {
+ if (path_sv.data[i] == '/') {
+ last_slash = i + 1;
+ }
+ }
+
+ stringv directory = sv_slice(path_sv, 0, last_slash);
+ stringv filename2 = sv_slice(path_sv, last_slash, path_sv.length);
+
+ print_sv(" Full path", path_sv);
+ print_sv(" Directory", directory);
+ print_sv(" Filename", filename2);
+ printf("\n");
+
+ // Example 7: Tokenizing without allocation
+ const char *sentence = "one two three four";
+ stringv sent_sv = sv_from_cstr(sentence);
+
+ size_t start = 0;
+ for (size_t i = 0; i <= sent_sv.length; i++) {
+ if (i == sent_sv.length || sent_sv.data[i] == ' ') {
+ if (i > start) {
+ stringv token = sv_slice(sent_sv, start, i);
+ print_sv(" Token", token);
+ }
+ start = i + 1;
+ }
+ }
+ printf("\n");
+
+ return 0;
+}
diff --git a/examples/test_basic.txt b/examples/test_basic.txt
new file mode 100644
index 0000000..8ab686e
--- /dev/null
+++ b/examples/test_basic.txt
@@ -0,0 +1 @@
+Hello, World!
diff --git a/examples/test_sb.txt b/examples/test_sb.txt
new file mode 100644
index 0000000..4ed02a4
--- /dev/null
+++ b/examples/test_sb.txt
@@ -0,0 +1 @@
+Hello from String Builder!
diff --git a/examples/test_sv.txt b/examples/test_sv.txt
new file mode 100644
index 0000000..f4b8e86
--- /dev/null
+++ b/examples/test_sv.txt
@@ -0,0 +1 @@
+Hello from String View!
diff --git a/minunit.h b/minunit.h
new file mode 100644
index 0000000..5e89245
--- /dev/null
+++ b/minunit.h
@@ -0,0 +1,391 @@
+/*
+ * Copyright (c) 2012 David SiƱuela Pastor, siu.4coders@gmail.com
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef MINUNIT_MINUNIT_H
+#define MINUNIT_MINUNIT_H
+
+#ifdef __cplusplus
+ extern "C" {
+#endif
+
+#if defined(_WIN32)
+#include <Windows.h>
+#if defined(_MSC_VER) && _MSC_VER < 1900
+ #define snprintf _snprintf
+ #define __func__ __FUNCTION__
+#endif
+
+#elif defined(__unix__) || defined(__unix) || defined(unix) || (defined(__APPLE__) && defined(__MACH__))
+
+/* Change POSIX C SOURCE version for pure c99 compilers */
+#if !defined(_POSIX_C_SOURCE) || _POSIX_C_SOURCE < 200112L
+#undef _POSIX_C_SOURCE
+#define _POSIX_C_SOURCE 200112L
+#endif
+
+#include <unistd.h> /* POSIX flags */
+#include <time.h> /* clock_gettime(), time() */
+#include <sys/time.h> /* gethrtime(), gettimeofday() */
+#include <sys/resource.h>
+#include <sys/times.h>
+#include <string.h>
+
+#if defined(__MACH__) && defined(__APPLE__)
+#include <mach/mach.h>
+#include <mach/mach_time.h>
+#endif
+
+#if __GNUC__ >= 5 && !defined(__STDC_VERSION__)
+#define __func__ __extension__ __FUNCTION__
+#endif
+
+#else
+#error "Unable to define timers for an unknown OS."
+#endif
+
+#include <stdio.h>
+#include <math.h>
+
+/* Maximum length of last message */
+#define MINUNIT_MESSAGE_LEN 1024
+/* Accuracy with which floats are compared */
+#define MINUNIT_EPSILON 1E-12
+
+/* Misc. counters */
+static int minunit_run = 0;
+static int minunit_assert = 0;
+static int minunit_fail = 0;
+static int minunit_status = 0;
+
+/* Timers */
+static double minunit_real_timer = 0;
+static double minunit_proc_timer = 0;
+
+/* Last message */
+static char minunit_last_message[MINUNIT_MESSAGE_LEN];
+
+/* Test setup and teardown function pointers */
+static void (*minunit_setup)(void) = NULL;
+static void (*minunit_teardown)(void) = NULL;
+
+/* Definitions */
+#define MU_TEST(method_name) static void method_name(void)
+#define MU_TEST_SUITE(suite_name) static void suite_name(void)
+
+#define MU__SAFE_BLOCK(block) do {\
+ block\
+} while(0)
+
+/* Run test suite and unset setup and teardown functions */
+#define MU_RUN_SUITE(suite_name) MU__SAFE_BLOCK(\
+ suite_name();\
+ minunit_setup = NULL;\
+ minunit_teardown = NULL;\
+)
+
+/* Configure setup and teardown functions */
+#define MU_SUITE_CONFIGURE(setup_fun, teardown_fun) MU__SAFE_BLOCK(\
+ minunit_setup = setup_fun;\
+ minunit_teardown = teardown_fun;\
+)
+
+/* Test runner */
+#define MU_RUN_TEST(test) MU__SAFE_BLOCK(\
+ if (minunit_real_timer==0 && minunit_proc_timer==0) {\
+ minunit_real_timer = mu_timer_real();\
+ minunit_proc_timer = mu_timer_cpu();\
+ }\
+ if (minunit_setup) (*minunit_setup)();\
+ minunit_status = 0;\
+ test();\
+ minunit_run++;\
+ if (minunit_status) {\
+ minunit_fail++;\
+ printf("F");\
+ printf("\n%s\n", minunit_last_message);\
+ }\
+ (void)fflush(stdout);\
+ if (minunit_teardown) (*minunit_teardown)();\
+)
+
+/* Report */
+#define MU_REPORT() MU__SAFE_BLOCK(\
+ double minunit_end_real_timer;\
+ double minunit_end_proc_timer;\
+ printf("\n\n%d tests, %d assertions, %d failures\n", minunit_run, minunit_assert, minunit_fail);\
+ minunit_end_real_timer = mu_timer_real();\
+ minunit_end_proc_timer = mu_timer_cpu();\
+ printf("\nFinished in %.8f seconds (real) %.8f seconds (proc)\n\n",\
+ minunit_end_real_timer - minunit_real_timer,\
+ minunit_end_proc_timer - minunit_proc_timer);\
+)
+#define MU_EXIT_CODE minunit_fail
+
+/* Assertions */
+#define mu_check(test) MU__SAFE_BLOCK(\
+ minunit_assert++;\
+ if (!(test)) {\
+ (void)snprintf(minunit_last_message, MINUNIT_MESSAGE_LEN, "%s failed:\n\t%s:%d: %s", __func__, __FILE__, __LINE__, #test);\
+ minunit_status = 1;\
+ return;\
+ } else {\
+ printf(".");\
+ }\
+)
+
+#define mu_fail(message) MU__SAFE_BLOCK(\
+ minunit_assert++;\
+ (void)snprintf(minunit_last_message, MINUNIT_MESSAGE_LEN, "%s failed:\n\t%s:%d: %s", __func__, __FILE__, __LINE__, message);\
+ minunit_status = 1;\
+ return;\
+)
+
+#define mu_assert(test, message) MU__SAFE_BLOCK(\
+ minunit_assert++;\
+ if (!(test)) {\
+ (void)snprintf(minunit_last_message, MINUNIT_MESSAGE_LEN, "%s failed:\n\t%s:%d: %s", __func__, __FILE__, __LINE__, message);\
+ minunit_status = 1;\
+ return;\
+ } else {\
+ printf(".");\
+ }\
+)
+
+#define mu_assert_int_eq(expected, result) MU__SAFE_BLOCK(\
+ int minunit_tmp_e;\
+ int minunit_tmp_r;\
+ minunit_assert++;\
+ minunit_tmp_e = (expected);\
+ minunit_tmp_r = (result);\
+ if (minunit_tmp_e != minunit_tmp_r) {\
+ (void)snprintf(minunit_last_message, MINUNIT_MESSAGE_LEN, "%s failed:\n\t%s:%d: %d expected but was %d", __func__, __FILE__, __LINE__, minunit_tmp_e, minunit_tmp_r);\
+ minunit_status = 1;\
+ return;\
+ } else {\
+ printf(".");\
+ }\
+)
+
+#define mu_assert_double_eq(expected, result) MU__SAFE_BLOCK(\
+ double minunit_tmp_e;\
+ double minunit_tmp_r;\
+ minunit_assert++;\
+ minunit_tmp_e = (expected);\
+ minunit_tmp_r = (result);\
+ if (fabs(minunit_tmp_e-minunit_tmp_r) > MINUNIT_EPSILON) {\
+ int minunit_significant_figures = 1 - log10(MINUNIT_EPSILON);\
+ (void)snprintf(minunit_last_message, MINUNIT_MESSAGE_LEN, "%s failed:\n\t%s:%d: %.*g expected but was %.*g", __func__, __FILE__, __LINE__, minunit_significant_figures, minunit_tmp_e, minunit_significant_figures, minunit_tmp_r);\
+ minunit_status = 1;\
+ return;\
+ } else {\
+ printf(".");\
+ }\
+)
+
+#define mu_assert_string_eq(expected, result) MU__SAFE_BLOCK(\
+ const char* minunit_tmp_e = expected;\
+ const char* minunit_tmp_r = result;\
+ minunit_assert++;\
+ if (!minunit_tmp_e) {\
+ minunit_tmp_e = "<null pointer>";\
+ }\
+ if (!minunit_tmp_r) {\
+ minunit_tmp_r = "<null pointer>";\
+ }\
+ if(strcmp(minunit_tmp_e, minunit_tmp_r) != 0) {\
+ (void)snprintf(minunit_last_message, MINUNIT_MESSAGE_LEN, "%s failed:\n\t%s:%d: '%s' expected but was '%s'", __func__, __FILE__, __LINE__, minunit_tmp_e, minunit_tmp_r);\
+ minunit_status = 1;\
+ return;\
+ } else {\
+ printf(".");\
+ }\
+)
+
+/*
+ * The following two functions were written by David Robert Nadeau
+ * from http://NadeauSoftware.com/ and distributed under the
+ * Creative Commons Attribution 3.0 Unported License
+ */
+
+/**
+ * Returns the real time, in seconds, or -1.0 if an error occurred.
+ *
+ * Time is measured since an arbitrary and OS-dependent start time.
+ * The returned real time is only useful for computing an elapsed time
+ * between two calls to this function.
+ */
+static double mu_timer_real(void)
+{
+#if defined(_WIN32)
+ /* Windows 2000 and later. ---------------------------------- */
+ LARGE_INTEGER Time;
+ LARGE_INTEGER Frequency;
+
+ QueryPerformanceFrequency(&Frequency);
+ QueryPerformanceCounter(&Time);
+
+ Time.QuadPart *= 1000000;
+ Time.QuadPart /= Frequency.QuadPart;
+
+ return (double)Time.QuadPart / 1000000.0;
+
+#elif (defined(__hpux) || defined(hpux)) || ((defined(__sun__) || defined(__sun) || defined(sun)) && (defined(__SVR4) || defined(__svr4__)))
+ /* HP-UX, Solaris. ------------------------------------------ */
+ return (double)gethrtime( ) / 1000000000.0;
+
+#elif defined(__MACH__) && defined(__APPLE__)
+ /* OSX. ----------------------------------------------------- */
+ static double timeConvert = 0.0;
+ if ( timeConvert == 0.0 )
+ {
+ mach_timebase_info_data_t timeBase;
+ (void)mach_timebase_info( &timeBase );
+ timeConvert = (double)timeBase.numer /
+ (double)timeBase.denom /
+ 1000000000.0;
+ }
+ return (double)mach_absolute_time( ) * timeConvert;
+
+#elif defined(_POSIX_VERSION)
+ /* POSIX. --------------------------------------------------- */
+ struct timeval tm;
+#if defined(_POSIX_TIMERS) && (_POSIX_TIMERS > 0)
+ {
+ struct timespec ts;
+#if defined(CLOCK_MONOTONIC_PRECISE)
+ /* BSD. --------------------------------------------- */
+ const clockid_t id = CLOCK_MONOTONIC_PRECISE;
+#elif defined(CLOCK_MONOTONIC_RAW)
+ /* Linux. ------------------------------------------- */
+ const clockid_t id = CLOCK_MONOTONIC_RAW;
+#elif defined(CLOCK_HIGHRES)
+ /* Solaris. ----------------------------------------- */
+ const clockid_t id = CLOCK_HIGHRES;
+#elif defined(CLOCK_MONOTONIC)
+ /* AIX, BSD, Linux, POSIX, Solaris. ----------------- */
+ const clockid_t id = CLOCK_MONOTONIC;
+#elif defined(CLOCK_REALTIME)
+ /* AIX, BSD, HP-UX, Linux, POSIX. ------------------- */
+ const clockid_t id = CLOCK_REALTIME;
+#else
+ const clockid_t id = (clockid_t)-1; /* Unknown. */
+#endif /* CLOCK_* */
+ if ( id != (clockid_t)-1 && clock_gettime( id, &ts ) != -1 )
+ return (double)ts.tv_sec +
+ (double)ts.tv_nsec / 1000000000.0;
+ /* Fall thru. */
+ }
+#endif /* _POSIX_TIMERS */
+
+ /* AIX, BSD, Cygwin, HP-UX, Linux, OSX, POSIX, Solaris. ----- */
+ gettimeofday( &tm, NULL );
+ return (double)tm.tv_sec + (double)tm.tv_usec / 1000000.0;
+#else
+ return -1.0; /* Failed. */
+#endif
+}
+
+/**
+ * Returns the amount of CPU time used by the current process,
+ * in seconds, or -1.0 if an error occurred.
+ */
+static double mu_timer_cpu(void)
+{
+#if defined(_WIN32)
+ /* Windows -------------------------------------------------- */
+ FILETIME createTime;
+ FILETIME exitTime;
+ FILETIME kernelTime;
+ FILETIME userTime;
+
+ /* This approach has a resolution of 1/64 second. Unfortunately, Windows' API does not offer better */
+ if ( GetProcessTimes( GetCurrentProcess( ),
+ &createTime, &exitTime, &kernelTime, &userTime ) != 0 )
+ {
+ ULARGE_INTEGER userSystemTime;
+ memcpy(&userSystemTime, &userTime, sizeof(ULARGE_INTEGER));
+ return (double)userSystemTime.QuadPart / 10000000.0;
+ }
+
+#elif defined(__unix__) || defined(__unix) || defined(unix) || (defined(__APPLE__) && defined(__MACH__))
+ /* AIX, BSD, Cygwin, HP-UX, Linux, OSX, and Solaris --------- */
+
+#if defined(_POSIX_TIMERS) && (_POSIX_TIMERS > 0)
+ /* Prefer high-res POSIX timers, when available. */
+ {
+ clockid_t id;
+ struct timespec ts;
+#if _POSIX_CPUTIME > 0
+ /* Clock ids vary by OS. Query the id, if possible. */
+ if ( clock_getcpuclockid( 0, &id ) == -1 )
+#endif
+#if defined(CLOCK_PROCESS_CPUTIME_ID)
+ /* Use known clock id for AIX, Linux, or Solaris. */
+ id = CLOCK_PROCESS_CPUTIME_ID;
+#elif defined(CLOCK_VIRTUAL)
+ /* Use known clock id for BSD or HP-UX. */
+ id = CLOCK_VIRTUAL;
+#else
+ id = (clockid_t)-1;
+#endif
+ if ( id != (clockid_t)-1 && clock_gettime( id, &ts ) != -1 )
+ return (double)ts.tv_sec +
+ (double)ts.tv_nsec / 1000000000.0;
+ }
+#endif
+
+#if defined(RUSAGE_SELF)
+ {
+ struct rusage rusage;
+ if ( getrusage( RUSAGE_SELF, &rusage ) != -1 )
+ return (double)rusage.ru_utime.tv_sec +
+ (double)rusage.ru_utime.tv_usec / 1000000.0;
+ }
+#endif
+
+#if defined(_SC_CLK_TCK)
+ {
+ const double ticks = (double)sysconf( _SC_CLK_TCK );
+ struct tms tms;
+ if ( times( &tms ) != (clock_t)-1 )
+ return (double)tms.tms_utime / ticks;
+ }
+#endif
+
+#if defined(CLOCKS_PER_SEC)
+ {
+ clock_t cl = clock( );
+ if ( cl != (clock_t)-1 )
+ return (double)cl / (double)CLOCKS_PER_SEC;
+ }
+#endif
+
+#endif
+
+ return -1; /* Failed. */
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* MINUNIT_MINUNIT_H */
diff --git a/nonstd.h b/nonstd.h
new file mode 100644
index 0000000..939c030
--- /dev/null
+++ b/nonstd.h
@@ -0,0 +1,445 @@
+#ifndef NONSTD_H
+#define NONSTD_H
+
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#ifndef NONSTD_DEF
+#ifdef NONSTD_STATIC
+#define NONSTD_DEF static
+#else
+#define NONSTD_DEF extern
+#endif
+#endif
+
+typedef int8_t i8;
+typedef uint8_t u8;
+typedef int16_t i16;
+typedef uint16_t u16;
+typedef int32_t i32;
+typedef uint32_t u32;
+typedef int64_t i64;
+typedef uint64_t u64;
+typedef unsigned int uint;
+typedef unsigned long ulong;
+typedef intptr_t isize;
+typedef uintptr_t usize;
+typedef char c8;
+
+#define countof(a) (sizeof(a) / sizeof((a)[0]))
+
+#define static_foreach(type, var, array) \
+ for (size_t _i_##var = 0, _n_##var = countof(array); \
+ _i_##var < _n_##var && ((var) = (array)[_i_##var], 1); ++_i_##var)
+
+#define ALLOC(type, n) ((type *)malloc((n) * sizeof(type)))
+#define REALLOC(ptr, type, n) ((type *)realloc((ptr), (n) * sizeof(type)))
+#define FREE(ptr) \
+ do { \
+ free(ptr); \
+ ptr = NULL; \
+ } while (0)
+
+#define MIN(a, b) ((a) < (b) ? (a) : (b))
+#define MAX(a, b) ((a) > (b) ? (a) : (b))
+#define CLAMP(x, lo, hi) (MIN((hi), MAX((lo), (x))))
+
+#define UNUSED(x) (void)(x)
+
+#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
+#define STATIC_ASSERT(expr, msg) _Static_assert((expr), msg)
+#else
+#define STATIC_ASSERT(expr, msg) \
+ typedef char static_assertion_##msg[(expr) ? 1 : -1]
+#endif
+
+// String view - read-only, non-owning reference to a string
+typedef struct {
+ const char *data;
+ size_t length;
+} stringv;
+
+NONSTD_DEF stringv sv_from_cstr(const char *s);
+NONSTD_DEF stringv sv_from_parts(const char *data, size_t length);
+NONSTD_DEF stringv sv_slice(stringv sv, size_t start, size_t end);
+NONSTD_DEF int sv_equals(stringv a, stringv b);
+NONSTD_DEF int sv_starts_with(stringv sv, stringv prefix);
+NONSTD_DEF int sv_ends_with(stringv sv, stringv suffix);
+
+// String builder - owning, mutable, dynamically growing string buffer
+typedef struct {
+ char *data;
+ size_t length;
+ size_t capacity;
+} stringb;
+
+NONSTD_DEF void sb_init(stringb *sb, size_t initial_cap);
+NONSTD_DEF void sb_free(stringb *sb);
+NONSTD_DEF void sb_ensure(stringb *sb, size_t additional);
+NONSTD_DEF void sb_append_cstr(stringb *sb, const char *s);
+NONSTD_DEF void sb_append_sv(stringb *sb, stringv sv);
+NONSTD_DEF void sb_append_char(stringb *sb, char c);
+NONSTD_DEF stringv sb_as_sv(const stringb *sb);
+
+// Slice - generic non-owning view into an array
+// Usage:
+// SLICE_DEF(int); // Define slice_int type
+// slice(int) view = ...; // Use it
+#define SLICE_DEF(T) \
+ typedef struct { \
+ T *data; \
+ size_t length; \
+ } slice_##T
+
+#define slice(T) slice_##T
+
+#define make_slice(T, ptr, len) ((slice(T)){.data = (ptr), .length = (len)})
+
+#define array_as_slice(T, arr) \
+ ((slice(T)){.data = (arr).data, .length = (arr).length})
+
+// Dynamic array - generic type-safe growable array using macros
+// Usage: array(int) numbers; array_init(numbers);
+#define array(T) \
+ struct { \
+ T *data; \
+ size_t length; \
+ size_t capacity; \
+ }
+
+#define array_init(arr) \
+ do { \
+ (arr).capacity = 0; \
+ (arr).data = NULL; \
+ (arr).length = 0; \
+ } while (0)
+
+#define array_init_cap(arr, initial_cap) \
+ do { \
+ (arr).capacity = (initial_cap) ? (initial_cap) : 16; \
+ (arr).data = ALLOC(__typeof__(*(arr).data), (arr).capacity); \
+ (arr).length = 0; \
+ } while (0)
+
+#define array_free(arr) \
+ do { \
+ FREE((arr).data); \
+ (arr).length = 0; \
+ (arr).capacity = 0; \
+ } while (0)
+
+#define array_ensure(arr, additional) \
+ do { \
+ size_t _needed = (arr).length + (additional); \
+ if (_needed > (arr).capacity) { \
+ size_t _new_cap = (arr).capacity ? (arr).capacity : 16; \
+ while (_new_cap < _needed) { \
+ _new_cap *= 2; \
+ } \
+ (arr).data = \
+ REALLOC((arr).data, __typeof__(*(arr).data), _new_cap); \
+ (arr).capacity = _new_cap; \
+ } \
+ } while (0)
+
+#define array_push(arr, value) \
+ do { \
+ array_ensure((arr), 1); \
+ (arr).data[(arr).length++] = (value); \
+ } while (0)
+
+#define array_pop(arr) ((arr).length > 0 ? (arr).data[--(arr).length] : 0)
+
+#define array_get(arr, index) ((arr).data[index])
+
+#define array_set(arr, index, value) \
+ do { \
+ if ((index) < (arr).length) { \
+ (arr).data[index] = (value); \
+ } \
+ } while (0)
+
+#define array_insert(arr, index, value) \
+ do { \
+ if ((index) <= (arr).length) { \
+ array_ensure((arr), 1); \
+ for (size_t _i = (arr).length; _i > (index); --_i) { \
+ (arr).data[_i] = (arr).data[_i - 1]; \
+ } \
+ (arr).data[index] = (value); \
+ (arr).length++; \
+ } \
+ } while (0)
+
+#define array_remove(arr, index) \
+ do { \
+ if ((index) < (arr).length) { \
+ for (size_t _i = (index); _i < (arr).length - 1; ++_i) { \
+ (arr).data[_i] = (arr).data[_i + 1]; \
+ } \
+ (arr).length--; \
+ } \
+ } while (0)
+
+#define array_clear(arr) \
+ do { \
+ (arr).length = 0; \
+ } while (0)
+
+#define array_reserve(arr, new_capacity) \
+ do { \
+ if ((new_capacity) > (arr).capacity) { \
+ (arr).data = \
+ REALLOC((arr).data, __typeof__(*(arr).data), (new_capacity)); \
+ (arr).capacity = (new_capacity); \
+ } \
+ } while (0)
+
+#define array_foreach(arr, var) \
+ for (size_t _i_##var = 0; \
+ _i_##var < (arr).length && ((var) = (arr).data[_i_##var], 1); \
+ ++_i_##var)
+
+#define array_foreach_i(arr, var, index) \
+ for (size_t index = 0; \
+ index < (arr).length && ((var) = (arr).data[index], 1); ++index)
+
+// Arena - block-based memory allocator
+// Usage: Arena a = arena_make(); void* p = arena_alloc(&a, 100);
+// arena_free(&a);
+typedef struct {
+ char *ptr;
+ char *end;
+ array(char *) blocks;
+} Arena;
+
+#define ARENA_DEFAULT_BLOCK_SIZE (4096)
+
+NONSTD_DEF Arena arena_make(void);
+NONSTD_DEF void arena_grow(Arena *a, size_t min_size);
+NONSTD_DEF void *arena_alloc(Arena *a, size_t size);
+NONSTD_DEF void arena_free(Arena *a);
+
+// File I/O helpers
+NONSTD_DEF char *read_entire_file(const char *filepath, size_t *out_size);
+NONSTD_DEF int write_entire_file(const char *filepath, const void *data, size_t size);
+NONSTD_DEF stringv read_entire_file_sv(const char *filepath);
+NONSTD_DEF stringb read_entire_file_sb(const char *filepath);
+NONSTD_DEF int write_file_sv(const char *filepath, stringv sv);
+NONSTD_DEF int write_file_sb(const char *filepath, const stringb *sb);
+
+#endif // NONSTD_H
+
+#ifdef NONSTD_IMPLEMENTATION
+
+NONSTD_DEF stringv sv_from_cstr(const char *s) {
+ return (stringv){.data = s, .length = s ? strlen(s) : 0};
+}
+
+NONSTD_DEF stringv sv_from_parts(const char *data, size_t length) {
+ return (stringv){.data = data, .length = length};
+}
+
+NONSTD_DEF stringv sv_slice(stringv sv, size_t start, size_t end) {
+ if (start > sv.length) {
+ start = sv.length;
+ }
+ if (end > sv.length) {
+ end = sv.length;
+ }
+ if (start > end) {
+ start = end;
+ }
+ return (stringv){.data = sv.data + start, .length = end - start};
+}
+
+NONSTD_DEF int sv_equals(stringv a, stringv b) {
+ return a.length == b.length && (a.length == 0 || memcmp(a.data, b.data, a.length) == 0);
+}
+
+NONSTD_DEF int sv_starts_with(stringv sv, stringv prefix) {
+ return sv.length >= prefix.length && memcmp(sv.data, prefix.data, prefix.length) == 0;
+}
+
+NONSTD_DEF int sv_ends_with(stringv sv, stringv suffix) {
+ return sv.length >= suffix.length && memcmp(sv.data + sv.length - suffix.length, suffix.data, suffix.length) == 0;
+}
+
+NONSTD_DEF void sb_init(stringb *sb, size_t initial_cap) {
+ sb->capacity = initial_cap ? initial_cap : 16;
+ sb->data = ALLOC(char, sb->capacity);
+ sb->length = 0;
+ if (sb->data) {
+ sb->data[0] = '\0';
+ }
+}
+
+NONSTD_DEF void sb_free(stringb *sb) {
+ FREE(sb->data);
+ sb->length = 0;
+ sb->capacity = 0;
+}
+
+NONSTD_DEF void sb_ensure(stringb *sb, size_t additional) {
+ size_t needed = sb->length + additional + 1;
+ if (needed > sb->capacity) {
+ while (sb->capacity < needed) {
+ sb->capacity *= 2;
+ }
+ sb->data = REALLOC(sb->data, char, sb->capacity);
+ }
+}
+
+NONSTD_DEF void sb_append_cstr(stringb *sb, const char *s) {
+ if (!s) {
+ return;
+ }
+ size_t slength = strlen(s);
+ sb_ensure(sb, slength);
+ memcpy(sb->data + sb->length, s, slength);
+ sb->length += slength;
+ sb->data[sb->length] = '\0';
+}
+
+NONSTD_DEF void sb_append_sv(stringb *sb, stringv sv) {
+ if (!sv.data || sv.length == 0) {
+ return;
+ }
+ sb_ensure(sb, sv.length);
+ memcpy(sb->data + sb->length, sv.data, sv.length);
+ sb->length += sv.length;
+ sb->data[sb->length] = '\0';
+}
+
+NONSTD_DEF void sb_append_char(stringb *sb, char c) {
+ sb_ensure(sb, 1);
+ sb->data[sb->length++] = c;
+ sb->data[sb->length] = '\0';
+}
+
+NONSTD_DEF stringv sb_as_sv(const stringb *sb) {
+ return (stringv){.data = sb->data, .length = sb->length};
+}
+
+NONSTD_DEF Arena arena_make(void) {
+ Arena a = {0};
+ array_init(a.blocks);
+ return a;
+}
+
+NONSTD_DEF void arena_grow(Arena *a, size_t min_size) {
+ size_t size = MAX(ARENA_DEFAULT_BLOCK_SIZE, min_size);
+ char *block = ALLOC(char, size);
+ a->ptr = block;
+ a->end = block + size;
+ array_push(a->blocks, block);
+}
+
+NONSTD_DEF void *arena_alloc(Arena *a, size_t size) {
+ // Align to 8 bytes basically
+ size_t align = sizeof(void *);
+ uintptr_t current = (uintptr_t)a->ptr;
+ uintptr_t aligned = (current + align - 1) & ~(align - 1);
+ uintptr_t available = (uintptr_t)a->end - aligned;
+
+ if (available < size) {
+ arena_grow(a, size);
+ current = (uintptr_t)a->ptr;
+ aligned = (current + align - 1) & ~(align - 1);
+ }
+
+ a->ptr = (char *)(aligned + size);
+ return (void *)aligned;
+}
+
+NONSTD_DEF void arena_free(Arena *a) {
+ char *block;
+ array_foreach(a->blocks, block) { FREE(block); }
+ array_free(a->blocks);
+ a->ptr = NULL;
+ a->end = NULL;
+}
+
+NONSTD_DEF char *read_entire_file(const char *filepath, size_t *out_size) {
+ FILE *f = fopen(filepath, "rb");
+ if (!f) {
+ return NULL;
+ }
+
+ fseek(f, 0, SEEK_END);
+ long length = ftell(f);
+ fseek(f, 0, SEEK_SET);
+
+ if (length < 0) {
+ fclose(f);
+ return NULL;
+ }
+
+ size_t size = (size_t)length;
+ char *buffer = ALLOC(char, size + 1);
+ if (!buffer) {
+ fclose(f);
+ return NULL;
+ }
+
+ size_t read = fread(buffer, 1, size, f);
+ fclose(f);
+
+ if (read != size) {
+ FREE(buffer);
+ return NULL;
+ }
+
+ buffer[size] = '\0';
+ if (out_size) {
+ *out_size = size;
+ }
+
+ return buffer;
+}
+
+NONSTD_DEF int write_entire_file(const char *filepath, const void *data, size_t size) {
+ FILE *f = fopen(filepath, "wb");
+ if (!f) {
+ return 0;
+ }
+
+ size_t written = fwrite(data, 1, size, f);
+ fclose(f);
+
+ return written == size;
+}
+
+NONSTD_DEF stringv read_entire_file_sv(const char *filepath) {
+ size_t size = 0;
+ char *data = read_entire_file(filepath, &size);
+ if (!data) {
+ return (stringv){0};
+ }
+ return (stringv){.data = data, .length = size};
+}
+
+NONSTD_DEF stringb read_entire_file_sb(const char *filepath) {
+ size_t size = 0;
+ char *data = read_entire_file(filepath, &size);
+ stringb sb = {0};
+ if (data) {
+ sb.data = data;
+ sb.length = size;
+ sb.capacity = size + 1;
+ }
+ return sb;
+}
+
+NONSTD_DEF int write_file_sv(const char *filepath, stringv sv) {
+ return write_entire_file(filepath, sv.data, sv.length);
+}
+
+NONSTD_DEF int write_file_sb(const char *filepath, const stringb *sb) {
+ return write_entire_file(filepath, sb->data, sb->length);
+}
+
+#endif // NONSTD_IMPLEMENTATION \ No newline at end of file
diff --git a/tests.c b/tests.c
new file mode 100644
index 0000000..ca42275
--- /dev/null
+++ b/tests.c
@@ -0,0 +1,984 @@
+#define _POSIX_C_SOURCE 200112L
+#include "minunit.h"
+
+#define NONSTD_IMPLEMENTATION
+#include "nonstd.h"
+
+#include <fcntl.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+// Custom test runner that prints test names
+#define RUN_TEST_WITH_NAME(test) \
+ do { \
+ printf(" %-50s ", #test); \
+ fflush(stdout); \
+ int before_fail = minunit_fail; \
+ int saved_stdout = dup(STDOUT_FILENO); \
+ int devnull = open("/dev/null", O_WRONLY); \
+ dup2(devnull, STDOUT_FILENO); \
+ close(devnull); \
+ MU_RUN_TEST(test); \
+ fflush(stdout); \
+ dup2(saved_stdout, STDOUT_FILENO); \
+ close(saved_stdout); \
+ if (minunit_fail == before_fail) { \
+ printf("PASS\n"); \
+ } \
+ } while (0)
+
+// String view tests
+MU_TEST(test_sv_from_cstr) {
+ stringv sv = sv_from_cstr("hello");
+ mu_assert_int_eq(5, sv.length);
+ mu_check(sv.data != NULL);
+ mu_check(strncmp(sv.data, "hello", 5) == 0);
+}
+
+MU_TEST(test_sv_from_cstr_null) {
+ stringv sv = sv_from_cstr(NULL);
+ mu_assert_int_eq(0, sv.length);
+}
+
+MU_TEST(test_sv_from_parts) {
+ const char *str = "hello world";
+ stringv sv = sv_from_parts(str, 5);
+ mu_assert_int_eq(5, sv.length);
+ mu_check(sv.data == str);
+}
+
+MU_TEST(test_sv_from_cstr_literal) {
+ stringv sv = sv_from_cstr("test");
+ mu_assert_int_eq(4, sv.length);
+ mu_check(strncmp(sv.data, "test", 4) == 0);
+}
+
+MU_TEST(test_sv_slice_normal) {
+ stringv sv = sv_from_cstr("hello world");
+ stringv sliced = sv_slice(sv, 0, 5);
+ mu_assert_int_eq(5, sliced.length);
+ mu_check(strncmp(sliced.data, "hello", 5) == 0);
+}
+
+MU_TEST(test_sv_slice_middle) {
+ stringv sv = sv_from_cstr("hello world");
+ stringv sliced = sv_slice(sv, 6, 11);
+ mu_assert_int_eq(5, sliced.length);
+ mu_check(strncmp(sliced.data, "world", 5) == 0);
+}
+
+MU_TEST(test_sv_slice_out_of_bounds) {
+ stringv sv = sv_from_cstr("hello");
+ stringv sliced = sv_slice(sv, 0, 100);
+ mu_assert_int_eq(5, sliced.length);
+}
+
+MU_TEST(test_sv_slice_invalid_range) {
+ stringv sv = sv_from_cstr("hello");
+ stringv sliced = sv_slice(sv, 10, 5);
+ mu_assert_int_eq(0, sliced.length);
+}
+
+MU_TEST(test_sv_equals_same) {
+ stringv a = sv_from_cstr("hello");
+ stringv b = sv_from_cstr("hello");
+ mu_check(sv_equals(a, b));
+}
+
+MU_TEST(test_sv_equals_different) {
+ stringv a = sv_from_cstr("hello");
+ stringv b = sv_from_cstr("world");
+ mu_check(!sv_equals(a, b));
+}
+
+MU_TEST(test_sv_equals_different_length) {
+ stringv a = sv_from_cstr("hello");
+ stringv b = sv_from_cstr("hi");
+ mu_check(!sv_equals(a, b));
+}
+
+MU_TEST(test_sv_equals_empty) {
+ stringv a = sv_from_parts(NULL, 0);
+ stringv b = sv_from_parts(NULL, 0);
+ mu_check(sv_equals(a, b));
+}
+
+MU_TEST(test_sv_starts_with_true) {
+ stringv sv = sv_from_cstr("hello world");
+ stringv prefix = sv_from_cstr("hello");
+ mu_check(sv_starts_with(sv, prefix));
+}
+
+MU_TEST(test_sv_starts_with_false) {
+ stringv sv = sv_from_cstr("hello world");
+ stringv prefix = sv_from_cstr("world");
+ mu_check(!sv_starts_with(sv, prefix));
+}
+
+MU_TEST(test_sv_starts_with_empty_prefix) {
+ stringv sv = sv_from_cstr("hello");
+ stringv prefix = sv_from_parts(NULL, 0);
+ mu_check(sv_starts_with(sv, prefix));
+}
+
+MU_TEST(test_sv_starts_with_longer_prefix) {
+ stringv sv = sv_from_cstr("hi");
+ stringv prefix = sv_from_cstr("hello");
+ mu_check(!sv_starts_with(sv, prefix));
+}
+
+MU_TEST(test_sv_ends_with_true) {
+ stringv sv = sv_from_cstr("hello world");
+ stringv suffix = sv_from_cstr("world");
+ mu_check(sv_ends_with(sv, suffix));
+}
+
+MU_TEST(test_sv_ends_with_false) {
+ stringv sv = sv_from_cstr("hello world");
+ stringv suffix = sv_from_cstr("hello");
+ mu_check(!sv_ends_with(sv, suffix));
+}
+
+MU_TEST(test_sv_ends_with_empty_suffix) {
+ stringv sv = sv_from_cstr("hello");
+ stringv suffix = sv_from_parts(NULL, 0);
+ mu_check(sv_ends_with(sv, suffix));
+}
+
+MU_TEST(test_sv_ends_with_longer_suffix) {
+ stringv sv = sv_from_cstr("hi");
+ stringv suffix = sv_from_cstr("world");
+ mu_check(!sv_ends_with(sv, suffix));
+}
+
+// Macro tests
+MU_TEST(test_countof) {
+ int array[10];
+ mu_assert_int_eq(10, countof(array));
+
+ char str[] = "hello";
+ mu_assert_int_eq(6, countof(str)); // includes null terminator
+}
+
+MU_TEST(test_min_max_clamp) {
+ mu_assert_int_eq(5, MIN(5, 10));
+ mu_assert_int_eq(5, MIN(10, 5));
+
+ mu_assert_int_eq(10, MAX(5, 10));
+ mu_assert_int_eq(10, MAX(10, 5));
+
+ mu_assert_int_eq(5, CLAMP(3, 5, 10));
+ mu_assert_int_eq(7, CLAMP(7, 5, 10));
+ mu_assert_int_eq(10, CLAMP(15, 5, 10));
+}
+
+MU_TEST(test_static_foreach_macro) {
+ int values[] = {1, 2, 3, 4, 5};
+ int sum = 0;
+ int val;
+
+ static_foreach(int, val, values) { sum += val; }
+
+ mu_assert_int_eq(15, sum);
+}
+
+MU_TEST(test_static_foreach_with_strings) {
+ const char *words[] = {"hello", "world", "test"};
+ int count = 0;
+ const char *word;
+
+ static_foreach(const char *, word, words) {
+ count++;
+ mu_check(word != NULL);
+ }
+
+ mu_assert_int_eq(3, count);
+}
+
+MU_TEST(test_static_foreach_single_element) {
+ int single[] = {42};
+ int result = 0;
+ int val;
+
+ static_foreach(int, val, single) { result = val; }
+
+ mu_assert_int_eq(42, result);
+}
+
+MU_TEST(test_static_foreach_modify_counter) {
+ u32 numbers[] = {10, 20, 30, 40};
+ u32 max = 0;
+ u32 num;
+
+ static_foreach(u32, num, numbers) {
+ if (num > max)
+ max = num;
+ }
+
+ mu_assert_int_eq(40, max);
+}
+
+MU_TEST(test_static_foreach_with_structs) {
+ struct Point {
+ int x, y;
+ };
+
+ struct Point pts[] = {{1, 2}, {3, 4}, {5, 6}};
+ int sum_x = 0;
+ int sum_y = 0;
+ struct Point p;
+
+ static_foreach(struct Point, p, pts) {
+ sum_x += p.x;
+ sum_y += p.y;
+ }
+
+ mu_assert_int_eq(9, sum_x); // 1 + 3 + 5
+ mu_assert_int_eq(12, sum_y); // 2 + 4 + 6
+}
+
+MU_TEST(test_alloc_and_free) {
+ int *ptr = ALLOC(int, 10);
+ mu_check(ptr != NULL);
+
+ // Use the memory
+ ptr[0] = 42;
+ ptr[9] = 99;
+ mu_assert_int_eq(42, ptr[0]);
+ mu_assert_int_eq(99, ptr[9]);
+
+ FREE(ptr);
+ mu_check(ptr == NULL);
+}
+
+MU_TEST(test_realloc) {
+ int *ptr = ALLOC(int, 5);
+ mu_check(ptr != NULL);
+
+ ptr[0] = 1;
+ ptr[4] = 5;
+
+ ptr = REALLOC(ptr, int, 10);
+ mu_check(ptr != NULL);
+
+ mu_assert_int_eq(1, ptr[0]);
+ mu_assert_int_eq(5, ptr[4]);
+ ptr[9] = 99;
+ mu_assert_int_eq(99, ptr[9]);
+
+ free(ptr);
+}
+
+MU_TEST(test_typedefs) {
+ // Just verify the types compile and have expected sizes
+ i8 a = -1;
+ u8 b = 255;
+ i16 c = -1000;
+ u16 d = 60000;
+ i32 e = -100000;
+ u32 f = 4000000000U;
+ i64 g = -1000000000LL;
+ u64 h = 10000000000ULL;
+
+ mu_check(a == -1);
+ mu_check(b == 255);
+ mu_check(c == -1000);
+ mu_check(d == 60000);
+ mu_check(e == -100000);
+ mu_check(f == 4000000000U);
+ mu_check(g == -1000000000LL);
+ mu_check(h == 10000000000ULL);
+}
+
+// String builder tests
+MU_TEST(test_sb_init) {
+ stringb sb;
+ sb_init(&sb, 0);
+ mu_check(sb.data != NULL);
+ mu_assert_int_eq(0, sb.length);
+ mu_check(sb.capacity >= 16); // default capacity
+ mu_assert_int_eq('\0', sb.data[0]);
+ sb_free(&sb);
+}
+
+MU_TEST(test_sb_init_with_capacity) {
+ stringb sb;
+ sb_init(&sb, 64);
+ mu_check(sb.data != NULL);
+ mu_assert_int_eq(0, sb.length);
+ mu_assert_int_eq(64, sb.capacity);
+ sb_free(&sb);
+}
+
+MU_TEST(test_sb_append_cstr) {
+ stringb sb;
+ sb_init(&sb, 0);
+ sb_append_cstr(&sb, "hello");
+ mu_assert_int_eq(5, sb.length);
+ mu_check(strcmp(sb.data, "hello") == 0);
+ sb_free(&sb);
+}
+
+MU_TEST(test_sb_append_cstr_multiple) {
+ stringb sb;
+ sb_init(&sb, 0);
+ sb_append_cstr(&sb, "hello");
+ sb_append_cstr(&sb, " ");
+ sb_append_cstr(&sb, "world");
+ mu_assert_int_eq(11, sb.length);
+ mu_check(strcmp(sb.data, "hello world") == 0);
+ sb_free(&sb);
+}
+
+MU_TEST(test_sb_append_cstr_null) {
+ stringb sb;
+ sb_init(&sb, 0);
+ sb_append_cstr(&sb, "test");
+ sb_append_cstr(&sb, NULL);
+ mu_assert_int_eq(4, sb.length);
+ mu_check(strcmp(sb.data, "test") == 0);
+ sb_free(&sb);
+}
+
+MU_TEST(test_sb_append_sv) {
+ stringb sb;
+ sb_init(&sb, 0);
+ stringv sv = sv_from_cstr("hello");
+ sb_append_sv(&sb, sv);
+ mu_assert_int_eq(5, sb.length);
+ mu_check(strcmp(sb.data, "hello") == 0);
+ sb_free(&sb);
+}
+
+MU_TEST(test_sb_append_sv_slice) {
+ stringb sb;
+ sb_init(&sb, 0);
+ stringv full = sv_from_cstr("hello world");
+ stringv slice = sv_slice(full, 6, 11);
+ sb_append_sv(&sb, slice);
+ mu_assert_int_eq(5, sb.length);
+ mu_check(strcmp(sb.data, "world") == 0);
+ sb_free(&sb);
+}
+
+MU_TEST(test_sb_append_sv_empty) {
+ stringb sb;
+ sb_init(&sb, 0);
+ sb_append_cstr(&sb, "test");
+ stringv empty = sv_from_parts(NULL, 0);
+ sb_append_sv(&sb, empty);
+ mu_assert_int_eq(4, sb.length);
+ mu_check(strcmp(sb.data, "test") == 0);
+ sb_free(&sb);
+}
+
+MU_TEST(test_sb_append_char) {
+ stringb sb;
+ sb_init(&sb, 0);
+ sb_append_char(&sb, 'a');
+ sb_append_char(&sb, 'b');
+ sb_append_char(&sb, 'c');
+ mu_assert_int_eq(3, sb.length);
+ mu_check(strcmp(sb.data, "abc") == 0);
+ sb_free(&sb);
+}
+
+MU_TEST(test_sb_mixed_append) {
+ stringb sb;
+ sb_init(&sb, 0);
+ sb_append_cstr(&sb, "Hello");
+ sb_append_char(&sb, ' ');
+ stringv sv = sv_from_cstr("beautiful");
+ sb_append_sv(&sb, sv);
+ sb_append_char(&sb, ' ');
+ sb_append_cstr(&sb, "world!");
+ mu_assert_int_eq(22, sb.length);
+ mu_check(strcmp(sb.data, "Hello beautiful world!") == 0);
+ sb_free(&sb);
+}
+
+MU_TEST(test_sb_as_sv) {
+ stringb sb;
+ sb_init(&sb, 0);
+ sb_append_cstr(&sb, "test string");
+ stringv sv = sb_as_sv(&sb);
+ mu_assert_int_eq(11, sv.length);
+ mu_check(sv.data == sb.data);
+ mu_check(strncmp(sv.data, "test string", sv.length) == 0);
+ sb_free(&sb);
+}
+
+MU_TEST(test_sb_growth) {
+ stringb sb;
+ sb_init(&sb, 4); // very small initial capacity
+ mu_assert_int_eq(4, sb.capacity);
+
+ // Append enough to trigger growth
+ sb_append_cstr(&sb, "this is a long string that will exceed initial capacity");
+ mu_check(sb.capacity > 4); // capacity should have grown
+ mu_check(sb.length == strlen("this is a long string that will exceed initial capacity"));
+ mu_check(strcmp(sb.data, "this is a long string that will exceed initial capacity") == 0);
+ sb_free(&sb);
+}
+
+MU_TEST(test_sb_free) {
+ stringb sb;
+ sb_init(&sb, 32);
+ sb_append_cstr(&sb, "test");
+ sb_free(&sb);
+ mu_check(sb.data == NULL);
+ mu_assert_int_eq(0, sb.length);
+ mu_assert_int_eq(0, sb.capacity);
+}
+
+MU_TEST(test_sb_sv_interop) {
+ // Test that sb can be used to build a string, then viewed with sv
+ stringb sb;
+ sb_init(&sb, 0);
+
+ const char *words[] = {"one", "two", "three"};
+ const char *word;
+ static_foreach(const char *, word, words) {
+ if (sb.length > 0) {
+ sb_append_char(&sb, ',');
+ }
+ sb_append_cstr(&sb, word);
+ }
+
+ stringv result = sb_as_sv(&sb);
+ mu_check(sv_equals(result, sv_from_cstr("one,two,three")));
+
+ // Can use sv operations on the builder's content
+ mu_check(sv_starts_with(result, sv_from_cstr("one")));
+ mu_check(sv_ends_with(result, sv_from_cstr("three")));
+
+ sb_free(&sb);
+}
+
+MU_TEST(test_sb_append_sv_from_sb) {
+ // Test appending a string view of one builder to another
+ stringb sb1, sb2;
+ sb_init(&sb1, 0);
+ sb_init(&sb2, 0);
+
+ sb_append_cstr(&sb1, "hello");
+ sb_append_cstr(&sb2, "world");
+
+ stringv sv1 = sb_as_sv(&sb1);
+ sb_append_char(&sb2, ' ');
+ sb_append_sv(&sb2, sv1);
+
+ mu_check(strcmp(sb2.data, "world hello") == 0);
+
+ sb_free(&sb1);
+ sb_free(&sb2);
+}
+
+// Array tests
+MU_TEST(test_array_init) {
+ array(int) arr;
+ array_init(arr);
+ mu_check(arr.data == NULL);
+ mu_assert_int_eq(0, arr.length);
+ mu_assert_int_eq(0, arr.capacity);
+ array_free(arr);
+}
+
+MU_TEST(test_array_init_cap) {
+ array(int) arr;
+ array_init_cap(arr, 32);
+ mu_check(arr.data != NULL);
+ mu_assert_int_eq(0, arr.length);
+ mu_assert_int_eq(32, arr.capacity);
+ array_free(arr);
+}
+
+MU_TEST(test_array_push) {
+ array(int) arr;
+ array_init(arr);
+ array_push(arr, 10);
+ array_push(arr, 20);
+ array_push(arr, 30);
+
+ mu_assert_int_eq(3, arr.length);
+ mu_check(arr.capacity >= 3);
+ mu_assert_int_eq(10, arr.data[0]);
+ mu_assert_int_eq(20, arr.data[1]);
+ mu_assert_int_eq(30, arr.data[2]);
+
+ array_free(arr);
+}
+
+MU_TEST(test_array_pop) {
+ array(int) arr;
+ array_init(arr);
+ array_push(arr, 10);
+ array_push(arr, 20);
+
+ int val = array_pop(arr);
+ mu_assert_int_eq(20, val);
+ mu_assert_int_eq(1, arr.length);
+
+ val = array_pop(arr);
+ mu_assert_int_eq(10, val);
+ mu_assert_int_eq(0, arr.length);
+
+ array_free(arr);
+}
+
+MU_TEST(test_array_pop_empty) {
+ array(int) arr;
+ array_init(arr);
+ int val = array_pop(arr);
+ mu_assert_int_eq(0, val);
+ mu_assert_int_eq(0, arr.length);
+ array_free(arr);
+}
+
+MU_TEST(test_array_get_set) {
+ array(int) arr;
+ array_init(arr);
+ array_push(arr, 10);
+ array_push(arr, 20);
+
+ mu_assert_int_eq(10, array_get(arr, 0));
+ mu_assert_int_eq(20, array_get(arr, 1));
+
+ array_set(arr, 0, 100);
+ mu_assert_int_eq(100, array_get(arr, 0));
+
+ // Test out of bounds set (should do nothing)
+ array_set(arr, 5, 500);
+ mu_assert_int_eq(2, arr.length);
+
+ array_free(arr);
+}
+
+MU_TEST(test_array_insert) {
+ array(int) arr;
+ array_init(arr);
+ array_push(arr, 10);
+ array_push(arr, 30);
+
+ array_insert(arr, 1, 20); // Insert between 10 and 30
+ mu_assert_int_eq(3, arr.length);
+ mu_assert_int_eq(10, arr.data[0]);
+ mu_assert_int_eq(20, arr.data[1]);
+ mu_assert_int_eq(30, arr.data[2]);
+
+ array_insert(arr, 0, 5); // Insert at start
+ mu_assert_int_eq(5, arr.data[0]);
+ mu_assert_int_eq(10, arr.data[1]);
+
+ array_insert(arr, 4, 40); // Insert at end
+ mu_assert_int_eq(40, arr.data[4]);
+ mu_assert_int_eq(5, arr.length);
+
+ array_free(arr);
+}
+
+MU_TEST(test_array_remove) {
+ array(int) arr;
+ array_init(arr);
+ array_push(arr, 10);
+ array_push(arr, 20);
+ array_push(arr, 30);
+ array_push(arr, 40);
+
+ array_remove(arr, 1); // Remove 20
+ mu_assert_int_eq(3, arr.length);
+ mu_assert_int_eq(10, arr.data[0]);
+ mu_assert_int_eq(30, arr.data[1]);
+ mu_assert_int_eq(40, arr.data[2]);
+
+ array_remove(arr, 0); // Remove 10 (start)
+ mu_assert_int_eq(30, arr.data[0]);
+
+ array_remove(arr, 1); // Remove 40 (end)
+ mu_assert_int_eq(30, arr.data[0]);
+ mu_assert_int_eq(1, arr.length);
+
+ array_free(arr);
+}
+
+MU_TEST(test_array_growth) {
+ array(int) arr;
+ array_init_cap(arr, 4);
+
+ for (int i = 0; i < 20; i++) {
+ array_push(arr, i);
+ }
+
+ mu_assert_int_eq(20, arr.length);
+ mu_check(arr.capacity >= 20);
+ mu_check(arr.capacity > 4);
+
+ for (int i = 0; i < 20; i++) {
+ mu_assert_int_eq(i, arr.data[i]);
+ }
+
+ array_free(arr);
+}
+
+MU_TEST(test_array_reserve) {
+ array(int) arr;
+ array_init(arr);
+
+ array_reserve(arr, 100);
+ mu_assert_int_eq(100, arr.capacity);
+ mu_assert_int_eq(0, arr.length);
+ mu_check(arr.data != NULL);
+
+ array_free(arr);
+}
+
+MU_TEST(test_array_clear) {
+ array(int) arr;
+ array_init(arr);
+ array_push(arr, 1);
+ array_push(arr, 2);
+
+ size_t cap = arr.capacity;
+ array_clear(arr);
+
+ mu_assert_int_eq(0, arr.length);
+ mu_assert_int_eq(cap, arr.capacity); // Capacity stays
+ mu_check(arr.data != NULL);
+
+ array_free(arr);
+}
+
+MU_TEST(test_array_foreach) {
+ array(int) arr;
+ array_init(arr);
+ array_push(arr, 1);
+ array_push(arr, 2);
+ array_push(arr, 3);
+
+ int sum = 0;
+ int val;
+ array_foreach(arr, val) { sum += val; }
+
+ mu_assert_int_eq(6, sum);
+ array_free(arr);
+}
+
+MU_TEST(test_array_foreach_i) {
+ array(int) arr;
+ array_init(arr);
+ array_push(arr, 10);
+ array_push(arr, 20);
+ array_push(arr, 30);
+
+ int sum_val = 0;
+ size_t sum_idx = 0;
+ int val;
+
+ array_foreach_i(arr, val, i) {
+ sum_val += val;
+ sum_idx += i;
+ mu_assert_int_eq(val, arr.data[i]);
+ }
+
+ mu_assert_int_eq(60, sum_val);
+ mu_assert_int_eq(3, sum_idx); // 0+1+2
+ array_free(arr);
+}
+
+// Slice Tests
+SLICE_DEF(int);
+
+MU_TEST(test_slice_make) {
+ int nums[] = {1, 2, 3, 4, 5};
+ slice(int) s = make_slice(int, nums, 5);
+
+ mu_assert_int_eq(5, s.length);
+ mu_check(s.data == nums);
+ mu_assert_int_eq(1, s.data[0]);
+ mu_assert_int_eq(5, s.data[4]);
+}
+
+MU_TEST(test_slice_make_partial) {
+ int nums[] = {10, 20, 30, 40, 50};
+ slice(int) s = make_slice(int, nums + 1, 3); // 20, 30, 40
+
+ mu_assert_int_eq(3, s.length);
+ mu_assert_int_eq(20, s.data[0]);
+ mu_assert_int_eq(40, s.data[2]);
+}
+
+MU_TEST(test_slice_from_dynamic_array) {
+ array(int) arr;
+ array_init(arr);
+ array_push(arr, 100);
+ array_push(arr, 200);
+
+ slice(int) s = array_as_slice(int, arr);
+
+ mu_assert_int_eq(2, s.length);
+ mu_check(s.data == arr.data);
+ mu_assert_int_eq(100, s.data[0]);
+
+ array_free(arr);
+}
+
+MU_TEST(test_slice_modification) {
+ int nums[] = {1, 1, 1};
+ slice(int) s = make_slice(int, nums, 3);
+
+ s.data[1] = 99;
+
+ mu_assert_int_eq(99, nums[1]);
+ mu_assert_int_eq(1, nums[0]);
+}
+
+// Arena tests
+MU_TEST(test_arena_basic) {
+ Arena a = arena_make();
+ void *p1 = arena_alloc(&a, 10);
+ void *p2 = arena_alloc(&a, 20);
+
+ mu_check(p1 != NULL);
+ mu_check(p2 != NULL);
+ mu_check(p1 != p2);
+
+ // Basic usage check
+ memset(p1, 1, 10);
+ memset(p2, 2, 20);
+
+ arena_free(&a);
+}
+
+MU_TEST(test_arena_growth) {
+ Arena a = arena_make();
+
+ // Force growth by allocating more than default block size (4096)
+ // or by allocating many small objects.
+ // Let's alloc a big chunk first.
+ void *big = arena_alloc(&a, 5000);
+ mu_check(big != NULL);
+ mu_check(a.blocks.length >= 1);
+
+ // Alloc another big chunk
+ void *big2 = arena_alloc(&a, 5000);
+ mu_check(big2 != NULL);
+ mu_check(big2 != big);
+ mu_check(a.blocks.length >= 2);
+
+ arena_free(&a);
+}
+
+MU_TEST(test_arena_alignment) {
+ Arena a = arena_make();
+
+ void *p1 = arena_alloc(&a, 1); // 1 byte
+ void *p2 = arena_alloc(&a, 1); // 1 byte
+
+ uintptr_t addr1 = (uintptr_t)p1;
+ uintptr_t addr2 = (uintptr_t)p2;
+
+ mu_check(addr1 % sizeof(void *) == 0);
+ mu_check(addr2 % sizeof(void *) == 0);
+ mu_check(addr2 - addr1 >= sizeof(void *));
+
+ arena_free(&a);
+}
+
+// File I/O tests
+MU_TEST(test_file_io_basic) {
+ const char *filename = "test_io_basic.txt";
+ const char *content = "Hello, file!";
+ size_t len = strlen(content);
+
+ // Write
+ mu_check(write_entire_file(filename, content, len));
+
+ // Read
+ size_t read_len;
+ char *read_content = read_entire_file(filename, &read_len);
+ mu_check(read_content != NULL);
+ mu_assert_int_eq(len, read_len);
+ mu_check(strncmp(content, read_content, len) == 0);
+
+ FREE(read_content);
+ remove(filename);
+}
+
+MU_TEST(test_file_io_sv) {
+ const char *filename = "test_io_sv.txt";
+ stringv sv = sv_from_cstr("Hello from sv!");
+
+ // Write
+ mu_check(write_file_sv(filename, sv));
+
+ // Read
+ stringv read_sv = read_entire_file_sv(filename);
+ mu_check(read_sv.data != NULL);
+ mu_assert_int_eq(sv.length, read_sv.length);
+ mu_check(sv_equals(sv, read_sv));
+
+ // The data returned by read_entire_file_sv is malloc'd and needs freeing
+ // We cast away const to free it since we know it was allocated
+ free((char *)read_sv.data);
+ remove(filename);
+}
+
+MU_TEST(test_file_io_sb) {
+ const char *filename = "test_io_sb.txt";
+ stringb sb;
+ sb_init(&sb, 0);
+ sb_append_cstr(&sb, "Hello from sb!");
+
+ // Write
+ mu_check(write_file_sb(filename, &sb));
+
+ // Read
+ stringb read_sb = read_entire_file_sb(filename);
+ mu_check(read_sb.data != NULL);
+ mu_assert_int_eq(sb.length, read_sb.length);
+ mu_check(strcmp(sb.data, read_sb.data) == 0);
+
+ sb_free(&sb);
+ sb_free(&read_sb);
+ remove(filename);
+}
+
+MU_TEST(test_file_io_read_missing) {
+ size_t len;
+ char *content = read_entire_file("non_existent_file.txt", &len);
+ mu_check(content == NULL);
+}
+
+MU_TEST(test_file_io_read_missing_sv) {
+ stringv sv = read_entire_file_sv("non_existent_file.txt");
+ mu_check(sv.data == NULL);
+ mu_assert_int_eq(0, sv.length);
+}
+
+MU_TEST(test_file_io_read_missing_sb) {
+ stringb sb = read_entire_file_sb("non_existent_file.txt");
+ mu_check(sb.data == NULL);
+ mu_assert_int_eq(0, sb.length);
+}
+
+// Test suites
+MU_TEST_SUITE(test_suite_stringv) {
+ printf("\n[String View Tests]\n");
+ RUN_TEST_WITH_NAME(test_sv_from_cstr);
+ RUN_TEST_WITH_NAME(test_sv_from_cstr_null);
+ RUN_TEST_WITH_NAME(test_sv_from_parts);
+ RUN_TEST_WITH_NAME(test_sv_from_cstr_literal);
+ RUN_TEST_WITH_NAME(test_sv_slice_normal);
+ RUN_TEST_WITH_NAME(test_sv_slice_middle);
+ RUN_TEST_WITH_NAME(test_sv_slice_out_of_bounds);
+ RUN_TEST_WITH_NAME(test_sv_slice_invalid_range);
+ RUN_TEST_WITH_NAME(test_sv_equals_same);
+ RUN_TEST_WITH_NAME(test_sv_equals_different);
+ RUN_TEST_WITH_NAME(test_sv_equals_different_length);
+ RUN_TEST_WITH_NAME(test_sv_equals_empty);
+ RUN_TEST_WITH_NAME(test_sv_starts_with_true);
+ RUN_TEST_WITH_NAME(test_sv_starts_with_false);
+ RUN_TEST_WITH_NAME(test_sv_starts_with_empty_prefix);
+ RUN_TEST_WITH_NAME(test_sv_starts_with_longer_prefix);
+ RUN_TEST_WITH_NAME(test_sv_ends_with_true);
+ RUN_TEST_WITH_NAME(test_sv_ends_with_false);
+ RUN_TEST_WITH_NAME(test_sv_ends_with_empty_suffix);
+ RUN_TEST_WITH_NAME(test_sv_ends_with_longer_suffix);
+}
+
+MU_TEST_SUITE(test_suite_stringb) {
+ printf("\n[String Builder Tests]\n");
+ RUN_TEST_WITH_NAME(test_sb_init);
+ RUN_TEST_WITH_NAME(test_sb_init_with_capacity);
+ RUN_TEST_WITH_NAME(test_sb_append_cstr);
+ RUN_TEST_WITH_NAME(test_sb_append_cstr_multiple);
+ RUN_TEST_WITH_NAME(test_sb_append_cstr_null);
+ RUN_TEST_WITH_NAME(test_sb_append_sv);
+ RUN_TEST_WITH_NAME(test_sb_append_sv_slice);
+ RUN_TEST_WITH_NAME(test_sb_append_sv_empty);
+ RUN_TEST_WITH_NAME(test_sb_append_char);
+ RUN_TEST_WITH_NAME(test_sb_mixed_append);
+ RUN_TEST_WITH_NAME(test_sb_as_sv);
+ RUN_TEST_WITH_NAME(test_sb_growth);
+ RUN_TEST_WITH_NAME(test_sb_free);
+ RUN_TEST_WITH_NAME(test_sb_sv_interop);
+ RUN_TEST_WITH_NAME(test_sb_append_sv_from_sb);
+}
+
+MU_TEST_SUITE(test_suite_macros) {
+ printf("\n[Macro Tests]\n");
+ RUN_TEST_WITH_NAME(test_countof);
+ RUN_TEST_WITH_NAME(test_min_max_clamp);
+ RUN_TEST_WITH_NAME(test_static_foreach_macro);
+ RUN_TEST_WITH_NAME(test_static_foreach_with_strings);
+ RUN_TEST_WITH_NAME(test_static_foreach_single_element);
+ RUN_TEST_WITH_NAME(test_static_foreach_modify_counter);
+ RUN_TEST_WITH_NAME(test_static_foreach_with_structs);
+ RUN_TEST_WITH_NAME(test_alloc_and_free);
+ RUN_TEST_WITH_NAME(test_realloc);
+}
+
+MU_TEST_SUITE(test_suite_array) {
+ printf("\n[Array Tests]\n");
+ RUN_TEST_WITH_NAME(test_array_init);
+ RUN_TEST_WITH_NAME(test_array_init_cap);
+ RUN_TEST_WITH_NAME(test_array_push);
+ RUN_TEST_WITH_NAME(test_array_pop);
+ RUN_TEST_WITH_NAME(test_array_pop_empty);
+ RUN_TEST_WITH_NAME(test_array_get_set);
+ RUN_TEST_WITH_NAME(test_array_insert);
+ RUN_TEST_WITH_NAME(test_array_remove);
+ RUN_TEST_WITH_NAME(test_array_growth);
+ RUN_TEST_WITH_NAME(test_array_reserve);
+ RUN_TEST_WITH_NAME(test_array_clear);
+ RUN_TEST_WITH_NAME(test_array_foreach);
+ RUN_TEST_WITH_NAME(test_array_foreach_i);
+}
+
+MU_TEST_SUITE(test_suite_slice) {
+ printf("\n[Slice Tests]\n");
+ RUN_TEST_WITH_NAME(test_slice_make);
+ RUN_TEST_WITH_NAME(test_slice_make_partial);
+ RUN_TEST_WITH_NAME(test_slice_from_dynamic_array);
+ RUN_TEST_WITH_NAME(test_slice_modification);
+}
+
+MU_TEST_SUITE(test_suite_types) {
+ printf("\n[Type Tests]\n");
+ RUN_TEST_WITH_NAME(test_typedefs);
+}
+
+MU_TEST_SUITE(test_suite_arena) {
+ printf("\n[Arena Tests]\n");
+ RUN_TEST_WITH_NAME(test_arena_basic);
+ RUN_TEST_WITH_NAME(test_arena_growth);
+ RUN_TEST_WITH_NAME(test_arena_alignment);
+}
+
+MU_TEST_SUITE(test_suite_files) {
+ printf("\n[File I/O Tests]\n");
+ RUN_TEST_WITH_NAME(test_file_io_basic);
+ RUN_TEST_WITH_NAME(test_file_io_sv);
+ RUN_TEST_WITH_NAME(test_file_io_sb);
+ RUN_TEST_WITH_NAME(test_file_io_read_missing);
+ RUN_TEST_WITH_NAME(test_file_io_read_missing_sv);
+ RUN_TEST_WITH_NAME(test_file_io_read_missing_sb);
+}
+
+int main(int argc, char *argv[]) {
+ (void)argc;
+ (void)argv;
+
+ MU_RUN_SUITE(test_suite_stringv);
+ MU_RUN_SUITE(test_suite_stringb);
+ MU_RUN_SUITE(test_suite_macros);
+ MU_RUN_SUITE(test_suite_array);
+ MU_RUN_SUITE(test_suite_slice);
+ MU_RUN_SUITE(test_suite_types);
+ MU_RUN_SUITE(test_suite_arena);
+ MU_RUN_SUITE(test_suite_files);
+
+ MU_REPORT();
+
+ return MU_EXIT_CODE;
+}