diff --git a/src/guf_str.h b/src/guf_str.h index 8365b0a..ffda8fb 100644 --- a/src/guf_str.h +++ b/src/guf_str.h @@ -60,8 +60,8 @@ typedef struct guf_str { #ifdef __cplusplus // Standard C++ does not have compound literals like C99... - #define GUF_CSTR_TO_VIEW_CPP(CSTR) guf_str_view{.str = (CSTR), .len = (ptrdiff_t)strlen(CSTR)} - #define GUF_CSTR_LIT_TO_VIEW_CPP(CSTR) guf_str_view{.str = (CSTR), .len = (ptrdiff_t)sizeof(CSTR) - 1} + #define GUF_CSTR_TO_VIEW_CPP(CSTR) guf_str_view {.str = (CSTR), .len = (ptrdiff_t)strlen(CSTR)} + #define GUF_CSTR_LIT_TO_VIEW_CPP(CSTR) guf_str_view {.str = (CSTR), .len = (ptrdiff_t)sizeof(CSTR) - 1} #define GUF_STR_UNINITIALISED_CPP guf_str{.allocator = NULL, .data.shrt.size = 0, .data.shrt.c_str[0] = '\0'} #endif @@ -84,10 +84,9 @@ GUF_STR_KWRDS guf_str_view guf_str_view_trim_left_ascii(guf_str_view sv); GUF_STR_KWRDS guf_str_view guf_str_next_tok(guf_str_view *input, const guf_str_view *delims, ptrdiff_t num_delims, const guf_str_view *preserved_delims, ptrdiff_t num_preserved_delims); // guf_str: - -// DONE: GUF_STR_KWRDS guf_str *guf_str_try_init(guf_str *str, guf_str_view str_view, guf_allocator *alloc, guf_err *err); GUF_STR_KWRDS guf_str *guf_str_init(guf_str *str, guf_str_view str_view, guf_allocator *alloc); +GUF_STR_KWRDS guf_str *guf_str_init_empty(guf_str *str, guf_allocator *alloc); GUF_STR_KWRDS guf_str *guf_str_try_init_from_cstr(guf_str *str, const char* c_str, guf_allocator *alloc, guf_err *err); GUF_STR_KWRDS guf_str *guf_str_init_from_cstr(guf_str *str, const char* c_str, guf_allocator *alloc); @@ -98,11 +97,24 @@ GUF_STR_KWRDS guf_str guf_str_new(guf_str_view str_view, guf_allocator *alloc); GUF_STR_KWRDS void guf_str_free(guf_str *str, void *ctx); GUF_STR_KWRDS guf_str *guf_str_copy(guf_str *dst, const guf_str *src, void *ctx); GUF_STR_KWRDS guf_str *guf_str_move(guf_str *dst, guf_str *src, void *ctx); - GUF_STR_KWRDS bool guf_str_equal(const guf_str *a, const guf_str *b); GUF_STR_KWRDS int guf_str_cmp(const guf_str *a, const guf_str *b); -// DONE: +// Reserve at least min_capacity characters (excluding the null-terminator) (try to double the current capacity first; if that's not at least min_capacity, set the new capacity to min_capacity instead). +GUF_STR_KWRDS guf_str *guf_str_try_reserve(guf_str *str, ptrdiff_t min_capacity, guf_err *err); +GUF_STR_KWRDS guf_str *guf_str_reserve(guf_str *str, ptrdiff_t min_capacity); +// Shrink the capacity of the string so it does not waste space (short-string-optimisation will be applied if the new capacity <= GUF_STR_SSO_BUF_CAP) +GUF_STR_KWRDS guf_str *guf_str_try_shrink_to_fit(guf_str *str, guf_err *err); +GUF_STR_KWRDS guf_str *guf_str_shrink_to_fit(guf_str *str); + +// Set the contents of str to the given string view (mutating str) -> return the mutated str +GUF_STR_KWRDS guf_str *guf_str_try_set(guf_str *str, guf_str_view str_view, guf_err *err); +GUF_STR_KWRDS guf_str *guf_str_set(guf_str *str, guf_str_view str_view); + +// Return a view of the string. +GUF_STR_KWRDS guf_str_view guf_str_to_view(const guf_str *str); + +// Return a non-const pointer to the character at the specified index of str (if possible) GUF_STR_KWRDS char *guf_str_try_at(guf_str *str, ptrdiff_t idx, guf_err *err); GUF_STR_KWRDS char *guf_str_at(guf_str *str, ptrdiff_t idx); GUF_STR_KWRDS char *guf_str_try_back(guf_str *str, guf_err *err); @@ -110,6 +122,7 @@ GUF_STR_KWRDS char *guf_str_back(guf_str *str); GUF_STR_KWRDS char *guf_str_try_front(guf_str *str, guf_err *err); GUF_STR_KWRDS char *guf_str_front(guf_str *str); +// Return a copy of the char at the specified index of str (if possible) GUF_STR_KWRDS char guf_str_try_at_cpy(const guf_str *str, ptrdiff_t idx, guf_err *err); GUF_STR_KWRDS char guf_str_at_cpy(const guf_str *str, ptrdiff_t idx); GUF_STR_KWRDS char guf_str_try_back_cpy(const guf_str *str, guf_err *err); @@ -117,45 +130,42 @@ GUF_STR_KWRDS char guf_str_back_cpy(const guf_str *str); GUF_STR_KWRDS char guf_str_try_front_cpy(const guf_str *str, guf_err *err); GUF_STR_KWRDS char guf_str_front_cpy(const guf_str *str); -// DONE: -// Make substring in-place (constant time if pos == 0, otherwise copying count chars to the beginning of the str, i.e. linear time) +/* + Turn str into the substring in range [pos, pos + count) (mutating str) -> return the mutated str + (Constant time if pos == 0, otherwise copying count chars to the beginning of the str, i.e. linear time.) + NOTE: To make a substring-copy (instead of mutating str), create a guf_str_view of str and use guf_str_view_substr +*/ GUF_STR_KWRDS guf_str *guf_str_try_substr(guf_str *str, ptrdiff_t pos, ptrdiff_t count, guf_err *err); GUF_STR_KWRDS guf_str *guf_str_substr(guf_str *str, ptrdiff_t pos, ptrdiff_t count); -// TODO: +// Remove the last character from str if possible (mutating str) -> return the popped char +GUF_STR_KWRDS char guf_str_try_pop_back(guf_str *str, guf_err *err); GUF_STR_KWRDS char guf_str_pop_back(guf_str *str); -GUF_STR_KWRDS char guf_str_pop_front(guf_str *str); -// DONE: +// Append a char to str (n times; times must be >= 0) (mutating str) -> return the mutated str GUF_STR_KWRDS guf_str *guf_str_try_append_char(guf_str *str, char c, ptrdiff_t times, guf_err *err); GUF_STR_KWRDS guf_str *guf_str_append_char(guf_str *str, char c, ptrdiff_t times); GUF_STR_KWRDS guf_str *guf_str_try_append_one_char(guf_str *str, char c, guf_err *err); GUF_STR_KWRDS guf_str *guf_str_append_one_char(guf_str *str, char c); +// Append str_view to str (mutating str) -> return the mutated str GUF_STR_KWRDS guf_str *guf_str_try_append(guf_str *str, guf_str_view sv, guf_err *err); GUF_STR_KWRDS guf_str *guf_str_append(guf_str *str, guf_str_view sv); -GUF_STR_KWRDS guf_str *guf_str_try_append_cstr(guf_str *str, const char *c_str, guf_err *err); -GUF_STR_KWRDS guf_str *guf_str_append_cstr(guf_str *str, const char *c_str); - -// DONE: -GUF_STR_KWRDS guf_str *guf_str_try_reserve(guf_str *str, ptrdiff_t min_capacity, guf_err *err); -GUF_STR_KWRDS guf_str *guf_str_reserve(guf_str *str, ptrdiff_t min_capacity); - -// TODO: -GUF_STR_KWRDS guf_str *guf_str_try_shrink_to_fit(guf_str *str, guf_err *err); -GUF_STR_KWRDS guf_str *guf_str_shrink_to_fit(guf_str *str); - -// DONE: +// Return a pointer to the null-terminated char array representing the string (works like std::string::c_str in C++) GUF_STR_KWRDS const char *guf_str_const_cstr(const guf_str *str); GUF_STR_KWRDS char *guf_str_try_get_cstr(guf_str *str, guf_err *err); // Error if str is readonly. GUF_STR_KWRDS char *guf_str_cstr(guf_str *str); // Panics if str is readonly. -GUF_STR_KWRDS ptrdiff_t guf_str_len(const guf_str *str); // The length (in chars) without the final zero-terminator. -GUF_STR_KWRDS ptrdiff_t guf_str_capacity(const guf_str *str); // The capacity (in chars) without the final zero-terminator. +// Return the length/capacity (in chars) *without* the final null-terminator. +GUF_STR_KWRDS ptrdiff_t guf_str_len(const guf_str *str); +GUF_STR_KWRDS ptrdiff_t guf_str_capacity(const guf_str *str); +// Return true if the char data of the string lives directly within the guf_str itself (short-string optimisation) instead of in a separate dynamic allocation GUF_STR_KWRDS bool guf_str_is_short(const guf_str *str); +// Return true if the string is in readonly ("view") mode, i.e. can't be modified, copied etc. which is useful for guf_dict so we don't have to use guf_str_view but can use guf_str (by passing a read-only guf_str) for the lookup functions. GUF_STR_KWRDS bool guf_str_is_readonly(const guf_str *str); +// Return true if the string's data does not violate its invariants (useful for debugging, should never be false after initialising if there are not bugs in guf_str). GUF_STR_KWRDS bool guf_str_is_valid(const guf_str *str); GUF_STR_KWRDS guf_str guf_str_new_uninitialised(void); @@ -198,7 +208,7 @@ GUF_STR_KWRDS bool guf_str_is_uninit(const guf_str *str); } static inline void guf_str_set_shrt_size_(guf_str *str, unsigned char size_with_null) { - GUF_ASSERT(size_with_null < GUF_STR_SSO_BUF_CAP && size_with_null < 0x80); + GUF_ASSERT(size_with_null <= GUF_STR_SSO_BUF_CAP && size_with_null < 0x80); // TODO: was < SSO_CAP, should be <= SSO_CAP? str->data.shrt.size = (unsigned char)(size_with_null << 1); } #elif defined(GUF_PLATFORM_BIG_ENDIAN) @@ -214,7 +224,7 @@ GUF_STR_KWRDS bool guf_str_is_uninit(const guf_str *str); } static inline void guf_str_set_shrt_size_(guf_str *str, unsigned char size_with_null) { - GUF_ASSERT(size_with_null < GUF_STR_SSO_BUF_CAP && size_with_null < 0x80); + GUF_ASSERT(size_with_null <= GUF_STR_SSO_BUF_CAP && size_with_null < 0x80); // TODO: was < SSO_CAP, should be <= str->data.shrt.size = size_with_null; } #else @@ -328,20 +338,25 @@ GUF_STR_KWRDS bool guf_str_is_valid(const guf_str *str) return size > 0 && size <= GUF_STR_SSO_BUF_CAP && str->data.shrt.c_str[size - 1] == '\0'; } else { const size_t cap_with_null = guf_str_cap_internal_(str) + 1; + const size_t size = guf_str_size_internal_(str); // len + 1 const bool valid_cap = cap_with_null > GUF_STR_SSO_BUF_CAP && cap_with_null <= PTRDIFF_MAX && (cap_with_null % 2 == 0); - return valid_cap && str->data.lng.c_str && str->data.lng.size > 0 && str->data.lng.size <= cap_with_null; + return valid_cap && size >= 1 && str->data.lng.c_str && str->data.lng.size > 0 && str->data.lng.size <= cap_with_null && str->data.lng.c_str[size - 1] == '\0'; } } GUF_STR_KWRDS guf_str *guf_str_try_reserve(guf_str *str, ptrdiff_t new_cap_min, guf_err *err) { GUF_ASSERT(guf_str_is_valid(str)); - GUF_ASSERT(!guf_str_is_readonly(str)); + + if (guf_str_is_readonly(str)) { + guf_err_set_or_panic(err, GUF_ERR_INVALID_ARG, GUF_ERR_MSG("in guf_str_try_reserve: guf_str is readonly")); + return NULL; + } const size_t old_cap_with_null = guf_str_cap_internal_(str) + 1; const size_t len_with_null = guf_str_len_internal_(str) + 1; - if (new_cap_min <= (ptrdiff_t)old_cap_with_null) { // No need to grow. + if (new_cap_min < (ptrdiff_t)old_cap_with_null) { // No need to grow. TODO: was <=, should be < ? guf_err_set_if_not_null(err, GUF_ERR_NONE); return str; } @@ -382,6 +397,7 @@ GUF_STR_KWRDS guf_str *guf_str_try_reserve(guf_str *str, ptrdiff_t new_cap_min, } memcpy(c_str_new, str->data.shrt.c_str, len_with_null); str->data.lng.c_str = c_str_new; + str->data.lng.size = len_with_null; guf_str_set_lng_cap_(str, new_cap_min_with_null); } else { // b) Was long string -> need re-allocation char *c_str_new = str->allocator->realloc(str->data.lng.c_str, old_cap_with_null, new_cap_min_with_null, str->allocator->ctx); @@ -398,9 +414,67 @@ GUF_STR_KWRDS guf_str *guf_str_try_reserve(guf_str *str, ptrdiff_t new_cap_min, return str; } +GUF_STR_KWRDS guf_str *guf_str_reserve(guf_str *str, ptrdiff_t new_cap_min) +{ + return guf_str_try_reserve(str, new_cap_min, NULL); +} + + +GUF_STR_KWRDS guf_str *guf_str_try_shrink_to_fit(guf_str *str, guf_err *err) +{ + GUF_ASSERT(guf_str_is_valid(str)); + + if (guf_str_is_readonly(str)) { + guf_err_set_or_panic(err, GUF_ERR_INVALID_ARG, GUF_ERR_MSG("in guf_str_try_shrink_to_fit: guf_str is readonly")); + return NULL; + } + + const size_t old_cap_with_null = guf_str_cap_internal_(str) + 1; + const size_t len_with_null = guf_str_len_internal_(str) + 1; + GUF_ASSERT(len_with_null <= old_cap_with_null); + + if (old_cap_with_null == len_with_null || guf_str_is_short_internal_(str)) { + guf_err_set_if_not_null(err, GUF_ERR_NONE); + return str; + } + + char *c_str_old = guf_str_cstr(str); + GUF_ASSERT(c_str_old); + + if (len_with_null <= GUF_STR_SSO_BUF_CAP) { // a) Shrunk size fits into short.string. + GUF_ASSERT(len_with_null <= UCHAR_MAX) + guf_str_set_shrt_size_(str, (unsigned char)len_with_null); + memcpy(str->data.shrt.c_str, c_str_old, len_with_null); + str->allocator->free(c_str_old, old_cap_with_null, str->allocator->ctx); + GUF_ASSERT(guf_str_is_short(str)); + GUF_ASSERT(guf_str_is_valid(str)); + guf_err_set_if_not_null(err, GUF_ERR_NONE); + return str; + } + // b) Shrunk size does not fit into short-string. + char *c_str_new = str->allocator->realloc(c_str_old, old_cap_with_null, len_with_null, str->allocator->ctx); + if (!c_str_new) { + guf_err_set_or_panic(err, GUF_ERR_ALLOC_FAIL, GUF_ERR_MSG("in guf_str_try_shrink_to_fit: realloc failed")); + return NULL; + } else { + str->data.lng.c_str = c_str_new; + guf_str_set_lng_cap_(str, len_with_null); + GUF_ASSERT(!guf_str_is_short(str)); + GUF_ASSERT(guf_str_is_valid(str)); + guf_err_set_if_not_null(err, GUF_ERR_NONE); + return str; + } +} + +GUF_STR_KWRDS guf_str *guf_str_shrink_to_fit(guf_str *str) +{ + return guf_str_try_shrink_to_fit(str, NULL); +} + + static char *guf_str_get_cstr_internal_(guf_str *str) { - if (guf_str_is_short(str)) { + if (guf_str_is_short_internal_(str)) { return str->data.shrt.c_str; } else { return str->data.lng.c_str; @@ -470,14 +544,15 @@ GUF_STR_KWRDS bool guf_str_is_uninit(const guf_str *str) GUF_STR_KWRDS guf_str *guf_str_init_empty(guf_str *str, guf_allocator *allocator) { - GUF_ASSERT(str && allocator); + GUF_ASSERT_RELEASE(str && allocator); + GUF_ASSERT_RELEASE(allocator->alloc && allocator->realloc && allocator->free); str->allocator = allocator; guf_str_set_shrt_size_(str, 1); str->data.shrt.c_str[0] = '\0'; + GUF_ASSERT(guf_str_is_valid(str)); return str; } - GUF_STR_KWRDS guf_str *guf_str_try_init(guf_str *str, guf_str_view str_view, guf_allocator *alloc, guf_err *err) { if (!str) { @@ -498,11 +573,14 @@ GUF_STR_KWRDS guf_str *guf_str_try_init(guf_str *str, guf_str_view str_view, guf if (str_view.len == 0) { GUF_ASSERT(!guf_str_is_readonly(str)); GUF_ASSERT(guf_str_is_valid(str)); + guf_err_set_if_not_null(err, GUF_ERR_NONE); return str; } GUF_ASSERT(str_view.str && str_view.len > 0); guf_str_try_reserve(str, str_view.len, err); + GUF_ASSERT(guf_str_is_valid(str)); + if (err && *err != GUF_ERR_NONE) { guf_panic(*err, GUF_ERR_MSG("in guf_str_try_init: Initial allocation failed")); return NULL; @@ -512,9 +590,10 @@ GUF_STR_KWRDS guf_str *guf_str_try_init(guf_str *str, guf_str_view str_view, guf GUF_ASSERT(!guf_str_is_readonly(str)); char *c_str_dst = guf_str_get_cstr_internal_(str); - GUF_ASSERT_RELEASE(c_str_dst); + GUF_ASSERT(c_str_dst); memcpy(c_str_dst, str_view.str, str_view.len); c_str_dst[str_view.len] = '\0'; + guf_str_set_len_internal_(str, str_view.len); GUF_ASSERT(!guf_str_is_readonly(str)); GUF_ASSERT(guf_str_is_valid(str)); @@ -536,13 +615,65 @@ GUF_STR_KWRDS guf_str guf_str_try_new(guf_str_view str_view, guf_allocator *allo return guf_str_new_uninitialised(); } else { GUF_ASSERT(!guf_str_is_uninit(&str)); + guf_err_set_if_not_null(err, GUF_ERR_NONE); return str; } } +GUF_STR_KWRDS guf_str guf_str_new(guf_str_view str_view, guf_allocator *alloc) +{ + return guf_str_try_new(str_view, alloc, NULL); +} + +GUF_STR_KWRDS guf_str *guf_str_try_init_from_cstr(guf_str *str, const char* c_str, guf_allocator *alloc, guf_err *err) +{ + GUF_ASSERT(str); + if (!str) { + guf_err_set_or_panic(err, GUF_ERR_NULL_PTR, GUF_ERR_MSG("in guf_str_try_init_from_cstr: str is NULL")); + return NULL; + } else if (!c_str) { + guf_err_set_or_panic(err, GUF_ERR_NULL_PTR, GUF_ERR_MSG("in guf_str_try_init_from_cstr: c_str is NULL")); + return NULL; + } else if (!alloc || !alloc->alloc || !alloc->realloc || !alloc->free) { + guf_err_set_or_panic(err, GUF_ERR_NULL_PTR, GUF_ERR_MSG("in guf_str_try_init_from_cstr: alloc (or allocs function pointers) is/are NULL")); + return NULL; + } + + const size_t len = strlen(c_str); + if (len >= PTRDIFF_MAX) { + guf_err_set_or_panic(err, GUF_ERR_INT_OVERFLOW, GUF_ERR_MSG("in guf_str_try_init_from_cstr: stlen(c_str) >= PTRDIFF_MAX")); + return NULL; + } + + guf_str_try_init(str, (guf_str_view){.str = c_str, .len = (ptrdiff_t)len}, alloc, err); + if (err && *err != GUF_ERR_NONE) { + guf_err_set_or_panic(err, *err, GUF_ERR_MSG("in guf_str_try_init_from_cstr: guf_str_try_init failed")); + return NULL; + } else { + guf_err_set_if_not_null(err, GUF_ERR_NONE); + return str; + } +} + +GUF_STR_KWRDS guf_str *guf_str_init_from_cstr(guf_str *str, const char* c_str, guf_allocator *alloc) +{ + return guf_str_try_init_from_cstr(str, c_str, alloc, NULL); +} + +GUF_STR_KWRDS guf_str_view guf_str_to_view(const guf_str *str) +{ + GUF_ASSERT(guf_str_is_valid(str)); + guf_str_view sv = { + .str = guf_str_const_cstr(str), + .len = guf_str_len(str) + }; + GUF_ASSERT(guf_str_view_is_valid(sv)); + return sv; +} + GUF_STR_KWRDS char *guf_str_try_at(guf_str *str, ptrdiff_t idx, guf_err *err) { - GUF_ASSERT_RELEASE(guf_str_is_valid(str)); + GUF_ASSERT(guf_str_is_valid(str)); const ptrdiff_t len = guf_str_len(str); @@ -555,6 +686,7 @@ GUF_STR_KWRDS char *guf_str_try_at(guf_str *str, ptrdiff_t idx, guf_err *err) } else { char *c_str = guf_str_try_get_cstr(str, err); if (err && *err != GUF_ERR_NONE) { + guf_err_set_or_panic(err, *err, GUF_ERR_MSG("in guf_str_try_at: guf_str_try_get_cstr failed (guf_str is readonly)")); return NULL; } GUF_ASSERT(c_str); @@ -576,6 +708,7 @@ GUF_STR_KWRDS char *guf_str_try_back(guf_str *str, guf_err *err) guf_err_set_or_panic(err, GUF_ERR_IDX_RANGE, GUF_ERR_MSG("in guf_str_try_back: len == 0")); return NULL; } else { + guf_err_set_if_not_null(err, GUF_ERR_NONE); return guf_str_try_at(str, len - 1, err); } } @@ -593,6 +726,7 @@ GUF_STR_KWRDS char *guf_str_try_front(guf_str *str, guf_err *err) guf_err_set_or_panic(err, GUF_ERR_IDX_RANGE, GUF_ERR_MSG("in guf_str_try_front: len == 0")); return NULL; } else { + guf_err_set_if_not_null(err, GUF_ERR_NONE); return guf_str_try_at(str, 0, err); } } @@ -604,8 +738,8 @@ GUF_STR_KWRDS char *guf_str_front(guf_str *str) GUF_STR_KWRDS char guf_str_try_at_cpy(const guf_str *str, ptrdiff_t idx, guf_err *err) { + GUF_ASSERT(guf_str_is_valid(str)); const ptrdiff_t len = guf_str_len(str); - if (idx < 0) { guf_err_set_or_panic(err, GUF_ERR_IDX_RANGE, GUF_ERR_MSG("in guf_str_try_at_cpy: idx < 0")); return '\0'; @@ -633,6 +767,7 @@ GUF_STR_KWRDS char guf_str_try_back_cpy(const guf_str *str, guf_err *err) guf_err_set_or_panic(err, GUF_ERR_IDX_RANGE, GUF_ERR_MSG("in guf_str_try_back_cpy: len == 0")); return '\0'; } else { + guf_err_set_if_not_null(err, GUF_ERR_NONE); return guf_str_try_at_cpy(str, len - 1, err); } } @@ -650,6 +785,7 @@ GUF_STR_KWRDS char guf_str_try_front_cpy(const guf_str *str, guf_err *err) guf_err_set_or_panic(err, GUF_ERR_IDX_RANGE, GUF_ERR_MSG("in guf_str_try_front_cpy: len == 0")); return '\0'; } else { + guf_err_set_if_not_null(err, GUF_ERR_NONE); return guf_str_try_at_cpy(str, 0, err); } } @@ -763,17 +899,55 @@ GUF_STR_KWRDS int guf_str_cmp(const guf_str *a, const guf_str *b) return memcmp(a_cstr, b_cstr, shorter_len); } +GUF_STR_KWRDS guf_str *guf_str_try_set(guf_str *str, guf_str_view sv, guf_err *err) +{ + GUF_ASSERT(guf_str_is_valid(str)); + if (guf_str_is_readonly(str)) { + guf_err_set_or_panic(err, GUF_ERR_INVALID_ARG, GUF_ERR_MSG("in guf_str_try_set: guf_str is readonly")); + return NULL; + } else if (!guf_str_view_is_valid(sv)) { + guf_err_set_or_panic(err, GUF_ERR_INVALID_ARG, GUF_ERR_MSG("in guf_str_try_set: str_view is invalid")); + return NULL; + } + + guf_str_try_reserve(str, sv.len, err); + if (err && *err != GUF_ERR_NONE) { + guf_err_set_or_panic(err, *err, GUF_ERR_MSG("in guf_str_try_set: guf_str_try_reserve failed")); + return NULL; + } + + char *c_str_dst = guf_str_cstr(str); + GUF_ASSERT(c_str_dst); + if (sv.len > 0) { + GUF_ASSERT(sv.str); + memcpy(c_str_dst, sv.str, sv.len); + } + c_str_dst[sv.len] = '\0'; + guf_str_set_len_internal_(str, sv.len); + + GUF_ASSERT(guf_str_is_valid(str)); + guf_err_set_if_not_null(err, GUF_ERR_NONE); + return str; +} + +GUF_STR_KWRDS guf_str *guf_str_set(guf_str *str, guf_str_view sv) +{ + return guf_str_try_set(str, sv, NULL); +} + + + GUF_STR_KWRDS guf_str *guf_str_try_append_char(guf_str *str, char c, ptrdiff_t times, guf_err *err) { GUF_ASSERT(guf_str_is_valid(str)); - if (guf_str_is_readonly(str)) { - guf_err_set_or_panic(err, GUF_ERR_NULL_PTR, "in guf_str_try_append_char: str is readonly"); + if (guf_str_is_readonly(str)) { + guf_err_set_or_panic(err, GUF_ERR_NULL_PTR, GUF_ERR_MSG("in guf_str_try_append_char: str is readonly")); return NULL; } if (times < 0) { - guf_err_set_or_panic(err, GUF_ERR_INVALID_ARG, "in guf_str_try_append_char: repeats < 0"); + guf_err_set_or_panic(err, GUF_ERR_INVALID_ARG, GUF_ERR_MSG("in guf_str_try_append_char: repeats < 0")); return NULL; } else if (times == 0) { guf_err_set_if_not_null(err, GUF_ERR_NONE); @@ -787,12 +961,12 @@ GUF_STR_KWRDS guf_str *guf_str_try_append_char(guf_str *str, char c, ptrdiff_t t const size_t new_len = old_len + (size_t)times; if (new_len <= old_len || new_len >= (size_t)PTRDIFF_MAX) { // Handle overflow. - guf_err_set_or_panic(err, GUF_ERR_INT_OVERFLOW, "in guf_str_try_append_char: new length would overflow ptrdiff_t"); + guf_err_set_or_panic(err, GUF_ERR_INT_OVERFLOW, GUF_ERR_MSG("in guf_str_try_append_char: new length would overflow ptrdiff_t")); return NULL; } else if (new_len > old_cap) { // Need to grow capacity. guf_str_try_reserve(str, new_len, err); if (err && *err != GUF_ERR_NONE) { - guf_err_set_or_panic(err, *err, "in guf_str_try_append_char: failed to reserve capacity"); + guf_err_set_or_panic(err, *err, GUF_ERR_MSG("in guf_str_try_append_char: failed to reserve capacity")); return NULL; } } @@ -833,10 +1007,10 @@ GUF_STR_KWRDS guf_str *guf_str_try_append(guf_str *str, guf_str_view sv, guf_err GUF_ASSERT(guf_str_is_valid(str)); if (!guf_str_view_is_valid(sv)) { - guf_err_set_or_panic(err, GUF_ERR_INVALID_ARG, "in guf_str_try_append_view: str_view is invalid"); + guf_err_set_or_panic(err, GUF_ERR_INVALID_ARG, GUF_ERR_MSG("in guf_str_try_append_view: str_view is invalid")); return NULL; } else if (guf_str_is_readonly(str)) { - guf_err_set_or_panic(err, GUF_ERR_NULL_PTR, "in in guf_str_try_append_view: str is readonly"); + guf_err_set_or_panic(err, GUF_ERR_NULL_PTR, GUF_ERR_MSG("in in guf_str_try_append_view: str is readonly")); return NULL; } @@ -850,12 +1024,12 @@ GUF_STR_KWRDS guf_str *guf_str_try_append(guf_str *str, guf_str_view sv, guf_err const size_t old_len = guf_str_len_internal_(str); const size_t new_len = old_len + (size_t)sv.len; if (new_len <= old_len || new_len >= (size_t)PTRDIFF_MAX) { // Handle overflow. - guf_err_set_or_panic(err, GUF_ERR_INT_OVERFLOW, "in guf_str_try_append_view: new length would overflow ptrdiff_t"); + guf_err_set_or_panic(err, GUF_ERR_INT_OVERFLOW, GUF_ERR_MSG("in guf_str_try_append_view: new length would overflow ptrdiff_t")); return NULL; } else if (new_len > old_cap) { // Growth necessary. guf_str_try_reserve(str, new_len, err); if (err && *err != GUF_ERR_NONE) { - guf_err_set_or_panic(err, *err, "in guf_str_try_append_view: failed to reserve capacity"); + guf_err_set_or_panic(err, *err, GUF_ERR_MSG("in guf_str_try_append_view: failed to reserve capacity")); return NULL; } } @@ -882,55 +1056,6 @@ GUF_STR_KWRDS guf_str *guf_str_append(guf_str *str, guf_str_view sv) return guf_str_try_append(str, sv, NULL); } -GUF_STR_KWRDS guf_str *guf_str_try_append_cstr(guf_str *str, const char *c_str, guf_err *err) -{ - GUF_ASSERT(guf_str_is_valid(str)); - - if (!c_str) { - guf_err_set_or_panic(err, GUF_ERR_NULL_PTR, "in guf_str_try_append_cstr: c_str is NULL"); - return NULL; - } else if (guf_str_is_readonly(str)) { - guf_err_set_or_panic(err, GUF_ERR_NULL_PTR, "in guf_str_try_append_cstr: str is readonly"); - return NULL; - } - - char *dst_cstr = guf_str_get_cstr_internal_(str); - size_t i = 0; - do { - size_t cap = guf_str_cap_internal_(str); - size_t len = guf_str_len_internal_(str); - GUF_ASSERT(len <= cap); - - if (len == cap) { // Grow if necessary. - if (cap == PTRDIFF_MAX) { - guf_err_set_or_panic(err, GUF_ERR_INT_OVERFLOW, "in guf_str_try_append_cstr: cannot grow (capacity is PTRDIFF_MAX)"); - return NULL; - } - guf_str_try_reserve(str, cap + 1, err); - if (err && *err != GUF_ERR_NONE) { - guf_err_set_or_panic(err, GUF_ERR_INT_OVERFLOW, "in guf_str_try_append_cstr: failed to reserve"); - return NULL; - } - cap = guf_str_cap_internal_(str); - len = guf_str_len_internal_(str); - } - - dst_cstr[len] = c_str[i]; - guf_str_set_len_internal_(str, len + 1); - } while (c_str[i++] != '\0'); - - GUF_ASSERT(guf_str_is_valid(str)); - GUF_ASSERT(dst_cstr[guf_str_len_internal_(str)] == '\0'); - - guf_err_set_if_not_null(err, GUF_ERR_NONE); - return str; -} - -GUF_STR_KWRDS guf_str *guf_str_append_cstr(guf_str *str, const char *c_str) -{ - return guf_str_try_append_cstr(str, c_str, NULL); -} - GUF_STR_KWRDS guf_str *guf_str_try_substr(guf_str *str, ptrdiff_t pos, ptrdiff_t count, guf_err *err) { GUF_ASSERT(guf_str_is_valid(str)); @@ -975,6 +1100,33 @@ GUF_STR_KWRDS guf_str *guf_str_substr(guf_str *str, ptrdiff_t pos, ptrdiff_t cou return guf_str_try_substr(str, pos, count, NULL); } +GUF_STR_KWRDS char guf_str_try_pop_back(guf_str *str, guf_err *err) +{ + GUF_ASSERT(guf_str_is_valid(str)); + + const ptrdiff_t len = guf_str_len(str); + if (len <= 0) { + guf_err_set_or_panic(err, GUF_ERR_IDX_RANGE, GUF_ERR_MSG("in guf_str_try_pop_back: len <= 0")); + return '\0'; + } + GUF_ASSERT(len - 1 >= 0); + const char last = guf_str_at_cpy(str, len - 1); + guf_str_try_substr(str, 0, len - 1, err); + if (err && *err != GUF_ERR_NONE) { + guf_err_set_or_panic(err, *err, GUF_ERR_MSG("in guf_str_try_pop_back: guf_str_try_substr failed")); + return '\0'; + } else { + GUF_ASSERT(guf_str_is_valid(str)); + GUF_ASSERT(guf_str_len(str) == len - 1); + guf_err_set_if_not_null(err, GUF_ERR_NONE); + return last; + } +} + +GUF_STR_KWRDS char guf_str_pop_back(guf_str *str) +{ + return guf_str_try_pop_back(str, NULL); +} // guf_str_view: diff --git a/src/test/test.cpp b/src/test/test.cpp index 2646288..5a4b6a5 100644 --- a/src/test/test.cpp +++ b/src/test/test.cpp @@ -11,6 +11,7 @@ extern "C" { #include "test_dbuf.hpp" #include "test_dict.hpp" #include "test_utf8.hpp" +#include "test_str.hpp" std::unordered_set> g_tests {}; @@ -31,6 +32,10 @@ void init_tests() test = std::make_unique("UTF8Test"); GUF_ASSERT_RELEASE(test.get()); g_tests.insert(std::move(test)); + + test = std::make_unique("StrTest"); + GUF_ASSERT_RELEASE(test.get()) + g_tests.insert(std::move(test)); } int main() diff --git a/src/test/test_str.hpp b/src/test/test_str.hpp new file mode 100644 index 0000000..4e4562a --- /dev/null +++ b/src/test/test_str.hpp @@ -0,0 +1,212 @@ +#include +#include + +#include "test.hpp" + +extern "C" { + #include "guf_alloc_libc.h" + #include "guf_str.h" +} + +struct StrTest : public Test +{ +public: + StrTest(const std::string& name) : Test(name) {}; + +private: + void test_init_free(std::string str) + { + guf_str s0; + guf_str_init(&s0, GUF_CSTR_TO_VIEW_CPP(str.c_str()), &guf_allocator_libc); + guf_str s1 = guf_str_new(GUF_CSTR_TO_VIEW_CPP(str.c_str()), &guf_allocator_libc); + guf_str s2; + guf_str_init_from_cstr(&s2, str.c_str(), &guf_allocator_libc); + + TEST_CHECK(guf_str_equal(&s0, &s1)); + TEST_CHECK(guf_str_equal(&s0, &s2)); + TEST_CHECK(guf_str_equal(&s1, &s2)); + + TEST_CHECK((ptrdiff_t)str.size() == guf_str_len(&s0)); + TEST_CHECK(str == guf_str_const_cstr(&s0)); + TEST_CHECK(str == guf_str_cstr(&s0)); + + TEST_CHECK((ptrdiff_t)str.size() == guf_str_len(&s1)); + TEST_CHECK(str == guf_str_const_cstr(&s1)); + TEST_CHECK(str == guf_str_cstr(&s1)); + + TEST_CHECK((ptrdiff_t)str.size() == guf_str_len(&s2)); + TEST_CHECK(str == guf_str_const_cstr(&s2)); + TEST_CHECK(str == guf_str_cstr(&s2)); + + guf_str_free(&s0, NULL); + guf_str_free(&s1, NULL); + guf_str_free(&s2, NULL); + TEST_CHECK(guf_str_is_uninit(&s0)); + TEST_CHECK(guf_str_is_uninit(&s1)); + TEST_CHECK(guf_str_is_uninit(&s2)); + } + + void test_init_empty() + { + std::string str = ""; + guf_str s = GUF_STR_UNINITIALISED_CPP; + guf_str_init_empty(&s, &guf_allocator_libc); + TEST_CHECK(guf_str_len(&s) == 0); + TEST_CHECK(str == guf_str_const_cstr(&s)); + + guf_str_append_char(&s, 'a', 1024); + str.append(1024, 'a'); + TEST_CHECK(guf_str_len(&s) == (ptrdiff_t)str.size()); + TEST_CHECK(guf_str_const_cstr(&s) == str); + + guf_str_append_char(&s, 'b', 24); + str.append(24, 'b'); + TEST_CHECK(guf_str_len(&s) == (ptrdiff_t)str.size()); + TEST_CHECK(guf_str_const_cstr(&s) == str); + + guf_str_append_char(&s, 'c', 255); + str.append(255, 'c'); + TEST_CHECK(guf_str_len(&s) == (ptrdiff_t)str.size()); + TEST_CHECK(guf_str_const_cstr(&s) == str); + + *guf_str_at(&s, 0) = '<'; + str.at(0) = '<'; + TEST_CHECK(guf_str_len(&s) == (ptrdiff_t)str.size()); + TEST_CHECK(guf_str_const_cstr(&s) == str); + + *guf_str_at(&s, guf_str_len(&s) - 1) = '>'; + str.at(str.size() - 1) = '>'; + TEST_CHECK(guf_str_len(&s) == (ptrdiff_t)str.size()); + TEST_CHECK(guf_str_const_cstr(&s) == str); + + guf_err err = GUF_ERR_NONE; + TEST_CHECK(NULL == guf_str_try_at(&s, guf_str_len(&s), &err)); + TEST_CHECK(err != GUF_ERR_NONE && err == GUF_ERR_IDX_RANGE); + err = GUF_ERR_NONE; + TEST_CHECK(NULL == guf_str_try_at(&s, -1, &err)); + TEST_CHECK(err != GUF_ERR_NONE && err == GUF_ERR_IDX_RANGE); + + guf_str_free(&s, NULL); + TEST_CHECK(guf_str_is_uninit(&s)); + } + + void test_append_char(std::string str, bool include_null = false) + { + guf_str s0 = guf_str_new(guf_str_view{.str = str.c_str(), .len = (ptrdiff_t)str.size()}, &guf_allocator_libc); + + TEST_CHECK((ptrdiff_t)str.size() == guf_str_len(&s0)); + TEST_CHECK((str == std::string_view{guf_str_const_cstr(&s0), (size_t)guf_str_len(&s0)})); + + for (int i = include_null ? 0 : 1; i < 128; ++i) { + char ch = (char)i; + guf_str_append_one_char(&s0, ch); + str.append(1, ch); + TEST_CHECK(guf_str_len(&s0) == (ptrdiff_t)str.size()); + TEST_CHECK((str == std::string_view{guf_str_const_cstr(&s0), (size_t)guf_str_len(&s0)})); + } + + for (int i = include_null ? 0 : 1; i < 128; ++i) { + char ch = (char)i; + guf_str_append_char(&s0, ch, i); + str.append(i, ch); + TEST_CHECK(guf_str_len(&s0) == (ptrdiff_t)str.size()); + TEST_CHECK((str == std::string_view{guf_str_const_cstr(&s0), (size_t)guf_str_len(&s0)})); + guf_str_append_char(&s0, ch, i * 16); + str.append(i * 16, ch); + TEST_CHECK(guf_str_len(&s0) == (ptrdiff_t)str.size()); + TEST_CHECK((str == std::string_view{guf_str_const_cstr(&s0), (size_t)guf_str_len(&s0)})); + } + + guf_str_free(&s0, NULL); + TEST_CHECK(guf_str_is_uninit(&s0)); + } + + void append_str(const std::string& a, const std::string& b) + { + std::string str0 = a; + guf_str s0 = guf_str_new(guf_str_view{.str = str0.c_str(), .len = (ptrdiff_t)str0.size()}, &guf_allocator_libc); + TEST_CHECK(guf_str_len(&s0) == (ptrdiff_t)str0.size()); + TEST_CHECK((str0 == std::string_view{guf_str_const_cstr(&s0), (size_t)guf_str_len(&s0)})); + TEST_CHECK((str0 == std::string_view{guf_str_cstr(&s0), (size_t)guf_str_len(&s0)})); + + for (int i = 0; i <= 64; ++i) { + str0.append(b); + guf_str_append(&s0, guf_str_view{.str = b.c_str(), .len = (ptrdiff_t)b.size()}); + TEST_CHECK(guf_str_len(&s0) == (ptrdiff_t)str0.size()); + TEST_CHECK((str0 == std::string_view{guf_str_const_cstr(&s0), (size_t)guf_str_len(&s0)})); + TEST_CHECK((str0 == std::string_view{guf_str_cstr(&s0), (size_t)guf_str_len(&s0)})); + } + + guf_str_free(&s0, NULL); + TEST_CHECK(guf_str_is_uninit(&s0)); + } + +public: + bool run() + { + if (done) { + return passed; + } + + const std::vector words = { + "", + "\0", + "Hello", + "Othell\0o", + "f\0\0", + "\0", + "0", + "a", + "ab", + "🌈 waow a rainboge!", + "orange cat(1) :3", + "xes yag", + "Hello, world! This is a pretty darn long string I'd say...", + "I want to eat crayons. I crave crayons because they are tasty, and everybody telling me crayons are not edible must be either lying or dumb. I like trains. 42 is a number. 3.14159265... is not a rational number, and it is called pi. I ate some pie (it was a crayon pie).", + std::string(32, 'a'), + std::string(64, 'b'), + std::string(1024, 'a'), + std::string(2048, 'a'), + std::string(4096, 'a'), + std::string(5001, 'a'), + std::string(7121, 'a'), + std::string(2000, 'a'), + std::string(GUF_STR_SSO_BUF_CAP, 'a'), + std::string(GUF_STR_SSO_BUF_CAP - 1, 'a'), + std::string(GUF_STR_SSO_BUF_CAP + 1, 'a'), + std::string(GUF_STR_SSO_BUF_CAP - 2, 'a'), + std::string(GUF_STR_SSO_BUF_CAP + 2, 'a'), + std::string(GUF_STR_SSO_BUF_CAP - 3, 'a'), + std::string(GUF_STR_SSO_BUF_CAP + 3, 'a'), + std::string(GUF_STR_SSO_BUF_CAP * 2, 'a'), + std::string(GUF_STR_SSO_BUF_CAP * 3, 'a'), + std::string(GUF_STR_SSO_BUF_CAP * 4, 'a'), + std::string(GUF_STR_SSO_BUF_CAP * 5, 'a'), + std::string(GUF_STR_SSO_BUF_CAP * 6, 'a'), + std::string(GUF_STR_SSO_BUF_CAP * 7, 'a'), + }; + + test_init_empty(); + + for (const auto& word : words) { + test_init_free(word); + test_append_char(word); + test_append_char(word, true); + } + + for (size_t i = 0; i < words.size(); ++i) { + const auto& w1 = words.at(i); + append_str(w1, w1); + append_str(w1, w1); + for (size_t j = i + 1; j < words.size(); ++j) { + const auto& w2 = words.at(j); + append_str(w1, w2); + append_str(w2, w1); + } + } + + done = true; + passed = (num_failed_checks == 0); + return passed; + } +}; \ No newline at end of file diff --git a/todo.txt b/todo.txt index 2af0728..79eca7f 100644 --- a/todo.txt +++ b/todo.txt @@ -9,7 +9,6 @@ - guf_stack, guf_queue, guf_dqueue, guf_prio_queue (using a heap), guf_ringbuf - - track allocs for test (implement alloc tracker): - each thread needs its own alloc and alloc_ctx; don't track granular, give each allocator it's unique id maybe?