generic slice type, buddy allocator, string type, other stuff

This commit is contained in:
Abdulmujeeb Raji 2025-01-19 19:20:50 +00:00
parent 70bbccff71
commit 52ea920ff3
Signed by: midnadimple
GPG key ID: EB02C582F8C3962B
13 changed files with 749 additions and 140 deletions

View file

@ -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.

13
bastd.c
View file

@ -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

1
bastd/builtins.c Normal file
View file

@ -0,0 +1 @@
// Premade generics used in the codebase

View file

@ -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;
}

View file

@ -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();
// 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, permanent_allocator);
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();
}

19
bastd/examples/string.c Normal file
View file

@ -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();
}

View file

@ -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

369
bastd/memory.c Normal file
View file

@ -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

View file

@ -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)

View file

@ -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);
}

49
bastd/slice.c Normal file
View file

@ -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

View file

@ -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

View file

@ -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