Compare commits
98 Commits
44ed06f72c
...
4b29883c7e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4b29883c7e | ||
|
|
2f50db1858 | ||
|
|
a964466c8b | ||
|
|
45f07986c3 | ||
|
|
ae83ee66e1 | ||
|
|
c54fc75221 | ||
|
|
2355feaa70 | ||
|
|
096b83638b | ||
|
|
4217ef6e0b | ||
|
|
57f0e47efc | ||
|
|
7ec2af0c33 | ||
|
|
8b78803ad8 | ||
|
|
614a9716cc | ||
|
|
7585848ac9 | ||
|
|
9b64b22806 | ||
|
|
466982ffcb | ||
|
|
ec074d5753 | ||
|
|
b99434a611 | ||
|
|
562dcc532d | ||
|
|
776c4a8899 | ||
|
|
894d9e8fc4 | ||
|
|
37f9011166 | ||
|
|
6ffb79f7a0 | ||
|
|
1dba7661e6 | ||
|
|
c4f3616b23 | ||
|
|
6091eac820 | ||
|
|
873cdf20b1 | ||
|
|
9c417d2aa1 | ||
|
|
c4b68d5ad2 | ||
|
|
864bd70ece | ||
|
|
1013616b2d | ||
|
|
c6b0aa8d89 | ||
|
|
e98dc3b91e | ||
|
|
0910ee4bd8 | ||
|
|
f3e184da73 | ||
|
|
ae7814fe7c | ||
|
|
d062784425 | ||
|
|
05f995e855 | ||
|
|
461006746e | ||
|
|
9e0cd79ca0 | ||
|
|
3ff4cd7572 | ||
|
|
3ee07078f0 | ||
|
|
7630ecfdcf | ||
|
|
275f46226d | ||
|
|
21d1b04e6b | ||
|
|
534ff261ca | ||
|
|
c039e24996 | ||
|
|
29014f4c52 | ||
|
|
48b366d8e9 | ||
|
|
7e1bbc769e | ||
|
|
1e534731bb | ||
|
|
b01e087c85 | ||
|
|
6a7bd2bd97 | ||
|
|
e535d39e3d | ||
|
|
cc0413116d | ||
|
|
ad884ee1e9 | ||
|
|
9ddea4bb07 | ||
|
|
c860a6ccfe | ||
|
|
ae104919e0 | ||
|
|
dac1d159b1 | ||
|
|
9096a4f9c9 | ||
|
|
7b433cd776 | ||
|
|
364dd603cf | ||
|
|
d057d76334 | ||
|
|
e489dd694b | ||
|
|
fc8118c182 | ||
|
|
24bc8d5a15 | ||
|
|
301477f5c6 | ||
|
|
9d62df6a83 | ||
|
|
8e6ffcdc70 | ||
|
|
a41ff868f2 | ||
|
|
4d97725bba | ||
|
|
cd1c1cd5db | ||
|
|
8b02eff3b7 | ||
|
|
217622d816 | ||
|
|
d450cd8a45 | ||
|
|
60e2849b01 | ||
|
|
7a990c810e | ||
|
|
6abe12c4c1 | ||
|
|
c1e125dfcb | ||
|
|
4c35d180e8 | ||
|
|
1f16b0e849 | ||
|
|
13abedd2ad | ||
|
|
81fc19aa85 | ||
|
|
22ebd8aa05 | ||
|
|
f911933588 | ||
|
|
500fd47986 | ||
|
|
6b222eafc1 | ||
|
|
fd58daa8b5 | ||
|
|
df9d1c9c10 | ||
|
|
67b5759e03 | ||
|
|
8c324c00f7 | ||
|
|
c9b18a220e | ||
|
|
be2daf72fd | ||
|
|
e668be2612 | ||
|
|
ad97de47a6 | ||
|
|
73eb83484a | ||
|
|
9e0da64cb4 |
0
.gitignore
vendored
Normal file → Executable file
0
.gitignore
vendored
Normal file → Executable file
@ -2,50 +2,55 @@ 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)
|
||||
set(CMAKE_C_STANDARD 17)
|
||||
|
||||
if (TARGET libguf_test)
|
||||
message("-- Configure libguf_test...")
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
|
||||
set(CMAKE_DEBUG_POSTFIX _dbg)
|
||||
if (NOT DEFINED CMAKE_RUNTIME_OUTPUT_DIRECTORY)
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/bin)
|
||||
endif ()
|
||||
|
||||
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 ()
|
||||
if (NOT DEFINED CMAKE_DEBUG_POSTFIX)
|
||||
set(CMAKE_DEBUG_POSTFIX _dbg)
|
||||
endif ()
|
||||
|
||||
set_target_properties(libguf_test PROPERTIES DEBUG_POSTFIX ${CMAKE_DEBUG_POSTFIX})
|
||||
if (NOT DEFINED MSVC)
|
||||
set(WARNING_FLAGS_C -Wall -Wextra -Wpedantic -Wvla -Wshadow -Wundef -Wmisleading-indentation -Wnull-dereference -Wswitch-default -Wstrict-overflow=5 -Wconversion -Wno-sign-conversion -Wcast-align -Wcast-qual -Wdouble-promotion -Wformat=2 -Winit-self -Wdisabled-optimization -Wno-unused-function)
|
||||
set(WARNING_FLAGS_CXX -Wall -Wextra -Wpedantic -Wvla -Wshadow -Wundef -Wmisleading-indentation -Wnull-dereference -Wswitch-default -Wstrict-overflow=5 -Wconversion -Wno-sign-conversion -Wsign-promo -Wcast-align -Wcast-qual -Wdouble-promotion -Wformat=2 -Winit-self -Wdisabled-optimization -Woverloaded-virtual -Wredundant-decls -Wctor-dtor-privacy -Wno-unused-function)
|
||||
set(DBG_FLAGS -fsanitize=undefined,address -g3 -Og)
|
||||
else ()
|
||||
set(WARNING_FLAGS_C /W4)
|
||||
set(WARNING_FLAGS_CXX /W4)
|
||||
set(DBG_FLAGS /fsanitize=address)
|
||||
endif ()
|
||||
|
||||
if (APPLE OR UNIX OR LINUX)
|
||||
set(DBG_FLAGS -fsanitize=undefined,address -g3 -glldb -Og)
|
||||
else ()
|
||||
set(DBG_FLAGS /fsanitize=address)
|
||||
endif()
|
||||
add_executable(libguf_example src/test/example.c src/test/impls/str_impl.c src/test/impls/dict_impl.c src/test/impls/linalg_impl.c src/test/impls/alloc_tracker_impl.c )
|
||||
target_include_directories(libguf_example PRIVATE src src/test)
|
||||
|
||||
target_compile_options(libguf_test PRIVATE ${WARNING_FLAGS_C} $<$<CONFIG:Debug>: ${DBG_FLAGS}>) # Note: no higher optimisations at all for debugger to work...
|
||||
target_link_options(libguf_test PRIVATE ${WARNING_FLAGS_C} $<$<CONFIG:Debug>: ${DBG_FLAGS}> )
|
||||
add_executable(libguf_test src/test/test.cpp src/test/test_dbuf.cpp src/test/test_dict.cpp src/test/test_str.cpp src/test/test_ckdint.cpp src/test/test_utf8.cpp src/test/impls/init_impl.c src/test/impls/dbuf_impl.c src/test/impls/str_impl.c src/test/impls/dict_impl.c src/test/impls/rand_impl.c src/test/impls/sort_impl.c src/test/impls/linalg_impl.c src/test/impls/ckdint_impl.c src/test/impls/alloc_tracker_impl.c src/test/impls/alloc_libc_impl.c )
|
||||
target_include_directories(libguf_test PRIVATE src src/test)
|
||||
|
||||
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()
|
||||
set_target_properties(libguf_example libguf_test PROPERTIES DEBUG_POSTFIX ${CMAKE_DEBUG_POSTFIX})
|
||||
|
||||
message("-- Configured libguf_test")
|
||||
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(libguf_example libguf_test PROPERTIES INTERPROCEDURAL_OPTIMIZATION TRUE)
|
||||
else()
|
||||
message(STATUS "LTO disabled")
|
||||
endif()
|
||||
|
||||
message(STATUS "Configure libguf_test...")
|
||||
target_compile_definitions(libguf_test PUBLIC TEST_DATA_DIR="${CMAKE_CURRENT_SOURCE_DIR}/src/test/data/")
|
||||
target_compile_options(libguf_test PRIVATE $<$<COMPILE_LANGUAGE:CXX>:${WARNING_FLAGS_CXX}> $<$<COMPILE_LANGUAGE:C>:${WARNING_FLAGS_C}> $<$<CONFIG:Debug>: ${DBG_FLAGS}>)
|
||||
target_link_options(libguf_test PRIVATE $<$<COMPILE_LANGUAGE:CXX>:${WARNING_FLAGS_CXX}> $<$<COMPILE_LANGUAGE:C>:${WARNING_FLAGS_C}> $<$<CONFIG:Debug>: ${DBG_FLAGS}> -lm)
|
||||
message(STATUS "Configured libguf_test")
|
||||
|
||||
message(STATUS "Configure libguf_example...")
|
||||
target_compile_options(libguf_example PRIVATE ${WARNING_FLAGS_C} $<$<CONFIG:Debug>: ${DBG_FLAGS}>)
|
||||
target_link_options(libguf_example PRIVATE ${WARNING_FLAGS_C} $<$<CONFIG:Debug>: ${DBG_FLAGS}> -lm)
|
||||
message(STATUS "Configured libguf_example")
|
||||
|
||||
9
LICENSE.txt
Normal file
9
LICENSE.txt
Normal file
@ -0,0 +1,9 @@
|
||||
Copyright 2025 ~fruit
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
cf. https://opensource.org/license/mit (last-retrieved 2025-12-21)
|
||||
56
README.txt
Normal file
56
README.txt
Normal file
@ -0,0 +1,56 @@
|
||||
libguf - typesafe C99 containers
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
1. Intro
|
||||
----------
|
||||
"Look mom, no void pointers!"
|
||||
|
||||
This is a work in progress of my personal header only datastructure and
|
||||
algorithm utility library which (ab)uses the C pre-processor to achieve
|
||||
typesafety.
|
||||
|
||||
There is no reason this exists apart from "this is kind of fun and silly",
|
||||
you should just use C++ :)
|
||||
|
||||
It uses a similar approach of "include parametrisation" to achieve strongly
|
||||
typed data structures as https://github.com/ludocode/pottery (last-retrieved
|
||||
2025-12-21)
|
||||
|
||||
Look at "src/test/example.c" for an example.
|
||||
|
||||
|
||||
2. How to build
|
||||
-----------------
|
||||
Setup CMake with
|
||||
$ cmake -DCMAKE_BUILD_TYPE=Debug -S . -B build/debug
|
||||
|
||||
And build the example with
|
||||
$ cmake --build build/debug/ --target libguf_example
|
||||
|
||||
|
||||
3. Tests
|
||||
----------
|
||||
The tests in src/test are written in C++20, which made it easy to just use the
|
||||
STL types to check their behaviour against the custom libguf C types.
|
||||
|
||||
|
||||
4. What it includes
|
||||
--------------------
|
||||
- guf_dbuf.h: a dynamic array (like std::vector)
|
||||
- guf_dict.h: a hashtable (elements are stored contiguously, like python3's
|
||||
compact dicts)
|
||||
- guf_str.h: a string-buffer with short-string-optimisation (guf_str) and
|
||||
a string-view (guf_str_view) type
|
||||
- guf_rand.h: random number algorithms
|
||||
- guf_sort.h: sort algorithms
|
||||
- guf_math.h: math I guess?
|
||||
- guf_linalg.h: linear algebra types and algorithms for computer graphics etc.
|
||||
- ... and more
|
||||
|
||||
|
||||
5. License
|
||||
-----------
|
||||
MIT (cf. LICENSE.txt).
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
~fruit
|
||||
190
dbuf_tests.py
190
dbuf_tests.py
@ -1,190 +0,0 @@
|
||||
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
|
||||
0
doc/guf_dict-diagram.png
Normal file → Executable file
0
doc/guf_dict-diagram.png
Normal file → Executable file
|
Before Width: | Height: | Size: 133 KiB After Width: | Height: | Size: 133 KiB |
31
src/guf_alloc.h
Executable file
31
src/guf_alloc.h
Executable file
@ -0,0 +1,31 @@
|
||||
/*
|
||||
is parametrized: no
|
||||
*/
|
||||
|
||||
#ifndef GUF_ALLOC_H
|
||||
#define GUF_ALLOC_H
|
||||
#include "guf_common.h"
|
||||
#include "guf_assert.h"
|
||||
|
||||
// A custom allocator interface as described in https://nullprogram.com/blog/2023/12/17/ (last-retrieved 2025-01-25)
|
||||
typedef struct guf_allocator {
|
||||
void *(*alloc)(ptrdiff_t size, void *ctx);
|
||||
void *(*realloc)(void *ptr, ptrdiff_t old_size, ptrdiff_t new_size, void *ctx);
|
||||
void (*free)(void *ptr, ptrdiff_t size, void *ctx);
|
||||
void *ctx;
|
||||
} guf_allocator;
|
||||
|
||||
// typedef enum guf_alloc_fn_type {
|
||||
// GUF_ALLOC_FN_TYPE_ALLOC,
|
||||
// GUF_ALLOC_FN_TYPE_REALLOC,
|
||||
// GUF_ALLOC_FN_TYPE_FREE,
|
||||
// } guf_alloc_fn_type;
|
||||
|
||||
/*
|
||||
GUF_ALLOC_MAX_BYTES: Largest number of bytes an allocated buffer of elements of TYPE can have.
|
||||
GUF_ALLOC_MAX_CAPACITY: Largest number of elements an allocated buffer of elements of TYPE can have.
|
||||
*/
|
||||
#define GUF_ALLOC_MAX_BYTES(TYPE) ( PTRDIFF_MAX - ( PTRDIFF_MAX % sizeof(TYPE) ) )
|
||||
#define GUF_ALLOC_MAX_CAPACITY(TYPE) ( GUF_ALLOC_MAX_BYTES(TYPE) / sizeof(TYPE) )
|
||||
|
||||
#endif
|
||||
120
src/guf_alloc_libc.h
Executable file
120
src/guf_alloc_libc.h
Executable file
@ -0,0 +1,120 @@
|
||||
/*
|
||||
is parametrized: no
|
||||
*/
|
||||
|
||||
#if defined(GUF_ALLOC_LIBC_IMPL_STATIC)
|
||||
#define GUF_ALLOC_LIBC_KWRDS static inline
|
||||
#else
|
||||
#define GUF_ALLOC_LIBC_KWRDS
|
||||
#endif
|
||||
|
||||
#ifndef GUF_ALLOC_LIBC_H
|
||||
#define GUF_ALLOC_LIBC_H
|
||||
#include <memory.h>
|
||||
#include "guf_alloc.h"
|
||||
#define GUF_ALLOC_TRACKER_IMPL_STATIC
|
||||
#include "guf_alloc_tracker.h"
|
||||
|
||||
typedef struct guf_libc_alloc_ctx {
|
||||
guf_alloc_tracker tracker;
|
||||
bool zero_init;
|
||||
} guf_libc_alloc_ctx;
|
||||
|
||||
#if !defined(GUF_ALLOC_LIBC_IMPL_STATIC) && !defined(GUF_ALLOC_LIBC_IMPL)
|
||||
GUF_ALLOC_LIBC_KWRDS void *guf_libc_alloc(ptrdiff_t size, void *ctx);
|
||||
GUF_ALLOC_LIBC_KWRDS void *guf_libc_realloc(void *ptr, ptrdiff_t old_size, ptrdiff_t new_size, void *ctx);
|
||||
GUF_ALLOC_LIBC_KWRDS void guf_libc_free(void *ptr, ptrdiff_t size, void *ctx);
|
||||
|
||||
GUF_ALLOC_LIBC_KWRDS guf_allocator *guf_libc_allocator_init(guf_allocator *a, guf_libc_alloc_ctx *ctx);
|
||||
GUF_ALLOC_LIBC_KWRDS guf_allocator guf_libc_allocator_new(guf_libc_alloc_ctx *ctx);
|
||||
#endif
|
||||
|
||||
#if defined(GUF_ALLOC_LIBC_IMPL_STATIC) || defined(GUF_ALLOC_LIBC_IMPL)
|
||||
|
||||
GUF_ALLOC_LIBC_KWRDS void *guf_libc_alloc(ptrdiff_t size, void *ctx)
|
||||
{
|
||||
GUF_ASSERT_RELEASE(size >= 0);
|
||||
guf_libc_alloc_ctx *alloc_ctx = (guf_libc_alloc_ctx*)ctx;
|
||||
|
||||
void *res = NULL;
|
||||
if (size == 0) {
|
||||
res = NULL;
|
||||
} else if (alloc_ctx && alloc_ctx->zero_init) {
|
||||
res = calloc(size, 1);
|
||||
} else {
|
||||
res = malloc(size);
|
||||
}
|
||||
|
||||
if (alloc_ctx && alloc_ctx->tracker.enabled) {
|
||||
const bool succ = guf_track_alloc(&alloc_ctx->tracker, size);
|
||||
GUF_ASSERT(succ);
|
||||
(void)succ;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
GUF_ALLOC_LIBC_KWRDS void *guf_libc_realloc(void *ptr, ptrdiff_t old_size, ptrdiff_t new_size, void *ctx)
|
||||
{
|
||||
GUF_ASSERT_RELEASE(ptr);
|
||||
GUF_ASSERT_RELEASE(old_size >= 0 && new_size >= 0);
|
||||
if (old_size == new_size) {
|
||||
return ptr;
|
||||
}
|
||||
guf_libc_alloc_ctx *alloc_ctx = (guf_libc_alloc_ctx*)ctx;
|
||||
|
||||
void *new_ptr = realloc(ptr, new_size);
|
||||
if (!new_ptr || new_size == 0) {
|
||||
new_ptr = NULL;
|
||||
} else if (alloc_ctx && alloc_ctx->zero_init && new_size > old_size) {
|
||||
const ptrdiff_t len = new_size - old_size;
|
||||
GUF_ASSERT(len > 0);
|
||||
GUF_ASSERT(old_size + len == new_size);
|
||||
memset((char*)new_ptr + old_size, 0, len); // TODO: sketchy
|
||||
}
|
||||
|
||||
if (alloc_ctx && alloc_ctx->tracker.enabled) {
|
||||
const bool succ = guf_track_realloc(&alloc_ctx->tracker, old_size, new_size);
|
||||
GUF_ASSERT(succ);
|
||||
(void)succ;
|
||||
}
|
||||
|
||||
return new_ptr;
|
||||
}
|
||||
|
||||
GUF_ALLOC_LIBC_KWRDS void guf_libc_free(void *ptr, ptrdiff_t size, void *ctx)
|
||||
{
|
||||
free(ptr);
|
||||
|
||||
guf_libc_alloc_ctx *alloc_ctx = (guf_libc_alloc_ctx*)ctx;
|
||||
if (alloc_ctx && alloc_ctx->tracker.enabled) {
|
||||
const bool succ = guf_track_free(&alloc_ctx->tracker, size);
|
||||
GUF_ASSERT(succ);
|
||||
(void)succ;
|
||||
}
|
||||
}
|
||||
|
||||
GUF_ALLOC_LIBC_KWRDS guf_allocator *guf_libc_allocator_init(guf_allocator *a, guf_libc_alloc_ctx *ctx)
|
||||
{
|
||||
GUF_ASSERT_RELEASE(a);
|
||||
a->alloc = guf_libc_alloc;
|
||||
a->realloc = guf_libc_realloc;
|
||||
a->free = guf_libc_free;
|
||||
a->ctx = ctx;
|
||||
return a;
|
||||
}
|
||||
|
||||
GUF_ALLOC_LIBC_KWRDS guf_allocator guf_libc_allocator_new(guf_libc_alloc_ctx *ctx)
|
||||
{
|
||||
guf_allocator a;
|
||||
guf_libc_allocator_init(&a, ctx);
|
||||
return a;
|
||||
}
|
||||
|
||||
#endif /* End impl */
|
||||
|
||||
#endif /* End header-guard */
|
||||
|
||||
#undef GUF_ALLOC_LIBC_IMPL_STATIC
|
||||
#undef GUF_ALLOC_LIBC_IMPL
|
||||
#undef GUF_ALLOC_LIBC_KWRDS
|
||||
212
src/guf_alloc_tracker.h
Executable file
212
src/guf_alloc_tracker.h
Executable file
@ -0,0 +1,212 @@
|
||||
#if defined(GUF_ALLOC_TRACKER_IMPL_STATIC)
|
||||
#define GUF_ALLOC_TRACKER_KWRDS static inline
|
||||
#else
|
||||
#define GUF_ALLOC_TRACKER_KWRDS
|
||||
#endif
|
||||
|
||||
#ifndef GUF_ALLOC_TRACKER_H
|
||||
#define GUF_ALLOC_TRACKER_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include "guf_common.h"
|
||||
#include "guf_str_view_type.h"
|
||||
|
||||
typedef struct guf_alloc_tracker {
|
||||
FILE *log, *err_log;
|
||||
const char *name;
|
||||
size_t alloc_count, realloc_count, free_count;
|
||||
ptrdiff_t allocated_bytes;
|
||||
unsigned id;
|
||||
bool enabled;
|
||||
} guf_alloc_tracker;
|
||||
|
||||
#if !defined(GUF_ALLOC_TRACKER_IMPL_STATIC) && !defined(GUF_ALLOC_TRACKER_IMPL)
|
||||
GUF_ALLOC_TRACKER_KWRDS guf_alloc_tracker *guf_alloc_tracker_init(guf_alloc_tracker *t, unsigned id, const char* name, FILE *log, FILE *err_log);
|
||||
GUF_ALLOC_TRACKER_KWRDS guf_alloc_tracker guf_alloc_tracker_new(unsigned id, const char* name, FILE *log, FILE *err_log);
|
||||
|
||||
GUF_ALLOC_TRACKER_KWRDS void guf_alloc_tracker_print(const guf_alloc_tracker *t, FILE *f);
|
||||
GUF_ALLOC_TRACKER_KWRDS bool guf_alloc_tracker_found_leak(const guf_alloc_tracker *t);
|
||||
|
||||
GUF_ALLOC_TRACKER_KWRDS bool guf_track_alloc(guf_alloc_tracker *t, ptrdiff_t size);
|
||||
GUF_ALLOC_TRACKER_KWRDS bool guf_track_realloc(guf_alloc_tracker *t, ptrdiff_t old_size, ptrdiff_t new_size);
|
||||
GUF_ALLOC_TRACKER_KWRDS bool guf_track_free(guf_alloc_tracker *t, ptrdiff_t size);
|
||||
#endif
|
||||
|
||||
#if defined(GUF_ALLOC_TRACKER_IMPL_STATIC) || defined(GUF_ALLOC_TRACKER_IMPL)
|
||||
|
||||
#include "guf_alloc.h"
|
||||
#define GUF_MATH_CKDINT_IMPL_STATIC
|
||||
#include "guf_math_ckdint.h"
|
||||
|
||||
GUF_ALLOC_TRACKER_KWRDS guf_alloc_tracker *guf_alloc_tracker_init(guf_alloc_tracker *t, unsigned id, const char* name, FILE *log, FILE *err_log)
|
||||
{
|
||||
GUF_ASSERT_RELEASE(t);
|
||||
t->log = log;
|
||||
t->err_log = err_log;
|
||||
t->alloc_count = t->realloc_count = t->free_count = 0;
|
||||
t->allocated_bytes = 0;
|
||||
t->name = name;
|
||||
t->id = id;
|
||||
t->enabled = true;
|
||||
return t;
|
||||
}
|
||||
|
||||
GUF_ALLOC_TRACKER_KWRDS guf_alloc_tracker guf_alloc_tracker_new(unsigned id, const char* name, FILE *log, FILE *err_log)
|
||||
{
|
||||
guf_alloc_tracker t;
|
||||
guf_alloc_tracker_init(&t, id, name, log, err_log);
|
||||
return t;
|
||||
}
|
||||
|
||||
GUF_ALLOC_TRACKER_KWRDS bool guf_alloc_tracker_found_leak(const guf_alloc_tracker *t)
|
||||
{
|
||||
GUF_ASSERT_RELEASE(t);
|
||||
return (t->allocated_bytes != 0) || (t->alloc_count != t->free_count);
|
||||
}
|
||||
|
||||
|
||||
GUF_ALLOC_TRACKER_KWRDS void guf_alloc_tracker_print(const guf_alloc_tracker *t, FILE *f)
|
||||
{
|
||||
GUF_ASSERT(t);
|
||||
if (!f) {
|
||||
f = stdout;
|
||||
}
|
||||
|
||||
if (!t) {
|
||||
fprintf(f, "guf_alloc_tracker_fprint: guf_alloc_tracker is NULL");
|
||||
return;
|
||||
}
|
||||
|
||||
fprintf(f,
|
||||
"guf_alloc_tracker (name '%s' id = %u):\n"
|
||||
"allocated_bytes: %td\n"
|
||||
"alloc_count: %zu\n"
|
||||
"realloc_count: %zu\n"
|
||||
"free_count: %zu\n",
|
||||
t->name ? t->name : "unnamed", t->id, t->allocated_bytes, t->alloc_count, t->realloc_count, t->free_count
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
GUF_ALLOC_TRACKER_KWRDS bool guf_track_alloc(guf_alloc_tracker *t, ptrdiff_t size)
|
||||
{
|
||||
GUF_ASSERT(t);
|
||||
GUF_ASSERT(size >= 0);
|
||||
|
||||
bool success = true;
|
||||
|
||||
if (guf_saturating_add_size_t(t->alloc_count, 1, &t->alloc_count) != GUF_MATH_CKD_SUCCESS && t->err_log) {
|
||||
fprintf(t->err_log, "WARNING in guf_track_alloc (name '%s' id %u): alloc_count reached SIZE_MAX\n", t->name ? t->name : "unnamed", t->id);
|
||||
}
|
||||
|
||||
if (guf_saturating_add_ptrdiff_t(t->allocated_bytes, size, &t->allocated_bytes) != GUF_MATH_CKD_SUCCESS) {
|
||||
if (t->err_log) {
|
||||
fprintf(t->err_log, "ERROR in guf_track_alloc (name '%s' id %u): allocated_byte overflow\n", t->name ? t->name : "unnamed", t->id);
|
||||
}
|
||||
success = false;
|
||||
}
|
||||
|
||||
if (t->allocated_bytes < 0) {
|
||||
if (t->err_log) {
|
||||
fprintf(t->err_log, "ERROR in guf_track_alloc (name '%s' id %u): allocated_bytes < 0\n", t->name ? t->name : "unnamed", t->id);
|
||||
}
|
||||
success = false;
|
||||
}
|
||||
|
||||
if (t->log) {
|
||||
fprintf(t->log, "guf_alloc_tracker (name '%s' id %u): alloc (%td bytes) %s\n", t->name ? t->name : "unnamed", t->id, size, success ? "SUCCESS" : "FAILURE");
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
|
||||
GUF_ALLOC_TRACKER_KWRDS bool guf_track_realloc(guf_alloc_tracker *t, ptrdiff_t old_size, ptrdiff_t new_size)
|
||||
{
|
||||
GUF_ASSERT(t);
|
||||
GUF_ASSERT(old_size >= 0 && new_size >= 0);
|
||||
bool success = true;
|
||||
|
||||
if (guf_saturating_add_size_t(t->realloc_count, 1, &t->realloc_count) && t->err_log) {
|
||||
fprintf(t->err_log, "WARNING in guf_track_realloc (name '%s' id %u): realloc_count reached SIZE_MAX\n", t->name ? t->name : "unnamed", t->id);
|
||||
}
|
||||
|
||||
if (old_size < 0 || new_size < 0) {
|
||||
success = false;
|
||||
if (t->err_log) {
|
||||
fprintf(t->err_log, "ERROR in guf_track_realloc (name '%s' id %u): old_size < 0 or new_size < 0\n", t->name ? t->name : "unnamed", t->id);
|
||||
}
|
||||
}
|
||||
|
||||
if (guf_saturating_sub_ptrdiff_t(t->allocated_bytes, old_size, &t->allocated_bytes)) {
|
||||
success = false;
|
||||
if (t->err_log) {
|
||||
fprintf(t->err_log, "ERROR in guf_track_realloc (name '%s' id %u): allocated_bytes - old_size under/overflows\n", t->name ? t->name : "unnamed", t->id);
|
||||
}
|
||||
}
|
||||
if (t->allocated_bytes < 0) {
|
||||
success = false;
|
||||
fprintf(t->err_log, "ERROR in guf_track_realloc (name '%s' id %u): allocated_bytes < 0 after subtracting old_size\n", t->name ? t->name : "unnamed", t->id);
|
||||
}
|
||||
|
||||
if (guf_saturating_add_ptrdiff_t(t->allocated_bytes, new_size, &t->allocated_bytes)) {
|
||||
success = false;
|
||||
if (t->err_log) {
|
||||
fprintf(t->err_log, "ERROR in guf_track_realloc (name '%s' id %u): allocated_bytes overflow \n", t->name ? t->name : "unnamed", t->id);
|
||||
}
|
||||
}
|
||||
if (t->allocated_bytes < 0) {
|
||||
success = false;
|
||||
fprintf(t->err_log, "ERROR in guf_track_realloc (name '%s' id %u): allocated_bytes < 0 after adding new_size\n", t->name ? t->name : "unnamed", t->id);
|
||||
}
|
||||
|
||||
if (t->log) {
|
||||
fprintf(t->log, "guf_alloc_tracker (name '%s' id %u): realloc (from %td to %td bytes) %s\n", t->name ? t->name : "unnamed", t->id, old_size, new_size, (success ? "SUCCESS" : "FAILURE"));
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
GUF_ALLOC_TRACKER_KWRDS bool guf_track_free(guf_alloc_tracker *t, ptrdiff_t size)
|
||||
{
|
||||
GUF_ASSERT(t);
|
||||
GUF_ASSERT(size >= 0);
|
||||
bool success = true;
|
||||
|
||||
if (guf_saturating_add_size_t(t->free_count, 1, &t->free_count) && t->err_log) {
|
||||
fprintf(t->err_log, "WARNING in guf_track_free (name '%s' id %u): free_count reached SIZE_MAX\n", t->name ? t->name : "unnamed", t->id);
|
||||
}
|
||||
|
||||
if (size < 0) {
|
||||
success = false;
|
||||
if (t->err_log) {
|
||||
fprintf(t->err_log, "ERROR in guf_track_free (name '%s' id %u): size < 0\n", t->name ? t->name : "unnamed", t->id);
|
||||
}
|
||||
}
|
||||
|
||||
if (t->allocated_bytes < size) {
|
||||
success = false;
|
||||
if (t->err_log) {
|
||||
fprintf(t->err_log, "ERROR in guf_track_free (name '%s' id %u): freed more bytes than allocated\n", t->name ? t->name : "unnamed", t->id);
|
||||
}
|
||||
}
|
||||
if (guf_saturating_sub_ptrdiff_t(t->allocated_bytes, size, &t->allocated_bytes)) {
|
||||
success = false;
|
||||
if (t->err_log) {
|
||||
fprintf(t->err_log, "ERROR in guf_track_free (name '%s' id %u): allocated_bytes - size under/overflows\n", t->name ? t->name : "unnamed", t->id);
|
||||
}
|
||||
}
|
||||
|
||||
if (t->log) {
|
||||
fprintf(t->log, "guf_alloc_tracker (name '%s' id %u): free (%td bytes) %s\n", t->name ? t->name : "unnamed", t->id, size, (success ? "SUCCESS" : "FAILURE"));
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
#endif /* End impl */
|
||||
|
||||
#endif /* End header-guard */
|
||||
|
||||
#undef GUF_ALLOC_TRACKER_KWRDS
|
||||
#undef GUF_ALLOC_TRACKER_IMPL
|
||||
#undef GUF_ALLOC_TRACKER_IMPL_STATIC
|
||||
166
src/guf_assert.h
Normal file → Executable file
166
src/guf_assert.h
Normal file → Executable file
@ -1,3 +1,165 @@
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
/*
|
||||
is parametrized: no, but needs to be included with GUF_INIT in the init implementation
|
||||
|
||||
TOOD: - Thread safety?
|
||||
- Maybe allow user defined guf_errs?
|
||||
*/
|
||||
|
||||
#ifndef GUF_ASSERT_H
|
||||
#define GUF_ASSERT_H
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
#include "guf_common.h"
|
||||
|
||||
typedef enum guf_err {
|
||||
GUF_ERR_NONE = 0,
|
||||
GUF_ERR_UNSPECIFIED,
|
||||
GUF_ERR_ALLOC_FAIL,
|
||||
GUF_ERR_NULL_PTR,
|
||||
GUF_ERR_INT_OVERFLOW,
|
||||
GUF_ERR_DIV_BY_ZERO,
|
||||
GUF_ERR_DOMAIN,
|
||||
GUF_ERR_IDX_RANGE,
|
||||
GUF_ERR_INVALID_ARG,
|
||||
GUF_ERR_RUNTIME,
|
||||
GUF_ERR_LOGIC,
|
||||
GUF_ERR_NOT_FOUND,
|
||||
GUF_ERR_ALREADY_EXISTS,
|
||||
GUF_ERR_ASSERT_FAIL,
|
||||
GUF_ERR_IO,
|
||||
GUF_ERR_TYPES_NUM
|
||||
} guf_err;
|
||||
|
||||
typedef void(*guf_panic_handler_fn)(guf_err err, const char *msg);
|
||||
|
||||
extern guf_panic_handler_fn guf_global_panic_handler;
|
||||
|
||||
extern void guf_set_global_panic_handler(guf_panic_handler_fn panic_handler);
|
||||
extern void guf_panic_handler_default(guf_err err, const char *msg);
|
||||
extern const char *guf_err_to_str(guf_err err);
|
||||
|
||||
#define GUF_FILE_LINE_STR() "file '" __FILE__ "' line " GUF_STRINGIFY(__LINE__)
|
||||
#define GUF_ERR_MSG(msg) msg " (" GUF_FILE_LINE_STR() ")"
|
||||
#define GUF_ERR_MSG_EMPTY() "(" GUF_FILE_LINE_STR() ")"
|
||||
|
||||
extern void guf_panic(guf_err err, const char *msg);
|
||||
|
||||
#define GUF_PANIC(err) guf_panic(err, "(" GUF_FILE_LINE_STR() ")");
|
||||
|
||||
// Break on debug, cf. https://nullprogram.com/blog/2022/06/26/ (last retrieved 2025-01-07)
|
||||
#if __GNUC__ || __clang__
|
||||
#define GUF_DEBUG_BREAK_CODE __builtin_trap()
|
||||
#elif _MSC_VER
|
||||
#define GUF_DEBUG_BREAK_CODE __debugbreak()
|
||||
#else
|
||||
#define GUF_DEBUG_BREAK_CODE abort()
|
||||
#endif
|
||||
|
||||
#ifndef NDEBUG
|
||||
#define GUF_ASSERT(COND) do { \
|
||||
if (!(COND)) { \
|
||||
guf_global_panic_handler(GUF_ERR_ASSERT_FAIL, "(assertion '" #COND "' " GUF_FILE_LINE_STR() ")"); \
|
||||
abort(); \
|
||||
}\
|
||||
} while(0);
|
||||
#else
|
||||
#define GUF_ASSERT(COND)
|
||||
#endif
|
||||
|
||||
#define GUF_ASSERT_RELEASE(COND) do { \
|
||||
if (!(COND)) { \
|
||||
guf_global_panic_handler(GUF_ERR_ASSERT_FAIL, "(release assertion '" #COND "' " GUF_FILE_LINE_STR() ")"); \
|
||||
abort();\
|
||||
} \
|
||||
} while (0);
|
||||
|
||||
static inline void guf_err_set_or_panic(guf_err *err, guf_err err_val, const char *panic_msg)
|
||||
{
|
||||
if (!err) {
|
||||
guf_panic(err_val, panic_msg);
|
||||
} else {
|
||||
*err = err_val;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void guf_err_set_if_not_null(guf_err *err, guf_err err_val)
|
||||
{
|
||||
if (err) {
|
||||
*err = err_val;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef GUF_INIT
|
||||
#undef GUF_INIT
|
||||
static const char *guf_err_type_str[] = {
|
||||
[GUF_ERR_NONE] = "Not an error",
|
||||
[GUF_ERR_UNSPECIFIED] = "Error",
|
||||
[GUF_ERR_ALLOC_FAIL] = "Allocation error",
|
||||
[GUF_ERR_NULL_PTR] = "Null pointer dereference",
|
||||
[GUF_ERR_DOMAIN] = "Domain error",
|
||||
[GUF_ERR_INT_OVERFLOW] = "Integer overflow",
|
||||
[GUF_ERR_DIV_BY_ZERO] = "Division by zero",
|
||||
[GUF_ERR_IDX_RANGE] = "Index out of range",
|
||||
[GUF_ERR_INVALID_ARG] = "Invalid argument",
|
||||
[GUF_ERR_NOT_FOUND] = "Not found error",
|
||||
[GUF_ERR_ALREADY_EXISTS] = "Already exists error",
|
||||
[GUF_ERR_RUNTIME] = "Runtime error",
|
||||
[GUF_ERR_LOGIC] = "Logic error",
|
||||
[GUF_ERR_ASSERT_FAIL] = "Assertion failed"
|
||||
};
|
||||
|
||||
extern void guf_panic(guf_err err, const char *msg)
|
||||
{
|
||||
if (!guf_global_panic_handler) {
|
||||
fputs("libguf panic (note: guf_global_panic_handler is NULL)\n", stderr);
|
||||
if (msg) {
|
||||
fputs(": ", stderr);
|
||||
fputs(msg, stderr);
|
||||
}
|
||||
abort();
|
||||
}
|
||||
guf_global_panic_handler(err, msg);
|
||||
abort(); // Just in case...
|
||||
}
|
||||
|
||||
extern void guf_set_global_panic_handler(guf_panic_handler_fn panic_handler)
|
||||
{
|
||||
guf_global_panic_handler = panic_handler;
|
||||
}
|
||||
|
||||
extern const char *guf_err_to_str(guf_err err)
|
||||
{
|
||||
if (GUF_ARR_SIZE(guf_err_type_str) != GUF_ERR_TYPES_NUM) {
|
||||
fputs("libguf warning (in guf_assert.h): array size of guf_err_type_str != GUF_ERR_TYPES_NUM", stderr);
|
||||
}
|
||||
|
||||
if (err < 0 || err >= GUF_ERR_TYPES_NUM) {
|
||||
return "Invalid error code";
|
||||
} else {
|
||||
if (err > GUF_ARR_SIZE(guf_err_type_str)) {
|
||||
return "Invalid error string";
|
||||
}
|
||||
return guf_err_type_str[err];
|
||||
}
|
||||
}
|
||||
|
||||
extern void guf_panic_handler_default(guf_err err, const char *msg)
|
||||
{
|
||||
fputs("libguf panic: ", stderr);
|
||||
fputs(guf_err_to_str(err), stderr);
|
||||
fputc(' ', stderr);
|
||||
if (msg && err != GUF_ERR_NONE) {
|
||||
fputs(msg, stderr);
|
||||
}
|
||||
fputc('\n', stderr);
|
||||
|
||||
#ifndef NDEBUG
|
||||
GUF_DEBUG_BREAK_CODE;
|
||||
#endif
|
||||
abort();
|
||||
}
|
||||
|
||||
guf_panic_handler_fn guf_global_panic_handler = guf_panic_handler_default;
|
||||
|
||||
#endif
|
||||
|
||||
177
src/guf_common.c
177
src/guf_common.c
@ -1,177 +0,0 @@
|
||||
#include "guf_common.h"
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
#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);
|
||||
// }
|
||||
// }
|
||||
182
src/guf_common.h
Normal file → Executable file
182
src/guf_common.h
Normal file → Executable file
@ -1,72 +1,130 @@
|
||||
/*
|
||||
is parametrized: no
|
||||
*/
|
||||
|
||||
#ifndef GUF_COMMON_H
|
||||
#define GUF_COMMON_H
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <limits.h>
|
||||
#include <inttypes.h>
|
||||
#include <stddef.h>
|
||||
#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;
|
||||
#ifndef GUF_PLATFORM_BIG_ENDIAN
|
||||
#define GUF_PLATFORM_LITTLE_ENDIAN
|
||||
#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); \
|
||||
} \
|
||||
#define GUF_UINT8_MAX 0xffu
|
||||
#define GUF_UINT16_MAX 0xffffu
|
||||
#define GUF_UINT32_MAX 0xfffffffful
|
||||
#define GUF_UINT64_MAX 0xffffffffffffffffull
|
||||
|
||||
#define GUF_UWRAP_8(UINT) ( (UINT) & GUF_UINT8_MAX )
|
||||
#define GUF_UWRAP_16(UINT) ( (UINT) & GUF_UINT16_MAX )
|
||||
#define GUF_UWRAP_32(UINT) ( (UINT) & GUF_UINT32_MAX )
|
||||
#define GUF_UWRAP_64(UINT) ( (UINT) & GUF_UINT64_MAX )
|
||||
|
||||
#define GUF_INT8_MAX 127
|
||||
#define GUF_INT8_MIN -128
|
||||
|
||||
#define GUF_INT16_MAX 32767
|
||||
#define GUF_INT16_MIN (-GUF_INT16_MAX - 1)
|
||||
|
||||
#define GUF_INT32_MAX 2147483647L
|
||||
#define GUF_INT32_MIN (-GUF_INT32_MAX - 1)
|
||||
|
||||
#define GUF_INT64_MAX 9223372036854775807LL
|
||||
#define GUF_INT64_MIN (-GUF_INT64_MAX - 1)
|
||||
|
||||
#if SIZE_MAX == GUF_UINT64_MAX
|
||||
#define GUF_PLATFORM_BITS 64
|
||||
#elif SIZE_MAX == GUF_UINT32_MAX
|
||||
#define GUF_PLATFORM_BITS 32
|
||||
#elif SIZE_MAX == GUF_UINT16_MAX
|
||||
#define GUF_PLATFORM_BITS 16
|
||||
#elif SIZE_MAX == GUF_UINT8_MAX
|
||||
#define GUF_PLATFORM_BITS 8
|
||||
#else
|
||||
#define GUF_PLATFORM_BITS 64
|
||||
#error "libguf: Could not detect GUF_PLATFORM_BITS"
|
||||
#endif
|
||||
|
||||
#if GUF_PLATFORM_BITS <= 32
|
||||
#define GUF_HASH_32_BIT
|
||||
#define GUF_RAND_32_BIT
|
||||
#endif
|
||||
|
||||
#if defined(__STDC_VERSION__)
|
||||
#if __STDC_VERSION__ >= 199901L
|
||||
#define GUF_STDC_AT_LEAST_C99
|
||||
#else
|
||||
#error "libguf only supports C99 and above"
|
||||
#endif
|
||||
#if __STDC_VERSION__ >= 201112L
|
||||
#define GUF_STDC_AT_LEAST_C11
|
||||
#endif
|
||||
#if __STDC_VERSION__ >= 201710L
|
||||
#define GUF_STDC_AT_LEAST_C17
|
||||
#endif
|
||||
#if __STDC_VERSION__ >= 202311L
|
||||
#define GUF_STDC_AT_LEAST_C23
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(__cplusplus)
|
||||
#if __cplusplus >= 201103L
|
||||
#define GUF_STDCPP_AT_LEAST_CPP11
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/*
|
||||
// Copy- and move constructors:
|
||||
GUF_T_COPY: GUF_T *(*copy)(GUF_T *dst, const GUF_T *src, void *ctx);
|
||||
GUF_T_MOVE: GUF_T *(*move)(GUF_T *dst, GUF_T *src, void *ctx); // "Steals" the resources of src (named after move constructors in C++)
|
||||
|
||||
// Destructor:
|
||||
GUF_T_FREE: void (*free)(GUF_T *obj, void *ctx);
|
||||
|
||||
// Comparison- and hash operators:
|
||||
GUF_T_CMP: int (*cmp)(const GUF_T *a, const GUF_T *b); // a < b -> -1; a == b -> 0; a > b -> 1
|
||||
GUF_T_EQ: bool (*eq)(const GUF_T *a, const GUF_T *b);
|
||||
GUF_T_HASH: guf_hash_size_t (*hash)(const GUF_T *obj);
|
||||
*/
|
||||
|
||||
typedef enum guf_cpy_opt {
|
||||
GUF_CPY_VALUE = 0,
|
||||
GUF_CPY_DEEP = 1,
|
||||
GUF_CPY_MOVE = 2,
|
||||
} guf_cpy_opt;
|
||||
|
||||
#define GUF_SWAP(TYPE, val_a, val_b) do {TYPE guf_swap_tmp = val_a; val_a = val_b; val_b = guf_swap_tmp;} while (0);
|
||||
|
||||
#define GUF_ARR_SIZE(ARR) (sizeof((ARR)) / (sizeof((ARR)[0])))
|
||||
|
||||
#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)))
|
||||
|
||||
// The GUF_CAT/GUF_TOK_CAT indirection is necessary because the ## operation alone does not evaluate the macro arguments.
|
||||
#define GUF_TOK_CAT(a, b) a##b
|
||||
#define GUF_CAT(a, b) GUF_TOK_CAT(a, b)
|
||||
|
||||
// See comment above.
|
||||
#define GUF_TOK_STRINGIFY(x) #x
|
||||
#define GUF_STRINGIFY(x) GUF_TOK_STRINGIFY(x)
|
||||
|
||||
#define GUF_CNT_NPOS PTRDIFF_MIN
|
||||
|
||||
#define GUF_CNT_FOREACH(CNT_PTR, CNT_TYPE, IT_NAME) for (GUF_CAT(CNT_TYPE, _iter) IT_NAME = GUF_CAT(CNT_TYPE, _begin)(CNT_PTR); IT_NAME.ptr != GUF_CAT(CNT_TYPE, _end)(CNT_PTR).ptr; IT_NAME = GUF_CAT(CNT_TYPE, _iter_next)(CNT_PTR, IT_NAME, 1))
|
||||
#define GUF_CNT_FOREACH_STEP(CNT_PTR, CNT_TYPE, IT_NAME, STEP) for (GUF_CAT(CNT_TYPE, _iter) IT_NAME = GUF_CAT(CNT_TYPE, _begin)(CNT_PTR); IT_NAME.ptr != GUF_CAT(CNT_TYPE, _end)(CNT_PTR).ptr; IT_NAME = GUF_CAT(CNT_TYPE, _iter_next)(CNT_PTR, IT_NAME, STEP))
|
||||
|
||||
#define GUF_CNT_FOREACH_REVERSE(CNT_PTR, CNT_TYPE, IT_NAME) for (GUF_CAT(CNT_TYPE, _iter) IT_NAME = GUF_CAT(CNT_TYPE, _rbegin)(CNT_PTR); IT_NAME.ptr != GUF_CAT(CNT_TYPE, _rend)(CNT_PTR).ptr; IT_NAME = GUF_CAT(CNT_TYPE, _iter_next)(CNT_PTR, IT_NAME, 1))
|
||||
#define GUF_CNT_FOREACH_REVERSE_STEP(CNT_PTR, CNT_TYPE, IT_NAME, STEP) for (GUF_CAT(CNT_TYPE, _iter) IT_NAME = GUF_CAT(CNT_TYPE, _rbegin)(CNT_PTR); IT_NAME.ptr != GUF_CAT(CNT_TYPE, _rend)(CNT_PTR).ptr; IT_NAME = GUF_CAT(CNT_TYPE, _iter_next)(CNT_PTR, IT_NAME, STEP))
|
||||
|
||||
#define GUF_CNT_LIFETIME_BLOCK(GUF_CNT_TYPE, CNT_VARNAME, CODE) do { \
|
||||
GUF_CNT_TYPE CNT_VARNAME; \
|
||||
CODE; \
|
||||
GUF_CAT(GUF_CNT_TYPE, _free)(&CNT_VARNAME, NULL); \
|
||||
} 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
|
||||
|
||||
90
src/guf_cstr.h
Executable file
90
src/guf_cstr.h
Executable file
@ -0,0 +1,90 @@
|
||||
/*
|
||||
is parametrized: no
|
||||
*/
|
||||
|
||||
#ifndef GUF_CSTR_H
|
||||
#define GUF_CSTR_H
|
||||
#include <string.h>
|
||||
#include "guf_common.h"
|
||||
#include "guf_assert.h"
|
||||
#include "guf_hash.h"
|
||||
|
||||
static inline char *guf_cstr_dup(const char *cstr)
|
||||
{
|
||||
char *cpy = (char*)calloc(strlen(cstr) + 1, 1); // Explicit conversion for cpp...
|
||||
if (cpy) {
|
||||
strcpy(cpy, cstr);
|
||||
}
|
||||
return cpy;
|
||||
}
|
||||
|
||||
typedef const char* guf_cstr_const;
|
||||
|
||||
static inline int guf_cstr_const_cmp(const guf_cstr_const *a, const guf_cstr_const *b)
|
||||
{
|
||||
GUF_ASSERT_RELEASE(a && b);
|
||||
GUF_ASSERT_RELEASE(*a && *b);
|
||||
return strcmp(*a, *b);
|
||||
}
|
||||
|
||||
static inline bool guf_cstr_const_eq(const guf_cstr_const *a, const guf_cstr_const *b)
|
||||
{
|
||||
GUF_ASSERT_RELEASE(a && b);
|
||||
GUF_ASSERT_RELEASE(*a && *b);
|
||||
return 0 == strcmp(*a, *b);
|
||||
}
|
||||
|
||||
static inline guf_hash_size_t guf_cstr_const_hash(const guf_cstr_const *a)
|
||||
{
|
||||
GUF_ASSERT_RELEASE(a);
|
||||
GUF_ASSERT_RELEASE(*a);
|
||||
return guf_hash(*a, strlen(*a), GUF_HASH_INIT);
|
||||
}
|
||||
|
||||
typedef char* guf_cstr_heap;
|
||||
|
||||
static inline guf_cstr_heap *guf_cstr_heap_copy(guf_cstr_heap *dst, const guf_cstr_heap *src, void *ctx)
|
||||
{
|
||||
(void)ctx;
|
||||
GUF_ASSERT_RELEASE(dst && src);
|
||||
if (*src == NULL) {
|
||||
*dst = NULL;
|
||||
return dst;
|
||||
}
|
||||
*dst = guf_cstr_dup(*src);
|
||||
GUF_ASSERT_RELEASE(*dst);
|
||||
return dst;
|
||||
}
|
||||
//
|
||||
static inline guf_cstr_heap *guf_cstr_heap_move(guf_cstr_heap *dst, guf_cstr_heap *src, void *ctx)
|
||||
{
|
||||
(void)ctx;
|
||||
GUF_ASSERT_RELEASE(dst && src);
|
||||
*dst = *src;
|
||||
*src = NULL;
|
||||
return dst;
|
||||
}
|
||||
|
||||
static inline void guf_cstr_heap_free(guf_cstr_heap *a, void *ctx)
|
||||
{
|
||||
(void)ctx;
|
||||
GUF_ASSERT_RELEASE(a);
|
||||
free(*a);
|
||||
}
|
||||
|
||||
static inline int guf_cstr_heap_cmp(const guf_cstr_heap *a, const guf_cstr_heap *b)
|
||||
{
|
||||
|
||||
GUF_ASSERT_RELEASE(a && b);
|
||||
GUF_ASSERT_RELEASE(*a && *b);
|
||||
return strcmp(*a, *b);
|
||||
}
|
||||
|
||||
static inline bool guf_cstr_heap_eq(const guf_cstr_heap *a, const guf_cstr_heap *b)
|
||||
{
|
||||
GUF_ASSERT_RELEASE(a && b);
|
||||
GUF_ASSERT_RELEASE(*a && *b);
|
||||
return 0 == strcmp(*a, *b);
|
||||
}
|
||||
|
||||
#endif
|
||||
234
src/guf_darr.h
234
src/guf_darr.h
@ -1,234 +0,0 @@
|
||||
#ifndef GUF_DARR_H
|
||||
#define GUF_DARR_H
|
||||
#include <stddef.h>
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#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
|
||||
|
||||
260
src/guf_dbuf.c
260
src/guf_dbuf.c
@ -1,260 +0,0 @@
|
||||
#include <string.h>
|
||||
#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;
|
||||
}
|
||||
1121
src/guf_dbuf.h
Normal file → Executable file
1121
src/guf_dbuf.h
Normal file → Executable file
File diff suppressed because it is too large
Load Diff
649
src/guf_dict.c
649
src/guf_dict.c
@ -1,649 +0,0 @@
|
||||
// #include <stdint.h>
|
||||
// #include <stdbool.h>
|
||||
// #include <stdlib.h>
|
||||
// #include <string.h>
|
||||
// #include <stdio.h>
|
||||
|
||||
// #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.
|
||||
// */
|
||||
1120
src/guf_dict.h
1120
src/guf_dict.h
File diff suppressed because it is too large
Load Diff
@ -1,348 +0,0 @@
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#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,
|
||||
};
|
||||
82
src/guf_hash.h
Executable file
82
src/guf_hash.h
Executable file
@ -0,0 +1,82 @@
|
||||
/*
|
||||
is parametrized: no (but recieves GUF_HASH_32_BIT from guf_common.h to set guf_hash_size_t depending on platform)
|
||||
*/
|
||||
|
||||
#if defined(GUF_HASH_IMPL_STATIC)
|
||||
#define GUF_HASH_KWRDS static
|
||||
#else
|
||||
#define GUF_HASH_KWRDS
|
||||
#endif
|
||||
|
||||
#ifndef GUF_HASH_H
|
||||
#define GUF_HASH_H
|
||||
#include "guf_common.h"
|
||||
|
||||
/*
|
||||
FNV-1a (32-bit and 64-bit) hash functions.
|
||||
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)
|
||||
*/
|
||||
|
||||
#define GUF_HASH32_INIT UINT32_C(2166136261)
|
||||
#define GUF_HASH64_INIT UINT64_C(14695981039346656037)
|
||||
|
||||
GUF_HASH_KWRDS uint_least32_t guf_hash32(const void *data, ptrdiff_t num_bytes, uint_least32_t hash); // FNV-1a (32 bit)
|
||||
GUF_HASH_KWRDS uint_least64_t guf_hash64(const void *data, ptrdiff_t num_bytes, uint_least64_t hash); // FNV-1a (64 bit)
|
||||
|
||||
#ifdef GUF_HASH_32_BIT
|
||||
typedef uint_least32_t guf_hash_size_t;
|
||||
#define GUF_HASH_INIT GUF_HASH32_INIT
|
||||
#define GUF_HASH_MAX GUF_UINT32_MAX
|
||||
static inline guf_hash_size_t guf_hash(const void *data, ptrdiff_t num_bytes, guf_hash_size_t hash) {
|
||||
return guf_hash32(data, num_bytes, hash);
|
||||
}
|
||||
#else
|
||||
typedef uint_least64_t guf_hash_size_t;
|
||||
#define GUF_HASH_INIT GUF_HASH64_INIT
|
||||
#define GUF_HASH_MAX GUF_UINT64_MAX
|
||||
static inline guf_hash_size_t guf_hash(const void *data, ptrdiff_t num_bytes, guf_hash_size_t hash) {
|
||||
return guf_hash64(data, num_bytes, hash);
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
#if defined(GUF_HASH_IMPL) || defined(GUF_HASH_IMPL_STATIC)
|
||||
|
||||
#include "guf_assert.h"
|
||||
|
||||
GUF_HASH_KWRDS uint_least32_t guf_hash32(const void *data, ptrdiff_t num_bytes, uint_least32_t hash)
|
||||
{
|
||||
hash = GUF_UWRAP_32(hash);
|
||||
GUF_ASSERT_RELEASE(data);
|
||||
GUF_ASSERT_RELEASE(num_bytes >= 0);
|
||||
const unsigned char *data_bytes = (const unsigned char*)data; // This does not break strict-aliasing rules I think...
|
||||
const uint_least32_t FNV_32_PRIME = UINT32_C(16777619);
|
||||
for (ptrdiff_t i = 0; i < num_bytes; ++i) {
|
||||
hash = GUF_UWRAP_32(1u * hash ^ data_bytes[i]);
|
||||
hash = GUF_UWRAP_32(1u * hash * FNV_32_PRIME);
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
GUF_HASH_KWRDS uint_least64_t guf_hash64(const void *data, ptrdiff_t num_bytes, uint_least64_t hash)
|
||||
{
|
||||
hash = GUF_UWRAP_64(hash);
|
||||
GUF_ASSERT_RELEASE(data);
|
||||
GUF_ASSERT_RELEASE(num_bytes >= 0);
|
||||
const unsigned char *data_bytes = (const unsigned char*)data; // This does not break strict-aliasing rules I think...
|
||||
const uint_least64_t FNV_64_PRIME = UINT64_C(1099511628211);
|
||||
for (ptrdiff_t i = 0; i < num_bytes; ++i) {
|
||||
hash = GUF_UWRAP_64(1u * hash ^ data_bytes[i]);
|
||||
hash = GUF_UWRAP_64(1u * hash * FNV_64_PRIME);
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
#undef GUF_HASH_IMPL
|
||||
#undef GUF_HASH_IMPL_STATIC
|
||||
|
||||
#endif /* endif GUF_IMPL/GUF_IMPL_STATIC */
|
||||
|
||||
#undef GUF_HASH_KWRDS
|
||||
276
src/guf_id_pool.h
Executable file
276
src/guf_id_pool.h
Executable file
@ -0,0 +1,276 @@
|
||||
/*
|
||||
is parametrized: yes
|
||||
*/
|
||||
|
||||
#if defined(GUF_ID_POOL_IMPL_STATIC)
|
||||
#define GUF_ID_POOL_KWRDS static
|
||||
#else
|
||||
#define GUF_ID_POOL_KWRDS
|
||||
#endif
|
||||
|
||||
#ifndef GUF_ID_POOL_H
|
||||
#define GUF_ID_POOL_H
|
||||
#include "guf_common.h"
|
||||
#include "guf_assert.h"
|
||||
|
||||
#define GUF_ID_I32_NULL (-1)
|
||||
#define GUF_ID_I16_NULL (-1)
|
||||
#define GUF_ID_I8_NULL (-1)
|
||||
|
||||
#define GUF_ID_U32_NULL (GUF_UINT32_MAX)
|
||||
#define GUF_ID_U16_NULL (GUF_UINT16_MAX)
|
||||
#define GUF_ID_U8_NULL (GUF_UINT8_MAX)
|
||||
#endif
|
||||
|
||||
/*
|
||||
ID-pool which allows for allocating and freeing unique IDs (useful when implementing object pools):
|
||||
cf. https://github.com/erincatto/box2d/blob/main/src/id_pool.c (last-retrieved 2025-03-17)
|
||||
*/
|
||||
|
||||
// // debug beg
|
||||
// #define GUF_ID_POOL_IMPL_STATIC
|
||||
// #define GUF_ID_POOL_i32
|
||||
// // debug end
|
||||
|
||||
#if defined(GUF_ID_POOL_i32)
|
||||
#undef GUF_ID_POOL_i32
|
||||
#define GUF_ID_POOL_T int_least32_t
|
||||
#define GUF_ID_POOL_T_MAX GUF_INT32_MAX
|
||||
#define GUF_ID_POOL_NULL GUF_ID_I32_NULL
|
||||
#ifndef GUF_ID_POOL_NAME
|
||||
#define GUF_ID_POOL_NAME guf_idpool_i32
|
||||
#endif
|
||||
#elif defined(GUF_ID_POOL_i16)
|
||||
#undef GUF_ID_POOL_i16
|
||||
#define GUF_ID_POOL_T int_least16_t
|
||||
#define GUF_ID_POOL_T_MAX GUF_INT16_MAX
|
||||
#define GUF_ID_POOL_NULL GUF_ID_I16_NULL
|
||||
#ifndef GUF_ID_POOL_NAME
|
||||
#define GUF_ID_POOL_NAME guf_idpool_i16
|
||||
#endif
|
||||
#elif defined(GUF_ID_POOL_i8)
|
||||
#undef GUF_ID_POOL_i8
|
||||
#define GUF_ID_POOL_T int_least8_t
|
||||
#define GUF_ID_POOL_T_MAX GUF_INT8_MAX
|
||||
#define GUF_ID_POOL_NULL GUF_ID_I8_NULL
|
||||
#ifndef GUF_ID_POOL_NAME
|
||||
#define GUF_ID_POOL_NAME guf_idpool_i8
|
||||
#endif
|
||||
#elif defined(GUF_ID_POOL_u32)
|
||||
#undef GUF_ID_POOL_u32
|
||||
#define GUF_ID_POOL_T uint_least32_t
|
||||
#define GUF_ID_POOL_T_MAX (GUF_UINT32_MAX - 1)
|
||||
#define GUF_ID_POOL_NULL GUF_ID_U32_NULL
|
||||
#ifndef GUF_ID_POOL_NAME
|
||||
#define GUF_ID_POOL_NAME guf_idpool_u32
|
||||
#endif
|
||||
#elif defined(GUF_ID_POOL_u16)
|
||||
#undef GUF_ID_POOL_u16
|
||||
#define GUF_ID_POOL_T uint_least16_t
|
||||
#define GUF_ID_POOL_T_MAX (GUF_UINT16_MAX - 1)
|
||||
#define GUF_ID_POOL_NULL GUF_ID_U16_NULL
|
||||
#ifndef GUF_ID_POOL_NAME
|
||||
#define GUF_ID_POOL_NAME guf_idpool_u16
|
||||
#endif
|
||||
#elif defined(GUF_ID_POOL_u8)
|
||||
#undef GUF_ID_POOL_u8
|
||||
#define GUF_ID_POOL_T uint_least8_t
|
||||
#define GUF_ID_POOL_T_MAX (GUF_UINT8_MAX - 1)
|
||||
#define GUF_ID_POOL_NULL GUF_ID_U8_NULL
|
||||
#ifndef GUF_ID_POOL_NAME
|
||||
#define GUF_ID_POOL_NAME guf_idpool_u8
|
||||
#endif
|
||||
#else
|
||||
#error "Must define GUF_ID_POOL_i32, GUF_ID_POOL_i16, GUF_ID_POOL_i8, GUF_ID_POOL_u32, GUF_ID_POOL_u16, or GUF_ID_POOL_u8"
|
||||
#endif
|
||||
|
||||
#define GUF_ID_POOL_DBUF GUF_CAT(GUF_ID_POOL_NAME, _id_dbuf)
|
||||
|
||||
#define GUF_DBUF_NAME GUF_ID_POOL_DBUF
|
||||
#define GUF_T GUF_ID_POOL_T
|
||||
#define GUF_T_IS_INTEGRAL_TYPE
|
||||
#define GUF_DBUF_ONLY_TYPES
|
||||
#include "guf_dbuf.h"
|
||||
|
||||
#ifdef GUF_ID_POOL_ONLY_TYPES
|
||||
#undef GUF_ID_POOL_ONLY_TYPES
|
||||
typedef struct GUF_ID_POOL_NAME {
|
||||
GUF_ID_POOL_DBUF free_id_dbuf;
|
||||
GUF_ID_POOL_T next_id;
|
||||
} GUF_ID_POOL_NAME;
|
||||
#else
|
||||
|
||||
#ifndef GUF_ID_POOL_IMPL
|
||||
typedef struct GUF_ID_POOL_NAME {
|
||||
GUF_ID_POOL_DBUF free_id_dbuf; // Stores all the freed ids to reuse.
|
||||
GUF_ID_POOL_T next_id; // The next-id to assign in case we don't have any freed ids to reuse.
|
||||
} GUF_ID_POOL_NAME;
|
||||
|
||||
GUF_ID_POOL_NAME *GUF_CAT(GUF_ID_POOL_NAME, _try_init)(GUF_ID_POOL_NAME *pool, ptrdiff_t initial_cap, guf_allocator *allocator, guf_err *err);
|
||||
GUF_ID_POOL_NAME *GUF_CAT(GUF_ID_POOL_NAME, _init)(GUF_ID_POOL_NAME *pool, ptrdiff_t initial_cap, guf_allocator *allocator);
|
||||
|
||||
void GUF_CAT(GUF_ID_POOL_NAME, _free)(GUF_ID_POOL_NAME *pool, void *ctx);
|
||||
|
||||
GUF_ID_POOL_T GUF_CAT(GUF_ID_POOL_NAME, _try_alloc_id)(GUF_ID_POOL_NAME *pool, guf_err *err);
|
||||
GUF_ID_POOL_T GUF_CAT(GUF_ID_POOL_NAME, _alloc_id)(GUF_ID_POOL_NAME *pool);
|
||||
|
||||
void GUF_CAT(GUF_ID_POOL_NAME, _try_free_id)(GUF_ID_POOL_NAME *pool, GUF_ID_POOL_T id, guf_err *err);
|
||||
void GUF_CAT(GUF_ID_POOL_NAME, _free_id)(GUF_ID_POOL_NAME *pool, GUF_ID_POOL_T id);
|
||||
|
||||
GUF_ID_POOL_NAME ptrdiff_t GUF_CAT(GUF_ID_POOL_NAME, _num_allocated)(const GUF_ID_POOL_NAME *pool); // TODO: test
|
||||
|
||||
#endif
|
||||
|
||||
#if defined(GUF_ID_POOL_IMPL) || defined(GUF_ID_POOL_IMPL_STATIC)
|
||||
|
||||
#define GUF_DBUF_NAME GUF_ID_POOL_DBUF
|
||||
#define GUF_T GUF_ID_POOL_T
|
||||
#define GUF_T_IS_INTEGRAL_TYPE
|
||||
#define GUF_DBUF_WITHOUT_TYPES
|
||||
#define GUF_DBUF_IMPL_STATIC
|
||||
#include "guf_dbuf.h"
|
||||
|
||||
static bool GUF_CAT(GUF_ID_POOL_NAME, _is_valid)(const GUF_ID_POOL_NAME *pool)
|
||||
{
|
||||
if (!pool) {
|
||||
return false;
|
||||
}
|
||||
bool valid_next_id = (pool->next_id == GUF_ID_POOL_NULL) || (pool->next_id >= 0 && pool->next_id <= GUF_ID_POOL_T_MAX);
|
||||
bool valid_bufsize = ((pool->free_id_dbuf.size <= (ptrdiff_t)pool->next_id) || (pool->next_id == GUF_ID_POOL_NULL)) && (size_t)pool->free_id_dbuf.size <= (size_t)GUF_ID_POOL_T_MAX + 1;
|
||||
return valid_next_id && valid_bufsize && GUF_CAT(GUF_ID_POOL_DBUF, _valid)(&pool->free_id_dbuf) && pool->free_id_dbuf.size >= 0;
|
||||
}
|
||||
|
||||
GUF_ID_POOL_NAME ptrdiff_t GUF_CAT(GUF_ID_POOL_NAME, _num_allocated)(const GUF_ID_POOL_NAME *pool)
|
||||
{
|
||||
GUF_ASSERT((ptrdiff_t)pool->next_id >= free_id_dbuf.size)
|
||||
return pool->next_id - free_id_dbuf.size;
|
||||
}
|
||||
|
||||
GUF_ID_POOL_NAME *GUF_CAT(GUF_ID_POOL_NAME, _try_init)(GUF_ID_POOL_NAME *pool, ptrdiff_t initial_cap, guf_allocator *allocator, guf_err *err)
|
||||
{
|
||||
if (!pool) {
|
||||
guf_err_set_or_panic(err, GUF_ERR_NULL_PTR, GUF_ERR_MSG("in guf_idpool_try_init: pool is NULL"));
|
||||
return NULL;
|
||||
} else if (!allocator || !allocator->alloc || !allocator->realloc || !allocator->free) {
|
||||
guf_err_set_or_panic(err, GUF_ERR_NULL_PTR, GUF_ERR_MSG("in guf_idpool_try_init: allocator is NULL"));
|
||||
return NULL;
|
||||
} else if (initial_cap < 0) {
|
||||
guf_err_set_or_panic(err, GUF_ERR_INVALID_ARG, GUF_ERR_MSG("in guf_idpool_try_init: initial_cap is < 0"));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pool->free_id_dbuf = {0};
|
||||
GUF_CAT(GUF_ID_POOL_DBUF, _try_init)(&pool->free_id_dbuf, initial_cap, allocator, err);
|
||||
if (err && *err != GUF_ERR_NONE) {
|
||||
guf_err_set_or_panic(err, *err, GUF_ERR_MSG("in guf_idpool_try_init: failed to allocate dbuf"));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pool->next_id = 0;
|
||||
|
||||
guf_err_set_if_not_null(err, GUF_ERR_NONE);
|
||||
return pool;
|
||||
}
|
||||
|
||||
GUF_ID_POOL_NAME *GUF_CAT(GUF_ID_POOL_NAME, _init)(GUF_ID_POOL_NAME *pool, ptrdiff_t initial_cap, guf_allocator *allocator)
|
||||
{
|
||||
return GUF_CAT(GUF_ID_POOL_NAME, _try_init)(pool, initial_cap, allocator, NULL);
|
||||
}
|
||||
|
||||
void GUF_CAT(GUF_ID_POOL_NAME, _free)(GUF_ID_POOL_NAME *pool, void *ctx)
|
||||
{
|
||||
(void)ctx;
|
||||
if (!pool) {
|
||||
return;
|
||||
}
|
||||
GUF_ASSERT(GUF_CAT(GUF_ID_POOL_NAME, _is_valid)(pool));
|
||||
|
||||
GUF_CAT(GUF_ID_POOL_DBUF, _free)(&pool->free_id_dbuf, NULL);
|
||||
pool->free_id_dbuf = {0};
|
||||
pool->next_id = 0;
|
||||
}
|
||||
|
||||
GUF_ID_POOL_T GUF_CAT(GUF_ID_POOL_NAME, _try_alloc_id)(GUF_ID_POOL_NAME *pool, guf_err *err)
|
||||
{
|
||||
GUF_ASSERT(pool);
|
||||
GUF_ASSERT(GUF_CAT(GUF_ID_POOL_NAME, _is_valid)(pool));
|
||||
|
||||
if (pool->free_id_dbuf.size > 0) {
|
||||
GUF_ID_POOL_T id = *GUF_CAT(GUF_ID_POOL_DBUF, _back)(&pool->free_id_dbuf);
|
||||
GUF_CAT(GUF_ID_POOL_DBUF, _pop)(&pool->free_id_dbuf);
|
||||
GUF_ASSERT(id != GUF_ID_POOL_NULL && id <= GUF_ID_POOL_T_MAX);
|
||||
guf_err_set_if_not_null(err, GUF_ERR_NONE);
|
||||
return id;
|
||||
}
|
||||
|
||||
GUF_ID_POOL_T id = pool->next_id;
|
||||
if (id == GUF_ID_POOL_T_MAX || id == GUF_ID_POOL_NULL) {
|
||||
pool->next_id = GUF_ID_POOL_NULL;
|
||||
} else {
|
||||
pool->next_id += 1;
|
||||
}
|
||||
if (id == GUF_ID_POOL_NULL) {
|
||||
guf_err_set_or_panic(err, GUF_ERR_INT_OVERFLOW, GUF_ERR_MSG("in guf_idpool_try_alloc_id: Out of ids."));
|
||||
} else {
|
||||
guf_err_set_if_not_null(err, GUF_ERR_NONE);
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
GUF_ID_POOL_T GUF_CAT(GUF_ID_POOL_NAME, _alloc_id)(GUF_ID_POOL_NAME *pool)
|
||||
{
|
||||
return GUF_CAT(GUF_ID_POOL_NAME, _try_alloc_id)(pool, NULL);
|
||||
}
|
||||
|
||||
void GUF_CAT(GUF_ID_POOL_NAME, _try_free_id)(GUF_ID_POOL_NAME *pool, GUF_ID_POOL_T id, guf_err *err)
|
||||
{
|
||||
GUF_ASSERT(pool);
|
||||
GUF_ASSERT(GUF_CAT(GUF_ID_POOL_NAME, _is_valid)(pool));
|
||||
|
||||
if (id == GUF_ID_POOL_NULL) { // ID is NULL.
|
||||
guf_err_set_or_panic(err, GUF_ERR_IDX_RANGE, GUF_ERR_MSG("in guf_idpool_try_free_id: id is NULL"));
|
||||
return;
|
||||
} else if (id < 0 || id >= pool->next_id) { // ID is beyond the allocated range.
|
||||
guf_err_set_or_panic(err, GUF_ERR_IDX_RANGE, GUF_ERR_MSG("in guf_idpool_try_free_id: id < 0 or id >= pool->next_id"));
|
||||
return;
|
||||
} else if (pool->next_id == 0) { // There haven't been any IDs allocated yet.
|
||||
GUF_ASSERT(pool->free_id_dbuf.size == 0);
|
||||
guf_err_set_or_panic(err, GUF_ERR_IDX_RANGE, GUF_ERR_MSG("in guf_idpool_try_free_id: pool->next_id == 0"));
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
// NOTE: https://github.com/erincatto/box2d/issues/893 (last-retrieved 2025-04-09)
|
||||
if (id == pool->next_id - 1) { // Optimisation in case we remove the largest allocated id so far
|
||||
pool->next_id -= 1;
|
||||
guf_err_set_if_not_null(err, GUF_ERR_NONE);
|
||||
return;
|
||||
}
|
||||
*/
|
||||
|
||||
GUF_CAT(GUF_ID_POOL_DBUF, _try_push_val)(&pool->free_id_dbuf, id, err);
|
||||
if (err && *err != GUF_ERR_NONE) {
|
||||
guf_err_set_or_panic(err, *err, GUF_ERR_MSG("in guf_idpool_try_free_id: failed to push id"));
|
||||
} else {
|
||||
guf_err_set_if_not_null(err, GUF_ERR_NONE);
|
||||
}
|
||||
}
|
||||
|
||||
void GUF_CAT(GUF_ID_POOL_NAME, _free_id)(GUF_ID_POOL_NAME *pool, GUF_ID_POOL_T id)
|
||||
{
|
||||
GUF_CAT(GUF_ID_POOL_NAME, _try_free_id)(pool, id, NULL);
|
||||
}
|
||||
|
||||
#endif /* end impl */
|
||||
|
||||
#endif /* end only-types */
|
||||
|
||||
#undef GUF_ID_POOL_KWRDS
|
||||
#undef GUF_ID_POOL_IMPL
|
||||
#undef GUF_ID_POOL_IMPL_STATIC
|
||||
|
||||
#undef GUF_ID_POOL_T
|
||||
#undef GUF_ID_POOL_T_MAX
|
||||
#undef GUF_ID_POOL_NULL
|
||||
#undef GUF_ID_POOL_NAME
|
||||
|
||||
17
src/guf_init.h
Executable file
17
src/guf_init.h
Executable file
@ -0,0 +1,17 @@
|
||||
/*
|
||||
is parametrized: no, but must to be implemented once (libguf always requires implementing guf_assert and guf_hash)
|
||||
*/
|
||||
|
||||
#ifndef GUF_INIT_H
|
||||
#define GUF_INIT_H
|
||||
|
||||
#include "guf_common.h"
|
||||
|
||||
// Set up the global panic handler.
|
||||
#define GUF_INIT
|
||||
#include "guf_assert.h"
|
||||
|
||||
#define GUF_HASH_IMPL
|
||||
#include "guf_hash.h"
|
||||
|
||||
#endif
|
||||
891
src/guf_linalg.h
Executable file
891
src/guf_linalg.h
Executable file
@ -0,0 +1,891 @@
|
||||
/*
|
||||
is parametrized: no
|
||||
*/
|
||||
|
||||
#if defined(GUF_LINALG_IMPL_STATIC)
|
||||
#define GUF_LINALG_KWRDS static
|
||||
#else
|
||||
#define GUF_LINALG_KWRDS
|
||||
#endif
|
||||
|
||||
#ifndef GUF_LINALG_H
|
||||
#define GUF_LINALG_H
|
||||
#include "guf_assert.h"
|
||||
#include "guf_math.h"
|
||||
|
||||
#define GUF_QUAT_TO_EULER_EPS (1e-6f)
|
||||
#define GUF_QUAT_TO_AXIS_ANGLE_EPS (1e-6f)
|
||||
// #define GUF_QUAT_NORMALISE_EPS (1e-6f)
|
||||
|
||||
typedef struct guf_vec2 {
|
||||
float x, y;
|
||||
} guf_vec2;
|
||||
|
||||
typedef struct guf_vec3 {
|
||||
float x, y, z;
|
||||
} guf_vec3;
|
||||
|
||||
typedef struct guf_vec4 {
|
||||
float x, y, z, w;
|
||||
} guf_vec4;
|
||||
|
||||
// Row-major-matrix (vectors "are" column-vectors; correct multiplication order: mat * vec)
|
||||
// (e.g. the x-basis vector is (guf_vec4f){.x = m[0][0], .y = m[1][0], .z = m[2][0], .w = m[3][0]})
|
||||
typedef struct guf_mat4x4 {
|
||||
float data[4][4];
|
||||
} guf_mat4x4;
|
||||
|
||||
typedef struct guf_mat3x3 {
|
||||
float data[3][3];
|
||||
} guf_mat3x3;
|
||||
|
||||
typedef struct guf_quaternion {
|
||||
float x, y, z; // Vectorial (imaginary) part.
|
||||
float w; // Scalar (real) part.
|
||||
} guf_quaternion;
|
||||
|
||||
// Convention: Tait-Bryan angles
|
||||
// (order yaw-pitch-roll; intrinsic rotation; active rotation; right handed coordinate system)
|
||||
typedef struct guf_euler_angles {
|
||||
float yaw, pitch, roll;
|
||||
} guf_euler_angles;
|
||||
|
||||
typedef struct guf_axis_angle {
|
||||
guf_vec3 axis;
|
||||
float angle;
|
||||
} guf_axis_angle;
|
||||
|
||||
GUF_LINALG_KWRDS void guf_mat4x4_init_zero(guf_mat4x4 *m);
|
||||
GUF_LINALG_KWRDS void guf_mat4x4_init_identity(guf_mat4x4 *m);
|
||||
GUF_LINALG_KWRDS void guf_mat4x4_init_with_basis(guf_mat4x4 *m, guf_vec3 x_basis, guf_vec3 y_basis, guf_vec3 z_basis);
|
||||
GUF_LINALG_KWRDS void guf_mat4x4_init_from_quaternion(guf_mat4x4 *rotmat, guf_quaternion q);
|
||||
|
||||
GUF_LINALG_KWRDS void guf_mat3x3_init_zero(guf_mat3x3 *m);
|
||||
GUF_LINALG_KWRDS void guf_mat3x3_init_identity(guf_mat3x3 *m);
|
||||
GUF_LINALG_KWRDS void guf_mat3x3_init_with_basis(guf_mat3x3 *m, guf_vec3 x_basis, guf_vec3 y_basis, guf_vec3 z_basis);
|
||||
GUF_LINALG_KWRDS void guf_mat3x3_init_from_quaternion(guf_mat3x3 *rotmat, guf_quaternion q);
|
||||
|
||||
#ifdef __cplusplus
|
||||
// C++ has no restrict keyword like C99...
|
||||
GUF_LINALG_KWRDS void guf_mat4x4_transposed(const guf_mat4x4 *m, guf_mat4x4 *transposed);
|
||||
#else
|
||||
GUF_LINALG_KWRDS void guf_mat4x4_transposed(const guf_mat4x4 *m, guf_mat4x4 * restrict transposed);
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
// C++ has no restrict keyword like C99...
|
||||
GUF_LINALG_KWRDS void guf_mat3x3_transposed(const guf_mat3x3 *m, guf_mat3x3 *transposed);
|
||||
#else
|
||||
GUF_LINALG_KWRDS void guf_mat3x3_transposed(const guf_mat3x3 *m, guf_mat3x3 * restrict transposed);
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
// C++ has no restrict keyword like C99...
|
||||
GUF_LINALG_KWRDS void guf_mat4x4_mul(const guf_mat4x4 *a, const guf_mat4x4 *b, guf_mat4x4 *result);
|
||||
#else
|
||||
GUF_LINALG_KWRDS void guf_mat4x4_mul(const guf_mat4x4 *a, const guf_mat4x4 *b, guf_mat4x4 * restrict result);
|
||||
#endif
|
||||
|
||||
GUF_LINALG_KWRDS bool guf_mat4x4_inverted(const guf_mat4x4 *m, guf_mat4x4 *inverted); // Using Gaussian elemination/row-reduction.
|
||||
GUF_LINALG_KWRDS bool guf_mat3x3_inverted(const guf_mat3x3 *m, guf_mat3x3 *inverted); // Using a faster approach from wikipedia I don't get.
|
||||
|
||||
#ifdef __cplusplus
|
||||
// C++ has no restrict keyword like C99...
|
||||
GUF_LINALG_KWRDS void guf_mat3x3_mul(const guf_mat3x3 *a, const guf_mat3x3 *b, guf_mat3x3 *result);
|
||||
#else
|
||||
GUF_LINALG_KWRDS void guf_mat3x3_mul(const guf_mat3x3 *a, const guf_mat3x3 *b, guf_mat3x3 * restrict result);
|
||||
#endif
|
||||
|
||||
static inline void guf_mat4x4_set_trans(guf_mat4x4 *m, guf_vec3 trans)
|
||||
{
|
||||
GUF_ASSERT(m);
|
||||
m->data[0][3] = trans.x;
|
||||
m->data[1][3] = trans.y;
|
||||
m->data[2][3] = trans.z;
|
||||
}
|
||||
|
||||
GUF_LINALG_KWRDS void guf_mat4x4_set_rotmat(guf_mat4x4 *m, const guf_mat3x3 *rotmat_3x3);
|
||||
|
||||
GUF_LINALG_KWRDS bool guf_mat4x4_is_identity(const guf_mat4x4 *m, float eps);
|
||||
GUF_LINALG_KWRDS bool guf_mat3x3_is_identity(const guf_mat3x3 *m, float eps);
|
||||
|
||||
GUF_LINALG_KWRDS bool guf_mat4x4_nearly_equal(const guf_mat4x4 *a, const guf_mat4x4 *b, float rel_tol, float abs_tol);
|
||||
GUF_LINALG_KWRDS bool guf_mat3x3_nearly_equal(const guf_mat3x3 *a, const guf_mat3x3 *b, float rel_tol, float abs_tol);
|
||||
|
||||
GUF_LINALG_KWRDS guf_vec3 guf_vec3_transformed(const guf_mat4x4 *mat, const guf_vec3 *v);
|
||||
GUF_LINALG_KWRDS guf_vec4 guf_vec4_transformed(const guf_mat4x4 *mat, const guf_vec4 *v);
|
||||
|
||||
static inline guf_vec2 guf_vec2_add(guf_vec2 a, guf_vec2 b)
|
||||
{
|
||||
guf_vec2 sum = {
|
||||
.x = a.x + b.x,
|
||||
.y = a.y + b.y
|
||||
};
|
||||
return sum;
|
||||
}
|
||||
static inline guf_vec2 guf_vec2_sub(guf_vec2 a, guf_vec2 b)
|
||||
{
|
||||
guf_vec2 diff = {
|
||||
.x = a.x - b.x,
|
||||
.y = a.y - b.y
|
||||
};
|
||||
return diff;
|
||||
}
|
||||
static inline guf_vec2 guf_vec2_scaled(guf_vec2 a, float scale)
|
||||
{
|
||||
guf_vec2 s = {.x = a.x * scale, .y = a.y * scale};
|
||||
return s;
|
||||
}
|
||||
|
||||
static inline guf_vec3 guf_vec3_add(guf_vec3 a, guf_vec3 b)
|
||||
{
|
||||
guf_vec3 sum = {
|
||||
.x = a.x + b.x,
|
||||
.y = a.y + b.y,
|
||||
.z = a.z + b.z
|
||||
};
|
||||
return sum;
|
||||
}
|
||||
static inline guf_vec3 guf_vec3_sub(guf_vec3 a, guf_vec3 b)
|
||||
{
|
||||
guf_vec3 diff = {
|
||||
.x = a.x - b.x,
|
||||
.y = a.y - b.y,
|
||||
.z = a.z - b.z
|
||||
};
|
||||
return diff;
|
||||
}
|
||||
static inline guf_vec3 guf_vec3_scaled(guf_vec3 a, float scale)
|
||||
{
|
||||
guf_vec3 s = {.x = a.x * scale, .y = a.y * scale, .z = a.z * scale};
|
||||
return s;
|
||||
}
|
||||
|
||||
static inline float guf_vec2_mag(guf_vec2 v) {return sqrtf(v.x * v.x + v.y * v.y);}
|
||||
static inline float guf_vec3_mag(guf_vec3 v) {return sqrtf(v.x * v.x + v.y * v.y + v.z * v.z);}
|
||||
static inline float guf_vec2_mag_squared(guf_vec2 v) {return v.x * v.x + v.y * v.y;}
|
||||
static inline float guf_vec3_mag_squared(guf_vec3 v) {return v.x * v.x + v.y * v.y + v.z * v.z;}
|
||||
|
||||
static inline float guf_vec2_dot(guf_vec2 a, guf_vec2 b) {return a.x * b.x + a.y * b.y;}
|
||||
static inline float guf_vec3_dot(guf_vec3 a, guf_vec3 b) {return a.x * b.x + a.y * b.y + a.z * b.z;}
|
||||
|
||||
static inline guf_vec3 guf_vec3_cross(guf_vec3 a, guf_vec3 b)
|
||||
{
|
||||
guf_vec3 cross = {
|
||||
.x = a.y * b.z - a.z * b.y,
|
||||
.y = a.z * b.x - a.x * b.z,
|
||||
.z = a.x * b.y - a.y * b.x
|
||||
};
|
||||
return cross;
|
||||
}
|
||||
static inline float guf_vec2_perpdot(guf_vec2 a, guf_vec2 b)
|
||||
{
|
||||
return a.x * b.y - a.y * b.x; // The z-component of a 3d cross-product of two vectors in a plane.
|
||||
}
|
||||
|
||||
GUF_LINALG_KWRDS guf_vec2 *guf_vec2_normalise(guf_vec2 *v);
|
||||
GUF_LINALG_KWRDS guf_vec3 *guf_vec3_normalise(guf_vec3 *v);
|
||||
|
||||
static inline guf_vec2 guf_vec2_normalised(guf_vec2 v) {return *guf_vec2_normalise(&v);}
|
||||
static inline guf_vec3 guf_vec3_normalised(guf_vec3 v) {return *guf_vec3_normalise(&v);}
|
||||
|
||||
GUF_LINALG_KWRDS guf_vec3 guf_vec4_to_vec3(guf_vec4 v);
|
||||
|
||||
static inline void guf_quaternion_init_identity(guf_quaternion *q)
|
||||
{
|
||||
GUF_ASSERT(q);
|
||||
q->w = 1;
|
||||
q->x = q->y = q->z = 0;
|
||||
}
|
||||
|
||||
static inline float guf_quaternion_mag(guf_quaternion q)
|
||||
{
|
||||
return sqrtf(q.w * q.w + q.x * q.x + q.y * q.y + q.z * q.z);
|
||||
}
|
||||
|
||||
static inline float guf_quaternion_mag_squared(guf_quaternion q)
|
||||
{
|
||||
return q.w * q.w + q.x * q.x + q.y * q.y + q.z * q.z;
|
||||
}
|
||||
|
||||
GUF_LINALG_KWRDS guf_quaternion *guf_quaternion_normalise(guf_quaternion *q);
|
||||
|
||||
static inline guf_quaternion guf_quaternion_normalised(guf_quaternion q)
|
||||
{
|
||||
return *guf_quaternion_normalise(&q);
|
||||
}
|
||||
|
||||
static inline guf_quaternion guf_quaternion_inverted(guf_quaternion q)
|
||||
{
|
||||
q.x = -q.x;
|
||||
q.y = -q.y;
|
||||
q.z = -q.z;
|
||||
return q;
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
// C++ has no restrict keyword like C99...
|
||||
GUF_LINALG_KWRDS void guf_quaternion_mul(const guf_quaternion *a, const guf_quaternion *b, guf_quaternion *result);
|
||||
#else
|
||||
GUF_LINALG_KWRDS void guf_quaternion_mul(const guf_quaternion *a, const guf_quaternion *b, guf_quaternion * restrict result);
|
||||
#endif
|
||||
|
||||
GUF_LINALG_KWRDS guf_vec3 *guf_vec3_rotate_by_quat(guf_vec3 *p, const guf_quaternion *q);
|
||||
static inline guf_vec3 guf_vec3_rotated_by_quat(guf_vec3 p, const guf_quaternion *q)
|
||||
{
|
||||
return *guf_vec3_rotate_by_quat(&p, q);
|
||||
}
|
||||
|
||||
GUF_LINALG_KWRDS guf_quaternion guf_quaternion_from_axis_angle(float angle, guf_vec3 unit_axis);
|
||||
|
||||
GUF_LINALG_KWRDS guf_quaternion guf_quaternion_from_euler(guf_euler_angles euler);
|
||||
GUF_LINALG_KWRDS guf_euler_angles guf_quaternion_to_euler(guf_quaternion q);
|
||||
|
||||
GUF_LINALG_KWRDS void guf_mat4x4_print(const guf_mat4x4 *m, FILE *outfile);
|
||||
GUF_LINALG_KWRDS void guf_mat4x4_print_with_precision(const guf_mat4x4 *m, FILE *outfile, int num_frac_digits);
|
||||
#endif
|
||||
|
||||
|
||||
#if defined(GUF_LINALG_IMPL) || defined(GUF_LINALG_IMPL_STATIC)
|
||||
|
||||
#include <string.h>
|
||||
|
||||
GUF_LINALG_KWRDS void guf_mat4x4_init_zero(guf_mat4x4 *m)
|
||||
{
|
||||
GUF_ASSERT(m);
|
||||
for (int row = 0; row < 4; ++row) {
|
||||
for (int col = 0; col < 4; ++col) {
|
||||
m->data[row][col] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GUF_LINALG_KWRDS void guf_mat3x3_init_zero(guf_mat3x3 *m)
|
||||
{
|
||||
GUF_ASSERT(m);
|
||||
for (int row = 0; row < 3; ++row) {
|
||||
for (int col = 0; col < 3; ++col) {
|
||||
m->data[row][col] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GUF_LINALG_KWRDS void guf_mat4x4_init_identity(guf_mat4x4 *m)
|
||||
{
|
||||
GUF_ASSERT(m);
|
||||
guf_mat4x4_init_zero(m);
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
m->data[i][i] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
GUF_LINALG_KWRDS void guf_mat3x3_init_identity(guf_mat3x3 *m)
|
||||
{
|
||||
GUF_ASSERT(m);
|
||||
guf_mat3x3_init_zero(m);
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
m->data[i][i] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
GUF_LINALG_KWRDS void guf_mat4x4_init_with_basis(guf_mat4x4 *m, guf_vec3 x_basis, guf_vec3 y_basis, guf_vec3 z_basis)
|
||||
{
|
||||
GUF_ASSERT(m);
|
||||
|
||||
m->data[0][0] = x_basis.x;
|
||||
m->data[1][0] = x_basis.y;
|
||||
m->data[2][0] = x_basis.z;
|
||||
m->data[3][0] = 0;
|
||||
|
||||
m->data[0][1] = y_basis.x;
|
||||
m->data[1][1] = y_basis.y;
|
||||
m->data[2][1] = y_basis.z;
|
||||
m->data[3][1] = 0;
|
||||
|
||||
m->data[0][2] = z_basis.x;
|
||||
m->data[1][2] = z_basis.y;
|
||||
m->data[2][2] = z_basis.z;
|
||||
m->data[3][2] = 0;
|
||||
|
||||
m->data[0][3] = m->data[1][3] = m->data[2][3] = 0;
|
||||
m->data[3][3] = 1;
|
||||
}
|
||||
|
||||
GUF_LINALG_KWRDS void guf_mat3x3_init_with_basis(guf_mat3x3 *m, guf_vec3 x_basis, guf_vec3 y_basis, guf_vec3 z_basis)
|
||||
{
|
||||
GUF_ASSERT(m);
|
||||
|
||||
m->data[0][0] = x_basis.x;
|
||||
m->data[1][0] = x_basis.y;
|
||||
m->data[2][0] = x_basis.z;
|
||||
|
||||
m->data[0][1] = y_basis.x;
|
||||
m->data[1][1] = y_basis.y;
|
||||
m->data[2][1] = y_basis.z;
|
||||
|
||||
m->data[0][2] = z_basis.x;
|
||||
m->data[1][2] = z_basis.y;
|
||||
m->data[2][2] = z_basis.z;
|
||||
}
|
||||
|
||||
#if defined(__cplusplus) && defined(GUF_LINALG_IMPL_STATIC)
|
||||
GUF_LINALG_KWRDS void guf_mat4x4_mul(const guf_mat4x4 *a, const guf_mat4x4 *b, guf_mat4x4 *result)
|
||||
#else
|
||||
GUF_LINALG_KWRDS void guf_mat4x4_mul(const guf_mat4x4 *a, const guf_mat4x4 *b, guf_mat4x4 * restrict result)
|
||||
#endif
|
||||
{
|
||||
GUF_ASSERT(a && b && result);
|
||||
GUF_ASSERT(a != result && b != result);
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
for (int j = 0; j < 4; ++j) {
|
||||
result->data[j][i] = 0;
|
||||
for (int k = 0; k < 4; ++k) {
|
||||
result->data[j][i] += a->data[j][k] * b->data[k][i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(__cplusplus) && defined(GUF_LINALG_IMPL_STATIC)
|
||||
GUF_LINALG_KWRDS void guf_mat3x3_mul(const guf_mat3x3 *a, const guf_mat3x3 *b, guf_mat3x3 *result)
|
||||
#else
|
||||
GUF_LINALG_KWRDS void guf_mat3x3_mul(const guf_mat3x3 *a, const guf_mat3x3 *b, guf_mat3x3 * restrict result)
|
||||
#endif
|
||||
{
|
||||
GUF_ASSERT(a && b && result);
|
||||
GUF_ASSERT(a != result && b != result);
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
for (int j = 0; j < 3; ++j) {
|
||||
result->data[j][i] = 0;
|
||||
for (int k = 0; k < 3; ++k) {
|
||||
result->data[j][i] += a->data[j][k] * b->data[k][i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(__cplusplus) && defined(GUF_LINALG_IMPL_STATIC)
|
||||
GUF_LINALG_KWRDS void guf_mat4x4_transposed(const guf_mat4x4 *m, guf_mat4x4 *transposed)
|
||||
#else
|
||||
GUF_LINALG_KWRDS void guf_mat4x4_transposed(const guf_mat4x4 *m, guf_mat4x4 * restrict transposed)
|
||||
#endif
|
||||
{
|
||||
GUF_ASSERT(m && transposed);
|
||||
GUF_ASSERT(m != transposed);
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
for (int j = 0; j < 4; ++j) {
|
||||
transposed->data[i][j] = m->data[j][i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(__cplusplus) && defined(GUF_LINALG_IMPL_STATIC)
|
||||
GUF_LINALG_KWRDS void guf_mat3x3_transposed(const guf_mat3x3 *m, guf_mat3x3 *transposed)
|
||||
#else
|
||||
GUF_LINALG_KWRDS void guf_mat3x3_transposed(const guf_mat3x3 *m, guf_mat3x3 * restrict transposed)
|
||||
#endif
|
||||
{
|
||||
GUF_ASSERT(m && transposed);
|
||||
GUF_ASSERT(m != transposed);
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
for (int j = 0; j < 3; ++j) {
|
||||
transposed->data[i][j] = m->data[j][i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GUF_LINALG_KWRDS void guf_mat4x4_set_rotmat(guf_mat4x4 *m, const guf_mat3x3 *rotmat_3x3)
|
||||
{
|
||||
GUF_ASSERT(m && rotmat_3x3)
|
||||
for (int row = 0; row < 3; ++row) {
|
||||
for (int col = 0; col < 3; ++col) {
|
||||
m->data[row][col] = rotmat_3x3->data[row][col];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GUF_LINALG_KWRDS bool guf_mat3x3_is_identity(const guf_mat3x3 *m, float eps)
|
||||
{
|
||||
for (int row = 0; row < 3; ++row) {
|
||||
for (int col = 0; col < 3; ++col) {
|
||||
if (row == col) { // Check 1-entries.
|
||||
if (!guf_nearly_one_f32(m->data[row][col], eps)) {
|
||||
return false;
|
||||
}
|
||||
} else { // Check 0-entries.
|
||||
if (!guf_nearly_zero_f32(m->data[row][col], eps)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
GUF_LINALG_KWRDS bool guf_mat4x4_is_identity(const guf_mat4x4 *m, float eps)
|
||||
{
|
||||
for (int row = 0; row < 4; ++row) {
|
||||
for (int col = 0; col < 4; ++col) {
|
||||
if (row == col) { // Check 1-entries.
|
||||
if (!guf_nearly_one_f32(m->data[row][col], eps)) {
|
||||
return false;
|
||||
}
|
||||
} else { // Check 0-entries.
|
||||
if (!guf_nearly_zero_f32(m->data[row][col], eps)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
GUF_LINALG_KWRDS bool guf_mat3x3_nearly_equal(const guf_mat3x3 *a, const guf_mat3x3 *b, float rel_tol, float abs_tol)
|
||||
{
|
||||
GUF_ASSERT(a && b);
|
||||
for (int row = 0; row < 3; ++row) {
|
||||
for (int col = 0; col < 3; ++col) {
|
||||
if (!guf_isclose_f32(a->data[row][col], b->data[row][col], rel_tol, abs_tol)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
GUF_LINALG_KWRDS bool guf_mat4x4_nearly_equal(const guf_mat4x4 *a, const guf_mat4x4 *b, float rel_tol, float abs_tol)
|
||||
{
|
||||
GUF_ASSERT(a && b);
|
||||
for (int row = 0; row < 4; ++row) {
|
||||
for (int col = 0; col < 4; ++col) {
|
||||
if (!guf_isclose_f32(a->data[row][col], b->data[row][col], rel_tol, abs_tol)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void guf_mat4x4_swap_rows(guf_mat4x4 *m, int row_a_idx, int row_b_idx)
|
||||
{
|
||||
GUF_ASSERT(row_a_idx >= 0 && row_a_idx < 4);
|
||||
GUF_ASSERT(row_b_idx >= 0 && row_b_idx < 4);
|
||||
|
||||
if (row_a_idx == row_b_idx) {
|
||||
return;
|
||||
}
|
||||
|
||||
const size_t ROW_BYTES = sizeof(m->data[0]);
|
||||
float row_a_cpy[4];
|
||||
memcpy(row_a_cpy, m->data + row_a_idx, ROW_BYTES); // row_a_cpy := row_a
|
||||
memcpy(m->data + row_a_idx, m->data + row_b_idx, ROW_BYTES); // row_a := row_b
|
||||
memcpy(m->data + row_b_idx, row_a_cpy, ROW_BYTES); // row_b := row_a_cpy
|
||||
}
|
||||
|
||||
/*
|
||||
Find the inverse of a 4x4 matrix using gaussian elimination (if it is invertible, returns true, false otherwise).
|
||||
cf. https://en.wikipedia.org/wiki/Gaussian_elimination#Finding_the_inverse_of_a_matrix (last-retrieved 2025-03-07)
|
||||
Note: In the special case of square orthonormal matrices, the matrix inverse can be calculated more efficiently
|
||||
and accurately using matrix transposition (e.g. valid 3x3 rotation matrices can be inverted by transposing them).
|
||||
*/
|
||||
GUF_LINALG_KWRDS bool guf_mat4x4_inverted(const guf_mat4x4 *m, guf_mat4x4 *inverted)
|
||||
{
|
||||
GUF_ASSERT(m && inverted);
|
||||
|
||||
guf_mat4x4_init_identity(inverted); // This matrix starts as the identity matrix and will be transfomed into the inverse.
|
||||
guf_mat4x4 rref = *m; // This initial matrix will be transformed into the identity matrix if m is invertible.
|
||||
|
||||
const float ASSERT_EPS = 1e-3f;
|
||||
const float EPS = 1e-18f;
|
||||
|
||||
for (int row_start = 0; row_start < 3; ++row_start) { // 1.) Try to bring into row echelon form.
|
||||
int max_pivot_row = -1;
|
||||
float max_pivot_abs = -INFINITY;
|
||||
for (int row = row_start; row < 4; ++row) { // Find max pivot element in the current column ("Partial pivoting")
|
||||
const float candidate = fabsf(rref.data[row][row_start]);
|
||||
if (!guf_nearly_zero_f32(candidate, EPS) && candidate > max_pivot_abs) {
|
||||
max_pivot_abs = candidate;
|
||||
max_pivot_row = row;
|
||||
}
|
||||
}
|
||||
if (max_pivot_row == -1) { // If all potential pivots were zero, m is not invertible.
|
||||
GUF_ASSERT(max_pivot_abs == -INFINITY);
|
||||
GUF_ASSERT(!guf_mat4x4_is_identity(&rref, ASSERT_EPS));
|
||||
return false;
|
||||
}
|
||||
GUF_ASSERT(max_pivot_abs > 0);
|
||||
|
||||
// Swap the found pivot-row with the row at row_start.
|
||||
guf_mat4x4_swap_rows(&rref, row_start, max_pivot_row);
|
||||
guf_mat4x4_swap_rows(inverted, row_start, max_pivot_row);
|
||||
const int pivot_idx = row_start; // Same for pivot_col and pivot_row (pivot is the diagonal element)
|
||||
const float pivot_val = rref.data[pivot_idx][pivot_idx];
|
||||
GUF_ASSERT(!isnan(pivot_val));
|
||||
GUF_ASSERT(!guf_nearly_zero_f32(pivot_val, EPS));
|
||||
|
||||
for (int row = pivot_idx + 1; row < 4; ++row) { // Make all values below the pivot element zero.
|
||||
// We want to turn rref.data[row][pivot_idx] into zero.
|
||||
// pivot_val * scale = rref.data[row][pivot_idx] <=> scale = rref.data[row][pivot_idx] / pivot_val
|
||||
const float scale = rref.data[row][pivot_idx] / pivot_val; // TODO: Not sure if it's more accurate to not pre-calculate the scale (like in commit dac1d159b1cfa7d6cd71236d693b2cab6218ba51 (Fri Mar 7 15:37:55 2025 +0100))
|
||||
for (int col = 0; col < 4; ++col) {
|
||||
if (col >= pivot_idx) {
|
||||
rref.data[row][col] -= rref.data[pivot_idx][col] * scale; // Subtract the scaled pivot-row from row
|
||||
} else {
|
||||
GUF_ASSERT(rref.data[pivot_idx][col] == 0);
|
||||
GUF_ASSERT(rref.data[row][col] == 0);
|
||||
}
|
||||
inverted->data[row][col] -= inverted->data[pivot_idx][col] * scale;
|
||||
}
|
||||
rref.data[row][pivot_idx] = 0; // -= rref.data[pivot_idx][pivot_idx] * rref.data[row][pivot_idx] / pivot_val
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 3; i >= 0; --i) { // 2.) Try to bring into *reduced* row echelon form.
|
||||
if (rref.data[i][i] == 0) {
|
||||
return false;
|
||||
}
|
||||
for (int col = 0; col < 4; ++col) { // Scale row_i to make elem[i][i] == 1
|
||||
inverted->data[i][col] /= rref.data[i][i];
|
||||
GUF_ASSERT((col != i) ? rref.data[i][col] == 0 : rref.data[i][col] != 0);
|
||||
}
|
||||
rref.data[i][i] = 1; // rref.data[i][i] / rref.data[i][i] == 1
|
||||
|
||||
for (int row = i - 1; row >= 0; --row) { // Make all elements in column above rref.data[i][i] zero (by adding a scaled row_i to row).
|
||||
for (int col = 0; col < 4; ++col) {
|
||||
inverted->data[row][col] -= inverted->data[i][col] * rref.data[row][i];
|
||||
}
|
||||
rref.data[row][i] = 0; // -= rref.data[i][i] * rref.data[row][i] (== 0)
|
||||
}
|
||||
}
|
||||
|
||||
GUF_ASSERT(guf_mat4x4_is_identity(&rref, ASSERT_EPS));
|
||||
(void)ASSERT_EPS;
|
||||
return true;
|
||||
}
|
||||
|
||||
GUF_LINALG_KWRDS bool guf_mat3x3_inverted(const guf_mat3x3 *m, guf_mat3x3 *inverted)
|
||||
{
|
||||
// TODO: Test this.
|
||||
GUF_ASSERT(m && inverted);
|
||||
const float EPS = 1e-12f; // TODO: Not sure about this EPS.
|
||||
|
||||
// cf. https://en.wikipedia.org/wiki/Determinant (last-retrieved 2025-03-08)
|
||||
const float det = m->data[0][0] * m->data[1][1] * m->data[2][2] +
|
||||
m->data[0][1] * m->data[1][2] * m->data[2][0] +
|
||||
m->data[0][2] * m->data[1][0] * m->data[2][1] -
|
||||
m->data[0][1] * m->data[1][0] * m->data[2][2] -
|
||||
m->data[0][0] * m->data[1][2] * m->data[2][1];
|
||||
|
||||
if (guf_nearly_zero_f32(det, EPS)) { // m is only invertible if the determinant != 0
|
||||
return false;
|
||||
}
|
||||
|
||||
// cf. https://en.wikipedia.org/wiki/Invertible_matrix#Inversion_of_3_%C3%97_3_matrices (last-retrieved 2025-03-08)
|
||||
// (I don't understand this method of matrix inversion, but it looks fast...)
|
||||
const float A = (m->data[1][1] * m->data[2][2] - m->data[1][2] * m->data[2][1]);
|
||||
const float B = -(m->data[1][0] * m->data[2][2] - m->data[1][2] * m->data[2][0]);
|
||||
const float C = (m->data[1][0] * m->data[2][1] - m->data[1][1] * m->data[2][0]);
|
||||
const float D = -(m->data[0][1] * m->data[2][2] - m->data[0][2] * m->data[2][1]);
|
||||
const float E = (m->data[0][0] * m->data[2][2] - m->data[0][2] * m->data[2][0]);
|
||||
const float F = -(m->data[0][0] * m->data[2][1] - m->data[0][1] * m->data[2][0]);
|
||||
const float G = (m->data[0][1] * m->data[1][2] - m->data[0][2] * m->data[1][1]);
|
||||
const float H = -(m->data[0][0] * m->data[1][2] - m->data[0][2] * m->data[1][0]);
|
||||
const float I = (m->data[0][0] * m->data[1][1] - m->data[0][1] * m->data[1][0]);
|
||||
|
||||
inverted->data[0][0] = A / det;
|
||||
inverted->data[1][0] = B / det;
|
||||
inverted->data[2][0] = C / det;
|
||||
|
||||
inverted->data[0][1] = D / det;
|
||||
inverted->data[1][1] = E / det;
|
||||
inverted->data[2][1] = F / det;
|
||||
|
||||
inverted->data[0][2] = G / det;
|
||||
inverted->data[1][2] = H / det;
|
||||
inverted->data[2][2] = I / det;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Assumes the last row of mat is [0, 0, 0, 1] and v->w implicitly is 1 (affine transformation)
|
||||
GUF_LINALG_KWRDS guf_vec3 guf_vec3_transformed(const guf_mat4x4 *mat, const guf_vec3 *v)
|
||||
{
|
||||
GUF_ASSERT(mat && v);
|
||||
guf_vec3 v_transformed = {
|
||||
.x = mat->data[0][0] * v->x + mat->data[0][1] * v->y + mat->data[0][2] * v->z + mat->data[0][3],
|
||||
.y = mat->data[1][0] * v->x + mat->data[1][1] * v->y + mat->data[1][2] * v->z + mat->data[1][3],
|
||||
.z = mat->data[2][0] * v->x + mat->data[2][1] * v->y + mat->data[2][2] * v->z + mat->data[2][3]
|
||||
};
|
||||
return v_transformed;
|
||||
}
|
||||
|
||||
GUF_LINALG_KWRDS guf_vec4 guf_vec4_transformed(const guf_mat4x4 *mat, const guf_vec4 *v)
|
||||
{
|
||||
GUF_ASSERT(mat && v);
|
||||
guf_vec4 v_transformed = {
|
||||
.x = mat->data[0][0] * v->x + mat->data[0][1] * v->y + mat->data[0][2] * v->z + v->w * mat->data[0][3],
|
||||
.y = mat->data[1][0] * v->x + mat->data[1][1] * v->y + mat->data[1][2] * v->z + v->w * mat->data[1][3],
|
||||
.z = mat->data[2][0] * v->x + mat->data[2][1] * v->y + mat->data[2][2] * v->z + v->w * mat->data[2][3],
|
||||
.w = mat->data[3][0] * v->x + mat->data[3][1] * v->y + mat->data[3][2] * v->z + v->w * mat->data[3][3],
|
||||
};
|
||||
return v_transformed;
|
||||
}
|
||||
|
||||
GUF_LINALG_KWRDS guf_vec2 *guf_vec2_normalise(guf_vec2 *v)
|
||||
{
|
||||
GUF_ASSERT(v);
|
||||
const float mag = guf_vec2_mag(*v);
|
||||
if (mag == 0 || isnan(mag)) {
|
||||
v->x = v->y = (float)INFINITY;
|
||||
} else {
|
||||
v->x /= mag;
|
||||
v->y /= mag;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
GUF_LINALG_KWRDS guf_vec3 *guf_vec3_normalise(guf_vec3 *v)
|
||||
{
|
||||
GUF_ASSERT(v);
|
||||
const float mag = guf_vec3_mag(*v);
|
||||
if (mag == 0 || isnan(mag)) {
|
||||
v->x = v->y = v->z = (float)INFINITY;
|
||||
} else {
|
||||
v->x /= mag;
|
||||
v->y /= mag;
|
||||
v->z /= mag;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
GUF_LINALG_KWRDS guf_vec3 guf_vec4_to_vec3(guf_vec4 v)
|
||||
{
|
||||
if (guf_isclose_abstol_f32(v.w, 0, 1e-21f)) { // If w is extremely close 0, we treat it as zero.
|
||||
guf_vec3 inf_vec = {(float)INFINITY, (float)INFINITY, (float)INFINITY};
|
||||
return inf_vec;
|
||||
} else if (guf_isclose_reltol_f32(v.w, 1, 1e-9f)) { // If w is almost 1, we treat it as exactly 1.
|
||||
guf_vec3 vec = {.x = v.x, .y = v.y, .z = v.z};
|
||||
return vec;
|
||||
} else {
|
||||
guf_vec3 vec = {.x = v.x / v.w, .y = v.y / v.w, .z = v.z / v.w};
|
||||
return vec;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
(Unit)quaternion operations:
|
||||
cf. https://danceswithcode.net/engineeringnotes/quaternions/quaternions.html (last-retrieved 2025-03-06)
|
||||
*/
|
||||
|
||||
GUF_LINALG_KWRDS guf_quaternion guf_quaternion_from_axis_angle(float angle, guf_vec3 unit_axis)
|
||||
{
|
||||
const float sin_halfangle = sinf(angle / 2.f);
|
||||
guf_quaternion q = {
|
||||
.w = cosf(angle / 2.f),
|
||||
.x = unit_axis.x * sin_halfangle,
|
||||
.y = unit_axis.y * sin_halfangle,
|
||||
.z = unit_axis.z * sin_halfangle,
|
||||
};
|
||||
return q;
|
||||
}
|
||||
|
||||
GUF_LINALG_KWRDS guf_quaternion guf_quaternion_from_euler(guf_euler_angles euler)
|
||||
{
|
||||
const float r = euler.roll / 2;
|
||||
const float p = euler.pitch / 2;
|
||||
const float y = euler.yaw / 2;
|
||||
guf_quaternion q = {
|
||||
.w = cosf(r) * cosf(p) * cosf(y) + sinf(r) * sinf(p) * sinf(y),
|
||||
.x = sinf(r) * cosf(p) * cosf(y) - cosf(r) * sinf(p) * sinf(y),
|
||||
.y = cosf(r) * sinf(p) * cosf(y) + sinf(r) * cosf(p) * sinf(y),
|
||||
.z = cosf(r) * cosf(p) * sinf(y) + sinf(r) * sinf(p) * cosf(y)
|
||||
};
|
||||
return q;
|
||||
}
|
||||
|
||||
GUF_LINALG_KWRDS guf_euler_angles guf_quaternion_to_euler(guf_quaternion q)
|
||||
{
|
||||
guf_euler_angles euler = {0};
|
||||
euler.pitch = asinf(2 * (q.w * q.y - q.x * q.z));
|
||||
|
||||
const float half_pi = GUF_PI_F32 / 2;
|
||||
if (fabsf(euler.pitch - half_pi) <= GUF_QUAT_TO_EULER_EPS) { // Gimbal lock: pitch == pi/2
|
||||
euler.roll = 0;
|
||||
euler.yaw = -2 * atan2f(q.x, q.w);
|
||||
} else if (fabsf(euler.pitch - (-half_pi)) <= GUF_QUAT_TO_EULER_EPS) { // Gimbal lock: pitch == -pi/2
|
||||
euler.roll = 0;
|
||||
euler.yaw = 2 * atan2f(q.x, q.w);
|
||||
} else { // No gimbal-lock
|
||||
euler.roll = atan2f(2 * (q.w * q.x + q.y * q.z), q.w*q.w - q.x*q.x - q.y*q.y + q.z*q.z);
|
||||
euler.yaw = atan2f(2 * (q.w * q.z + q.x * q.y), q.w*q.w + q.x*q.x - q.y*q.y - q.z*q.z);
|
||||
}
|
||||
return euler;
|
||||
}
|
||||
|
||||
GUF_LINALG_KWRDS float guf_quaternion_to_axis_angle(const guf_quaternion *q, guf_vec3 *axis_result)
|
||||
{
|
||||
GUF_ASSERT(q);
|
||||
if (fabsf(q->w - 1) <= GUF_QUAT_TO_AXIS_ANGLE_EPS) { // Singularity: q->w is very close to 1 (identity-quaternions produce no rotation).
|
||||
const float angle = 0;
|
||||
if (axis_result) {
|
||||
axis_result->x = axis_result->y = axis_result->z = 0;
|
||||
}
|
||||
return angle;
|
||||
}
|
||||
|
||||
const float angle = 2 * acosf(q->w);
|
||||
if (axis_result) {
|
||||
const float sin_halfangle = sinf(angle / 2.f);
|
||||
axis_result->x = q->x / sin_halfangle;
|
||||
axis_result->y = q->y / sin_halfangle;
|
||||
axis_result->z = q->z / sin_halfangle;
|
||||
}
|
||||
return angle;
|
||||
}
|
||||
|
||||
GUF_LINALG_KWRDS void guf_mat4x4_init_from_quaternion(guf_mat4x4 *rotmat, guf_quaternion q)
|
||||
{
|
||||
GUF_ASSERT(rotmat);
|
||||
|
||||
rotmat->data[0][0] = 1 - 2 * q.y * q.y - 2 * q.z * q.z;
|
||||
rotmat->data[0][1] = 2 * q.x * q.y - 2 * q.w * q.z;
|
||||
rotmat->data[0][2] = 2 * q.x * q.z - 2 * q.w * q.y;
|
||||
rotmat->data[0][3] = 0;
|
||||
|
||||
rotmat->data[1][0] = 2 * q.x * q.y + 2 * q.w * q.z;
|
||||
rotmat->data[1][1] = 1 - 2 * q.x * q.x - 2 * q.z * q.z;
|
||||
rotmat->data[1][2] = 2 * q.y * q.z - 2 * q.w * q.x;
|
||||
rotmat->data[1][3] = 0;
|
||||
|
||||
rotmat->data[2][0] = 2 * q.x * q.z - 2 * q.w * q.y;
|
||||
rotmat->data[2][1] = 2 * q.y * q.z + 2 * q.w * q.x;
|
||||
rotmat->data[2][2] = 1 - 2 * q.x * q.x - 2 * q.y * q.y;
|
||||
rotmat->data[2][3] = 0;
|
||||
|
||||
rotmat->data[3][0] = rotmat->data[3][1] = rotmat->data[3][2] = 0;
|
||||
rotmat->data[3][3] = 1;
|
||||
}
|
||||
|
||||
GUF_LINALG_KWRDS void guf_mat3x3_init_from_quaternion(guf_mat3x3 *rotmat, guf_quaternion q)
|
||||
{
|
||||
GUF_ASSERT(rotmat);
|
||||
|
||||
rotmat->data[0][0] = 1 - 2 * q.y * q.y - 2 * q.z * q.z;
|
||||
rotmat->data[0][1] = 2 * q.x * q.y - 2 * q.w * q.z;
|
||||
rotmat->data[0][2] = 2 * q.x * q.z - 2 * q.w * q.y;
|
||||
|
||||
rotmat->data[1][0] = 2 * q.x * q.y + 2 * q.w * q.z;
|
||||
rotmat->data[1][1] = 1 - 2 * q.x * q.x - 2 * q.z * q.z;
|
||||
rotmat->data[1][2] = 2 * q.y * q.z - 2 * q.w * q.x;
|
||||
|
||||
rotmat->data[2][0] = 2 * q.x * q.z - 2 * q.w * q.y;
|
||||
rotmat->data[2][1] = 2 * q.y * q.z + 2 * q.w * q.x;
|
||||
rotmat->data[2][2] = 1 - 2 * q.x * q.x - 2 * q.y * q.y;
|
||||
}
|
||||
|
||||
GUF_LINALG_KWRDS guf_quaternion *guf_quaternion_normalise(guf_quaternion *q)
|
||||
{
|
||||
GUF_ASSERT(q);
|
||||
const float sq_mag = guf_quaternion_mag_squared(*q);
|
||||
if (sq_mag == 0 || sq_mag == 1) { // Prevent div by zero and don't normalise if already normalised.
|
||||
return q;
|
||||
}
|
||||
|
||||
const float mag = sqrtf(sq_mag);
|
||||
if (mag == 0) { // Just in case.
|
||||
return q;
|
||||
}
|
||||
q->w /= mag;
|
||||
q->x /= mag;
|
||||
q->y /= mag;
|
||||
q->z /= mag;
|
||||
return q;
|
||||
}
|
||||
|
||||
#if defined(__cplusplus) && defined(GUF_LINALG_IMPL_STATIC)
|
||||
GUF_LINALG_KWRDS void guf_quaternion_mul(const guf_quaternion *a, const guf_quaternion *b, guf_quaternion *result)
|
||||
#else
|
||||
GUF_LINALG_KWRDS void guf_quaternion_mul(const guf_quaternion *a, const guf_quaternion *b, guf_quaternion * restrict result)
|
||||
#endif
|
||||
{
|
||||
GUF_ASSERT(a && b && result);
|
||||
GUF_ASSERT(a != result && b != result);
|
||||
result->w = a->w * b->w - a->x * b->x - a->y * b->y - a->z * b->z; // (r0s0 − r1s1 − r2s2 − r3s3)
|
||||
result->x = a->w * b->x + a->x * b->w - a->y * b->z + a->z * b->y; // (r0s1 + r1s0 − r2s3 + r3s2)
|
||||
result->y = a->w * b->y + a->x * b->z + a->y * b->w - a->z * b->x; // (r0s2 + r1s3 + r2s0 − r3s1)
|
||||
result->z = a->w * b->z - a->x * b->y + a->y * b->x + a->z * b->w; // (r0s3 − r1s2 + r2s1 + r3s0)
|
||||
}
|
||||
|
||||
GUF_LINALG_KWRDS guf_vec3 *guf_vec3_rotate_by_quat(guf_vec3 *p, const guf_quaternion *q)
|
||||
{
|
||||
// Active rotation of the point/vector p by the quaternion q.
|
||||
GUF_ASSERT(p && q);
|
||||
const guf_quaternion inv_q = guf_quaternion_inverted(*q);
|
||||
guf_quaternion p_quat = {.w = 0, .x = p->x, .y = p->y, .z = p->z};
|
||||
|
||||
// (x) active rotation: inv(q) * p_quat * q (the point is rotated with respect to the coordinate system)
|
||||
// ( ) passive rotation: q * p_quat * inv(q) (the coordinate system is rotated with respect to the point)
|
||||
guf_quaternion tmp;
|
||||
guf_quaternion_mul(&inv_q, &p_quat, &tmp); // tmp := inv(q) * pquat
|
||||
guf_quaternion_mul(&tmp, q, &p_quat); // pquat := tmp * q
|
||||
|
||||
p->x = p_quat.x;
|
||||
p->y = p_quat.y;
|
||||
p->z = p_quat.z;
|
||||
return p;
|
||||
}
|
||||
|
||||
GUF_LINALG_KWRDS void guf_mat4x4_print_with_precision(const guf_mat4x4 *m, FILE *outfile, int num_frac_digits)
|
||||
{
|
||||
if (!outfile) {
|
||||
outfile = stdout;
|
||||
}
|
||||
if (!m) {
|
||||
fprintf(outfile, "guf_mat4x4 NULL\n");
|
||||
return;
|
||||
}
|
||||
|
||||
int max_digits = 1;
|
||||
for (int row = 0; row < 4; ++row) { // Find how many digits before the . we need.
|
||||
for (int col = 0; col < 4; ++col) {
|
||||
const float elem = m->data[row][col];
|
||||
int add_digits = 0;
|
||||
if (isnan(elem) || isinf(elem)) {
|
||||
add_digits += 3;
|
||||
}
|
||||
if (elem == 0) {
|
||||
continue;
|
||||
}
|
||||
const float elem_log10 = floorf(log10f(fabsf(elem)));
|
||||
int digits = (int)elem_log10 + add_digits;
|
||||
digits = GUF_CLAMP(digits, 0, 32);
|
||||
if (digits > max_digits) {
|
||||
max_digits = digits;
|
||||
}
|
||||
}
|
||||
}
|
||||
const int whole_digits = max_digits + 1;
|
||||
num_frac_digits = GUF_CLAMP(num_frac_digits, 0, 32);
|
||||
|
||||
for (int row = 0; row < 4; ++row) {
|
||||
for (int col = 0; col < 4; ++col) {
|
||||
fprintf(outfile, "% *.*f ", whole_digits + num_frac_digits + 2, num_frac_digits, (double)m->data[row][col]);
|
||||
}
|
||||
fputc('\n', outfile);
|
||||
}
|
||||
fputc('\n', outfile);
|
||||
}
|
||||
|
||||
|
||||
GUF_LINALG_KWRDS void guf_mat4x4_print(const guf_mat4x4 *m, FILE *outfile)
|
||||
{
|
||||
const int DEFAULT_FRAC_DIGITS = 3;
|
||||
guf_mat4x4_print_with_precision(m, outfile, DEFAULT_FRAC_DIGITS);
|
||||
}
|
||||
|
||||
#undef GUF_LINALG_IMPL
|
||||
#undef GUF_LINALG_IMPL_STATIC
|
||||
|
||||
#endif /* end impl */
|
||||
|
||||
#undef GUF_LINALG_KWRDS
|
||||
421
src/guf_math.h
Executable file
421
src/guf_math.h
Executable file
@ -0,0 +1,421 @@
|
||||
/*
|
||||
is parametrized: no
|
||||
*/
|
||||
#ifndef GUF_MATH_H
|
||||
#define GUF_MATH_H
|
||||
#include "guf_common.h"
|
||||
#include "guf_assert.h"
|
||||
#include <math.h>
|
||||
#include <float.h>
|
||||
|
||||
#define GUF_PI 3.14159265358979323846264338327950288
|
||||
#define GUF_PI_F32 3.14159265358979323846264338327950288f
|
||||
|
||||
// Biggest float/double less than one, cf. https://stackoverflow.com/a/33832753 (last-retrieved 2025-03-05)
|
||||
#define GUF_MAX_F32_LT_ONE (1.f - FLT_EPSILON/FLT_RADIX)
|
||||
#define GUF_MAX_F64_LT_ONE (1.0 - DBL_EPSILON/FLT_RADIX)
|
||||
|
||||
// Typesafe unsigned integer wrapping functions (generated with libguf/tools/intwrap-gen.py)
|
||||
static inline uint_least8_t guf_wrap8_least_u8(uint_least8_t a) { return a & GUF_UINT8_MAX; }
|
||||
static inline uint_least16_t guf_wrap16_least_u16(uint_least16_t a) { return a & GUF_UINT16_MAX; }
|
||||
static inline uint_least32_t guf_wrap32_least_u32(uint_least32_t a) { return a & GUF_UINT32_MAX; }
|
||||
static inline uint_least64_t guf_wrap64_least_u64(uint_least64_t a) { return a & GUF_UINT64_MAX; }
|
||||
|
||||
static inline unsigned char guf_wrap8_uchar(unsigned char a) { return a & GUF_UINT8_MAX; } // unsigned char: >= 8 bits
|
||||
static inline unsigned short guf_wrap16_ushort(unsigned short a) { return a & GUF_UINT16_MAX; } // unsigned short: >= 16 bits
|
||||
static inline unsigned long guf_wrap32_ulong(unsigned long a) { return a & GUF_UINT32_MAX; } // unsigned long: >= 32 bits
|
||||
static inline unsigned long long guf_wrap64_ulong_long(unsigned long long a) { return a & GUF_UINT64_MAX; } // unsigned long long: >= 64 bits
|
||||
|
||||
// Rotate left.
|
||||
#ifdef UINT32_MAX
|
||||
static inline uint32_t guf_rotl_u32(uint32_t x, int k)
|
||||
{
|
||||
GUF_ASSERT(k > 0);
|
||||
return (1u*x << k) | (1u*x >> (32 - k));
|
||||
}
|
||||
#endif
|
||||
#ifdef UINT64_MAX
|
||||
static inline uint64_t guf_rotl_u64(uint64_t x, int k)
|
||||
{
|
||||
GUF_ASSERT(k > 0);
|
||||
return (1u*x << k) | (1u*x >> (64 - k));
|
||||
}
|
||||
#endif
|
||||
|
||||
static inline uint_least32_t guf_rotl32_least_u32(uint_least32_t x, int k)
|
||||
{
|
||||
GUF_ASSERT(k > 0);
|
||||
x = guf_wrap32_least_u32(x);
|
||||
return guf_wrap32_least_u32( (1u*x << k) | (1u*x >> (32 - k)) );
|
||||
}
|
||||
static inline uint_least64_t guf_rotl64_least_u64(uint_least64_t x, int k)
|
||||
{
|
||||
GUF_ASSERT(k > 0);
|
||||
x = guf_wrap64_least_u64(x);
|
||||
return guf_wrap64_least_u64( (1u*x << k) | (1u*x >> (64 - k)) );
|
||||
}
|
||||
|
||||
static inline unsigned long guf_rotl32_ulong(unsigned long x, int k)
|
||||
{
|
||||
GUF_ASSERT(k > 0);
|
||||
x = guf_wrap32_ulong(x);
|
||||
return guf_wrap32_ulong( (x << k) | (x >> (32 - k)) );
|
||||
}
|
||||
static inline unsigned long long guf_rotl64_ulong_long(unsigned long long x, int k)
|
||||
{
|
||||
GUF_ASSERT(k > 0);
|
||||
x = guf_wrap64_ulong_long(x);
|
||||
return guf_wrap64_ulong_long( (x << k) | (x >> (64 - k)) );
|
||||
}
|
||||
|
||||
|
||||
// Signed min, max, clamp functions (generated with libguf/tools/min_max_clamp-gen.py)
|
||||
static inline char guf_min_char(char a, char b) { return a < b ? a : b; }
|
||||
static inline char guf_max_char(char a, char b) { return a > b ? a : b; }
|
||||
static inline char guf_clamp_char(char x, char min, char max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
|
||||
|
||||
static inline int guf_min_int(int a, int b) { return a < b ? a : b; }
|
||||
static inline int guf_max_int(int a, int b) { return a > b ? a : b; }
|
||||
static inline int guf_clamp_int(int x, int min, int max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
|
||||
|
||||
static inline long guf_min_long(long a, long b) { return a < b ? a : b; }
|
||||
static inline long guf_max_long(long a, long b) { return a > b ? a : b; }
|
||||
static inline long guf_clamp_long(long x, long min, long max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
|
||||
|
||||
static inline long long guf_min_long_long(long long a, long long b) { return a < b ? a : b; }
|
||||
static inline long long guf_max_long_long(long long a, long long b) { return a > b ? a : b; }
|
||||
static inline long long guf_clamp_long_long(long long x, long long min, long long max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
|
||||
|
||||
static inline ptrdiff_t guf_min_ptrdiff_t(ptrdiff_t a, ptrdiff_t b) { return a < b ? a : b; }
|
||||
static inline ptrdiff_t guf_max_ptrdiff_t(ptrdiff_t a, ptrdiff_t b) { return a > b ? a : b; }
|
||||
static inline ptrdiff_t guf_clamp_ptrdiff_t(ptrdiff_t x, ptrdiff_t min, ptrdiff_t max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
|
||||
|
||||
static inline int_least8_t guf_min_least_i8(int_least8_t a, int_least8_t b) { return a < b ? a : b; }
|
||||
static inline int_least8_t guf_max_least_i8(int_least8_t a, int_least8_t b) { return a > b ? a : b; }
|
||||
static inline int_least8_t guf_clamp_least_i8(int_least8_t x, int_least8_t min, int_least8_t max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
|
||||
|
||||
static inline int_least16_t guf_min_least_i16(int_least16_t a, int_least16_t b) { return a < b ? a : b; }
|
||||
static inline int_least16_t guf_max_least_i16(int_least16_t a, int_least16_t b) { return a > b ? a : b; }
|
||||
static inline int_least16_t guf_clamp_least_i16(int_least16_t x, int_least16_t min, int_least16_t max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
|
||||
|
||||
static inline int_least32_t guf_min_least_i32(int_least32_t a, int_least32_t b) { return a < b ? a : b; }
|
||||
static inline int_least32_t guf_max_least_i32(int_least32_t a, int_least32_t b) { return a > b ? a : b; }
|
||||
static inline int_least32_t guf_clamp_least_i32(int_least32_t x, int_least32_t min, int_least32_t max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
|
||||
|
||||
static inline int_least64_t guf_min_least_i64(int_least64_t a, int_least64_t b) { return a < b ? a : b; }
|
||||
static inline int_least64_t guf_max_least_i64(int_least64_t a, int_least64_t b) { return a > b ? a : b; }
|
||||
static inline int_least64_t guf_clamp_least_i64(int_least64_t x, int_least64_t min, int_least64_t max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
|
||||
|
||||
static inline float guf_min_f32(float a, float b) { return a < b ? a : b; }
|
||||
static inline float guf_max_f32(float a, float b) { return a > b ? a : b; }
|
||||
static inline float guf_clamp_f32(float x, float min, float max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
|
||||
|
||||
static inline double guf_min_f64(double a, double b) { return a < b ? a : b; }
|
||||
static inline double guf_max_f64(double a, double b) { return a > b ? a : b; }
|
||||
static inline double guf_clamp_f64(double x, double min, double max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
|
||||
|
||||
#ifdef INT8_MAX
|
||||
static inline int8_t guf_min_i8(int8_t a, int8_t b) { return a < b ? a : b; }
|
||||
static inline int8_t guf_max_i8(int8_t a, int8_t b) { return a > b ? a : b; }
|
||||
static inline int8_t guf_clamp_i8(int8_t x, int8_t min, int8_t max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
|
||||
#endif
|
||||
|
||||
#ifdef INT16_MAX
|
||||
static inline int16_t guf_min_i16(int16_t a, int16_t b) { return a < b ? a : b; }
|
||||
static inline int16_t guf_max_i16(int16_t a, int16_t b) { return a > b ? a : b; }
|
||||
static inline int16_t guf_clamp_i16(int16_t x, int16_t min, int16_t max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
|
||||
#endif
|
||||
|
||||
#ifdef INT32_MAX
|
||||
static inline int32_t guf_min_i32(int32_t a, int32_t b) { return a < b ? a : b; }
|
||||
static inline int32_t guf_max_i32(int32_t a, int32_t b) { return a > b ? a : b; }
|
||||
static inline int32_t guf_clamp_i32(int32_t x, int32_t min, int32_t max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
|
||||
#endif
|
||||
|
||||
#ifdef INT64_MAX
|
||||
static inline int64_t guf_min_i64(int64_t a, int64_t b) { return a < b ? a : b; }
|
||||
static inline int64_t guf_max_i64(int64_t a, int64_t b) { return a > b ? a : b; }
|
||||
static inline int64_t guf_clamp_i64(int64_t x, int64_t min, int64_t max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
|
||||
#endif
|
||||
|
||||
// Unsigned min, max, clamp functions (generated with libguf/tools/min_max_clamp-gen.py)
|
||||
static inline unsigned char guf_min_uchar(unsigned char a, unsigned char b) { return a < b ? a : b; }
|
||||
static inline unsigned char guf_max_uchar(unsigned char a, unsigned char b) { return a > b ? a : b; }
|
||||
static inline unsigned char guf_clamp_uchar(unsigned char x, unsigned char min, unsigned char max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
|
||||
|
||||
static inline unsigned guf_min_unsigned(unsigned a, unsigned b) { return a < b ? a : b; }
|
||||
static inline unsigned guf_max_unsigned(unsigned a, unsigned b) { return a > b ? a : b; }
|
||||
static inline unsigned guf_clamp_unsigned(unsigned x, unsigned min, unsigned max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
|
||||
|
||||
static inline unsigned long guf_min_ulong(unsigned long a, unsigned long b) { return a < b ? a : b; }
|
||||
static inline unsigned long guf_max_ulong(unsigned long a, unsigned long b) { return a > b ? a : b; }
|
||||
static inline unsigned long guf_clamp_ulong(unsigned long x, unsigned long min, unsigned long max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
|
||||
|
||||
static inline unsigned long long guf_min_ulong_long(unsigned long long a, unsigned long long b) { return a < b ? a : b; }
|
||||
static inline unsigned long long guf_max_ulong_long(unsigned long long a, unsigned long long b) { return a > b ? a : b; }
|
||||
static inline unsigned long long guf_clamp_ulong_long(unsigned long long x, unsigned long long min, unsigned long long max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
|
||||
|
||||
static inline size_t guf_min_size_t(size_t a, size_t b) { return a < b ? a : b; }
|
||||
static inline size_t guf_max_size_t(size_t a, size_t b) { return a > b ? a : b; }
|
||||
static inline size_t guf_clamp_size_t(size_t x, size_t min, size_t max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
|
||||
|
||||
static inline uint_least8_t guf_min_least_u8(uint_least8_t a, uint_least8_t b) { return a < b ? a : b; }
|
||||
static inline uint_least8_t guf_max_least_u8(uint_least8_t a, uint_least8_t b) { return a > b ? a : b; }
|
||||
static inline uint_least8_t guf_clamp_least_u8(uint_least8_t x, uint_least8_t min, uint_least8_t max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
|
||||
|
||||
static inline uint_least16_t guf_min_least_u16(uint_least16_t a, uint_least16_t b) { return a < b ? a : b; }
|
||||
static inline uint_least16_t guf_max_least_u16(uint_least16_t a, uint_least16_t b) { return a > b ? a : b; }
|
||||
static inline uint_least16_t guf_clamp_least_u16(uint_least16_t x, uint_least16_t min, uint_least16_t max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
|
||||
|
||||
static inline uint_least32_t guf_min_least_u32(uint_least32_t a, uint_least32_t b) { return a < b ? a : b; }
|
||||
static inline uint_least32_t guf_max_least_u32(uint_least32_t a, uint_least32_t b) { return a > b ? a : b; }
|
||||
static inline uint_least32_t guf_clamp_least_u32(uint_least32_t x, uint_least32_t min, uint_least32_t max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
|
||||
|
||||
static inline uint_least64_t guf_min_least_u64(uint_least64_t a, uint_least64_t b) { return a < b ? a : b; }
|
||||
static inline uint_least64_t guf_max_least_u64(uint_least64_t a, uint_least64_t b) { return a > b ? a : b; }
|
||||
static inline uint_least64_t guf_clamp_least_u64(uint_least64_t x, uint_least64_t min, uint_least64_t max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
|
||||
|
||||
#ifdef UINT8_MAX
|
||||
static inline uint8_t guf_min_u8(uint8_t a, uint8_t b) { return a < b ? a : b; }
|
||||
static inline uint8_t guf_max_u8(uint8_t a, uint8_t b) { return a > b ? a : b; }
|
||||
static inline uint8_t guf_clamp_u8(uint8_t x, uint8_t min, uint8_t max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
|
||||
#endif
|
||||
|
||||
#ifdef UINT16_MAX
|
||||
static inline uint16_t guf_min_u16(uint16_t a, uint16_t b) { return a < b ? a : b; }
|
||||
static inline uint16_t guf_max_u16(uint16_t a, uint16_t b) { return a > b ? a : b; }
|
||||
static inline uint16_t guf_clamp_u16(uint16_t x, uint16_t min, uint16_t max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
|
||||
#endif
|
||||
|
||||
#ifdef UINT32_MAX
|
||||
static inline uint32_t guf_min_u32(uint32_t a, uint32_t b) { return a < b ? a : b; }
|
||||
static inline uint32_t guf_max_u32(uint32_t a, uint32_t b) { return a > b ? a : b; }
|
||||
static inline uint32_t guf_clamp_u32(uint32_t x, uint32_t min, uint32_t max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
|
||||
#endif
|
||||
|
||||
#ifdef UINT64_MAX
|
||||
static inline uint64_t guf_min_u64(uint64_t a, uint64_t b) { return a < b ? a : b; }
|
||||
static inline uint64_t guf_max_u64(uint64_t a, uint64_t b) { return a > b ? a : b; }
|
||||
static inline uint64_t guf_clamp_u64(uint64_t x, uint64_t min, uint64_t max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
|
||||
#endif
|
||||
|
||||
|
||||
// abs functions with signed result (can fail/panic for abs(INT_TYPE_MIN) for platforms with two's complement signed ints; C2X for example guarantees two's complement)
|
||||
// static inline int guf_abs_int(int x) {if (x >= 0) {return x;} GUF_ASSERT_RELEASE(-INT_MAX == INT_MIN || x > INT_MIN); return -x;} // I would not drink that...
|
||||
// static inline int8_t guf_abs_i8 (int8_t x) {if (x >= 0) {return x;} GUF_ASSERT_RELEASE(-INT8_MAX == INT8_MIN || x > INT8_MIN); return -x;}
|
||||
// static inline int16_t guf_abs_i16(int16_t x) {if (x >= 0) {return x;} GUF_ASSERT_RELEASE(-INT16_MAX == INT16_MIN || x > INT16_MIN); return -x;}
|
||||
// static inline int32_t guf_abs_i32(int32_t x) {if (x >= 0) {return x;} GUF_ASSERT_RELEASE(-INT32_MAX == INT32_MIN || x > INT32_MIN); return -x;}
|
||||
// static inline int64_t guf_abs_i64(int64_t x) {if (x >= 0) {return x;} GUF_ASSERT_RELEASE(-INT64_MAX == INT64_MIN || x > INT64_MIN); return -x;}
|
||||
// static inline ptrdiff_t guf_abs_ptrdiff(ptrdiff_t x) {if (x >= 0) {return x;} GUF_ASSERT_RELEASE(-PTRDIFF_MAX == PTRDIFF_MIN || x > PTRDIFF_MIN); return -x;}
|
||||
|
||||
// abs functions with unsigned result functions (cannot fail)
|
||||
static inline unsigned guf_uabs_int(int x) {if (x >= 0) {return x;} else if (x == INT_MIN && -INT_MAX != INT_MIN) {return (unsigned)INT_MAX + 1u;} else {return -x;}}
|
||||
static inline unsigned long guf_uabs_long(long x) {if (x >= 0) {return x;} else if (x == LONG_MIN && -LONG_MAX != LONG_MIN) {return (unsigned long)LONG_MAX + 1u;} else {return -x;}}
|
||||
static inline unsigned long long guf_uabs_long_long(long long x) {if (x >= 0) {return x;} else if (x == LLONG_MIN && -LLONG_MAX != LONG_MIN) {return (unsigned long long)LLONG_MAX + 1u;} else {return -x;}}
|
||||
static inline size_t guf_uabs_ptrdiff_t(ptrdiff_t x) {if (x >= 0) {return x;} else if (x == PTRDIFF_MIN && -PTRDIFF_MAX != PTRDIFF_MIN) {return (size_t)PTRDIFF_MAX + 1u;} else {return -x;}}
|
||||
|
||||
#if defined(UINT8_MAX) && defined(INT8_MAX)
|
||||
static inline uint8_t guf_uabs_i8(int8_t x) {if (x >= 0) {return x;} else if (x == INT8_MIN) {return (uint8_t)INT8_MAX + (uint8_t)1u;} else {return (uint8_t)-x;}}
|
||||
#endif
|
||||
#if defined(UINT16_MAX) && defined(INT16_MAX)
|
||||
static inline uint16_t guf_uabs_i16(int16_t x) {if (x >= 0) {return x;} else if (x == INT16_MIN) {return (uint16_t)INT16_MAX + (uint16_t)1u;} else {return (uint16_t)-x;}}
|
||||
#endif
|
||||
#if defined(UINT32_MAX) && defined(INT32_MAX)
|
||||
static inline uint32_t guf_uabs_i32(int32_t x) {if (x >= 0) {return x;} else if (x == INT32_MIN) {return (uint32_t)INT32_MAX + (uint32_t)1u;} else {return (uint32_t)-x;}}
|
||||
#endif
|
||||
#if defined(UINT64_MAX) && defined(INT64_MAX)
|
||||
static inline uint64_t guf_uabs_i64(int64_t x) {if (x >= 0) {return x;} else if (x == INT64_MIN) {return (uint64_t)INT64_MAX + (uint64_t)1u;} else {return (uint64_t)-x;}}
|
||||
#endif
|
||||
|
||||
// absdiff functions with unsigned result (cannot fail)
|
||||
static inline unsigned char guf_absdiff_char(char a, char b) {return a > b ? (unsigned char)a - (unsigned char)b : (unsigned char)b - (unsigned char)a;}
|
||||
static inline unsigned short guf_absdiff_short(short a, short b) {return a > b ? (unsigned short)a - (unsigned short)b : (unsigned short)b - (unsigned short)a;}
|
||||
static inline unsigned guf_absdiff_int(int a, int b) {return a > b ? (unsigned)a - (unsigned)b : (unsigned)b - (unsigned)a;}
|
||||
static inline unsigned long guf_absdiff_long(long a, long b) {return a > b ? (unsigned long)a - (unsigned long)b : (unsigned long)b - (unsigned long)a;}
|
||||
static inline unsigned long long guf_absdiff_long_long(long long a, long long b) {return a > b ? (unsigned long long)a - (unsigned long long)b : (unsigned long long)b - (unsigned long long)a;}
|
||||
static inline size_t guf_absdiff_ptrdiff_t(ptrdiff_t a, ptrdiff_t b) {return a > b ? (size_t)a - (size_t)b : (size_t)b - (size_t)a;}
|
||||
|
||||
static inline uint_least8_t guf_absdiff_least_i8(int_least8_t a, int_least8_t b) {return a > b ? GUF_UWRAP_8( (uint_least8_t)a - (uint_least8_t)b) : GUF_UWRAP_8( (uint_least8_t)b - (uint_least8_t)a);}
|
||||
static inline uint_least16_t guf_absdiff_least_i16(int_least16_t a, int_least16_t b) {return a > b ? GUF_UWRAP_16((uint_least16_t)a - (uint_least16_t)b) : GUF_UWRAP_16((uint_least16_t)b - (uint_least16_t)a);}
|
||||
static inline uint_least32_t guf_absdiff_least_i32(int_least32_t a, int_least32_t b) {return a > b ? GUF_UWRAP_32((uint_least32_t)a - (uint_least32_t)b) : GUF_UWRAP_32((uint_least32_t)b - (uint_least32_t)a);}
|
||||
static inline uint_least64_t guf_absdiff_least_i64(int_least64_t a, int_least64_t b) {return a > b ? GUF_UWRAP_64((uint_least64_t)a - (uint_least64_t)b) : GUF_UWRAP_64((uint_least64_t)b - (uint_least64_t)a);}
|
||||
|
||||
#if defined(UINT8_MAX) && defined(INT8_MAX)
|
||||
static inline uint8_t guf_absdiff_i8(int8_t a, int8_t b) {return a > b ? (uint8_t)a - (uint8_t)b : (uint8_t)b - (uint8_t)a;}
|
||||
#endif
|
||||
#if defined(UINT16_MAX) && defined(INT16_MAX)
|
||||
static inline uint16_t guf_absdiff_i16(int16_t a, int16_t b) {return a > b ? (uint16_t)a - (uint16_t)b : (uint16_t)b - (uint16_t)a;}
|
||||
#endif
|
||||
#if defined(UINT32_MAX) && defined(INT32_MAX)
|
||||
static inline uint32_t guf_absdiff_i32(int32_t a, int32_t b) {return a > b ? (uint32_t)a - (uint32_t)b : (uint32_t)b - (uint32_t)a;}
|
||||
#endif
|
||||
#if defined(UINT64_MAX) && defined(INT64_MAX)
|
||||
static inline uint64_t guf_absdiff_i64(int64_t a, int64_t b) {return a > b ? (uint64_t)a - (uint64_t)b : (uint64_t)b - (uint64_t)a;}
|
||||
#endif
|
||||
|
||||
|
||||
static inline bool guf_add_is_overflow_size_t(size_t a, size_t b)
|
||||
{
|
||||
return (a + b) < a;
|
||||
}
|
||||
static inline bool guf_sub_is_overflow_size_t(size_t a, size_t b)
|
||||
{
|
||||
return (a - b) > a;
|
||||
}
|
||||
static inline bool guf_mul_is_overflow_size_t(size_t a, size_t b)
|
||||
{
|
||||
const size_t c = a * b;
|
||||
return a != 0 && ((c / a) != b);
|
||||
}
|
||||
|
||||
static inline bool guf_size_calc_safe(ptrdiff_t count, ptrdiff_t sizeof_elem, ptrdiff_t *result)
|
||||
{
|
||||
if (count < 0 || sizeof_elem <= 0) {
|
||||
return false;
|
||||
}
|
||||
if (guf_mul_is_overflow_size_t((size_t)count, (size_t)sizeof_elem)) {
|
||||
return false;
|
||||
}
|
||||
const size_t size = (size_t)count * (size_t)sizeof_elem;
|
||||
|
||||
const bool is_safe = size <= PTRDIFF_MAX;
|
||||
if (result) {
|
||||
*result = is_safe ? (ptrdiff_t)size : -1;
|
||||
}
|
||||
return is_safe;
|
||||
}
|
||||
|
||||
|
||||
// cf. https://graphics.stanford.edu/~seander/bithacks.html#DetermineIfPowerOf2 (last-retrieved 2025-03-19)
|
||||
static inline bool guf_is_pow2_uchar(unsigned char x) { return x && !(x & (x - 1)); }
|
||||
static inline bool guf_is_pow2_ushort(unsigned short x) { return x && !(x & (x - 1)); }
|
||||
static inline bool guf_is_pow2_unsigned(unsigned x) { return x && !(x & (x - 1)); }
|
||||
static inline bool guf_is_pow2_ulong(unsigned long x) { return x && !(x & (x - 1)); }
|
||||
static inline bool guf_is_pow2_ulong_long(unsigned long long x) { return x && !(x & (x - 1)); }
|
||||
static inline bool guf_is_pow2_size_t(size_t x) { return x && !(x & (x - 1)); }
|
||||
#ifdef UINT8_MAX
|
||||
static inline bool guf_is_pow2_u8(uint8_t x) { return x && !(x & (x - 1)); }
|
||||
#endif
|
||||
#ifdef UINT16_MAX
|
||||
static inline bool guf_is_pow2_u16(uint16_t x) { return x && !(x & (x - 1)); }
|
||||
#endif
|
||||
#ifdef UINT32_MAX
|
||||
static inline bool guf_is_pow2_u32(uint32_t x) { return x && !(x & (x - 1)); }
|
||||
#endif
|
||||
#if UINT64_MAX
|
||||
static inline bool guf_is_pow2_u64(uint64_t x) { return x && !(x & (x - 1)); }
|
||||
#endif
|
||||
|
||||
static bool guf_nearly_zero_f32(float x, float eps) {return fabsf(x) <= eps;}
|
||||
static bool guf_nearly_one_f32(float x, float eps) {return fabsf(x - 1) <= eps;}
|
||||
static bool guf_nearly_zero_f64(double x, double eps) {return fabs(x) <= eps;}
|
||||
static bool guf_nearly_one_f64(double x, double eps) {return fabs(x - 1) <= eps;}
|
||||
|
||||
/*
|
||||
General floating-point comparison:
|
||||
|
||||
cf. https://peps.python.org/pep-0485/ (last-retrieved 2025-03-04)
|
||||
https://github.com/PythonCHB/close_pep/blob/master/is_close.py (last-retrieved 2025-03-04)
|
||||
|
||||
Based on is_close.py (Copyright: Christopher H. Barker, License: Apache License 2.0 http://opensource.org/licenses/apache2.0.php)
|
||||
|
||||
rel_tol: "The relative tolerance -- the amount of error allowed, relative to the magnitude of the input values."
|
||||
(Example: To set a tolerance of 5%, pass rel_tol=0.05)
|
||||
(Example: rel_tol=1e-9 assures that the two values are the same within about 9 decimal digits)
|
||||
abs_tol: "The minimum absolute tolerance level -- useful for comparisons to zero." (or close to zero, I think).
|
||||
*/
|
||||
static bool guf_isclose_f32(float a, float b, float rel_tol, float abs_tol)
|
||||
{
|
||||
if (a == b) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isinf(a) || isinf(b)) { // "Two infinities of opposite sign, or one infinity and one finite number."
|
||||
return false;
|
||||
}
|
||||
|
||||
rel_tol = (rel_tol < 0) ? 0 : ((rel_tol > 1) ? 1 : rel_tol); // [0, 1]
|
||||
abs_tol = (abs_tol < 0) ? 0 : abs_tol; // [0, inf]
|
||||
|
||||
// "The relative tolerance is scaled by the larger of the two values."
|
||||
const float diff = fabsf(b - a);
|
||||
return ((diff <= fabsf(rel_tol * b)) || (diff <= fabsf(rel_tol * a))) || (diff <= abs_tol);
|
||||
}
|
||||
|
||||
static inline bool guf_isclose_reltol_f32(float a, float b, float rel_tol)
|
||||
{
|
||||
return guf_isclose_f32(a, b, rel_tol, 0);
|
||||
}
|
||||
static inline bool guf_isclose_abstol_f32(float a, float b, float abs_tol)
|
||||
{
|
||||
return guf_isclose_f32(a, b, 0, abs_tol);
|
||||
}
|
||||
|
||||
static bool guf_isclose_f64(double a, double b, double rel_tol, double abs_tol)
|
||||
{
|
||||
if (a == b) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isinf(a) || isinf(b)) { // "Two infinities of opposite sign, or one infinity and one finite number."
|
||||
return false;
|
||||
}
|
||||
|
||||
rel_tol = (rel_tol < 0) ? 0 : ((rel_tol > 1) ? 1 : rel_tol); // [0, 1]
|
||||
abs_tol = (abs_tol < 0) ? 0 : abs_tol; // [0, inf]
|
||||
|
||||
// "The relative tolerance is scaled by the larger of the two values."
|
||||
const double diff = fabs(b - a);
|
||||
return ((diff <= fabs(rel_tol * b)) || (diff <= fabs(rel_tol * a))) || (diff <= abs_tol);
|
||||
}
|
||||
|
||||
static inline bool guf_isclose_reltol_f64(double a, double b, double rel_tol)
|
||||
{
|
||||
return guf_isclose_f64(a, b, rel_tol, 0);
|
||||
}
|
||||
static inline bool guf_isclose_abstol_f64(double a, double b, double abs_tol)
|
||||
{
|
||||
return guf_isclose_f64(a, b, 0, abs_tol);
|
||||
}
|
||||
|
||||
|
||||
// An alternative lerp would be a + alpha * (b - a) (advantage: would be weakly monotonic, disadvantage: would not guarantee a for alpha = 0 and b for alpha = 1)
|
||||
static inline float guf_lerp_f32(float a, float b, float alpha) {return (1 - alpha) * a + alpha * b;}
|
||||
static inline double guf_lerp_f64(double a, double b, double alpha) {return (1 - alpha) * a + alpha * b;}
|
||||
|
||||
// smoothstep interpolation, cf. https://en.wikipedia.org/wiki/Smoothstep (last-retrieved 2025-02-18)
|
||||
static inline float guf_smoothstep_f32(float edge0, float edge1, float x)
|
||||
{
|
||||
if (edge0 == edge1) { // Prevent division by zero.
|
||||
return 1;
|
||||
}
|
||||
x = guf_clamp_f32((x - edge0) / (edge1 - edge0), 0, 1); // Bring in range [0, 1]
|
||||
return x * x * (3.f - 2.f * x);
|
||||
}
|
||||
static inline float guf_smootherstep_f32(float edge0, float edge1, float x)
|
||||
{
|
||||
if (edge0 == edge1) { // Prevent division by zero.
|
||||
return 1;
|
||||
}
|
||||
x = guf_clamp_f32((x - edge0) / (edge1 - edge0), 0, 1); // Bring in range [0, 1]
|
||||
return x * x * x * (x * (6.f * x - 15.f) + 10.f);
|
||||
}
|
||||
|
||||
static inline double guf_smoothstep_f64(double edge0, double edge1, double x)
|
||||
{
|
||||
if (edge0 == edge1) { // Prevent division by zero.
|
||||
return 1;
|
||||
}
|
||||
x = guf_clamp_f64((x - edge0) / (edge1 - edge0), 0, 1); // Bring in range [0, 1]
|
||||
return x * x * (3.0 - 2.0 * x);
|
||||
}
|
||||
static inline double guf_smootherstep_f64(double edge0, double edge1, double x)
|
||||
{
|
||||
if (edge0 == edge1) { // Prevent division by zero.
|
||||
return 1;
|
||||
}
|
||||
x = guf_clamp_f64((x - edge0) / (edge1 - edge0), 0, 1); // Bring in range [0, 1]
|
||||
return x * x * x * (x * (6.0 * x - 15.0) + 10.0);
|
||||
}
|
||||
|
||||
#endif
|
||||
3960
src/guf_math_ckdint.h
Executable file
3960
src/guf_math_ckdint.h
Executable file
File diff suppressed because it is too large
Load Diff
@ -1,74 +0,0 @@
|
||||
#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)
|
||||
};
|
||||
127
src/guf_obj.h
127
src/guf_obj.h
@ -1,127 +0,0 @@
|
||||
#ifndef GUF_OBJ_H
|
||||
#define GUF_OBJ_H
|
||||
#include <string.h>
|
||||
#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
|
||||
1007
src/guf_rand.h
Executable file
1007
src/guf_rand.h
Executable file
File diff suppressed because it is too large
Load Diff
193
src/guf_sort.h
Executable file
193
src/guf_sort.h
Executable file
@ -0,0 +1,193 @@
|
||||
/*
|
||||
is parametrized: yes
|
||||
*/
|
||||
|
||||
#ifndef GUF_SORT_H
|
||||
#define GUF_SORT_H
|
||||
#include "guf_common.h"
|
||||
|
||||
typedef enum guf_sort_opt {
|
||||
GUF_SORT_ASCENDING = 0,
|
||||
GUF_SORT_DESCENDING = 1
|
||||
} guf_sort_opt;
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef GUF_T
|
||||
|
||||
#ifndef GUF_FN_NAME_PREFIX
|
||||
#define GUF_FN_NAME_PREFIX GUF_CAT(GUF_T, _arr)
|
||||
#endif
|
||||
|
||||
#if defined(GUF_SORT_IMPL_STATIC)
|
||||
#define GUF_SORT_KWRDS static
|
||||
#else
|
||||
#define GUF_SORT_KWRDS
|
||||
#endif
|
||||
|
||||
GUF_SORT_KWRDS GUF_T *GUF_CAT(GUF_FN_NAME_PREFIX, _insertion_sort)(GUF_T *arr, ptrdiff_t n, guf_sort_opt sort_opt, int(*cmp)(const GUF_T *a, const GUF_T *b));
|
||||
GUF_SORT_KWRDS GUF_T *GUF_CAT(GUF_FN_NAME_PREFIX, _merge_sort)(GUF_T *restrict arr, GUF_T *restrict arr_tmp, ptrdiff_t n, guf_sort_opt sort_opt, int(*cmp)(const GUF_T *a, const GUF_T *b));
|
||||
GUF_SORT_KWRDS GUF_T *GUF_CAT(GUF_FN_NAME_PREFIX, _qsort)(GUF_T *arr, ptrdiff_t n, guf_sort_opt sort_opt, int(*cmp)(const GUF_T *a, const GUF_T *b));
|
||||
|
||||
GUF_SORT_KWRDS bool GUF_CAT(GUF_FN_NAME_PREFIX, _is_sorted)(GUF_T *arr, ptrdiff_t n, guf_sort_opt sort_opt, int(*cmp)(const GUF_T *a, const GUF_T *b));
|
||||
|
||||
#if defined(GUF_SORT_IMPL) || defined(GUF_SORT_IMPL_STATIC)
|
||||
|
||||
#define guf_before(a_ptr, b_ptr) (cmp ? (sort_opt == GUF_SORT_ASCENDING ? 1 : -1) * cmp(a_ptr, b_ptr) == -1 : (sort_opt == GUF_SORT_ASCENDING) ? *(a_ptr) < *(b_ptr) : *(a_ptr) > *(b_ptr))
|
||||
#define guf_before_or_equal(a_ptr, b_ptr) (cmp ? (sort_opt == GUF_SORT_ASCENDING ? 1 : -1) * cmp(a_ptr, b_ptr) <= 0 : (sort_opt == GUF_SORT_ASCENDING) ? *(a_ptr) <= *(b_ptr) : *(a_ptr) >= *(b_ptr))
|
||||
|
||||
/*
|
||||
Insertion sort.
|
||||
- stable: yes
|
||||
- time: worst O(n^2); average O(n^2); best O(n) (if arr is already sorted)
|
||||
- space: O(1)
|
||||
*/
|
||||
GUF_SORT_KWRDS GUF_T *GUF_CAT(GUF_FN_NAME_PREFIX, _insertion_sort)(GUF_T *arr, ptrdiff_t n, guf_sort_opt sort_opt, int(*cmp)(const GUF_T *a, const GUF_T *b))
|
||||
{
|
||||
GUF_ASSERT_RELEASE(arr);
|
||||
GUF_ASSERT_RELEASE(n >= 0);
|
||||
GUF_ASSERT_RELEASE(sort_opt == GUF_SORT_ASCENDING || sort_opt == GUF_SORT_DESCENDING);
|
||||
for (ptrdiff_t i = 1; i < n; ++i) { // Range [0, i) is sorted.
|
||||
ptrdiff_t j = i;
|
||||
while (j > 0 && guf_before(&arr[j], &arr[j - 1])) {
|
||||
GUF_SWAP(GUF_T, arr[j], arr[j - 1]);
|
||||
j = j - 1;
|
||||
}
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
/*
|
||||
Iterative bottom-up merge-sort: cf. https://en.wikipedia.org/wiki/Merge_sort#Bottom-up_implementation (last-retrieved: 2025-01-26)
|
||||
- stable: yes
|
||||
- time: O(n * log n) (worst, average, and best)
|
||||
- space: always O(n) (for arr_tmp, allocated and freed by the caller)
|
||||
*/
|
||||
GUF_SORT_KWRDS GUF_T *GUF_CAT(GUF_FN_NAME_PREFIX, _merge_sort)(GUF_T *restrict arr, GUF_T *restrict arr_tmp, ptrdiff_t n, guf_sort_opt sort_opt, int(*cmp)(const GUF_T *a, const GUF_T *b))
|
||||
{
|
||||
GUF_ASSERT_RELEASE(arr);
|
||||
GUF_ASSERT_RELEASE(n >= 0);
|
||||
GUF_ASSERT_RELEASE(sort_opt == GUF_SORT_ASCENDING || sort_opt == GUF_SORT_DESCENDING);
|
||||
|
||||
GUF_T *in = arr;
|
||||
GUF_T *out = arr_tmp;
|
||||
const size_t arr_len = n;
|
||||
for (size_t len = 1; len < arr_len; len = (len * 2 > len) ? 2 * len : SIZE_MAX) { // Subarray len 1, 2, 4, 8, ...
|
||||
for (size_t i = 0; i < arr_len; i = ((i + 2 * len) > i) ? (i + 2 * len) : SIZE_MAX) { // For each pair of subarrays of length len:
|
||||
const size_t left_begin = i; // left subarray: [left_begin, right_begin)
|
||||
const size_t right_begin = GUF_MIN(i + len, arr_len), right_end = GUF_MIN(i + 2 * len, arr_len); // right subarray [right_begin, right_end)
|
||||
size_t left_idx = left_begin, right_idx = right_begin;
|
||||
for (size_t idx = left_begin; idx < right_end; ++idx) { // Merge the left and right subarrays into arr_tmp.
|
||||
if (left_idx < right_begin && (right_idx >= right_end || guf_before_or_equal(&in[left_idx], &in[right_idx]))) {
|
||||
out[idx] = in[left_idx++];
|
||||
} else {
|
||||
GUF_ASSERT(right_idx < right_end);
|
||||
out[idx] = in[right_idx++];
|
||||
}
|
||||
}
|
||||
}
|
||||
GUF_SWAP(GUF_T*, in, out); // Avoid copying memory by switching pointers.
|
||||
}
|
||||
|
||||
if (in != arr) {
|
||||
memcpy(arr, in, sizeof(GUF_T) * arr_len);
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
/*
|
||||
Quicksort with "Median-of-3" partitioning (non-randomised) and minimal O(log n) stack usage as described in CLRS.
|
||||
cf. Cormen; Leiserson; Riverst; Stein (2009). "II.7 Quicksort". In "Introduction to Algorithms" (3rd ed, pp. 170-190). MIT Press
|
||||
cf. https://en.wikipedia.org/wiki/Quicksort#Choice_of_pivot
|
||||
cf. https://en.wikipedia.org/wiki/Quicksort#Optimizations
|
||||
- stable: no
|
||||
- time: worst O(n^2); average O(n * log n); best O(n * log n)
|
||||
- space: worst O(log n) (stack space used for the recursive function calls)
|
||||
*/
|
||||
static GUF_T *GUF_CAT(GUF_FN_NAME_PREFIX, _qsort_range)(GUF_T *arr, ptrdiff_t first_idx, ptrdiff_t last_idx, guf_sort_opt sort_opt, int(*cmp)(const GUF_T *a, const GUF_T *b))
|
||||
{
|
||||
GUF_ASSERT(arr);
|
||||
GUF_ASSERT(sort_opt == GUF_SORT_ASCENDING || sort_opt == GUF_SORT_DESCENDING);
|
||||
|
||||
while (first_idx >= 0 && first_idx < last_idx) {
|
||||
// 1.) Partition using "Median-of-3 partitioning", cf. https://en.wikipedia.org/wiki/Quicksort#Choice_of_pivot (last-retrieved 2025-01-30)
|
||||
const ptrdiff_t mid_idx = first_idx + ((last_idx - first_idx) / 2); // Equivalent to (first_idx + last_idx) / 2, cf. https://research.google/blog/extra-extra-read-all-about-it-nearly-all-binary-searches-and-mergesorts-are-broken/ (last-retrieved 2025-01-24)
|
||||
GUF_ASSERT(mid_idx >= 0 && mid_idx <= last_idx);
|
||||
ptrdiff_t pivot_idx = last_idx;
|
||||
if (guf_before(&arr[mid_idx], &arr[first_idx])) {
|
||||
GUF_SWAP(GUF_T, arr[first_idx], arr[mid_idx]);
|
||||
}
|
||||
if (guf_before(&arr[last_idx], &arr[first_idx])) {
|
||||
GUF_SWAP(GUF_T, arr[first_idx], arr[last_idx]);
|
||||
}
|
||||
if (guf_before(&arr[mid_idx], &arr[last_idx])) {
|
||||
GUF_SWAP(GUF_T, arr[mid_idx], arr[last_idx]);
|
||||
}
|
||||
ptrdiff_t i = first_idx - 1; // i: end idx of the subarray with elements <= pivot
|
||||
for (ptrdiff_t j = first_idx; j < last_idx; ++j) { // j: end idx of the subarray with elements > pivot
|
||||
if (guf_before_or_equal(&arr[j], &arr[pivot_idx])) {
|
||||
++i;
|
||||
GUF_ASSERT(i >= 0 && i < last_idx);
|
||||
GUF_SWAP(GUF_T, arr[i], arr[j]);
|
||||
}
|
||||
}
|
||||
GUF_ASSERT(i + 1 >= 0 && i + 1 < last_idx);
|
||||
GUF_SWAP(GUF_T, arr[i + 1], arr[last_idx]); // The pivot element is always <= itself (or >= for descending sorts).
|
||||
pivot_idx = i + 1;
|
||||
|
||||
// 2.) Sort the two partitions [first_idx, pivot_idx) and (pivot_idx, last_idx] recursively.
|
||||
/*
|
||||
"To make sure at most O(log n) space is used, recur first into the smaller side of the partition,
|
||||
then use a tail call to recur into the other, or update the parameters to no longer include the now sorted smaller side,
|
||||
and iterate to sort the larger side.", cf. https://en.wikipedia.org/wiki/Quicksort#Optimizations (last-retrieved 2025-01-25)
|
||||
|
||||
(Solution to exercise "II.7.4c. Stack depth for quicksort". In "Introduction to Algorithms" (3rd ed, p. 188))
|
||||
*/
|
||||
if (pivot_idx <= mid_idx) { // a.) Left subarray is smaller or equal than the right subarray -> recur into the smaller left subarray first.
|
||||
GUF_CAT(GUF_FN_NAME_PREFIX, _qsort_range)(arr, first_idx, pivot_idx - 1, sort_opt, cmp);
|
||||
first_idx = pivot_idx + 1;
|
||||
} else { // b.) Right subarray is smaller than the left subarray -> recur into the smaller right subarray first.
|
||||
GUF_CAT(GUF_FN_NAME_PREFIX, _qsort_range)(arr, pivot_idx + 1, last_idx, sort_opt, cmp);
|
||||
last_idx = pivot_idx - 1;
|
||||
}
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
GUF_SORT_KWRDS GUF_T *GUF_CAT(GUF_FN_NAME_PREFIX, _qsort)(GUF_T *arr, ptrdiff_t n, guf_sort_opt sort_opt, int(*cmp)(const GUF_T *a, const GUF_T *b))
|
||||
{
|
||||
GUF_ASSERT_RELEASE(arr);
|
||||
GUF_ASSERT_RELEASE(sort_opt == GUF_SORT_ASCENDING || sort_opt == GUF_SORT_DESCENDING);
|
||||
GUF_ASSERT_RELEASE(n >= 0);
|
||||
if (n <= 1) {
|
||||
return arr;
|
||||
} else if (n <= 4) {
|
||||
return GUF_CAT(GUF_FN_NAME_PREFIX, _insertion_sort)(arr, n, sort_opt, cmp);
|
||||
} else {
|
||||
return GUF_CAT(GUF_FN_NAME_PREFIX, _qsort_range)(arr, 0, n - 1, sort_opt, cmp);
|
||||
}
|
||||
}
|
||||
|
||||
GUF_SORT_KWRDS bool GUF_CAT(GUF_FN_NAME_PREFIX, _is_sorted)(GUF_T *arr, ptrdiff_t n, guf_sort_opt sort_opt, int(*cmp)(const GUF_T *a, const GUF_T *b))
|
||||
{
|
||||
GUF_ASSERT_RELEASE(arr);
|
||||
for (ptrdiff_t i = 0; i < n - 1; ++i) {
|
||||
if (!guf_before_or_equal(arr + i, arr + (i+1))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#undef guf_before
|
||||
#undef guf_before_or_equal
|
||||
|
||||
#undef GUF_SORT_IMPL
|
||||
#undef GUF_SORT_IMPL_STATIC
|
||||
#endif /* end #ifdef GUF_IMPL */
|
||||
|
||||
#undef GUF_SORT_KWRDS
|
||||
#undef GUF_T
|
||||
#undef GUF_FN_NAME_PREFIX
|
||||
|
||||
#endif /* end #ifdef GUF_T */
|
||||
756
src/guf_str.c
756
src/guf_str.c
@ -1,756 +0,0 @@
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#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, )
|
||||
1703
src/guf_str.h
Normal file → Executable file
1703
src/guf_str.h
Normal file → Executable file
File diff suppressed because it is too large
Load Diff
14
src/guf_str_view_type.h
Executable file
14
src/guf_str_view_type.h
Executable file
@ -0,0 +1,14 @@
|
||||
/*
|
||||
is parametrized: no
|
||||
*/
|
||||
|
||||
#ifndef GUF_STR_VIEW_TYPE_H
|
||||
#define GUF_STR_VIEW_TYPE_H
|
||||
#include <stddef.h>
|
||||
|
||||
typedef struct guf_str_view {
|
||||
const char *str;
|
||||
ptrdiff_t len;
|
||||
} guf_str_view;
|
||||
|
||||
#endif
|
||||
@ -1,93 +0,0 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
387
src/guf_utf8.h
Executable file
387
src/guf_utf8.h
Executable file
@ -0,0 +1,387 @@
|
||||
/*
|
||||
is parametrized: no
|
||||
NOTE: don't include if you already use guf_str.h
|
||||
*/
|
||||
|
||||
#if defined(GUF_UTF8_IMPL_STATIC)
|
||||
#define GUF_UTF8_KWRDS static
|
||||
#else
|
||||
#define GUF_UTF8_KWRDS
|
||||
#endif
|
||||
|
||||
#ifndef GUF_UTF8_H
|
||||
#define GUF_UTF8_H
|
||||
#include "guf_common.h"
|
||||
#include "guf_str_view_type.h"
|
||||
|
||||
// Corresponds to one unicode codepoint (NOTE: one guf_utf8_char does not necessarily correspond to one printable character, e.g. combining characters).
|
||||
typedef struct guf_utf8_char {
|
||||
char bytes[4];
|
||||
} guf_utf8_char;
|
||||
|
||||
typedef enum guf_utf8_stat {
|
||||
GUF_UTF8_READ_DONE,
|
||||
GUF_UTF8_READ_VALID,
|
||||
GUF_UTF8_READ_INVALID,
|
||||
GUF_UTF8_READ_TRUNCATED,
|
||||
} guf_utf8_stat;
|
||||
|
||||
static inline bool guf_char_is_ascii(int c) {return c <= 0 && c <= 127;}
|
||||
static inline bool guf_uchar_is_ascii(unsigned char c) {return c <= 127;}
|
||||
static inline bool guf_char_isspace_ascii(int c) {return c == ' ' || c == '\n' || c == '\t' || c == '\v' || c == '\f' || c == '\r';}
|
||||
|
||||
GUF_UTF8_KWRDS int guf_utf8_num_bytes(unsigned char c);
|
||||
GUF_UTF8_KWRDS int guf_utf8_char_num_bytes(const guf_utf8_char *c);
|
||||
GUF_UTF8_KWRDS bool guf_utf8_char_is_valid(const guf_utf8_char *c);
|
||||
GUF_UTF8_KWRDS bool guf_utf8_char_is_whitespace(const guf_utf8_char *c);
|
||||
|
||||
GUF_UTF8_KWRDS guf_utf8_char guf_utf8_char_new(uint_least32_t codepoint); // Returns GUF_UTF8_REPLACEMENT_CHAR for invalid codepoints (and for GUF_UTF8_REPLACEMENT_CHAR_CODEPOINT).
|
||||
GUF_UTF8_KWRDS bool guf_utf8_encode(guf_utf8_char *result, uint_least32_t codepoint); // Returns false for invalid codepoints.
|
||||
GUF_UTF8_KWRDS int_least32_t guf_utf8_decode(const guf_utf8_char *utf8); // Returns -1 for invalid utf-8.
|
||||
|
||||
GUF_UTF8_KWRDS bool guf_utf8_equal(const guf_utf8_char *a, const guf_utf8_char *b);
|
||||
|
||||
GUF_UTF8_KWRDS guf_utf8_stat guf_utf8_char_next(guf_utf8_char *res, guf_str_view *str);
|
||||
|
||||
extern const char* const GUF_UTF8_WHITESPACE[25];
|
||||
extern const char* const GUF_UTF8_COMMON_PUNCT[32];
|
||||
|
||||
extern const guf_utf8_char GUF_UTF8_REPLACEMENT_CHAR; // Replacement character "<22>" (U+FFFD)
|
||||
#define GUF_UTF8_REPLACEMENT_CHAR_CODEPOINT UINT32_C(0xFFFD)
|
||||
|
||||
#endif
|
||||
|
||||
#if defined(GUF_UTF8_IMPL) || defined(GUF_UTF8_IMPL_STATIC)
|
||||
#include <string.h>
|
||||
|
||||
// All utf-8 whitespace, cf. https://en.wikipedia.org/wiki/Whitespace_character#Unicode (last-retrieved 2025-02-27)
|
||||
const char* const GUF_UTF8_WHITESPACE[25] =
|
||||
{
|
||||
" ", "\n", "\t", "\r", "\v", "\f",
|
||||
"\xC2\x85", "\xC2\xA0",
|
||||
"\xE1\x9A\x80", "\xE2\x80\x80", "\xE2\x80\x81", "\xE2\x80\x82", "\xE2\x80\x83", "\xE2\x80\x84", "\xE2\x80\x85", "\xE2\x80\x86", "\xE2\x80\x87", "\xE2\x80\x88", "\xE2\x80\x89", "\xE2\x80\x8A", "\xE2\x80\xA8", "\xE2\x80\xA9", "\xE2\x80\xAF", "\xE2\x81\x9F", "\xE3\x80\x80"
|
||||
};
|
||||
|
||||
// Common punctuation (TODO: make more exhaustive; use \x escapes)
|
||||
const char* const GUF_UTF8_COMMON_PUNCT[32] =
|
||||
{
|
||||
".", ",", ";", ":", "(", ")", "[", "]", "!", "?", "¿", "¡", "&", "+", "-", "/", "*", "\"", "'", "„", "“", "´", "»", "«", "`", "\\", "%", "‒", "–", "—", "—", "_"
|
||||
};
|
||||
|
||||
const guf_utf8_char GUF_UTF8_REPLACEMENT_CHAR = {.bytes = {'\xEF','\xBF','\xBD', '\0'}};
|
||||
|
||||
#ifndef GUF_FN_KEYWORDS
|
||||
#define GUF_FN_KEYWORDS
|
||||
#endif
|
||||
|
||||
GUF_UTF8_KWRDS bool guf_utf8_equal(const guf_utf8_char *a, const guf_utf8_char *b)
|
||||
{
|
||||
const int num_bytes_a = guf_utf8_char_num_bytes(a);
|
||||
const int num_bytes_b = guf_utf8_char_num_bytes(b);
|
||||
|
||||
if (num_bytes_a != num_bytes_b) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const int n = (num_bytes_a != 0) ? GUF_CLAMP(num_bytes_a, 1, 4) : 4;
|
||||
for (int i = 0; i < n; ++i) {
|
||||
if (a->bytes[i] != b->bytes[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// cf. https://datatracker.ietf.org/doc/html/rfc3629#section-3 (last-retrieved 2025-03-02)
|
||||
GUF_UTF8_KWRDS bool guf_utf8_encode(guf_utf8_char *result, uint_least32_t cp)
|
||||
{
|
||||
GUF_ASSERT(result);
|
||||
|
||||
// "The definition of UTF-8 prohibits encoding character numbers between U+D800 and U+DFFF" (surrogate pairs).
|
||||
const bool might_be_valid = (cp <= 0x10FFFF) && !(cp >= 0xD800 && cp <= 0xDFFF);
|
||||
if (!might_be_valid) {
|
||||
*result = GUF_UTF8_REPLACEMENT_CHAR;
|
||||
return false;
|
||||
}
|
||||
|
||||
memset(result->bytes, '\0', GUF_ARR_SIZE(result->bytes));
|
||||
|
||||
int num_bytes = 0, first_byte_bits = 0;
|
||||
if (cp <= 0x7F) { // binary: 0xxx.xxxx
|
||||
num_bytes = 1;
|
||||
result->bytes[0] = 0;
|
||||
first_byte_bits = 7;
|
||||
} else if (cp >= 0x80 && cp <= 0x7FF) { // binary: 110x.xxxx 10xx.xxxx
|
||||
num_bytes = 2;
|
||||
result->bytes[0] = (char)0xC0;
|
||||
first_byte_bits = 5;
|
||||
} else if (cp >= 0x800 && cp <= 0xFFFF) { // binary: 1110.xxxx 10xx.xxxx 10xx.xxxx
|
||||
num_bytes = 3;
|
||||
result->bytes[0] = (char)0xE0;
|
||||
first_byte_bits = 4;
|
||||
} else if (cp >= 0x10000 && cp <= 0x10FFFF) { // binary: 1111.0xxx 10xx.xxxx 10xx.xxxx 10xx.xxxx
|
||||
num_bytes = 4;
|
||||
result->bytes[0] = (char)0xF0;
|
||||
first_byte_bits = 3;
|
||||
}
|
||||
|
||||
if (num_bytes == 0) {
|
||||
*result = GUF_UTF8_REPLACEMENT_CHAR;
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 1; i < num_bytes; ++i) {
|
||||
result->bytes[i] = (char)0x80; // binary: 10xx.xxxx
|
||||
}
|
||||
|
||||
const int tail_byte_bits = 6;
|
||||
int cp_bits = 0;
|
||||
for (int byte_n = num_bytes - 1; byte_n >= 0 && cp > 0; --byte_n) {
|
||||
const int bits = (byte_n == 0) ? first_byte_bits : tail_byte_bits;
|
||||
const uint_least32_t cp_mask = GUF_UWRAP_32( (UINT32_C(1) << bits) - 1 );
|
||||
result->bytes[byte_n] = (char)(1u * (unsigned char)result->bytes[byte_n] | (cp & cp_mask));
|
||||
cp = cp >> bits;
|
||||
cp_bits += bits;
|
||||
}
|
||||
GUF_ASSERT(cp_bits <= first_byte_bits + (num_bytes - 1) * tail_byte_bits);
|
||||
GUF_ASSERT(cp_bits <= 21);
|
||||
(void)cp_bits;
|
||||
|
||||
if (guf_utf8_char_is_valid(result)) {
|
||||
return true;
|
||||
} else {
|
||||
*result = GUF_UTF8_REPLACEMENT_CHAR;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
GUF_UTF8_KWRDS guf_utf8_char guf_utf8_char_new(uint_least32_t codepoint)
|
||||
{
|
||||
guf_utf8_char result = GUF_UTF8_REPLACEMENT_CHAR;
|
||||
guf_utf8_encode(&result, codepoint);
|
||||
return result;
|
||||
}
|
||||
|
||||
// cf. https://datatracker.ietf.org/doc/html/rfc3629#section-3 (last-retrieved 2025-03-02)
|
||||
GUF_UTF8_KWRDS int_least32_t guf_utf8_decode(const guf_utf8_char *c)
|
||||
{
|
||||
if (!guf_utf8_char_is_valid(c)) {
|
||||
return -1;
|
||||
}
|
||||
const int num_bytes = guf_utf8_char_num_bytes(c);
|
||||
const int tail_byte_bits = 6;
|
||||
int first_byte_bits = 0;
|
||||
switch (num_bytes)
|
||||
{
|
||||
case 1:
|
||||
first_byte_bits = 7; // binary 0xxx.xxxx
|
||||
break;
|
||||
case 2:
|
||||
first_byte_bits = 5; // binary: 110x.xxxx 10xx.xxxx
|
||||
break;
|
||||
case 3:
|
||||
first_byte_bits = 4; // binary: 1110.xxxx 10xx.xxxx 10xx.xxxx
|
||||
break;
|
||||
case 4:
|
||||
first_byte_bits = 3; // binary: 1111.0xxx 10xx.xxxx 10xx.xxxx 10xx.xxxx
|
||||
break;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint_least32_t cp = 0;
|
||||
int cp_bits = 0;
|
||||
for (int byte_n = num_bytes - 1; byte_n >= 0; --byte_n) {
|
||||
const int bits = (byte_n == 0) ? first_byte_bits : tail_byte_bits;
|
||||
const uint_least32_t byte_mask = GUF_UWRAP_32( (UINT32_C(1) << bits) - 1 );
|
||||
cp = GUF_UWRAP_32( cp | GUF_UWRAP_32( 1u * ((uint_least32_t)c->bytes[byte_n] & byte_mask) << cp_bits ) );
|
||||
cp_bits += bits;
|
||||
}
|
||||
GUF_ASSERT(cp_bits == first_byte_bits + (num_bytes - 1) * tail_byte_bits);
|
||||
GUF_ASSERT(cp_bits <= 21);
|
||||
|
||||
const bool valid = (cp <= 0x10FFFF) && !(cp >= 0xD800 && cp <= 0xDFFF);
|
||||
if (!valid) {
|
||||
return -1;
|
||||
} else {
|
||||
#ifdef INT32_MAX
|
||||
GUF_ASSERT(cp <= INT32_MAX);
|
||||
#endif
|
||||
GUF_ASSERT(cp <= INT_LEAST32_MAX);
|
||||
return (int_least32_t)cp;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
GUF_UTF8_KWRDS guf_utf8_stat guf_utf8_char_next(guf_utf8_char *res, guf_str_view *str)
|
||||
{
|
||||
GUF_ASSERT(res);
|
||||
GUF_ASSERT(str);
|
||||
|
||||
if (str->len <= 0 || str->str == NULL) {
|
||||
return GUF_UTF8_READ_DONE;
|
||||
}
|
||||
|
||||
int consumed = 0;
|
||||
res->bytes[consumed++] = str->str[0];
|
||||
str->len--;
|
||||
str->str = str->len ? str->str + 1 : NULL;
|
||||
|
||||
for (size_t i = 1; i < GUF_ARR_SIZE(res->bytes); ++i) {
|
||||
res->bytes[i] = '\0';
|
||||
}
|
||||
|
||||
const int num_bytes = guf_utf8_char_num_bytes(res);
|
||||
|
||||
if (!num_bytes) {
|
||||
return GUF_UTF8_READ_INVALID;
|
||||
}
|
||||
|
||||
while (consumed < num_bytes && str->len > 0) {
|
||||
res->bytes[consumed++] = str->str[0];
|
||||
str->len--;
|
||||
str->str = str->len ? str->str + 1 : NULL;
|
||||
}
|
||||
|
||||
if (consumed < num_bytes) {
|
||||
return GUF_UTF8_READ_TRUNCATED;
|
||||
} else if (guf_utf8_char_is_valid(res)) {
|
||||
return GUF_UTF8_READ_VALID;
|
||||
} else {
|
||||
// TODO: this means str will point one past the last read character (maybe it would be better to skip to one past the first?)
|
||||
return GUF_UTF8_READ_INVALID;
|
||||
}
|
||||
}
|
||||
|
||||
// cf. https://www.rfc-editor.org/rfc/rfc3629#page-4
|
||||
GUF_UTF8_KWRDS int guf_utf8_num_bytes(unsigned char c)
|
||||
{
|
||||
if (c <= 0x7F) { // bits: 0xxx.xxxx
|
||||
return 1;
|
||||
} else if (c >= 0xC2 && c <= 0xDF) { // bits: 110x.xxxx (without 0xC0 and 0xC1)
|
||||
return 2;
|
||||
} else if (c >= 0xE0 && c <= 0xEF) { // bits: 1110.xxxx
|
||||
return 3;
|
||||
} else if (c >= 0xF0 && c <= 0xF4) { // bits: b1111.0xxx (without 0xF5 to 0xFF)
|
||||
return 4;
|
||||
} else {
|
||||
return 0; // Invalid byte.
|
||||
}
|
||||
}
|
||||
|
||||
GUF_UTF8_KWRDS int guf_utf8_char_num_bytes(const guf_utf8_char *c)
|
||||
{
|
||||
GUF_ASSERT(c);
|
||||
return guf_utf8_num_bytes(c->bytes[0]);
|
||||
}
|
||||
|
||||
|
||||
GUF_UTF8_KWRDS bool guf_utf8_char_is_valid(const guf_utf8_char *c)
|
||||
{
|
||||
const int num_bytes = guf_utf8_num_bytes(c->bytes[0]);
|
||||
|
||||
if (!num_bytes) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const unsigned char *bytes = (const unsigned char*)c->bytes; // It's important to cast to unsigned char* here!
|
||||
|
||||
for (int i = 0; i < num_bytes; ++i) {
|
||||
// "The octet values C0, C1, F5 to FF never appear.", cf. https://www.rfc-editor.org/rfc/rfc3629#page-5
|
||||
if (bytes[i] == 0xC0 || bytes[i] == 0xC1 || (bytes[i] >= 0xF5)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Binary: 10xx.xxxx
|
||||
#define guf_valid_tail(byte) ((byte) >= 0x80 && (byte) <= 0xBF)
|
||||
|
||||
// cf. https://datatracker.ietf.org/doc/html/rfc3629#page-5
|
||||
switch (num_bytes)
|
||||
{
|
||||
case 1:
|
||||
return true;
|
||||
|
||||
case 2:
|
||||
return guf_valid_tail(bytes[1]);
|
||||
|
||||
case 3:
|
||||
if ((bytes[0] == 0xE0) && (bytes[1] >= 0xA0 && bytes[1] <= 0xBF) && guf_valid_tail(bytes[2])) {
|
||||
return true;
|
||||
}
|
||||
if ((bytes[0] >= 0xE1 && bytes[0] <= 0xEC) && guf_valid_tail(bytes[1]) && guf_valid_tail(bytes[2])) {
|
||||
return true;
|
||||
}
|
||||
if ((bytes[0] == 0xED) && (bytes[1] >= 0x80 && bytes[1] <= 0x9F) && guf_valid_tail(bytes[2])) {
|
||||
return true;
|
||||
}
|
||||
if ((bytes[0] >= 0xEE && bytes[0] <= 0xEF) && guf_valid_tail(bytes[1]) && guf_valid_tail(bytes[2])) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
case 4:
|
||||
if ((bytes[0] == 0xF0) && (bytes[1] >= 0x90 && bytes[1] <= 0xBF) && guf_valid_tail(bytes[2]) && guf_valid_tail(bytes[3])) {
|
||||
return true;
|
||||
}
|
||||
if ((bytes[0] >= 0xF1 && bytes[0] <= 0xF3) && guf_valid_tail(bytes[1]) && guf_valid_tail(bytes[2]) && guf_valid_tail(bytes[3])) {
|
||||
return true;
|
||||
}
|
||||
if ((bytes[0] == 0xF4) && (bytes[1] >= 0x80 && bytes[1] <= 0x8F) && guf_valid_tail(bytes[2]) && guf_valid_tail(bytes[3])) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
#undef guf_valid_tail
|
||||
}
|
||||
|
||||
GUF_UTF8_KWRDS bool guf_utf8_char_is_whitespace(const guf_utf8_char *c)
|
||||
{
|
||||
GUF_ASSERT(c);
|
||||
|
||||
// cf. https://en.wikipedia.org/wiki/Whitespace_character#Unicode (last-retrieved 2025-02-27)
|
||||
const char *ws_one_byte[] = {" ", "\n", "\t", "\t", "\v", "\f"};
|
||||
const char *ws_two_bytes[] = {"\xC2\x85", "\xC2\xA0"};
|
||||
const char *ws_three_bytes[] = {"\xE1\x9A\x80", "\xE2\x80\x80", "\xE2\x80\x81", "\xE2\x80\x82", "\xE2\x80\x83", "\xE2\x80\x84", "\xE2\x80\x85", "\xE2\x80\x86", "\xE2\x80\x87", "\xE2\x80\x88", "\xE2\x80\x89", "\xE2\x80\x8A", "\xE2\x80\xA8", "\xE2\x80\xA9", "\xE2\x80\xAF", "\xE2\x81\x9F", "\xE3\x80\x80"};
|
||||
|
||||
const int num_bytes = guf_utf8_num_bytes(c->bytes[0]);
|
||||
|
||||
switch (num_bytes)
|
||||
{
|
||||
case 1:
|
||||
for (size_t i = 0; i < GUF_ARR_SIZE(ws_one_byte); ++i) {
|
||||
if (c->bytes[0] == ws_one_byte[i][0]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
case 2:
|
||||
for (size_t i = 0; i < GUF_ARR_SIZE(ws_two_bytes); ++i) {
|
||||
if (c->bytes[0] == ws_two_bytes[i][0] && c->bytes[1] == ws_two_bytes[i][1]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
case 3:
|
||||
for (size_t i = 0; i < GUF_ARR_SIZE(ws_three_bytes); ++i) {
|
||||
if (c->bytes[0] == ws_three_bytes[i][0] && c->bytes[1] == ws_three_bytes[i][1] && c->bytes[2] == ws_three_bytes[i][2]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#undef GUF_UTF8_IMPL
|
||||
#undef GUF_UTF8_IMPL_STATIC
|
||||
#endif /* end impl */
|
||||
|
||||
#undef GUF_UTF8_KWRDS
|
||||
72
src/guf_utils.h
Executable file
72
src/guf_utils.h
Executable file
@ -0,0 +1,72 @@
|
||||
/*
|
||||
is parametrized: no
|
||||
*/
|
||||
|
||||
#ifndef GUF_UTILS_H
|
||||
#define GUF_UTILS_H
|
||||
#include "guf_common.h"
|
||||
#include "guf_assert.h"
|
||||
|
||||
static inline void guf_platform_assert_endianness(void)
|
||||
{
|
||||
const unsigned i = 1;
|
||||
const char *bytes = (const char*)&i;
|
||||
const bool is_big_endian = bytes[0] != 1;
|
||||
#if defined(GUF_PLATFORM_LITTLE_ENDIAN)
|
||||
GUF_ASSERT_RELEASE(!is_big_endian)
|
||||
#elif defined(GUF_PLATFORM_BIG_ENDIAN)
|
||||
GUF_ASSERT_RELEASE(is_big_endian)
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline void guf_platform_assert_native_word_bits(void)
|
||||
{
|
||||
const int bits = sizeof(void*) * CHAR_BIT;
|
||||
GUF_ASSERT_RELEASE(GUF_PLATFORM_BITS == bits);
|
||||
}
|
||||
|
||||
#ifdef NDEBUG
|
||||
#define GUF_DBG_STR "release"
|
||||
#else
|
||||
#define GUF_DBG_STR "debug"
|
||||
#endif
|
||||
|
||||
#if defined(GUF_STDC_AT_LEAST_C23)
|
||||
#ifdef GUF_PLATFORM_LITTLE_ENDIAN
|
||||
#define GUF_PLATFORM_STRING "C23 (or above) " GUF_STRINGIFY(GUF_PLATFORM_BITS) "-bit " "little-endian " GUF_DBG_STR
|
||||
#else
|
||||
#define GUF_PLATFORM_STRING "C23 (or above) " GUF_STRINGIFY(GUF_PLATFORM_BITS) "-bit " "big-endian " GUF_DBG_STR
|
||||
#endif
|
||||
#elif defined(GUF_STDC_AT_LEAST_C17)
|
||||
#ifdef GUF_PLATFORM_LITTLE_ENDIAN
|
||||
#define GUF_PLATFORM_STRING "C17 " GUF_STRINGIFY(GUF_PLATFORM_BITS) "-bit " "little-endian " GUF_DBG_STR
|
||||
#else
|
||||
#define GUF_PLATFORM_STRING "C17 " GUF_STRINGIFY(GUF_PLATFORM_BITS) "-bit " "big-endian " GUF_DBG_STR
|
||||
#endif
|
||||
#elif defined(GUF_STDC_AT_LEAST_C11)
|
||||
#ifdef GUF_PLATFORM_LITTLE_ENDIAN
|
||||
#define GUF_PLATFORM_STRING "C11 " GUF_STRINGIFY(GUF_PLATFORM_BITS) "-bit " "little-endian " GUF_DBG_STR
|
||||
#else
|
||||
#define GUF_PLATFORM_STRING "C11 " GUF_STRINGIFY(GUF_PLATFORM_BITS) "-bit " "big-endian " GUF_DBG_STR
|
||||
#endif
|
||||
#elif defined(GUF_STDC_AT_LEAST_C99)
|
||||
#ifdef GUF_PLATFORM_LITTLE_ENDIAN
|
||||
#define GUF_PLATFORM_STRING "C99 " GUF_STRINGIFY(GUF_PLATFORM_BITS) "-bit " "little-endian " GUF_DBG_STR
|
||||
#else
|
||||
#define GUF_PLATFORM_STRING "C99 " GUF_STRINGIFY(GUF_PLATFORM_BITS) "-bit " "big-endian " GUF_DBG_STR
|
||||
#endif
|
||||
#elif defined(GUF_STDCPP_AT_LEAST_CPP11)
|
||||
#ifdef GUF_PLATFORM_LITTLE_ENDIAN
|
||||
#define GUF_PLATFORM_STRING "C++11 (or above) " GUF_STRINGIFY(GUF_PLATFORM_BITS) "-bit " "little-endian " GUF_DBG_STR
|
||||
#else
|
||||
#define GUF_PLATFORM_STRING "C++11 (or above) " GUF_STRINGIFY(GUF_PLATFORM_BITS) "-bit " "big-endian " GUF_DBG_STR
|
||||
#endif
|
||||
#else
|
||||
#ifdef GUF_PLATFORM_LITTLE_ENDIAN
|
||||
#define GUF_PLATFORM_STRING "C?? " GUF_STRINGIFY(GUF_PLATFORM_BITS) "-bit " "little-endian " GUF_DBG_STR
|
||||
#else
|
||||
#define GUF_PLATFORM_STRING "C?? " GUF_STRINGIFY(GUF_PLATFORM_BITS) "-bit " "big-endian " GUF_DBG_STR
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#endif
|
||||
1941
src/test/data/bartleby.txt
Executable file
1941
src/test/data/bartleby.txt
Executable file
File diff suppressed because it is too large
Load Diff
67
src/test/data/utf8-test.txt
Executable file
67
src/test/data/utf8-test.txt
Executable file
@ -0,0 +1,67 @@
|
||||
„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.“
|
||||
|
||||
Det var i den Tid, jeg gik omkring og sulted i Kristiania, denne forunderlige By,
|
||||
som ingen forlader, før han har fået Mærker af den . . . .
|
||||
Jeg ligger vågen på min Kvist og hører en Klokke nedenunder mig slå seks Slag; det var allerede ganske lyst,
|
||||
og Folk begyndte at færdes op og ned i Trapperne. Nede ved Døren, hvor mit Rum var tapetseret med gamle Numre
|
||||
af »Morgenbladet«, kunde jeg så tydelig se en Bekendtgørelse fra Fyrdirektøren, og lidt tilvenstre derfra et fedt,
|
||||
bugnende Avertissement fra Bager Fabian Olsen om nybagt Brød.
|
||||
|
||||
The quick brown fox jumps over the lazy dog.
|
||||
|
||||
Quizdeltagerne spiste jordbær med fløde, mens cirkusklovnen Wolther spillede på xylofon.
|
||||
|
||||
Falsches Üben von Xylophonmusik quält jeden größeren Zwerg.
|
||||
|
||||
Ξεσκεπάζω τὴν ψυχοφθόρα βδελυγμία.
|
||||
|
||||
El pingüino Wenceslao hizo kilómetros bajo exhaustiva lluvia y frío, añoraba a su querido cachorro.
|
||||
|
||||
Le cœur déçu mais l'âme plutôt naïve, Louÿs rêva de crapaüter en
|
||||
canoë au delà des îles, près du mälström où brûlent les novæ.
|
||||
|
||||
D'fhuascail Íosa, Úrmhac na hÓighe Beannaithe, pór Éava agus Ádhaimh.
|
||||
|
||||
Árvíztűrő tükörfúrógép.
|
||||
|
||||
Pchnąć w tę łódź jeża lub ośm skrzyń fig.
|
||||
|
||||
Kæmi ný öxi hér ykist þjófum nú bæði víl og ádrepa.
|
||||
|
||||
В чащах юга жил бы цитрус? Да, но фальшивый экземпляр!
|
||||
|
||||
Pijamalı hasta, yağız şoföre çabucak güvendi.
|
||||
|
||||
Albert osti fagotin ja töräytti puhkuvan melodian.
|
||||
|
||||
דג סקרן שט בים מאוכזב ולפתע מצא חברה
|
||||
|
||||
نص حكيم له سر قاطع وذو شأن عظيم مكتوب على ثوب أخضر ومغلف بجلد أزرق
|
||||
|
||||
بر اثر چنین تلقین و شستشوی مغزی جامعی، سطح و پایهٔ ذهن و فهم و نظر بعضی اشخاص واژگونه و معکوس میشود
|
||||
|
||||
키스의 고유조건은 입술끼리 만나야 하고 특별한 기술은 필요치 않다.
|
||||
|
||||
いろはにほへとちりぬるを
|
||||
わかよたれそつねならむ
|
||||
うゐのおくやまけふこえて
|
||||
あさきゆめみしゑひもせす
|
||||
|
||||
イロハニホヘト チリヌルヲ ワカヨタレソ ツネナラム
|
||||
ウヰノオクヤマ ケフコエテ アサキユメミシ ヱヒモセスン
|
||||
|
||||
ᚠᛇᚻ᛫ᛒᛦᚦ᛫ᚠᚱᚩᚠᚢᚱ᛫ᚠᛁᚱᚪ᛫ᚷᛖᚻᚹᛦᛚᚳᚢᛗ
|
||||
ᛋᚳᛖᚪᛚ᛫ᚦᛖᚪᚻ᛫ᛗᚪᚾᚾᚪ᛫ᚷᛖᚻᚹᛦᛚᚳ᛫ᛗᛁᚳᛚᚢᚾ᛫ᚻᛦᛏ᛫ᛞᚫᛚᚪᚾ
|
||||
ᚷᛁᚠ᛫ᚻᛖ᛫ᚹᛁᛚᛖ᛫ᚠᚩᚱ᛫ᛞᚱᛁᚻᛏᚾᛖ᛫ᛞᚩᛗᛖᛋ᛫ᚻᛚᛇᛏᚪᚾ᛬
|
||||
320
src/test/example.c
Executable file
320
src/test/example.c
Executable file
@ -0,0 +1,320 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "guf_init.h" /* Must be included once (or compiled in a separate .c file and linked) */
|
||||
|
||||
#define GUF_ALLOC_LIBC_IMPL_STATIC
|
||||
#include "guf_alloc_libc.h"
|
||||
|
||||
#include "guf_cstr.h"
|
||||
#include "guf_linalg.h"
|
||||
#include "guf_utils.h"
|
||||
|
||||
#define GUF_T float
|
||||
#define GUF_SORT_IMPL_STATIC
|
||||
#include "guf_sort.h"
|
||||
|
||||
#define GUF_T int
|
||||
#define GUF_SORT_IMPL_STATIC
|
||||
#include "guf_sort.h"
|
||||
|
||||
#define GUF_DBUF_NAME dbuf_int
|
||||
#define GUF_T int
|
||||
#define GUF_T_IS_INTEGRAL_TYPE
|
||||
#define GUF_DBUF_IMPL_STATIC
|
||||
#include "guf_dbuf.h"
|
||||
|
||||
#define GUF_DBUF_NAME dbuf_float
|
||||
#define GUF_T float
|
||||
#define GUF_T_IS_INTEGRAL_TYPE
|
||||
#define GUF_DBUF_IMPL_STATIC
|
||||
#include "guf_dbuf.h"
|
||||
|
||||
#define GUF_T guf_cstr_heap
|
||||
#define GUF_DBUF_NAME dbuf_heap_cstr
|
||||
#define GUF_T_COPY guf_cstr_heap_copy
|
||||
#define GUF_T_MOVE guf_cstr_heap_move
|
||||
#define GUF_T_FREE guf_cstr_heap_free
|
||||
#define GUF_T_EQ guf_cstr_heap_eq
|
||||
#define GUF_DBUF_IMPL_STATIC
|
||||
// #define GUF_CNT_WITH_ELEM_CTX
|
||||
#include "guf_dbuf.h"
|
||||
|
||||
#define GUF_T guf_cstr_const
|
||||
#define GUF_DBUF_NAME dbuf_const_cstr
|
||||
#define GUF_T_EQ guf_cstr_const_eq
|
||||
#define GUF_DBUF_IMPL_STATIC
|
||||
#include "guf_dbuf.h"
|
||||
|
||||
#define GUF_RAND_IMPL_STATIC
|
||||
// #define GUF_RAND_32_BIT
|
||||
#include "guf_rand.h"
|
||||
|
||||
#include "impls/dict_impl.h"
|
||||
|
||||
int main(void)
|
||||
{
|
||||
guf_allocator allocator;
|
||||
guf_libc_alloc_ctx allocator_ctx;
|
||||
guf_alloc_tracker_init(&allocator_ctx.tracker, 1, "example_allocator", NULL, NULL);
|
||||
allocator_ctx.zero_init = false;
|
||||
guf_libc_allocator_init(&allocator, &allocator_ctx);
|
||||
|
||||
printf("libguf example: " GUF_PLATFORM_STRING "\n");
|
||||
guf_platform_assert_endianness();
|
||||
guf_platform_assert_native_word_bits();
|
||||
|
||||
guf_allocator zero_init_allocator;
|
||||
guf_libc_alloc_ctx zero_init_allocator_ctx;
|
||||
guf_alloc_tracker_init(&zero_init_allocator_ctx.tracker, 2, "example_zero_init_allocator", stdout, stderr);
|
||||
zero_init_allocator_ctx.zero_init = true;
|
||||
guf_libc_allocator_init(&zero_init_allocator, &zero_init_allocator_ctx);
|
||||
|
||||
dict_cstr_int ht;
|
||||
dict_cstr_int_init(&ht, &zero_init_allocator);
|
||||
|
||||
dict_cstr_int_insert_val_arg(&ht, "Hello", 42, GUF_CPY_VALUE, GUF_CPY_VALUE);
|
||||
dict_cstr_int_insert_val_arg(&ht, "World", 64, GUF_CPY_VALUE, GUF_CPY_VALUE);
|
||||
|
||||
int kv_iter = 0;
|
||||
GUF_CNT_FOREACH(&ht, dict_cstr_int, kv_it) {
|
||||
printf("%d: %s -> %d\n", kv_iter++, kv_it.ptr->key, kv_it.ptr->val);
|
||||
}
|
||||
|
||||
guf_cstr_const key = "World";
|
||||
int *res = dict_cstr_int_at_val_arg(&ht, "World");
|
||||
if (res) {
|
||||
printf("%s: %d\n", key, *res);
|
||||
} else {
|
||||
printf("key '%s' not found\n", key);
|
||||
}
|
||||
|
||||
GUF_ASSERT(dict_cstr_int_at_val_arg(&ht, "World"));
|
||||
GUF_ASSERT(dict_cstr_int_at_val_arg(&ht, "Hello"));
|
||||
GUF_ASSERT(dict_cstr_int_at_val_arg(&ht, "hello") == NULL);
|
||||
GUF_ASSERT(dict_cstr_int_at_val_arg(&ht, "") == NULL);
|
||||
|
||||
GUF_ASSERT(dict_cstr_int_contains_val_arg(&ht, "World"));
|
||||
GUF_ASSERT(dict_cstr_int_contains_val_arg(&ht, "Hello"));
|
||||
|
||||
const int ht_needle_val = 64;
|
||||
const dict_cstr_int_iter ht_it = dict_cstr_int_find_val(&ht, dict_cstr_int_begin(&ht), dict_cstr_int_end(&ht), &ht_needle_val);
|
||||
|
||||
if (!dict_cstr_int_iter_is_end(&ht, ht_it)) {
|
||||
printf("found value %d (key %s)\n", ht_needle_val, ht_it.ptr->key);
|
||||
}
|
||||
|
||||
dict_cstr_int_free(&ht, NULL);
|
||||
|
||||
|
||||
GUF_CNT_LIFETIME_BLOCK(dbuf_float, floats, {
|
||||
floats = dbuf_float_new(&allocator);
|
||||
|
||||
for (int i = 0; i <= 16; ++i) {
|
||||
dbuf_float_push_val(&floats, i % 2 ? (float)i * -2.f : (float)i * 2.f);
|
||||
}
|
||||
|
||||
// float *tmp = test_allocator.alloc(floats.size * sizeof(float), &test_allocator_ctx);
|
||||
// float *res = float_arr_merge_sort(floats.data, tmp, floats.size, GUF_SORT_ASCENDING, NULL);
|
||||
// test_allocator.free(tmp, floats.size * sizeof(float), &test_allocator_ctx);
|
||||
// GUF_ASSERT_RELEASE(res == floats.data);
|
||||
|
||||
float_arr_qsort(floats.data, floats.size, GUF_SORT_ASCENDING, NULL);
|
||||
GUF_ASSERT_RELEASE(float_arr_is_sorted(floats.data, floats.size, GUF_SORT_ASCENDING, NULL));
|
||||
|
||||
GUF_CNT_FOREACH(&floats, dbuf_float, it) {
|
||||
printf("float: %f\n", (double)*it.ptr);
|
||||
}
|
||||
})
|
||||
|
||||
dbuf_heap_cstr strings = dbuf_heap_cstr_new(&allocator);
|
||||
dbuf_heap_cstr_push_val_cpy(&strings, "Foo 1");
|
||||
dbuf_heap_cstr_push_val_cpy(&strings, "Bar 2");
|
||||
char *move_me = guf_cstr_dup("Baz 3");
|
||||
dbuf_heap_cstr_push(&strings, &move_me, GUF_CPY_MOVE);
|
||||
GUF_ASSERT_RELEASE(move_me == NULL);
|
||||
|
||||
dbuf_heap_cstr_push_val_cpy(&strings, "Boz 4");
|
||||
|
||||
char *findme = "Baz 3";
|
||||
dbuf_heap_cstr_iter beg = dbuf_heap_cstr_begin(&strings);
|
||||
dbuf_heap_cstr_iter end = dbuf_heap_cstr_end(&strings);
|
||||
dbuf_heap_cstr_iter fnd_it = dbuf_heap_cstr_find(&strings, beg, end, &findme);
|
||||
if (!dbuf_heap_cstr_iter_is_end(&strings, fnd_it)) {
|
||||
printf("%s found in range [%td, %td) at idx %td\n", findme, dbuf_heap_cstr_iter_to_idx(&strings, beg), dbuf_heap_cstr_iter_to_idx(&strings, end), dbuf_heap_cstr_iter_to_idx(&strings, fnd_it));
|
||||
} else {
|
||||
printf("%s not found in range [%td, %td) at idx %td\n", findme, dbuf_heap_cstr_iter_to_idx(&strings, beg), dbuf_heap_cstr_iter_to_idx(&strings, end), dbuf_heap_cstr_iter_to_idx(&strings, fnd_it));
|
||||
}
|
||||
|
||||
if (dbuf_heap_cstr_contains_val(&strings, "Baz 3")) {
|
||||
printf("contains\n");
|
||||
} else {
|
||||
printf("does not contain\n");
|
||||
}
|
||||
|
||||
GUF_CNT_FOREACH(&strings, dbuf_heap_cstr, it) {
|
||||
printf("%s\n", *it.ptr);
|
||||
}
|
||||
dbuf_heap_cstr_free(&strings, NULL);
|
||||
|
||||
dbuf_const_cstr const_strings = dbuf_const_cstr_new(&allocator);
|
||||
dbuf_const_cstr_push_val(&const_strings, "Const 1");
|
||||
dbuf_const_cstr_push_val(&const_strings, "Const 2");
|
||||
const char *foo = "Const 3";
|
||||
dbuf_const_cstr_push(&const_strings, &foo, GUF_CPY_VALUE);
|
||||
|
||||
dbuf_const_cstr_iter found_it = dbuf_const_cstr_find(&const_strings, dbuf_const_cstr_begin(&const_strings), dbuf_const_cstr_end(&const_strings), &foo);
|
||||
if (found_it.ptr != dbuf_const_cstr_end(&const_strings).ptr) {
|
||||
*found_it.ptr = "Found!";
|
||||
}
|
||||
|
||||
GUF_CNT_FOREACH(&const_strings, dbuf_const_cstr, it) {
|
||||
printf("%s\n", *it.ptr);
|
||||
}
|
||||
dbuf_const_cstr_free(&const_strings, NULL);
|
||||
|
||||
dbuf_int integers = dbuf_int_new(&allocator);
|
||||
dbuf_int_push_val(&integers, 420);
|
||||
dbuf_int_push_val(&integers, 520);
|
||||
dbuf_int_push_val(&integers, 620);
|
||||
dbuf_int_push_val(&integers, 720);
|
||||
dbuf_int_push_val(&integers, 820);
|
||||
|
||||
guf_err err;
|
||||
dbuf_int_try_at(&integers, 16, &err);
|
||||
if (err) {
|
||||
printf("%s %s\n", guf_err_to_str(err), GUF_ERR_MSG_EMPTY());
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
GUF_DBUF_FOREACH(integers, int, elem) {
|
||||
printf("elem %d: %d\n", i, *elem);
|
||||
++i;
|
||||
}
|
||||
|
||||
GUF_CNT_FOREACH(&integers, dbuf_int, it) {
|
||||
printf("it-elem: %d", *it.ptr);
|
||||
if (dbuf_int_iter_next(&integers, it, 1).ptr != dbuf_int_end(&integers).ptr) {
|
||||
printf(", it-next: %d", *dbuf_int_iter_next(&integers, it, 1).ptr);
|
||||
}
|
||||
if (dbuf_int_iter_next(&integers, it, -1).ptr != dbuf_int_end(&integers).ptr) {
|
||||
printf(", it-prev: %d", *dbuf_int_iter_next(&integers, it, -1).ptr);
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
for (dbuf_int_iter it = dbuf_int_begin(&integers); it.ptr != dbuf_int_end(&integers).ptr; it = dbuf_int_iter_next(&integers, it, 2)) {
|
||||
printf("every other: %d\n", *it.ptr);
|
||||
}
|
||||
|
||||
for (dbuf_int_iter it = dbuf_int_rbegin(&integers); it.ptr != dbuf_int_rend(&integers).ptr; it = dbuf_int_iter_next(&integers, it, 1)) {
|
||||
printf("reverse: %d\n", *it.ptr);
|
||||
}
|
||||
|
||||
for (dbuf_int_iter it = dbuf_int_rbegin(&integers); it.ptr != dbuf_int_rend(&integers).ptr; it = dbuf_int_iter_next(&integers, it, 2)) {
|
||||
printf("every other reverse: %d (idx %td)\n", *it.ptr, dbuf_int_iter_to_idx(&integers, it));
|
||||
}
|
||||
|
||||
dbuf_int_free(&integers, NULL);
|
||||
printf("\n");
|
||||
guf_randstate rng;
|
||||
guf_randstate_init(&rng, (guf_rand_seed_t)time(NULL));
|
||||
int heads = 0, tails = 0;
|
||||
int throws = 10;
|
||||
for (i = 0; i < throws; ++i) {
|
||||
bool is_head = guf_rand_flip(&rng);
|
||||
if (is_head) {
|
||||
puts("head");
|
||||
++heads;
|
||||
} else {
|
||||
puts("tail");
|
||||
++tails;
|
||||
}
|
||||
}
|
||||
printf("n: %d\nheads: %d\ntails: %d\n", throws, heads, tails);
|
||||
|
||||
int result[256];
|
||||
memset(result, 0, sizeof result);
|
||||
for (int n = 0; n < 32000; ++n) {
|
||||
float r = roundf(guf_rand_normal_sample_one_f32(&rng, 100, 15));
|
||||
r = guf_clamp_f32(r, 0, 255);
|
||||
result[(int)r] += 1;
|
||||
}
|
||||
for (size_t n = 60; n <= 140; ++n) {
|
||||
printf("%zu:\t", n);
|
||||
for (int j = 0; j < result[n] / 8; ++j) {
|
||||
putc('#', stdout);
|
||||
}
|
||||
puts("");
|
||||
}
|
||||
|
||||
|
||||
for (float angle = 0; angle <= 8.f * GUF_PI_F32; angle += 0.001f) {
|
||||
guf_quaternion rotq = guf_quaternion_from_axis_angle(angle, guf_vec3_normalised((guf_vec3){-2324234.3f, 1.4f, -1.3f}));
|
||||
guf_mat4x4 rotmat, rotmat_inv;
|
||||
guf_mat4x4_init_from_quaternion(&rotmat, rotq);
|
||||
GUF_ASSERT_RELEASE(guf_mat4x4_inverted(&rotmat, &rotmat_inv))
|
||||
|
||||
guf_mat4x4_set_trans(&rotmat, (guf_vec3){42.1234f, -512.2f, 3.1415926f});
|
||||
GUF_ASSERT_RELEASE(guf_mat4x4_inverted(&rotmat, &rotmat_inv));
|
||||
|
||||
GUF_ASSERT_RELEASE(guf_mat4x4_inverted(&rotmat_inv, &rotmat));
|
||||
GUF_ASSERT_RELEASE(guf_mat4x4_inverted(&rotmat, &rotmat_inv));
|
||||
}
|
||||
|
||||
guf_quaternion q = guf_quaternion_from_axis_angle(GUF_PI_F32 / 8.f, guf_vec3_normalised((guf_vec3){0.3f, 10.2f, -25.f}));
|
||||
|
||||
guf_mat4x4 mat;
|
||||
guf_mat4x4_init_from_quaternion(&mat, q);
|
||||
guf_mat4x4_set_trans(&mat, (guf_vec3){42.1234f, -512.2f, 3.1415926f});
|
||||
|
||||
mat.data[2][0] *= 100000.4f;
|
||||
|
||||
const guf_mat4x4 mat_cpy = mat;
|
||||
|
||||
printf("Matrix:\n");
|
||||
guf_mat4x4_print_with_precision(&mat, stdout, 8);
|
||||
|
||||
guf_mat4x4 mat_inv;
|
||||
bool invertible = guf_mat4x4_inverted(&mat, &mat_inv);
|
||||
if (!invertible) {
|
||||
printf("Not invertible\n");
|
||||
} else {
|
||||
printf("Inverse:\n");
|
||||
guf_mat4x4_print_with_precision(&mat_inv, stdout, 8);
|
||||
GUF_ASSERT_RELEASE(guf_mat4x4_inverted(&mat_inv, &mat));
|
||||
GUF_ASSERT_RELEASE(guf_mat4x4_nearly_equal(&mat, &mat_cpy, 1e-4f, 1e-5f));
|
||||
}
|
||||
|
||||
mat = (guf_mat4x4) {.data = {
|
||||
{1, 1.3f, 1, 1},
|
||||
{2, 2.6f, 2, 2},
|
||||
{0, 0, 2, 4},
|
||||
{0, 0, 0, 1}
|
||||
}};
|
||||
printf("Matrix 2:\n");
|
||||
guf_mat4x4_print_with_precision(&mat, stdout, 8);
|
||||
invertible = guf_mat4x4_inverted(&mat, &mat_inv);
|
||||
if (!invertible) {
|
||||
printf("Not invertible\n");
|
||||
} else {
|
||||
printf("Inverse:\n");
|
||||
guf_mat4x4_print_with_precision(&mat_inv, stdout, 8);
|
||||
}
|
||||
|
||||
bool leak = false;
|
||||
if (guf_alloc_tracker_found_leak(&allocator_ctx.tracker)) {
|
||||
printf("Found memory leak:\n");
|
||||
guf_alloc_tracker_print(&allocator_ctx.tracker, stderr);
|
||||
leak = true;
|
||||
}
|
||||
if (guf_alloc_tracker_found_leak(&zero_init_allocator_ctx.tracker)) {
|
||||
printf("Found memory leak:\n");
|
||||
guf_alloc_tracker_print(&zero_init_allocator_ctx.tracker, stderr);
|
||||
leak = true;
|
||||
}
|
||||
|
||||
return leak ? EXIT_FAILURE: EXIT_SUCCESS;
|
||||
}
|
||||
2
src/test/impls/alloc_libc_impl.c
Executable file
2
src/test/impls/alloc_libc_impl.c
Executable file
@ -0,0 +1,2 @@
|
||||
#define GUF_ALLOC_LIBC_IMPL
|
||||
#include "guf_alloc_libc.h"
|
||||
2
src/test/impls/alloc_tracker_impl.c
Executable file
2
src/test/impls/alloc_tracker_impl.c
Executable file
@ -0,0 +1,2 @@
|
||||
#define GUF_ALLOC_TRACKER_IMPL
|
||||
#include "guf_alloc_tracker.h"
|
||||
2
src/test/impls/ckdint_impl.c
Executable file
2
src/test/impls/ckdint_impl.c
Executable file
@ -0,0 +1,2 @@
|
||||
#define GUF_MATH_CKDINT_IMPL
|
||||
#include "guf_math_ckdint.h"
|
||||
55
src/test/impls/dbuf_impl.c
Executable file
55
src/test/impls/dbuf_impl.c
Executable file
@ -0,0 +1,55 @@
|
||||
#include "dbuf_impl.h"
|
||||
|
||||
#define GUF_DBUF_NAME dbuf_int
|
||||
#define GUF_T int
|
||||
#define GUF_T_IS_INTEGRAL_TYPE
|
||||
#define GUF_DBUF_IMPL
|
||||
#include "guf_dbuf.h"
|
||||
|
||||
#define GUF_DBUF_NAME dbuf_i32
|
||||
#define GUF_T int32_t
|
||||
#define GUF_T_IS_INTEGRAL_TYPE
|
||||
#define GUF_DBUF_IMPL
|
||||
#include "guf_dbuf.h"
|
||||
|
||||
#define GUF_DBUF_NAME dbuf_char
|
||||
#define GUF_T char
|
||||
#define GUF_T_IS_INTEGRAL_TYPE
|
||||
#define GUF_DBUF_IMPL
|
||||
#include "guf_dbuf.h"
|
||||
|
||||
#define GUF_DBUF_NAME dbuf_float
|
||||
#define GUF_T float
|
||||
#define GUF_T_IS_INTEGRAL_TYPE
|
||||
#define GUF_DBUF_IMPL
|
||||
#include "guf_dbuf.h"
|
||||
|
||||
#define GUF_T guf_cstr_heap
|
||||
#define GUF_DBUF_NAME dbuf_heap_cstr
|
||||
#define GUF_T_COPY guf_cstr_heap_copy
|
||||
#define GUF_T_MOVE guf_cstr_heap_move
|
||||
#define GUF_T_FREE guf_cstr_heap_free
|
||||
#define GUF_T_EQ guf_cstr_heap_eq
|
||||
#define GUF_DBUF_IMPL
|
||||
#include "guf_dbuf.h"
|
||||
|
||||
#define GUF_T guf_cstr_const
|
||||
#define GUF_DBUF_NAME dbuf_const_cstr
|
||||
#define GUF_T_EQ guf_cstr_const_eq
|
||||
#define GUF_DBUF_IMPL
|
||||
#include "guf_dbuf.h"
|
||||
|
||||
#define GUF_T guf_str_view
|
||||
#define GUF_DBUF_NAME dbuf_str_view
|
||||
#define GUF_T_EQ guf_str_view_equal
|
||||
#define GUF_DBUF_IMPL
|
||||
#include "guf_dbuf.h"
|
||||
|
||||
#define GUF_T guf_str
|
||||
#define GUF_DBUF_NAME dbuf_str
|
||||
#define GUF_T_COPY guf_str_copy
|
||||
#define GUF_T_MOVE guf_str_move
|
||||
#define GUF_T_FREE guf_str_free
|
||||
#define GUF_T_EQ guf_str_equal
|
||||
#define GUF_DBUF_IMPL
|
||||
#include "guf_dbuf.h"
|
||||
53
src/test/impls/dbuf_impl.h
Executable file
53
src/test/impls/dbuf_impl.h
Executable file
@ -0,0 +1,53 @@
|
||||
#ifndef GUF_DBUF_IMPL_H
|
||||
#define GUF_DBUF_IMPL_H
|
||||
|
||||
#include "guf_cstr.h"
|
||||
#include "guf_str.h"
|
||||
|
||||
#define GUF_DBUF_NAME dbuf_int
|
||||
#define GUF_T int
|
||||
#define GUF_T_IS_INTEGRAL_TYPE
|
||||
#include "guf_dbuf.h"
|
||||
|
||||
#define GUF_DBUF_NAME dbuf_i32
|
||||
#define GUF_T int32_t
|
||||
#define GUF_T_IS_INTEGRAL_TYPE
|
||||
#include "guf_dbuf.h"
|
||||
|
||||
#define GUF_DBUF_NAME dbuf_char
|
||||
#define GUF_T char
|
||||
#define GUF_T_IS_INTEGRAL_TYPE
|
||||
#include "guf_dbuf.h"
|
||||
|
||||
#define GUF_DBUF_NAME dbuf_float
|
||||
#define GUF_T float
|
||||
#define GUF_T_IS_INTEGRAL_TYPE
|
||||
#include "guf_dbuf.h"
|
||||
|
||||
#define GUF_T guf_cstr_heap
|
||||
#define GUF_DBUF_NAME dbuf_heap_cstr
|
||||
#define GUF_T_COPY guf_cstr_heap_copy
|
||||
#define GUF_T_MOVE guf_cstr_heap_move
|
||||
#define GUF_T_FREE guf_cstr_heap_free
|
||||
#define GUF_T_EQ guf_cstr_heap_eq
|
||||
#include "guf_dbuf.h"
|
||||
|
||||
#define GUF_T guf_cstr_const
|
||||
#define GUF_DBUF_NAME dbuf_const_cstr
|
||||
#define GUF_T_EQ guf_cstr_const_eq
|
||||
#include "guf_dbuf.h"
|
||||
|
||||
#define GUF_T guf_str_view
|
||||
#define GUF_DBUF_NAME dbuf_str_view
|
||||
#define GUF_T_EQ guf_str_view_equal
|
||||
#include "guf_dbuf.h"
|
||||
|
||||
#define GUF_T guf_str
|
||||
#define GUF_DBUF_NAME dbuf_str
|
||||
#define GUF_T_COPY guf_str_copy
|
||||
#define GUF_T_MOVE guf_str_move
|
||||
#define GUF_T_FREE guf_str_free
|
||||
#define GUF_T_EQ guf_str_equal
|
||||
#include "guf_dbuf.h"
|
||||
|
||||
#endif
|
||||
47
src/test/impls/dict_impl.c
Executable file
47
src/test/impls/dict_impl.c
Executable file
@ -0,0 +1,47 @@
|
||||
#include "dict_impl.h"
|
||||
|
||||
#define GUF_DICT_KEY_T guf_cstr_const
|
||||
#define GUF_DICT_KEY_T_EQ guf_cstr_const_eq
|
||||
#define GUF_DICT_KEY_HASH guf_cstr_const_hash
|
||||
#define GUF_DICT_VAL_T int
|
||||
#define GUF_DICT_VAL_T_IS_INTEGRAL_TYPE
|
||||
#define GUF_DICT_NAME dict_cstr_int
|
||||
#define GUF_DICT_IMPL
|
||||
#include "guf_dict.h"
|
||||
|
||||
#define GUF_DICT_KEY_T guf_str_view
|
||||
#define GUF_DICT_KEY_HASH guf_str_view_hash
|
||||
#define GUF_DICT_KEY_T_EQ guf_str_view_equal
|
||||
#define GUF_DICT_VAL_T int32_t
|
||||
#define GUF_DICT_VAL_T_IS_INTEGRAL_TYPE
|
||||
#define GUF_DICT_NAME dict_sv_i32
|
||||
#define GUF_DICT_IMPL
|
||||
// #define GUF_DICT_64_BIT_IDX
|
||||
// #define GUF_DICT_PROBE_LINEAR
|
||||
// #define GUF_DICT_32_BIT_HASH
|
||||
#include "guf_dict.h"
|
||||
|
||||
#define GUF_DICT_KEY_T guf_str
|
||||
#define GUF_DICT_KEY_HASH guf_str_hash
|
||||
#define GUF_DICT_KEY_T_EQ guf_str_equal
|
||||
#define GUF_DICT_KEY_T_CMP guf_str_cmp
|
||||
#define GUF_DICT_KEY_T_COPY guf_str_copy
|
||||
#define GUF_DICT_KEY_T_MOVE guf_str_move
|
||||
#define GUF_DICT_KEY_T_FREE guf_str_free
|
||||
#define GUF_DICT_VAL_T int32_t
|
||||
#define GUF_DICT_VAL_T_IS_INTEGRAL_TYPE
|
||||
#define GUF_DICT_NAME dict_str_i32
|
||||
#define GUF_DICT_IMPL
|
||||
// #define GUF_DICT_64_BIT_IDX
|
||||
// #define GUF_DICT_PROBE_LINEAR
|
||||
// #define GUF_DICT_32_BIT_HASH
|
||||
#include "guf_dict.h"
|
||||
|
||||
#define GUF_DICT_KEY_T int32_t
|
||||
#define GUF_DICT_KEY_HASH int32_hash
|
||||
#define GUF_DICT_KEY_T_EQ int32_eq
|
||||
#define GUF_DICT_VAL_T bool
|
||||
#define GUF_DICT_VAL_T_IS_INTEGRAL_TYPE
|
||||
#define GUF_DICT_NAME dict_i32_bool
|
||||
#define GUF_DICT_IMPL
|
||||
#include "guf_dict.h"
|
||||
61
src/test/impls/dict_impl.h
Executable file
61
src/test/impls/dict_impl.h
Executable file
@ -0,0 +1,61 @@
|
||||
#ifndef GUF_DICT_IMPL_H
|
||||
#define GUF_DICT_IMPL_H
|
||||
|
||||
#include "guf_common.h"
|
||||
#include "guf_cstr.h"
|
||||
#include "guf_str.h"
|
||||
|
||||
#include "guf_hash.h"
|
||||
|
||||
#define GUF_DICT_KEY_T guf_cstr_const
|
||||
#define GUF_DICT_KEY_HASH guf_cstr_const_hash
|
||||
#define GUF_DICT_KEY_T_EQ guf_cstr_const_eq
|
||||
#define GUF_DICT_VAL_T int
|
||||
#define GUF_DICT_VAL_T_IS_INTEGRAL_TYPE
|
||||
#define GUF_DICT_NAME dict_cstr_int
|
||||
#include "guf_dict.h"
|
||||
|
||||
#define GUF_DICT_KEY_T guf_str_view
|
||||
#define GUF_DICT_KEY_HASH guf_str_view_hash
|
||||
#define GUF_DICT_KEY_T_EQ guf_str_view_equal
|
||||
#define GUF_DICT_VAL_T int32_t
|
||||
#define GUF_DICT_VAL_T_IS_INTEGRAL_TYPE
|
||||
#define GUF_DICT_NAME dict_sv_i32
|
||||
// #define GUF_DICT_64_BIT_IDX
|
||||
// #define GUF_DICT_PROBE_LINEAR
|
||||
// #define GUF_DICT_32_BIT_HASH
|
||||
#include "guf_dict.h"
|
||||
|
||||
#define GUF_DICT_KEY_T guf_str
|
||||
#define GUF_DICT_KEY_HASH guf_str_hash
|
||||
#define GUF_DICT_KEY_T_EQ guf_str_equal
|
||||
#define GUF_DICT_KEY_T_CMP guf_str_cmp
|
||||
#define GUF_DICT_KEY_T_COPY guf_str_copy
|
||||
#define GUF_DICT_KEY_T_MOVE guf_str_move
|
||||
#define GUF_DICT_KEY_T_FREE guf_str_free
|
||||
#define GUF_DICT_VAL_T int32_t
|
||||
#define GUF_DICT_VAL_T_IS_INTEGRAL_TYPE
|
||||
#define GUF_DICT_NAME dict_str_i32
|
||||
// #define GUF_DICT_64_BIT_IDX
|
||||
// #define GUF_DICT_PROBE_LINEAR
|
||||
// #define GUF_DICT_32_BIT_HASH
|
||||
#include "guf_dict.h"
|
||||
|
||||
static inline guf_hash_size_t int32_hash(const int32_t *a)
|
||||
{
|
||||
return guf_hash(a, sizeof(int32_t), GUF_HASH_INIT); // TODO: byte order...
|
||||
}
|
||||
static inline bool int32_eq(const int32_t *a, const int32_t *b)
|
||||
{
|
||||
return *a == *b;
|
||||
}
|
||||
|
||||
#define GUF_DICT_KEY_T int32_t
|
||||
#define GUF_DICT_KEY_HASH int32_hash
|
||||
#define GUF_DICT_KEY_T_EQ int32_eq
|
||||
#define GUF_DICT_VAL_T bool
|
||||
#define GUF_DICT_VAL_T_IS_INTEGRAL_TYPE
|
||||
#define GUF_DICT_NAME dict_i32_bool
|
||||
#include "guf_dict.h"
|
||||
|
||||
#endif
|
||||
1
src/test/impls/init_impl.c
Executable file
1
src/test/impls/init_impl.c
Executable file
@ -0,0 +1 @@
|
||||
#include "guf_init.h"
|
||||
2
src/test/impls/linalg_impl.c
Executable file
2
src/test/impls/linalg_impl.c
Executable file
@ -0,0 +1,2 @@
|
||||
#define GUF_LINALG_IMPL
|
||||
#include "guf_linalg.h"
|
||||
2
src/test/impls/rand_impl.c
Executable file
2
src/test/impls/rand_impl.c
Executable file
@ -0,0 +1,2 @@
|
||||
#define GUF_RAND_IMPL
|
||||
#include "guf_rand.h"
|
||||
17
src/test/impls/sort_impl.c
Executable file
17
src/test/impls/sort_impl.c
Executable file
@ -0,0 +1,17 @@
|
||||
#include "sort_impl.h"
|
||||
|
||||
#define GUF_T float
|
||||
#define GUF_SORT_IMPL
|
||||
#include "guf_sort.h"
|
||||
|
||||
#define GUF_T int32_t
|
||||
#define GUF_SORT_IMPL
|
||||
#include "guf_sort.h"
|
||||
|
||||
#define GUF_T int8_t
|
||||
#define GUF_SORT_IMPL
|
||||
#include "guf_sort.h"
|
||||
|
||||
#define GUF_T guf_cstr_heap
|
||||
#define GUF_SORT_IMPL
|
||||
#include "guf_sort.h"
|
||||
18
src/test/impls/sort_impl.h
Executable file
18
src/test/impls/sort_impl.h
Executable file
@ -0,0 +1,18 @@
|
||||
#ifndef GUF_SORT_IMPL_H
|
||||
#define GUF_SORT_IMPL_H
|
||||
|
||||
#include "guf_cstr.h"
|
||||
|
||||
#define GUF_T float
|
||||
#include "guf_sort.h"
|
||||
|
||||
#define GUF_T int32_t
|
||||
#include "guf_sort.h"
|
||||
|
||||
#define GUF_T int8_t
|
||||
#include "guf_sort.h"
|
||||
|
||||
#define GUF_T guf_cstr_heap
|
||||
#include "guf_sort.h"
|
||||
|
||||
#endif
|
||||
2
src/test/impls/str_impl.c
Executable file
2
src/test/impls/str_impl.c
Executable file
@ -0,0 +1,2 @@
|
||||
#define GUF_STR_IMPL
|
||||
#include "guf_str.h"
|
||||
64
src/test/test.cpp
Executable file
64
src/test/test.cpp
Executable file
@ -0,0 +1,64 @@
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <cstdio>
|
||||
#include <iostream>
|
||||
|
||||
#include "test_dbuf.hpp"
|
||||
#include "test_dict.hpp"
|
||||
#include "test_utf8.hpp"
|
||||
#include "test_str.hpp"
|
||||
#include "test_ckdint.hpp"
|
||||
|
||||
extern "C"
|
||||
{
|
||||
#include "guf_assert.h"
|
||||
#include "guf_math.h"
|
||||
}
|
||||
|
||||
static std::vector<std::unique_ptr<Test>> g_tests {};
|
||||
|
||||
static void init_tests()
|
||||
{
|
||||
g_tests.push_back(std::make_unique<DbufIntTest>("DbufIntTest"));
|
||||
g_tests.push_back(std::make_unique<DbufCstringTest>("DbufCstringTest"));
|
||||
g_tests.push_back(std::make_unique<DbufStrTest>("DbufStrTest"));
|
||||
|
||||
g_tests.push_back(std::make_unique<DictSvToIntTest>("DictSvToIntTest"));
|
||||
g_tests.push_back(std::make_unique<UTF8Test>("UTF8Test"));
|
||||
g_tests.push_back(std::make_unique<StrTest>("StrTest"));
|
||||
g_tests.push_back(std::make_unique<CkdIntTest>("CkdIntTest"));
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
init_tests();
|
||||
|
||||
std::cout << "Running " << g_tests.size() << " tests...\n";
|
||||
|
||||
// std::cout << "max cap 1:" << dict_sv_i32_max_capacity() << "\n";
|
||||
// std::cout << "max cap 2:" << dict_cstr_int_max_capacity() << "\n";
|
||||
|
||||
size_t num_passed = 0;
|
||||
for (auto &test : g_tests) {
|
||||
Test *tst = test.get();
|
||||
GUF_ASSERT_RELEASE(tst);
|
||||
tst->before_run();
|
||||
tst->run();
|
||||
tst->after_run();
|
||||
std::cout << "- " << *tst << "\n";
|
||||
if (tst->passed) {
|
||||
++num_passed;
|
||||
}
|
||||
}
|
||||
|
||||
const bool passed_all = (num_passed == g_tests.size());
|
||||
GUF_ASSERT_RELEASE(num_passed <= g_tests.size());
|
||||
|
||||
if (passed_all) {
|
||||
std::cout << "-> SUCCESS: Passed all (" << num_passed << "/" << g_tests.size() << ") tests.\n";
|
||||
} else {
|
||||
std::cout << "-> FAILURE: Failed " << (g_tests.size() - num_passed) << "/" << g_tests.size() << " tests.\n";
|
||||
}
|
||||
|
||||
return passed_all ? EXIT_SUCCESS : EXIT_FAILURE;
|
||||
}
|
||||
108
src/test/test.hpp
Executable file
108
src/test/test.hpp
Executable file
@ -0,0 +1,108 @@
|
||||
#ifndef GUF_TEST_HPP
|
||||
#define GUF_TEST_HPP
|
||||
|
||||
#include <stack>
|
||||
#include <string>
|
||||
#include <cstdio>
|
||||
#include <iostream>
|
||||
#include <chrono>
|
||||
#include <iomanip>
|
||||
extern "C" {
|
||||
#include "guf_common.h"
|
||||
#include "guf_assert.h"
|
||||
}
|
||||
|
||||
#define TEST_CHECK(COND) (check((COND), GUF_STRINGIFY(COND), __LINE__, __FILE__))
|
||||
|
||||
struct Test
|
||||
{
|
||||
private:
|
||||
std::chrono::steady_clock::time_point time_start, time_end;
|
||||
|
||||
protected:
|
||||
std::stack<std::string> check_name_stack;
|
||||
std::string full_check_name = "";
|
||||
|
||||
void push_check_name(const std::string& check_name)
|
||||
{
|
||||
check_name_stack.push(check_name);
|
||||
full_check_name = full_check_name + "::" + check_name;
|
||||
}
|
||||
|
||||
void pop_check_name()
|
||||
{
|
||||
const size_t sep_idx = full_check_name.rfind("::");
|
||||
GUF_ASSERT_RELEASE(sep_idx != std::string::npos);
|
||||
full_check_name = full_check_name.substr(0, sep_idx);
|
||||
check_name_stack.pop();
|
||||
}
|
||||
|
||||
bool check(bool cond, std::string_view msg, size_t line, std::string_view fname)
|
||||
{
|
||||
if (!cond) {
|
||||
std::cerr << name << full_check_name << ": ";
|
||||
std::cerr << "FAILED CHECK (" << msg << ") on line " << line << " in file " << fname << "\n"; \
|
||||
++num_failed_checks;
|
||||
} else {
|
||||
++num_passed_checks;
|
||||
}
|
||||
return cond;
|
||||
}
|
||||
|
||||
public:
|
||||
const std::string name {};
|
||||
std::chrono::duration<float, std::milli> runtime_ms {0};
|
||||
bool passed {false}, done {false};
|
||||
size_t num_failed_checks {0}, num_passed_checks {0};
|
||||
|
||||
Test(const std::string& nm) : name{nm} {}
|
||||
virtual ~Test() = default;
|
||||
|
||||
size_t total_checks() const
|
||||
{
|
||||
return num_passed_checks + num_failed_checks;
|
||||
}
|
||||
|
||||
virtual void run() = 0;
|
||||
|
||||
void before_run()
|
||||
{
|
||||
time_start = std::chrono::steady_clock::now();
|
||||
}
|
||||
|
||||
void after_run()
|
||||
{
|
||||
done = true;
|
||||
passed = (num_failed_checks == 0);
|
||||
|
||||
time_end = std::chrono::steady_clock::now();
|
||||
runtime_ms = std::chrono::duration_cast<decltype(runtime_ms)>(time_end - time_start);
|
||||
}
|
||||
|
||||
friend std::ostream& operator<<(std::ostream &os, const Test& tst)
|
||||
{
|
||||
std::ios_base::fmtflags os_flags = os.flags();
|
||||
std::streamsize os_precision = os.precision();
|
||||
|
||||
os << tst.name << ": " << (tst.passed ? "PASS" : "FAIL");
|
||||
os << std::fixed << std::setprecision(2);
|
||||
os << " (" << tst.num_passed_checks << "/" << tst.total_checks() << ") in " << tst.runtime_ms.count() << " ms";
|
||||
|
||||
os.precision(os_precision);
|
||||
os.flags(os_flags);
|
||||
return os;
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct std::hash<std::unique_ptr<Test>> {
|
||||
std::size_t operator()(const std::unique_ptr<Test>& test) const
|
||||
{
|
||||
if (test.get() == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
return std::hash<std::string>()(test.get()->name);
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
361
src/test/test_ckdint.cpp
Executable file
361
src/test/test_ckdint.cpp
Executable file
@ -0,0 +1,361 @@
|
||||
#include "test_ckdint.hpp"
|
||||
|
||||
extern "C"
|
||||
{
|
||||
#include "guf_math_ckdint.h"
|
||||
}
|
||||
|
||||
/*
|
||||
CkdIntTest:
|
||||
*/
|
||||
|
||||
void CkdIntTest::run()
|
||||
{
|
||||
push_check_name("test_ckd");
|
||||
test_ckd();
|
||||
pop_check_name();
|
||||
|
||||
push_check_name("test_ckd_uint");
|
||||
test_ckd_uint();
|
||||
pop_check_name();
|
||||
}
|
||||
|
||||
void CkdIntTest::test_ckd()
|
||||
{
|
||||
for (int32_t a = INT8_MIN; a <= INT8_MAX; ++a) {
|
||||
for (int32_t b = INT8_MIN; b <= INT8_MAX; ++b) {
|
||||
const int32_t add_res = a + b;
|
||||
const guf_math_ckd_result ckd_add = guf_ckd_add_i8((int8_t)a, (int8_t)b);
|
||||
TEST_CHECK(ckd_add == guf_ckd_add_i8((int8_t)b, (int8_t)a));
|
||||
TEST_CHECK(ckd_add == guf_ckd_add_least_i8((int_least8_t)a, (int_least8_t)b));
|
||||
if (add_res > INT8_MAX) {
|
||||
TEST_CHECK(ckd_add == GUF_MATH_CKD_OVERFLOW_POS);
|
||||
int8_t saturated, saturated2;
|
||||
TEST_CHECK(guf_saturating_add_i8(static_cast<int8_t>(a), static_cast<int8_t>(b), &saturated) == GUF_MATH_CKD_OVERFLOW_POS);
|
||||
TEST_CHECK(saturated == INT8_MAX);
|
||||
TEST_CHECK(guf_saturating_add_i8(static_cast<int8_t>(b), static_cast<int8_t>(a), &saturated2) == GUF_MATH_CKD_OVERFLOW_POS);
|
||||
TEST_CHECK(saturated == saturated2);
|
||||
|
||||
int8_t wrapped, wrapped2;
|
||||
TEST_CHECK(guf_wrapping_add_i8(static_cast<int8_t>(a), static_cast<int8_t>(b), &wrapped) == GUF_MATH_CKD_OVERFLOW_POS);
|
||||
TEST_CHECK(static_cast<int32_t>(wrapped) == INT8_MIN + (add_res % (INT8_MAX + 1)));
|
||||
TEST_CHECK(guf_wrapping_add_i8(static_cast<int8_t>(b), static_cast<int8_t>(a), &wrapped2) == GUF_MATH_CKD_OVERFLOW_POS);
|
||||
TEST_CHECK(wrapped == wrapped2);
|
||||
}
|
||||
else if (add_res < INT8_MIN) {
|
||||
TEST_CHECK(ckd_add == GUF_MATH_CKD_OVERFLOW_NEG);
|
||||
int8_t saturated, saturated2;
|
||||
TEST_CHECK(guf_saturating_add_i8(static_cast<int8_t>(a), static_cast<int8_t>(b), &saturated) == GUF_MATH_CKD_OVERFLOW_NEG);
|
||||
TEST_CHECK(saturated == INT8_MIN);
|
||||
TEST_CHECK(guf_saturating_add_i8(static_cast<int8_t>(b), static_cast<int8_t>(a), &saturated2) == GUF_MATH_CKD_OVERFLOW_NEG);
|
||||
TEST_CHECK(saturated == saturated2);
|
||||
|
||||
int8_t wrapped, wrapped2;
|
||||
TEST_CHECK(guf_wrapping_add_i8(static_cast<int8_t>(a), static_cast<int8_t>(b), &wrapped) == GUF_MATH_CKD_OVERFLOW_NEG);
|
||||
TEST_CHECK(static_cast<int32_t>(wrapped) == INT8_MAX - (-add_res % (-INT8_MIN + 1)));
|
||||
TEST_CHECK(guf_wrapping_add_i8(static_cast<int8_t>(b), static_cast<int8_t>(a), &wrapped2) == GUF_MATH_CKD_OVERFLOW_NEG);
|
||||
TEST_CHECK(wrapped == wrapped2);
|
||||
}
|
||||
else {
|
||||
TEST_CHECK(ckd_add == GUF_MATH_CKD_SUCCESS);
|
||||
int8_t saturated, saturated2;
|
||||
TEST_CHECK(guf_saturating_add_i8(static_cast<int8_t>(a), static_cast<int8_t>(b), &saturated) == GUF_MATH_CKD_SUCCESS);
|
||||
TEST_CHECK(static_cast<int32_t>(saturated) == add_res);
|
||||
TEST_CHECK(guf_saturating_add_i8(static_cast<int8_t>(b), static_cast<int8_t>(a), &saturated2) == GUF_MATH_CKD_SUCCESS);
|
||||
TEST_CHECK(saturated == saturated2);
|
||||
|
||||
int8_t wrapped, wrapped2;
|
||||
TEST_CHECK(guf_wrapping_add_i8(static_cast<int8_t>(a), static_cast<int8_t>(b), &wrapped) == GUF_MATH_CKD_SUCCESS);
|
||||
TEST_CHECK(static_cast<int32_t>(wrapped) == add_res);
|
||||
TEST_CHECK(guf_wrapping_add_i8(static_cast<int8_t>(b), static_cast<int8_t>(a), &wrapped2) == GUF_MATH_CKD_SUCCESS);
|
||||
TEST_CHECK(wrapped == wrapped2);
|
||||
}
|
||||
|
||||
const int32_t sub_res = a - b;
|
||||
const guf_math_ckd_result ckd_sub = guf_ckd_sub_i8((int8_t)a, (int8_t)b);
|
||||
TEST_CHECK(ckd_sub == guf_ckd_sub_least_i8((int_least8_t)a, (int_least8_t)b));
|
||||
if (sub_res > INT8_MAX) {
|
||||
TEST_CHECK(ckd_sub == GUF_MATH_CKD_OVERFLOW_POS);
|
||||
int8_t saturated;
|
||||
TEST_CHECK(guf_saturating_sub_i8(static_cast<int8_t>(a), static_cast<int8_t>(b), &saturated) == GUF_MATH_CKD_OVERFLOW_POS);
|
||||
TEST_CHECK(saturated == INT8_MAX);
|
||||
int8_t wrapped;
|
||||
TEST_CHECK(guf_wrapping_sub_i8(static_cast<int8_t>(a), static_cast<int8_t>(b), &wrapped) == GUF_MATH_CKD_OVERFLOW_POS);
|
||||
TEST_CHECK(static_cast<int32_t>(wrapped) == INT8_MIN + (sub_res % (INT8_MAX + 1)));
|
||||
} else if (sub_res < INT8_MIN) {
|
||||
TEST_CHECK(ckd_sub == GUF_MATH_CKD_OVERFLOW_NEG);
|
||||
int8_t saturated;
|
||||
TEST_CHECK(guf_saturating_sub_i8(static_cast<int8_t>(a), static_cast<int8_t>(b), &saturated) == GUF_MATH_CKD_OVERFLOW_NEG);
|
||||
TEST_CHECK(saturated == INT8_MIN);
|
||||
int8_t wrapped;
|
||||
TEST_CHECK(guf_wrapping_sub_i8(static_cast<int8_t>(a), static_cast<int8_t>(b), &wrapped) == GUF_MATH_CKD_OVERFLOW_NEG);
|
||||
TEST_CHECK(static_cast<int32_t>(wrapped) == INT8_MAX - (-sub_res % (-INT8_MIN + 1)));
|
||||
} else {
|
||||
TEST_CHECK(ckd_sub == GUF_MATH_CKD_SUCCESS);
|
||||
int8_t saturated;
|
||||
TEST_CHECK(guf_saturating_sub_i8(static_cast<int8_t>(a), static_cast<int8_t>(b), &saturated) == GUF_MATH_CKD_SUCCESS);
|
||||
TEST_CHECK(static_cast<int32_t>(saturated) == sub_res);
|
||||
int8_t wrapped;
|
||||
TEST_CHECK(guf_wrapping_sub_i8(static_cast<int8_t>(a), static_cast<int8_t>(b), &wrapped) == GUF_MATH_CKD_SUCCESS);
|
||||
TEST_CHECK(static_cast<int32_t>(wrapped) == sub_res);
|
||||
}
|
||||
|
||||
const int32_t mul_res = a * b;
|
||||
const guf_math_ckd_result ckd_mul = guf_ckd_mul_i8((int8_t)a, (int8_t)b);
|
||||
TEST_CHECK(ckd_mul == guf_ckd_mul_least_i8((int_least8_t)a, (int_least8_t)b));
|
||||
TEST_CHECK(ckd_mul == guf_ckd_mul_i8((int8_t)b, (int8_t)a));
|
||||
if (mul_res > INT8_MAX) {
|
||||
TEST_CHECK(ckd_mul == GUF_MATH_CKD_OVERFLOW_POS);
|
||||
int8_t saturated, saturated2;
|
||||
TEST_CHECK(guf_saturating_mul_i8(static_cast<int8_t>(a), static_cast<int8_t>(b), &saturated) == GUF_MATH_CKD_OVERFLOW_POS);
|
||||
TEST_CHECK(saturated == INT8_MAX);
|
||||
|
||||
TEST_CHECK(guf_saturating_mul_i8(static_cast<int8_t>(b), static_cast<int8_t>(a), &saturated2) == GUF_MATH_CKD_OVERFLOW_POS);
|
||||
TEST_CHECK(saturated == saturated2);
|
||||
|
||||
int8_t wrapped, wrapped2;
|
||||
TEST_CHECK(guf_wrapping_mul_i8(static_cast<int8_t>(a), static_cast<int8_t>(b), &wrapped) == GUF_MATH_CKD_OVERFLOW_POS);
|
||||
TEST_CHECK(guf_wrapping_mul_i8(static_cast<int8_t>(b), static_cast<int8_t>(a), &wrapped2) == GUF_MATH_CKD_OVERFLOW_POS);
|
||||
TEST_CHECK(wrapped == wrapped2);
|
||||
// TODO: check wrapped
|
||||
} else if (mul_res < INT8_MIN) {
|
||||
TEST_CHECK(ckd_mul == GUF_MATH_CKD_OVERFLOW_NEG);
|
||||
int8_t saturated, saturated2;
|
||||
TEST_CHECK(guf_saturating_mul_i8(static_cast<int8_t>(a), static_cast<int8_t>(b), &saturated) == GUF_MATH_CKD_OVERFLOW_NEG);
|
||||
TEST_CHECK(saturated == INT8_MIN);
|
||||
|
||||
TEST_CHECK(guf_saturating_mul_i8(static_cast<int8_t>(b), static_cast<int8_t>(a), &saturated2) == GUF_MATH_CKD_OVERFLOW_NEG);
|
||||
TEST_CHECK(saturated == saturated2);
|
||||
|
||||
int8_t wrapped, wrapped2;
|
||||
TEST_CHECK(guf_wrapping_mul_i8(static_cast<int8_t>(a), static_cast<int8_t>(b), &wrapped) == GUF_MATH_CKD_OVERFLOW_NEG);
|
||||
TEST_CHECK(guf_wrapping_mul_i8(static_cast<int8_t>(b), static_cast<int8_t>(a), &wrapped2) == GUF_MATH_CKD_OVERFLOW_NEG);
|
||||
TEST_CHECK(wrapped == wrapped2);
|
||||
// TODO: check wrapped
|
||||
} else {
|
||||
TEST_CHECK(ckd_mul == GUF_MATH_CKD_SUCCESS);
|
||||
int8_t saturated, saturated2;
|
||||
TEST_CHECK(guf_saturating_mul_i8(static_cast<int8_t>(a), static_cast<int8_t>(b), &saturated) == GUF_MATH_CKD_SUCCESS);
|
||||
TEST_CHECK(static_cast<int32_t>(saturated) == mul_res);
|
||||
|
||||
TEST_CHECK(guf_saturating_mul_i8(static_cast<int8_t>(b), static_cast<int8_t>(a), &saturated2) == GUF_MATH_CKD_SUCCESS);
|
||||
TEST_CHECK(saturated == saturated2);
|
||||
|
||||
int8_t wrapped, wrapped2;
|
||||
TEST_CHECK(guf_wrapping_mul_i8(static_cast<int8_t>(a), static_cast<int8_t>(b), &wrapped) == GUF_MATH_CKD_SUCCESS);
|
||||
TEST_CHECK(static_cast<int32_t>(wrapped) == mul_res);
|
||||
TEST_CHECK(guf_wrapping_mul_i8(static_cast<int8_t>(b), static_cast<int8_t>(a), &wrapped2) == GUF_MATH_CKD_SUCCESS);
|
||||
TEST_CHECK(wrapped == wrapped2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int8_t mul_i8_res = -1;
|
||||
TEST_CHECK(guf_wrapping_mul_i8(42, 5, &mul_i8_res) == GUF_MATH_CKD_OVERFLOW_POS);
|
||||
TEST_CHECK(mul_i8_res == -46);
|
||||
mul_i8_res = -1;
|
||||
TEST_CHECK(guf_wrapping_mul_i8(5, 42, &mul_i8_res) == GUF_MATH_CKD_OVERFLOW_POS);
|
||||
TEST_CHECK(mul_i8_res == -46);
|
||||
|
||||
int16_t mul_i16_res = -1245;
|
||||
TEST_CHECK(guf_wrapping_mul_i16(32767, 2, &mul_i16_res) == GUF_MATH_CKD_OVERFLOW_POS);
|
||||
TEST_CHECK(mul_i16_res == -2);
|
||||
mul_i16_res = -1245;
|
||||
TEST_CHECK(guf_wrapping_mul_i16(-32767, 2, &mul_i16_res) == GUF_MATH_CKD_OVERFLOW_NEG);
|
||||
TEST_CHECK(mul_i16_res == 2);
|
||||
/*
|
||||
// https://play.rust-lang.org/?version=stable&mode=debug&edition=2024
|
||||
|
||||
use std::num::Wrapping;
|
||||
|
||||
fn main() {
|
||||
let a = Wrapping(-314159265_i32);
|
||||
let b = Wrapping(4096_i32);
|
||||
println!("{}", a * b);
|
||||
}
|
||||
*/
|
||||
|
||||
int32_t mul_i32_res = -12345;
|
||||
TEST_CHECK(guf_wrapping_mul_i32(INT32_MAX, 2, &mul_i32_res) == GUF_MATH_CKD_OVERFLOW_POS);
|
||||
TEST_CHECK(mul_i32_res == -2);
|
||||
mul_i32_res = -12345;
|
||||
TEST_CHECK(guf_wrapping_mul_i32(2, INT32_MAX, &mul_i32_res) == GUF_MATH_CKD_OVERFLOW_POS);
|
||||
TEST_CHECK(mul_i32_res == -2);
|
||||
|
||||
mul_i32_res = -12345;
|
||||
TEST_CHECK(guf_wrapping_mul_i32(INT32_MAX, -2, &mul_i32_res) == GUF_MATH_CKD_OVERFLOW_NEG);
|
||||
TEST_CHECK(mul_i32_res == 2);
|
||||
mul_i32_res = -12345;
|
||||
TEST_CHECK(guf_wrapping_mul_i32(-2, INT32_MAX, &mul_i32_res) == GUF_MATH_CKD_OVERFLOW_NEG);
|
||||
TEST_CHECK(mul_i32_res == 2);
|
||||
|
||||
TEST_CHECK(guf_wrapping_mul_i32(42002718, 314159265, &mul_i32_res) == GUF_MATH_CKD_OVERFLOW_POS);
|
||||
TEST_CHECK(mul_i32_res == -972735522);
|
||||
mul_i32_res = -12345;
|
||||
TEST_CHECK(guf_wrapping_mul_i32(314159265, 42002718, &mul_i32_res) == GUF_MATH_CKD_OVERFLOW_POS);
|
||||
TEST_CHECK(mul_i32_res == -972735522);
|
||||
|
||||
mul_i32_res = 12345;
|
||||
guf_wrapping_mul_i32(42002718, 314159265, &mul_i32_res);
|
||||
TEST_CHECK(mul_i32_res == -972735522);
|
||||
|
||||
mul_i32_res = 12345;
|
||||
guf_wrapping_mul_i32(-42002718, 314159265, &mul_i32_res);
|
||||
TEST_CHECK(mul_i32_res == 972735522);
|
||||
|
||||
mul_i32_res = 12345;
|
||||
guf_wrapping_mul_i32(-88888888, 99999999, &mul_i32_res);
|
||||
TEST_CHECK(mul_i32_res == 1374494264);
|
||||
|
||||
mul_i32_res = 12345;
|
||||
guf_wrapping_mul_i32(INT32_MIN, -1, &mul_i32_res);
|
||||
TEST_CHECK(mul_i32_res == INT32_MIN);
|
||||
|
||||
mul_i32_res = 12345;
|
||||
guf_wrapping_mul_i32(-2147483648, 2147483640, &mul_i32_res);
|
||||
TEST_CHECK(mul_i32_res == 0);
|
||||
|
||||
mul_i32_res = 12345;
|
||||
guf_wrapping_mul_i32(-2048, -314159265, &mul_i32_res);
|
||||
TEST_CHECK(mul_i32_res == -846919680);
|
||||
|
||||
mul_i32_res = 12345;
|
||||
guf_wrapping_mul_i32(4096, -314159265, &mul_i32_res);
|
||||
TEST_CHECK(mul_i32_res == 1693839360);
|
||||
|
||||
|
||||
int_least32_t mul_i32least_res = -12345;
|
||||
TEST_CHECK(guf_wrapping_mul_least_i32(INT32_MAX, 2, &mul_i32least_res) == GUF_MATH_CKD_OVERFLOW_POS);
|
||||
TEST_CHECK(mul_i32least_res == -2);
|
||||
mul_i32least_res = -12345;
|
||||
TEST_CHECK(guf_wrapping_mul_least_i32(2, INT32_MAX, &mul_i32least_res) == GUF_MATH_CKD_OVERFLOW_POS);
|
||||
TEST_CHECK(mul_i32least_res == -2);
|
||||
|
||||
mul_i32least_res = -12345;
|
||||
TEST_CHECK(guf_wrapping_mul_least_i32(INT32_MAX, -2, &mul_i32least_res) == GUF_MATH_CKD_OVERFLOW_NEG);
|
||||
TEST_CHECK(mul_i32least_res == 2);
|
||||
mul_i32least_res = -12345;
|
||||
TEST_CHECK(guf_wrapping_mul_least_i32(-2, INT32_MAX, &mul_i32least_res) == GUF_MATH_CKD_OVERFLOW_NEG);
|
||||
TEST_CHECK(mul_i32least_res == 2);
|
||||
|
||||
TEST_CHECK(guf_wrapping_mul_least_i32(42002718, 314159265, &mul_i32least_res) == GUF_MATH_CKD_OVERFLOW_POS);
|
||||
TEST_CHECK(mul_i32least_res == -972735522);
|
||||
mul_i32least_res = -12345;
|
||||
TEST_CHECK(guf_wrapping_mul_least_i32(314159265, 42002718, &mul_i32least_res) == GUF_MATH_CKD_OVERFLOW_POS);
|
||||
TEST_CHECK(mul_i32least_res == -972735522);
|
||||
|
||||
mul_i32least_res = 12345;
|
||||
guf_wrapping_mul_least_i32(42002718, 314159265, &mul_i32least_res);
|
||||
TEST_CHECK(mul_i32least_res == -972735522);
|
||||
|
||||
mul_i32least_res = 12345;
|
||||
guf_wrapping_mul_least_i32(-42002718, 314159265, &mul_i32least_res);
|
||||
TEST_CHECK(mul_i32least_res == 972735522);
|
||||
|
||||
mul_i32least_res = 12345;
|
||||
guf_wrapping_mul_least_i32(-88888888, 99999999, &mul_i32least_res);
|
||||
TEST_CHECK(mul_i32least_res == 1374494264);
|
||||
|
||||
mul_i32least_res = 12345;
|
||||
guf_wrapping_mul_least_i32(INT32_MIN, -1, &mul_i32least_res);
|
||||
TEST_CHECK(mul_i32least_res == INT32_MIN);
|
||||
|
||||
mul_i32least_res = 12345;
|
||||
guf_wrapping_mul_least_i32(-2147483648, 2147483640, &mul_i32least_res);
|
||||
TEST_CHECK(mul_i32least_res == 0);
|
||||
|
||||
mul_i32least_res = 12345;
|
||||
guf_wrapping_mul_least_i32(-2048, -314159265, &mul_i32least_res);
|
||||
TEST_CHECK(mul_i32least_res == -846919680);
|
||||
|
||||
mul_i32least_res = 12345;
|
||||
guf_wrapping_mul_least_i32(4096, -314159265, &mul_i32least_res);
|
||||
TEST_CHECK(mul_i32least_res == 1693839360);
|
||||
|
||||
|
||||
|
||||
ptrdiff_t ptrdiff_res = -1234;
|
||||
TEST_CHECK(guf_saturating_add_ptrdiff_t(PTRDIFF_MAX, 1, &ptrdiff_res) == GUF_MATH_CKD_OVERFLOW_POS);
|
||||
TEST_CHECK(ptrdiff_res == PTRDIFF_MAX);
|
||||
ptrdiff_res = -1234;
|
||||
TEST_CHECK(guf_saturating_add_ptrdiff_t(PTRDIFF_MIN, -1, &ptrdiff_res) == GUF_MATH_CKD_OVERFLOW_NEG);
|
||||
TEST_CHECK(ptrdiff_res == PTRDIFF_MIN);
|
||||
|
||||
ptrdiff_res = -1234;
|
||||
TEST_CHECK(guf_saturating_mul_ptrdiff_t(PTRDIFF_MAX, 2, &ptrdiff_res) == GUF_MATH_CKD_OVERFLOW_POS);
|
||||
TEST_CHECK(ptrdiff_res == PTRDIFF_MAX);
|
||||
ptrdiff_res = -1234;
|
||||
TEST_CHECK(guf_saturating_mul_ptrdiff_t(PTRDIFF_MIN, 2, &ptrdiff_res) == GUF_MATH_CKD_OVERFLOW_NEG);
|
||||
TEST_CHECK(ptrdiff_res == PTRDIFF_MIN);
|
||||
}
|
||||
|
||||
void CkdIntTest::test_ckd_uint()
|
||||
{
|
||||
for (int32_t a = 0; a <= UINT8_MAX; ++a) {
|
||||
for (int32_t b = 0; b <= UINT8_MAX; ++b) {
|
||||
const int32_t add_res = a + b;
|
||||
const guf_math_ckd_result ckd_add = guf_ckd_add_u8((uint8_t)a, (uint8_t)b);
|
||||
GUF_ASSERT(ckd_add == guf_ckd_add_least_u8((uint_least8_t)a, (uint_least8_t)b));
|
||||
if (add_res > UINT8_MAX) {
|
||||
TEST_CHECK(ckd_add == GUF_MATH_CKD_OVERFLOW_POS);
|
||||
uint8_t saturated;
|
||||
TEST_CHECK(guf_saturating_add_u8(static_cast<uint8_t>(a), static_cast<uint8_t>(b), &saturated) == GUF_MATH_CKD_OVERFLOW_POS);
|
||||
TEST_CHECK(saturated == UINT8_MAX);
|
||||
uint8_t wrapped;
|
||||
TEST_CHECK(guf_wrapping_add_u8(static_cast<uint8_t>(a), static_cast<uint8_t>(b), &wrapped) == GUF_MATH_CKD_OVERFLOW_POS);
|
||||
TEST_CHECK(static_cast<int32_t>(wrapped) == 0 + (add_res % (UINT8_MAX + 1)));
|
||||
}
|
||||
else {
|
||||
TEST_CHECK(ckd_add == GUF_MATH_CKD_SUCCESS);
|
||||
uint8_t saturated;
|
||||
TEST_CHECK(guf_saturating_add_u8(static_cast<uint8_t>(a), static_cast<uint8_t>(b), &saturated) == GUF_MATH_CKD_SUCCESS);
|
||||
TEST_CHECK(static_cast<int32_t>(saturated) == add_res);
|
||||
uint8_t wrapped;
|
||||
TEST_CHECK(guf_wrapping_add_u8(static_cast<uint8_t>(a), static_cast<uint8_t>(b), &wrapped) == GUF_MATH_CKD_SUCCESS);
|
||||
TEST_CHECK(static_cast<int32_t>(wrapped) == add_res);
|
||||
}
|
||||
|
||||
const int32_t sub_res = a - b;
|
||||
const guf_math_ckd_result ckd_sub = guf_ckd_sub_u8((uint8_t)a, (uint8_t)b);
|
||||
GUF_ASSERT(ckd_sub == guf_ckd_sub_least_u8((uint_least8_t)a, (uint_least8_t)b));
|
||||
if (sub_res < 0) {
|
||||
TEST_CHECK(ckd_sub == GUF_MATH_CKD_OVERFLOW_NEG);
|
||||
uint8_t saturated;
|
||||
TEST_CHECK(guf_saturating_sub_u8(static_cast<uint8_t>(a), static_cast<uint8_t>(b), &saturated) == GUF_MATH_CKD_OVERFLOW_NEG);
|
||||
TEST_CHECK(saturated == 0);
|
||||
uint8_t wrapped;
|
||||
TEST_CHECK(guf_wrapping_sub_u8(static_cast<uint8_t>(a), static_cast<uint8_t>(b), &wrapped) == GUF_MATH_CKD_OVERFLOW_NEG);
|
||||
TEST_CHECK(wrapped == static_cast<uint8_t>(static_cast<uint8_t>(a) - static_cast<uint8_t>(b)));
|
||||
} else {
|
||||
TEST_CHECK(ckd_sub == GUF_MATH_CKD_SUCCESS);
|
||||
uint8_t saturated;
|
||||
TEST_CHECK(guf_saturating_sub_u8(static_cast<uint8_t>(a), static_cast<uint8_t>(b), &saturated) == GUF_MATH_CKD_SUCCESS);
|
||||
TEST_CHECK(static_cast<int32_t>(saturated) == sub_res);
|
||||
uint8_t wrapped;
|
||||
TEST_CHECK(guf_wrapping_sub_u8(static_cast<uint8_t>(a), static_cast<uint8_t>(b), &wrapped) == GUF_MATH_CKD_SUCCESS);
|
||||
TEST_CHECK(static_cast<int32_t>(wrapped) == sub_res);
|
||||
}
|
||||
|
||||
const int32_t mul_res = a * b;
|
||||
const guf_math_ckd_result ckd_mul = guf_ckd_mul_u8((uint8_t)a, (uint8_t)b);
|
||||
GUF_ASSERT(ckd_mul == guf_ckd_mul_least_u8((uint_least8_t)a, (uint_least8_t)b));
|
||||
if (mul_res > UINT8_MAX) {
|
||||
TEST_CHECK(ckd_mul == GUF_MATH_CKD_OVERFLOW_POS);
|
||||
uint8_t saturated;
|
||||
TEST_CHECK(guf_saturating_mul_u8(static_cast<uint8_t>(a), static_cast<uint8_t>(b), &saturated) == GUF_MATH_CKD_OVERFLOW_POS);
|
||||
TEST_CHECK(saturated == UINT8_MAX);
|
||||
uint8_t wrapped;
|
||||
TEST_CHECK(guf_wrapping_mul_u8(static_cast<uint8_t>(a), static_cast<uint8_t>(b), &wrapped) == GUF_MATH_CKD_OVERFLOW_POS);
|
||||
TEST_CHECK(wrapped == static_cast<uint8_t>(static_cast<uint8_t>(a) * static_cast<uint8_t>(b)));
|
||||
} else {
|
||||
TEST_CHECK(ckd_mul == GUF_MATH_CKD_SUCCESS);
|
||||
uint8_t saturated;
|
||||
TEST_CHECK(guf_saturating_mul_u8(static_cast<uint8_t>(a), static_cast<uint8_t>(b), &saturated) == GUF_MATH_CKD_SUCCESS);
|
||||
TEST_CHECK(static_cast<int32_t>(saturated) == mul_res);
|
||||
uint8_t wrapped;
|
||||
TEST_CHECK(guf_wrapping_mul_u8(static_cast<uint8_t>(a), static_cast<uint8_t>(b), &wrapped) == GUF_MATH_CKD_SUCCESS);
|
||||
TEST_CHECK(static_cast<int32_t>(wrapped) == mul_res);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
12
src/test/test_ckdint.hpp
Executable file
12
src/test/test_ckdint.hpp
Executable file
@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
#include "test.hpp"
|
||||
|
||||
struct CkdIntTest : public Test
|
||||
{
|
||||
CkdIntTest(const std::string& nm) : Test(nm) {};
|
||||
void run() override;
|
||||
|
||||
private:
|
||||
void test_ckd();
|
||||
void test_ckd_uint();
|
||||
};
|
||||
685
src/test/test_dbuf.cpp
Executable file
685
src/test/test_dbuf.cpp
Executable file
@ -0,0 +1,685 @@
|
||||
#include "test_dbuf.hpp"
|
||||
|
||||
extern "C"
|
||||
{
|
||||
#include "guf_alloc_libc.h"
|
||||
#include "impls/dbuf_impl.h"
|
||||
}
|
||||
|
||||
/*
|
||||
DbufIntTest
|
||||
*/
|
||||
|
||||
void DbufIntTest::run()
|
||||
{
|
||||
if (done) {
|
||||
return;
|
||||
}
|
||||
|
||||
// allocator_ctx.tracker.log = fopen("alloc_log.txt", "w");
|
||||
// allocator_ctx.tracker.err_log = fopen("alloc_err_log.txt", "w");
|
||||
|
||||
dbuf_int dbuf {};
|
||||
dbuf_int_init(&dbuf, 0, &allocator);
|
||||
|
||||
push_check_name("test_push");
|
||||
|
||||
test_push(&dbuf, 256);
|
||||
test_push(&dbuf, 128);
|
||||
test_push(&dbuf, 17);
|
||||
TEST_CHECK(dbuf.size == (256 + 128 + 17));
|
||||
|
||||
dbuf_int_free(&dbuf, NULL);
|
||||
TEST_CHECK(dbuf.size == 0 && dbuf.capacity == 0 && dbuf.data == NULL);
|
||||
|
||||
dbuf_int_init(&dbuf, 24, &allocator);
|
||||
TEST_CHECK(dbuf.size == 0 && dbuf.capacity == 24 && dbuf.data);
|
||||
|
||||
test_push(&dbuf, 365);
|
||||
test_push(&dbuf, 4);
|
||||
test_push(&dbuf, 25);
|
||||
test_push(&dbuf, 64);
|
||||
TEST_CHECK(dbuf.size == (365 + 4 + 25 + 64));
|
||||
|
||||
dbuf_int_free(&dbuf, NULL);
|
||||
TEST_CHECK(dbuf.size == 0 && dbuf.capacity == 0 && dbuf.data == NULL);
|
||||
|
||||
pop_check_name();
|
||||
|
||||
push_check_name("insert_remove");
|
||||
|
||||
for (int n = 0; n <= 128; ++n) {
|
||||
test_insert_remove(n);
|
||||
}
|
||||
test_insert_remove(400);
|
||||
test_insert_remove(401);
|
||||
test_insert_remove(512);
|
||||
test_insert_remove(513);
|
||||
test_insert_remove(601);
|
||||
test_insert_remove(2048);
|
||||
test_insert_remove(2049);
|
||||
|
||||
pop_check_name();
|
||||
|
||||
TEST_CHECK(!guf_alloc_tracker_found_leak(&allocator_ctx.tracker));
|
||||
// guf_alloc_tracker_print(&allocator_ctx.tracker, stdout);
|
||||
// puts("");
|
||||
// fclose(allocator_ctx.tracker.log);
|
||||
// fclose(allocator_ctx.tracker.err_log);
|
||||
}
|
||||
|
||||
std::vector<int> DbufIntTest::dbuf_to_vec(dbuf_int *dbuf)
|
||||
{
|
||||
std::vector<int> vec;
|
||||
GUF_CNT_FOREACH(dbuf, dbuf_int, it) {
|
||||
vec.push_back(*it.ptr);
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
void DbufIntTest::test_push(dbuf_int *dbuf, int n)
|
||||
{
|
||||
std::vector<int> vec = dbuf_to_vec(dbuf);
|
||||
|
||||
TEST_CHECK(std::ssize(vec) == dbuf->size);
|
||||
|
||||
for (int i = 0; i < n; ++i) {
|
||||
dbuf_int_push_val(dbuf, i);
|
||||
vec.push_back(i);
|
||||
TEST_CHECK(*dbuf_int_back(dbuf) == vec.back());
|
||||
}
|
||||
|
||||
ptrdiff_t i = 0;
|
||||
GUF_CNT_FOREACH(dbuf, dbuf_int, it) {
|
||||
TEST_CHECK(*it.ptr == vec.at(i++));
|
||||
}
|
||||
TEST_CHECK(i == dbuf->size);
|
||||
|
||||
i = dbuf->size - 1;
|
||||
GUF_CNT_FOREACH_REVERSE(dbuf, dbuf_int, rit) {
|
||||
TEST_CHECK(*rit.ptr == vec.at(i--));
|
||||
}
|
||||
TEST_CHECK(i == -1);
|
||||
|
||||
}
|
||||
|
||||
void DbufIntTest::test_insert_remove(int n)
|
||||
{
|
||||
dbuf_int dbuf = {};
|
||||
dbuf_int_init(&dbuf, 0, &allocator);
|
||||
std::vector<int> vec = dbuf_to_vec(&dbuf);
|
||||
|
||||
guf_err err = GUF_ERR_NONE;
|
||||
dbuf_int_try_erase(&dbuf, 0, &err);
|
||||
TEST_CHECK(err == GUF_ERR_IDX_RANGE);
|
||||
|
||||
err = GUF_ERR_NONE;
|
||||
dbuf_int_try_erase(&dbuf, 12, &err);
|
||||
TEST_CHECK(err == GUF_ERR_IDX_RANGE);
|
||||
|
||||
err = GUF_ERR_NONE;
|
||||
dbuf_int_try_front(&dbuf, &err);
|
||||
TEST_CHECK(err == GUF_ERR_IDX_RANGE);
|
||||
|
||||
err = GUF_ERR_NONE;
|
||||
dbuf_int_try_back(&dbuf, &err);
|
||||
TEST_CHECK(err == GUF_ERR_IDX_RANGE);
|
||||
|
||||
err = GUF_ERR_NONE;
|
||||
dbuf_int_try_at(&dbuf, 0, &err);
|
||||
TEST_CHECK(err == GUF_ERR_IDX_RANGE);
|
||||
|
||||
for (int i = 0; i < n; ++i) {
|
||||
dbuf_int_insert_val(&dbuf, i, i);
|
||||
dbuf_int_insert_val(&dbuf, i * 2, 0);
|
||||
dbuf_int_insert_val(&dbuf, i * 4, dbuf.size);
|
||||
|
||||
vec.insert(vec.begin() + i, i);
|
||||
vec.insert(vec.begin(), i * 2);
|
||||
vec.insert(vec.end(), i * 4);
|
||||
}
|
||||
TEST_CHECK(std::ssize(vec) == dbuf.size);
|
||||
|
||||
// Iterate
|
||||
dbuf_int_iter it_dbuf = dbuf_int_begin(&dbuf);
|
||||
std::vector<int>::const_iterator it_vec = vec.begin();
|
||||
while (!dbuf_int_iter_is_end(&dbuf, it_dbuf) && it_vec != vec.end()) {
|
||||
TEST_CHECK(*it_dbuf.ptr == *it_vec);
|
||||
it_dbuf = dbuf_int_iter_next(&dbuf, it_dbuf, 1);
|
||||
std::advance(it_vec, 1);
|
||||
}
|
||||
TEST_CHECK(dbuf_int_iter_is_end(&dbuf, it_dbuf) && it_vec == vec.end());
|
||||
|
||||
// Step iterate.
|
||||
it_dbuf = dbuf_int_begin(&dbuf);
|
||||
it_vec = vec.begin();
|
||||
while (!dbuf_int_iter_is_end(&dbuf, it_dbuf) && it_vec != vec.end()) {
|
||||
TEST_CHECK(*it_dbuf.ptr == *it_vec);
|
||||
it_dbuf = dbuf_int_iter_next(&dbuf, it_dbuf, 7);
|
||||
|
||||
if (dbuf_int_iter_is_end(&dbuf, it_dbuf)) {
|
||||
it_vec = vec.end();
|
||||
} else {
|
||||
std::advance(it_vec, 7);
|
||||
}
|
||||
}
|
||||
TEST_CHECK(dbuf_int_iter_is_end(&dbuf, it_dbuf) && it_vec == vec.end());
|
||||
|
||||
// Reverse iterate.
|
||||
dbuf_int_iter rit_dbuf = dbuf_int_rbegin(&dbuf);
|
||||
std::vector<int>::const_reverse_iterator rit_vec = vec.crbegin();
|
||||
while (!dbuf_int_iter_is_end(&dbuf, rit_dbuf) && rit_vec != vec.crend()) {
|
||||
TEST_CHECK(*rit_dbuf.ptr == *rit_vec);
|
||||
rit_dbuf = dbuf_int_iter_next(&dbuf, rit_dbuf, 1);
|
||||
std::advance(rit_vec, 1);
|
||||
}
|
||||
TEST_CHECK(dbuf_int_iter_is_end(&dbuf, rit_dbuf) && rit_vec == vec.rend());
|
||||
|
||||
// Reverse iterate step.
|
||||
rit_dbuf = dbuf_int_rbegin(&dbuf);
|
||||
rit_vec = vec.crbegin();
|
||||
while (!dbuf_int_iter_is_end(&dbuf, rit_dbuf) && rit_vec != vec.crend()) {
|
||||
TEST_CHECK(*rit_dbuf.ptr == *rit_vec);
|
||||
rit_dbuf = dbuf_int_iter_next(&dbuf, rit_dbuf, 4);
|
||||
if (dbuf_int_iter_is_end(&dbuf, rit_dbuf)) {
|
||||
rit_vec = vec.rend();
|
||||
} else {
|
||||
std::advance(rit_vec, 4);
|
||||
}
|
||||
}
|
||||
TEST_CHECK(dbuf_int_iter_is_end(&dbuf, rit_dbuf) && rit_vec == vec.rend());
|
||||
|
||||
TEST_CHECK(dbuf.size == std::ssize(vec));
|
||||
|
||||
for (ptrdiff_t i = 0; i < dbuf.size; i += 8) {
|
||||
dbuf_int_erase(&dbuf, i);
|
||||
dbuf_int_erase(&dbuf, 0);
|
||||
dbuf_int_pop(&dbuf);
|
||||
|
||||
vec.erase(vec.begin() + i);
|
||||
vec.erase(vec.begin() + 0);
|
||||
vec.pop_back();
|
||||
}
|
||||
|
||||
TEST_CHECK(dbuf.size == std::ssize(vec));
|
||||
|
||||
for (ptrdiff_t i = 0; i < dbuf.size; i += 8) {
|
||||
TEST_CHECK(*dbuf_int_at(&dbuf, i) == vec.at(i));
|
||||
}
|
||||
|
||||
const ptrdiff_t size = dbuf.size;
|
||||
for (ptrdiff_t i = 0; i < size; ++i) {
|
||||
int a = dbuf_int_pop_move(&dbuf);
|
||||
int b = vec.back();
|
||||
TEST_CHECK(a == b);
|
||||
vec.pop_back();
|
||||
}
|
||||
TEST_CHECK(dbuf.size == 0 && vec.size() == 0);
|
||||
|
||||
dbuf_int_free(&dbuf, NULL);
|
||||
TEST_CHECK(dbuf.size == 0 && dbuf.capacity == 0 && !dbuf.data);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
DbufCstringTest
|
||||
*/
|
||||
|
||||
void DbufCstringTest::run()
|
||||
{
|
||||
if (done) {
|
||||
return;
|
||||
}
|
||||
|
||||
// allocator_ctx.tracker.log = fopen("alloc_log.txt", "w");
|
||||
// allocator_ctx.tracker.err_log = fopen("alloc_err_log.txt", "w");
|
||||
|
||||
push_check_name("push_insert_erase");
|
||||
|
||||
for (int i = 1; i <= 32; ++i) {
|
||||
test_push_insert_erase(i);
|
||||
test_push_insert_erase(i, i - 1);
|
||||
test_push_insert_erase(i, i + 1);
|
||||
test_push_insert_erase(i, i);
|
||||
test_push_insert_erase(i, i / 2);
|
||||
}
|
||||
test_push_insert_erase(2048);
|
||||
test_push_insert_erase(2048, 11);
|
||||
|
||||
dbuf_heap_cstr str_dbuf = {};
|
||||
dbuf_heap_cstr_init(&str_dbuf, 0, &allocator);
|
||||
std::vector<std::string> str_vec {};
|
||||
|
||||
for (int i = 0; i < 512; ++i) {
|
||||
char buf[128];
|
||||
memset(buf, '\0', GUF_ARR_SIZE(buf));
|
||||
snprintf(buf, GUF_ARR_SIZE(buf), "This is a pretty guf string (number %d)", i);
|
||||
guf_cstr_heap str = buf;
|
||||
dbuf_heap_cstr_push(&str_dbuf, &str, GUF_CPY_DEEP);
|
||||
str_vec.push_back(std::string{buf});
|
||||
}
|
||||
for (int i = 0; i < str_dbuf.size + 16; ++i) {
|
||||
test_iter(str_vec, &str_dbuf, i);
|
||||
}
|
||||
|
||||
dbuf_heap_cstr_free(&str_dbuf, NULL);
|
||||
TEST_CHECK(str_dbuf.size == 0 && str_dbuf.capacity == 0 && !str_dbuf.data);
|
||||
|
||||
pop_check_name();
|
||||
|
||||
push_check_name("find");
|
||||
test_find();
|
||||
test_find(3);
|
||||
test_find(42);
|
||||
test_find(129);
|
||||
pop_check_name();
|
||||
|
||||
TEST_CHECK(!guf_alloc_tracker_found_leak(&allocator_ctx.tracker));
|
||||
// guf_alloc_tracker_print(&allocator_ctx.tracker, stdout);
|
||||
// puts("");
|
||||
// fclose(allocator_ctx.tracker.log);
|
||||
// fclose(allocator_ctx.tracker.err_log);
|
||||
}
|
||||
|
||||
void DbufCstringTest::test_iter(std::vector<std::string>& str_vec, dbuf_heap_cstr *str_dbuf, int step)
|
||||
{
|
||||
GUF_ASSERT_RELEASE(str_dbuf);
|
||||
if (step <= 0) {
|
||||
step = 1;
|
||||
}
|
||||
|
||||
ptrdiff_t i = 0;
|
||||
GUF_CNT_FOREACH(str_dbuf, dbuf_heap_cstr, it) {
|
||||
char *str = *it.ptr;
|
||||
TEST_CHECK(str_vec.at(i) == str);
|
||||
++i;
|
||||
}
|
||||
TEST_CHECK(i == str_dbuf->size);
|
||||
|
||||
i = str_dbuf->size - 1;
|
||||
GUF_CNT_FOREACH_REVERSE(str_dbuf, dbuf_heap_cstr, rit) {
|
||||
char *str = *rit.ptr;
|
||||
TEST_CHECK(str_vec.at(i) == str);
|
||||
--i;
|
||||
}
|
||||
TEST_CHECK(i == -1);
|
||||
|
||||
dbuf_heap_cstr_iter it_dbuf = dbuf_heap_cstr_begin(str_dbuf);
|
||||
std::vector<std::string>::iterator it_vec = str_vec.begin();
|
||||
while (!dbuf_heap_cstr_iter_is_end(str_dbuf, it_dbuf)) {
|
||||
TEST_CHECK(it_vec != str_vec.end());
|
||||
TEST_CHECK(*it_vec == *it_dbuf.ptr);
|
||||
it_dbuf = dbuf_heap_cstr_iter_next(str_dbuf, it_dbuf, step);
|
||||
if (!dbuf_heap_cstr_iter_is_end(str_dbuf, it_dbuf)) {
|
||||
std::advance(it_vec, step);
|
||||
} else {
|
||||
it_vec = str_vec.end();
|
||||
}
|
||||
}
|
||||
TEST_CHECK(dbuf_heap_cstr_iter_is_end(str_dbuf, it_dbuf) && it_vec == str_vec.end());
|
||||
|
||||
dbuf_heap_cstr_iter rit_dbuf = dbuf_heap_cstr_rbegin(str_dbuf);
|
||||
std::vector<std::string>::reverse_iterator rit_vec = str_vec.rbegin();
|
||||
while (!dbuf_heap_cstr_iter_is_end(str_dbuf, rit_dbuf)) {
|
||||
TEST_CHECK(rit_vec != str_vec.rend());
|
||||
TEST_CHECK(*rit_vec == *rit_dbuf.ptr);
|
||||
rit_dbuf = dbuf_heap_cstr_iter_next(str_dbuf, rit_dbuf, step);
|
||||
if (!dbuf_heap_cstr_iter_is_end(str_dbuf, rit_dbuf)) {
|
||||
std::advance(rit_vec, step);
|
||||
} else {
|
||||
rit_vec = str_vec.rend();
|
||||
}
|
||||
}
|
||||
TEST_CHECK(dbuf_heap_cstr_iter_is_end(str_dbuf, rit_dbuf) && rit_vec == str_vec.rend());
|
||||
|
||||
for (i = 0; i < str_dbuf->size; ++i) {
|
||||
char *str = *dbuf_heap_cstr_at(str_dbuf, i);
|
||||
TEST_CHECK(str_vec.at(i) == str);
|
||||
}
|
||||
}
|
||||
|
||||
void DbufCstringTest::test_push_insert_erase(int n, ptrdiff_t start_cap)
|
||||
{
|
||||
std::vector<std::string> str_vec;
|
||||
dbuf_heap_cstr str_dbuf {};
|
||||
dbuf_heap_cstr_init(&str_dbuf, start_cap, &allocator);
|
||||
|
||||
for (int i = 0; i < n; ++i) {
|
||||
constexpr int BUF_SZ = 128;
|
||||
char buf[BUF_SZ];
|
||||
memset(buf, '\0', BUF_SZ);
|
||||
snprintf(buf, BUF_SZ, "This is string number %d", i);
|
||||
guf_cstr_heap str = buf;
|
||||
|
||||
dbuf_heap_cstr_push(&str_dbuf, &str, GUF_CPY_DEEP);
|
||||
dbuf_heap_cstr_push_val_cpy(&str_dbuf, str);
|
||||
char *heap_buf = strdup("Move me plz");
|
||||
dbuf_heap_cstr_push(&str_dbuf, &heap_buf, GUF_CPY_MOVE);
|
||||
TEST_CHECK(heap_buf == NULL);
|
||||
|
||||
TEST_CHECK(strncmp(*dbuf_heap_cstr_back(&str_dbuf), "Move me plz", BUF_SZ) == 0);
|
||||
TEST_CHECK(strncmp(*dbuf_heap_cstr_at(&str_dbuf, str_dbuf.size - 2), buf, BUF_SZ) == 0);
|
||||
TEST_CHECK(strncmp(*dbuf_heap_cstr_at(&str_dbuf, str_dbuf.size - 3), buf, BUF_SZ) == 0);
|
||||
|
||||
str_vec.push_back(std::string{buf});
|
||||
str_vec.push_back(std::string{buf});
|
||||
str_vec.emplace_back("Move me plz");
|
||||
}
|
||||
|
||||
TEST_CHECK(str_dbuf.size == std::ssize(str_vec));
|
||||
TEST_CHECK(str_dbuf.size == 3 * n);
|
||||
|
||||
for (int i = 1; i <= 8; ++i) {
|
||||
test_iter(str_vec, &str_dbuf, i);
|
||||
}
|
||||
test_iter(str_vec, &str_dbuf, (int)str_dbuf.size);
|
||||
test_iter(str_vec, &str_dbuf, (int)str_dbuf.size - 1);
|
||||
test_iter(str_vec, &str_dbuf, (int)str_dbuf.size + 1);
|
||||
|
||||
for (ptrdiff_t i = 0; i < str_dbuf.size; ++i) {
|
||||
TEST_CHECK(str_vec.at(i) == *dbuf_heap_cstr_at(&str_dbuf, i));
|
||||
}
|
||||
|
||||
// Insert front.
|
||||
for (ptrdiff_t i = 0; i < 16; ++i) {
|
||||
char str[] = "front";
|
||||
dbuf_heap_cstr_insert_val_cpy(&str_dbuf, str, 0);
|
||||
str_vec.insert(str_vec.begin(), std::string{str});
|
||||
}
|
||||
TEST_CHECK(std::ssize(str_vec) == str_dbuf.size);
|
||||
for (ptrdiff_t i = 0; i < str_dbuf.size; ++i) {
|
||||
TEST_CHECK(str_vec.at(i) == *dbuf_heap_cstr_at(&str_dbuf, i));
|
||||
}
|
||||
// Insert back.
|
||||
for (ptrdiff_t i = 0; i < 16; ++i) {
|
||||
char str[] = "front";
|
||||
dbuf_heap_cstr_insert_val_cpy(&str_dbuf, str, str_dbuf.size);
|
||||
str_vec.insert(str_vec.end(), std::string{str});
|
||||
}
|
||||
TEST_CHECK(std::ssize(str_vec) == str_dbuf.size);
|
||||
for (ptrdiff_t i = 0; i < str_dbuf.size; ++i) {
|
||||
TEST_CHECK(str_vec.at(i) == *dbuf_heap_cstr_at(&str_dbuf, i));
|
||||
}
|
||||
|
||||
// Insert at i.
|
||||
char str[] = "guf";
|
||||
dbuf_heap_cstr_insert_val_cpy(&str_dbuf, str, str_dbuf.size / 2);
|
||||
str_vec.insert(str_vec.begin() + str_vec.size() / 2, str);
|
||||
dbuf_heap_cstr_insert_val_cpy(&str_dbuf, str, str_dbuf.size / 4);
|
||||
str_vec.insert(str_vec.begin() + str_vec.size() / 4, str);
|
||||
dbuf_heap_cstr_insert_val_cpy(&str_dbuf, str, 1);
|
||||
str_vec.insert(str_vec.begin() + 1, str);
|
||||
dbuf_heap_cstr_insert_val_cpy(&str_dbuf, str, str_dbuf.size - 1);
|
||||
str_vec.insert(str_vec.begin() + (str_vec.size() - 1), str);
|
||||
|
||||
for (ptrdiff_t i = 0; i < str_dbuf.size; ++i) {
|
||||
TEST_CHECK(str_vec.at(i) == *dbuf_heap_cstr_at(&str_dbuf, i));
|
||||
}
|
||||
|
||||
guf_err err = GUF_ERR_NONE;
|
||||
dbuf_heap_cstr_try_insert_val_cpy(&str_dbuf, str, str_dbuf.size + 1, &err);
|
||||
TEST_CHECK(err == GUF_ERR_IDX_RANGE);
|
||||
|
||||
err = GUF_ERR_NONE;
|
||||
dbuf_heap_cstr_try_insert_val_cpy(&str_dbuf, str, -1, &err);
|
||||
TEST_CHECK(err == GUF_ERR_IDX_RANGE);
|
||||
|
||||
err = GUF_ERR_NONE;
|
||||
dbuf_heap_cstr_try_insert_val_cpy(&str_dbuf, str, str_dbuf.size + 2, &err);
|
||||
TEST_CHECK(err == GUF_ERR_IDX_RANGE);
|
||||
|
||||
TEST_CHECK(std::ssize(str_vec) == str_dbuf.size);
|
||||
for (ptrdiff_t i = 0; i < str_dbuf.size; ++i) {
|
||||
TEST_CHECK(str_vec.at(i) == *dbuf_heap_cstr_at(&str_dbuf, i));
|
||||
}
|
||||
|
||||
if (str_dbuf.size) {
|
||||
dbuf_heap_cstr_erase(&str_dbuf, str_dbuf.size - 1);
|
||||
str_vec.erase(str_vec.end() - 1);
|
||||
}
|
||||
|
||||
ptrdiff_t to_rem = 8;
|
||||
while (str_dbuf.size && to_rem--) {
|
||||
dbuf_heap_cstr_erase(&str_dbuf, 0);
|
||||
str_vec.erase(str_vec.begin());
|
||||
TEST_CHECK(std::ssize(str_vec) == str_dbuf.size);
|
||||
if (str_dbuf.size) {
|
||||
dbuf_heap_cstr_pop(&str_dbuf);
|
||||
str_vec.pop_back();
|
||||
TEST_CHECK(std::ssize(str_vec) == str_dbuf.size);
|
||||
}
|
||||
if (str_dbuf.size) {
|
||||
dbuf_heap_cstr_erase(&str_dbuf, str_dbuf.size / 2);
|
||||
str_vec.erase(str_vec.begin() + (str_vec.size() / 2));
|
||||
TEST_CHECK(std::ssize(str_vec) == str_dbuf.size);
|
||||
}
|
||||
}
|
||||
|
||||
dbuf_heap_cstr_free(&str_dbuf, NULL);
|
||||
TEST_CHECK(str_dbuf.size == 0 && str_dbuf.capacity == 0 && !str_dbuf.data);
|
||||
}
|
||||
|
||||
void DbufCstringTest::test_find(int n)
|
||||
{
|
||||
if (n < 2) {
|
||||
n = 2;
|
||||
}
|
||||
std::vector<std::string> str_vec {};
|
||||
|
||||
dbuf_heap_cstr str_dbuf = {};
|
||||
dbuf_heap_cstr_init(&str_dbuf, 0, &allocator);
|
||||
|
||||
for (int i = 0; i < n; ++i) {
|
||||
constexpr int BUF_SZ = 128;
|
||||
char buf[BUF_SZ];
|
||||
memset(buf, '\0', BUF_SZ);
|
||||
snprintf(buf, BUF_SZ, "String number %d", i);
|
||||
|
||||
dbuf_heap_cstr_push_val_cpy(&str_dbuf, buf);
|
||||
str_vec.push_back(buf);
|
||||
}
|
||||
char *move_me = strdup("Moved string");
|
||||
dbuf_heap_cstr_push(&str_dbuf, &move_me, GUF_CPY_MOVE);
|
||||
GUF_ASSERT_RELEASE(move_me == NULL);
|
||||
str_vec.emplace_back("Moved string");
|
||||
|
||||
TEST_CHECK(std::ssize(str_vec) == str_dbuf.size);
|
||||
|
||||
for (ptrdiff_t i = 0; i < str_dbuf.size; ++i) {
|
||||
char *needle = *dbuf_heap_cstr_at(&str_dbuf, i);
|
||||
TEST_CHECK(str_vec.at(i) == needle);
|
||||
|
||||
TEST_CHECK(dbuf_heap_cstr_contains_val(&str_dbuf, needle));
|
||||
|
||||
dbuf_heap_cstr_iter fnd_it = dbuf_heap_cstr_find_val(&str_dbuf, dbuf_heap_cstr_begin(&str_dbuf), dbuf_heap_cstr_end(&str_dbuf), needle);
|
||||
TEST_CHECK(!dbuf_heap_cstr_iter_is_end(&str_dbuf, fnd_it));
|
||||
TEST_CHECK(std::find(str_vec.cbegin(), str_vec.cend(), needle) != str_vec.end());
|
||||
|
||||
dbuf_heap_cstr_iter begin = dbuf_heap_cstr_iter_next(&str_dbuf, dbuf_heap_cstr_begin(&str_dbuf), i);
|
||||
dbuf_heap_cstr_iter end = dbuf_heap_cstr_end(&str_dbuf);
|
||||
fnd_it = dbuf_heap_cstr_find_val(&str_dbuf, begin, end, needle);
|
||||
TEST_CHECK(!dbuf_heap_cstr_iter_is_end(&str_dbuf, fnd_it));
|
||||
TEST_CHECK(std::find(str_vec.cbegin() + i, str_vec.cend(), needle) != str_vec.end());
|
||||
|
||||
begin = dbuf_heap_cstr_iter_next(&str_dbuf, dbuf_heap_cstr_begin(&str_dbuf), i + 1);
|
||||
end = dbuf_heap_cstr_end(&str_dbuf);
|
||||
fnd_it = dbuf_heap_cstr_find_val(&str_dbuf, begin, end, needle);
|
||||
TEST_CHECK(dbuf_heap_cstr_iter_is_end(&str_dbuf, fnd_it));
|
||||
TEST_CHECK(std::find(str_vec.cbegin() + i + 1, str_vec.cend(), needle) == str_vec.end());
|
||||
|
||||
// Reverse.
|
||||
fnd_it = dbuf_heap_cstr_find_val(&str_dbuf, dbuf_heap_cstr_rbegin(&str_dbuf), dbuf_heap_cstr_rend(&str_dbuf), needle);
|
||||
TEST_CHECK(!dbuf_heap_cstr_iter_is_end(&str_dbuf, fnd_it));
|
||||
TEST_CHECK(std::find(str_vec.crbegin(), str_vec.crend(), needle) != str_vec.rend());
|
||||
}
|
||||
|
||||
char needle[] = "Definitely not inside";
|
||||
dbuf_heap_cstr_iter fnd_it = dbuf_heap_cstr_find_val(&str_dbuf, dbuf_heap_cstr_begin(&str_dbuf), dbuf_heap_cstr_end(&str_dbuf), needle);
|
||||
TEST_CHECK(dbuf_heap_cstr_iter_is_end(&str_dbuf, fnd_it));
|
||||
TEST_CHECK(std::find(str_vec.cbegin(), str_vec.cend(), needle) == str_vec.end());
|
||||
|
||||
fnd_it = dbuf_heap_cstr_find_val(&str_dbuf, dbuf_heap_cstr_rbegin(&str_dbuf), dbuf_heap_cstr_rend(&str_dbuf), needle);
|
||||
TEST_CHECK(dbuf_heap_cstr_iter_is_end(&str_dbuf, fnd_it));
|
||||
TEST_CHECK(std::find(str_vec.crbegin(), str_vec.crend(), needle) == str_vec.rend());
|
||||
|
||||
char *needle2 = *dbuf_heap_cstr_at(&str_dbuf, 0);
|
||||
fnd_it = dbuf_heap_cstr_find_val(&str_dbuf, dbuf_heap_cstr_iter_next(&str_dbuf, dbuf_heap_cstr_begin(&str_dbuf), 1), dbuf_heap_cstr_end(&str_dbuf), needle2);
|
||||
TEST_CHECK(dbuf_heap_cstr_iter_is_end(&str_dbuf, fnd_it));
|
||||
TEST_CHECK(std::find(str_vec.cbegin() + 1, str_vec.cend(), needle2) == str_vec.end());
|
||||
|
||||
needle2 = *dbuf_heap_cstr_back(&str_dbuf);
|
||||
fnd_it = dbuf_heap_cstr_find_val(&str_dbuf, dbuf_heap_cstr_iter_next(&str_dbuf, dbuf_heap_cstr_begin(&str_dbuf), 1), dbuf_heap_cstr_iter_next(&str_dbuf, dbuf_heap_cstr_end(&str_dbuf), -1), needle2);
|
||||
TEST_CHECK(dbuf_heap_cstr_iter_is_end(&str_dbuf, fnd_it));
|
||||
TEST_CHECK(std::find(str_vec.begin(), str_vec.end() - 1, needle2) == (str_vec.end() - 1));
|
||||
|
||||
needle2 = *dbuf_heap_cstr_at(&str_dbuf, 0);
|
||||
fnd_it = dbuf_heap_cstr_find_val(&str_dbuf, dbuf_heap_cstr_begin(&str_dbuf), dbuf_heap_cstr_begin(&str_dbuf), needle2);
|
||||
TEST_CHECK(dbuf_heap_cstr_iter_is_end(&str_dbuf, fnd_it));
|
||||
TEST_CHECK(std::find(str_vec.cbegin(), str_vec.cbegin(), needle2) == str_vec.cbegin());
|
||||
|
||||
dbuf_heap_cstr_free(&str_dbuf, NULL);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
DbufStrTest:
|
||||
*/
|
||||
|
||||
void DbufStrTest::run()
|
||||
{
|
||||
test_push_insert_erase(16);
|
||||
test_push_insert_erase(16, 1);
|
||||
test_push_insert_erase(16, 15);
|
||||
test_push_insert_erase(16, 16);
|
||||
test_push_insert_erase(16, 97);
|
||||
test_push_insert_erase(16, 256);
|
||||
|
||||
test_push_insert_erase(500);
|
||||
test_push_insert_erase(500, 1);
|
||||
test_push_insert_erase(500, 499);
|
||||
test_push_insert_erase(500, 500);
|
||||
test_push_insert_erase(500, 97);
|
||||
test_push_insert_erase(500, 256);
|
||||
|
||||
}
|
||||
|
||||
|
||||
void DbufStrTest::test_push_insert_erase(size_t n, ptrdiff_t start_cap)
|
||||
{
|
||||
dbuf_str strings = start_cap < 0 ? dbuf_str_new(&allocator) : dbuf_str_new_with_capacity(start_cap, &allocator);
|
||||
std::vector<std::string> strings_cpp {};
|
||||
|
||||
guf_libc_alloc_ctx str_allocator_ctx = {
|
||||
.tracker = guf_alloc_tracker_new(42, "test_push_insert_erase: local str allocator", NULL, NULL),
|
||||
.zero_init = false
|
||||
};
|
||||
guf_allocator str_allocator = guf_libc_allocator_new(&str_allocator_ctx);
|
||||
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
std::string str;
|
||||
for (size_t c = 0; c < 512 + GUF_STR_SSO_BUF_CAP; ++c) {
|
||||
str += (char)(c % 10 + '0');
|
||||
}
|
||||
const guf_str_view sv_long = guf_str_view{.str = str.data(), .len = (ptrdiff_t)str.size()};
|
||||
const guf_str_view sv_short = guf_str_view{.str = str.data(), .len = (ptrdiff_t)GUF_STR_SSO_BUF_CAP - 1};
|
||||
|
||||
guf_str long_str = GUF_STR_UNINITIALISED_CPP, short_str = GUF_STR_UNINITIALISED_CPP;
|
||||
guf_str_init(&long_str, sv_long, &str_allocator);
|
||||
guf_str_init(&short_str, sv_short, &str_allocator);
|
||||
|
||||
TEST_CHECK(str_allocator_ctx.tracker.alloc_count == 1 + (i*3));
|
||||
|
||||
// Move
|
||||
guf_err err;
|
||||
dbuf_str_try_push(&strings, &long_str, GUF_CPY_MOVE, &err);
|
||||
TEST_CHECK(err == GUF_ERR_NONE);
|
||||
dbuf_str_try_push(&strings, &short_str, GUF_CPY_MOVE, &err);
|
||||
TEST_CHECK(err == GUF_ERR_NONE);
|
||||
|
||||
strings_cpp.push_back(str.substr(0, str.size()));
|
||||
strings_cpp.push_back(str.substr(0, GUF_STR_SSO_BUF_CAP - 1));
|
||||
|
||||
TEST_CHECK(str_allocator_ctx.tracker.alloc_count == 1 + (i*3));
|
||||
|
||||
TEST_CHECK(guf_str_is_uninit(&long_str));
|
||||
TEST_CHECK(guf_str_is_uninit(&short_str));
|
||||
|
||||
// Deep-copy
|
||||
guf_str_init(&long_str, sv_long, &str_allocator);
|
||||
guf_str_init(&short_str, sv_short, &str_allocator);
|
||||
|
||||
TEST_CHECK(str_allocator_ctx.tracker.alloc_count == 2 + (i*3));
|
||||
|
||||
dbuf_str_try_push(&strings, &long_str, GUF_CPY_DEEP, &err);
|
||||
TEST_CHECK(err == GUF_ERR_NONE);
|
||||
dbuf_str_try_push(&strings, &short_str, GUF_CPY_DEEP, &err);
|
||||
TEST_CHECK(err == GUF_ERR_NONE);
|
||||
|
||||
strings_cpp.push_back(str.substr(0, str.size()));
|
||||
strings_cpp.push_back(str.substr(0, GUF_STR_SSO_BUF_CAP - 1));
|
||||
|
||||
TEST_CHECK(str_allocator_ctx.tracker.alloc_count == 3 + (i*3));
|
||||
|
||||
TEST_CHECK(guf_str_is_valid(&long_str) && guf_str_is_valid(&short_str));
|
||||
TEST_CHECK(guf_str_view_equal_val_arg(guf_str_view_from_str(&long_str), sv_long));
|
||||
TEST_CHECK(guf_str_view_equal_val_arg(guf_str_view_from_str(&short_str), sv_short));
|
||||
|
||||
guf_str_free(&long_str, NULL);
|
||||
guf_str_free(&short_str, NULL);
|
||||
|
||||
TEST_CHECK(str_allocator_ctx.tracker.free_count == 1 + (i*1));
|
||||
TEST_CHECK(str_allocator_ctx.tracker.alloc_count == 3 + (i*3));
|
||||
}
|
||||
|
||||
TEST_CHECK(str_allocator_ctx.tracker.free_count == n);
|
||||
TEST_CHECK(str_allocator_ctx.tracker.alloc_count == 3 * n);
|
||||
|
||||
TEST_CHECK(strings.size == 4 * (ptrdiff_t)n);
|
||||
TEST_CHECK(strings.size == std::ssize(strings_cpp));
|
||||
|
||||
std::string str;
|
||||
for (size_t c = 0; c < 512 + GUF_STR_SSO_BUF_CAP; ++c) {
|
||||
str += (char)(c % 10 + '0');
|
||||
}
|
||||
const guf_str_view sv_long = guf_str_view{.str = str.data(), .len = (ptrdiff_t)str.size()};
|
||||
const guf_str_view sv_short = guf_str_view{.str = str.data(), .len = (ptrdiff_t)GUF_STR_SSO_BUF_CAP - 1};
|
||||
size_t i = 0;
|
||||
GUF_CNT_FOREACH(&strings, dbuf_str, it) {
|
||||
if (TEST_CHECK(it.ptr)) {
|
||||
const guf_str *s = it.ptr;
|
||||
TEST_CHECK(guf_str_view_equal_val_arg(guf_str_view_from_str(s), i % 2 == 0 ? sv_long : sv_short));
|
||||
const guf_str_view sv_cpp = guf_str_view {.str = strings_cpp.at(i).data(), .len = (ptrdiff_t)strings_cpp.at(i).size()};
|
||||
TEST_CHECK(guf_str_view_equal_val_arg(guf_str_view_from_str(s), sv_cpp));
|
||||
}
|
||||
++i;
|
||||
}
|
||||
|
||||
GUF_CNT_FOREACH(&strings, dbuf_str, it) {
|
||||
if (TEST_CHECK(it.ptr)) {
|
||||
guf_str *s = it.ptr;
|
||||
TEST_CHECK(guf_str_append(s, GUF_CSTR_LIT_TO_VIEW_CPP("<END>")));
|
||||
}
|
||||
}
|
||||
std::vector<guf_str_view> delims = {guf_str_view{.str = "<END>", .len = 5}};
|
||||
i = 0;
|
||||
GUF_CNT_FOREACH(&strings, dbuf_str, it) {
|
||||
if (TEST_CHECK(it.ptr)) {
|
||||
const guf_str *s = it.ptr;
|
||||
guf_str_tok_state tk_state = guf_str_tok_state_new(guf_str_view_from_str(s), delims.data(), std::ssize(delims), GUF_STR_TOK_DELIM_OPT_MATCH_LONGEST);
|
||||
size_t tok_n = 0;
|
||||
while (guf_str_tok_next(&tk_state, true)) {
|
||||
TEST_CHECK(guf_str_view_equal_val_arg(tk_state.cur_tok , i % 2 == 0 ? sv_long : sv_short));
|
||||
TEST_CHECK(guf_str_view_equal_val_arg(tk_state.cur_delim, guf_str_view{.str = "<END>", .len = 5}));
|
||||
++tok_n;
|
||||
}
|
||||
TEST_CHECK(tok_n == 1);
|
||||
}
|
||||
++i;
|
||||
}
|
||||
|
||||
dbuf_str_free(&strings, NULL);
|
||||
TEST_CHECK(!guf_alloc_tracker_found_leak(&str_allocator_ctx.tracker));
|
||||
}
|
||||
69
src/test/test_dbuf.hpp
Executable file
69
src/test/test_dbuf.hpp
Executable file
@ -0,0 +1,69 @@
|
||||
#pragma once
|
||||
#include <vector>
|
||||
#include "test.hpp"
|
||||
|
||||
extern "C"
|
||||
{
|
||||
#include "guf_alloc_libc.h"
|
||||
#include "impls/dbuf_impl.h"
|
||||
}
|
||||
|
||||
struct DbufIntTest : public Test
|
||||
{
|
||||
DbufIntTest(const std::string& nm) : Test(nm)
|
||||
{
|
||||
allocator_ctx.zero_init = false;
|
||||
guf_alloc_tracker_init(&allocator_ctx.tracker, 1, "DbufIntTest_allocator", NULL, NULL);
|
||||
guf_libc_allocator_init(&allocator, &allocator_ctx);
|
||||
}
|
||||
|
||||
void run() override;
|
||||
|
||||
private:
|
||||
guf_allocator allocator;
|
||||
guf_libc_alloc_ctx allocator_ctx;
|
||||
|
||||
std::vector<int> dbuf_to_vec(dbuf_int *dbuf);
|
||||
void test_push(dbuf_int *dbuf, int n);
|
||||
void test_insert_remove(int n);
|
||||
};
|
||||
|
||||
|
||||
struct DbufCstringTest : public Test
|
||||
{
|
||||
DbufCstringTest(std::string nm) : Test(nm)
|
||||
{
|
||||
allocator_ctx.zero_init = false;
|
||||
guf_alloc_tracker_init(&allocator_ctx.tracker, 2, "DbufCstringTest_allocator", NULL, NULL);
|
||||
guf_libc_allocator_init(&allocator, &allocator_ctx);
|
||||
}
|
||||
|
||||
void run() override;
|
||||
|
||||
private:
|
||||
guf_allocator allocator;
|
||||
guf_libc_alloc_ctx allocator_ctx;
|
||||
|
||||
void test_iter(std::vector<std::string>& str_vec, dbuf_heap_cstr *str_dbuf, int step = 1);
|
||||
void test_push_insert_erase(int n, ptrdiff_t start_cap = 0);
|
||||
void test_find(int n = 32);
|
||||
};
|
||||
|
||||
|
||||
struct DbufStrTest : public Test
|
||||
{
|
||||
DbufStrTest(std::string nm) : Test(nm)
|
||||
{
|
||||
allocator_ctx.zero_init = false;
|
||||
guf_alloc_tracker_init(&allocator_ctx.tracker, 3, "DbufStrTest_allocator", NULL, NULL);
|
||||
guf_libc_allocator_init(&allocator, &allocator_ctx);
|
||||
}
|
||||
|
||||
void run() override;
|
||||
|
||||
private:
|
||||
guf_allocator allocator;
|
||||
guf_libc_alloc_ctx allocator_ctx;
|
||||
|
||||
void test_push_insert_erase(size_t n, ptrdiff_t start_cap = 0);
|
||||
};
|
||||
431
src/test/test_dict.cpp
Executable file
431
src/test/test_dict.cpp
Executable file
@ -0,0 +1,431 @@
|
||||
#include "test_dict.hpp"
|
||||
|
||||
#include <unordered_map>
|
||||
#include <cstring>
|
||||
extern "C"
|
||||
{
|
||||
#include "guf_alloc_libc.h"
|
||||
#include "guf_str.h"
|
||||
#include "impls/dict_impl.h"
|
||||
#include "impls/dbuf_impl.h"
|
||||
}
|
||||
|
||||
/*
|
||||
DictSvToIntTest:
|
||||
*/
|
||||
|
||||
void DictSvToIntTest::run()
|
||||
{
|
||||
if (done) {
|
||||
return;
|
||||
}
|
||||
|
||||
push_check_name("insert_lookup(\"utf8-test.txt\")");
|
||||
if (TEST_CHECK(load_file(TEST_DATA_DIR "/utf8-test.txt"))) {
|
||||
insert_lookup();
|
||||
for (ptrdiff_t i = 0; i <= 64; ++i) {
|
||||
insert_lookup(i);
|
||||
}
|
||||
insert_lookup(512);
|
||||
insert_lookup(1997);
|
||||
insert_lookup(1999);
|
||||
}
|
||||
free_file();
|
||||
pop_check_name();
|
||||
|
||||
push_check_name("insert_lookup(\"bartleby.txt\")");
|
||||
if (TEST_CHECK(load_file(TEST_DATA_DIR "/bartleby.txt"))) {
|
||||
insert_lookup();
|
||||
insert_lookup(201);
|
||||
}
|
||||
free_file();
|
||||
pop_check_name();
|
||||
|
||||
//guf_alloc_tracker_print(&allocator_ctx.tracker, NULL);
|
||||
TEST_CHECK(!guf_alloc_tracker_found_leak(&allocator_ctx.tracker));
|
||||
}
|
||||
|
||||
void DictSvToIntTest::insert_lookup(std::optional<ptrdiff_t> inital_dict_cap)
|
||||
{
|
||||
std::unordered_map<std::string_view, int32_t> word_cnt_map {};
|
||||
dict_sv_i32 word_cnt_dict {};
|
||||
dict_str_i32 word_cnt_dict_str {};
|
||||
|
||||
if (inital_dict_cap) {
|
||||
dict_sv_i32_init_with_capacity(&word_cnt_dict, &allocator, inital_dict_cap.value());
|
||||
dict_str_i32_init_with_capacity(&word_cnt_dict_str, &allocator, inital_dict_cap.value());
|
||||
} else {
|
||||
dict_sv_i32_init(&word_cnt_dict, &allocator);
|
||||
dict_str_i32_init(&word_cnt_dict_str, &allocator);
|
||||
}
|
||||
|
||||
dbuf_str_view delims = dbuf_str_view_new(&allocator);
|
||||
for (size_t i = 0; i < GUF_ARR_SIZE(GUF_UTF8_WHITESPACE); ++i) {
|
||||
guf_str_view d = {.str = GUF_UTF8_WHITESPACE[i], .len = (ptrdiff_t)strlen(GUF_UTF8_WHITESPACE[i])};
|
||||
dbuf_str_view_push_val(&delims, d);
|
||||
}
|
||||
for (size_t i = 0; i < GUF_ARR_SIZE(GUF_UTF8_COMMON_PUNCT); ++i) {
|
||||
guf_str_view d = {.str = GUF_UTF8_COMMON_PUNCT[i], .len = (ptrdiff_t)strlen(GUF_UTF8_COMMON_PUNCT[i])};
|
||||
dbuf_str_view_push_val(&delims, d);
|
||||
}
|
||||
|
||||
guf_str_tok_state tok_state = guf_str_tok_state_new(guf_str_view{.str = text_buf.data, .len = text_buf.size}, delims.data, delims.size, GUF_STR_TOK_DELIM_OPT_MATCH_LONGEST);
|
||||
while (guf_str_tok_next(&tok_state, true)) {
|
||||
guf_str_view tok = tok_state.cur_tok;
|
||||
// if (tok.len <= 0) {
|
||||
// continue;
|
||||
// }
|
||||
std::string_view sv(tok.str , tok.len);
|
||||
//std::cout << sv << std::string_view(tok_state.cur_delim.str, tok_state.cur_delim.len);
|
||||
TEST_CHECK(dict_sv_i32_contains(&word_cnt_dict, &tok) == word_cnt_map.contains(sv));
|
||||
if (!dict_sv_i32_contains(&word_cnt_dict, &tok)) {
|
||||
dict_sv_i32_insert_val_arg(&word_cnt_dict, tok, 1, GUF_CPY_VALUE, GUF_CPY_VALUE);
|
||||
word_cnt_map.insert({sv, 1});
|
||||
if (TEST_CHECK(!dict_str_i32_contains_val_arg(&word_cnt_dict_str, guf_str_new_readonly(tok)))) {
|
||||
dict_str_i32_insert_val_arg(&word_cnt_dict_str, guf_str_new(tok, &allocator), 1, GUF_CPY_MOVE, GUF_CPY_VALUE);
|
||||
}
|
||||
} else {
|
||||
int32_t *cnt = dict_sv_i32_at_val_arg(&word_cnt_dict, tok);
|
||||
if (TEST_CHECK(cnt)) {
|
||||
*cnt += 1;
|
||||
}
|
||||
int32_t *cnt_2 = dict_str_i32_at_val_arg(&word_cnt_dict_str, guf_str_new_readonly(tok));
|
||||
if (TEST_CHECK(cnt_2)) {
|
||||
*cnt_2 += 1;
|
||||
}
|
||||
// else {
|
||||
// std::cout << "tok: " << std::string_view{tok.str, (size_t)tok.len} << "\n";
|
||||
// }
|
||||
word_cnt_map.at(sv) += 1;
|
||||
}
|
||||
// printf("tok_len: %td ", tok.len);
|
||||
// printf("'%.*s'\n", (int)tok.len, tok.str);
|
||||
TEST_CHECK(dict_sv_i32_debug_valid_size(&word_cnt_dict));
|
||||
TEST_CHECK(dict_str_i32_debug_valid_size(&word_cnt_dict_str));
|
||||
|
||||
}
|
||||
dbuf_str_view_free(&delims, NULL);
|
||||
|
||||
TEST_CHECK(dict_sv_i32_size(&word_cnt_dict) == std::ssize(word_cnt_map));
|
||||
TEST_CHECK(dict_sv_i32_debug_valid_size(&word_cnt_dict));
|
||||
|
||||
TEST_CHECK(dict_str_i32_size(&word_cnt_dict_str) == std::ssize(word_cnt_map));
|
||||
TEST_CHECK(dict_str_i32_debug_valid_size(&word_cnt_dict_str));
|
||||
|
||||
for (const auto & [word, cnt] : word_cnt_map ) {
|
||||
guf_str_view sv = {.str = word.data(), .len = (ptrdiff_t)word.size()};
|
||||
int32_t *res = dict_sv_i32_at(&word_cnt_dict, &sv);
|
||||
int32_t *res2 = dict_str_i32_at_val_arg(&word_cnt_dict_str, guf_str_new_readonly(sv));
|
||||
TEST_CHECK(res && *res == cnt);
|
||||
TEST_CHECK(res2 && *res2 == cnt);
|
||||
}
|
||||
|
||||
ptrdiff_t i = 0;
|
||||
GUF_CNT_FOREACH(&word_cnt_dict, dict_sv_i32, kv_it) {
|
||||
const dict_sv_i32_kv *kv = kv_it.ptr;
|
||||
if (TEST_CHECK(kv)) {
|
||||
const int32_t cnt = kv->val;
|
||||
const std::string_view sv(kv->key.str, kv->key.len);
|
||||
if (TEST_CHECK(word_cnt_map.contains(sv))) {
|
||||
TEST_CHECK(word_cnt_map.at(sv) == cnt);
|
||||
}
|
||||
}
|
||||
++i;
|
||||
}
|
||||
TEST_CHECK(i == dict_sv_i32_size(&word_cnt_dict));
|
||||
TEST_CHECK(i == std::ssize(word_cnt_map));
|
||||
TEST_CHECK(dict_sv_i32_debug_valid_size(&word_cnt_dict));
|
||||
|
||||
i = 0;
|
||||
GUF_CNT_FOREACH(&word_cnt_dict_str, dict_str_i32, kv_it) {
|
||||
const dict_str_i32_kv *kv = kv_it.ptr;
|
||||
if (TEST_CHECK(kv)) {
|
||||
const int32_t cnt = kv->val;
|
||||
const std::string_view sv(guf_str_const_cstr(&kv->key), guf_str_len(&kv->key));
|
||||
// std::cout << sv << "\n";
|
||||
|
||||
if (TEST_CHECK(word_cnt_map.contains(sv))) {
|
||||
TEST_CHECK(word_cnt_map.at(sv) == cnt);
|
||||
}
|
||||
}
|
||||
++i;
|
||||
}
|
||||
TEST_CHECK(i == dict_str_i32_size(&word_cnt_dict_str));
|
||||
TEST_CHECK(i == std::ssize(word_cnt_map));
|
||||
TEST_CHECK(dict_str_i32_debug_valid_size(&word_cnt_dict_str));
|
||||
|
||||
// std::cout << "load fac: " << dict_sv_i32_load_factor(&word_cnt_dict) << ", cap: " << word_cnt_dict.kv_indices_cap << " elem cap: " << word_cnt_dict.kv_elems.capacity << "\n";
|
||||
// std::cout << "size: " << dict_sv_i32_size(&word_cnt_dict) << ", max probelen: " << word_cnt_dict.max_probelen << "\n";
|
||||
// std::cout << "mem usage: " << dict_sv_i32_memory_usage(&word_cnt_dict) << "\n";
|
||||
|
||||
// Erase tests:
|
||||
const double load_fac_before_erase = dict_sv_i32_load_factor(&word_cnt_dict);
|
||||
const ptrdiff_t size_before_erase = dict_sv_i32_size(&word_cnt_dict);
|
||||
ptrdiff_t num_del = 0;
|
||||
while (dict_sv_i32_size(&word_cnt_dict) > size_before_erase / 2) {
|
||||
dict_sv_i32_kv *kv = NULL;
|
||||
if (num_del % 2) {
|
||||
dict_sv_i32_iter it = dict_sv_i32_begin(&word_cnt_dict);
|
||||
GUF_ASSERT_RELEASE(!dict_sv_i32_iter_is_end(&word_cnt_dict, it));
|
||||
kv = it.ptr;
|
||||
} else {
|
||||
dict_sv_i32_iter rit = dict_sv_i32_rbegin(&word_cnt_dict);
|
||||
GUF_ASSERT_RELEASE(!dict_sv_i32_iter_is_end(&word_cnt_dict, rit));
|
||||
kv = rit.ptr;
|
||||
}
|
||||
GUF_ASSERT_RELEASE(kv);
|
||||
|
||||
const guf_str_view key = kv->key;
|
||||
|
||||
const bool del_success = dict_sv_i32_erase(&word_cnt_dict, &key);
|
||||
TEST_CHECK(del_success);
|
||||
TEST_CHECK(!dict_sv_i32_contains(&word_cnt_dict, &key));
|
||||
|
||||
std::string_view sv(key.str, (size_t)key.len);
|
||||
if (TEST_CHECK(word_cnt_map.contains(sv))) {
|
||||
word_cnt_map.erase(sv);
|
||||
}
|
||||
TEST_CHECK(!word_cnt_map.contains(sv));
|
||||
|
||||
if (del_success) {
|
||||
++num_del;
|
||||
}
|
||||
}
|
||||
TEST_CHECK(dict_sv_i32_size(&word_cnt_dict) >= 0);
|
||||
TEST_CHECK(size_before_erase - num_del == dict_sv_i32_size(&word_cnt_dict));
|
||||
TEST_CHECK(std::ssize(word_cnt_map) == dict_sv_i32_size(&word_cnt_dict));
|
||||
|
||||
if (dict_sv_i32_size(&word_cnt_dict) != 0) {
|
||||
TEST_CHECK(load_fac_before_erase == dict_sv_i32_load_factor(&word_cnt_dict));
|
||||
} else {
|
||||
TEST_CHECK(dict_sv_i32_load_factor(&word_cnt_dict) == 0);
|
||||
}
|
||||
|
||||
if (dict_sv_i32_size(&word_cnt_dict) >= 4) {
|
||||
dict_sv_i32_kv_dbuf_iter it = dict_sv_i32_begin(&word_cnt_dict);
|
||||
it = dict_sv_i32_iter_next(&word_cnt_dict, it, 1);
|
||||
GUF_ASSERT_RELEASE(!dict_sv_i32_iter_is_end(&word_cnt_dict, it));
|
||||
|
||||
guf_str_view key = it.ptr->key;
|
||||
|
||||
bool del_success = dict_sv_i32_erase(&word_cnt_dict, &key);
|
||||
TEST_CHECK(del_success);
|
||||
TEST_CHECK(!dict_sv_i32_contains(&word_cnt_dict, &key));
|
||||
|
||||
std::string_view sv(key.str, (size_t)key.len);
|
||||
if (TEST_CHECK(word_cnt_map.contains(sv))) {
|
||||
word_cnt_map.erase(sv);
|
||||
}
|
||||
|
||||
it = dict_sv_i32_rbegin(&word_cnt_dict);
|
||||
it = dict_sv_i32_iter_next(&word_cnt_dict, it, 1);
|
||||
GUF_ASSERT_RELEASE(!dict_sv_i32_iter_is_end(&word_cnt_dict, it));
|
||||
key = it.ptr->key;
|
||||
|
||||
del_success = dict_sv_i32_erase(&word_cnt_dict, &key);
|
||||
TEST_CHECK(del_success);
|
||||
TEST_CHECK(!dict_sv_i32_contains(&word_cnt_dict, &key));
|
||||
|
||||
sv = std::string_view(key.str, (size_t)key.len);
|
||||
if (TEST_CHECK(word_cnt_map.contains(sv))) {
|
||||
word_cnt_map.erase(sv);
|
||||
}
|
||||
}
|
||||
TEST_CHECK(std::ssize(word_cnt_map) == dict_sv_i32_size(&word_cnt_dict));
|
||||
|
||||
i = 0;
|
||||
GUF_CNT_FOREACH(&word_cnt_dict, dict_sv_i32, kv_it) {
|
||||
const dict_sv_i32_kv *kv = kv_it.ptr;
|
||||
if (TEST_CHECK(kv)) {
|
||||
const int32_t cnt = kv->val;
|
||||
const std::string_view sv(kv->key.str, (size_t)kv->key.len);
|
||||
if (TEST_CHECK(word_cnt_map.contains(sv))) {
|
||||
TEST_CHECK(word_cnt_map.at(sv) == cnt);
|
||||
}
|
||||
++i;
|
||||
}
|
||||
}
|
||||
TEST_CHECK(i == word_cnt_dict.kv_elems.size);
|
||||
TEST_CHECK(i == std::ssize(word_cnt_map));
|
||||
|
||||
while (dict_sv_i32_size(&word_cnt_dict) > 0) {
|
||||
const dict_sv_i32_iter beg = dict_sv_i32_begin(&word_cnt_dict);
|
||||
if (TEST_CHECK(!dict_sv_i32_iter_is_end(&word_cnt_dict, beg))) {
|
||||
const guf_str_view key = beg.ptr->key;
|
||||
if (TEST_CHECK(dict_sv_i32_contains(&word_cnt_dict, &key))) {
|
||||
const bool del_success = dict_sv_i32_erase(&word_cnt_dict, &key);
|
||||
TEST_CHECK(del_success);
|
||||
TEST_CHECK(!dict_sv_i32_contains(&word_cnt_dict, &key));
|
||||
}
|
||||
const std::string_view sv(key.str, (size_t)key.len);
|
||||
if (TEST_CHECK(word_cnt_map.contains(sv))) {
|
||||
word_cnt_map.erase(sv);
|
||||
}
|
||||
}
|
||||
}
|
||||
TEST_CHECK(dict_sv_i32_size(&word_cnt_dict) == 0 && word_cnt_map.size() == 0);
|
||||
TEST_CHECK(word_cnt_dict.num_tombstones == 0);
|
||||
TEST_CHECK(dict_sv_i32_load_factor(&word_cnt_dict) == 0);
|
||||
|
||||
dict_sv_i32_insert_val_arg(&word_cnt_dict, GUF_CSTR_TO_VIEW_CPP("Hej"), (size_t)64, GUF_CPY_VALUE, GUF_CPY_VALUE);
|
||||
dict_sv_i32_insert_val_arg(&word_cnt_dict, GUF_CSTR_TO_VIEW_CPP("verden!"), (size_t)128, GUF_CPY_VALUE, GUF_CPY_VALUE);
|
||||
dict_sv_i32_insert_val_arg(&word_cnt_dict, GUF_CSTR_TO_VIEW_CPP("Flødeskum"), (size_t)256, GUF_CPY_VALUE, GUF_CPY_VALUE);
|
||||
dict_sv_i32_insert_val_arg(&word_cnt_dict, GUF_CSTR_TO_VIEW_CPP("med"), (size_t)512, GUF_CPY_VALUE, GUF_CPY_VALUE);
|
||||
dict_sv_i32_insert_val_arg(&word_cnt_dict, GUF_CSTR_TO_VIEW_CPP("Faxe Kondi."), (size_t)1024, GUF_CPY_VALUE, GUF_CPY_VALUE);
|
||||
|
||||
TEST_CHECK(dict_sv_i32_size(&word_cnt_dict) == 5);
|
||||
|
||||
int32_t *val = dict_sv_i32_at_val_arg(&word_cnt_dict, GUF_CSTR_TO_VIEW_CPP("Hej"));
|
||||
TEST_CHECK(val && *val == 64);
|
||||
val = dict_sv_i32_at_val_arg(&word_cnt_dict, GUF_CSTR_TO_VIEW_CPP("Flødeskum"));
|
||||
TEST_CHECK(val && *val == 256);
|
||||
val = dict_sv_i32_at_val_arg(&word_cnt_dict, GUF_CSTR_TO_VIEW_CPP("Faxe Kondi."));
|
||||
TEST_CHECK(val && *val == 1024);
|
||||
val = dict_sv_i32_at_val_arg(&word_cnt_dict, GUF_CSTR_TO_VIEW_CPP("verden!"));
|
||||
TEST_CHECK(val && *val == 128);
|
||||
val = dict_sv_i32_at_val_arg(&word_cnt_dict, GUF_CSTR_TO_VIEW_CPP("med"));
|
||||
TEST_CHECK(val && *val == 512);
|
||||
|
||||
TEST_CHECK(word_cnt_dict.kv_elems.size == 5);
|
||||
|
||||
TEST_CHECK(word_cnt_dict.kv_elems.data[0].val == 64 && std::strcmp(word_cnt_dict.kv_elems.data[0].key.str, "Hej") == 0);
|
||||
TEST_CHECK(word_cnt_dict.kv_elems.data[1].val == 128 && std::strcmp(word_cnt_dict.kv_elems.data[1].key.str, "verden!") == 0);
|
||||
TEST_CHECK(word_cnt_dict.kv_elems.data[2].val == 256 && std::strcmp(word_cnt_dict.kv_elems.data[2].key.str, "Flødeskum") == 0);
|
||||
TEST_CHECK(word_cnt_dict.kv_elems.data[3].val == 512 && std::strcmp(word_cnt_dict.kv_elems.data[3].key.str, "med") == 0);
|
||||
TEST_CHECK(word_cnt_dict.kv_elems.data[4].val == 1024 && std::strcmp(word_cnt_dict.kv_elems.data[4].key.str, "Faxe Kondi.") == 0);
|
||||
|
||||
const double load_fac_beg = dict_sv_i32_load_factor(&word_cnt_dict);
|
||||
const ptrdiff_t cap_begin = word_cnt_dict.kv_indices_cap;
|
||||
ptrdiff_t del = 0;
|
||||
|
||||
TEST_CHECK(dict_sv_i32_erase_val_arg(&word_cnt_dict, GUF_CSTR_TO_VIEW_CPP("Hej")));
|
||||
TEST_CHECK(word_cnt_dict.num_tombstones == ++del);
|
||||
TEST_CHECK(dict_sv_i32_load_factor(&word_cnt_dict) == load_fac_beg);
|
||||
for (ptrdiff_t n = 0; n < cap_begin + 128; ++n) {
|
||||
dict_sv_i32_insert_val_arg(&word_cnt_dict, GUF_CSTR_TO_VIEW_CPP("Hej"), 64, GUF_CPY_VALUE, GUF_CPY_VALUE);
|
||||
TEST_CHECK(word_cnt_dict.num_tombstones == --del);
|
||||
TEST_CHECK(dict_sv_i32_load_factor(&word_cnt_dict) == load_fac_beg);
|
||||
|
||||
TEST_CHECK(dict_sv_i32_erase_val_arg(&word_cnt_dict, GUF_CSTR_TO_VIEW_CPP("Hej")));
|
||||
TEST_CHECK(word_cnt_dict.num_tombstones == ++del);
|
||||
TEST_CHECK(dict_sv_i32_load_factor(&word_cnt_dict) == load_fac_beg);
|
||||
}
|
||||
TEST_CHECK(word_cnt_dict.kv_indices_cap == cap_begin);
|
||||
|
||||
|
||||
TEST_CHECK(dict_sv_i32_erase_val_arg(&word_cnt_dict, GUF_CSTR_TO_VIEW_CPP("Faxe Kondi.")));
|
||||
TEST_CHECK(word_cnt_dict.num_tombstones == ++del);
|
||||
TEST_CHECK(dict_sv_i32_load_factor(&word_cnt_dict) == load_fac_beg);
|
||||
for (ptrdiff_t n = 0; n < 256; ++n) {
|
||||
dict_sv_i32_insert_val_arg(&word_cnt_dict, GUF_CSTR_TO_VIEW_CPP("Faxe Kondi."), 128, GUF_CPY_VALUE, GUF_CPY_VALUE);
|
||||
TEST_CHECK(word_cnt_dict.num_tombstones == --del);
|
||||
TEST_CHECK(dict_sv_i32_load_factor(&word_cnt_dict) == load_fac_beg);
|
||||
|
||||
TEST_CHECK(dict_sv_i32_erase_val_arg(&word_cnt_dict, GUF_CSTR_TO_VIEW_CPP("Faxe Kondi.")));
|
||||
TEST_CHECK(word_cnt_dict.num_tombstones == ++del);
|
||||
TEST_CHECK(dict_sv_i32_load_factor(&word_cnt_dict) == load_fac_beg);
|
||||
}
|
||||
TEST_CHECK(word_cnt_dict.kv_indices_cap == cap_begin);
|
||||
|
||||
TEST_CHECK(dict_sv_i32_erase_val_arg(&word_cnt_dict, GUF_CSTR_TO_VIEW_CPP("med")));
|
||||
TEST_CHECK(word_cnt_dict.num_tombstones == ++del);
|
||||
TEST_CHECK(dict_sv_i32_load_factor(&word_cnt_dict) == load_fac_beg);
|
||||
for (ptrdiff_t n = 0; n < 512 + cap_begin; ++n) {
|
||||
dict_sv_i32_insert_val_arg(&word_cnt_dict, GUF_CSTR_TO_VIEW_CPP("med"), 256, GUF_CPY_VALUE, GUF_CPY_VALUE);
|
||||
TEST_CHECK(word_cnt_dict.num_tombstones == --del);
|
||||
TEST_CHECK(dict_sv_i32_load_factor(&word_cnt_dict) == load_fac_beg);
|
||||
|
||||
TEST_CHECK(dict_sv_i32_erase_val_arg(&word_cnt_dict, GUF_CSTR_TO_VIEW_CPP("med")));
|
||||
TEST_CHECK(word_cnt_dict.num_tombstones == ++del);
|
||||
TEST_CHECK(dict_sv_i32_load_factor(&word_cnt_dict) == load_fac_beg);
|
||||
}
|
||||
TEST_CHECK(word_cnt_dict.kv_indices_cap == cap_begin);
|
||||
|
||||
TEST_CHECK(dict_sv_i32_erase_val_arg(&word_cnt_dict, GUF_CSTR_TO_VIEW_CPP("Flødeskum")));
|
||||
TEST_CHECK(word_cnt_dict.num_tombstones == ++del);
|
||||
TEST_CHECK(dict_sv_i32_load_factor(&word_cnt_dict) == load_fac_beg);
|
||||
for (ptrdiff_t n = 0; n < 71; ++n) {
|
||||
dict_sv_i32_insert_val_arg(&word_cnt_dict, GUF_CSTR_TO_VIEW_CPP("Flødeskum"), 512, GUF_CPY_VALUE, GUF_CPY_VALUE);
|
||||
TEST_CHECK(word_cnt_dict.num_tombstones == --del);
|
||||
TEST_CHECK(dict_sv_i32_load_factor(&word_cnt_dict) == load_fac_beg);
|
||||
|
||||
TEST_CHECK(dict_sv_i32_erase_val_arg(&word_cnt_dict, GUF_CSTR_TO_VIEW_CPP("Flødeskum")));
|
||||
TEST_CHECK(word_cnt_dict.num_tombstones == ++del);
|
||||
TEST_CHECK(dict_sv_i32_load_factor(&word_cnt_dict) == load_fac_beg);
|
||||
}
|
||||
TEST_CHECK(word_cnt_dict.kv_indices_cap == cap_begin);
|
||||
|
||||
TEST_CHECK(dict_sv_i32_erase_val_arg(&word_cnt_dict, GUF_CSTR_TO_VIEW_CPP("verden!")));
|
||||
TEST_CHECK(word_cnt_dict.num_tombstones == 0);
|
||||
TEST_CHECK(dict_sv_i32_load_factor(&word_cnt_dict) == 0);
|
||||
for (ptrdiff_t n = 0; n < 201; ++n) {
|
||||
dict_sv_i32_insert_val_arg(&word_cnt_dict, GUF_CSTR_TO_VIEW_CPP("verden!"), 128, GUF_CPY_VALUE, GUF_CPY_VALUE);
|
||||
TEST_CHECK(word_cnt_dict.num_tombstones == 0);
|
||||
TEST_CHECK(dict_sv_i32_load_factor(&word_cnt_dict) > 0);
|
||||
|
||||
TEST_CHECK(dict_sv_i32_erase_val_arg(&word_cnt_dict, GUF_CSTR_TO_VIEW_CPP("verden!")));
|
||||
TEST_CHECK(word_cnt_dict.num_tombstones == 0);
|
||||
TEST_CHECK(dict_sv_i32_load_factor(&word_cnt_dict) == 0);
|
||||
}
|
||||
TEST_CHECK(word_cnt_dict.kv_indices_cap == cap_begin);
|
||||
|
||||
TEST_CHECK(word_cnt_dict.kv_elems.size == 0);
|
||||
TEST_CHECK(dict_sv_i32_size(&word_cnt_dict) == 0);
|
||||
|
||||
|
||||
std::string str;
|
||||
for (size_t c = 0; c < GUF_STR_SSO_BUF_CAP * 4; ++c) {
|
||||
str += c % 2 ? "AAA" : "aaa";
|
||||
}
|
||||
|
||||
guf_str str_cpy = guf_str_new(guf_str_view{.str = str.data(), .len = (ptrdiff_t)str.size()}, &allocator);
|
||||
dict_str_i32_insert_val_arg(&word_cnt_dict_str, str_cpy, 42, GUF_CPY_DEEP, GUF_CPY_VALUE);
|
||||
int32_t *foo = dict_str_i32_at_val_arg(&word_cnt_dict_str, guf_str_new_readonly(guf_str_view_from_str(&str_cpy)));
|
||||
if (TEST_CHECK(foo)) {
|
||||
TEST_CHECK(*foo == 42);
|
||||
}
|
||||
guf_str_append(&str_cpy, GUF_CSTR_LIT_TO_VIEW_CPP("Foobar"));
|
||||
int32_t *foo2 = dict_str_i32_at_val_arg(&word_cnt_dict_str, guf_str_new_readonly(guf_str_view{.str = str.data(), .len = (ptrdiff_t)str.size()}));
|
||||
if (TEST_CHECK(foo2)) {
|
||||
TEST_CHECK(*foo2 == 42);
|
||||
}
|
||||
|
||||
guf_str_free(&str_cpy, NULL);
|
||||
|
||||
dict_sv_i32_free(&word_cnt_dict, NULL);
|
||||
dict_str_i32_free(&word_cnt_dict_str, NULL);
|
||||
|
||||
bool dbuf_null = !word_cnt_dict.kv_elems.data && !word_cnt_dict.kv_elems.allocator && !word_cnt_dict.kv_elems.capacity && !word_cnt_dict.kv_elems.size;
|
||||
TEST_CHECK(dbuf_null && !word_cnt_dict.kv_indices && !word_cnt_dict.kv_indices_cap && !word_cnt_dict.max_probelen && !word_cnt_dict.num_tombstones);
|
||||
}
|
||||
|
||||
bool DictSvToIntTest::load_file(const char *fname)
|
||||
{
|
||||
FILE *in_file {nullptr};
|
||||
if (!in_file) {
|
||||
in_file = fopen(fname, "r");
|
||||
}
|
||||
|
||||
GUF_ASSERT_RELEASE(in_file);
|
||||
|
||||
dbuf_char_init(&text_buf, 128, &allocator);
|
||||
|
||||
int c = EOF;
|
||||
while ((c = fgetc(in_file)) != EOF) {
|
||||
dbuf_char_push_val(&text_buf, (char)c);
|
||||
text_vec.push_back((char)c);
|
||||
}
|
||||
fclose(in_file);
|
||||
|
||||
// dbuf_char_insert_val(&text_buf, '\xC0', 1);
|
||||
// text_vec.insert(text_vec.cbegin() + 1, '\xC0');
|
||||
|
||||
return TEST_CHECK(std::ssize(text_vec) == text_buf.size);
|
||||
}
|
||||
|
||||
void DictSvToIntTest::free_file()
|
||||
{
|
||||
dbuf_char_free(&text_buf, NULL);
|
||||
text_buf = {};
|
||||
text_vec.clear();
|
||||
}
|
||||
35
src/test/test_dict.hpp
Executable file
35
src/test/test_dict.hpp
Executable file
@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
#include <vector>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include "test.hpp"
|
||||
|
||||
extern "C"
|
||||
{
|
||||
#include "impls/dbuf_impl.h"
|
||||
#include "guf_alloc_libc.h"
|
||||
}
|
||||
|
||||
struct DictSvToIntTest : public Test
|
||||
{
|
||||
DictSvToIntTest(const std::string& nm) : Test(nm)
|
||||
{
|
||||
allocator_ctx.zero_init = false;
|
||||
guf_alloc_tracker_init(&allocator_ctx.tracker, 3, "DictSvToIntTest_allocator", NULL, NULL);
|
||||
guf_libc_allocator_init(&allocator, &allocator_ctx);
|
||||
};
|
||||
|
||||
void run() override;
|
||||
|
||||
private:
|
||||
guf_allocator allocator;
|
||||
guf_libc_alloc_ctx allocator_ctx;
|
||||
|
||||
dbuf_char text_buf {};
|
||||
std::vector<char> text_vec {};
|
||||
|
||||
bool load_file(const char *fname);
|
||||
void free_file();
|
||||
|
||||
void insert_lookup(std::optional<ptrdiff_t> inital_dict_cap = {});
|
||||
};
|
||||
379
src/test/test_str.cpp
Executable file
379
src/test/test_str.cpp
Executable file
@ -0,0 +1,379 @@
|
||||
#include "test_str.hpp"
|
||||
extern "C"
|
||||
{
|
||||
#include "guf_alloc_libc.h"
|
||||
}
|
||||
|
||||
/*
|
||||
StrTest:
|
||||
*/
|
||||
|
||||
void StrTest::run()
|
||||
{
|
||||
if (done) {
|
||||
return;
|
||||
}
|
||||
|
||||
const std::vector<std::string> words = {
|
||||
"",
|
||||
"\0",
|
||||
"Hello",
|
||||
"Othell\0o",
|
||||
"f\0\0",
|
||||
"\0",
|
||||
"0",
|
||||
"a",
|
||||
"ab",
|
||||
"🌈 waow a rainboge!",
|
||||
"orange cat(1) :3",
|
||||
"xes yag",
|
||||
"Hello, world! This is a pretty darn long string I'd say...",
|
||||
"I want to eat crayons. I crave crayons because they are tasty, and everybody telling me crayons are not edible must be either lying or dumb. I like trains. 42 is a number. 3.14159265... is not a rational number, and it is called pi. I ate some pie (it was a crayon pie).",
|
||||
std::string(32, 'a'),
|
||||
std::string(64, 'b'),
|
||||
std::string(1024, 'a'),
|
||||
std::string(2048, 'a'),
|
||||
std::string(4096, 'a'),
|
||||
std::string(5001, 'a'),
|
||||
std::string(7121, 'a'),
|
||||
std::string(2000, 'a'),
|
||||
std::string(GUF_STR_SSO_BUF_CAP, 'a'),
|
||||
std::string(GUF_STR_SSO_BUF_CAP - 1, 'a'),
|
||||
std::string(GUF_STR_SSO_BUF_CAP + 1, 'a'),
|
||||
std::string(GUF_STR_SSO_BUF_CAP - 2, 'a'),
|
||||
std::string(GUF_STR_SSO_BUF_CAP + 2, 'a'),
|
||||
std::string(GUF_STR_SSO_BUF_CAP - 3, 'a'),
|
||||
std::string(GUF_STR_SSO_BUF_CAP + 3, 'a'),
|
||||
std::string(GUF_STR_SSO_BUF_CAP * 2, 'a'),
|
||||
std::string(GUF_STR_SSO_BUF_CAP * 3, 'a'),
|
||||
std::string(GUF_STR_SSO_BUF_CAP * 4, 'a'),
|
||||
std::string(GUF_STR_SSO_BUF_CAP * 5, 'a'),
|
||||
std::string(GUF_STR_SSO_BUF_CAP * 6, 'a'),
|
||||
std::string(GUF_STR_SSO_BUF_CAP * 7, 'a'),
|
||||
};
|
||||
|
||||
|
||||
push_check_name("init_empy");
|
||||
test_init_empty();
|
||||
pop_check_name();
|
||||
|
||||
push_check_name("append_char");
|
||||
for (const auto& word : words) {
|
||||
test_init_free(word);
|
||||
test_append_char(word);
|
||||
test_append_char(word, true);
|
||||
}
|
||||
pop_check_name();
|
||||
|
||||
push_check_name("append_str");
|
||||
for (size_t i = 0; i < words.size(); ++i) {
|
||||
const auto& w1 = words.at(i);
|
||||
append_str(w1, w1);
|
||||
append_str(w1, w1);
|
||||
for (size_t j = i + 1; j < words.size(); ++j) {
|
||||
const auto& w2 = words.at(j);
|
||||
append_str(w1, w2);
|
||||
append_str(w2, w1);
|
||||
}
|
||||
}
|
||||
pop_check_name();
|
||||
|
||||
push_check_name("test_popsplit");
|
||||
std::vector<std::string_view> split = test_popsplit("1997-04-01", "-");
|
||||
if (TEST_CHECK(split.size() == 3)) {
|
||||
TEST_CHECK(split.at(0) == "1997" && split.at(1) == "04" && split.at(2) == "01");
|
||||
}
|
||||
split = test_popsplit("1997-04-01-", "-");
|
||||
if (TEST_CHECK(split.size() == 3)) {
|
||||
TEST_CHECK(split.at(0) == "1997" && split.at(1) == "04" && split.at(2) == "01");
|
||||
}
|
||||
|
||||
split = test_popsplit("2025/05/08", "/");
|
||||
if (TEST_CHECK(split.size() == 3)) {
|
||||
TEST_CHECK(split.at(0) == "2025" && split.at(1) == "05" && split.at(2) == "08");
|
||||
}
|
||||
split = test_popsplit("2025/05/08/", "/");
|
||||
if (TEST_CHECK(split.size() == 3)) {
|
||||
TEST_CHECK(split.at(0) == "2025" && split.at(1) == "05" && split.at(2) == "08");
|
||||
}
|
||||
split = test_popsplit("2025/05/08//", "/");
|
||||
if (TEST_CHECK(split.size() == 4)) {
|
||||
TEST_CHECK(split.at(0) == "2025" && split.at(1) == "05" && split.at(2) == "08" && split.at(3) == "");
|
||||
}
|
||||
|
||||
split = test_popsplit("/2025/05/08", "/");
|
||||
if (TEST_CHECK(split.size() == 4)) {
|
||||
TEST_CHECK(split.at(0) == "" && split.at(1) == "2025" && split.at(2) == "05" && split.at(3) == "08");
|
||||
}
|
||||
split = test_popsplit("//2025/05/08", "/");
|
||||
if (TEST_CHECK(split.size() == 5)) {
|
||||
TEST_CHECK(split.at(0) == "" && split.at(1) == "" && split.at(2) == "2025" && split.at(3) == "05" && split.at(4) == "08");
|
||||
}
|
||||
|
||||
split = test_popsplit("I eat formidable crayons, oof, for real", "foo");
|
||||
if (TEST_CHECK(split.size() == 1)) {
|
||||
TEST_CHECK(split.at(0) == "I eat formidable crayons, oof, for real");
|
||||
}
|
||||
|
||||
split = test_popsplit("Hej <<", "<<");
|
||||
if (TEST_CHECK(split.size() == 1)) {
|
||||
TEST_CHECK(split.at(0) == "Hej ");
|
||||
}
|
||||
split = test_popsplit("Hej << verden", "<<");
|
||||
if (TEST_CHECK(split.size() == 2)) {
|
||||
TEST_CHECK(split.at(0) == "Hej " && split.at(1) == " verden");
|
||||
}
|
||||
split = test_popsplit("<< Hej << verden", "<<");
|
||||
if (TEST_CHECK(split.size() == 3)) {
|
||||
TEST_CHECK(split.at(0) == "" && split.at(1) == " Hej " && split.at(2) == " verden");
|
||||
}
|
||||
split = test_popsplit("<< Hej << verden <<< foo<>", "<<");
|
||||
if (TEST_CHECK(split.size() == 4)) {
|
||||
TEST_CHECK(split.at(0) == "" && split.at(1) == " Hej " && split.at(2) == " verden " && split.at(3) == "< foo<>");
|
||||
}
|
||||
|
||||
split = test_popsplit("I eat tofu", "");
|
||||
if (TEST_CHECK(split.size() == 1)) {
|
||||
TEST_CHECK(split.at(0) == "I eat tofu");
|
||||
}
|
||||
|
||||
split = test_popsplit("At 3 a.m. during FULL-moon FULL-STOP Next to the public-library's -STOP sign FULL-STOP", "FULL-STOP");
|
||||
if (TEST_CHECK(split.size() == 2)) {
|
||||
TEST_CHECK(split.at(0) == "At 3 a.m. during FULL-moon " && split.at(1) == " Next to the public-library's -STOP sign ");
|
||||
}
|
||||
split = test_popsplit("At 3 a.m. during FULL-moon FULL-STOP Next to the public-library's -STOP sign FULL-STOPI like trains, FULL-STO", "FULL-STOP");
|
||||
if (TEST_CHECK(split.size() == 3)) {
|
||||
TEST_CHECK(split.at(0) == "At 3 a.m. during FULL-moon " && split.at(1) == " Next to the public-library's -STOP sign " && split.at(2) == "I like trains, FULL-STO");
|
||||
}
|
||||
split = test_popsplit("At 3 a.m. during FULL-moon FULL-STOP Next to the public-library's -STOP sign FULL-STOPI like trains, FULL-STO Poo", "FULL-STOP");
|
||||
if (TEST_CHECK(split.size() == 3)) {
|
||||
TEST_CHECK(split.at(0) == "At 3 a.m. during FULL-moon " && split.at(1) == " Next to the public-library's -STOP sign " && split.at(2) == "I like trains, FULL-STO Poo");
|
||||
}
|
||||
pop_check_name();
|
||||
|
||||
push_check_name("get_toks");
|
||||
std::vector<std::string_view> tok_words = {"hello", "world", "cat", "vertex", "normal", "pizza", "running", "mouse", "playing", "adjacent"};
|
||||
std::vector<std::string_view> delims = {",", " ", "\n", "\t", "\r"};
|
||||
|
||||
for (int is_trailing = 0; is_trailing < 2; ++is_trailing) {
|
||||
for (ptrdiff_t num_words = 1; num_words < std::ssize(tok_words); ++num_words) {
|
||||
std::string str = "";
|
||||
for (ptrdiff_t j = 0; j < num_words; ++j) {
|
||||
str += tok_words.at(j);
|
||||
if (j < num_words - 1 || is_trailing) {
|
||||
str += ", ";
|
||||
}
|
||||
}
|
||||
std::vector<std::string_view> toks = get_toks(std::string_view{str}, delims, false, GUF_STR_TOK_DELIM_OPT_MATCH_LONGEST);
|
||||
if (TEST_CHECK(std::ssize(toks) == num_words)) {
|
||||
for (ptrdiff_t i = 0; i < num_words; ++i) {
|
||||
TEST_CHECK(toks.at(i) == tok_words.at(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string_view tok_str = "<stats>age: 28, occupation: NULL, crayons_eaten: 256 </stats>";
|
||||
delims = {"<stats>", "</stats>", ":", ",", " ", "\t", "<stats", "<", ">", "</"};
|
||||
auto tok_result = get_toks(tok_str, delims, true, GUF_STR_TOK_DELIM_OPT_MATCH_LONGEST);
|
||||
TEST_CHECK(tok_result.size() == 19);
|
||||
TEST_CHECK(tok_result.at(18) == "</stats>" && tok_result.at(0) == "<stats>" && tok_result.at(1) == "age" && tok_result.at(2) == ":" && tok_result.at(3) == " " && tok_result.at(4) == "28");
|
||||
|
||||
tok_result = get_toks(tok_str, delims, false, GUF_STR_TOK_DELIM_OPT_MATCH_LONGEST);
|
||||
TEST_CHECK(tok_result.size() == 6);
|
||||
TEST_CHECK(tok_result.at(0) == "age" && tok_result.at(1) == "28" && tok_result.at(2) == "occupation" && tok_result.at(3) == "NULL" &&
|
||||
tok_result.at(4) == "crayons_eaten" && tok_result.at(5) == "256");
|
||||
pop_check_name();
|
||||
|
||||
// guf_alloc_tracker_print(&allocator_ctx.tracker, NULL);
|
||||
TEST_CHECK(!guf_alloc_tracker_found_leak(&allocator_ctx.tracker));
|
||||
}
|
||||
|
||||
|
||||
void StrTest::test_init_free(std::string str)
|
||||
{
|
||||
guf_str s0;
|
||||
guf_str_init(&s0, GUF_CSTR_TO_VIEW_CPP(str.c_str()), &allocator);
|
||||
guf_str s1 = guf_str_new(GUF_CSTR_TO_VIEW_CPP(str.c_str()), &allocator);
|
||||
guf_str s2;
|
||||
guf_str_init_from_cstr(&s2, str.c_str(), &allocator);
|
||||
|
||||
TEST_CHECK(guf_str_equal(&s0, &s1));
|
||||
TEST_CHECK(guf_str_equal(&s0, &s2));
|
||||
TEST_CHECK(guf_str_equal(&s1, &s2));
|
||||
|
||||
TEST_CHECK((ptrdiff_t)str.size() == guf_str_len(&s0));
|
||||
TEST_CHECK(str == guf_str_const_cstr(&s0));
|
||||
TEST_CHECK(str == guf_str_cstr(&s0));
|
||||
|
||||
TEST_CHECK((ptrdiff_t)str.size() == guf_str_len(&s1));
|
||||
TEST_CHECK(str == guf_str_const_cstr(&s1));
|
||||
TEST_CHECK(str == guf_str_cstr(&s1));
|
||||
|
||||
TEST_CHECK((ptrdiff_t)str.size() == guf_str_len(&s2));
|
||||
TEST_CHECK(str == guf_str_const_cstr(&s2));
|
||||
TEST_CHECK(str == guf_str_cstr(&s2));
|
||||
|
||||
guf_str_free(&s0, NULL);
|
||||
guf_str_free(&s1, NULL);
|
||||
guf_str_free(&s2, NULL);
|
||||
TEST_CHECK(guf_str_is_uninit(&s0));
|
||||
TEST_CHECK(guf_str_is_uninit(&s1));
|
||||
TEST_CHECK(guf_str_is_uninit(&s2));
|
||||
}
|
||||
|
||||
void StrTest::test_init_empty()
|
||||
{
|
||||
std::string str = "";
|
||||
guf_str s = GUF_STR_UNINITIALISED_CPP;
|
||||
guf_str_init_empty(&s, &allocator);
|
||||
TEST_CHECK(guf_str_len(&s) == 0);
|
||||
TEST_CHECK(str == guf_str_const_cstr(&s));
|
||||
|
||||
guf_str_append_char(&s, 'a', 1024);
|
||||
str.append(1024, 'a');
|
||||
TEST_CHECK(guf_str_len(&s) == (ptrdiff_t)str.size());
|
||||
TEST_CHECK(guf_str_const_cstr(&s) == str);
|
||||
|
||||
guf_str_append_char(&s, 'b', 24);
|
||||
str.append(24, 'b');
|
||||
TEST_CHECK(guf_str_len(&s) == (ptrdiff_t)str.size());
|
||||
TEST_CHECK(guf_str_const_cstr(&s) == str);
|
||||
|
||||
guf_str_append_char(&s, 'c', 255);
|
||||
str.append(255, 'c');
|
||||
TEST_CHECK(guf_str_len(&s) == (ptrdiff_t)str.size());
|
||||
TEST_CHECK(guf_str_const_cstr(&s) == str);
|
||||
|
||||
*guf_str_at(&s, 0) = '<';
|
||||
str.at(0) = '<';
|
||||
TEST_CHECK(guf_str_len(&s) == (ptrdiff_t)str.size());
|
||||
TEST_CHECK(guf_str_const_cstr(&s) == str);
|
||||
|
||||
*guf_str_at(&s, guf_str_len(&s) - 1) = '>';
|
||||
str.at(str.size() - 1) = '>';
|
||||
TEST_CHECK(guf_str_len(&s) == (ptrdiff_t)str.size());
|
||||
TEST_CHECK(guf_str_const_cstr(&s) == str);
|
||||
|
||||
guf_err err = GUF_ERR_NONE;
|
||||
TEST_CHECK(NULL == guf_str_try_at(&s, guf_str_len(&s), &err));
|
||||
TEST_CHECK(err != GUF_ERR_NONE && err == GUF_ERR_IDX_RANGE);
|
||||
err = GUF_ERR_NONE;
|
||||
TEST_CHECK(NULL == guf_str_try_at(&s, -1, &err));
|
||||
TEST_CHECK(err != GUF_ERR_NONE && err == GUF_ERR_IDX_RANGE);
|
||||
|
||||
guf_str_free(&s, NULL);
|
||||
TEST_CHECK(guf_str_is_uninit(&s));
|
||||
}
|
||||
|
||||
void StrTest::test_append_char(std::string str, bool include_null)
|
||||
{
|
||||
guf_str s0 = guf_str_new(guf_str_view{.str = str.c_str(), .len = (ptrdiff_t)str.size()}, &allocator);
|
||||
|
||||
TEST_CHECK((ptrdiff_t)str.size() == guf_str_len(&s0));
|
||||
TEST_CHECK((str == std::string_view{guf_str_const_cstr(&s0), (size_t)guf_str_len(&s0)}));
|
||||
|
||||
for (int i = include_null ? 0 : 1; i < 128; ++i) {
|
||||
char ch = (char)i;
|
||||
guf_str_append_one_char(&s0, ch);
|
||||
str.append(1, ch);
|
||||
TEST_CHECK(guf_str_len(&s0) == (ptrdiff_t)str.size());
|
||||
TEST_CHECK((str == std::string_view{guf_str_const_cstr(&s0), (size_t)guf_str_len(&s0)}));
|
||||
}
|
||||
|
||||
for (int i = include_null ? 0 : 1; i < 128; ++i) {
|
||||
char ch = (char)i;
|
||||
guf_str_append_char(&s0, ch, i);
|
||||
str.append(i, ch);
|
||||
TEST_CHECK(guf_str_len(&s0) == (ptrdiff_t)str.size());
|
||||
TEST_CHECK((str == std::string_view{guf_str_const_cstr(&s0), (size_t)guf_str_len(&s0)}));
|
||||
guf_str_append_char(&s0, ch, i * 16);
|
||||
str.append(i * 16, ch);
|
||||
TEST_CHECK(guf_str_len(&s0) == (ptrdiff_t)str.size());
|
||||
TEST_CHECK((str == std::string_view{guf_str_const_cstr(&s0), (size_t)guf_str_len(&s0)}));
|
||||
}
|
||||
|
||||
guf_str_free(&s0, NULL);
|
||||
TEST_CHECK(guf_str_is_uninit(&s0));
|
||||
}
|
||||
|
||||
void StrTest::append_str(const std::string& a, const std::string& b)
|
||||
{
|
||||
std::string str0 = a;
|
||||
guf_str s0 = guf_str_new(guf_str_view{.str = str0.c_str(), .len = (ptrdiff_t)str0.size()}, &allocator);
|
||||
TEST_CHECK(guf_str_len(&s0) == (ptrdiff_t)str0.size());
|
||||
TEST_CHECK((str0 == std::string_view{guf_str_const_cstr(&s0), (size_t)guf_str_len(&s0)}));
|
||||
TEST_CHECK((str0 == std::string_view{guf_str_cstr(&s0), (size_t)guf_str_len(&s0)}));
|
||||
|
||||
for (int i = 0; i <= 64; ++i) {
|
||||
str0.append(b);
|
||||
guf_str_append(&s0, guf_str_view{.str = b.c_str(), .len = (ptrdiff_t)b.size()});
|
||||
TEST_CHECK(guf_str_len(&s0) == (ptrdiff_t)str0.size());
|
||||
TEST_CHECK((str0 == std::string_view{guf_str_const_cstr(&s0), (size_t)guf_str_len(&s0)}));
|
||||
TEST_CHECK((str0 == std::string_view{guf_str_cstr(&s0), (size_t)guf_str_len(&s0)}));
|
||||
}
|
||||
|
||||
guf_str_free(&s0, NULL);
|
||||
TEST_CHECK(guf_str_is_uninit(&s0));
|
||||
}
|
||||
|
||||
std::vector<std::string_view> StrTest::test_popsplit(std::string_view str, std::string_view delim)
|
||||
{
|
||||
std::vector<std::string_view> result = {};
|
||||
|
||||
if (delim.size() > 0) { // NOTE: str.find with an empty delimiter returns 0, not std::string::npos
|
||||
std::string_view src_cpp = str;
|
||||
for (size_t idx = src_cpp.find(delim, 0); src_cpp.size() > 0; idx = src_cpp.find(delim, 0)) {
|
||||
result.push_back(src_cpp.substr(0, idx));
|
||||
if (idx == std::string::npos) {
|
||||
break;
|
||||
}
|
||||
src_cpp = src_cpp.substr(idx + delim.size());
|
||||
}
|
||||
} else {
|
||||
result.push_back(str);
|
||||
}
|
||||
|
||||
|
||||
const guf_str_view delim_sv = guf_str_view{.str = delim.data(), .len = (ptrdiff_t)delim.size()};
|
||||
guf_str_view src = guf_str_view{.str = str.data(), .len = (ptrdiff_t)str.size()};
|
||||
size_t n = 0;
|
||||
do {
|
||||
const guf_str_view popped = guf_str_view_pop_split(&src, delim_sv);
|
||||
TEST_CHECK(n < result.size());
|
||||
TEST_CHECK(std::string_view(popped.str, (size_t)popped.len) == result.at(n));
|
||||
const guf_str_view res = {.str = result.at(n).data(), .len = (ptrdiff_t)result.at(n).size()};
|
||||
TEST_CHECK(guf_str_view_equal(&popped, &res));
|
||||
TEST_CHECK(guf_str_view_equal_val_arg(popped, res));
|
||||
// std::cout << "guf: " << std::string_view{popped.str, (size_t)popped.len} << "\n";
|
||||
// std::cout << "cpp: " << std::string_view{res.str, (size_t)res.len} << "\n";
|
||||
++n;
|
||||
} while (src.len > 0);
|
||||
TEST_CHECK(n == result.size());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<std::string_view> StrTest::get_toks(std::string_view sv_in, const std::vector<std::string_view>& delims_in, bool preserve_delims, guf_str_tok_delim_opt opt)
|
||||
{
|
||||
const guf_str_view sv = guf_str_view{.str = sv_in.data(), .len = (ptrdiff_t)sv_in.size()};
|
||||
std::vector<guf_str_view> delims;
|
||||
for (const auto delim : delims_in) {
|
||||
delims.push_back(guf_str_view{.str = delim.data(), .len = (ptrdiff_t)delim.size()});
|
||||
}
|
||||
guf_str_tok_state tok_state = guf_str_tok_state_new(sv, delims.data(), std::ssize(delims), opt);
|
||||
|
||||
std::vector<std::string_view> toks_out;
|
||||
while (guf_str_tok_next(&tok_state, preserve_delims)) {
|
||||
if (tok_state.cur_tok.len > 0) {
|
||||
toks_out.push_back( std::string_view{tok_state.cur_tok.str, (size_t)tok_state.cur_tok.len});
|
||||
}
|
||||
if (preserve_delims && tok_state.cur_delim.len > 0) {
|
||||
toks_out.push_back( std::string_view{tok_state.cur_delim.str, (size_t)tok_state.cur_delim.len});
|
||||
}
|
||||
}
|
||||
TEST_CHECK(tok_state.done);
|
||||
const ptrdiff_t num_toks = preserve_delims ? tok_state.num_delims_read + tok_state.num_toks_read : tok_state.num_toks_read;
|
||||
TEST_CHECK(num_toks == std::ssize(toks_out));
|
||||
return toks_out;
|
||||
}
|
||||
33
src/test/test_str.hpp
Executable file
33
src/test/test_str.hpp
Executable file
@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include "test.hpp"
|
||||
extern "C"
|
||||
{
|
||||
#include "guf_str.h"
|
||||
#include "guf_alloc_libc.h"
|
||||
}
|
||||
|
||||
struct StrTest : public Test
|
||||
{
|
||||
StrTest(const std::string& nm) : Test(nm)
|
||||
{
|
||||
allocator_ctx.zero_init = false;
|
||||
guf_alloc_tracker_init(&allocator_ctx.tracker, 4, "StrTest_allocator", NULL, NULL);
|
||||
guf_libc_allocator_init(&allocator, &allocator_ctx);
|
||||
};
|
||||
|
||||
void run() override;
|
||||
|
||||
private:
|
||||
guf_allocator allocator;
|
||||
guf_libc_alloc_ctx allocator_ctx;
|
||||
|
||||
void test_init_free(std::string str);
|
||||
void test_init_empty();
|
||||
void test_append_char(std::string str, bool include_null = false);
|
||||
void append_str(const std::string& a, const std::string& b);
|
||||
std::vector<std::string_view> test_popsplit(std::string_view str, std::string_view delim);
|
||||
std::vector<std::string_view> get_toks(std::string_view sv_in, const std::vector<std::string_view>& delims_in, bool preserve_delims = false, guf_str_tok_delim_opt opt = GUF_STR_TOK_DELIM_OPT_MATCH_LONGEST);
|
||||
};
|
||||
387
src/test/test_utf8.cpp
Executable file
387
src/test/test_utf8.cpp
Executable file
@ -0,0 +1,387 @@
|
||||
#include "test_utf8.hpp"
|
||||
extern "C"
|
||||
{
|
||||
#include "guf_alloc_libc.h"
|
||||
#include "guf_str.h"
|
||||
#include "impls/dict_impl.h"
|
||||
}
|
||||
|
||||
/*
|
||||
UTF8Test:
|
||||
*/
|
||||
|
||||
void UTF8Test::run()
|
||||
{
|
||||
if (done) {
|
||||
return;
|
||||
}
|
||||
|
||||
push_check_name("read_utf8_chars");
|
||||
ptrdiff_t valid = 0, invalid = 0;
|
||||
read_utf8_chars(TEST_DATA_DIR "/" "utf8-test.txt", &valid, &invalid);
|
||||
TEST_CHECK(valid == 2635 && invalid == 0);
|
||||
read_utf8_chars(TEST_DATA_DIR "/" "bartleby.txt", &valid, &invalid);
|
||||
TEST_CHECK(valid > 16000 && invalid == 0);
|
||||
pop_check_name();
|
||||
|
||||
push_check_name("count_words");
|
||||
dbuf_str_view delims = dbuf_str_view_new(&allocator);
|
||||
for (size_t i = 0; i < GUF_ARR_SIZE(GUF_UTF8_WHITESPACE); ++i) {
|
||||
guf_str_view d = {.str = GUF_UTF8_WHITESPACE[i], .len = (ptrdiff_t)strlen(GUF_UTF8_WHITESPACE[i])};
|
||||
dbuf_str_view_push_val(&delims, d);
|
||||
}
|
||||
for (size_t i = 0; i < GUF_ARR_SIZE(GUF_UTF8_COMMON_PUNCT); ++i) {
|
||||
guf_str_view d = {.str = GUF_UTF8_COMMON_PUNCT[i], .len = (ptrdiff_t)strlen(GUF_UTF8_COMMON_PUNCT[i])};
|
||||
dbuf_str_view_push_val(&delims, d);
|
||||
}
|
||||
int words = count_words(TEST_DATA_DIR "/" "utf8-test.txt", &delims);
|
||||
TEST_CHECK(words == 422);
|
||||
int words_with_delims = count_words_with_delims(TEST_DATA_DIR "/" "utf8-test.txt", &delims);
|
||||
TEST_CHECK(words_with_delims == 950);
|
||||
|
||||
int words2 = count_words(TEST_DATA_DIR "/" "bartleby.txt", &delims);
|
||||
TEST_CHECK(words2 > 2048);
|
||||
|
||||
dbuf_str_view_free(&delims, NULL);
|
||||
pop_check_name();
|
||||
|
||||
push_check_name("encode_decode");
|
||||
encode_decode();
|
||||
encode_decode_file(TEST_DATA_DIR "/" "utf8-test.txt");
|
||||
encode_decode_file(TEST_DATA_DIR "/" "bartleby.txt");
|
||||
pop_check_name();
|
||||
|
||||
//guf_alloc_tracker_print(&allocator_ctx.tracker, NULL);
|
||||
TEST_CHECK(!guf_alloc_tracker_found_leak(&allocator_ctx.tracker));
|
||||
}
|
||||
|
||||
|
||||
bool UTF8Test::load_text(const char *fname)
|
||||
{
|
||||
FILE *in_file {nullptr};
|
||||
if (!in_file) {
|
||||
in_file = fopen(fname, "r");
|
||||
}
|
||||
|
||||
if (!in_file) {
|
||||
return false;
|
||||
}
|
||||
|
||||
dbuf_char_init(&text_buf, 128, &allocator);
|
||||
|
||||
int c = EOF;
|
||||
while ((c = fgetc(in_file)) != EOF) {
|
||||
dbuf_char_push_val(&text_buf, (char)c);
|
||||
text_vec.push_back((char)c);
|
||||
}
|
||||
fclose(in_file);
|
||||
|
||||
return TEST_CHECK(std::ssize(text_vec) == text_buf.size);
|
||||
}
|
||||
|
||||
void UTF8Test::free_text()
|
||||
{
|
||||
dbuf_char_free(&text_buf, NULL);
|
||||
text_vec.clear();
|
||||
}
|
||||
|
||||
|
||||
void UTF8Test::read_utf8_chars(const char *fname, ptrdiff_t *n_valid, ptrdiff_t *n_invalid)
|
||||
{
|
||||
GUF_ASSERT_RELEASE(load_text(fname));
|
||||
|
||||
ptrdiff_t valid_chars = 0, invalid_chars = 0, bytes = 0;
|
||||
guf_str_view input_str = {.str = text_buf.data, .len = text_buf.size};
|
||||
guf_utf8_char ch = {};
|
||||
for (guf_utf8_stat stat = guf_utf8_char_next(&ch, &input_str); stat != GUF_UTF8_READ_DONE; stat = guf_utf8_char_next(&ch, &input_str)) {
|
||||
if (stat == GUF_UTF8_READ_VALID) {
|
||||
++valid_chars;
|
||||
// printf("%s", ch.bytes);
|
||||
} else {
|
||||
++invalid_chars;
|
||||
// printf("::INVALID_UTF8_CHAR::");
|
||||
}
|
||||
bytes += guf_utf8_char_num_bytes(&ch);
|
||||
}
|
||||
TEST_CHECK(input_str.len == 0 && input_str.str == NULL);
|
||||
TEST_CHECK(bytes == text_buf.size);
|
||||
|
||||
// printf("\nread %td bytes\n", bytes);
|
||||
// printf("read %td valid and %td invalid utf-8 characters\n", valid_chars, invalid_chars);
|
||||
|
||||
free_text();
|
||||
|
||||
if (n_valid)
|
||||
*n_valid = valid_chars;
|
||||
if (n_invalid)
|
||||
*n_invalid = invalid_chars;
|
||||
}
|
||||
|
||||
int UTF8Test::count_words(const char *fname, const dbuf_str_view *delims)
|
||||
{
|
||||
GUF_ASSERT_RELEASE(load_text(fname));
|
||||
|
||||
int num_words = 0;
|
||||
|
||||
guf_str_tok_state tok_state = guf_str_tok_state_new(guf_str_view{.str = text_buf.data, .len = text_buf.size}, delims->data, delims->size, GUF_STR_TOK_DELIM_OPT_MATCH_LONGEST);
|
||||
while (guf_str_tok_next(&tok_state, false)) {
|
||||
TEST_CHECK(tok_state.cur_tok.len > 0);
|
||||
++num_words;
|
||||
}
|
||||
|
||||
free_text();
|
||||
return num_words;
|
||||
}
|
||||
|
||||
int UTF8Test::count_words_with_delims(const char *fname, const dbuf_str_view *delims)
|
||||
{
|
||||
GUF_ASSERT_RELEASE(load_text(fname));
|
||||
|
||||
int num_words = 0, num_delims = 0;
|
||||
guf_str_tok_state tok_state = guf_str_tok_state_new(guf_str_view{.str = text_buf.data, .len = text_buf.size}, delims->data, delims->size, GUF_STR_TOK_DELIM_OPT_MATCH_LONGEST);
|
||||
while (guf_str_tok_next(&tok_state, true)) {
|
||||
if (tok_state.cur_tok.len) {
|
||||
++num_words;
|
||||
// printf("'%.*s'\n", (int)tok_state.cur_tok.len, tok_state.cur_tok.str);
|
||||
}
|
||||
if (tok_state.cur_delim.len) {
|
||||
++num_delims;
|
||||
// if (tok_state.cur_delim.str[0] == '\n')
|
||||
// printf("'\\n'\n");
|
||||
// else
|
||||
// printf("'%.*s'\n", (int)tok_state.cur_delim.len, tok_state.cur_delim.str);
|
||||
}
|
||||
}
|
||||
free_text();
|
||||
return num_words + num_delims;
|
||||
}
|
||||
|
||||
void UTF8Test::encode_decode_file(const char *fname)
|
||||
{
|
||||
GUF_ASSERT_RELEASE(load_text(fname));
|
||||
|
||||
dbuf_i32 cp_buf = dbuf_i32_new(&allocator);
|
||||
|
||||
ptrdiff_t valid_chars = 0, invalid_chars = 0;
|
||||
guf_str_view input_str = {.str = text_buf.data, .len = text_buf.size};
|
||||
guf_utf8_char ch = {};
|
||||
for (guf_utf8_stat stat = guf_utf8_char_next(&ch, &input_str); stat != GUF_UTF8_READ_DONE; stat = guf_utf8_char_next(&ch, &input_str)) {
|
||||
if (stat == GUF_UTF8_READ_VALID) {
|
||||
++valid_chars;
|
||||
const int32_t codepoint = guf_utf8_decode(&ch);
|
||||
TEST_CHECK(codepoint >= 0);
|
||||
dbuf_i32_push_val(&cp_buf, codepoint);
|
||||
} else {
|
||||
++invalid_chars;
|
||||
const int32_t codepoint = guf_utf8_decode(&ch);
|
||||
TEST_CHECK(codepoint < 0);
|
||||
dbuf_i32_push_val(&cp_buf, -1);
|
||||
}
|
||||
}
|
||||
TEST_CHECK(cp_buf.size == valid_chars + invalid_chars);
|
||||
|
||||
guf_str_view in_str = {.str = text_buf.data, .len = text_buf.size};
|
||||
GUF_CNT_FOREACH(&cp_buf, dbuf_i32, it) {
|
||||
GUF_ASSERT_RELEASE(it.ptr);
|
||||
const int32_t codepoint = *it.ptr;
|
||||
guf_utf8_char utf8_ch = {};
|
||||
const guf_utf8_stat stat = guf_utf8_char_next(&utf8_ch, &in_str);
|
||||
if (codepoint >= 0) {
|
||||
TEST_CHECK(stat == GUF_UTF8_READ_VALID);
|
||||
guf_utf8_char encoded_ch = {};
|
||||
TEST_CHECK(guf_utf8_encode(&encoded_ch, codepoint));
|
||||
TEST_CHECK(guf_utf8_equal(&encoded_ch, &utf8_ch));
|
||||
}
|
||||
}
|
||||
guf_utf8_char utf8_ch = {};
|
||||
const guf_utf8_stat stat = guf_utf8_char_next(&utf8_ch, &in_str);
|
||||
TEST_CHECK(stat == GUF_UTF8_READ_DONE);
|
||||
|
||||
dbuf_i32_free(&cp_buf, NULL);
|
||||
|
||||
free_text();
|
||||
}
|
||||
|
||||
void UTF8Test::encode_decode()
|
||||
{
|
||||
guf_utf8_char utf8 = {0};
|
||||
|
||||
// 1 byte characters.
|
||||
for (uint8_t ascii = 0; ascii <= 0x7F; ++ascii) {
|
||||
TEST_CHECK(guf_utf8_encode(&utf8, ascii));
|
||||
TEST_CHECK(guf_utf8_char_num_bytes(&utf8) == 1);
|
||||
TEST_CHECK(utf8.bytes[0] == ascii);
|
||||
TEST_CHECK(utf8.bytes[1] == '\0');
|
||||
TEST_CHECK(guf_utf8_decode(&utf8) == ascii);
|
||||
}
|
||||
|
||||
// 2 byte characters:
|
||||
TEST_CHECK(guf_utf8_encode(&utf8, 0x00E6)); // "æ" (Latin Small Letter Ae)
|
||||
TEST_CHECK(guf_utf8_char_num_bytes(&utf8) == 2);
|
||||
TEST_CHECK(utf8.bytes[0] == '\xC3' && utf8.bytes[1] == '\xA6');
|
||||
TEST_CHECK(utf8.bytes[2] == '\0');
|
||||
TEST_CHECK(guf_utf8_decode(&utf8) == 0x00E6);
|
||||
|
||||
TEST_CHECK(guf_utf8_encode(&utf8, 0x00E5)); // "å" (Latin Small Letter A with Ring Above)
|
||||
TEST_CHECK(guf_utf8_char_num_bytes(&utf8) == 2);
|
||||
TEST_CHECK(utf8.bytes[0] == '\xC3' && utf8.bytes[1] == '\xA5');
|
||||
TEST_CHECK(utf8.bytes[2] == '\0');
|
||||
TEST_CHECK(guf_utf8_decode(&utf8) == 0x00E5);
|
||||
|
||||
TEST_CHECK(guf_utf8_encode(&utf8, 0x00F8)); // "ø" (Latin Small Letter O with Stroke)
|
||||
TEST_CHECK(guf_utf8_char_num_bytes(&utf8) == 2);
|
||||
TEST_CHECK(utf8.bytes[0] == '\xC3' && utf8.bytes[1] == '\xB8');
|
||||
TEST_CHECK(utf8.bytes[2] == '\0');
|
||||
TEST_CHECK(guf_utf8_decode(&utf8) == 0x00F8);
|
||||
|
||||
TEST_CHECK(guf_utf8_encode(&utf8, 0x00E4)); // "ä" (Latin Small Letter A with Diaeresis)
|
||||
TEST_CHECK(guf_utf8_char_num_bytes(&utf8) == 2);
|
||||
TEST_CHECK(utf8.bytes[0] == '\xC3' && utf8.bytes[1] == '\xA4');
|
||||
TEST_CHECK(utf8.bytes[2] == '\0');
|
||||
TEST_CHECK(guf_utf8_decode(&utf8) == 0x00E4);
|
||||
|
||||
TEST_CHECK(guf_utf8_encode(&utf8, 0x00F6)); // "ö" (Latin Small Letter O with Diaeresis)
|
||||
TEST_CHECK(guf_utf8_char_num_bytes(&utf8) == 2);
|
||||
TEST_CHECK(utf8.bytes[0] == '\xC3' && utf8.bytes[1] == '\xB6');
|
||||
TEST_CHECK(utf8.bytes[2] == '\0');
|
||||
TEST_CHECK(guf_utf8_decode(&utf8) == 0x00F6);
|
||||
|
||||
TEST_CHECK(guf_utf8_encode(&utf8, 0x00D6)); // "Ö" (Latin Capital Letter O with Diaeresis)
|
||||
TEST_CHECK(guf_utf8_char_num_bytes(&utf8) == 2);
|
||||
TEST_CHECK(utf8.bytes[0] == '\xC3' && utf8.bytes[1] == '\x96');
|
||||
TEST_CHECK(utf8.bytes[2] == '\0');
|
||||
TEST_CHECK(guf_utf8_decode(&utf8) == 0x00D6);
|
||||
|
||||
TEST_CHECK(guf_utf8_encode(&utf8, 0x00FC)); // "ü" (Latin Small Letter U with Diaeresis)
|
||||
TEST_CHECK(guf_utf8_char_num_bytes(&utf8) == 2);
|
||||
TEST_CHECK(utf8.bytes[0] == '\xC3' && utf8.bytes[1] == '\xBC');
|
||||
TEST_CHECK(utf8.bytes[2] == '\0');
|
||||
TEST_CHECK(guf_utf8_decode(&utf8) == 0x00FC);
|
||||
|
||||
TEST_CHECK(guf_utf8_encode(&utf8, 0x00B5)); // "µ" (Micro Sign)
|
||||
TEST_CHECK(guf_utf8_char_num_bytes(&utf8) == 2);
|
||||
TEST_CHECK(utf8.bytes[0] == '\xC2' && utf8.bytes[1] == '\xB5');
|
||||
TEST_CHECK(utf8.bytes[2] == '\0');
|
||||
TEST_CHECK(guf_utf8_decode(&utf8) == 0x00B5);
|
||||
|
||||
TEST_CHECK(guf_utf8_encode(&utf8, 0x030A)); // "◌̊" (Combining Ring Above)
|
||||
TEST_CHECK(guf_utf8_char_num_bytes(&utf8) == 2);
|
||||
TEST_CHECK(utf8.bytes[0] == '\xCC' && utf8.bytes[1] == '\x8A');
|
||||
TEST_CHECK(utf8.bytes[2] == '\0');
|
||||
TEST_CHECK(guf_utf8_decode(&utf8) == 0x030A);
|
||||
|
||||
// 3 byte characters:
|
||||
TEST_CHECK(guf_utf8_encode(&utf8, 0x7121)); // "無" (Nothingness; CJK Unified Ideograph-7121)
|
||||
TEST_CHECK(guf_utf8_char_num_bytes(&utf8) == 3);
|
||||
TEST_CHECK(!guf_utf8_equal(&utf8, &GUF_UTF8_REPLACEMENT_CHAR));
|
||||
TEST_CHECK(utf8.bytes[0] == '\xE7' && utf8.bytes[1] == '\x84' && utf8.bytes[2] == '\xA1');
|
||||
TEST_CHECK(utf8.bytes[3] == '\0');
|
||||
TEST_CHECK(guf_utf8_decode(&utf8) == 0x7121);
|
||||
|
||||
TEST_CHECK(guf_utf8_encode(&utf8, 0x201E)); // "„" (Double Low-9 Quotation Mark)
|
||||
TEST_CHECK(guf_utf8_char_num_bytes(&utf8) == 3);
|
||||
TEST_CHECK(!guf_utf8_equal(&utf8, &GUF_UTF8_REPLACEMENT_CHAR));
|
||||
TEST_CHECK(utf8.bytes[0] == '\xE2' && utf8.bytes[1] == '\x80' && utf8.bytes[2] == '\x9E');
|
||||
TEST_CHECK(utf8.bytes[3] == '\0');
|
||||
TEST_CHECK(guf_utf8_decode(&utf8) == 0x201E);
|
||||
|
||||
TEST_CHECK(guf_utf8_encode(&utf8, 0x20AC)); // "€" (Euro Sign)
|
||||
TEST_CHECK(guf_utf8_char_num_bytes(&utf8) == 3);
|
||||
TEST_CHECK(!guf_utf8_equal(&utf8, &GUF_UTF8_REPLACEMENT_CHAR));
|
||||
TEST_CHECK(utf8.bytes[0] == '\xE2' && utf8.bytes[1] == '\x82' && utf8.bytes[2] == '\xAC');
|
||||
TEST_CHECK(utf8.bytes[3] == '\0');
|
||||
TEST_CHECK(guf_utf8_decode(&utf8) == 0x20AC);
|
||||
|
||||
TEST_CHECK(guf_utf8_encode(&utf8, 0xFC51)); // "ﱑ" (Arabic Ligature Heh with Jeem Isolated Form)
|
||||
TEST_CHECK(guf_utf8_char_num_bytes(&utf8) == 3);
|
||||
TEST_CHECK(!guf_utf8_equal(&utf8, &GUF_UTF8_REPLACEMENT_CHAR));
|
||||
TEST_CHECK(utf8.bytes[0] == '\xEF' && utf8.bytes[1] == '\xB1' && utf8.bytes[2] == '\x91');
|
||||
TEST_CHECK(utf8.bytes[3] == '\0');
|
||||
TEST_CHECK(guf_utf8_decode(&utf8) == 0xFC51);
|
||||
|
||||
TEST_CHECK(guf_utf8_encode(&utf8, 0x1AA3)); // "᪣" (Tai Tham Sign Keow)
|
||||
TEST_CHECK(guf_utf8_char_num_bytes(&utf8) == 3);
|
||||
TEST_CHECK(!guf_utf8_equal(&utf8, &GUF_UTF8_REPLACEMENT_CHAR));
|
||||
TEST_CHECK(utf8.bytes[0] == '\xE1' && utf8.bytes[1] == '\xAA' && utf8.bytes[2] == '\xA3');
|
||||
TEST_CHECK(utf8.bytes[3] == '\0');
|
||||
TEST_CHECK(guf_utf8_decode(&utf8) == 0x1AA3);
|
||||
|
||||
TEST_CHECK(guf_utf8_encode(&utf8, GUF_UTF8_REPLACEMENT_CHAR_CODEPOINT)); // "<22>" (Replacement Character)
|
||||
TEST_CHECK(guf_utf8_char_num_bytes(&utf8) == 3);
|
||||
TEST_CHECK(utf8.bytes[0] == '\xEF' && utf8.bytes[1] == '\xBF' && utf8.bytes[2] == '\xBD');
|
||||
TEST_CHECK(utf8.bytes[3] == '\0');
|
||||
TEST_CHECK(guf_utf8_equal(&utf8, &GUF_UTF8_REPLACEMENT_CHAR));
|
||||
TEST_CHECK(guf_utf8_decode(&utf8) == GUF_UTF8_REPLACEMENT_CHAR_CODEPOINT);
|
||||
|
||||
// 4 byte characters:
|
||||
TEST_CHECK(guf_utf8_encode(&utf8, 0x1F308)); // "🌈" (Rainbow)
|
||||
TEST_CHECK(guf_utf8_char_num_bytes(&utf8) == 4);
|
||||
TEST_CHECK(utf8.bytes[0] == '\xF0' && utf8.bytes[1] == '\x9F' && utf8.bytes[2] == '\x8C' && utf8.bytes[3] == '\x88');
|
||||
TEST_CHECK(guf_utf8_decode(&utf8) == 0x1F308);
|
||||
|
||||
TEST_CHECK(guf_utf8_encode(&utf8, 0x130B8)); // "𓂸" (Egyptian Hieroglyph D052)
|
||||
TEST_CHECK(guf_utf8_char_num_bytes(&utf8) == 4);
|
||||
TEST_CHECK(utf8.bytes[0] == '\xF0' && utf8.bytes[1] == '\x93' && utf8.bytes[2] == '\x82' && utf8.bytes[3] == '\xB8');
|
||||
TEST_CHECK(guf_utf8_decode(&utf8) == 0x130B8);
|
||||
|
||||
TEST_CHECK(guf_utf8_encode(&utf8, 0x1F97A)); // "🥺" (Face with Pleading Eyes)
|
||||
TEST_CHECK(guf_utf8_char_num_bytes(&utf8) == 4);
|
||||
TEST_CHECK(utf8.bytes[0] == '\xF0' && utf8.bytes[1] == '\x9F' && utf8.bytes[2] == '\xA5' && utf8.bytes[3] == '\xBA');
|
||||
TEST_CHECK(guf_utf8_decode(&utf8) == 0x1F97A);
|
||||
|
||||
TEST_CHECK(guf_utf8_encode(&utf8, 0x1F980)); // "🦀" (Crab)
|
||||
TEST_CHECK(guf_utf8_char_num_bytes(&utf8) == 4);
|
||||
TEST_CHECK(utf8.bytes[0] == '\xF0' && utf8.bytes[1] == '\x9F' && utf8.bytes[2] == '\xA6' && utf8.bytes[3] == '\x80');
|
||||
TEST_CHECK(guf_utf8_decode(&utf8) == 0x1F980);
|
||||
|
||||
// Invalid characters:
|
||||
utf8 = {.bytes = {'\xC0', '\x80', 0, 0}};
|
||||
TEST_CHECK(guf_utf8_decode(&utf8) < 0);
|
||||
|
||||
utf8 = {.bytes = {'\xC0', 0, 0, 0}};
|
||||
TEST_CHECK(guf_utf8_decode(&utf8) < 0);
|
||||
|
||||
utf8 = {.bytes = {'\x80', 0, 0, 0}};
|
||||
TEST_CHECK(guf_utf8_decode(&utf8) < 0);
|
||||
|
||||
// "The definition of UTF-8 prohibits encoding character numbers between U+D800 and U+DFFF" (surrogate pairs).
|
||||
TEST_CHECK(!guf_utf8_encode(&utf8, 0xD800));
|
||||
TEST_CHECK(guf_utf8_equal(&utf8, &GUF_UTF8_REPLACEMENT_CHAR));
|
||||
TEST_CHECK(guf_utf8_decode(&utf8) == GUF_UTF8_REPLACEMENT_CHAR_CODEPOINT);
|
||||
|
||||
TEST_CHECK(!guf_utf8_encode(&utf8, 0xDFFF));
|
||||
TEST_CHECK(guf_utf8_equal(&utf8, &GUF_UTF8_REPLACEMENT_CHAR));
|
||||
TEST_CHECK(guf_utf8_decode(&utf8) == GUF_UTF8_REPLACEMENT_CHAR_CODEPOINT);
|
||||
|
||||
TEST_CHECK(!guf_utf8_encode(&utf8, 0xDA00));
|
||||
TEST_CHECK(guf_utf8_equal(&utf8, &GUF_UTF8_REPLACEMENT_CHAR));
|
||||
TEST_CHECK(guf_utf8_decode(&utf8) == GUF_UTF8_REPLACEMENT_CHAR_CODEPOINT);
|
||||
|
||||
char buf[] = {'\x2F', '\xC0', '\xAE', '\x2E', '\x2F'};
|
||||
guf_str_view input_str = {.str = buf, .len = GUF_ARR_SIZE(buf)};
|
||||
guf_utf8_char ch = {};
|
||||
int valid_chars = 0, invalid_chars = 0;
|
||||
for (guf_utf8_stat stat = guf_utf8_char_next(&ch, &input_str); stat != GUF_UTF8_READ_DONE; stat = guf_utf8_char_next(&ch, &input_str)) {
|
||||
if (stat == GUF_UTF8_READ_VALID) {
|
||||
++valid_chars;
|
||||
} else {
|
||||
++invalid_chars;
|
||||
}
|
||||
}
|
||||
TEST_CHECK(invalid_chars == 2 && valid_chars == 3);
|
||||
|
||||
char buf2[] = {'\xE0', '\x80', 'a', 'b', 'c'}; // 1 invalid 3-byte-character, 2 valid 1-byte-characters
|
||||
input_str = {.str = buf2, .len = GUF_ARR_SIZE(buf2)};
|
||||
ch = {};
|
||||
valid_chars = invalid_chars = 0;
|
||||
for (guf_utf8_stat stat = guf_utf8_char_next(&ch, &input_str); stat != GUF_UTF8_READ_DONE; stat = guf_utf8_char_next(&ch, &input_str)) {
|
||||
if (stat == GUF_UTF8_READ_VALID) {
|
||||
// printf("%s", ch.bytes);
|
||||
++valid_chars;
|
||||
} else {
|
||||
// printf("%s", GUF_UTF8_REPLACEMENT_CHAR.bytes);
|
||||
++invalid_chars;
|
||||
}
|
||||
}
|
||||
TEST_CHECK(invalid_chars == 1 && valid_chars == 2);
|
||||
}
|
||||
36
src/test/test_utf8.hpp
Executable file
36
src/test/test_utf8.hpp
Executable file
@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
#include <vector>
|
||||
#include "test.hpp"
|
||||
extern "C"
|
||||
{
|
||||
#include "impls/dbuf_impl.h"
|
||||
#include "guf_alloc_libc.h"
|
||||
}
|
||||
|
||||
struct UTF8Test : public Test
|
||||
{
|
||||
UTF8Test(const std::string& nm) : Test(nm)
|
||||
{
|
||||
allocator_ctx.zero_init = false;
|
||||
guf_alloc_tracker_init(&allocator_ctx.tracker, 5, "UTF8Test_allocator", NULL, NULL);
|
||||
guf_libc_allocator_init(&allocator, &allocator_ctx);
|
||||
};
|
||||
|
||||
void run() override;
|
||||
|
||||
private:
|
||||
guf_allocator allocator;
|
||||
guf_libc_alloc_ctx allocator_ctx;
|
||||
|
||||
dbuf_char text_buf {};
|
||||
std::vector<char> text_vec;
|
||||
|
||||
bool load_text(const char *fname);
|
||||
void free_text();
|
||||
|
||||
void read_utf8_chars(const char *fname, ptrdiff_t *n_valid, ptrdiff_t *n_invalid);
|
||||
int count_words(const char *fname, const dbuf_str_view *delims);
|
||||
int count_words_with_delims(const char *fname, const dbuf_str_view *delims);
|
||||
void encode_decode_file(const char *fname);
|
||||
void encode_decode();
|
||||
};
|
||||
@ -1,13 +0,0 @@
|
||||
„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.“
|
||||
29
testgen.py
29
testgen.py
@ -1,29 +0,0 @@
|
||||
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)
|
||||
22
todo.txt
Executable file
22
todo.txt
Executable file
@ -0,0 +1,22 @@
|
||||
- fix readonly str/uninit?
|
||||
|
||||
- dict: allow set
|
||||
|
||||
- guf_stack, guf_queue, guf_dqueue, guf_prio_queue (using a heap), guf_ringbuf
|
||||
- sort: add cpp #ifdef to remove restrict from declaration
|
||||
|
||||
- str: start_with, ends_with, find
|
||||
|
||||
- guf_wrapping_mul_TYPE: Not 100 % sure if it does not depend on implementation defined behaviour, but it shouldn't
|
||||
- https://en.cppreference.com/w/c/types/integer (apparently the signed fixed width integer types are guaranteed to be two's complement...)
|
||||
|
||||
- tests for guf_dict with GUF_DICT_64_BIT_IDX (and also hash32/hash64); maybe pass kv_type to insert to avoid copy
|
||||
- dict elems shrink to fit; allow to pass GUF_DBUF_USE_GROWTH_FAC_ONE_POINT_FIVE; start capacity (for elems and kv_indices?)
|
||||
- dict: if load factor is high due to mostly tombstones, just try rehashing without resizing first?
|
||||
|
||||
- bench
|
||||
|
||||
- no guf_init.h
|
||||
|
||||
- unicode normalisation
|
||||
- handle right-to-left text properly
|
||||
642
tools/ckdint-gen.py
Executable file
642
tools/ckdint-gen.py
Executable file
@ -0,0 +1,642 @@
|
||||
"""
|
||||
Generate typesafe checked arithmetic functions for libguf/src/guf_math_ckdint.h
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Tuple
|
||||
import textwrap
|
||||
|
||||
@dataclass
|
||||
class IntType:
|
||||
INT_TYPE: str
|
||||
INT_TYPE_ABBR: str
|
||||
INT_MIN: str
|
||||
INT_MAX: str
|
||||
UINT_TYPE: str
|
||||
UINT_MAX: str
|
||||
is_optional: bool = False
|
||||
|
||||
|
||||
@dataclass
|
||||
class UintType:
|
||||
INT_TYPE: str
|
||||
INT_TYPE_ABBR: str
|
||||
INT_MIN: str
|
||||
INT_MAX: str
|
||||
is_optional: bool = False
|
||||
|
||||
def generate_ckdint_functions(int_types: list, uint_types: list) -> Tuple[str, str]:
|
||||
|
||||
ckd_add_sub_uint_header = textwrap.dedent("""\
|
||||
GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_add_{type_abbr}({type} a, {type} b);
|
||||
GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_sub_{type_abbr}({type} a, {type} b);
|
||||
GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_mul_{type_abbr}({type} a, {type} b);
|
||||
""")
|
||||
|
||||
ckd_add_sub_uint = textwrap.dedent("""\
|
||||
GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_add_{type_abbr}({type} a, {type} b)
|
||||
{{
|
||||
if (b > 0 && a > {int_max} - b) {{
|
||||
return GUF_MATH_CKD_OVERFLOW_POS;
|
||||
}} else {{
|
||||
return GUF_MATH_CKD_SUCCESS;
|
||||
}}
|
||||
}}
|
||||
GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_sub_{type_abbr}({type} a, {type} b)
|
||||
{{
|
||||
if (b > a) {{
|
||||
return GUF_MATH_CKD_OVERFLOW_NEG;
|
||||
}} else {{
|
||||
return GUF_MATH_CKD_SUCCESS;
|
||||
}}
|
||||
}}
|
||||
GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_mul_{type_abbr}({type} a, {type} b)
|
||||
{{
|
||||
const {type} c = 1u * a * b;
|
||||
return a != 0 && ((1u * c / a) != b) ? GUF_MATH_CKD_OVERFLOW_POS : GUF_MATH_CKD_SUCCESS;
|
||||
}}
|
||||
""")
|
||||
|
||||
ckd_add_sub_uint_LEAST = textwrap.dedent("""\
|
||||
GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_add_{type_abbr}({type} a, {type} b)
|
||||
{{
|
||||
a = GUF_UWRAP_{bits}(a);
|
||||
b = GUF_UWRAP_{bits}(b);
|
||||
if (b > 0 && a > {int_max} - b) {{
|
||||
return GUF_MATH_CKD_OVERFLOW_POS;
|
||||
}} else {{
|
||||
return GUF_MATH_CKD_SUCCESS;
|
||||
}}
|
||||
}}
|
||||
GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_sub_{type_abbr}({type} a, {type} b)
|
||||
{{
|
||||
a = GUF_UWRAP_{bits}(a);
|
||||
b = GUF_UWRAP_{bits}(b);
|
||||
if (b > a) {{
|
||||
return GUF_MATH_CKD_OVERFLOW_NEG;
|
||||
}} else {{
|
||||
return GUF_MATH_CKD_SUCCESS;
|
||||
}}
|
||||
}}
|
||||
GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_mul_{type_abbr}({type} a, {type} b)
|
||||
{{
|
||||
a = GUF_UWRAP_{bits}(a);
|
||||
b = GUF_UWRAP_{bits}(b);
|
||||
const {type} c = GUF_UWRAP_{bits}( 1u * a * b );
|
||||
return a != 0 && ((1u * c / a) != b) ? GUF_MATH_CKD_OVERFLOW_POS : GUF_MATH_CKD_SUCCESS;
|
||||
}}
|
||||
""")
|
||||
|
||||
ckd_add_sub_int_header = textwrap.dedent("""\
|
||||
GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_add_{type_abbr}({type} a, {type} b);
|
||||
GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_sub_{type_abbr}({type} a, {type} b);
|
||||
GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_mul_{type_abbr}({type} a, {type} b);
|
||||
""")
|
||||
|
||||
ckd_add_sub_int = textwrap.dedent("""\
|
||||
GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_add_{type_abbr}({type} a, {type} b)
|
||||
{{
|
||||
if (b > 0 && a > {int_max} - b) {{
|
||||
return GUF_MATH_CKD_OVERFLOW_POS;
|
||||
}} else if (b < 0 && a < {int_min} - b) {{
|
||||
return GUF_MATH_CKD_OVERFLOW_NEG;
|
||||
}} else {{
|
||||
return GUF_MATH_CKD_SUCCESS;
|
||||
}}
|
||||
}}
|
||||
GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_sub_{type_abbr}({type} a, {type} b)
|
||||
{{
|
||||
if (b < 0 && a > {int_max} + b) {{
|
||||
return GUF_MATH_CKD_OVERFLOW_POS;
|
||||
}} else if (b > 0 && a < {int_min} + b) {{
|
||||
return GUF_MATH_CKD_OVERFLOW_NEG;
|
||||
}} else {{
|
||||
return GUF_MATH_CKD_SUCCESS;
|
||||
}}
|
||||
}}
|
||||
GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_mul_{type_abbr}({type} a, {type} b)
|
||||
{{
|
||||
if (b > 0) {{
|
||||
if (a > {int_max} / b) {{
|
||||
return GUF_MATH_CKD_OVERFLOW_POS;
|
||||
}} else if (a < {int_min} / b) {{
|
||||
return GUF_MATH_CKD_OVERFLOW_NEG;
|
||||
}} else {{
|
||||
return GUF_MATH_CKD_SUCCESS;
|
||||
}}
|
||||
}} else if (b < 0) {{
|
||||
if ({int_min} != -{int_max} && b == -1) {{ // Prevent potential ({int_min} / b) overflow for b == -1
|
||||
return a == {int_min} ? GUF_MATH_CKD_OVERFLOW_POS : GUF_MATH_CKD_SUCCESS;
|
||||
}} else if (a < {int_max} / b) {{
|
||||
return GUF_MATH_CKD_OVERFLOW_POS;
|
||||
}} else if (a > {int_min} / b) {{
|
||||
return GUF_MATH_CKD_OVERFLOW_NEG;
|
||||
}} else {{
|
||||
return GUF_MATH_CKD_SUCCESS;
|
||||
}}
|
||||
}} else {{
|
||||
return GUF_MATH_CKD_SUCCESS;
|
||||
}}
|
||||
}}
|
||||
""")
|
||||
|
||||
|
||||
|
||||
saturating_wrapping_int_header = textwrap.dedent("""\
|
||||
GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_add_{type_abbr}({type} a, {type} b, {type} *result);
|
||||
GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_sub_{type_abbr}({type} a, {type} b, {type} *result);
|
||||
GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_mul_{type_abbr}({type} a, {type} b, {type} *result);
|
||||
|
||||
GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_add_{type_abbr}({type} a, {type} b, {type} *result);
|
||||
GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_sub_{type_abbr}({type} a, {type} b, {type} *result);
|
||||
GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_mul_{type_abbr}({type} a, {type} b, {type} *result);
|
||||
""")
|
||||
|
||||
saturating_wrapping_int = textwrap.dedent("""\
|
||||
GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_add_{type_abbr}({type} a, {type} b, {type} *result)
|
||||
{{
|
||||
const guf_math_ckd_result check = guf_ckd_add_{type_abbr}(a, b);
|
||||
if (result) {{
|
||||
switch (check) {{
|
||||
case GUF_MATH_CKD_SUCCESS:
|
||||
*result = a + b;
|
||||
break;
|
||||
case GUF_MATH_CKD_OVERFLOW_POS:
|
||||
*result = {int_max};
|
||||
break;
|
||||
case GUF_MATH_CKD_OVERFLOW_NEG:
|
||||
*result = {int_min};
|
||||
break;
|
||||
default:
|
||||
GUF_ASSERT(false);
|
||||
}}
|
||||
}}
|
||||
return check;
|
||||
}}
|
||||
GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_sub_{type_abbr}({type} a, {type} b, {type} *result)
|
||||
{{
|
||||
const guf_math_ckd_result check = guf_ckd_sub_{type_abbr}(a, b);
|
||||
if (result) {{
|
||||
switch (check) {{
|
||||
case GUF_MATH_CKD_SUCCESS:
|
||||
*result = a - b;
|
||||
break;
|
||||
case GUF_MATH_CKD_OVERFLOW_POS:
|
||||
*result = {int_max};
|
||||
break;
|
||||
case GUF_MATH_CKD_OVERFLOW_NEG:
|
||||
*result = {int_min};
|
||||
break;
|
||||
default:
|
||||
GUF_ASSERT(false);
|
||||
}}
|
||||
}}
|
||||
return check;
|
||||
}}
|
||||
GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_mul_{type_abbr}({type} a, {type} b, {type} *result)
|
||||
{{
|
||||
const guf_math_ckd_result check = guf_ckd_mul_{type_abbr}(a, b);
|
||||
if (result) {{
|
||||
switch (check) {{
|
||||
case GUF_MATH_CKD_SUCCESS:
|
||||
*result = a * b;
|
||||
break;
|
||||
case GUF_MATH_CKD_OVERFLOW_POS:
|
||||
*result = {int_max};
|
||||
break;
|
||||
case GUF_MATH_CKD_OVERFLOW_NEG:
|
||||
*result = {int_min};
|
||||
break;
|
||||
default:
|
||||
GUF_ASSERT(false);
|
||||
}}
|
||||
}}
|
||||
return check;
|
||||
}}
|
||||
|
||||
GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_add_{type_abbr}({type} a, {type} b, {type} *result)
|
||||
{{
|
||||
const guf_math_ckd_result check = guf_ckd_add_{type_abbr}(a, b);
|
||||
if (result) {{
|
||||
switch (check) {{
|
||||
case GUF_MATH_CKD_SUCCESS:
|
||||
*result = a + b;
|
||||
break;
|
||||
case GUF_MATH_CKD_OVERFLOW_POS:
|
||||
*result = ({type})( (a + ({type}){int_min}) + (b + ({type}){int_min}) );
|
||||
break;
|
||||
case GUF_MATH_CKD_OVERFLOW_NEG:
|
||||
*result = ({type})( (a - ({type}){int_min}) + (b - ({type}){int_min}) );
|
||||
break;
|
||||
default:
|
||||
GUF_ASSERT(false);
|
||||
}}
|
||||
}}
|
||||
return check;
|
||||
}}
|
||||
GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_sub_{type_abbr}({type} a, {type} b, {type} *result)
|
||||
{{
|
||||
const guf_math_ckd_result check = guf_ckd_sub_{type_abbr}(a, b);
|
||||
if (result) {{
|
||||
switch (check) {{
|
||||
case GUF_MATH_CKD_SUCCESS:
|
||||
*result = a - b;
|
||||
break;
|
||||
case GUF_MATH_CKD_OVERFLOW_POS:
|
||||
GUF_ASSERT(b < 0);
|
||||
*result = ({type})( (a + ({type}){int_min}) - (b - ({type}){int_min}) ); // TODO: not sure
|
||||
break;
|
||||
case GUF_MATH_CKD_OVERFLOW_NEG:
|
||||
GUF_ASSERT(b > 0);
|
||||
*result = ({type})( (a - ({type}){int_min}) - (b + ({type}){int_min}) ); // TODO: not sure
|
||||
break;
|
||||
default:
|
||||
GUF_ASSERT(false);
|
||||
}}
|
||||
}}
|
||||
return check;
|
||||
}}
|
||||
GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_mul_{type_abbr}({type} a, {type} b, {type} *result)
|
||||
{{
|
||||
const guf_math_ckd_result check = guf_ckd_mul_{type_abbr}(a, b);
|
||||
if (result) {{
|
||||
switch (check) {{
|
||||
case GUF_MATH_CKD_SUCCESS:
|
||||
*result = a * b;
|
||||
break;
|
||||
case GUF_MATH_CKD_OVERFLOW_POS:
|
||||
case GUF_MATH_CKD_OVERFLOW_NEG: {{
|
||||
{uint_type} res = 1u * ({uint_type})a * ({uint_type})b;
|
||||
if (res > {int_max}) {{ // This is the fix for implementation defined conversion from unsigned to signed.
|
||||
const {uint_type} mod = ({uint_type}){int_max} + 1u;
|
||||
GUF_ASSERT(mod > 0);
|
||||
res = 1u * res % mod;
|
||||
*result = ({type}){int_min} + ({type})res;
|
||||
}} else {{
|
||||
*result = ({type})res;
|
||||
}}
|
||||
break;
|
||||
}}
|
||||
default:
|
||||
GUF_ASSERT(false);
|
||||
}}
|
||||
}}
|
||||
return check;
|
||||
}}
|
||||
""")
|
||||
|
||||
|
||||
saturating_wrapping_uint_header = textwrap.dedent("""\
|
||||
GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_add_{type_abbr}({type} a, {type} b, {type} *result);
|
||||
GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_sub_{type_abbr}({type} a, {type} b, {type} *result);
|
||||
GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_mul_{type_abbr}({type} a, {type} b, {type} *result);
|
||||
|
||||
GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_add_{type_abbr}({type} a, {type} b, {type} *result);
|
||||
GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_sub_{type_abbr}({type} a, {type} b, {type} *result);
|
||||
GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_mul_{type_abbr}({type} a, {type} b, {type} *result);
|
||||
""")
|
||||
|
||||
saturating_wrapping_uint = textwrap.dedent("""\
|
||||
GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_add_{type_abbr}({type} a, {type} b, {type} *result)
|
||||
{{
|
||||
const guf_math_ckd_result check = guf_ckd_add_{type_abbr}(a, b);
|
||||
GUF_ASSERT(check == GUF_MATH_CKD_SUCCESS || check == GUF_MATH_CKD_OVERFLOW_POS);
|
||||
if (result) {{
|
||||
switch (check) {{
|
||||
case GUF_MATH_CKD_SUCCESS:
|
||||
*result = a + b;
|
||||
break;
|
||||
case GUF_MATH_CKD_OVERFLOW_POS:
|
||||
*result = {int_max};
|
||||
break;
|
||||
default:
|
||||
GUF_ASSERT(false);
|
||||
}}
|
||||
}}
|
||||
return check;
|
||||
}}
|
||||
GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_sub_{type_abbr}({type} a, {type} b, {type} *result)
|
||||
{{
|
||||
const guf_math_ckd_result check = guf_ckd_sub_{type_abbr}(a, b);
|
||||
GUF_ASSERT(check == GUF_MATH_CKD_SUCCESS || check == GUF_MATH_CKD_OVERFLOW_NEG);
|
||||
if (result) {{
|
||||
switch (check) {{
|
||||
case GUF_MATH_CKD_SUCCESS:
|
||||
*result = a - b;
|
||||
break;
|
||||
case GUF_MATH_CKD_OVERFLOW_NEG:
|
||||
*result = 0;
|
||||
break;
|
||||
default:
|
||||
GUF_ASSERT(false);
|
||||
}}
|
||||
}}
|
||||
return check;
|
||||
}}
|
||||
GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_mul_{type_abbr}({type} a, {type} b, {type} *result)
|
||||
{{
|
||||
const guf_math_ckd_result check = guf_ckd_mul_{type_abbr}(a, b);
|
||||
GUF_ASSERT(check == GUF_MATH_CKD_SUCCESS || check == GUF_MATH_CKD_OVERFLOW_POS);
|
||||
if (result) {{
|
||||
switch (check) {{
|
||||
case GUF_MATH_CKD_SUCCESS:
|
||||
*result = a * b;
|
||||
break;
|
||||
case GUF_MATH_CKD_OVERFLOW_POS:
|
||||
*result = {int_max};
|
||||
break;
|
||||
default:
|
||||
GUF_ASSERT(false);
|
||||
}}
|
||||
}}
|
||||
return check;
|
||||
}}
|
||||
|
||||
GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_add_{type_abbr}({type} a, {type} b, {type} *result)
|
||||
{{
|
||||
const guf_math_ckd_result check = guf_ckd_add_{type_abbr}(a, b);
|
||||
if (result) {{
|
||||
*result = 1u * a + b;
|
||||
}}
|
||||
return check;
|
||||
}}
|
||||
GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_sub_{type_abbr}({type} a, {type} b, {type} *result)
|
||||
{{
|
||||
const guf_math_ckd_result check = guf_ckd_sub_{type_abbr}(a, b);
|
||||
if (result) {{
|
||||
*result = 1u * a - b;
|
||||
}}
|
||||
return check;
|
||||
}}
|
||||
GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_mul_{type_abbr}({type} a, {type} b, {type} *result)
|
||||
{{
|
||||
const guf_math_ckd_result check = guf_ckd_mul_{type_abbr}(a, b);
|
||||
if (result) {{
|
||||
*result = 1u * a * b;
|
||||
}}
|
||||
return check;
|
||||
}}
|
||||
""")
|
||||
|
||||
saturating_wrapping_uint_LEAST = textwrap.dedent("""\
|
||||
GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_add_{type_abbr}({type} a, {type} b, {type} *result)
|
||||
{{
|
||||
const guf_math_ckd_result check = guf_ckd_add_{type_abbr}(a, b);
|
||||
GUF_ASSERT(check == GUF_MATH_CKD_SUCCESS || check == GUF_MATH_CKD_OVERFLOW_POS);
|
||||
if (result) {{
|
||||
switch (check) {{
|
||||
case GUF_MATH_CKD_SUCCESS:
|
||||
*result = a + b;
|
||||
break;
|
||||
case GUF_MATH_CKD_OVERFLOW_POS:
|
||||
*result = {int_max};
|
||||
break;
|
||||
default:
|
||||
GUF_ASSERT(false);
|
||||
}}
|
||||
}}
|
||||
return check;
|
||||
}}
|
||||
GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_sub_{type_abbr}({type} a, {type} b, {type} *result)
|
||||
{{
|
||||
const guf_math_ckd_result check = guf_ckd_sub_{type_abbr}(a, b);
|
||||
GUF_ASSERT(check == GUF_MATH_CKD_SUCCESS || check == GUF_MATH_CKD_OVERFLOW_NEG);
|
||||
if (result) {{
|
||||
switch (check) {{
|
||||
case GUF_MATH_CKD_SUCCESS:
|
||||
*result = a - b;
|
||||
break;
|
||||
case GUF_MATH_CKD_OVERFLOW_NEG:
|
||||
*result = 0;
|
||||
break;
|
||||
default:
|
||||
GUF_ASSERT(false);
|
||||
}}
|
||||
}}
|
||||
return check;
|
||||
}}
|
||||
GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_mul_{type_abbr}({type} a, {type} b, {type} *result)
|
||||
{{
|
||||
const guf_math_ckd_result check = guf_ckd_mul_{type_abbr}(a, b);
|
||||
GUF_ASSERT(check == GUF_MATH_CKD_SUCCESS || check == GUF_MATH_CKD_OVERFLOW_POS);
|
||||
if (result) {{
|
||||
switch (check) {{
|
||||
case GUF_MATH_CKD_SUCCESS:
|
||||
*result = a * b;
|
||||
break;
|
||||
case GUF_MATH_CKD_OVERFLOW_POS:
|
||||
*result = {int_max};
|
||||
break;
|
||||
default:
|
||||
GUF_ASSERT(false);
|
||||
}}
|
||||
}}
|
||||
return check;
|
||||
}}
|
||||
|
||||
GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_add_{type_abbr}({type} a, {type} b, {type} *result)
|
||||
{{
|
||||
a = GUF_UWRAP_{bits}(a);
|
||||
b = GUF_UWRAP_{bits}(b);
|
||||
const guf_math_ckd_result check = guf_ckd_add_{type_abbr}(a, b);
|
||||
if (result) {{
|
||||
*result = GUF_UWRAP_{bits}( 1u * a + b );
|
||||
}}
|
||||
return check;
|
||||
}}
|
||||
GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_sub_{type_abbr}({type} a, {type} b, {type} *result)
|
||||
{{
|
||||
a = GUF_UWRAP_{bits}(a);
|
||||
b = GUF_UWRAP_{bits}(b);
|
||||
const guf_math_ckd_result check = guf_ckd_sub_{type_abbr}(a, b);
|
||||
if (result) {{
|
||||
*result = GUF_UWRAP_{bits}( 1u * a - b );
|
||||
}}
|
||||
return check;
|
||||
}}
|
||||
GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_mul_{type_abbr}({type} a, {type} b, {type} *result)
|
||||
{{
|
||||
a = GUF_UWRAP_{bits}(a);
|
||||
b = GUF_UWRAP_{bits}(b);
|
||||
const guf_math_ckd_result check = guf_ckd_mul_{type_abbr}(a, b);
|
||||
if (result) {{
|
||||
*result = GUF_UWRAP_{bits}( 1u * a * b );
|
||||
}}
|
||||
return check;
|
||||
}}
|
||||
""")
|
||||
|
||||
text_result = "// Signed integer arithmetic checks (generated with libguf/tools/ckdint-gen.py)\n"
|
||||
text_result_header = text_result
|
||||
for type in int_types:
|
||||
end = "\n"
|
||||
if type.is_optional:
|
||||
text_result += f"#ifdef {type.INT_MAX}\n"
|
||||
text_result_header += f"#ifdef {type.INT_MAX}\n"
|
||||
end = ""
|
||||
|
||||
text_result += ckd_add_sub_int.format(type = type.INT_TYPE, type_abbr = type.INT_TYPE_ABBR, int_min = type.INT_MIN, int_max = type.INT_MAX, uint_max = type.UINT_MAX) + end
|
||||
text_result_header += ckd_add_sub_int_header.format(type = type.INT_TYPE, type_abbr = type.INT_TYPE_ABBR, int_min = type.INT_MIN, int_max = type.INT_MAX, uint_max = type.UINT_MAX) + end
|
||||
|
||||
if type.is_optional:
|
||||
text_result += "#endif\n\n"
|
||||
text_result_header += "#endif\n\n"
|
||||
|
||||
|
||||
text_result += "\n// Unsigned integer arithmetic checks (generated with libguf/tools/ckdint-gen.py) \n"
|
||||
text_result_header += "\n// Unsigned integer arithmetic checks (generated with libguf/tools/ckdint-gen.py) \n"
|
||||
for type in uint_types:
|
||||
end = "\n"
|
||||
if type.is_optional:
|
||||
text_result += f"#ifdef {type.INT_MAX}\n"
|
||||
text_result_header += f"#ifdef {type.INT_MAX}\n"
|
||||
end = ""
|
||||
|
||||
if "uint_least" in type.INT_TYPE:
|
||||
bits = 0
|
||||
if "least8" in type.INT_TYPE:
|
||||
bits = 8
|
||||
elif "least16" in type.INT_TYPE:
|
||||
bits = 16
|
||||
elif "least32" in type.INT_TYPE:
|
||||
bits = 32
|
||||
elif "least64" in type.INT_TYPE:
|
||||
bits = 64
|
||||
else:
|
||||
assert(False)
|
||||
text_result += ckd_add_sub_uint_LEAST.format(type = type.INT_TYPE, type_abbr = type.INT_TYPE_ABBR, int_min = type.INT_MIN, int_max = type.INT_MAX, bits = str(bits))
|
||||
else:
|
||||
text_result += ckd_add_sub_uint.format(type = type.INT_TYPE, type_abbr = type.INT_TYPE_ABBR, int_min = type.INT_MIN, int_max = type.INT_MAX) + end
|
||||
text_result_header += ckd_add_sub_uint_header.format(type = type.INT_TYPE, type_abbr = type.INT_TYPE_ABBR, int_min = type.INT_MIN, int_max = type.INT_MAX) + end
|
||||
|
||||
if type.is_optional:
|
||||
text_result += "#endif\n\n"
|
||||
text_result_header += "#endif\n\n"
|
||||
|
||||
|
||||
text_result += "\n\n// Signed saturating/wrapping arithmetic (generated with libguf/tools/ckdint-gen.py)\n"
|
||||
text_result_header += "\n\n// Signed saturating/wrapping arithmetic (generated with libguf/tools/ckdint-gen.py)\n"
|
||||
for type in int_types:
|
||||
end = "\n"
|
||||
if type.is_optional:
|
||||
text_result += f"#ifdef {type.INT_MAX}\n"
|
||||
text_result_header += f"#ifdef {type.INT_MAX}\n"
|
||||
end = ""
|
||||
|
||||
text_result += saturating_wrapping_int.format(type = type.INT_TYPE, type_abbr = type.INT_TYPE_ABBR, int_min = type.INT_MIN, int_max = type.INT_MAX, uint_type = type.UINT_TYPE, uint_max = type.UINT_MAX) + end
|
||||
text_result_header += saturating_wrapping_int_header.format(type = type.INT_TYPE, type_abbr = type.INT_TYPE_ABBR, int_min = type.INT_MIN, int_max = type.INT_MAX, uint_type = type.UINT_TYPE, uint_max = type.UINT_MAX) + end
|
||||
|
||||
if type.is_optional:
|
||||
text_result += "#endif\n\n"
|
||||
text_result_header += "#endif\n\n"
|
||||
|
||||
|
||||
text_result += "\n// Unsigned saturating/wrapping arithmetic (generated with libguf/tools/ckdint-gen.py)\n"
|
||||
text_result_header += "\n// Unsigned saturating/wrapping arithmetic (generated with libguf/tools/ckdint-gen.py)\n"
|
||||
for type in uint_types:
|
||||
end = "\n"
|
||||
if type.is_optional:
|
||||
text_result += f"#ifdef {type.INT_MAX}\n"
|
||||
text_result_header += f"#ifdef {type.INT_MAX}\n"
|
||||
end = ""
|
||||
|
||||
if "uint_least" in type.INT_TYPE:
|
||||
bits = 0
|
||||
if "least8" in type.INT_TYPE:
|
||||
bits = 8
|
||||
elif "least16" in type.INT_TYPE:
|
||||
bits = 16
|
||||
elif "least32" in type.INT_TYPE:
|
||||
bits = 32
|
||||
elif "least64" in type.INT_TYPE:
|
||||
bits = 64
|
||||
else:
|
||||
assert(False)
|
||||
text_result += saturating_wrapping_uint_LEAST.format(type = type.INT_TYPE, type_abbr = type.INT_TYPE_ABBR, int_min = type.INT_MIN, int_max = type.INT_MAX, bits = str(bits)) + end
|
||||
else:
|
||||
text_result += saturating_wrapping_uint.format(type = type.INT_TYPE, type_abbr = type.INT_TYPE_ABBR, int_min = type.INT_MIN, int_max = type.INT_MAX) + end
|
||||
|
||||
text_result_header += saturating_wrapping_uint_header.format(type = type.INT_TYPE, type_abbr = type.INT_TYPE_ABBR, int_min = type.INT_MIN, int_max = type.INT_MAX) + end
|
||||
|
||||
if type.is_optional:
|
||||
text_result += "#endif\n\n"
|
||||
text_result_header += "#endif\n\n"
|
||||
|
||||
|
||||
return (text_result_header, text_result)
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
int_types = [
|
||||
IntType(INT_TYPE = "int", INT_TYPE_ABBR = "int", INT_MIN = "INT_MIN", INT_MAX = "INT_MAX", UINT_TYPE = "unsigned", UINT_MAX = "UINT_MAX"),
|
||||
IntType(INT_TYPE = "long", INT_TYPE_ABBR = "long", INT_MIN = "LONG_MIN", INT_MAX = "LONG_MAX", UINT_TYPE = "unsigned long", UINT_MAX = "ULONG_MAX"),
|
||||
IntType(INT_TYPE = "long long", INT_TYPE_ABBR = "long_long", INT_MIN = "LLONG_MIN", INT_MAX = "LLONG_MAX", UINT_TYPE = "unsigned long long", UINT_MAX = "ULLONG_MAX"),
|
||||
|
||||
# TODO: size_t is not necessarily the unsigned ptrdiff_t equivalent
|
||||
IntType(INT_TYPE = "ptrdiff_t", INT_TYPE_ABBR = "ptrdiff_t", INT_MIN = "PTRDIFF_MIN", INT_MAX = "PTRDIFF_MAX", UINT_TYPE = "size_t", UINT_MAX = "SIZE_MAX"),
|
||||
|
||||
IntType(INT_TYPE = "int_least8_t", INT_TYPE_ABBR = "least_i8", INT_MIN = "GUF_INT8_MIN", INT_MAX = "GUF_INT8_MAX", UINT_TYPE = "uint_least8_t", UINT_MAX = "GUF_UINT8_MAX"),
|
||||
IntType(INT_TYPE = "int_least16_t", INT_TYPE_ABBR = "least_i16", INT_MIN = "GUF_INT16_MIN", INT_MAX = "GUF_INT16_MAX", UINT_TYPE = "uint_least16_t", UINT_MAX = "GUF_UINT16_MAX"),
|
||||
IntType(INT_TYPE = "int_least32_t", INT_TYPE_ABBR = "least_i32", INT_MIN = "GUF_INT32_MIN", INT_MAX = "GUF_INT32_MAX", UINT_TYPE = "uint_least32_t", UINT_MAX = "GUF_UINT32_MAX"),
|
||||
IntType(INT_TYPE = "int_least64_t", INT_TYPE_ABBR = "least_i64", INT_MIN = "GUF_INT64_MIN", INT_MAX = "GUF_INT64_MAX", UINT_TYPE = "uint_least64_t", UINT_MAX = "GUF_UINT64_MAX"),
|
||||
|
||||
IntType(INT_TYPE = "int8_t", INT_TYPE_ABBR = "i8", INT_MIN = "INT8_MIN", INT_MAX = "INT8_MAX", UINT_TYPE = "uint8_t", UINT_MAX = "GUF_UINT8_MAX", is_optional = True),
|
||||
IntType(INT_TYPE = "int16_t", INT_TYPE_ABBR = "i16", INT_MIN = "INT16_MIN", INT_MAX = "INT16_MAX", UINT_TYPE = "uint16_t", UINT_MAX = "GUF_UINT16_MAX", is_optional = True),
|
||||
IntType(INT_TYPE = "int32_t", INT_TYPE_ABBR = "i32", INT_MIN = "INT32_MIN", INT_MAX = "INT32_MAX", UINT_TYPE = "uint32_t", UINT_MAX = "GUF_UINT32_MAX", is_optional = True),
|
||||
IntType(INT_TYPE = "int64_t", INT_TYPE_ABBR = "i64", INT_MIN = "INT64_MIN", INT_MAX = "INT64_MAX", UINT_TYPE = "uint64_t", UINT_MAX = "GUF_UINT64_MAX", is_optional = True),
|
||||
]
|
||||
|
||||
uint_types = [
|
||||
UintType(INT_TYPE = "unsigned char", INT_TYPE_ABBR = "uchar", INT_MIN = "0", INT_MAX = "UCHAR_MAX"),
|
||||
UintType(INT_TYPE = "unsigned", INT_TYPE_ABBR = "unsigned", INT_MIN = "0", INT_MAX = "UINT_MAX"),
|
||||
UintType(INT_TYPE = "unsigned long", INT_TYPE_ABBR = "ulong", INT_MIN = "0", INT_MAX = "ULONG_MAX"),
|
||||
UintType(INT_TYPE = "unsigned long long", INT_TYPE_ABBR = "ulong_long", INT_MIN = "0", INT_MAX = "ULLONG_MAX"),
|
||||
|
||||
UintType(INT_TYPE = "size_t", INT_TYPE_ABBR = "size_t", INT_MIN = "0", INT_MAX = "SIZE_MAX"),
|
||||
|
||||
UintType(INT_TYPE = "uint_least8_t", INT_TYPE_ABBR = "least_u8", INT_MIN = "0", INT_MAX = "GUF_UINT8_MAX"),
|
||||
UintType(INT_TYPE = "uint_least16_t", INT_TYPE_ABBR = "least_u16", INT_MIN = "0", INT_MAX = "GUF_UINT16_MAX"),
|
||||
UintType(INT_TYPE = "uint_least32_t", INT_TYPE_ABBR = "least_u32", INT_MIN = "0", INT_MAX = "GUF_UINT32_MAX"),
|
||||
UintType(INT_TYPE = "uint_least64_t", INT_TYPE_ABBR = "least_u64", INT_MIN = "0", INT_MAX = "GUF_UINT64_MAX"),
|
||||
|
||||
UintType(INT_TYPE = "uint8_t", INT_TYPE_ABBR = "u8", INT_MIN = "0", INT_MAX = "UINT8_MAX", is_optional = True),
|
||||
UintType(INT_TYPE = "uint16_t", INT_TYPE_ABBR = "u16", INT_MIN = "0", INT_MAX = "UINT16_MAX", is_optional = True),
|
||||
UintType(INT_TYPE = "uint32_t", INT_TYPE_ABBR = "u32", INT_MIN = "0", INT_MAX = "UINT32_MAX", is_optional = True),
|
||||
UintType(INT_TYPE = "uint64_t", INT_TYPE_ABBR = "u64", INT_MIN = "0", INT_MAX = "UINT64_MAX", is_optional = True),
|
||||
]
|
||||
|
||||
code_header, code_impl = generate_ckdint_functions(int_types = int_types, uint_types= uint_types)
|
||||
|
||||
print(textwrap.dedent(
|
||||
"""
|
||||
#if defined(GUF_MATH_CKDINT_IMPL_STATIC)
|
||||
#define GUF_MATH_CKDINT_KWRDS static inline
|
||||
#else
|
||||
#define GUF_MATH_CKDINT_KWRDS
|
||||
#endif
|
||||
|
||||
#ifndef GUF_MATH_CKDINT_H
|
||||
#define GUF_MATH_CKDINT_H
|
||||
#include "guf_common.h"
|
||||
|
||||
typedef enum guf_math_ckd_result {GUF_MATH_CKD_SUCCESS = 0, GUF_MATH_CKD_OVERFLOW_POS, GUF_MATH_CKD_OVERFLOW_NEG} guf_math_ckd_result;
|
||||
|
||||
"""))
|
||||
|
||||
|
||||
print("#if !defined(GUF_MATH_CKDINT_IMPL_STATIC) && !defined(GUF_MATH_CKDINT_IMPL)")
|
||||
print(code_header)
|
||||
print("#endif")
|
||||
|
||||
print("#if defined(GUF_MATH_CKDINT_IMPL) || defined(GUF_MATH_CKDINT_IMPL_STATIC)")
|
||||
print('#include "guf_assert.h"')
|
||||
print(code_impl)
|
||||
print("#endif /* End impl */\n")
|
||||
|
||||
print("#endif /* End header-guard */\n")
|
||||
|
||||
print("#undef GUF_MATH_CKDINT_KWRDS")
|
||||
print("#undef GUF_MATH_CKDINT_IMPL")
|
||||
print("#undef GUF_MATH_CKDINT_IMPL_STATIC\n")
|
||||
41
tools/intwrap-gen.py
Executable file
41
tools/intwrap-gen.py
Executable file
@ -0,0 +1,41 @@
|
||||
"""
|
||||
Generate typesafe unsigned integer wrapping functions
|
||||
"""
|
||||
|
||||
def gen_uwrap_code(uint_type: str, uint_type_abbr: str, wrap_width: int, wrap_max_uint: str) -> str:
|
||||
template = "static inline {uint_type} guf_wrap{wrap_width}_{uint_type_abbr}({uint_type} a) {{ return a & {wrap_max_uint}; }}\n"
|
||||
return template.format(uint_type = uint_type, uint_type_abbr = uint_type_abbr, wrap_width = wrap_width, wrap_max_uint = wrap_max_uint)
|
||||
|
||||
if __name__ == "__main__":
|
||||
uint_types = [
|
||||
{"uint_type": "uint_least8_t", "uint_type_abbr": "uint_least8_t", "wrap_width": 8, "wrap_max_uint": "GUF_UINT8_MAX"},
|
||||
{"uint_type": "uint_fast8_t", "uint_type_abbr": "uint_fast8_t", "wrap_width": 8, "wrap_max_uint": "GUF_UINT8_MAX"},
|
||||
|
||||
"\n",
|
||||
{"uint_type": "uint_least16_t", "uint_type_abbr": "uint_least16_t", "wrap_width": 16, "wrap_max_uint": "GUF_UINT16_MAX"},
|
||||
{"uint_type": "uint_fast16_t", "uint_type_abbr": "uint_fast16_t", "wrap_width": 16, "wrap_max_uint": "GUF_UINT16_MAX"},
|
||||
|
||||
"\n",
|
||||
{"uint_type": "uint_least32_t", "uint_type_abbr": "uint_least32_t", "wrap_width": 32, "wrap_max_uint": "GUF_UINT32_MAX"},
|
||||
{"uint_type": "uint_fast32_t", "uint_type_abbr": "uint_fast32_t", "wrap_width": 32, "wrap_max_uint": "GUF_UINT32_MAX"},
|
||||
|
||||
"\n",
|
||||
{"uint_type": "uint_least64_t", "uint_type_abbr": "uint_least64_t", "wrap_width": 64, "wrap_max_uint": "GUF_UINT64_MAX"},
|
||||
{"uint_type": "uint_fast64_t", "uint_type_abbr": "uint_fast64_t", "wrap_width": 64, "wrap_max_uint": "GUF_UINT64_MAX"},
|
||||
|
||||
"\n",
|
||||
{"uint_type": "unsigned char", "uint_type_abbr": "uchar", "wrap_width": 8, "wrap_max_uint": "GUF_UINT8_MAX"},
|
||||
{"uint_type": "unsigned short", "uint_type_abbr": "ushort", "wrap_width": 16, "wrap_max_uint": "GUF_UINT16_MAX"},
|
||||
{"uint_type": "unsigned long", "uint_type_abbr": "ulong", "wrap_width": 32, "wrap_max_uint": "GUF_UINT32_MAX"},
|
||||
{"uint_type": "unsigned long long", "uint_type_abbr": "ulong_long", "wrap_width": 64, "wrap_max_uint": "GUF_UINT64_MAX"},
|
||||
]
|
||||
|
||||
|
||||
code = "// Typesafe unsigned integer wrapping functions (generated with tools/intwrap-gen.py)\n"
|
||||
for uint_t in uint_types:
|
||||
if isinstance(uint_t, str):
|
||||
code += uint_t
|
||||
else:
|
||||
code += gen_uwrap_code(**uint_t)
|
||||
|
||||
print(code)
|
||||
102
tools/min_max_clamp-gen.py
Executable file
102
tools/min_max_clamp-gen.py
Executable file
@ -0,0 +1,102 @@
|
||||
"""
|
||||
Generate typesafe min, max and clamp functions for libguf/src/guf_math.h
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass
|
||||
import textwrap
|
||||
|
||||
@dataclass
|
||||
class IntType:
|
||||
INT_TYPE: str
|
||||
INT_TYPE_ABBR: str
|
||||
INT_MIN: str
|
||||
INT_MAX: str
|
||||
is_optional: bool = False
|
||||
|
||||
@dataclass
|
||||
class UintType:
|
||||
INT_TYPE: str
|
||||
INT_TYPE_ABBR: str
|
||||
INT_MIN: str
|
||||
INT_MAX: str
|
||||
is_optional: bool = False
|
||||
|
||||
|
||||
def gen_min_max_clamp(int_types: list, uint_types: list) -> str:
|
||||
template = textwrap.dedent("""\
|
||||
static inline {type} guf_min_{type_abbr}({type} a, {type} b) {{ return a < b ? a : b; }}
|
||||
static inline {type} guf_max_{type_abbr}({type} a, {type} b) {{ return a > b ? a : b; }}
|
||||
static inline {type} guf_clamp_{type_abbr}({type} x, {type} min, {type} max) {{ if (x < min) {{return min;}} if (x > max) {{return max;}} return x; }}
|
||||
""")
|
||||
|
||||
result = "\n// Signed min, max, clamp functions (generated with libguf/tools/min_max_clamp-gen.py)\n"
|
||||
for t in int_types:
|
||||
if t.is_optional:
|
||||
result += "#ifdef {int_max}\n".format(int_max = t.INT_MAX)
|
||||
result += template.format(type = t.INT_TYPE, type_abbr = t.INT_TYPE_ABBR)
|
||||
if t.is_optional:
|
||||
result += "#endif\n"
|
||||
result += "\n"
|
||||
|
||||
result += "\n// Unsigned min, max, clamp functions (generated with libguf/tools/min_max_clamp-gen.py)\n"
|
||||
for t in uint_types:
|
||||
if t.is_optional:
|
||||
result +="#ifdef {int_max}\n".format(int_max = t.INT_MAX)
|
||||
result += template.format(type = t.INT_TYPE, type_abbr = t.INT_TYPE_ABBR)
|
||||
if t.is_optional:
|
||||
result += "#endif\n"
|
||||
result += "\n"
|
||||
return result
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
int_types = [
|
||||
IntType(INT_TYPE = "char", INT_TYPE_ABBR = "char", INT_MIN = "CHAR_MIN", INT_MAX = "CHAR_MAX"),
|
||||
IntType(INT_TYPE = "int", INT_TYPE_ABBR = "int", INT_MIN = "INT_MIN", INT_MAX = "INT_MAX"),
|
||||
IntType(INT_TYPE = "long", INT_TYPE_ABBR = "long", INT_MIN = "LONG_MIN", INT_MAX = "LONG_MAX"),
|
||||
IntType(INT_TYPE = "long long", INT_TYPE_ABBR = "long_long", INT_MIN = "LLONG_MIN", INT_MAX = "LLONG_MAX"),
|
||||
|
||||
IntType(INT_TYPE = "ptrdiff_t", INT_TYPE_ABBR = "ptrdiff_t", INT_MIN = "PTRDIFF_MIN", INT_MAX = "PTRDIFF_MAX"),
|
||||
|
||||
IntType(INT_TYPE = "int_fast8_t", INT_TYPE_ABBR = "i8_fast", INT_MIN = "GUF_INT8_MIN", INT_MAX = "GUF_INT8_MAX"),
|
||||
IntType(INT_TYPE = "int_fast16_t", INT_TYPE_ABBR = "i16_fast", INT_MIN = "GUF_INT16_MIN", INT_MAX = "GUF_INT16_MAX"),
|
||||
IntType(INT_TYPE = "int_fast32_t", INT_TYPE_ABBR = "i32_fast", INT_MIN = "GUF_INT32_MIN", INT_MAX = "GUF_INT32_MAX"),
|
||||
IntType(INT_TYPE = "int_fast64_t", INT_TYPE_ABBR = "i64_fast", INT_MIN = "GUF_INT64_MIN", INT_MAX = "GUF_INT64_MAX"),
|
||||
|
||||
IntType(INT_TYPE = "float", INT_TYPE_ABBR = "f32", INT_MIN = "-FLT_MAX", INT_MAX = "FLT_MAX"),
|
||||
IntType(INT_TYPE = "double", INT_TYPE_ABBR = "f64", INT_MIN = "-DBL_MAX", INT_MAX = "DBL_MAX"),
|
||||
|
||||
IntType(INT_TYPE = "int8_t", INT_TYPE_ABBR = "i8", INT_MIN = "INT8_MIN", INT_MAX = "INT8_MAX", is_optional=True),
|
||||
IntType(INT_TYPE = "int16_t", INT_TYPE_ABBR = "i16", INT_MIN = "INT16_MIN", INT_MAX = "INT16_MAX", is_optional=True),
|
||||
IntType(INT_TYPE = "int32_t", INT_TYPE_ABBR = "i32", INT_MIN = "INT32_MIN", INT_MAX = "INT32_MAX", is_optional=True),
|
||||
IntType(INT_TYPE = "int64_t", INT_TYPE_ABBR = "i64", INT_MIN = "INT64_MIN", INT_MAX = "INT64_MAX", is_optional=True),
|
||||
|
||||
|
||||
]
|
||||
|
||||
uint_types = [
|
||||
UintType(INT_TYPE = "unsigned char", INT_TYPE_ABBR = "uchar", INT_MIN = "0", INT_MAX = "UCHAR_MAX"),
|
||||
UintType(INT_TYPE = "unsigned", INT_TYPE_ABBR = "unsigned", INT_MIN = "0", INT_MAX = "UINT_MAX"),
|
||||
UintType(INT_TYPE = "unsigned long", INT_TYPE_ABBR = "ulong", INT_MIN = "0", INT_MAX = "ULONG_MAX"),
|
||||
UintType(INT_TYPE = "unsigned long long", INT_TYPE_ABBR = "ulong_long", INT_MIN = "0", INT_MAX = "ULLONG_MAX"),
|
||||
|
||||
UintType(INT_TYPE = "size_t", INT_TYPE_ABBR = "size_t", INT_MIN = "0", INT_MAX = "SIZE_MAX"),
|
||||
|
||||
UintType(INT_TYPE = "uint_fast8_t", INT_TYPE_ABBR = "u8_fast", INT_MIN = "0", INT_MAX = "GUF_UINT8_MAX"),
|
||||
UintType(INT_TYPE = "uint_fast16_t", INT_TYPE_ABBR = "u16_fast", INT_MIN = "0", INT_MAX = "GUF_UINT16_MAX"),
|
||||
UintType(INT_TYPE = "uint_fast32_t", INT_TYPE_ABBR = "u32_fast", INT_MIN = "0", INT_MAX = "GUF_UINT32_MAX"),
|
||||
UintType(INT_TYPE = "uint_fast64_t", INT_TYPE_ABBR = "u64_fast", INT_MIN = "0", INT_MAX = "GUF_UINT64_MAX"),
|
||||
|
||||
UintType(INT_TYPE = "uint8_t", INT_TYPE_ABBR = "u8", INT_MIN = "0", INT_MAX = "UINT8_MAX", is_optional=True),
|
||||
UintType(INT_TYPE = "uint16_t", INT_TYPE_ABBR = "u16", INT_MIN = "0", INT_MAX = "UINT16_MAX", is_optional=True),
|
||||
UintType(INT_TYPE = "uint32_t", INT_TYPE_ABBR = "u32", INT_MIN = "0", INT_MAX = "UINT32_MAX", is_optional=True),
|
||||
UintType(INT_TYPE = "uint64_t", INT_TYPE_ABBR = "u64", INT_MIN = "0", INT_MAX = "UINT64_MAX", is_optional=True),
|
||||
]
|
||||
|
||||
|
||||
code = gen_min_max_clamp(int_types=int_types, uint_types=uint_types)
|
||||
print(code)
|
||||
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user