3.0 KiB
GEBS - the Good Enough Build System
GEBS is a reiteration of my previous build system "MIBS" (or MIni Build System). It takes some inspiration from Tsoding's nobuild and later on nob.h. The key difference is the way GEBS is implemented on the inside, which makes it more powerful and extensible than nob.h. GEBS also includes a bunch of extra helper macros, which turn C into a language more akin to Go or Zig, but more on that later.
So what makes GEBS different?
Allocators
So one thing I've noticed is that nob.h is used alongside of arena. If you look into the implementation you can see some things, which are somewhat redundant like `arena_sprintf()` or `arena_da_append()`, `arena_sb_append_cstr()` and so on... First of all, why is an arena library managing string builders and dynamic arrays? In my opinion it should be the other way around. A string builder should rather accept a generic allocator interface, which it can then utilize to get it's memory. In GEBS this is done via a `Gebs_Allocator` interface.
```c typedef struct { void *(*malloc)(void *self, size_t size); void (*free)(void *self, void *memory); void *(*realloc)(void *self, void *memory, size_t prev_size, size_t new_size); } Gebs_Allocator;
// Wrapper macros
#define gebs_malloc(alloc, size) ((alloc)->malloc((void *)(alloc), (size)))
#define gebs_free(alloc, memory) ((alloc)->free((void *)(alloc), (memory)))
#define gebs_realloc(alloc, memory, prev_size, new_size)
((alloc)->realloc((void *)(alloc), (memory), (prev_size), (new_size)))
```
We then can implement an allocator that conforms to this interface and it will work with any dynamic structure. This is my version of the `XXX_da_append()` macro:
```c #define gebs_list_append_alloc(alloc, list, item) \ do { \ if ((list)->items == nil) { \ (list)->capacity = 1; \ (list)->items = gebs_malloc((alloc), \ sizeof((list)->items) * (list)->capacity); \ } else { \ if ((list)->count == (list)->capacity) { \ size_t __prev_capacity = (list)->capacity; \ (list)->capacity = 2; \ (list)->items = gebs_realloc((alloc), (list)->items, \ sizeof((list)->items) * __prev_capacity, \ sizeof((list)->items) * (list)->capacity); \ } \ } \ (list)->items[(list)->count++] = (item); \ } while(0)
#define gebs_list_append(list, item) \ gebs_list_append_alloc(&gebs_default_allocator, (list), (item)) ```
This way a dynamic list can work with any kind of allocator - the default libc allocator, an arena or literally anything else. We're not tied to the libc allocator and then have to implement the same macro of all other allocators.