From 6cb22fd7f2c20be2cf268d6bcd236252d7847763 Mon Sep 17 00:00:00 2001 From: Mitja Felicijan Date: Wed, 21 Jan 2026 17:53:40 +0100 Subject: Engage! --- .clang-format | 4 + .gitignore | 1 + LICENSE | 24 ++ Makefile | 19 + README.md | 163 ++++++++ examples/.gitignore | 8 + examples/Makefile | 56 +++ examples/arena.c | 59 +++ examples/array.c | 158 ++++++++ examples/files.c | 52 +++ examples/foreach.c | 68 ++++ examples/slice.c | 79 ++++ examples/stringb.c | 130 +++++++ examples/stringv.c | 97 +++++ examples/test_basic.txt | 1 + examples/test_sb.txt | 1 + examples/test_sv.txt | 1 + minunit.h | 391 +++++++++++++++++++ nonstd.h | 445 ++++++++++++++++++++++ tests.c | 984 ++++++++++++++++++++++++++++++++++++++++++++++++ 20 files changed, 2741 insertions(+) create mode 100644 .clang-format create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100644 examples/.gitignore create mode 100644 examples/Makefile create mode 100644 examples/arena.c create mode 100644 examples/array.c create mode 100644 examples/files.c create mode 100644 examples/foreach.c create mode 100644 examples/slice.c create mode 100644 examples/stringb.c create mode 100644 examples/stringv.c create mode 100644 examples/test_basic.txt create mode 100644 examples/test_sb.txt create mode 100644 examples/test_sv.txt create mode 100644 minunit.h create mode 100644 nonstd.h create mode 100644 tests.c 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 + +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 + +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 + +// 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 + +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 + +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 + +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 + +// 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 + +// 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 +#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 /* POSIX flags */ +#include /* clock_gettime(), time() */ +#include /* gethrtime(), gettimeofday() */ +#include +#include +#include + +#if defined(__MACH__) && defined(__APPLE__) +#include +#include +#endif + +#if __GNUC__ >= 5 && !defined(__STDC_VERSION__) +#define __func__ __extension__ __FUNCTION__ +#endif + +#else +#error "Unable to define timers for an unknown OS." +#endif + +#include +#include + +/* 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 = "";\ + }\ + if (!minunit_tmp_r) {\ + minunit_tmp_r = "";\ + }\ + 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 +#include +#include +#include +#include + +#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 +#include +#include + +// 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; +} -- cgit v1.2.3