From 14623baa07633c0deeaf57dbcdc1d7a8cd3b6dde Mon Sep 17 00:00:00 2001 From: Brendan Dileo <124395674+brendanddev@users.noreply.github.com> Date: Mon, 29 Jun 2026 09:38:52 -0400 Subject: [PATCH 01/15] Create CONTRIBUTING and setup MemoryManager struct --- CONTRIBUTING.md | 120 +++++++++++++++++++++++++++++++++++++++ include/memory_manager.h | 12 ++++ src/memory_manager.c | 0 tests/mm_test.c | 0 4 files changed, 132 insertions(+) create mode 100644 CONTRIBUTING.md create mode 100644 include/memory_manager.h create mode 100644 src/memory_manager.c create mode 100644 tests/mm_test.c diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..dea542a --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,120 @@ +# Contributing to struccs + +Thanks for your interest in contributing. struccs is a handwritten, dependency-free +C library built from scratch with a focus on learning, memory management, and +type-agnostic design. Contributions are welcome as long as they align with that philosophy. + +--- + +## Use of AI Tools + +AI tools (Claude, Codex, Cursor) have been used in a limited capacity during development: + +- Discussing ideas, design decisions, and tradeoffs +- Code review and feedback on existing implementations +- Mechanical refactoring tasks (e.g. find-and-replace style changes) +- Documentation suggestions + +All core implementations - data structures, algorithms, memory management, and API +design — were written by hand without AI generation. This is intentional; the primary +goal of struccs is deep learning and genuine understanding of systems-level C programming. + +Contributors are expected to follow the same standard. AI-generated core logic or +implementations will not be accepted. + +--- + +## Project Philosophy + +struccs is built on a few core principles: + +- No dependencies — no standard container libraries, no third party code +- Generic by design — structures work with any data type via `void*` and function pointers +- Memory conscious — every allocation has a corresponding free, ownership is always clear +- Readable — code should be understandable to someone learning C, not just experts + +Keep these in mind when contributing. + +--- + +## Getting Started + +```bash +git clone https://github.com/brendanddev/struccs.git +cd struccs +make +make test_all +``` + +See [README.md](README.md) for full build and usage instructions. + +--- + +## Adding a New Structure + +Follow this checklist when adding a new data structure: + +1. Create `include/structure_name.h` — public API and struct definition +2. Create `src/structure_name.c` — implementation +3. Create `tests/test_structure_name.c` — test suite +4. Add a test target to the `Makefile` +5. Add the structure to `test_all` in the `Makefile` +6. Update the structures table in `README.md` +7. Add the header to `struccs.h` if an umbrella header exists + +--- + +## Code Style + +**Naming** +- Functions follow the `structure_operation` pattern — `ga_append`, `bst_insert`, `ht_remove` +- Structs use PascalCase — `GenericArray`, `BinarySearchTree` +- Internal/static helpers are lowercase with underscores — `shift_left`, `resize` + +**Comments** +- Comment *why*, not *what* — avoid restating what the code already says +- Public headers get a brief docstring per function +- Implementation files get a file header comment, nothing more unless the logic is non-obvious + +**Memory** +- Every allocation must have a corresponding free +- Ownership must be clear — document who is responsible for freeing data passed via `void*` +- Always handle allocation failure — never assume `malloc` succeeds + +--- + +## Testing + +Every new structure needs a test file in `tests/` covering: + +- Initialization +- Basic operations +- Edge cases (empty structure, single element, duplicates where relevant) +- Cleanup — no memory leaks + +Tests use the macros defined in `tests/test_utils.h`: + +```c +ASSERT_EQ(actual, expected); +ASSERT_TRUE(expr); +ASSERT_FALSE(expr); +ASSERT_NULL(ptr); +ASSERT_NOT_NULL(ptr); +TEST(function_name); +``` + +--- + +## Submitting Changes + +- Fork the repo and create a branch for your change +- Keep PRs focused — one structure or fix per PR +- Make sure `make test_all` passes before submitting +- Update documentation if your change affects the public API + +--- + +## License + +By contributing, you agree that your contributions will be licensed under the +same MIT License that covers this project. diff --git a/include/memory_manager.h b/include/memory_manager.h new file mode 100644 index 0000000..921924a --- /dev/null +++ b/include/memory_manager.h @@ -0,0 +1,12 @@ +/** + * memory_manager.h + */ + +#ifndef MEMORY_MANAGER_H +#define MEMORY_MANAGER_H + +#include +#include +#include + +#endif \ No newline at end of file diff --git a/src/memory_manager.c b/src/memory_manager.c new file mode 100644 index 0000000..e69de29 diff --git a/tests/mm_test.c b/tests/mm_test.c new file mode 100644 index 0000000..e69de29 From 4a738008e6c8dc2725570fb1fbbe9fb3004e49eb Mon Sep 17 00:00:00 2001 From: Brendan Dileo <124395674+brendanddev@users.noreply.github.com> Date: Mon, 29 Jun 2026 13:48:04 -0400 Subject: [PATCH 02/15] Implement MemoryPool v1 --- README.md | 18 ++++++++- include/memory_manager.h | 12 ------ include/memory_pool.h | 41 ++++++++++++++++++++ include/struccs.h | 1 + src/memory_pool.c | 50 +++++++++++++++++++++++++ tests/mm_test.c | 0 src/memory_manager.c => tests/test_mp.c | 0 7 files changed, 109 insertions(+), 13 deletions(-) delete mode 100644 include/memory_manager.h create mode 100644 include/memory_pool.h create mode 100644 src/memory_pool.c delete mode 100644 tests/mm_test.c rename src/memory_manager.c => tests/test_mp.c (100%) diff --git a/README.md b/README.md index b478ecd..ae1fa68 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ A lightweight, generic C library of fundamental data structures — built from scratch with a focus on memory management, performance, and type-agnostic design. -> Version 0.10.8 +> Version 0.11.8 No dependencies. No standard container libraries. Just `void*`, function pointers, and manual memory management. @@ -65,6 +65,12 @@ For full examples per structure, see [docs/USAGE.md](docs/USAGE.md). Each structure ships with its own header and source file, and can be used independently or together via the umbrella header. +## Memory Management + +| Component | Description | Header | +|----------------|-------------------------------------------------------------|------------------| +| `MemoryPool` | Fixed-size block allocator backed by a pre-allocated buffer | `memory_pool.h` | + --- ## Usage @@ -121,6 +127,16 @@ struccs/ --- +## Contributing + +See [CONTRIBUTING.md](CONTRIBUTING.md) for full guidelines on: +- Project design principles (no dependencies, generic by design, memory-conscious) +- Code style and structure conventions +- Testing requirements +- Pull request process + +--- + ## License MIT License — see [LICENSE](LICENSE) for details. diff --git a/include/memory_manager.h b/include/memory_manager.h deleted file mode 100644 index 921924a..0000000 --- a/include/memory_manager.h +++ /dev/null @@ -1,12 +0,0 @@ -/** - * memory_manager.h - */ - -#ifndef MEMORY_MANAGER_H -#define MEMORY_MANAGER_H - -#include -#include -#include - -#endif \ No newline at end of file diff --git a/include/memory_pool.h b/include/memory_pool.h new file mode 100644 index 0000000..f0a0617 --- /dev/null +++ b/include/memory_pool.h @@ -0,0 +1,41 @@ +/** + * memory_pool.h + * Header for the generic memory pool implementation. + * Brendan Dileo - 2026 + */ + +#ifndef MEMORY_POOL_H +#define MEMORY_POOL_H + +#include +#include +#include + +#define MEMORY_SIZE(bsize, nblocks) ((bsize) + sizeof(struct MemoryBlockHeader)) * (nblocks) + +/** + * Represents the header metadata stored at the start of each memory block. + * Blocks are linked together via the next pointer to track free blocks. + */ +struct MemoryBlockHeader { + struct MemoryBlockHeader *next; +}; + +/** + * Represents a fixed-size memory pool. + * Manages a pre-allocated buffer split into equal-sized blocks. + */ +struct MemoryPool { + char *memory; + struct MemoryBlockHeader *first_free; + int num_blocks; + int num_free_blocks; + int block_size; +}; + +void mp_create(struct MemoryPool *pool); +void *mp_alloc(struct MemoryPool *pool); +void mp_free(struct MemoryPool *pool, void *ptr); +void mp_stats(struct MemoryPool *pool); + +#endif \ No newline at end of file diff --git a/include/struccs.h b/include/struccs.h index e53a541..1b4f0a8 100644 --- a/include/struccs.h +++ b/include/struccs.h @@ -15,5 +15,6 @@ #include "binary_tree.h" #include "binarysearch_tree.h" #include "heap.h" +#include "memory_pool.h" #endif \ No newline at end of file diff --git a/src/memory_pool.c b/src/memory_pool.c new file mode 100644 index 0000000..f558b6e --- /dev/null +++ b/src/memory_pool.c @@ -0,0 +1,50 @@ +#include +#include +#include +#include +#include "memory_pool.h" + +void mp_create(struct MemoryPool *pool) { + memset(pool->memory, 0, MEMORY_SIZE(pool->block_size, pool->num_blocks)); + pool->first_free = (struct MemoryBlockHeader*) pool->memory; + + struct MemoryBlockHeader* current_block = pool->first_free; + for (int i = 0; i < pool->num_blocks - 1; i++) { + current_block->next = (struct MemoryBlockHeader *)((char *)(current_block + 1) + pool->block_size); + current_block = current_block->next; + pool->num_free_blocks++; + } + + current_block->next = NULL; + pool->num_free_blocks++; +} + +void *mp_alloc(struct MemoryPool *pool) { + void *data = NULL; + + if (pool->num_free_blocks > 0) { + data = (void *)((char *) pool->first_free + sizeof(struct MemoryBlockHeader)); + pool->first_free = pool->first_free->next; + pool->num_free_blocks--; + } + return data; +} + +void mp_free(struct MemoryPool *pool, void *ptr) { + if (pool->num_free_blocks < pool->num_blocks) { + struct MemoryBlockHeader *block = (struct MemoryBlockHeader *)((char *) ptr - sizeof(struct MemoryBlockHeader)); + block->next = pool->first_free; + pool->first_free = block; + pool->num_free_blocks++; + } +} + +void mp_stats(struct MemoryPool *pool) { + printf("Number of free blocks: %d\n", pool->num_free_blocks); + + struct MemoryBlockHeader *block = pool->first_free; + while (block != NULL) { + printf("%p\n", block); + block = block->next; + } +} diff --git a/tests/mm_test.c b/tests/mm_test.c deleted file mode 100644 index e69de29..0000000 diff --git a/src/memory_manager.c b/tests/test_mp.c similarity index 100% rename from src/memory_manager.c rename to tests/test_mp.c From 786bd31499b87de5d9323c1dc59215b1bdaea664 Mon Sep 17 00:00:00 2001 From: Brendan Dileo <124395674+brendanddev@users.noreply.github.com> Date: Mon, 29 Jun 2026 16:50:32 -0400 Subject: [PATCH 03/15] Implement MemoryPool v2 --- Makefile | 4 +++ README.md | 4 +-- include/memory_pool.h | 5 ++-- src/memory_pool.c | 58 ++++++++++++++++++++++++++++++++----- tests/test_mp.c | 67 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 127 insertions(+), 11 deletions(-) diff --git a/Makefile b/Makefile index fb49415..380c923 100644 --- a/Makefile +++ b/Makefile @@ -67,6 +67,10 @@ test_set: $(BUILD_DIR) $(CC) $(TESTFLAGS) $(SRC_DIR)/set.c $(SRC_DIR)/hash_table.c $(TEST_DIR)/test_set.c -o $(BUILD_DIR)/test_set ./$(BUILD_DIR)/test_set +test_mp: $(BUILD_DIR) + $(CC) $(TESTFLAGS) $(SRC_DIR)/memory_pool.c $(TEST_DIR)/test_mp.c -o $(BUILD_DIR)/test_mp + ./$(BUILD_DIR)/test_mp + # Run all tests test_all: test_bt test_bst test_ga test_ll test_ht test_stack test_queue test_heap test_set diff --git a/README.md b/README.md index ae1fa68..71eb8b3 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # struccs -A lightweight, generic C library of fundamental data structures — built from scratch with a focus on memory management, performance, and type-agnostic design. +A lightweight, generic C library of data structures and low-level memory management tools — built from scratch with a focus on memory management, performance, and type-agnostic design. -> Version 0.11.8 +> Version 0.12.8 No dependencies. No standard container libraries. Just `void*`, function pointers, and manual memory management. diff --git a/include/memory_pool.h b/include/memory_pool.h index f0a0617..35b3304 100644 --- a/include/memory_pool.h +++ b/include/memory_pool.h @@ -33,9 +33,10 @@ struct MemoryPool { int block_size; }; -void mp_create(struct MemoryPool *pool); +struct MemoryPool *mp_create(int num_blocks, int block_size); void *mp_alloc(struct MemoryPool *pool); void mp_free(struct MemoryPool *pool, void *ptr); -void mp_stats(struct MemoryPool *pool); +void mp_print(struct MemoryPool *pool); +void mp_discard(struct MemoryPool *pool); #endif \ No newline at end of file diff --git a/src/memory_pool.c b/src/memory_pool.c index f558b6e..e9033f0 100644 --- a/src/memory_pool.c +++ b/src/memory_pool.c @@ -1,25 +1,51 @@ +/** + * memory_pool.c + * Implementation of a fixed-size memory pool allocator backed by a free list. + * Brendan Dileo - 2026 + */ + #include #include #include #include #include "memory_pool.h" -void mp_create(struct MemoryPool *pool) { +/** + * Creates a MemoryPool backed by a single contiguous memory region. + * Initializes metadata for fixed-size block allocation. + */ +struct MemoryPool *mp_create(int num_blocks, int block_size) { + struct MemoryPool *pool = malloc(sizeof(struct MemoryPool)); + if (pool == NULL) return NULL; + + pool->memory = malloc(MEMORY_SIZE(block_size, num_blocks)); + if (pool->memory == NULL) { + free(pool); + return NULL; + } + + pool->num_blocks = num_blocks; + pool->block_size = block_size; + pool->num_free_blocks = num_blocks; + memset(pool->memory, 0, MEMORY_SIZE(pool->block_size, pool->num_blocks)); pool->first_free = (struct MemoryBlockHeader*) pool->memory; - struct MemoryBlockHeader* current_block = pool->first_free; + struct MemoryBlockHeader *current_block = pool->first_free; for (int i = 0; i < pool->num_blocks - 1; i++) { current_block->next = (struct MemoryBlockHeader *)((char *)(current_block + 1) + pool->block_size); current_block = current_block->next; - pool->num_free_blocks++; } current_block->next = NULL; - pool->num_free_blocks++; + return pool; } -void *mp_alloc(struct MemoryPool *pool) { +/** + * Allocates a block from the memory pool and returns a pointer + * to its usable data region. + */ +void *mp_alloc(struct MemoryPool *pool) { void *data = NULL; if (pool->num_free_blocks > 0) { @@ -30,8 +56,13 @@ void *mp_alloc(struct MemoryPool *pool) { return data; } -void mp_free(struct MemoryPool *pool, void *ptr) { +/** + * Frees a block of memory and reinserts it into the pool's free list. + */ +void mp_free(struct MemoryPool *pool, void *ptr) { if (pool->num_free_blocks < pool->num_blocks) { + + // Convert user pointer back to block header struct MemoryBlockHeader *block = (struct MemoryBlockHeader *)((char *) ptr - sizeof(struct MemoryBlockHeader)); block->next = pool->first_free; pool->first_free = block; @@ -39,7 +70,10 @@ void mp_free(struct MemoryPool *pool, void *ptr) { } } -void mp_stats(struct MemoryPool *pool) { +/** + * Prints the addresses of all free memory blocks in the pool. + */ +void mp_print(struct MemoryPool *pool) { printf("Number of free blocks: %d\n", pool->num_free_blocks); struct MemoryBlockHeader *block = pool->first_free; @@ -48,3 +82,13 @@ void mp_stats(struct MemoryPool *pool) { block = block->next; } } + +/** + * Frees the memory pool and all memory it allocated. + */ +void mp_discard(struct MemoryPool *pool) { + if (pool != NULL) { + free(pool->memory); + free(pool); + } +} diff --git a/tests/test_mp.c b/tests/test_mp.c index e69de29..337c801 100644 --- a/tests/test_mp.c +++ b/tests/test_mp.c @@ -0,0 +1,67 @@ +/** + * test_mp.c + * Test suite for the fixed-size MemoryPool allocator. + * Brendan Dileo - 2026 + */ + +#include +#include +#include +#include +#include "test_utils.h" +#include "memory_pool.h" + +#define NUM_OF_BLOCKS 8 +#define BLOCK_SIZE 16 + +void test_mp_create(void); +void test_mp_alloc(void); +void test_mp_free(void); + +int main(void) { + TEST(test_mp_create); + TEST(test_mp_alloc); + TEST(test_mp_free); + return 0; +} + +void test_mp_create(void) { + struct MemoryPool *pool = mp_create(NUM_OF_BLOCKS, BLOCK_SIZE); + ASSERT_NOT_NULL(pool); + ASSERT_NOT_NULL(pool->memory); + + ASSERT_EQ(pool->num_blocks, NUM_OF_BLOCKS); + ASSERT_EQ(pool->num_free_blocks, NUM_OF_BLOCKS); + ASSERT_EQ(pool->block_size, BLOCK_SIZE); + + mp_discard(pool); + pool = NULL; +} + +void test_mp_alloc(void) { + struct MemoryPool *pool = mp_create(NUM_OF_BLOCKS, BLOCK_SIZE); + ASSERT_NOT_NULL(pool); + + void *block_one = mp_alloc(pool); + + ASSERT_NOT_NULL(block_one); + ASSERT_EQ(pool->num_free_blocks, NUM_OF_BLOCKS - 1); + + mp_discard(pool); + pool = NULL; +} + +void test_mp_free(void) { + struct MemoryPool *pool = mp_create(NUM_OF_BLOCKS, BLOCK_SIZE); + ASSERT_NOT_NULL(pool); + + void *block_one = mp_alloc(pool); + ASSERT_NOT_NULL(block_one); + ASSERT_EQ(pool->num_free_blocks, NUM_OF_BLOCKS - 1); + + mp_free(pool, block_one); + ASSERT_EQ(pool->num_free_blocks, NUM_OF_BLOCKS); + + mp_discard(pool); + pool = NULL; +} From 714751b9da0b73ea2005cb8a164734a7beb99a1a Mon Sep 17 00:00:00 2001 From: Brendan Dileo <124395674+brendanddev@users.noreply.github.com> Date: Fri, 10 Jul 2026 18:13:48 -0400 Subject: [PATCH 04/15] Start string implementation and bump version --- README.md | 2 +- include/str.h | 33 +++++++++++++++++ src/str.c | 101 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 135 insertions(+), 1 deletion(-) create mode 100644 include/str.h create mode 100644 src/str.c diff --git a/README.md b/README.md index 71eb8b3..ded42c5 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ A lightweight, generic C library of data structures and low-level memory management tools — built from scratch with a focus on memory management, performance, and type-agnostic design. -> Version 0.12.8 +> Version 0.13.0 No dependencies. No standard container libraries. Just `void*`, function pointers, and manual memory management. diff --git a/include/str.h b/include/str.h new file mode 100644 index 0000000..7d7c110 --- /dev/null +++ b/include/str.h @@ -0,0 +1,33 @@ +// str.h +// Header for the string type implementation +// Brendan Dileo - 2026 + +#ifndef STR_H +#define STR_H + +#include + +#define INITIAL_CAPACITY 8 + +// Defines the String struct type +// `data` is a pointer to where the buffer is stored in memory +// `length` is the number of characters currently stored (excluding '\0') +// `initial_capacity` is the starting number of characters the buffer can store +// `capacity` is the current number of characters the buffer can store +typedef struct String { + char *data; + size_t length; + size_t initial_capacity; + size_t capacity; +} String; + +String* str_create(const char *initial); +bool str_append(String *str, const char *data); +bool str_contains(String *str, const char *substr); +int str_compare(String *a, String *b); +String* str_copy(String *src); +size_t str_length(const char *src); +void str_print(String *str); +void str_discard(String *str); + +#endif diff --git a/src/str.c b/src/str.c new file mode 100644 index 0000000..b681f53 --- /dev/null +++ b/src/str.c @@ -0,0 +1,101 @@ +// str.c +// Implementation of a string +// Brendan Dileo - 2026 + +#include +#include +#include "str.h" + + +String* str_create(const char *initial) { + String *str = malloc(sizeof(String)); + if (str == NULL) return NULL; + + str->initial_capacity = INITIAL_CAPACITY; + str->capacity = INITIAL_CAPACITY; + + if (initial == NULL) { + str->data = malloc(sizeof(char) * str->initial_capacity); + if (str->data == NULL) { + free(str); + return NULL; + } + + // Dereference to set the first character stored in the buffer to null terminator + *str->data = '\0'; + str->length = 0; + + } else { + size_t length = str_length(initial); + resize(str, length); + + + } + + return str; +} + +// // Otherwsie need to copy the string passed in, going to the address of where the string points to, +// // and copy it over using memcpy +// } else { +// char *current = initial; +// int num = 0; +// while (*current != '\0') { +// current++; +// num++; +// } + +// // If the size of the string passed in is larger than initial capacity, +// // need to resize +// if (num + 1 > INITIAL_CAPACITY) { +// resize(str); +// memcpy(str->data, initial, num + 1); + +// // Otherwise just copy in +// } else { +// memcpy(str->data, initial, num + 1); +// } +// } + +// return str; +// } + + + + +// Returns the length of the string +size_t str_length(const char *src) { + const char *current = src; + size_t length = 0; + + while (*current != '\0') { + current++; + length++; + } + + return length; +} + + +// bool str_append(String *str, const char *data); +// bool str_contains(String *str, const char *substr); +// int str_compare(String *a, String *b); +// String* str_copy(String *src); +// int str_length(String *src); +// void str_print(String *str); +// void str_discard(String *str); + + +// Resizes the internal buffer when capacity is reached +static void resize(String *str, size_t size) { + size_t new_capacity = str->capacity * 2; + + while (size > new_capacity) { + new_capacity = new_capacity * 2; + } + + char *tmp = realloc(str->data, (new_capacity + 1) * sizeof(char)); + + str->capacity = new_capacity; + str->data = tmp; +} From 2991e67b933894c645803975f3c0c2f48c6564d9 Mon Sep 17 00:00:00 2001 From: Brendan Dileo <124395674+brendanddev@users.noreply.github.com> Date: Wed, 5 Aug 2026 20:55:43 -0400 Subject: [PATCH 05/15] Implement string v1 --- include/str.h | 6 +++ src/str.c | 141 ++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 113 insertions(+), 34 deletions(-) diff --git a/include/str.h b/include/str.h index 7d7c110..1400328 100644 --- a/include/str.h +++ b/include/str.h @@ -25,8 +25,14 @@ String* str_create(const char *initial); bool str_append(String *str, const char *data); bool str_contains(String *str, const char *substr); int str_compare(String *a, String *b); +bool str_equals(String *a, String *b); +String* str_concat(String *str, String *substr); String* str_copy(String *src); +String* str_to_upper(String *str); +String* str_to_lower(String *str); +String *str_trim(String *str); size_t str_length(const char *src); +size_t str_len(String *str); void str_print(String *str); void str_discard(String *str); diff --git a/src/str.c b/src/str.c index b681f53..8a2f99e 100644 --- a/src/str.c +++ b/src/str.c @@ -4,9 +4,13 @@ #include #include +#include #include "str.h" +// Prototypes +static bool resize(String *str, size_t size); +// Creates a new String String* str_create(const char *initial) { String *str = malloc(sizeof(String)); if (str == NULL) return NULL; @@ -15,7 +19,7 @@ String* str_create(const char *initial) { str->capacity = INITIAL_CAPACITY; if (initial == NULL) { - str->data = malloc(sizeof(char) * str->initial_capacity); + str->data = malloc(sizeof(char) * (str->initial_capacity + 1)); if (str->data == NULL) { free(str); return NULL; @@ -27,41 +31,91 @@ String* str_create(const char *initial) { } else { size_t length = str_length(initial); - resize(str, length); + str->data = malloc(sizeof(char) * (str->initial_capacity + 1)); + if (str->data == NULL) { + free(str); + return NULL; + } + if (resize(str, length)) { + memcpy(str->data, initial, length + 1); + str->length = length; + } else { + free(str); + return NULL; + } } return str; } -// // Otherwsie need to copy the string passed in, going to the address of where the string points to, -// // and copy it over using memcpy -// } else { -// char *current = initial; -// int num = 0; -// while (*current != '\0') { -// current++; -// num++; -// } - -// // If the size of the string passed in is larger than initial capacity, -// // need to resize -// if (num + 1 > INITIAL_CAPACITY) { -// resize(str); -// memcpy(str->data, initial, num + 1); +// Adds more characters to the end of an existing String +bool str_append(String *str, const char *data) { + size_t append_length = str_length(data); + size_t new_length = str->length + append_length; -// // Otherwise just copy in -// } else { -// memcpy(str->data, initial, num + 1); -// } -// } + if (resize(str, new_length)) { + char *buffer_end = str->data + str->length; + memcpy(buffer_end, data, append_length + 1); + str->length = new_length; + return true; + } else { + return false; + } +} -// return str; -// } +// Checks if a string contains a given substring +bool str_contains(String *str, const char *substr) { + char *curr_str = str->data; + char *curr_substr = substr; + + while (*curr_str != '\0' && *curr_substr != '\0') { + curr_substr = substr; + while (*curr_str == *curr_substr) { + curr_str++; + curr_substr++; + if (*curr_substr == '\0') { + return true; + } + } + curr_str++; + } + return false; +} +// Compares two strings lexicographically +// Returns 0 if equal, 1 if a > b, -1 if a < b, +// or the character difference if lengths differ +int str_compare(String *a, String *b) { + char *curr_a = a->data; + char *curr_b = b->data; + + while (*curr_a != '\0' && *curr_b != '\0') { + if (*curr_a > *curr_b) { + return 1; + } else if (*curr_a < *curr_b) { + return -1; + } else { + curr_a++; + curr_b++; + } + } + // return 0; + return *curr_a - *curr_b; +} +// Returns a copy of the provided string +String* str_copy(String *src) { + if (src == NULL) return NULL; + String *new_str = str_create(src->data); + return new_str; +} +// Checks if two strings are equal +bool str_equals(String *a, String *b) { + return str_compare(a, b) == 0; +} // Returns the length of the string size_t str_length(const char *src) { @@ -76,26 +130,45 @@ size_t str_length(const char *src) { return length; } +// Returns the length of string based on length member +size_t str_len(String *str) { + return str->length; +} + +// Prints the contents of the String +void str_print(String *str) { + char *current = str->data; + while (*current != '\0') { + printf("%c", *current); + current++; + } +} -// bool str_append(String *str, const char *data); -// bool str_contains(String *str, const char *substr); -// int str_compare(String *a, String *b); -// String* str_copy(String *src); -// int str_length(String *src); -// void str_print(String *str); -// void str_discard(String *str); +// Releases the String buffer and the String struct memory +void str_discard(String *str) { + if (str != NULL) { + free(str->data); + free(str); + } +} +// Ensures the internal buffer has enough capacity for the requested number of characters +static bool resize(String *str, size_t size) { + if (size <= str->capacity) { + return true; + } -// Resizes the internal buffer when capacity is reached -static void resize(String *str, size_t size) { size_t new_capacity = str->capacity * 2; - while (size > new_capacity) { new_capacity = new_capacity * 2; } char *tmp = realloc(str->data, (new_capacity + 1) * sizeof(char)); + if (tmp == NULL) { + return false; + } str->capacity = new_capacity; str->data = tmp; + return true; } From b55be85e821590509d65136e793ddf48d16e3f74 Mon Sep 17 00:00:00 2001 From: Brendan Dileo <124395674+brendanddev@users.noreply.github.com> Date: Mon, 10 Aug 2026 14:58:08 -0400 Subject: [PATCH 06/15] Add Result type --- README.md | 28 ++++++++++++++++------------ include/result.h | 26 ++++++++++++++++++++++++++ src/result.c | 39 +++++++++++++++++++++++++++++++++++++++ tests/test_result.c | 0 tests/test_utils.h | 2 ++ 5 files changed, 83 insertions(+), 12 deletions(-) create mode 100644 include/result.h create mode 100644 src/result.c create mode 100644 tests/test_result.c diff --git a/README.md b/README.md index ded42c5..85f7dc1 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,16 @@ # struccs -A lightweight, generic C library of data structures and low-level memory management tools — built from scratch with a focus on memory management, performance, and type-agnostic design. +A lightweight, generic C library, built from scratch, providing data structures, common types, and low-level memory tools. No dependencies, no standard container libraries. Just `void*`, function pointers, and manual memory management. > Version 0.13.0 -No dependencies. No standard container libraries. Just `void*`, function pointers, and manual memory management. - --- ## Why struccs? -Most C developers end up rewriting the same data structures over and over, a linked list here, a hash table there, each tied to one specific type. **struccs** gives you a set of generic, reusable structures that work with *any* data type via `void*` pointers and user-supplied function pointers (for comparison, hashing, freeing, etc.), so you write the logic once and reuse it everywhere. - -It's also a deep dive into how these structures work under the hood, useful if you're learning C, data structures, or memory management. +C gives you the building blocks but none of the infrastructure. struccs fills that gap — +generic data structures, common types, and memory tools, all handwritten, all +dependency-free, all designed to work together. --- @@ -65,6 +63,12 @@ For full examples per structure, see [docs/USAGE.md](docs/USAGE.md). Each structure ships with its own header and source file, and can be used independently or together via the umbrella header. +## Types + +| Type | Description | Header | +|-----------|--------------------------|----------| +| `String` | Dynamic string type | `str.h` | + ## Memory Management | Component | Description | Header | @@ -106,12 +110,12 @@ Each structure has its own test target (`test_ll`, `test_bt`, `test_hash`, etc.) ## Project Structure struccs/ -├── include/ # Public headers -├── src/ # Implementations -├── tests/ # Test suites -├── docs/ # Documentation -│ ├── USAGE.md # Usage examples for each structure -│ ├── API.md # Full API reference +├── include/ # Public headers +├── src/ # Implementations +├── tests/ # Test suites +├── docs/ # Documentation +│ ├── USAGE.md # Usage examples for each structure +│ ├── API.md # Full API reference │ └── PERFORMANCE.md # Benchmarks & complexity notes ├── Makefile ├── LICENSE diff --git a/include/result.h b/include/result.h new file mode 100644 index 0000000..f16d0c4 --- /dev/null +++ b/include/result.h @@ -0,0 +1,26 @@ +// result.h +// Header for the generic Result type used for error handling. +// Brendan Dileo - 2026 + +#ifndef RESULT_H +#define RESULT_H + +#include +#include + +// Result is only valid when error == NULL +// NULL means success +typedef struct Result { + void *value; + const char *error; +} Result; + +Result result_ok(void *value); +Result result_err(const char *message); +bool result_is_ok(const Result *r); +bool result_is_err(const Result *r); +void *result_value(const Result *r); +const char *result_error(const Result *r); +void *result_unwrap_or(const Result *r, void *default_value); + +#endif diff --git a/src/result.c b/src/result.c new file mode 100644 index 0000000..4b346e7 --- /dev/null +++ b/src/result.c @@ -0,0 +1,39 @@ +// result.c +// A simple Result type that pairs a value with an error message. +// Brendan Dileo - 2026 + +#include "result.h" + +Result result_ok(void *value) { + Result res; + res.value = value; + res.error = NULL; + return res; +} + +Result result_err(const char *message) { + Result res; + res.value = NULL; + res.error = message; + return res; +} + +bool result_is_ok(const Result *res) { + return res != NULL && res->error == NULL; +} + +bool result_is_err(const Result *res) { + return res == NULL || res->error != NULL; +} + +void *result_value(const Result *res) { + return result_is_err(res) ? res->error : NULL; +} + +const char *result_error(const Result *res) { + return result_is_err(res) ? res->error : NULL; +} + +void *result_unwrap_or(const Result *res, void *default_value) { + return result_is_ok(res) ? res->value : default_value; +} diff --git a/tests/test_result.c b/tests/test_result.c new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_utils.h b/tests/test_utils.h index 2332870..5048a8e 100644 --- a/tests/test_utils.h +++ b/tests/test_utils.h @@ -1,4 +1,6 @@ +// test_utils.h // Lightweight test macros for struccs test suites +// Brendan Dileo - 2026 #define ASSERT_EQ(actual, expected) \ if ((actual) != (expected)) { \ From 6b0e1a651cd118a78f31b5427720d39144bae420 Mon Sep 17 00:00:00 2001 From: Brendan Dileo <124395674+brendanddev@users.noreply.github.com> Date: Mon, 10 Aug 2026 15:00:57 -0400 Subject: [PATCH 07/15] Add Result tests --- Makefile | 4 ++ tests/test_result.c | 89 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+) diff --git a/Makefile b/Makefile index 380c923..8be7971 100644 --- a/Makefile +++ b/Makefile @@ -71,6 +71,10 @@ test_mp: $(BUILD_DIR) $(CC) $(TESTFLAGS) $(SRC_DIR)/memory_pool.c $(TEST_DIR)/test_mp.c -o $(BUILD_DIR)/test_mp ./$(BUILD_DIR)/test_mp +test_result: $(BUILD_DIR) + $(CC) $(TESTFLAGS) $(SRC_DIR)/result.c $(TEST_DIR)/test_result.c -o $(BUILD_DIR)/test_result + ./$(BUILD_DIR)/test_result + # Run all tests test_all: test_bt test_bst test_ga test_ll test_ht test_stack test_queue test_heap test_set diff --git a/tests/test_result.c b/tests/test_result.c index e69de29..b0b5780 100644 --- a/tests/test_result.c +++ b/tests/test_result.c @@ -0,0 +1,89 @@ +// test_result.c +// Tests for the generic Result type +// Brendan Dileo - 2026 + +#include +#include "test_utils.h" +#include "result.h" + +// Prototypes +void test_result_ok(void); +void test_result_err(void); +void test_result_is_ok_is_err(void); +void test_result_value_and_error(void); +void test_result_unwrap_or(void); +void test_result_null_safety(void); + +int main(void) { + TEST(test_result_ok); + TEST(test_result_err); + TEST(test_result_is_ok_is_err); + TEST(test_result_value_and_error); + TEST(test_result_unwrap_or); + TEST(test_result_null_safety); + + return 0; +} + +void test_result_ok(void) { + int x = 42; + Result r = result_ok(&x); + + ASSERT_TRUE(result_is_ok(&r)); + ASSERT_FALSE(result_is_err(&r)); + ASSERT_NOT_NULL(result_value(&r)); + ASSERT_NULL(result_error(&r)); + ASSERT_EQ(result_value(&r), &x); +} + +void test_result_err(void) { + Result r = result_err("something failed"); + + ASSERT_TRUE(result_is_err(&r)); + ASSERT_FALSE(result_is_ok(&r)); + ASSERT_NULL(result_value(&r)); + ASSERT_NOT_NULL(result_error(&r)); +} + +void test_result_is_ok_is_err(void) { + int x = 10; + Result ok = result_ok(&x); + Result err = result_err("error"); + + ASSERT_TRUE(result_is_ok(&ok)); + ASSERT_FALSE(result_is_err(&ok)); + + ASSERT_TRUE(result_is_err(&err)); + ASSERT_FALSE(result_is_ok(&err)); +} + +void test_result_value_and_error(void) { + int x = 99; + Result ok = result_ok(&x); + Result err = result_err("failed"); + + ASSERT_EQ(result_value(&ok), &x); + ASSERT_NULL(result_error(&ok)); + + ASSERT_NULL(result_value(&err)); + ASSERT_NOT_NULL(result_error(&err)); +} + +void test_result_unwrap_or(void) { + int x = 10; + int y = 20; + + Result ok = result_ok(&x); + Result err = result_err("fail"); + + ASSERT_EQ(result_unwrap_or(&ok, &y), &x); + ASSERT_EQ(result_unwrap_or(&err, &y), &y); +} + +void test_result_null_safety(void) { + // Passing NULL should be treated as an error state + ASSERT_FALSE(result_is_ok(NULL)); + ASSERT_TRUE(result_is_err(NULL)); + ASSERT_NULL(result_value(NULL)); + ASSERT_NULL(result_error(NULL)); +} From a6d679c75e9d51870dfeea4760cb92b58a3dda43 Mon Sep 17 00:00:00 2001 From: Brendan Dileo <124395674+brendanddev@users.noreply.github.com> Date: Mon, 10 Aug 2026 15:05:25 -0400 Subject: [PATCH 08/15] Fix issue with Result value handler --- README.md | 2 +- src/result.c | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 85f7dc1..7ddc904 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ A lightweight, generic C library, built from scratch, providing data structures, common types, and low-level memory tools. No dependencies, no standard container libraries. Just `void*`, function pointers, and manual memory management. -> Version 0.13.0 +> Version 0.13.1 --- diff --git a/src/result.c b/src/result.c index 4b346e7..86bf18b 100644 --- a/src/result.c +++ b/src/result.c @@ -27,11 +27,14 @@ bool result_is_err(const Result *res) { } void *result_value(const Result *res) { - return result_is_err(res) ? res->error : NULL; + return result_is_ok(res) ? res->value : NULL; } const char *result_error(const Result *res) { - return result_is_err(res) ? res->error : NULL; + if (res == NULL) { + return NULL; + } + return res->error; } void *result_unwrap_or(const Result *res, void *default_value) { From 2dfed138d2be4f6ad57d20d09d45a372113eac41 Mon Sep 17 00:00:00 2001 From: Brendan Dileo <124395674+brendanddev@users.noreply.github.com> Date: Mon, 10 Aug 2026 16:11:21 -0400 Subject: [PATCH 09/15] Update String with new functions, add string tests --- Makefile | 5 +- include/result.h | 3 +- src/str.c | 84 +++++++++++++++- tests/test_str.c | 244 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 332 insertions(+), 4 deletions(-) create mode 100644 tests/test_str.c diff --git a/Makefile b/Makefile index 8be7971..ab6d853 100644 --- a/Makefile +++ b/Makefile @@ -71,11 +71,14 @@ test_mp: $(BUILD_DIR) $(CC) $(TESTFLAGS) $(SRC_DIR)/memory_pool.c $(TEST_DIR)/test_mp.c -o $(BUILD_DIR)/test_mp ./$(BUILD_DIR)/test_mp +test_str: $(BUILD_DIR) + $(CC) $(TESTFLAGS) $(SRC_DIR)/str.c $(TEST_DIR)/test_str.c -o $(BUILD_DIR)/test_str + ./$(BUILD_DIR)/test_str + test_result: $(BUILD_DIR) $(CC) $(TESTFLAGS) $(SRC_DIR)/result.c $(TEST_DIR)/test_result.c -o $(BUILD_DIR)/test_result ./$(BUILD_DIR)/test_result -# Run all tests test_all: test_bt test_bst test_ga test_ll test_ht test_stack test_queue test_heap test_set sandbox: $(LIB) diff --git a/include/result.h b/include/result.h index f16d0c4..78eae3b 100644 --- a/include/result.h +++ b/include/result.h @@ -8,8 +8,7 @@ #include #include -// Result is only valid when error == NULL -// NULL means success +// error == NULL indicates success, non-NULL indicates failure typedef struct Result { void *value; const char *error; diff --git a/src/str.c b/src/str.c index 8a2f99e..c9fac17 100644 --- a/src/str.c +++ b/src/str.c @@ -9,6 +9,7 @@ // Prototypes static bool resize(String *str, size_t size); +static bool is_whitespace(char c); // Creates a new String String* str_create(const char *initial) { @@ -117,6 +118,83 @@ bool str_equals(String *a, String *b) { return str_compare(a, b) == 0; } +// Concatenates two strings, and returns a copy +String* str_concat(String *str, String *substr) { + if (str == NULL) { + return NULL; + } + String *concat_str = str_create(str->data); + if (str_append(concat_str, substr->data)) { + return concat_str; + } else { + str_discard(concat_str); + return NULL; + } +} + +// Converts a string to uppercase, and returns a copy of that string +String* str_to_upper(String *str) { + if (str == NULL) { + return NULL; + } + + String *str_upper = str_create(str->data); + char *current = str_upper->data; + while (*current != '\0') { + if (*current >= 'a' && *current <= 'z') { + *current = *current - 32; + } + current++; + } + return str_upper; +} + +// Converts a string to lowercase, and returns a copy of that string +String* str_to_lower(String *str) { + if (str == NULL) { + return NULL; + } + + String *str_lower = str_create(str->data); + char *current = str_lower->data; + while (*current != '\0') { + if (*current >= 'A' && *current <= 'Z') { + *current = *current + 32; + } + current++; + } + return str_lower; +} + +// Trims whitespace from the start and end of a string, returns a copy +String *str_trim(String *str) { + if (str == NULL) { + return NULL; + } + + char *start = str->data; + char *end = str->data + str_len(str) - 1; + + while (is_whitespace(*start) && *start != '\0') { + start++; + } + + while (is_whitespace(*end) && end > start) { + end--; + } + + char *tempbuff = malloc(end - start + 2); + if (tempbuff == NULL) { + return NULL; + } + memcpy(tempbuff, start, end - start + 1); + tempbuff[end - start + 1] = '\0'; + + String *trimmed_str = str_create(tempbuff); + free(tempbuff); + return trimmed_str; +} + // Returns the length of the string size_t str_length(const char *src) { const char *current = src; @@ -126,7 +204,6 @@ size_t str_length(const char *src) { current++; length++; } - return length; } @@ -172,3 +249,8 @@ static bool resize(String *str, size_t size) { str->data = tmp; return true; } + +// Returns true if char is whitespace, otherwise false +static bool is_whitespace(char c) { + return c == ' ' || c == '\t' || c == '\n' || c == '\r'; +} diff --git a/tests/test_str.c b/tests/test_str.c new file mode 100644 index 0000000..19ceb30 --- /dev/null +++ b/tests/test_str.c @@ -0,0 +1,244 @@ +// test_str.c +// Tests for the String type +// Brendan Dileo - 2026 + +#include +#include +#include "test_utils.h" +#include "str.h" + +// Prototypes +void test_str_create_empty(void); +void test_str_create_with_content(void); +void test_str_create_null(void); +void test_str_append(void); +void test_str_append_empty(void); +void test_str_append_triggers_resize(void); +void test_str_contains(void); +void test_str_contains_not_found(void); +void test_str_compare(void); +void test_str_equals(void); +void test_str_copy(void); +void test_str_concat(void); +void test_str_to_upper(void); +void test_str_to_lower(void); +void test_str_trim(void); +void test_str_length_and_len(void); +void test_str_discard_null(void); +void test_str_stress_append(void); +void test_str_null_inputs(void); + +int main(void) { + TEST(test_str_create_empty); + TEST(test_str_create_with_content); + TEST(test_str_create_null); + TEST(test_str_append); + TEST(test_str_append_empty); + TEST(test_str_append_triggers_resize); + TEST(test_str_contains); + TEST(test_str_contains_not_found); + TEST(test_str_compare); + TEST(test_str_equals); + TEST(test_str_copy); + TEST(test_str_concat); + TEST(test_str_to_upper); + TEST(test_str_to_lower); + TEST(test_str_trim); + TEST(test_str_length_and_len); + TEST(test_str_discard_null); + TEST(test_str_stress_append); + TEST(test_str_null_inputs); + + return 0; +} + +void test_str_create_empty(void) { + String *s = str_create(NULL); + ASSERT_NOT_NULL(s); + ASSERT_EQ(str_len(s), 0); + ASSERT_TRUE(s->data[0] == '\0'); + str_discard(s); +} + +void test_str_create_with_content(void) { + String *s = str_create("hello"); + ASSERT_NOT_NULL(s); + ASSERT_EQ(str_len(s), 5); + ASSERT_TRUE(strcmp(s->data, "hello") == 0); + str_discard(s); +} + +void test_str_create_null(void) { + String *s = str_create(NULL); + ASSERT_NOT_NULL(s); + ASSERT_EQ(str_len(s), 0); + str_discard(s); +} + +void test_str_append(void) { + String *s = str_create("hello"); + ASSERT_TRUE(str_append(s, " world")); + ASSERT_EQ(str_len(s), 11); + ASSERT_TRUE(strcmp(s->data, "hello world") == 0); + str_discard(s); +} + +void test_str_append_empty(void) { + String *s = str_create("test"); + ASSERT_TRUE(str_append(s, "")); + ASSERT_EQ(str_len(s), 4); + ASSERT_TRUE(strcmp(s->data, "test") == 0); + str_discard(s); +} + +void test_str_append_triggers_resize(void) { + String *s = str_create("a"); + for (int i = 0; i < 100; i++) { + ASSERT_TRUE(str_append(s, "x")); + } + ASSERT_EQ(str_len(s), 101); + str_discard(s); +} + +void test_str_contains(void) { + String *s = str_create("hello world"); + ASSERT_TRUE(str_contains(s, "hello")); + ASSERT_TRUE(str_contains(s, "world")); + ASSERT_TRUE(str_contains(s, "lo wo")); + ASSERT_FALSE(str_contains(s, "")); // empty substring is usually considered found + str_discard(s); +} + +void test_str_contains_not_found(void) { + String *s = str_create("hello"); + ASSERT_FALSE(str_contains(s, "xyz")); + ASSERT_FALSE(str_contains(s, "helloo")); + str_discard(s); +} + +void test_str_compare(void) { + String *a = str_create("apple"); + String *b = str_create("banana"); + String *c = str_create("apple"); + + ASSERT_TRUE(str_compare(a, b) < 0); + ASSERT_TRUE(str_compare(b, a) > 0); + ASSERT_EQ(str_compare(a, c), 0); + + str_discard(a); + str_discard(b); + str_discard(c); +} + +void test_str_equals(void) { + String *a = str_create("test"); + String *b = str_create("test"); + String *c = str_create("Test"); + + ASSERT_TRUE(str_equals(a, b)); + ASSERT_FALSE(str_equals(a, c)); + + str_discard(a); + str_discard(b); + str_discard(c); +} + +void test_str_copy(void) { + String *orig = str_create("copy me"); + String *copy = str_copy(orig); + + ASSERT_NOT_NULL(copy); + ASSERT_TRUE(str_equals(orig, copy)); + ASSERT_TRUE(orig->data != copy->data); // different buffers + + str_discard(orig); + str_discard(copy); +} + +void test_str_concat(void) { + String *a = str_create("hello"); + String *b = str_create(" world"); + String *c = str_concat(a, b); + + ASSERT_NOT_NULL(c); + ASSERT_EQ(str_len(c), 11); + ASSERT_TRUE(strcmp(c->data, "hello world") == 0); + + str_discard(a); + str_discard(b); + str_discard(c); +} + +void test_str_to_upper(void) { + String *s = str_create("Hello World 123"); + String *upper = str_to_upper(s); + + ASSERT_NOT_NULL(upper); + ASSERT_TRUE(strcmp(upper->data, "HELLO WORLD 123") == 0); + + str_discard(s); + str_discard(upper); +} + +void test_str_to_lower(void) { + String *s = str_create("Hello World 123"); + String *lower = str_to_lower(s); + + ASSERT_NOT_NULL(lower); + ASSERT_TRUE(strcmp(lower->data, "hello world 123") == 0); + + str_discard(s); + str_discard(lower); +} + +void test_str_trim(void) { + String *s = str_create(" hello world \t\n"); + String *trimmed = str_trim(s); + + ASSERT_NOT_NULL(trimmed); + ASSERT_TRUE(strcmp(trimmed->data, "hello world") == 0); + ASSERT_EQ(str_len(trimmed), 11); + + str_discard(s); + str_discard(trimmed); +} + +void test_str_length_and_len(void) { + ASSERT_EQ(str_length("hello"), 5); + ASSERT_EQ(str_length(""), 0); + + String *s = str_create("testing"); + ASSERT_EQ(str_len(s), 7); + ASSERT_EQ(str_length(s->data), 7); + + str_discard(s); +} + +void test_str_discard_null(void) { + str_discard(NULL); + ASSERT_TRUE(1); // just for assertion +} + +void test_str_stress_append(void) { + String *s = str_create(NULL); + + for (int i = 0; i < 1000; i++) { + ASSERT_TRUE(str_append(s, "a")); + } + ASSERT_EQ(str_len(s), 1000); + + for (int i = 0; i < 500; i++) { + ASSERT_TRUE(str_append(s, "bc")); + } + ASSERT_EQ(str_len(s), 2000); + + str_discard(s); +} + +void test_str_null_inputs(void) { + ASSERT_NULL(str_copy(NULL)); // should not crash + ASSERT_NULL(str_concat(NULL, NULL)); + ASSERT_NULL(str_to_upper(NULL)); + ASSERT_NULL(str_to_lower(NULL)); + ASSERT_NULL(str_trim(NULL)); +} From dcaed8bec49cfe2ff169e389d24ba48d222d63f3 Mon Sep 17 00:00:00 2001 From: Brendan Dileo <124395674+brendanddev@users.noreply.github.com> Date: Mon, 10 Aug 2026 16:14:18 -0400 Subject: [PATCH 10/15] Fix issue with double advancing in str_contains --- README.md | 2 +- src/str.c | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7ddc904..fb37dac 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ A lightweight, generic C library, built from scratch, providing data structures, common types, and low-level memory tools. No dependencies, no standard container libraries. Just `void*`, function pointers, and manual memory management. -> Version 0.13.1 +> Version 0.13.2 --- diff --git a/src/str.c b/src/str.c index c9fac17..13c6359 100644 --- a/src/str.c +++ b/src/str.c @@ -70,9 +70,11 @@ bool str_append(String *str, const char *data) { bool str_contains(String *str, const char *substr) { char *curr_str = str->data; char *curr_substr = substr; + char *match_start = NULL; while (*curr_str != '\0' && *curr_substr != '\0') { curr_substr = substr; + match_start = curr_str + 1; while (*curr_str == *curr_substr) { curr_str++; curr_substr++; @@ -80,7 +82,7 @@ bool str_contains(String *str, const char *substr) { return true; } } - curr_str++; + curr_str = match_start; } return false; } From 86b821231c37175cbdb44072d9c9200c79806bb5 Mon Sep 17 00:00:00 2001 From: Brendan Dileo <124395674+brendanddev@users.noreply.github.com> Date: Mon, 10 Aug 2026 16:41:43 -0400 Subject: [PATCH 11/15] Bump version and cleanup old comments --- README.md | 7 +- src/binary_tree.c | 115 ++++++----------------- src/binarysearch_tree.c | 185 ++++++++++--------------------------- src/generic_array.c | 197 ++++++---------------------------------- 4 files changed, 106 insertions(+), 398 deletions(-) diff --git a/README.md b/README.md index fb37dac..e89961c 100644 --- a/README.md +++ b/README.md @@ -2,15 +2,13 @@ A lightweight, generic C library, built from scratch, providing data structures, common types, and low-level memory tools. No dependencies, no standard container libraries. Just `void*`, function pointers, and manual memory management. -> Version 0.13.2 +> Version 0.14.0 --- ## Why struccs? -C gives you the building blocks but none of the infrastructure. struccs fills that gap — -generic data structures, common types, and memory tools, all handwritten, all -dependency-free, all designed to work together. +C gives you the building blocks but none of the infrastructure. struccs fills that gap — generic data structures, common types, and memory tools, all handwritten, all dependency-free, all designed to work together. --- @@ -67,6 +65,7 @@ Each structure ships with its own header and source file, and can be used indepe | Type | Description | Header | |-----------|--------------------------|----------| +| `Result` | Operation result wrapping a value and error state | `result.h` | | `String` | Dynamic string type | `str.h` | ## Memory Management diff --git a/src/binary_tree.c b/src/binary_tree.c index 8fd0298..74cc7e4 100644 --- a/src/binary_tree.c +++ b/src/binary_tree.c @@ -1,8 +1,7 @@ -/** - * binary_tree.c - * A generic implementation of a complete binary tree. - * Brendan Dileo - 2025 - */ +// binary_tree.c +// A generic implementation of a complete binary tree. +// Brendan Dileo - 2025 + #include #include #include @@ -17,22 +16,16 @@ static int parent_index(int index); static int left_child_index(int index); static int right_child_index(int index); -/** - * Creates a new binary tree, storing elements of the provided size. - */ +// Creates a new binary tree struct BinaryTree* bt_create(size_t element_size) { - - // Allocate memory for the binary tree struct itself and handle allocation failure struct BinaryTree *tree = malloc(sizeof(struct BinaryTree)); if (tree == NULL) return NULL; - // Set initial capacity, length and size of elements stored in the tree tree->capacity = 12; tree->length = 0; tree->element_size = element_size; - // Allocate memory for the underlying array that will hold the tree elements - // and handle allocation failure + // Allocate memory for the underlying array used to hold elements in the tree tree->elements = malloc(tree->capacity * tree->element_size); if (tree->elements == NULL) { free(tree); @@ -41,30 +34,23 @@ struct BinaryTree* bt_create(size_t element_size) { return tree; } -/** - * Inserts a value at the next available position in the tree. - */ +// Insert a value into the trees next available position void bt_insert(struct BinaryTree *tree, void *value) { - - // Check if a resize is needed if (tree->length >= tree->capacity) { resize(tree); } - // Get the pointer to the next available position in the tree - // then copy the value into the trees array + + // Grab pointer to next available position in the tree, and copy value into it void *curr = (char*) tree->elements + tree->length * tree->element_size; memcpy(curr, value, tree->element_size); tree->length++; } -/** - * Removes the element at the provided index in the binary tree - */ +// Removes the element at the provided index in the binary tree void bt_remove(struct BinaryTree *tree, int index) { if (index < 0 || index >= tree->length) return; - // Get pointer to the element being removed and - // the last element in the tree + // Grab pointer to element being removed and last element in tree void *curr = (char*) tree->elements + index * tree->element_size; void *last = (char*) tree->elements + (tree->length - 1) * tree->element_size; @@ -73,22 +59,17 @@ void bt_remove(struct BinaryTree *tree, int index) { tree->length--; } -/** - * Returns a pointer to the element at the provided index in the tree - */ +// Returns a pointer to the element at the provided index in the tree bool bt_get(struct BinaryTree *tree, int index, void *out) { if (index < 0 || index >= tree->length) return false; - // Get pointer to the element at the provided index, and - // copy raw bytes between memory locations to return the value to user + // Grab pointer to element at provided index, copy raw bytes between memory locations void *current = (char*) tree->elements + index * tree->element_size; memcpy(out, current, tree->element_size); return true; } -/** - * Sets the value of the element at the provided index to the provided value - */ +// Sets the value of the element at the provided index to the provided value bool bt_set(struct BinaryTree *tree, int index, void *value) { if (index < 0 || index >= tree->length) return false; @@ -97,9 +78,7 @@ bool bt_set(struct BinaryTree *tree, int index, void *value) { return true; } -/** - * Checks if the binary tree contains the provided value - */ +// Checks if the binary tree contains the provided value bool bt_contains(struct BinaryTree *tree, void *value, int (*comparator)(void*, void*)) { for (int i = 0; i < tree->length; i++) { void *current = (char*) tree->elements + i * tree->element_size; @@ -110,10 +89,8 @@ bool bt_contains(struct BinaryTree *tree, void *value, int (*comparator)(void*, return false; } -/** - * Finds and returns the index of the provided value in the tree - * If not found, returns -1 - */ +// Attempts to find and return the index of the provided value in the tree, +// If not found, returns -1 int bt_find(struct BinaryTree *tree, void *value, int (*comparator)(void*, void*)) { for (int i = 0; i < tree->length; i++) { void *current = (char*) tree->elements + i * tree->element_size; @@ -124,23 +101,17 @@ int bt_find(struct BinaryTree *tree, void *value, int (*comparator)(void*, void* return -1; } -/** - * Returns the height of the binary tree, which is the largest number of edges in a path from - * the root to a leaf node - */ +// Returns the height of the tree (largest number of edges in a path from root to a leaf node) int bt_height(struct BinaryTree *tree) { if (bt_isempty(tree)) return -1; - // Store the height of the tree and the last element in the tree + // Store the height and last element in the tree int height = 0; int last = tree->length - 1; - // Loop starting from the last element, while the root is not reached, - // to count edges from the deepest leaf back to the root + // Start loop from last element, while root is not reached, count edges + // from deepest leaf back to root while (last > 0) { - - // Calculate parent index from the current element and - // reassign the current element to point to the parent to continue moving up the tree int parent = parent_index(last); height++; last = parent; @@ -148,19 +119,15 @@ int bt_height(struct BinaryTree *tree) { return height; } -/** - * Returns the number of nodes with no children (leaf) in the binary tree - */ +// Returns the number of nodes with no children (leaf) in the binary tree int bt_leaves(struct BinaryTree *tree) { if (bt_isempty(tree)) return 0; - // Track index of current element and number of leaves int current = 0; int leaves = 0; // Continue looping starting from the root element to the last while (current < tree->length) { - // Calculate left child index and check if the child exists // If it does, we know that the current node is not a leaf // If it dosent, then we know the current node is a leaf, @@ -173,20 +140,15 @@ int bt_leaves(struct BinaryTree *tree) { return leaves; } -/** - * Public interface for printing the contents of the binary tree - */ +// Public interface for printing the contents of the binary tree void bt_print(struct BinaryTree *tree, void (*print_fn)(void*)) { if (bt_isempty(tree)) return; print_recursive(tree, 0, 0, print_fn); } -/** - * Recursive helper for printing the contents of the binary tree in a tree-like structure - */ +// Recursive helper for printing the contents of the binary tree in a tree-like structure static void print_recursive(struct BinaryTree *tree, int index, int depth, void (*print_fn)(void*)) { - - // Base case - we reached an index outside of the arrays bounds + // Base case if (index >= tree->length) return; // Get pointer to the element at the provided index @@ -201,37 +163,27 @@ static void print_recursive(struct BinaryTree *tree, int index, int depth, void print_recursive(tree, left_child_index(index), depth + 1, print_fn); } -/** - * Clears the contents of the binary tree - */ +// Clears the contents of the binary tree void bt_clear(struct BinaryTree *tree) { tree->length = 0; } -/** - * Returns the current size (length) of the binary tree - */ +// Returns the current size (length) of the binary tree int bt_size(struct BinaryTree *tree) { return tree->length; } -/** - * Returns the current capacity of the tree's underlying array - */ +// Returns the current capacity of the tree's underlying array int bt_capacity(struct BinaryTree *tree) { return tree->capacity; } -/** - * Checks if the binary tree is empty - */ +// Checks if the binary tree is empty bool bt_isempty(struct BinaryTree *tree) { return tree->length == 0; } -/** - * Frees the memory previously allocated for the binary tree - */ +// Frees memory previously allocated for the binary tree void bt_discard(struct BinaryTree *tree) { if (tree != NULL) { free(tree->elements); @@ -240,9 +192,6 @@ void bt_discard(struct BinaryTree *tree) { } -// Private helper functions - linkage limited to this file - - // Resizes the binary trees internal array to twice the current capacity static bool resize(struct BinaryTree *tree) { @@ -262,9 +211,6 @@ static bool resize(struct BinaryTree *tree) { // Swaps the raw bytes at the two memory locations pointed to by the provided pointers static void swap(void *a, void *b, size_t element_size) { - - // Create a variable length array to hold element_size bytes - // on the stack char temp[element_size]; // Copy the raw bytes between the memory locations to swap values @@ -287,4 +233,3 @@ static int left_child_index(int index) { static int right_child_index(int index) { return 2 * index + 2; } - diff --git a/src/binarysearch_tree.c b/src/binarysearch_tree.c index aa58e90..494aa12 100644 --- a/src/binarysearch_tree.c +++ b/src/binarysearch_tree.c @@ -1,22 +1,23 @@ -/** - * binarysearch_tree.c - * A generic implementation of a binary search tree - * Brendan Dileo - 2025 - */ +// binarysearch_tree.c +// A generic implementation of a binary search tree +// Brendan Dileo - 2025 + #include #include #include #include #include "binarysearch_tree.h" -/** - * Defines the Node struct type - */ +// Defines the Node struct type +// `value` is a pointer to the memory where the value is stored +// `value_size` is the number of bytes the value will occupy in memory\ +// `left` is a pointer to the current nodes left child +// `right` is a pointer to the current nodes right child typedef struct Node { - void *value; // Pointer to the memory where the value is stored - size_t value_size; // Number of bytes the value will occupy in memory pointed to for the value - struct Node *left; // Pointer to the current nodes left child in the tree - struct Node *right; // Pointer to the current nodes right child in the tree + void *value; + size_t value_size; + struct Node *left; + struct Node *right; } Node; @@ -38,70 +39,47 @@ static void bst_discard_node(struct Node *node); static void bst_discard_all_nodes(struct BinarySearchTree *tree); static void bst_discard_all_nodes_rec(struct Node *root); -/** - * Creates a new binary search tree - */ -struct BinarySearchTree* bst_create() { - // Allocate memory for the tree itself and handle allocation failure +// Creates a new binary search tree +struct BinarySearchTree* bst_create() { struct BinarySearchTree *tree = malloc(sizeof(struct BinarySearchTree)); if (tree == NULL) return NULL; - // Set the initial root pointer and length of the tree tree->root = NULL; tree->length = 0; - return tree; } -/** - * Public interface for inserting a new value into the binary search tree - */ +// Public interface for inserting a new value into the binary search tree void bst_insert(struct BinarySearchTree *tree, void *value, size_t value_size, int (*compare)(void*, void*)) { tree->root = bst_insert_rec(tree->root, value, value_size, compare); tree->length++; } -/** - * Recursive helper function for inserting a value into the binary search tree - */ +// Recursive helper for inserting a value into the binary search tree static struct Node* bst_insert_rec(struct Node *root, void *value, size_t value_size, int (*compare)(void*, void*)) { - // Base case - if the root node is empty, return it to insert if (root == NULL) { return bst_create_node(value, value_size); - - // Compare memory blocks pointed to by the value being inserted and the current node value - // to see if we need to recurse left in the tree } else if (compare(value, root->value) < 0) { - - // Recurse to the left of the tree to insert the value, then assign the returned - // subtree back to the root nodes left child to maintain the connection, and return - // root so the caller can update their pointer to this subtree root->left = bst_insert_rec(root->left, value, value_size, compare); return root; - // Otherwise recurse right in the tree } else { root->right = bst_insert_rec(root->right, value, value_size, compare); return root; } } -/** - * Public interface for removing a value from the binary search tree - */ +// Public interface for removing a value from the binary search tree void bst_remove(struct BinarySearchTree *tree, void *value, int (*compare)(void*, void*)) { bool found = false; tree->root = bst_remove_rec(tree->root, value, compare, &found); if (found) tree->length--; } -/** - * Recursive helper for removing a value from the binary search tree - */ +// Recursive helper for removing a value from the binary search tree static Node* bst_remove_rec(struct Node *root, void *value, int (*compare)(void*, void*), bool *found) { - - // Base case - we found an empty node + // Base case: we found an empty node if (root == NULL) { *found = false; return root; @@ -142,7 +120,6 @@ static Node* bst_remove_rec(struct Node *root, void *value, int (*compare)(void* // Otherwise node has two children } else { - // Find the successor node struct Node *successor = bst_get_successor(root->right); @@ -155,35 +132,27 @@ static Node* bst_remove_rec(struct Node *root, void *value, int (*compare)(void* // Didnt find the value to remove, need to recurse the tree further } else { - // Check if we need to recurse to the left in the tree if (compare(value, root->value) < 0) { - root->left = bst_remove_rec(root->left, value, compare, found); return root; // Otherwise recurse to the right } else { - root->right = bst_remove_rec(root->right, value, compare, found); return root; } } } -/** - * Public interface for checking if the binary search tree contains a value - */ +// Public interface for checking if the binary search tree contains a value bool bst_contains(struct BinarySearchTree *tree, void *value, int (*compare)(void*, void*)) { if (bst_isempty(tree)) return false; return bst_contains_rec(tree->root, value, compare); } -/** - * Recursive helper for checking if the tree contains a given value - */ +// Recursive helper for checking if the tree contains a given value static bool bst_contains_rec(struct Node *root, void *value, int (*compare)(void*, void*)) { - // Base case - if we have reached an empty node without finding value its not in the tree if (root == NULL) return false; @@ -201,17 +170,13 @@ static bool bst_contains_rec(struct Node *root, void *value, int (*compare)(void } } -/** - * Public interface for searching the binary search tree for a value - */ +// Public interface for searching the binary search tree for a value struct Node* bst_search(struct BinarySearchTree *tree, void *value, int (*compare)(void*, void*)) { if (bst_isempty(tree)) return NULL; return bst_search_rec(tree->root, value, compare); } -/** - * Recursive helper for searching for a value in the tree - */ +// Recursive helper for searching for a value in the tree static struct Node* bst_search_rec(struct Node *root, void *value, int (*compare)(void*, void*)) { // Base case - if we have reached an empty node without finding value, its not in the tree @@ -232,54 +197,40 @@ static struct Node* bst_search_rec(struct Node *root, void *value, int (*compare } } -/** - * Public interface for finding the minumum value in the binary search tree - */ +// Public interface for finding the minumum value in the binary search tree void* bst_min(struct BinarySearchTree *tree) { if (bst_isempty(tree)) return NULL; return bst_min_rec(tree->root); } -/** - * Recursive helper for finding the minumum value in the binary search tree - */ +// Recursive helper for finding the minumum value in the binary search tree static void* bst_min_rec(struct Node *root) { - // Base case - reached an empty node in the left subtree if (root->left == NULL) return root->value; return bst_min_rec(root->left); } -/** - * Public interface for finding the maximum value in the binary search tree - */ +// Public interface for finding the maximum value in the binary search tree void* bst_max(struct BinarySearchTree *tree) { if (bst_isempty(tree)) return NULL; return bst_max_rec(tree->root); } -/** - * Recursive helper for finding the maximum value in the binary search tree - */ +// Recursive helper for finding the maximum value in the binary search tree static void* bst_max_rec(struct Node *root) { if (root->right == NULL) return root->value; return bst_max_rec(root->right); } -/** - * Public interface for finding the height of the binary search tree - */ +// Public interface for finding the height of the binary search tree int bst_height(struct BinarySearchTree *tree) { if (bst_isempty(tree)) return 0; return bst_height_rec(tree->root); } -/** - * Recursive helper for finding the height of the binary search tree, which is the length of the - * longest path from the root down to the deepest leaf node - */ +// Recursive helper for finding the height of the binary search tree +// (length of longest path from root down to deepest leaf node) static int bst_height_rec(struct Node *root) { - // Base case - we reached an empty node if (root == NULL) return 0; @@ -293,18 +244,13 @@ static int bst_height_rec(struct Node *root) { return right_height + 1; } -/** - * Public interface for printing the contents of the binary search tree - */ +// Public interface for printing the contents of the binary search tree void bst_print(struct BinarySearchTree *tree, void (* print_fn)(void*)) { bst_print_rec(tree->root, print_fn, 0); } -/** - * Recursive helper function for printing the contents of the binary search tree in order traversal - */ +// Recursive helper function for printing the contents of the binary search tree in order traversal static void bst_print_rec(struct Node *root, void (* print_fn)(void*), int depth) { - // Base case - if we hit a empty node, weve hit the end of the branch if (root == NULL) return; @@ -320,101 +266,75 @@ static void bst_print_rec(struct Node *root, void (* print_fn)(void*), int depth bst_print_rec(root->right, print_fn, depth + 1); } -/** - * Public interface for traversing and printing the contents of the tree in-order - */ +// Public interface for traversing and printing the contents of the tree in-order void bst_inorder(struct BinarySearchTree *tree, void (* print_fn)(void*)) { if (bst_isempty(tree)) return; bst_inorder_rec(tree->root, print_fn); } -/** - * Recursive helper for traversing and printing the contents of the tree in-order - */ +// Recursive helper for traversing and printing the contents of the tree in-order static void bst_inorder_rec(struct Node *root, void (* print_fn)(void*)) { - // Base case - hit an empty node if (root == NULL) return; - // Traverse the tree, first traversing the left side of the tree, - // the root node, then the right + // Traverse the tree, first left side, then root, then right bst_inorder_rec(root->left, print_fn); print_fn(root->value); bst_inorder_rec(root->right, print_fn); } -/** - * Public interface for traversing and printing the contents of the tree post-order - */ +// Public interface for traversing and printing the contents of the tree post-order void bst_postorder(struct BinarySearchTree *tree, void (* print_fn)(void*)) { if (bst_isempty(tree)) return; bst_postorder_rec(tree->root, print_fn); } -/** - * Recursive helper for traversing and printing the contents of the tree post-order - */ +// Recursive helper for traversing and printing the contents of the tree post-order static void bst_postorder_rec(struct Node *root, void (* print_fn)(void*)) { - // Base case - hit an empty node if (root == NULL) return; - // Traverse the left side of the tree first, then the right side, - // then print the root + // Traverse left, then right, then print root bst_postorder_rec(root->left, print_fn); bst_postorder_rec(root->right, print_fn); print_fn(root->value); } -/** - * Public interface for traversing and printing the contents of the tree pre-order - */ +// Public interface for traversing and printing the contents of the tree pre-order void bst_preorder(struct BinarySearchTree *tree, void (* print_fn)(void*)) { if (bst_isempty(tree)) return; bst_preorder_rec(tree->root, print_fn); } -/** - * Recursive helper for traversing and printing the contents of the tree pre-order - */ +// Recursive helper for traversing and printing the contents of the tree pre-order static void bst_preorder_rec(struct Node *root, void (* print_fn)(void*)) { - // Base case - hit an empty node if (root == NULL) return; - // Print the root node first, then recurse the left side of tree, - // then right side + // Print root node first, then left, then right print_fn(root->value); bst_preorder_rec(root->left, print_fn); bst_preorder_rec(root->right, print_fn); } -/** - * Returns the number of nodes in the binary search tree - */ +// Returns the number of nodes in the binary search tree int bst_size(struct BinarySearchTree *tree) { return tree->length; } -/** - * Checks whether the binary search tree is empty - */ +// Checks whether the binary search tree is empty bool bst_isempty(struct BinarySearchTree *tree) { return tree->length == 0; } -/** - * Clears the contents of the binary search tree - */ +// Clears the contents of the binary search tree void bst_clear(struct BinarySearchTree *tree) { bst_discard_all_nodes(tree); tree->root = NULL; tree->length = 0; } -/** - * Frees the memory previously allocated by all nodes in the tree and the tree itself - */ +// Frees the memory previously allocated by all nodes in the tree and the tree itself void bst_discard(struct BinarySearchTree *tree) { if (tree != NULL) { bst_discard_all_nodes(tree); @@ -423,9 +343,6 @@ void bst_discard(struct BinarySearchTree *tree) { } -// Private helper functions - linkage limited to this file - - // Finds the minumum node in the tree, which when passed a node on the right subtree, returns the // successor static struct Node* bst_get_successor(struct Node *root) { @@ -435,28 +352,20 @@ static struct Node* bst_get_successor(struct Node *root) { // Creates a new node to be stored in the tree static struct Node* bst_create_node(void *value, size_t value_size) { - - // Allocate memory for the node itself and handle allocation failure struct Node *node = malloc(sizeof(struct Node)); if (node == NULL) return NULL; - // Set size of value to be stored in the node and - // initial left/right child pointers node->value_size = value_size; node->left = NULL; node->right = NULL; - // Allocate memory for the value itself stored in the node, store pointer to the address, and - // handle allocation failure node->value = malloc(node->value_size); if (node->value == NULL) { free(node); return NULL; } - // Copy the raw memory pointed to by the parameter into the memory pointed to by the nodes member memcpy(node->value, value, node->value_size); - return node; } @@ -475,8 +384,6 @@ static void bst_discard_all_nodes(struct BinarySearchTree *tree) { // Recursive helper function for freeing the memory allocated by each node in the tree static void bst_discard_all_nodes_rec(struct Node *root) { - - // Base case - hit an empty node if (root == NULL) return; // Free left and right child branches of the root node and diff --git a/src/generic_array.c b/src/generic_array.c index b519eef..e9eeca1 100644 --- a/src/generic_array.c +++ b/src/generic_array.c @@ -1,8 +1,7 @@ -/** - * generic_array.c - * A generic implementation of a resizing array. - * Brendan Dileo - 2025 - */ +// generic_array.c +// A generic implementation of a resizing array. +// Brendan Dileo - 2025 + #include #include #include @@ -16,16 +15,10 @@ static void shift_right(struct GenericArray *ga, int index); static void shift_left(struct GenericArray *ga, int index); static void swap(struct GenericArray *ga, void *val1, void *val2); -/** - * Initializes a new GenericArray - */ +// Initializes a new GenericArray struct GenericArray * ga_init(size_t item_size) { - - // Allocate memory for the struct struct GenericArray *ga = NULL; ga = malloc(sizeof(struct GenericArray)); - - // Handle allocation failure if (ga == NULL) { fprintf(stderr, "Memory allocation failed during initialization.\n"); exit(EXIT_FAILURE); @@ -36,49 +29,32 @@ struct GenericArray * ga_init(size_t item_size) { ga->length = 0; ga->item_size = item_size; - // Allocate memory for the internal array void *pTmp = malloc(item_size * ga->capacity); if (pTmp == NULL) { fprintf(stderr, "Memory allocation failed for internal array.\n"); - free(ga); // Free previously allocated struct to avoid memory leak + free(ga); exit(EXIT_FAILURE); } - ga->ptrData = pTmp; return ga; } -/** - * Adds an item to the end of the array - */ +// Adds an item to the end of the array bool ga_append(struct GenericArray *ga, void *itemPtr) { - - // Check if resize is needed if (ga->length == ga->capacity) { resize(ga); } - // Calculate the address where the new item should be stored - // Starting at the base of the internal array (ga->ptrData) - // Move forward by (length * item_size) bytes to reach the next free slot - // Gets casted to (char *) so the pointer arithmetic is done in bytes void *dest = (char *)ga->ptrData + ga->length * ga->item_size; - - // Copy the memory of the item into the calculated destination - // Takes the raw bytes starting at 'itemPtr' the item passed to the func, - // and copies them into the memory at the destination, which is the next free slot in the array memcpy(dest, itemPtr, ga->item_size); ga->length++; return true; } -/** - * Inserts a new item at a specified index within the GenericArray - */ +// Inserts a new item at a specified index within the GenericArray bool ga_add(struct GenericArray *ga, int index, void *in_ptr) { if (index < 0 || index > ga->length) return false; - // Check if resize is needed if (ga->length == ga->capacity) { resize(ga); } @@ -86,56 +62,35 @@ bool ga_add(struct GenericArray *ga, int index, void *in_ptr) { // Shift subsequent items to the right to make room for item being inserted shift_right(ga, index); - // Calculate the memory location for the item being inserted void *dest_ptr = (char *) ga->ptrData + index * ga->item_size; - - // Copy the memory of the item into destination and increment length memcpy(dest_ptr, in_ptr, ga->item_size); ga->length++; return true; } -/** - * Retrieves an item from a specified index in the array - */ +// Retrieves an item from a specified index in the array bool ga_get(struct GenericArray *ga, int index, void *out_ptr) { if (index < 0 || index >= ga->length) return false; - // Calculate the memory address of the item at the given index by - // Starting from the base pointer of the array (ga->ptrData) - // Casting to (char *) so pointer arithmetic is in bytes - // Adding index * item_size to move to the correct element - // And using memcpy to copy the bytes of that element into the output ptr void *dest = (char *)ga->ptrData + index * ga->item_size; memcpy(out_ptr, dest, ga->item_size); return true; } -/** - * Sets the value at the provided index - */ +// Sets the value at the provided index bool ga_set(struct GenericArray *ga, int index, void *in_ptr) { if (index < 0 || index >= ga->length) return false; - // Calculate the memory address of the item at the given index - // Start from base pointer which is the first byte of the allocated memory block - // and move the offset (index * item_size) in bytes from base pointer to find - // the specified index void *dest = (char *) ga->ptrData + index * ga->item_size; memcpy(dest, in_ptr, ga->item_size); return true; } -/** - * Find the index of a specified item in the array - * If not found, return -1 - */ +// Find the index of a specified item in the array, if not found, return -1 int ga_find(struct GenericArray *ga, void *item_ptr, bool (*comparator)(void*, void*)) { for (int i = 0; i < ga->length; i++) { void *curr = (char *) ga->ptrData + i * ga->item_size; - // Compare the item being searched for to the current item - // based on the caller provided definition if (comparator(item_ptr, curr)) { return i; } @@ -143,9 +98,7 @@ int ga_find(struct GenericArray *ga, void *item_ptr, bool (*comparator)(void*, v return -1; } -/** - * Removes the last item in the GenericArray - */ +// Removes the last item in the GenericArray bool ga_remove_last(struct GenericArray *ga) { if (!ga || ga->length == 0) return false; ga->length--; @@ -158,14 +111,11 @@ bool ga_remove_last(struct GenericArray *ga) { return true; } -/** - * Removes an item from the specified index in the GenericArray - */ +// Removes an item from the specified index in the GenericArray bool ga_remove_at(struct GenericArray *ga, int index) { if (index < 0 || index >= ga->length) return false; ga->length--; - // Check if memory is underutilized if (ga_usage(ga) < SHRINK_THRESHOLD) { printf("SHRINKING...\n"); shrink(ga); @@ -176,21 +126,11 @@ bool ga_remove_at(struct GenericArray *ga, int index) { return true; } -/** - * Checks if the GenericArray contains another item - * - * Takes a pointer to another function that defines how the two items will be compared, - * and another pointer to the target item we are searching the array for - */ +// Checks if the GenericArray contains another item bool ga_contains(struct GenericArray *ga, bool (*funcptr)(void*, void*), void *trgtptr) { for (int i = 0; i < ga->length; i++) { - // Current address = start of memory + index * item_size - // For the item at index i: Start at base address of the array - // Move forward i * item_size bytes to reach the i-th element void *curr = (char *) ga->ptrData + i * ga->item_size; - // If the user provided function for comparison returns true, - // items are equal and we return true if (funcptr(curr, trgtptr)) { return true; } @@ -198,43 +138,33 @@ bool ga_contains(struct GenericArray *ga, bool (*funcptr)(void*, void*), void *t return false; } -/** - * Clears the contents of the array by setting the length to 0. - * The allocated memory and existing data remain intact but are marked as unused, - * old values will be overwritten when new items are added - */ +// Clears the contents of the array by setting the length to 0. +// Allocated memory and existing data remain intact, but are marked as unused, +// old values will be overwritten when new items are added bool ga_clear(struct GenericArray *ga) { if (!ga) return false; ga->length = 0; return true; } -/** - * Returns the length of the GenericArray - */ +// Returns the length of the GenericArray int ga_size(struct GenericArray *ga) { return ga->length; } -/** - * Returns the current capacity of the GenericArray - */ +// Returns the current capacity of the GenericArray int ga_capacity(struct GenericArray *ga) { return ga->capacity; } -/** - * Calculates how full the array is internally based on the current number of elements - * and total allocated space - */ +// Calculates how full the array is internally based on current +// num of elements and total allocated space double ga_usage(struct GenericArray *ga) { double usage = (double) ga->length / ga->capacity * 100; return usage; } -/** - * Frees any memory allocated by the GenericArray internal array and struct - */ +// Frees any memory allocated by the GenericArray internal array and struct void ga_discard(struct GenericArray *ga) { if (ga != NULL) { free(ga->ptrData); @@ -242,9 +172,7 @@ void ga_discard(struct GenericArray *ga) { } } -/** - * Prints the current state of the GenericArray - */ +// Prints the current state of the GenericArray void ga_print(struct GenericArray *ga, void (* print_fn)(void*)) { if (ga->length == 0) return; for (int i = 0; i < ga->length; i++) { @@ -253,23 +181,14 @@ void ga_print(struct GenericArray *ga, void (* print_fn)(void*)) { } } -/** - * Bubble Sort implementation for an array of integers with a caller defined - * comparator function to determine how to order elements - */ +// Bubble Sort implementation for an array of integers with a caller defined comparator void ga_sort(struct GenericArray *ga, bool (* comparator)(void*, void*)) { bool is_swapped; for (int i = 0; i < ga->length - 1; i++) { - is_swapped = false; for (int j = 0; j < ga->length - i - 1; j++) { - - // Starting from the first address in the allocated memory pointed to by the base pointer (ga->ptrData) - // Calculate the address of the current item and the next item in the array based on the index bytes offset void *curr = (char *) ga->ptrData + j * ga->item_size; void *next = (char *) ga->ptrData + (j + 1) * ga->item_size; - - // Call the user defined comparator function through the function pointer if (comparator(curr, next)) { swap(ga, curr, next); is_swapped = true; @@ -282,15 +201,9 @@ void ga_sort(struct GenericArray *ga, bool (* comparator)(void*, void*)) { } } -/** - * Reverses items in-place within the array - */ +// Reverses items in-place within the array void ga_reverse(struct GenericArray *ga) { - - // Loop until the middle index of the array to swap front/back pairs in each iteration - // Once half the array is handled, the entire array is reversed for (int i = 0; i < ga->length / 2; i++) { - // Calculate the memory addresses of the pair of items being swapped, // which will be the item at index `i` and the item at index `length - i - 1` void *front = (char *) ga->ptrData + i * ga->item_size; @@ -299,46 +212,26 @@ void ga_reverse(struct GenericArray *ga) { } } -/** - * Makes and returns a deep copy of the GenericArray provided - */ +// Makes and returns a deep copy of the GenericArray provided struct GenericArray* ga_copy(struct GenericArray *ga) { - - // Allocate memory for the copy of the struct itself struct GenericArray *ga_copy = NULL; ga_copy = malloc(sizeof(struct GenericArray)); - // Allocate memory for the copy of the internal array void *new_ga_ptrData = NULL; new_ga_ptrData = malloc(ga->item_size * ga->capacity); - // Copy raw memory from the original structs memory location to the new location - // This copies all fields, including the pointer to the internal array, - // which will be immediately overriden to point to the new array memcpy(ga_copy, ga, sizeof(struct GenericArray)); - - // Copy raw memory from the original internal array to the new memory allocated for the copy of the internal array - // Where the size in bytes is based on the size of the original array memcpy(new_ga_ptrData, ga->ptrData, ga->item_size * ga->length); - - // Point the pointer to the memory allocated for the copy of the internal array - // instead of the pointer to the old data in memory ga_copy->ptrData = new_ga_ptrData; - return ga_copy; } -/** - * Searches for an item within a sorted array using a Binary Search implementation and a - * caller defined comparator function to determine how items in the array should be compared - */ +// Searches for an item within a sorted array using a Binary Search implementation bool ga_binary_find(struct GenericArray *ga, void *item_ptr, int (* comparator)(void*, void*)) { - // Initialize low and high indexes int low = 0; int high = ga->length - 1; while (low <= high) { - // Calculate the middle items address based on the middle index int mid_idx = low + (high - low) / 2; void *mid = (char *) ga->ptrData + mid_idx * ga->item_size; @@ -346,10 +239,8 @@ bool ga_binary_find(struct GenericArray *ga, void *item_ptr, int (* comparator)( if (cmp == 0) { return true; - // Check if `mid < item_ptr` } else if (cmp == -1) { low = mid_idx + 1; - // `mid > item_ptr` } else { high = mid_idx - 1; } @@ -358,99 +249,65 @@ bool ga_binary_find(struct GenericArray *ga, void *item_ptr, int (* comparator)( } -// Private helper functions - linkage limited to this file - - // Resizes the internal array when its limit is reached static void resize(struct GenericArray *ga) { - // Calculate new capacity int new_capacity = ga->capacity * 2; - // Reallocate more memory for the new array size void *tmp_ptr = NULL; tmp_ptr = realloc(ga->ptrData, new_capacity * ga->item_size); - - // Handle reallocation failure if (tmp_ptr == NULL) { fprintf(stderr, "Memory allocation failed during resizing!\n"); exit(EXIT_FAILURE); } - // Set new capacity and reassign pointer to memory ga->capacity = new_capacity; ga->ptrData = tmp_ptr; } // Shrinks the internal array when its underutilized by reallocating less memory static void shrink(struct GenericArray *ga) { - - // Determine new capacity - // If the current size is larger than the initial capacity, grow to 2.5x the current length - // Otherwise, grow to 1.5x the initial capacity to avoid reallocating too little memory int new_capacity; - // Length is larger than initial capacity (default = > 4) if (ga->length > ga->initial_capacity) { new_capacity = (int)(ga->length * 2.5); } else { new_capacity = (int)(ga->initial_capacity * 1.5); } - // Reallocate less memory void *tmp = NULL; tmp = realloc(ga->ptrData, new_capacity * ga->item_size); - - // Handle reallocation failure if (tmp == NULL) { fprintf(stderr, "Memory allocation failed during shrinking!\n"); exit(EXIT_FAILURE); } - - // Set new capacity and reassign pointer ga->capacity = new_capacity; ga->ptrData = tmp; } // Shifts items to the right, starting from the end to the index to make room for the item being added static void shift_right(struct GenericArray *ga, int index) { - // Loop from end to insertion point for (int i = ga->length - 1; i >= index; i--) { - // Calculate the memory address of the source item at index i and the destination item at index i + 1 void *src = (char *) ga->ptrData + i * ga->item_size; void *dest = (char *) ga->ptrData + (i + 1) * ga->item_size; - - // Copy the memory from src* into dest* effectively doing i + 1 = i memcpy(dest, src, ga->item_size); - // memmove(dest, src, ga->item_size); } } // Shifts items to the left starting at 'index', filling the gap created by the removed item static void shift_left(struct GenericArray *ga, int index) { - // Loop from index to end for (int i = index; i < ga->length - 1; i++) { - // Calculate memory address of the source item and the destination item void *src = (char *) ga->ptrData + (i + 1) * ga->item_size; void *dest = (char *) ga->ptrData + i * ga->item_size; - - // Copies the memory contents at the source location into the destination effectively moving the value into the new position memcpy(dest, src, ga->item_size); } } // Swaps two items in the array static void swap(struct GenericArray *ga, void *val1, void *val2) { - - // Declare and initialize a pointer to memory allocated for the temporary var void *temp = NULL; temp = malloc(ga->item_size); - // Copy the raw memory pointed to by `val1` into the memory location pointed to by the `temp` pointer memcpy(temp, val1, ga->item_size); memcpy(val1, val2, ga->item_size); memcpy(val2, temp, ga->item_size); - - // Free the memory allocated for the temporary pointer free(temp); } - - From 39ce1553e7718c6e68b785d32b4762995b183f79 Mon Sep 17 00:00:00 2001 From: Brendan Dileo <124395674+brendanddev@users.noreply.github.com> Date: Thu, 10 Sep 2026 18:35:04 -0400 Subject: [PATCH 12/15] chore: Cleanup code and unecessary comments --- include/binary_tree.h | 32 ++++- include/binarysearch_tree.h | 29 +++- include/generic_array.h | 44 ++++-- include/hash_table.h | 26 ++-- include/heap.h | 28 +++- include/linked_list.h | 33 ++++- include/memory_pool.h | 25 ++-- include/queue.h | 23 +-- include/result.h | 10 +- include/set.h | 11 +- include/stack.h | 20 ++- include/str.h | 30 +++- src/binary_tree.c | 59 ++------ src/binarysearch_tree.c | 124 ++++------------ src/generic_array.c | 60 +++----- src/hash_table.c | 184 ++++-------------------- src/heap.c | 127 +++-------------- src/linked_list.c | 276 ++++++------------------------------ src/memory_pool.c | 24 +--- src/queue.c | 65 +-------- src/set.c | 28 +--- src/stack.c | 68 ++------- src/str.c | 36 ++--- 23 files changed, 420 insertions(+), 942 deletions(-) diff --git a/include/binary_tree.h b/include/binary_tree.h index ca6c691..d754fc4 100644 --- a/include/binary_tree.h +++ b/include/binary_tree.h @@ -12,31 +12,49 @@ #include /** - * Defines the BinaryTree struct type. - * Elements are stored in a flat array using index-based parent/child relationships. + * Elements are stored in a flat array, parent/child links are derived from + * array indices (left = 2i+1, right = 2i+2), so the tree is always complete. */ typedef struct BinaryTree { - int length; // Number of elements in the tree - int capacity; // Capacity of the trees underlying array - void *elements; // Pointer to the memory where the elements are stored - size_t element_size; // Size of the elements stored in the tree + int length; // number of elements in use + int capacity; // slots allocated in the backing array + void *elements; + size_t element_size; } BinaryTree; +// Allocates an empty tree for elements of element_size bytes. Caller frees with +// bt_discard. Returns NULL on allocation failure. struct BinaryTree* bt_create(size_t element_size); +// Appends value at the next free slot, growing the backing array if full. +// Copies element_size bytes from value. void bt_insert(struct BinaryTree *tree, void *value); +// Removes the element at index by overwriting it with the last element, so +// neither insertion order nor tree shape below index is preserved. void bt_remove(struct BinaryTree *tree, int index); +// Copies the element at index into out. Returns false if index is out of range. bool bt_get(struct BinaryTree *tree, int index, void *out); +// Overwrites the element at index with value. Returns false if index is out of range. bool bt_set(struct BinaryTree *tree, int index, void *value); +// Returns true if any element compares equal (comparator returns 0) to value. O(n). bool bt_contains(struct BinaryTree *tree, void *value, int (*comparator)(void*, void*)); +// Returns the index of the first element equal to value, or -1 if absent. O(n). int bt_find(struct BinaryTree *tree, void *value, int (*comparator)(void*, void*)); +// Returns height in edges: root-only tree = 0, empty tree = -1. int bt_height(struct BinaryTree *tree); +// Returns the number of leaf nodes (nodes with no left child). int bt_leaves(struct BinaryTree *tree); +// Returns true if the tree holds no elements. bool bt_isempty(struct BinaryTree *tree); +// Returns the number of elements in the tree. int bt_size(struct BinaryTree *tree); +// Returns the current capacity of the backing array. int bt_capacity(struct BinaryTree *tree); +// Resets length to 0; the backing array is kept and reused by later inserts. void bt_clear(struct BinaryTree *tree); +// Prints the tree rotated 90 degrees (root at the left) using print_fn per element. void bt_print(struct BinaryTree *tree, void (*print_fn)(void*)); +// Frees the backing array and the tree struct. void bt_discard(struct BinaryTree *tree); -#endif \ No newline at end of file +#endif diff --git a/include/binarysearch_tree.h b/include/binarysearch_tree.h index a4e04d6..e6af815 100644 --- a/include/binarysearch_tree.h +++ b/include/binarysearch_tree.h @@ -11,33 +11,48 @@ #include #include -// Forward declaration of the struct Node stored in the binary search tree +// Node is opaque to callers, its layout lives in binarysearch_tree.c. struct Node; -/** - * Defines the BinarySearchTree struct type - */ typedef struct BinarySearchTree { - struct Node *root; // Pointer to the root of the tree - int length; // Number of nodes in the tree + struct Node *root; + int length; } BinarySearchTree; +// Allocates an empty tree. Caller frees with bst_discard. Returns NULL on failure. struct BinarySearchTree* bst_create(); +// Inserts a copy of value_size bytes from value, ordered by compare. Duplicates +// go to the right subtree. length is incremented unconditionally. void bst_insert(struct BinarySearchTree *tree, void *value, size_t value_size, int (*compare)(void*, void*)); +// Removes the first node equal to value (compare returns 0). No-op if absent. void bst_remove(struct BinarySearchTree *tree, void *value, int (*compare)(void*, void*)); +// Returns true if a node equal to value exists. bool bst_contains(struct BinarySearchTree *tree, void *value, int (*compare)(void*, void*)); +// Returns the node equal to value, or NULL if absent. The node is owned by the +// tree - do not free it or hold it past a remove/discard. struct Node* bst_search(struct BinarySearchTree *tree, void *value, int (*compare)(void*, void*)); +// Returns a borrowed pointer to the smallest stored value, or NULL if empty. void* bst_min(struct BinarySearchTree *tree); +// Returns a borrowed pointer to the largest stored value, or NULL if empty. void* bst_max(struct BinarySearchTree *tree); +// Returns height in nodes: empty tree = 0, root-only tree = 1. int bst_height(struct BinarySearchTree *tree); +// Prints the tree rotated onto its side (ascending top-to-bottom). void bst_print(struct BinarySearchTree *tree, void (* print_fn)(void*)); +// Applies print_fn to every value in in-order (sorted) order. void bst_inorder(struct BinarySearchTree *tree, void (* print_fn)(void*)); +// Applies print_fn to every value in post-order (children before parent). void bst_postorder(struct BinarySearchTree *tree, void (* print_fn)(void*)); +// Applies print_fn to every value in pre-order (parent before children). void bst_preorder(struct BinarySearchTree *tree, void (* print_fn)(void*)); +// Returns the number of nodes in the tree. int bst_size(struct BinarySearchTree *tree); +// Returns true if the tree has no nodes. bool bst_isempty(struct BinarySearchTree *tree); +// Frees every node but keeps the tree struct for reuse. void bst_clear(struct BinarySearchTree *tree); +// Frees every node and the tree struct. void bst_discard(struct BinarySearchTree *tree); -#endif \ No newline at end of file +#endif diff --git a/include/generic_array.h b/include/generic_array.h index 2ae40c5..77f8fde 100644 --- a/include/generic_array.h +++ b/include/generic_array.h @@ -1,47 +1,71 @@ /** - * generic_array.h + * generic_array.h * Header for the generic array implementation. * Brendan Dileo - 2025 */ #ifndef GENERIC_ARRAY_H #define GENERIC_ARRAY_H +// Percent-full below which the backing array is shrunk on removal. #define SHRINK_THRESHOLD 5.0 #include #include #include -/** - * Defines the GenericArray struct type. - */ typedef struct GenericArray { - void *ptrData; // Pointer to the memory where the array elements will be stored - size_t item_size; // The size of items stored in the array - int capacity; // The current capacity of the internal array - int initial_capacity; // The initial capacity - int length; // The length of the internal array + void *ptrData; + size_t item_size; + int capacity; + int initial_capacity; // floor used when shrinking + int length; } GenericArray; +// Allocates an empty array for items of item_size bytes. Caller frees with +// ga_discard. Exits the process on allocation failure. struct GenericArray* ga_init(size_t item_size); +// Copies item_size bytes from item_ptr onto the end, growing if full. Returns true. bool ga_append(struct GenericArray *ga, void *item_ptr); +// Inserts a copy of in_ptr at index (0..length), shifting later items right. +// Returns false if index is out of range. bool ga_add(struct GenericArray *ga, int index, void *in_ptr); +// Copies the item at index into out_ptr. Returns false if index is out of range. bool ga_get(struct GenericArray *ga, int index, void *out_ptr); +// Overwrites the item at index with in_ptr. Returns false if index is out of range. bool ga_set(struct GenericArray *ga, int index, void *in_ptr); +// Returns the index of the first item for which comparator(item_ptr, elem) is +// true, or -1 if none. int ga_find(struct GenericArray *ga, void *item_ptr, bool (* comparator)(void*, void*)); +// Drops the last item, shrinking the backing array if it falls below +// SHRINK_THRESHOLD. Returns false if already empty. bool ga_remove_last(struct GenericArray *ga); +// Drops the item at index and shifts later items left. Returns false if index +// is out of range. bool ga_remove_at(struct GenericArray *ga, int index); +// Returns true if any item satisfies funcptr(elem, trgtptr). bool ga_contains(struct GenericArray *ga, bool (*funcptr)(void*, void*), void *trgtptr); +// Resets length to 0; the backing array and its bytes are left in place. bool ga_clear(struct GenericArray *ga); +// Returns the number of items stored. int ga_size(struct GenericArray *ga); +// Returns the current capacity of the backing array. int ga_capacity(struct GenericArray *ga); +// Returns how full the array is, as a percentage (length / capacity * 100). double ga_usage(struct GenericArray *ga); +// Frees the backing array and the struct. void ga_discard(struct GenericArray *ga); +// Applies print_fn to each item in order. void ga_print(struct GenericArray *ga, void (* print_fn)(void*)); +// In-place bubble sort; comparator returns true when its first arg should sort +// after its second. void ga_sort(struct GenericArray *ga, bool (* comparator)(void*, void*)); +// Reverses the items in place. void ga_reverse(struct GenericArray *ga); +// Returns a newly allocated deep copy; caller frees it with ga_discard. struct GenericArray* ga_copy(struct GenericArray *ga); +// Binary search over an already-sorted array. comparator must return a value +// that is exactly -1, 0, or 1 (not just a sign). Returns true if found. bool ga_binary_find(struct GenericArray *ga, void *item_ptr, int (* comparator)(void*, void*)); -#endif \ No newline at end of file +#endif diff --git a/include/hash_table.h b/include/hash_table.h index 516016f..3048b08 100644 --- a/include/hash_table.h +++ b/include/hash_table.h @@ -11,30 +11,40 @@ #include #include -// Forward declaration of the struct Node stored in the buckets of the hash table +// Node is opaque to callers, its layout lives in hash_table.c. struct Node; -/** - * Defines the HashTable struct type - */ typedef struct HashTable { - struct Node **buckets; // Pointer to an array of node pointers, where each node pointer represents a bucket - int capacity; // Number of buckets in the hash table - int length; // Number of key/value pairs (nodes) in the hash table + struct Node **buckets; // array of `capacity` chain heads + int capacity; // number of buckets + int length; // number of key/value pairs stored } HashTable; +// Allocates an empty table. Caller frees with ht_discard. NULL on failure. struct HashTable* ht_create(); +// Inserts a copy of the key (ksize bytes) and value (vsize bytes), or overwrites +// the value in place if the key is already present. Returns true. bool ht_insert(struct HashTable *hashtable, void *key, size_t ksize, void *value, size_t vsize); +// Removes the entry for key. Returns false if the key is absent. bool ht_remove(struct HashTable *hashtable, void *key, size_t ksize); +// Copies the value for key into out. Returns false if the key is absent. bool ht_get(struct HashTable *hashtable, void *key, size_t ksize, void *out); +// Returns true if key is present. bool ht_contains(struct HashTable *hashtable, void *key, size_t ksize); +// Frees every entry and resets the table to its initial capacity. void ht_clear(struct HashTable *hashtable); +// Returns true if no entries are stored. bool ht_is_empty(struct HashTable *hashtable); +// Returns the number of key/value pairs stored. int ht_size(struct HashTable *hashtable); +// Returns the number of buckets. int ht_capacity(struct HashTable *hashtable); +// Returns length / capacity. float ht_load_factor(struct HashTable *hashtable); +// Applies print_fn(key, value) to every entry, bucket by bucket. void ht_print(struct HashTable *hashtable, void (* print_fn)(void*, void*)); +// Frees every entry, the bucket array, and the table struct. void ht_discard(struct HashTable *hashtable); -#endif \ No newline at end of file +#endif diff --git a/include/heap.h b/include/heap.h index 02750da..eaf2a5c 100644 --- a/include/heap.h +++ b/include/heap.h @@ -12,26 +12,42 @@ #include /** - * Defines the Heap (max) struct type. + * Array-backed binary heap. Ordering is whatever the compare callback passed to + * insert/remove defines, pass a max-first comparator for a max-heap. */ typedef struct Heap { - void *elements; // Pointer to an array in memory where the heaps elements will be stored - size_t element_size; // The size in bytes of each element stored in the heap - int capacity; // The capacity of the heap - int length; // The length of the heap + void *elements; + size_t element_size; + int capacity; + int length; } Heap; +// Allocates an empty heap for elements of element_size bytes. Caller frees with +// heap_discard. Returns NULL on allocation failure. struct Heap* heap_create(size_t element_size); +// Copies vsize bytes from value into the heap and sifts it up. vsize is expected +// to equal element_size. Returns false only if a needed resize fails. bool heap_insert(struct Heap *heap, void *value, size_t vsize, int (*compare)(void*, void*)); +// Copies the root element into out, moves the last element to the root and sifts +// it down. Returns false if the heap is empty. bool heap_remove(struct Heap *heap, void *out, int (*compare)(void*, void*)); +// Returns a borrowed pointer to the root element (no bounds check - do not call +// on an empty heap). void* heap_peek(struct Heap *heap); +// Returns the number of elements in the heap. int heap_size(struct Heap *heap); +// Returns the current capacity of the backing array. int heap_capacity(struct Heap *heap); +// Returns true if the heap has no elements. bool heap_isempty(struct Heap *heap); +// Resets length to 0; the backing array is kept. void heap_clear(struct Heap *heap); +// Frees the backing array and the heap struct. void heap_discard(struct Heap *heap); +// Applies print_fn to each element in array order (no tree layout). void heap_debug(struct Heap *heap, void (* print_fn)(void*)); +// Prints the heap rotated onto its side (root flush left, depth by indent). void heap_print(struct Heap *heap, void (* print_fn)(void*)); -#endif \ No newline at end of file +#endif diff --git a/include/linked_list.h b/include/linked_list.h index a3ce60b..bfdec0b 100644 --- a/include/linked_list.h +++ b/include/linked_list.h @@ -11,37 +11,56 @@ #include #include -// Forward declaration of the struct Node stored in the linked list +// Node is opaque to callers, its layout lives in linked_list.c. struct Node; -/** - * Defines the LinkedList (doubly) struct type - */ typedef struct LinkedList { int length; - struct Node *head; // Pointer to the head node (first element) - struct Node *tail; // Pointer to the tail node (last element) + struct Node *head; + struct Node *tail; } LinkedList; +// Allocates an empty list. Caller frees with ll_discard. Returns NULL on failure. struct LinkedList* ll_create(); +// Prepends a node holding a copy of item_size bytes from value. void ll_insert(struct LinkedList *list, void *value, size_t item_size); +// Appends a node holding a copy of item_size bytes from value. void ll_insert_tail(struct LinkedList *list, void *value, size_t item_size); +// Inserts a copy of value at position index (0..length). Out-of-range is a no-op. void ll_insert_at(struct LinkedList *list, void *value, size_t item_size, int index); +// Removes the head node. No-op on an empty list. void ll_remove(struct LinkedList *list); +// Removes the tail node. No-op on an empty list. void ll_remove_tail(struct LinkedList *list); +// Removes the node at index. Out-of-range is a no-op. void ll_remove_at(struct LinkedList *list, int index); +// Copies the value at index into out. Returns false if index is out of range. bool ll_get(struct LinkedList *list, int index, void *out); +// Overwrites the value at index from in. Returns false if index is out of range. bool ll_set(struct LinkedList *list, int index, void *in); +// Returns the index of the first node for which comparator(node_value, item) is +// true, or -1 if none. int ll_find(struct LinkedList *list, void *item, bool (* comparator)(void*, void*)); +// Returns true if any node satisfies comparator(node_value, item). bool ll_contains(struct LinkedList *list, void *item, bool (* comparator)(void*, void*)); +// Frees every node but keeps the list struct for reuse. void ll_clear(struct LinkedList *list); +// Returns a newly allocated deep copy; caller frees it with ll_discard. struct LinkedList* ll_copy(struct LinkedList *orig); +// Reverses the list in place (swapping each node's next/prev, then head/tail). void ll_reverse(struct LinkedList *list); +// Returns the number of nodes. int ll_size(struct LinkedList *list); +// Returns true if the list has no nodes. bool ll_is_empty(struct LinkedList *list); +// Frees every node and the list struct. void ll_discard(struct LinkedList *list); +// Applies print_fn to each value from head to tail. void ll_print(struct LinkedList *list, void (* print_fn)(void*)); +// Dumps each node's address and links. Assumes an int payload. void ll_debug(struct LinkedList *list); +// In-place bubble sort by value; comparator returns true when the first value +// should sort after the second. void ll_bsort(struct LinkedList *list, bool (* comparator)(void*, void*)); -#endif \ No newline at end of file +#endif diff --git a/include/memory_pool.h b/include/memory_pool.h index 35b3304..99484b7 100644 --- a/include/memory_pool.h +++ b/include/memory_pool.h @@ -11,32 +11,41 @@ #include #include +// Total bytes for nblocks blocks: each block carries a header plus bsize usable bytes. #define MEMORY_SIZE(bsize, nblocks) ((bsize) + sizeof(struct MemoryBlockHeader)) * (nblocks) /** - * Represents the header metadata stored at the start of each memory block. - * Blocks are linked together via the next pointer to track free blocks. + * Header prefixed to every block. While a block is free, next links it into the + * pool's free list; while it is handed out, the bytes after the header are the + * caller's to use. */ struct MemoryBlockHeader { struct MemoryBlockHeader *next; }; /** - * Represents a fixed-size memory pool. - * Manages a pre-allocated buffer split into equal-sized blocks. + * Fixed-size block allocator over one pre-allocated buffer. */ struct MemoryPool { - char *memory; - struct MemoryBlockHeader *first_free; + char *memory; // the single backing allocation + struct MemoryBlockHeader *first_free; // head of the free list int num_blocks; int num_free_blocks; - int block_size; + int block_size; // usable bytes per block, excluding the header }; +// Allocates a pool of num_blocks blocks, each with block_size usable bytes. +// Caller frees it with mp_discard. Returns NULL on allocation failure. struct MemoryPool *mp_create(int num_blocks, int block_size); +// Returns a pointer to a free block's usable region, or NULL if the pool is +// exhausted. The memory belongs to the pool; return it with mp_free, never free(). void *mp_alloc(struct MemoryPool *pool); +// Returns a block obtained from mp_alloc to the free list. ptr must be a live +// pointer from this pool. void mp_free(struct MemoryPool *pool, void *ptr); +// Prints the free-block count and the address of each free block. void mp_print(struct MemoryPool *pool); +// Frees the backing buffer and the pool struct. void mp_discard(struct MemoryPool *pool); -#endif \ No newline at end of file +#endif diff --git a/include/queue.h b/include/queue.h index 597825c..3a58064 100644 --- a/include/queue.h +++ b/include/queue.h @@ -11,27 +11,34 @@ #include #include -// Forward declaration of the struct Node stored in the queue +// Node is opaque to callers; its layout lives in queue.c. struct Node; -/** - * Defines the Queue struct type - */ typedef struct Queue { - struct Node *head; // Pointer to the head node of the queue - struct Node *tail; // Pointer to the tail node of the queue - int length; // The length of the queue + struct Node *head; + struct Node *tail; + int length; } Queue; +// Allocates an empty queue. Caller frees with queue_discard. NULL on failure. struct Queue* queue_create(); +// Appends a node holding a copy of item_size bytes from val (adds at the tail). void queue_enqueue(struct Queue *queue, void *val, size_t item_size); +// Copies the head value into out without removing it. False if the queue is empty. bool queue_peek(struct Queue *queue, void *out); +// Copies the head value into out and removes the head. False if the queue is empty. bool queue_dequeue(struct Queue *queue, void *out); +// Returns true if the queue has no nodes. bool queue_is_empty(struct Queue *queue); +// Returns the number of nodes in the queue. int queue_length(struct Queue *queue); +// Frees every node but keeps the queue struct for reuse. void queue_clear(struct Queue *queue); +// Frees every node and the queue struct. void queue_discard(struct Queue *queue); +// Applies print_fn to each value from head to tail. void queue_print(struct Queue *queue, void (* print_fn)(void*)); +// Prints the queue's length and its head/tail links for debugging. void queue_debug(struct Queue *queue); -#endif \ No newline at end of file +#endif diff --git a/include/result.h b/include/result.h index 78eae3b..4395953 100644 --- a/include/result.h +++ b/include/result.h @@ -8,18 +8,26 @@ #include #include -// error == NULL indicates success, non-NULL indicates failure +// error == NULL means success; a non-NULL error string means failure. Result is +// passed and returned by value; it does not own value or error. typedef struct Result { void *value; const char *error; } Result; +// Builds a success Result carrying value. Result result_ok(void *value); +// Builds a failure Result carrying message (borrowed; must outlive the Result). Result result_err(const char *message); +// True if r is non-NULL and holds no error. bool result_is_ok(const Result *r); +// True if r is NULL or holds an error. bool result_is_err(const Result *r); +// Returns r's value, or NULL if r is an error / NULL. void *result_value(const Result *r); +// Returns r's error string, or NULL if r is a success / NULL. const char *result_error(const Result *r); +// Returns r's value if ok, otherwise default_value. void *result_unwrap_or(const Result *r, void *default_value); #endif diff --git a/include/set.h b/include/set.h index 82305d1..641f6ba 100644 --- a/include/set.h +++ b/include/set.h @@ -13,19 +13,28 @@ #include "hash_table.h" /** - * Defines the Set struct type + * Thin wrapper over HashTable: stores each element as a key mapped to a dummy + * value. element_size is the byte width of every element. */ typedef struct Set { size_t element_size; HashTable *table; } Set; +// Allocates an empty set for elements of element_size bytes. Caller frees with +// set_discard. Returns NULL on allocation failure. Set* set_create(size_t element_size); +// Adds a copy of value. Returns true if it was inserted, false if already present. bool set_add(Set *set, void *value); +// Removes value. Returns true if it was present. bool set_remove(Set *set, void *value); +// Returns true if value is in the set. bool set_contains(Set *set, void *value); +// Returns the number of elements. int set_size(Set *set); +// Applies printfunc(element, dummy_value) to every element. void set_print(Set *set, void (*printfunc)(void*, void*)); +// Frees the backing table and the set struct. void set_discard(Set *set); #endif diff --git a/include/stack.h b/include/stack.h index 39abc89..b7650b3 100644 --- a/include/stack.h +++ b/include/stack.h @@ -11,25 +11,31 @@ #include #include -// Forward declaration of the struct Node stored in the stack +// Node is opaque to callers; its layout lives in stack.c. struct Node; -/** - * Defines the Stack struct type - */ typedef struct Stack { - struct Node *top; // Pointer to the top node in the stack - int length; // current length of the stack + struct Node *top; + int length; } Stack; +// Allocates an empty stack. Caller frees with stack_discard. NULL on failure. struct Stack* stack_create(); +// Pushes a node holding a copy of size bytes from value. Returns true. bool stack_push(struct Stack *stack, void *value, size_t size); +// Copies the top value into out without popping. False if the stack is empty. bool stack_peek(struct Stack *stack, void *out); +// Copies the top value into out and pops it. False if the stack is empty. bool stack_pop(struct Stack *stack, void *out); +// Frees every node but keeps the stack struct for reuse. void stack_clear(struct Stack *stack); +// Returns true if the stack has no nodes. bool stack_is_empty(struct Stack *stack); +// Returns the number of nodes on the stack. int stack_size(struct Stack *stack); +// Applies print_fn to each value from top to bottom. void stack_print(struct Stack *stack, void (* print_fn)(void*)); +// Frees every node and the stack struct. void stack_discard(struct Stack *stack); -#endif \ No newline at end of file +#endif diff --git a/include/str.h b/include/str.h index 1400328..dac7423 100644 --- a/include/str.h +++ b/include/str.h @@ -7,13 +7,15 @@ #include +// Starting capacity of a String's buffer, in characters (excluding the '\0'). #define INITIAL_CAPACITY 8 -// Defines the String struct type -// `data` is a pointer to where the buffer is stored in memory -// `length` is the number of characters currently stored (excluding '\0') -// `initial_capacity` is the starting number of characters the buffer can store -// `capacity` is the current number of characters the buffer can store +/** + * Growable, always NUL-terminated string. + * data - heap buffer, capacity + 1 bytes (room for the '\0') + * length - characters stored, not counting the '\0' + * capacity - characters the buffer can hold before it must grow + */ typedef struct String { char *data; size_t length; @@ -21,19 +23,37 @@ typedef struct String { size_t capacity; } String; +// Creates a String from a C string (or an empty String if initial is NULL). +// Caller frees it with str_discard. Returns NULL on allocation failure. String* str_create(const char *initial); +// Appends the C string data in place, growing the buffer as needed. Returns false +// if a needed reallocation fails. bool str_append(String *str, const char *data); +// Returns true if substr occurs in str (naive O(n*m) scan). bool str_contains(String *str, const char *substr); +// Lexicographic compare: 0 if equal, +/-1 on an in-string mismatch, or the +// signed char delta when one string is a prefix of the other. int str_compare(String *a, String *b); +// Returns true if a and b compare equal. bool str_equals(String *a, String *b); +// Returns a newly allocated str followed by substr. Caller frees with str_discard. String* str_concat(String *str, String *substr); +// Returns a newly allocated duplicate of src. Caller frees with str_discard. String* str_copy(String *src); +// Returns a newly allocated upper-cased copy. Caller frees with str_discard. String* str_to_upper(String *str); +// Returns a newly allocated lower-cased copy. Caller frees with str_discard. String* str_to_lower(String *str); +// Returns a newly allocated copy with leading/trailing whitespace removed. +// Caller frees with str_discard. String *str_trim(String *str); +// Returns the length of a raw C string (like strlen). size_t str_length(const char *src); +// Returns str->length (no scan). size_t str_len(String *str); +// Writes the String's characters to stdout. void str_print(String *str); +// Frees the buffer and the String struct. void str_discard(String *str); #endif diff --git a/src/binary_tree.c b/src/binary_tree.c index 74cc7e4..aee4b9d 100644 --- a/src/binary_tree.c +++ b/src/binary_tree.c @@ -16,7 +16,6 @@ static int parent_index(int index); static int left_child_index(int index); static int right_child_index(int index); -// Creates a new binary tree struct BinaryTree* bt_create(size_t element_size) { struct BinaryTree *tree = malloc(sizeof(struct BinaryTree)); if (tree == NULL) return NULL; @@ -25,7 +24,6 @@ struct BinaryTree* bt_create(size_t element_size) { tree->length = 0; tree->element_size = element_size; - // Allocate memory for the underlying array used to hold elements in the tree tree->elements = malloc(tree->capacity * tree->element_size); if (tree->elements == NULL) { free(tree); @@ -34,42 +32,35 @@ struct BinaryTree* bt_create(size_t element_size) { return tree; } -// Insert a value into the trees next available position void bt_insert(struct BinaryTree *tree, void *value) { if (tree->length >= tree->capacity) { resize(tree); } - // Grab pointer to next available position in the tree, and copy value into it void *curr = (char*) tree->elements + tree->length * tree->element_size; memcpy(curr, value, tree->element_size); tree->length++; } -// Removes the element at the provided index in the binary tree void bt_remove(struct BinaryTree *tree, int index) { if (index < 0 || index >= tree->length) return; - // Grab pointer to element being removed and last element in tree void *curr = (char*) tree->elements + index * tree->element_size; void *last = (char*) tree->elements + (tree->length - 1) * tree->element_size; - - // Swap the two values and decrement length to remove the value + + // Overwrite the hole with the last element and drop the last slot. swap(curr, last, tree->element_size); tree->length--; } -// Returns a pointer to the element at the provided index in the tree bool bt_get(struct BinaryTree *tree, int index, void *out) { if (index < 0 || index >= tree->length) return false; - // Grab pointer to element at provided index, copy raw bytes between memory locations void *current = (char*) tree->elements + index * tree->element_size; memcpy(out, current, tree->element_size); return true; } -// Sets the value of the element at the provided index to the provided value bool bt_set(struct BinaryTree *tree, int index, void *value) { if (index < 0 || index >= tree->length) return false; @@ -78,7 +69,6 @@ bool bt_set(struct BinaryTree *tree, int index, void *value) { return true; } -// Checks if the binary tree contains the provided value bool bt_contains(struct BinaryTree *tree, void *value, int (*comparator)(void*, void*)) { for (int i = 0; i < tree->length; i++) { void *current = (char*) tree->elements + i * tree->element_size; @@ -89,8 +79,6 @@ bool bt_contains(struct BinaryTree *tree, void *value, int (*comparator)(void*, return false; } -// Attempts to find and return the index of the provided value in the tree, -// If not found, returns -1 int bt_find(struct BinaryTree *tree, void *value, int (*comparator)(void*, void*)) { for (int i = 0; i < tree->length; i++) { void *current = (char*) tree->elements + i * tree->element_size; @@ -101,16 +89,14 @@ int bt_find(struct BinaryTree *tree, void *value, int (*comparator)(void*, void* return -1; } -// Returns the height of the tree (largest number of edges in a path from root to a leaf node) int bt_height(struct BinaryTree *tree) { if (bt_isempty(tree)) return -1; - // Store the height and last element in the tree + // The tree is complete, so the last array element sits at maximum depth. + // Counting parent hops from it back to the root gives the height. int height = 0; int last = tree->length - 1; - // Start loop from last element, while root is not reached, count edges - // from deepest leaf back to root while (last > 0) { int parent = parent_index(last); height++; @@ -119,19 +105,14 @@ int bt_height(struct BinaryTree *tree) { return height; } -// Returns the number of nodes with no children (leaf) in the binary tree int bt_leaves(struct BinaryTree *tree) { if (bt_isempty(tree)) return 0; int current = 0; int leaves = 0; - // Continue looping starting from the root element to the last while (current < tree->length) { - // Calculate left child index and check if the child exists - // If it does, we know that the current node is not a leaf - // If it dosent, then we know the current node is a leaf, - // since the complete binary tree cannot have a right node without a left + // In a complete tree a node is a leaf exactly when it has no left child. if (left_child_index(current) >= tree->length) { leaves++; } @@ -140,50 +121,40 @@ int bt_leaves(struct BinaryTree *tree) { return leaves; } -// Public interface for printing the contents of the binary tree void bt_print(struct BinaryTree *tree, void (*print_fn)(void*)) { if (bt_isempty(tree)) return; print_recursive(tree, 0, 0, print_fn); } -// Recursive helper for printing the contents of the binary tree in a tree-like structure static void print_recursive(struct BinaryTree *tree, int index, int depth, void (*print_fn)(void*)) { - // Base case if (index >= tree->length) return; - - // Get pointer to the element at the provided index + void *curr = (char*) tree->elements + index * tree->element_size; - // Recurse down the right side of the tree first - // then print the current element with indentation - // then recurse down the left side of the tree + // Reverse in-order (right, node, left) so the output reads as the tree + // rotated onto its side, with the root flush left and depth shown by indent. print_recursive(tree, right_child_index(index), depth + 1, print_fn); for (int i = 0; i < depth; i++) printf(" "); print_fn(curr); print_recursive(tree, left_child_index(index), depth + 1, print_fn); } -// Clears the contents of the binary tree void bt_clear(struct BinaryTree *tree) { tree->length = 0; } -// Returns the current size (length) of the binary tree int bt_size(struct BinaryTree *tree) { return tree->length; } -// Returns the current capacity of the tree's underlying array int bt_capacity(struct BinaryTree *tree) { return tree->capacity; } -// Checks if the binary tree is empty bool bt_isempty(struct BinaryTree *tree) { return tree->length == 0; } -// Frees memory previously allocated for the binary tree void bt_discard(struct BinaryTree *tree) { if (tree != NULL) { free(tree->elements); @@ -192,44 +163,34 @@ void bt_discard(struct BinaryTree *tree) { } -// Resizes the binary trees internal array to twice the current capacity +// Doubles the backing array. Returns false if reallocation fails (tree unchanged). static bool resize(struct BinaryTree *tree) { - - // Calculate new capacity int new_capacity = tree->capacity * 2; - - // Reallocate more memory for the new larger array and handle allocation failure void *temp = realloc(tree->elements, new_capacity * tree->element_size); if (temp == NULL) return false; - // If reallocation succeeds, update the pointer to point to the new larger memory block - // and set the new capacity of the binary tree tree->elements = temp; tree->capacity = new_capacity; return true; } -// Swaps the raw bytes at the two memory locations pointed to by the provided pointers +// Byte-wise swap of two element_size-byte regions. static void swap(void *a, void *b, size_t element_size) { char temp[element_size]; - // Copy the raw bytes between the memory locations to swap values memcpy(temp, a, element_size); memcpy(a, b, element_size); memcpy(b, temp, element_size); } -// Returns the array index of the parent of the element at index `index` in the tree static int parent_index(int index) { return (index - 1) / 2; } -// Returns the array index of the left child of the element at the provided index in the tree static int left_child_index(int index) { return 2 * index + 1; } -// Returns the array index of the right child of the element at the provided index in the tree static int right_child_index(int index) { return 2 * index + 2; } diff --git a/src/binarysearch_tree.c b/src/binarysearch_tree.c index 494aa12..b41b803 100644 --- a/src/binarysearch_tree.c +++ b/src/binarysearch_tree.c @@ -8,11 +8,7 @@ #include #include "binarysearch_tree.h" -// Defines the Node struct type -// `value` is a pointer to the memory where the value is stored -// `value_size` is the number of bytes the value will occupy in memory\ -// `left` is a pointer to the current nodes left child -// `right` is a pointer to the current nodes right child +// Each node owns a heap copy of its value (value_size bytes). typedef struct Node { void *value; size_t value_size; @@ -29,18 +25,17 @@ static struct Node* bst_search_rec(struct Node *root, void *value, int (*compare static void* bst_min_rec(struct Node *root); static void* bst_max_rec(struct Node *root); static int bst_height_rec(struct Node *root); -static void bst_print_rec(struct Node *root, void (* print_fn)(void*), int depth); +static void bst_print_rec(struct Node *root, void (* print_fn)(void*), int depth); static void bst_inorder_rec(struct Node *root, void (* print_fn)(void*)); static void bst_postorder_rec(struct Node *root, void (* print_fn)(void*)); static void bst_preorder_rec(struct Node *root, void (* print_fn)(void*)); -static struct Node* bst_get_successor(struct Node *root); +static struct Node* bst_get_successor(struct Node *root); static struct Node* bst_create_node(void *value, size_t value_size); static void bst_discard_node(struct Node *node); static void bst_discard_all_nodes(struct BinarySearchTree *tree); static void bst_discard_all_nodes_rec(struct Node *root); -// Creates a new binary search tree struct BinarySearchTree* bst_create() { struct BinarySearchTree *tree = malloc(sizeof(struct BinarySearchTree)); if (tree == NULL) return NULL; @@ -50,15 +45,14 @@ struct BinarySearchTree* bst_create() { return tree; } -// Public interface for inserting a new value into the binary search tree void bst_insert(struct BinarySearchTree *tree, void *value, size_t value_size, int (*compare)(void*, void*)) { tree->root = bst_insert_rec(tree->root, value, value_size, compare); tree->length++; } -// Recursive helper for inserting a value into the binary search tree +// Each call returns the (possibly new) root of the subtree it was handed, which +// the caller rebinds into root->left / root->right as it unwinds. static struct Node* bst_insert_rec(struct Node *root, void *value, size_t value_size, int (*compare)(void*, void*)) { - // Base case - if the root node is empty, return it to insert if (root == NULL) { return bst_create_node(value, value_size); } else if (compare(value, root->value) < 0) { @@ -70,47 +64,36 @@ static struct Node* bst_insert_rec(struct Node *root, void *value, size_t value_ } } -// Public interface for removing a value from the binary search tree -void bst_remove(struct BinarySearchTree *tree, void *value, int (*compare)(void*, void*)) { +void bst_remove(struct BinarySearchTree *tree, void *value, int (*compare)(void*, void*)) { bool found = false; tree->root = bst_remove_rec(tree->root, value, compare, &found); if (found) tree->length--; } -// Recursive helper for removing a value from the binary search tree -static Node* bst_remove_rec(struct Node *root, void *value, int (*compare)(void*, void*), bool *found) { - // Base case: we found an empty node +// Returns the replacement root for this subtree; every branch hands back the node +// the parent should point its child pointer at (NULL, the lone child, or root +// itself carrying the successor's value). *found reports whether value was present. +static Node* bst_remove_rec(struct Node *root, void *value, int (*compare)(void*, void*), bool *found) { if (root == NULL) { *found = false; return root; - // Check if we found the value to remove } else if (compare(value, root->value) == 0) { *found = true; - // Check if the node being removed has no children if (root->left == NULL && root->right == NULL) { - - // Remove the node and return the new subtree root back up to the call stack so the - // parent can update its child pointer bst_discard_node(root); root = NULL; return root; - // Check if the node being removed has one child } else if (root->left == NULL || root->right == NULL) { - // Check if left child is not empty if (root->left != NULL) { - - // Save pointer to the left child, free the current node, and return the left child - // back up to the call stack so the parent can update its child pointer struct Node *left = root->left; bst_discard_node(root); root = NULL; return left; - // Otherwise right child is not empty } else { struct Node *right = root->right; bst_discard_node(root); @@ -118,26 +101,21 @@ static Node* bst_remove_rec(struct Node *root, void *value, int (*compare)(void* return right; } - // Otherwise node has two children } else { - // Find the successor node + // Two children: overwrite this node's value with its in-order + // successor (smallest value in the right subtree), then delete the + // successor from that subtree. struct Node *successor = bst_get_successor(root->right); - - // Copy the value from the successor into the root and - // remove the successor itself by recursing down the right side of the tree memcpy(root->value, successor->value, root->value_size); root->right = bst_remove_rec(root->right, successor->value, compare, found); return root; } - // Didnt find the value to remove, need to recurse the tree further } else { - // Check if we need to recurse to the left in the tree if (compare(value, root->value) < 0) { root->left = bst_remove_rec(root->left, value, compare, found); return root; - // Otherwise recurse to the right } else { root->right = bst_remove_rec(root->right, value, compare, found); return root; @@ -145,196 +123,149 @@ static Node* bst_remove_rec(struct Node *root, void *value, int (*compare)(void* } } -// Public interface for checking if the binary search tree contains a value bool bst_contains(struct BinarySearchTree *tree, void *value, int (*compare)(void*, void*)) { if (bst_isempty(tree)) return false; return bst_contains_rec(tree->root, value, compare); } -// Recursive helper for checking if the tree contains a given value -static bool bst_contains_rec(struct Node *root, void *value, int (*compare)(void*, void*)) { - // Base case - if we have reached an empty node without finding value its not in the tree +static bool bst_contains_rec(struct Node *root, void *value, int (*compare)(void*, void*)) { if (root == NULL) return false; - // Check if we have found the value if (compare(value, root->value) == 0) { return true; - - // Check if we need to traverse in the left branch } else if (compare(value, root->value) < 0) { return bst_contains_rec(root->left, value, compare); - - // Otherwise we need to traverse in the right branch } else { return bst_contains_rec(root->right, value, compare); } } -// Public interface for searching the binary search tree for a value struct Node* bst_search(struct BinarySearchTree *tree, void *value, int (*compare)(void*, void*)) { if (bst_isempty(tree)) return NULL; return bst_search_rec(tree->root, value, compare); } -// Recursive helper for searching for a value in the tree static struct Node* bst_search_rec(struct Node *root, void *value, int (*compare)(void*, void*)) { - - // Base case - if we have reached an empty node without finding value, its not in the tree if (root == NULL) return NULL; - // Check if we found the value if (compare(value, root->value) == 0) { return root; - - // Check if value is less than the value stored in the current node, - // if it is, traverse left down the tree } else if (compare(value, root->value) < 0) { return bst_search_rec(root->left, value, compare); - - // Otherwise value is equal to or larger, so traverse right down the tree } else { return bst_search_rec(root->right, value, compare); } } -// Public interface for finding the minumum value in the binary search tree void* bst_min(struct BinarySearchTree *tree) { if (bst_isempty(tree)) return NULL; return bst_min_rec(tree->root); } -// Recursive helper for finding the minumum value in the binary search tree +// Precondition: root != NULL (callers guard with bst_isempty). Walks left to the +// smallest value; bst_max_rec is the mirror image. static void* bst_min_rec(struct Node *root) { - // Base case - reached an empty node in the left subtree if (root->left == NULL) return root->value; return bst_min_rec(root->left); } -// Public interface for finding the maximum value in the binary search tree void* bst_max(struct BinarySearchTree *tree) { if (bst_isempty(tree)) return NULL; return bst_max_rec(tree->root); } -// Recursive helper for finding the maximum value in the binary search tree static void* bst_max_rec(struct Node *root) { if (root->right == NULL) return root->value; return bst_max_rec(root->right); } -// Public interface for finding the height of the binary search tree int bst_height(struct BinarySearchTree *tree) { if (bst_isempty(tree)) return 0; return bst_height_rec(tree->root); } -// Recursive helper for finding the height of the binary search tree -// (length of longest path from root down to deepest leaf node) static int bst_height_rec(struct Node *root) { - // Base case - we reached an empty node if (root == NULL) return 0; - // Recurse down the left and right side of the tree to get height of both paths int left_height = bst_height_rec(root->left); int right_height = bst_height_rec(root->right); - // Each non-NULL node adds 1 to the height as recursion unwinds - // Return the taller path plus 1 for the current node + // Height in nodes: taller child subtree plus one for the current node. if (left_height > right_height) return left_height + 1; return right_height + 1; } -// Public interface for printing the contents of the binary search tree void bst_print(struct BinarySearchTree *tree, void (* print_fn)(void*)) { bst_print_rec(tree->root, print_fn, 0); } -// Recursive helper function for printing the contents of the binary search tree in order traversal static void bst_print_rec(struct Node *root, void (* print_fn)(void*), int depth) { - // Base case - if we hit a empty node, weve hit the end of the branch if (root == NULL) return; - // Print right subtree + // In-order (left, node, right): values print in ascending order, one per + // line, indented by depth. bst_print_rec(root->left, print_fn, depth + 1); - // Print current node with indentation for (int i = 0; i < depth; i++) printf(" "); print_fn(root->value); printf("\n"); - // Print left subtree bst_print_rec(root->right, print_fn, depth + 1); } -// Public interface for traversing and printing the contents of the tree in-order void bst_inorder(struct BinarySearchTree *tree, void (* print_fn)(void*)) { if (bst_isempty(tree)) return; bst_inorder_rec(tree->root, print_fn); } -// Recursive helper for traversing and printing the contents of the tree in-order static void bst_inorder_rec(struct Node *root, void (* print_fn)(void*)) { - // Base case - hit an empty node if (root == NULL) return; - // Traverse the tree, first left side, then root, then right bst_inorder_rec(root->left, print_fn); print_fn(root->value); bst_inorder_rec(root->right, print_fn); } -// Public interface for traversing and printing the contents of the tree post-order void bst_postorder(struct BinarySearchTree *tree, void (* print_fn)(void*)) { if (bst_isempty(tree)) return; bst_postorder_rec(tree->root, print_fn); } -// Recursive helper for traversing and printing the contents of the tree post-order static void bst_postorder_rec(struct Node *root, void (* print_fn)(void*)) { - // Base case - hit an empty node if (root == NULL) return; - // Traverse left, then right, then print root bst_postorder_rec(root->left, print_fn); bst_postorder_rec(root->right, print_fn); print_fn(root->value); } -// Public interface for traversing and printing the contents of the tree pre-order void bst_preorder(struct BinarySearchTree *tree, void (* print_fn)(void*)) { if (bst_isempty(tree)) return; bst_preorder_rec(tree->root, print_fn); } -// Recursive helper for traversing and printing the contents of the tree pre-order static void bst_preorder_rec(struct Node *root, void (* print_fn)(void*)) { - // Base case - hit an empty node if (root == NULL) return; - // Print root node first, then left, then right print_fn(root->value); bst_preorder_rec(root->left, print_fn); bst_preorder_rec(root->right, print_fn); } -// Returns the number of nodes in the binary search tree int bst_size(struct BinarySearchTree *tree) { return tree->length; } -// Checks whether the binary search tree is empty bool bst_isempty(struct BinarySearchTree *tree) { return tree->length == 0; } -// Clears the contents of the binary search tree -void bst_clear(struct BinarySearchTree *tree) { +void bst_clear(struct BinarySearchTree *tree) { bst_discard_all_nodes(tree); tree->root = NULL; tree->length = 0; } -// Frees the memory previously allocated by all nodes in the tree and the tree itself void bst_discard(struct BinarySearchTree *tree) { if (tree != NULL) { bst_discard_all_nodes(tree); @@ -343,14 +274,14 @@ void bst_discard(struct BinarySearchTree *tree) { } -// Finds the minumum node in the tree, which when passed a node on the right subtree, returns the -// successor +// Leftmost node of the given subtree. Pass root->right to get the in-order +// successor of root. static struct Node* bst_get_successor(struct Node *root) { if (root->left == NULL) return root; return bst_get_successor(root->left); } -// Creates a new node to be stored in the tree +// Deep-copies value_size bytes from value; the node owns the copy. static struct Node* bst_create_node(void *value, size_t value_size) { struct Node *node = malloc(sizeof(struct Node)); if (node == NULL) return NULL; @@ -369,7 +300,6 @@ static struct Node* bst_create_node(void *value, size_t value_size) { return node; } -// Frees the memory previously allocated by a node static void bst_discard_node(struct Node *node) { if (node != NULL) { free(node->value); @@ -377,17 +307,15 @@ static void bst_discard_node(struct Node *node) { } } -// Interface for freeing the memory previously allocated by all nodes in the tree -static void bst_discard_all_nodes(struct BinarySearchTree *tree) { +static void bst_discard_all_nodes(struct BinarySearchTree *tree) { bst_discard_all_nodes_rec(tree->root); } -// Recursive helper function for freeing the memory allocated by each node in the tree static void bst_discard_all_nodes_rec(struct Node *root) { if (root == NULL) return; - // Free left and right child branches of the root node and - // then free the root node to avoid accessing freed memory + // Post-order: free both children before the parent so we never follow a + // pointer into an already-freed node. bst_discard_all_nodes_rec(root->left); bst_discard_all_nodes_rec(root->right); bst_discard_node(root); diff --git a/src/generic_array.c b/src/generic_array.c index e9eeca1..d9fa6e2 100644 --- a/src/generic_array.c +++ b/src/generic_array.c @@ -15,7 +15,6 @@ static void shift_right(struct GenericArray *ga, int index); static void shift_left(struct GenericArray *ga, int index); static void swap(struct GenericArray *ga, void *val1, void *val2); -// Initializes a new GenericArray struct GenericArray * ga_init(size_t item_size) { struct GenericArray *ga = NULL; ga = malloc(sizeof(struct GenericArray)); @@ -39,8 +38,7 @@ struct GenericArray * ga_init(size_t item_size) { return ga; } -// Adds an item to the end of the array -bool ga_append(struct GenericArray *ga, void *itemPtr) { +bool ga_append(struct GenericArray *ga, void *itemPtr) { if (ga->length == ga->capacity) { resize(ga); } @@ -51,7 +49,6 @@ bool ga_append(struct GenericArray *ga, void *itemPtr) { return true; } -// Inserts a new item at a specified index within the GenericArray bool ga_add(struct GenericArray *ga, int index, void *in_ptr) { if (index < 0 || index > ga->length) return false; @@ -59,7 +56,6 @@ bool ga_add(struct GenericArray *ga, int index, void *in_ptr) { resize(ga); } - // Shift subsequent items to the right to make room for item being inserted shift_right(ga, index); void *dest_ptr = (char *) ga->ptrData + index * ga->item_size; @@ -68,7 +64,6 @@ bool ga_add(struct GenericArray *ga, int index, void *in_ptr) { return true; } -// Retrieves an item from a specified index in the array bool ga_get(struct GenericArray *ga, int index, void *out_ptr) { if (index < 0 || index >= ga->length) return false; @@ -77,7 +72,6 @@ bool ga_get(struct GenericArray *ga, int index, void *out_ptr) { return true; } -// Sets the value at the provided index bool ga_set(struct GenericArray *ga, int index, void *in_ptr) { if (index < 0 || index >= ga->length) return false; @@ -86,7 +80,6 @@ bool ga_set(struct GenericArray *ga, int index, void *in_ptr) { return true; } -// Find the index of a specified item in the array, if not found, return -1 int ga_find(struct GenericArray *ga, void *item_ptr, bool (*comparator)(void*, void*)) { for (int i = 0; i < ga->length; i++) { void *curr = (char *) ga->ptrData + i * ga->item_size; @@ -98,35 +91,30 @@ int ga_find(struct GenericArray *ga, void *item_ptr, bool (*comparator)(void*, v return -1; } -// Removes the last item in the GenericArray bool ga_remove_last(struct GenericArray *ga) { if (!ga || ga->length == 0) return false; ga->length--; - // Check if memory is underutilized - if (ga_usage(ga) < SHRINK_THRESHOLD) { + if (ga_usage(ga) < SHRINK_THRESHOLD) { printf("SHRINKING...\n"); shrink(ga); } return true; } -// Removes an item from the specified index in the GenericArray bool ga_remove_at(struct GenericArray *ga, int index) { if (index < 0 || index >= ga->length) return false; ga->length--; - if (ga_usage(ga) < SHRINK_THRESHOLD) { + if (ga_usage(ga) < SHRINK_THRESHOLD) { printf("SHRINKING...\n"); shrink(ga); } - // Shift every element after the index of the item being removed shift_left(ga, index); return true; } -// Checks if the GenericArray contains another item bool ga_contains(struct GenericArray *ga, bool (*funcptr)(void*, void*), void *trgtptr) { for (int i = 0; i < ga->length; i++) { void *curr = (char *) ga->ptrData + i * ga->item_size; @@ -138,33 +126,27 @@ bool ga_contains(struct GenericArray *ga, bool (*funcptr)(void*, void*), void *t return false; } -// Clears the contents of the array by setting the length to 0. -// Allocated memory and existing data remain intact, but are marked as unused, -// old values will be overwritten when new items are added +// Length only; the backing bytes are left untouched and get overwritten as new +// items are appended. bool ga_clear(struct GenericArray *ga) { if (!ga) return false; ga->length = 0; return true; } -// Returns the length of the GenericArray int ga_size(struct GenericArray *ga) { return ga->length; } -// Returns the current capacity of the GenericArray int ga_capacity(struct GenericArray *ga) { return ga->capacity; } -// Calculates how full the array is internally based on current -// num of elements and total allocated space double ga_usage(struct GenericArray *ga) { double usage = (double) ga->length / ga->capacity * 100; return usage; } -// Frees any memory allocated by the GenericArray internal array and struct void ga_discard(struct GenericArray *ga) { if (ga != NULL) { free(ga->ptrData); @@ -172,7 +154,6 @@ void ga_discard(struct GenericArray *ga) { } } -// Prints the current state of the GenericArray void ga_print(struct GenericArray *ga, void (* print_fn)(void*)) { if (ga->length == 0) return; for (int i = 0; i < ga->length; i++) { @@ -181,8 +162,8 @@ void ga_print(struct GenericArray *ga, void (* print_fn)(void*)) { } } -// Bubble Sort implementation for an array of integers with a caller defined comparator -void ga_sort(struct GenericArray *ga, bool (* comparator)(void*, void*)) { +// Bubble sort, bailing out early on the first pass that makes no swaps. +void ga_sort(struct GenericArray *ga, bool (* comparator)(void*, void*)) { bool is_swapped; for (int i = 0; i < ga->length - 1; i++) { is_swapped = false; @@ -201,18 +182,14 @@ void ga_sort(struct GenericArray *ga, bool (* comparator)(void*, void*)) { } } -// Reverses items in-place within the array void ga_reverse(struct GenericArray *ga) { for (int i = 0; i < ga->length / 2; i++) { - // Calculate the memory addresses of the pair of items being swapped, - // which will be the item at index `i` and the item at index `length - i - 1` void *front = (char *) ga->ptrData + i * ga->item_size; void *back = (char *) ga->ptrData + (ga->length - i - 1) * ga->item_size; swap(ga, front, back); } } -// Makes and returns a deep copy of the GenericArray provided struct GenericArray* ga_copy(struct GenericArray *ga) { struct GenericArray *ga_copy = NULL; ga_copy = malloc(sizeof(struct GenericArray)); @@ -226,7 +203,6 @@ struct GenericArray* ga_copy(struct GenericArray *ga) { return ga_copy; } -// Searches for an item within a sorted array using a Binary Search implementation bool ga_binary_find(struct GenericArray *ga, void *item_ptr, int (* comparator)(void*, void*)) { int low = 0; int high = ga->length - 1; @@ -237,6 +213,8 @@ bool ga_binary_find(struct GenericArray *ga, void *item_ptr, int (* comparator)( int cmp = comparator(mid, item_ptr); + // Relies on comparator returning exactly -1/0/1: any other negative + // value would fall through to the high = mid - 1 branch. if (cmp == 0) { return true; } else if (cmp == -1) { @@ -249,7 +227,7 @@ bool ga_binary_find(struct GenericArray *ga, void *item_ptr, int (* comparator)( } -// Resizes the internal array when its limit is reached +// Doubles the backing array; exits the process on allocation failure. static void resize(struct GenericArray *ga) { int new_capacity = ga->capacity * 2; @@ -264,13 +242,14 @@ static void resize(struct GenericArray *ga) { ga->ptrData = tmp_ptr; } -// Shrinks the internal array when its underutilized by reallocating less memory +// Shrinks an underused array: to 2.5x the live count, or 1.5x initial_capacity +// once the count drops that low. Exits the process on allocation failure. static void shrink(struct GenericArray *ga) { int new_capacity; - if (ga->length > ga->initial_capacity) { - new_capacity = (int)(ga->length * 2.5); - } else { - new_capacity = (int)(ga->initial_capacity * 1.5); + if (ga->length > ga->initial_capacity) { + new_capacity = (int)(ga->length * 2.5); + } else { + new_capacity = (int)(ga->initial_capacity * 1.5); } void *tmp = NULL; @@ -283,7 +262,7 @@ static void shrink(struct GenericArray *ga) { ga->ptrData = tmp; } -// Shifts items to the right, starting from the end to the index to make room for the item being added +// Walks from the tail so each slot is copied forward before it is overwritten. static void shift_right(struct GenericArray *ga, int index) { for (int i = ga->length - 1; i >= index; i--) { void *src = (char *) ga->ptrData + i * ga->item_size; @@ -292,7 +271,7 @@ static void shift_right(struct GenericArray *ga, int index) { } } -// Shifts items to the left starting at 'index', filling the gap created by the removed item +// Walks from index forward, closing the gap left by a removed item. static void shift_left(struct GenericArray *ga, int index) { for (int i = index; i < ga->length - 1; i++) { void *src = (char *) ga->ptrData + (i + 1) * ga->item_size; @@ -301,8 +280,7 @@ static void shift_left(struct GenericArray *ga, int index) { } } -// Swaps two items in the array -static void swap(struct GenericArray *ga, void *val1, void *val2) { +static void swap(struct GenericArray *ga, void *val1, void *val2) { void *temp = NULL; temp = malloc(ga->item_size); diff --git a/src/hash_table.c b/src/hash_table.c index 4b3de07..5d7fd29 100644 --- a/src/hash_table.c +++ b/src/hash_table.c @@ -9,18 +9,17 @@ #include #include "hash_table.h" -// Defines the maximum load factor allowed before the hash table should resize +// Grow the bucket array once the load factor climbs past this. #define LOAD_THRESHOLD 0.75 -/** - * Defines the Node struct type to be stored in the hash table - */ +// One entry in a bucket's chain. The node owns heap copies of both the key +// (key_size bytes) and the value (value_size bytes). typedef struct Node { - void *key; // Pointer to the memory where the key data is stored - size_t key_size; // The number of bytes the key will occupy in the memory pointed to for the key - void *value; // Pointer to the memory where the value data is stored - size_t value_size; // The number of bytes that the value will occupy in the memory pointed to for the value - struct Node *next; // Pointer to the next node in a bucket + void *key; + size_t key_size; + void *value; + size_t value_size; + struct Node *next; } Node; // Prototypes @@ -30,22 +29,14 @@ static void ht_resize(struct HashTable *hashtable); static void ht_discard_node(struct Node *node); static void ht_discard_all_nodes(struct HashTable *hashtable); -/** - * Creates a new hash table - */ -struct HashTable* ht_create() { - - // Allocate memory for the hash table itself and handle allocation failure +struct HashTable* ht_create() { struct HashTable *hashtable = malloc(sizeof(struct HashTable)); if (hashtable == NULL) return NULL; - // Set initial capacity and length of the hash table hashtable->capacity = 8; hashtable->length = 0; - // Allocate memory for the hash tables internal array of buckets - // using calloc to initialize each bucket to NULL to know whether they are empty, - // and handle allocation failure + // calloc so every empty bucket reads back as a NULL chain head. hashtable->buckets = calloc(hashtable->capacity, sizeof(struct Node*)); if (hashtable->buckets == NULL) { free(hashtable); @@ -55,58 +46,38 @@ struct HashTable* ht_create() { return hashtable; } -/** - * Inserts a new node into a bucket in the hash table based on the nodes key, updating the value of the - * node if it already exists - */ bool ht_insert(struct HashTable *hashtable, void *key, size_t ksize, void *value, size_t vsize) { - - // Check if resize is needed if (ht_load_factor(hashtable) > LOAD_THRESHOLD) { printf("Resizing internal array...\n"); ht_resize(hashtable); } - // Hash the key to find the bucket index int hash = ht_hash(key, ksize, hashtable->capacity); - // Find head node of the current bucket based on the hash, could be NULL if empty + // Chain head for this bucket; NULL when the bucket is empty. struct Node *head = hashtable->buckets[hash]; - // If bucket is empty, need to create new node and set as head of linked nodes if (head == NULL) { - - // Create new node with key/value and set as head of the buckets linked nodes struct Node *newnode = ht_create_node(key, ksize, value, vsize); hashtable->buckets[hash] = newnode; hashtable->length++; return true; - // Otherwise traverse the linked nodes in the bucket and search for the key to update, - // appending a new node if not found } else { - - // Keep track of the node before the current node + // Walk the chain: overwrite in place if the key is already here, + // otherwise fall through and append a new node. struct Node *previous = NULL; - // Traverse from the head node in the bucket while (head != NULL) { - - // Compare the raw memory pointed to by the current nodes key and the key being inserted - // to check for equality if (memcmp(head->key, key, head->key_size) == 0) { - - // If key already exists, update the value by copying raw memory memcpy(head->value, value, vsize); return true; } - // Advance both pointers to continue traversal previous = head; head = head->next; } - // If we get here, no matching key was found so append a new node to the bucket struct Node *newnode = ht_create_node(key, ksize, value ,vsize); previous->next = newnode; hashtable->length++; @@ -115,45 +86,26 @@ bool ht_insert(struct HashTable *hashtable, void *key, size_t ksize, void *value return false; } -/** - * Removes a node from a bucket in the hash table based on the keys hash - */ bool ht_remove(struct HashTable *hashtable, void *key, size_t ksize) { - if (ht_is_empty(hashtable)) return false; - // Hash the key to find the nodes bucket int hash = ht_hash(key, ksize, hashtable->capacity); - // Start from the head of the given bucket - // and track previous node incase of non-head node being removed + // Track previous so a non-head node can be unlinked. struct Node *current = hashtable->buckets[hash]; struct Node *previous = NULL; - // Traverse the linked nodes inside the current bucket to search for the key, - // starting from head of link while (current != NULL) { - - // Compare raw memory pointed to by the current nodes key and the key to search for - // to check for equality if (memcmp(current->key, key, ksize) == 0) { - // If the key to remove is found, need to check which node in the link it is - // Check if head is node being removed if (current == hashtable->buckets[hash]) { - - // Store pointer to the next node in the link to prevent losing the link, - // free the current head of the link, and re-point the head to the next node + // Removing the chain head: repoint the bucket at node 2. struct Node *next = current->next; ht_discard_node(current); hashtable->buckets[hash] = next; - - // Otherwise middle or tail node being removed - } else { - // Store pointer to the next node in the link to prevent losing the link, - // free the current node, and re-link the surrounding nodes by re-pointing - // the previous pointer to the next node + } else { + // Removing a middle/tail node: bridge previous over it. struct Node *next = current->next; ht_discard_node(current); previous->next = next; @@ -163,33 +115,20 @@ bool ht_remove(struct HashTable *hashtable, void *key, size_t ksize) { return true; } - // Advance pointers to continue traversal if key not found yet previous = current; current = current->next; } return false; } -/** - * Retrieves the value associated with a key if found, false if not found. - */ bool ht_get(struct HashTable *hashtable, void *key, size_t ksize, void *out) { if (ht_is_empty(hashtable)) return false; - // Hash the provided key to find the bucket index of the node int hash = ht_hash(key, ksize, hashtable->capacity); - // Start from the head node in the given bucket and - // traverse the bucket to search for the key struct Node *current = hashtable->buckets[hash]; while (current != NULL) { - - // Compare raw memory pointed to by the current nodes key and the key to search for to check - // for equality if (memcmp(current->key, key, ksize) == 0) { - - // If the key is found, copy the values raw bytes in memory pointed to by the node - // into the memory pointed to by the out pointer memcpy(out, current->value, current->value_size); return true; } @@ -198,60 +137,41 @@ bool ht_get(struct HashTable *hashtable, void *key, size_t ksize, void *out) { return false; } -/** - * Checks if the hash table contains a given key - */ bool ht_contains(struct HashTable *hashtable, void *key, size_t ksize) { if (ht_is_empty(hashtable)) return false; - // Hash the key to find bucket index of the node and - // traverse from the head node in the bucket int hash = ht_hash(key, ksize, hashtable->capacity); struct Node *current = hashtable->buckets[hash]; while (current != NULL) { - // Check for equality between the two values pointed to in memory if (memcmp(current->key, key, current->key_size) == 0) return true; current = current->next; } return false; } -/** - * Clears the entire contents of the hash table, including everything stored inside its buckets - */ void ht_clear(struct HashTable *hashtable) { if (ht_is_empty(hashtable)) return; - // Free all nodes and the array of pointers to the buckets ht_discard_all_nodes(hashtable); free(hashtable->buckets); - // Reset back to initial state + // Back to the initial state. hashtable->length = 0; hashtable->capacity = 8; - // Allocate fresh memory for the new internal array of pointers to the buckets hashtable->buckets = calloc(hashtable->capacity, sizeof(struct Node*)); if (hashtable->buckets == NULL) return; } -/** - * Prints the contents of the hash table, visiting each bucket and printing its contents - */ void ht_print(struct HashTable *hashtable, void (* print_fn)(void*, void*)) { - - // Loop through each bucket in the hash table for (int i = 0; i < hashtable->capacity; i++) { printf("Bucket %d -> ", i); - // The head node of the current bucket struct Node *current = hashtable->buckets[i]; if (current == NULL) { printf("NULL\n"); } else { - - // For each node in the bucket, print its key and value while (current != NULL) { print_fn(current->key, current->value); if (current->next != NULL) { @@ -264,9 +184,6 @@ void ht_print(struct HashTable *hashtable, void (* print_fn)(void*, void*)) { } } -/** - * Frees the memory previously allocated by the hash table - */ void ht_discard(struct HashTable *hashtable) { if (hashtable != NULL) { ht_discard_all_nodes(hashtable); @@ -275,30 +192,18 @@ void ht_discard(struct HashTable *hashtable) { } } -/** - * Checks whether the hash table is empty or not - */ bool ht_is_empty(struct HashTable *hashtable) { return hashtable->length == 0; } -/** - * Returns the number of key/value pairs (nodes) in the hash table - */ int ht_size(struct HashTable *hashtable) { return hashtable->length; } -/** - * Returns the number of buckets in the hash table - */ int ht_capacity(struct HashTable *hashtable) { return hashtable->capacity; } -/** - * Returns the load factor for the hash table, showing how full the hash table is - */ float ht_load_factor(struct HashTable *hashtable) { return (float) hashtable->length / hashtable->capacity; } @@ -307,30 +212,23 @@ float ht_load_factor(struct HashTable *hashtable) { // Private helper functions - linkage limited to this file -// Creates a new node +// Deep-copies key (ksize bytes) and value (vsize bytes); the node owns both. static struct Node* ht_create_node(void *key, size_t ksize, void *value, size_t vsize) { - - // Allocate memory for the node itself and handle allocation failure struct Node *node = malloc(sizeof(struct Node)); if (node == NULL) { return NULL; } - // Set size of key and value being stored in the node - // and initial pointer to the next node node->key_size = ksize; node->value_size = vsize; node->next = NULL; - // Allocate memory for the key and handle allocation failure node->key = malloc(node->key_size); if (node->key == NULL) { free(node); return NULL; } - // Allocate memory for the value and handle allocation failure, - // freeing the memory allocated for the key and node itself node->value = malloc(node->value_size); if (node->value == NULL) { free(node->key); @@ -338,76 +236,56 @@ static struct Node* ht_create_node(void *key, size_t ksize, void *value, size_t return NULL; } - // Copy the raw memory pointed to by the pointer arguments into the memory pointed to by - // the nodes members memcpy(node->key, key, node->key_size); memcpy(node->value, value, node->value_size); return node; } -// Hashes a given key and returns its bucket index +// Sum of the key's bytes modulo capacity: cheap, but order-insensitive and +// clusters badly for similar keys. static int ht_hash(void *key, size_t key_size, int capacity) { - - // Cast so each induvidual byte in the keys memory can be accessed unsigned char *bytes = (unsigned char *) key; int hash_value = 0; - // Loop through each byte for key_size bytes to add each byte value to the rolling sum for (int i = 0; i < key_size; i++) { - // Calculate memory address of the i-th byte from the start of the key in memory - // and dereference the address to get the actual byte value at that position hash_value += *(bytes + i); } - // Modulo to ensure hash value fits within the buckets array bounds return hash_value % capacity; } -// Resizes the internal array of pointers to the buckets when the load factor reaches the max +// Doubles the bucket count and rehashes every node into the new array. static void ht_resize(struct HashTable *hashtable) { - - // Create a copy of the buckets array and save the tables old capacity + // Hold onto the old array/capacity while we rehash out of it. struct Node **bucketscopy = hashtable->buckets; int old_capacity = hashtable->capacity; - // Compute and set new capacity hashtable->capacity = hashtable->capacity * 2; - // Allocate a larger block of memory for the internal array of buckets based on the new capacity hashtable->buckets = calloc(hashtable->capacity, sizeof(struct Node*)); - // Loop through each of the buckets in the old array of buckets to copy over the nodes for (int i = 0; i < old_capacity; i++) { - - // Start from the head node in each bucket of the old buckets and - // traverse in order to rehash each node and move to new bucket struct Node *current = bucketscopy[i]; while (current != NULL) { - - // Compute new hash based on the tables new capacity int hash = ht_hash(current->key, current->key_size, hashtable->capacity); - // Save the current nodes `next` pointer and - // the node at the head of the current bucket + // Save next before we overwrite current->next while relinking. struct Node *next = current->next; struct Node *currhead = hashtable->buckets[hash]; - // Link the current node to the existing chain and - // set the current node as the head of the new bucket + // Prepend the node to its new bucket's chain. current->next = currhead; hashtable->buckets[hash] = current; - // Advance pointer to continue traversal current = next; } } - // Free the copy used to rehash all nodes + // The old array holds no nodes now, only stale head pointers. free(bucketscopy); bucketscopy = NULL; } -// Frees the memory previously allocated by a node static void ht_discard_node(struct Node *node) { if (node != NULL) { free(node->key); @@ -416,19 +294,11 @@ static void ht_discard_node(struct Node *node) { } } -// Frees the memory previously allocated by all nodes in the hash table static void ht_discard_all_nodes(struct HashTable *hashtable) { - - // Loop through each bucket in the hash table for (int i = 0; i < hashtable->capacity; i++) { - - // Start from head node in each bucket and - // loop through each node in the bucket to free each struct Node *current = hashtable->buckets[i]; while (current != NULL) { - - // Store pointer to the next node in the bucket to prevent - // losing the link to the rest of the nodes when freeing the current + // Grab next before freeing current. struct Node *next = current->next; ht_discard_node(current); current = next; diff --git a/src/heap.c b/src/heap.c index 9277eea..9620147 100644 --- a/src/heap.c +++ b/src/heap.c @@ -16,23 +16,14 @@ static void heapify_up(struct Heap *heap, int current_idx, int (*compare)(void*, static void heapify_down(struct Heap *heap, int current_idx, int (*compare)(void*, void*)); static void heap_print_rec(struct Heap *heap, void (* print_fn)(void*), int index, int depth); -/** - * Creates a new empty heap storing elements of the provided size - */ struct Heap* heap_create(size_t element_size) { - - // Initialize a pointer to the address of the memory allocated for the heap struct - // and handle allocation failure struct Heap *heap = malloc(sizeof(struct Heap)); if (heap == NULL) return NULL; - // Set initial capacity, length, and size of elements that the heap will store heap->capacity = 8; heap->length = 0; heap->element_size = element_size; - // Initialize a pointer to the address of the memory allocated for the values - // to be stored in the heap, and handle allocation failure heap->elements = malloc(heap->element_size * heap->capacity); if (heap->elements == NULL) { free(heap); @@ -42,86 +33,59 @@ struct Heap* heap_create(size_t element_size) { return heap; } -/** - * Inserts a new element at the next available position in the last level of the heaps underlying tree - */ bool heap_insert(struct Heap *heap, void *value, size_t vsize, int (*compare)(void*, void*)) { - - // Check if the heaps capacity has been reached, if it has, need to resize if (heap->length >= heap->capacity) { if (!heap_resize(heap)) return false; } - // Copy the value into the array and increment length to insert the element + // vsize is trusted to equal heap->element_size. memcpy((char*) heap->elements + heap->length * heap->element_size, value, vsize); heap->length++; - // Compute the index of the newly inserted element and heapify up to maintain max heap + // New element sits at the end of the array; sift it up into place. int current_idx = heap->length - 1; heapify_up(heap, current_idx, compare); return true; } -/** - * Removes and returns the element currently located at the root (index=0) - */ bool heap_remove(struct Heap *heap, void *out, int (*compare)(void*, void*)) { if (heap_isempty(heap)) return false; - // Copy the value being removed into the output pointer before removal + // Hand back the root before it is overwritten. memcpy(out, heap->elements, heap->element_size); - // Compute index and pointer to the last element in memory int last_idx = heap->length - 1; void *last = (char *) heap->elements + last_idx * heap->element_size; - // Replace the root element with the last element in the heap + // Promote the last element to the root, then sift it down. memcpy(heap->elements, last, heap->element_size); heap->length--; - // Heapify down to maintain the max-heap property heapify_down(heap, 0, compare); return true; } -/** - * Returns the value stored at the root of the heap (index=0) - */ void* heap_peek(struct Heap *heap) { return heap->elements; } -/** - * Returns the size (length) of the heap - */ int heap_size(struct Heap *heap) { return heap->length; } -/** - * Returns the current capacity of the heap - */ int heap_capacity(struct Heap *heap) { return heap->capacity; } -/** - * Checks whether the heap is empty or not - */ bool heap_isempty(struct Heap *heap) { return heap->length == 0; } -/** - * Clears the contents of the heap (memory remains allocated to be overwritten) - */ void heap_clear(struct Heap *heap) { + // Length only; the backing array stays allocated and gets overwritten. heap->length = 0; } -/** - * Frees the memory block previously allocated for the heap - */ void heap_discard(struct Heap *heap) { if (heap != NULL) { free(heap->elements); @@ -129,9 +93,6 @@ void heap_discard(struct Heap *heap) { } } -/** - * Prints the contents of the heap with no visualization - */ void heap_debug(struct Heap *heap, void (* print_fn)(void*)) { if (heap_isempty(heap)) return; @@ -140,9 +101,6 @@ void heap_debug(struct Heap *heap, void (* print_fn)(void*)) { } } -/** - * Public interface for printing the contents of the heap in tree-like structure - */ void heap_print(struct Heap *heap, void (* print_fn)(void*)) { if (heap_isempty(heap)) return; heap_print_rec(heap, print_fn, 0, 0); @@ -152,32 +110,25 @@ void heap_print(struct Heap *heap, void (* print_fn)(void*)) { // Private helper functions - linkage limited to this file -// Recursive helper for printing the contents of the heap in a tree-like structure static void heap_print_rec(struct Heap *heap, void (* print_fn)(void*), int index, int depth) { - - // Base case - stop if current index is out of bounds if (index >= heap->length) return; - // Compute right child index and recurse down on the right side + // Right subtree, then node, then left subtree: prints the heap on its side + // with the root flush left. int right_idx = 2 * index + 2; - heap_print_rec(heap, print_fn, right_idx, depth + 1); + heap_print_rec(heap, print_fn, right_idx, depth + 1); - // Print current node with indentation for (int i = 0; i < depth; i++) printf(" "); void *current_element = (char*) heap->elements + index * heap->element_size; - print_fn(current_element); + print_fn(current_element); printf("\n"); - // Compute left child index and recurse down on left side int left_child = 2 * index + 1; - heap_print_rec(heap, print_fn, left_child, depth + 1); + heap_print_rec(heap, print_fn, left_child, depth + 1); } -// Resizes the heaps internal array +// Doubles the backing array. Returns false if realloc fails (heap unchanged). static bool heap_resize(struct Heap *heap) { - - // Compute new capacity, reallocate more memory for the larger array, and - // handle allocation failure int new_capacity = heap->capacity * 2; void *new_elements = realloc(heap->elements, new_capacity * heap->element_size); if (new_elements == NULL) return false; @@ -187,42 +138,26 @@ static bool heap_resize(struct Heap *heap) { return true; } -// Swaps the raw bytes between two memory locations to swap the values stored in two variables +// Byte-wise swap of two element_size-byte elements. static void heap_swap(void *a, void *b, size_t element_size) { - - // Allocate memory to temporarily store the memory of the value being swapped - // and copy value into it void *temp = malloc(element_size); memcpy(temp, a, element_size); - - // Copy contents from `b` into `a` - memcpy(a, b, element_size); - - // Copy contents from `temp` into `b` + memcpy(a, b, element_size); memcpy(b, temp, element_size); - // Free memory used by the memory allocated for the temporary free(temp); temp = NULL; } -// Repeatedly compares the element at the provided index with its parent, swapping if the parent -// is smaller to maintain the max heap property +// Sift up: while this element outranks its parent by compare(), swap them and +// keep walking toward the root. static void heapify_up(struct Heap *heap, int current_idx, int (*compare)(void*, void*)) { - - // Continue moving up the heap until we reach the root while (current_idx > 0) { - - // Compute the pointer to the element at the current index, the parent index, - // and the pointer to the parent element in memory void *current = (char*) heap->elements + current_idx * heap->element_size; int parent_idx = (current_idx - 1) / 2; void *parent = (char*) heap->elements + parent_idx * heap->element_size; - - // Compare the newly added element with parent to see if parent is smaller + if (compare(current, parent) > 0) { - // If parent is smaller, swap the current value with parent to maintain max heap property - // and advance current index and current pointer to continue walking up the heap heap_swap(current, parent, heap->element_size); current_idx = parent_idx; current = parent; @@ -232,55 +167,39 @@ static void heapify_up(struct Heap *heap, int current_idx, int (*compare)(void*, } } -// Repeatedly compares the element at the provided index with its children, swapping with the larger -// child if it exists to maintain the max-heap property +// Sift down: repeatedly swap with the higher-ranked child until neither child +// outranks this element. static void heapify_down(struct Heap *heap, int current_idx, int (*compare)(void*, void*)) { - - // Declared to hold the larger index between the left and right children int max_child_idx; - // Continue looping while there is atleast a left child (if no left child, there will be no right) - // If the calculated index for the left child is less than the total length of the array, the left childs index points - // to a valid memory slot inside the arrays boundaries that is currently occupied by a valid element, so the left child exists. - // Otherwise the left index falls outside of the arrays bounds, and does not exist. + // Loop as long as a left child is in bounds (no left child => no right child). while (2 * current_idx + 1 < heap->length) { - // Compute left and right child indexes int left_idx = 2 * current_idx + 1; int right_idx = 2 * current_idx + 2; - // Set max child index to left child by default since we know it exists + // Left child is known to exist; prefer the right one only if it also + // exists and outranks the left. max_child_idx = left_idx; - // Check if the right child exists based on whether the calculated index is within valid bounds of the array if (right_idx < heap->length) { - - // Compute left and right child pointers now that we know both exist void *left_child = (char *) heap->elements + left_idx * heap->element_size; void *right_child = (char *) heap->elements + right_idx * heap->element_size; - - // Check if the right child is larger + if (compare(right_child, left_child) > 0) { max_child_idx = right_idx; } } - // Compute pointer to the current element and larger child element in memory void *current = (char *) heap->elements + current_idx * heap->element_size; void *max_child = (char *) heap->elements + max_child_idx * heap->element_size; - // Check if the child is larger than the current element if (compare(max_child, current) > 0) { - - // Swap the current and max child and - // set current index to max child index to walk further down the heap heap_swap(current, max_child, heap->element_size); current_idx = max_child_idx; } else { - // If child is not larger, max-heap property satisfied and we can exit + // Neither child outranks current: heap order is restored. break; } } } - - diff --git a/src/linked_list.c b/src/linked_list.c index 22240d8..221ac74 100644 --- a/src/linked_list.c +++ b/src/linked_list.c @@ -9,14 +9,12 @@ #include #include "linked_list.h" -/** - * Defines the doubly Node struct type - */ +// Each node owns a heap copy of its value (item_size bytes). typedef struct Node { - void *value; // Pointer to the value stored in this node - size_t item_size; // The size of the item stored in the node - struct Node *next; // Pointer to the next node in the list - struct Node *prev; // Pointer to the previous node in the list + void *value; + size_t item_size; + struct Node *next; + struct Node *prev; } Node; // Prototypes @@ -26,19 +24,12 @@ static void ll_discard_all_nodes(struct LinkedList *list); static void swap_node_positions(struct LinkedList *list, struct Node *a, struct Node *b); static void swap_nodes(struct Node *a, struct Node *b); -/** - * Creates a new linked list - */ struct LinkedList * ll_create() { - // Allocate mmeory for the linked list itself struct LinkedList *linked_list = malloc(sizeof(struct LinkedList)); - - // Handle allocation failure - if (linked_list == NULL) { + if (linked_list == NULL) { return NULL; } - // Set initial length of the linked list and pointers to head/tail to NULL linked_list->length = 0; linked_list->head = NULL; linked_list->tail = NULL; @@ -46,30 +37,19 @@ struct LinkedList * ll_create() { return linked_list; } -/** - * Inserts a node at the head of the linked list - */ void ll_insert(struct LinkedList *list, void *value, size_t item_size) { - - // Create node to insert struct Node *node = ll_create_node(value, item_size); - // Check if the `head` of the list points to NULL since we - // can only update the old heads `prev` pointer if there is an old head + // Only the old head has a prev pointer to fix up; an empty list has none. if (list->head != NULL) { list->head->prev = node; } - // Point the node being inserted `next` pointer to whatever the head of the list - // currently points to, and the `prev` pointer of the node being inserted to NULL - // since its the new head of the list. Point the head of the list to the node being - // inserted - node->next = list->head; - node->prev = NULL; + node->next = list->head; + node->prev = NULL; list->head = node; - // Check if the tail of the list is NULL, meaning list is empty - // If it is, set the tail to the node being inserted since its the only node in the list + // First node: it is the tail as well as the head. if (list->tail == NULL) { list->tail = node; } @@ -77,30 +57,18 @@ void ll_insert(struct LinkedList *list, void *value, size_t item_size) { list->length++; } -/** - * Inserts a node at the tail of the linked list - */ void ll_insert_tail(struct LinkedList *list, void *value, size_t item_size) { - - // Create node to insert struct Node *node = ll_create_node(value, item_size); - // Check if list is empty if (list->head == NULL) { - // If list is empty, point the node being inserted `next` pointer to whatever the head of - // the list currently points to, the node being inserted `prev` to NULL since its the new - // head of the list, and the tail to the node inserted since its the only node in the list + // Empty list: the new node is both head and tail. node->next = list->head; node->prev = NULL; list->head = node; list->tail = node; list->length++; - // List not empty, need to access and insert at tail } else { - // If list is not empty, set the current tail `next` pointer to the node being inserted, - // and the node being inserted `next` to NULL since its the new tail. To link the new tail - // with the old, set the `prev` pointer of the node being inserted to point to the old tail list->tail->next = node; node->next = NULL; node->prev = list->tail; @@ -109,45 +77,26 @@ void ll_insert_tail(struct LinkedList *list, void *value, size_t item_size) { } } -/** - * Inserts a node at the specified location in the linked list - */ void ll_insert_at(struct LinkedList *list, void *value, size_t item_size, int index) { - - // Create node to insert if (index < 0 || index > list->length) return; struct Node *node = ll_create_node(value, item_size); - // Check if insertion point is the head of the list if (index == 0) { ll_insert(list, value, item_size); return; - // Check if insertion point is the tail of the list } else if (index == list->length) { ll_insert_tail(list, value, item_size); return; - // If insertion point is not head or tail, its somewhere in between } else { - - // Traverse the list starting from the head to find the insertion point int idx = 0; for (struct Node *current = list->head; current != NULL; current = current->next) { - - // Check if weve reached the insertion point if (idx == index) { - - // If weve reached the insertion point, store temporary pointers - // to the node before the current node and the current node itself struct Node *previous = current->prev; struct Node *next = current; - // Update pointers to insert the new node - // Point the previous nodes `next` to the new node - // Point the current nodes `prev` to the new node - // Point the new nodes `next` to the current node - // Point the new nodes `prev` to the previous node + // Splice node in between previous and current. previous->next = node; next->prev = node; node->next = next; @@ -160,39 +109,27 @@ void ll_insert_at(struct LinkedList *list, void *value, size_t item_size, int in } } -/** - * Removes the node at the head of the linked list - */ void ll_remove(struct LinkedList *list) { if (list->head == NULL) return; - // Store pointer to the old head before removal to keep the link to rest of the list - // and free memory allocated by the old head to remove it and set new head struct Node *temp = list->head->next; free(list->head->value); free(list->head); list->head = temp; - // Check if the list is empty after removing the old head if (list->head != NULL) { - // If list not empty, update the new head `prev` pointer to point to NULL list->head->prev = NULL; } else { - // If the list is now empty, update the tail pointer since no nodes are left + // That was the only node, so the tail pointer is now stale too. list->tail = NULL; } list->length--; } -/** - * Removes the node at the tail of the linked list - */ void ll_remove_tail(struct LinkedList *list) { if (list->head == NULL) return; - // Check if there is only one node in the list if (list->head == list->tail) { - // Free the memory allocated by the head node and update the head/tail pointers free(list->head->value); free(list->head); list->head = NULL; @@ -200,18 +137,11 @@ void ll_remove_tail(struct LinkedList *list) { list->length--; return; - // Otherwise access the tail node to append the new node } else { - - // Store pointer to the node before the current tail, - // this is the node that will become the new tail, and - // free memory allocated by the current tail struct Node *new_tail = list->tail->prev; free(list->tail->value); free(list->tail); - // Set the new tail of the list and update the new tails - // `next` pointer to point to NULL list->tail = new_tail; list->tail->next = NULL; list->length--; @@ -219,44 +149,27 @@ void ll_remove_tail(struct LinkedList *list) { } } -/** - * Removes a node at a specified location in the linked list - */ void ll_remove_at(struct LinkedList *list, int index) { if (index < 0 || index >= list->length) return; - // Check if head node is being removed if (index == 0) { ll_remove(list); return; - - // Check if tail node is being removed + } else if (index == list->length - 1) { ll_remove_tail(list); return; - // Otherwise node being removed is in between } else { - - // Need to traverse from head to tail to find location of node being removed int idx = 0; for (struct Node *current = list->head; current != NULL; current = current->next) { - - // Check if were at the removal point - // Current node is the node to remove if (idx == index) { - - // Store pointers to the nodes before and after the current node struct Node *previous = current->prev; struct Node *next = current->next; - // Free the current node to remove it free(current->value); free(current); - // Relink the surrounding nodes by setting the previous nodes `next` to the - // node that followed the removed node, and the next nodes `prev` to the - // previous node previous->next = next; next->prev = previous; list->length--; @@ -267,33 +180,22 @@ void ll_remove_at(struct LinkedList *list, int index) { } } -/** - * Retrieves the value stored in the node at the location specified - */ bool ll_get(struct LinkedList *list, int index, void *out) { if (index < 0 || index >= list->length) return false; - // Check if location (index) is in the second half of the list + // Walk in from whichever end is closer to index. if (index > list->length / 2) { - - // Traverse starting from the tail of the list int idx = list->length - 1; for (struct Node *current = list->tail; current != NULL; current = current->prev) { if (idx == index) { - // If position is reached, copy the raw memory from where the value is stored into the - // memory pointed to by the `out` pointer memcpy(out, current->value, current->item_size); return true; } idx--; } - // Otherwise start traversal from head } else { int idx = 0; for (struct Node *current = list->head; current != NULL; current = current->next) { - - // If weve reached the position of the node, copy its memory contents into the `out` pointer - // and return true if (idx == index) { memcpy(out, current->value, current->item_size); return true; @@ -304,33 +206,22 @@ bool ll_get(struct LinkedList *list, int index, void *out) { return false; } -/** - * Sets the value of the node at the specified location (index) in the linked list - */ bool ll_set(struct LinkedList *list, int index, void *in) { if (index < 0 || index >= list->length) return false; - // Check if location is closer to tail + // Walk in from whichever end is closer to index. if (index > list->length / 2) { - - // Traverse from tail to head int idx = list->length - 1; for (struct Node *current = list->tail; current != NULL; current = current->prev) { - // If weve reached the target node, copy the data pointed to by the `in` pointer - // into the memory allocated for the nodes value if (idx == index) { memcpy(current->value, in, current->item_size); return true; } idx--; } - // Location is closer to head } else { - - // Traverse from head to tail int idx = 0; for (struct Node *current = list->head; current != NULL; current = current->next) { - // If reached target node, copy memory pointed to by `in` into `current->value` if (idx == index) { memcpy(current->value, in, current->item_size); return true; @@ -341,18 +232,11 @@ bool ll_set(struct LinkedList *list, int index, void *in) { return false; } -/** - * Finds the node with the specified value and returns the location (index) of the node, or -1 if not found. - * Relies on a caller defined comparator function to determine how to compare items of the type stored - */ int ll_find(struct LinkedList *list, void *item, bool (* comparator)(void*, void*)) { if (list->head == NULL) return -1; - // Traverse from head to tail int idx = 0; for (struct Node *current = list->head; current != NULL; current = current->next) { - - // If the current value is equal to the value being searched for, return current index if (comparator(current->value, item)) { return idx; } @@ -361,9 +245,6 @@ int ll_find(struct LinkedList *list, void *item, bool (* comparator)(void*, void return -1; } -/** - * Returns a boolean value indicating whether the linked list contains the value specified or not - */ bool ll_contains(struct LinkedList *list, void *item, bool (* comparator)(void*, void*)) { if (list->head == NULL) return false; @@ -377,9 +258,6 @@ bool ll_contains(struct LinkedList *list, void *item, bool (* comparator)(void*, return false; } -/** - * Clears each node in the linked list - */ void ll_clear(struct LinkedList *list) { if (list->head == NULL) return; @@ -389,73 +267,47 @@ void ll_clear(struct LinkedList *list) { list->length = 0; } -/** - * Creates and returns a deep copy of a linked list - */ struct LinkedList* ll_copy(struct LinkedList *orig) { struct LinkedList *copy = ll_create(); - // Traverse from the original lists head to tail, and for each node in the original list - // initialize a new node which is a copy of the current node to prevent breaking the links - // between the nodes for (struct Node *current = orig->head; current != NULL; current = current->next) { struct Node *cnode = ll_create_node(current->value, current->item_size); - ll_insert_tail(copy, cnode->value, cnode->item_size); // CHECK THIS !!!!! + // FIXME: ll_insert_tail makes its own copy of the value, so cnode + // (node struct + value buffer) is never linked in and never freed. + ll_insert_tail(copy, cnode->value, cnode->item_size); } return copy; } -/** - * Reverses the contents of the linked list in place - */ void ll_reverse(struct LinkedList *list) { if (list->head == NULL) return; - // Store pointers to the current node and the most recently processed node - // Start at the head of the list, and with the most recently processed node set to NULL struct Node *current = list->head; struct Node *last = NULL; - // Rely on the node currently being processed while (current != NULL) { - - // Store the current nodes `next` node to prevent losing the link to the rest of the list - // Then we point the current nodes `next` pointer to its `prev` to flip the pointer + // Save next before we overwrite this node's link fields. struct Node *temp = current->next; current->next = current->prev; - - // To move to the next node, point the current nodes `prev` to the original next node - // Store pointer to the node just reversed in `last` to track most recently reversed node - // Point the current node to the original next node current->prev = temp; last = current; current = temp; } - // Point the tail of the list to the head, - // and the head to the most recently reversed node + // Old head is now the tail; the last node visited is the new head. list->tail = list->head; list->head = last; } -/** - * Returns whether the linked list is empty or not - */ bool ll_is_empty(struct LinkedList *list) { if (list->head == NULL) return true; return false; } -/** - * Returns the size (length) of the linked list - */ int ll_size(struct LinkedList *list) { return list->length; } -/** - * Frees all memory associated with a linked list - */ void ll_discard(struct LinkedList *list) { if (list != NULL) { ll_discard_all_nodes(list); @@ -463,10 +315,6 @@ void ll_discard(struct LinkedList *list) { } } -/** - * Prints all items stored in the linked list, requiring a caller defined print function - * that knows how to print each value - */ void ll_print(struct LinkedList *list, void (* print_fn)(void*)) { for (struct Node *current = list->head; current != NULL; current = current->next) { print_fn(current->value); @@ -474,26 +322,19 @@ void ll_print(struct LinkedList *list, void (* print_fn)(void*)) { printf("\n"); } -/** - * Prints each node in the linked list with its address, value, and next/prev pointers for debugging - */ void ll_debug(struct LinkedList *list) { struct Node *curr = list->head; while (curr != NULL) { - printf("Node %p | value=%d | prev=%p | next=%p\n", - (void *) curr, - * (int *) curr->value, - (void *) curr->prev, + printf("Node %p | value=%d | prev=%p | next=%p\n", + (void *) curr, + * (int *) curr->value, + (void *) curr->prev, (void *) curr->next); curr = curr->next; } } -/** - * Sorts a linked list using a Bubble Sort implementation and relying on a caller defined comparator to - * know how to order items - */ -void ll_bsort(struct LinkedList *list, bool (* comparator)(void*, void*)) { +void ll_bsort(struct LinkedList *list, bool (* comparator)(void*, void*)) { if (list->head == NULL) return; bool swapped = true; @@ -516,39 +357,28 @@ void ll_bsort(struct LinkedList *list, bool (* comparator)(void*, void*)) { // Private helper functions - linkage limited to this file -// Creates a new node +// Deep-copies size bytes from val; the returned node owns the copy. struct Node * ll_create_node(void *val, size_t size) { - - // Allocate memory for the node itself and struct Node *node = malloc(sizeof(struct Node)); - - // Handle allocation failure if (node == NULL) { return NULL; } - // Set the size of the item stored in the node and initialize - // the nodes `next` and `prev` pointers to NULL node->item_size = size; node->next = NULL; node->prev = NULL; - // Allocate memory for the value that will be stored in the node node->value = malloc(node->item_size); - - // Handle allocation failure if (node->value == NULL) { free(node); return NULL; } - // Copy the raw memory contents from the memory pointed to by `val` into `node->value` memcpy(node->value, val, node->item_size); return node; } -// Frees the memory allocated by a single node static void ll_discard_node(struct Node *node) { if (node != NULL) { free(node->value); @@ -556,14 +386,11 @@ static void ll_discard_node(struct Node *node) { } } -// Frees the memory allocated by each node in a linked list static void ll_discard_all_nodes(struct LinkedList *list) { if (list != NULL) { - // Start from the head of the list, traverse to the end struct Node *current = list->head; while (current != NULL) { - // Store the nodes `next` pointer before freeing the current node - // to avoid losing reference to the rest of the list + // Grab next before freeing current. struct Node *next = current->next; ll_discard_node(current); current = next; @@ -571,87 +398,77 @@ static void ll_discard_all_nodes(struct LinkedList *list) { } } -// Swaps the value stored within two nodes +// Swaps the values held by two nodes, leaving the nodes themselves in place. +// This is what ll_bsort uses; swap_node_positions (below) relinks instead. static void swap_nodes(struct Node *a, struct Node *b) { void *temp = a->value; a->value = b->value; b->value = temp; } -// Swaps the positions of two nodes in the linked list +// Relinks two nodes so they trade places in the list. Currently unused. static void swap_node_positions(struct LinkedList *list, struct Node *a, struct Node *b) { - // Check if the two nodes are the same, if they are no swap required if (a == b) { return; - // Check if the two nodes are adjacent (are beside eachother) } else if (a->next == b || b->next == a) { + // Adjacent nodes: handled on their own since there are fewer links to fix. - // Handle case where ... if (b->next == a) { - // swap the local variables so that `a` is always the left node + // Normalize so a is always the left (earlier) node. struct Node *tmp = a; a = b; b = tmp; } - // Track surrounding nodes - struct Node *aprev = a->prev; // could be NULL, need to check - struct Node *bnext = b->next; + struct Node *aprev = a->prev; // may be NULL (a is the head) + struct Node *bnext = b->next; // may be NULL (b is the tail) - // Update pointers of the two nodes themselves - b->prev = aprev; // this could be NULL and that is ok - b->next = a; // point next to `a` + b->prev = aprev; + b->next = a; - a->next = bnext; // this could also be NULL, also ok - a->prev = b; // point back to `b` + a->next = bnext; + a->prev = b; - if (aprev != NULL) { // as long as a->prev is not NULL then it can point `next` to b + // The outer nodes only need fixing up when they actually exist. + if (aprev != NULL) { aprev->next = b; } - if (bnext != NULL) { // as long as b->next is not NULL then it can point `prev` to a + if (bnext != NULL) { bnext->prev = a; } - // If head was swapped, reassign it + // Move head/tail if either endpoint was one of the swapped nodes. if (list->head == a) { list->head = b; } else if (list->head == b) { list->head = a; } - // If tail was swapped, reassign it if (list->tail == a) { list->tail = b; } else if (list->tail == b) { list->tail = a; } - // Nodes not adjadent } else { - - // Track all surrounding nodes of the two nodes being swapped + // Non-adjacent nodes: point each one at the other's old neighbours. struct Node *aprev = a->prev; struct Node *anext = a->next; struct Node *bprev = b->prev; struct Node *bnext = b->next; - // Reassign the `b` node to the nodes previosly pointed to by `a` b->prev = aprev; b->next = anext; - // Reassign the node `a` to the nodes previously pointed to by `b` a->next = bnext; a->prev = bprev; - // If the node `aprev` exists, point its `next` pointer to `b` to reconnect the - // previous node to the new position of `b` + // Reconnect the surrounding nodes that exist. if (aprev != NULL) { aprev->next = b; } - // If the node `anext` exists, point its `prev` pointer to reconnect the - // next node to the new position of `b` if (anext != NULL) { anext->prev = b; } @@ -664,14 +481,13 @@ static void swap_node_positions(struct LinkedList *list, struct Node *a, struct bnext->prev = a; } - // If head was swapped, reassign it + // Move head/tail if either endpoint was one of the swapped nodes. if (list->head == a) { list->head = b; } else if (list->head == b) { list->head = a; } - // If tail was swapped, reassign it if (list->tail == a) { list->tail = b; } else if (list->tail == b) { diff --git a/src/memory_pool.c b/src/memory_pool.c index e9033f0..f4fcaa6 100644 --- a/src/memory_pool.c +++ b/src/memory_pool.c @@ -10,10 +10,6 @@ #include #include "memory_pool.h" -/** - * Creates a MemoryPool backed by a single contiguous memory region. - * Initializes metadata for fixed-size block allocation. - */ struct MemoryPool *mp_create(int num_blocks, int block_size) { struct MemoryPool *pool = malloc(sizeof(struct MemoryPool)); if (pool == NULL) return NULL; @@ -31,6 +27,9 @@ struct MemoryPool *mp_create(int num_blocks, int block_size) { memset(pool->memory, 0, MEMORY_SIZE(pool->block_size, pool->num_blocks)); pool->first_free = (struct MemoryBlockHeader*) pool->memory; + // Thread every block onto the free list. From one header, (current_block + 1) + // steps past the header and + block_size steps past the usable region, landing + // on the next block's header. struct MemoryBlockHeader *current_block = pool->first_free; for (int i = 0; i < pool->num_blocks - 1; i++) { current_block->next = (struct MemoryBlockHeader *)((char *)(current_block + 1) + pool->block_size); @@ -41,14 +40,11 @@ struct MemoryPool *mp_create(int num_blocks, int block_size) { return pool; } -/** - * Allocates a block from the memory pool and returns a pointer - * to its usable data region. - */ void *mp_alloc(struct MemoryPool *pool) { void *data = NULL; if (pool->num_free_blocks > 0) { + // Hand back the region just past the block's header. data = (void *)((char *) pool->first_free + sizeof(struct MemoryBlockHeader)); pool->first_free = pool->first_free->next; pool->num_free_blocks--; @@ -56,13 +52,9 @@ void *mp_alloc(struct MemoryPool *pool) { return data; } -/** - * Frees a block of memory and reinserts it into the pool's free list. - */ void mp_free(struct MemoryPool *pool, void *ptr) { if (pool->num_free_blocks < pool->num_blocks) { - - // Convert user pointer back to block header + // Step back from the user pointer to its header, then push onto the free list. struct MemoryBlockHeader *block = (struct MemoryBlockHeader *)((char *) ptr - sizeof(struct MemoryBlockHeader)); block->next = pool->first_free; pool->first_free = block; @@ -70,9 +62,6 @@ void mp_free(struct MemoryPool *pool, void *ptr) { } } -/** - * Prints the addresses of all free memory blocks in the pool. - */ void mp_print(struct MemoryPool *pool) { printf("Number of free blocks: %d\n", pool->num_free_blocks); @@ -83,9 +72,6 @@ void mp_print(struct MemoryPool *pool) { } } -/** - * Frees the memory pool and all memory it allocated. - */ void mp_discard(struct MemoryPool *pool) { if (pool != NULL) { free(pool->memory); diff --git a/src/queue.c b/src/queue.c index c2c207f..b22899c 100644 --- a/src/queue.c +++ b/src/queue.c @@ -9,13 +9,11 @@ #include #include "queue.h" -/** - * Defines the Node struct type stored in the queue. - */ +// Each node owns a heap copy of its value (item_size bytes). typedef struct Node { - void *value; // Pointer to the value stored in the node - size_t item_size; // The size of the item stored in this node - struct Node *next; // Pointer to the next node in the queue + void *value; + size_t item_size; + struct Node *next; } Node; // Prototypes @@ -23,62 +21,42 @@ static struct Node* queue_create_node(void *val, size_t size); static void queue_discard_node(struct Node *node); static void queue_discard_all_nodes(struct Queue *queue); -/** - * Creates a new queue - */ struct Queue* queue_create() { - // Allocate memory for the queue itself and handle allocation failure struct Queue *queue = malloc(sizeof(struct Queue)); if (queue == NULL) return NULL; - // Set initial length and pointers to head/tail queue->length = 0; queue->head = NULL; queue->tail = NULL; return queue; } -/** - * Adds an item to the tail of the queue - */ void queue_enqueue(struct Queue *queue, void *val, size_t item_size) { - struct Node *node = queue_create_node(val, item_size); if (queue_is_empty(queue)) { - // Queue is empty so node being inserted is both the head and tail + // First node is both head and tail. queue->tail = node; queue->head = node; queue->length++; return; } else { - // Queue isnt empty so enqueue to the new node to the tail of the queue queue->tail->next = node; queue->tail = node; queue->length++; } } -/** - * Returns the value stored in the head node of the queue but does not remove it - */ bool queue_peek(struct Queue *queue, void *out) { if (queue_is_empty(queue)) return false; memcpy(out, queue->head->value, queue->head->item_size); return true; } -/** - * Removes and returns the node at the head of the queue - */ bool queue_dequeue(struct Queue *queue, void *out) { if (queue_is_empty(queue)) return false; - // Copy raw memory pointed to by the head into the `out` pointer memcpy(out, queue->head->value, queue->head->item_size); - // Store the `next` pointer of the current head of the queue - // and discard the memory allocated by the current head - // then reassign the new head struct Node *temp = queue->head->next; queue_discard_node(queue->head); queue->head = temp; @@ -86,24 +64,15 @@ bool queue_dequeue(struct Queue *queue, void *out) { return true; } -/** - * Returns a boolean value indicating whether the queue is empty or not. - */ bool queue_is_empty(struct Queue *queue) { if (queue->head == NULL) return true; return false; } -/** - * Returns the length of the queue. - */ int queue_length(struct Queue *queue) { return queue->length; } -/** - * Clears the contents of the queue - */ void queue_clear(struct Queue *queue) { if (queue_is_empty(queue)) return; queue_discard_all_nodes(queue); @@ -112,9 +81,6 @@ void queue_clear(struct Queue *queue) { queue->length = 0; } -/** - * Prints the contents of the queue - */ void queue_print(struct Queue *queue, void (* print_fn)(void*)) { struct Node *current = queue->head; while (current != NULL) { @@ -124,17 +90,11 @@ void queue_print(struct Queue *queue, void (* print_fn)(void*)) { printf("\n"); } -/** - * Prints the links between the nodes stored in the queue - */ void queue_debug(struct Queue *queue) { if (queue_is_empty(queue)) return; printf("Queue: Length: %d, Head: %p, Head Next: %p, Tail: %p, Tail Next: %p\n", queue_length(queue), queue->head, queue->head->next, queue->tail, queue->tail->next); } -/** - * Frees the memory occupied by the queue. - */ void queue_discard(struct Queue *queue) { if (queue != NULL) { queue_discard_all_nodes(queue); @@ -146,46 +106,35 @@ void queue_discard(struct Queue *queue) { // Private helper functions - linkage limited to this file -// Creates a new node to be stored in the queue +// Deep-copies size bytes from val; the returned node owns the copy. static struct Node* queue_create_node(void *val, size_t size) { - - // Allocate memory for the node itself and handle allocation failure struct Node *node = malloc(sizeof(struct Node)); if (node == NULL) return NULL; - // Set size of item stored in node and initialize next pointer node->item_size = size; node->next = NULL; - // Allocate memory for the value to be stored in the node - // and handle allocation failure node->value = malloc(node->item_size); if (node->value == NULL) { free(node); return NULL; } - // Copy memory pointed to by `val` into the memory pointed to by the node memcpy(node->value, val, node->item_size); return node; } -// Frees the memory occupied by a single node static void queue_discard_node(struct Node *node) { if (node != NULL) { - // Free memory occupied by the value stored in the node - // and the node itself free(node->value); free(node); } } -// Frees the memory occupied by each node in the queue static void queue_discard_all_nodes(struct Queue *queue) { struct Node *current = queue->head; while (current != NULL) { - // Store pointer to next node in queue before freeing to prevent - // losing reference to the rest of queue + // Grab next before freeing current. struct Node *next = current->next; queue_discard_node(current); current = next; diff --git a/src/set.c b/src/set.c index 5bb9455..a00223c 100644 --- a/src/set.c +++ b/src/set.c @@ -9,11 +9,9 @@ #include #include "set.h" +// Stand-in value stored against every key; the set only cares about presence. static const char SET_DUMMY = 1; -/** - * Creates and returns a new Set. - */ Set* set_create(size_t element_size) { Set *set = malloc(sizeof(Set)); if (set == NULL) return NULL; @@ -29,11 +27,6 @@ Set* set_create(size_t element_size) { return set; } -/** - * Inserts a new value into the set if it does not already exist. - * - * @return true if the value did not exist in the set and was inserted, otherwise false. - */ bool set_add(Set *set, void *value) { if (!ht_contains(set->table, value, set->element_size)) { ht_insert(set->table, value, set->element_size, (void*) &SET_DUMMY, sizeof(char)); @@ -42,41 +35,22 @@ bool set_add(Set *set, void *value) { return false; } -/** - * Removes a value from the set if it exists. - * - * @return true if the value was removed, false otherwise. - */ bool set_remove(Set *set, void *value) { return ht_remove(set->table, value, set->element_size); } -/** - * Checks if the Set contains a given value. - * - * @return true if the value is found in the set, otherwise false. - */ bool set_contains(Set *set, void *value) { return ht_contains(set->table, value, set->element_size); } -/** - * Returns the size (length) of the set. - */ int set_size(Set *set) { return ht_size(set->table); } -/** - * Prints the contents of the set. - */ void set_print(Set *set, void (*printfunc)(void*, void*)) { ht_print(set->table, printfunc); } -/** - * Frees the memory previous allocated by the set. - */ void set_discard(Set *set) { if (set != NULL) { ht_discard(set->table); diff --git a/src/stack.c b/src/stack.c index aa1845a..cce960a 100644 --- a/src/stack.c +++ b/src/stack.c @@ -9,13 +9,11 @@ #include #include "stack.h" -/** - * Defines the Node struct type stored in the stack. - */ +// Each node owns a heap copy of its value (item_size bytes). typedef struct Node { - void *value; // Pointer to the memory holding the value stored in this node - size_t item_size; // Size of the item stored in the node - struct Node *next; // Pointer to the next node in the stack + void *value; + size_t item_size; + struct Node *next; } Node; // Prototypes @@ -23,66 +21,43 @@ static struct Node* stack_create_node(void *val, size_t size); static void stack_discard_node(struct Node *node); static void stack_discard_all_nodes(struct Stack *stack); -/** - * Creates a new stack - */ -struct Stack* stack_create() { - - // Allocate memory for the stack struct and - // handle allocation failure +struct Stack* stack_create() { struct Stack *stack = malloc(sizeof(struct Stack)); if (stack == NULL) return NULL; - // Set pointer to top node to NULL and length to 0 - // as default stack->top = NULL; stack->length = 0; return stack; } -/** - * Pushes an item to the top of the stack. - */ bool stack_push(struct Stack *stack, void *value, size_t size) { - struct Node *node = stack_create_node(value, size); if (stack_is_empty(stack)) { - // Set the node as the top of the stack stack->top = node; stack->top->next = NULL; stack->length++; return true; } else { - // Store pointer to the current top node, reassign the top to the - // node being pushed, and relink nodes to maintain stack + // Link the new node on top of the current top. struct Node *temp = stack->top; - stack->top = node; + stack->top = node; stack->top->next = temp; stack->length++; return true; } } -/** - * Returns the value stored in the node at the top of the stack without removing the node. - */ bool stack_peek(struct Stack *stack, void *out) { if (stack_is_empty(stack)) return false; memcpy(out, stack->top->value, stack->top->item_size); return true; } -/** - * Pops the node stored at the top of the stack. - */ bool stack_pop(struct Stack *stack, void *out) { if (stack_is_empty(stack)) return false; - // Store pointer to the next node pointed to by the top of the stack, - // copy its memory contents into the memory pointed to by the `out` pointer - // Then free the node and relink the stack struct Node *temp = stack->top->next; memcpy(out, stack->top->value, stack->top->item_size); stack_discard_node(stack->top); @@ -91,42 +66,27 @@ bool stack_pop(struct Stack *stack, void *out) { return true; } -/** - * Clears the contents of the stack but keeps the stack itself. - */ -void stack_clear(struct Stack *stack) { +void stack_clear(struct Stack *stack) { stack_discard_all_nodes(stack); stack->top = NULL; stack->length = 0; } -/** - * Returns a boolean value indicating whether the stack is empty or not. - */ bool stack_is_empty(struct Stack *stack) { if (stack->top == NULL) return true; return false; } -/** - * Returns the current size of the stack (length). - */ int stack_size(struct Stack *stack) { return stack->length; } -/** - * Prints the contents of the stack from top to bottom. - */ void stack_print(struct Stack *stack, void (* print_fn)(void*)) { for (struct Node *current = stack->top; current != NULL; current = current->next) { print_fn(current->value); } } -/** - * Frees the memory occupied by the stack. - */ void stack_discard(struct Stack *stack) { if (stack != NULL) { stack_discard_all_nodes(stack); @@ -138,33 +98,24 @@ void stack_discard(struct Stack *stack) { // Private helper functions - linkage limited to this file -// Creates a new node to be stored in the stack +// Deep-copies size bytes from val; the returned node owns the copy. static struct Node* stack_create_node(void *val, size_t size) { - - // Allocate memory for the node struct itself and - // handle allocation failure struct Node *node = malloc(sizeof(struct Node)); if (node == NULL) return NULL; - // Set size of item stored in the node and initial pointer to next node to NULL node->item_size = size; node->next = NULL; - // Allocate memory for the value the node will store and - // handle allocation failure node->value = malloc(size); if (node->value == NULL) { free(node); return NULL; } - // Copies the raw bytes from the memory pointed to by `val` into - // the memory allocated for this node at `node->value` memcpy(node->value, val, node->item_size); return node; } -// Frees the memory occupied by a single node static void stack_discard_node(struct Node *node) { if (node != NULL) { free(node->value); @@ -172,7 +123,6 @@ static void stack_discard_node(struct Node *node) { } } -// Frees the memory occupied by each node in the stack static void stack_discard_all_nodes(struct Stack *stack) { struct Node *current = stack->top; while (current != NULL) { diff --git a/src/str.c b/src/str.c index 13c6359..f34dc0a 100644 --- a/src/str.c +++ b/src/str.c @@ -11,7 +11,6 @@ static bool resize(String *str, size_t size); static bool is_whitespace(char c); -// Creates a new String String* str_create(const char *initial) { String *str = malloc(sizeof(String)); if (str == NULL) return NULL; @@ -26,13 +25,11 @@ String* str_create(const char *initial) { return NULL; } - // Dereference to set the first character stored in the buffer to null terminator *str->data = '\0'; str->length = 0; } else { size_t length = str_length(initial); - str->data = malloc(sizeof(char) * (str->initial_capacity + 1)); if (str->data == NULL) { free(str); @@ -40,18 +37,16 @@ String* str_create(const char *initial) { } if (resize(str, length)) { - memcpy(str->data, initial, length + 1); + memcpy(str->data, initial, length + 1); str->length = length; } else { free(str); return NULL; } } - return str; } -// Adds more characters to the end of an existing String bool str_append(String *str, const char *data) { size_t append_length = str_length(data); size_t new_length = str->length + append_length; @@ -66,12 +61,13 @@ bool str_append(String *str, const char *data) { } } -// Checks if a string contains a given substring -bool str_contains(String *str, const char *substr) { +bool str_contains(String *str, const char *substr) { char *curr_str = str->data; char *curr_substr = substr; char *match_start = NULL; + // Naive search: retry the match one character further along str after every + // failed attempt. while (*curr_str != '\0' && *curr_substr != '\0') { curr_substr = substr; match_start = curr_str + 1; @@ -87,9 +83,6 @@ bool str_contains(String *str, const char *substr) { return false; } -// Compares two strings lexicographically -// Returns 0 if equal, 1 if a > b, -1 if a < b, -// or the character difference if lengths differ int str_compare(String *a, String *b) { char *curr_a = a->data; char *curr_b = b->data; @@ -104,23 +97,21 @@ int str_compare(String *a, String *b) { curr_b++; } } - // return 0; + // Loop ended because a string ran out: 0 if both did, else the signed value + // of the leftover character (not necessarily +/-1). return *curr_a - *curr_b; } -// Returns a copy of the provided string String* str_copy(String *src) { if (src == NULL) return NULL; String *new_str = str_create(src->data); return new_str; } -// Checks if two strings are equal bool str_equals(String *a, String *b) { return str_compare(a, b) == 0; } -// Concatenates two strings, and returns a copy String* str_concat(String *str, String *substr) { if (str == NULL) { return NULL; @@ -134,7 +125,6 @@ String* str_concat(String *str, String *substr) { } } -// Converts a string to uppercase, and returns a copy of that string String* str_to_upper(String *str) { if (str == NULL) { return NULL; @@ -151,7 +141,6 @@ String* str_to_upper(String *str) { return str_upper; } -// Converts a string to lowercase, and returns a copy of that string String* str_to_lower(String *str) { if (str == NULL) { return NULL; @@ -168,13 +157,14 @@ String* str_to_lower(String *str) { return str_lower; } -// Trims whitespace from the start and end of a string, returns a copy String *str_trim(String *str) { if (str == NULL) { return NULL; } char *start = str->data; + // NOTE: assumes str is non-empty. For a zero-length String this is data - 1, + // and the trailing-whitespace loop below then reads before the buffer. char *end = str->data + str_len(str) - 1; while (is_whitespace(*start) && *start != '\0') { @@ -191,13 +181,12 @@ String *str_trim(String *str) { } memcpy(tempbuff, start, end - start + 1); tempbuff[end - start + 1] = '\0'; - + String *trimmed_str = str_create(tempbuff); free(tempbuff); return trimmed_str; } -// Returns the length of the string size_t str_length(const char *src) { const char *current = src; size_t length = 0; @@ -209,12 +198,10 @@ size_t str_length(const char *src) { return length; } -// Returns the length of string based on length member size_t str_len(String *str) { return str->length; } -// Prints the contents of the String void str_print(String *str) { char *current = str->data; while (*current != '\0') { @@ -223,7 +210,6 @@ void str_print(String *str) { } } -// Releases the String buffer and the String struct memory void str_discard(String *str) { if (str != NULL) { free(str->data); @@ -231,7 +217,8 @@ void str_discard(String *str) { } } -// Ensures the internal buffer has enough capacity for the requested number of characters +// Grows data so it can hold at least `size` characters plus the '\0', doubling +// capacity until it fits. No-op when the buffer already fits; false if realloc fails. static bool resize(String *str, size_t size) { if (size <= str->capacity) { return true; @@ -252,7 +239,6 @@ static bool resize(String *str, size_t size) { return true; } -// Returns true if char is whitespace, otherwise false static bool is_whitespace(char c) { return c == ' ' || c == '\t' || c == '\n' || c == '\r'; } From 7a1e0178adf8f5de053d7476b44f38be8962a1c5 Mon Sep 17 00:00:00 2001 From: Brendan Dileo <124395674+brendanddev@users.noreply.github.com> Date: Tue, 15 Sep 2026 17:59:38 -0400 Subject: [PATCH 13/15] chore: Cleanup comments --- include/binary_tree.h | 14 +++++--------- include/binarysearch_tree.h | 8 +++----- include/generic_array.h | 8 +++----- include/hash_table.h | 8 +++----- include/heap.h | 14 +++++--------- include/linked_list.h | 8 +++----- include/memory_pool.h | 20 +++++++------------- include/queue.h | 8 +++----- include/set.h | 14 +++++--------- include/stack.h | 8 +++----- include/str.h | 10 ++++------ include/struccs.h | 8 +++----- src/hash_table.c | 9 ++++----- src/heap.c | 9 ++++----- src/linked_list.c | 9 ++++----- src/memory_pool.c | 8 +++----- src/queue.c | 9 ++++----- src/set.c | 9 ++++----- src/stack.c | 9 ++++----- tests/test_bst.c | 9 ++++----- tests/test_bt.c | 8 +++----- tests/test_ga.c | 8 +++----- tests/test_heap.c | 8 +++----- tests/test_ht.c | 8 +++----- tests/test_ll.c | 8 +++----- tests/test_mp.c | 8 +++----- tests/test_queue.c | 8 +++----- tests/test_set.c | 8 +++----- tests/test_stack.c | 8 +++----- 29 files changed, 105 insertions(+), 166 deletions(-) diff --git a/include/binary_tree.h b/include/binary_tree.h index d754fc4..2558ef9 100644 --- a/include/binary_tree.h +++ b/include/binary_tree.h @@ -1,8 +1,6 @@ -/** - * binary_tree.h - * Header for the generic complete binary tree implementation, backed by an array. - * Brendan Dileo - 2025 - */ +// binary_tree.h +// Header for the generic complete binary tree implementation, backed by an array. +// Brendan Dileo - 2025 #ifndef BINARY_TREE_H #define BINARY_TREE_H @@ -11,10 +9,8 @@ #include #include -/** - * Elements are stored in a flat array, parent/child links are derived from - * array indices (left = 2i+1, right = 2i+2), so the tree is always complete. - */ +// Elements are stored in a flat array, parent/child links are derived from +// array indices (left = 2i+1, right = 2i+2), so the tree is always complete. typedef struct BinaryTree { int length; // number of elements in use int capacity; // slots allocated in the backing array diff --git a/include/binarysearch_tree.h b/include/binarysearch_tree.h index e6af815..01476b6 100644 --- a/include/binarysearch_tree.h +++ b/include/binarysearch_tree.h @@ -1,8 +1,6 @@ -/** - * binarysearch_tree.h - * Header for the generic binary search tree implementation with linked nodes. - * Brendan Dileo - 2025 - */ +// binarysearch_tree.h +// Header for the generic binary search tree implementation with linked nodes. +// Brendan Dileo - 2025 #ifndef BINARYSEARCH_TREE_H #define BINARYSEARCH_TREE_H diff --git a/include/generic_array.h b/include/generic_array.h index 77f8fde..1f4c235 100644 --- a/include/generic_array.h +++ b/include/generic_array.h @@ -1,8 +1,6 @@ -/** - * generic_array.h - * Header for the generic array implementation. - * Brendan Dileo - 2025 - */ +// generic_array.h +// Header for the generic array implementation. +// Brendan Dileo - 2025 #ifndef GENERIC_ARRAY_H #define GENERIC_ARRAY_H diff --git a/include/hash_table.h b/include/hash_table.h index 3048b08..34af456 100644 --- a/include/hash_table.h +++ b/include/hash_table.h @@ -1,8 +1,6 @@ -/** - * hash_table.h - * Header for the generic hash table implementation using seperate chaining to handle collisions. - * Brendan Dileo - 2025 - */ +// hash_table.h +// Header for the generic hash table implementation using seperate chaining to handle collisions. +// Brendan Dileo - 2025 #ifndef HASH_TABLE_H #define HASH_TABLE_H diff --git a/include/heap.h b/include/heap.h index eaf2a5c..4b6df87 100644 --- a/include/heap.h +++ b/include/heap.h @@ -1,8 +1,6 @@ -/** - * heap.h - * Header for the generic heap implementation backed by an array. - * Brendan Dileo - 2025 - */ +// heap.h +// Header for the generic heap implementation backed by an array. +// Brendan Dileo - 2025 #ifndef HEAP_H #define HEAP_H @@ -11,10 +9,8 @@ #include #include -/** - * Array-backed binary heap. Ordering is whatever the compare callback passed to - * insert/remove defines, pass a max-first comparator for a max-heap. - */ +// Array-backed binary heap. Ordering is whatever the compare callback passed to +// insert/remove defines, pass a max-first comparator for a max-heap. typedef struct Heap { void *elements; size_t element_size; diff --git a/include/linked_list.h b/include/linked_list.h index bfdec0b..7db6654 100644 --- a/include/linked_list.h +++ b/include/linked_list.h @@ -1,8 +1,6 @@ -/** - * linked_list.h - * Header for the generic doubly linked list implementation - * Brendan Dileo - 2025 - */ +// linked_list.h +// Header for the generic doubly linked list implementation +// Brendan Dileo - 2025 #ifndef LINKED_LIST_H #define LINKED_LIST_H diff --git a/include/memory_pool.h b/include/memory_pool.h index 99484b7..f9d8266 100644 --- a/include/memory_pool.h +++ b/include/memory_pool.h @@ -1,8 +1,6 @@ -/** - * memory_pool.h - * Header for the generic memory pool implementation. - * Brendan Dileo - 2026 - */ +// memory_pool.h +// Header for the generic memory pool implementation. +// Brendan Dileo - 2026 #ifndef MEMORY_POOL_H #define MEMORY_POOL_H @@ -14,18 +12,14 @@ // Total bytes for nblocks blocks: each block carries a header plus bsize usable bytes. #define MEMORY_SIZE(bsize, nblocks) ((bsize) + sizeof(struct MemoryBlockHeader)) * (nblocks) -/** - * Header prefixed to every block. While a block is free, next links it into the - * pool's free list; while it is handed out, the bytes after the header are the - * caller's to use. - */ +// Header prefixed to every block. While a block is free, next links it into the +// pool's free list; while it is handed out, the bytes after the header are the +// caller's to use. struct MemoryBlockHeader { struct MemoryBlockHeader *next; }; -/** - * Fixed-size block allocator over one pre-allocated buffer. - */ +// Fixed-size block allocator over one pre-allocated buffer. struct MemoryPool { char *memory; // the single backing allocation struct MemoryBlockHeader *first_free; // head of the free list diff --git a/include/queue.h b/include/queue.h index 3a58064..b51c27d 100644 --- a/include/queue.h +++ b/include/queue.h @@ -1,8 +1,6 @@ -/** - * queue.h - * Header for the generic queue implementation backed by linked nodes. - * Brendan Dileo - 2025 - */ +// queue.h +// Header for the generic queue implementation backed by linked nodes. +// Brendan Dileo - 2025 #ifndef QUEUE_H #define QUEUE_H diff --git a/include/set.h b/include/set.h index 641f6ba..07a4ac6 100644 --- a/include/set.h +++ b/include/set.h @@ -1,8 +1,6 @@ -/** - * set.h - * Header for the generic set implementation, backed by a hash table. - * Brendan Dileo - 2026 - */ +// set.h +// Header for the generic set implementation, backed by a hash table. +// Brendan Dileo - 2026 #ifndef SET_H #define SET_H @@ -12,10 +10,8 @@ #include #include "hash_table.h" -/** - * Thin wrapper over HashTable: stores each element as a key mapped to a dummy - * value. element_size is the byte width of every element. - */ +// Thin wrapper over HashTable: stores each element as a key mapped to a dummy +// value. element_size is the byte width of every element. typedef struct Set { size_t element_size; HashTable *table; diff --git a/include/stack.h b/include/stack.h index b7650b3..d9ff0ee 100644 --- a/include/stack.h +++ b/include/stack.h @@ -1,8 +1,6 @@ -/** - * stack.h - * Header for the generic stack implementation backed by linked nodes. - * Brendan Dileo - 2025 - */ +// stack.h +// Header for the generic stack implementation backed by linked nodes. +// Brendan Dileo - 2025 #ifndef STACK_H #define STACK_H diff --git a/include/str.h b/include/str.h index dac7423..936a418 100644 --- a/include/str.h +++ b/include/str.h @@ -10,12 +10,10 @@ // Starting capacity of a String's buffer, in characters (excluding the '\0'). #define INITIAL_CAPACITY 8 -/** - * Growable, always NUL-terminated string. - * data - heap buffer, capacity + 1 bytes (room for the '\0') - * length - characters stored, not counting the '\0' - * capacity - characters the buffer can hold before it must grow - */ +// Growable, always NUL-terminated string. +// data - heap buffer, capacity + 1 bytes (room for the '\0') +// length - characters stored, not counting the '\0' +// capacity - characters the buffer can hold before it must grow typedef struct String { char *data; size_t length; diff --git a/include/struccs.h b/include/struccs.h index 1b4f0a8..2f5a577 100644 --- a/include/struccs.h +++ b/include/struccs.h @@ -1,8 +1,6 @@ -/** - * struccs.h - * Umbrella header for the struccs library. - * Brendan Dileo - 2026 - */ +// struccs.h +// Umbrella header for the struccs library. +// Brendan Dileo - 2026 #ifndef STRUCCS_H #define STRUCCS_H diff --git a/src/hash_table.c b/src/hash_table.c index 5d7fd29..2372265 100644 --- a/src/hash_table.c +++ b/src/hash_table.c @@ -1,8 +1,7 @@ -/** - * hash_table.c - * A generic implementation of a hash table - * Brendan Dileo - 2025 - */ +// hash_table.c +// A generic implementation of a hash table +// Brendan Dileo - 2025 + #include #include #include diff --git a/src/heap.c b/src/heap.c index 9620147..9cf1fff 100644 --- a/src/heap.c +++ b/src/heap.c @@ -1,8 +1,7 @@ -/** - * heap.c - * A generic implementation of a heap backed by an array - * Brendan Dileo - 2025 - */ +// heap.c +// A generic implementation of a heap backed by an array +// Brendan Dileo - 2025 + #include #include #include diff --git a/src/linked_list.c b/src/linked_list.c index 221ac74..29cc494 100644 --- a/src/linked_list.c +++ b/src/linked_list.c @@ -1,8 +1,7 @@ -/** - * linked_list.c - * A generic implementation of a double linked list. - * Brendan Dileo - 2025 - */ +// linked_list.c +// A generic implementation of a double linked list. +// Brendan Dileo - 2025 + #include #include #include diff --git a/src/memory_pool.c b/src/memory_pool.c index f4fcaa6..a5c5701 100644 --- a/src/memory_pool.c +++ b/src/memory_pool.c @@ -1,8 +1,6 @@ -/** - * memory_pool.c - * Implementation of a fixed-size memory pool allocator backed by a free list. - * Brendan Dileo - 2026 - */ +// memory_pool.c +// Implementation of a fixed-size memory pool allocator backed by a free list. +// Brendan Dileo - 2026 #include #include diff --git a/src/queue.c b/src/queue.c index b22899c..1832431 100644 --- a/src/queue.c +++ b/src/queue.c @@ -1,8 +1,7 @@ -/** - * queue.c - * A generic implementation of a queue backed by linked nodes. - * Brendan Dileo - 2025 - */ +// queue.c +// A generic implementation of a queue backed by linked nodes. +// Brendan Dileo - 2025 + #include #include #include diff --git a/src/set.c b/src/set.c index a00223c..2a2730f 100644 --- a/src/set.c +++ b/src/set.c @@ -1,8 +1,7 @@ -/** - * set.c - * A generic implementation of a set backed by a hash table. - * Brendan Dileo - 2026 - */ +// set.c +// A generic implementation of a set backed by a hash table. +// Brendan Dileo - 2026 + #include #include #include diff --git a/src/stack.c b/src/stack.c index cce960a..57b8009 100644 --- a/src/stack.c +++ b/src/stack.c @@ -1,8 +1,7 @@ -/** - * stack.c - * A generic implementation of a stack backed by linked nodes. - * Brendan Dileo - 2025 - */ +// stack.c +// A generic implementation of a stack backed by linked nodes. +// Brendan Dileo - 2025 + #include #include #include diff --git a/tests/test_bst.c b/tests/test_bst.c index 05954c0..4dc7a84 100644 --- a/tests/test_bst.c +++ b/tests/test_bst.c @@ -1,8 +1,7 @@ -/** - * test_bt.c - * The test suite for the BinarySearchTree implementation. - * Brendan Dileo - 2025 - */ +// test_bt.c +// The test suite for the BinarySearchTree implementation. +// Brendan Dileo - 2025 + #include #include #include diff --git a/tests/test_bt.c b/tests/test_bt.c index 4c8d1c7..9939534 100644 --- a/tests/test_bt.c +++ b/tests/test_bt.c @@ -1,8 +1,6 @@ -/** - * test_bt.c - * The test suite for the BinaryTree implementation. - * Brendan Dileo - 2025 - */ +// test_bt.c +// The test suite for the BinaryTree implementation. +// Brendan Dileo - 2025 #include #include #include diff --git a/tests/test_ga.c b/tests/test_ga.c index 62e26ee..2f99121 100644 --- a/tests/test_ga.c +++ b/tests/test_ga.c @@ -1,8 +1,6 @@ -/** - * test_ga.c - * The test suite for the GenericArray implementation. - * Brendan Dileo - 2025 - */ +// test_ga.c +// The test suite for the GenericArray implementation. +// Brendan Dileo - 2025 #include #include #include diff --git a/tests/test_heap.c b/tests/test_heap.c index a34819f..c3d0a00 100644 --- a/tests/test_heap.c +++ b/tests/test_heap.c @@ -1,8 +1,6 @@ -/** - * test_heap.c - * The test suite for the Heap implementation - * Brendan Dileo - 2025 - */ +// test_heap.c +// The test suite for the Heap implementation +// Brendan Dileo - 2025 #include #include #include diff --git a/tests/test_ht.c b/tests/test_ht.c index b064dc1..cf36ba4 100644 --- a/tests/test_ht.c +++ b/tests/test_ht.c @@ -1,8 +1,6 @@ -/** - * test_ht.c - * The test suite for the HashTable implementation. - * Brendan Dileo - 2025 - */ +// test_ht.c +// The test suite for the HashTable implementation. +// Brendan Dileo - 2025 #include #include #include diff --git a/tests/test_ll.c b/tests/test_ll.c index 5e68265..0b0f5a8 100644 --- a/tests/test_ll.c +++ b/tests/test_ll.c @@ -1,8 +1,6 @@ -/** - * test_ll.c - * The test suite for the LinkedList implementation. - * Brendan Dileo - 2025 - */ +// test_ll.c +// The test suite for the LinkedList implementation. +// Brendan Dileo - 2025 #include #include #include diff --git a/tests/test_mp.c b/tests/test_mp.c index 337c801..9d2af74 100644 --- a/tests/test_mp.c +++ b/tests/test_mp.c @@ -1,8 +1,6 @@ -/** - * test_mp.c - * Test suite for the fixed-size MemoryPool allocator. - * Brendan Dileo - 2026 - */ +// test_mp.c +// Test suite for the fixed-size MemoryPool allocator. +// Brendan Dileo - 2026 #include #include diff --git a/tests/test_queue.c b/tests/test_queue.c index c938cdc..c1901f0 100644 --- a/tests/test_queue.c +++ b/tests/test_queue.c @@ -1,8 +1,6 @@ -/** - * test_queue.c - * The test suite for the Queue implementation. - * Brendan Dileo - 2025 - */ +// test_queue.c +// The test suite for the Queue implementation. +// Brendan Dileo - 2025 #include #include #include diff --git a/tests/test_set.c b/tests/test_set.c index 163075f..d7061e7 100644 --- a/tests/test_set.c +++ b/tests/test_set.c @@ -1,8 +1,6 @@ -/** - * test_set.c - * The test suite for the Set implementation backed by a hash table. - * Brendan Dileo - 2026 - */ +// test_set.c +// The test suite for the Set implementation backed by a hash table. +// Brendan Dileo - 2026 #include #include #include diff --git a/tests/test_stack.c b/tests/test_stack.c index 9cb63dc..c8d491e 100644 --- a/tests/test_stack.c +++ b/tests/test_stack.c @@ -1,8 +1,6 @@ -/** - * test_stack.c - * The test suite for the Stack implementation. - * Brendan Dileo - 2025 - */ +// test_stack.c +// The test suite for the Stack implementation. +// Brendan Dileo - 2025 #include #include #include From f9d13a972fabd1b3105d50b28bc8fa89a4fdeadb Mon Sep 17 00:00:00 2001 From: Brendan Dileo <124395674+brendanddev@users.noreply.github.com> Date: Tue, 15 Sep 2026 18:27:41 -0400 Subject: [PATCH 14/15] feat: Add asan to make and implement Option type --- Makefile | 20 +++++++--- include/option.h | 31 +++++++++++++++ src/option.c | 37 ++++++++++++++++++ tests/test_option.c | 95 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 178 insertions(+), 5 deletions(-) create mode 100644 include/option.h create mode 100644 src/option.c create mode 100644 tests/test_option.c diff --git a/Makefile b/Makefile index ab6d853..03c55f8 100644 --- a/Makefile +++ b/Makefile @@ -1,17 +1,15 @@ -# Project PROJECT = struccs # Compiler and flags CC = clang CFLAGS = -Wall -Wextra -I include TESTFLAGS = $(CFLAGS) -I tests +CFLAGS_ASAN = -Wall -Wextra -I include -fsanitize=address,undefined -g -# Directories +# Directories and source files SRC_DIR = src TEST_DIR = tests BUILD_DIR = build - -# Source files SRCS = $(wildcard $(SRC_DIR)/*.c) # Static library @@ -79,7 +77,11 @@ test_result: $(BUILD_DIR) $(CC) $(TESTFLAGS) $(SRC_DIR)/result.c $(TEST_DIR)/test_result.c -o $(BUILD_DIR)/test_result ./$(BUILD_DIR)/test_result -test_all: test_bt test_bst test_ga test_ll test_ht test_stack test_queue test_heap test_set +test_option: $(BUILD_DIR) + $(CC) $(TESTFLAGS) $(SRC_DIR)/option.c $(TEST_DIR)/test_option.c -o $(BUILD_DIR)/test_option + ./$(BUILD_DIR)/test_option + +test_all: test_bt test_bst test_ga test_ll test_ht test_stack test_queue test_heap test_set test_mp test_str test_result test_option sandbox: $(LIB) $(CC) $(CFLAGS) sandbox/scratch.c \ @@ -88,6 +90,14 @@ sandbox: $(LIB) -g -fsanitize=address \ -o $(BUILD_DIR)/sandbox && ./$(BUILD_DIR)/sandbox +asan: $(BUILD_DIR) + @for src in $(TEST_DIR)/test_*.c; do \ + name=$$(basename $$src .c); \ + $(CC) $(CFLAGS_ASAN) $(SRCS) $$src -o $(BUILD_DIR)/$${name}_asan; \ + echo "Running $${name}_asan..."; \ + ./$(BUILD_DIR)/$${name}_asan; \ + done + # Clean build artifacts clean: rm -rf $(BUILD_DIR) \ No newline at end of file diff --git a/include/option.h b/include/option.h new file mode 100644 index 0000000..9103ea4 --- /dev/null +++ b/include/option.h @@ -0,0 +1,31 @@ +// option.h +// Header for the generic Option type, representing an optional value. +// Brendan Dileo - 2026 + +#ifndef OPTION_H +#define OPTION_H + +#include +#include + +// `present` == false means the Option holds no value; value is meaningless in that case. +// Option is passed and returned by value; it does not own value. +typedef struct Option { + void *value; + bool present; +} Option; + +// Builds an Option holding value. +Option option_some(void *value); +// Builds an empty Option. +Option option_none(void); +// True if o is non-NULL and holds a value. +bool option_is_some(const Option *o); +// True if o is NULL or holds no value. +bool option_is_none(const Option *o); +// Returns o's value, or NULL if o is empty / NULL. +void *option_value(const Option *o); +// Returns o's value if present, otherwise default_value. +void *option_unwrap_or(const Option *o, void *default_value); + +#endif diff --git a/src/option.c b/src/option.c new file mode 100644 index 0000000..9c00896 --- /dev/null +++ b/src/option.c @@ -0,0 +1,37 @@ +// option.c +// Implementation of the generic Option type, representing an optional value. +// Brendan Dileo - 2026 + +#include "option.h" + +Option option_some(void *value) { + Option option; + option.value = value; + option.present = true; + return option; +} + +Option option_none(void) { + Option option; + option.value = NULL; + option.present = false; + return option; +} + +bool option_is_some(const Option *o) { + return (o != NULL && o->present); +} + +bool option_is_none(const Option *o) { + return (o == NULL || !o->present); +} + +void *option_value(const Option *o) { + if (!option_is_some(o)) return NULL; + return o->value; +} + +void *option_unwrap_or(const Option *o, void *default_value) { + if (!option_is_some(o)) return default_value; + return o->value; +} diff --git a/tests/test_option.c b/tests/test_option.c new file mode 100644 index 0000000..99d395d --- /dev/null +++ b/tests/test_option.c @@ -0,0 +1,95 @@ +// test_option.c +// Tests for the generic Option type +// Brendan Dileo - 2026 + +#include +#include "test_utils.h" +#include "option.h" + +// Prototypes +void test_option_some(void); +void test_option_none(void); +void test_option_is_some_is_none(void); +void test_option_value(void); +void test_option_unwrap_or(void); +void test_option_null_safety(void); +void test_option_some_with_null_value(void); + +int main(void) { + TEST(test_option_some); + TEST(test_option_none); + TEST(test_option_is_some_is_none); + TEST(test_option_value); + TEST(test_option_unwrap_or); + TEST(test_option_null_safety); + TEST(test_option_some_with_null_value); + + return 0; +} + +void test_option_some(void) { + int x = 42; + Option o = option_some(&x); + + ASSERT_TRUE(option_is_some(&o)); + ASSERT_FALSE(option_is_none(&o)); + ASSERT_NOT_NULL(option_value(&o)); + ASSERT_EQ(option_value(&o), &x); +} + +void test_option_none(void) { + Option o = option_none(); + + ASSERT_TRUE(option_is_none(&o)); + ASSERT_FALSE(option_is_some(&o)); + ASSERT_NULL(option_value(&o)); +} + +void test_option_is_some_is_none(void) { + int x = 10; + Option some = option_some(&x); + Option none = option_none(); + + ASSERT_TRUE(option_is_some(&some)); + ASSERT_FALSE(option_is_none(&some)); + + ASSERT_TRUE(option_is_none(&none)); + ASSERT_FALSE(option_is_some(&none)); +} + +void test_option_value(void) { + int x = 99; + Option some = option_some(&x); + Option none = option_none(); + + ASSERT_EQ(option_value(&some), &x); + ASSERT_NULL(option_value(&none)); +} + +void test_option_unwrap_or(void) { + int x = 10; + int y = 20; + + Option some = option_some(&x); + Option none = option_none(); + + ASSERT_EQ(option_unwrap_or(&some, &y), &x); + ASSERT_EQ(option_unwrap_or(&none, &y), &y); +} + +void test_option_null_safety(void) { + // Passing NULL should be treated as an empty Option + ASSERT_FALSE(option_is_some(NULL)); + ASSERT_TRUE(option_is_none(NULL)); + ASSERT_NULL(option_value(NULL)); +} + +void test_option_some_with_null_value(void) { + // A Some explicitly holding NULL is still "present" - this is the + // exact case present/value are separate fields for + Option o = option_some(NULL); + + ASSERT_TRUE(option_is_some(&o)); + ASSERT_FALSE(option_is_none(&o)); + ASSERT_NULL(option_value(&o)); +} From 3f386d3ec3886417635c0cfc444711e35bb4687f Mon Sep 17 00:00:00 2001 From: Brendan Dileo <124395674+brendanddev@users.noreply.github.com> Date: Tue, 15 Sep 2026 18:39:00 -0400 Subject: [PATCH 15/15] feat: Add header tracking project version and update ci workflow to use valgrind --- .github/workflows/ci.yml | 14 ++++++++++++-- README.md | 9 +++++---- include/version.h | 9 +++++++++ 3 files changed, 26 insertions(+), 6 deletions(-) create mode 100644 include/version.h diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1e474b8..a1bbf8d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,10 +15,20 @@ jobs: uses: actions/checkout@v4 - name: Install dependencies - run: sudo apt-get install -y clang + run: sudo apt-get install -y clang valgrind - name: Build run: make - name: Run tests - run: make test_all \ No newline at end of file + run: make test_all + + - name: Run tests with ASan + run: make asan + + - name: Run tests with Valgrind + run: | + for test in build/test_*; do + echo "Running valgrind on $test..." + valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=1 $test + done diff --git a/README.md b/README.md index e89961c..a344be9 100644 --- a/README.md +++ b/README.md @@ -63,10 +63,11 @@ Each structure ships with its own header and source file, and can be used indepe ## Types -| Type | Description | Header | -|-----------|--------------------------|----------| -| `Result` | Operation result wrapping a value and error state | `result.h` | -| `String` | Dynamic string type | `str.h` | +| Type | Description | Header | +|-----------|--------------------------------------------|------------| +| `Result` | Operation result wrapping a value or error | `result.h` | +| `Option` | Wrapper representing an optional value | `option.h` | +| `String` | Dynamic, resizable string type | `str.h` | ## Memory Management diff --git a/include/version.h b/include/version.h new file mode 100644 index 0000000..dbd8909 --- /dev/null +++ b/include/version.h @@ -0,0 +1,9 @@ +#ifndef VERSION_H +#define VERSION_H + +#define STRUCCS_VERSION_MAJOR 0 +#define STRUCCS_VERSION_MINOR 14 +#define STRUCCS_VERSION_PATCH 0 +#define STRUCCS_VERSION "0.14.0" + +#endif