Finish curious case of gebs

This commit is contained in:
kamkow1
2025-06-18 19:04:21 +02:00
parent ed1d61d976
commit bcd1255965

View File

@ -12,8 +12,9 @@ GEBS also includes a bunch of extra helper macros, which turn C into a language
So one thing I've noticed is that nob.h is used alongside of [arena](https://github.com/tsoding/arena). If you look into the implementation So one thing I've noticed is that nob.h is used alongside of [arena](https://github.com/tsoding/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... 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. 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 A string builder should rather accept a generic allocator interface, which it can then utilize to get it's memory.
via a \`Gebs_Allocator\` interface. Basically we supplement a dynamic structure with an allocator of choice.
In GEBS this is done via a \`Gebs_Allocator\` interface.
\`\`\`c \`\`\`c
typedef struct { typedef struct {
@ -58,3 +59,82 @@ This is my version of the \`XXX_da_append()\` macro:
This way a dynamic list can work with any kind of allocator - the default libc allocator, an arena or literally anything else. 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. We're not tied to the libc allocator and then have to implement the same macro of all other allocators.
### Defer macro
Ever forgot to place a \`free()\` call on function exit or an \`fclose()\`? The defer macro comes to the rescue. Here's a short snippet:
(Taken straight form the source code of this website btw.)
\`\`\`c
cJSON *root = cJSON_CreateObject();
defer { cJSON_Delete(root); }
char *time = __TIME__;
uchar md5_buf[16];
md5String(time, md5_buf);
String_Builder sb = {0};
defer { sb_free(&sb); }
for (size_t i = 0; i < 16; i++) {
sb_append_nstr(&sb, fmt("%02x", md5_buf[i]));
}
sb_finish(&sb);
cJSON_AddItemToObject(root, "build_id", cJSON_CreateString(sb.items));
make_application_json(result, 200, root);
\`\`\`
If not for the \`defer { ... }\` macro, remebering when to free memory would have been quite hellish.
Another example:
\`\`\`c
NString_List env = {0};
defer { list_free(&env); }
String_Builder out = {0};
defer { sb_free(&out); }
char path[PATH_MAX] = {0};
if (!get_baked_resource_path("home.html", path, sizeof(path))) {
make_internal_server_error(result);
return;
}
\`\`\`
On \`return\` we'd have to **NOT FORGET** to add \`list_free()\` and \`sb_free()\`, but now that we have our defer,
we can kind of shut the brain off and not concern ourselves with freeing the memory. We can be 100% sure it's going to
be freed if we step into the return statement.
The implementation is quite simple, actually
\`\`\`
#define defer defer__2(__COUNTER__)
#define defer__2(X) defer__3(X)
#define defer__3(X) defer__4(defer__id##X)
#define defer__4(ID) auto void ID##func(char (*)[]); __attribute__((cleanup(ID##func))) char ID##var[0]; void ID##func(char (*ID##param)[])
\`\`\`
Source article: https://gustedt.wordpress.com/2025/01/06/simple-defer-ready-to-use/
### compile_flags.txt
Clang/LLVM docs: https://clang.llvm.org/docs/JSONCompilationDatabase.html
I use clangd inside of my vim. Clangd can be configured via a json database compile_commands.json. It's quite complicated for GEBS in a sense
that it uses the \`XX.c -> XX.o\` building pattern, while GEBS is focused more on unity builds (it's on the programmer to implement caching).
Luckily, clangd can be configured via a simple and minimalistic config file - \`compile_flags.txt\`, which holds only compiler flags that
are used to compile our C files. We can for eg. put some include paths in there and clangd will pick them up.
In GEBS we can generate a \`compile_flags.txt\` file using a built-in macro:
\`\`\`c
#define CFLAGS \\
"-I.", \\
"-I./some-lib", \\
"-Wall", \\
"-Wextra" \\
// #define other stuff like CC, LDFLAGS, SOURCES
make_compile_flags(CFLAGS); // Will output the file
\`\`\`