Implement guf_str
This commit is contained in:
parent
9ddea4bb07
commit
ad884ee1e9
@ -4,8 +4,8 @@
|
||||
#include "guf_alloc.h"
|
||||
|
||||
typedef struct guf_libc_alloc_ctx {
|
||||
bool zero_init;
|
||||
int alloc_type_id, thread_id;
|
||||
bool zero_init;
|
||||
} guf_libc_alloc_ctx;
|
||||
|
||||
static inline void *guf_libc_alloc(ptrdiff_t size, void *ctx)
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
#include "guf_alloc.h"
|
||||
|
||||
typedef struct guf_alloc_info {
|
||||
const char *name;
|
||||
ptrdiff_t allocated_bytes, freed_bytes;
|
||||
size_t alloc_count, realloc_count, free_count;
|
||||
} guf_alloc_info;
|
||||
|
||||
@ -480,6 +480,7 @@ static inline bool GUF_CAT(GUF_DBUF_NAME, _copy_opt_available)(guf_cpy_opt cpy_o
|
||||
GUF_DBUF_KWRDS GUF_T *GUF_CAT(GUF_DBUF_NAME, _try_insert)(GUF_DBUF_NAME *dbuf, GUF_T *elem, ptrdiff_t idx, guf_cpy_opt cpy_opt, guf_err *err)
|
||||
{
|
||||
GUF_ASSERT(GUF_CAT(GUF_DBUF_NAME,_valid)(dbuf));
|
||||
GUF_ASSERT(elem);
|
||||
|
||||
if (idx < 0 || idx > dbuf->size) {
|
||||
guf_err_set_or_panic(err, GUF_ERR_IDX_RANGE, GUF_ERR_MSG("in function dbuf_try_insert"));
|
||||
|
||||
124
src/guf_id_pool.h
Normal file
124
src/guf_id_pool.h
Normal file
@ -0,0 +1,124 @@
|
||||
#if defined(GUF_ID_POOL_IMPL_STATIC)
|
||||
#define GUF_ID_POOL_KWRDS static
|
||||
#else
|
||||
#define GUF_ID_POOL_KWRDS
|
||||
#endif
|
||||
|
||||
#ifndef GUF_ID_POOL_H
|
||||
#define GUF_ID_POOL_H
|
||||
#include "guf_common.h"
|
||||
#include "guf_assert.h"
|
||||
#endif
|
||||
|
||||
// cf. https://github.com/erincatto/box2d/blob/main/src/id_pool.c (last-retrieved 2025-03-11)
|
||||
|
||||
// // test beg
|
||||
#define GUF_ID_POOL_IMPL_STATIC
|
||||
#define GUF_ID_POOL_T uint32_t
|
||||
#define GUF_ID_POOL_NAME guf_idpool_u32
|
||||
// // test end
|
||||
|
||||
#ifndef GUF_ID_POOL_T
|
||||
#error "Must pass GUF_ID_POOL_T"
|
||||
#endif
|
||||
|
||||
#ifndef GUF_ID_POOL_NAME
|
||||
#error "Must pass GUF_ID_POOL_NAME"
|
||||
#endif
|
||||
|
||||
#define GUF_ID_POOL_DBUF GUF_CAT(GUF_ID_POOL_NAME, _id_dbuf)
|
||||
|
||||
#define GUF_DBUF_NAME GUF_ID_POOL_DBUF
|
||||
#define GUF_T GUF_ID_POOL_T
|
||||
#define GUF_T_IS_INTEGRAL_TYPE
|
||||
#define GUF_DBUF_ONLY_TYPES
|
||||
#include "guf_dbuf.h"
|
||||
|
||||
#ifndef GUF_ID_POOL_IMPL
|
||||
typedef struct GUF_ID_POOL_NAME {
|
||||
GUF_ID_POOL_DBUF free_id_dbuf;
|
||||
GUF_ID_POOL_T next_id;
|
||||
} GUF_ID_POOL_NAME;
|
||||
#endif
|
||||
|
||||
#if defined(GUF_ID_POOL_IMPL) || defined(GUF_ID_POOL_IMPL_STATIC)
|
||||
|
||||
#define GUF_DBUF_NAME GUF_ID_POOL_DBUF
|
||||
#define GUF_T GUF_ID_POOL_T
|
||||
#define GUF_T_IS_INTEGRAL_TYPE
|
||||
#define GUF_DBUF_WITHOUT_TYPES
|
||||
#define GUF_DBUF_IMPL_STATIC
|
||||
#include "guf_dbuf.h"
|
||||
|
||||
GUF_ID_POOL_NAME *GUF_CAT(GUF_ID_POOL_NAME, _try_init)(GUF_ID_POOL_NAME *pool, ptrdiff_t initial_cap, guf_allocator *allocator, guf_err *err)
|
||||
{
|
||||
if (!pool) {
|
||||
guf_err_set_or_panic(err, GUF_ERR_NULL_PTR, "in guf_idpool_try_init: pool is NULL");
|
||||
return NULL;
|
||||
} else if (!allocator || !allocator->alloc || !allocator->realloc || !allocator->free) {
|
||||
guf_err_set_or_panic(err, GUF_ERR_NULL_PTR, "in guf_idpool_try_init: allocator is NULL");
|
||||
return NULL;
|
||||
} else if (initial_cap < 0) {
|
||||
guf_err_set_or_panic(err, GUF_ERR_INVALID_ARG, "in guf_idpool_try_init: initial_cap is < 0");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
GUF_CAT(GUF_ID_POOL_DBUF, _try_init)(&pool->free_id_dbuf, initial_cap, allocator, err);
|
||||
if (err && *err != GUF_ERR_NONE) {
|
||||
guf_err_set_or_panic(err, *err, "in guf_idpool_try_init: failed to allocate dbuf");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pool->next_id = 0;
|
||||
|
||||
guf_err_set_if_not_null(err, GUF_ERR_NONE);
|
||||
return pool;
|
||||
}
|
||||
|
||||
GUF_ID_POOL_T GUF_CAT(GUF_ID_POOL_NAME, _alloc_id)(GUF_ID_POOL_NAME *pool)
|
||||
{
|
||||
GUF_ASSERT(pool);
|
||||
GUF_ASSERT(pool->free_id_dbuf.size >= 0);
|
||||
|
||||
if (pool->free_id_dbuf.size > 0) {
|
||||
GUF_ID_POOL_T id = *GUF_CAT(GUF_ID_POOL_DBUF, _back)(&pool->free_id_dbuf);
|
||||
GUF_CAT(GUF_ID_POOL_DBUF, _pop)(&pool->free_id_dbuf);
|
||||
return id;
|
||||
} else {
|
||||
GUF_ID_POOL_T id = pool->next_id;
|
||||
pool->next_id += 1; // TODO: handle overflow (need a guf_id_pool_t_max)
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
void GUF_CAT(GUF_ID_POOL_NAME, _try_free_id)(GUF_ID_POOL_NAME *pool, GUF_ID_POOL_T id, guf_err *err)
|
||||
{
|
||||
GUF_ASSERT(pool);
|
||||
GUF_ASSERT(pool->next_id > 0);
|
||||
|
||||
if (id < 0 || id >= pool->next_id) {
|
||||
guf_err_set_or_panic(err, GUF_ERR_IDX_RANGE, "in guf_idpool_try_free_id: id < 0 or id >= pool->next_id");
|
||||
return;
|
||||
}
|
||||
|
||||
if (id == pool->next_id - 1) {
|
||||
pool->next_id -= 1;
|
||||
return;
|
||||
}
|
||||
|
||||
GUF_CAT(GUF_ID_POOL_DBUF, _try_push_val)(&pool->free_id_dbuf, id, err);
|
||||
|
||||
if (err && *err != GUF_ERR_NONE) {
|
||||
guf_err_set_or_panic(err, *err, "in guf_idpool_try_free_id: failed to push id");
|
||||
return;
|
||||
} else {
|
||||
guf_err_set_if_not_null(err, GUF_ERR_NONE);
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* end impl */
|
||||
|
||||
#undef GUF_ID_POOL_KWRDS
|
||||
#undef GUF_ID_POOL_IMPL
|
||||
#undef GUF_ID_POOL_IMPL_STATIC
|
||||
|
||||
@ -51,6 +51,17 @@ GUF_DEFINE_MIN_MAX_CLAMP(double, f64)
|
||||
|
||||
#undef GUF_DEFINE_MIN_MAX_CLAMP
|
||||
|
||||
static inline bool guf_add_is_overflow_size_t(size_t a, size_t b)
|
||||
{
|
||||
return (a + b) > a;
|
||||
}
|
||||
|
||||
static inline bool guf_mul_is_overflow_size_t(size_t a, size_t b)
|
||||
{
|
||||
const size_t c = a * b;
|
||||
return a != 0 && ((c / a) != b);
|
||||
}
|
||||
|
||||
static inline int guf_abs_int(int x) {if (x >= 0) {return x;} GUF_ASSERT_RELEASE(x > INT_MIN); return -x;} // I would not drink that...
|
||||
static inline int8_t guf_abs_i8 (int8_t x) {if (x >= 0) {return x;} GUF_ASSERT_RELEASE(x > INT8_MIN); return -x;}
|
||||
static inline int16_t guf_abs_i16(int16_t x) {if (x >= 0) {return x;} GUF_ASSERT_RELEASE(x > INT16_MIN); return -x;}
|
||||
|
||||
689
src/guf_str.h
689
src/guf_str.h
@ -15,7 +15,7 @@
|
||||
// cf. libc++ short-string optimisation: https://joellaity.com/2020/01/31/string.html (last-retrieved 2025-03-10)
|
||||
|
||||
typedef struct guf_str_internal_long_ {
|
||||
size_t capacity; // If long string: capacity's least significant bit always set to 1 (or its most significant bit for big-endian platforms)
|
||||
size_t capacity; // If long string: capacity's least significant bit always set to 1 (or its most significant bit for big-endian platforms); the actual capacity must be even
|
||||
size_t size;
|
||||
char *c_str;
|
||||
} guf_str_internal_long_;
|
||||
@ -45,12 +45,15 @@ typedef struct guf_str {
|
||||
guf_allocator *allocator; // Wasteful (8 bytes on 64-bit platforms...), but keeping this pointer also allows us to have "read-only strings" (a string is read-only if allocator == NULL)
|
||||
} guf_str; // Total: 32 bytes on 64-bit platforms, 16 bytes on 32-bit platforms.
|
||||
|
||||
|
||||
#define GUF_CSTR_TO_VIEW(CSTR) ((guf_str_view){.str = (CSTR), .len = (ptrdiff_t)strlen((CSTR))})
|
||||
#define GUF_CSTR_LIT_TO_VIEW(CSTR) ((guf_str_view){.str = (CSTR), .len = (ptrdiff_t)sizeof((CSTR)) - 1})
|
||||
#define GUF_STR_TO_VIEW(GUF_STR_PTR) ((guf_str_view){.str = guf_str_const_cstr((GUF_STR_PTR)), .len = (ptrdiff_t)guf_str_len((GUF_STR_PTR))})
|
||||
#define GUF_CSTR_TO_READONLY_STR(CSTR) ((guf_str){.allocator = NULL, .data.lng.c_str = (CSTR), .data.lng.size = strlen(CSTR) + 1, .data.lng.capacity = 0})
|
||||
#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}
|
||||
#endif
|
||||
|
||||
// guf_str_view:
|
||||
@ -72,52 +75,79 @@ GUF_STR_KWRDS int guf_str_view_cmp(const void *str_view_a, const void *str_view_
|
||||
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_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);
|
||||
GUF_STR_KWRDS guf_str *guf_str_init_empty(guf_str *str, guf_allocator *alloc);
|
||||
|
||||
GUF_STR_KWRDS guf_str guf_str_try_new(guf_str_view str_view, guf_allocator *alloc, guf_err *err);
|
||||
GUF_STR_KWRDS guf_str guf_str_new(guf_str_view str_view, guf_allocator *alloc);
|
||||
GUF_STR_KWRDS guf_str guf_str_try_new_from_cstr(const char *c_str, guf_allocator *alloc, guf_err *err);
|
||||
GUF_STR_KWRDS guf_str guf_str_new_from_cstr(const char *c_str, guf_allocator *alloc);
|
||||
GUF_STR_KWRDS guf_str guf_str_new_empty(guf_allocator *alloc);
|
||||
|
||||
GUF_STR_KWRDS void guf_str_free(guf_str *str, void *ctx);
|
||||
|
||||
// TODO:
|
||||
GUF_STR_KWRDS guf_str guf_str_try_new_substr(guf_str_view str_view, ptrdiff_t pos, ptrdiff_t len, guf_allocator *alloc, guf_err *err);
|
||||
GUF_STR_KWRDS guf_str guf_str_new_substr(guf_str_view str_view, ptrdiff_t pos, ptrdiff_t len, guf_allocator *alloc);
|
||||
|
||||
// TODO:
|
||||
GUF_STR_KWRDS bool guf_str_equal(const guf_str *a, const guf_str *b);
|
||||
GUF_STR_KWRDS bool guf_str_equals_cstr(const guf_str *a, const char *c_str);
|
||||
GUF_STR_KWRDS bool guf_str_equals_strview(const guf_str *a, guf_str_view b);
|
||||
|
||||
GUF_STR_KWRDS void guf_str_free(guf_str *str, void *ctx);
|
||||
// DONE:
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
|
||||
GUF_STR_KWRDS guf_str *guf_str_append(guf_str *str, guf_str_view to_append);
|
||||
GUF_STR_KWRDS guf_str *guf_str_append_cstr(guf_str *str, const char *cstr_to_append); // Not necessary
|
||||
GUF_STR_KWRDS guf_str *guf_str_substr(guf_str* str, size_t pos, size_t count);
|
||||
|
||||
// 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);
|
||||
|
||||
// TODO:
|
||||
GUF_STR_KWRDS char guf_str_pop_back(guf_str *str);
|
||||
GUF_STR_KWRDS char guf_str_pop_front(guf_str *str);
|
||||
|
||||
// TODO:
|
||||
GUF_STR_KWRDS char *guf_str_at(guf_str *str, size_t idx);
|
||||
GUF_STR_KWRDS char *guf_str_back(guf_str *str);
|
||||
GUF_STR_KWRDS char *guf_str_front(guf_str *str);
|
||||
GUF_STR_KWRDS const char *guf_str_const_cstr(const guf_str *str);
|
||||
|
||||
GUF_STR_KWRDS size_t guf_str_len(const guf_str *str); // The length (in chars) without the final zero-terminator.
|
||||
GUF_STR_KWRDS size_t guf_str_capacity(const guf_str *str); // The capacity (in chars) without the final zero-terminator.
|
||||
// DONE:
|
||||
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.
|
||||
|
||||
GUF_STR_KWRDS bool guf_str_is_short(const guf_str *str);
|
||||
GUF_STR_KWRDS bool guf_str_is_readonly(const guf_str *str);
|
||||
GUF_STR_KWRDS bool guf_str_is_valid(const guf_str *str);
|
||||
|
||||
GUF_STR_KWRDS guf_str guf_str_new_uninitialised(void);
|
||||
GUF_STR_KWRDS bool guf_str_is_uninit(const guf_str *str);
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
// #define GUF_STR_IMPL_STATIC /*debug*/
|
||||
// #define GUF_STR_IMPL_STATIC /* debug */
|
||||
|
||||
#if defined(GUF_STR_IMPL) || defined(GUF_STR_IMPL_STATIC)
|
||||
|
||||
@ -126,6 +156,7 @@ GUF_STR_KWRDS bool guf_str_is_readonly(const guf_str *str);
|
||||
#endif
|
||||
|
||||
#include "guf_common.h"
|
||||
#include "guf_math.h"
|
||||
#include <string.h>
|
||||
|
||||
#ifdef GUF_STR_IMPL
|
||||
@ -142,39 +173,130 @@ GUF_STR_KWRDS bool guf_str_is_readonly(const guf_str *str);
|
||||
#define GUF_STR_IS_LONG_MASK ((unsigned char)1) /* binary 0000.0001 */
|
||||
#define GUF_STR_GET_CAP_MASK (~(size_t)1) /* binary 1111.1111 (1111.1111)* 1111.1110 */
|
||||
|
||||
static inline void guf_str_set_lng_cap_(guf_str *str, size_t capacity)
|
||||
static inline void guf_str_set_lng_cap_(guf_str *str, size_t cap_with_null)
|
||||
{
|
||||
GUF_ASSERT(capacity % 2 == 0);
|
||||
GUF_ASSERT(capacity > GUF_STR_SSO_BUF_CAP);
|
||||
str->data.lng.capacity = capacity | ((size_t)1);
|
||||
GUF_ASSERT(cap_with_null % 2 == 0);
|
||||
GUF_ASSERT(cap_with_null <= PTRDIFF_MAX);
|
||||
GUF_ASSERT(cap_with_null > GUF_STR_SSO_BUF_CAP);
|
||||
str->data.lng.capacity = cap_with_null | ((size_t)1);
|
||||
}
|
||||
static inline void guf_str_set_shrt_size_(guf_str *str, unsigned char size)
|
||||
static inline void guf_str_set_shrt_size_(guf_str *str, unsigned char size_with_null)
|
||||
{
|
||||
GUF_ASSERT(size < GUF_STR_SSO_BUF_CAP && size < 0x80);
|
||||
str->data.shrt.size = (unsigned char)(size << 1u);
|
||||
GUF_ASSERT(size_with_null < GUF_STR_SSO_BUF_CAP && size_with_null < 0x80);
|
||||
str->data.shrt.size = (unsigned char)(size_with_null << 1);
|
||||
}
|
||||
#elif defined(GUF_PLATFORM_BIG_ENDIAN)
|
||||
#define GUF_STR_IS_LONG_MASK ((unsigned char)0x80) /* binary 1000 0000 */
|
||||
#define GUF_STR_IS_LONG_MASK ((unsigned char)0x80) /* binary 1000 0000 */
|
||||
#define GUF_STR_GET_CAP_MASK ((size_t)SIZE_T_MAX >> 1u) /* binary 0111.1111 (1111.1111)* 1111.1111 */
|
||||
|
||||
static inline void guf_str_set_lng_cap_(guf_str *str, size_t capacity)
|
||||
static inline void guf_str_set_lng_cap_(guf_str *str, size_t cap_with_null)
|
||||
{
|
||||
GUF_ASSERT(capacity % 2 == 0);
|
||||
GUF_ASSERT(capacity > GUF_STR_SSO_BUF_CAP);
|
||||
str->data.lng.capacity = ~GUF_STR_GET_CAP_MASK | (capacity >> 1);
|
||||
GUF_ASSERT(cap_with_null % 2 == 0);
|
||||
GUF_ASSERT(cap_with_null <= PTRDIFF_MAX);
|
||||
GUF_ASSERT(cap_with_null > GUF_STR_SSO_BUF_CAP);
|
||||
str->data.lng.capacity = ~GUF_STR_GET_CAP_MASK | (cap_with_null >> 1);
|
||||
}
|
||||
static inline void guf_str_set_shrt_size_(guf_str *str, unsigned char size)
|
||||
static inline void guf_str_set_shrt_size_(guf_str *str, unsigned char size_with_null)
|
||||
{
|
||||
GUF_ASSERT(size < GUF_STR_SSO_BUF_CAP && size < 0x80);
|
||||
str->data.shrt.size = size;
|
||||
GUF_ASSERT(size_with_null < GUF_STR_SSO_BUF_CAP && size_with_null < 0x80);
|
||||
str->data.shrt.size = size_with_null;
|
||||
}
|
||||
#else
|
||||
#error "guf_str: neither GUF_PLATFORM_LITTLE_ENDIAN nor GUF_PLATFORM_BIG_ENDIAN is defined"
|
||||
#endif
|
||||
|
||||
static bool guf_str_is_valid(const guf_str *str)
|
||||
GUF_STR_KWRDS bool guf_str_is_readonly(const guf_str *str)
|
||||
{
|
||||
GUF_ASSERT(str);
|
||||
return !str->allocator;
|
||||
}
|
||||
|
||||
static bool guf_str_is_short_internal_(const guf_str *str)
|
||||
{
|
||||
if (guf_str_is_readonly(str)) {
|
||||
return false;
|
||||
}
|
||||
const unsigned char first_byte = str->data.shrt.size; // union type-punning (only legal in C99 and above; undefined behaviour in C++ I think).
|
||||
return (first_byte & GUF_STR_IS_LONG_MASK) == 0;
|
||||
}
|
||||
|
||||
// Returns the capacity without the final null-terminator
|
||||
static size_t guf_str_cap_internal_(const guf_str *str)
|
||||
{
|
||||
if (guf_str_is_short_internal_(str)) {
|
||||
return GUF_STR_SSO_BUF_CAP - 1;
|
||||
} else if (guf_str_is_readonly(str)) {
|
||||
return 0;
|
||||
} else {
|
||||
// Precondition: all capacities for data.lng must be even.
|
||||
#if defined(GUF_PLATFORM_LITTLE_ENDIAN)
|
||||
GUF_ASSERT(str->data.lng.capacity & ~GUF_STR_GET_CAP_MASK); // Assert the is_long bit is actually set.
|
||||
const size_t cap_with_null = str->data.lng.capacity & GUF_STR_GET_CAP_MASK;
|
||||
GUF_ASSERT(cap_with_null % 2 == 0);
|
||||
#elif defined(GUF_PLATFORM_BIG_ENDIAN)
|
||||
GUF_ASSERT(str->data.lng.capacity & ~GUF_STR_GET_CAP_MASK); // Assert the is_long bit is actually set.
|
||||
const size_t cap_with_null = (str->data.lng.capacity & GUF_STR_GET_CAP_MASK) << 1;
|
||||
GUF_ASSERT(cap_with_null % 2 == 0);
|
||||
#endif
|
||||
GUF_ASSERT(cap_with_null > 0 && cap_with_null > GUF_STR_SSO_BUF_CAP);
|
||||
GUF_ASSERT(cap_with_null <= PTRDIFF_MAX);
|
||||
return cap_with_null - 1;
|
||||
}
|
||||
}
|
||||
|
||||
static size_t guf_str_size_internal_(const guf_str *str)
|
||||
{
|
||||
if (guf_str_is_short_internal_(str)) {
|
||||
GUF_ASSERT(str->data.shrt.size > 0);
|
||||
#if defined(GUF_PLATFORM_LITTLE_ENDIAN)
|
||||
const size_t size = (str->data.shrt.size >> 1);
|
||||
#elif defined(GUF_PLATFORM_BIG_ENDIAN)
|
||||
const size_t size = (str->data.shrt.size);
|
||||
#endif
|
||||
GUF_ASSERT(size > 0 && size <= GUF_STR_SSO_BUF_CAP);
|
||||
return size;
|
||||
} else {
|
||||
const size_t size = str->data.lng.size;
|
||||
GUF_ASSERT(size > 0 && size <= PTRDIFF_MAX);
|
||||
return size;
|
||||
}
|
||||
}
|
||||
|
||||
static size_t guf_str_len_internal_(const guf_str *str)
|
||||
{
|
||||
const size_t size = guf_str_size_internal_(str);
|
||||
GUF_ASSERT(size > 0);
|
||||
if (size == 0) {
|
||||
return 0;
|
||||
} else {
|
||||
return size - 1;
|
||||
}
|
||||
}
|
||||
|
||||
GUF_STR_KWRDS bool guf_str_is_short(const guf_str *str)
|
||||
{
|
||||
GUF_ASSERT(guf_str_is_valid(str));
|
||||
return guf_str_is_short_internal_(str);
|
||||
}
|
||||
|
||||
GUF_STR_KWRDS ptrdiff_t guf_str_capacity(const guf_str *str)
|
||||
{
|
||||
GUF_ASSERT(guf_str_is_valid(str));
|
||||
return (ptrdiff_t)guf_str_cap_internal_(str);
|
||||
}
|
||||
|
||||
GUF_STR_KWRDS ptrdiff_t guf_str_len(const guf_str *str)
|
||||
{
|
||||
GUF_ASSERT(guf_str_is_valid(str));
|
||||
return (ptrdiff_t)guf_str_len_internal_(str);
|
||||
}
|
||||
|
||||
GUF_STR_KWRDS bool guf_str_is_valid(const guf_str *str)
|
||||
{
|
||||
GUF_ASSERT(str);
|
||||
if (!str || guf_str_is_uninit(str)) {
|
||||
return false;
|
||||
}
|
||||
const bool is_readonly = !str->allocator;
|
||||
if (is_readonly) {
|
||||
bool valid_readonly = str->data.lng.c_str && str->data.lng.capacity == 0 && str->data.lng.size > 0;
|
||||
@ -185,162 +307,413 @@ static bool guf_str_is_valid(const guf_str *str)
|
||||
return false;
|
||||
}
|
||||
|
||||
const unsigned char first_byte = str->data.shrt.size; // union type-punning (only legal in C99 and above; undefined behaviour in C++ I think).
|
||||
const bool is_short = (first_byte & GUF_STR_IS_LONG_MASK) == 0;
|
||||
|
||||
if (is_short) {
|
||||
const size_t size = (str->data.shrt.size >> 1);
|
||||
if (guf_str_is_short_internal_(str)) {
|
||||
const size_t size = guf_str_size_internal_(str); // len + 1
|
||||
return size > 0 && size <= GUF_STR_SSO_BUF_CAP && str->data.shrt.c_str[size - 1] == '\0';
|
||||
} else {
|
||||
const size_t cap_with_null = str->data.lng.capacity & ~(size_t)1;
|
||||
return str->data.lng.c_str && cap_with_null > GUF_STR_SSO_BUF_CAP && str->data.lng.size > 0 && str->data.lng.size <= cap_with_null;
|
||||
const size_t cap_with_null = guf_str_cap_internal_(str) + 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;
|
||||
}
|
||||
}
|
||||
|
||||
GUF_STR_KWRDS bool guf_str_is_readonly(const guf_str *str)
|
||||
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));
|
||||
return !str->allocator;
|
||||
GUF_ASSERT(!guf_str_is_readonly(str));
|
||||
|
||||
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.
|
||||
guf_err_set_if_not_null(err, GUF_ERR_NONE);
|
||||
return str;
|
||||
}
|
||||
|
||||
if (new_cap_min >= PTRDIFF_MAX - 1) {
|
||||
guf_err_set_or_panic(err, GUF_ERR_INT_OVERFLOW, "in guf_str_try_reserve: new_cap_min >= PTRDIFF_MAX - 1");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
size_t new_cap_min_with_null = (size_t)new_cap_min + 1;
|
||||
if (new_cap_min_with_null % 2 != 0) { // Only an even lng.capacity is allowed.
|
||||
new_cap_min_with_null += 1;
|
||||
}
|
||||
|
||||
// Try if we can reach at least new_cap_min_with_null by doubling the capacity.
|
||||
const size_t GUF_STR_GROWTH_FAC = 2;
|
||||
size_t times_two_cap = old_cap_with_null * GUF_STR_GROWTH_FAC;
|
||||
if (guf_mul_is_overflow_size_t(old_cap_with_null, GUF_STR_GROWTH_FAC) || times_two_cap >= PTRDIFF_MAX) {
|
||||
times_two_cap = (PTRDIFF_MAX % 2 == 0) ? PTRDIFF_MAX : PTRDIFF_MAX - 1;
|
||||
}
|
||||
if (times_two_cap > new_cap_min_with_null) {
|
||||
new_cap_min_with_null = times_two_cap;
|
||||
}
|
||||
GUF_ASSERT(new_cap_min_with_null >= len_with_null && new_cap_min_with_null <= PTRDIFF_MAX);
|
||||
|
||||
const size_t space_remaining = (new_cap_min_with_null - len_with_null);
|
||||
if (new_cap_min_with_null < (PTRDIFF_MAX - 8) && space_remaining < 4) {
|
||||
new_cap_min_with_null += 4 - space_remaining; // Have some leeway.
|
||||
}
|
||||
|
||||
GUF_ASSERT(new_cap_min_with_null % 2 == 0);
|
||||
|
||||
if (guf_str_is_short_internal_(str)) { // a.) Was short string -> need initial allocation.
|
||||
char *c_str_new = str->allocator->alloc(new_cap_min_with_null, str->allocator->ctx);
|
||||
if (!c_str_new) {
|
||||
guf_err_set_or_panic(err, GUF_ERR_ALLOC_FAIL, "in guf_str_try_grow_if_necessary: Initial allocation failed.");
|
||||
return NULL;
|
||||
}
|
||||
memcpy(c_str_new, str->data.shrt.c_str, len_with_null);
|
||||
str->data.lng.c_str = c_str_new;
|
||||
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);
|
||||
if (!c_str_new) {
|
||||
guf_err_set_or_panic(err, GUF_ERR_ALLOC_FAIL, "in guf_str_try_grow_if_necessary: re-allocation failed.");
|
||||
return NULL;
|
||||
}
|
||||
str->data.lng.c_str = c_str_new;
|
||||
guf_str_set_lng_cap_(str, new_cap_min_with_null);
|
||||
}
|
||||
|
||||
GUF_ASSERT(guf_str_is_valid(str));
|
||||
guf_err_set_if_not_null(err, GUF_ERR_NONE);
|
||||
return str;
|
||||
}
|
||||
|
||||
GUF_STR_KWRDS bool guf_str_is_short(const guf_str *str)
|
||||
static char *guf_str_get_cstr_internal_(guf_str *str)
|
||||
{
|
||||
if (guf_str_is_short(str)) {
|
||||
return str->data.shrt.c_str;
|
||||
} else {
|
||||
return str->data.lng.c_str;
|
||||
}
|
||||
}
|
||||
|
||||
static const char *guf_str_get_const_cstr_internal_(const guf_str *str)
|
||||
{
|
||||
if (guf_str_is_short(str)) {
|
||||
return str->data.shrt.c_str;
|
||||
} else {
|
||||
return str->data.lng.c_str;
|
||||
}
|
||||
}
|
||||
|
||||
GUF_STR_KWRDS const char *guf_str_const_cstr(const guf_str *str)
|
||||
{
|
||||
GUF_ASSERT(guf_str_is_valid(str));
|
||||
const char* c_str = guf_str_get_const_cstr_internal_(str);
|
||||
GUF_ASSERT(c_str);
|
||||
return c_str;
|
||||
}
|
||||
|
||||
GUF_STR_KWRDS char *guf_str_try_get_cstr(guf_str *str, guf_err *err)
|
||||
{
|
||||
GUF_ASSERT(guf_str_is_valid(str));
|
||||
if (guf_str_is_readonly(str)) {
|
||||
return false;
|
||||
guf_err_set_or_panic(err, GUF_ERR_INVALID_ARG, "in guf_str_try_get_cstr: cannot return non-const char pointer because str is readonly");
|
||||
return NULL;
|
||||
}
|
||||
const unsigned char first_byte = str->data.shrt.size; // union type-punning (only legal in C99 and above; undefined behaviour in C++ I think).
|
||||
return (first_byte & GUF_STR_IS_LONG_MASK) == 0;
|
||||
char *c_str = guf_str_get_cstr_internal_(str);
|
||||
GUF_ASSERT(c_str);
|
||||
return c_str;
|
||||
}
|
||||
|
||||
GUF_STR_KWRDS size_t guf_str_capacity(const guf_str *str)
|
||||
GUF_STR_KWRDS char *guf_str_cstr(guf_str *str)
|
||||
{
|
||||
return guf_str_try_get_cstr(str, NULL);
|
||||
}
|
||||
|
||||
|
||||
static void guf_str_set_len_internal_(guf_str *str, size_t len)
|
||||
{
|
||||
GUF_ASSERT(len <= guf_str_cap_internal_(str));
|
||||
GUF_ASSERT(!guf_str_is_readonly(str));
|
||||
const size_t len_with_null = len + 1;
|
||||
if (guf_str_is_short_internal_(str)) {
|
||||
GUF_ASSERT(len_with_null <= UCHAR_MAX)
|
||||
guf_str_set_shrt_size_(str, (unsigned char)len_with_null);
|
||||
} else {
|
||||
str->data.lng.size = len_with_null;
|
||||
}
|
||||
}
|
||||
|
||||
GUF_STR_KWRDS guf_str guf_str_new_uninitialised(void)
|
||||
{
|
||||
guf_str str = {.allocator = NULL, .data.shrt.size = 0, .data.shrt.c_str[0] = '\0'};
|
||||
return str;
|
||||
}
|
||||
|
||||
GUF_STR_KWRDS bool guf_str_is_uninit(const guf_str *str)
|
||||
{
|
||||
GUF_ASSERT(str);
|
||||
return !str->allocator && !str->data.shrt.size && str->data.shrt.c_str[0] == '\0';
|
||||
}
|
||||
|
||||
|
||||
GUF_STR_KWRDS guf_str *guf_str_init_empty(guf_str *str, guf_allocator *allocator)
|
||||
{
|
||||
GUF_ASSERT(str && allocator);
|
||||
str->allocator = allocator;
|
||||
guf_str_set_shrt_size_(str, 1);
|
||||
str->data.shrt.c_str[0] = '\0';
|
||||
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) {
|
||||
guf_err_set_or_panic(err, GUF_ERR_NULL_PTR, "in guf_str_try_init: str is NULL");
|
||||
return NULL;
|
||||
} else if (!alloc || !alloc->alloc || !alloc->realloc || !alloc->free) {
|
||||
guf_err_set_or_panic(err, GUF_ERR_NULL_PTR, "in guf_str_try_init: alloc (or allocs function pointers) is/are NULL");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!guf_str_view_is_valid(str_view)) {
|
||||
guf_err_set_or_panic(err, GUF_ERR_NULL_PTR, "in guf_str_try_init: invalid str_view");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
guf_str_init_empty(str, alloc);
|
||||
|
||||
if (str_view.len == 0) {
|
||||
GUF_ASSERT(!guf_str_is_readonly(str));
|
||||
GUF_ASSERT(guf_str_is_valid(str));
|
||||
return str;
|
||||
}
|
||||
GUF_ASSERT(str_view.str && str_view.len > 0);
|
||||
|
||||
guf_str_try_reserve(str, str_view.len, err);
|
||||
if (err && *err != GUF_ERR_NONE) {
|
||||
guf_panic(*err, "in guf_str_try_init: Initial allocation failed");
|
||||
return NULL;
|
||||
}
|
||||
GUF_ASSERT(guf_str_len_internal_(str) == 0);
|
||||
GUF_ASSERT(guf_str_cap_internal_(str) >= (size_t)str_view.len);
|
||||
GUF_ASSERT(!guf_str_is_readonly(str));
|
||||
|
||||
char *c_str_dst = guf_str_get_cstr_internal_(str);
|
||||
GUF_ASSERT_RELEASE(c_str_dst);
|
||||
memcpy(c_str_dst, str_view.str, str_view.len);
|
||||
c_str_dst[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_STR_KWRDS guf_str *guf_str_init(guf_str *str, guf_str_view str_view, guf_allocator *alloc)
|
||||
{
|
||||
return guf_str_try_init(str, str_view, alloc, NULL);
|
||||
}
|
||||
|
||||
GUF_STR_KWRDS guf_str guf_str_try_new(guf_str_view str_view, guf_allocator *alloc, guf_err *err)
|
||||
{
|
||||
guf_str str = guf_str_new_uninitialised();
|
||||
guf_str_try_init(&str, str_view, alloc, err);
|
||||
if (err && *err != GUF_ERR_NONE) {
|
||||
guf_err_set_or_panic(err, *err, "in guf_str_try_new: failed init");
|
||||
return guf_str_new_uninitialised();
|
||||
} else {
|
||||
GUF_ASSERT(!guf_str_is_uninit(&str));
|
||||
return str;
|
||||
}
|
||||
}
|
||||
|
||||
GUF_STR_KWRDS void guf_str_free(guf_str *str, void *ctx)
|
||||
{
|
||||
(void)ctx;
|
||||
|
||||
if (!str || guf_str_is_uninit(str)) {
|
||||
return;
|
||||
} else if (guf_str_is_readonly(str)) { // Don't need to de-allocate anything for read-only strings.
|
||||
*str = guf_str_new_uninitialised();
|
||||
return;
|
||||
} else if (!guf_str_is_short(str)) { // Need to de-allocate.
|
||||
GUF_ASSERT(guf_str_capacity(str) < PTRDIFF_MAX);
|
||||
const ptrdiff_t cap_with_null = guf_str_capacity(str) + 1;
|
||||
GUF_ASSERT((cap_with_null % 2) == 0);
|
||||
char *c_str = guf_str_cstr(str);
|
||||
GUF_ASSERT(str->allocator->free);
|
||||
if (str->allocator->free) {
|
||||
str->allocator->free(c_str, cap_with_null, str->allocator->ctx);
|
||||
}
|
||||
*str = guf_str_new_uninitialised();
|
||||
return;
|
||||
} else {
|
||||
GUF_ASSERT(guf_str_is_short(str));
|
||||
*str = guf_str_new_uninitialised();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
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_short(str)) {
|
||||
return GUF_STR_SSO_BUF_CAP - 1;
|
||||
|
||||
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");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (times < 0) {
|
||||
guf_err_set_or_panic(err, GUF_ERR_INVALID_ARG, "in guf_str_try_append_char: repeats < 0");
|
||||
return NULL;
|
||||
} else if (times == 0) {
|
||||
guf_err_set_if_not_null(err, GUF_ERR_NONE);
|
||||
return str;
|
||||
}
|
||||
|
||||
GUF_ASSERT(guf_str_len_internal_(str) <= guf_str_cap_internal_(str));
|
||||
|
||||
const size_t old_cap = guf_str_cap_internal_(str);
|
||||
const size_t old_len = guf_str_len_internal_(str);
|
||||
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");
|
||||
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");
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
const size_t new_cap = guf_str_cap_internal_(str);
|
||||
GUF_ASSERT(guf_str_len_internal_(str) == old_len);
|
||||
GUF_ASSERT(new_cap >= new_len && new_cap >= old_cap);
|
||||
GUF_ASSERT(((ptrdiff_t)new_cap - (ptrdiff_t)old_len) >= times);
|
||||
|
||||
char *c_str = guf_str_get_cstr_internal_(str);
|
||||
for (size_t i = old_len; i < new_len; ++i) {
|
||||
c_str[i] = c;
|
||||
}
|
||||
guf_str_set_len_internal_(str, new_len);
|
||||
c_str[new_len] = '\0';
|
||||
|
||||
guf_err_set_if_not_null(err, GUF_ERR_NONE);
|
||||
return str;
|
||||
}
|
||||
|
||||
GUF_STR_KWRDS guf_str *guf_str_append_char(guf_str *str, char c, ptrdiff_t times)
|
||||
{
|
||||
return guf_str_try_append_char(str, c, times, NULL);
|
||||
}
|
||||
|
||||
GUF_STR_KWRDS guf_str *guf_str_try_append_one_char(guf_str *str, char c, guf_err *err)
|
||||
{
|
||||
return guf_str_try_append_char(str, c, 1, err);
|
||||
}
|
||||
|
||||
GUF_STR_KWRDS guf_str *guf_str_append_one_char(guf_str *str, char c)
|
||||
{
|
||||
return guf_str_try_append_one_char(str, c, NULL);
|
||||
}
|
||||
|
||||
|
||||
GUF_STR_KWRDS guf_str *guf_str_try_append(guf_str *str, guf_str_view sv, guf_err *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");
|
||||
return NULL;
|
||||
} else if (guf_str_is_readonly(str)) {
|
||||
return 0;
|
||||
} else {
|
||||
// Precondition: all capacities for data.lng must be even.
|
||||
#if defined(GUF_PLATFORM_LITTLE_ENDIAN)
|
||||
GUF_ASSERT(str->data.lng.capacity & ~GUF_STR_GET_CAP_MASK); // Assert the is_long bit is actually set.
|
||||
const size_t cap_with_null = str->data.lng.capacity & GUF_STR_GET_CAP_MASK;
|
||||
#elif defined(GUF_PLATFORM_BIG_ENDIAN)
|
||||
GUF_ASSERT(str->data.lng.capacity & ~GUF_STR_GET_CAP_MASK); // Assert the is_long bit is actually set.
|
||||
const size_t cap_with_null = (str->data.lng.capacity & GUF_STR_GET_CAP_MASK) << 1;
|
||||
#endif
|
||||
GUF_ASSERT(cap_with_null > 0 && cap_with_null > GUF_STR_SSO_BUF_CAP);
|
||||
return cap_with_null - 1;
|
||||
guf_err_set_or_panic(err, GUF_ERR_NULL_PTR, "in in guf_str_try_append_view: str is readonly");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (sv.len == 0) {
|
||||
guf_err_set_if_not_null(err, GUF_ERR_NONE);
|
||||
return str;
|
||||
}
|
||||
GUF_ASSERT(sv.str && sv.len > 0);
|
||||
|
||||
const size_t old_cap = guf_str_cap_internal_(str);
|
||||
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");
|
||||
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");
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
const size_t new_cap = guf_str_cap_internal_(str);
|
||||
GUF_ASSERT(new_cap >= old_cap && new_cap >= new_len);
|
||||
GUF_ASSERT(((ptrdiff_t)new_cap - (ptrdiff_t)old_len) >= sv.len);
|
||||
|
||||
char *c_str_dst = guf_str_get_cstr_internal_(str);
|
||||
for (size_t dst_i = old_len, src_i = 0; dst_i < new_len; ++dst_i, ++src_i) {
|
||||
GUF_ASSERT(src_i < (size_t)sv.len);
|
||||
c_str_dst[dst_i] = sv.str[src_i];
|
||||
}
|
||||
c_str_dst[new_len] = '\0';
|
||||
guf_str_set_len_internal_(str, new_len);
|
||||
|
||||
GUF_ASSERT(guf_str_is_valid(str));
|
||||
guf_err_set_if_not_null(err, GUF_ERR_NONE);
|
||||
return str;
|
||||
}
|
||||
|
||||
GUF_STR_KWRDS size_t guf_str_len(const guf_str *str)
|
||||
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 (guf_str_is_short(str)) {
|
||||
GUF_ASSERT(str->data.shrt.size > 0);
|
||||
#if defined(GUF_PLATFORM_LITTLE_ENDIAN)
|
||||
const size_t size = (str->data.shrt.size >> 1);
|
||||
#elif defined(GUF_PLATFORM_BIG_ENDIAN)
|
||||
const size_t size = (str->data.shrt.size);
|
||||
#endif
|
||||
GUF_ASSERT(size > 0 && size <= GUF_STR_SSO_BUF_CAP);
|
||||
return size - 1;
|
||||
} else {
|
||||
const size_t size = str->data.lng.size;
|
||||
GUF_ASSERT(size > 0);
|
||||
return size - 1;
|
||||
|
||||
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.
|
||||
guf_str_try_reserve(str, cap < PTRDIFF_MAX ? cap + 1 : PTRDIFF_MAX, 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_try_reserve(guf_str *str, ptrdiff_t new_cap, guf_err *err)
|
||||
// {
|
||||
// GUF_ASSERT_RELEASE(guf_str_is_valid_writable(str));
|
||||
|
||||
// const ptrdiff_t size = guf_str_len(str) + 1;
|
||||
// const ptrdiff_t old_cap = guf_str_capacity(str);
|
||||
|
||||
// if (new_cap <= old_cap) { // Growth not necessary.
|
||||
// guf_err_set_if_not_null(err, GUF_ERR_NONE);
|
||||
// return str;
|
||||
// }
|
||||
|
||||
// if (guf_str_is_short(str)) { // a.) Was short string -> need initial allocation.
|
||||
// GUF_ASSERT(size == GUF_STR_SSO_BUFCAP);
|
||||
// char *c_str_new = str->allocator->alloc(new_cap, str->allocator->ctx);
|
||||
// if (!c_str_new) {
|
||||
// guf_err_set_or_panic(err, GUF_ERR_ALLOC_FAIL, "in guf_str_try_grow_if_necessary: Initial allocation failed.");
|
||||
// return NULL;
|
||||
// }
|
||||
// memcpy(c_str_new, str->data.stack.c_str, str->data.stack.size);
|
||||
// str->state = GUF_STR_STATE_INIT;
|
||||
// str->data.heap.c_str = c_str_new;
|
||||
// str->data.heap.size = size;
|
||||
// str->data.heap.capacity = new_cap;
|
||||
// guf_err_set_if_not_null(err, GUF_ERR_NONE);
|
||||
// return str;
|
||||
// }
|
||||
// // b.) Was already allocated -> need to grow existing allocation.
|
||||
// char *c_str_new = str->allocator->realloc(str->data.heap.c_str, old_cap, new_cap, str->allocator->ctx);
|
||||
// if (!c_str_new) {
|
||||
// guf_err_set_or_panic(err, GUF_ERR_ALLOC_FAIL, "in guf_str_try_grow_if_necessary: re-allocation failed.");
|
||||
// return NULL;
|
||||
// }
|
||||
// str->data.heap.c_str = c_str_new;
|
||||
// str->data.heap.capacity = new_cap;
|
||||
// guf_err_set_if_not_null(err, GUF_ERR_NONE);
|
||||
// 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) {
|
||||
// guf_err_set_or_panic(err, GUF_ERR_NULL_PTR, "in guf_str_try_init: str is NULL");
|
||||
// return NULL;
|
||||
// } else if (!alloc || !alloc->alloc || !alloc->realloc || !alloc->free) {
|
||||
// guf_err_set_or_panic(err, GUF_ERR_NULL_PTR, "in guf_str_try_init: alloc (or allocs function pointers) is/are NULL");
|
||||
// return NULL;
|
||||
// }
|
||||
|
||||
// if (!guf_str_view_is_valid(str_view)) {
|
||||
// guf_err_set_or_panic(err, GUF_ERR_NULL_PTR, "in guf_str_try_init: invalid str_view");
|
||||
// return NULL;
|
||||
// }
|
||||
|
||||
// str->allocator = alloc;
|
||||
// str->state = GUF_STR_STATE_SHORT;
|
||||
// str->data.stack.size = 1;
|
||||
// str->data.stack.c_str[0] = '\0';
|
||||
|
||||
// if (str_view.len == PTRDIFF_MAX) {
|
||||
// guf_err_set_or_panic(err, GUF_ERR_NULL_PTR, "in guf_str_try_init: str_view.len + 1 would overflow ptrdiff_t");
|
||||
// return NULL;
|
||||
// }
|
||||
// const ptrdiff_t size = str_view.len + 1;
|
||||
// GUF_ASSERT(size > 0);
|
||||
|
||||
// if (size <= GUF_STR_SSO_BUFCAP) { // a) Fits in short-string.
|
||||
// str->state = GUF_STR_STATE_SHORT;
|
||||
// GUF_ASSERT(size <= UCHAR_MAX);
|
||||
// str->data.stack.size = (unsigned char)size;
|
||||
// memcpy(str->data.stack.c_str, str_view.str, str_view.len);
|
||||
// str->data.stack.c_str[str_view.len] = '\0';
|
||||
// guf_err_set_if_not_null(err, GUF_ERR_NONE);
|
||||
// return str;
|
||||
// }
|
||||
// // b) Needs initial allocation.
|
||||
// guf_str_try_reserve(str, size, err);
|
||||
// if (err && *err != GUF_ERR_NONE) {
|
||||
// guf_panic(*err, "in guf_str_try_init: Initial allocation failed");
|
||||
// return NULL;
|
||||
// }
|
||||
// GUF_ASSERT(!guf_str_is_short(str));
|
||||
// memcpy(str->data.heap.c_str, str_view.str, str_view.len);
|
||||
// str->data.heap.c_str[str_view.len] = '\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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
32
todo.txt
32
todo.txt
@ -1,10 +1,36 @@
|
||||
- guf_stack, guf_queue, guf_ringbuf
|
||||
- sort: add cpp #ifdef to remove restrict from declaration
|
||||
|
||||
- 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?
|
||||
|
||||
- potential idea for alloc: instead of using a pointer (8 or 4 bytes), use a 2 byte id to save space (and avoid dangling pointers):
|
||||
typedef guf_allocator_id uint16_t; // allows for up to (UINT16_MAX - 1) allocators (and 0 for NULL)
|
||||
guf_allocator_dbuf guf_global_allocators;
|
||||
// cf. https://github.com/erincatto/box2d/blob/main/src/id_pool.c
|
||||
typedef struct guf_allocator_id_pool { // Implement generically with macro "templates"
|
||||
guf_allocator_id_dbuf free_ids;
|
||||
guf_allocator_id next_free_id;
|
||||
} guf_allocator_id_pool;
|
||||
|
||||
guf_allocator_id_pool guf_global_allocator_id_pool;
|
||||
|
||||
guf_allocator_id guf_register_allocator(guf_allocator allocator) {
|
||||
guf_allocator_id id = get_free_id(&guf_global_allocator_id_pool);
|
||||
if (!id) {
|
||||
return 0;
|
||||
}
|
||||
guf_global_allocators[id] = allocator;
|
||||
}
|
||||
|
||||
guf_allocator *guf_get_allocator(guf_allocator_id id) {
|
||||
return (id == 0 || id >= UINT16_MAX) ? NULL : (guf_global_allocators + id);
|
||||
}
|
||||
|
||||
- no guf_init.h
|
||||
|
||||
- linalg: float precision question += elem * -val / pivot_val vs elem * (-val / pivot_val)
|
||||
|
||||
- unicode normalisation
|
||||
- track allocs for test (implement alloc tracker)
|
||||
- handle right-to-left text properly
|
||||
- fix 32-bit dict (and add 32/64 bit defs and 32/64-bit platform detection in common.h)
|
||||
Loading…
x
Reference in New Issue
Block a user