From 65ea60f79d2367d50f9f1c95479a157c39b1aa03 Mon Sep 17 00:00:00 2001 From: Nick Bors Date: Mon, 13 Apr 2026 21:54:59 +0300 Subject: [PATCH 1/5] change: string api cleanup and new attributes --- include/common/arena.h | 2 +- include/common/portability.h | 3 ++ include/common/str.h | 20 ++++++--- include/common/vec.h | 8 +++- src/common/str.c | 62 ++++++++++++++-------------- src/common/vec.c | 79 ++++++++++++++++++++++++++++++++++++ 6 files changed, 135 insertions(+), 39 deletions(-) diff --git a/include/common/arena.h b/include/common/arena.h index b992089..0d89cd9 100644 --- a/include/common/arena.h +++ b/include/common/arena.h @@ -13,7 +13,7 @@ typedef struct ArenaState { void arena_init(Arena* arena); void arena_destroy(Arena* arena); -void* arena_alloc(Arena* arena, usize size, usize align); +void* arena_alloc(Arena* arena, usize size, usize align) ALLOC_ALIGN(2, 3); ArenaState arena_save(Arena* arena); void arena_restore(Arena* arena, ArenaState save); diff --git a/include/common/portability.h b/include/common/portability.h index 0693c2d..aa48f19 100644 --- a/include/common/portability.h +++ b/include/common/portability.h @@ -38,6 +38,9 @@ #define FALLTHROUGH __attribute__((fallthrough)) #define UNUSED __attribute__((unused)) #define NORETURN __attribute__((noreturn)) + #define ALLOC_ALIGN(sz, al) __attribute__((malloc, alloc_size(sz), alloc_align(al))) + #define ALLOC_SIZE(sz) __attribute__((malloc, alloc_size(sz))) + #define FORMAT_CHECK(fmt_pos, args_pos) __attribute__((format(printf, fmt_pos, args_pos))) #elif defined(_MSC_VER) #define WEAK __declspec(selectany) #define INLINE __forceinline diff --git a/include/common/str.h b/include/common/str.h index 3e19a58..3383cc6 100644 --- a/include/common/str.h +++ b/include/common/str.h @@ -4,12 +4,14 @@ #include #include "common/type.h" +#include "common/portability.h" +#include "common/util.h" // strings and string-related utils. typedef struct string { char* raw; - size_t len; + usize len; } string; #define NULL_STR ((string){nullptr, 0}) @@ -27,17 +29,23 @@ typedef struct string { #define string_wrap(cstring) ((string){(char*)(cstring), strlen((cstring))}) #define strlit(cstring) ((string){(char*)cstring, sizeof(cstring)-1}) +#define string_lt(a, b) (string_cmp((a), (b)) < 0) +#define string_gt(a, b) (string_cmp((a), (b)) > 0) +#define string_le(a, b) (string_cmp((a), (b)) <= 0) +#define string_ge(a, b) (string_cmp((a), (b)) >= 0) + +#define string_free(str) free(str.raw) + char* clone_to_cstring(string str); // this allocates void printstr(string str); -string strprintf(char* format, ...); +string strprintf(char* format, ...) FORMAT_CHECK(1, 2); -string string_alloc(size_t len); -#define string_free(str) free(str.raw) +string string_alloc(usize len); string string_clone(string str); // this allocates as well string string_concat(string a, string b); // allocates -void string_concat_buf(string buf, string a, string b); // this does not +void string_concat_buf(string buf, string a, string b); // this does not -int string_cmp(string a, string b); +u8 string_cmp(string a, string b); bool string_eq(string a, string b); bool string_ends_with(string source, string ending); diff --git a/include/common/vec.h b/include/common/vec.h index 545e713..fbd3b9d 100644 --- a/include/common/vec.h +++ b/include/common/vec.h @@ -5,6 +5,7 @@ #include #include +#include "common/str.h" /// /// +--------+--------+--------+--------+--------+--------+ @@ -37,7 +38,7 @@ typedef struct VecHeader { /// This vector's `VecHeader`. #define vec_header(vec) ((VecHeader*)((char*)(vec) - sizeof(VecHeader))) /// Get the pointer to a vec's elements from the header. -#define vec_elems_from_header(header) ((void*)((char*)(header) + sizeof(VecHeader))) +#define vec_elems_from_header(header) ((void*)((u8*)(header) + sizeof(VecHeader))) /// This vector's length. #define vec_len(vec) vec_header(vec)->len /// This vector's capacity. @@ -89,4 +90,9 @@ void _vec_reserve1(Vec(void)* v, size_t stride); void _vec_shrink(Vec(void)* v, size_t stride); void _vec_destroy(Vec(void)* v); +/* string specifics */ + +Vec(char) cstring_to_vec(char* str); // always reallocates str +Vec(char) string_to_vec(string str); // same +void vec_appendf(Vec(char) str, const char* format, ...) FORMAT_CHECK(2, 3); #endif // VEC_H diff --git a/src/common/str.c b/src/common/str.c index 99d4307..80ca860 100644 --- a/src/common/str.c +++ b/src/common/str.c @@ -3,16 +3,16 @@ #include #include -#include "common/portability.h" #include "common/str.h" +// Prints to a new string. The result is guaranteed to be null terminated. string strprintf(char* format, ...) { string c = NULL_STR; va_list a; va_start(a, format); va_list b; va_copy(b, a); - size_t bufferlen = 1 + vsnprintf("", 0, format, a); + usize bufferlen = 1 + vsnprintf("", 0, format, a); c = string_alloc(bufferlen); vsnprintf(c.raw, c.len, format, b); c.len--; @@ -21,18 +21,26 @@ string strprintf(char* format, ...) { return c; } +// Allocates a buffer containing the concatenation of a and b. +// The result is not guaranteed to be null terminated. string string_concat(string a, string b) { string c = string_alloc(a.len + b.len); string_concat_buf(c, a, b); return c; } +// Concatenates two strings into buf. The buffer size is unchecked, so you must +// ensure it can contain at least a length of a.len + b.len. Crashes otherwise. void string_concat_buf(string buf, string a, string b) { - for (size_t i = 0; i < a.len; ++i) { + if (buf.len < a.len + b.len) + CRASH("Buffer is too small. %zu < %zu + %zu", buf.len, a.len, b.len); + + usize i; + for_n(i, 0, a.len) { buf.raw[i] = a.raw[i]; } - for (size_t i = 0; i < b.len; ++i) { - buf.raw[i + a.len] = b.raw[i]; + for_n(i, 0, b.len) { + buf.raw[a.len + i] = b.raw[i]; } } @@ -42,16 +50,13 @@ bool string_ends_with(string source, string ending) { return string_eq(substring_len(source, source.len-ending.len, ending.len), ending); } -string string_alloc(size_t len) { - char* raw = malloc(len); - - memset(raw, '\0', len); +// Returns a buffer strictly of size len. The result is zero-filled. +inline string string_alloc(usize len) { + return (string){calloc(len, sizeof(char*)), len}; +} ALLOC_SIZE(1); - return (string){raw, len}; - -} - -int string_cmp(string a, string b) { +// returns 0 when equal, -1 when a < b, and 1 when a > b +u8 string_cmp(string a, string b) { // copied from odin's implementation lmfao int res = memcmp(a.raw, b.raw, a.len < b.len ? a.len : b.len); if (res == 0 && a.len != b.len) return a.len <= b.len ? -1 : 1; @@ -59,36 +64,31 @@ int string_cmp(string a, string b) { return res; } -bool string_eq(string a, string b) { - if (a.len != b.len) return false; - for (size_t i = 0; i < a.len; ++i) { - if (a.raw[i] != b.raw[i]) return false; - } - return true; +inline bool string_eq(string a, string b) { + return a.len == 0 || (a.len == b.len && string_cmp(a, b) == 0); } -char* clone_to_cstring(string str) { - if (is_null_str(str)) return ""; - - char* cstr = malloc(str.len + 1); - if (cstr == nullptr) return nullptr; - memcpy(cstr, str.raw, str.len); - cstr[str.len] = '\0'; - return cstr; +// Allocates a null-terminated C string, and copies the source str +inline char* clone_to_cstring(string str) { + // NOTE: the returned char* is always allocated, so no null literal + // (otherwise subsequent realloc results in undefined behaviour). + // The last null character is always allocated internally. + return strndup(str.raw, str.len); } +// Clones str into new string. The result is not null-terminated. string string_clone(string str) { string new_str = string_alloc(str.len); if (memmove(new_str.raw, str.raw, str.len) != new_str.raw) return NULL_STR; return new_str; } -void printn(char* text, size_t len) { - size_t c = 0; +void printn(char* text, usize len) { + usize c = 0; while (c < len && text[c] != '\0') putchar(text[c++]); } -void printstr(string str) { +inline void printstr(string str) { printn(str.raw, str.len); } diff --git a/src/common/vec.c b/src/common/vec.c index ecf3270..85b9f40 100644 --- a/src/common/vec.c +++ b/src/common/vec.c @@ -1,5 +1,6 @@ #include #include +#include #include "common/portability.h" #include "common/vec.h" @@ -40,3 +41,81 @@ WEAK void _vec_destroy(Vec(void)* v) { free(vec_header(*v)); *v = nullptr; } + +/* string specifics */ + +// Copies string to newly allocated owned vector. The result is null terminated. +Vec(char) string_to_owned(string str) { + Vec(char) v = vec_new(char, str.len + 1); + + memcpy(v, str.raw, str.len); + v[str.len] = '\0'; + return v; +} + +// Takes ownership of cstring and creates a vector. Moves the string in-place +// to insert a leading header, and returning the new vec. +Vec(char) cstring_to_vec(char* str) { + // The assumption is that you make a vec to append to it, so we reserve + // more up-front. The growth factor of 2 may be changed but idk + usize len = strlen(str); + str = realloc(str, len * 2 + sizeof(VecHeader)); + + // memmove so that strings can overlap. + memmove(str, (u8*)str + sizeof(VecHeader), len); + + Vec(char) v = vec_elems_from_header(str); + + if (v == nullptr) + return nullptr; + + vec_cap(v) = len * 2; + vec_len(v) = len; + + return v; +} + +// Takes ownership of string and creates a vector. Moves the string in-place +// to insert a leading header, and returning the new vec. +Vec(char) string_to_vec(string str) { + // The assumption is that you make a vec to append to it, so we reserve + // more up-front. The growth factor of 2 may be changed but idk + str.raw = realloc(str.raw, str.len * 2 + sizeof(VecHeader)); + + // memmove so that strings can overlap. + memmove(str.raw, (u8*)str.raw + sizeof(VecHeader), str.len); + + Vec(char) v = vec_elems_from_header(str.raw); + + if (v == nullptr) + return nullptr; + + vec_cap(v) = str.len * 2; + vec_len(v) = str.len; + + return v; +} + +// Appends formatted string to a Vec(char), leaving the result null-terminated. +// reallocating to make space for the new format. Begins printing at the +// source's trailing null character, or its length if none is present. +void vec_appendf(Vec(char) str, const char* format, ...) { + va_list a; + va_start(a, format); + va_list b; + va_copy(b, a); + + usize printlen = 1 + vsnprintf("", 0, format, a); + + // if already null-terminated, go back and print over it + if (str[vec_len(str)] == '\0') + vec_len(str)--; + + vec_reserve(&str, printlen); + + vsnprintf(&str[vec_len(str)], printlen, format, b); + str[printlen] = '\0'; + + va_end(a); + va_end(b); +} From a48e52db2b14951f8b0631490632c6237759aae9 Mon Sep 17 00:00:00 2001 From: Nick Bors Date: Tue, 14 Apr 2026 12:03:29 +0300 Subject: [PATCH 2/5] docs (mainly) + misc fixes --- Makefile | 1 + include/common/arena.h | 2 +- include/common/vec.h | 2 +- src/common/str.c | 84 ++++++++++++++++++++++++--- src/common/vec.c | 128 ++++++++++++++++++++++++++--------------- 5 files changed, 160 insertions(+), 57 deletions(-) diff --git a/Makefile b/Makefile index b8b447d..af96f82 100644 --- a/Makefile +++ b/Makefile @@ -26,6 +26,7 @@ $(COMMON_OUT_DIR)/%.o: src/common/%.c .PHONY: clean clean: rm -rf $(COMMON_OUT_DIR) + @mkdir -p $(COMMON_OUT_DIR) # generate compile commands with bear if u got it!!! # very good highly recommended ʕ·ᴥ·ʔ diff --git a/include/common/arena.h b/include/common/arena.h index 0d89cd9..573741e 100644 --- a/include/common/arena.h +++ b/include/common/arena.h @@ -13,7 +13,7 @@ typedef struct ArenaState { void arena_init(Arena* arena); void arena_destroy(Arena* arena); -void* arena_alloc(Arena* arena, usize size, usize align) ALLOC_ALIGN(2, 3); +ALLOC_ALIGN(2, 3) void* arena_alloc(Arena* arena, usize size, usize align); ArenaState arena_save(Arena* arena); void arena_restore(Arena* arena, ArenaState save); diff --git a/include/common/vec.h b/include/common/vec.h index fbd3b9d..41eb603 100644 --- a/include/common/vec.h +++ b/include/common/vec.h @@ -92,7 +92,7 @@ void _vec_destroy(Vec(void)* v); /* string specifics */ -Vec(char) cstring_to_vec(char* str); // always reallocates str +Vec(char) cstring_to_vec(const char* str); // always reallocates str Vec(char) string_to_vec(string str); // same void vec_appendf(Vec(char) str, const char* format, ...) FORMAT_CHECK(2, 3); #endif // VEC_H diff --git a/src/common/str.c b/src/common/str.c index 80ca860..190f4a5 100644 --- a/src/common/str.c +++ b/src/common/str.c @@ -5,7 +5,14 @@ #include "common/str.h" -// Prints to a new string. The result is guaranteed to be null terminated. +/** + * @brief Prints to a newly allocated string. + * + * @param format printf format specifier + * @param ... printf format arguments + * + * @result str null-terminated string, or NULL_STR on allocation fail. + */ string strprintf(char* format, ...) { string c = NULL_STR; va_list a; @@ -21,16 +28,27 @@ string strprintf(char* format, ...) { return c; } -// Allocates a buffer containing the concatenation of a and b. -// The result is not guaranteed to be null terminated. +/** + * @brief concats two strings + * + * Allocates a buffer containing the concatenation of a and b. + * + * @result str Allocated string containing ab, without a null terminator. + */ string string_concat(string a, string b) { string c = string_alloc(a.len + b.len); string_concat_buf(c, a, b); return c; } -// Concatenates two strings into buf. The buffer size is unchecked, so you must -// ensure it can contain at least a length of a.len + b.len. Crashes otherwise. +/** + * @brief Concatenates two strings into buf. + * + * @param[out] buf buffer of minimum length a.len + b.len. + * buf is filled with the concatenation ab, without a null terminator + * + * @warning buffer must be large enough contain a and b, crashes otherwise. + */ void string_concat_buf(string buf, string a, string b) { if (buf.len < a.len + b.len) CRASH("Buffer is too small. %zu < %zu + %zu", buf.len, a.len, b.len); @@ -50,12 +68,32 @@ bool string_ends_with(string source, string ending) { return string_eq(substring_len(source, source.len-ending.len, ending.len), ending); } -// Returns a buffer strictly of size len. The result is zero-filled. +/** + * @brief Allocates len bytes for a new string + * + * @param len Length of buffer + * + * @return string A string with a buffer of at least length len. The resulting + * buffer is zero-filled. + * + * @note a null-terminated string can be allocated by providing len + 1. + */ inline string string_alloc(usize len) { return (string){calloc(len, sizeof(char*)), len}; -} ALLOC_SIZE(1); +}; // returns 0 when equal, -1 when a < b, and 1 when a > b +/** + * @brief Compares two strings. + * + * @return cond Returns 0 when equal, -1 when a < b, and 1 when a > b + * (their byte-wise, aggregate u8 difference is used to compare). + * + * @note for equality, string_eq is more efficient, using heuristic length + * checks before checking character by character. + * + * @see string_eq + */ u8 string_cmp(string a, string b) { // copied from odin's implementation lmfao int res = memcmp(a.raw, b.raw, a.len < b.len ? a.len : b.len); @@ -64,11 +102,26 @@ u8 string_cmp(string a, string b) { return res; } +/** + * @brief checks if two strings are equal. + * + * @return true if equal, else false + * + * @see string_cmp + */ inline bool string_eq(string a, string b) { return a.len == 0 || (a.len == b.len && string_cmp(a, b) == 0); } -// Allocates a null-terminated C string, and copies the source str +/** + * @brief Clones the source string to owned C string. + * + * Allocates a null-terminated C string, and copies the source string. + * + * @return string Owned null terminated C string. + * + * @see clone_to_string + */ inline char* clone_to_cstring(string str) { // NOTE: the returned char* is always allocated, so no null literal // (otherwise subsequent realloc results in undefined behaviour). @@ -76,7 +129,15 @@ inline char* clone_to_cstring(string str) { return strndup(str.raw, str.len); } -// Clones str into new string. The result is not null-terminated. +/** + * @brief Clones the source string. + * + * Allocates and copies the source string, without inserting a null terminator. + * + * @return string Owned string + * + * @see clone_to_cstring + */ string string_clone(string str) { string new_str = string_alloc(str.len); if (memmove(new_str.raw, str.raw, str.len) != new_str.raw) return NULL_STR; @@ -89,6 +150,11 @@ void printn(char* text, usize len) { putchar(text[c++]); } +/** + * @brief Prints a string + * + * Emits each character up to the sources length to stdout. + */ inline void printstr(string str) { printn(str.raw, str.len); } diff --git a/src/common/vec.c b/src/common/vec.c index 85b9f40..89b711a 100644 --- a/src/common/vec.c +++ b/src/common/vec.c @@ -44,61 +44,102 @@ WEAK void _vec_destroy(Vec(void)* v) { /* string specifics */ -// Copies string to newly allocated owned vector. The result is null terminated. +/** + * @brief Copies string to newly allocated owned vector. + * + * @param str source slice to copy + * @returns vec char vector containing str + * (of capacity equal to the input's length). + * + * @warning The result is not null-terminated. + */ Vec(char) string_to_owned(string str) { - Vec(char) v = vec_new(char, str.len + 1); + Vec(char) v = vec_new(char, str.len); - memcpy(v, str.raw, str.len); - v[str.len] = '\0'; - return v; + memcpy(v, str.raw, str.len); + return v; } -// Takes ownership of cstring and creates a vector. Moves the string in-place -// to insert a leading header, and returning the new vec. -Vec(char) cstring_to_vec(char* str) { - // The assumption is that you make a vec to append to it, so we reserve - // more up-front. The growth factor of 2 may be changed but idk - usize len = strlen(str); - str = realloc(str, len * 2 + sizeof(VecHeader)); - - // memmove so that strings can overlap. - memmove(str, (u8*)str + sizeof(VecHeader), len); - - Vec(char) v = vec_elems_from_header(str); - - if (v == nullptr) - return nullptr; - - vec_cap(v) = len * 2; - vec_len(v) = len; - - return v; +/* @brief Takes ownership of string and creates a char vector. + * + * Allocates a new space for a header at the start of the string's raw pointer, + * then moves the string in-place, and returning the new vec. + * + * @param str dynamically allocated null-terminated string. + * + * @warning The result is not null-terminated. + * + * @warning To allocate space, the function uses realloc, so the underlying + * data must have been allocated by an *alloc family function (i.e malloc, + * calloc, alligned_alloc etc). + **/ +Vec(char) cstring_to_vec(const char* str) { + // The assumption is that you make a vec to append to it, so we reserve + // more up-front. The growth factor of 2 may be changed but idk + usize len = strlen(str); + str = realloc((u8*)str, len * 2 + sizeof(VecHeader)); + + // memmove so that strings can overlap. + memmove((u8*)str, (u8*)str + sizeof(VecHeader), len); + + Vec(char) v = vec_elems_from_header(str); + + if (v == nullptr) + return nullptr; + + vec_cap(v) = len * 2; + vec_len(v) = len; + + return v; } -// Takes ownership of string and creates a vector. Moves the string in-place -// to insert a leading header, and returning the new vec. +/** + * @brief Takes ownership of string and creates a char vector. + * + * Allocates a new space for a header at the start of the string's raw pointer, + * then moves the string in-place, and returning the new vec. + * + * @param str dynamically allocated string, possibly null-terminated. + * @return vec char vector containing str + * (with a capacity of 1.5x the input's length). + * + * @warning The result is not null-terminated. + * + * @warning To allocate space, the function uses realloc, so the underlying + * data must have been allocated by an *alloc family function (i.e malloc, + * calloc, alligned_alloc etc). + */ Vec(char) string_to_vec(string str) { - // The assumption is that you make a vec to append to it, so we reserve - // more up-front. The growth factor of 2 may be changed but idk - str.raw = realloc(str.raw, str.len * 2 + sizeof(VecHeader)); + // The assumption is that you make a vec to append to it, so we reserve + // more up-front. + str.raw = realloc(str.raw, str.len * 1.5 + sizeof(VecHeader)); - // memmove so that strings can overlap. - memmove(str.raw, (u8*)str.raw + sizeof(VecHeader), str.len); + // memmove so that strings can overlap. + memmove(str.raw, (u8*)str.raw + sizeof(VecHeader), str.len); - Vec(char) v = vec_elems_from_header(str.raw); + Vec(char) v = vec_elems_from_header(str.raw); - if (v == nullptr) - return nullptr; + if (v == nullptr) + return nullptr; - vec_cap(v) = str.len * 2; - vec_len(v) = str.len; + vec_cap(v) += str.len / 2; + vec_len(v) = str.len; - return v; + return v; } -// Appends formatted string to a Vec(char), leaving the result null-terminated. -// reallocating to make space for the new format. Begins printing at the -// source's trailing null character, or its length if none is present. +/** + * @brief Format print to the end of a char vec + * + * Appends formatted string to a Vec(char), reallocating to make space. + * + * @param str string vector, possibly null-terminated + * @param format printf format specifier + * @param ... printf format arguments + * + * @warning The result is not null-terminated, nor does the function write + * over any old null terminators. + */ void vec_appendf(Vec(char) str, const char* format, ...) { va_list a; va_start(a, format); @@ -107,14 +148,9 @@ void vec_appendf(Vec(char) str, const char* format, ...) { usize printlen = 1 + vsnprintf("", 0, format, a); - // if already null-terminated, go back and print over it - if (str[vec_len(str)] == '\0') - vec_len(str)--; - vec_reserve(&str, printlen); vsnprintf(&str[vec_len(str)], printlen, format, b); - str[printlen] = '\0'; va_end(a); va_end(b); From 636b4e8381b640413e3e570632e19e6f4fd9b86f Mon Sep 17 00:00:00 2001 From: Nick Bors Date: Tue, 14 Apr 2026 20:13:30 +0300 Subject: [PATCH 3/5] fix: formatting doxygen comments --- src/common/str.c | 131 ++++++++++++++++++++--------------------------- 1 file changed, 56 insertions(+), 75 deletions(-) diff --git a/src/common/str.c b/src/common/str.c index 190f4a5..3cc8468 100644 --- a/src/common/str.c +++ b/src/common/str.c @@ -5,14 +5,12 @@ #include "common/str.h" -/** - * @brief Prints to a newly allocated string. - * - * @param format printf format specifier - * @param ... printf format arguments - * - * @result str null-terminated string, or NULL_STR on allocation fail. - */ +/// \brief Prints to a newly allocated string. +/// +/// \param format printf format specifier +/// \param ... printf format arguments +/// +/// \result str null-terminated string, or NULL_STR on allocation fail. string strprintf(char* format, ...) { string c = NULL_STR; va_list a; @@ -28,27 +26,23 @@ string strprintf(char* format, ...) { return c; } -/** - * @brief concats two strings - * - * Allocates a buffer containing the concatenation of a and b. - * - * @result str Allocated string containing ab, without a null terminator. - */ +/// \brief concats two strings +/// +///Allocates a buffer containing the concatenation of a and b. +/// +/// \result str Allocated string containing ab, without a null terminator. string string_concat(string a, string b) { string c = string_alloc(a.len + b.len); string_concat_buf(c, a, b); return c; } -/** - * @brief Concatenates two strings into buf. - * - * @param[out] buf buffer of minimum length a.len + b.len. - * buf is filled with the concatenation ab, without a null terminator - * - * @warning buffer must be large enough contain a and b, crashes otherwise. - */ +/// \brief Concatenates two strings into buf. +/// +/// \param[out] buf buffer of minimum length a.len + b.len. +/// buf is filled with the concatenation ab, without a null terminator +/// +/// \warning buffer must be large enough contain a and b, crashes otherwise. void string_concat_buf(string buf, string a, string b) { if (buf.len < a.len + b.len) CRASH("Buffer is too small. %zu < %zu + %zu", buf.len, a.len, b.len); @@ -68,32 +62,27 @@ bool string_ends_with(string source, string ending) { return string_eq(substring_len(source, source.len-ending.len, ending.len), ending); } -/** - * @brief Allocates len bytes for a new string - * - * @param len Length of buffer - * - * @return string A string with a buffer of at least length len. The resulting - * buffer is zero-filled. - * - * @note a null-terminated string can be allocated by providing len + 1. - */ +/// \brief Allocates len bytes for a new string +/// +/// \param len Length of buffer +/// +/// \return string A string with a buffer of at least length len. The resulting +/// buffer is zero-filled. +/// +/// \note a null-terminated string can be allocated by providing len + 1. inline string string_alloc(usize len) { return (string){calloc(len, sizeof(char*)), len}; }; -// returns 0 when equal, -1 when a < b, and 1 when a > b -/** - * @brief Compares two strings. - * - * @return cond Returns 0 when equal, -1 when a < b, and 1 when a > b - * (their byte-wise, aggregate u8 difference is used to compare). - * - * @note for equality, string_eq is more efficient, using heuristic length - * checks before checking character by character. - * - * @see string_eq - */ +/// \brief Compares two strings. +/// +/// \return cond Returns 0 when equal, -1 when a < b, and 1 when a > b +/// (their byte-wise, aggregate u8 difference is used to compare). +/// +/// \note for equality, string_eq is more efficient, using heuristic length +///checks before checking character by character. +/// +/// \see string_eq u8 string_cmp(string a, string b) { // copied from odin's implementation lmfao int res = memcmp(a.raw, b.raw, a.len < b.len ? a.len : b.len); @@ -102,26 +91,22 @@ u8 string_cmp(string a, string b) { return res; } -/** - * @brief checks if two strings are equal. - * - * @return true if equal, else false - * - * @see string_cmp - */ +/// \brief checks if two strings are equal. +/// +/// \return true if equal, else false +/// +/// \see string_cmp inline bool string_eq(string a, string b) { return a.len == 0 || (a.len == b.len && string_cmp(a, b) == 0); } -/** - * @brief Clones the source string to owned C string. - * - * Allocates a null-terminated C string, and copies the source string. - * - * @return string Owned null terminated C string. - * - * @see clone_to_string - */ +/// \brief Clones the source string to owned C string. +/// +///Allocates a null-terminated C string, and copies the source string. +/// +/// \return string Owned null terminated C string. +/// +/// \see clone_to_string inline char* clone_to_cstring(string str) { // NOTE: the returned char* is always allocated, so no null literal // (otherwise subsequent realloc results in undefined behaviour). @@ -129,15 +114,13 @@ inline char* clone_to_cstring(string str) { return strndup(str.raw, str.len); } -/** - * @brief Clones the source string. - * - * Allocates and copies the source string, without inserting a null terminator. - * - * @return string Owned string - * - * @see clone_to_cstring - */ +/// \brief Clones the source string. +/// +/// Allocates and copies the source string, without inserting a null terminator. +/// +/// \return string Owned string +/// +/// \see clone_to_cstring string string_clone(string str) { string new_str = string_alloc(str.len); if (memmove(new_str.raw, str.raw, str.len) != new_str.raw) return NULL_STR; @@ -150,11 +133,9 @@ void printn(char* text, usize len) { putchar(text[c++]); } -/** - * @brief Prints a string - * - * Emits each character up to the sources length to stdout. - */ +/// \brief Prints a string +/// +/// Emits each character up to the sources length to stdout. inline void printstr(string str) { printn(str.raw, str.len); } From 55d7f11f19ecc4748f5ae71cc003501b64605a28 Mon Sep 17 00:00:00 2001 From: Nick Bors Date: Tue, 14 Apr 2026 21:24:52 +0300 Subject: [PATCH 4/5] fix: realloc functions + header comments --- include/common/str.h | 81 ++++++++++++++++++++++++++++++++++++++++---- include/common/vec.h | 80 +++++++++++++++++++++++++++++++++++++++++-- src/common/str.c | 56 ------------------------------ src/common/vec.c | 78 +++++------------------------------------- 4 files changed, 159 insertions(+), 136 deletions(-) diff --git a/include/common/str.h b/include/common/str.h index 3383cc6..a3eb28a 100644 --- a/include/common/str.h +++ b/include/common/str.h @@ -36,17 +36,84 @@ typedef struct string { #define string_free(str) free(str.raw) -char* clone_to_cstring(string str); // this allocates -void printstr(string str); +/// \brief Allocates len bytes for a new string +/// +/// \param len Length of buffer +/// +/// \return string A string with a buffer of at least length len. The resulting +/// buffer is zero-filled. +/// +/// \note a null-terminated string can be allocated by providing len + 1. +string string_alloc(usize len); + +/// \brief Clones the source string to owned C string. +/// +/// Allocates a null-terminated C string, and copies the source string. +/// +/// \return cstring Owned null terminated C string. +/// +/// \see clone_to_string +char* clone_to_cstring(string str); // this allocates + +/// \brief Prints a string +/// +/// Emits each character up to the sources length to stdout. +void printstr(string str); + +/// \brief Prints to a newly allocated string. +/// +/// \param format printf format specifier +/// \param ... printf format arguments +/// +/// \return str null-terminated string, or NULL_STR on allocation fail. string strprintf(char* format, ...) FORMAT_CHECK(1, 2); -string string_alloc(usize len); -string string_clone(string str); // this allocates as well -string string_concat(string a, string b); // allocates -void string_concat_buf(string buf, string a, string b); // this does not +/// \brief Clones the source string. +/// +/// Allocates and copies the source string, without inserting a null terminator. +/// +/// \return string Owned string +/// +/// \see clone_to_cstring +string string_clone(string str); // this allocates as well + +/// \brief Concatenates two strings. +/// +/// Allocates a buffer containing the concatenation of a and b. +/// +/// \return str Allocated string containing ab, without a null terminator. +string string_concat(string a, string b); // allocates -u8 string_cmp(string a, string b); +/// \brief Concatenates two strings into buf. +/// +/// \param[out] buf buffer of minimum length a.len + b.len. +/// buf is filled with the concatenation ab, without a null terminator +/// +/// \warning buffer must be large enough contain a and b, crashes otherwise. +void string_concat_buf(string buf, string a, string b); // this does not + +/// \brief Compares two strings. +/// +/// \return cond Returns 0 when equal, -1 when a < b, and 1 when a > b +/// (their byte-wise, aggregate u8 difference is used to compare). +/// +/// \note for equality, string_eq is more efficient, using heuristic length +/// checks before checking character by character. +/// +/// \see string_eq +u8 string_cmp(string a, string b); + +/// \brief Checks if two strings are equal. +/// +/// \return cond true if equal, else false +/// +/// \see string_cmp bool string_eq(string a, string b); + +/// \brief Checks if string ends with another. +/// +/// \return cond true if yes, else false. If the ending string is larger than +/// the source, returns false. bool string_ends_with(string source, string ending); #endif // ORBIT_STRING_H diff --git a/include/common/vec.h b/include/common/vec.h index 41eb603..04e881f 100644 --- a/include/common/vec.h +++ b/include/common/vec.h @@ -92,7 +92,81 @@ void _vec_destroy(Vec(void)* v); /* string specifics */ -Vec(char) cstring_to_vec(const char* str); // always reallocates str -Vec(char) string_to_vec(string str); // same -void vec_appendf(Vec(char) str, const char* format, ...) FORMAT_CHECK(2, 3); + +/// \brief Copies string to newly allocated owned vector. +/// +/// \param str source slice to copy +/// \return vec char vector containing str +/// (of capacity equal to the input's length). +/// +/// \warning The result is not null-terminated. +/// +/// It may be preferable to realloc in-place to reduce fragmentation. For +/// dynamically allocated strings, +/// \see realloc_string_to_vec +Vec(char) string_to_vec(string str); + +/// \brief Copies a C string to newly allocated owned vector. +/// +/// \param str source C string to copy +/// \return vec char vector containing str +/// (of capacity equal to the input's length). +/// +/// \warning The result is not null-terminated. +/// +/// It may be preferable to create a vector in-place to reduce fragmentation. +/// For dynamically allocated C strings, +/// \see realloc_string_to_vec +Vec(char) cstring_to_vec(const char* str); + +/// \brief Takes ownership of string and creates a char vector. +/// +/// Allocates a new space for a header at the start of the string's raw pointer, +/// then moves the string in-place, and returning the new vec. +/// +/// \param str dynamically allocated string, possibly null-terminated. +/// \return vec char vector containing str +/// (with a capacity of 1.5x the input's length). +/// +/// \warning The result is not null-terminated. +/// +/// \warning To allocate space, the function uses realloc, so the underlying +/// data must have been allocated by an *alloc family function (i.e malloc, +/// calloc, alligned_alloc etc). +/// +/// For a copy only function, +/// \see string_to_vec +Vec(char) realloc_string_to_vec(string str); + +/// \brief Takes ownership of string and creates a char vector. +/// +/// Allocates a new space for a header at the start of the str, +/// then moves the string in-place, and returning the new vec. +/// +/// \param str dynamically allocated null-terminated string. +/// \return vec char vector containing str +/// (with a capacity of 1.5x the input's length). +/// +/// \warning The result is not null-terminated. +/// +/// \warning To allocate space, the function uses realloc, so the source str +/// must have been allocated by an *alloc family function (i.e malloc, +/// calloc, alligned_alloc etc). +/// +/// For a copy only function, +/// \see cstring_to_vec +Vec(char) realloc_cstring_to_vec(char* str); + +/// \brief Format print to the end of a char vec +/// +/// Appends formatted string to a Vec(char), reallocating to make space. +/// +/// \param str string vector, possibly null-terminated +/// \param format printf format specifier +/// \param ... printf format arguments +/// +/// \warning The result is not null-terminated, nor does the function write +/// over any old null terminators. +FORMAT_CHECK(2, 3) void vec_appendf(Vec(char) str, const char* format, ...); + #endif // VEC_H diff --git a/src/common/str.c b/src/common/str.c index 3cc8468..df2ccd1 100644 --- a/src/common/str.c +++ b/src/common/str.c @@ -5,12 +5,6 @@ #include "common/str.h" -/// \brief Prints to a newly allocated string. -/// -/// \param format printf format specifier -/// \param ... printf format arguments -/// -/// \result str null-terminated string, or NULL_STR on allocation fail. string strprintf(char* format, ...) { string c = NULL_STR; va_list a; @@ -26,23 +20,12 @@ string strprintf(char* format, ...) { return c; } -/// \brief concats two strings -/// -///Allocates a buffer containing the concatenation of a and b. -/// -/// \result str Allocated string containing ab, without a null terminator. string string_concat(string a, string b) { string c = string_alloc(a.len + b.len); string_concat_buf(c, a, b); return c; } -/// \brief Concatenates two strings into buf. -/// -/// \param[out] buf buffer of minimum length a.len + b.len. -/// buf is filled with the concatenation ab, without a null terminator -/// -/// \warning buffer must be large enough contain a and b, crashes otherwise. void string_concat_buf(string buf, string a, string b) { if (buf.len < a.len + b.len) CRASH("Buffer is too small. %zu < %zu + %zu", buf.len, a.len, b.len); @@ -62,27 +45,10 @@ bool string_ends_with(string source, string ending) { return string_eq(substring_len(source, source.len-ending.len, ending.len), ending); } -/// \brief Allocates len bytes for a new string -/// -/// \param len Length of buffer -/// -/// \return string A string with a buffer of at least length len. The resulting -/// buffer is zero-filled. -/// -/// \note a null-terminated string can be allocated by providing len + 1. inline string string_alloc(usize len) { return (string){calloc(len, sizeof(char*)), len}; }; -/// \brief Compares two strings. -/// -/// \return cond Returns 0 when equal, -1 when a < b, and 1 when a > b -/// (their byte-wise, aggregate u8 difference is used to compare). -/// -/// \note for equality, string_eq is more efficient, using heuristic length -///checks before checking character by character. -/// -/// \see string_eq u8 string_cmp(string a, string b) { // copied from odin's implementation lmfao int res = memcmp(a.raw, b.raw, a.len < b.len ? a.len : b.len); @@ -91,22 +57,10 @@ u8 string_cmp(string a, string b) { return res; } -/// \brief checks if two strings are equal. -/// -/// \return true if equal, else false -/// -/// \see string_cmp inline bool string_eq(string a, string b) { return a.len == 0 || (a.len == b.len && string_cmp(a, b) == 0); } -/// \brief Clones the source string to owned C string. -/// -///Allocates a null-terminated C string, and copies the source string. -/// -/// \return string Owned null terminated C string. -/// -/// \see clone_to_string inline char* clone_to_cstring(string str) { // NOTE: the returned char* is always allocated, so no null literal // (otherwise subsequent realloc results in undefined behaviour). @@ -114,13 +68,6 @@ inline char* clone_to_cstring(string str) { return strndup(str.raw, str.len); } -/// \brief Clones the source string. -/// -/// Allocates and copies the source string, without inserting a null terminator. -/// -/// \return string Owned string -/// -/// \see clone_to_cstring string string_clone(string str) { string new_str = string_alloc(str.len); if (memmove(new_str.raw, str.raw, str.len) != new_str.raw) return NULL_STR; @@ -133,9 +80,6 @@ void printn(char* text, usize len) { putchar(text[c++]); } -/// \brief Prints a string -/// -/// Emits each character up to the sources length to stdout. inline void printstr(string str) { printn(str.raw, str.len); } diff --git a/src/common/vec.c b/src/common/vec.c index 89b711a..bdc9eb3 100644 --- a/src/common/vec.c +++ b/src/common/vec.c @@ -44,72 +44,18 @@ WEAK void _vec_destroy(Vec(void)* v) { /* string specifics */ -/** - * @brief Copies string to newly allocated owned vector. - * - * @param str source slice to copy - * @returns vec char vector containing str - * (of capacity equal to the input's length). - * - * @warning The result is not null-terminated. - */ -Vec(char) string_to_owned(string str) { +Vec(char) string_to_vec(string str) { Vec(char) v = vec_new(char, str.len); memcpy(v, str.raw, str.len); return v; } -/* @brief Takes ownership of string and creates a char vector. - * - * Allocates a new space for a header at the start of the string's raw pointer, - * then moves the string in-place, and returning the new vec. - * - * @param str dynamically allocated null-terminated string. - * - * @warning The result is not null-terminated. - * - * @warning To allocate space, the function uses realloc, so the underlying - * data must have been allocated by an *alloc family function (i.e malloc, - * calloc, alligned_alloc etc). - **/ -Vec(char) cstring_to_vec(const char* str) { - // The assumption is that you make a vec to append to it, so we reserve - // more up-front. The growth factor of 2 may be changed but idk - usize len = strlen(str); - str = realloc((u8*)str, len * 2 + sizeof(VecHeader)); - - // memmove so that strings can overlap. - memmove((u8*)str, (u8*)str + sizeof(VecHeader), len); - - Vec(char) v = vec_elems_from_header(str); - - if (v == nullptr) - return nullptr; - - vec_cap(v) = len * 2; - vec_len(v) = len; - - return v; +inline Vec(char) cstring_to_vec(const char* str) { + return string_to_vec(string_wrap(str)); } -/** - * @brief Takes ownership of string and creates a char vector. - * - * Allocates a new space for a header at the start of the string's raw pointer, - * then moves the string in-place, and returning the new vec. - * - * @param str dynamically allocated string, possibly null-terminated. - * @return vec char vector containing str - * (with a capacity of 1.5x the input's length). - * - * @warning The result is not null-terminated. - * - * @warning To allocate space, the function uses realloc, so the underlying - * data must have been allocated by an *alloc family function (i.e malloc, - * calloc, alligned_alloc etc). - */ -Vec(char) string_to_vec(string str) { +Vec(char) realloc_string_to_vec(string str) { // The assumption is that you make a vec to append to it, so we reserve // more up-front. str.raw = realloc(str.raw, str.len * 1.5 + sizeof(VecHeader)); @@ -128,18 +74,10 @@ Vec(char) string_to_vec(string str) { return v; } -/** - * @brief Format print to the end of a char vec - * - * Appends formatted string to a Vec(char), reallocating to make space. - * - * @param str string vector, possibly null-terminated - * @param format printf format specifier - * @param ... printf format arguments - * - * @warning The result is not null-terminated, nor does the function write - * over any old null terminators. - */ +inline Vec(char) realloc_cstring_to_vec(char* str) { + return realloc_string_to_vec(string_wrap(str)); +} + void vec_appendf(Vec(char) str, const char* format, ...) { va_list a; va_start(a, format); From dce1510e5fe5d5f678a358a63b034156bb83bb52 Mon Sep 17 00:00:00 2001 From: Nick Bors Date: Tue, 14 Apr 2026 22:10:52 +0300 Subject: [PATCH 5/5] fix: string_eq impl and static inlines --- include/common/str.h | 14 +++++++------- include/common/vec.h | 5 ++--- src/common/str.c | 16 ++++++++++------ src/common/vec.c | 4 ++-- 4 files changed, 21 insertions(+), 18 deletions(-) diff --git a/include/common/str.h b/include/common/str.h index a3eb28a..a0516fc 100644 --- a/include/common/str.h +++ b/include/common/str.h @@ -44,7 +44,7 @@ typedef struct string { /// buffer is zero-filled. /// /// \note a null-terminated string can be allocated by providing len + 1. -string string_alloc(usize len); +static inline string string_alloc(usize len); /// \brief Clones the source string to owned C string. /// @@ -53,12 +53,12 @@ string string_alloc(usize len); /// \return cstring Owned null terminated C string. /// /// \see clone_to_string -char* clone_to_cstring(string str); // this allocates +static inline char* clone_to_cstring(string str); /// \brief Prints a string /// /// Emits each character up to the sources length to stdout. -void printstr(string str); +static inline void printstr(string str); /// \brief Prints to a newly allocated string. /// @@ -75,14 +75,14 @@ string strprintf(char* format, ...) FORMAT_CHECK(1, 2); /// \return string Owned string /// /// \see clone_to_cstring -string string_clone(string str); // this allocates as well +string string_clone(string str); /// \brief Concatenates two strings. /// /// Allocates a buffer containing the concatenation of a and b. /// /// \return str Allocated string containing ab, without a null terminator. -string string_concat(string a, string b); // allocates +string string_concat(string a, string b); /// \brief Concatenates two strings into buf. /// @@ -90,7 +90,7 @@ string string_concat(string a, string b); // allocates /// buf is filled with the concatenation ab, without a null terminator /// /// \warning buffer must be large enough contain a and b, crashes otherwise. -void string_concat_buf(string buf, string a, string b); // this does not +void string_concat_buf(string buf, string a, string b); /// \brief Compares two strings. /// @@ -101,7 +101,7 @@ void string_concat_buf(string buf, string a, string b); // this does not /// checks before checking character by character. /// /// \see string_eq -u8 string_cmp(string a, string b); +isize string_cmp(string a, string b); /// \brief Checks if two strings are equal. /// diff --git a/include/common/vec.h b/include/common/vec.h index 04e881f..5715270 100644 --- a/include/common/vec.h +++ b/include/common/vec.h @@ -92,7 +92,6 @@ void _vec_destroy(Vec(void)* v); /* string specifics */ - /// \brief Copies string to newly allocated owned vector. /// /// \param str source slice to copy @@ -117,7 +116,7 @@ Vec(char) string_to_vec(string str); /// It may be preferable to create a vector in-place to reduce fragmentation. /// For dynamically allocated C strings, /// \see realloc_string_to_vec -Vec(char) cstring_to_vec(const char* str); +static inline Vec(char) cstring_to_vec(const char* str); /// \brief Takes ownership of string and creates a char vector. /// @@ -155,7 +154,7 @@ Vec(char) realloc_string_to_vec(string str); /// /// For a copy only function, /// \see cstring_to_vec -Vec(char) realloc_cstring_to_vec(char* str); +static inline Vec(char) realloc_cstring_to_vec(char* str); /// \brief Format print to the end of a char vec /// diff --git a/src/common/str.c b/src/common/str.c index df2ccd1..11a5fb9 100644 --- a/src/common/str.c +++ b/src/common/str.c @@ -45,11 +45,11 @@ bool string_ends_with(string source, string ending) { return string_eq(substring_len(source, source.len-ending.len, ending.len), ending); } -inline string string_alloc(usize len) { +static inline string string_alloc(usize len) { return (string){calloc(len, sizeof(char*)), len}; }; -u8 string_cmp(string a, string b) { +isize string_cmp(string a, string b) { // copied from odin's implementation lmfao int res = memcmp(a.raw, b.raw, a.len < b.len ? a.len : b.len); if (res == 0 && a.len != b.len) return a.len <= b.len ? -1 : 1; @@ -57,11 +57,15 @@ u8 string_cmp(string a, string b) { return res; } -inline bool string_eq(string a, string b) { - return a.len == 0 || (a.len == b.len && string_cmp(a, b) == 0); +bool string_eq(string a, string b) { + if (a.len != b.len) return false; + for (size_t i = 0; i < a.len; ++i) { + if (a.raw[i] != b.raw[i]) return false; + } + return true; } -inline char* clone_to_cstring(string str) { +static inline char* clone_to_cstring(string str) { // NOTE: the returned char* is always allocated, so no null literal // (otherwise subsequent realloc results in undefined behaviour). // The last null character is always allocated internally. @@ -80,6 +84,6 @@ void printn(char* text, usize len) { putchar(text[c++]); } -inline void printstr(string str) { +static inline void printstr(string str) { printn(str.raw, str.len); } diff --git a/src/common/vec.c b/src/common/vec.c index bdc9eb3..bcaef43 100644 --- a/src/common/vec.c +++ b/src/common/vec.c @@ -51,7 +51,7 @@ Vec(char) string_to_vec(string str) { return v; } -inline Vec(char) cstring_to_vec(const char* str) { +static inline Vec(char) cstring_to_vec(const char* str) { return string_to_vec(string_wrap(str)); } @@ -74,7 +74,7 @@ Vec(char) realloc_string_to_vec(string str) { return v; } -inline Vec(char) realloc_cstring_to_vec(char* str) { +static inline Vec(char) realloc_cstring_to_vec(char* str) { return realloc_string_to_vec(string_wrap(str)); }