diff --git a/src/guf_alloc_libc.h b/src/guf_alloc_libc.h index 8afc555..83a5f7b 100644 --- a/src/guf_alloc_libc.h +++ b/src/guf_alloc_libc.h @@ -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) diff --git a/src/guf_alloc_tracker.c b/src/guf_alloc_tracker.c index 2cc6730..66729a4 100644 --- a/src/guf_alloc_tracker.c +++ b/src/guf_alloc_tracker.c @@ -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; diff --git a/src/guf_dbuf.h b/src/guf_dbuf.h index caa0616..47edb78 100644 --- a/src/guf_dbuf.h +++ b/src/guf_dbuf.h @@ -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")); diff --git a/src/guf_id_pool.h b/src/guf_id_pool.h new file mode 100644 index 0000000..5a41d06 --- /dev/null +++ b/src/guf_id_pool.h @@ -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 + diff --git a/src/guf_math.h b/src/guf_math.h index 868cf9c..e1f4430 100644 --- a/src/guf_math.h +++ b/src/guf_math.h @@ -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;} diff --git a/src/guf_str.h b/src/guf_str.h index a825793..ed431d4 100644 --- a/src/guf_str.h +++ b/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 #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); +} diff --git a/todo.txt b/todo.txt index a7b5500..9e14561 100644 --- a/todo.txt +++ b/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) \ No newline at end of file +- handle right-to-left text properly \ No newline at end of file