commit 0751726fc51f5fe9ae2c2b60bf7f95ecb090b1e9 Author: jun <83899451+zeichensystem@users.noreply.github.com> Date: Fri Jan 3 10:38:57 2025 +0100 Initial commit 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 0000000..7fac769 Binary files /dev/null and b/doc/guf_dict-diagram.png differ 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)