diff --git a/src/guf_common.h b/src/guf_common.h index b8ddfae..08ac3cd 100644 --- a/src/guf_common.h +++ b/src/guf_common.h @@ -6,6 +6,9 @@ #include #include +#define GUF_PLATFORM_LITTLE_ENDIAN +// #define GUF_PLATFORM_BIG_ENDIAN + #if SIZE_MAX == UINT64_MAX #define GUF_PLATFORM_BITS 64 #elif SIZE_MAX == UINT32_MAX diff --git a/src/guf_str.h b/src/guf_str.h index 7083c65..a825793 100644 --- a/src/guf_str.h +++ b/src/guf_str.h @@ -12,104 +12,120 @@ #include "guf_utf8.h" #include "guf_hash.h" -typedef enum guf_str_state { - GUF_STR_STATE_INIT = 0, - GUF_STR_STATE_SHORT = 1, - GUF_STR_STATE_VIEW = 2, - GUF_STR_STATE_ALLOC_ERR = 4 -} guf_str_state; +// 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 size; + char *c_str; +} guf_str_internal_long_; + +#define GUF_STR_SSO_BUF_CAP (sizeof(guf_str_internal_long_) - sizeof(unsigned char)) /* 23 bytes on 64-bit platforms, 11 bytes on 32-bit platforms */ + +#if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L) || (defined(__cplusplus) && __cplusplus >= 201103L) + static_assert(GUF_STR_SSO_BUF_CAP > 0, "GUF_STR_SSO_BUF_CAP < 0 (this is very weird)"); // Basically cannot fail. + static_assert(GUF_STR_SSO_BUF_CAP < 0x80, "GUF_STR_SSO_BUF_CAP >= 128 (no support for platforms with wordsize >= 512-bits)"); // Could fail on hypothetical platforms with 512-bit wordsize (and above). +#endif + +typedef struct guf_str_internal_short_ { + unsigned char size; // size overlaps with the first byte of guf_str_internal_long_.capacity [1] + char c_str[GUF_STR_SSO_BUF_CAP]; +} guf_str_internal_short_; + +/* + [1] The first byte of guf_str_internal_long_.capacity is its least-significant-byte on little-endian + platforms, and its most-significant byte on big-endian platforms. +*/ typedef struct guf_str { - union { - struct heap { - char *c_str; - size_t len, capacity; // len and capacity do not include the null-terminator. - } heap; - struct stack { // Short-string optimisation. - #define GUF_STR_SSO_BUFSIZE (sizeof(struct heap) - sizeof(unsigned char)) - #define GUF_STR_SSO_BUFCAP (GUF_STR_SSO_BUFSIZE - 1) - char c_str[GUF_STR_SSO_BUFSIZE]; - unsigned char len; - } stack; - } data; - guf_allocator *allocator; - guf_str_state state; -} guf_str; + union { + guf_str_internal_long_ lng; + guf_str_internal_short_ shrt; + } data; // 24 bytes on 64-bit platforms, 12 bytes on 32-bit platforms. + 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 = strlen((CSTR))}) -#define GUF_STR_TO_VIEW(GUF_STR_PTR) ((guf_str_view){.str = guf_str_const_cstr((GUF_STR_PTR)), .len = guf_str_len((GUF_STR_PTR))}) -#define GUF_CSTR_TO_READONLY_STR(CSTR) ((guf_str){.state = GUF_STR_STATE_VIEW, .allocator = NULL, .data.heap.c_str = CSTR, .data.heap.len = strlen(CSTR), .data.heap.capacity = 0}) +#define GUF_CSTR_TO_VIEW(CSTR) ((guf_str_view){.str = (CSTR), .len = (ptrdiff_t)strlen((CSTR))}) +#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)} #endif -// Creation: -GUF_STR_KWRDS guf_str *guf_str_init(guf_str *str, guf_str_view str_view); -GUF_STR_KWRDS guf_str *guf_str_init_from_cstr(guf_str *str, const char* c_str); -GUF_STR_KWRDS guf_str *guf_str_init_empty_with_capacity(guf_str *str, size_t capacity); -// guf_str_new functions return GUF_DICT_UNINITIALISED or GUF_STR_UNINITIALISED_FAILED_ALLOC on failure (can be checked with guf_str_alloc_success) -GUF_STR_KWRDS guf_str guf_str_new(guf_str_view str_view); -GUF_STR_KWRDS guf_str guf_str_new_substr(guf_str_view str_view, ptrdiff_t pos, ptrdiff_t len); - -GUF_STR_KWRDS guf_str guf_str_new_from_cstr(const char *c_str); -GUF_STR_KWRDS guf_str guf_str_new_empty_with_capacity(size_t capacity); - -// Destruction: -GUF_STR_KWRDS void guf_str_free(guf_str *str); - -// Modification: -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); - -GUF_STR_KWRDS guf_str *guf_str_reserve(guf_str *str, size_t bufsize); -GUF_STR_KWRDS guf_str *guf_str_shrink_capacity(guf_str *str, size_t shrink_trigger_fac, bool shrink_exact); - -GUF_STR_KWRDS char guf_str_pop_back(guf_str *str); -GUF_STR_KWRDS char guf_str_pop_front(guf_str *str); - -// Copying and viewing: -GUF_STR_KWRDS guf_str guf_str_substr_cpy(guf_str_view str, size_t pos, size_t count); // not necessary +// guf_str_view: +GUF_STR_KWRDS bool guf_str_view_is_valid(guf_str_view sv); +GUF_STR_KWRDS guf_str guf_str_substr_cpy(guf_str_view str, ptrdiff_t pos, size_t count); // not necessary GUF_STR_KWRDS guf_str_view guf_substr_view(guf_str_view str, ptrdiff_t pos, ptrdiff_t count); GUF_STR_KWRDS guf_str_view guf_str_view_trim_left(guf_str_view str); GUF_STR_KWRDS guf_str_view guf_str_view_trim_right(guf_str_view str); - -// Tokenising/Iterating. -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); - -// Indexing: -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); - -// Metadata retrieval: -GUF_STR_KWRDS size_t guf_str_len(const guf_str *str); // The size (in chars) without the final zero-terminator (size - 1). -GUF_STR_KWRDS size_t guf_str_capacity(const guf_str *str); -GUF_STR_KWRDS bool guf_str_is_stack_allocated(const guf_str *str); -GUF_STR_KWRDS bool guf_str_is_valid(const guf_str *str); -GUF_STR_KWRDS bool guf_str_alloc_success(const guf_str *str); - GUF_STR_KWRDS guf_hash_size_t guf_str_view_hash(const guf_str_view *sv); GUF_STR_KWRDS uint64_t guf_str_view_hash64(const guf_str_view *sv); GUF_STR_KWRDS uint32_t guf_str_view_hash32(const guf_str_view *sv); -// Comparison: GUF_STR_KWRDS bool guf_str_view_equal(const guf_str_view* a, const guf_str_view* b); GUF_STR_KWRDS bool guf_str_view_equal_val_arg(guf_str_view a_val, guf_str_view b_val); GUF_STR_KWRDS int guf_str_view_cmp(const void *str_view_a, const void *str_view_b); // For qsort etc. +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: +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 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); + 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); + +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); + +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); +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); + +GUF_STR_KWRDS char guf_str_pop_back(guf_str *str); +GUF_STR_KWRDS char guf_str_pop_front(guf_str *str); + +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. + +GUF_STR_KWRDS bool guf_str_is_short(const guf_str *str); +GUF_STR_KWRDS bool guf_str_is_readonly(const guf_str *str); #endif +// #define GUF_STR_IMPL_STATIC /*debug*/ + #if defined(GUF_STR_IMPL) || defined(GUF_STR_IMPL_STATIC) +#ifdef __cplusplus + #error "Must compile guf_str as C99 (or above) because type-punning with unions is undefined behaviour in C++" +#endif + +#include "guf_common.h" #include #ifdef GUF_STR_IMPL @@ -121,6 +137,224 @@ GUF_STR_KWRDS bool guf_str_equals_strview(const guf_str *a, guf_str_view b); // TODO: find_first_of +// guf_str: +#if defined(GUF_PLATFORM_LITTLE_ENDIAN) + #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) + { + GUF_ASSERT(capacity % 2 == 0); + GUF_ASSERT(capacity > GUF_STR_SSO_BUF_CAP); + str->data.lng.capacity = capacity | ((size_t)1); + } + static inline void guf_str_set_shrt_size_(guf_str *str, unsigned char size) + { + GUF_ASSERT(size < GUF_STR_SSO_BUF_CAP && size < 0x80); + str->data.shrt.size = (unsigned char)(size << 1u); + } +#elif defined(GUF_PLATFORM_BIG_ENDIAN) + #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) + { + GUF_ASSERT(capacity % 2 == 0); + GUF_ASSERT(capacity > GUF_STR_SSO_BUF_CAP); + str->data.lng.capacity = ~GUF_STR_GET_CAP_MASK | (capacity >> 1); + } + static inline void guf_str_set_shrt_size_(guf_str *str, unsigned char size) + { + GUF_ASSERT(size < GUF_STR_SSO_BUF_CAP && size < 0x80); + str->data.shrt.size = size; + } +#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_ASSERT(str); + 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; + return valid_readonly; + } + const bool valid_allocator = str->allocator && str->allocator->alloc && str->allocator->free && str->allocator->realloc; + if (!valid_allocator) { + 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); + 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; + } +} + +GUF_STR_KWRDS bool guf_str_is_readonly(const guf_str *str) +{ + GUF_ASSERT(guf_str_is_valid(str)); + return !str->allocator; +} + +GUF_STR_KWRDS bool guf_str_is_short(const guf_str *str) +{ + GUF_ASSERT(guf_str_is_valid(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; +} + +GUF_STR_KWRDS size_t guf_str_capacity(const guf_str *str) +{ + GUF_ASSERT(guf_str_is_valid(str)); + if (guf_str_is_short(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; + #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_STR_KWRDS size_t guf_str_len(const guf_str *str) +{ + 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; + } +} + +// 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_view: + +GUF_STR_KWRDS bool guf_str_view_is_valid(guf_str_view sv) +{ + if (sv.str) { + return sv.len >= 0; + } else { + return sv.len == 0; + } +} + 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) { if (input->len <= 0 || input->str == NULL) { @@ -233,7 +467,6 @@ GUF_STR_KWRDS guf_str_view guf_str_view_trim_right(guf_str_view sv) return sv; } - GUF_STR_KWRDS guf_str_view guf_substr_view(guf_str_view str, ptrdiff_t pos, ptrdiff_t count) { GUF_ASSERT(str.str); @@ -278,7 +511,6 @@ GUF_STR_KWRDS uint32_t guf_str_view_hash32(const guf_str_view *sv) return guf_hash32(sv->str, sv->len, GUF_HASH32_INIT); } -// Comparison: GUF_STR_KWRDS bool guf_str_view_equal(const guf_str_view* a, const guf_str_view* b) { GUF_ASSERT(a && b); diff --git a/src/test/example.c b/src/test/example.c index 8fe9193..6cf386d 100644 --- a/src/test/example.c +++ b/src/test/example.c @@ -48,7 +48,7 @@ #define GUF_RAND_IMPL_STATIC #include "guf_rand.h" -#include "guf_dict_impl.h" +#include "guf_dict_impl.h" int main(void) {