From 52ea920ff30f5e67d2cf0008beb5d67fb5248d7b Mon Sep 17 00:00:00 2001 From: Abdulmujeeb Raji Date: Sun, 19 Jan 2025 19:20:50 +0000 Subject: [PATCH] generic slice type, buddy allocator, string type, other stuff --- README.md | 6 +- bastd.c | 13 +- bastd/builtins.c | 1 + bastd/examples/fib_slice.c | 52 ++++++ bastd/examples/memory.c | 61 ++++-- bastd/examples/string.c | 19 ++ bastd/mem.c | 116 ------------ bastd/memory.c | 369 +++++++++++++++++++++++++++++++++++++ bastd/os.c | 4 +- bastd/os_windows.c | 6 +- bastd/slice.c | 49 +++++ bastd/string.c | 189 ++++++++++++++++++- build.bat | 4 +- 13 files changed, 749 insertions(+), 140 deletions(-) create mode 100644 bastd/builtins.c create mode 100644 bastd/examples/fib_slice.c create mode 100644 bastd/examples/string.c delete mode 100644 bastd/mem.c create mode 100644 bastd/memory.c create mode 100644 bastd/slice.c diff --git a/README.md b/README.md index d3a86b4..e54d10d 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ data. This buffer type also acts as a **string builder**, by letting you append data to the buffer, then compile it to a string type. - [ ] A logging system built ontop of this I/O system. You can either log out to a file or to stdout. -- [ ] Generic data-structures. The premier one is the +- [x] Generic data-structures. The premier one is the [Dynamic Array](https://dylanfalconer.com/articles/dynamic-arrays-in-c), which brings the power of C++'s `std::vector` to C - [ ] A simple GUI initialization system that gets you an OpenGL window with @@ -56,6 +56,10 @@ in C. C is great. Don't let anyone tell you it's not. +## Examples +You can change the build script to any of the examples. Once built, you should +step through each example in a debugger in order to see how the programs work. + ## Setup Download the [latest release](https://github.com/midnadimple/bastd/releases/latest) whenever you want to start a new project. diff --git a/bastd.c b/bastd.c index aaf6bc0..2f6cd99 100644 --- a/bastd.c +++ b/bastd.c @@ -173,7 +173,7 @@ typedef uintptr_t UPtr; typedef ptrdiff_t ISize; typedef size_t USize; -typedef U32 B32; +typedef U8 B8; #define TRUE 1 #define FALSE 0 @@ -194,9 +194,16 @@ typedef U32 B32; #define COUNT_OF(a) (ISize)(sizeof(a) / sizeof(*(a))) #define LENGTH_OF(s) (COUNT_OF(s) - 1) +#define KILO (1024) +#define MEGA (KILO * 1024) +#define GIGA (MEGA * 1024) +#define TERA (GIGA * 1024) +#define PETA (TERA * 1024) + // Includes +#include "bastd/os.c" +#include "bastd/memory.c" +#include "bastd/builtins.c" #include "bastd/string.c" -#include "bastd/os_windows.c" -#include "bastd/mem.c" #endif //BASTD_C \ No newline at end of file diff --git a/bastd/builtins.c b/bastd/builtins.c new file mode 100644 index 0000000..2531c4d --- /dev/null +++ b/bastd/builtins.c @@ -0,0 +1 @@ +// Premade generics used in the codebase \ No newline at end of file diff --git a/bastd/examples/fib_slice.c b/bastd/examples/fib_slice.c new file mode 100644 index 0000000..b8eff11 --- /dev/null +++ b/bastd/examples/fib_slice.c @@ -0,0 +1,52 @@ +#include "../../bastd.c" + +/* The sl_TYPE macro defines the type of the generic. It is cleared after every + include of "../slice.c" +*/ +#define sl_TYPE I32 +#include "../slice.c" + +int +main(void) +{ + m_Buddy buddy = m_Buddy_create(os_alloc(2 * GIGA), 2 * GIGA); + m_Allocator perm = m_BUDDY_ALLOCATOR(buddy); + + // Slices can be created with a backing array to start with. + I32 init[] = {0, 1}; + sl_I32 fib = sl_I32_create(init, 2); + + os_DEBUGBREAK(); + + for (;;) { + // The data in a slice can be accessed like a normal array + I32 a = fib.elems[fib.len - 2]; + I32 b = fib.elems[fib.len - 1]; + if (a + b > 255) { + break; + } + + /* The sl_TYPE_push function automatically doubles the capacity of the + array if the slice is full and returns a pointer to a new element + */ + *sl_I32_push(&fib, &perm) = a + b; + } + + os_DEBUGBREAK(); + + sl_S8 msgs = sl_S8_create(NIL, 0); + *sl_S8_push(&msgs, &perm) = S8("C standard library!"); + *sl_S8_push(&msgs, &perm) = S8("a Bravely Arranged "); + *sl_S8_push(&msgs, &perm) = S8("This is "); + *sl_S8_push(&msgs, &perm) = S8("World! "); + *sl_S8_push(&msgs, &perm) = S8("Hello, "); + + os_DEBUGBREAK(); + + for (; msgs.len > 0;) { + S8 check_debugger = sl_S8_pop(&msgs); + os_DEBUGBREAK(); + } + + return 0; +} \ No newline at end of file diff --git a/bastd/examples/memory.c b/bastd/examples/memory.c index 2466448..36ef064 100644 --- a/bastd/examples/memory.c +++ b/bastd/examples/memory.c @@ -3,9 +3,13 @@ int main(void) { - ISize buffer_size = 100 * sizeof(U8); - m_Arena arena = m_Arena_create(OS_Alloc(buffer_size), buffer_size); - m_Allocator permanent_allocator = m_ARENA_ALLOCATOR(arena); + ISize buffer_size = 2 * KILO; + + /* Arena allocator allocates new values at an incrementing offset, and can + only free by resetting the beginning pointer to a previous offset + */ + m_Arena arena = m_Arena_create(os_alloc(buffer_size), buffer_size); + m_Allocator arena_allocator = m_ARENA_ALLOCATOR(arena); { /* By cloning an arena at the start of the scope, all changes to the @@ -15,29 +19,62 @@ main(void) m_Arena scratch_arena = arena; m_Allocator scratch_allocator = m_ARENA_ALLOCATOR(scratch_arena); - U8* array = m_MAKE(U8, 20, scratch_allocator); + U8* array = m_MAKE(U8, 20, &scratch_allocator); for (int i = 0; i < 20; i++) { array[i] = i; } } // Auto clears here - os_DEBUGBREAK(); - U8* array = m_MAKE(U8, 10, permanent_allocator); + // The same can be accomplished by saving the arena's offset, then restoring it + m_ArenaOffset savepoint = m_Arena_getOffset(arena); + + U8* temp_array = m_MAKE(U8, 20, &arena_allocator); + for (int i = 0; i < 20; i++) { + temp_array[i] = i; + } + + m_Arena_setOffset(&arena, savepoint); // Clears here + + os_DEBUGBREAK(); + + U8* array = m_MAKE(U8, 10, &arena_allocator); for (int i = 10; i-- > 0;) { array[10 - i] = i; } - os_DEBUGBREAK(); - array = m_RESIZE(array, 10, 20, permanent_allocator); + // Arenas allow for the resizing of blocks. + os_DEBUGBREAK(); + array = m_RESIZE(array, 10, 20, &arena_allocator); for (int i = 20; i-- > 0;) { array[20 - i] = i; } + os_DEBUGBREAK(); - m_RELEASE(array, 20, permanent_allocator); - F64 *vector2 = m_MAKE(F32, 2, permanent_allocator); - vector2[0] = 0x1.921fb6p+1f; - vector2[1] = 490875; + /* For an arena allocator, you can deallocate the last block allcoated on + the arena. + */ + m_RELEASE(array, 20, &arena_allocator); + os_DEBUGBREAK(); + + /* Buddy allocator is more general-purpose than arena allocator, but is + slower and should preferrably be used for long-lasting allocations, + rather than temporary ones. + */ + m_Buddy buddy = m_Buddy_create(os_alloc(buffer_size), buffer_size); + m_Allocator buddy_allocator = m_BUDDY_ALLOCATOR(buddy); + + // You can allocate and deallocate any sized object in any order + U8 *file = m_MAKE(U8, 20, &buddy_allocator); + F32 *vec2 = m_MAKE(F32, 2, &buddy_allocator); + char *raw_str = m_MAKE(char, 50, &buddy_allocator); + + os_DEBUGBREAK(); + + m_RELEASE(vec2, 2, &buddy_allocator); + m_RELEASE(raw_str, 50, &buddy_allocator); + m_RELEASE(file, 20, &buddy_allocator); + os_DEBUGBREAK(); } \ No newline at end of file diff --git a/bastd/examples/string.c b/bastd/examples/string.c new file mode 100644 index 0000000..2f3de2e --- /dev/null +++ b/bastd/examples/string.c @@ -0,0 +1,19 @@ +#include "../../bastd.c" + +int +main(void) +{ + m_Buddy buddy = m_Buddy_create(os_alloc(2 * MEGA), 2 * MEGA); + m_Allocator perm = m_BUDDY_ALLOCATOR(buddy); + + S8 init[] = {S8("Hello"), S8("World")}; + sl_S8 str_list = sl_S8_create(init, 2); + S8 greeting = s8Join(str_list, S8(", "), &perm); + os_DEBUGBREAK(); + + S8 hello = s8Sub(greeting, 0, 5, &perm); + os_DEBUGBREAK(); + + sl_S8 bye_bye = s8Split(S8("Goodbye! Sayonara! Ciao!"), S8(" "), &perm); + os_DEBUGBREAK(); +} \ No newline at end of file diff --git a/bastd/mem.c b/bastd/mem.c deleted file mode 100644 index 9cef216..0000000 --- a/bastd/mem.c +++ /dev/null @@ -1,116 +0,0 @@ -#ifndef BASTD_MEM_C -#define BASTD_MEM_C - -FUNCTION void * -m_memorySet(void *buffer, U8 value, ISize length) -{ - U8* p = buffer; - while (length-- > 0) { - *p++ = value; - } - return buffer; -} - -FUNCTION void * -m_memoryCopy(void *dst, void *src, size_t n) -{ - U8 *s = (U8 *)src; - U8 *d = (U8 *)dst; - - for (I32 i = 0; i < n; i++) { - d[i] = s[i]; - } - - return dst; -} - -/* Alloc function taken from Lua. - If ptr == NIL, old_size irrelevant - If ptr != NIL, old_size = size of block of memory at ptr - If new_size > 0, function like realloc and either resize or create - If new_size <= 0, function like free and clear the memory -*/ -typedef void *(*m_AllocFunc)(void *ctx, void *ptr, ISize old_size, ISize new_size); - -typedef struct m_Allocator m_Allocator; -struct m_Allocator { - m_AllocFunc alloc; - void* ctx; -}; - -#define m_MAKE(T, n, a) ((T *)((a).alloc((a).ctx, NIL, 0, sizeof(T) * n))) -#define m_RESIZE(p, o, n, a) ((a).alloc((a).ctx, p, sizeof(*(p)) * o, sizeof(*(p)) * n)) -#define m_RELEASE(p, s, a) ((a).alloc((a).ctx, p, sizeof(*(p)) * s, 0)) - -/* Linear Allocator (Arena) - Allocates variably-sized regions from a fixed-size block of memory. - A set of regions is freed by setting the allocator's offset to an earlier - value. -*/ -typedef struct m_Arena m_Arena; -struct m_Arena { - U8* beg; - U8* end; -}; - -#define DEFAULT_ALIGNMENT (2 * sizeof(void *)) - -FUNCTION void * -m_Arena_alloc(void *ctx, void *ptr, ISize old_size, ISize new_size) -{ - m_Arena *a = (m_Arena *)ctx; - - if (new_size <= 0) { - /* Arena can only free the most recent block. This allows to follow stack pattern. - What we do is move the end pointer forward by the size of the block - Then, because there is now more available space, the data will be - ignored following the next allocation. - */ - ISize padding = -old_size & (DEFAULT_ALIGNMENT - 1); - a->end += old_size + padding; - return NIL; - } else { - // Allocate a block - ASSERT(new_size > old_size, "Can't reallocate to a smaller block"); - - ISize padding = -(UPtr)a->beg & (DEFAULT_ALIGNMENT - 1); - ISize available = a->end - a->beg - padding; - - if (available < 0 || new_size > available) { - OS_Abort(S8("Out of Memory!")); - } - - void *p = NIL; - - if (a->beg == (U8 *)ptr + old_size) { - // This was the last allocated block, can extend allocation for realloc - p = ptr; - a->beg += padding + new_size; - } else { - // New allocation; - p = a->beg + padding; - a->beg += padding + new_size; - p = m_memorySet(p, 0, new_size); - - if (ptr != NIL) { - // Arbitrary block, copy data from old pointer - p = m_memoryCopy(p, ptr, old_size); - } - } - - return p; - } -} - -FUNCTION m_Arena -m_Arena_create(void* buffer, ISize capacity) -{ - m_Arena arena = {0}; - arena.beg = (U8 *)buffer; - arena.end = arena.beg ? arena.beg + capacity : 0; - return arena; -} - -#define m_ARENA_ALLOCATOR(a) (m_Allocator){m_Arena_alloc, (void *)&(a)} - -#endif//BASTD_MEM_C \ No newline at end of file diff --git a/bastd/memory.c b/bastd/memory.c new file mode 100644 index 0000000..14883fd --- /dev/null +++ b/bastd/memory.c @@ -0,0 +1,369 @@ +#ifndef BASTD_MEM_C +#define BASTD_MEM_C + +FUNCTION void * +m_memorySet(void *buffer, U8 value, ISize length) +{ + U8* p = buffer; + while (length-- > 0) { + *p++ = value; + } + return buffer; +} + +FUNCTION void * +m_memoryCopy(void *dst, void *src, ISize n) +{ + U8 *s = (U8 *)src; + U8 *d = (U8 *)dst; + + for (ISize i = 0; i < n; i++) { + d[i] = s[i]; + } + + return dst; +} + +FUNCTION ISize +m_memoryDifference(void *dst, void *src, ISize n) +{ + U8 *s = (U8 *)src; + U8 *d = (U8 *)dst; + + ISize count = 0; + for (ISize i = 0; i < n; i++) { + if (d[i] != s[i]) { + count++; + } + } + + return count; +} + +/* Alloc function taken from Lua. + If ptr == NIL, old_size irrelevant + If ptr != NIL, old_size = size of block of memory at ptr + If new_size > 0, function like realloc and either resize or create + If new_size <= 0, function like free and clear the memory +*/ +typedef void *(*m_AllocFunc)(void *ctx, void *ptr, ISize old_size, ISize new_size); + +typedef struct m_Allocator m_Allocator; +struct m_Allocator { + m_AllocFunc alloc; + void* ctx; +}; + +#define m_MAKE(T, n, a) ((T *)((a)->alloc((a)->ctx, NIL, 0, sizeof(T) * n))) +#define m_RESIZE(p, o, n, a) ((a)->alloc((a)->ctx, p, sizeof(*(p)) * o, sizeof(*(p)) * n)) +#define m_RELEASE(p, s, a) ((a)->alloc((a)->ctx, p, sizeof(*(p)) * s, 0)) + +/* Linear Allocator (Arena) + Allocates variably-sized regions from a fixed-size block of memory. + A set of regions is freed by setting the allocator's offset to an earlier + value. This should be used as an allocator for tasks where the lifetime of + the data isn't important to its implementation. This would be an update loop + in a game, or a threaded web request, for example. +*/ +typedef struct m_Arena m_Arena; +struct m_Arena { + U8* beg; + U8* end; +}; + +#define DEFAULT_ALIGNMENT (2 * sizeof(void *)) + +FUNCTION void * +m_Arena_alloc(void *ctx, void *ptr, ISize old_size, ISize new_size) +{ + m_Arena *a = (m_Arena *)ctx; + + if (new_size <= 0) { + /* Arena can only free the most recent block. This allows to follow stack pattern. + All we do is move the beginning pointer back the size of the allocation. + */ + ISize padding = -old_size & (DEFAULT_ALIGNMENT - 1); + a->beg -= (old_size + padding); + return NIL; + } else { + // Allocate a block + ASSERT(new_size > old_size, "Can't reallocate to a smaller block"); + + ISize padding = -(UPtr)a->beg & (DEFAULT_ALIGNMENT - 1); + ISize available = a->end - a->beg - padding; + + if (available < 0 || new_size > available) { + os_abort("Out of Memory!"); + } + + void *p = NIL; + + if (a->beg == (U8 *)ptr + old_size) { + // This was the last allocated block, can extend allocation for realloc + p = ptr; + a->beg += padding + new_size; + } else { + // New allocation; + p = a->beg + padding; + a->beg += padding + new_size; + p = m_memorySet(p, 0, new_size); + + if (ptr != NIL) { + // Arbitrary block, copy data from old pointer + p = m_memoryCopy(p, ptr, old_size); + } + } + + return p; + } +} + +FUNCTION m_Arena +m_Arena_create(void* buffer, ISize capacity) +{ + m_Arena arena = {0}; + arena.beg = (U8 *)buffer; + arena.end = arena.beg ? arena.beg + capacity : 0; + return arena; +} + +// Just to make it clear what you're saving +typedef U8 *m_ArenaOffset; + +FUNCTION m_ArenaOffset +m_Arena_getOffset(m_Arena arena) +{ + return arena.beg; +} + +FUNCTION void +m_Arena_setOffset(m_Arena *arena, m_ArenaOffset offset) +{ + arena->beg = offset; +} + +#define m_ARENA_ALLOCATOR(a) (m_Allocator){m_Arena_alloc, (void *)&(a)} + +/* Buddy Allocator (General-Purpose) + Allocates any size data in any order, by splitting a fixed block of memory + into half-size "buddy" blocks recursively until the allocation fits in the + smallest possible block. This is a general purpose allocator, so you have to + keep track of individual object lifetimes. THe best use-case for this is for + objects that live for the entirety of a program, although static global + variables / function parameters should be preferred. +*/ +typedef struct m_BuddyBlock m_BuddyBlock; +struct m_BuddyBlock { + ISize size; + B8 is_free; +}; + +FUNCTION m_BuddyBlock * +m_BuddyBlock_next(m_BuddyBlock *block) +{ + return (m_BuddyBlock *)((char *)block + block->size); +} + +FUNCTION m_BuddyBlock * +m_BuddyBlock_split(m_BuddyBlock *block, ISize size) +{ + if (block != NIL && size != 0) { + while (size < block->size) { + ISize sz = block->size >> 1; + block->size = sz; + block = m_BuddyBlock_next(block); + block->size = sz; + block->is_free = TRUE; + } + + if (size <= block->size) { + return block; + } + } + + return NIL; +} + +FUNCTION m_BuddyBlock * +m_BuddyBlock_findBest(m_BuddyBlock *head, m_BuddyBlock *tail, ISize size) +{ + m_BuddyBlock *best_block = NIL; + m_BuddyBlock *block = head; + m_BuddyBlock *buddy = m_BuddyBlock_next(block); + + if (buddy == tail && block->is_free) { + return m_BuddyBlock_split(block, size); + } + + while (block < tail && buddy < tail) { + // Coalesce to reduce fragmentation + if (block->is_free && buddy->is_free && block->size == buddy->size) { + block->size <<= 1; + if (size <= block->size && (best_block == NIL || block->size <= best_block->size)) { + best_block = block; + } + + block = m_BuddyBlock_next(buddy); + if (block < tail) { + // Delay the buddy block for the next iteration + buddy = m_BuddyBlock_next(block); + } + continue; + } + + if (block->is_free && size <= block->size && + (best_block == NIL || block->size <= best_block->size)) { + best_block = block; + } + + if (buddy->is_free && size <= buddy->size && + (best_block == NIL || buddy->size < best_block->size)) { + // If each buddy are the same size, then it makes more sense + // to pick the buddy as it "bounces around" less + best_block = buddy; + } + + if (block->size <= buddy->size) { + block = m_BuddyBlock_next(buddy); + if (block < tail) { + // Delay the buddy block for the next iteration + buddy = m_BuddyBlock_next(block); + } + } else { + // Buddy was split into smaller blocks + block = buddy; + buddy = m_BuddyBlock_next(buddy); + } + } + + if (best_block != NIL) { + return m_BuddyBlock_split(best_block, size); + } + + os_abort("Failed to buddy allocate, possibly out of memory"); + return NIL; +} + +typedef struct m_Buddy m_Buddy; +struct m_Buddy { + m_BuddyBlock *head; + m_BuddyBlock *tail; + ISize alignment; +}; + +FUNCTION m_Buddy +m_Buddy_create(void *data, ISize size) +{ + m_Buddy res = {0}; + res.alignment = DEFAULT_ALIGNMENT; + if (res.alignment < sizeof(m_BuddyBlock)) { + res.alignment = sizeof(m_BuddyBlock); + } + + ASSERT((UPtr)data & res.alignment == 0, "Data not aligned to minimum alignment"); + + res.head = (m_BuddyBlock *)data; + res.head->size = size; + res.head->is_free = TRUE; + + res.tail = m_BuddyBlock_next(res.head); + + return res; +} + +FUNCTION ISize +m_Buddy_requiredSize(m_Buddy *b, ISize size) { + ISize actual_size = b->alignment; + + size += sizeof(m_BuddyBlock); + ISize padding = -size & (b->alignment - 1); + size += b->alignment + padding; + + while (size > actual_size) { + actual_size <<= 1; + } + + return actual_size; +} + +FUNCTION void +m_Buddy_coalesce(m_BuddyBlock *head, m_BuddyBlock *tail) { + for (;;) { + // Keep looping until there are no more buddies to coalesce + + m_BuddyBlock *block = head; + m_BuddyBlock *buddy = m_BuddyBlock_next(block); + + B8 no_coalescence = TRUE; + while (block < tail && buddy < tail) { // make sure the buddies are within the range + if (block->is_free && buddy->is_free && block->size == buddy->size) { + // Coalesce buddies into one + block->size <<= 1; + block = m_BuddyBlock_next(block); + if (block < tail) { + buddy = m_BuddyBlock_next(block); + no_coalescence = FALSE; + } + } else if (block->size < buddy->size) { + // The buddy block is split into smaller blocks + block = buddy; + buddy = m_BuddyBlock_next(buddy); + } else { + block = m_BuddyBlock_next(buddy); + if (block < tail) { + // Leave the buddy block for the next iteration + buddy = m_BuddyBlock_next(block); + } + } + } + + if (no_coalescence) { + return; + } + } +} + +FUNCTION void * +m_Buddy_alloc(void *ctx, void *ptr, ISize old_size, ISize new_size) { + m_Buddy *b = (m_Buddy *)ctx; + + if (new_size <= 0 && ptr != NIL) { + m_BuddyBlock *block; + + ASSERT(b->head <= ptr, "Head is greater than pointer to free"); + ASSERT(ptr < b->tail, "Tail is less than pointer to free"); + + block = (m_BuddyBlock *)((char *)ptr - b->alignment); + block->is_free = TRUE; + + m_Buddy_coalesce(b->head, b->tail); + + return NIL; + } else { + size_t actual_size = m_Buddy_requiredSize(b, new_size); + + m_BuddyBlock *found = m_BuddyBlock_findBest(b->head, b->tail, actual_size); + if (found == NIL) { + // Try to coalesce all the free buddy blocks and then search again + m_Buddy_coalesce(b->head, b->tail); + found = m_BuddyBlock_findBest(b->head, b->tail, actual_size); + } + + if (found != NIL) { + found->is_free = FALSE; + U8 *p = (U8 *)found + b->alignment; + if (ptr != NIL) { + m_memoryCopy(p, ptr, old_size); + } + return (void *)(p); + } + + os_abort("Buddy Allcoator out of memory, possibly too much internal fragmentation"); + } + + return NIL; +} + +#define m_BUDDY_ALLOCATOR(a) (m_Allocator){m_Buddy_alloc, (void *)&(a)} + +#endif//BASTD_MEM_C \ No newline at end of file diff --git a/bastd/os.c b/bastd/os.c index 1c8ac48..2434582 100644 --- a/bastd/os.c +++ b/bastd/os.c @@ -1,8 +1,8 @@ #ifndef BASTD_OS_C #define BASTD_OS_C -FUNCTION void OS_Abort(S8 msg); -FUNCTION void *OS_Alloc(ISize cap); +FUNCTION void os_abort(char *msg); +FUNCTION void *os_alloc(ISize cap); #if defined(OS_WINDOWS) diff --git a/bastd/os_windows.c b/bastd/os_windows.c index 7e0cbe6..d0c7c23 100644 --- a/bastd/os_windows.c +++ b/bastd/os_windows.c @@ -6,16 +6,16 @@ #define os_DEBUGBREAK() __debugbreak(); FUNCTION void -OS_Abort(S8 msg) +os_abort(char *msg) { HANDLE stderr = GetStdHandle(-12); U32 dummy; - WriteFile(stderr, msg.raw, msg.length, &dummy, 0); + WriteFile(stderr, msg, LENGTH_OF(msg), &dummy, 0); ExitProcess(101); } FUNCTION void * -OS_Alloc(ISize cap) +os_alloc(ISize cap) { return VirtualAlloc(NIL, cap, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); } diff --git a/bastd/slice.c b/bastd/slice.c new file mode 100644 index 0000000..0d43077 --- /dev/null +++ b/bastd/slice.c @@ -0,0 +1,49 @@ +// GENERIC FILE, REDEFINED ON EVERY INCLUDE +#ifndef sl_TYPE +#error "No sl_TYPE given" +#endif + +#define sl_NAME CONCAT(sl_, sl_TYPE) + +typedef struct sl_NAME sl_NAME; +struct sl_NAME { + sl_TYPE *elems; + ISize len; + ISize cap; +}; + +FUNCTION sl_NAME +CONCAT(sl_NAME, _create) (sl_TYPE *initial_data, ISize initial_len) +{ + sl_NAME res = {0}; + res.elems = initial_data; + + res.len = initial_len; + res.cap = initial_len; + + return res; +} + +FUNCTION sl_TYPE* +CONCAT(sl_NAME, _push) (sl_NAME *slice, m_Allocator *perm) +{ + if (slice->len >= slice->cap) { + // Slice is full, Grow slice to double its current size + slice->cap = slice->cap ? slice->cap : 1; + ISize current_size = sizeof(*slice->elems) * slice->cap; + slice->elems = perm->alloc(perm->ctx, slice->elems, current_size, current_size*2); + slice->cap *= 2; + } + + // Return the slot the end of the slice and increase the length + return slice->elems + slice->len++; +} + +FUNCTION sl_TYPE +CONCAT(sl_NAME, _pop) (sl_NAME *slice) +{ + return *(slice->elems + --slice->len); +} + +#undef sl_NAME +#undef sl_TYPE \ No newline at end of file diff --git a/bastd/string.c b/bastd/string.c index 13ed6c7..a957957 100644 --- a/bastd/string.c +++ b/bastd/string.c @@ -3,10 +3,195 @@ typedef struct S8 S8; struct S8 { - ISize length; + ISize len; U8 *raw; }; -#define S8(s) (S8){(U8 *)s, LENGTH_OF(s)} +#define sl_TYPE S8 +#include "slice.c" + +#define S8(s) (S8){.len = LENGTH_OF(s), .raw = (U8 *)s} + +FUNCTION S8 +s8Alloc(ISize len, m_Allocator *perm) +{ + S8 res = {0}; + res.len = len; + res.raw = m_MAKE(U8, len + 1, perm); + res.raw[len] = 0; + return res; +} + +FUNCTION S8 +s8Concat(S8 s1, S8 s2, m_Allocator *perm) +{ + ISize len = s1.len + s2.len; + S8 s = s8Alloc(len, perm); + m_memoryCopy(s.raw, s1.raw, s1.len); + m_memoryCopy(&s.raw[s1.len], s2.raw, s2.len); + return s; +} + +FUNCTION S8 +s8Sub(S8 s, ISize start, ISize end, m_Allocator *perm) +{ + S8 res = {0}; + if (end <= s.len && start < end) { + res = s8Alloc(end - start, perm); + m_memoryCopy(res.raw, &s.raw[start], res.len); + } + return res; +} + +FUNCTION B8 +s8Contains(S8 haystack, S8 needle) +{ + B8 found = FALSE; + for (ISize i = 0, j = 0; i < haystack.len && !found; i++) { + while (haystack.raw[i] == needle.raw[j]) { + j += 1; + i += 1; + if (j == needle.len) { + found = TRUE; + break; + } + } + } + return found; +} + +FUNCTION ISize +s8IndexOf(S8 haystack, S8 needle) +{ + for (ISize i = 0; i < haystack.len; i += 1) { + ISize j = 0; + ISize start = i; + while (haystack.raw[i] == needle.raw[j]) { + j += 1; + i += 1; + if (j == needle.len) { + return start; + } + } + } + return (ISize)-1; +} + +FUNCTION S8 +s8SubView(S8 haystack, S8 needle) +{ + S8 r = {0}; + ISize start_index = str_index_of(haystack, needle); + if (start_index < haystack.len) { + r.raw = &haystack.raw[start_index]; + r.len = needle.len; + } + return r; +} + +FUNCTION B8 +s8Equal(S8 a, S8 b) +{ + if (a.len != b.len) { + return FALSE; + } + return m_memoryDifference(a.raw, b.raw, a.len) == 0; +} + +FUNCTION S8 +s8View(S8 s, ISize start, ISize end) +{ + if (end < start || end - start > s.len) { + return (S8){0}; + } + return (S8){end - start, s.raw + start}; +} + +FUNCTION S8 +s8Clone(S8 s, m_Allocator *perm) +{ + S8 r = {0}; + if (s.len) { + r.raw = perm->alloc(perm->ctx, NIL, 0, s.len); + r.len = s.len; + m_memoryCopy(r.raw, s.raw, s.len); + } + return r; +} + +FUNCTION sl_S8 +s8Split(S8 s, S8 delimiter, m_Allocator *perm) +{ + sl_S8 arr = sl_S8_create(NIL, 0); + ISize start = 0; + for (ISize i = 0; i < s.len; i += 1) { + if (s.raw[i] != delimiter.raw[0]) { + continue; + } + + if (m_memoryDifference(&s.raw[i], delimiter.raw, delimiter.len) == 0) { + // Clone the substring before the delimiter. + ISize end = i; + S8 cloned = s8Sub(s, start, end, perm); + *sl_S8_push(&arr, perm) = cloned; + start = end + delimiter.len; + } + } + // Get the last segment. + if (start + delimiter.len < s.len) { + S8 cloned = s8Sub(s, start, s.len, perm); + *sl_S8_push(&arr, perm) = cloned; + } + return arr; +} + +FUNCTION sl_S8 +s8SplitView(S8 s, S8 delimiter, m_Allocator *perm) { + sl_S8 arr = sl_S8_create(NIL, 0); + ISize start = 0; + for (ISize i = 0; i < s.len; i += 1) { + if (s.raw[i] != delimiter.raw[0]) { + continue; + } + + if (m_memoryDifference(&s.raw[i], delimiter.raw, delimiter.len) == 0) { + ISize end = i; + S8 view = s8View(s, start, end); + *sl_S8_push(&arr, perm) = view; + start = end + delimiter.len; + } + } + if (start + delimiter.len < s.len) { + S8 view = s8View(s, start, s.len); + *sl_S8_push(&arr, perm) = view; + } + return arr; +} + +FUNCTION S8 +s8Join(sl_S8 s, S8 join, m_Allocator *perm) { + ISize total_length = s.len * join.len; + for (ISize i = 0; i < s.len; i += 1) { + total_length += s.elems[i].len; + } + + U8 *mem = m_MAKE(U8, total_length + 1, perm); + ISize offset = 0; + for (ISize i = 0; i < s.len; i += 1) { + m_memoryCopy(&mem[offset], s.elems[i].raw, s.elems[i].len); + offset += s.elems[i].len; + + if (i == s.len - 1) { + break; + } + + m_memoryCopy(&mem[offset], join.raw, join.len); + offset += join.len; + } + + mem[total_length] = 0; + + return (S8){total_length, mem}; +} #endif//BASTD_STRING_C \ No newline at end of file diff --git a/build.bat b/build.bat index 72eaf06..6f09f3f 100644 --- a/build.bat +++ b/build.bat @@ -5,8 +5,10 @@ pushd build REM Uncomment one of these to run an example. Try it! They're great documentation. REM cl /Zi ..\bastd\examples\memory.c +REM cl /Zi ..\bastd\examples\fib_slice.c +cl /Zi ..\bastd\examples\string.c REM This builds your application with debug symbols. -cl /Zi ..\bastd\examples\memory.c +REM cl /Zi ..\main.c popd \ No newline at end of file