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 b992089..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) 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/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..a0516fc 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,18 +29,91 @@ typedef struct string { #define string_wrap(cstring) ((string){(char*)(cstring), strlen((cstring))}) #define strlit(cstring) ((string){(char*)cstring, sizeof(cstring)-1}) -char* clone_to_cstring(string str); // this allocates -void printstr(string str); -string strprintf(char* format, ...); +#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) -string string_alloc(size_t len); #define string_free(str) free(str.raw) -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 -int string_cmp(string a, string b); +/// \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. +static inline 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 +static inline char* clone_to_cstring(string str); + +/// \brief Prints a string +/// +/// Emits each character up to the sources length to stdout. +static inline 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); + +/// \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); + +/// \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); + +/// \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); + +/// \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 +isize 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 545e713..5715270 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,82 @@ 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 */ + +/// \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 +static inline 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 +static inline 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 99d4307..11a5fb9 100644 --- a/src/common/str.c +++ b/src/common/str.c @@ -3,7 +3,6 @@ #include #include -#include "common/portability.h" #include "common/str.h" string strprintf(char* format, ...) { @@ -12,7 +11,7 @@ string strprintf(char* format, ...) { 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--; @@ -28,11 +27,15 @@ string string_concat(string a, string b) { } 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 +45,11 @@ 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); - - return (string){raw, len}; +static inline string string_alloc(usize len) { + return (string){calloc(len, sizeof(char*)), len}; +}; -} - -int 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; @@ -67,14 +65,11 @@ bool string_eq(string a, string b) { return true; } -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; +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. + return strndup(str.raw, str.len); } string string_clone(string str) { @@ -83,12 +78,12 @@ string string_clone(string 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) { +static inline void printstr(string str) { printn(str.raw, str.len); } diff --git a/src/common/vec.c b/src/common/vec.c index ecf3270..bcaef43 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,55 @@ WEAK void _vec_destroy(Vec(void)* v) { free(vec_header(*v)); *v = nullptr; } + +/* string specifics */ + +Vec(char) string_to_vec(string str) { + Vec(char) v = vec_new(char, str.len); + + memcpy(v, str.raw, str.len); + return v; +} + +static inline Vec(char) cstring_to_vec(const char* str) { + return string_to_vec(string_wrap(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)); + + // 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; +} + +static 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); + va_list b; + va_copy(b, a); + + usize printlen = 1 + vsnprintf("", 0, format, a); + + vec_reserve(&str, printlen); + + vsnprintf(&str[vec_len(str)], printlen, format, b); + + va_end(a); + va_end(b); +}