From 0751726fc51f5fe9ae2c2b60bf7f95ecb090b1e9 Mon Sep 17 00:00:00 2001 From: jun <83899451+zeichensystem@users.noreply.github.com> Date: Fri, 3 Jan 2025 10:38:57 +0100 Subject: [PATCH] Initial commit --- .gitignore | 5 + CMakeLists.txt | 51 +++ dbuf_tests.py | 190 ++++++++++ doc/guf_dict-diagram.png | Bin 0 -> 135986 bytes src/guf_assert.h | 3 + src/guf_common.c | 177 +++++++++ src/guf_common.h | 72 ++++ src/guf_darr.h | 234 ++++++++++++ src/guf_dbuf.c | 260 ++++++++++++++ src/guf_dbuf.h | 81 +++++ src/guf_dict.c | 649 +++++++++++++++++++++++++++++++++ src/guf_dict.h | 111 ++++++ src/guf_dict_impls.c | 348 ++++++++++++++++++ src/guf_obj.c | 74 ++++ src/guf_obj.h | 127 +++++++ src/guf_str.c | 756 +++++++++++++++++++++++++++++++++++++++ src/guf_str.h | 136 +++++++ src/guf_test.c | 93 +++++ test/data_01.txt | 13 + testgen.py | 29 ++ 20 files changed, 3409 insertions(+) create mode 100644 .gitignore create mode 100755 CMakeLists.txt create mode 100644 dbuf_tests.py create mode 100644 doc/guf_dict-diagram.png create mode 100644 src/guf_assert.h create mode 100644 src/guf_common.c create mode 100644 src/guf_common.h create mode 100644 src/guf_darr.h create mode 100644 src/guf_dbuf.c create mode 100644 src/guf_dbuf.h create mode 100755 src/guf_dict.c create mode 100755 src/guf_dict.h create mode 100644 src/guf_dict_impls.c create mode 100644 src/guf_obj.c create mode 100644 src/guf_obj.h create mode 100644 src/guf_str.c create mode 100644 src/guf_str.h create mode 100644 src/guf_test.c create mode 100644 test/data_01.txt create mode 100644 testgen.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..806bf2b --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +bin/ +build/ +.vscode/ +**/.DS_Store +**/__pycache__ \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100755 index 0000000..dadddc8 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,51 @@ +cmake_minimum_required(VERSION 3.12) +set(PROJECT_NAME libguf) +project(${PROJECT_NAME}) + +set(SOURCES src/guf_common.c src/guf_str.c src/guf_dict.c src/guf_dbuf.c src/guf_obj.c) + +add_library(${PROJECT_NAME} STATIC ${SOURCES}) +target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}}/lib/guf) +# target_include_directories(${PROJECT_NAME} PRIVATE src) + +add_executable(libguf_test ${SOURCES} src/guf_test.c) +target_include_directories(libguf_test PRIVATE src) + +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS OFF) +set(CMAKE_C_STANDARD 99) + +if (TARGET libguf_test) + message("-- Configure libguf_test...") + + set(CMAKE_DEBUG_POSTFIX _dbg) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/bin) + + if (APPLE OR UNIX OR LINUX) + set(WARNING_FLAGS_C -Wall -Wextra -Wpedantic -Wvla -Wshadow -Wundef -Wmisleading-indentation -Wnull-dereference -Wswitch-default -Wno-newline-eof -Wno-unused-function -Wno-unused-parameter) + endif () + + set_target_properties(libguf_test PROPERTIES DEBUG_POSTFIX ${CMAKE_DEBUG_POSTFIX}) + + if (APPLE OR UNIX OR LINUX) + set(DBG_FLAGS -fsanitize=undefined,address -g3 -glldb -Og) + else () + set(DBG_FLAGS /fsanitize=address) + endif() + + target_compile_options(libguf_test PRIVATE ${WARNING_FLAGS_C} $<$: ${DBG_FLAGS}>) # Note: no higher optimisations at all for debugger to work... + target_link_options(libguf_test PRIVATE ${WARNING_FLAGS_C} $<$: ${DBG_FLAGS}> ) + + include(CheckIPOSupported) + check_ipo_supported(RESULT ipo_available) + if (ipo_available AND (CMAKE_BUILD_TYPE STREQUAL "Release" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")) + message(STATUS "LTO enabled") + set_target_properties(${PROJECT_NAME} PROPERTIES INTERPROCEDURAL_OPTIMIZATION TRUE) + else() + message(STATUS "LTO disabled") + endif() + + message("-- Configured libguf_test") +endif() + + diff --git a/dbuf_tests.py b/dbuf_tests.py new file mode 100644 index 0000000..d2ff4c8 --- /dev/null +++ b/dbuf_tests.py @@ -0,0 +1,190 @@ +from functools import partial +from testgen import gen_test_struct, gen_res_str + +DEFAULT_N = 4 +test_funs = list() + +def test_push(is_str = False, n = DEFAULT_N): + buf = list() + for i in range(n): + if is_str: + buf.append("element at index " + str(i)) + else: + buf.append(i) + + name = "" + if is_str: + name = "str" + return gen_test_struct(f"tst_push{name}", f"guf_dbuf_test: push {name}", gen_res_str(buf)) + +test_funs.append(partial(test_push, False)) +test_funs.append(partial(test_push, True)) + + +def test_insert_empty_front(): + buf = list() + buf.insert(0, 3141) + return gen_test_struct("tst_insert_empty_front", "guf_dbuf_test: insert empty front", gen_res_str(buf)) + +test_funs.append(test_insert_empty_front) + +def test_insert_empty_back(): + buf = list() + buf.insert(1, 3141) + return gen_test_struct("tst_insert_empty_back", "guf_dbuf_test: insert empty back", gen_res_str(buf)) + +test_funs.append(test_insert_empty_back) + +def test_insert(is_str = False, n = DEFAULT_N): + buf = list() + for i in range(n): + if i % 7 == 0: + idx = len(buf) + elif i % 2 == 0: + idx = 1 + else: + assert(len(buf) > 0) + idx = len(buf) - 1 + if is_str: + buf.insert(idx, f"element at index {idx}") + else: + buf.insert(idx, i) + + if is_str: + start = "pi" * 64 + end = "euler" * 64 + else: + start = 3141 + end = 2718 + + buf.insert(0, start) + buf.insert(len(buf), end) + + buf.insert(1, start * 2) + buf.insert(len(buf) - 1, end * 2) + + name = "int" + if is_str: + name = "str" + + return gen_test_struct(f"tst_insert{name}", f"guf_dbuf_test: insert {name}", gen_res_str(buf)) + +test_funs.append(partial(test_insert, False)) +test_funs.append(partial(test_insert, True)) + +def test_erase(is_str = False, n = DEFAULT_N): + buf = list() + for i in range(n): + if is_str: + buf.append("element at index " + str(i)) + else: + buf.append(i) + + for i, elem in enumerate(buf): + if i % 2 == 0: + del elem + + name = "int" + if is_str: + name = "str" + + return gen_test_struct(f"tst_erase{name}", f"guf_dbuf_test: erase {name}", gen_res_str(buf)) + +test_funs.append(partial(test_erase, False)) +test_funs.append(partial(test_erase, True)) + +def test_erase_all(is_str = False, n = DEFAULT_N): + buf = list() + for i in range(n): + if is_str: + buf.append("element at index " + str(i)) + else: + buf.append(i) + + for i, elem in enumerate(buf): + del elem + + name = "int" + if is_str: + name = "str" + return gen_test_struct(f"tst_remove{name}", f"guf_dbuf_test: erase {name} all", gen_res_str(buf)) + +test_funs.append(partial(test_erase_all, False)) +test_funs.append(partial(test_erase_all, True)) + +def test_pop(is_str = False, n = DEFAULT_N): + buf = list() + + for i in range(n): + if is_str: + buf.append("element at index " + str(i)) + else: + buf.append(i) + + new_buf = list() + for i in range(len(buf)): + new_buf.append(buf.pop()) + + new_buf.append(len(buf)) + + name = "int" + if is_str: + name = "str" + return gen_test_struct(f"tst_pop{name}", f"guf_dbuf_test: pop {name}", gen_res_str(buf)) + +test_funs.append(partial(test_pop, False)) +test_funs.append(partial(test_pop, True)) + +def test_front_back(is_str = False, n = DEFAULT_N): + buf = list() + new_buf = list() + for i in range(n): + if is_str: + buf.append("element at index " + str(i)) + else: + buf.append(i) + + if i % 2: + new_buf.append(buf[0]) # front + else: + new_buf.append(buf[-1]) # back + + if is_str: + new_buf[0] = "first elem" + new_buf[-1] = "last elem" + else: + new_buf[0] = 12345 + new_buf[-1] = 54321 + + new_buf.append(len(new_buf)) + + name = "int" + if is_str: + name = "str" + return gen_test_struct(f"tst_front_back{name}", f"guf_dbuf_test: front() back() {name}", gen_res_str(new_buf)) + +test_funs.append(partial(test_front_back, False)) +test_funs.append(partial(test_front_back, True)) + +def test_at(is_str = False, n = DEFAULT_N): + buf = list() + for i in range(n): + if is_str: + buf.append("element at index " + str(i)) + else: + buf.append(i) + + new_buf = list() + for elem in reversed(buf): + new_buf.append(elem * 2) + + name = "int" + if is_str: + name = "str" + return gen_test_struct(f"tst_at{name}", f"guf_dbuf_test: at() {name}", gen_res_str(new_buf)) + +test_funs.append(partial(test_at, False)) +test_funs.append(partial(test_at, True)) + +def all_tests(): + return test_funs \ No newline at end of file diff --git a/doc/guf_dict-diagram.png b/doc/guf_dict-diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..7fac7693436c8737c875fc43573e4a2cdfd43c0d GIT binary patch literal 135986 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 agaj*Ilhd{!pGN@xNI#T+kbD2B*Z&0ti$4|s literal 0 HcmV?d00001 diff --git a/src/guf_assert.h b/src/guf_assert.h new file mode 100644 index 0000000..04f1b7a --- /dev/null +++ b/src/guf_assert.h @@ -0,0 +1,3 @@ +#include +#include + diff --git a/src/guf_common.c b/src/guf_common.c new file mode 100644 index 0000000..f26229d --- /dev/null +++ b/src/guf_common.c @@ -0,0 +1,177 @@ +#include "guf_common.h" +#include +#include + +#include "guf_dict.h" +#include "guf_str.h" + +bool guf_is_big_endian(void) +{ + unsigned i = 1; + const char *bytes = (const char*)&i; + return bytes[0] != 1; +} + +// typedef struct alloc_info { +// size_t num_alloc, num_free; +// } alloc_info; + + +// static bool init = false; +// static guf_dict alloc_table; +// static guf_dict pointer_cnt; + +// bool guf_alloc_init(void) +// { +// alloc_table = GUF_DICT_UNINITIALISED; +// pointer_cnt = GUF_DICT_UNINITIALISED; + +// guf_dict_kv_funcs alloc_info_funcs = GUF_DICT_FUNCS_NULL; +// alloc_info_funcs.type_size = sizeof(alloc_info); + +// bool success = guf_dict_init(&alloc_table, 64, &GUF_DICT_FUNCS_GUF_STR, &alloc_info_funcs); +// if (!success) { +// return false; +// } + +// guf_dict_kv_funcs void_ptr_funcs = GUF_DICT_FUNCS_NULL; +// void_ptr_funcs.type_size = sizeof(void*); + +// guf_dict_kv_funcs size_t_funcs = GUF_DICT_FUNCS_NULL; +// size_t_funcs.type_size = sizeof(size_t); + +// success = guf_dict_init(&pointer_cnt, 128, &void_ptr_funcs, &size_t_funcs); + +// if (!success) { +// return false; +// } + +// if (success) { +// init = true; +// } +// return success; +// } + +// static void track_alloc(void *ptr, const char *name) +// { +// if (!init) { +// return; +// } + +// if (guf_dict_contains_key(&pointer_cnt, &ptr)) { +// GUF_ASSERT_RELEASE(false); +// } else { +// size_t cnt = 1; +// bool succ = guf_dict_insert(&pointer_cnt, &ptr, &cnt, GUF_DICT_CPY_KEY_VAL); +// GUF_ASSERT_RELEASE(succ); +// } + +// guf_str name_str = guf_str_new_view_from_cstr(name); + +// if (guf_dict_contains_key(&alloc_table, &name_str)) { +// alloc_info *ai = guf_dict_get_val(&alloc_table, &name_str); +// GUF_ASSERT_RELEASE(ai); +// ai->num_alloc += 1; +// return; +// } else { +// guf_str new_str = guf_str_new(name); +// GUF_ASSERT_RELEASE(guf_str_is_valid(&new_str)); +// alloc_info ai = {.num_alloc = 1, .num_free = 0}; +// bool succ = guf_dict_insert(&alloc_table, &new_str, &ai, 0); +// GUF_ASSERT_RELEASE(succ); +// GUF_ASSERT(guf_dict_contains_key(&alloc_table, &name_str)); +// } +// } + +// static void track_free(void *ptr, const char *name) +// { +// if (!init) { +// return; +// } + +// GUF_ASSERT_RELEASE(guf_dict_contains_key(&pointer_cnt, &ptr)); + +// size_t *cnt = guf_dict_get_val(&pointer_cnt, &ptr); +// GUF_ASSERT_RELEASE(cnt); +// if (*cnt == 0) { +// fprintf(stderr, "Double free for %s\n", name); +// GUF_ASSERT_RELEASE(false); +// } else{ +// GUF_ASSERT(*cnt == 1); +// *cnt = 0; +// } + +// const guf_str name_str = guf_str_new_view_from_cstr(name); + +// if (guf_dict_contains_key(&alloc_table, &name_str)) { +// alloc_info *ai = guf_dict_get_val(&alloc_table, &name_str); +// GUF_ASSERT_RELEASE(ai); +// GUF_ASSERT_RELEASE(ai->num_alloc > 0); +// ai->num_free += 1; +// } +// } + +// void *guf_malloc(size_t size, const char *name) +// { +// void *ptr = malloc(size); +// if (!ptr) { +// return ptr; +// } + +// track_alloc(ptr, name); + +// return ptr; +// } + +// void *guf_calloc(size_t count, size_t size, const char *name) +// { +// void *ptr = calloc(count, size); +// if (!ptr) { +// return ptr; +// } + +// track_alloc(ptr, name); + +// return ptr; +// } + +// void *guf_realloc(void *ptr, size_t size, const char *name) +// { +// void *new_ptr = realloc(ptr, size); +// if (!ptr) { +// return new_ptr; +// } + +// track_alloc(ptr, name); + +// return new_ptr; +// } + +// void guf_free(void *ptr, const char *name) +// { +// if (!ptr) { +// return; +// } + +// track_free(ptr, name); +// free(ptr); + +// return; +// } + +// void guf_alloc_print(void) +// { +// if (!init) { +// printf("guf_alloc_print: guf_alloc not initialised\n"); +// return; +// } + +// printf("size: %zu\n", alloc_table.size); + +// for (guf_dict_iter it = guf_dict_iter_begin(&alloc_table); !guf_dict_iter_is_end(&it); guf_dict_iter_advance(&it)) { +// const guf_str *key = it.key; +// alloc_info *val = it.val; +// // printf("idx: %zu elem %zu\n", it.idx, it.elems_seen); +// printf("'%s':\n - %zu alloc(s)\n - %zu free(s)\n\n", guf_str_get_const_c_str(key), val->num_alloc, val->num_free); +// } +// } \ No newline at end of file diff --git a/src/guf_common.h b/src/guf_common.h new file mode 100644 index 0000000..ffd4f01 --- /dev/null +++ b/src/guf_common.h @@ -0,0 +1,72 @@ +#ifndef GUF_COMMON_H +#define GUF_COMMON_H +#include +#include +#include +#include "guf_assert.h" + +#define GUF_DICT_USE_32_BIT_HASH + +#ifdef GUF_DICT_USE_32_BIT_HASH + typedef uint32_t guf_hash_size_t; +#else + typedef uint64_t guf_hash_size_t; +#endif + +#define GUF_ASSERT(COND) assert(COND) +#define GUF_ASSERT_RELEASE(COND) do { \ + if (!(COND)) { \ + fprintf(stderr, "libguf release assertion failed: " #COND ", file " __FILE__ ", line %d\n", __LINE__); \ + exit(EXIT_FAILURE); \ + } \ +} while (0); + + +#define GUF_STATIC_BUF_SIZE(BUF) (sizeof((BUF)) / (sizeof((BUF)[0]))) + +#define GUF_ABS(X) ((X) >= 0 ? (X) : -(X)) +#define GUF_MIN(X, Y) ((X) <= (Y) ? (X) : (Y)) +#define GUF_MAX(X, Y) ((X) >= (Y) ? (X) : (Y)) +#define GUF_CLAMP(X, MIN, MAX) GUF_MAX(GUF_MIN((X), (MAX)), (MIN)) + +static inline bool guf_is_mul_overflow_size_t(size_t a, size_t b) +{ + size_t c = a * b; + return a != 0 && ((c / a) != b); +} + +static inline size_t guf_safe_mul_size_t(size_t a, size_t b) +{ + GUF_ASSERT_RELEASE(!guf_is_mul_overflow_size_t(a, b)); + return a * b; +} + +static inline bool guf_is_safe_size_calc(ptrdiff_t count, ptrdiff_t sizeof_elem) +{ + if (count < 0 || sizeof_elem <= 0) { + return false; + } + size_t size = guf_safe_mul_size_t(count, sizeof_elem); + return size <= PTRDIFF_MAX; +} + +static inline ptrdiff_t guf_safe_size_calc(ptrdiff_t count, ptrdiff_t sizeof_elem) +{ + GUF_ASSERT_RELEASE(count >= 0); + GUF_ASSERT_RELEASE(sizeof_elem > 0); + size_t size = guf_safe_mul_size_t(count, sizeof_elem); + GUF_ASSERT_RELEASE(size <= PTRDIFF_MAX); + return size; +} + +bool guf_is_big_endian(void); + +bool guf_alloc_init(void); +void *guf_malloc(size_t size, const char *name); +void *guf_calloc(size_t count, size_t size, const char *name); +void *guf_realloc(void *ptr, size_t size, const char *name); +void guf_free(void *ptr, const char *name); +void guf_alloc_print(void); + + +#endif diff --git a/src/guf_darr.h b/src/guf_darr.h new file mode 100644 index 0000000..2c81986 --- /dev/null +++ b/src/guf_darr.h @@ -0,0 +1,234 @@ +#ifndef GUF_DARR_H +#define GUF_DARR_H +#include +#include +#include +#include +#include "guf_assert.h" + +#define GUF_DARR_NEW_CAPACITY(CAP) ((CAP) * 2) +#define GUF_DARR_FOREACH(ARR, ELEM_T, ELEM_PTR) assert((ARR).capacity); for (ELEM_T *ELEM_PTR = (ARR).data, *end = (ARR).data + (ARR).size; ELEM_PTR != end; ++ELEM_PTR) + +// TODO: move and copy semantics? (TYPE vs pointer to TYPE); append_val vs append_ptr +// cpy makes only sense for ptrs or ref/handle types +#define GUF_DARR_DEFINE(TYPE, TYPENAME, ELEM_CPY, ELEM_FREE) \ +typedef struct guf_darr_##TYPENAME { \ + TYPE *data; \ + size_t size, capacity; \ + TYPE (*elem_cpy)(const TYPE elem); /* Can be NULL */ \ + void (*elem_free)(TYPE elem); /* Can be NULL */ \ +} guf_darr_##TYPENAME; \ +\ +bool guf_darr_##TYPENAME##_init(guf_darr_##TYPENAME *arr, size_t start_cap) { \ + assert(arr); \ + if (!arr) { \ + return false; \ + } \ + if (start_cap < 1) { \ + start_cap = 1; \ + } \ + const size_t buf_size = start_cap * sizeof(TYPE); \ + if (buf_size < start_cap) { /* Overflow */ \ + return false; \ + } \ + arr->data = malloc(buf_size); \ + if (!arr->data) { \ + arr->size = arr->capacity = 0; \ + return false; \ + } \ + arr->size = 0; \ + arr->capacity = start_cap; \ + arr->elem_cpy = ELEM_CPY; \ + arr->elem_free = ELEM_FREE; \ + return true; \ +}\ +\ +bool guf_darr_##TYPENAME##_append(guf_darr_##TYPENAME *arr, TYPE elem) { \ + bool valid = arr && arr->data && arr->capacity && arr->size <= arr->capacity; \ + assert(valid); \ + if (!valid) { \ + return false; \ + }\ + if (arr->size == arr->capacity) { \ + const size_t new_cap = GUF_DARR_NEW_CAPACITY(arr->capacity); \ + if (new_cap <= arr->capacity) { /* Overflow */ \ + return false; \ + } \ + const size_t buf_size = new_cap * sizeof(TYPE); \ + if (buf_size < new_cap) { /* Overflow */ \ + return false; \ + } \ + TYPE *data_new = realloc(arr->data, buf_size); \ + if (!data_new) { \ + return false; \ + } \ + arr->data = data_new; \ + arr->capacity = new_cap; \ + } \ + assert(arr->size < arr->capacity); \ + if (arr->elem_cpy) { \ + arr->data[arr->size++] = arr->elem_cpy(elem); \ + } else { \ + arr->data[arr->size++] = elem; \ + } \ + return true; \ +}\ +\ +bool guf_darr_##TYPENAME##_insert_at(guf_darr_##TYPENAME *arr, TYPE elem, size_t idx) { \ + bool valid = arr && arr->data && arr->capacity && arr->size <= arr->capacity; \ + assert(valid); \ + if (!valid) { \ + return false; \ + }\ + assert(idx < arr->size); \ + if (idx >= arr->size) { \ + return false; \ + } \ + assert(arr->size != 0); \ + if (arr->size == arr->capacity) { \ + const size_t new_cap = GUF_DARR_NEW_CAPACITY(arr->capacity); \ + if (new_cap <= arr->capacity) { /* Overflow */ \ + return false; \ + } \ + const size_t buf_size = new_cap * sizeof(TYPE); \ + if (buf_size < new_cap) { /* Overflow */ \ + return false; \ + } \ + TYPE *data_new = realloc(arr->data, buf_size); \ + if (!data_new) { \ + return false; \ + } \ + arr->data = data_new; \ + arr->capacity = new_cap; \ + } \ + assert(arr->size < arr->capacity); \ + const size_t new_last_idx = arr->size; \ + for (size_t i = new_last_idx; i > idx; --i) { \ + arr->data[i] = arr->data[i - 1]; \ + } \ + if (arr->elem_cpy) { \ + arr->data[idx] = arr->elem_cpy(elem); \ + } else { \ + arr->data[idx] = elem; \ + } \ + ++arr->size; \ + return true; \ +}\ +\ +void guf_darr_##TYPENAME##_pop_back(guf_darr_##TYPENAME *arr) { \ + bool valid = arr && arr->data && arr->capacity && arr->size <= arr->capacity; \ + assert(valid); \ + if (!valid) { \ + return; \ + }\ + if (arr->size == 0) { \ + return; \ + } \ + if (arr->elem_free) { \ + arr->elem_free(arr->data[arr->size - 1]); \ + } \ + --arr->size; \ +}\ +\ +TYPE *guf_darr_##TYPENAME##_back(const guf_darr_##TYPENAME *arr) { \ + bool valid = arr && arr->data && arr->capacity && arr->size <= arr->capacity; \ + assert(valid); \ + if (!valid) { \ + return NULL; \ + }\ + if (arr->size == 0) { \ + return NULL; \ + } \ + return arr->data + (arr->size - 1);\ +}\ +\ +TYPE *guf_darr_##TYPENAME##_front(const guf_darr_##TYPENAME *arr) { \ + bool valid = arr && arr->data && arr->capacity && arr->size <= arr->capacity; \ + assert(valid); \ + if (!valid) { \ + return NULL; \ + }\ + if (arr->size == 0) { \ + return NULL; \ + } \ + return arr->data + 0;\ +}\ +\ +TYPE *guf_darr_##TYPENAME##_at(const guf_darr_##TYPENAME *arr, size_t idx) { \ + bool valid = arr && arr->data && arr->capacity && arr->size <= arr->capacity; \ + assert(valid); \ + if (!valid) { \ + return NULL; \ + }\ + if (idx >= arr->size) { \ + return NULL; \ + } \ + assert(arr->size != 0); \ + return arr->data + idx; \ +}\ +\ +bool guf_darr_##TYPENAME##_erase_at(guf_darr_##TYPENAME *arr, size_t idx) { \ + bool valid = arr && arr->data && arr->capacity && arr->size <= arr->capacity; \ + assert(valid); \ + if (!valid) { \ + return false; \ + }\ + if (idx >= arr->size) { \ + return false; \ + } \ + assert(arr->size != 0); \ + if (arr->elem_free) { \ + arr->elem_free(arr->data[idx]); \ + } \ + if (idx == arr->size - 1) { \ + --arr->size; \ + return true; \ + }\ + if (idx + 1 < idx) { /* Overflow */ \ + return false; \ + } \ + for (size_t i = idx + 1; i < arr->size; ++i) { \ + arr->data[i - 1] = arr->data[i]; \ + } \ + --arr->size; \ + return true; \ +}\ +\ +bool guf_darr_##TYPENAME##_shrink_to_fit(guf_darr_##TYPENAME *arr) { \ + bool valid = arr && arr->data && arr->capacity && arr->size <= arr->capacity; \ + assert(valid); \ + if (!valid) { \ + return false; \ + }\ + if (arr->size == arr->capacity) { \ + return true; \ + }\ + const size_t new_cap = arr->size == 0 ? 1 : arr->size; \ + TYPE *data_new = realloc(arr->data, sizeof(TYPE) * new_cap); \ + if (!data_new) { \ + return false; \ + } \ + arr->data = data_new; \ + arr->capacity = new_cap; \ + return true; \ +}\ +\ +bool guf_darr_##TYPENAME##_free(guf_darr_##TYPENAME *arr) { \ + bool valid = arr && arr->data && arr->capacity && arr->size <= arr->capacity; \ + assert(valid); \ + if (!valid) { \ + return false; \ + }\ + if (arr->elem_free) { \ + for (size_t i = 0; i < arr->size; ++i) { \ + arr->elem_free(arr->data[i]); \ + } \ + } \ + free(arr->data); \ + arr->data = NULL; \ + arr->capacity = arr->size = 0; \ + return true; \ +}\ + +#endif + diff --git a/src/guf_dbuf.c b/src/guf_dbuf.c new file mode 100644 index 0000000..5f3731b --- /dev/null +++ b/src/guf_dbuf.c @@ -0,0 +1,260 @@ +#include +#include "guf_dbuf.h" +#include "guf_common.h" + +static inline bool dbuf_valid_and_not_empty(const guf_dbuf* dbuf) { + return dbuf && guf_obj_meta_sizeof_obj(dbuf->elem_meta) > 0 && dbuf->data && dbuf->capacity > 0 && dbuf->size > 0 && dbuf->size <= dbuf->capacity; +} + +static inline bool dbuf_valid_and_maybe_empty(const guf_dbuf* dbuf) { + GUF_ASSERT_RELEASE((!dbuf->data && !dbuf->capacity) || (dbuf->data && dbuf->capacity)); + return dbuf && guf_obj_meta_sizeof_obj(dbuf->elem_meta) > 0 && dbuf->capacity >= 0 && dbuf->size >= 0 && dbuf->size <= dbuf->capacity; +} + +bool guf_dbuf_reserve(guf_dbuf *dbuf, ptrdiff_t min_capacity) +{ + GUF_ASSERT_RELEASE(dbuf_valid_and_maybe_empty(dbuf)); + GUF_ASSERT_RELEASE(min_capacity >= 0); + + const ptrdiff_t sizeof_elem = guf_obj_meta_sizeof_obj(dbuf->elem_meta); + + if (min_capacity <= dbuf->capacity) { + return true; + } + + if (!dbuf->data) { + GUF_ASSERT_RELEASE(guf_is_safe_size_calc(min_capacity, sizeof_elem)); + void *data = calloc(min_capacity, sizeof_elem); + GUF_ASSERT(data); + if (!data) { + return false; + } + dbuf->data = data; + } else { + void *data = realloc(dbuf->data, guf_safe_size_calc(min_capacity, sizeof_elem)); + GUF_ASSERT(data); + if (!data) { + return false; + } + dbuf->data = data; + } + dbuf->capacity = min_capacity; + return true; +} + +bool guf_dbuf_init(guf_dbuf *dbuf, guf_obj_meta elem_meta, ptrdiff_t start_cap) +{ + GUF_ASSERT_RELEASE(dbuf); + GUF_ASSERT_RELEASE(start_cap >= 0); + + const ptrdiff_t sizeof_elem = guf_obj_meta_sizeof_obj(elem_meta); + GUF_ASSERT_RELEASE(sizeof_elem > 0); + dbuf->elem_meta = elem_meta; + + dbuf->size = dbuf->capacity = 0; + + if (start_cap == 0) { + dbuf->data = NULL; + return true; + } + + bool success = guf_dbuf_reserve(dbuf, start_cap); + if (success) { + dbuf->capacity = start_cap; + } + GUF_ASSERT(success); + return success; +} + +guf_dbuf guf_dbuf_new(guf_obj_meta elem_meta) +{ + guf_dbuf dbuf = {0}; + bool success = guf_dbuf_init(&dbuf, elem_meta, 0); + GUF_ASSERT_RELEASE(success); + return dbuf; +} + +guf_dbuf guf_dbuf_new_with_capacity(guf_obj_meta elem_meta, ptrdiff_t capacity) +{ + GUF_ASSERT_RELEASE(capacity >= 0); + guf_dbuf dbuf = {0}; + bool success = guf_dbuf_init(&dbuf, elem_meta, capacity); + GUF_ASSERT_RELEASE(success); + return dbuf; +} + +static inline void *get_elem(guf_dbuf *dbuf, ptrdiff_t idx) +{ + GUF_ASSERT_RELEASE(dbuf_valid_and_not_empty(dbuf)); + GUF_ASSERT_RELEASE(idx >= 0); + GUF_ASSERT_RELEASE(idx < dbuf->size && idx < dbuf->capacity); + char *ptr = (char*)dbuf->data; + return ptr + guf_safe_size_calc(idx, guf_obj_meta_sizeof_obj(dbuf->elem_meta)); +} + +static inline ptrdiff_t next_capacity(ptrdiff_t old_cap) +{ + GUF_ASSERT_RELEASE(old_cap >= 0); + size_t new_cap = 0; + if (old_cap == 0) { + new_cap = GUF_DBUF_INITIAL_CAP; + } else if (old_cap < 8) { + new_cap = (size_t)old_cap * 2ull; + } else { + new_cap = (size_t)old_cap * 3ull / 2ull; + } + GUF_ASSERT_RELEASE(new_cap > (size_t)old_cap); // Fail on overflow. + GUF_ASSERT_RELEASE(new_cap <= PTRDIFF_MAX); + return new_cap; +} + +static inline bool grow_if_full(guf_dbuf *dbuf) +{ + GUF_ASSERT_RELEASE(dbuf->capacity >= 0 && dbuf->size >= 0); + if (dbuf->size == dbuf->capacity) { + bool success = guf_dbuf_reserve(dbuf, next_capacity(dbuf->capacity)); + if (!success) { + return false; + } + } + GUF_ASSERT_RELEASE(dbuf->size < dbuf->capacity); + return true; +} + +static inline void *cpy_to(guf_dbuf *dbuf, ptrdiff_t idx, void *elem, guf_obj_cpy_opt cpy_opt) +{ + GUF_ASSERT_RELEASE(dbuf_valid_and_not_empty(dbuf)); + GUF_ASSERT_RELEASE(elem); + GUF_ASSERT_RELEASE(idx >= 0 && idx < dbuf->capacity && idx < dbuf->size); + + void *dst = get_elem(dbuf, idx); + dst = guf_cpy(dst, elem, dbuf->elem_meta, cpy_opt); + GUF_ASSERT_RELEASE(dst); + return dst; +} + +void *guf_dbuf_push(guf_dbuf *dbuf, void *elem, guf_obj_cpy_opt cpy_opt) +{ + GUF_ASSERT_RELEASE(dbuf_valid_and_maybe_empty(dbuf)); + + bool success = grow_if_full(dbuf); + GUF_ASSERT(success); + if (!success) { + return NULL; + } + + return cpy_to(dbuf, dbuf->size++, elem, cpy_opt); +} + +void *guf_dbuf_insert(guf_dbuf *dbuf, void *elem, ptrdiff_t idx, guf_obj_cpy_opt cpy_opt) +{ + GUF_ASSERT_RELEASE(dbuf_valid_and_maybe_empty(dbuf)); + GUF_ASSERT_RELEASE(idx >= 0 && idx <= dbuf->size); + + if (idx == dbuf->size) { + return guf_dbuf_push(dbuf, elem, cpy_opt); + } + GUF_ASSERT_RELEASE(idx < dbuf->size); + + bool success = grow_if_full(dbuf); + GUF_ASSERT(success); + if (!success) { + return NULL; + } + + for (ptrdiff_t free_idx = dbuf->size++; free_idx > idx; --free_idx) { + void *dst = get_elem(dbuf, free_idx); + void *src = get_elem(dbuf, free_idx - 1); + guf_cpy(dst, src, dbuf->elem_meta, GUF_CPY_VALUE); + } + return cpy_to(dbuf, idx, elem, cpy_opt); +} + +void guf_dbuf_erase(guf_dbuf *dbuf, ptrdiff_t idx) +{ + GUF_ASSERT_RELEASE(dbuf_valid_and_not_empty(dbuf)); + GUF_ASSERT_RELEASE(idx >= 0); + GUF_ASSERT_RELEASE(idx < dbuf->size); + + void *to_erase = get_elem(dbuf, idx); + if (dbuf->elem_meta.has_ops && dbuf->elem_meta.data.ops->free) { + dbuf->elem_meta.data.ops->free(to_erase); + } + + for (ptrdiff_t free_idx = idx; free_idx < dbuf->size - 1; ++free_idx) { + void *dst = get_elem(dbuf, free_idx); + void *src = get_elem(dbuf, free_idx + 1); + guf_cpy(dst, src, dbuf->elem_meta, GUF_CPY_VALUE); + } +} + +void *guf_dbuf_pop(guf_dbuf *dbuf) +{ + GUF_ASSERT_RELEASE(dbuf_valid_and_not_empty(dbuf)); + void *popped = get_elem(dbuf, dbuf->size - 1); + dbuf->size -= 1; + return popped; +} + +void *guf_dbuf_at(guf_dbuf *dbuf, ptrdiff_t idx) +{ + GUF_ASSERT_RELEASE(dbuf_valid_and_not_empty(dbuf)); + GUF_ASSERT_RELEASE(idx >= 0 && idx < dbuf->size) + return get_elem(dbuf, idx); +} + +void *guf_dbuf_front(guf_dbuf *dbuf) +{ + GUF_ASSERT_RELEASE(dbuf_valid_and_not_empty(dbuf)); + return get_elem(dbuf, 0); +} + +void *guf_dbuf_back(guf_dbuf *dbuf) +{ + GUF_ASSERT_RELEASE(dbuf_valid_and_not_empty(dbuf)); + return get_elem(dbuf, (ptrdiff_t)dbuf->size - 1); +} + +bool guf_dbuf_shrink_to_fit(guf_dbuf *dbuf) +{ + GUF_ASSERT_RELEASE(dbuf_valid_and_maybe_empty(dbuf)); + + const ptrdiff_t new_capacity = dbuf->size; + if (new_capacity == dbuf->capacity) { + return true; + } + + GUF_ASSERT_RELEASE(dbuf->data); + + void *data = realloc(dbuf->data, guf_safe_size_calc(new_capacity, guf_obj_meta_sizeof_obj(dbuf->elem_meta))); + GUF_ASSERT(data); + if (!data) { + return false; + } + dbuf->data = data; + dbuf->capacity = new_capacity; + return true; +} + +void guf_dbuf_free(guf_dbuf *dbuf) +{ + GUF_ASSERT_RELEASE(dbuf_valid_and_maybe_empty(dbuf)); + + if (dbuf->capacity == 0) { + GUF_ASSERT_RELEASE(!dbuf->data); + GUF_ASSERT_RELEASE(dbuf->size == 0); + return; + } + + GUF_ASSERT_RELEASE(dbuf->data); + + if (dbuf->elem_meta.has_ops && dbuf->elem_meta.data.ops->free) { + for (ptrdiff_t idx = 0; idx < dbuf->size; ++idx) { + // printf("freeing %s\n",*(char**)get_elem(dbuf, idx)); + dbuf->elem_meta.data.ops->free(get_elem(dbuf, idx)); + } + } + free(dbuf->data); + dbuf->data = NULL; + dbuf->capacity = dbuf->size = 0; +} diff --git a/src/guf_dbuf.h b/src/guf_dbuf.h new file mode 100644 index 0000000..8d77e0c --- /dev/null +++ b/src/guf_dbuf.h @@ -0,0 +1,81 @@ +#ifndef GUF_DBUF_H +#define GUF_DBUF_H + +#include +#include +#include +#include +#include "guf_assert.h" +#include "guf_obj.h" + +// Used for the first growth if dbuf->capacity is zero. +#define GUF_DBUF_INITIAL_CAP 8 + +typedef struct guf_dbuf { + bool is_init; + void *data; + ptrdiff_t size, capacity; + guf_obj_meta elem_meta; +} guf_dbuf; + +bool guf_dbuf_init(guf_dbuf *dbuf, guf_obj_meta elem_meta, ptrdiff_t start_cap); +guf_dbuf guf_dbuf_new_with_capacity(guf_obj_meta elem_meta, ptrdiff_t start_cap); +guf_dbuf guf_dbuf_new(guf_obj_meta elem_meta); + +void guf_dbuf_free(guf_dbuf *dbuf); + +bool guf_dbuf_reserve(guf_dbuf *dbuf, ptrdiff_t min_capacity); +bool guf_dbuf_shrink_to_fit(guf_dbuf *dbuf); + +void *guf_dbuf_push(guf_dbuf *dbuf, void *elem, guf_obj_cpy_opt cpy_opt); +void *guf_dbuf_insert(guf_dbuf *dbuf, void *elem, ptrdiff_t idx, guf_obj_cpy_opt cpy_opt); +void guf_dbuf_erase(guf_dbuf *dbuf, ptrdiff_t idx); +void *guf_dbuf_pop(guf_dbuf *dbuf); + +void *guf_dbuf_at(guf_dbuf *dbuf, ptrdiff_t idx); +void *guf_dbuf_front(guf_dbuf *dbuf); +void *guf_dbuf_back(guf_dbuf *dbuf); + +void guf_dbuf_sort(guf_dbuf *dbuf, void (*cmp)(const void *a, const void *b)); +void guf_dbuf_ascending(guf_dbuf *dbuf); +void guf_dbuf_descending(guf_dbuf *dbuf); + +// Convenience macros: +#define GUF_DBUF_NEW(elem_type) guf_dbuf_new((guf_obj_meta){.has_ops = false, .data.sizeof_obj = sizeof(elem_type)}) +#define GUF_DBUF_NEW_WITH_CAP(elem_type, capacity) guf_dbuf_new_with_capacity((guf_obj_meta){.has_ops = false, .data.sizeof_obj = sizeof(elem_type)}, capacity) + +// #define GUF_DBUF_NEW_FROM_OPS(obj_ops_ptr) guf_dbuf_new((guf_obj_meta){.has_ops = true, .data = obj_ops_ptr}) +// #define GUF_DBUF_NEW_FROM_OPS_WITH_CAP(obj_ops_ptr, capacity) guf_dbuf_new_with_capacity(-1, (obj_ops_ptr), (capacity)) + +#define GUF_DBUF_PUSH_VAL(dbuf_ptr, elem_type, elem_val) do { \ + elem_type tmp_lvalue = elem_val; \ + void *res = guf_dbuf_push(dbuf_ptr, &tmp_lvalue, GUF_CPY_VALUE); \ + GUF_ASSERT_RELEASE(res); \ +} while (0); \ + +#define GUF_DBUF_PUSH_VAL_CPY(dbuf_ptr, elem_type, elem_val) do { \ + elem_type tmp_lvalue = elem_val; \ + void *res = guf_dbuf_push(dbuf_ptr, &tmp_lvalue, GUF_CPY_DEEP); \ + GUF_ASSERT_RELEASE(res); \ +} while (0); \ + +#define GUF_DBUF_TRY_PUSH_VAL(dbuf_ptr, elem_type, elem_val, success_bool_name) do { \ + elem_type tmp_lvalue = elem_val; \ + void *res = guf_dbuf_push(dbuf_ptr, &tmp_lvalue, GUF_CPY_VALUE); \ + success_bool_name = res != NULL; \ +} while (0); \ + +#define GUF_DBUF_TRY_PUSH_VAL_CPY(dbuf_ptr, elem_type, elem_val, success_bool_name); do { \ + elem_type tmp_lvalue = elem_val; \ + void *res = guf_dbuf_push(dbuf_ptr, &tmp_lvalue, GUF_CPY_DEEP); \ + success_bool_name = res != NULL; \ +} while (0); \ + +#define GUF_DBUF_AT_VAL(dbuf_ptr, elem_type, idx) *(elem_type*)guf_dbuf_at(dbuf_ptr, idx) +#define GUF_DBUF_POP_VAL(dbuf_ptr, elem_type) *(elem_type*)guf_dbuf_pop(dbuf_ptr) +#define GUF_DBUF_LAST_VAL(dbuf_ptr, elem_type) *(elem_type*)guf_dbuf_back(dbuf_ptr) +#define GUF_DBUF_FIRST_VAL(dbuf_ptr, elem_type) *(elem_type*)guf_dbuf_front(dbuf_ptr) + +#define GUF_DBUF_FOREACH(DBUF, ELEM_TYPE, ELEM_PTR_NAME) for (ELEM_TYPE *ELEM_PTR_NAME = (ELEM_TYPE*)(DBUF).data, *end = ((ELEM_TYPE*)(DBUF).data) + (DBUF).size; ELEM_PTR_NAME != end; ++ELEM_PTR_NAME) + +#endif \ No newline at end of file diff --git a/src/guf_dict.c b/src/guf_dict.c new file mode 100755 index 0000000..ad26e69 --- /dev/null +++ b/src/guf_dict.c @@ -0,0 +1,649 @@ +// #include +// #include +// #include +// #include +// #include + +// #include "guf_common.h" +// #include "guf_dict.h" + +// /* +// FNV-1a (32 bit) hash function. +// Generally, you should always call csr_hash with GUF_HASH_INIT as the hash argument, unless you want to create "chains" of hashes. +// cf. http://www.isthe.com/chongo/tech/comp/fnv/ (last retrieved: 2023-11-30) +// */ + +// uint32_t guf_hash32(const void *data, size_t num_bytes, uint32_t hash) +// { +// GUF_ASSERT_RELEASE(data); +// const unsigned char *data_bytes = (const unsigned char*)data; // This does not break strict-aliasing rules I think... +// const uint32_t FNV_32_PRIME = 16777619ul; + +// for (size_t i = 0; i < num_bytes; ++i) { +// hash ^= data_bytes[i]; +// hash *= FNV_32_PRIME; +// } +// return hash; +// } + +// uint64_t guf_hash64(const void *data, size_t num_bytes, uint64_t hash) +// { +// GUF_ASSERT_RELEASE(data); +// const unsigned char *data_bytes = (const unsigned char*)data; // This does not break strict-aliasing rules I think... +// const uint64_t FNV_64_PRIME = 1099511628211ull; + +// for (size_t i = 0; i < num_bytes; ++i) { +// hash ^= data_bytes[i]; +// hash *= FNV_64_PRIME; +// } + +// return hash; +// } + +// static inline size_t find_next_power_of_two(size_t num) +// { +// GUF_ASSERT_RELEASE(num > 0); +// size_t pof2 = 1; +// while (pof2 < num) { +// GUF_ASSERT_RELEASE(pof2 * 2 > pof2); +// pof2 *= 2; +// } +// return pof2; +// } + +// static const guf_dict_kv_id KV_ID_NULL = GUF_DICT_HASH_MAX; +// static const guf_dict_kv_id KV_ID_TOMBSTONE = GUF_DICT_HASH_MAX - 1; +// static const guf_dict_kv_id KV_ID_MAX = GUF_DICT_HASH_MAX - 2; + +// const guf_dict GUF_DICT_UNINITIALISED = { +// .capacity_kv_status = 0, +// .size = 0, +// .num_tombstones = 0, +// .keys = NULL, +// .vals = NULL, +// .val_funcs = {.eq = NULL, .hash = NULL, .cpy = NULL, .move = NULL, .free = NULL, .type_size = 0}, +// .kv_status = NULL, +// .probe_t = 0, +// .max_load_fac_fx10 = 0, +// .max_probelen = 0, +// }; + +// static inline void *cpy_key(guf_dict *ht, void *dst, const void *src, bool default_cpy) +// { +// if (default_cpy || ht->key_funcs.cpy == NULL) { // Default copy. +// return memcpy(dst, src, ht->key_funcs.type_size); +// } else { +// return ht->key_funcs.cpy(dst, src); +// } +// } + +// static inline void *cpy_val(guf_dict *ht, void *dst, const void *src, bool default_cpy) +// { +// if (dst == NULL || src == NULL) { +// GUF_ASSERT_RELEASE(ht->val_funcs.type_size == 0); +// return NULL; +// } +// if (default_cpy || ht->val_funcs.cpy == NULL) { // Default copy. +// return memcpy(dst, src, ht->val_funcs.type_size); +// } else { +// return ht->val_funcs.cpy(dst, src); +// } +// } + +// static inline void *cpy_or_move_val(guf_dict *ht, void *dst, void *src, guf_dict_insert_opt opts) +// { +// if (dst == NULL || src == NULL) { +// GUF_ASSERT_RELEASE(ht->val_funcs.type_size == 0); +// return NULL; +// } +// if ((opts & GUF_DICT_MOVE_VAL)) { +// GUF_ASSERT_RELEASE(ht->val_funcs.move); +// return ht->val_funcs.move(dst, src); +// } else { // Default copy. +// return cpy_val(ht, dst, src, false); +// } +// } + +// static inline void *cpy_or_move_key(guf_dict *ht, void *dst, void *src, guf_dict_insert_opt opts) +// { +// if ((opts & GUF_DICT_MOVE_KEY)) { +// GUF_ASSERT_RELEASE(ht->key_funcs.move); +// return ht->key_funcs.move(dst, src); +// } else { +// return cpy_key(ht, dst, src, false); +// } +// } + +// static inline guf_hash_size_t key_hash(const guf_dict *ht, const void *key) +// { +// if (ht->key_funcs.hash) { +// return ht->key_funcs.hash(key); +// } else { // Default hash function. +// return guf_hash(key, ht->key_funcs.type_size, GUF_HASH_INIT); +// } +// } + + +// static inline bool kv_stat_occupied(guf_dict_kv_status kv_stat) { +// return kv_stat.kv_id != KV_ID_NULL && kv_stat.kv_id != KV_ID_TOMBSTONE; +// } + + + +// static void *key_get(guf_dict *ht, guf_dict_kv_id idx) +// { +// GUF_ASSERT_RELEASE(idx <= KV_ID_MAX); +// char *ptr = (char*)ht->keys; +// return ptr + idx * ht->key_funcs.type_size; + +// } + +// static void *val_get(guf_dict *ht, guf_dict_kv_id idx) +// { +// if (ht->val_funcs.type_size == 0) { +// return NULL; +// } +// GUF_ASSERT_RELEASE(idx <= KV_ID_MAX); + +// char *ptr = (char*)ht->vals; +// return ptr + idx * ht->val_funcs.type_size ; +// } + +// static inline bool key_eq(guf_dict *ht, const void *key_a, const void *key_b) +// { +// if (ht->key_funcs.eq) { +// return ht->key_funcs.eq(key_a, key_b); +// } else { // Default equality function. +// return 0 == memcmp(key_a, key_b, ht->key_funcs.type_size); +// } +// } + +// static inline bool key_eq_at(guf_dict *ht, size_t idx, const void *key, guf_hash_size_t hash_of_key) +// { +// GUF_ASSERT(idx < ht->capacity_kv_status); +// GUF_ASSERT(kv_stat_occupied(ht->kv_status[idx])); + +// if (ht->kv_status[idx].k_hash == hash_of_key) { // Hashes match -> we check if the keys are actually equal. +// return key_eq(ht, key_get(ht, ht->kv_status[idx].kv_id), key); +// } else { +// // Hashes don't match -> early exit (we save a memory load from ht->kv_elems). +// return false; +// } +// } + +// static inline size_t probe_offset(size_t probe_len, guf_dict_probe_type probe_t) +// { +// GUF_ASSERT(probe_len > 0); +// switch (probe_t) +// { +// case GUF_DICT_PROBE_LINEAR: +// default: +// return 1; +// case GUF_DICT_PROBE_QUADRATIC: +// /* +// Guaranteed to visit each index once for capacities which are powers of two. +// cf. https://fgiesen.wordpress.com/2015/02/22/triangular-numbers-mod-2n/ (last-retrieved 2024-07-29) +// */ +// return probe_len * (probe_len + 1) / 2; // 1, 3, 6, 10, 15, ... (starting from probe_len == 1) +// } +// } + +// static inline size_t mod_pow2(size_t a, size_t b) // a mod b (with b being a power of two.) +// { +// GUF_ASSERT(b > 0); +// return a & (b - 1); +// } + +// static size_t find_idx(guf_dict *ht, const void *key, bool *key_exists, bool find_first_free) +// { +// const guf_hash_size_t hash = key_hash(ht, key); +// size_t idx = mod_pow2(hash, ht->capacity_kv_status); // hash % ht->capacity +// const size_t start_idx = idx; +// size_t probe_len = 1; +// size_t first_tombstone_idx = SIZE_MAX; +// do { +// if (ht->kv_status[idx].kv_id == KV_ID_NULL) { // 1.) Empty. +// if (first_tombstone_idx != SIZE_MAX) { +// idx = first_tombstone_idx; +// } +// ht->max_probelen = GUF_MAX(probe_len, ht->max_probelen); +// GUF_ASSERT(!kv_stat_occupied(ht->kv_status[idx])); +// *key_exists = false; +// return idx; +// } else if (ht->kv_status[idx].kv_id == KV_ID_TOMBSTONE) { // 2.) Tombstone. +// if (first_tombstone_idx == SIZE_MAX) { +// first_tombstone_idx = idx; +// } +// if (find_first_free) { +// goto end; +// } else { +// goto probe; +// } +// } else if (key_eq_at(ht, idx, key, hash)) { // 3.) Key already exists. +// ht->max_probelen = GUF_MAX(probe_len, ht->max_probelen); +// GUF_ASSERT(kv_stat_occupied(ht->kv_status[idx])); +// *key_exists = true; +// return idx; +// } else { // 4.) Have to probe due to hash-collision (idx is already occupied, but not by the key). +// probe: +// idx = mod_pow2(idx + probe_offset(probe_len, ht->probe_t), ht->capacity_kv_status); +// ++probe_len; +// GUF_ASSERT_RELEASE(probe_len < UINT32_MAX); +// } +// } while (idx != start_idx); + +// end: +// if (first_tombstone_idx != SIZE_MAX) { // Edge case: No empty slots, but found tombstone. +// ht->max_probelen = GUF_MAX(probe_len, ht->max_probelen); +// GUF_ASSERT(!kv_stat_occupied(ht->kv_status[first_tombstone_idx])); +// *key_exists = false; +// return first_tombstone_idx; +// } + +// *key_exists = false; +// return SIZE_MAX; // Failed to find an idx. +// } + +// static void insert_kv(guf_dict *ht, void *key, void *val, size_t idx, guf_dict_insert_opt opts, bool default_cpy_key, bool default_cpy_val) +// { +// GUF_ASSERT_RELEASE(idx < ht->capacity_kv_status); +// GUF_ASSERT_RELEASE(ht->kv_status[idx].kv_id == KV_ID_NULL || ht->kv_status[idx].kv_id == KV_ID_TOMBSTONE); +// GUF_ASSERT_RELEASE(!kv_stat_occupied(ht->kv_status[idx])); + +// if (!default_cpy_key) { +// if (!cpy_or_move_key(ht, key_get(ht, idx), key, opts)) { +// cpy_key(ht, key_get(ht, idx), key, true); +// } +// } else { +// cpy_key(ht, key_get(ht, idx), key, true); +// } +// if (!default_cpy_val) { +// if (!cpy_or_move_val(ht, val_get(ht, idx), val, opts)) { +// cpy_val(ht, val_get(ht, idx), val, true); +// } +// } else { +// cpy_val(ht, val_get(ht, idx), val, true); +// } + +// if (ht->kv_status[idx].kv_id == KV_ID_TOMBSTONE) { +// GUF_ASSERT_RELEASE(ht->num_tombstones > 0); +// --ht->num_tombstones; +// } + +// ht->kv_status[idx].k_hash = key_hash(ht, key_get(ht, idx)); +// ++ht->size; +// } + +// static void update_v(guf_dict *ht, void *val, size_t idx, guf_dict_insert_opt opts) +// { +// GUF_ASSERT_RELEASE(idx < ht->capacity_kv_status); +// GUF_ASSERT_RELEASE(kv_stat_occupied(ht->kv_status[idx])); + +// if (ht->val_funcs.free) { +// ht->val_funcs.free(val_get(ht, idx)); +// } +// cpy_or_move_val(ht, val_get(ht, idx), val, opts); +// } + +// bool guf_dict_init(guf_dict *ht, size_t start_capacity, const guf_dict_kv_funcs *key_funcs, const guf_dict_kv_funcs *val_funcs) +// { +// GUF_ASSERT_RELEASE(ht); +// GUF_ASSERT_RELEASE(ht->capacity_kv_status == 0 && ht->size == 0 && ht->num_tombstones == 0 && ht->max_probelen == 0); +// GUF_ASSERT_RELEASE(ht->keys == NULL && ht->vals == NULL); + +// GUF_ASSERT_RELEASE(key_funcs && val_funcs); +// ht->key_funcs = *key_funcs; +// ht->val_funcs = *val_funcs; + +// if (val_funcs->type_size == 0) { +// // TODO: is a set! +// } +// if (start_capacity < 1) { +// start_capacity = 1; +// } + +// ht->capacity_kv_status = find_next_power_of_two(start_capacity); +// ht->size = ht->num_tombstones = 0; +// ht->max_probelen = 0; + +// GUF_ASSERT_RELEASE(ht->key_funcs.type_size > 0); +// // GUF_ASSERT_RELEASE(ht->val_funcs.type_size > 0); + +// ht->probe_t = GUF_DICT_PROBE_QUADRATIC; +// ht->max_load_fac_fx10 = GUF_DICT_MAX_LOAD_FAC_FX10_DEFAULT; +// GUF_ASSERT_RELEASE(ht->max_load_fac_fx10 != 0 && ht->max_load_fac_fx10 <= 1024); + +// ht->keys = calloc(ht->capacity_kv_status, ht->val_funcs.type_size); +// if (!ht->keys) { +// return false; +// } + +// ht->vals = NULL; +// if (ht->val_funcs.type_size > 0) { +// ht->vals = calloc(ht->capacity_kv_status, ht->val_funcs.type_size); +// if (!ht->vals) { +// free(ht->keys); +// return false; +// } +// } + +// ht->kv_status = calloc(ht->capacity_kv_status, sizeof(uint8_t)); +// if (!ht->kv_status) { +// free(ht->keys); +// free(ht->vals); +// return false; +// } +// for (size_t i = 0; i < ht->capacity_kv_status; ++i) { +// GUF_ASSERT(ht->kv_status[i].kv_id == KV_ID_NULL); +// } + +// return ht; +// } + +// bool guf_dict_insert(guf_dict *ht, void *key, void *val, guf_dict_insert_opt opts) +// { +// GUF_ASSERT_RELEASE(ht); +// GUF_ASSERT_RELEASE(ht->capacity_kv_status > 0 && ht->size <= ht->capacity_kv_status); +// GUF_ASSERT_RELEASE(ht->keys && ht->vals && ht->kv_status); + +// if ((opts & GUF_DICT_MOVE_KEY) && ht->key_funcs.move == NULL) { +// // Ignore -Wunused-value. +// fprintf(stderr, "guf_dict_insert: key_funcs.move is NULL while GUF_DICT_MOVE_KEY is set\n"); +// GUF_ASSERT(false); +// return false; +// } +// if ((opts & GUF_DICT_MOVE_VAL) && ht->val_funcs.move == NULL) { +// // Ignore -Wunused-value. +// fprintf(stderr, "guf_dict_insert: val_funcs.move is NULL while GUF_DICT_MOVE_VAL is set\n"); +// GUF_ASSERT(false); +// return false; +// } + +// if (guf_dict_load_factor_fx10(ht) > ht->max_load_fac_fx10 || ht->capacity_kv_status == ht->size) { // Handle growth: +// const size_t new_cap = 2 * ht->capacity_kv_status; // TODO: Limit to MAX_CAPACITY +// bool overflow = new_cap <= ht->capacity_kv_status; +// GUF_ASSERT(!overflow); +// if (overflow) { +// return false; +// } +// void *new_keys = calloc(new_cap, ht->key_funcs.type_size); +// if (!new_keys) { +// return false; +// } + +// void *new_vals = NULL; +// if (ht->val_funcs.type_size > 0) { +// new_vals = calloc(new_cap, ht->val_funcs.type_size); +// if (!new_vals) { +// free(new_keys); +// return false; +// } +// } else { +// GUF_ASSERT_RELEASE(ht->vals == NULL); +// } + +// guf_dict_kv_status *new_kv_status = calloc(new_cap, sizeof(guf_dict_kv_status)); // No realloc here! +// if (!new_kv_status) { +// free(new_keys); +// free(new_vals); +// return false; +// } +// for (size_t i = 0; i < new_cap; ++i) { +// new_kv_status[i].kv_id = KV_ID_NULL; +// new_kv_status[i].k_hash = 0; +// } + +// guf_dict ht_new = *ht; +// ht_new.kv_status = new_kv_status; +// ht_new.keys = new_keys; +// ht_new.vals = new_vals; +// ht_new.size = 0; +// ht_new.capacity_kv_status = new_cap; +// ht_new.max_probelen = 0; + +// size_t new_size = 0; +// for (size_t i = 0; i < ht->capacity_kv_status; ++i) { +// if (kv_stat_occupied(ht->kv_status[i])) { +// bool key_exists = false; +// const size_t new_idx = find_idx(&ht_new, key_get(ht, i), &key_exists, true); +// GUF_ASSERT_RELEASE(new_idx != SIZE_MAX); +// GUF_ASSERT_RELEASE(!key_exists); +// bool dumb_copy_key = ht->key_funcs.move == NULL; +// bool dumb_copy_val = ht->val_funcs.move == NULL; +// insert_kv(&ht_new, key_get(ht, i), val_get(ht, i), new_idx, GUF_DICT_MOVE_KEY | GUF_DICT_MOVE_VAL, dumb_copy_key, dumb_copy_val); +// ++new_size; +// } +// } +// GUF_ASSERT_RELEASE(new_size == ht->size); +// free(ht->kv_status); +// free(ht->keys); +// free(ht->vals); +// *ht = ht_new; +// } + +// bool key_exists = false; +// const size_t idx = find_idx(ht, key, &key_exists, true); +// GUF_ASSERT_RELEASE(idx != SIZE_MAX); +// GUF_ASSERT_RELEASE(!kv_stat_occupied(ht->kv_status[idx])); +// if (key_exists) { +// update_v(ht, val, idx, opts); +// } else { +// insert_kv(ht, key, val, idx, opts, false, false); +// } + +// return true; +// } + +// bool guf_dict_contains_key(const guf_dict *ht, const void *key) +// { +// GUF_ASSERT_RELEASE(ht); +// GUF_ASSERT_RELEASE(ht->capacity_kv_status > 0 && ht->size <= ht->capacity_kv_status); +// GUF_ASSERT_RELEASE (ht->keys && ht->vals && ht->kv_status); + +// bool key_exists = false; +// const size_t idx = find_idx((guf_dict*)ht, key, &key_exists, false); // TODO: const cast +// GUF_ASSERT_RELEASE(!(key_exists && idx == SIZE_MAX)); +// return key_exists; +// } + +// void *guf_dict_get_val(guf_dict *ht, const void *key) +// { +// GUF_ASSERT_RELEASE(ht); +// GUF_ASSERT_RELEASE(ht->capacity_kv_status > 0 && ht->size <= ht->capacity_kv_status); +// GUF_ASSERT_RELEASE(ht->keys && ht->vals && ht->kv_status); +// if (ht->capacity_kv_status == 0 || ht->size == 0) { +// return NULL; +// } +// bool key_exists = false; +// const size_t idx = find_idx(ht, key, &key_exists, false); +// GUF_ASSERT_RELEASE(idx != SIZE_MAX); + +// if (!key_exists) { +// return NULL; +// } else { +// GUF_ASSERT(kv_stat_occupied(ht->kv_status[idx])); +// return val_get(ht, idx); +// } +// } + +// bool guf_dict_remove(guf_dict *ht, const void *key) +// { +// GUF_ASSERT_RELEASE(ht); +// GUF_ASSERT_RELEASE(ht->capacity_kv_status > 0 && ht->size <= ht->capacity_kv_status); +// GUF_ASSERT_RELEASE(ht->keys && ht->vals && ht->kv_status); + +// if (ht->size == 0) { +// return false; +// } + +// bool key_exists = false; +// const size_t idx = find_idx(ht, key, &key_exists, false); +// if (!key_exists) { +// return false; +// } + +// if (ht->key_funcs.free) { +// ht->key_funcs.free(key_get(ht, idx)); +// } +// if (ht->val_funcs.free) { +// ht->val_funcs.free(val_get(ht, idx)); +// } +// ht->kv_status[idx].kv_id = KV_ID_TOMBSTONE; +// --ht->size; +// ++ht->num_tombstones; +// GUF_ASSERT(ht->size + ht->num_tombstones <= ht->capacity_kv_status); +// return true; +// } + +// uint32_t guf_dict_load_factor_fx10(const guf_dict *ht) +// { +// const uint64_t fx10_scale = 1024; // 2^10 (represents 1 in fx10 fixed point format). + +// GUF_ASSERT_RELEASE(ht->capacity_kv_status >= ht->size); + +// if (ht->capacity_kv_status == 0) { +// return 0; +// } +// size_t size = ht->size + ht->num_tombstones; +// // <=> size * fx_scale * fx_scale) / (ht->capacity * fx_scale) +// GUF_ASSERT(size <= ht->capacity_kv_status); +// uint64_t load_fac = (size * fx10_scale) / (uint64_t)(ht->capacity_kv_status); +// GUF_ASSERT_RELEASE(load_fac <= fx10_scale); +// return (uint32_t)load_fac; +// } + +// double guf_dict_load_factor_double(const guf_dict *ht) +// { +// GUF_ASSERT_RELEASE(ht->capacity_kv_status >= ht->size); + +// if (ht->capacity_kv_status == 0 || ht->capacity_kv_status == 0) { +// return 0.f; +// } + +// size_t size = ht->size + ht->num_tombstones; +// GUF_ASSERT(size <= ht->capacity_kv_status); + +// return size / (double)(ht->capacity_kv_status); +// } + +// void guf_dict_free(guf_dict *ht) +// { +// if (!ht) { +// return; +// } +// if (!ht->kv_status && !ht->keys && !ht->vals && ht->size == 0) { +// return; +// } + +// for (size_t i = 0; i < ht->size; ++i) { // TODO: ht->size, not ht->capacity ? +// if (ht->keys && ht->key_funcs.free) { +// ht->key_funcs.free(key_get(ht, i)); +// } +// if (ht->vals && ht->val_funcs.free) { +// ht->val_funcs.free(val_get(ht, i)); +// } +// } + + +// free(ht->keys); +// free(ht->vals); +// free(ht->kv_status); +// ht->keys = NULL; +// ht->vals = NULL; +// ht->kv_status = NULL; +// ht->capacity_kv_status = ht->size = ht->num_tombstones = ht->max_probelen = 0; +// *ht = GUF_DICT_UNINITIALISED; +// } + +// guf_dict_iter guf_dict_iter_begin(guf_dict *ht) +// { +// guf_dict_iter iter = {.elems_seen = ht->size + 1, .idx = 0, .ht = ht, .key = NULL, .val = NULL}; + +// if (ht->size == 0) { +// return iter; // end iter +// } + +// for (size_t idx = 0; idx < ht->capacity_kv_status; ++idx) { +// if (kv_stat_occupied(ht->kv_status[idx])) { +// iter.idx = idx; +// iter.elems_seen = 1; +// iter.key = key_get(iter.ht, iter.idx); +// iter.val = val_get(iter.ht, iter.idx); +// GUF_ASSERT_RELEASE(iter.key != NULL); +// return iter; +// } +// } + +// return iter; // end iter +// } + +// bool guf_dict_iter_is_end(guf_dict_iter *iter) +// { +// return iter->elems_seen == iter->ht->size + 1; +// } + +// void guf_dict_iter_advance(guf_dict_iter *iter) +// { +// if (guf_dict_iter_is_end(iter)) { +// return; +// } + +// if (iter->elems_seen == iter->ht->size) { +// ++iter->elems_seen; +// iter->key = NULL; +// iter->val = NULL; +// return; +// } + +// GUF_ASSERT_RELEASE(iter->elems_seen < iter->ht->size); +// GUF_ASSERT_RELEASE(iter->idx < iter->ht->capacity_kv_status); +// GUF_ASSERT_RELEASE(iter->key); + +// for (size_t idx = iter->idx + 1; idx < iter->ht->capacity_kv_status; ++idx) { +// if (kv_stat_occupied(iter->ht->kv_status[idx])) { +// iter->idx = idx; +// iter->key = key_get(iter->ht, iter->idx); +// iter->val = val_get(iter->ht, iter->idx); +// ++iter->elems_seen; +// return; +// } +// } +// GUF_ASSERT_RELEASE(false); +// iter->elems_seen = iter->ht->size + 1; +// } + + +// /* +// Removal of keys without tombstones (only would work for linear probing I think). + +// cf. https://stackoverflow.com/questions/9127207/hash-table-why-deletion-is-difficult-in-open-addressing-scheme/24886657#24886657 (last-retrieved 2024-07-26) +// The following del function from https://github.com/attractivechaos/klib/blob/6f73c80c6409d6f91cdf66ec1a002177274da2e7/cpp/khashl.hpp#L142-L150 (last-retrieved 2024-07-26) + +// int del(khint_t i) { +// khint_t j = i, k, mask, nb = n_buckets(); +// if (keys == 0 || i >= nb) return 0; +// mask = nb - khint_t(1); +// while (1) { +// j = (j + khint_t(1)) & mask; +// if (j == i || !__kh_used(used, j)) break; //j==i only when the table is completely full +// k = __kh_h2b(Hash()(keys[j]), bits); +// if (k <= i || k > j) +// keys[i] = keys[j], i = j; +// } +// __kh_set_unused(used, i); +// --count; +// return 1; +// } + +// cf. https://en.wikipedia.org/w/index.php?title=Hash_table&oldid=95275577 (last-retrieved 2024-07-26) +// Note: +// - For all records in a cluster, there must be no vacant slots between their natural hash position +// and their current position (else lookups will terminate before finding the record). + +// - i is a vacant slot that might be invalidating this property for subsequent records in the cluster. +// - j is such a subsequent record. +// - k is the raw hash where the record at j would naturally land in the hash table if there were no collisions. + +// - This test is asking if the record at j is invalidly positioned with respect +// to the required properties of a cluster now that i is vacant. +// */ diff --git a/src/guf_dict.h b/src/guf_dict.h new file mode 100755 index 0000000..7c6ac47 --- /dev/null +++ b/src/guf_dict.h @@ -0,0 +1,111 @@ +#ifndef GUF_DICT_H +#define GUF_DICT_H + +#include +#include +#include +#include "guf_common.h" + +typedef enum guf_dict_probe_type {GUF_DICT_PROBE_LINEAR = 0, GUF_DICT_PROBE_QUADRATIC} guf_dict_probe_type; + +// ~0.65 in fx10 fixed point (0.65 * 2^10) +#define GUF_DICT_MAX_LOAD_FAC_FX10_DEFAULT 666ul +#define GUF_DICT_PROBE_TYPE_DEFAULT GUF_DICT_PROBE_QUADRATIC + +#define GUF_HASH32_INIT 2166136261ul +#define GUF_HASH64_INIT 14695981039346656037ull +uint32_t guf_hash32(const void *data, size_t num_bytes, uint32_t hash); +uint64_t guf_hash64(const void *data, size_t num_bytes, uint64_t hash); + +#ifdef GUF_DICT_USE_32_BIT_HASH + #define GUF_HASH_INIT GUF_HASH32_INIT + static inline uint32_t guf_hash(const void *data, size_t num_bytes, uint32_t hash) { + return guf_hash32(data, num_bytes, hash); + } + #define GUF_DICT_HASH_MAX UINT32_MAX +#else + #define GUF_HASH_INIT GUF_HASH64_INIT + static inline uint64_t guf_hash(const void *data, size_t num_bytes, uint64_t hash) { + return guf_hash64(data, num_bytes, hash); + } + #define GUF_DICT_HASH_MAX UINT64_MAX +#endif + +typedef struct guf_dict_kv_funcs { + // Only used for keys: + bool (*eq)(const void *key_a, const void *key_b); // Can be NULL for keys and vals. Can be left uninitialised for vals. + guf_hash_size_t (*hash)(const void *key); // Can be NULL for keys and vals. Can be left uninitialised for vals. + + // Used for keys and vals: + void *(*cpy)(void *key_or_val_dst, const void *key_or_val_src); // Can be NULL for keys and vals. Never leave uninitialised. + void *(*move)(void *dst, void *key_or_val_src); // Can be NULL for keys and vals. Never leave uninitialised. + void (*free)(void *key_or_val); // Can be NULL for keys and vals. Never leave uninitialised. + size_t type_size; // Must always be set to sizeof(key_or_val). + +} guf_dict_kv_funcs; + +typedef guf_hash_size_t guf_dict_kv_id; + +typedef struct guf_dict_kv_status { + guf_dict_kv_id kv_id; + guf_hash_size_t k_hash; +} guf_dict_kv_status; + +typedef struct guf_dict { + guf_dict_kv_status *kv_status; + void *keys, *vals; + guf_dict_kv_funcs key_funcs, val_funcs; + guf_dict_probe_type probe_t; + uint32_t max_load_fac_fx10; + size_t size, capacity_kv_status, capacity_key_val, num_tombstones, max_probelen; +} guf_dict; + +extern const guf_dict GUF_DICT_UNINITIALISED; + +typedef enum guf_dict_insert_opt { + GUF_DICT_CPY_KEY_VAL = 0, + GUF_DICT_MOVE_KEY = 1, + GUF_DICT_MOVE_VAL = 2, +} guf_dict_insert_opt; + +bool guf_dict_init(guf_dict *ht, size_t start_capacity, const guf_dict_kv_funcs *key_funcs, const guf_dict_kv_funcs *val_funcs); +void guf_dict_free(guf_dict *ht); + +bool guf_dict_insert(guf_dict *ht, void *key, void *val); // bool copy_by_value +bool guf_dict_remove(guf_dict *ht, const void *key); + +void *guf_dict_get_val(guf_dict *ht, const void *key); +bool guf_dict_contains_key(const guf_dict *ht, const void *key); + +uint32_t guf_dict_load_factor_fx10(const guf_dict *ht); +double guf_dict_load_factor_double(const guf_dict *ht); +static inline uint32_t guf_dict_dbl_to_fx10_load_fac(double n) +{ + n = GUF_CLAMP(n, 0, 1); + const uint32_t fx10_scale = 1024; // 2^10 + return n * fx10_scale; +} + +typedef struct guf_dict_iter { + guf_dict *ht; + size_t idx; + size_t elems_seen; + const void *key; + void *val; +} guf_dict_iter; + +guf_dict_iter guf_dict_iter_begin(guf_dict *ht); +bool guf_dict_iter_is_end(guf_dict_iter *iter); +void guf_dict_iter_advance(guf_dict_iter *iter); + +extern const guf_dict_kv_funcs GUF_DICT_FUNCS_CSTR; +extern const guf_dict_kv_funcs GUF_DICT_FUNCS_GUF_STR; +extern const guf_dict_kv_funcs GUF_DICT_FUNCS_i32; +extern const guf_dict_kv_funcs GUF_DICT_FUNCS_i64; +extern const guf_dict_kv_funcs GUF_DICT_FUNCS_u32; +extern const guf_dict_kv_funcs GUF_DICT_FUNCS_u64; +extern const guf_dict_kv_funcs GUF_DICT_FUNCS_float; +extern const guf_dict_kv_funcs GUF_DICT_FUNCS_double; +extern const guf_dict_kv_funcs GUF_DICT_FUNCS_NULL; + +#endif \ No newline at end of file diff --git a/src/guf_dict_impls.c b/src/guf_dict_impls.c new file mode 100644 index 0000000..f513ec8 --- /dev/null +++ b/src/guf_dict_impls.c @@ -0,0 +1,348 @@ +#include +#include +#include +#include + +#include "guf_common.h" +#include "guf_dict.h" +#include "guf_str.h" + +// Guf_str +static bool guf_dict_eq_guf_str(const void *guf_str_a, const void *guf_str_b) +{ + return guf_str_equals((const guf_str*)guf_str_a, (const guf_str*)guf_str_b); +} + +static guf_hash_size_t guf_dict_hash_guf_str(const void *str) +{ + const char *c_str = guf_str_get_const_c_str_non_zero_term((const guf_str*)str); + size_t num_bytes = guf_str_len((const guf_str*)str); + return guf_hash(c_str, num_bytes, GUF_HASH_INIT); +} + +static void *guf_dict_cpy_guf_str(void *dst, const void *key_or_val) +{ + GUF_ASSERT_RELEASE(dst && key_or_val); + const guf_str *src_ptr = (guf_str*)key_or_val; + guf_str *dst_ptr = (guf_str*)dst; + guf_str cpy = guf_str_cpy(src_ptr, false); + *dst_ptr = cpy; + return dst_ptr; +} + +static void *guf_dict_move_guf_str(void *dst, void *key_or_val) +{ + GUF_ASSERT_RELEASE(dst && key_or_val); + if (guf_str_is_view(key_or_val) && !guf_str_is_stack_allocated(key_or_val)) { + printf("gufdictmove nulllllll\n"); + return NULL; + } + + guf_str *src_ptr = (guf_str*)key_or_val; + guf_str *dst_ptr = (guf_str*)dst; + guf_str moved = guf_str_move(src_ptr); + *dst_ptr = moved; + return dst_ptr; +} + +static void guf_dict_free_guf_str(void *key_or_val) +{ + GUF_ASSERT_RELEASE(key_or_val); + guf_str_free((guf_str*)key_or_val); +} + + + +// Regular cstr +static bool guf_dict_eq_cstr(const void *str_a, const void *str_b) +{ + return 0 == strcmp(*(const char**)str_a, *(const char**)str_b); +} + +static guf_hash_size_t guf_dict_hash_cstr(const void *str) +{ + size_t num_bytes = strlen(*(const char **)str); + return guf_hash(*(const char**)str, num_bytes, GUF_HASH_INIT); +} + +static void *guf_dict_cpy_cstr(void *dst, const void *key_or_val) +{ + GUF_ASSERT_RELEASE(dst && key_or_val); + char **dst_ptr = (char**)dst; + *dst_ptr = strdup(*(const char**)key_or_val); + GUF_ASSERT(*dst_ptr); + return dst_ptr; +} + +static void *guf_dict_move_cstr(void *dst, void *src) +{ + GUF_ASSERT_RELEASE(dst && src); + char **dst_ptr = (char**)dst; + char **src_ptr = (char**)src; + *dst_ptr = *src_ptr; + *src_ptr = NULL; + return dst_ptr; +} + +static void guf_dict_free_cstr(void *key_or_val) +{ + GUF_ASSERT_RELEASE(key_or_val); + free(*(char**)key_or_val); +} + + +// Signed ints. + +static guf_hash_size_t guf_dict_hash_i8(const void *n) +{ + if (!guf_is_big_endian()) { + return guf_hash(n, sizeof(int8_t), GUF_HASH_INIT); + } else { + const unsigned char *bytes = (const unsigned char*)n; + unsigned char bytes_reversed[sizeof(int8_t)]; + for (size_t i = 0; i < sizeof(bytes); ++i) { + bytes_reversed[i] = bytes[sizeof(bytes) - 1 - i]; + } + return guf_hash(bytes_reversed, sizeof(int8_t), GUF_HASH_INIT); + + } +} +static guf_hash_size_t guf_dict_hash_i16(const void *n) +{ + if (!guf_is_big_endian()) { + return guf_hash(n, sizeof(int16_t), GUF_HASH_INIT); + } else { + const unsigned char *bytes = (const unsigned char*)n; + unsigned char bytes_reversed[sizeof(int16_t)]; + for (size_t i = 0; i < sizeof(bytes); ++i) { + bytes_reversed[i] = bytes[sizeof(bytes) - 1 - i]; + } + return guf_hash(bytes_reversed, sizeof(int16_t), GUF_HASH_INIT); + + } +} +static guf_hash_size_t guf_dict_hash_i32(const void *n) +{ + if (!guf_is_big_endian()) { + return guf_hash(n, sizeof(int32_t), GUF_HASH_INIT); + } else { + const unsigned char *bytes = (const unsigned char*)n; + unsigned char bytes_reversed[sizeof(int32_t)]; + for (size_t i = 0; i < sizeof(bytes); ++i) { + bytes_reversed[i] = bytes[sizeof(bytes) - 1 - i]; + } + return guf_hash(bytes_reversed, sizeof(int32_t), GUF_HASH_INIT); + + } +} +static guf_hash_size_t guf_dict_hash_i64(const void *n) +{ + if (!guf_is_big_endian()) { + return guf_hash(n, sizeof(int64_t), GUF_HASH_INIT); + } else { + const unsigned char *bytes = (const unsigned char*)n; + unsigned char bytes_reversed[sizeof(int64_t)]; + for (size_t i = 0; i < sizeof(bytes); ++i) { + bytes_reversed[i] = bytes[sizeof(bytes) - 1 - i]; + } + return guf_hash(bytes_reversed, sizeof(int64_t), GUF_HASH_INIT); + + } +} + +// Unsigned ints. + +static guf_hash_size_t guf_dict_hash_u8(const void *n) +{ + if (!guf_is_big_endian()) { + return guf_hash(n, sizeof(uint8_t), GUF_HASH_INIT); + } else { + const unsigned char *bytes = (const unsigned char*)n; + unsigned char bytes_reversed[sizeof(uint8_t)]; + for (size_t i = 0; i < sizeof(bytes); ++i) { + bytes_reversed[i] = bytes[sizeof(bytes) - 1 - i]; + } + return guf_hash(bytes_reversed, sizeof(uint8_t), GUF_HASH_INIT); + } +} +static guf_hash_size_t guf_dict_hash_u16(const void *n) +{ + if (!guf_is_big_endian()) { + return guf_hash(n, sizeof(uint16_t), GUF_HASH_INIT); + } else { + const unsigned char *bytes = (const unsigned char*)n; + unsigned char bytes_reversed[sizeof(uint16_t)]; + for (size_t i = 0; i < sizeof(bytes); ++i) { + bytes_reversed[i] = bytes[sizeof(bytes) - 1 - i]; + } + return guf_hash(bytes_reversed, sizeof(uint16_t), GUF_HASH_INIT); + + } +} +static guf_hash_size_t guf_dict_hash_u32(const void *n) +{ + if (!guf_is_big_endian()) { + return guf_hash(n, sizeof(uint32_t), GUF_HASH_INIT); + } else { + const unsigned char *bytes = (const unsigned char*)n; + unsigned char bytes_reversed[sizeof(uint32_t)]; + for (size_t i = 0; i < sizeof(bytes); ++i) { + bytes_reversed[i] = bytes[sizeof(bytes) - 1 - i]; + } + return guf_hash(bytes_reversed, sizeof(uint32_t), GUF_HASH_INIT); + + } +} +static guf_hash_size_t guf_dict_hash_u64(const void *n) +{ + if (!guf_is_big_endian()) { + return guf_hash(n, sizeof(uint64_t), GUF_HASH_INIT); + } else { + const unsigned char *bytes = (const unsigned char*)n; + unsigned char bytes_reversed[sizeof(uint64_t)]; + for (size_t i = 0; i < sizeof(bytes); ++i) { + bytes_reversed[i] = bytes[sizeof(bytes) - 1 - i]; + } + return guf_hash(bytes_reversed, sizeof(uint64_t), GUF_HASH_INIT); + } +} + + +// Floats. + +static guf_hash_size_t guf_dict_hash_float(const void *n) +{ + if (!guf_is_big_endian()) { + return guf_hash(n, sizeof(float), GUF_HASH_INIT); + } else { + const unsigned char *bytes = (const unsigned char*)n; + unsigned char bytes_reversed[sizeof(float)]; + for (size_t i = 0; i < sizeof(bytes); ++i) { + bytes_reversed[i] = bytes[sizeof(bytes) - 1 - i]; + } + return guf_hash(bytes_reversed, sizeof(float), GUF_HASH_INIT); + } +} +static guf_hash_size_t guf_dict_hash_double(const void *n) +{ + if (!guf_is_big_endian()) { + return guf_hash(n, sizeof(double), GUF_HASH_INIT); + } else { + const unsigned char *bytes = (const unsigned char*)n; + unsigned char bytes_reversed[sizeof(double)]; + for (size_t i = 0; i < sizeof(bytes); ++i) { + bytes_reversed[i] = bytes[sizeof(bytes) - 1 - i]; + } + return guf_hash(bytes_reversed, sizeof(double), GUF_HASH_INIT); + } +} + +const guf_dict_kv_funcs GUF_DICT_FUNCS_CSTR = { + .type_size = sizeof(char*), + .eq = guf_dict_eq_cstr, + .hash = guf_dict_hash_cstr, + .cpy = guf_dict_cpy_cstr, + .move = guf_dict_move_cstr, + .free = guf_dict_free_cstr, +}; + +const guf_dict_kv_funcs GUF_DICT_FUNCS_GUF_STR = { + .type_size = sizeof(guf_str), + .eq = guf_dict_eq_guf_str, + .hash = guf_dict_hash_guf_str, + .cpy = guf_dict_cpy_guf_str, + .move = guf_dict_move_guf_str, + .free = guf_dict_free_guf_str, +}; + +const guf_dict_kv_funcs GUF_DICT_FUNCS_i8 = { + .type_size = sizeof(int8_t), + .eq = NULL, + .hash = guf_dict_hash_i8, + .cpy = NULL, + .move = NULL, + .free = NULL, +}; +const guf_dict_kv_funcs GUF_DICT_FUNCS_i16 = { + .type_size = sizeof(int16_t), + .eq = NULL, + .hash = guf_dict_hash_i16, + .cpy = NULL, + .move = NULL, + .free = NULL, +}; +const guf_dict_kv_funcs GUF_DICT_FUNCS_i32 = { + .type_size = sizeof(int32_t), + .eq = NULL, + .hash = guf_dict_hash_i32, + .cpy = NULL, + .move = NULL, + .free = NULL, +}; +const guf_dict_kv_funcs GUF_DICT_FUNCS_i64 = { + .type_size = sizeof(int64_t), + .eq = NULL, + .hash = guf_dict_hash_i64, + .cpy = NULL, + .move = NULL, + .free = NULL, +}; + +const guf_dict_kv_funcs GUF_DICT_FUNCS_u8 = { + .type_size = sizeof(uint8_t), + .eq = NULL, + .hash = guf_dict_hash_u8, + .cpy = NULL, + .move = NULL, + .free = NULL, +}; +const guf_dict_kv_funcs GUF_DICT_FUNCS_u16 = { + .type_size = sizeof(uint16_t), + .eq = NULL, + .hash = guf_dict_hash_u16, + .cpy = NULL, + .move = NULL, + .free = NULL, +}; +const guf_dict_kv_funcs GUF_DICT_FUNCS_u32 = { + .type_size = sizeof(uint32_t), + .eq = NULL, + .hash = guf_dict_hash_u32, + .cpy = NULL, + .move = NULL, + .free = NULL, +}; +const guf_dict_kv_funcs GUF_DICT_FUNCS_u64 = { + .type_size = sizeof(uint64_t), + .eq = NULL, + .hash = guf_dict_hash_u64, + .cpy = NULL, + .move = NULL, + .free = NULL, +}; + +const guf_dict_kv_funcs GUF_DICT_FUNCS_float = { + .type_size = sizeof(float), + .eq = NULL, + .hash = guf_dict_hash_float, + .cpy = NULL, + .move = NULL, + .free = NULL, +}; +const guf_dict_kv_funcs GUF_DICT_FUNCS_double = { + .type_size = sizeof(double), + .eq = NULL, + .hash = guf_dict_hash_double, + .cpy = NULL, + .move = NULL, + .free = NULL, +}; + +const guf_dict_kv_funcs GUF_DICT_FUNCS_NULL = { + .type_size = 0, + .eq = NULL, + .hash = NULL, + .cpy = NULL, + .move = NULL, + .free = NULL, +}; diff --git a/src/guf_obj.c b/src/guf_obj.c new file mode 100644 index 0000000..50c1afb --- /dev/null +++ b/src/guf_obj.c @@ -0,0 +1,74 @@ +#include "guf_obj.h" + +static int cstr_ptr_cmp(const void *a, const void *b){ + GUF_ASSERT_RELEASE(a && b); + // typeof dst/src: pointer to const char* (const char**) + const char **a_ptr = (const char**)a; + const char **b_ptr = (const char**)b; + return strcmp(*a_ptr, *b_ptr); +} + +static GUF_OBJ_DEFINE_CMP_DESC(cstr_ptr_cmp, cstr_ptr_cmp_desc) + +static bool cstr_ptr_eq(const void *a, const void *b) { + GUF_ASSERT_RELEASE(a && b); + return 0 == cstr_ptr_cmp(a, b); +} + +static void *cstr_default_init(void *dst) +{ + GUF_ASSERT_RELEASE(dst); + char **dst_ptr = (char**)dst; + *dst_ptr = NULL; + return dst; +} + +static void *cstr_cpy_init(void *dst, const void *src) +{ + GUF_ASSERT_RELEASE(dst && src); + char **dst_ptr = (char**)dst; + const char **src_ptr = (const char**)src; + char *cpy = strdup(*src_ptr); + GUF_ASSERT_RELEASE(cpy); + *dst_ptr = cpy; + return dst; +} + +void *cstr_move_init(void *dst, void *src) +{ + GUF_ASSERT_RELEASE(dst && src); + char **dst_ptr = (char**)dst; + char **src_ptr = (char**)src; + *dst_ptr = *src_ptr; + *src_ptr = NULL; + return dst; +} + +void cstr_ptr_free(void *ptr) +{ + GUF_ASSERT_RELEASE(ptr); + char **cstr_ptr = (char**)ptr; + free(*cstr_ptr); +} + +static const guf_obj_ops guf_cstr_ops = { + .sizeof_obj = sizeof(guf_cstr_type), + .cmp = cstr_ptr_cmp, + .cmp_desc = cstr_ptr_cmp_desc, + .eq = cstr_ptr_eq , + .default_init = cstr_default_init, + .copy_init = cstr_cpy_init, + .move_init = cstr_move_init, + .free = cstr_ptr_free, + .hash = NULL, +}; + +const guf_obj_meta guf_cstr_obj_meta = { + .has_ops = true, + .data.ops = &guf_cstr_ops +}; + +const guf_obj_meta guf_const_cstr_obj_meta = { + .has_ops = false, + .data.sizeof_obj = sizeof(guf_const_cstr_type) +}; \ No newline at end of file diff --git a/src/guf_obj.h b/src/guf_obj.h new file mode 100644 index 0000000..9c92b4d --- /dev/null +++ b/src/guf_obj.h @@ -0,0 +1,127 @@ +#ifndef GUF_OBJ_H +#define GUF_OBJ_H +#include +#include "guf_common.h" + +typedef enum guf_obj_cpy_opt { + GUF_CPY_VALUE = 0, + GUF_CPY_DEEP = 1, + GUF_CPY_MOVE = 2, +} guf_obj_cpy_opt; + +typedef struct guf_obj_ops { + ptrdiff_t sizeof_obj; + void *(*default_init)(void *dst_obj); // Default constructor. + void *(*copy_init)(void *dst_obj, const void *src_obj); // Copy constructor (deep copies src). + void *(*move_init)(void *dst_obj, void *src_obj); // Move constructor ("steals" from src). + void (*free)(void *obj); + bool (*eq)(const void *obj_a, const void *obj_b); + int (*cmp)(const void *obj_a, const void *obj_b); + int (*cmp_desc)(const void *obj_a, const void *obj_b); // Define with GUF_OBJ_DEFINE_CMP_DESC + guf_hash_size_t (*hash)(const void *obj); + const char *type_str; +} guf_obj_ops; + +#define GUF_OBJ_DEFINE_CMP_DESC(CMP_FN, CMP_DESC_FN_NAME) int CMP_DESC_FN_NAME(const void *a, const void *b) {return -CMP_FN(a, b);} + +typedef struct guf_obj_meta { + bool has_ops; + union { + const guf_obj_ops *ops; + ptrdiff_t sizeof_obj; + } data; +} guf_obj_meta; + +static inline ptrdiff_t guf_obj_meta_sizeof_obj(guf_obj_meta meta) +{ + ptrdiff_t size = meta.has_ops ? meta.data.ops->sizeof_obj : meta.data.sizeof_obj; + GUF_ASSERT_RELEASE(size > 0); + return size; +} + +static inline bool guf_obj_meta_same(guf_obj_meta a, guf_obj_meta b) { + if (a.has_ops != b.has_ops) { + return false; + } + if (a.has_ops) { + return a.data.ops == b.data.ops; + } else { + return a.data.sizeof_obj == b.data.sizeof_obj; + } +} + +static inline void *guf_cpy(void *dst_ptr, void *src_ptr, guf_obj_meta obj_meta, guf_obj_cpy_opt cpy_opt) +{ + GUF_ASSERT_RELEASE(dst_ptr); + GUF_ASSERT_RELEASE(src_ptr); + + if (obj_meta.has_ops) { + GUF_ASSERT_RELEASE(obj_meta.data.ops != NULL); + } + + const ptrdiff_t sizeof_obj = obj_meta.has_ops ? obj_meta.data.ops->sizeof_obj : obj_meta.data.sizeof_obj; + const guf_obj_ops *ops = obj_meta.has_ops ? obj_meta.data.ops : NULL; + GUF_ASSERT_RELEASE(sizeof_obj > 0); + + switch (cpy_opt) { + case GUF_CPY_VALUE: + dst_ptr = memcpy(dst_ptr, src_ptr, sizeof_obj); + GUF_ASSERT_RELEASE(dst_ptr); // This should never fail. + break; + case GUF_CPY_DEEP: + GUF_ASSERT_RELEASE(ops->copy_init != NULL); + dst_ptr = ops->copy_init(dst_ptr, src_ptr); + break; + case GUF_CPY_MOVE: + GUF_ASSERT_RELEASE(ops->move_init != NULL); + dst_ptr = ops->move_init(dst_ptr, src_ptr); + break; + default: + GUF_ASSERT_RELEASE(false); + } + GUF_ASSERT(dst_ptr); + return dst_ptr; +} + +static inline guf_obj_meta guf_obj_get_meta(void *obj) +{ + guf_obj_meta *meta_ptr = (guf_obj_meta*)obj; // IMPORTANT: Assumes the obj's first member is of type guf_obj_meta, otherwise this is undefined behaviour. + GUF_ASSERT_RELEASE(meta_ptr); + return *meta_ptr; +} + +static inline void *guf_obj_copy(void *dst_obj, void *src_obj, guf_obj_cpy_opt cpy_opts) +{ + guf_obj_meta meta = guf_obj_get_meta(dst_obj); + guf_obj_meta meta_src = guf_obj_get_meta(src_obj); + GUF_ASSERT_RELEASE(guf_obj_meta_same(meta, meta_src)); + return guf_cpy(dst_obj, src_obj, meta, cpy_opts); +} + +#define GUF_OBJ_META_NAME guf_obj_meta_member + +#define GUF_OBJ_DECLARE_OBJ_META() guf_obj_meta GUF_OBJ_META_NAME; +#define GUF_OBJ_GET_META_TYPESAFE(PTR) (PTR)->GUF_OBJ_META_NAME + +#define GUF_OBJ_FREE_TYPESAFE(ptr) if (GUF_OBJ_GET_META_TYPESAFE(ptr).has_ops && GUF_OBJ_GET_META_TYPESAFE(ptr).data.ops->free) {GUF_OBJ_GET_META_TYPESAFE(ptr).data.ops->free(ptr);} +#define GUF_OBJ_FREE(PTR) if (guf_obj_get_meta(PTR).has_ops && guf_obj_get_meta(PTR).data.ops->free) { guf_obj_get_meta(PTR).data.ops->free(PTR); } + +#define GUF_OBJ_LIFETIME_BLOCK_TYPESAFE(ptr, code_block) \ +do { \ + code_block \ + GUF_OBJ_FREE_TYPESAFE((ptr)); \ +} while (0); + +#define GUF_OBJ_LIFETIME_BLOCK(ptr, code_block) \ +do { \ + code_block \ + GUF_OBJ_FREE((ptr)); \ +} while (0); \ + + +typedef const char* guf_const_cstr_type; +typedef char* guf_cstr_type; +extern const guf_obj_meta guf_cstr_obj_meta; +extern const guf_obj_meta guf_const_cstr_obj_meta; + +#endif \ No newline at end of file diff --git a/src/guf_str.c b/src/guf_str.c new file mode 100644 index 0000000..375fb4b --- /dev/null +++ b/src/guf_str.c @@ -0,0 +1,756 @@ +#include +#include +#include +#include + +#include "guf_common.h" +#include "guf_str.h" + +static inline size_t capacity_grow(size_t size) +{ + return (size * 2); +} + +static inline void set_flag(guf_str *str, guf_str_state flag) +{ + GUF_ASSERT(str); + str->state |= flag; +} + +static inline void unset_flag(guf_str *str, guf_str_state flag) +{ + GUF_ASSERT(str); + str->state = str->state & (~flag); +} + +static inline bool has_state(const guf_str *str, guf_str_state flag) +{ + GUF_ASSERT(str); + return str->state & flag; +} + +static inline bool is_short(const guf_str *str) +{ + GUF_ASSERT(str); + return has_state(str, GUF_STR_STATE_SHORT); +} + +static inline void set_len(guf_str *str, size_t len) +{ + GUF_ASSERT(str); + if (is_short(str)) { + GUF_ASSERT(len <= GUF_STR_SSO_BUFSIZE); + str->data.stack.len = len; + } else { + GUF_ASSERT(len <= str->data.heap.capacity); + str->data.heap.len = len; + } +} + +static inline char *get_cstr(guf_str *str) +{ + GUF_ASSERT(str); + if (is_short(str)) { + GUF_ASSERT(str->data.stack.c_str); + return str->data.stack.c_str; + } else { + GUF_ASSERT(str->data.heap.c_str); + return str->data.heap.c_str; + } +} + +static inline const char *get_const_cstr(const guf_str *str) +{ + GUF_ASSERT(str); + if (is_short(str)) { + GUF_ASSERT(str->data.stack.c_str); + return str->data.stack.c_str; + } else { + GUF_ASSERT(str->data.heap.c_str); + return str->data.heap.c_str; + } +} + +static inline bool integrity_check(const guf_str *str) +{ + GUF_ASSERT(str); + bool good_len_cap = guf_str_len(str) <= guf_str_capacity(str); + + GUF_ASSERT(good_len_cap); + if (!good_len_cap) { + return false; + } + + const char *c_str = get_const_cstr(str); + GUF_ASSERT(c_str); + if (!c_str) { + return false; + } + bool good_null_term = c_str[guf_str_len(str)] == '\0'; + GUF_ASSERT(good_null_term); + + return good_len_cap && c_str != NULL && good_null_term; +} + +static inline bool handle_alloc_fail(const guf_str *str) +{ + GUF_ASSERT(str); + bool good_alloc = !has_state(str, GUF_STR_STATE_ALLOC_ERR); + #ifdef GUF_STR_ABORT_ON_ALLOC_FAILURE + GUF_ASSERT_RELEASE(good_alloc) + #else + GUF_ASSERT(good_alloc); + #endif + return good_alloc; +} + +bool guf_str_is_valid(const guf_str *str) +{ + bool not_null = str != NULL; + GUF_ASSERT(str); + if (!not_null) { + return false; + } + bool integrity = integrity_check(str); + GUF_ASSERT(integrity); + if (!integrity) { + return false; + } + bool good_alloc = handle_alloc_fail(str) ; + GUF_ASSERT(good_alloc); + return not_null && integrity && good_alloc; +} + +const guf_str GUF_STR_UNINITIALISED_FAILED_ALLOC = { + .state = GUF_STR_STATE_INIT | GUF_STR_STATE_SHORT | GUF_STR_STATE_ALLOC_ERR, + .data.stack.len = 0, + .data.stack.c_str = {'\0'} +}; + +const guf_str GUF_STR_UNINITIALISED = { + .state = GUF_STR_STATE_INIT | GUF_STR_STATE_SHORT, + .data.stack.len = 0, + .data.stack.c_str = {'\0'} +}; + +bool guf_str_alloc_success(const guf_str *str) +{ + bool fail = str->state & GUF_STR_STATE_ALLOC_ERR; + return !fail; +} + +// Creation: + +guf_str *guf_str_reserve(guf_str *str, size_t new_cap) +{ + GUF_ASSERT_RELEASE(guf_str_is_valid(str)); + + const size_t str_len = guf_str_len(str); + const size_t current_cap = guf_str_capacity(str); + + if (new_cap <= current_cap) { + return str; + } + GUF_ASSERT(new_cap > GUF_STR_SSO_BUFCAP); + GUF_ASSERT(new_cap + 1 > GUF_STR_SSO_BUFSIZE); + + if (is_short(str)) { // a) Was short string. + char tmp_buf[GUF_STR_SSO_BUFSIZE]; + GUF_ASSERT_RELEASE(GUF_STATIC_BUF_SIZE(tmp_buf) >= str_len + 1); + memcpy(tmp_buf, str->data.stack.c_str, str_len + 1); + + str->data.heap.c_str = calloc(new_cap + 1, sizeof(str->data.heap.c_str[0])); + if (!str->data.heap.c_str) { + set_flag(str, GUF_STR_STATE_ALLOC_ERR); + str->data.heap.capacity = str->data.heap.len = 0; + handle_alloc_fail(str); + return NULL; + } + str->data.heap.capacity = new_cap; + str->data.heap.len = str_len; + memcpy(str->data.heap.c_str, tmp_buf, str_len + 1); + return str; + } + // b) Was already heap allocated. + GUF_ASSERT_RELEASE(str->data.heap.c_str); + char *new_cstr = realloc(str->data.heap.c_str, new_cap + 1); + if (!new_cstr) { + set_flag(str, GUF_STR_STATE_ALLOC_ERR); + handle_alloc_fail(str); + return NULL; + } + str->data.heap.c_str = new_cstr; + str->data.heap.capacity = new_cap; + return str; +} + +guf_str guf_str_new(guf_str_view str_view) +{ + GUF_ASSERT(str_view.str); + if (!str_view.str) { + return GUF_STR_UNINITIALISED; + } + + guf_str str = GUF_STR_UNINITIALISED; + + // Temporary debug; TODO: remove + GUF_ASSERT_RELEASE(GUF_STATIC_BUF_SIZE(str.data.stack.c_str) == GUF_STR_SSO_BUFSIZE); + for (size_t i = 0; i < GUF_STATIC_BUF_SIZE(str.data.stack.c_str); ++i) { + GUF_ASSERT_RELEASE(str.data.stack.c_str[i] == '\0'); + } + + if (!guf_str_reserve(&str, str_view.len)) { + return str; + } + GUF_ASSERT_RELEASE(guf_str_capacity(&str) == str_view.len); + + char *c_str = get_cstr(&str); + memcpy(c_str, str_view.str, str_view.len); + c_str[str_view.len] = '\0'; + + GUF_ASSERT_RELEASE(guf_str_is_valid(&str)); + return str; +} + +guf_str guf_str_new_with_extra_cap(guf_str_view str_view, size_t extra_capacity) +{ + GUF_ASSERT(str_view.str); + if (!str_view.str) { + return GUF_STR_UNINITIALISED; + } + + guf_str str = GUF_STR_UNINITIALISED; + + // Temporary debug; TODO: remove + GUF_ASSERT_RELEASE(GUF_STATIC_BUF_SIZE(str.data.stack.c_str) == GUF_STR_SSO_BUFSIZE); + for (size_t i = 0; i < GUF_STATIC_BUF_SIZE(str.data.stack.c_str); ++i) { + GUF_ASSERT_RELEASE(str.data.stack.c_str[i] == '\0'); + } + + const size_t capacity = str_view.len + extra_capacity; + if (!guf_str_reserve(&str, capacity)) { + return str; + } + GUF_ASSERT_RELEASE(guf_str_capacity(&str) == capacity); + + char *c_str = get_cstr(&str); + memcpy(c_str, str_view.str, str_view.len); + c_str[str_view.len] = '\0'; + + GUF_ASSERT_RELEASE(guf_str_is_valid(&str)); + return str; +} + + +guf_str guf_str_new_from_cstr(const char *c_str) +{ + return guf_str_new(GUF_CSTR_TO_VIEW(c_str)); +} + +guf_str *guf_str_init(guf_str *str, guf_str_view str_view) +{ + GUF_ASSERT_RELEASE(str); + *str = guf_str_new(str_view); + bool fail = handle_alloc_fail(str); + if (!fail) { + GUF_ASSERT_RELEASE(guf_str_is_valid(str)); + return str; + } else { + return NULL; + } +} + +guf_str *guf_str_init_from_cstr(guf_str *str, const char* c_str) +{ + GUF_ASSERT_RELEASE(str); + *str = guf_str_new(GUF_CSTR_TO_VIEW(c_str)); + bool fail = handle_alloc_fail(str); + if (!fail) { + GUF_ASSERT_RELEASE(guf_str_is_valid(str)); + return str; + } else { + return NULL; + } +} + +guf_str guf_str_new_empty_with_capacity(size_t capacity) +{ + guf_str str = guf_str_new_from_cstr(""); + bool fail = handle_alloc_fail(&str); + if (!fail) { + guf_str_reserve(&str, capacity); + fail = handle_alloc_fail(&str); + } + GUF_ASSERT_RELEASE(guf_str_is_valid(&str)); + return str; +} + +guf_str *guf_str_init_empty_with_capacity(guf_str *str, size_t capacity) +{ + GUF_ASSERT_RELEASE(str); + *str = guf_str_new_empty_with_capacity(capacity); + bool fail = handle_alloc_fail(str); + if (!fail) { + GUF_ASSERT_RELEASE(guf_str_is_valid(str)); + return str; + } else { + return NULL; + } +} + +// Copying: + +guf_str guf_str_substr_cpy(guf_str_view str, size_t pos, size_t count) +{ + GUF_ASSERT(str.str); + + if (str.len == 0 || count == 0 || pos >= str.len || str.str == NULL) { + return guf_str_new_from_cstr(""); + } + + guf_str substr = GUF_STR_UNINITIALISED; + + const size_t substr_len = pos + count > str.len ? str.len - pos : count; + GUF_ASSERT(substr_len >= 1); + GUF_ASSERT(substr_len <= str.len); + GUF_ASSERT(substr_len <= count); + + if (!guf_str_reserve(&substr, substr_len)) { + return substr; + } + GUF_ASSERT_RELEASE(guf_str_capacity(&substr) == substr_len); + + char *c_str = get_cstr(&substr); + memcpy(c_str, str.str + pos, substr_len); + c_str[substr_len] = '\0'; + + GUF_ASSERT_RELEASE(guf_str_is_valid(&substr)); + return substr; +} + +guf_str_view guf_str_substr_view(guf_str_view str, size_t pos, size_t count) +{ + GUF_ASSERT(str.str); + + if (str.len == 0 || count == 0 || pos >= str.len || str.str == NULL) { + return (guf_str_view){.str = str.str, .len = 0}; + } + + const size_t substr_len = pos + count > str.len ? str.len - pos : count; + GUF_ASSERT(substr_len >= 1); + GUF_ASSERT(substr_len <= str.len); + + return (guf_str_view){.str = str.str + pos, .len = substr_len}; +} + +// Modifying: + +guf_str *guf_str_substr(guf_str* str, size_t pos, size_t count) +{ + GUF_ASSERT_RELEASE(guf_str_is_valid(str)); + const size_t len = guf_str_len(str); + const size_t cap = guf_str_capacity(str); + + const char *c_str = guf_str_const_cstr(str); + if (guf_str_len(str) == 0 || count == 0 || pos >= len || c_str == NULL) { + return str; + } + + const size_t substr_len = pos + count > len ? len - pos : count; + GUF_ASSERT(substr_len >= 1); + GUF_ASSERT(substr_len <= len); + + if (is_short(str)) { // a) Short string (stack). + GUF_ASSERT(pos + substr_len <= GUF_STR_SSO_BUFCAP); + str->data.stack.len = substr_len; + memcpy(str->data.stack.c_str, c_str + pos, substr_len); + str->data.stack.c_str[substr_len] = '\0'; + set_len(str, substr_len); + return str; + } + // b) Long string (heap) (Don't shrink capacity here). + GUF_ASSERT(pos + substr_len <= len && pos + substr_len <= cap); + size_t num_moved = 0; + for (size_t i = pos; i < pos + substr_len; ++i) { + str->data.heap.c_str[num_moved++] = str->data.heap.c_str[i]; + } + GUF_ASSERT(num_moved == len); + str->data.heap.c_str[len] = '\0'; + set_len(str, substr_len); + return str; +} + +guf_str *guf_str_append(guf_str *str, guf_str_view to_append) +{ + GUF_ASSERT_RELEASE(guf_str_is_valid(str)); + + const size_t str_len = guf_str_len(str); + const size_t total_len = str_len + to_append.len; + + if (to_append.len == 0) { + return str; + } + + if (guf_str_capacity(str) < total_len) { // The capacity of the destination string is too small -> grow. + str = guf_str_reserve(str, capacity_grow(total_len)); + GUF_ASSERT_RELEASE(str != NULL); + GUF_ASSERT_RELEASE(guf_str_capacity(str) >= total_len); + } + + char *dst_ptr = get_cstr(str); + const char *src_ptr = to_append.str; + size_t num_copied = 0; + for (size_t dst_idx = str_len; dst_idx < total_len; ++dst_idx) { + GUF_ASSERT(num_copied <= to_append.len); + GUF_ASSERT(dst_idx < guf_str_capacity(str)); + dst_ptr[dst_idx] = src_ptr[num_copied++]; + } + GUF_ASSERT_RELEASE(num_copied == to_append.len); + dst_ptr[total_len] = '\0'; + set_len(str, total_len); + return str; +} + +guf_str *guf_str_append_cstr(guf_str *str, const char *cstr_to_append) +{ + return guf_str_append(str, GUF_CSTR_TO_VIEW(cstr_to_append)); +} + + +guf_str *guf_str_shrink_to_fit(guf_str *str) +{ + GUF_ASSERT_RELEASE(guf_str_is_valid(str)); + + if (is_short(str)) { + return str; + } + + const size_t len = guf_str_len(str); + GUF_ASSERT(str->data.heap.c_str); + GUF_ASSERT(str->data.heap.capacity >= len); + + if (len == str->data.heap.capacity) { + return str; + } + + const size_t new_cap = len; + GUF_ASSERT(len <= new_cap); + + if (new_cap <= GUF_STR_SSO_BUFCAP) { // a) Short string. + char *src = str->data.heap.c_str; + GUF_ASSERT(src); + str->data.heap.c_str = NULL; + set_flag(str, GUF_STR_STATE_SHORT); + str->data.stack.len = len; + memcpy(str->data.stack.c_str, src, len); + str->data.stack.c_str[len] = '\0'; + free(src); + return str; + } else { // b) Long string. + char *new_cstr = realloc(str->data.heap.c_str, new_cap + 1); + GUF_ASSERT(new_cstr); + if (!new_cstr) { + set_flag(str, GUF_STR_STATE_ALLOC_ERR); + handle_alloc_fail(str); + return str; + } + str->data.heap.c_str = new_cstr; + str->data.heap.capacity = new_cap; + GUF_ASSERT(str->data.heap.c_str[len] == '\0'); + return str; + } +} + +char guf_str_pop_back(guf_str *str) +{ + GUF_ASSERT_RELEASE(guf_str_is_valid(str)); + + const size_t len = guf_str_len(str); + if (len == 0) { + return '\0'; + } + char *last_char = guf_str_at(str, len - 1); + GUF_ASSERT(last_char); + char popped = *last_char; + *last_char = '\0'; + set_len(str, len - 1); + return popped; +} + +char guf_str_pop_front(guf_str *str) +{ + GUF_ASSERT_RELEASE(guf_str_is_valid(str)); + + const size_t len = guf_str_len(str); + if (len == 0) { + return '\0'; + } + + char *first_char = guf_str_at(str, 0); + GUF_ASSERT(first_char); + char popped = *first_char; + + char *c_str = get_cstr(str); + for (size_t dst_idx = 0; dst_idx < len; ++dst_idx) { // Move the remaining string to the left. + GUF_ASSERT(dst_idx + 1 <= len + 1); + c_str[dst_idx] = c_str[dst_idx + 1]; + } + GUF_ASSERT_RELEASE(c_str[len - 1] == '\0'); + set_len(str, len - 1); + return popped; +} + + +// Non-modifying: + +// The size (in chars) without the final null-terminator. +size_t guf_str_len(const guf_str *str) +{ + GUF_ASSERT_RELEASE(str); + GUF_ASSERT_RELEASE(integrity_check(str)); + + if (is_short(str)) { + return str->data.stack.len; + } else { + GUF_ASSERT_RELEASE(str->data.heap.capacity > GUF_STR_SSO_BUFCAP); + return str->data.heap.len; + } +} + +size_t guf_str_capacity(const guf_str *str) +{ + GUF_ASSERT_RELEASE(str); + GUF_ASSERT_RELEASE(integrity_check(str)); + + if (is_short(str)) { + return GUF_STR_SSO_BUFCAP; + } else { + // GUF_ASSERT(str->data.heap.capacity > GUF_STR_SSO_BUFCAP); // TODO: Not sure... + return str->data.heap.capacity; + } +} + +bool guf_str_view_equal(guf_str_view a, guf_str_view b) +{ + GUF_ASSERT_RELEASE(a.str && b.str); + if (a.len != b.len) { + return false; + } + return 0 == memcmp(a.str, b.str, a.len); +} + +bool guf_str_equal(const guf_str *a, const guf_str *b) +{ + GUF_ASSERT_RELEASE(guf_str_is_valid(a) && guf_str_is_valid(b)); + return guf_str_view_equal(GUF_STR_TO_VIEW(a), GUF_STR_TO_VIEW(b)); +} + +bool guf_str_equals_cstr(const guf_str *a, const char *c_str) +{ + GUF_ASSERT_RELEASE(a && c_str); + GUF_ASSERT_RELEASE(guf_str_is_valid(a)); + return guf_str_view_equal(GUF_STR_TO_VIEW(a), GUF_CSTR_TO_VIEW(c_str)); +} + +bool guf_str_equals_strview(const guf_str *a, guf_str_view b) +{ + GUF_ASSERT_RELEASE(a && b.str); + GUF_ASSERT_RELEASE(guf_str_is_valid(a)); + return guf_str_view_equal(GUF_STR_TO_VIEW(a), b); +} + +int guf_str_view_cmp(const void *str_view_a, const void *str_view_b) +{ // For qsort etc. + GUF_ASSERT_RELEASE(str_view_a && str_view_b); + const guf_str_view *a = (const guf_str_view*)str_view_a; + const guf_str_view *b = (const guf_str_view*)str_view_b; + GUF_ASSERT_RELEASE(a->str && b->str); + + if (a->len != b->len) { + return false; + } + return memcmp(a->str, b->str, a->len); +} + +bool guf_str_is_stack_allocated(const guf_str *str) +{ + GUF_ASSERT_RELEASE(str); + return is_short(str); +} + +// Indexing operations: + +const char *guf_str_const_cstr(const guf_str *str) +{ + return get_const_cstr(str); +} + +char *guf_str_cstr(guf_str *str) +{ + return get_cstr(str); +} + +char *guf_str_at(guf_str *str, size_t idx) +{ + GUF_ASSERT_RELEASE(guf_str_is_valid(str)); + + GUF_ASSERT(idx < guf_str_len(str)); + if (idx >= guf_str_len(str)) { + return NULL; + } + char *c_str = get_cstr(str); + GUF_ASSERT(c_str != NULL); + return c_str + idx; +} + +char *guf_str_back(guf_str *str) +{ + GUF_ASSERT_RELEASE(guf_str_is_valid(str)); + + const size_t len = guf_str_len(str); + GUF_ASSERT_RELEASE(len > 0); + GUF_ASSERT_RELEASE(len < guf_str_capacity(str)); + return guf_str_at(str, len - 1); +} + +char *guf_str_front(guf_str *str) +{ + GUF_ASSERT_RELEASE(guf_str_is_valid(str)); + + const size_t len = guf_str_len(str); + GUF_ASSERT_RELEASE(len > 0); + return guf_str_at(str, 0); +} + + +// Destruction: + +void guf_str_free(guf_str *str) +{ + GUF_ASSERT(integrity_check(str)); + + if (is_short(str)) { + GUF_ASSERT_RELEASE(str->data.stack.len <= GUF_STR_SSO_BUFCAP); + str->data.stack.len = 0; + str->data.stack.c_str[0] = '\0'; + return; + } + // GUF_ASSERT_RELEASE(str->data.heap.capacity > GUF_STR_SSO_BUFCAP); + if (str->data.heap.c_str) { + free(str->data.heap.c_str); + str->data.heap.c_str = NULL; + } + set_flag(str, GUF_STR_STATE_SHORT); + str->data.stack.len = 0; + str->data.stack.c_str[0] = '\0'; +} + +// UTF-8 + +bool guf_str_char_is_ascii(char c) +{ + return c >= 0 && c <= 127; +} + +bool guf_str_is_ascii(const guf_str *str) +{ + const char *c_str = get_const_cstr(str); + for (size_t i = 0; i < guf_str_len(str); ++i) { + if (!guf_str_char_is_ascii(c_str[i])) { + return false; + } + } + GUF_ASSERT(c_str[guf_str_len(str)] == '\0'); + return true; +} + +typedef struct guf_str_codepoint_utf8 { + unsigned char num_bytes; + unsigned char bytes[5]; + bool valid; +} guf_str_codepoint_utf8; + + +bool guf_str_iter_done(const guf_str_codepoint_utf8 *cp) +{ + return cp->valid && cp->num_bytes == 1 && cp->bytes[0] == '\0'; +} + +guf_str_codepoint_utf8 guf_str_iterate_utf8(const guf_str *str, size_t *idx) +{ + GUF_ASSERT(idx); + const char *c_str = get_const_cstr(str); + size_t len = guf_str_len(str); + + guf_str_codepoint_utf8 cp = {.num_bytes = 1, .bytes = {'\0', '\0', '\0', '\0', '\0'}, .valid = true}; + + const unsigned char four_bytes_mask = 240; + const unsigned char three_bytes_mask = 224; + const unsigned char two_bytes_mask = 192; + + size_t i = *idx; + if (guf_str_char_is_ascii(c_str[i])) { + cp.num_bytes = 1; + cp.bytes[0] = c_str[i]; + *idx = i + 1; + if (i == len) { + GUF_ASSERT(c_str[i] == '\0'); + } + return cp; + } + else if ((unsigned char)c_str[i] & four_bytes_mask) { + cp.num_bytes = 4; + if (i + cp.num_bytes >= len - 1) { + cp.valid = false; + return cp; + } + } + else if ((unsigned char)c_str[i] & three_bytes_mask) { + cp.num_bytes = 3; + if (i + cp.num_bytes >= len - 1) { + cp.valid = false; + return cp; + } + } + else if ((unsigned char)c_str[i] & two_bytes_mask) { + cp.num_bytes = 2; + if (i + cp.num_bytes >= len - 1) { + cp.valid = false; + return cp; + } + } + else { + cp.valid = false; + return cp; + } + + cp.bytes[0] = c_str[i]; + for (size_t j = 1; j < cp.num_bytes; ++j) { + size_t id = i + j; + assert(id < len); + unsigned char byte = c_str[id]; + if (byte >= 128 && byte < 192) { // Binary: 10...... + cp.bytes[id] = byte; + } else { + cp.valid = false; + return cp; + } + } + *idx = i + cp.num_bytes; + return cp; +} + +// Length without null-terminator. +size_t guf_str_len_utf8(const guf_str *str) +{ + size_t idx = 0; + size_t n = 0; + + for (guf_str_codepoint_utf8 cp = guf_str_iterate_utf8(str, &idx); !guf_str_iter_done(&cp); cp = guf_str_iterate_utf8(str, &idx)) { + ++n; + } + assert(n >= 1); + return n - 1; +} + +// guf_str_tokenise (const guf_str *str, const char *delims, const char *preserved_delims, ) diff --git a/src/guf_str.h b/src/guf_str.h new file mode 100644 index 0000000..b1e230e --- /dev/null +++ b/src/guf_str.h @@ -0,0 +1,136 @@ +#ifndef GUF_STR_H +#define GUF_STR_H + +#include +#include + +#include "guf_common.h" +#include "guf_obj.h" + +#define GUF_STR_ABORT_ON_ALLOC_FAILURE 1 + +// TODO: don't allocate self but take allocator? + +typedef enum guf_str_state { + GUF_STR_STATE_INIT = 0, + GUF_STR_STATE_SHORT = 1, + GUF_STR_STATE_ALLOC_ERR = 2 +} guf_str_state; + +typedef struct guf_str { + guf_str_state state; + 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_str; + +typedef struct guf_str_view { + const char *str; + size_t len; +} guf_str_view; + +#define GUF_CSTR_TO_VIEW(C_STR_PTR) ((guf_str_view){.str = (C_STR_PTR), .len = strlen((C_STR_PTR))}) +#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))}) + +extern const guf_str GUF_STR_UNINITIALISED; +extern const guf_str GUF_STR_UNINITIALISED_FAILED_ALLOC; + +// TODO: find_first_of and tokenise -> for parsing, see aoclib. + +// Creation: +guf_str *guf_str_init(guf_str *str, guf_str_view str_view); +guf_str *guf_str_init_from_cstr(guf_str *str, const char* c_str); +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 guf_str_new(guf_str_view str_view); +guf_str guf_str_new_from_cstr(const char *c_str); +guf_str guf_str_new_empty_with_capacity(size_t capacity); + +// Destruction: +void guf_str_free(guf_str *str); + +// Modification: +guf_str *guf_str_append(guf_str *str, guf_str_view to_append); +guf_str *guf_str_append_cstr(guf_str *str, const char *cstr_to_append); +guf_str *guf_str_substr(guf_str* str, size_t pos, size_t count); + +guf_str *guf_str_reserve(guf_str *str, size_t bufsize); +guf_str *guf_str_shrink_capacity(guf_str *str, size_t shrink_trigger_fac, bool shrink_exact); + +char guf_str_pop_back(guf_str *str); +char guf_str_pop_front(guf_str *str); + +// Copying and viewing: +guf_str guf_str_substr_cpy(guf_str_view str, size_t pos, size_t count); +guf_str_view guf_str_substr_view(guf_str_view str, size_t pos, size_t count); + +// Indexing: +char *guf_str_at(guf_str *str, size_t idx); +char *guf_str_back(guf_str *str); +char *guf_str_front(guf_str *str); +const char *guf_str_const_cstr(const guf_str *str); + +// Metadata retrieval: +size_t guf_str_len(const guf_str *str); // The size (in chars) without the final zero-terminator (size - 1). +size_t guf_str_capacity(const guf_str *str); +bool guf_str_is_stack_allocated(const guf_str *str); +bool guf_str_is_valid(const guf_str *str); +bool guf_str_alloc_success(const guf_str *str); + +// Comparison: +bool guf_str_view_equal(guf_str_view a, guf_str_view b); +bool guf_str_equal(const guf_str *a, const guf_str *b); +bool guf_str_equals_cstr(const guf_str *a, const char *c_str); +bool guf_str_equals_strview(const guf_str *a, guf_str_view b); +int guf_str_view_cmp(const void *str_view_a, const void *str_view_b); // For qsort etc. + +// UTF-8 operations. +bool guf_str_char_is_ascii(char c); +bool guf_str_is_ascii(const guf_str *str); + +// TODO: +// typedef struct guf_str_pool_elem { +// guf_str str; +// bool in_use; +// struct guf_str_pool_elem *next_free; +// } guf_str_pool_elem; + +// typedef struct guf_str_pool { +// guf_str_pool_elem *elems; +// size_t capacity, num_in_use; +// size_t min_str_bufsize; +// guf_str_pool_elem *first_free; +// } guf_str_pool; + +// void guf_str_pool_init(guf_str_pool *pool, size_t capacity, size_t str_initial_size) +// { +// if (capacity < 1) { +// capacity = 1; +// } +// pool->num_in_use = 0; +// pool->capacity = capacity; +// pool->elems = calloc(capacity, sizeof(guf_str_pool_elem)); +// pool->min_str_bufsize = str_initial_size; +// GUF_ASSERT_RELEASE(pool->elems); + +// pool->first_free = pool->elems + 0; +// for (size_t i = 0; i < pool->capacity; ++i) { +// pool->elems[i].in_use = false; +// pool->elems[i].str = guf_str_new_empty_with_capacity(pool->min_str_bufsize); +// pool->elems[i].next_free = i + 1 < pool->capacity ? pool->elems + i + 1 : NULL; +// } +// } + +// find_free and find_free_with_cap + +#endif \ No newline at end of file diff --git a/src/guf_test.c b/src/guf_test.c new file mode 100644 index 0000000..be36a58 --- /dev/null +++ b/src/guf_test.c @@ -0,0 +1,93 @@ +#include +#include +#include + +#include "guf_dbuf.h" +#include "guf_str.h" + +typedef struct guf_test { + const char *name, *expected_output; + char *output; + void (*test_fn)(struct guf_test *test); + uint64_t runtime_ms; + bool passed; +} guf_test; + +static void guf_dbuf_test(guf_test *test) +{ + guf_dbuf ints = GUF_DBUF_NEW(int); + for (int i = 0; i < 2048; ++i) { + GUF_DBUF_PUSH_VAL(&ints, int, i); + } + + int last_int = GUF_DBUF_LAST_VAL(&ints, int); + int first_int = GUF_DBUF_FIRST_VAL(&ints, int); + int nth = GUF_DBUF_AT_VAL(&ints, int, 14); + + printf("First: %d, Last: %d, 14th: %d\n", first_int, last_int, nth); + + guf_dbuf strings = GUF_DBUF_NEW(guf_const_cstr_type); + GUF_DBUF_PUSH_VAL(&strings, guf_const_cstr_type, "First elem"); + GUF_DBUF_PUSH_VAL(&strings, guf_const_cstr_type, "Second elem"); + GUF_DBUF_PUSH_VAL(&strings, guf_const_cstr_type, "Third elem"); + + int i = 0; + GUF_DBUF_FOREACH(strings, guf_const_cstr_type, elem) { + printf("elem %d: %s\n", i++, *elem); + } + + guf_dbuf mut_strings = guf_dbuf_new(guf_cstr_obj_meta); + char foo[] = "Hello, world!"; + char bar[] = "Hej, verden!"; + char *baz = calloc(64, sizeof(char)); + strcpy(baz, "Farvel, I forbandede hundehoveder!"); + + GUF_DBUF_PUSH_VAL_CPY(&mut_strings, guf_cstr_type, &foo[0]); + GUF_DBUF_PUSH_VAL_CPY(&mut_strings, guf_cstr_type, &bar[0]); + + guf_dbuf_push(&mut_strings, &baz, GUF_CPY_MOVE); + GUF_ASSERT_RELEASE(baz == NULL); + + // char *popped = GUF_DBUF_POP_VAL(&mut_strings, cstr_type); + // printf("popped: %s\n", popped); + // free(popped); + + printf("First str_mut: %s, second: %s, last: %s\n", GUF_DBUF_FIRST_VAL(&mut_strings, guf_cstr_type), GUF_DBUF_AT_VAL(&mut_strings, guf_cstr_type, 1), GUF_DBUF_LAST_VAL(&mut_strings, guf_cstr_type)); + + guf_dbuf_free(&mut_strings); +} + +int main(void) +{ + + bool success = true; + + guf_dbuf_test(NULL); + // guf_test_arr_register(); + + // bool alloc_init = guf_alloc_init(); + // GUF_ASSERT_RELEASE(alloc_init); + + // void *ptr = guf_malloc(sizeof(int) * 42, "int alloc"); + + // void *ptr2 = guf_malloc(sizeof(int) * 4, "int alloc 2 "); + + // void *ptr3 = guf_malloc(sizeof(int) * 4, "int alloc 3aaaaaaaaaafsjfjsdkfjksldjflssdfsdfjjjsdflkdjflsd"); + + // guf_free(ptr, "int alloc"); + // guf_free(ptr3, "int alloc 3"); + // guf_free(ptr2, "int alloc 2"); + + // guf_alloc_print(); + + // GUF_ARR_FOREACH(test_arr, guf_test, test) { + // test->test_fn(test); + // if (guf_str_equals(&test->expected_output, &test->output)) { + // printf("Test %s passed!\n", guf_str_get_const_c_str(&test->name)); + // } else { + // printf("Test %s failed!\n", guf_str_get_const_c_str(&test->name)); + // } + // } + + return success ? EXIT_SUCCESS : EXIT_FAILURE; +} \ No newline at end of file diff --git a/test/data_01.txt b/test/data_01.txt new file mode 100644 index 0000000..66a6a6c --- /dev/null +++ b/test/data_01.txt @@ -0,0 +1,13 @@ +„Ich weiß nicht“, rief ich ohne Klang „ich weiß ja nicht. Wenn +niemand kommt, dann kommt eben niemand. Ich habe niemandem etwas +Böses getan, niemand hat mir etwas Böses getan, niemand aber will +mir helfen. Lauter niemand. Aber so ist es doch nicht. Nur daß mir +niemand hilft —, sonst wäre lauter niemand hübsch. Ich würde ganz +gern — warum denn nicht — einen Ausflug mit einer Gesellschaft von +lauter Niemand machen. Natürlich ins Gebirge, wohin denn sonst? Wie +sich diese Niemand aneinander drängen, diese vielen quer gestreckten +und eingehängten Arme, diese vielen Füße, durch winzige Schritte +getrennt! Versteht sich, daß alle in Frack sind. Wir gehen so lala, +der Wind fährt durch die Lücken, die wir und unsere Gliedmaßen offen +lassen. Die Hälse werden im Gebirge frei! Es ist ein Wunder, daß +wir nicht singen.“ \ No newline at end of file diff --git a/testgen.py b/testgen.py new file mode 100644 index 0000000..29d4946 --- /dev/null +++ b/testgen.py @@ -0,0 +1,29 @@ +import textwrap +import dbuf_tests + +def gen_test_struct(test_fn_name: str, name: str, expected_output: str) -> str: + return textwrap.dedent(f""" + (guf_test) {{ + .test_fn = {test_fn_name}, .name = "{name}", + .expected_output = "{expected_output}", + .output = NULL, + .passed = false, + .runtime_ms = 0, + }}""") + +def gen_res_str(buf): + res = "" + for elem in buf: + res += str(elem) + "," + res = res[:-1] + return res + + +if __name__ == "__main__": + test_array_definition = "static const guf_test dbuf_tests[] = {" + for test_fn in dbuf_tests.all_tests(): + test_array_definition += textwrap.indent(test_fn() + ",", " ") + + test_array_definition += "\n};" + + print(test_array_definition)