commit 76ea6e5d18572dd78df0865f1a06ed2431e5f654 Author: zeichensystem <83899451+zeichensystem@users.noreply.github.com> Date: Tue Dec 2 21:08:15 2025 +0100 Solve Day 01 diff --git a/.gitignore b/.gitignore new file mode 100755 index 0000000..0966b7c --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +input/day-??.txt +bin/ +build/ +.vscode/ +result.txt +session.txt \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100755 index 0000000..e914fba --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,82 @@ +# Within directory build/Release or build/Debug: Select debug/release variant with % cmake -DCMAKE_BUILD_TYPE=Debug/Release ../.. +# Build and run: make run-day-XX or cmake --build . --target run-day-nn + +cmake_minimum_required(VERSION 3.10) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS OFF) +set(CMAKE_C_STANDARD 99) + +add_definitions(-DCMAKE_EXPORT_COMPILE_COMMANDS=ON) + +if (NOT MSVC) + set(WARNING_FLAGS_C -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 -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 -glldb -Og) +else () + set(WARNING_FLAGS_C /W4) + set(WARNING_FLAGS_CXX /W4) + set(DBG_FLAGS /fsanitize=address) +endif () + +project(aoc-2025) +set(CMAKE_DEBUG_POSTFIX _dbg) + +if (NOT DEFINED CMAKE_RUNTIME_OUTPUT_DIRECTORY) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/bin) +endif() + +# set(CMAKE_VERBOSE_MAKEFILE OFF) + +include(CheckIPOSupported) +check_ipo_supported(RESULT ipo_available) + +if (ipo_available AND (NOT CMAKE_BUILD_TYPE MATCHES Debug) AND (NOT CMAKE_BUILD_TYPE MATCHES RelWithDebInfo)) + message("-- IPO enabled") +endif() + +set(TARGETS day-01) # Add the other days as you please. + +list(LENGTH TARGETS NUM_TARGETS) + +set(LIBGUF_IMPLS ${CMAKE_CURRENT_SOURCE_DIR}/libguf_impls/guf_alloc_libc_impl.c ${CMAKE_CURRENT_SOURCE_DIR}/libguf_impls/guf_str_impl.c ${CMAKE_CURRENT_SOURCE_DIR}/libguf_impls/guf_init_impl.c ) + +foreach(current_target IN LISTS TARGETS) + add_executable(${current_target} ${current_target}/${current_target}.c ${LIBGUF_IMPLS}) + set_target_properties(${current_target} PROPERTIES DEBUG_POSTFIX ${CMAKE_DEBUG_POSTFIX}) + target_include_directories(${current_target} PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/" "${CMAKE_CURRENT_SOURCE_DIR}/libguf/src/") + + target_compile_options(${current_target} PRIVATE ${WARNING_FLAGS_C} $<$:${DBG_FLAGS}>) + target_link_options(${current_target} PRIVATE ${WARNING_FLAGS_C} $<$:${DBG_FLAGS}>) + + target_compile_definitions(${current_target} PRIVATE AOC_DAY_NAME="${current_target}") + + if(ipo_available AND (NOT CMAKE_BUILD_TYPE MATCHES Debug) AND (NOT CMAKE_BUILD_TYPE MATCHES RelWithDebInfo)) + set_property(TARGET ${current_target} PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE) + endif() + + add_custom_target("run-${current_target}" + DEPENDS ${current_target} + COMMAND ${current_target} ${CMAKE_CURRENT_SOURCE_DIR}/input/${current_target}.txt + WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY} + ) + add_custom_target("run-${current_target}-example" + DEPENDS ${current_target} + COMMAND ${current_target} ${CMAKE_CURRENT_SOURCE_DIR}/input/${current_target}-example.txt + WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY} + ) +endforeach(current_target) + +# TODO... +add_custom_target("run-all" + DEPENDS ${TARGETS} + COMMAND day-01 ${CMAKE_CURRENT_SOURCE_DIR}/input/day-01.txt + WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY} +) + +add_custom_target("run-all-examples" + DEPENDS ${TARGETS} + COMMAND day-01 ${CMAKE_CURRENT_SOURCE_DIR}/input/day-01-example.txt + WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY} +) diff --git a/aocfetch.py b/aocfetch.py new file mode 100755 index 0000000..8882ec7 --- /dev/null +++ b/aocfetch.py @@ -0,0 +1,80 @@ +import sys +import os +import shutil +import time +import urllib.request +import ssl + +SESSION_COOKIE_PATH = "session.txt" +SRC_DIRECTORY_PATH = os.path.dirname(os.path.abspath(__file__)) +USAGE_STRING = f'Usage: aocfetch N (to prepare day N; make sure to put your session cookie into "{SESSION_COOKIE_PATH}")' + +def download_input(day_n: int, session: str): + request = urllib.request.Request(f'https://adventofcode.com/2025/day/{day_n}/input') + request.add_header("Cookie", f'session={session}') + context = ssl._create_unverified_context() + try: + with urllib.request.urlopen(request, context=context) as response: + return response.read().decode("utf-8") + except: + return None + +def read_session_file(session_path): + with open(session_path, "r") as f: + for line in f: + line = line.lstrip().rstrip() + if len(line) > 0: + return line + return None + +if __name__ == "__main__": + if len(sys.argv) <= 1: + print(USAGE_STRING) + sys.exit(0) + + os.chdir(SRC_DIRECTORY_PATH) + + try: + day_n = int(sys.argv[1]) + except ValueError: + print("Error: Invalid day argument (must be between 1 and 25).") + print(USAGE_STRING) + sys.exit(-1) + + if len(str(day_n)) == 1: + day_str = f'day-0{day_n}' + else: + day_str = f'day-{day_n}' + + if os.path.exists(os.path.join("input", f'{day_str}.txt')): + print(f'Day {day_n} is already ready to solve. Done.') + sys.exit(0) + + session_cookie = read_session_file(SESSION_COOKIE_PATH) + if not session_cookie: + print(f'Error: No session cookie in file {SESSION_COOKIE_PATH}.') + print(USAGE_STRING) + sys.exit(-1) + + print("Downloading input...") + time.sleep(0.2) + input_txt = download_input(day_n, session_cookie) + if not input_txt: + print('Error: Could not fetch input file. Make sure your session cookie is valid.\nProceeding to create empty example-input file and to copy code template...') + + print("Creating input files...") + if input_txt: + with open(os.path.join("input", f'{day_str}.txt'), "w") as input_file: + input_file.write(input_txt) + + with open(os.path.join("input", f'{day_str}-example.txt'), "w") as example_input_file: + example_input_file.write("") + + print("Copying code template...") + if not os.path.exists(day_str): + os.mkdir(day_str) + code_outfile = f'{day_str}/{day_str}.c' + if not os.path.exists(code_outfile): + shutil.copyfile('day-xy/day-xy.c', code_outfile) + + print(f'Done. Have fun solving day {day_n} :)') diff --git a/day-01/day-01.c b/day-01/day-01.c new file mode 100644 index 0000000..c662b8b --- /dev/null +++ b/day-01/day-01.c @@ -0,0 +1,82 @@ +#include "guf_common.h" +#include "guf_alloc_libc.h" +#include "guf_str.h" + +/* + - Part 1: 1191 (Example: 3) + - modular arithmetic + + - Part 2: 6858 (Example: 6) + - modular arithmetic and integer division + - note the special case for handling left turns when the dial is at zero. + - took me two tries to figure out %) +*/ + +guf_allocator g_allocator; +guf_libc_alloc_ctx g_allocator_ctx; + +int solution(guf_str_view input, bool part_two) +{ + guf_str_view linebreak_delim = GUF_CSTR_LIT_TO_VIEW("\n"); + guf_str_tok_state tokenizer = guf_str_tok_state_new(input, &linebreak_delim, 1, GUF_STR_TOK_DELIM_OPT_MATCH_LONGEST); + + const int DIAL_SIZE = 100; // 0 to 99 + int cur_dial_pos = 50; + int num_zeroes_encountered = 0; + + while (guf_str_tok_next(&tokenizer, false)) { // For each line. + guf_str_view line = guf_str_view_trim_left_ascii(tokenizer.cur_tok); + if (line.len < 2) { + continue; + } + const int dir = line.str[0] == 'L' ? -1 : 1; + line = guf_str_view_substr(line, 1, line.len - 1); + const int num_clicks = guf_str_view_read_uint(&line); + + if (!part_two) { // Part 1: + cur_dial_pos = ( DIAL_SIZE + ((cur_dial_pos + dir * num_clicks) % DIAL_SIZE) ) % DIAL_SIZE; + if (cur_dial_pos == 0) { + ++num_zeroes_encountered; + } + } else { // Part 2: + int passed_zeroes = 0; + if (dir == -1) { // Left (counter-clockwise) turn + passed_zeroes = (DIAL_SIZE - cur_dial_pos + num_clicks) / DIAL_SIZE; + if (cur_dial_pos == 0 && passed_zeroes > 0 && num_clicks > 0) { // Special case for left turns: Don't count twice if the current position is zero. + passed_zeroes -= 1; + } + } else { // Right (clockwise) turn + passed_zeroes = (cur_dial_pos + num_clicks) / DIAL_SIZE; + } + num_zeroes_encountered += passed_zeroes; + cur_dial_pos = ( DIAL_SIZE + ((cur_dial_pos + dir * num_clicks) % DIAL_SIZE) ) % DIAL_SIZE; // Same as in part one + } + } + + return num_zeroes_encountered; +} + +int main(int argc, const char **argv) +{ + g_allocator_ctx.zero_init = false; + guf_alloc_tracker_init(&g_allocator_ctx.tracker, 1, "Day-1 heap allocator", NULL, stderr); + guf_libc_allocator_init(&g_allocator, &g_allocator_ctx); + + if (argc < 2) { + printf("No input file given.\n"); + return EXIT_FAILURE; + } + + guf_str input_str = GUF_STR_UNINITIALISED; + guf_str_init_empty(&input_str, &g_allocator); + guf_str_append_file(&input_str, argv[1]); + + const int p1_result = solution(guf_str_view_from_str(&input_str), false); + printf("Part one: %d\n", p1_result); + const int p2_result = solution(guf_str_view_from_str(&input_str), true); + printf("Part two: %d\n", p2_result); + + guf_str_free(&input_str, NULL); + + return EXIT_SUCCESS; +} diff --git a/day-xy/day-xy.c b/day-xy/day-xy.c new file mode 100644 index 0000000..8b0983c --- /dev/null +++ b/day-xy/day-xy.c @@ -0,0 +1,44 @@ +#include "guf_common.h" +#include "guf_alloc_libc.h" +#include "guf_str.h" + +/* + - Part 1: (Example) + + - Part 2: (Example: ) +*/ + +guf_allocator g_allocator; +guf_libc_alloc_ctx g_allocator_ctx; + + +int solution(guf_str_view input, bool part_two) +{ + return 0; +} + + +int main(int argc, const char **argv) +{ + g_allocator_ctx.zero_init = false; + guf_alloc_tracker_init(&g_allocator_ctx.tracker, 1, "Day-1 heap allocator", NULL, stderr); + guf_libc_allocator_init(&g_allocator, &g_allocator_ctx); + + if (argc < 2) { + printf("No input file given.\n"); + return EXIT_FAILURE; + } + + guf_str input_str = GUF_STR_UNINITIALISED; + guf_str_init_empty(&input_str, &g_allocator); + guf_str_append_file(&input_str, argv[1]); + + const int p1_result = solution(guf_str_view_from_str(&input_str), false); + printf("Part one: %d\n", p1_result); + const int p2_result = solution(guf_str_view_from_str(&input_str), true); + printf("Part two: %d\n", p2_result); + + guf_str_free(&input_str, NULL); + + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/input/day-01-example.txt b/input/day-01-example.txt new file mode 100644 index 0000000..53287c7 --- /dev/null +++ b/input/day-01-example.txt @@ -0,0 +1,10 @@ +L68 +L30 +R48 +L5 +R60 +L55 +L1 +L99 +R14 +L82 diff --git a/libguf/src/.DS_Store b/libguf/src/.DS_Store new file mode 100755 index 0000000..1d9d7f5 Binary files /dev/null and b/libguf/src/.DS_Store differ diff --git a/libguf/src/guf_alloc.h b/libguf/src/guf_alloc.h new file mode 100755 index 0000000..6a116b2 --- /dev/null +++ b/libguf/src/guf_alloc.h @@ -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 diff --git a/libguf/src/guf_alloc_libc.h b/libguf/src/guf_alloc_libc.h new file mode 100755 index 0000000..2adb63c --- /dev/null +++ b/libguf/src/guf_alloc_libc.h @@ -0,0 +1,121 @@ +/* + 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 +#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_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*)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 diff --git a/libguf/src/guf_alloc_tracker.h b/libguf/src/guf_alloc_tracker.h new file mode 100755 index 0000000..3af8c5d --- /dev/null +++ b/libguf/src/guf_alloc_tracker.h @@ -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 +#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 diff --git a/libguf/src/guf_assert.h b/libguf/src/guf_assert.h new file mode 100755 index 0000000..1077185 --- /dev/null +++ b/libguf/src/guf_assert.h @@ -0,0 +1,165 @@ +/* + 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 +#include +#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 diff --git a/libguf/src/guf_common.h b/libguf/src/guf_common.h new file mode 100755 index 0000000..6781d08 --- /dev/null +++ b/libguf/src/guf_common.h @@ -0,0 +1,130 @@ +/* + is parametrized: no +*/ + +#ifndef GUF_COMMON_H +#define GUF_COMMON_H +#include +#include +#include +#include +#include + +#ifndef GUF_PLATFORM_BIG_ENDIAN + #define GUF_PLATFORM_LITTLE_ENDIAN +#endif + +#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); + +#endif diff --git a/libguf/src/guf_cstr.h b/libguf/src/guf_cstr.h new file mode 100755 index 0000000..db30b62 --- /dev/null +++ b/libguf/src/guf_cstr.h @@ -0,0 +1,81 @@ +/* + is parametrized: no +*/ + +#ifndef GUF_CSTR_H +#define GUF_CSTR_H +#include +#include "guf_common.h" +#include "guf_assert.h" +#include "guf_hash.h" + +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 = strdup(*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 diff --git a/libguf/src/guf_dbuf.h b/libguf/src/guf_dbuf.h new file mode 100755 index 0000000..5b56c91 --- /dev/null +++ b/libguf/src/guf_dbuf.h @@ -0,0 +1,1060 @@ +/* + is parametrized: yes +*/ + +#if defined(GUF_DBUF_STATIC_IMPL) + #define GUF_DBUF_KWRDS static +#else + #define GUF_DBUF_KWRDS +#endif + +#ifndef GUF_DBUF_H +#define GUF_DBUF_H + #include "guf_common.h" + #include "guf_assert.h" + #include "guf_alloc.h" + #define GUF_DBUF_FOREACH(DBUF, ELEM_TYPE, ELEM_PTR_NAME) for (ELEM_TYPE *ELEM_PTR_NAME = (DBUF).data, *guf_foreach_end = (DBUF).data ? (DBUF).data + (DBUF).size : NULL; ELEM_PTR_NAME != guf_foreach_end; ++ELEM_PTR_NAME) +#endif + +/* + Template parameters: + - GUF_T: The value type stored in the container. + - GUF_DBUF_NAME: The typename of the resulting container (default: dbuf_GUF_T) + - GUF_T_IS_INTEGRAL_TYPE: whether the type is an integral type (if defined, must not define COPY, MOVE, FREE and EQ) + + - GUF_T_COPY: cpy function with signature GUF_T *copy(GUF_T *dst, const GUF_T *src, void *ctx) (default: copy by value) + - GUF_T_MOVE: move function with signature GUF_T *move(GUF_T *dst, GUF_T *src, void *ctx) (default: undefined) + - GUF_T_FREE: free function with signature void free(GUF_T *a, void *ctx) (default: undefined) + - GUF_T_EQ: equality function with signature bool eq(const GUF_T *a, const GUF_T *a) (default: undefined, or equality by value if GUF_T_IS_INTEGRAL_TYPE is defined) + + - GUF_DBUF_INITIAL_CAP: The capacity used for the first allocation if the container got initialised with a capacity of zero (default: 8) + - GUF_DBUF_USE_GROWTH_FAC_ONE_POINT_FIVE: If defined, always use 1.5 as the growth-factor (instead of 2) +*/ + +#ifndef GUF_T + #error "Undefined container template GUF_T" +#endif + +#ifndef GUF_DBUF_NAME + #define GUF_DBUF_NAME GUF_CAT(dbuf_, GUF_T) +#endif + +#ifdef GUF_DBUF_ONLY_TYPES + typedef struct GUF_DBUF_NAME { + GUF_T *data; + ptrdiff_t size, capacity; + guf_allocator *allocator; + #ifdef GUF_CNT_WITH_ELEM_CTX + void *elem_ctx; // NULL by default; is passed as the ctx argument to GUF_T_COPY, GUF_T_MOVE and GUF_T_FREE + #endif + } GUF_DBUF_NAME; + + typedef struct GUF_CAT(GUF_DBUF_NAME, _iter) { + GUF_T *ptr; + GUF_T *base; // Not NULL For reverse iterators (unless dbuf->size == 0, then ptr and base are NULL for both iterator types) + } GUF_CAT(GUF_DBUF_NAME, _iter); +#else + +// Used for the first growth if dbuf->capacity is zero. +#ifndef GUF_DBUF_INITIAL_CAP + #define GUF_DBUF_INITIAL_CAP 8 +#endif + +#if (defined(GUF_T_COPY) || defined(GUF_T_MOVE)) && !defined(GUF_T_FREE) + #error "Must define GUF_T_FREE because GUF_T_COPY or GUF_T_MOVE is defined" +#endif + +#if defined(GUF_T_IS_INTEGRAL_TYPE) && (defined(GUF_T_COPY) || defined(GUF_CNT_MOVE) || defined(GUF_CNT_FREE) || defined(GUF_T_EQ)) + #error "Integral types do not need COPY, MOVE, FREE or EQ functions" +#endif + +#if !defined(GUF_DBUF_IMPL) && !defined(GUF_DBUF_WITHOUT_TYPES) + +typedef struct GUF_DBUF_NAME { + GUF_T *data; + ptrdiff_t size, capacity; + guf_allocator *allocator; + #ifdef GUF_CNT_WITH_ELEM_CTX + void *elem_ctx; // NULL by default; is passed as the ctx argument to GUF_T_COPY, GUF_T_MOVE and GUF_T_FREE + #endif +} GUF_DBUF_NAME; + +/* + - Regular iterator: base is always NULL + - Reverse iterator: base points the element after ptr (unless dbuf->size == 0, then ptr and base are NULL for both iterator types) + Examples: + - rbegin(): base points to end().ptr, and ptr to end().ptr - 1 + - rend(): base points to begin().ptr and ptr to NULL + - reverse iterator for the first element of the container: base points to the second element, ptr points to the first element +*/ +typedef struct GUF_CAT(GUF_DBUF_NAME, _iter) { + GUF_T *ptr; + GUF_T *base; // Not NULL For reverse iterators (unless dbuf->size == 0, then ptr and base are NULL for both iterator types) +} GUF_CAT(GUF_DBUF_NAME, _iter); + +#endif + +GUF_DBUF_KWRDS bool GUF_CAT(GUF_DBUF_NAME, _valid)(const GUF_DBUF_NAME* dbuf); + +GUF_DBUF_KWRDS ptrdiff_t GUF_CAT(GUF_DBUF_NAME, _max_capacity)(void); + +GUF_DBUF_KWRDS void GUF_CAT(GUF_DBUF_NAME, _try_reserve)(GUF_DBUF_NAME *dbuf, ptrdiff_t min_capacity, guf_err *err); +GUF_DBUF_KWRDS void GUF_CAT(GUF_DBUF_NAME, _reserve)(GUF_DBUF_NAME *dbuf, ptrdiff_t min_capacity); + +GUF_DBUF_KWRDS void GUF_CAT(GUF_DBUF_NAME, _try_init)(GUF_DBUF_NAME *dbuf, ptrdiff_t capacity, guf_allocator *allocator, guf_err *err); +GUF_DBUF_KWRDS void GUF_CAT(GUF_DBUF_NAME, _init)(GUF_DBUF_NAME *dbuf, ptrdiff_t start_cap, guf_allocator *allocator); +GUF_DBUF_KWRDS GUF_DBUF_NAME GUF_CAT(GUF_DBUF_NAME, _new)(guf_allocator *allocator); +GUF_DBUF_KWRDS GUF_DBUF_NAME GUF_CAT(GUF_DBUF_NAME, _try_new_with_capacity)(ptrdiff_t capacity, guf_allocator *allocator, guf_err *err); +GUF_DBUF_KWRDS GUF_DBUF_NAME GUF_CAT(GUF_DBUF_NAME, _new_with_capacity)(ptrdiff_t capacity, guf_allocator *allocator); + +#ifdef GUF_CNT_WITH_ELEM_CTX + GUF_DBUF_KWRDS void GUF_CAT(GUF_DBUF_NAME, _set_elem_ctx)(GUF_DBUF_NAME *dbuf, void *elem_ctx); +#endif +GUF_DBUF_KWRDS void *GUF_CAT(GUF_DBUF_NAME, _get_elem_ctx)(const GUF_DBUF_NAME *dbuf); // Always returns NULL if GUF_CNT_WITH_ELEM_CTX is not defined + +GUF_DBUF_KWRDS void GUF_CAT(GUF_DBUF_NAME, _free)(GUF_DBUF_NAME *dbuf, void *ctx); +GUF_DBUF_KWRDS GUF_DBUF_NAME *GUF_CAT(GUF_DBUF_NAME, _copy)(GUF_DBUF_NAME *dst, const GUF_DBUF_NAME *src, void *ctx); +GUF_DBUF_KWRDS GUF_DBUF_NAME *GUF_CAT(GUF_DBUF_NAME, _move)(GUF_DBUF_NAME *dst, GUF_DBUF_NAME *src, void *ctx); + +GUF_DBUF_KWRDS ptrdiff_t GUF_CAT(GUF_DBUF_NAME, _try_get_next_capacity)(ptrdiff_t old_cap, guf_err *err); +GUF_DBUF_KWRDS void GUF_CAT(GUF_DBUF_NAME, _try_grow_if_full)(GUF_DBUF_NAME *dbuf, guf_err *err); +GUF_DBUF_KWRDS void GUF_CAT(GUF_DBUF_NAME, _try_shrink_to_fit)(GUF_DBUF_NAME *dbuf, guf_err *err); + +GUF_DBUF_KWRDS GUF_T *GUF_CAT(GUF_DBUF_NAME, _try_insert)(GUF_DBUF_NAME *dbuf, GUF_T *elem, ptrdiff_t idx, guf_cpy_opt cpy_opt, guf_err *err); +GUF_DBUF_KWRDS GUF_T *GUF_CAT(GUF_DBUF_NAME, _insert)(GUF_DBUF_NAME *dbuf, GUF_T *elem, ptrdiff_t idx, guf_cpy_opt cpy_opt); +GUF_DBUF_KWRDS GUF_T *GUF_CAT(GUF_DBUF_NAME, _try_insert_val)(GUF_DBUF_NAME *dbuf, GUF_T elem, ptrdiff_t idx, guf_err *err); +GUF_DBUF_KWRDS GUF_T *GUF_CAT(GUF_DBUF_NAME, _insert_val)(GUF_DBUF_NAME *dbuf, GUF_T elem, ptrdiff_t idx); +#ifdef GUF_T_COPY +GUF_DBUF_KWRDS GUF_T *GUF_CAT(GUF_DBUF_NAME, _try_insert_val_cpy)(GUF_DBUF_NAME *dbuf, GUF_T elem, ptrdiff_t idx, guf_err *err); +GUF_DBUF_KWRDS GUF_T *GUF_CAT(GUF_DBUF_NAME, _insert_val_cpy)(GUF_DBUF_NAME *dbuf, GUF_T elem, ptrdiff_t idx); +#endif + +GUF_DBUF_KWRDS GUF_T *GUF_CAT(GUF_DBUF_NAME, _try_push)(GUF_DBUF_NAME *dbuf, GUF_T *elem, guf_cpy_opt cpy_opt, guf_err *err); +GUF_DBUF_KWRDS GUF_T *GUF_CAT(GUF_DBUF_NAME, _push)(GUF_DBUF_NAME *dbuf, GUF_T *elem, guf_cpy_opt cpy_opt); +GUF_DBUF_KWRDS GUF_T *GUF_CAT(GUF_DBUF_NAME, _try_push_val)(GUF_DBUF_NAME *dbuf, GUF_T elem, guf_err *err); +GUF_DBUF_KWRDS GUF_T *GUF_CAT(GUF_DBUF_NAME, _push_val)(GUF_DBUF_NAME *dbuf, GUF_T elem); +#ifdef GUF_T_COPY +GUF_DBUF_KWRDS GUF_T *GUF_CAT(GUF_DBUF_NAME, _try_push_val_cpy)(GUF_DBUF_NAME *dbuf, GUF_T elem, guf_err *err); +GUF_DBUF_KWRDS GUF_T *GUF_CAT(GUF_DBUF_NAME, _push_val_cpy)(GUF_DBUF_NAME *dbuf, GUF_T elem); +#endif + +GUF_DBUF_KWRDS bool GUF_CAT(GUF_DBUF_NAME, _try_erase)(GUF_DBUF_NAME *dbuf, ptrdiff_t idx, guf_err *err); +GUF_DBUF_KWRDS void GUF_CAT(GUF_DBUF_NAME, _erase)(GUF_DBUF_NAME *dbuf, ptrdiff_t idx); + +GUF_DBUF_KWRDS bool GUF_CAT(GUF_DBUF_NAME, _try_pop)(GUF_DBUF_NAME *dbuf, guf_err *err); +GUF_DBUF_KWRDS void GUF_CAT(GUF_DBUF_NAME, _pop)(GUF_DBUF_NAME *dbuf); +GUF_DBUF_KWRDS GUF_T GUF_CAT(GUF_DBUF_NAME, _try_pop_move)(GUF_DBUF_NAME *dbuf, guf_err *err); +GUF_DBUF_KWRDS GUF_T GUF_CAT(GUF_DBUF_NAME, _pop_move)(GUF_DBUF_NAME *dbuf); + +GUF_DBUF_KWRDS GUF_T *GUF_CAT(GUF_DBUF_NAME, _try_at)(GUF_DBUF_NAME *dbuf, ptrdiff_t idx, guf_err *err); +GUF_DBUF_KWRDS GUF_T *GUF_CAT(GUF_DBUF_NAME, _at)(GUF_DBUF_NAME *dbuf, ptrdiff_t idx); +GUF_DBUF_KWRDS GUF_T *GUF_CAT(GUF_DBUF_NAME, _try_front)(GUF_DBUF_NAME *dbuf, guf_err *err); +GUF_DBUF_KWRDS GUF_T *GUF_CAT(GUF_DBUF_NAME, _front)(GUF_DBUF_NAME *dbuf); +GUF_DBUF_KWRDS GUF_T *GUF_CAT(GUF_DBUF_NAME, _try_back)(GUF_DBUF_NAME *dbuf, guf_err *err); +GUF_DBUF_KWRDS GUF_T *GUF_CAT(GUF_DBUF_NAME, _back)(GUF_DBUF_NAME *dbuf); + +GUF_DBUF_KWRDS GUF_CAT(GUF_DBUF_NAME, _iter) GUF_CAT(GUF_DBUF_NAME, _begin)(const GUF_DBUF_NAME* dbuf); +GUF_DBUF_KWRDS GUF_CAT(GUF_DBUF_NAME, _iter) GUF_CAT(GUF_DBUF_NAME, _end)(const GUF_DBUF_NAME* dbuf); +GUF_DBUF_KWRDS GUF_CAT(GUF_DBUF_NAME, _iter) GUF_CAT(GUF_DBUF_NAME, _rbegin)(const GUF_DBUF_NAME* dbuf); +GUF_DBUF_KWRDS GUF_CAT(GUF_DBUF_NAME, _iter) GUF_CAT(GUF_DBUF_NAME, _rend)(const GUF_DBUF_NAME* dbuf); +GUF_DBUF_KWRDS bool GUF_CAT(GUF_DBUF_NAME, _iter_is_end)(const GUF_DBUF_NAME* dbuf, GUF_CAT(GUF_DBUF_NAME, _iter) it); + +GUF_DBUF_KWRDS GUF_CAT(GUF_DBUF_NAME, _iter) GUF_CAT(GUF_DBUF_NAME, _iter_next)(const GUF_DBUF_NAME *dbuf, GUF_CAT(GUF_DBUF_NAME, _iter) it, ptrdiff_t step); +GUF_DBUF_KWRDS GUF_CAT(GUF_DBUF_NAME, _iter) GUF_CAT(GUF_DBUF_NAME, _iter_at_idx)(const GUF_DBUF_NAME* dbuf, ptrdiff_t idx); +GUF_DBUF_KWRDS GUF_CAT(GUF_DBUF_NAME, _iter) GUF_CAT(GUF_DBUF_NAME, _reverse_iter_at_idx)(const GUF_DBUF_NAME* dbuf, ptrdiff_t idx); +GUF_DBUF_KWRDS ptrdiff_t GUF_CAT(GUF_DBUF_NAME, _iter_to_idx)(const GUF_DBUF_NAME* dbuf, GUF_CAT(GUF_DBUF_NAME, _iter) it); + +#if defined(GUF_T_IS_INTEGRAL_TYPE) || defined(GUF_T_EQ) +GUF_DBUF_KWRDS GUF_CAT(GUF_DBUF_NAME, _iter) GUF_CAT(GUF_DBUF_NAME, _find)(GUF_DBUF_NAME *dbuf, GUF_CAT(GUF_DBUF_NAME, _iter) begin, GUF_CAT(GUF_DBUF_NAME, _iter) end, const GUF_T *needle); +GUF_DBUF_KWRDS GUF_CAT(GUF_DBUF_NAME, _iter) GUF_CAT(GUF_DBUF_NAME, _find_val)(GUF_DBUF_NAME *dbuf, GUF_CAT(GUF_DBUF_NAME, _iter) begin, GUF_CAT(GUF_DBUF_NAME, _iter) end, GUF_T needle_val); +GUF_DBUF_KWRDS bool GUF_CAT(GUF_DBUF_NAME, _contains)(GUF_DBUF_NAME *dbuf, const GUF_T *needle); +GUF_DBUF_KWRDS bool GUF_CAT(GUF_DBUF_NAME, _contains_val)(GUF_DBUF_NAME *dbuf, GUF_T needle); +#endif +GUF_DBUF_KWRDS GUF_CAT(GUF_DBUF_NAME, _iter) GUF_CAT(GUF_DBUF_NAME, _find_if)(GUF_DBUF_NAME *dbuf, GUF_CAT(GUF_DBUF_NAME, _iter) begin, GUF_CAT(GUF_DBUF_NAME, _iter) end, bool (*predicate)(const GUF_T *)); + + +#if defined(GUF_DBUF_IMPL) || defined(GUF_DBUF_IMPL_STATIC) + +#include "guf_math.h" + +GUF_DBUF_KWRDS bool GUF_CAT(GUF_DBUF_NAME, _valid)(const GUF_DBUF_NAME* dbuf) +{ + if (!dbuf) { + return false; + } + bool valid_data_ptr = (!dbuf->data && !dbuf->capacity) || (dbuf->data && dbuf->capacity); + bool valid_allocator = dbuf->allocator && dbuf->allocator->alloc && dbuf->allocator->realloc && dbuf->allocator->free; + return valid_data_ptr && valid_allocator && dbuf->capacity >= 0 && dbuf->size >= 0 && dbuf->size <= dbuf->capacity; +} + +GUF_DBUF_KWRDS ptrdiff_t GUF_CAT(GUF_DBUF_NAME, _max_capacity)(void) +{ + ptrdiff_t DBUF_MAX_BYTES = GUF_ALLOC_MAX_BYTES(GUF_T); + GUF_ASSERT((DBUF_MAX_BYTES % sizeof(GUF_T)) == 0); + (void)DBUF_MAX_BYTES; + + const ptrdiff_t DBUF_MAX_CAP = GUF_ALLOC_MAX_CAPACITY(GUF_T); + GUF_ASSERT(DBUF_MAX_CAP <= PTRDIFF_MAX); + return DBUF_MAX_CAP; +} + + +GUF_DBUF_KWRDS void GUF_CAT(GUF_DBUF_NAME, _try_reserve)(GUF_DBUF_NAME *dbuf, ptrdiff_t min_capacity, guf_err *err) +{ + GUF_ASSERT_RELEASE(GUF_CAT(GUF_DBUF_NAME, _valid)(dbuf)); + GUF_ASSERT_RELEASE(min_capacity >= 0); + + if (min_capacity <= dbuf->capacity || min_capacity == 0) { + guf_err_set_if_not_null(err, GUF_ERR_NONE); + return; + } + GUF_ASSERT(min_capacity > 0); + + const ptrdiff_t DBUF_MAX_CAP = GUF_CAT(GUF_DBUF_NAME, _max_capacity)(); + min_capacity = GUF_MIN(min_capacity, DBUF_MAX_CAP); + + if (min_capacity <= dbuf->capacity) { + guf_err_set_or_panic(err, GUF_ERR_ALLOC_FAIL, GUF_ERR_MSG("in function dbuf_try_reserve: already at max capacity")); + return; + } + + ptrdiff_t new_alloc_bytes = -1; + if (!guf_size_calc_safe(min_capacity, sizeof(GUF_T), &new_alloc_bytes)) { + GUF_ASSERT_RELEASE(false); + guf_err_set_or_panic(err, GUF_ERR_ALLOC_FAIL, GUF_ERR_MSG("in function dbuf_try_reserve: overflow in size calculation (BUG: this should never happen...)")); + return; + } + GUF_ASSERT(new_alloc_bytes > 0); + + if (!dbuf->data) { // a.) Allocate. + GUF_T *data = dbuf->allocator->alloc(new_alloc_bytes, dbuf->allocator->ctx); + if (!data) { + guf_err_set_or_panic(err, GUF_ERR_ALLOC_FAIL, GUF_ERR_MSG("in function dbuf_try_reserve: failed to allocate initial dbuf->data")); + return; + } + dbuf->data = data; + } else { // b.) Re-allocate. + ptrdiff_t old_alloc_bytes = dbuf->capacity * sizeof(GUF_T); + GUF_ASSERT(old_alloc_bytes < new_alloc_bytes); + GUF_T *data = dbuf->allocator->realloc(dbuf->data, old_alloc_bytes, new_alloc_bytes, dbuf->allocator->ctx); + if (!data) { + guf_err_set_or_panic(err, GUF_ERR_ALLOC_FAIL, GUF_ERR_MSG("in function dbuf_try_reserve: failed to reallocate dbuf->data")); + return; + } + dbuf->data = data; + } + + guf_err_set_if_not_null(err, GUF_ERR_NONE); + dbuf->capacity = min_capacity; + GUF_ASSERT(dbuf->data && dbuf->capacity); +} + +GUF_DBUF_KWRDS void GUF_CAT(GUF_DBUF_NAME, _reserve)(GUF_DBUF_NAME *dbuf, ptrdiff_t min_capacity) +{ + GUF_CAT(GUF_DBUF_NAME, _try_reserve)(dbuf, min_capacity, NULL); +} + +GUF_DBUF_KWRDS void GUF_CAT(GUF_DBUF_NAME, _try_init)(GUF_DBUF_NAME *dbuf, ptrdiff_t capacity, guf_allocator *allocator, guf_err *err) +{ + GUF_ASSERT_RELEASE(dbuf); + GUF_ASSERT_RELEASE(capacity >= 0); + + if (dbuf->size != 0 || dbuf->capacity != 0 || dbuf->data || GUF_CAT(GUF_DBUF_NAME, _get_elem_ctx)(dbuf)) { + guf_err_set_or_panic(err, GUF_ERR_INVALID_ARG, GUF_ERR_MSG("in function dbuf_try_init: dbuf might have been already initialised")); + return; + } + + dbuf->size = dbuf->capacity = 0; + #ifdef GUF_CNT_WITH_ELEM_CTX + dbuf->elem_ctx = NULL; + #endif + + if (!allocator || !(allocator->alloc && allocator->realloc && allocator->free)) { + guf_err_set_or_panic(err, GUF_ERR_INVALID_ARG, GUF_ERR_MSG("in function dbuf_try_init: allocator (or one of it's function pointers) is NULL")); + return; + } else { + dbuf->allocator = allocator; + } + + if (capacity == 0) { + dbuf->data = NULL; + guf_err_set_if_not_null(err, GUF_ERR_NONE); + } else { + GUF_CAT(GUF_DBUF_NAME, _try_reserve)(dbuf, capacity, err); + } +} + +GUF_DBUF_KWRDS void GUF_CAT(GUF_DBUF_NAME, _init)(GUF_DBUF_NAME *dbuf, ptrdiff_t start_cap, guf_allocator *allocator) +{ + GUF_CAT(GUF_DBUF_NAME, _try_init)(dbuf, start_cap, allocator, NULL); +} + +GUF_DBUF_KWRDS GUF_DBUF_NAME GUF_CAT(GUF_DBUF_NAME, _new)(guf_allocator *allocator) +{ + GUF_DBUF_NAME dbuf = {0}; + GUF_CAT(GUF_DBUF_NAME, _init)(&dbuf, 0, allocator); + GUF_ASSERT(dbuf.size == 0 && dbuf.capacity == 0); + return dbuf; +} + +GUF_DBUF_KWRDS GUF_DBUF_NAME GUF_CAT(GUF_DBUF_NAME, _try_new_with_capacity)(ptrdiff_t capacity, guf_allocator *allocator, guf_err *err) +{ + GUF_DBUF_NAME dbuf = {0}; + + GUF_CAT(GUF_DBUF_NAME, _try_init)(&dbuf, capacity, allocator, err); + if (err && *err != GUF_ERR_NONE) { + return (GUF_DBUF_NAME){0}; + } + guf_err_set_if_not_null(err, GUF_ERR_NONE); + return dbuf; +} + +GUF_DBUF_KWRDS GUF_DBUF_NAME GUF_CAT(GUF_DBUF_NAME, _new_with_capacity)(ptrdiff_t capacity, guf_allocator *allocator) +{ + return GUF_CAT(GUF_DBUF_NAME, _try_new_with_capacity)(capacity, allocator, NULL); +} + +#ifdef GUF_CNT_WITH_ELEM_CTX +GUF_DBUF_KWRDS void GUF_CAT(GUF_DBUF_NAME, _set_elem_ctx)(GUF_DBUF_NAME *dbuf, void *elem_ctx) +{ + GUF_ASSERT_RELEASE(GUF_CAT(GUF_DBUF_NAME, _valid)(dbuf)); + #ifdef GUF_CNT_WITH_ELEM_CTX + dbuf->elem_ctx = elem_ctx; + #else + (void)elem_ctx; + GUF_ASSERT_RELEASE(false); + #endif +} +#endif + +GUF_DBUF_KWRDS void *GUF_CAT(GUF_DBUF_NAME, _get_elem_ctx)(const GUF_DBUF_NAME *dbuf) +{ + #ifdef GUF_CNT_WITH_ELEM_CTX + return dbuf->elem_ctx; + #else + (void)dbuf; + return NULL; + #endif +} + +GUF_DBUF_KWRDS void GUF_CAT(GUF_DBUF_NAME, _free)(GUF_DBUF_NAME *dbuf, void *ctx) +{ + (void)ctx; + GUF_ASSERT_RELEASE(GUF_CAT(GUF_DBUF_NAME, _valid)(dbuf)); + if (dbuf->capacity == 0) { + GUF_ASSERT_RELEASE(!dbuf->data); + GUF_ASSERT_RELEASE(dbuf->size == 0); + return; + } + GUF_ASSERT_RELEASE(dbuf->data); + + #ifdef GUF_T_FREE + for (ptrdiff_t idx = 0; idx < dbuf->size; ++idx) { + GUF_T_FREE(dbuf->data + idx, GUF_CAT(GUF_DBUF_NAME, _get_elem_ctx)(dbuf)); + } + #endif + + dbuf->allocator->free(dbuf->data, sizeof(GUF_T) * dbuf->capacity, dbuf->allocator->ctx); + dbuf->data = NULL; + dbuf->capacity = dbuf->size = 0; + dbuf->allocator = NULL; + #ifdef GUF_CNT_WITH_ELEM_CTX + dbuf->elem_ctx = NULL; + #endif +} + +GUF_DBUF_KWRDS GUF_DBUF_NAME *GUF_CAT(GUF_DBUF_NAME, _copy)(GUF_DBUF_NAME *dst, const GUF_DBUF_NAME *src, void *ctx) +{ + (void)ctx; + if (!dst || !src || GUF_CAT(GUF_DBUF_NAME, _valid)(src)) { + return NULL; + } + + if (src->capacity == 0) { + GUF_ASSERT(!src->data); + GUF_ASSERT(src->size == 0); + *dst = *src; + return dst; + } + + GUF_ASSERT(src->data && src->capacity > 0); + + dst->allocator = src->allocator; + #ifdef GUF_CNT_WITH_ELEM_CTX + dst->elem_ctx = src->elem_ctx; + #endif + + if (src->size == 0) { + dst->capacity = 0; + dst->size = 0; + dst->data = NULL; + return dst; + } else { + ptrdiff_t dst_cap = src->size; + GUF_T *dst_data = src->allocator->alloc(dst_cap * sizeof(GUF_T), src->allocator->ctx); + dst->data = dst_data; + if (!dst->data) { + dst->capacity = 0; + dst->size = 0; + return NULL; + } else { + dst->capacity = dst_cap; + dst->size = src->size; + } + + for (ptrdiff_t i = 0; i < src->size; ++i) { + #ifdef GUF_T_COPY + GUF_T *cpy_success = GUF_T_COPY(dst->data + i, src->data + i, GUF_CAT(GUF_DBUF_NAME, _get_elem_ctx)(src)); + if (!cpy_success) { + dst->size = i; + return NULL; + } + #else + dst->data[i] = src->data[i]; + #endif + } + return dst; + } +} + +GUF_DBUF_KWRDS GUF_DBUF_NAME *GUF_CAT(GUF_DBUF_NAME, _move)(GUF_DBUF_NAME *dst, GUF_DBUF_NAME *src, void *ctx) +{ + (void)ctx; + if (!dst || !src) { + return NULL; + } + + *dst = *src; + + src->capacity = 0; + src->size = 0; + src->data = NULL; + src->allocator = NULL; + #ifdef GUF_CNT_WITH_ELEM_CTX + src->elem_ctx = NULL; + #endif + + return dst; +} + +GUF_DBUF_KWRDS ptrdiff_t GUF_CAT(GUF_DBUF_NAME, _try_get_next_capacity)(ptrdiff_t old_cap, guf_err *err) +{ + 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 * 2u; + } else { + #ifdef GUF_DBUF_USE_GROWTH_FAC_ONE_POINT_FIVE + if (guf_mul_is_overflow_size_t(old_cap, 3)) { + if (guf_mul_is_overflow_size_t((size_t)old_cap / 2u, 3)) { // Try (old_cap / 2) * 3 + new_cap = PTRDIFF_MAX; + } else { + new_cap = ((size_t)old_cap / 2u) * 3u; + } + } else { + new_cap = (size_t)old_cap * 3u / 2u; + } + #else + if (guf_mul_is_overflow_size_t(old_cap, 2)) { + new_cap = PTRDIFF_MAX; + } else { + new_cap = (size_t)old_cap * 2u; + } + #endif + } + + const size_t DBUF_MAX_CAP = (size_t)GUF_CAT(GUF_DBUF_NAME, _max_capacity)(); + new_cap = GUF_MIN(new_cap, DBUF_MAX_CAP); + + if (new_cap <= (size_t)old_cap) { // Detect overflow. + guf_err_set_or_panic(err, GUF_ERR_INT_OVERFLOW, GUF_ERR_MSG("in function dbuf_try_get_next_capacity: reached max capacity")); + return -1; + } else { + guf_err_set_if_not_null(err, GUF_ERR_NONE); + return new_cap; + } +} + +GUF_DBUF_KWRDS void GUF_CAT(GUF_DBUF_NAME, _try_grow_if_full)(GUF_DBUF_NAME *dbuf, guf_err *err) +{ + GUF_ASSERT_RELEASE(GUF_CAT(GUF_DBUF_NAME, _valid)(dbuf)); + GUF_ASSERT_RELEASE(dbuf->capacity >= 0 && dbuf->size >= 0); + + if (dbuf->size == dbuf->capacity) { + ptrdiff_t next_cap = GUF_CAT(GUF_DBUF_NAME, _try_get_next_capacity)(dbuf->capacity, err); + if (err && *err != GUF_ERR_NONE) { + return; + } + GUF_ASSERT(next_cap > 0); + GUF_CAT(GUF_DBUF_NAME, _try_reserve)(dbuf, next_cap, err); + if (err && *err != GUF_ERR_NONE) { + return; + } + } + + GUF_ASSERT_RELEASE(dbuf->size < dbuf->capacity); + guf_err_set_if_not_null(err, GUF_ERR_NONE); +} + +static inline bool GUF_CAT(GUF_DBUF_NAME, _copy_opt_available_)(guf_cpy_opt cpy_opt) +{ + if (cpy_opt == GUF_CPY_DEEP) { + #ifdef GUF_T_COPY + return true; + #else + return false; + #endif + } else if (cpy_opt == GUF_CPY_MOVE) { + #ifdef GUF_T_MOVE + return true; + #else + return false; + #endif + } else if (cpy_opt == GUF_CPY_VALUE) { + return true; + } else { + return false; + } +} + +GUF_DBUF_KWRDS GUF_T *GUF_CAT(GUF_DBUF_NAME, _try_insert)(GUF_DBUF_NAME *dbuf, GUF_T *elem, ptrdiff_t idx, guf_cpy_opt cpy_opt, guf_err *err) +{ + GUF_ASSERT(GUF_CAT(GUF_DBUF_NAME,_valid)(dbuf)); + GUF_ASSERT(elem); + + if (idx < 0 || idx > dbuf->size) { + guf_err_set_or_panic(err, GUF_ERR_IDX_RANGE, GUF_ERR_MSG("in function dbuf_try_insert")); + return NULL; + } + + GUF_CAT(GUF_DBUF_NAME, _try_grow_if_full)(dbuf, err); + if (err && *err != GUF_ERR_NONE) { + return NULL; + } + + for (ptrdiff_t free_idx = dbuf->size; free_idx > idx; --free_idx) { // Shift (Make space by moving elements to the right, or do nothing if idx == dbuf->size) + GUF_ASSERT(free_idx >= 1 && free_idx < dbuf->capacity); + dbuf->data[free_idx] = dbuf->data[free_idx - 1]; + } + + GUF_T *dst = dbuf->data + idx; + + if (!GUF_CAT(GUF_DBUF_NAME, _copy_opt_available_)(cpy_opt)) { + guf_err_set_or_panic(err, GUF_ERR_INVALID_ARG, GUF_ERR_MSG("in function " GUF_STRINGIFY(GUF_CAT(GUF_DBUF_NAME, _copy_opt_available_)) ": cpy_opt unavailable")); + return NULL; + } else if (cpy_opt == GUF_CPY_DEEP) { + #ifdef GUF_T_COPY + dst = GUF_T_COPY(dst, elem, GUF_CAT(GUF_DBUF_NAME, _get_elem_ctx)(dbuf)); + #else + GUF_ASSERT_RELEASE(false); + #endif + } else if (cpy_opt == GUF_CPY_MOVE) { + #ifdef GUF_T_MOVE + dst = GUF_T_MOVE(dst, elem, GUF_CAT(GUF_DBUF_NAME, _get_elem_ctx)(dbuf)); + #else + GUF_ASSERT_RELEASE(false); + #endif + } else { + GUF_ASSERT_RELEASE(cpy_opt == GUF_CPY_VALUE); + *dst = *elem; + } + + if (!dst) { + guf_err_set_or_panic(err, GUF_ERR_ALLOC_FAIL, GUF_ERR_MSG("in function dbuf_try_insert: Failed to copy elem")); + for (ptrdiff_t free_idx = idx; free_idx < dbuf->size; ++free_idx) { // Undo shift to restore dbuf's state before the failed insert. + GUF_ASSERT(free_idx + 1 < dbuf->capacity); + dbuf->data[free_idx] = dbuf->data[free_idx + 1]; + } + return NULL; + } else { + dbuf->size++; + guf_err_set_if_not_null(err, GUF_ERR_NONE); + return dst; + } +} + +GUF_DBUF_KWRDS GUF_T *GUF_CAT(GUF_DBUF_NAME, _insert)(GUF_DBUF_NAME *dbuf, GUF_T *elem, ptrdiff_t idx, guf_cpy_opt cpy_opt) +{ + return GUF_CAT(GUF_DBUF_NAME, _try_insert)(dbuf, elem, idx, cpy_opt, NULL); +} + +GUF_DBUF_KWRDS GUF_T *GUF_CAT(GUF_DBUF_NAME, _try_insert_val)(GUF_DBUF_NAME *dbuf, GUF_T elem, ptrdiff_t idx, guf_err *err) +{ + return GUF_CAT(GUF_DBUF_NAME, _try_insert)(dbuf, &elem, idx, GUF_CPY_VALUE, err); +} + +GUF_DBUF_KWRDS GUF_T *GUF_CAT(GUF_DBUF_NAME, _insert_val)(GUF_DBUF_NAME *dbuf, GUF_T elem, ptrdiff_t idx) +{ + return GUF_CAT(GUF_DBUF_NAME, _try_insert_val)(dbuf, elem, idx, NULL); +} + + +#ifdef GUF_T_COPY +GUF_DBUF_KWRDS GUF_T *GUF_CAT(GUF_DBUF_NAME, _try_insert_val_cpy)(GUF_DBUF_NAME *dbuf, GUF_T elem, ptrdiff_t idx, guf_err *err) +{ + return GUF_CAT(GUF_DBUF_NAME, _try_insert)(dbuf, &elem, idx, GUF_CPY_DEEP, err); +} + +GUF_DBUF_KWRDS GUF_T *GUF_CAT(GUF_DBUF_NAME, _insert_val_cpy)(GUF_DBUF_NAME *dbuf, GUF_T elem, ptrdiff_t idx) +{ + return GUF_CAT(GUF_DBUF_NAME, _try_insert_val_cpy)(dbuf, elem, idx, NULL); +} +#endif + +GUF_DBUF_KWRDS GUF_T *GUF_CAT(GUF_DBUF_NAME, _try_push)(GUF_DBUF_NAME *dbuf, GUF_T *elem, guf_cpy_opt cpy_opt, guf_err *err) +{ + GUF_ASSERT(GUF_CAT(GUF_DBUF_NAME, _valid)(dbuf)); + return GUF_CAT(GUF_DBUF_NAME, _try_insert)(dbuf, elem, dbuf->size, cpy_opt, err); +} + +GUF_DBUF_KWRDS GUF_T *GUF_CAT(GUF_DBUF_NAME, _push)(GUF_DBUF_NAME *dbuf, GUF_T *elem, guf_cpy_opt cpy_opt) +{ + return GUF_CAT(GUF_DBUF_NAME, _try_push)(dbuf, elem, cpy_opt, NULL); +} + +GUF_DBUF_KWRDS GUF_T *GUF_CAT(GUF_DBUF_NAME, _try_push_val)(GUF_DBUF_NAME *dbuf, GUF_T elem, guf_err *err) +{ + return GUF_CAT(GUF_DBUF_NAME, _try_push)(dbuf, &elem, GUF_CPY_VALUE, err); +} + +GUF_DBUF_KWRDS GUF_T *GUF_CAT(GUF_DBUF_NAME, _push_val)(GUF_DBUF_NAME *dbuf, GUF_T elem) +{ + return GUF_CAT(GUF_DBUF_NAME, _try_push_val)(dbuf, elem, NULL); +} + +#ifdef GUF_T_COPY +GUF_DBUF_KWRDS GUF_T *GUF_CAT(GUF_DBUF_NAME, _try_push_val_cpy)(GUF_DBUF_NAME *dbuf, GUF_T elem, guf_err *err) +{ + return GUF_CAT(GUF_DBUF_NAME, _try_push)(dbuf, &elem, GUF_CPY_DEEP, err); +} + +GUF_DBUF_KWRDS GUF_T *GUF_CAT(GUF_DBUF_NAME, _push_val_cpy)(GUF_DBUF_NAME *dbuf, GUF_T elem) +{ + return GUF_CAT(GUF_DBUF_NAME, _try_push_val_cpy)(dbuf, elem, NULL); +} +#endif + + +GUF_DBUF_KWRDS bool GUF_CAT(GUF_DBUF_NAME, _try_erase)(GUF_DBUF_NAME *dbuf, ptrdiff_t idx, guf_err *err) +{ + GUF_ASSERT_RELEASE(GUF_CAT(GUF_DBUF_NAME, _valid)(dbuf)); + + if (dbuf->size == 0) { + guf_err_set_or_panic(err, GUF_ERR_IDX_RANGE, GUF_ERR_MSG("in function dbuf_try_erase: cannot erase from empty buffer")); + return false; + } + if (idx < 0 || idx >= dbuf->size) { + guf_err_set_or_panic(err, GUF_ERR_IDX_RANGE, GUF_ERR_MSG("in function dbuf_try_erase")); + return false; + } + + #ifdef GUF_T_FREE + GUF_T_FREE(dbuf->data + idx, GUF_CAT(GUF_DBUF_NAME, _get_elem_ctx)(dbuf)); + #endif + + for (ptrdiff_t free_idx = idx; free_idx < dbuf->size - 1; ++free_idx) { // Make space by moving elements to the left if necessary. + GUF_ASSERT(free_idx + 1 < dbuf->size); + dbuf->data[free_idx] = dbuf->data[free_idx + 1]; + } + dbuf->size--; + guf_err_set_if_not_null(err, GUF_ERR_NONE); + return true; +} + +GUF_DBUF_KWRDS void GUF_CAT(GUF_DBUF_NAME, _erase)(GUF_DBUF_NAME *dbuf, ptrdiff_t idx) +{ + GUF_CAT(GUF_DBUF_NAME, _try_erase)(dbuf, idx, NULL); +} + +GUF_DBUF_KWRDS bool GUF_CAT(GUF_DBUF_NAME, _try_pop)(GUF_DBUF_NAME *dbuf, guf_err *err) +{ + GUF_ASSERT_RELEASE(GUF_CAT(GUF_DBUF_NAME, _valid)(dbuf)); + + if (dbuf->size == 0) { + guf_err_set_or_panic(err, GUF_ERR_IDX_RANGE, GUF_ERR_MSG("in function dbuf_try_pop: Cannot pop from empty dbuf")); + return false; + } + + #ifdef GUF_T_FREE + GUF_T *popped = dbuf->data + --dbuf->size; + GUF_T_FREE(popped, GUF_CAT(GUF_DBUF_NAME, _get_elem_ctx)(dbuf)); + #else + --dbuf->size; + #endif + + guf_err_set_if_not_null(err, GUF_ERR_NONE); + return true; +} + +GUF_DBUF_KWRDS void GUF_CAT(GUF_DBUF_NAME, _pop)(GUF_DBUF_NAME *dbuf) +{ + GUF_CAT(GUF_DBUF_NAME, _try_pop)(dbuf, NULL); +} + +GUF_DBUF_KWRDS GUF_T GUF_CAT(GUF_DBUF_NAME, _try_pop_move)(GUF_DBUF_NAME *dbuf, guf_err *err) +{ + GUF_ASSERT_RELEASE(GUF_CAT(GUF_DBUF_NAME, _valid)(dbuf)); + + if (dbuf->size == 0) { + guf_err_set_or_panic(err, GUF_ERR_IDX_RANGE, GUF_ERR_MSG("in function dbuf_try_pop_move: Cannot pop from empty dbuf")); + GUF_T dummy; + memset(&dummy, 0, sizeof(GUF_T)); + return dummy; + } + + GUF_T *popped = dbuf->data + (dbuf->size - 1); + GUF_T popped_val; + GUF_T *dst = &popped_val; + #if defined(GUF_T_MOVE) + dst = GUF_T_MOVE(dst, popped, GUF_CAT(GUF_DBUF_NAME, _get_elem_ctx)(dbuf)); + #else + *dst = *popped; + memset(popped, 0, sizeof(GUF_T)); + #endif + + if (!dst) { + guf_err_set_or_panic(err, GUF_ERR_ALLOC_FAIL, GUF_ERR_MSG("in function dbuf_try_pop_move: Failed on move")); + memset(&popped_val, 0, sizeof(GUF_T)); + return popped_val; // Return a dummy value in case the copy failed (as we have to return something). + } else { + dbuf->size--; + guf_err_set_if_not_null(err, GUF_ERR_NONE); + return popped_val; + } +} + +GUF_DBUF_KWRDS GUF_T GUF_CAT(GUF_DBUF_NAME, _pop_move)(GUF_DBUF_NAME *dbuf) +{ + return GUF_CAT(GUF_DBUF_NAME, _try_pop_move)(dbuf, NULL); +} + + +GUF_DBUF_KWRDS GUF_T *GUF_CAT(GUF_DBUF_NAME, _try_at)(GUF_DBUF_NAME *dbuf, ptrdiff_t idx, guf_err *err) +{ + GUF_ASSERT_RELEASE(GUF_CAT(GUF_DBUF_NAME, _valid)(dbuf)); + + if (dbuf->size == 0) { + guf_err_set_or_panic(err, GUF_ERR_IDX_RANGE, GUF_ERR_MSG("in function dbuf_try_at: dbuf is empty")); + return NULL; + } + if (idx < 0 || idx >= dbuf->size) { + guf_err_set_or_panic(err, GUF_ERR_IDX_RANGE, GUF_ERR_MSG("in function dbuf_try_at")); + return NULL; + } + GUF_ASSERT(dbuf->data); + guf_err_set_if_not_null(err, GUF_ERR_NONE); + return dbuf->data + idx; +} + +GUF_DBUF_KWRDS GUF_T *GUF_CAT(GUF_DBUF_NAME, _at)(GUF_DBUF_NAME *dbuf, ptrdiff_t idx) +{ + return GUF_CAT(GUF_DBUF_NAME, _try_at)(dbuf, idx, NULL); +} + + +GUF_DBUF_KWRDS GUF_T *GUF_CAT(GUF_DBUF_NAME, _try_front)(GUF_DBUF_NAME *dbuf, guf_err *err) +{ + GUF_ASSERT_RELEASE(GUF_CAT(GUF_DBUF_NAME, _valid)(dbuf)); + + if (dbuf->size == 0) { + guf_err_set_or_panic(err, GUF_ERR_IDX_RANGE, GUF_ERR_MSG("in function dbuf_front: dbuf is empty")); + return NULL; + } + return dbuf->data + 0; +} + +GUF_DBUF_KWRDS GUF_T *GUF_CAT(GUF_DBUF_NAME, _front)(GUF_DBUF_NAME *dbuf) +{ + return GUF_CAT(GUF_DBUF_NAME, _try_front)(dbuf, NULL); +} + + +GUF_DBUF_KWRDS GUF_T *GUF_CAT(GUF_DBUF_NAME, _try_back)(GUF_DBUF_NAME *dbuf, guf_err *err) +{ + GUF_ASSERT_RELEASE(GUF_CAT(GUF_DBUF_NAME, _valid)(dbuf)); + + if (dbuf->size == 0) { + guf_err_set_or_panic(err, GUF_ERR_IDX_RANGE, GUF_ERR_MSG("in function dbuf_try_back: dbuf is empty")); + return NULL; + } + guf_err_set_if_not_null(err, GUF_ERR_NONE); + return dbuf->data + (dbuf->size - 1); +} + +GUF_DBUF_KWRDS GUF_T *GUF_CAT(GUF_DBUF_NAME, _back)(GUF_DBUF_NAME *dbuf) +{ + return GUF_CAT(GUF_DBUF_NAME, _try_back)(dbuf, NULL); +} + +GUF_DBUF_KWRDS void GUF_CAT(GUF_DBUF_NAME, _try_shrink_to_fit)(GUF_DBUF_NAME *dbuf, guf_err *err) +{ + GUF_ASSERT_RELEASE(GUF_CAT(GUF_DBUF_NAME, _valid)(dbuf)); + const ptrdiff_t new_capacity = dbuf->size; + if (new_capacity == dbuf->capacity || (!dbuf->data && !dbuf->capacity)) { + guf_err_set_if_not_null(err, GUF_ERR_NONE); + return; + } + GUF_ASSERT_RELEASE(new_capacity < dbuf->capacity); + GUF_ASSERT_RELEASE(dbuf->data); + + if (new_capacity == 0) { + dbuf->allocator->free(dbuf->data, sizeof(GUF_T) * dbuf->capacity, dbuf->allocator->ctx); + return; + } + + GUF_T *data = dbuf->allocator->realloc(dbuf->data, new_capacity, sizeof(GUF_T), dbuf->allocator->ctx); + if (!data) { + guf_err_set_or_panic(err, GUF_ERR_ALLOC_FAIL, GUF_ERR_MSG("in function _try_shrink_to_fit")); + return; + } + dbuf->data = data; + dbuf->capacity = new_capacity; + guf_err_set_if_not_null(err, GUF_ERR_NONE); +} + +/* Iterator functions */ + +GUF_DBUF_KWRDS GUF_CAT(GUF_DBUF_NAME, _iter) GUF_CAT(GUF_DBUF_NAME, _begin)(const GUF_DBUF_NAME* dbuf) +{ + GUF_ASSERT_RELEASE(GUF_CAT(GUF_DBUF_NAME, _valid)(dbuf)); + + return (GUF_CAT(GUF_DBUF_NAME, _iter)) { + .ptr = dbuf->data && dbuf->size ? dbuf->data : NULL, + .base = NULL + }; +} + +GUF_DBUF_KWRDS GUF_CAT(GUF_DBUF_NAME, _iter) GUF_CAT(GUF_DBUF_NAME, _end)(const GUF_DBUF_NAME* dbuf) +{ + GUF_ASSERT_RELEASE(GUF_CAT(GUF_DBUF_NAME, _valid)(dbuf)); + return (GUF_CAT(GUF_DBUF_NAME, _iter)) { + .ptr = dbuf->data && dbuf->size ? dbuf->data + dbuf->size : NULL, + .base = NULL + }; +} + +GUF_DBUF_KWRDS GUF_CAT(GUF_DBUF_NAME, _iter) GUF_CAT(GUF_DBUF_NAME, _rbegin)(const GUF_DBUF_NAME* dbuf) +{ + GUF_ASSERT_RELEASE(GUF_CAT(GUF_DBUF_NAME, _valid)(dbuf)); + return (GUF_CAT(GUF_DBUF_NAME, _iter)) { + .base = dbuf->data && dbuf->size ? dbuf->data + dbuf->size : NULL, + .ptr = dbuf->data && dbuf->size ? dbuf->data + (dbuf->size - 1) : NULL + }; +} + +GUF_DBUF_KWRDS GUF_CAT(GUF_DBUF_NAME, _iter) GUF_CAT(GUF_DBUF_NAME, _rend)(const GUF_DBUF_NAME* dbuf) +{ + GUF_ASSERT_RELEASE(GUF_CAT(GUF_DBUF_NAME, _valid)(dbuf)); + return (GUF_CAT(GUF_DBUF_NAME, _iter)) { + .base = dbuf->data && dbuf->size ? dbuf->data : NULL, + .ptr = NULL + }; +} + +GUF_DBUF_KWRDS bool GUF_CAT(GUF_DBUF_NAME, _iter_is_end)(const GUF_DBUF_NAME* dbuf, GUF_CAT(GUF_DBUF_NAME, _iter) it) +{ + const bool is_reverse_it = it.base != NULL; + const GUF_CAT(GUF_DBUF_NAME, _iter) dbuf_end_it = is_reverse_it ? GUF_CAT(GUF_DBUF_NAME, _rend)(dbuf) : GUF_CAT(GUF_DBUF_NAME, _end)(dbuf); + return it.ptr == dbuf_end_it.ptr; +} + +GUF_DBUF_KWRDS GUF_CAT(GUF_DBUF_NAME, _iter) GUF_CAT(GUF_DBUF_NAME, _iter_at_idx)(const GUF_DBUF_NAME* dbuf, ptrdiff_t idx) +{ + GUF_ASSERT(GUF_CAT(GUF_DBUF_NAME, _valid)(dbuf)); + + GUF_CAT(GUF_DBUF_NAME, _iter) it; + it.base = NULL; + + if (!dbuf->data || !dbuf->size) { + it.ptr = NULL; + return it; + } + + if (idx <= 0) { // begin() + it.ptr = dbuf->data; + } else if (idx >= dbuf->size) { // end() + it.ptr = dbuf->data + dbuf->size; + } else { + it.ptr = dbuf->data + idx; + } + + return it; +} + +GUF_DBUF_KWRDS GUF_CAT(GUF_DBUF_NAME, _iter) GUF_CAT(GUF_DBUF_NAME, _reverse_iter_at_idx)(const GUF_DBUF_NAME* dbuf, ptrdiff_t idx) +{ + GUF_CAT(GUF_DBUF_NAME, _iter) it = GUF_CAT(GUF_DBUF_NAME, _iter_at_idx)(dbuf, idx); + + if (!dbuf->data || !dbuf->size) { + it.base = NULL; + it.ptr = NULL; + return it; + } + + if (idx < 0) { // rend() + it.base = dbuf->data; + it.ptr = NULL; + } else if (idx >= dbuf->size) { // rbegin() + it.base = dbuf->data + dbuf->size; + it.ptr = it.base - 1; + } else { + it.base = dbuf->data + idx + 1; + it.ptr = it.base - 1; + } + return it; +} + +GUF_DBUF_KWRDS ptrdiff_t GUF_CAT(GUF_DBUF_NAME, _iter_to_idx)(const GUF_DBUF_NAME* dbuf, GUF_CAT(GUF_DBUF_NAME, _iter) it) +{ + GUF_ASSERT(GUF_CAT(GUF_DBUF_NAME, _valid)(dbuf)); + + if ((!it.ptr && !it.base) || !dbuf->data || !dbuf->size) { + return GUF_CNT_NPOS; + } + + const bool is_reverse_it = it.base != NULL; + const GUF_CAT(GUF_DBUF_NAME, _iter) end_it = is_reverse_it ? GUF_CAT(GUF_DBUF_NAME, _rend)(dbuf) : GUF_CAT(GUF_DBUF_NAME, _end)(dbuf); + + if (it.ptr == end_it.ptr) { + return is_reverse_it ? -1 : dbuf->size; + } + + if (is_reverse_it) { + return (ptrdiff_t)(it.base - dbuf->data) - 1; + } else { + return (ptrdiff_t)(it.ptr - dbuf->data); + } +} + +GUF_DBUF_KWRDS GUF_CAT(GUF_DBUF_NAME, _iter) GUF_CAT(GUF_DBUF_NAME, _iter_next)(const GUF_DBUF_NAME *dbuf, GUF_CAT(GUF_DBUF_NAME, _iter) it, ptrdiff_t step) +{ + GUF_ASSERT(GUF_CAT(GUF_DBUF_NAME, _valid)(dbuf)); + + if (!dbuf->size || !dbuf->data || (!it.base && !it.ptr)) { + it.ptr = NULL; + it.base = NULL; + return it; + } + + const bool is_reverse_it = it.base != NULL; + + if (is_reverse_it) { + if (step < 0) { + GUF_ASSERT(step > PTRDIFF_MIN); // Catch overflow. + } + step = -step; + } + + if (is_reverse_it) { + ptrdiff_t idx = (ptrdiff_t)(it.base - dbuf->data) + step; + idx = GUF_CLAMP(idx, 0, dbuf->size); + it.base = dbuf->data + idx; + it.ptr = idx == 0 ? NULL : it.base - 1; + } else { + GUF_ASSERT(it.ptr); + ptrdiff_t idx = (ptrdiff_t)(it.ptr - dbuf->data) + step; + idx = GUF_CLAMP(idx, 0, dbuf->size); + it.ptr = dbuf->data + idx; + } + + return it; +} + +#if defined(GUF_T_IS_INTEGRAL_TYPE) || defined(GUF_T_EQ) +GUF_DBUF_KWRDS GUF_CAT(GUF_DBUF_NAME, _iter) GUF_CAT(GUF_DBUF_NAME, _find)(GUF_DBUF_NAME *dbuf, GUF_CAT(GUF_DBUF_NAME, _iter) begin, GUF_CAT(GUF_DBUF_NAME, _iter) end, const GUF_T *needle) +{ + GUF_ASSERT_RELEASE(GUF_CAT(GUF_DBUF_NAME, _valid)(dbuf)); + GUF_ASSERT_RELEASE(needle); + + const bool is_reverse_it = begin.base != NULL; + GUF_ASSERT_RELEASE(is_reverse_it == (end.base != NULL)); // begin and end must be the same iterator type. + const GUF_CAT(GUF_DBUF_NAME, _iter) dbuf_end_it = is_reverse_it ? GUF_CAT(GUF_DBUF_NAME, _rend)(dbuf) : GUF_CAT(GUF_DBUF_NAME, _end)(dbuf); + + if (!dbuf->data || !dbuf->size || (!begin.ptr && !begin.base) || (!end.ptr && !end.base)) { + return dbuf_end_it; + } + if ((begin.ptr == dbuf_end_it.ptr) || (!is_reverse_it && begin.ptr >= end.ptr) || (is_reverse_it && begin.base <= end.base)) { + return dbuf_end_it; + } + + for (GUF_CAT(GUF_DBUF_NAME, _iter) it = begin; it.ptr != end.ptr && it.ptr != NULL; it = GUF_CAT(GUF_DBUF_NAME, _iter_next)(dbuf, it, 1)) { + #ifdef GUF_T_EQ + if (GUF_T_EQ(it.ptr, needle)) { + return it; + } + #else + if (*it.ptr == *needle) { + return it; + } + #endif + } + return dbuf_end_it; +} + +GUF_DBUF_KWRDS GUF_CAT(GUF_DBUF_NAME, _iter) GUF_CAT(GUF_DBUF_NAME, _find_val)(GUF_DBUF_NAME *dbuf, GUF_CAT(GUF_DBUF_NAME, _iter) begin, GUF_CAT(GUF_DBUF_NAME, _iter) end, GUF_T needle_val) +{ + return GUF_CAT(GUF_DBUF_NAME, _find)(dbuf, begin, end, &needle_val); +} + +GUF_DBUF_KWRDS bool GUF_CAT(GUF_DBUF_NAME, _contains)(GUF_DBUF_NAME *dbuf, const GUF_T *needle) +{ + GUF_CAT(GUF_DBUF_NAME, _iter) beg = GUF_CAT(GUF_DBUF_NAME, _begin)(dbuf); + GUF_CAT(GUF_DBUF_NAME, _iter) end = GUF_CAT(GUF_DBUF_NAME, _end)(dbuf); + return GUF_CAT(GUF_DBUF_NAME, _find)(dbuf, beg, end, needle).ptr != end.ptr; +} + +GUF_DBUF_KWRDS bool GUF_CAT(GUF_DBUF_NAME, _contains_val)(GUF_DBUF_NAME *dbuf, GUF_T needle) +{ + return GUF_CAT(GUF_DBUF_NAME, _contains)(dbuf, &needle); +} +#endif + +GUF_DBUF_KWRDS GUF_CAT(GUF_DBUF_NAME, _iter) GUF_CAT(GUF_DBUF_NAME, _find_if)(GUF_DBUF_NAME *dbuf, GUF_CAT(GUF_DBUF_NAME, _iter) begin, GUF_CAT(GUF_DBUF_NAME, _iter) end, bool (*predicate)(const GUF_T *)) +{ + GUF_ASSERT_RELEASE(GUF_CAT(GUF_DBUF_NAME, _valid)(dbuf)); + GUF_ASSERT_RELEASE(predicate); + + const bool is_reverse_it = begin.base != NULL; + GUF_ASSERT_RELEASE(is_reverse_it == (end.base != NULL)); // begin and end must be the same iterator type. + const GUF_CAT(GUF_DBUF_NAME, _iter) dbuf_end_it = is_reverse_it ? GUF_CAT(GUF_DBUF_NAME, _rend)(dbuf) : GUF_CAT(GUF_DBUF_NAME, _end)(dbuf); + + if (!dbuf->data || !dbuf->size || (!begin.ptr && !begin.base) || (!end.ptr && !end.base)) { + return dbuf_end_it; + } + if ((begin.ptr == dbuf_end_it.ptr) || (!is_reverse_it && begin.ptr >= end.ptr) || (is_reverse_it && begin.base <= end.base)) { + return dbuf_end_it; + } + + for (GUF_CAT(GUF_DBUF_NAME, _iter) it = begin; it.ptr != end.ptr && it.ptr != NULL; GUF_CAT(GUF_DBUF_NAME, _iter_next)(dbuf, it, 1)) { + if (predicate(it.ptr)) { + return it; + } + } + return GUF_CAT(GUF_DBUF_NAME, _end)(dbuf); +} + +#endif /* end #ifdef GUF_IMPL */ + +#endif /* end GUF_DBUF_ONLY_TYPES */ + +#undef GUF_DBUF_INITIAL_CAP +#undef GUF_DBUF_USE_GROWTH_FAC_ONE_POINT_FIVE + +#undef GUF_DBUF_NAME +#undef GUF_CNT_WITH_ELEM_CTX + +#undef GUF_T +#undef GUF_T_COPY +#undef GUF_T_MOVE +#undef GUF_T_FREE +#undef GUF_T_EQ +#undef GUF_T_IS_INTEGRAL_TYPE + +#undef GUF_DBUF_KWRDS +#undef GUF_DBUF_IMPL +#undef GUF_DBUF_IMPL_STATIC + +#undef GUF_DBUF_WITHOUT_TYPES +#undef GUF_DBUF_ONLY_TYPES diff --git a/libguf/src/guf_dict.h b/libguf/src/guf_dict.h new file mode 100755 index 0000000..37308fe --- /dev/null +++ b/libguf/src/guf_dict.h @@ -0,0 +1,1053 @@ +/* + is parametrized: yes +*/ + +#if defined(GUF_DICT_IMPL_STATIC) + #define GUF_DICT_KWRDS static +#else + #define GUF_DICT_KWRDS +#endif + +#ifndef GUF_DICT_H +#define GUF_DICT_H + #include "guf_common.h" + #include "guf_alloc.h" + #include "guf_hash.h" + // MAX_LOAD_FACTOR must be in range [0.1, 0.9] + #define GUF_DICT_MAX_LOAD_FACTOR_DEFAULT 0.666 +#endif + +#ifndef GUF_DICT_KEY_T + #error "Undefined container template GUF_DICT_KEY_T" +#endif + +#ifndef GUF_DICT_KEY_HASH + #error "Undefined container template GUF_DICT_KEY_HASH" +#endif + +#if !defined(GUF_DICT_KEY_T_EQ) && !defined(GUF_DICT_KEY_T_IS_INTEGRAL_TYPE) + #error "Undefined container template GUF_DICT_KEY_T_EQ" +#endif + +#ifndef GUF_DICT_VAL_T + #define GUF_DICT_IS_SET +#endif + +#if defined(GUF_DICT_32_BIT_HASH) + #define GUF_DICT_HASH_T uint_least32_t + #define GUF_DICT_HASH_T_MAX GUF_UINT32_MAX +#elif defined(GUF_DICT_64_BIT_HASH) + #define GUF_DICT_HASH_T uint_least64_t + #define GUF_DICT_HASH_T_MAX GUF_UINT64_MAX +#else + #define GUF_DICT_HASH_T guf_hash_size_t + #define GUF_DICT_HASH_T_MAX GUF_HASH_MAX +#endif + +#if defined (GUF_DICT_64_BIT_IDX) + #define GUF_DICT_KV_META_T uint_least64_t + /* + Store a 16-bit hash-fragment in the upper 16-bits of kv_meta + -> (2^48 - 1 is IDX_NULL, 2^48 - 2 is IDX_TOMBSTONE, 2^48 - 3 is the largest actual idx, + i.e. the max amount of actual kv_elems the dict could hold is 2^48 - 2 = 281,474,976,710,654). + */ + #define GUF_DICT_KV_META_HASHFRAG_MASK UINT64_C(0xffff000000000000) + #define GUF_DICT_KV_META_IDX_MASK (~UINT64_C(0xffff000000000000)) + + #if GUF_DICT_HASH_T_MAX == GUF_UINT64_MAX + #define GUF_DICT_HASH_T_GET_HASHFRAG(HASH) ( (HASH) & GUF_DICT_KV_META_HASHFRAG_MASK ) + #elif GUF_DICT_HASH_T_MAX == GUF_UINT32_MAX + #define GUF_DICT_HASH_T_GET_HASHFRAG(HASH) ( (((uint_least64_t)(HASH)) << 32u) & GUF_DICT_KV_META_HASHFRAG_MASK ) + #else + #error "guf_dict: invalid hash size (should not happen)" + #endif +#else + #define GUF_DICT_KV_META_T uint_least32_t + /* + Store a 7-bit hash-fragment in the upper 7-bits of kv_meta + -> (2^25 - 1 is IDX_NULL, 2^25 - 2 is IDX_TOMBSTONE, 2^25 - 3 is the largest actual idx, + i.e. the max amount of actual kv_elems the dict could hold is 2^25 - 2 = 33,554,430). + */ + #define GUF_DICT_KV_META_HASHFRAG_MASK UINT32_C(0xfe000000) + #define GUF_DICT_KV_META_IDX_MASK (~UINT32_C(0xfe000000)) + + #if GUF_DICT_HASH_T_MAX == GUF_UINT64_MAX + #define GUF_DICT_HASH_T_GET_HASHFRAG(HASH) ( ((uint_least32_t)((HASH) >> 32u)) & GUF_DICT_KV_META_HASHFRAG_MASK ) + #elif GUF_DICT_HASH_T_MAX == GUF_UINT32_MAX + #define GUF_DICT_HASH_T_GET_HASHFRAG(HASH) ( (HASH) & GUF_DICT_KV_META_HASHFRAG_MASK ) + #else + #error "guf_dict: invalid hash size (should not happen)" + #endif +#endif + +#define GUF_DICT_KV_META_IDX_NULL GUF_DICT_KV_META_IDX_MASK +#define GUF_DICT_KV_META_IDX_TOMBSTONE (GUF_DICT_KV_META_IDX_NULL - 1) +#define GUF_DICT_KV_META_IDX_MAX (GUF_DICT_KV_META_IDX_TOMBSTONE - 1) + +#define GUF_DICT_META_GET_IDX(META) ( (META) & GUF_DICT_KV_META_IDX_MASK ) +#define GUF_DICT_META_GET_HASHFRAG(META) ( (META) & GUF_DICT_KV_META_HASHFRAG_MASK ) +#define GUF_DICT_META_IS_NULL(META) ( GUF_DICT_META_GET_IDX(META) == GUF_DICT_KV_META_IDX_NULL ) +#define GUF_DICT_META_IS_TOMBSTONE(META) ( GUF_DICT_META_GET_IDX(META) == GUF_DICT_KV_META_IDX_TOMBSTONE ) + + +#ifndef GUF_DICT_NAME + #define GUF_DICT_NAME GUF_CAT(dict_, GUF_CAT(GUF_DICT_KEY_T, GUF_CAT(_to_, GUF_DICT_VAL_T))) +#endif +#ifndef GUF_DICT_KV_NAME + #define GUF_DICT_KV_NAME GUF_CAT(GUF_DICT_NAME, _kv) +#endif + +#ifndef GUF_DICT_MAX_LOAD_FACTOR + #define GUF_DICT_MAX_LOAD_FACTOR GUF_DICT_MAX_LOAD_FACTOR_DEFAULT +#endif + +#define GUF_DICT_KV_DBUF GUF_CAT(GUF_DICT_KV_NAME, _dbuf) + +// - GUF_T_COPY: cpy function with signature GUF_T *copy(GUF_T *dst, const GUF_T *src, void *ctx) (default: copy by value) +// - GUF_T_MOVE: move function with signature GUF_T *move(GUF_T *dst, GUF_T *src, void *ctx) (default: undefined) +// - GUF_T_FREE: free function with signature void free(GUF_T *a, void *ctx) (default: undefined) +// - GUF_T_EQ: equality function with signature bool eq(const GUF_T *a, const GUF_T *a) (default: undefined, or equality by value if GUF_T_IS_INTEGRAL_TYPE is defined) + +#ifndef GUF_DICT_IMPL + +typedef struct GUF_DICT_KV_NAME { + GUF_DICT_KEY_T key; + #ifdef GUF_DICT_VAL_T + GUF_DICT_VAL_T val; + #endif +} GUF_DICT_KV_NAME; + +#define GUF_T GUF_DICT_KV_NAME +#define GUF_DBUF_NAME GUF_DICT_KV_DBUF +#define GUF_DBUF_ONLY_TYPES +#include "guf_dbuf.h" + +typedef struct GUF_DICT_NAME { + GUF_DICT_KV_DBUF kv_elems; // The actual key-value elements (contiguous in memory) + GUF_DICT_KV_META_T *kv_indices; // Indices into the kv_elems dbuf. + ptrdiff_t kv_indices_cap, num_tombstones; + ptrdiff_t max_probelen; // Stores the worst-case probe-length (for performance measurement) +} GUF_DICT_NAME; + +typedef GUF_CAT(GUF_DICT_KV_DBUF, _iter) GUF_CAT(GUF_DICT_NAME, _iter); + +#endif + +GUF_DICT_KWRDS GUF_DICT_NAME *GUF_CAT(GUF_DICT_NAME, _try_init_with_capacity)(GUF_DICT_NAME *ht, guf_allocator *alloc, ptrdiff_t kv_elem_capacity, guf_err *err); +GUF_DICT_KWRDS GUF_DICT_NAME *GUF_CAT(GUF_DICT_NAME, _init_with_capacity)(GUF_DICT_NAME *ht, guf_allocator *alloc, ptrdiff_t kv_elem_capacity); +GUF_DICT_KWRDS GUF_DICT_NAME *GUF_CAT(GUF_DICT_NAME, _try_init)(GUF_DICT_NAME *ht, guf_allocator *alloc, guf_err *err); +GUF_DICT_KWRDS GUF_DICT_NAME *GUF_CAT(GUF_DICT_NAME, _init)(GUF_DICT_NAME *ht, guf_allocator *alloc); + +GUF_DICT_KWRDS void GUF_CAT(GUF_DICT_NAME, _free)(GUF_DICT_NAME *ht, void *ctx); +GUF_DICT_KWRDS GUF_DICT_NAME *GUF_CAT(GUF_DICT_NAME, _copy)(GUF_DICT_NAME *dst, const GUF_DICT_NAME *src, void *ctx); +GUF_DICT_KWRDS GUF_DICT_NAME *GUF_CAT(GUF_DICT_NAME, _move)(GUF_DICT_NAME *dst, GUF_DICT_NAME *src, void *ctx); + +GUF_DICT_KWRDS void GUF_CAT(GUF_DICT_NAME, _try_insert)(GUF_DICT_NAME *ht, GUF_DICT_KEY_T *key, GUF_DICT_VAL_T *val, guf_cpy_opt key_opt, guf_cpy_opt val_opt, guf_err *err); +GUF_DICT_KWRDS void GUF_CAT(GUF_DICT_NAME, _insert)(GUF_DICT_NAME *ht, GUF_DICT_KEY_T *key, GUF_DICT_VAL_T *val, guf_cpy_opt key_opt, guf_cpy_opt val_opt); +GUF_DICT_KWRDS void GUF_CAT(GUF_DICT_NAME, _try_insert_val_arg)(GUF_DICT_NAME *ht, GUF_DICT_KEY_T key, GUF_DICT_VAL_T val, guf_cpy_opt key_opt, guf_cpy_opt val_opt, guf_err *err); +GUF_DICT_KWRDS void GUF_CAT(GUF_DICT_NAME, _insert_val_arg)(GUF_DICT_NAME *ht, GUF_DICT_KEY_T key, GUF_DICT_VAL_T val, guf_cpy_opt key_opt, guf_cpy_opt val_opt); + +GUF_DICT_KWRDS bool GUF_CAT(GUF_DICT_NAME, _erase)(GUF_DICT_NAME *ht, const GUF_DICT_KEY_T *key); +GUF_DICT_KWRDS bool GUF_CAT(GUF_DICT_NAME, _erase_val_arg)(GUF_DICT_NAME *ht, GUF_DICT_KEY_T key); + +#ifdef GUF_DICT_VAL_T + GUF_DICT_KWRDS GUF_DICT_VAL_T *GUF_CAT(GUF_DICT_NAME, _at)(GUF_DICT_NAME *ht, const GUF_DICT_KEY_T *key); + GUF_DICT_KWRDS GUF_DICT_VAL_T *GUF_CAT(GUF_DICT_NAME, _at_val_arg)(GUF_DICT_NAME *ht, GUF_DICT_KEY_T key); +#endif + +GUF_DICT_KWRDS bool GUF_CAT(GUF_DICT_NAME, _contains)(GUF_DICT_NAME *ht, const GUF_DICT_KEY_T *key); +GUF_DICT_KWRDS bool GUF_CAT(GUF_DICT_NAME, _contains_val_arg)(GUF_DICT_NAME *ht, GUF_DICT_KEY_T key); + +GUF_DICT_KWRDS ptrdiff_t GUF_CAT(GUF_DICT_NAME, _size)(const GUF_DICT_NAME *ht); +GUF_DICT_KWRDS double GUF_CAT(GUF_DICT_NAME, _load_factor)(const GUF_DICT_NAME *ht); +GUF_DICT_KWRDS double GUF_CAT(GUF_DICT_NAME, _load_factor_without_tombstones)(const GUF_DICT_NAME *ht); + +GUF_DICT_KWRDS void GUF_CAT(GUF_DICT_NAME, _try_rehash_and_grow)(GUF_DICT_NAME *ht, guf_err *err); +GUF_DICT_KWRDS void GUF_CAT(GUF_DICT_NAME, _rehash_without_growth)(GUF_DICT_NAME *ht); + +GUF_DICT_KWRDS void GUF_CAT(GUF_DICT_NAME, _try_shrink_to_fit)(GUF_DICT_NAME *ht, guf_err *err); +GUF_DICT_KWRDS void GUF_CAT(GUF_DICT_NAME, _shrink_to_fit)(GUF_DICT_NAME *ht); + +GUF_DICT_KWRDS ptrdiff_t GUF_CAT(GUF_DICT_NAME, _max_capacity)(void); +GUF_DICT_KWRDS size_t GUF_CAT(GUF_DICT_NAME, _memory_usage)(const GUF_DICT_NAME *ht); + +GUF_DICT_KWRDS bool GUF_CAT(GUF_DICT_NAME, _valid)(const GUF_DICT_NAME *ht); +GUF_DICT_KWRDS bool GUF_CAT(GUF_DICT_NAME, _debug_valid_size)(const GUF_DICT_NAME *ht); + +/* Iterator functions */ +GUF_DICT_KWRDS GUF_CAT(GUF_DICT_NAME, _iter) GUF_CAT(GUF_DICT_NAME, _begin)(const GUF_DICT_NAME* ht); +GUF_DICT_KWRDS GUF_CAT(GUF_DICT_NAME, _iter) GUF_CAT(GUF_DICT_NAME, _end)(const GUF_DICT_NAME* ht); +GUF_DICT_KWRDS GUF_CAT(GUF_DICT_NAME, _iter) GUF_CAT(GUF_DICT_NAME, _rbegin)(const GUF_DICT_NAME* ht); +GUF_DICT_KWRDS GUF_CAT(GUF_DICT_NAME, _iter) GUF_CAT(GUF_DICT_NAME, _rend)(const GUF_DICT_NAME* ht); +GUF_DICT_KWRDS bool GUF_CAT(GUF_DICT_NAME, _iter_is_end)(const GUF_DICT_NAME* ht, GUF_CAT(GUF_DICT_NAME, _iter) it); + +GUF_DICT_KWRDS GUF_CAT(GUF_DICT_NAME, _iter) GUF_CAT(GUF_DICT_NAME, _iter_next)(const GUF_DICT_NAME *ht, GUF_CAT(GUF_DICT_NAME, _iter) it, ptrdiff_t step); +GUF_DICT_KWRDS GUF_CAT(GUF_DICT_NAME, _iter) GUF_CAT(GUF_DICT_NAME, _iter_at_idx)(const GUF_DICT_NAME *ht, ptrdiff_t idx); +GUF_DICT_KWRDS GUF_CAT(GUF_DICT_NAME, _iter) GUF_CAT(GUF_DICT_NAME, _reverse_iter_at_idx)(const GUF_DICT_NAME *ht, ptrdiff_t idx); +GUF_DICT_KWRDS ptrdiff_t GUF_CAT(GUF_DICT_NAME, _iter_to_idx)(const GUF_DICT_NAME *ht, GUF_CAT(GUF_DICT_NAME, _iter) it); + +#if defined(GUF_DICT_VAL_T) && (defined(GUF_DICT_VAL_T_EQ) || defined(GUF_DICT_VAL_T_IS_INTEGRAL_TYPE)) + GUF_DICT_KWRDS GUF_CAT(GUF_DICT_NAME, _iter) GUF_CAT(GUF_DICT_NAME, _find_val)(GUF_DICT_NAME *ht, GUF_CAT(GUF_DICT_NAME, _iter) begin, GUF_CAT(GUF_DICT_NAME, _iter) end, const GUF_DICT_VAL_T *needle_val); + GUF_DICT_KWRDS GUF_CAT(GUF_DICT_NAME, _iter) GUF_CAT(GUF_DICT_NAME, _find_val_val_arg)(GUF_DICT_NAME *ht, GUF_CAT(GUF_DICT_NAME, _iter) begin, GUF_CAT(GUF_DICT_NAME, _iter) end, GUF_DICT_VAL_T needle_val); +#endif +#if defined(GUF_DICT_VAL_T) + GUF_DICT_KWRDS GUF_CAT(GUF_DICT_NAME, _iter) GUF_CAT(GUF_DICT_NAME, _find_val_if)(GUF_DICT_NAME *ht, GUF_CAT(GUF_DICT_NAME, _iter) begin, GUF_CAT(GUF_DICT_NAME, _iter) end, bool (*predicate)(const GUF_DICT_VAL_T *)); +#endif + +// #define GUF_DICT_IMPL /* DEBUGGGGGGGGG */ + +#if defined(GUF_DICT_IMPL) || defined(GUF_DICT_IMPL_STATIC) +#include +#include "guf_assert.h" +#include "guf_math.h" + +GUF_DICT_KWRDS bool GUF_CAT(GUF_DICT_NAME, _debug_valid_size)(const GUF_DICT_NAME *ht) +{ + ptrdiff_t cnt = 0; + for (ptrdiff_t i = 0; i < ht->kv_indices_cap; ++i) { + if (!GUF_DICT_META_IS_NULL(ht->kv_indices[i]) && !GUF_DICT_META_IS_TOMBSTONE(ht->kv_indices[i])) { + ++cnt; + } + } + return cnt == ht->kv_elems.size; +} + +static inline void GUF_CAT(GUF_DICT_KV_NAME, _free)(GUF_DICT_KV_NAME *kv, void *ctx) +{ + (void)ctx; + #ifdef GUF_DICT_KEY_T_FREE + GUF_DICT_KEY_T_FREE(&kv->key, NULL); + #endif + #ifdef GUF_DICT_VAL_T_FREE + GUF_DICT_VAL_T_FREE(&kv->val, NULL); + #endif + #if !defined(GUF_DICT_KEY_T_FREE) && !defined(GUF_DICT_VAL_T_FREE) + (void)kv; + #endif +} +#define GUF_T GUF_DICT_KV_NAME +#define GUF_T_FREE GUF_CAT(GUF_DICT_KV_NAME, _free) +#define GUF_DBUF_NAME GUF_DICT_KV_DBUF +#define GUF_DBUF_WITHOUT_TYPES +#define GUF_DBUF_IMPL_STATIC +#include "guf_dbuf.h" + +GUF_DICT_KWRDS double GUF_CAT(GUF_DICT_NAME, _load_factor)(const GUF_DICT_NAME *ht) +{ + if (ht->kv_indices_cap == 0) { + return 1; + } + ptrdiff_t occupied_count = ht->kv_elems.size + ht->num_tombstones; + GUF_ASSERT(occupied_count <= ht->kv_indices_cap); + return (double)occupied_count / (double)ht->kv_indices_cap; +} + +GUF_DICT_KWRDS double GUF_CAT(GUF_DICT_NAME, _load_factor_without_tombstones)(const GUF_DICT_NAME *ht) +{ + if (ht->kv_indices_cap == 0) { + return 1; + } + GUF_ASSERT(ht->kv_elems.size <= ht->kv_indices_cap); + return (double)ht->kv_elems.size / (double)ht->kv_indices_cap; +} + + +GUF_DICT_KWRDS GUF_DICT_NAME *GUF_CAT(GUF_DICT_NAME, _try_init_with_capacity)(GUF_DICT_NAME *ht, guf_allocator *alloc, ptrdiff_t kv_elem_capacity, guf_err *err) +{ + GUF_ASSERT(GUF_DICT_MAX_LOAD_FACTOR >= 0.1 && GUF_DICT_MAX_LOAD_FACTOR <= 0.9); + if (!ht || !alloc) { + guf_err_set_or_panic(err, GUF_ERR_INVALID_ARG, GUF_ERR_MSG("in dict_try_init_with_capacity: ht or alloc NULL")); + return NULL; + } else if (kv_elem_capacity < 0 || kv_elem_capacity > GUF_CAT(GUF_DICT_NAME, _max_capacity)()) { + guf_err_set_or_panic(err, GUF_ERR_INVALID_ARG, GUF_ERR_MSG("in dict_try_init_with_capacity: kv_elem_capacity < 0 or kv_elem_capacity > max_capacity")); + return NULL; + } + + ht->kv_indices = NULL; + ht->kv_indices_cap = 0; + ht->num_tombstones = ht->max_probelen = 0; + + ht->kv_elems = (GUF_DICT_KV_DBUF){0}; + GUF_CAT(GUF_DICT_KV_DBUF, _try_init)(&ht->kv_elems, kv_elem_capacity, alloc, err); + if (err != GUF_ERR_NONE) { + return NULL; + } + + if (kv_elem_capacity > 0) { + const size_t MAX_IDX_CAP = GUF_ALLOC_MAX_CAPACITY(GUF_DICT_KV_META_T); + const size_t desired_idx_cap = (size_t)guf_min_f64(kv_elem_capacity * 1.0 / GUF_DICT_MAX_LOAD_FACTOR, MAX_IDX_CAP); + // Capacities must be powers of two. + size_t kv_idx_cap = 1; + while ((kv_idx_cap <= MAX_IDX_CAP / 2) && (kv_idx_cap <= desired_idx_cap)) { + kv_idx_cap <<= 1; + } + GUF_ASSERT_RELEASE(guf_is_pow2_size_t(kv_idx_cap)); + GUF_ASSERT_RELEASE(kv_idx_cap >= (size_t)ht->kv_elems.capacity && kv_idx_cap <= MAX_IDX_CAP); + const size_t num_bytes = kv_idx_cap * sizeof(GUF_DICT_KV_META_T); + GUF_ASSERT_RELEASE(!guf_mul_is_overflow_size_t(kv_idx_cap, sizeof(GUF_DICT_KV_META_T)) && num_bytes <= GUF_ALLOC_MAX_BYTES(GUF_DICT_KV_META_T)); + + GUF_DICT_KV_META_T *kv_indices = ht->kv_elems.allocator->alloc(num_bytes, ht->kv_elems.allocator->ctx); + if (!kv_indices) { + guf_err_set_or_panic(err, GUF_ERR_ALLOC_FAIL, GUF_ERR_MSG("in dict_try_init_with_capacity: allocation of ht->kv_indices failed")); + GUF_CAT(GUF_DICT_KV_DBUF, _free)(&ht->kv_elems, NULL); + return NULL; + } + ht->kv_indices = kv_indices; + ht->kv_indices_cap = kv_idx_cap; + GUF_ASSERT(ht->kv_indices_cap >= ht->kv_elems.capacity); + for (ptrdiff_t i = 0; i < ht->kv_indices_cap; ++i) { + ht->kv_indices[i] = GUF_DICT_KV_META_IDX_NULL; + } + } + return ht; +} + +GUF_DICT_KWRDS GUF_DICT_NAME *GUF_CAT(GUF_DICT_NAME, _init_with_capacity)(GUF_DICT_NAME *ht, guf_allocator *alloc, ptrdiff_t kv_elem_capacity) +{ + return GUF_CAT(GUF_DICT_NAME, _try_init_with_capacity)(ht, alloc, kv_elem_capacity, NULL); +} + + +GUF_DICT_KWRDS GUF_DICT_NAME *GUF_CAT(GUF_DICT_NAME, _try_init)(GUF_DICT_NAME *ht, guf_allocator *alloc, guf_err *err) +{ + return GUF_CAT(GUF_DICT_NAME, _try_init_with_capacity)(ht, alloc, 0, err); +} + +GUF_DICT_KWRDS GUF_DICT_NAME *GUF_CAT(GUF_DICT_NAME, _init)(GUF_DICT_NAME *ht, guf_allocator *alloc) +{ + return GUF_CAT(GUF_DICT_NAME, _try_init)(ht, alloc, NULL); +} + +GUF_DICT_KWRDS bool GUF_CAT(GUF_DICT_NAME, _valid)(const GUF_DICT_NAME *ht) +{ + if (!ht) { + return false; + } + bool kv_dbuf_valid = GUF_CAT(GUF_DICT_KV_DBUF, _valid)(&ht->kv_elems); + bool kv_meta_buf_valid = (!ht->kv_indices && !ht->kv_indices_cap) || (ht->kv_indices && ht->kv_indices_cap && guf_is_pow2_size_t(ht->kv_indices_cap)); + bool count_valid = ht->num_tombstones >= 0 && ht->kv_elems.size >= 0 && (size_t)ht->kv_elems.size <= GUF_DICT_KV_META_IDX_MAX && ((ht->kv_elems.size + ht->num_tombstones) <= ht->kv_indices_cap); + return kv_dbuf_valid && kv_meta_buf_valid && count_valid; +} + +GUF_DICT_KWRDS ptrdiff_t GUF_CAT(GUF_DICT_NAME, _max_capacity)(void) +{ + const size_t max_cap_kv_elems = GUF_MIN(GUF_DICT_KV_META_IDX_MAX + 1, GUF_ALLOC_MAX_CAPACITY(GUF_DICT_KV_NAME)); + + const size_t max_cap_kv_indices = GUF_ALLOC_MAX_CAPACITY(GUF_DICT_KV_META_T); + // Find next power of two (capacities must be powers of two). + size_t pow2_cap = 1; + const size_t MAX_SIZE_POW2 = SIZE_MAX & ~(SIZE_MAX >> 1); + while ( (pow2_cap < MAX_SIZE_POW2) && ((pow2_cap << 1) <= max_cap_kv_indices) ) { + pow2_cap <<= 1; + } + GUF_ASSERT(guf_is_pow2_size_t(pow2_cap) && pow2_cap <= max_cap_kv_indices && pow2_cap > 1); + + return GUF_MIN(GUF_MIN(max_cap_kv_elems, pow2_cap), PTRDIFF_MAX); +} + +GUF_DICT_KWRDS size_t GUF_CAT(GUF_DICT_NAME, _memory_usage)(const GUF_DICT_NAME *ht) +{ + GUF_ASSERT_RELEASE(GUF_CAT(GUF_DICT_NAME, _valid)(ht)); + const size_t mem_kv_indices = (size_t)ht->kv_indices_cap * sizeof(GUF_DICT_KV_META_T); + const size_t mem_kv_elems = (size_t)ht->kv_elems.capacity * sizeof(GUF_DICT_KV_NAME); + return mem_kv_indices + mem_kv_elems; +} + +GUF_DICT_KWRDS void GUF_CAT(GUF_DICT_NAME, _free)(GUF_DICT_NAME *ht, void *ctx) +{ + (void)ctx; + GUF_ASSERT_RELEASE(GUF_CAT(GUF_DICT_NAME, _valid)(ht)); + guf_allocator *allocator = ht->kv_elems.allocator; + + if (ht->kv_indices) { + allocator->free(ht->kv_indices, ht->kv_indices_cap * sizeof(GUF_DICT_KV_META_T), allocator->ctx); + ht->kv_indices = NULL; + ht->kv_indices_cap = 0; + } + + GUF_CAT(GUF_DICT_KV_DBUF, _free)(&ht->kv_elems, NULL); + + ht->num_tombstones = 0; + ht->max_probelen = 0; +} + +GUF_DICT_KWRDS GUF_DICT_NAME *GUF_CAT(GUF_DICT_NAME, _copy)(GUF_DICT_NAME *dst, const GUF_DICT_NAME *src, void *ctx) +{ + (void)ctx; + GUF_ASSERT_RELEASE(dst); + GUF_ASSERT_RELEASE(GUF_CAT(GUF_DICT_NAME, _valid)(src)); + GUF_ASSERT_RELEASE(dst != src); + + dst->kv_indices = NULL; + dst->kv_indices_cap = dst->max_probelen = dst->num_tombstones = 0; + dst->kv_elems.allocator = NULL; + dst->kv_elems.data = NULL; + dst->kv_elems.capacity = dst->kv_elems.size = 0; + + GUF_DICT_KV_DBUF *kv_elems_cpy = GUF_CAT(GUF_DICT_KV_DBUF, _copy)(&dst->kv_elems, &src->kv_elems, NULL); + if (!kv_elems_cpy) { + return NULL; + } + + if (src->kv_indices) { + GUF_ASSERT(src->kv_indices_cap > 0); + const ptrdiff_t num_bytes = src->kv_indices_cap * sizeof(src->kv_indices[0]); + dst->kv_indices = src->kv_elems.allocator->alloc(num_bytes, src->kv_elems.allocator->ctx); + if (!dst->kv_indices) { + GUF_CAT(GUF_DICT_KV_DBUF, _free)(&dst->kv_elems, NULL); + return NULL; + } + memcpy(dst->kv_indices, src->kv_indices, num_bytes); + dst->kv_indices_cap = src->kv_indices_cap; + } else { + dst->kv_indices = NULL; + } + + dst->max_probelen = src->max_probelen; + dst->num_tombstones = src->num_tombstones; + + GUF_ASSERT(dst->kv_elems.size == src->kv_elems.size && dst->kv_elems.capacity == src->kv_elems.capacity && dst->kv_elems.allocator == src->kv_elems.allocator); + GUF_ASSERT(dst->kv_indices_cap == src->kv_indices_cap); + return dst; +} + +GUF_DICT_KWRDS GUF_DICT_NAME *GUF_CAT(GUF_DICT_NAME, _move)(GUF_DICT_NAME *dst, GUF_DICT_NAME *src, void *ctx) +{ + (void)ctx; + GUF_ASSERT_RELEASE(dst); + GUF_ASSERT_RELEASE(GUF_CAT(GUF_DICT_NAME, _valid)(src)); + GUF_ASSERT_RELEASE(dst != src); + + dst->kv_elems = src->kv_elems; + dst->kv_indices = src->kv_indices; + dst->kv_indices_cap = src->kv_indices_cap; + dst->max_probelen = src->max_probelen; + dst->num_tombstones = src->num_tombstones; + + src->kv_indices = NULL; + src->kv_indices_cap = src->max_probelen = src->num_tombstones = 0; + + src->kv_elems.allocator = NULL; + src->kv_elems.data = NULL; + src->kv_elems.capacity = src->kv_elems.size = 0; + + return dst; +} + +GUF_DICT_KWRDS ptrdiff_t GUF_CAT(GUF_DICT_NAME, _size)(const GUF_DICT_NAME *ht) +{ + GUF_ASSERT_RELEASE(GUF_CAT(GUF_DICT_NAME, _valid)(ht)); + return ht->kv_elems.size; +} + +static inline size_t GUF_CAT(GUF_DICT_NAME, _probe_offset_)(size_t probe_len) +{ + GUF_ASSERT(probe_len > 0); + #ifdef GUF_DICT_PROBE_LINEAR + return probe_len; // 1, 2, 3, 4, 5, ... + #else + /* + Quadratic probing: + 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 + 1u) / 2u; // 1, 3, 6, 10, 15, 21 ... (starting from probe_len == 1) + #endif +} + +static size_t GUF_CAT(GUF_DICT_NAME, _find_idx_)(GUF_DICT_NAME *ht, const GUF_DICT_KEY_T *key, GUF_DICT_HASH_T key_hash, bool *key_exists) +{ + if (ht->kv_indices_cap <= 0) { + *key_exists = false; + return SIZE_MAX; + } + + const GUF_DICT_KV_META_T key_hash_frag = GUF_DICT_HASH_T_GET_HASHFRAG(key_hash); + + #define GUF_MOD_CAP(A) ((size_t)(A) & (size_t)(ht->kv_indices_cap - 1)) /* A % ht->kv_indices_cap (kv_indices_cap must be a power of two > 0) */ + + size_t idx = GUF_MOD_CAP(key_hash); + const size_t start_idx = idx; + size_t first_tombstone_idx = SIZE_MAX; + size_t probe_len = 0; + // size_t seen_occupied = 0; // This allows us to bail out early once we visited every non-null/non-tombstone kv_idx. + do { + const GUF_DICT_KV_META_T kv_idx = GUF_DICT_META_GET_IDX(ht->kv_indices[idx]); + const GUF_DICT_KV_META_T kv_hashfrag = GUF_DICT_META_GET_HASHFRAG(ht->kv_indices[idx]); + + if (kv_idx == GUF_DICT_KV_META_IDX_NULL) { // 1.) Empty. + if (first_tombstone_idx != SIZE_MAX) { + idx = first_tombstone_idx; + } + ht->max_probelen = GUF_MAX((ptrdiff_t)probe_len, ht->max_probelen); + *key_exists = false; + return idx; + } else if (kv_idx == GUF_DICT_KV_META_IDX_TOMBSTONE) { // 2.) Tombstone. + if (first_tombstone_idx == SIZE_MAX) { + first_tombstone_idx = idx; + } + goto probe; + } else if (key_hash_frag == kv_hashfrag && GUF_DICT_KEY_T_EQ(key, &(GUF_CAT(GUF_DICT_KV_DBUF, _at)(&ht->kv_elems, kv_idx)->key))) { // 3.) Key already exists. + ht->max_probelen = GUF_MAX((ptrdiff_t)probe_len, ht->max_probelen); + *key_exists = true; + return idx; + } else { // 4.) Probe because kv_idx was a tombstone or because key != kv_elems[kv_idx] + probe: + ++probe_len; + idx = GUF_MOD_CAP(start_idx + GUF_CAT(GUF_DICT_NAME, _probe_offset_)(probe_len)); // NOTE: Add probe_offset to start_idx and not to idx. + GUF_ASSERT((ptrdiff_t)probe_len <= (ht->kv_elems.size + ht->num_tombstones)); + } + } while (idx != start_idx && probe_len < (size_t)ht->kv_indices_cap); + + *key_exists = false; + if (first_tombstone_idx != SIZE_MAX) { // Edge case: No empty slots, but found tombstone. + ht->max_probelen = GUF_MAX((ptrdiff_t)probe_len, ht->max_probelen); + GUF_ASSERT(GUF_DICT_META_GET_IDX(ht->kv_indices[first_tombstone_idx]) == GUF_DICT_KV_META_IDX_NULL); + return first_tombstone_idx; + } else { // Failed to find an idx. + return SIZE_MAX; + } + #undef GUF_MOD_CAP +} + +static void GUF_CAT(GUF_DICT_NAME, _reinsert_elems_)(GUF_DICT_NAME *ht) +{ + GUF_ASSERT(GUF_CAT(GUF_DICT_NAME, _valid)(ht)); + GUF_ASSERT_RELEASE(ht->kv_indices && ht->kv_indices_cap > 0); + + for (ptrdiff_t i = 0; i < ht->kv_indices_cap; ++i) { + ht->kv_indices[i] = GUF_DICT_KV_META_IDX_NULL; + } + ht->num_tombstones = 0; + + GUF_ASSERT((size_t)ht->kv_elems.size < GUF_DICT_KV_META_IDX_MAX); + for (ptrdiff_t kv_idx = 0; kv_idx < ht->kv_elems.size; ++kv_idx) { + const GUF_DICT_KV_NAME *kv = GUF_CAT(GUF_DICT_KV_DBUF, _at)(&ht->kv_elems, kv_idx); + GUF_ASSERT(kv); + bool key_exists = false; + const GUF_DICT_HASH_T key_hash = GUF_DICT_KEY_HASH(&kv->key); + const size_t new_idx = GUF_CAT(GUF_DICT_NAME, _find_idx_)(ht, &kv->key, key_hash, &key_exists); + GUF_ASSERT(!key_exists); + GUF_ASSERT(new_idx < SIZE_MAX && new_idx < (size_t)ht->kv_indices_cap); + GUF_ASSERT((GUF_DICT_HASH_T_GET_HASHFRAG(key_hash) & (GUF_DICT_KV_META_T)kv_idx) == 0); + ht->kv_indices[new_idx] = GUF_DICT_HASH_T_GET_HASHFRAG(key_hash) | (GUF_DICT_KV_META_T)kv_idx; + } +} + +GUF_DICT_KWRDS void GUF_CAT(GUF_DICT_NAME, _rehash_without_growth)(GUF_DICT_NAME *ht) +{ + GUF_ASSERT_RELEASE(GUF_CAT(GUF_DICT_NAME, _valid)(ht)); + GUF_CAT(GUF_DICT_NAME, _reinsert_elems_)(ht); + GUF_ASSERT(ht->num_tombstones == 0); +} + +static void GUF_CAT(GUF_DICT_NAME, _try_grow_if_necessary_)(GUF_DICT_NAME *ht, bool always_grow, guf_err *err) +{ + GUF_ASSERT(GUF_CAT(GUF_DICT_NAME, _valid)(ht)); + + const double MAX_LOAD_FAC = GUF_DICT_MAX_LOAD_FACTOR; + GUF_ASSERT(GUF_DICT_MAX_LOAD_FACTOR >= 0.1 && GUF_DICT_MAX_LOAD_FACTOR <= 0.9); + + const ptrdiff_t KV_META_START_CAP = 32; // Must be a power of two > 0. + const ptrdiff_t KV_META_GROWTH_FAC = (ht->kv_indices_cap <= 128) ? 4 : 2; // Must be a power of two > 1. + + guf_allocator *allocator = ht->kv_elems.allocator; + + if (ht->kv_indices_cap == 0) { // 1.a) Allocate initial kv-index-buffer. + GUF_DICT_KV_META_T *new_kv_indices = allocator->alloc(KV_META_START_CAP * sizeof(GUF_DICT_KV_META_T), allocator->ctx); + if (new_kv_indices == NULL) { + guf_err_set_or_panic(err, GUF_ERR_ALLOC_FAIL, GUF_ERR_MSG("in function dict_try_grow: Initial allocation failed")); + return; + } + ht->kv_indices = new_kv_indices; + ht->kv_indices_cap = KV_META_START_CAP; + for (ptrdiff_t i = 0; i < ht->kv_indices_cap; ++i) { + new_kv_indices[i] = GUF_DICT_KV_META_IDX_NULL; + } + } else if ((GUF_CAT(GUF_DICT_NAME, _load_factor)(ht) >= MAX_LOAD_FAC) || always_grow) { // 1.b) Grow kv-index-buffer if necessary. + GUF_ASSERT(ht->kv_indices); + GUF_ASSERT((size_t)ht->kv_indices_cap <= GUF_ALLOC_MAX_CAPACITY(GUF_DICT_KV_META_T)); + const ptrdiff_t old_size_bytes = (size_t)ht->kv_indices_cap * sizeof(GUF_DICT_KV_META_T); + ptrdiff_t new_size_bytes = 0; + + const size_t MAX_SIZE_BYTES = (size_t)GUF_ALLOC_MAX_BYTES(GUF_DICT_KV_META_T); + const size_t new_size_bytes_test = (size_t)old_size_bytes * (size_t)KV_META_GROWTH_FAC; + if (guf_mul_is_overflow_size_t(old_size_bytes, KV_META_GROWTH_FAC) || new_size_bytes_test > MAX_SIZE_BYTES) { // Handle overflow (Remember: capacities have to be powers of two) + if (GUF_CAT(GUF_DICT_NAME, _load_factor_without_tombstones)(ht) < MAX_LOAD_FAC) { // Check if just removing tombstones without resizing would decrease the load factor enough. + GUF_CAT(GUF_DICT_NAME, _reinsert_elems_)(ht); + GUF_ASSERT(GUF_CAT(GUF_DICT_NAME, _load_factor)(ht) < MAX_LOAD_FAC); + guf_err_set_if_not_null(err, GUF_ERR_NONE); + return; + } + guf_err_set_or_panic(err, GUF_ERR_ALLOC_FAIL, GUF_ERR_MSG("in function dict_try_insert: New kv_indices_capacity would overflow)")); + return; + } else { + GUF_ASSERT(new_size_bytes_test <= PTRDIFF_MAX); + new_size_bytes = (ptrdiff_t)new_size_bytes_test; + } + GUF_ASSERT_RELEASE(new_size_bytes > old_size_bytes); + + // TODO: Not sure if alloc and free is better here than realloc (since we do not copy ht->kv_indices anyway.) + GUF_DICT_KV_META_T *new_kv_indices = allocator->alloc(new_size_bytes, allocator->ctx); + if (new_kv_indices == NULL) { + guf_err_set_or_panic(err, GUF_ERR_ALLOC_FAIL, GUF_ERR_MSG("in function dict_try_insert: re-allocation failed")); + return; + } + // NOTE: would be more memory-efficient to free first, but that would leave the dict in an invalid state if the alloc fails. + allocator->free(ht->kv_indices, old_size_bytes, allocator->ctx); + ht->kv_indices = new_kv_indices; + ht->kv_indices_cap = ht->kv_indices_cap * KV_META_GROWTH_FAC;; + GUF_ASSERT(guf_is_pow2_size_t(ht->kv_indices_cap)); + GUF_ASSERT(new_size_bytes / sizeof(GUF_DICT_KV_META_T) == ht->kv_indices_cap); + // ht->max_probelen = 0; + GUF_CAT(GUF_DICT_NAME, _reinsert_elems_)(ht); + GUF_ASSERT(ht->num_tombstones == 0); + } + + guf_err_set_if_not_null(err, GUF_ERR_NONE); + GUF_ASSERT(GUF_CAT(GUF_DICT_NAME, _load_factor)(ht) < MAX_LOAD_FAC); +} + +GUF_DICT_KWRDS void GUF_CAT(GUF_DICT_NAME, _try_rehash_and_grow)(GUF_DICT_NAME *ht, guf_err *err) +{ + GUF_ASSERT_RELEASE(GUF_CAT(GUF_DICT_NAME, _valid)(ht)); + GUF_CAT(GUF_DICT_NAME, _try_grow_if_necessary_)(ht, true, err); +} + +GUF_DICT_KWRDS void GUF_CAT(GUF_DICT_NAME, _try_insert)(GUF_DICT_NAME *ht, GUF_DICT_KEY_T *key, GUF_DICT_VAL_T *val, guf_cpy_opt key_opt, guf_cpy_opt val_opt, guf_err *err) +{ + GUF_ASSERT_RELEASE(GUF_CAT(GUF_DICT_NAME, _valid)(ht)); + + if (!key || !val) { + guf_err_set_or_panic(err, GUF_ERR_INVALID_ARG, GUF_ERR_MSG("in function dict_try_insert: key or val argument is NULL")); + return; + } + + if ((size_t)ht->kv_elems.size >= (GUF_DICT_KV_META_IDX_MAX + 1)) { + GUF_ASSERT(ht->kv_elems.size == (GUF_DICT_KV_META_IDX_MAX + 1)); + guf_err_set_or_panic(err, GUF_ERR_INT_OVERFLOW, GUF_ERR_MSG("in function dict_try_insert: dict has reached its max size 33,554,430 (or 2^48 - 2 for GUF_DICT_64_BIT_IDX)")); + return; + } + + // 1.) Grow kv-index-buffer if neccessary (or make the initial allocation.) + GUF_CAT(GUF_DICT_NAME, _try_grow_if_necessary_)(ht, false, err); + if (err != NULL && *err != GUF_ERR_NONE) { + guf_err_set_or_panic(err, *err, GUF_ERR_MSG("in function dict_try_insert: try_grow failed.")); + return; + } + GUF_ASSERT_RELEASE(ht->kv_indices_cap > ht->kv_elems.size); + + // 2.) Insert new key-value pair. + const GUF_DICT_HASH_T key_hash = GUF_DICT_KEY_HASH(key); + bool key_exists = false; + size_t idx = GUF_CAT(GUF_DICT_NAME, _find_idx_)(ht, key, key_hash, &key_exists); + if (key_exists) { + guf_err_set_or_panic(err, GUF_ERR_ALREADY_EXISTS, GUF_ERR_MSG("in function dict_try_insert: Key already exists")); + return; + } + GUF_ASSERT_RELEASE(idx < (size_t)ht->kv_indices_cap); + + if (GUF_DICT_META_IS_TOMBSTONE(ht->kv_indices[idx])) { + ht->num_tombstones -= 1; + GUF_ASSERT_RELEASE(ht->num_tombstones >= 0); + } + + GUF_ASSERT((GUF_DICT_HASH_T_GET_HASHFRAG(key_hash) & (GUF_DICT_KV_META_T)ht->kv_elems.size) == 0); + ht->kv_indices[idx] = GUF_DICT_HASH_T_GET_HASHFRAG(key_hash) | (GUF_DICT_KV_META_T)ht->kv_elems.size; + + GUF_DICT_KEY_T key_cpy; + GUF_DICT_KEY_T *key_cpy_res = NULL; + if (key_opt == GUF_CPY_DEEP) { + #ifdef GUF_DICT_KEY_T_COPY + key_cpy_res = GUF_DICT_KEY_T_COPY(&key_cpy, key, NULL); + #else + GUF_ASSERT_RELEASE(false); + #endif + } else if (key_opt == GUF_CPY_MOVE) { + #ifdef GUF_DICT_KEY_T_MOVE + key_cpy_res = GUF_DICT_KEY_T_MOVE(&key_cpy, key, NULL); + #else + GUF_ASSERT_RELEASE(false); + #endif + } else { + key_cpy = *key; + key_cpy_res = &key_cpy; + } + if (!key_cpy_res) { + guf_err_set_or_panic(err, GUF_ERR_ALLOC_FAIL, GUF_ERR_MSG("in function dict_try_insert: Failed to copy key")); + return; + } + + GUF_DICT_VAL_T val_cpy; + GUF_DICT_VAL_T *val_cpy_res = NULL; + if (val_opt == GUF_CPY_DEEP) { + #ifdef GUF_DICT_VAL_T_COPY + val_cpy_res = GUF_DICT_KEY_T_COPY(&val_cpy, val); + #else + GUF_ASSERT_RELEASE(false); + #endif + } else if (val_opt == GUF_CPY_MOVE) { + #ifdef GUF_DICT_VAL_T_MOVE + val_cpy_res = GUF_DICT_KEY_T_MOVE(&val_cpy, val); + #else + GUF_ASSERT_RELEASE(false); + #endif + } else { + val_cpy = *val; + val_cpy_res = &val_cpy; + } + if (!val_cpy_res) { + guf_err_set_or_panic(err, GUF_ERR_ALLOC_FAIL, GUF_ERR_MSG("in function dict_try_insert: Failed to copy value")); + if (key_opt == GUF_CPY_DEEP || key_opt == GUF_CPY_MOVE) { + #ifdef GUF_DICT_KEY_T_FREE + GUF_DICT_KEY_T_FREE(key_cpy_res, NULL); + #endif + } + return; + } + + GUF_DICT_KV_NAME kv = {.key = key_cpy, .val = val_cpy}; + GUF_CAT(GUF_DICT_KV_DBUF, _try_push_val)(&ht->kv_elems, kv, err); + + if (err && *err != GUF_ERR_NONE) { // Insertion failed. + GUF_ASSERT(*err != GUF_ERR_IDX_RANGE && *err != GUF_ERR_INVALID_ARG); + #ifdef GUF_DICT_KEY_T_FREE + GUF_DICT_KEY_T_FREE(&kv.key, NULL); + #endif + #ifdef GUF_DICT_VAL_T_FREE + GUF_DICT_VAL_T_FREE(&kv.val, NULL); + #endif + } +} + +GUF_DICT_KWRDS void GUF_CAT(GUF_DICT_NAME, _insert)(GUF_DICT_NAME *ht, GUF_DICT_KEY_T *key, GUF_DICT_VAL_T *val, guf_cpy_opt key_opt, guf_cpy_opt val_opt) +{ + GUF_CAT(GUF_DICT_NAME, _try_insert)(ht, key, val, key_opt, val_opt, NULL); +} + +GUF_DICT_KWRDS void GUF_CAT(GUF_DICT_NAME, _try_insert_val_arg)(GUF_DICT_NAME *ht, GUF_DICT_KEY_T key, GUF_DICT_VAL_T val, guf_cpy_opt key_opt, guf_cpy_opt val_opt, guf_err *err) +{ + GUF_CAT(GUF_DICT_NAME, _try_insert)(ht, &key, &val, key_opt, val_opt, err); +} + +GUF_DICT_KWRDS void GUF_CAT(GUF_DICT_NAME, _insert_val_arg)(GUF_DICT_NAME *ht, GUF_DICT_KEY_T key, GUF_DICT_VAL_T val, guf_cpy_opt key_opt, guf_cpy_opt val_opt) +{ + GUF_CAT(GUF_DICT_NAME, _insert)(ht, &key, &val, key_opt, val_opt); +} + + +#ifdef GUF_DICT_VAL_T + GUF_DICT_KWRDS GUF_DICT_VAL_T *GUF_CAT(GUF_DICT_NAME, _at)(GUF_DICT_NAME *ht, const GUF_DICT_KEY_T *key) + { + GUF_ASSERT_RELEASE(GUF_CAT(GUF_DICT_NAME, _valid)(ht)); + if (!key) { + return NULL; + } + const GUF_DICT_HASH_T key_hash = GUF_DICT_KEY_HASH(key); + bool key_exists = false; + const size_t idx = GUF_CAT(GUF_DICT_NAME, _find_idx_)(ht, key, key_hash, &key_exists); + if (!key_exists) { + return NULL; + } else { + GUF_ASSERT(idx != SIZE_MAX); + GUF_ASSERT((ptrdiff_t)idx < ht->kv_indices_cap); + const size_t kv_idx = GUF_DICT_META_GET_IDX(ht->kv_indices[idx]); + GUF_ASSERT(kv_idx <= PTRDIFF_MAX && (ptrdiff_t)kv_idx < ht->kv_elems.size); + return &GUF_CAT(GUF_DICT_KV_DBUF, _at)(&ht->kv_elems, kv_idx)->val; + } + } + + GUF_DICT_KWRDS GUF_DICT_VAL_T *GUF_CAT(GUF_DICT_NAME, _at_val_arg)(GUF_DICT_NAME *ht, GUF_DICT_KEY_T key) + { + return GUF_CAT(GUF_DICT_NAME, _at)(ht, &key); + } +#endif + +GUF_DICT_KWRDS bool GUF_CAT(GUF_DICT_NAME, _contains)(GUF_DICT_NAME *ht, const GUF_DICT_KEY_T *key) +{ + GUF_ASSERT_RELEASE(GUF_CAT(GUF_DICT_NAME, _valid)(ht)); + if (!key) { + return false; + } + bool key_exists = false; + const GUF_DICT_HASH_T key_hash = GUF_DICT_KEY_HASH(key); + const size_t idx = GUF_CAT(GUF_DICT_NAME, _find_idx_)(ht, key, key_hash, &key_exists); + if (key_exists) { + GUF_ASSERT(idx != SIZE_MAX); + GUF_ASSERT(!GUF_DICT_META_IS_TOMBSTONE(ht->kv_indices[idx])); + GUF_ASSERT(!GUF_DICT_META_IS_NULL(ht->kv_indices[idx])); + } + (void)idx; + return key_exists; +} + +GUF_DICT_KWRDS bool GUF_CAT(GUF_DICT_NAME, _contains_val_arg)(GUF_DICT_NAME *ht, GUF_DICT_KEY_T key) +{ + return GUF_CAT(GUF_DICT_NAME, _contains)(ht, &key); +} + + +GUF_DICT_KWRDS bool GUF_CAT(GUF_DICT_NAME, _erase)(GUF_DICT_NAME *ht, const GUF_DICT_KEY_T *key) +{ + GUF_ASSERT_RELEASE(GUF_CAT(GUF_DICT_NAME, _valid)(ht)); + + if (!key || ht->kv_elems.size == 0) { + return false; + } + + const GUF_DICT_HASH_T key_hash = GUF_DICT_KEY_HASH(key); + bool key_exists = false; + const size_t idx = GUF_CAT(GUF_DICT_NAME, _find_idx_)(ht, key, key_hash, &key_exists); + if (!key_exists) { + return false; + } + GUF_ASSERT(idx < SIZE_MAX && (ptrdiff_t)idx < ht->kv_indices_cap); + + const size_t kv_idx = (size_t)GUF_DICT_META_GET_IDX(ht->kv_indices[idx]); + GUF_ASSERT(kv_idx < (size_t)ht->kv_elems.size); + + ht->kv_indices[idx] = GUF_DICT_KV_META_IDX_TOMBSTONE; + ht->num_tombstones += 1; + + GUF_DICT_KV_NAME *kv = GUF_CAT(GUF_DICT_KV_DBUF, _at)(&ht->kv_elems, kv_idx); + GUF_ASSERT(kv); + + GUF_CAT(GUF_DICT_KV_NAME, _free)(kv, NULL); + + if (ht->kv_elems.size > 1 && (ptrdiff_t)kv_idx != ht->kv_elems.size - 1) { // Switch last kv-elem into the erased position and update its kv-index accordingly. + GUF_ASSERT(kv_idx <= GUF_DICT_KV_META_IDX_MAX); + // 1.) Switch kv_elem. + GUF_DICT_KV_NAME *last_kv = GUF_CAT(GUF_DICT_KV_DBUF, _back)(&ht->kv_elems); + GUF_ASSERT(last_kv); + GUF_ASSERT(kv != last_kv); + *kv = *last_kv; + // GUF_ASSERT(!GUF_DICT_KEY_T_EQ(key, &last_kv->key)); + + // 2.) Update kv_index. + const GUF_DICT_HASH_T last_key_hash = GUF_DICT_KEY_HASH(&last_kv->key); + bool last_key_exists = false; + const size_t last_idx = GUF_CAT(GUF_DICT_NAME, _find_idx_)(ht, &last_kv->key, last_key_hash, &last_key_exists); + GUF_ASSERT(last_idx != idx); + GUF_ASSERT(last_key_exists && (ptrdiff_t)last_idx < ht->kv_indices_cap); + GUF_ASSERT(GUF_DICT_META_GET_IDX(ht->kv_indices[last_idx]) == (GUF_DICT_KV_META_T)(ht->kv_elems.size - 1)); + ht->kv_indices[last_idx] = GUF_DICT_META_GET_HASHFRAG(ht->kv_indices[last_idx]) | (GUF_DICT_KV_META_T)kv_idx; + } + + ht->kv_elems.size -= 1; + + GUF_ASSERT(ht->kv_elems.size >= 0); + GUF_ASSERT(ht->num_tombstones <= ht->kv_indices_cap); + + // GUF_ASSERT(!GUF_CAT(GUF_DICT_NAME, _contains)(ht, key)); + + if (ht->kv_elems.size == 0 && ht->num_tombstones > 0) { // Optimisation: We can delete all tombstones here (TODO: not sure if actually a good idea...) + ptrdiff_t del_tombstone_cnt = 0; + for (ptrdiff_t i = 0; i < ht->kv_indices_cap && del_tombstone_cnt < ht->num_tombstones; ++i) { + const GUF_DICT_KV_META_T kv_del_idx = GUF_DICT_META_GET_IDX(ht->kv_indices[i]); + GUF_ASSERT(GUF_DICT_META_GET_HASHFRAG(ht->kv_indices[i]) == 0); + GUF_ASSERT(kv_del_idx == GUF_DICT_KV_META_IDX_TOMBSTONE || kv_del_idx == GUF_DICT_KV_META_IDX_NULL); + if (kv_del_idx == GUF_DICT_KV_META_IDX_TOMBSTONE) { + ht->kv_indices[i] = GUF_DICT_KV_META_IDX_NULL; + ++del_tombstone_cnt; + } else { + GUF_ASSERT(kv_del_idx == GUF_DICT_KV_META_IDX_NULL); + } + } + GUF_ASSERT(del_tombstone_cnt == ht->num_tombstones); + ht->num_tombstones = 0; + } + + return true; +} + +GUF_DICT_KWRDS bool GUF_CAT(GUF_DICT_NAME, _erase_val_arg)(GUF_DICT_NAME *ht, GUF_DICT_KEY_T key) +{ + return GUF_CAT(GUF_DICT_NAME, _erase)(ht, &key); +} + + +GUF_DICT_KWRDS void GUF_CAT(GUF_DICT_NAME, _try_shrink_to_fit)(GUF_DICT_NAME *ht, guf_err *err) +{ + GUF_ASSERT(GUF_CAT(GUF_DICT_NAME, _valid)(ht)); + GUF_CAT(GUF_DICT_KV_DBUF, _try_shrink_to_fit)(&ht->kv_elems, err); +} + +GUF_DICT_KWRDS void GUF_CAT(GUF_DICT_NAME, _shrink_to_fit)(GUF_DICT_NAME *ht) +{ + GUF_CAT(GUF_DICT_NAME, _try_shrink_to_fit)(ht, NULL); +} + +/* Iterator functions */ + +GUF_DICT_KWRDS GUF_CAT(GUF_DICT_NAME, _iter) GUF_CAT(GUF_DICT_NAME, _begin)(const GUF_DICT_NAME* ht) +{ + GUF_ASSERT_RELEASE(GUF_CAT(GUF_DICT_NAME, _valid)(ht)); + GUF_CAT(GUF_DICT_KV_DBUF, _iter) kv_it = GUF_CAT(GUF_DICT_KV_DBUF, _begin)(&ht->kv_elems); + return (GUF_CAT(GUF_DICT_NAME, _iter)){.ptr = kv_it.ptr, .base = kv_it.base}; +} + +GUF_DICT_KWRDS GUF_CAT(GUF_DICT_NAME, _iter) GUF_CAT(GUF_DICT_NAME, _end)(const GUF_DICT_NAME* ht) +{ + GUF_ASSERT_RELEASE(GUF_CAT(GUF_DICT_NAME, _valid)(ht)); + GUF_CAT(GUF_DICT_KV_DBUF, _iter) kv_it = GUF_CAT(GUF_DICT_KV_DBUF, _end)(&ht->kv_elems); + return (GUF_CAT(GUF_DICT_NAME, _iter)){.ptr = kv_it.ptr, .base = kv_it.base}; +} + +GUF_DICT_KWRDS GUF_CAT(GUF_DICT_NAME, _iter) GUF_CAT(GUF_DICT_NAME, _rbegin)(const GUF_DICT_NAME* ht) +{ + GUF_ASSERT_RELEASE(GUF_CAT(GUF_DICT_NAME, _valid)(ht)); + GUF_CAT(GUF_DICT_KV_DBUF, _iter) kv_it = GUF_CAT(GUF_DICT_KV_DBUF, _rbegin)(&ht->kv_elems); + return (GUF_CAT(GUF_DICT_NAME, _iter)){.ptr = kv_it.ptr, .base = kv_it.base}; +} + +GUF_DICT_KWRDS GUF_CAT(GUF_DICT_NAME, _iter) GUF_CAT(GUF_DICT_NAME, _rend)(const GUF_DICT_NAME* ht) +{ + GUF_ASSERT_RELEASE(GUF_CAT(GUF_DICT_NAME, _valid)(ht)); + GUF_CAT(GUF_DICT_KV_DBUF, _iter) kv_it = GUF_CAT(GUF_DICT_KV_DBUF, _rend)(&ht->kv_elems); + return (GUF_CAT(GUF_DICT_NAME, _iter)){.ptr = kv_it.ptr, .base = kv_it.base}; +} + +GUF_DICT_KWRDS bool GUF_CAT(GUF_DICT_NAME, _iter_is_end)(const GUF_DICT_NAME* ht, GUF_CAT(GUF_DICT_NAME, _iter) it) +{ + const bool is_reverse_it = it.base != NULL; + const GUF_CAT(GUF_DICT_NAME, _iter) dbuf_end_it = is_reverse_it ? GUF_CAT(GUF_DICT_NAME, _rend)(ht) : GUF_CAT(GUF_DICT_NAME, _end)(ht); + return it.ptr == dbuf_end_it.ptr; +} + +GUF_DICT_KWRDS GUF_CAT(GUF_DICT_NAME, _iter) GUF_CAT(GUF_DICT_NAME, _iter_next)(const GUF_DICT_NAME *ht, GUF_CAT(GUF_DICT_NAME, _iter) it, ptrdiff_t step) +{ + GUF_CAT(GUF_DICT_KV_DBUF, _iter) kv_it = GUF_CAT(GUF_DICT_KV_DBUF, _iter_next)(&ht->kv_elems, it, step); + return (GUF_CAT(GUF_DICT_NAME, _iter)){.ptr = kv_it.ptr, .base = kv_it.base}; +} +GUF_DICT_KWRDS GUF_CAT(GUF_DICT_NAME, _iter) GUF_CAT(GUF_DICT_NAME, _iter_at_idx)(const GUF_DICT_NAME *ht, ptrdiff_t idx) +{ + GUF_CAT(GUF_DICT_KV_DBUF, _iter) kv_it = GUF_CAT(GUF_DICT_KV_DBUF, _iter_at_idx)(&ht->kv_elems, idx); + return (GUF_CAT(GUF_DICT_NAME, _iter)){.ptr = kv_it.ptr, .base = kv_it.base}; +} + +GUF_DICT_KWRDS GUF_CAT(GUF_DICT_NAME, _iter) GUF_CAT(GUF_DICT_NAME, _reverse_iter_at_idx)(const GUF_DICT_NAME *ht, ptrdiff_t idx) +{ + GUF_CAT(GUF_DICT_KV_DBUF, _iter) kv_it = GUF_CAT(GUF_DICT_KV_DBUF, _reverse_iter_at_idx)(&ht->kv_elems, idx); + return (GUF_CAT(GUF_DICT_NAME, _iter)){.ptr = kv_it.ptr, .base = kv_it.base}; +} + +GUF_DICT_KWRDS ptrdiff_t GUF_CAT(GUF_DICT_NAME, _iter_to_idx)(const GUF_DICT_NAME *ht, GUF_CAT(GUF_DICT_NAME, _iter) it) +{ + return GUF_CAT(GUF_DICT_KV_DBUF, _iter_to_idx)(&ht->kv_elems, it); +} + +#if defined(GUF_DICT_VAL_T) && (defined(GUF_DICT_VAL_T_EQ) || defined(GUF_DICT_VAL_T_IS_INTEGRAL_TYPE)) +GUF_DICT_KWRDS GUF_CAT(GUF_DICT_NAME, _iter) GUF_CAT(GUF_DICT_NAME, _find_val)(GUF_DICT_NAME *ht, GUF_CAT(GUF_DICT_NAME, _iter) begin, GUF_CAT(GUF_DICT_NAME, _iter) end, const GUF_DICT_VAL_T *needle) +{ + GUF_ASSERT_RELEASE(GUF_CAT(GUF_DICT_NAME, _valid)(ht)); + GUF_ASSERT_RELEASE(needle); + + const bool is_reverse_it = begin.base != NULL; + GUF_ASSERT_RELEASE(is_reverse_it == (end.base != NULL)); // begin and end must be the same iterator type. + const GUF_CAT(GUF_DICT_NAME, _iter) dbuf_end_it = is_reverse_it ? GUF_CAT(GUF_DICT_NAME, _rend)(ht) : GUF_CAT(GUF_DICT_NAME, _end)(ht); + + if (!ht->kv_elems.data || !ht->kv_elems.size || (!begin.ptr && !begin.base) || (!end.ptr && !end.base)) { + return dbuf_end_it; + } + if ((begin.ptr == dbuf_end_it.ptr) || (!is_reverse_it && begin.ptr >= end.ptr) || (is_reverse_it && begin.base <= end.base)) { + return dbuf_end_it; + } + + for (GUF_CAT(GUF_DICT_NAME, _iter) it = begin; it.ptr != end.ptr && it.ptr != NULL; it = GUF_CAT(GUF_DICT_NAME, _iter_next)(ht, it, 1)) { + #ifdef GUF_DICT_VAL_T_EQ + if (GUF_DICT_VAL_T_EQ(&(it.ptr->val), needle)) { + return it; + } + #else + if (it.ptr->val == *needle) { + return it; + } + #endif + } + return dbuf_end_it; +} + +GUF_DICT_KWRDS GUF_CAT(GUF_DICT_NAME, _iter) GUF_CAT(GUF_DICT_NAME, _find_val_val_arg)(GUF_DICT_NAME *ht, GUF_CAT(GUF_DICT_NAME, _iter) begin, GUF_CAT(GUF_DICT_NAME, _iter) end, GUF_DICT_VAL_T needle) +{ + return GUF_CAT(GUF_DICT_NAME, _find_val)(ht, begin, end, &needle); +} +#endif + +#if defined(GUF_DICT_VAL_T) +GUF_DICT_KWRDS GUF_CAT(GUF_DICT_NAME, _iter) GUF_CAT(GUF_DICT_NAME, _find_val_if)(GUF_DICT_NAME *ht, GUF_CAT(GUF_DICT_NAME, _iter) begin, GUF_CAT(GUF_DICT_NAME, _iter) end, bool (*predicate)(const GUF_DICT_VAL_T *)) +{ + GUF_ASSERT_RELEASE(GUF_CAT(GUF_DICT_NAME, _valid)(ht)); + GUF_ASSERT_RELEASE(predicate); + + const bool is_reverse_it = begin.base != NULL; + GUF_ASSERT_RELEASE(is_reverse_it == (end.base != NULL)); // begin and end must be the same iterator type. + const GUF_CAT(GUF_DICT_NAME, _iter) dbuf_end_it = is_reverse_it ? GUF_CAT(GUF_DICT_NAME, _rend)(ht) : GUF_CAT(GUF_DICT_NAME, _end)(ht); + + if (!ht->kv_elems.data || !ht->kv_elems.size || (!begin.ptr && !begin.base) || (!end.ptr && !end.base)) { + return dbuf_end_it; + } + if ((begin.ptr == dbuf_end_it.ptr) || (!is_reverse_it && begin.ptr >= end.ptr) || (is_reverse_it && begin.base <= end.base)) { + return dbuf_end_it; + } + + for (GUF_CAT(GUF_DICT_NAME, _iter) it = begin; it.ptr != end.ptr && it.ptr != NULL; it = GUF_CAT(GUF_DICT_NAME, _iter_next)(ht, it, 1)) { + if (predicate(&(it.ptr->val))) { + return it; + } + } + return dbuf_end_it; +} +#endif + +#endif /* end GUF_IMPL/GUF_IMPL_STATIC */ + +#undef GUF_DICT_KV_META_T +#undef GUF_DICT_HASH_T +#undef GUF_DICT_HASH_T_MAX + +#undef GUF_DICT_32_BIT_HASH +#undef GUF_DICT_64_BIT_HASH +#undef GUF_DICT_64_BIT_IDX +#undef GUF_DICT_HASH_T_GET_HASHFRAG +#undef GUF_DICT_KV_META_HASHFRAG_MASK +#undef GUF_DICT_KV_META_IDX_MASK +#undef GUF_DICT_KV_META_IDX_NULL +#undef GUF_DICT_KV_META_IDX_TOMBSTONE +#undef GUF_DICT_KV_META_IDX_MAX +#undef GUF_DICT_META_GET_IDX +#undef GUF_DICT_META_GET_HASHFRAG +#undef GUF_DICT_META_IS_NULL +#undef GUF_DICT_META_IS_TOMBSTONE + +#undef GUF_DICT_NAME +#undef GUF_DICT_IS_SET +#undef GUF_DICT_PROBE_LINEAR +#undef GUF_DICT_PROBE_QUADRATIC +#undef GUF_DICT_MAX_LOAD_FACTOR + +#undef GUF_DICT_KEY_T +#undef GUF_DICT_KEY_T_IS_INTEGRAL_TYPE +#undef GUF_DICT_KEY_T_EQ +#undef GUF_DICT_KEY_T_FREE +#undef GUF_DICT_KEY_T_CMP +#undef GUF_DICT_KEY_T_COPY +#undef GUF_DICT_KEY_T_MOVE +#undef GUF_DICT_KEY_HASH + +#undef GUF_DICT_VAL_T +#undef GUF_DICT_VAL_T_IS_INTEGRAL_TYPE +#undef GUF_DICT_VAL_T_EQ +#undef GUF_DICT_VAL_T_FREE +#undef GUF_DICT_VAL_T_CMP +#undef GUF_DICT_VAL_T_COPY +#undef GUF_DICT_VAL_T_MOVE + +#undef GUF_DICT_KEY_LOOKUP_T +#undef GUF_DICT_KEY_TO_LOOKUP_KEY_CONVERT + +#undef GUF_DICT_KV_NAME +#undef GUF_DICT_KV_DBUF + +#undef GUF_DICT_IMPL_STATIC +#undef GUF_DICT_IMPL +#undef GUF_DICT_KWRDS diff --git a/libguf/src/guf_hash.h b/libguf/src/guf_hash.h new file mode 100755 index 0000000..bb3441b --- /dev/null +++ b/libguf/src/guf_hash.h @@ -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 diff --git a/libguf/src/guf_id_pool.h b/libguf/src/guf_id_pool.h new file mode 100755 index 0000000..1f9b739 --- /dev/null +++ b/libguf/src/guf_id_pool.h @@ -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 + diff --git a/libguf/src/guf_init.h b/libguf/src/guf_init.h new file mode 100755 index 0000000..93da0dd --- /dev/null +++ b/libguf/src/guf_init.h @@ -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 diff --git a/libguf/src/guf_linalg.h b/libguf/src/guf_linalg.h new file mode 100755 index 0000000..bfaded0 --- /dev/null +++ b/libguf/src/guf_linalg.h @@ -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 + +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 diff --git a/libguf/src/guf_math.h b/libguf/src/guf_math.h new file mode 100755 index 0000000..44f3896 --- /dev/null +++ b/libguf/src/guf_math.h @@ -0,0 +1,421 @@ +/* + is parametrized: no +*/ +#ifndef GUF_MATH_H +#define GUF_MATH_H +#include "guf_common.h" +#include "guf_assert.h" +#include +#include + +#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 + 1u;} else {return -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 + 1u;} else {return -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 + 1u;} else {return -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 + 1u;} else {return -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 diff --git a/libguf/src/guf_math_ckdint.h b/libguf/src/guf_math_ckdint.h new file mode 100755 index 0000000..d5aa5e9 --- /dev/null +++ b/libguf/src/guf_math_ckdint.h @@ -0,0 +1,3959 @@ +/* + is parametrized: no +*/ + +/* + // Functions for safely checking for over- and underflow of arithmetic operations + + guf_math_ckd_result guf_ckd_add_TYPE(TYPE a, TYPE b); + - if a + b doesn't over/underflow TYPE: return GUF_MATH_CKD_SUCCESS (falsy) + - if a + b overflows TYPE: return GUF_MATH_CKD_OVERFLOW_POS (truthy) + - if a + b "underflows" TYPE: return GUF_MATH_CKD_OVERFLOW_NEG (truthy) + + guf_math_ckd_result guf_ckd_sub_TYPE(TYPE a, TYPE b); + - if a - b doesn't over/underflow TYPE: return GUF_MATH_CKD_SUCCESS (falsy) + - if a - b overflows TYPE: return GUF_MATH_CKD_OVERFLOW_POS (truthy) + - if a - b "underflows" TYPE: return GUF_MATH_CKD_OVERFLOW_NEG (truthy) + + guf_math_ckd_result guf_ckd_mul_TYPE(TYPE a, TYPE b); + - if a * b doesn't over/underflow TYPE: return GUF_MATH_CKD_SUCCESS (falsy) + - if a * b overflows TYPE: return GUF_MATH_CKD_OVERFLOW_POS (truthy) + - if a * b "underflows" TYPE: return GUF_MATH_CKD_OVERFLOW_NEG (truthy) + + + // Functions for safely computing arithmetic operations with saturating over- and underflow semantics + // (cf. https://doc.rust-lang.org/std/intrinsics/fn.saturating_add.html (last-retrieved 2025-05-10)) + + guf_math_ckd_result guf_saturating_add_TYPE(TYPE a, TYPE b, TYPE *result); + - if a + b does not over-/underflow TYPE: return GUF_MATH_CKD_SUCCESS (if result is not NULL, set result to a + b) + - if a + b overflows TYPE: return GUF_MATH_CKD_OVERFLOW_POS (if result is not NULL, set result to TYPE_MAX, i.e. use saturating overflow semantics). + - if a + b "underflows" TYPE: return GUF_MATH_CKD_OVERFLOW_NEG (if result is not NULL, set result to TYPE_MIN, i.e. use saturating underflow semantics). + + guf_math_ckd_result guf_saturating_sub_TYPE(TYPE a, TYPE b, TYPE *result); + - if a - b does not over-/underflow TYPE: return GUF_MATH_CKD_SUCCESS (if result is not NULL, set result to a - b) + - if a - b overflows TYPE: return GUF_MATH_CKD_OVERFLOW_POS (if result is not NULL, set result to TYPE_MAX, i.e. use saturating overflow semantics). + - if a - b "underflows" TYPE: return GUF_MATH_CKD_OVERFLOW_NEG (if result is not NULL, set result to TYPE_MIN, i.e. use saturating underflow semantics). + + guf_math_ckd_result guf_saturating_mul_TYPE(TYPE a, TYPE b, TYPE *result); + - if a * b does not over-/underflow TYPE: return GUF_MATH_CKD_SUCCESS (if result is not NULL, set result to a * b) + - if a * b overflows TYPE: return GUF_MATH_CKD_OVERFLOW_POS (if result is not NULL, set result to TYPE_MAX, i.e. use saturating overflow semantics). + - if a * b "underflows" TYPE: return GUF_MATH_CKD_OVERFLOW_NEG (if result is not NULL, set result to TYPE_MIN, i.e. use saturating underflow semantics). + + + // Functions for safely computing arithmetic operations with wrapping over- and underflow semantics + // (cf. https://doc.rust-lang.org/std/intrinsics/fn.wrapping_add.html (last-retrieved 2025-05-10)) + + guf_math_ckd_result guf_wrapping_add_TYPE(TYPE a, TYPE b, TYPE *result); + - if a + b does not over-/underflow TYPE: return GUF_MATH_CKD_SUCCESS (if result is not NULL, set result to a + b) + - if a + b overflows TYPE: return GUF_MATH_CKD_OVERFLOW_POS (if result is not NULL, set result using two's complement wrap-around overflow semantics). + - if a + b "underflows" TYPE: return GUF_MATH_CKD_OVERFLOW_NEG (if result is not NULL, set result using two's complement wrap-around underflow semantics). + + guf_math_ckd_result guf_wrapping_sub_TYPE(TYPE a, TYPE b, TYPE *result); + - if a - b doesn't over-/underflow TYPE: return GUF_MATH_CKD_SUCCESS (if result is not NULL, set result to a - b) + - if a - b overflows TYPE: return GUF_MATH_CKD_OVERFLOW_POS (if result is not NULL, set result using two's complement wrap-around overflow semantics). + - if a - b "underflows" TYPE: return GUF_MATH_CKD_OVERFLOW_NEG (if result is not NULL, set result using two's complement wrap-around underflow semantics). + + guf_math_ckd_result guf_wrapping_mul_TYPE(TYPE a, TYPE b, TYPE *result); + // NOTE/TODO: guf_wrapping_mul_TYPE relies on implementation-defined unsigned-to-signed two's complement conversion on over/underflow + // cf. https://stackoverflow.com/questions/76900522/can-you-ensure-overflow-wrapping-behavior-for-signed-integer-arithmetic-in-c (last-retrieved 2025-05-13) + - if a * b doesn't over-/underflow TYPE: return GUF_MATH_CKD_SUCCESS (if result is not NULL, set result to a * b) + - if a * b overflows TYPE: return GUF_MATH_CKD_OVERFLOW_POS (if result is not NULL, set result using two's complement wrap-around overflow semantics). + - if a * b "underflows" TYPE: return GUF_MATH_CKD_OVERFLOW_NEG (if result is not NULL, set result using two's complement wrap-around underflow semantics). + + + cf. https://stackoverflow.com/questions/199333/how-do-i-detect-unsigned-integer-overflow (last-retrieved 2025-03-17) + cf. https://stackoverflow.com/questions/59307930/how-to-implement-wrapping-signed-int-addition-in-c (last-retrieved 2025-05-10) + cf. https://stackoverflow.com/questions/54318815/integer-overflow-w-multiplication-in-c (last-retrieved 2025-05-11) + cf. https://stackoverflow.com/questions/29808397/how-to-portably-find-out-minint-max-absint-min (last-retrieved 2025-05-11) + cf. https://stackoverflow.com/questions/27001604/32-bit-unsigned-multiply-on-64-bit-causing-undefined-behavior (last-retrieved 2025-05-15) +*/ + + +#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; + +#if !defined(GUF_MATH_CKDINT_IMPL_STATIC) && !defined(GUF_MATH_CKDINT_IMPL) + +// Signed integer arithmetic checks (generated with libguf/tools/ckdint-gen.py) +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_add_int(int a, int b); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_sub_int(int a, int b); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_mul_int(int a, int b); + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_add_long(long a, long b); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_sub_long(long a, long b); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_mul_long(long a, long b); + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_add_long_long(long long a, long long b); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_sub_long_long(long long a, long long b); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_mul_long_long(long long a, long long b); + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_add_ptrdiff_t(ptrdiff_t a, ptrdiff_t b); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_sub_ptrdiff_t(ptrdiff_t a, ptrdiff_t b); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_mul_ptrdiff_t(ptrdiff_t a, ptrdiff_t b); + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_add_least_i8(int_least8_t a, int_least8_t b); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_sub_least_i8(int_least8_t a, int_least8_t b); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_mul_least_i8(int_least8_t a, int_least8_t b); + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_add_least_i16(int_least16_t a, int_least16_t b); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_sub_least_i16(int_least16_t a, int_least16_t b); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_mul_least_i16(int_least16_t a, int_least16_t b); + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_add_least_i32(int_least32_t a, int_least32_t b); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_sub_least_i32(int_least32_t a, int_least32_t b); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_mul_least_i32(int_least32_t a, int_least32_t b); + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_add_least_i64(int_least64_t a, int_least64_t b); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_sub_least_i64(int_least64_t a, int_least64_t b); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_mul_least_i64(int_least64_t a, int_least64_t b); + +#ifdef INT8_MAX +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_add_i8(int8_t a, int8_t b); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_sub_i8(int8_t a, int8_t b); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_mul_i8(int8_t a, int8_t b); +#endif + +#ifdef INT16_MAX +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_add_i16(int16_t a, int16_t b); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_sub_i16(int16_t a, int16_t b); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_mul_i16(int16_t a, int16_t b); +#endif + +#ifdef INT32_MAX +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_add_i32(int32_t a, int32_t b); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_sub_i32(int32_t a, int32_t b); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_mul_i32(int32_t a, int32_t b); +#endif + +#ifdef INT64_MAX +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_add_i64(int64_t a, int64_t b); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_sub_i64(int64_t a, int64_t b); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_mul_i64(int64_t a, int64_t b); +#endif + + +// Unsigned integer arithmetic checks (generated with libguf/tools/ckdint-gen.py) +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_add_uchar(unsigned char a, unsigned char b); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_sub_uchar(unsigned char a, unsigned char b); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_mul_uchar(unsigned char a, unsigned char b); + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_add_unsigned(unsigned a, unsigned b); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_sub_unsigned(unsigned a, unsigned b); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_mul_unsigned(unsigned a, unsigned b); + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_add_ulong(unsigned long a, unsigned long b); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_sub_ulong(unsigned long a, unsigned long b); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_mul_ulong(unsigned long a, unsigned long b); + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_add_ulong_long(unsigned long long a, unsigned long long b); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_sub_ulong_long(unsigned long long a, unsigned long long b); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_mul_ulong_long(unsigned long long a, unsigned long long b); + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_add_size_t(size_t a, size_t b); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_sub_size_t(size_t a, size_t b); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_mul_size_t(size_t a, size_t b); + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_add_least_u8(uint_least8_t a, uint_least8_t b); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_sub_least_u8(uint_least8_t a, uint_least8_t b); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_mul_least_u8(uint_least8_t a, uint_least8_t b); + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_add_least_u16(uint_least16_t a, uint_least16_t b); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_sub_least_u16(uint_least16_t a, uint_least16_t b); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_mul_least_u16(uint_least16_t a, uint_least16_t b); + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_add_least_u32(uint_least32_t a, uint_least32_t b); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_sub_least_u32(uint_least32_t a, uint_least32_t b); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_mul_least_u32(uint_least32_t a, uint_least32_t b); + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_add_least_u64(uint_least64_t a, uint_least64_t b); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_sub_least_u64(uint_least64_t a, uint_least64_t b); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_mul_least_u64(uint_least64_t a, uint_least64_t b); + +#ifdef UINT8_MAX +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_add_u8(uint8_t a, uint8_t b); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_sub_u8(uint8_t a, uint8_t b); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_mul_u8(uint8_t a, uint8_t b); +#endif + +#ifdef UINT16_MAX +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_add_u16(uint16_t a, uint16_t b); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_sub_u16(uint16_t a, uint16_t b); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_mul_u16(uint16_t a, uint16_t b); +#endif + +#ifdef UINT32_MAX +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_add_u32(uint32_t a, uint32_t b); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_sub_u32(uint32_t a, uint32_t b); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_mul_u32(uint32_t a, uint32_t b); +#endif + +#ifdef UINT64_MAX +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_add_u64(uint64_t a, uint64_t b); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_sub_u64(uint64_t a, uint64_t b); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_mul_u64(uint64_t a, uint64_t b); +#endif + + + +// Signed saturating/wrapping arithmetic (generated with libguf/tools/ckdint-gen.py) +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_add_int(int a, int b, int *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_sub_int(int a, int b, int *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_mul_int(int a, int b, int *result); + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_add_int(int a, int b, int *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_sub_int(int a, int b, int *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_mul_int(int a, int b, int *result); + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_add_long(long a, long b, long *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_sub_long(long a, long b, long *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_mul_long(long a, long b, long *result); + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_add_long(long a, long b, long *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_sub_long(long a, long b, long *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_mul_long(long a, long b, long *result); + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_add_long_long(long long a, long long b, long long *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_sub_long_long(long long a, long long b, long long *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_mul_long_long(long long a, long long b, long long *result); + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_add_long_long(long long a, long long b, long long *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_sub_long_long(long long a, long long b, long long *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_mul_long_long(long long a, long long b, long long *result); + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_add_ptrdiff_t(ptrdiff_t a, ptrdiff_t b, ptrdiff_t *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_sub_ptrdiff_t(ptrdiff_t a, ptrdiff_t b, ptrdiff_t *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_mul_ptrdiff_t(ptrdiff_t a, ptrdiff_t b, ptrdiff_t *result); + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_add_ptrdiff_t(ptrdiff_t a, ptrdiff_t b, ptrdiff_t *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_sub_ptrdiff_t(ptrdiff_t a, ptrdiff_t b, ptrdiff_t *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_mul_ptrdiff_t(ptrdiff_t a, ptrdiff_t b, ptrdiff_t *result); + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_add_least_i8(int_least8_t a, int_least8_t b, int_least8_t *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_sub_least_i8(int_least8_t a, int_least8_t b, int_least8_t *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_mul_least_i8(int_least8_t a, int_least8_t b, int_least8_t *result); + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_add_least_i8(int_least8_t a, int_least8_t b, int_least8_t *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_sub_least_i8(int_least8_t a, int_least8_t b, int_least8_t *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_mul_least_i8(int_least8_t a, int_least8_t b, int_least8_t *result); + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_add_least_i16(int_least16_t a, int_least16_t b, int_least16_t *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_sub_least_i16(int_least16_t a, int_least16_t b, int_least16_t *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_mul_least_i16(int_least16_t a, int_least16_t b, int_least16_t *result); + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_add_least_i16(int_least16_t a, int_least16_t b, int_least16_t *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_sub_least_i16(int_least16_t a, int_least16_t b, int_least16_t *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_mul_least_i16(int_least16_t a, int_least16_t b, int_least16_t *result); + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_add_least_i32(int_least32_t a, int_least32_t b, int_least32_t *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_sub_least_i32(int_least32_t a, int_least32_t b, int_least32_t *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_mul_least_i32(int_least32_t a, int_least32_t b, int_least32_t *result); + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_add_least_i32(int_least32_t a, int_least32_t b, int_least32_t *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_sub_least_i32(int_least32_t a, int_least32_t b, int_least32_t *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_mul_least_i32(int_least32_t a, int_least32_t b, int_least32_t *result); + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_add_least_i64(int_least64_t a, int_least64_t b, int_least64_t *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_sub_least_i64(int_least64_t a, int_least64_t b, int_least64_t *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_mul_least_i64(int_least64_t a, int_least64_t b, int_least64_t *result); + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_add_least_i64(int_least64_t a, int_least64_t b, int_least64_t *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_sub_least_i64(int_least64_t a, int_least64_t b, int_least64_t *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_mul_least_i64(int_least64_t a, int_least64_t b, int_least64_t *result); + +#ifdef INT8_MAX +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_add_i8(int8_t a, int8_t b, int8_t *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_sub_i8(int8_t a, int8_t b, int8_t *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_mul_i8(int8_t a, int8_t b, int8_t *result); + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_add_i8(int8_t a, int8_t b, int8_t *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_sub_i8(int8_t a, int8_t b, int8_t *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_mul_i8(int8_t a, int8_t b, int8_t *result); +#endif + +#ifdef INT16_MAX +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_add_i16(int16_t a, int16_t b, int16_t *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_sub_i16(int16_t a, int16_t b, int16_t *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_mul_i16(int16_t a, int16_t b, int16_t *result); + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_add_i16(int16_t a, int16_t b, int16_t *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_sub_i16(int16_t a, int16_t b, int16_t *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_mul_i16(int16_t a, int16_t b, int16_t *result); +#endif + +#ifdef INT32_MAX +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_add_i32(int32_t a, int32_t b, int32_t *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_sub_i32(int32_t a, int32_t b, int32_t *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_mul_i32(int32_t a, int32_t b, int32_t *result); + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_add_i32(int32_t a, int32_t b, int32_t *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_sub_i32(int32_t a, int32_t b, int32_t *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_mul_i32(int32_t a, int32_t b, int32_t *result); +#endif + +#ifdef INT64_MAX +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_add_i64(int64_t a, int64_t b, int64_t *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_sub_i64(int64_t a, int64_t b, int64_t *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_mul_i64(int64_t a, int64_t b, int64_t *result); + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_add_i64(int64_t a, int64_t b, int64_t *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_sub_i64(int64_t a, int64_t b, int64_t *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_mul_i64(int64_t a, int64_t b, int64_t *result); +#endif + + +// Unsigned saturating/wrapping arithmetic (generated with libguf/tools/ckdint-gen.py) +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_add_uchar(unsigned char a, unsigned char b, unsigned char *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_sub_uchar(unsigned char a, unsigned char b, unsigned char *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_mul_uchar(unsigned char a, unsigned char b, unsigned char *result); + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_add_uchar(unsigned char a, unsigned char b, unsigned char *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_sub_uchar(unsigned char a, unsigned char b, unsigned char *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_mul_uchar(unsigned char a, unsigned char b, unsigned char *result); + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_add_unsigned(unsigned a, unsigned b, unsigned *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_sub_unsigned(unsigned a, unsigned b, unsigned *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_mul_unsigned(unsigned a, unsigned b, unsigned *result); + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_add_unsigned(unsigned a, unsigned b, unsigned *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_sub_unsigned(unsigned a, unsigned b, unsigned *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_mul_unsigned(unsigned a, unsigned b, unsigned *result); + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_add_ulong(unsigned long a, unsigned long b, unsigned long *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_sub_ulong(unsigned long a, unsigned long b, unsigned long *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_mul_ulong(unsigned long a, unsigned long b, unsigned long *result); + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_add_ulong(unsigned long a, unsigned long b, unsigned long *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_sub_ulong(unsigned long a, unsigned long b, unsigned long *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_mul_ulong(unsigned long a, unsigned long b, unsigned long *result); + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_add_ulong_long(unsigned long long a, unsigned long long b, unsigned long long *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_sub_ulong_long(unsigned long long a, unsigned long long b, unsigned long long *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_mul_ulong_long(unsigned long long a, unsigned long long b, unsigned long long *result); + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_add_ulong_long(unsigned long long a, unsigned long long b, unsigned long long *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_sub_ulong_long(unsigned long long a, unsigned long long b, unsigned long long *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_mul_ulong_long(unsigned long long a, unsigned long long b, unsigned long long *result); + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_add_size_t(size_t a, size_t b, size_t *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_sub_size_t(size_t a, size_t b, size_t *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_mul_size_t(size_t a, size_t b, size_t *result); + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_add_size_t(size_t a, size_t b, size_t *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_sub_size_t(size_t a, size_t b, size_t *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_mul_size_t(size_t a, size_t b, size_t *result); + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_add_least_u8(uint_least8_t a, uint_least8_t b, uint_least8_t *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_sub_least_u8(uint_least8_t a, uint_least8_t b, uint_least8_t *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_mul_least_u8(uint_least8_t a, uint_least8_t b, uint_least8_t *result); + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_add_least_u8(uint_least8_t a, uint_least8_t b, uint_least8_t *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_sub_least_u8(uint_least8_t a, uint_least8_t b, uint_least8_t *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_mul_least_u8(uint_least8_t a, uint_least8_t b, uint_least8_t *result); + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_add_least_u16(uint_least16_t a, uint_least16_t b, uint_least16_t *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_sub_least_u16(uint_least16_t a, uint_least16_t b, uint_least16_t *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_mul_least_u16(uint_least16_t a, uint_least16_t b, uint_least16_t *result); + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_add_least_u16(uint_least16_t a, uint_least16_t b, uint_least16_t *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_sub_least_u16(uint_least16_t a, uint_least16_t b, uint_least16_t *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_mul_least_u16(uint_least16_t a, uint_least16_t b, uint_least16_t *result); + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_add_least_u32(uint_least32_t a, uint_least32_t b, uint_least32_t *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_sub_least_u32(uint_least32_t a, uint_least32_t b, uint_least32_t *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_mul_least_u32(uint_least32_t a, uint_least32_t b, uint_least32_t *result); + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_add_least_u32(uint_least32_t a, uint_least32_t b, uint_least32_t *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_sub_least_u32(uint_least32_t a, uint_least32_t b, uint_least32_t *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_mul_least_u32(uint_least32_t a, uint_least32_t b, uint_least32_t *result); + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_add_least_u64(uint_least64_t a, uint_least64_t b, uint_least64_t *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_sub_least_u64(uint_least64_t a, uint_least64_t b, uint_least64_t *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_mul_least_u64(uint_least64_t a, uint_least64_t b, uint_least64_t *result); + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_add_least_u64(uint_least64_t a, uint_least64_t b, uint_least64_t *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_sub_least_u64(uint_least64_t a, uint_least64_t b, uint_least64_t *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_mul_least_u64(uint_least64_t a, uint_least64_t b, uint_least64_t *result); + +#ifdef UINT8_MAX +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_add_u8(uint8_t a, uint8_t b, uint8_t *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_sub_u8(uint8_t a, uint8_t b, uint8_t *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_mul_u8(uint8_t a, uint8_t b, uint8_t *result); + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_add_u8(uint8_t a, uint8_t b, uint8_t *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_sub_u8(uint8_t a, uint8_t b, uint8_t *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_mul_u8(uint8_t a, uint8_t b, uint8_t *result); +#endif + +#ifdef UINT16_MAX +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_add_u16(uint16_t a, uint16_t b, uint16_t *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_sub_u16(uint16_t a, uint16_t b, uint16_t *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_mul_u16(uint16_t a, uint16_t b, uint16_t *result); + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_add_u16(uint16_t a, uint16_t b, uint16_t *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_sub_u16(uint16_t a, uint16_t b, uint16_t *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_mul_u16(uint16_t a, uint16_t b, uint16_t *result); +#endif + +#ifdef UINT32_MAX +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_add_u32(uint32_t a, uint32_t b, uint32_t *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_sub_u32(uint32_t a, uint32_t b, uint32_t *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_mul_u32(uint32_t a, uint32_t b, uint32_t *result); + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_add_u32(uint32_t a, uint32_t b, uint32_t *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_sub_u32(uint32_t a, uint32_t b, uint32_t *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_mul_u32(uint32_t a, uint32_t b, uint32_t *result); +#endif + +#ifdef UINT64_MAX +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_add_u64(uint64_t a, uint64_t b, uint64_t *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_sub_u64(uint64_t a, uint64_t b, uint64_t *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_mul_u64(uint64_t a, uint64_t b, uint64_t *result); + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_add_u64(uint64_t a, uint64_t b, uint64_t *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_sub_u64(uint64_t a, uint64_t b, uint64_t *result); +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_mul_u64(uint64_t a, uint64_t b, uint64_t *result); +#endif + + +#endif +#if defined(GUF_MATH_CKDINT_IMPL) || defined(GUF_MATH_CKDINT_IMPL_STATIC) +#include "guf_assert.h" +// Signed integer arithmetic checks (generated with libguf/tools/ckdint-gen.py) +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_add_int(int a, int 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_int(int a, int 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_int(int a, int 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; + } +} + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_add_long(long a, long b) +{ + if (b > 0 && a > LONG_MAX - b) { + return GUF_MATH_CKD_OVERFLOW_POS; + } else if (b < 0 && a < LONG_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_long(long a, long b) +{ + if (b < 0 && a > LONG_MAX + b) { + return GUF_MATH_CKD_OVERFLOW_POS; + } else if (b > 0 && a < LONG_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_long(long a, long b) +{ + if (b > 0) { + if (a > LONG_MAX / b) { + return GUF_MATH_CKD_OVERFLOW_POS; + } else if (a < LONG_MIN / b) { + return GUF_MATH_CKD_OVERFLOW_NEG; + } else { + return GUF_MATH_CKD_SUCCESS; + } + } else if (b < 0) { + if (LONG_MIN != -LONG_MAX && b == -1) { // Prevent potential (LONG_MIN / b) overflow for b == -1 + return a == LONG_MIN ? GUF_MATH_CKD_OVERFLOW_POS : GUF_MATH_CKD_SUCCESS; + } else if (a < LONG_MAX / b) { + return GUF_MATH_CKD_OVERFLOW_POS; + } else if (a > LONG_MIN / b) { + return GUF_MATH_CKD_OVERFLOW_NEG; + } else { + return GUF_MATH_CKD_SUCCESS; + } + } else { + return GUF_MATH_CKD_SUCCESS; + } +} + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_add_long_long(long long a, long long b) +{ + if (b > 0 && a > LLONG_MAX - b) { + return GUF_MATH_CKD_OVERFLOW_POS; + } else if (b < 0 && a < LLONG_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_long_long(long long a, long long b) +{ + if (b < 0 && a > LLONG_MAX + b) { + return GUF_MATH_CKD_OVERFLOW_POS; + } else if (b > 0 && a < LLONG_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_long_long(long long a, long long b) +{ + if (b > 0) { + if (a > LLONG_MAX / b) { + return GUF_MATH_CKD_OVERFLOW_POS; + } else if (a < LLONG_MIN / b) { + return GUF_MATH_CKD_OVERFLOW_NEG; + } else { + return GUF_MATH_CKD_SUCCESS; + } + } else if (b < 0) { + if (LLONG_MIN != -LLONG_MAX && b == -1) { // Prevent potential (LLONG_MIN / b) overflow for b == -1 + return a == LLONG_MIN ? GUF_MATH_CKD_OVERFLOW_POS : GUF_MATH_CKD_SUCCESS; + } else if (a < LLONG_MAX / b) { + return GUF_MATH_CKD_OVERFLOW_POS; + } else if (a > LLONG_MIN / b) { + return GUF_MATH_CKD_OVERFLOW_NEG; + } else { + return GUF_MATH_CKD_SUCCESS; + } + } else { + return GUF_MATH_CKD_SUCCESS; + } +} + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_add_ptrdiff_t(ptrdiff_t a, ptrdiff_t b) +{ + if (b > 0 && a > PTRDIFF_MAX - b) { + return GUF_MATH_CKD_OVERFLOW_POS; + } else if (b < 0 && a < PTRDIFF_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_ptrdiff_t(ptrdiff_t a, ptrdiff_t b) +{ + if (b < 0 && a > PTRDIFF_MAX + b) { + return GUF_MATH_CKD_OVERFLOW_POS; + } else if (b > 0 && a < PTRDIFF_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_ptrdiff_t(ptrdiff_t a, ptrdiff_t b) +{ + if (b > 0) { + if (a > PTRDIFF_MAX / b) { + return GUF_MATH_CKD_OVERFLOW_POS; + } else if (a < PTRDIFF_MIN / b) { + return GUF_MATH_CKD_OVERFLOW_NEG; + } else { + return GUF_MATH_CKD_SUCCESS; + } + } else if (b < 0) { + if (PTRDIFF_MIN != -PTRDIFF_MAX && b == -1) { // Prevent potential (PTRDIFF_MIN / b) overflow for b == -1 + return a == PTRDIFF_MIN ? GUF_MATH_CKD_OVERFLOW_POS : GUF_MATH_CKD_SUCCESS; + } else if (a < PTRDIFF_MAX / b) { + return GUF_MATH_CKD_OVERFLOW_POS; + } else if (a > PTRDIFF_MIN / b) { + return GUF_MATH_CKD_OVERFLOW_NEG; + } else { + return GUF_MATH_CKD_SUCCESS; + } + } else { + return GUF_MATH_CKD_SUCCESS; + } +} + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_add_least_i8(int_least8_t a, int_least8_t b) +{ + if (b > 0 && a > GUF_INT8_MAX - b) { + return GUF_MATH_CKD_OVERFLOW_POS; + } else if (b < 0 && a < GUF_INT8_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_least_i8(int_least8_t a, int_least8_t b) +{ + if (b < 0 && a > GUF_INT8_MAX + b) { + return GUF_MATH_CKD_OVERFLOW_POS; + } else if (b > 0 && a < GUF_INT8_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_least_i8(int_least8_t a, int_least8_t b) +{ + if (b > 0) { + if (a > GUF_INT8_MAX / b) { + return GUF_MATH_CKD_OVERFLOW_POS; + } else if (a < GUF_INT8_MIN / b) { + return GUF_MATH_CKD_OVERFLOW_NEG; + } else { + return GUF_MATH_CKD_SUCCESS; + } + } else if (b < 0) { + if (GUF_INT8_MIN != -GUF_INT8_MAX && b == -1) { // Prevent potential (GUF_INT8_MIN / b) overflow for b == -1 + return a == GUF_INT8_MIN ? GUF_MATH_CKD_OVERFLOW_POS : GUF_MATH_CKD_SUCCESS; + } else if (a < GUF_INT8_MAX / b) { + return GUF_MATH_CKD_OVERFLOW_POS; + } else if (a > GUF_INT8_MIN / b) { + return GUF_MATH_CKD_OVERFLOW_NEG; + } else { + return GUF_MATH_CKD_SUCCESS; + } + } else { + return GUF_MATH_CKD_SUCCESS; + } +} + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_add_least_i16(int_least16_t a, int_least16_t b) +{ + if (b > 0 && a > GUF_INT16_MAX - b) { + return GUF_MATH_CKD_OVERFLOW_POS; + } else if (b < 0 && a < GUF_INT16_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_least_i16(int_least16_t a, int_least16_t b) +{ + if (b < 0 && a > GUF_INT16_MAX + b) { + return GUF_MATH_CKD_OVERFLOW_POS; + } else if (b > 0 && a < GUF_INT16_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_least_i16(int_least16_t a, int_least16_t b) +{ + if (b > 0) { + if (a > GUF_INT16_MAX / b) { + return GUF_MATH_CKD_OVERFLOW_POS; + } else if (a < GUF_INT16_MIN / b) { + return GUF_MATH_CKD_OVERFLOW_NEG; + } else { + return GUF_MATH_CKD_SUCCESS; + } + } else if (b < 0) { + if (GUF_INT16_MIN != -GUF_INT16_MAX && b == -1) { // Prevent potential (GUF_INT16_MIN / b) overflow for b == -1 + return a == GUF_INT16_MIN ? GUF_MATH_CKD_OVERFLOW_POS : GUF_MATH_CKD_SUCCESS; + } else if (a < GUF_INT16_MAX / b) { + return GUF_MATH_CKD_OVERFLOW_POS; + } else if (a > GUF_INT16_MIN / b) { + return GUF_MATH_CKD_OVERFLOW_NEG; + } else { + return GUF_MATH_CKD_SUCCESS; + } + } else { + return GUF_MATH_CKD_SUCCESS; + } +} + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_add_least_i32(int_least32_t a, int_least32_t b) +{ + if (b > 0 && a > GUF_INT32_MAX - b) { + return GUF_MATH_CKD_OVERFLOW_POS; + } else if (b < 0 && a < GUF_INT32_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_least_i32(int_least32_t a, int_least32_t b) +{ + if (b < 0 && a > GUF_INT32_MAX + b) { + return GUF_MATH_CKD_OVERFLOW_POS; + } else if (b > 0 && a < GUF_INT32_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_least_i32(int_least32_t a, int_least32_t b) +{ + if (b > 0) { + if (a > GUF_INT32_MAX / b) { + return GUF_MATH_CKD_OVERFLOW_POS; + } else if (a < GUF_INT32_MIN / b) { + return GUF_MATH_CKD_OVERFLOW_NEG; + } else { + return GUF_MATH_CKD_SUCCESS; + } + } else if (b < 0) { + if (GUF_INT32_MIN != -GUF_INT32_MAX && b == -1) { // Prevent potential (GUF_INT32_MIN / b) overflow for b == -1 + return a == GUF_INT32_MIN ? GUF_MATH_CKD_OVERFLOW_POS : GUF_MATH_CKD_SUCCESS; + } else if (a < GUF_INT32_MAX / b) { + return GUF_MATH_CKD_OVERFLOW_POS; + } else if (a > GUF_INT32_MIN / b) { + return GUF_MATH_CKD_OVERFLOW_NEG; + } else { + return GUF_MATH_CKD_SUCCESS; + } + } else { + return GUF_MATH_CKD_SUCCESS; + } +} + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_add_least_i64(int_least64_t a, int_least64_t b) +{ + if (b > 0 && a > GUF_INT64_MAX - b) { + return GUF_MATH_CKD_OVERFLOW_POS; + } else if (b < 0 && a < GUF_INT64_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_least_i64(int_least64_t a, int_least64_t b) +{ + if (b < 0 && a > GUF_INT64_MAX + b) { + return GUF_MATH_CKD_OVERFLOW_POS; + } else if (b > 0 && a < GUF_INT64_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_least_i64(int_least64_t a, int_least64_t b) +{ + if (b > 0) { + if (a > GUF_INT64_MAX / b) { + return GUF_MATH_CKD_OVERFLOW_POS; + } else if (a < GUF_INT64_MIN / b) { + return GUF_MATH_CKD_OVERFLOW_NEG; + } else { + return GUF_MATH_CKD_SUCCESS; + } + } else if (b < 0) { + if (GUF_INT64_MIN != -GUF_INT64_MAX && b == -1) { // Prevent potential (GUF_INT64_MIN / b) overflow for b == -1 + return a == GUF_INT64_MIN ? GUF_MATH_CKD_OVERFLOW_POS : GUF_MATH_CKD_SUCCESS; + } else if (a < GUF_INT64_MAX / b) { + return GUF_MATH_CKD_OVERFLOW_POS; + } else if (a > GUF_INT64_MIN / b) { + return GUF_MATH_CKD_OVERFLOW_NEG; + } else { + return GUF_MATH_CKD_SUCCESS; + } + } else { + return GUF_MATH_CKD_SUCCESS; + } +} + +#ifdef INT8_MAX +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_add_i8(int8_t a, int8_t b) +{ + if (b > 0 && a > INT8_MAX - b) { + return GUF_MATH_CKD_OVERFLOW_POS; + } else if (b < 0 && a < INT8_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_i8(int8_t a, int8_t b) +{ + if (b < 0 && a > INT8_MAX + b) { + return GUF_MATH_CKD_OVERFLOW_POS; + } else if (b > 0 && a < INT8_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_i8(int8_t a, int8_t b) +{ + if (b > 0) { + if (a > INT8_MAX / b) { + return GUF_MATH_CKD_OVERFLOW_POS; + } else if (a < INT8_MIN / b) { + return GUF_MATH_CKD_OVERFLOW_NEG; + } else { + return GUF_MATH_CKD_SUCCESS; + } + } else if (b < 0) { + if (INT8_MIN != -INT8_MAX && b == -1) { // Prevent potential (INT8_MIN / b) overflow for b == -1 + return a == INT8_MIN ? GUF_MATH_CKD_OVERFLOW_POS : GUF_MATH_CKD_SUCCESS; + } else if (a < INT8_MAX / b) { + return GUF_MATH_CKD_OVERFLOW_POS; + } else if (a > INT8_MIN / b) { + return GUF_MATH_CKD_OVERFLOW_NEG; + } else { + return GUF_MATH_CKD_SUCCESS; + } + } else { + return GUF_MATH_CKD_SUCCESS; + } +} +#endif + +#ifdef INT16_MAX +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_add_i16(int16_t a, int16_t b) +{ + if (b > 0 && a > INT16_MAX - b) { + return GUF_MATH_CKD_OVERFLOW_POS; + } else if (b < 0 && a < INT16_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_i16(int16_t a, int16_t b) +{ + if (b < 0 && a > INT16_MAX + b) { + return GUF_MATH_CKD_OVERFLOW_POS; + } else if (b > 0 && a < INT16_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_i16(int16_t a, int16_t b) +{ + if (b > 0) { + if (a > INT16_MAX / b) { + return GUF_MATH_CKD_OVERFLOW_POS; + } else if (a < INT16_MIN / b) { + return GUF_MATH_CKD_OVERFLOW_NEG; + } else { + return GUF_MATH_CKD_SUCCESS; + } + } else if (b < 0) { + if (INT16_MIN != -INT16_MAX && b == -1) { // Prevent potential (INT16_MIN / b) overflow for b == -1 + return a == INT16_MIN ? GUF_MATH_CKD_OVERFLOW_POS : GUF_MATH_CKD_SUCCESS; + } else if (a < INT16_MAX / b) { + return GUF_MATH_CKD_OVERFLOW_POS; + } else if (a > INT16_MIN / b) { + return GUF_MATH_CKD_OVERFLOW_NEG; + } else { + return GUF_MATH_CKD_SUCCESS; + } + } else { + return GUF_MATH_CKD_SUCCESS; + } +} +#endif + +#ifdef INT32_MAX +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_add_i32(int32_t a, int32_t b) +{ + if (b > 0 && a > INT32_MAX - b) { + return GUF_MATH_CKD_OVERFLOW_POS; + } else if (b < 0 && a < INT32_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_i32(int32_t a, int32_t b) +{ + if (b < 0 && a > INT32_MAX + b) { + return GUF_MATH_CKD_OVERFLOW_POS; + } else if (b > 0 && a < INT32_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_i32(int32_t a, int32_t b) +{ + if (b > 0) { + if (a > INT32_MAX / b) { + return GUF_MATH_CKD_OVERFLOW_POS; + } else if (a < INT32_MIN / b) { + return GUF_MATH_CKD_OVERFLOW_NEG; + } else { + return GUF_MATH_CKD_SUCCESS; + } + } else if (b < 0) { + if (INT32_MIN != -INT32_MAX && b == -1) { // Prevent potential (INT32_MIN / b) overflow for b == -1 + return a == INT32_MIN ? GUF_MATH_CKD_OVERFLOW_POS : GUF_MATH_CKD_SUCCESS; + } else if (a < INT32_MAX / b) { + return GUF_MATH_CKD_OVERFLOW_POS; + } else if (a > INT32_MIN / b) { + return GUF_MATH_CKD_OVERFLOW_NEG; + } else { + return GUF_MATH_CKD_SUCCESS; + } + } else { + return GUF_MATH_CKD_SUCCESS; + } +} +#endif + +#ifdef INT64_MAX +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_add_i64(int64_t a, int64_t b) +{ + if (b > 0 && a > INT64_MAX - b) { + return GUF_MATH_CKD_OVERFLOW_POS; + } else if (b < 0 && a < INT64_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_i64(int64_t a, int64_t b) +{ + if (b < 0 && a > INT64_MAX + b) { + return GUF_MATH_CKD_OVERFLOW_POS; + } else if (b > 0 && a < INT64_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_i64(int64_t a, int64_t b) +{ + if (b > 0) { + if (a > INT64_MAX / b) { + return GUF_MATH_CKD_OVERFLOW_POS; + } else if (a < INT64_MIN / b) { + return GUF_MATH_CKD_OVERFLOW_NEG; + } else { + return GUF_MATH_CKD_SUCCESS; + } + } else if (b < 0) { + if (INT64_MIN != -INT64_MAX && b == -1) { // Prevent potential (INT64_MIN / b) overflow for b == -1 + return a == INT64_MIN ? GUF_MATH_CKD_OVERFLOW_POS : GUF_MATH_CKD_SUCCESS; + } else if (a < INT64_MAX / b) { + return GUF_MATH_CKD_OVERFLOW_POS; + } else if (a > INT64_MIN / b) { + return GUF_MATH_CKD_OVERFLOW_NEG; + } else { + return GUF_MATH_CKD_SUCCESS; + } + } else { + return GUF_MATH_CKD_SUCCESS; + } +} +#endif + + +// Unsigned integer arithmetic checks (generated with libguf/tools/ckdint-gen.py) +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_add_uchar(unsigned char a, unsigned char b) +{ + if (b > 0 && a > UCHAR_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_uchar(unsigned char a, unsigned char 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_uchar(unsigned char a, unsigned char b) +{ + const unsigned char c = 1u * a * b; + return a != 0 && ((1u * c / a) != b) ? GUF_MATH_CKD_OVERFLOW_POS : GUF_MATH_CKD_SUCCESS; +} + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_add_unsigned(unsigned a, unsigned b) +{ + if (b > 0 && a > UINT_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_unsigned(unsigned a, unsigned 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_unsigned(unsigned a, unsigned b) +{ + const unsigned c = 1u * a * b; + return a != 0 && ((1u * c / a) != b) ? GUF_MATH_CKD_OVERFLOW_POS : GUF_MATH_CKD_SUCCESS; +} + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_add_ulong(unsigned long a, unsigned long b) +{ + if (b > 0 && a > ULONG_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_ulong(unsigned long a, unsigned long 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_ulong(unsigned long a, unsigned long b) +{ + const unsigned long c = 1u * a * b; + return a != 0 && ((1u * c / a) != b) ? GUF_MATH_CKD_OVERFLOW_POS : GUF_MATH_CKD_SUCCESS; +} + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_add_ulong_long(unsigned long long a, unsigned long long b) +{ + if (b > 0 && a > ULLONG_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_ulong_long(unsigned long long a, unsigned long long 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_ulong_long(unsigned long long a, unsigned long long b) +{ + const unsigned long long c = 1u * a * b; + return a != 0 && ((1u * c / a) != b) ? GUF_MATH_CKD_OVERFLOW_POS : GUF_MATH_CKD_SUCCESS; +} + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_add_size_t(size_t a, size_t b) +{ + if (b > 0 && a > SIZE_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_size_t(size_t a, size_t 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_size_t(size_t a, size_t b) +{ + const size_t c = 1u * a * b; + return a != 0 && ((1u * c / a) != b) ? GUF_MATH_CKD_OVERFLOW_POS : GUF_MATH_CKD_SUCCESS; +} + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_add_least_u8(uint_least8_t a, uint_least8_t b) +{ + a = GUF_UWRAP_8(a); + b = GUF_UWRAP_8(b); + if (b > 0 && a > GUF_UINT8_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_least_u8(uint_least8_t a, uint_least8_t b) +{ + a = GUF_UWRAP_8(a); + b = GUF_UWRAP_8(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_least_u8(uint_least8_t a, uint_least8_t b) +{ + a = GUF_UWRAP_8(a); + b = GUF_UWRAP_8(b); + const uint_least8_t c = GUF_UWRAP_8( 1u * a * b ); + return a != 0 && ((1u * c / a) != b) ? GUF_MATH_CKD_OVERFLOW_POS : GUF_MATH_CKD_SUCCESS; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_add_least_u16(uint_least16_t a, uint_least16_t b) +{ + a = GUF_UWRAP_16(a); + b = GUF_UWRAP_16(b); + if (b > 0 && a > GUF_UINT16_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_least_u16(uint_least16_t a, uint_least16_t b) +{ + a = GUF_UWRAP_16(a); + b = GUF_UWRAP_16(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_least_u16(uint_least16_t a, uint_least16_t b) +{ + a = GUF_UWRAP_16(a); + b = GUF_UWRAP_16(b); + const uint_least16_t c = GUF_UWRAP_16( 1u * a * b ); + return a != 0 && ((1u * c / a) != b) ? GUF_MATH_CKD_OVERFLOW_POS : GUF_MATH_CKD_SUCCESS; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_add_least_u32(uint_least32_t a, uint_least32_t b) +{ + a = GUF_UWRAP_32(a); + b = GUF_UWRAP_32(b); + if (b > 0 && a > GUF_UINT32_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_least_u32(uint_least32_t a, uint_least32_t b) +{ + a = GUF_UWRAP_32(a); + b = GUF_UWRAP_32(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_least_u32(uint_least32_t a, uint_least32_t b) +{ + a = GUF_UWRAP_32(a); + b = GUF_UWRAP_32(b); + const uint_least32_t c = GUF_UWRAP_32( 1u * a * b ); + return a != 0 && ((1u * c / a) != b) ? GUF_MATH_CKD_OVERFLOW_POS : GUF_MATH_CKD_SUCCESS; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_add_least_u64(uint_least64_t a, uint_least64_t b) +{ + a = GUF_UWRAP_64(a); + b = GUF_UWRAP_64(b); + if (b > 0 && a > GUF_UINT64_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_least_u64(uint_least64_t a, uint_least64_t b) +{ + a = GUF_UWRAP_64(a); + b = GUF_UWRAP_64(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_least_u64(uint_least64_t a, uint_least64_t b) +{ + a = GUF_UWRAP_64(a); + b = GUF_UWRAP_64(b); + const uint_least64_t c = GUF_UWRAP_64( 1u * a * b ); + return a != 0 && ((1u * c / a) != b) ? GUF_MATH_CKD_OVERFLOW_POS : GUF_MATH_CKD_SUCCESS; +} +#ifdef UINT8_MAX +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_add_u8(uint8_t a, uint8_t b) +{ + if (b > 0 && a > UINT8_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_u8(uint8_t a, uint8_t 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_u8(uint8_t a, uint8_t b) +{ + const uint8_t c = 1u * a * b; + return a != 0 && ((1u * c / a) != b) ? GUF_MATH_CKD_OVERFLOW_POS : GUF_MATH_CKD_SUCCESS; +} +#endif + +#ifdef UINT16_MAX +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_add_u16(uint16_t a, uint16_t b) +{ + if (b > 0 && a > UINT16_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_u16(uint16_t a, uint16_t 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_u16(uint16_t a, uint16_t b) +{ + const uint16_t c = 1u * a * b; + return a != 0 && ((1u * c / a) != b) ? GUF_MATH_CKD_OVERFLOW_POS : GUF_MATH_CKD_SUCCESS; +} +#endif + +#ifdef UINT32_MAX +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_add_u32(uint32_t a, uint32_t b) +{ + if (b > 0 && a > UINT32_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_u32(uint32_t a, uint32_t 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_u32(uint32_t a, uint32_t b) +{ + const uint32_t c = 1u * a * b; + return a != 0 && ((1u * c / a) != b) ? GUF_MATH_CKD_OVERFLOW_POS : GUF_MATH_CKD_SUCCESS; +} +#endif + +#ifdef UINT64_MAX +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_ckd_add_u64(uint64_t a, uint64_t b) +{ + if (b > 0 && a > UINT64_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_u64(uint64_t a, uint64_t 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_u64(uint64_t a, uint64_t b) +{ + const uint64_t c = 1u * a * b; + return a != 0 && ((1u * c / a) != b) ? GUF_MATH_CKD_OVERFLOW_POS : GUF_MATH_CKD_SUCCESS; +} +#endif + + + +// Signed saturating/wrapping arithmetic (generated with libguf/tools/ckdint-gen.py) +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_add_int(int a, int b, int *result) +{ + const guf_math_ckd_result check = guf_ckd_add_int(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_int(int a, int b, int *result) +{ + const guf_math_ckd_result check = guf_ckd_sub_int(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_int(int a, int b, int *result) +{ + const guf_math_ckd_result check = guf_ckd_mul_int(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_int(int a, int b, int *result) +{ + const guf_math_ckd_result check = guf_ckd_add_int(a, b); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a + b; + break; + case GUF_MATH_CKD_OVERFLOW_POS: + *result = (a + INT_MIN) + (b + INT_MIN); + break; + case GUF_MATH_CKD_OVERFLOW_NEG: + *result = (a - INT_MIN) + (b - INT_MIN); + break; + default: + GUF_ASSERT(false); + } + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_sub_int(int a, int b, int *result) +{ + const guf_math_ckd_result check = guf_ckd_sub_int(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 = (a + INT_MIN) - (b - INT_MIN); // TODO: not sure + break; + case GUF_MATH_CKD_OVERFLOW_NEG: + GUF_ASSERT(b > 0); + *result = (a - INT_MIN) - (b + INT_MIN); // TODO: not sure + break; + default: + GUF_ASSERT(false); + } + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_mul_int(int a, int b, int *result) +{ + const guf_math_ckd_result check = guf_ckd_mul_int(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: { + unsigned res = 1u * (unsigned)a * (unsigned)b; + if (res > INT_MAX) { // This is the fix for implementation defined conversion from unsigned to signed. + const unsigned mod = (unsigned)INT_MAX + 1u; + GUF_ASSERT(mod > 0); + res = 1u * res % mod; + *result = INT_MIN + (int)res; + } else { + *result = (int)res; + } + break; + } + default: + GUF_ASSERT(false); + } + } + return check; +} + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_add_long(long a, long b, long *result) +{ + const guf_math_ckd_result check = guf_ckd_add_long(a, b); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a + b; + break; + case GUF_MATH_CKD_OVERFLOW_POS: + *result = LONG_MAX; + break; + case GUF_MATH_CKD_OVERFLOW_NEG: + *result = LONG_MIN; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_sub_long(long a, long b, long *result) +{ + const guf_math_ckd_result check = guf_ckd_sub_long(a, b); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a - b; + break; + case GUF_MATH_CKD_OVERFLOW_POS: + *result = LONG_MAX; + break; + case GUF_MATH_CKD_OVERFLOW_NEG: + *result = LONG_MIN; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_mul_long(long a, long b, long *result) +{ + const guf_math_ckd_result check = guf_ckd_mul_long(a, b); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a * b; + break; + case GUF_MATH_CKD_OVERFLOW_POS: + *result = LONG_MAX; + break; + case GUF_MATH_CKD_OVERFLOW_NEG: + *result = LONG_MIN; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_add_long(long a, long b, long *result) +{ + const guf_math_ckd_result check = guf_ckd_add_long(a, b); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a + b; + break; + case GUF_MATH_CKD_OVERFLOW_POS: + *result = (a + LONG_MIN) + (b + LONG_MIN); + break; + case GUF_MATH_CKD_OVERFLOW_NEG: + *result = (a - LONG_MIN) + (b - LONG_MIN); + break; + default: + GUF_ASSERT(false); + } + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_sub_long(long a, long b, long *result) +{ + const guf_math_ckd_result check = guf_ckd_sub_long(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 = (a + LONG_MIN) - (b - LONG_MIN); // TODO: not sure + break; + case GUF_MATH_CKD_OVERFLOW_NEG: + GUF_ASSERT(b > 0); + *result = (a - LONG_MIN) - (b + LONG_MIN); // TODO: not sure + break; + default: + GUF_ASSERT(false); + } + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_mul_long(long a, long b, long *result) +{ + const guf_math_ckd_result check = guf_ckd_mul_long(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: { + unsigned long res = 1u * (unsigned long)a * (unsigned long)b; + if (res > LONG_MAX) { // This is the fix for implementation defined conversion from unsigned to signed. + const unsigned long mod = (unsigned long)LONG_MAX + 1u; + GUF_ASSERT(mod > 0); + res = 1u * res % mod; + *result = LONG_MIN + (long)res; + } else { + *result = (long)res; + } + break; + } + default: + GUF_ASSERT(false); + } + } + return check; +} + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_add_long_long(long long a, long long b, long long *result) +{ + const guf_math_ckd_result check = guf_ckd_add_long_long(a, b); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a + b; + break; + case GUF_MATH_CKD_OVERFLOW_POS: + *result = LLONG_MAX; + break; + case GUF_MATH_CKD_OVERFLOW_NEG: + *result = LLONG_MIN; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_sub_long_long(long long a, long long b, long long *result) +{ + const guf_math_ckd_result check = guf_ckd_sub_long_long(a, b); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a - b; + break; + case GUF_MATH_CKD_OVERFLOW_POS: + *result = LLONG_MAX; + break; + case GUF_MATH_CKD_OVERFLOW_NEG: + *result = LLONG_MIN; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_mul_long_long(long long a, long long b, long long *result) +{ + const guf_math_ckd_result check = guf_ckd_mul_long_long(a, b); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a * b; + break; + case GUF_MATH_CKD_OVERFLOW_POS: + *result = LLONG_MAX; + break; + case GUF_MATH_CKD_OVERFLOW_NEG: + *result = LLONG_MIN; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_add_long_long(long long a, long long b, long long *result) +{ + const guf_math_ckd_result check = guf_ckd_add_long_long(a, b); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a + b; + break; + case GUF_MATH_CKD_OVERFLOW_POS: + *result = (a + LLONG_MIN) + (b + LLONG_MIN); + break; + case GUF_MATH_CKD_OVERFLOW_NEG: + *result = (a - LLONG_MIN) + (b - LLONG_MIN); + break; + default: + GUF_ASSERT(false); + } + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_sub_long_long(long long a, long long b, long long *result) +{ + const guf_math_ckd_result check = guf_ckd_sub_long_long(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 = (a + LLONG_MIN) - (b - LLONG_MIN); // TODO: not sure + break; + case GUF_MATH_CKD_OVERFLOW_NEG: + GUF_ASSERT(b > 0); + *result = (a - LLONG_MIN) - (b + LLONG_MIN); // TODO: not sure + break; + default: + GUF_ASSERT(false); + } + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_mul_long_long(long long a, long long b, long long *result) +{ + const guf_math_ckd_result check = guf_ckd_mul_long_long(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: { + unsigned long long res = 1u * (unsigned long long)a * (unsigned long long)b; + if (res > LLONG_MAX) { // This is the fix for implementation defined conversion from unsigned to signed. + const unsigned long long mod = (unsigned long long)LLONG_MAX + 1u; + GUF_ASSERT(mod > 0); + res = 1u * res % mod; + *result = LLONG_MIN + (long long)res; + } else { + *result = (long long)res; + } + break; + } + default: + GUF_ASSERT(false); + } + } + return check; +} + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_add_ptrdiff_t(ptrdiff_t a, ptrdiff_t b, ptrdiff_t *result) +{ + const guf_math_ckd_result check = guf_ckd_add_ptrdiff_t(a, b); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a + b; + break; + case GUF_MATH_CKD_OVERFLOW_POS: + *result = PTRDIFF_MAX; + break; + case GUF_MATH_CKD_OVERFLOW_NEG: + *result = PTRDIFF_MIN; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_sub_ptrdiff_t(ptrdiff_t a, ptrdiff_t b, ptrdiff_t *result) +{ + const guf_math_ckd_result check = guf_ckd_sub_ptrdiff_t(a, b); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a - b; + break; + case GUF_MATH_CKD_OVERFLOW_POS: + *result = PTRDIFF_MAX; + break; + case GUF_MATH_CKD_OVERFLOW_NEG: + *result = PTRDIFF_MIN; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_mul_ptrdiff_t(ptrdiff_t a, ptrdiff_t b, ptrdiff_t *result) +{ + const guf_math_ckd_result check = guf_ckd_mul_ptrdiff_t(a, b); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a * b; + break; + case GUF_MATH_CKD_OVERFLOW_POS: + *result = PTRDIFF_MAX; + break; + case GUF_MATH_CKD_OVERFLOW_NEG: + *result = PTRDIFF_MIN; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_add_ptrdiff_t(ptrdiff_t a, ptrdiff_t b, ptrdiff_t *result) +{ + const guf_math_ckd_result check = guf_ckd_add_ptrdiff_t(a, b); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a + b; + break; + case GUF_MATH_CKD_OVERFLOW_POS: + *result = (a + PTRDIFF_MIN) + (b + PTRDIFF_MIN); + break; + case GUF_MATH_CKD_OVERFLOW_NEG: + *result = (a - PTRDIFF_MIN) + (b - PTRDIFF_MIN); + break; + default: + GUF_ASSERT(false); + } + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_sub_ptrdiff_t(ptrdiff_t a, ptrdiff_t b, ptrdiff_t *result) +{ + const guf_math_ckd_result check = guf_ckd_sub_ptrdiff_t(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 = (a + PTRDIFF_MIN) - (b - PTRDIFF_MIN); // TODO: not sure + break; + case GUF_MATH_CKD_OVERFLOW_NEG: + GUF_ASSERT(b > 0); + *result = (a - PTRDIFF_MIN) - (b + PTRDIFF_MIN); // TODO: not sure + break; + default: + GUF_ASSERT(false); + } + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_mul_ptrdiff_t(ptrdiff_t a, ptrdiff_t b, ptrdiff_t *result) +{ + const guf_math_ckd_result check = guf_ckd_mul_ptrdiff_t(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: { + size_t res = 1u * (size_t)a * (size_t)b; + if (res > PTRDIFF_MAX) { // This is the fix for implementation defined conversion from unsigned to signed. + const size_t mod = (size_t)PTRDIFF_MAX + 1u; + GUF_ASSERT(mod > 0); + res = 1u * res % mod; + *result = PTRDIFF_MIN + (ptrdiff_t)res; + } else { + *result = (ptrdiff_t)res; + } + break; + } + default: + GUF_ASSERT(false); + } + } + return check; +} + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_add_least_i8(int_least8_t a, int_least8_t b, int_least8_t *result) +{ + const guf_math_ckd_result check = guf_ckd_add_least_i8(a, b); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a + b; + break; + case GUF_MATH_CKD_OVERFLOW_POS: + *result = GUF_INT8_MAX; + break; + case GUF_MATH_CKD_OVERFLOW_NEG: + *result = GUF_INT8_MIN; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_sub_least_i8(int_least8_t a, int_least8_t b, int_least8_t *result) +{ + const guf_math_ckd_result check = guf_ckd_sub_least_i8(a, b); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a - b; + break; + case GUF_MATH_CKD_OVERFLOW_POS: + *result = GUF_INT8_MAX; + break; + case GUF_MATH_CKD_OVERFLOW_NEG: + *result = GUF_INT8_MIN; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_mul_least_i8(int_least8_t a, int_least8_t b, int_least8_t *result) +{ + const guf_math_ckd_result check = guf_ckd_mul_least_i8(a, b); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a * b; + break; + case GUF_MATH_CKD_OVERFLOW_POS: + *result = GUF_INT8_MAX; + break; + case GUF_MATH_CKD_OVERFLOW_NEG: + *result = GUF_INT8_MIN; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_add_least_i8(int_least8_t a, int_least8_t b, int_least8_t *result) +{ + const guf_math_ckd_result check = guf_ckd_add_least_i8(a, b); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a + b; + break; + case GUF_MATH_CKD_OVERFLOW_POS: + *result = (a + GUF_INT8_MIN) + (b + GUF_INT8_MIN); + break; + case GUF_MATH_CKD_OVERFLOW_NEG: + *result = (a - GUF_INT8_MIN) + (b - GUF_INT8_MIN); + break; + default: + GUF_ASSERT(false); + } + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_sub_least_i8(int_least8_t a, int_least8_t b, int_least8_t *result) +{ + const guf_math_ckd_result check = guf_ckd_sub_least_i8(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 = (a + GUF_INT8_MIN) - (b - GUF_INT8_MIN); // TODO: not sure + break; + case GUF_MATH_CKD_OVERFLOW_NEG: + GUF_ASSERT(b > 0); + *result = (a - GUF_INT8_MIN) - (b + GUF_INT8_MIN); // TODO: not sure + break; + default: + GUF_ASSERT(false); + } + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_mul_least_i8(int_least8_t a, int_least8_t b, int_least8_t *result) +{ + const guf_math_ckd_result check = guf_ckd_mul_least_i8(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_least8_t res = 1u * (uint_least8_t)a * (uint_least8_t)b; + if (res > GUF_INT8_MAX) { // This is the fix for implementation defined conversion from unsigned to signed. + const uint_least8_t mod = (uint_least8_t)GUF_INT8_MAX + 1u; + GUF_ASSERT(mod > 0); + res = 1u * res % mod; + *result = GUF_INT8_MIN + (int_least8_t)res; + } else { + *result = (int_least8_t)res; + } + break; + } + default: + GUF_ASSERT(false); + } + } + return check; +} + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_add_least_i16(int_least16_t a, int_least16_t b, int_least16_t *result) +{ + const guf_math_ckd_result check = guf_ckd_add_least_i16(a, b); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a + b; + break; + case GUF_MATH_CKD_OVERFLOW_POS: + *result = GUF_INT16_MAX; + break; + case GUF_MATH_CKD_OVERFLOW_NEG: + *result = GUF_INT16_MIN; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_sub_least_i16(int_least16_t a, int_least16_t b, int_least16_t *result) +{ + const guf_math_ckd_result check = guf_ckd_sub_least_i16(a, b); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a - b; + break; + case GUF_MATH_CKD_OVERFLOW_POS: + *result = GUF_INT16_MAX; + break; + case GUF_MATH_CKD_OVERFLOW_NEG: + *result = GUF_INT16_MIN; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_mul_least_i16(int_least16_t a, int_least16_t b, int_least16_t *result) +{ + const guf_math_ckd_result check = guf_ckd_mul_least_i16(a, b); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a * b; + break; + case GUF_MATH_CKD_OVERFLOW_POS: + *result = GUF_INT16_MAX; + break; + case GUF_MATH_CKD_OVERFLOW_NEG: + *result = GUF_INT16_MIN; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_add_least_i16(int_least16_t a, int_least16_t b, int_least16_t *result) +{ + const guf_math_ckd_result check = guf_ckd_add_least_i16(a, b); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a + b; + break; + case GUF_MATH_CKD_OVERFLOW_POS: + *result = (a + GUF_INT16_MIN) + (b + GUF_INT16_MIN); + break; + case GUF_MATH_CKD_OVERFLOW_NEG: + *result = (a - GUF_INT16_MIN) + (b - GUF_INT16_MIN); + break; + default: + GUF_ASSERT(false); + } + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_sub_least_i16(int_least16_t a, int_least16_t b, int_least16_t *result) +{ + const guf_math_ckd_result check = guf_ckd_sub_least_i16(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 = (a + GUF_INT16_MIN) - (b - GUF_INT16_MIN); // TODO: not sure + break; + case GUF_MATH_CKD_OVERFLOW_NEG: + GUF_ASSERT(b > 0); + *result = (a - GUF_INT16_MIN) - (b + GUF_INT16_MIN); // TODO: not sure + break; + default: + GUF_ASSERT(false); + } + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_mul_least_i16(int_least16_t a, int_least16_t b, int_least16_t *result) +{ + const guf_math_ckd_result check = guf_ckd_mul_least_i16(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_least16_t res = 1u * (uint_least16_t)a * (uint_least16_t)b; + if (res > GUF_INT16_MAX) { // This is the fix for implementation defined conversion from unsigned to signed. + const uint_least16_t mod = (uint_least16_t)GUF_INT16_MAX + 1u; + GUF_ASSERT(mod > 0); + res = 1u * res % mod; + *result = GUF_INT16_MIN + (int_least16_t)res; + } else { + *result = (int_least16_t)res; + } + break; + } + default: + GUF_ASSERT(false); + } + } + return check; +} + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_add_least_i32(int_least32_t a, int_least32_t b, int_least32_t *result) +{ + const guf_math_ckd_result check = guf_ckd_add_least_i32(a, b); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a + b; + break; + case GUF_MATH_CKD_OVERFLOW_POS: + *result = GUF_INT32_MAX; + break; + case GUF_MATH_CKD_OVERFLOW_NEG: + *result = GUF_INT32_MIN; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_sub_least_i32(int_least32_t a, int_least32_t b, int_least32_t *result) +{ + const guf_math_ckd_result check = guf_ckd_sub_least_i32(a, b); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a - b; + break; + case GUF_MATH_CKD_OVERFLOW_POS: + *result = GUF_INT32_MAX; + break; + case GUF_MATH_CKD_OVERFLOW_NEG: + *result = GUF_INT32_MIN; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_mul_least_i32(int_least32_t a, int_least32_t b, int_least32_t *result) +{ + const guf_math_ckd_result check = guf_ckd_mul_least_i32(a, b); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a * b; + break; + case GUF_MATH_CKD_OVERFLOW_POS: + *result = GUF_INT32_MAX; + break; + case GUF_MATH_CKD_OVERFLOW_NEG: + *result = GUF_INT32_MIN; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_add_least_i32(int_least32_t a, int_least32_t b, int_least32_t *result) +{ + const guf_math_ckd_result check = guf_ckd_add_least_i32(a, b); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a + b; + break; + case GUF_MATH_CKD_OVERFLOW_POS: + *result = (a + GUF_INT32_MIN) + (b + GUF_INT32_MIN); + break; + case GUF_MATH_CKD_OVERFLOW_NEG: + *result = (a - GUF_INT32_MIN) + (b - GUF_INT32_MIN); + break; + default: + GUF_ASSERT(false); + } + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_sub_least_i32(int_least32_t a, int_least32_t b, int_least32_t *result) +{ + const guf_math_ckd_result check = guf_ckd_sub_least_i32(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 = (a + GUF_INT32_MIN) - (b - GUF_INT32_MIN); // TODO: not sure + break; + case GUF_MATH_CKD_OVERFLOW_NEG: + GUF_ASSERT(b > 0); + *result = (a - GUF_INT32_MIN) - (b + GUF_INT32_MIN); // TODO: not sure + break; + default: + GUF_ASSERT(false); + } + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_mul_least_i32(int_least32_t a, int_least32_t b, int_least32_t *result) +{ + const guf_math_ckd_result check = guf_ckd_mul_least_i32(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_least32_t res = 1u * (uint_least32_t)a * (uint_least32_t)b; + if (res > GUF_INT32_MAX) { // This is the fix for implementation defined conversion from unsigned to signed. + const uint_least32_t mod = (uint_least32_t)GUF_INT32_MAX + 1u; + GUF_ASSERT(mod > 0); + res = 1u * res % mod; + *result = GUF_INT32_MIN + (int_least32_t)res; + } else { + *result = (int_least32_t)res; + } + break; + } + default: + GUF_ASSERT(false); + } + } + return check; +} + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_add_least_i64(int_least64_t a, int_least64_t b, int_least64_t *result) +{ + const guf_math_ckd_result check = guf_ckd_add_least_i64(a, b); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a + b; + break; + case GUF_MATH_CKD_OVERFLOW_POS: + *result = GUF_INT64_MAX; + break; + case GUF_MATH_CKD_OVERFLOW_NEG: + *result = GUF_INT64_MIN; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_sub_least_i64(int_least64_t a, int_least64_t b, int_least64_t *result) +{ + const guf_math_ckd_result check = guf_ckd_sub_least_i64(a, b); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a - b; + break; + case GUF_MATH_CKD_OVERFLOW_POS: + *result = GUF_INT64_MAX; + break; + case GUF_MATH_CKD_OVERFLOW_NEG: + *result = GUF_INT64_MIN; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_mul_least_i64(int_least64_t a, int_least64_t b, int_least64_t *result) +{ + const guf_math_ckd_result check = guf_ckd_mul_least_i64(a, b); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a * b; + break; + case GUF_MATH_CKD_OVERFLOW_POS: + *result = GUF_INT64_MAX; + break; + case GUF_MATH_CKD_OVERFLOW_NEG: + *result = GUF_INT64_MIN; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_add_least_i64(int_least64_t a, int_least64_t b, int_least64_t *result) +{ + const guf_math_ckd_result check = guf_ckd_add_least_i64(a, b); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a + b; + break; + case GUF_MATH_CKD_OVERFLOW_POS: + *result = (a + GUF_INT64_MIN) + (b + GUF_INT64_MIN); + break; + case GUF_MATH_CKD_OVERFLOW_NEG: + *result = (a - GUF_INT64_MIN) + (b - GUF_INT64_MIN); + break; + default: + GUF_ASSERT(false); + } + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_sub_least_i64(int_least64_t a, int_least64_t b, int_least64_t *result) +{ + const guf_math_ckd_result check = guf_ckd_sub_least_i64(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 = (a + GUF_INT64_MIN) - (b - GUF_INT64_MIN); // TODO: not sure + break; + case GUF_MATH_CKD_OVERFLOW_NEG: + GUF_ASSERT(b > 0); + *result = (a - GUF_INT64_MIN) - (b + GUF_INT64_MIN); // TODO: not sure + break; + default: + GUF_ASSERT(false); + } + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_mul_least_i64(int_least64_t a, int_least64_t b, int_least64_t *result) +{ + const guf_math_ckd_result check = guf_ckd_mul_least_i64(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_least64_t res = 1u * (uint_least64_t)a * (uint_least64_t)b; + if (res > GUF_INT64_MAX) { // This is the fix for implementation defined conversion from unsigned to signed. + const uint_least64_t mod = (uint_least64_t)GUF_INT64_MAX + 1u; + GUF_ASSERT(mod > 0); + res = 1u * res % mod; + *result = GUF_INT64_MIN + (int_least64_t)res; + } else { + *result = (int_least64_t)res; + } + break; + } + default: + GUF_ASSERT(false); + } + } + return check; +} + +#ifdef INT8_MAX +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_add_i8(int8_t a, int8_t b, int8_t *result) +{ + const guf_math_ckd_result check = guf_ckd_add_i8(a, b); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a + b; + break; + case GUF_MATH_CKD_OVERFLOW_POS: + *result = INT8_MAX; + break; + case GUF_MATH_CKD_OVERFLOW_NEG: + *result = INT8_MIN; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_sub_i8(int8_t a, int8_t b, int8_t *result) +{ + const guf_math_ckd_result check = guf_ckd_sub_i8(a, b); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a - b; + break; + case GUF_MATH_CKD_OVERFLOW_POS: + *result = INT8_MAX; + break; + case GUF_MATH_CKD_OVERFLOW_NEG: + *result = INT8_MIN; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_mul_i8(int8_t a, int8_t b, int8_t *result) +{ + const guf_math_ckd_result check = guf_ckd_mul_i8(a, b); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a * b; + break; + case GUF_MATH_CKD_OVERFLOW_POS: + *result = INT8_MAX; + break; + case GUF_MATH_CKD_OVERFLOW_NEG: + *result = INT8_MIN; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_add_i8(int8_t a, int8_t b, int8_t *result) +{ + const guf_math_ckd_result check = guf_ckd_add_i8(a, b); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a + b; + break; + case GUF_MATH_CKD_OVERFLOW_POS: + *result = (a + INT8_MIN) + (b + INT8_MIN); + break; + case GUF_MATH_CKD_OVERFLOW_NEG: + *result = (a - INT8_MIN) + (b - INT8_MIN); + break; + default: + GUF_ASSERT(false); + } + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_sub_i8(int8_t a, int8_t b, int8_t *result) +{ + const guf_math_ckd_result check = guf_ckd_sub_i8(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 = (a + INT8_MIN) - (b - INT8_MIN); // TODO: not sure + break; + case GUF_MATH_CKD_OVERFLOW_NEG: + GUF_ASSERT(b > 0); + *result = (a - INT8_MIN) - (b + INT8_MIN); // TODO: not sure + break; + default: + GUF_ASSERT(false); + } + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_mul_i8(int8_t a, int8_t b, int8_t *result) +{ + const guf_math_ckd_result check = guf_ckd_mul_i8(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: { + uint8_t res = 1u * (uint8_t)a * (uint8_t)b; + if (res > INT8_MAX) { // This is the fix for implementation defined conversion from unsigned to signed. + const uint8_t mod = (uint8_t)INT8_MAX + 1u; + GUF_ASSERT(mod > 0); + res = 1u * res % mod; + *result = INT8_MIN + (int8_t)res; + } else { + *result = (int8_t)res; + } + break; + } + default: + GUF_ASSERT(false); + } + } + return check; +} +#endif + +#ifdef INT16_MAX +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_add_i16(int16_t a, int16_t b, int16_t *result) +{ + const guf_math_ckd_result check = guf_ckd_add_i16(a, b); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a + b; + break; + case GUF_MATH_CKD_OVERFLOW_POS: + *result = INT16_MAX; + break; + case GUF_MATH_CKD_OVERFLOW_NEG: + *result = INT16_MIN; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_sub_i16(int16_t a, int16_t b, int16_t *result) +{ + const guf_math_ckd_result check = guf_ckd_sub_i16(a, b); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a - b; + break; + case GUF_MATH_CKD_OVERFLOW_POS: + *result = INT16_MAX; + break; + case GUF_MATH_CKD_OVERFLOW_NEG: + *result = INT16_MIN; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_mul_i16(int16_t a, int16_t b, int16_t *result) +{ + const guf_math_ckd_result check = guf_ckd_mul_i16(a, b); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a * b; + break; + case GUF_MATH_CKD_OVERFLOW_POS: + *result = INT16_MAX; + break; + case GUF_MATH_CKD_OVERFLOW_NEG: + *result = INT16_MIN; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_add_i16(int16_t a, int16_t b, int16_t *result) +{ + const guf_math_ckd_result check = guf_ckd_add_i16(a, b); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a + b; + break; + case GUF_MATH_CKD_OVERFLOW_POS: + *result = (a + INT16_MIN) + (b + INT16_MIN); + break; + case GUF_MATH_CKD_OVERFLOW_NEG: + *result = (a - INT16_MIN) + (b - INT16_MIN); + break; + default: + GUF_ASSERT(false); + } + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_sub_i16(int16_t a, int16_t b, int16_t *result) +{ + const guf_math_ckd_result check = guf_ckd_sub_i16(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 = (a + INT16_MIN) - (b - INT16_MIN); // TODO: not sure + break; + case GUF_MATH_CKD_OVERFLOW_NEG: + GUF_ASSERT(b > 0); + *result = (a - INT16_MIN) - (b + INT16_MIN); // TODO: not sure + break; + default: + GUF_ASSERT(false); + } + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_mul_i16(int16_t a, int16_t b, int16_t *result) +{ + const guf_math_ckd_result check = guf_ckd_mul_i16(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: { + uint16_t res = 1u * (uint16_t)a * (uint16_t)b; + if (res > INT16_MAX) { // This is the fix for implementation defined conversion from unsigned to signed. + const uint16_t mod = (uint16_t)INT16_MAX + 1u; + GUF_ASSERT(mod > 0); + res = 1u * res % mod; + *result = INT16_MIN + (int16_t)res; + } else { + *result = (int16_t)res; + } + break; + } + default: + GUF_ASSERT(false); + } + } + return check; +} +#endif + +#ifdef INT32_MAX +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_add_i32(int32_t a, int32_t b, int32_t *result) +{ + const guf_math_ckd_result check = guf_ckd_add_i32(a, b); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a + b; + break; + case GUF_MATH_CKD_OVERFLOW_POS: + *result = INT32_MAX; + break; + case GUF_MATH_CKD_OVERFLOW_NEG: + *result = INT32_MIN; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_sub_i32(int32_t a, int32_t b, int32_t *result) +{ + const guf_math_ckd_result check = guf_ckd_sub_i32(a, b); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a - b; + break; + case GUF_MATH_CKD_OVERFLOW_POS: + *result = INT32_MAX; + break; + case GUF_MATH_CKD_OVERFLOW_NEG: + *result = INT32_MIN; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_mul_i32(int32_t a, int32_t b, int32_t *result) +{ + const guf_math_ckd_result check = guf_ckd_mul_i32(a, b); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a * b; + break; + case GUF_MATH_CKD_OVERFLOW_POS: + *result = INT32_MAX; + break; + case GUF_MATH_CKD_OVERFLOW_NEG: + *result = INT32_MIN; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_add_i32(int32_t a, int32_t b, int32_t *result) +{ + const guf_math_ckd_result check = guf_ckd_add_i32(a, b); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a + b; + break; + case GUF_MATH_CKD_OVERFLOW_POS: + *result = (a + INT32_MIN) + (b + INT32_MIN); + break; + case GUF_MATH_CKD_OVERFLOW_NEG: + *result = (a - INT32_MIN) + (b - INT32_MIN); + break; + default: + GUF_ASSERT(false); + } + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_sub_i32(int32_t a, int32_t b, int32_t *result) +{ + const guf_math_ckd_result check = guf_ckd_sub_i32(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 = (a + INT32_MIN) - (b - INT32_MIN); // TODO: not sure + break; + case GUF_MATH_CKD_OVERFLOW_NEG: + GUF_ASSERT(b > 0); + *result = (a - INT32_MIN) - (b + INT32_MIN); // TODO: not sure + break; + default: + GUF_ASSERT(false); + } + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_mul_i32(int32_t a, int32_t b, int32_t *result) +{ + const guf_math_ckd_result check = guf_ckd_mul_i32(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: { + uint32_t res = 1u * (uint32_t)a * (uint32_t)b; + if (res > INT32_MAX) { // This is the fix for implementation defined conversion from unsigned to signed. + const uint32_t mod = (uint32_t)INT32_MAX + 1u; + GUF_ASSERT(mod > 0); + res = 1u * res % mod; + *result = INT32_MIN + (int32_t)res; + } else { + *result = (int32_t)res; + } + break; + } + default: + GUF_ASSERT(false); + } + } + return check; +} +#endif + +#ifdef INT64_MAX +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_add_i64(int64_t a, int64_t b, int64_t *result) +{ + const guf_math_ckd_result check = guf_ckd_add_i64(a, b); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a + b; + break; + case GUF_MATH_CKD_OVERFLOW_POS: + *result = INT64_MAX; + break; + case GUF_MATH_CKD_OVERFLOW_NEG: + *result = INT64_MIN; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_sub_i64(int64_t a, int64_t b, int64_t *result) +{ + const guf_math_ckd_result check = guf_ckd_sub_i64(a, b); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a - b; + break; + case GUF_MATH_CKD_OVERFLOW_POS: + *result = INT64_MAX; + break; + case GUF_MATH_CKD_OVERFLOW_NEG: + *result = INT64_MIN; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_mul_i64(int64_t a, int64_t b, int64_t *result) +{ + const guf_math_ckd_result check = guf_ckd_mul_i64(a, b); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a * b; + break; + case GUF_MATH_CKD_OVERFLOW_POS: + *result = INT64_MAX; + break; + case GUF_MATH_CKD_OVERFLOW_NEG: + *result = INT64_MIN; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_add_i64(int64_t a, int64_t b, int64_t *result) +{ + const guf_math_ckd_result check = guf_ckd_add_i64(a, b); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a + b; + break; + case GUF_MATH_CKD_OVERFLOW_POS: + *result = (a + INT64_MIN) + (b + INT64_MIN); + break; + case GUF_MATH_CKD_OVERFLOW_NEG: + *result = (a - INT64_MIN) + (b - INT64_MIN); + break; + default: + GUF_ASSERT(false); + } + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_sub_i64(int64_t a, int64_t b, int64_t *result) +{ + const guf_math_ckd_result check = guf_ckd_sub_i64(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 = (a + INT64_MIN) - (b - INT64_MIN); // TODO: not sure + break; + case GUF_MATH_CKD_OVERFLOW_NEG: + GUF_ASSERT(b > 0); + *result = (a - INT64_MIN) - (b + INT64_MIN); // TODO: not sure + break; + default: + GUF_ASSERT(false); + } + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_mul_i64(int64_t a, int64_t b, int64_t *result) +{ + const guf_math_ckd_result check = guf_ckd_mul_i64(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: { + uint64_t res = 1u * (uint64_t)a * (uint64_t)b; + if (res > INT64_MAX) { // This is the fix for implementation defined conversion from unsigned to signed. + const uint64_t mod = (uint64_t)INT64_MAX + 1u; + GUF_ASSERT(mod > 0); + res = 1u * res % mod; + *result = INT64_MIN + (int64_t)res; + } else { + *result = (int64_t)res; + } + break; + } + default: + GUF_ASSERT(false); + } + } + return check; +} +#endif + + +// Unsigned saturating/wrapping arithmetic (generated with libguf/tools/ckdint-gen.py) +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_add_uchar(unsigned char a, unsigned char b, unsigned char *result) +{ + const guf_math_ckd_result check = guf_ckd_add_uchar(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 = UCHAR_MAX; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_sub_uchar(unsigned char a, unsigned char b, unsigned char *result) +{ + const guf_math_ckd_result check = guf_ckd_sub_uchar(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_uchar(unsigned char a, unsigned char b, unsigned char *result) +{ + const guf_math_ckd_result check = guf_ckd_mul_uchar(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 = UCHAR_MAX; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_add_uchar(unsigned char a, unsigned char b, unsigned char *result) +{ + const guf_math_ckd_result check = guf_ckd_add_uchar(a, b); + if (result) { + *result = 1u * a + b; + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_sub_uchar(unsigned char a, unsigned char b, unsigned char *result) +{ + const guf_math_ckd_result check = guf_ckd_sub_uchar(a, b); + if (result) { + *result = 1u * a - b; + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_mul_uchar(unsigned char a, unsigned char b, unsigned char *result) +{ + const guf_math_ckd_result check = guf_ckd_mul_uchar(a, b); + if (result) { + *result = 1u * a * b; + } + return check; +} + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_add_unsigned(unsigned a, unsigned b, unsigned *result) +{ + const guf_math_ckd_result check = guf_ckd_add_unsigned(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 = UINT_MAX; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_sub_unsigned(unsigned a, unsigned b, unsigned *result) +{ + const guf_math_ckd_result check = guf_ckd_sub_unsigned(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_unsigned(unsigned a, unsigned b, unsigned *result) +{ + const guf_math_ckd_result check = guf_ckd_mul_unsigned(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 = UINT_MAX; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_add_unsigned(unsigned a, unsigned b, unsigned *result) +{ + const guf_math_ckd_result check = guf_ckd_add_unsigned(a, b); + if (result) { + *result = 1u * a + b; + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_sub_unsigned(unsigned a, unsigned b, unsigned *result) +{ + const guf_math_ckd_result check = guf_ckd_sub_unsigned(a, b); + if (result) { + *result = 1u * a - b; + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_mul_unsigned(unsigned a, unsigned b, unsigned *result) +{ + const guf_math_ckd_result check = guf_ckd_mul_unsigned(a, b); + if (result) { + *result = 1u * a * b; + } + return check; +} + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_add_ulong(unsigned long a, unsigned long b, unsigned long *result) +{ + const guf_math_ckd_result check = guf_ckd_add_ulong(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 = ULONG_MAX; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_sub_ulong(unsigned long a, unsigned long b, unsigned long *result) +{ + const guf_math_ckd_result check = guf_ckd_sub_ulong(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_ulong(unsigned long a, unsigned long b, unsigned long *result) +{ + const guf_math_ckd_result check = guf_ckd_mul_ulong(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 = ULONG_MAX; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_add_ulong(unsigned long a, unsigned long b, unsigned long *result) +{ + const guf_math_ckd_result check = guf_ckd_add_ulong(a, b); + if (result) { + *result = 1u * a + b; + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_sub_ulong(unsigned long a, unsigned long b, unsigned long *result) +{ + const guf_math_ckd_result check = guf_ckd_sub_ulong(a, b); + if (result) { + *result = 1u * a - b; + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_mul_ulong(unsigned long a, unsigned long b, unsigned long *result) +{ + const guf_math_ckd_result check = guf_ckd_mul_ulong(a, b); + if (result) { + *result = 1u * a * b; + } + return check; +} + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_add_ulong_long(unsigned long long a, unsigned long long b, unsigned long long *result) +{ + const guf_math_ckd_result check = guf_ckd_add_ulong_long(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 = ULLONG_MAX; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_sub_ulong_long(unsigned long long a, unsigned long long b, unsigned long long *result) +{ + const guf_math_ckd_result check = guf_ckd_sub_ulong_long(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_ulong_long(unsigned long long a, unsigned long long b, unsigned long long *result) +{ + const guf_math_ckd_result check = guf_ckd_mul_ulong_long(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 = ULLONG_MAX; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_add_ulong_long(unsigned long long a, unsigned long long b, unsigned long long *result) +{ + const guf_math_ckd_result check = guf_ckd_add_ulong_long(a, b); + if (result) { + *result = 1u * a + b; + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_sub_ulong_long(unsigned long long a, unsigned long long b, unsigned long long *result) +{ + const guf_math_ckd_result check = guf_ckd_sub_ulong_long(a, b); + if (result) { + *result = 1u * a - b; + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_mul_ulong_long(unsigned long long a, unsigned long long b, unsigned long long *result) +{ + const guf_math_ckd_result check = guf_ckd_mul_ulong_long(a, b); + if (result) { + *result = 1u * a * b; + } + return check; +} + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_add_size_t(size_t a, size_t b, size_t *result) +{ + const guf_math_ckd_result check = guf_ckd_add_size_t(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 = SIZE_MAX; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_sub_size_t(size_t a, size_t b, size_t *result) +{ + const guf_math_ckd_result check = guf_ckd_sub_size_t(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_size_t(size_t a, size_t b, size_t *result) +{ + const guf_math_ckd_result check = guf_ckd_mul_size_t(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 = SIZE_MAX; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_add_size_t(size_t a, size_t b, size_t *result) +{ + const guf_math_ckd_result check = guf_ckd_add_size_t(a, b); + if (result) { + *result = 1u * a + b; + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_sub_size_t(size_t a, size_t b, size_t *result) +{ + const guf_math_ckd_result check = guf_ckd_sub_size_t(a, b); + if (result) { + *result = 1u * a - b; + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_mul_size_t(size_t a, size_t b, size_t *result) +{ + const guf_math_ckd_result check = guf_ckd_mul_size_t(a, b); + if (result) { + *result = 1u * a * b; + } + return check; +} + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_add_least_u8(uint_least8_t a, uint_least8_t b, uint_least8_t *result) +{ + const guf_math_ckd_result check = guf_ckd_add_least_u8(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 = GUF_UINT8_MAX; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_sub_least_u8(uint_least8_t a, uint_least8_t b, uint_least8_t *result) +{ + const guf_math_ckd_result check = guf_ckd_sub_least_u8(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_least_u8(uint_least8_t a, uint_least8_t b, uint_least8_t *result) +{ + const guf_math_ckd_result check = guf_ckd_mul_least_u8(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 = GUF_UINT8_MAX; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_add_least_u8(uint_least8_t a, uint_least8_t b, uint_least8_t *result) +{ + a = GUF_UWRAP_8(a); + b = GUF_UWRAP_8(b); + const guf_math_ckd_result check = guf_ckd_add_least_u8(a, b); + if (result) { + *result = GUF_UWRAP_8( 1u * a + b ); + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_sub_least_u8(uint_least8_t a, uint_least8_t b, uint_least8_t *result) +{ + a = GUF_UWRAP_8(a); + b = GUF_UWRAP_8(b); + const guf_math_ckd_result check = guf_ckd_sub_least_u8(a, b); + if (result) { + *result = GUF_UWRAP_8( 1u * a - b ); + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_mul_least_u8(uint_least8_t a, uint_least8_t b, uint_least8_t *result) +{ + a = GUF_UWRAP_8(a); + b = GUF_UWRAP_8(b); + const guf_math_ckd_result check = guf_ckd_mul_least_u8(a, b); + if (result) { + *result = GUF_UWRAP_8( 1u * a * b ); + } + return check; +} + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_add_least_u16(uint_least16_t a, uint_least16_t b, uint_least16_t *result) +{ + const guf_math_ckd_result check = guf_ckd_add_least_u16(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 = GUF_UINT16_MAX; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_sub_least_u16(uint_least16_t a, uint_least16_t b, uint_least16_t *result) +{ + const guf_math_ckd_result check = guf_ckd_sub_least_u16(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_least_u16(uint_least16_t a, uint_least16_t b, uint_least16_t *result) +{ + const guf_math_ckd_result check = guf_ckd_mul_least_u16(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 = GUF_UINT16_MAX; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_add_least_u16(uint_least16_t a, uint_least16_t b, uint_least16_t *result) +{ + a = GUF_UWRAP_16(a); + b = GUF_UWRAP_16(b); + const guf_math_ckd_result check = guf_ckd_add_least_u16(a, b); + if (result) { + *result = GUF_UWRAP_16( 1u * a + b ); + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_sub_least_u16(uint_least16_t a, uint_least16_t b, uint_least16_t *result) +{ + a = GUF_UWRAP_16(a); + b = GUF_UWRAP_16(b); + const guf_math_ckd_result check = guf_ckd_sub_least_u16(a, b); + if (result) { + *result = GUF_UWRAP_16( 1u * a - b ); + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_mul_least_u16(uint_least16_t a, uint_least16_t b, uint_least16_t *result) +{ + a = GUF_UWRAP_16(a); + b = GUF_UWRAP_16(b); + const guf_math_ckd_result check = guf_ckd_mul_least_u16(a, b); + if (result) { + *result = GUF_UWRAP_16( 1u * a * b ); + } + return check; +} + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_add_least_u32(uint_least32_t a, uint_least32_t b, uint_least32_t *result) +{ + const guf_math_ckd_result check = guf_ckd_add_least_u32(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 = GUF_UINT32_MAX; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_sub_least_u32(uint_least32_t a, uint_least32_t b, uint_least32_t *result) +{ + const guf_math_ckd_result check = guf_ckd_sub_least_u32(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_least_u32(uint_least32_t a, uint_least32_t b, uint_least32_t *result) +{ + const guf_math_ckd_result check = guf_ckd_mul_least_u32(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 = GUF_UINT32_MAX; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_add_least_u32(uint_least32_t a, uint_least32_t b, uint_least32_t *result) +{ + a = GUF_UWRAP_32(a); + b = GUF_UWRAP_32(b); + const guf_math_ckd_result check = guf_ckd_add_least_u32(a, b); + if (result) { + *result = GUF_UWRAP_32( 1u * a + b ); + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_sub_least_u32(uint_least32_t a, uint_least32_t b, uint_least32_t *result) +{ + a = GUF_UWRAP_32(a); + b = GUF_UWRAP_32(b); + const guf_math_ckd_result check = guf_ckd_sub_least_u32(a, b); + if (result) { + *result = GUF_UWRAP_32( 1u * a - b ); + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_mul_least_u32(uint_least32_t a, uint_least32_t b, uint_least32_t *result) +{ + a = GUF_UWRAP_32(a); + b = GUF_UWRAP_32(b); + const guf_math_ckd_result check = guf_ckd_mul_least_u32(a, b); + if (result) { + *result = GUF_UWRAP_32( 1u * a * b ); + } + return check; +} + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_add_least_u64(uint_least64_t a, uint_least64_t b, uint_least64_t *result) +{ + const guf_math_ckd_result check = guf_ckd_add_least_u64(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 = GUF_UINT64_MAX; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_sub_least_u64(uint_least64_t a, uint_least64_t b, uint_least64_t *result) +{ + const guf_math_ckd_result check = guf_ckd_sub_least_u64(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_least_u64(uint_least64_t a, uint_least64_t b, uint_least64_t *result) +{ + const guf_math_ckd_result check = guf_ckd_mul_least_u64(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 = GUF_UINT64_MAX; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_add_least_u64(uint_least64_t a, uint_least64_t b, uint_least64_t *result) +{ + a = GUF_UWRAP_64(a); + b = GUF_UWRAP_64(b); + const guf_math_ckd_result check = guf_ckd_add_least_u64(a, b); + if (result) { + *result = GUF_UWRAP_64( 1u * a + b ); + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_sub_least_u64(uint_least64_t a, uint_least64_t b, uint_least64_t *result) +{ + a = GUF_UWRAP_64(a); + b = GUF_UWRAP_64(b); + const guf_math_ckd_result check = guf_ckd_sub_least_u64(a, b); + if (result) { + *result = GUF_UWRAP_64( 1u * a - b ); + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_mul_least_u64(uint_least64_t a, uint_least64_t b, uint_least64_t *result) +{ + a = GUF_UWRAP_64(a); + b = GUF_UWRAP_64(b); + const guf_math_ckd_result check = guf_ckd_mul_least_u64(a, b); + if (result) { + *result = GUF_UWRAP_64( 1u * a * b ); + } + return check; +} + +#ifdef UINT8_MAX +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_add_u8(uint8_t a, uint8_t b, uint8_t *result) +{ + const guf_math_ckd_result check = guf_ckd_add_u8(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 = UINT8_MAX; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_sub_u8(uint8_t a, uint8_t b, uint8_t *result) +{ + const guf_math_ckd_result check = guf_ckd_sub_u8(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_u8(uint8_t a, uint8_t b, uint8_t *result) +{ + const guf_math_ckd_result check = guf_ckd_mul_u8(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 = UINT8_MAX; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_add_u8(uint8_t a, uint8_t b, uint8_t *result) +{ + const guf_math_ckd_result check = guf_ckd_add_u8(a, b); + if (result) { + *result = 1u * a + b; + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_sub_u8(uint8_t a, uint8_t b, uint8_t *result) +{ + const guf_math_ckd_result check = guf_ckd_sub_u8(a, b); + if (result) { + *result = 1u * a - b; + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_mul_u8(uint8_t a, uint8_t b, uint8_t *result) +{ + const guf_math_ckd_result check = guf_ckd_mul_u8(a, b); + if (result) { + *result = 1u * a * b; + } + return check; +} +#endif + +#ifdef UINT16_MAX +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_add_u16(uint16_t a, uint16_t b, uint16_t *result) +{ + const guf_math_ckd_result check = guf_ckd_add_u16(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 = UINT16_MAX; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_sub_u16(uint16_t a, uint16_t b, uint16_t *result) +{ + const guf_math_ckd_result check = guf_ckd_sub_u16(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_u16(uint16_t a, uint16_t b, uint16_t *result) +{ + const guf_math_ckd_result check = guf_ckd_mul_u16(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 = UINT16_MAX; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_add_u16(uint16_t a, uint16_t b, uint16_t *result) +{ + const guf_math_ckd_result check = guf_ckd_add_u16(a, b); + if (result) { + *result = 1u * a + b; + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_sub_u16(uint16_t a, uint16_t b, uint16_t *result) +{ + const guf_math_ckd_result check = guf_ckd_sub_u16(a, b); + if (result) { + *result = 1u * a - b; + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_mul_u16(uint16_t a, uint16_t b, uint16_t *result) +{ + const guf_math_ckd_result check = guf_ckd_mul_u16(a, b); + if (result) { + *result = 1u * a * b; + } + return check; +} +#endif + +#ifdef UINT32_MAX +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_add_u32(uint32_t a, uint32_t b, uint32_t *result) +{ + const guf_math_ckd_result check = guf_ckd_add_u32(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 = UINT32_MAX; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_sub_u32(uint32_t a, uint32_t b, uint32_t *result) +{ + const guf_math_ckd_result check = guf_ckd_sub_u32(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_u32(uint32_t a, uint32_t b, uint32_t *result) +{ + const guf_math_ckd_result check = guf_ckd_mul_u32(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 = UINT32_MAX; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_add_u32(uint32_t a, uint32_t b, uint32_t *result) +{ + const guf_math_ckd_result check = guf_ckd_add_u32(a, b); + if (result) { + *result = 1u * a + b; + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_sub_u32(uint32_t a, uint32_t b, uint32_t *result) +{ + const guf_math_ckd_result check = guf_ckd_sub_u32(a, b); + if (result) { + *result = 1u * a - b; + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_mul_u32(uint32_t a, uint32_t b, uint32_t *result) +{ + const guf_math_ckd_result check = guf_ckd_mul_u32(a, b); + if (result) { + *result = 1u * a * b; + } + return check; +} +#endif + +#ifdef UINT64_MAX +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_add_u64(uint64_t a, uint64_t b, uint64_t *result) +{ + const guf_math_ckd_result check = guf_ckd_add_u64(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 = UINT64_MAX; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_saturating_sub_u64(uint64_t a, uint64_t b, uint64_t *result) +{ + const guf_math_ckd_result check = guf_ckd_sub_u64(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_u64(uint64_t a, uint64_t b, uint64_t *result) +{ + const guf_math_ckd_result check = guf_ckd_mul_u64(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 = UINT64_MAX; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} + +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_add_u64(uint64_t a, uint64_t b, uint64_t *result) +{ + const guf_math_ckd_result check = guf_ckd_add_u64(a, b); + if (result) { + *result = 1u * a + b; + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_sub_u64(uint64_t a, uint64_t b, uint64_t *result) +{ + const guf_math_ckd_result check = guf_ckd_sub_u64(a, b); + if (result) { + *result = 1u * a - b; + } + return check; +} +GUF_MATH_CKDINT_KWRDS guf_math_ckd_result guf_wrapping_mul_u64(uint64_t a, uint64_t b, uint64_t *result) +{ + const guf_math_ckd_result check = guf_ckd_mul_u64(a, b); + if (result) { + *result = 1u * a * b; + } + return check; +} +#endif + + +#endif /* End impl */ + +#endif /* End header-guard */ + +#undef GUF_MATH_CKDINT_KWRDS +#undef GUF_MATH_CKDINT_IMPL +#undef GUF_MATH_CKDINT_IMPL_STATIC diff --git a/libguf/src/guf_rand.h b/libguf/src/guf_rand.h new file mode 100755 index 0000000..867a4a4 --- /dev/null +++ b/libguf/src/guf_rand.h @@ -0,0 +1,1007 @@ +/* + is parametrized: yes +*/ + +#if defined(GUF_RAND_IMPL_STATIC) + #define GUF_RAND_KWRDS static +#else + #define GUF_RAND_KWRDS +#endif + +#ifndef GUF_RAND_H +#define GUF_RAND_H +#include "guf_common.h" + +/* + - guf_rand32 functions use the xoshiro128** 1.1 generator [1] (rng-state of 4 32-bit integers, i.e. 128 bits) + - guf_rand64 functions use the xoshiro256** 1.0 generator [2] (rng-state of 4 64-bit integers, i.e. 256 bits) + - guf_rand functions use either guf_rand32 or guf_rand64 depending on GUF_RAND_32_BIT (which is set globally in guf_common.h depending on the platform's word-size) + + [1] xoshiro128** 1.1 (public domain) written in 2018 by David Blackman and Sebastiano Vigna (vigna@acm.org) + cf. https://prng.di.unimi.it/xoshiro128starstar.c (last-retrieved 2025-02-11) + + [2] xoshiro256** 1.0 (public domain) written in 2018 by David Blackman and Sebastiano Vigna (vigna@acm.org) + cf. https://prng.di.unimi.it/xoshiro256starstar.c (last-retrieved 2025-02-11) +*/ + +// State for xoshiro128** 1.1 +typedef struct guf_rand32_state { + uint_least32_t s[4]; // Must not be all zero. +} guf_rand32_state; + +// State for xoshiro256** 1.0 +typedef struct guf_rand64_state { + uint_least64_t s[4]; // Must not be all zero. +} guf_rand64_state; + +#ifdef GUF_RAND_32_BIT + // Use guf_rand32_state (i.e. xoshiro128** 1.1) as default. + #define GUF_RAND_MAX GUF_UINT32_MAX + typedef guf_rand32_state guf_randstate; + typedef uint_least32_t guf_rand_seed_t; +#else + // Use guf_rand64_state (i.e. xoshiro256** 1.0) as default. + #define GUF_RAND_MAX GUF_UINT64_MAX + typedef guf_rand64_state guf_randstate; + typedef uint_least64_t guf_rand_seed_t; +#endif + +/* + - guf_randstate_init(state, seed) -> void + Initialise the rng-state from a single 64-bit (or 32-bit) seed. + The seed is scrambled by guf_rand_splitmix64/32 internally. + Non-permissible states, i.e. states where all four state-integers turn out to be zero, + will be automatically corrected, which means all seeds passed to guf_randstate_init are permissible. + (If you want to initialise the guf_randstate struct manually, you have to ensure yourself the four state-integers aren't all zero.) +*/ +GUF_RAND_KWRDS void guf_randstate_init(guf_randstate *state, guf_rand_seed_t seed); +GUF_RAND_KWRDS void guf_rand64_state_init(guf_rand64_state *state, uint_least64_t seed); +GUF_RAND_KWRDS void guf_rand32_state_init(guf_rand32_state *state, uint_least32_t seed); + +/* + - guf_randstate_jump(state) + -> void; advance the rng-state as if 2^128 (or 2^64 for rand32_state) calls to guf_rand_u32 had occured. + Can be used to generate 2^128 (or 2^64 for rand32_state) non-overlapping subsequences for parallel computations. +*/ +GUF_RAND_KWRDS void guf_randstate_jump(guf_randstate *state); // Equivalent to 2^128 (or 2^64) calls to guf_rand_u32/u64 +GUF_RAND_KWRDS void guf_rand64_state_jump(guf_rand64_state *state); // Equivalent to 2^128 calls to guf_rand64_u64 +GUF_RAND_KWRDS void guf_rand32_state_jump(guf_rand32_state *state); // Equivalent to 2^64 calls to guf_rand32_u32 + + +// Uniform distributions in default ranges: + +/* + - guf_rand_splitmix64(state) -> u64 in range [0, UINT64_MAX] + (Very simple rng with only 64-bits of state; used for "scrambling" 64-bit seeds in guf_randstate_init.) + - guf_rand_splitmix32(state) -> u32 in range [0, UINT32_MAX] + (Very simple rng with only 32-bits of state; used for "scrambling" 32-bit seeds in guf_randstate_init.) +*/ +GUF_RAND_KWRDS uint_least64_t guf_rand_splitmix64(uint_least64_t *state); +GUF_RAND_KWRDS uint_least32_t guf_rand_splitmix32(uint_least32_t *state); + +/* + - guf_rand_u32(state) -> u32 in range [0, UINT32_MAX] +*/ +GUF_RAND_KWRDS uint_least32_t guf_rand_u32(guf_randstate *state); +GUF_RAND_KWRDS uint_least32_t guf_rand64_u32(guf_rand64_state *state); +GUF_RAND_KWRDS uint_least32_t guf_rand32_u32(guf_rand32_state *state); + +/* + - guf_rand_u64(state) -> uint64_t (or uint_least64_t) in range [0, UINT64_MAX] + NOTE: May be slow on 32-bit platforms. + NOTE: If uint64_t is not available (optional according to the standards), use uint_least64_t (always available in C99 and above). +*/ +GUF_RAND_KWRDS uint_least64_t guf_rand_u64(guf_randstate *state); +GUF_RAND_KWRDS uint_least64_t guf_rand32_u64(guf_rand32_state *state); +GUF_RAND_KWRDS uint_least64_t guf_rand64_u64(guf_rand64_state *state); + +/* + - guf_rand_f64(state) -> double in range [0.0, 1.0) + NOTE: May be slow on 32-bit platforms (as it calls guf_rand_u64) +*/ +GUF_RAND_KWRDS double guf_rand_f64(guf_randstate *state); +GUF_RAND_KWRDS double guf_rand64_f64(guf_rand64_state *state); +GUF_RAND_KWRDS double guf_rand32_f64(guf_rand32_state *state); + +/* + - guf_rand_f32(state) -> float in range [0.f, 1.f) +*/ +GUF_RAND_KWRDS float guf_rand_f32(guf_randstate *state); +GUF_RAND_KWRDS float guf_rand64_f32(guf_rand64_state *state); +GUF_RAND_KWRDS float guf_rand32_f32(guf_rand32_state *state); + + +// Uniform distributions in custom ranges: + +/* + - guf_randrange_f32(state, min, end) -> float in range [min, end) (contrary to the integer equivalents, end is *not* inclusive) + - guf_randrange_f64(state, min, end) -> double in range [min, end) (contrary to the integer equivalents, end is *not* inclusive) + NOTE: f64 versions may be slow on 32-bit platforms. +*/ +GUF_RAND_KWRDS float guf_randrange_f32(guf_randstate *state, float min, float end); +GUF_RAND_KWRDS double guf_randrange_f64(guf_randstate *state, double min, double end); + +GUF_RAND_KWRDS float guf_rand64_range_f32(guf_rand64_state *state, float min, float end); +GUF_RAND_KWRDS double guf_rand64_range_f64(guf_rand64_state *state, double min, double end); + +GUF_RAND_KWRDS float guf_rand32_range_f32(guf_rand32_state *state, float min, float end); +GUF_RAND_KWRDS double guf_rand32_range_f64(guf_rand32_state *state, double min, double end); + +/* + - guf_randrange_i32(state, min, max) -> i32 in range [min, max] (contrary to the float equivalents, max *is* inclusive) + - guf_randrange_u32(state, min, max) -> u32 in range [min, max] (contrary to the float equivalents, max *is* inclusive) + NOTE: guf_randrange_u32 may be slow on 32-bit platforms (as it calls guf_rand_f64). + This does not apply to guf_randrange_i32 (as it doesn't call guf_rand_f64). +*/ +GUF_RAND_KWRDS int_least32_t guf_randrange_i32(guf_randstate *state, int_least32_t min, int_least32_t max); +GUF_RAND_KWRDS uint_least32_t guf_randrange_u32(guf_randstate *state, uint_least32_t min, uint_least32_t max); // NOTE: may be slow on 32-bit platforms (as it calls guf_rand_f64). + +GUF_RAND_KWRDS int_least32_t guf_rand64_range_i32(guf_rand64_state *state, int_least32_t min, int_least32_t max); +GUF_RAND_KWRDS uint_least32_t guf_rand64_range_u32(guf_rand64_state *state, uint_least32_t min, uint_least32_t max); + +GUF_RAND_KWRDS int_least32_t guf_rand32_range_i32(guf_rand32_state *state, int_least32_t min, int_least32_t max); +GUF_RAND_KWRDS uint_least32_t guf_rand32_range_u32(guf_rand32_state *state, uint_least32_t min, uint_least32_t max); // NOTE: may be slow on 32-bit platforms (as it calls guf_rand_f64). + +/* + - guf_randrange_i64(state, min, max) -> int64_t in range [min, max] (contrary to the float equivalents, max *is* inclusive) +*/ +GUF_RAND_KWRDS int_least64_t guf_randrange_i64(guf_randstate *state, int_least64_t min, int_least64_t max); +GUF_RAND_KWRDS int_least64_t guf_rand64_range_i64(guf_rand64_state *state, int_least64_t min, int_least64_t max); +GUF_RAND_KWRDS int_least64_t guf_rand32_range_i64(guf_rand32_state *state, int_least64_t min, int_least64_t max); + +// Bernoulli-trials: + +/* + - guf_rand_bernoulli_trial(state, p) -> return true with a probability of p, false with a probability of (1 - p) + NOTE: p will be clamped to be in range [0.0, 1.0] + NOTE: The f64 versions may be slow on 32-bit platforms. + - guf_rand_flip(state) -> return true with a probability of 50 %, i.e. fair coin flip (bernoulli trial with p == 0.5) +*/ +GUF_RAND_KWRDS bool guf_rand_bernoulli_trial_f32(guf_randstate *state, float p); +GUF_RAND_KWRDS bool guf_rand_bernoulli_trial_f64(guf_randstate *state, double p); +GUF_RAND_KWRDS bool guf_rand_flip(guf_randstate *state); + +GUF_RAND_KWRDS bool guf_rand64_bernoulli_trial_f32(guf_rand64_state *state, float p); +GUF_RAND_KWRDS bool guf_rand64_bernoulli_trial_f64(guf_rand64_state *state, double p); +GUF_RAND_KWRDS bool guf_rand64_flip(guf_rand64_state *state); + +GUF_RAND_KWRDS bool guf_rand32_bernoulli_trial_f32(guf_rand32_state *state, float p); +GUF_RAND_KWRDS bool guf_rand32_bernoulli_trial_f64(guf_rand32_state *state, double p); +GUF_RAND_KWRDS bool guf_rand32_flip(guf_rand32_state *state); + +// Normal distributions: + +/* + - guf_rand_normal_sample_f32/f64(state, mean, std_dev, result, n) + -> void; put n float/double samples following the given normal-distribution into result (result is allocated by the caller and must have enough space to hold at least n samples) + - guf_rand_normal_sample_one_f32/f64(state, mean, std_dev) + -> return one float/double sample following the given normal-distribution + - NOTE: the f64 versions may be slow on 32-bit platforms. +*/ +GUF_RAND_KWRDS void guf_rand_normal_sample_f64(guf_randstate *state, double mean, double std_dev, double *result, ptrdiff_t n); +GUF_RAND_KWRDS void guf_rand_normal_sample_f32(guf_randstate *state, float mean, float std_dev, float *result, ptrdiff_t n); +GUF_RAND_KWRDS double guf_rand_normal_sample_one_f64(guf_randstate *state, double mean, double std_dev); +GUF_RAND_KWRDS float guf_rand_normal_sample_one_f32(guf_randstate *state, float mean, float std_dev); + +GUF_RAND_KWRDS void guf_rand64_normal_sample_f32(guf_rand64_state *state, float mean, float std_dev, float *result, ptrdiff_t n); +GUF_RAND_KWRDS void guf_rand64_normal_sample_f64(guf_rand64_state *state, double mean, double std_dev, double *result, ptrdiff_t n); +GUF_RAND_KWRDS float guf_rand64_normal_sample_one_f32(guf_rand64_state *state, float mean, float std_dev); +GUF_RAND_KWRDS double guf_rand64_normal_sample_one_f64(guf_rand64_state *state, double mean, double std_dev); + +GUF_RAND_KWRDS void guf_rand32_normal_sample_f32(guf_rand32_state *state, float mean, float std_dev, float *result, ptrdiff_t n); +GUF_RAND_KWRDS void guf_rand32_normal_sample_f64(guf_rand32_state *state, double mean, double std_dev, double *result, ptrdiff_t n); +GUF_RAND_KWRDS float guf_rand32_normal_sample_one_f32(guf_rand32_state *state, float mean, float std_dev); +GUF_RAND_KWRDS double guf_rand32_normal_sample_one_f64(guf_rand32_state *state, double mean, double std_dev); + +#endif + +// #define GUF_RAND_IMPL_STATIC /* DEBUG */ + +#if defined(GUF_RAND_IMPL) || defined(GUF_RAND_IMPL_STATIC) +#include +#include +#include "guf_common.h" +#include "guf_assert.h" +#include "guf_math.h" +#define GUF_MATH_CKDINT_IMPL_STATIC +#include "guf_math_ckdint.h" + +/* + splitmix64 written in 2015 by Sebastiano Vigna (vigna@acm.org) (released as public domain) + cf. https://prng.di.unimi.it/splitmix64.c (last-retrieved 2025-02-11) +*/ +GUF_RAND_KWRDS uint_least64_t guf_rand_splitmix64(uint_least64_t *state) +{ + GUF_ASSERT(state); + *state = GUF_UWRAP_64(*state); + uint_least64_t z = ( *state = GUF_UWRAP_64(*state + 0x9e3779b97f4a7c15ull) ); + z = GUF_UWRAP_64( GUF_UWRAP_64(z ^ (z >> 30u)) * 0xbf58476d1ce4e5b9ull ); + z = GUF_UWRAP_64( GUF_UWRAP_64(z ^ (z >> 27u)) * 0x94d049bb133111ebull ); + return GUF_UWRAP_64(z ^ (z >> 31u)); +} + +/* + splitmix32 written in 2016 by Kaito Udagawa (released under CC0 ) + cf. https://github.com/umireon/my-random-stuff/blob/master/xorshift/splitmix32.c (last-retrieved 2025-03-28) +*/ +GUF_RAND_KWRDS uint_least32_t guf_rand_splitmix32(uint_least32_t *state) +{ + GUF_ASSERT(state); + uint_least32_t z = ( *state = GUF_UWRAP_32(*state + 0x9e3779b9ul) ); + z = GUF_UWRAP_32( GUF_UWRAP_32(z ^ (z >> 16u)) * 0x85ebca6bul ); + z = GUF_UWRAP_32( GUF_UWRAP_32(z ^ (z >> 13u)) * 0xc2b2ae35ul ); + return GUF_UWRAP_32(z ^ (z >> 16u)); +} + +GUF_RAND_KWRDS void guf_rand32_state_init(guf_rand32_state *state, uint_least32_t seed) +{ + for (size_t i = 0; i < GUF_ARR_SIZE(state->s); ++i) { + state->s[i] = guf_rand_splitmix32(&seed); + } + if (!state->s[0] && !state->s[1] && !state->s[2] && !state->s[3]) { // State must not be only zeroes: + state->s[0] = 0x9e3779b9; // arbitrary constant != 0 + seed = state->s[0]; + for (size_t i = 1; i < GUF_ARR_SIZE(state->s); ++i) { + state->s[i] = guf_rand_splitmix32(&seed); + } + } +} + +GUF_RAND_KWRDS void guf_rand64_state_init(guf_rand64_state *state, uint_least64_t seed) +{ + for (size_t i = 0; i < GUF_ARR_SIZE(state->s); ++i) { + state->s[i] = guf_rand_splitmix64(&seed); + } + if (!state->s[0] && !state->s[1] && !state->s[2] && !state->s[3]) { // State must not be only zeroes: + state->s[0] = 0x9e3779b97f4a7c15; // arbitrary constant != 0 + seed = state->s[0]; + for (size_t i = 1; i < GUF_ARR_SIZE(state->s); ++i) { + state->s[i] = guf_rand_splitmix64(&seed); + } + } +} + +GUF_RAND_KWRDS void guf_randstate_init(guf_randstate *state, guf_rand_seed_t seed) +{ + #ifdef GUF_RAND_32_BIT + guf_rand32_state_init(state, seed); + #else + guf_rand64_state_init(state, seed); + #endif +} + +GUF_RAND_KWRDS uint_least32_t guf_rand32_u32(guf_rand32_state *state) +{ + GUF_ASSERT(state); + GUF_ASSERT(state->s[0] || state->s[1] || state->s[2] || state->s[3]); + /* + xoshiro128** 1.1 (public domain) written in 2018 by David Blackman and Sebastiano Vigna (vigna@acm.org) + cf. https://prng.di.unimi.it/xoshiro128starstar.c (last-retrieved 2025-02-11) + */ + const uint_least32_t result = GUF_UWRAP_32( guf_rotl32_least_u32(state->s[1] * 5u, 7) * 9u ); + const uint_least32_t t = GUF_UWRAP_32( state->s[1] << 9u ); + + state->s[2] ^= state->s[0]; + state->s[3] ^= state->s[1]; + state->s[1] ^= state->s[2]; + state->s[0] ^= state->s[3]; + state->s[2] ^= t; + state->s[3] = guf_rotl32_least_u32(state->s[3], 11); + + state->s[0] = GUF_UWRAP_32(state->s[0]); + state->s[1] = GUF_UWRAP_32(state->s[1]); + state->s[2] = GUF_UWRAP_32(state->s[2]); + state->s[3] = GUF_UWRAP_32(state->s[3]); + + return result; +} + +GUF_RAND_KWRDS uint_least32_t guf_rand64_u32(guf_rand64_state *state) +{ + return (uint_least32_t)GUF_UWRAP_32( (guf_rand64_u64(state) >> 32u) ); +} + +GUF_RAND_KWRDS uint_least32_t guf_rand_u32(guf_randstate *state) +{ + GUF_ASSERT(state); + GUF_ASSERT(state->s[0] || state->s[1] || state->s[2] || state->s[3]); + #ifdef GUF_RAND_32_BIT + return guf_rand32_u32(state); + #else + return guf_rand64_u32(state); + #endif +} + + +GUF_RAND_KWRDS uint_least64_t guf_rand64_u64(guf_rand64_state *state) +{ + GUF_ASSERT(state); + GUF_ASSERT(state->s[0] || state->s[1] || state->s[2] || state->s[3]); + /* + xoshiro256** 1.0 (public domain) written in 2018 by David Blackman and Sebastiano Vigna (vigna@acm.org) + cf. https://prng.di.unimi.it/xoshiro256starstar.c (last-retrieved 2025-02-11) + */ + const uint_least64_t result = GUF_UWRAP_64( guf_rotl64_least_u64(state->s[1] * 5u, 7) * 9u ); + const uint_least64_t t = GUF_UWRAP_64( state->s[1] << 17u ); + + state->s[2] ^= state->s[0]; + state->s[3] ^= state->s[1]; + state->s[1] ^= state->s[2]; + state->s[0] ^= state->s[3]; + state->s[2] ^= t; + state->s[3] = guf_rotl64_least_u64(state->s[3], 45); + + state->s[0] = GUF_UWRAP_64(state->s[0]); + state->s[1] = GUF_UWRAP_64(state->s[1]); + state->s[2] = GUF_UWRAP_64(state->s[2]); + state->s[3] = GUF_UWRAP_64(state->s[3]); + + return result; +} + +GUF_RAND_KWRDS uint_least64_t guf_rand32_u64(guf_rand32_state *state) +{ + GUF_ASSERT(state); + GUF_ASSERT(state->s[0] || state->s[1] || state->s[2] || state->s[3]); + const uint_least32_t lower_bits = guf_rand32_u32(state); + const uint_least32_t upper_bits = guf_rand32_u32(state); + GUF_ASSERT( lower_bits <= GUF_UINT32_MAX && upper_bits <= GUF_UINT32_MAX ); + GUF_ASSERT( ( ((uint_least64_t)upper_bits << 32u) | (uint_least64_t)lower_bits ) <= GUF_UINT32_MAX); + return ((uint_least64_t)upper_bits << 32u) | (uint_least64_t)lower_bits; // TODO: not sure if that's a good idea... + +} + +GUF_RAND_KWRDS uint_least64_t guf_rand_u64(guf_randstate *state) +{ + #ifdef GUF_RAND_32_BIT + return guf_rand32_u64(state); + #else + return guf_rand64_u64(state); + #endif +} + +/* + Equivalent to 2^64 calls to guf_rand32_u32; can be used to generate 2^64 + non-overlapping subsequences for parallel computations. +*/ +GUF_RAND_KWRDS void guf_rand32_state_jump(guf_rand32_state *state) +{ + GUF_ASSERT(state); + static const uint_least32_t JUMP[] = { 0x8764000b, 0xf542d2d3, 0x6fa035c3, 0x77f2db5b }; + uint_least32_t s0 = 0; + uint_least32_t s1 = 0; + uint_least32_t s2 = 0; + uint_least32_t s3 = 0; + for (size_t i = 0; i < sizeof JUMP / sizeof *JUMP; ++i) { + for (int b = 0; b < 32; ++b) { + if (1u * JUMP[i] & UINT32_C(1) << b) { + s0 ^= state->s[0]; + s1 ^= state->s[1]; + s2 ^= state->s[2]; + s3 ^= state->s[3]; + s0 = GUF_UWRAP_32(s0); + s1 = GUF_UWRAP_32(s1); + s2 = GUF_UWRAP_32(s2); + s3 = GUF_UWRAP_32(s3); + } + guf_rand32_u32(state); + } + } + state->s[0] = GUF_UWRAP_32(s0); + state->s[1] = GUF_UWRAP_32(s1); + state->s[2] = GUF_UWRAP_32(s2); + state->s[3] = GUF_UWRAP_32(s3); +} + +/* + Equivalent to 2^128 calls to guf_rand64_u64(); can be used to generate 2^128 + non-overlapping subsequences for parallel computations. +*/ +GUF_RAND_KWRDS void guf_rand64_state_jump(guf_rand64_state *state) +{ + static const uint_least64_t JUMP[] = { 0x180ec6d33cfd0aba, 0xd5a61266f0c9392c, 0xa9582618e03fc9aa, 0x39abdc4529b1661c }; + uint_least64_t s0 = 0; + uint_least64_t s1 = 0; + uint_least64_t s2 = 0; + uint_least64_t s3 = 0; + for (size_t i = 0; i < sizeof JUMP / sizeof *JUMP; ++i) { + for (int b = 0; b < 64; ++b) { + if (1u * JUMP[i] & UINT64_C(1) << b) { + s0 ^= state->s[0]; + s1 ^= state->s[1]; + s2 ^= state->s[2]; + s3 ^= state->s[3]; + s0 = GUF_UWRAP_64(s0); + s1 = GUF_UWRAP_64(s1); + s2 = GUF_UWRAP_64(s2); + s3 = GUF_UWRAP_64(s3); + } + guf_rand64_u64(state); + } + } + state->s[0] = GUF_UWRAP_64(s0); + state->s[1] = GUF_UWRAP_64(s1); + state->s[2] = GUF_UWRAP_64(s2); + state->s[3] = GUF_UWRAP_64(s3); +} + +/* + Equivalent to 2^128 calls to guf_rand() (or 2^64 calls if GUF_RAND_32_BIT); it can be used to generate 2^128 (or 2^64) + non-overlapping subsequences for parallel computations. +*/ +GUF_RAND_KWRDS void guf_randstate_jump(guf_randstate *state) +{ + GUF_ASSERT(state); + #ifdef GUF_RAND_32_BIT + guf_rand32_state_jump(state); + #else + guf_rand64_state_jump(state); + #endif +} + + +GUF_RAND_KWRDS double guf_rand64_f64(guf_rand64_state *state) +{ + // cf. https://prng.di.unimi.it/ and https://dotat.at/@/2023-06-23-random-double.html (last-retrieved 2025-02-11) + return (guf_rand64_u64(state) >> 11u) * 0x1.0p-53; // 11 == 64 - 53 (double has a 53-bit mantissa/significand) +} + +GUF_RAND_KWRDS double guf_rand32_f64(guf_rand32_state *state) +{ + // cf. https://prng.di.unimi.it/ and https://dotat.at/@/2023-06-23-random-double.html (last-retrieved 2025-02-11) + return (guf_rand32_u64(state) >> 11u) * 0x1.0p-53; // 11 == 64 - 53 (double has a 53-bit mantissa/significand) +} + +// Generate double in the unit interval [0, 1) +GUF_RAND_KWRDS double guf_rand_f64(guf_randstate *state) +{ + // cf. https://prng.di.unimi.it/ and https://dotat.at/@/2023-06-23-random-double.html (last-retrieved 2025-02-11) + return (guf_rand_u64(state) >> 11u) * 0x1.0p-53; // 11 == 64 - 53 (double has a 53-bit mantissa/significand) +} + + +GUF_RAND_KWRDS float guf_rand64_f32(guf_rand64_state *state) +{ + return (guf_rand64_u64(state) >> 40u) * 0x1.0p-24f; // 40 == 64 - 24; (float has a 24-bit mantissa/significand) +} + +GUF_RAND_KWRDS float guf_rand32_f32(guf_rand32_state *state) +{ + return (guf_rand32_u32(state) >> 8u) * 0x1.0p-24f; // 8 == 32 - 24; (float has a 24-bit mantissa/significand) +} + +// Generate float in the unit interval [0, 1) +GUF_RAND_KWRDS float guf_rand_f32(guf_randstate *state) +{ + #ifdef GUF_RAND_32_BIT + return guf_rand32_f32(state); + #else + return guf_rand64_f32(state); + #endif +} + + +GUF_RAND_KWRDS bool guf_rand32_bernoulli_trial_f32(guf_rand32_state *state, float p) +{ + p = guf_clamp_f32(p, 0, 1); + return guf_rand32_f32(state) < p; // never true for p = 0, always true for p = 1 since guf_rand_f64 is in range [0, 1) +} + +GUF_RAND_KWRDS bool guf_rand64_bernoulli_trial_f32(guf_rand64_state *state, float p) +{ + p = guf_clamp_f32(p, 0, 1); + return guf_rand64_f32(state) < p; // never true for p = 0, always true for p = 1 since guf_rand_f64 is in range [0, 1) +} + +GUF_RAND_KWRDS bool guf_rand_bernoulli_trial_f32(guf_randstate *state, float p) +{ + p = guf_clamp_f32(p, 0, 1); + return guf_rand_f32(state) < p; // never true for p = 0, always true for p = 1 since guf_rand_f64 is in range [0, 1) +} + +GUF_RAND_KWRDS bool guf_rand32_bernoulli_trial_f64(guf_rand32_state *state, double p) +{ + p = guf_clamp_f64(p, 0, 1); + return guf_rand32_f64(state) < p; // never true for p = 0, always true for p = 1 since guf_rand_f64 is in range [0, 1) +} + +GUF_RAND_KWRDS bool guf_rand64_bernoulli_trial_f64(guf_rand64_state *state, double p) +{ + p = guf_clamp_f64(p, 0, 1); + return guf_rand64_f64(state) < p; // never true for p = 0, always true for p = 1 since guf_rand_f64 is in range [0, 1) +} + +GUF_RAND_KWRDS bool guf_rand_bernoulli_trial_f64(guf_randstate *state, double p) +{ + p = guf_clamp_f64(p, 0, 1); + return guf_rand_f64(state) < p; // never true for p = 0, always true for p = 1 since guf_rand_f64 is in range [0, 1) +} + + +GUF_RAND_KWRDS bool guf_rand32_flip(guf_rand32_state *state) +{ + return guf_rand32_bernoulli_trial_f32(state, 0.5f); +} + +GUF_RAND_KWRDS bool guf_rand64_flip(guf_rand64_state *state) +{ + return guf_rand64_bernoulli_trial_f64(state, 0.5); +} + +GUF_RAND_KWRDS bool guf_rand_flip(guf_randstate *state) +{ + #ifdef GUF_RAND_32_BIT + return guf_rand_bernoulli_trial_f32(state, 0.5f); + #else + return guf_rand_bernoulli_trial_f64(state, 0.5); + #endif +} + + +GUF_RAND_KWRDS double guf_rand32_range_f64(guf_rand32_state *state, double min, double end) +{ + if (min == (double)INFINITY) { + min = DBL_MAX; + } else if (min == (double)-INFINITY) { + min = -DBL_MAX; + } + if (end == (double)INFINITY) { + end = DBL_MAX; + } else if (end == (double)-INFINITY) { + end = -DBL_MAX; + } + GUF_ASSERT_RELEASE(end >= min); + return guf_rand32_f64(state) * (end - min) + min; +} + +GUF_RAND_KWRDS double guf_rand64_range_f64(guf_rand64_state *state, double min, double end) +{ + if (min == (double)INFINITY) { + min = DBL_MAX; + } else if (min == (double)-INFINITY) { + min = -DBL_MAX; + } + if (end == (double)INFINITY) { + end = DBL_MAX; + } else if (end == (double)-INFINITY) { + end = -DBL_MAX; + } + GUF_ASSERT_RELEASE(end >= min); + return guf_rand64_f64(state) * (end - min) + min; +} + +// returns uniformly-distributed random double in range [min, end) (or min if min == end) +GUF_RAND_KWRDS double guf_randrange_f64(guf_randstate *state, double min, double end) +{ + #ifdef GUF_RAND_32_BIT + return guf_rand32_range_f64(state, min, end); + #else + return guf_rand64_range_f64(state, min, end); + #endif +} + + +// returns uniformly-distributed random float in range [min, end) (or min if min == end) +GUF_RAND_KWRDS float guf_rand32_range_f32(guf_rand32_state *state, float min, float end) +{ + if (min == INFINITY) { + min = FLT_MAX; + } else if (min == -INFINITY) { + min = -FLT_MAX; + } + if (end == INFINITY) { + end = FLT_MAX; + } else if (end == -INFINITY) { + end = -FLT_MAX; + } + GUF_ASSERT_RELEASE(end >= min); + return guf_rand32_f32(state) * (end - min) + min; +} + +// returns uniformly-distributed random float in range [min, end) (or min if min == end) +GUF_RAND_KWRDS float guf_rand64_range_f32(guf_rand64_state *state, float min, float end) +{ + if (min == INFINITY) { + min = FLT_MAX; + } else if (min == -INFINITY) { + min = -FLT_MAX; + } + if (end == INFINITY) { + end = FLT_MAX; + } else if (end == -INFINITY) { + end = -FLT_MAX; + } + GUF_ASSERT_RELEASE(end >= min); + return guf_rand64_f32(state) * (end - min) + min; +} + +// returns uniformly-distributed random float in range [min, end) (or min if min == end) +GUF_RAND_KWRDS float guf_randrange_f32(guf_randstate *state, float min, float end) +{ + #ifdef GUF_RAND_32_BIT + return guf_rand32_range_f32(state, min, end); + #else + return guf_rand64_range_f32(state, min, end); + #endif +} + + +// returns uniformly-distributed random i32 in range [min, max] (max is inclusive as opposed to the f32/f64 versions) +GUF_RAND_KWRDS int_least32_t guf_rand64_range_i32(guf_rand64_state *state, int_least32_t min, int_least32_t max) +{ + GUF_ASSERT_RELEASE(max >= min); + if (min == max) { + return min; + } + const double delta = (double)max - (double)min; + // cf. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random (last-retrieved 2025-02-12) + const double result = floor(guf_rand64_f64(state) * (delta + 1.0) + min); + GUF_ASSERT(result >= min && result <= max); + GUF_ASSERT((int_least32_t)result <= GUF_INT32_MAX && (int_least32_t)result >= GUF_INT32_MIN); + return (int_least32_t)result; +} + +// returns uniformly-distributed random i32 in range [min, max] (max is inclusive as opposed to the f32/f64 versions) +GUF_RAND_KWRDS int_least32_t guf_rand32_range_i32(guf_rand32_state *state, int_least32_t min, int_least32_t max) +{ + GUF_ASSERT_RELEASE(max >= min); + if (min == max) { + return min; + } + + const uint_least32_t rand_max_i32 = GUF_UWRAP_32(GUF_UINT32_MAX >> 1u); // 2^31 - 1 (== INT32_MAX) + + const uint_least32_t delta = guf_absdiff_least_i32(max, min); + if (delta > rand_max_i32) { + guf_panic(GUF_ERR_INT_OVERFLOW, GUF_ERR_MSG("in function guf_rand32_range_i32: interval [min, max] larger than 2^31 - 1")); + return -1; + } + /* + We don't use the same approach as in guf_rand64_range_i32 because a 32-bit float only has a 24-bit mantissa + (and using double like in guf_rand64_range_i32 would require 64-bit arithmetic due to guf_rand32_f64). + + cf. https://c-faq.com/lib/randrange.html (last-retrieved 2025-02-11) + https://stackoverflow.com/a/6852396 (last-retrieved 2025-02-11) + */ + const uint_least32_t num_rand_vals = GUF_UWRAP_32(rand_max_i32 + 1u); // 2^31 + const uint_least32_t num_bins = GUF_UWRAP_32(delta + 1u); + + const uint_least32_t bin_size = GUF_UWRAP_32(num_rand_vals / num_bins); // bin_size = floor(num_rand_vals / num_bins) + const uint_least32_t limit = GUF_UWRAP_32(num_rand_vals - (num_rand_vals % num_bins)); // limit == bin_size * num_bins + GUF_ASSERT(limit == 1u * GUF_UWRAP_32(bin_size * num_bins)); + /* + since (num_rand_vals % num_bins) is at most 2^30 + 1 (I think...), the minimum limit is 2^31 - (2^30 + 1), + which means in the worst case, the chance of having to iterate (i.e. step >= limit) + is 1 - (2^31 - (2^30 + 1)) / 2^31 == 0.5 + */ + uint_least32_t step; + do { + step = GUF_UWRAP_32(guf_rand32_u32(state) >> 1u); // [0, 2^31 - 1] + } while (step >= limit); + step = GUF_UWRAP_32(step / bin_size); + + GUF_ASSERT(guf_ckd_add_least_i32(min, step) == GUF_MATH_CKD_SUCCESS); + + const int_least32_t rnd = min + (int_least32_t)step; + GUF_ASSERT(rnd >= min && rnd <= max); + GUF_ASSERT(rnd <= GUF_INT32_MAX && rnd >= GUF_INT32_MIN); + + return rnd; +} + +// returns uniformly-distributed random i32 in range [min, max] (max is inclusive as opposed to the f32/f64 versions) +GUF_RAND_KWRDS int_least32_t guf_randrange_i32(guf_randstate *state, int_least32_t min, int_least32_t max) +{ + #ifdef GUF_RAND_32_BIT + return guf_rand32_range_i32(state, min, max); + #else + return guf_rand64_range_i32(state, min, max); + #endif +} + +GUF_RAND_KWRDS uint_least32_t guf_rand32_range_u32(guf_rand32_state *state, uint_least32_t min, uint_least32_t max) +{ + /* + The method used in guf_rand32_range_i32 above (which uses only 32-bit integer arithmetic) could overflow here, + so we use the floating-point method since we have to use 64-bit arithmetic anyways. + */ + min = GUF_UWRAP_32(min); + max = GUF_UWRAP_32(max); + + GUF_ASSERT_RELEASE(max >= min); + + if (min == max) { + return min; + } + const double delta = (double)max - (double)min; + const double result = floor(guf_rand32_f64(state) * (delta + 1.0) + min); // NOTE: guf_rand32_f64 is slow for 32-bit platforms... + GUF_ASSERT(result >= min && result <= max); + GUF_ASSERT((uint_least32_t)result <= GUF_UINT32_MAX); + return (uint_least32_t)result; +} + +GUF_RAND_KWRDS uint_least32_t guf_rand64_range_u32(guf_rand64_state *state, uint_least32_t min, uint_least32_t max) +{ + min = GUF_UWRAP_32(min); + max = GUF_UWRAP_32(max); + + GUF_ASSERT_RELEASE(max >= min); + + if (min == max) { + return min; + } + const double delta = (double)max - (double)min; + const double result = floor(guf_rand64_f64(state) * (delta + 1.0) + min); + GUF_ASSERT(result >= min && result <= max); + GUF_ASSERT((uint_least32_t)result <= GUF_UINT32_MAX); + return (uint_least32_t)result; +} + +GUF_RAND_KWRDS uint_least32_t guf_randrange_u32(guf_randstate *state, uint_least32_t min, uint_least32_t max) +{ + #ifdef GUF_RAND_32_BIT + return guf_rand32_range_u32(state, min, max); + #else + return guf_rand64_range_u32(state, min, max); + #endif +} + +GUF_RAND_KWRDS int_least64_t guf_rand64_range_i64(guf_rand64_state *state, int_least64_t min, int_least64_t max) +{ + GUF_ASSERT_RELEASE(max >= min); + if (min == max) { + return min; + } + + const uint_least64_t rand_max_i64 = GUF_UWRAP_64(GUF_UINT64_MAX >> 1u); // 2^63 - 1 (== INT64_MAX) + + const uint_least64_t delta = guf_absdiff_least_i64(max, min); + if (delta > rand_max_i64) { + guf_panic(GUF_ERR_INT_OVERFLOW, GUF_ERR_MSG("in function guf_randrange_i64: interval [min, max] larger than 2^63 - 1")); + return -1; + } + /* + We should not use the same approach as in guf_rand64_range_i32 because (max - min) might be close to 2^63 - 1 + + cf. https://c-faq.com/lib/randrange.html (last-retrieved 2025-02-11) + https://stackoverflow.com/a/6852396 (last-retrieved 2025-02-11) + */ + const uint_least64_t num_rand_vals = GUF_UWRAP_64(rand_max_i64 + 1u); // 2^63 + const uint_least64_t num_bins = GUF_UWRAP_64(delta + 1u); + + const uint_least64_t bin_size = GUF_UWRAP_64(num_rand_vals / num_bins); // bin_size = floor(num_rand_vals / num_bins) + const uint_least64_t limit = GUF_UWRAP_64(num_rand_vals - (num_rand_vals % num_bins)); // limit == bin_size * num_bins + GUF_ASSERT(limit == 1u * GUF_UWRAP_64(bin_size * num_bins)); + /* + since (num_rand_vals % num_bins) is at most 2^62 + 1 (I think...), the minimum limit is 2^63 - (2^62 + 1), + which means in the worst case, the chance of having to iterate (i.e. step >= limit) + is 1 - (2^63 - (2^62 + 1)) / 2^63 == 0.5 + */ + uint_least64_t step; + do { + step = GUF_UWRAP_64(guf_rand64_u64(state) >> 1); // [0, 2^63 - 1] + } while (step >= limit); + step = GUF_UWRAP_64(step / bin_size); + + GUF_ASSERT(guf_ckd_add_least_i64(min, step) == GUF_MATH_CKD_SUCCESS); + + const int_least64_t rnd = min + (int_least64_t)step; + GUF_ASSERT(rnd >= min && rnd <= max); + return rnd; +} + +GUF_RAND_KWRDS int_least64_t guf_rand32_range_i64(guf_rand32_state *state, int_least64_t min, int_least64_t max) +{ + GUF_ASSERT_RELEASE(max >= min); + if (min == max) { + return min; + } + + const uint_least64_t rand_max_i64 = GUF_UWRAP_64(GUF_UINT64_MAX >> 1u); // 2^63 - 1 (== INT64_MAX) + + const uint_least64_t delta = guf_absdiff_least_i64(max, min); + if (delta > rand_max_i64) { + guf_panic(GUF_ERR_INT_OVERFLOW, GUF_ERR_MSG("in function guf_randrange_i64: interval [min, max] larger than 2^63 - 1")); + return -1; + } + /* + We should not use the same approach as in guf_rand64_range_i32 because (max - min) might be close to 2^63 - 1 + + cf. https://c-faq.com/lib/randrange.html (last-retrieved 2025-02-11) + https://stackoverflow.com/a/6852396 (last-retrieved 2025-02-11) + */ + const uint_least64_t num_rand_vals = GUF_UWRAP_64(rand_max_i64 + 1u); // 2^63 + const uint_least64_t num_bins = GUF_UWRAP_64(delta + 1u); + + const uint_least64_t bin_size = GUF_UWRAP_64(num_rand_vals / num_bins); // bin_size = floor(num_rand_vals / num_bins) + const uint_least64_t limit = GUF_UWRAP_64(num_rand_vals - (num_rand_vals % num_bins)); // limit == bin_size * num_bins + GUF_ASSERT(limit == 1u * GUF_UWRAP_64(bin_size * num_bins)); + /* + since (num_rand_vals % num_bins) is at most 2^62 + 1 (I think...), the minimum limit is 2^63 - (2^62 + 1), + which means in the worst case, the chance of having to iterate (i.e. step >= limit) + is 1 - (2^63 - (2^62 + 1)) / 2^63 == 0.5 + */ + uint_least64_t step; + do { + step = GUF_UWRAP_64(guf_rand32_u64(state) >> 1); // [0, 2^63 - 1] + } while (step >= limit); + step = GUF_UWRAP_64(step / bin_size); + + GUF_ASSERT(guf_ckd_add_least_i64(min, step) == GUF_MATH_CKD_SUCCESS); + + const int_least64_t rnd = min + (int_least64_t)step; + GUF_ASSERT(rnd >= min && rnd <= max); + return rnd; +} + +// returns uniformly-distributed random int64_t in range [min, max] (max is inclusive as opposed to the f32/f64 versions) +GUF_RAND_KWRDS int_least64_t guf_randrange_i64(guf_randstate *state, int_least64_t min, int_least64_t max) +{ + #ifdef GUF_RAND_32_BIT + return guf_rand32_range_i64(state, min, max); + #else + return guf_rand64_range_i64(state, min, max); + #endif +} + + +// Box-Müller-transform transcribed from wikipedia, cf. https://en.wikipedia.org/wiki/Box%E2%80%93Muller_transform (last-retrieved 2025-02-12) +GUF_RAND_KWRDS void guf_rand32_normal_sample_f64(guf_rand32_state *state, double mean, double std_dev, double *result, ptrdiff_t n) +{ + GUF_ASSERT_RELEASE(result); + GUF_ASSERT_RELEASE(n >= 0); + const double TAU = 2.0 * GUF_PI; + + ptrdiff_t i = 0; + while (i < n) { + double u1, u2; + do { + u1 = guf_rand32_f64(state); + } while (u1 == 0); + u2 = guf_rand32_f64(state); + + const double mag = std_dev * sqrt(-2.0 * log(u1)); + result[i++] = mag * cos(TAU * u2) + mean; + if (i < n) { + result[i++] = mag * sin(TAU * u2) + mean; + } + } +} + +// Box-Müller-transform transcribed from wikipedia, cf. https://en.wikipedia.org/wiki/Box%E2%80%93Muller_transform (last-retrieved 2025-02-12) +GUF_RAND_KWRDS void guf_rand64_normal_sample_f64(guf_rand64_state *state, double mean, double std_dev, double *result, ptrdiff_t n) +{ + GUF_ASSERT_RELEASE(result); + GUF_ASSERT_RELEASE(n >= 0); + const double TAU = 2.0 * GUF_PI; + + ptrdiff_t i = 0; + while (i < n) { + double u1, u2; + do { + u1 = guf_rand64_f64(state); + } while (u1 == 0); + u2 = guf_rand64_f64(state); + + const double mag = std_dev * sqrt(-2.0 * log(u1)); + result[i++] = mag * cos(TAU * u2) + mean; + if (i < n) { + result[i++] = mag * sin(TAU * u2) + mean; + } + } +} + +// Box-Müller-transform transcribed from wikipedia, cf. https://en.wikipedia.org/wiki/Box%E2%80%93Muller_transform (last-retrieved 2025-02-12) +GUF_RAND_KWRDS void guf_rand_normal_sample_f64(guf_randstate *state, double mean, double std_dev, double *result, ptrdiff_t n) +{ + #ifdef GUF_RAND_32_BIT + guf_rand32_normal_sample_f64(state, mean, std_dev, result, n); + #else + guf_rand64_normal_sample_f64(state, mean, std_dev, result, n); + #endif +} + +GUF_RAND_KWRDS void guf_rand32_normal_sample_f32(guf_rand32_state *state, float mean, float std_dev, float *result, ptrdiff_t n) +{ + GUF_ASSERT_RELEASE(result); + GUF_ASSERT_RELEASE(n >= 0); + const float TAU = 2.f * (float)GUF_PI; + + ptrdiff_t i = 0; + while (i < n) { + float u1, u2; + do { + u1 = guf_rand32_f32(state); + } while (u1 == 0); + u2 = guf_rand32_f32(state); + + const float mag = std_dev * sqrtf(-2.f * logf(u1)); + result[i++] = mag * cosf(TAU * u2) + mean; + if (i < n) { + result[i++] = mag * sinf(TAU * u2) + mean; + } + } +} + +GUF_RAND_KWRDS void guf_rand64_normal_sample_f32(guf_rand64_state *state, float mean, float std_dev, float *result, ptrdiff_t n) +{ + GUF_ASSERT_RELEASE(result); + GUF_ASSERT_RELEASE(n >= 0); + const float TAU = 2.f * (float)GUF_PI; + + ptrdiff_t i = 0; + while (i < n) { + float u1, u2; + do { + u1 = guf_rand64_f32(state); + } while (u1 == 0); + u2 = guf_rand64_f32(state); + + const float mag = std_dev * sqrtf(-2.f * logf(u1)); + result[i++] = mag * cosf(TAU * u2) + mean; + if (i < n) { + result[i++] = mag * sinf(TAU * u2) + mean; + } + } +} + +GUF_RAND_KWRDS void guf_rand_normal_sample_f32(guf_randstate *state, float mean, float std_dev, float *result, ptrdiff_t n) +{ + #ifdef GUF_RAND_32_BIT + guf_rand32_normal_sample_f32(state, mean, std_dev, result, n); + #else + guf_rand64_normal_sample_f32(state, mean, std_dev, result, n); + #endif +} + +GUF_RAND_KWRDS double guf_rand32_normal_sample_one_f64(guf_rand32_state *state, double mean, double std_dev) +{ + double result; + guf_rand32_normal_sample_f64(state, mean, std_dev, &result, 1); + return result; +} + +GUF_RAND_KWRDS double guf_rand64_normal_sample_one_f64(guf_rand64_state *state, double mean, double std_dev) +{ + double result; + guf_rand64_normal_sample_f64(state, mean, std_dev, &result, 1); + return result; +} + +GUF_RAND_KWRDS double guf_rand_normal_sample_one_f64(guf_randstate *state, double mean, double std_dev) +{ + #ifdef GUF_RAND_32_BIT + return guf_rand32_normal_sample_one_f64(state, mean, std_dev); + #else + return guf_rand64_normal_sample_one_f64(state, mean, std_dev); + #endif +} + +GUF_RAND_KWRDS float guf_rand32_normal_sample_one_f32(guf_rand32_state *state, float mean, float std_dev) +{ + float result; + guf_rand32_normal_sample_f32(state, mean, std_dev, &result, 1); + return result; +} + +GUF_RAND_KWRDS float guf_rand64_normal_sample_one_f32(guf_rand64_state *state, float mean, float std_dev) +{ + float result; + guf_rand64_normal_sample_f32(state, mean, std_dev, &result, 1); + return result; +} + +GUF_RAND_KWRDS float guf_rand_normal_sample_one_f32(guf_randstate *state, float mean, float std_dev) +{ + #ifdef GUF_RAND_32_BIT + return guf_rand32_normal_sample_one_f32(state, mean, std_dev); + #else + return guf_rand64_normal_sample_one_f32(state, mean, std_dev); + #endif +} + +#undef GUF_RAND_IMPL +#undef GUF_RAND_IMPL_STATIC +#endif /* end impl */ + +#undef GUF_RAND_KWRDS diff --git a/libguf/src/guf_sort.h b/libguf/src/guf_sort.h new file mode 100755 index 0000000..7582e47 --- /dev/null +++ b/libguf/src/guf_sort.h @@ -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 */ diff --git a/libguf/src/guf_str.h b/libguf/src/guf_str.h new file mode 100755 index 0000000..8bb9419 --- /dev/null +++ b/libguf/src/guf_str.h @@ -0,0 +1,1591 @@ +/* + is parametrized: no + NOTE: automatically includes/implements guf_utf8.h +*/ + +#if defined(GUF_STR_IMPL_STATIC) + #define GUF_STR_KWRDS static +#else + #define GUF_STR_KWRDS +#endif + +#ifndef GUF_STR_H +#define GUF_STR_H +#include "guf_common.h" +#include "guf_alloc.h" +#include "guf_str_view_type.h" +#include "guf_utf8.h" +#include "guf_hash.h" + +// cf. libc++ short-string optimisation: https://joellaity.com/2020/01/31/string.html (last-retrieved 2025-03-10) + +typedef struct guf_str_internal_long_ { + size_t capacity; // If long string: capacity's least significant bit always set to 1 (or its most significant bit for big-endian platforms); the actual capacity must be even + size_t size; + char *c_str; +} guf_str_internal_long_; + +#define GUF_STR_SSO_BUF_CAP (sizeof(guf_str_internal_long_) - sizeof(unsigned char)) /* 23 bytes on 64-bit platforms, 11 bytes on 32-bit platforms */ + +#if defined(GUF_STDC_AT_LEAST_C11) || defined(GUF_STDCPP_AT_LEAST_CPP11) + static_assert(GUF_STR_SSO_BUF_CAP > 0, "GUF_STR_SSO_BUF_CAP < 0 (this is very weird)"); // Basically cannot fail. + static_assert(GUF_STR_SSO_BUF_CAP < 0x80, "GUF_STR_SSO_BUF_CAP >= 128 (no support for platforms with wordsize >= 512-bits)"); // Could fail on hypothetical platforms with 512-bit wordsize (and above). +#endif + +typedef struct guf_str_internal_short_ { + unsigned char size; // size overlaps with the first byte of guf_str_internal_long_.capacity [1] + char c_str[GUF_STR_SSO_BUF_CAP]; +} guf_str_internal_short_; + +/* + [1] The first byte of guf_str_internal_long_.capacity is its least-significant-byte on little-endian + platforms, and its most-significant byte on big-endian platforms. +*/ + +typedef struct guf_str { + union { + guf_str_internal_long_ lng; + guf_str_internal_short_ shrt; + } data; // 24 bytes on 64-bit platforms, 12 bytes on 32-bit platforms. + guf_allocator *allocator; // Wasteful (8 bytes on 64-bit platforms...), but keeping this pointer also allows us to have "read-only strings" (a string is read-only if allocator == NULL) +} guf_str; // Total: 32 bytes on 64-bit platforms, 16 bytes on 32-bit platforms. + +typedef enum guf_str_tok_delim_opt { + GUF_STR_TOK_DELIM_OPT_MATCH_LONGEST = 0, + GUF_STR_TOK_DELIM_OPT_MATCH_SHORTEST, + GUF_STR_TOK_DELIM_OPT_MATCH_IN_ORDER, +} guf_str_tok_delim_opt; + +typedef struct guf_str_tok_state { + guf_str_view input; + guf_str_view cur_tok, cur_delim; + const guf_str_view *delims; + const ptrdiff_t delim_count; + ptrdiff_t num_toks_read, num_delims_read; + bool done; +} guf_str_tok_state; + +#define GUF_CSTR_TO_VIEW(CSTR) ((guf_str_view){.str = (CSTR), .len = (ptrdiff_t)strlen((CSTR))}) +#define GUF_CSTR_LIT_TO_VIEW(CSTR) ((guf_str_view){.str = (CSTR), .len = (ptrdiff_t)sizeof((CSTR)) - 1}) +#define GUF_STR_TO_VIEW(GUF_STR_PTR) ((guf_str_view){.str = guf_str_const_cstr((GUF_STR_PTR)), .len = (ptrdiff_t)guf_str_len((GUF_STR_PTR))}) +#define GUF_CSTR_TO_READONLY_STR(CSTR) ((guf_str){.allocator = NULL, .data.lng.c_str = (CSTR), .data.lng.size = strlen(CSTR) + 1, .data.lng.capacity = 0}) + +#define GUF_STR_UNINITIALISED (guf_str){.allocator = NULL, .data.lng.size = 0, .data.lng.capacity = 0, .data.lng.c_str = NULL} + +#ifdef __cplusplus + // Standard C++ does not have compound literals like C99... + #define GUF_CSTR_TO_VIEW_CPP(CSTR) guf_str_view {.str = (CSTR), .len = (ptrdiff_t)strlen(CSTR)} + #define GUF_CSTR_LIT_TO_VIEW_CPP(CSTR) guf_str_view {.str = (CSTR), .len = (ptrdiff_t)sizeof(CSTR) - 1} + + #define GUF_STR_UNINITIALISED_CPP guf_str{.allocator = NULL, .data.lng.size = 0, .data.lng.capacity = 0, .data.lng.c_str = NULL} +#endif + +// 1.) guf_str_view: + +GUF_STR_KWRDS guf_str_view guf_str_view_from_str(const guf_str* str); + +// Return a new guf_str_view corresponding to the substring in range [pos, pos + count) of str +GUF_STR_KWRDS guf_str_view guf_str_view_substr(guf_str_view str, ptrdiff_t pos, ptrdiff_t count); + +// Equality- and comparison-operators +GUF_STR_KWRDS bool guf_str_view_equal(const guf_str_view* a, const guf_str_view* b); +GUF_STR_KWRDS bool guf_str_view_equal_val_arg(guf_str_view a_val, guf_str_view b_val); +GUF_STR_KWRDS int guf_str_view_cmp(const void *str_view_a, const void *str_view_b); // For qsort etc. + +// Hash functions. +GUF_STR_KWRDS guf_hash_size_t guf_str_view_hash(const guf_str_view *sv); +GUF_STR_KWRDS uint_fast64_t guf_str_view_hash64(const guf_str_view *sv); +GUF_STR_KWRDS uint_fast32_t guf_str_view_hash32(const guf_str_view *sv); + +// Return a new guf_str_view corresponding to the substring with leading/trailing ascii-whitespace chars removed from the left/right +GUF_STR_KWRDS guf_str_view guf_str_view_trim_left_ascii(guf_str_view sv); +GUF_STR_KWRDS guf_str_view guf_str_view_trim_right_ascii(guf_str_view sv); + +// Return true if sv does not violate any of its invariants (.len must be >= 0, .str must not be NULL unless len is 0) +GUF_STR_KWRDS bool guf_str_view_is_valid(guf_str_view sv); + +/* + Return the substring up to the first delimiter "delim" and advance src to one past the delim (so the function can be called repeatedly) + cf. "str_pop_first_split": + - https://accu.org/conf-docs/PDFs_2021/luca_sass_modern_c_and_what_we_can_learn_from_it.pdf ("String handling in Modern C", page 128 of the pdf) + - https://youtu.be/QpAhX-gsHMs?si=lCvm6o60LrYHaAHc&t=3059 (last-retrieved 2025-04-30) +*/ +GUF_STR_KWRDS guf_str_view guf_str_view_pop_split(guf_str_view *src, guf_str_view delim); + +// Create a new tokeniser-state for guf_str_tok_next. +GUF_STR_KWRDS guf_str_tok_state guf_str_tok_state_new(guf_str_view str, guf_str_view *delims, ptrdiff_t delim_count, guf_str_tok_delim_opt delim_match_opt); +/* + Return true when the next token (or delimiter if preserve_delims == true) was encountered. + Put the current token into state->cur_tok. + If preserve_delims is true, every delimiter will be put into state->cur_delim. + If preserve_delims is false, delimiters will only be put into state->cur_delim if the current token is not empty + (otherwise, empty tokens are skipped for preserve_delims == false). + + Example: delims = ["-", "+"] + - "-1+2": tok_next(preserve_delims=false) will set state->cur_tok = "1" and state->cur_delim = "+" + - "-1+2": tok_next(preserve_delims=true) will set state->cur_tok = "" and state->cur_delim = "-" + Set preserve_delims to false if you don't care about processing the delimiters +*/ +GUF_STR_KWRDS bool guf_str_tok_next(guf_str_tok_state *state, bool preserve_delims); + +GUF_STR_KWRDS unsigned guf_str_view_read_uint(guf_str_view *sv); // TODO: Handle overflow, signs etc. + + +// 2.) guf_str: + +// Initialise the guf_str pointed to by str -> return the initalised str on success (or NULL on error) +GUF_STR_KWRDS guf_str *guf_str_try_init(guf_str *str, guf_str_view str_view, guf_allocator *alloc, guf_err *err); +GUF_STR_KWRDS guf_str *guf_str_init(guf_str *str, guf_str_view str_view, guf_allocator *alloc); +GUF_STR_KWRDS guf_str *guf_str_init_empty(guf_str *str, guf_allocator *alloc); +GUF_STR_KWRDS guf_str *guf_str_try_init_from_cstr(guf_str *str, const char* c_str, guf_allocator *alloc, guf_err *err); +GUF_STR_KWRDS guf_str *guf_str_init_from_cstr(guf_str *str, const char* c_str, guf_allocator *alloc); +GUF_STR_KWRDS guf_str *guf_str_init_readonly(guf_str *str, guf_str_view sv); +GUF_STR_KWRDS guf_str guf_str_new_readonly(guf_str_view sv); + +// Return an initialised guf_str (or GUF_STR_UNINITIALISED on error) +GUF_STR_KWRDS guf_str guf_str_try_new(guf_str_view str_view, guf_allocator *alloc, guf_err *err); +GUF_STR_KWRDS guf_str guf_str_new(guf_str_view str_view, guf_allocator *alloc); + +// Destructor, copy-constructor and move-constructor; equality- and comparison-operator (NOTE: ctx is ignored, just pass NULL) +GUF_STR_KWRDS void guf_str_free(guf_str *str, void *ctx); +GUF_STR_KWRDS guf_str *guf_str_copy(guf_str *dst, const guf_str *src, void *ctx); +GUF_STR_KWRDS guf_str *guf_str_move(guf_str *dst, guf_str *src, void *ctx); +GUF_STR_KWRDS bool guf_str_equal(const guf_str *a, const guf_str *b); +GUF_STR_KWRDS int guf_str_cmp(const guf_str *a, const guf_str *b); + +// Hash-functions. +GUF_STR_KWRDS guf_hash_size_t guf_str_hash(const guf_str *str); +GUF_STR_KWRDS uint_fast64_t guf_str_hash64(const guf_str *str); +GUF_STR_KWRDS uint_fast32_t guf_str_hash32(const guf_str *str); + +// Reserve at least min_capacity characters (excluding the null-terminator) (try to double the current capacity first; if that's not at least min_capacity, set the new capacity to min_capacity instead). +GUF_STR_KWRDS guf_str *guf_str_try_reserve(guf_str *str, ptrdiff_t min_capacity, guf_err *err); +GUF_STR_KWRDS guf_str *guf_str_reserve(guf_str *str, ptrdiff_t min_capacity); +// Shrink the capacity of the string so it does not waste space (short-string-optimisation will be applied if the new capacity <= GUF_STR_SSO_BUF_CAP) +GUF_STR_KWRDS guf_str *guf_str_try_shrink_to_fit(guf_str *str, guf_err *err); +GUF_STR_KWRDS guf_str *guf_str_shrink_to_fit(guf_str *str); + +// Set the contents of str to the given string view (mutating str) -> return the mutated str +GUF_STR_KWRDS guf_str *guf_str_try_set(guf_str *str, guf_str_view str_view, guf_err *err); +GUF_STR_KWRDS guf_str *guf_str_set(guf_str *str, guf_str_view str_view); + +// Return a view of the string. +GUF_STR_KWRDS guf_str_view guf_str_to_view(const guf_str *str); + +// Return a non-const pointer to the character at the specified index of str (if possible) +GUF_STR_KWRDS char *guf_str_try_at(guf_str *str, ptrdiff_t idx, guf_err *err); +GUF_STR_KWRDS char *guf_str_at(guf_str *str, ptrdiff_t idx); +GUF_STR_KWRDS char *guf_str_try_back(guf_str *str, guf_err *err); +GUF_STR_KWRDS char *guf_str_back(guf_str *str); +GUF_STR_KWRDS char *guf_str_try_front(guf_str *str, guf_err *err); +GUF_STR_KWRDS char *guf_str_front(guf_str *str); + +// Return a copy of the char at the specified index of str (if possible) +GUF_STR_KWRDS char guf_str_try_at_cpy(const guf_str *str, ptrdiff_t idx, guf_err *err); +GUF_STR_KWRDS char guf_str_at_cpy(const guf_str *str, ptrdiff_t idx); +GUF_STR_KWRDS char guf_str_try_back_cpy(const guf_str *str, guf_err *err); +GUF_STR_KWRDS char guf_str_back_cpy(const guf_str *str); +GUF_STR_KWRDS char guf_str_try_front_cpy(const guf_str *str, guf_err *err); +GUF_STR_KWRDS char guf_str_front_cpy(const guf_str *str); + +/* + Turn str into the substring in range [pos, pos + count) (mutating str) -> return the mutated str + (Constant time if pos == 0, otherwise copying count chars to the beginning of the str, i.e. linear time.) + NOTE: To make a substring-copy (instead of mutating str), create a guf_str_view of str and use guf_str_view_substr +*/ +GUF_STR_KWRDS guf_str *guf_str_try_substr(guf_str *str, ptrdiff_t pos, ptrdiff_t count, guf_err *err); +GUF_STR_KWRDS guf_str *guf_str_substr(guf_str *str, ptrdiff_t pos, ptrdiff_t count); + +// Remove the last character from str if possible (mutating str) -> return the popped char +GUF_STR_KWRDS char guf_str_try_pop_back(guf_str *str, guf_err *err); +GUF_STR_KWRDS char guf_str_pop_back(guf_str *str); + +// Append a char to str (n times; times must be >= 0) (mutating str) -> return the mutated str +GUF_STR_KWRDS guf_str *guf_str_try_append_char(guf_str *str, char c, ptrdiff_t times, guf_err *err); +GUF_STR_KWRDS guf_str *guf_str_append_char(guf_str *str, char c, ptrdiff_t times); +GUF_STR_KWRDS guf_str *guf_str_try_append_one_char(guf_str *str, char c, guf_err *err); +GUF_STR_KWRDS guf_str *guf_str_append_one_char(guf_str *str, char c); + +// Append str_view to str (mutating str) -> return the mutated str +GUF_STR_KWRDS guf_str *guf_str_try_append(guf_str *str, guf_str_view sv, guf_err *err); +GUF_STR_KWRDS guf_str *guf_str_append(guf_str *str, guf_str_view sv); + +// Append from file: +#define GUF_READ_FILE_BUFSIZE 128 +GUF_STR_KWRDS ptrdiff_t guf_str_try_append_file(guf_str *to_append, const char *fname, guf_err *err); +GUF_STR_KWRDS ptrdiff_t guf_str_append_file(guf_str *to_append, const char *fname); + +// Return a pointer to the null-terminated char array representing the string (works like std::string::c_str in C++) +GUF_STR_KWRDS const char *guf_str_const_cstr(const guf_str *str); +GUF_STR_KWRDS char *guf_str_try_get_cstr(guf_str *str, guf_err *err); // Error if str is readonly. +GUF_STR_KWRDS char *guf_str_cstr(guf_str *str); // Panics if str is readonly. + +// Return the length/capacity (in chars) *without* the final null-terminator. +GUF_STR_KWRDS ptrdiff_t guf_str_len(const guf_str *str); +GUF_STR_KWRDS ptrdiff_t guf_str_capacity(const guf_str *str); + +// Return true if the char data of the string lives directly within the guf_str itself (short-string optimisation) instead of in a separate dynamic allocation +GUF_STR_KWRDS bool guf_str_is_short(const guf_str *str); +// Return true if the string is in readonly ("view") mode, i.e. can't be modified, copied etc. which is useful for guf_dict so we don't have to use guf_str_view but can use guf_str (by passing a read-only guf_str) for the lookup functions. +GUF_STR_KWRDS bool guf_str_is_readonly(const guf_str *str); + +// Return an guf_str which is in explicitly uninitialised state. +GUF_STR_KWRDS guf_str guf_str_new_uninitialised(void); +// Return true if str is explicitly uninitialised. +GUF_STR_KWRDS bool guf_str_is_uninit(const guf_str *str); + +// Return true if the string's data does not violate its invariants (useful for debugging the library, should never be false after initialising a guf_str). +GUF_STR_KWRDS bool guf_str_is_valid(const guf_str *str); + +#endif + +// #define GUF_STR_IMPL_STATIC /* debug */ + +#if defined(GUF_STR_IMPL) || defined(GUF_STR_IMPL_STATIC) + +#ifdef __cplusplus + #error "Must compile guf_str as C99 (or above) because type-punning with unions is undefined behaviour in C++" +#endif + +#include "guf_common.h" +#include "guf_math.h" +#define GUF_MATH_CKDINT_IMPL_STATIC +#include "guf_math_ckdint.h" +#include + +#ifdef GUF_STR_IMPL + #define GUF_UTF8_IMPL +#else + #define GUF_UTF8_IMPL_STATIC +#endif +#include "guf_utf8.h" + +// TODO: find_first_of + +// guf_str: +#if defined(GUF_PLATFORM_LITTLE_ENDIAN) + #define GUF_STR_IS_LONG_MASK ((unsigned char)1) /* binary 0000.0001 */ + #define GUF_STR_GET_CAP_MASK (~(size_t)1) /* binary 1111.1111 (1111.1111)* 1111.1110 */ + + static inline void guf_str_set_lng_cap_(guf_str *str, size_t cap_with_null) + { + GUF_ASSERT(cap_with_null % 2 == 0); + GUF_ASSERT(cap_with_null <= PTRDIFF_MAX); + GUF_ASSERT(cap_with_null > GUF_STR_SSO_BUF_CAP); + str->data.lng.capacity = cap_with_null | ((size_t)1); + } + static inline void guf_str_set_shrt_size_(guf_str *str, unsigned char size_with_null) + { + GUF_ASSERT(size_with_null <= GUF_STR_SSO_BUF_CAP && size_with_null < 0x80); // TODO: was < SSO_CAP, should be <= SSO_CAP? + str->data.shrt.size = (unsigned char)(size_with_null << 1); + } +#elif defined(GUF_PLATFORM_BIG_ENDIAN) + #define GUF_STR_IS_LONG_MASK ((unsigned char)0x80) /* binary 1000 0000 */ + #define GUF_STR_GET_CAP_MASK ((size_t)SIZE_MAX >> 1u) /* binary 0111.1111 (1111.1111)* 1111.1111 */ + + static inline void guf_str_set_lng_cap_(guf_str *str, size_t cap_with_null) + { + GUF_ASSERT(cap_with_null % 2 == 0); + GUF_ASSERT(cap_with_null <= PTRDIFF_MAX); + GUF_ASSERT(cap_with_null > GUF_STR_SSO_BUF_CAP); + str->data.lng.capacity = ~GUF_STR_GET_CAP_MASK | (cap_with_null >> 1); + } + static inline void guf_str_set_shrt_size_(guf_str *str, unsigned char size_with_null) + { + GUF_ASSERT(size_with_null <= GUF_STR_SSO_BUF_CAP && size_with_null < 0x80); // TODO: was < SSO_CAP, should be <= + str->data.shrt.size = size_with_null; + } +#else + #error "guf_str: neither GUF_PLATFORM_LITTLE_ENDIAN nor GUF_PLATFORM_BIG_ENDIAN is defined" +#endif + +GUF_STR_KWRDS bool guf_str_is_readonly(const guf_str *str) +{ + GUF_ASSERT(str); + return !str->allocator; +} + +static bool guf_str_is_short_internal_(const guf_str *str) +{ + if (guf_str_is_readonly(str)) { + return false; + } + const unsigned char first_byte = str->data.shrt.size; // union type-punning (only legal in C99 and above; undefined behaviour in C++ I think). + return (first_byte & GUF_STR_IS_LONG_MASK) == 0; +} + +// Returns the capacity without the final null-terminator +static size_t guf_str_cap_internal_(const guf_str *str) +{ + if (guf_str_is_short_internal_(str)) { + return GUF_STR_SSO_BUF_CAP - 1; + } else if (guf_str_is_readonly(str)) { + return 0; + } else { + // Precondition: all capacities for data.lng must be even. + #if defined(GUF_PLATFORM_LITTLE_ENDIAN) + GUF_ASSERT(str->data.lng.capacity & ~GUF_STR_GET_CAP_MASK); // Assert the is_long bit is actually set. + const size_t cap_with_null = str->data.lng.capacity & GUF_STR_GET_CAP_MASK; + GUF_ASSERT(cap_with_null % 2 == 0); + #elif defined(GUF_PLATFORM_BIG_ENDIAN) + GUF_ASSERT(str->data.lng.capacity & ~GUF_STR_GET_CAP_MASK); // Assert the is_long bit is actually set. + const size_t cap_with_null = (str->data.lng.capacity & GUF_STR_GET_CAP_MASK) << 1; + GUF_ASSERT(cap_with_null % 2 == 0); + #endif + GUF_ASSERT(cap_with_null > 0 && cap_with_null > GUF_STR_SSO_BUF_CAP); + GUF_ASSERT(cap_with_null <= PTRDIFF_MAX); + return cap_with_null - 1; + } +} + +static size_t guf_str_size_internal_(const guf_str *str) +{ + if (guf_str_is_short_internal_(str)) { + GUF_ASSERT(str->data.shrt.size > 0); + #if defined(GUF_PLATFORM_LITTLE_ENDIAN) + const size_t size = (str->data.shrt.size >> 1); + #elif defined(GUF_PLATFORM_BIG_ENDIAN) + const size_t size = (str->data.shrt.size); + #endif + GUF_ASSERT(size > 0 && size <= GUF_STR_SSO_BUF_CAP); + return size; + } else { + const size_t size = str->data.lng.size; + GUF_ASSERT(size > 0 && size <= PTRDIFF_MAX); + return size; + } +} + +static size_t guf_str_len_internal_(const guf_str *str) +{ + const size_t size = guf_str_size_internal_(str); + GUF_ASSERT(size > 0); + if (size == 0) { + return 0; + } else { + return size - 1; + } +} + +GUF_STR_KWRDS bool guf_str_is_short(const guf_str *str) +{ + GUF_ASSERT(guf_str_is_valid(str)); + return guf_str_is_short_internal_(str); +} + +GUF_STR_KWRDS ptrdiff_t guf_str_capacity(const guf_str *str) +{ + GUF_ASSERT(guf_str_is_valid(str)); + return (ptrdiff_t)guf_str_cap_internal_(str); +} + +GUF_STR_KWRDS ptrdiff_t guf_str_len(const guf_str *str) +{ + GUF_ASSERT(guf_str_is_valid(str)); + return (ptrdiff_t)guf_str_len_internal_(str); +} + +GUF_STR_KWRDS bool guf_str_is_valid(const guf_str *str) +{ + GUF_ASSERT(str); + if (!str || guf_str_is_uninit(str)) { + return false; + } + const bool is_readonly = !str->allocator; + if (is_readonly) { + bool valid_readonly = str->data.lng.c_str && str->data.lng.capacity == 0 && str->data.lng.size > 0; + return valid_readonly; + } + const bool valid_allocator = str->allocator && str->allocator->alloc && str->allocator->free && str->allocator->realloc; + if (!valid_allocator) { + return false; + } + + if (guf_str_is_short_internal_(str)) { + const size_t size = guf_str_size_internal_(str); // len + 1 + return size > 0 && size <= GUF_STR_SSO_BUF_CAP && str->data.shrt.c_str[size - 1] == '\0'; + } else { + const size_t cap_with_null = guf_str_cap_internal_(str) + 1; + const size_t size = guf_str_size_internal_(str); // len + 1 + const bool valid_cap = cap_with_null > GUF_STR_SSO_BUF_CAP && cap_with_null <= PTRDIFF_MAX && (cap_with_null % 2 == 0); + return valid_cap && size >= 1 && str->data.lng.c_str && str->data.lng.size > 0 && str->data.lng.size <= cap_with_null && str->data.lng.c_str[size - 1] == '\0'; + } +} + +GUF_STR_KWRDS guf_str *guf_str_try_reserve(guf_str *str, ptrdiff_t new_cap_min, guf_err *err) +{ + GUF_ASSERT(guf_str_is_valid(str)); + + if (guf_str_is_readonly(str)) { + guf_err_set_or_panic(err, GUF_ERR_INVALID_ARG, GUF_ERR_MSG("in guf_str_try_reserve: guf_str is readonly")); + return NULL; + } + + const size_t old_cap_with_null = guf_str_cap_internal_(str) + 1; + const size_t len_with_null = guf_str_len_internal_(str) + 1; + + if (new_cap_min < (ptrdiff_t)old_cap_with_null) { // No need to grow. TODO: was <=, should be < ? + guf_err_set_if_not_null(err, GUF_ERR_NONE); + return str; + } + + if (new_cap_min >= PTRDIFF_MAX - 1) { + guf_err_set_or_panic(err, GUF_ERR_INT_OVERFLOW, GUF_ERR_MSG("in guf_str_try_reserve: new_cap_min >= PTRDIFF_MAX - 1")); + return NULL; + } + + size_t new_cap_min_with_null = (size_t)new_cap_min + 1; + if (new_cap_min_with_null % 2 != 0) { // Only an even lng.capacity is allowed. + new_cap_min_with_null += 1; + } + + // Try if we can reach at least new_cap_min_with_null by doubling the capacity. + const size_t GUF_STR_GROWTH_FAC = 2; + size_t times_two_cap = old_cap_with_null * GUF_STR_GROWTH_FAC; + if (guf_mul_is_overflow_size_t(old_cap_with_null, GUF_STR_GROWTH_FAC) || times_two_cap >= PTRDIFF_MAX) { + times_two_cap = (PTRDIFF_MAX % 2 == 0) ? PTRDIFF_MAX : PTRDIFF_MAX - 1; + } + if (times_two_cap > new_cap_min_with_null) { + new_cap_min_with_null = times_two_cap; + } + GUF_ASSERT(new_cap_min_with_null > len_with_null && new_cap_min_with_null <= PTRDIFF_MAX); + + const size_t space_remaining = (new_cap_min_with_null - len_with_null); + if (new_cap_min_with_null < (PTRDIFF_MAX - 8) && space_remaining < 4) { + new_cap_min_with_null += 4 - space_remaining; // Have some leeway. + } + + GUF_ASSERT(new_cap_min_with_null % 2 == 0); + + if (guf_str_is_short_internal_(str)) { // a.) Was short string -> need initial allocation. + char *c_str_new = str->allocator->alloc(new_cap_min_with_null, str->allocator->ctx); + if (!c_str_new) { + guf_err_set_or_panic(err, GUF_ERR_ALLOC_FAIL, GUF_ERR_MSG("in guf_str_try_reserve: Initial allocation failed.")); + return NULL; + } + memcpy(c_str_new, str->data.shrt.c_str, len_with_null); + str->data.lng.c_str = c_str_new; + str->data.lng.size = len_with_null; + guf_str_set_lng_cap_(str, new_cap_min_with_null); + } else { // b) Was long string -> need re-allocation + char *c_str_new = str->allocator->realloc(str->data.lng.c_str, old_cap_with_null, new_cap_min_with_null, str->allocator->ctx); + if (!c_str_new) { + guf_err_set_or_panic(err, GUF_ERR_ALLOC_FAIL, GUF_ERR_MSG("in guf_str_try_reserve: re-allocation failed.")); + return NULL; + } + str->data.lng.c_str = c_str_new; + guf_str_set_lng_cap_(str, new_cap_min_with_null); + } + + GUF_ASSERT(guf_str_is_valid(str)); + guf_err_set_if_not_null(err, GUF_ERR_NONE); + return str; +} + +GUF_STR_KWRDS guf_str *guf_str_reserve(guf_str *str, ptrdiff_t new_cap_min) +{ + return guf_str_try_reserve(str, new_cap_min, NULL); +} + + +GUF_STR_KWRDS guf_str *guf_str_try_shrink_to_fit(guf_str *str, guf_err *err) +{ + GUF_ASSERT(guf_str_is_valid(str)); + + if (guf_str_is_readonly(str)) { + guf_err_set_or_panic(err, GUF_ERR_INVALID_ARG, GUF_ERR_MSG("in guf_str_try_shrink_to_fit: guf_str is readonly")); + return NULL; + } + + const size_t old_cap_with_null = guf_str_cap_internal_(str) + 1; + const size_t len_with_null = guf_str_len_internal_(str) + 1; + GUF_ASSERT(len_with_null <= old_cap_with_null); + + if (old_cap_with_null == len_with_null || guf_str_is_short_internal_(str)) { + guf_err_set_if_not_null(err, GUF_ERR_NONE); + return str; + } + + char *c_str_old = guf_str_cstr(str); + GUF_ASSERT(c_str_old); + + if (len_with_null <= GUF_STR_SSO_BUF_CAP) { // a) Shrunk size fits into short.string. + GUF_ASSERT(len_with_null <= UCHAR_MAX) + guf_str_set_shrt_size_(str, (unsigned char)len_with_null); + memcpy(str->data.shrt.c_str, c_str_old, len_with_null); + str->allocator->free(c_str_old, old_cap_with_null, str->allocator->ctx); + GUF_ASSERT(guf_str_is_short(str)); + GUF_ASSERT(guf_str_is_valid(str)); + guf_err_set_if_not_null(err, GUF_ERR_NONE); + return str; + } + // b) Shrunk size does not fit into short-string. + char *c_str_new = str->allocator->realloc(c_str_old, old_cap_with_null, len_with_null, str->allocator->ctx); + if (!c_str_new) { + guf_err_set_or_panic(err, GUF_ERR_ALLOC_FAIL, GUF_ERR_MSG("in guf_str_try_shrink_to_fit: realloc failed")); + return NULL; + } else { + str->data.lng.c_str = c_str_new; + guf_str_set_lng_cap_(str, len_with_null); + GUF_ASSERT(!guf_str_is_short(str)); + GUF_ASSERT(guf_str_is_valid(str)); + guf_err_set_if_not_null(err, GUF_ERR_NONE); + return str; + } +} + +GUF_STR_KWRDS guf_str *guf_str_shrink_to_fit(guf_str *str) +{ + return guf_str_try_shrink_to_fit(str, NULL); +} + + +static char *guf_str_get_cstr_internal_(guf_str *str) +{ + if (guf_str_is_short_internal_(str)) { + return str->data.shrt.c_str; + } else { + return str->data.lng.c_str; + } +} + +static const char *guf_str_get_const_cstr_internal_(const guf_str *str) +{ + if (guf_str_is_short(str)) { + return str->data.shrt.c_str; + } else { + return str->data.lng.c_str; + } +} + +GUF_STR_KWRDS const char *guf_str_const_cstr(const guf_str *str) +{ + GUF_ASSERT(guf_str_is_valid(str)); + const char* c_str = guf_str_get_const_cstr_internal_(str); + GUF_ASSERT(c_str); + return c_str; +} + +GUF_STR_KWRDS char *guf_str_try_get_cstr(guf_str *str, guf_err *err) +{ + GUF_ASSERT(guf_str_is_valid(str)); + if (guf_str_is_readonly(str)) { + guf_err_set_or_panic(err, GUF_ERR_INVALID_ARG, GUF_ERR_MSG("in guf_str_try_get_cstr: cannot return non-const char pointer because str is readonly")); + return NULL; + } + char *c_str = guf_str_get_cstr_internal_(str); + GUF_ASSERT(c_str); + guf_err_set_if_not_null(err, GUF_ERR_NONE); + return c_str; +} + +GUF_STR_KWRDS char *guf_str_cstr(guf_str *str) +{ + return guf_str_try_get_cstr(str, NULL); +} + + +static void guf_str_set_len_internal_(guf_str *str, size_t len) +{ + GUF_ASSERT(len <= guf_str_cap_internal_(str)); + GUF_ASSERT(!guf_str_is_readonly(str)); + const size_t len_with_null = len + 1; + if (guf_str_is_short_internal_(str)) { + GUF_ASSERT(len_with_null <= UCHAR_MAX) + guf_str_set_shrt_size_(str, (unsigned char)len_with_null); + } else { + str->data.lng.size = len_with_null; + } +} + +GUF_STR_KWRDS guf_str guf_str_new_uninitialised(void) +{ + return GUF_STR_UNINITIALISED; +} + +GUF_STR_KWRDS bool guf_str_is_uninit(const guf_str *str) +{ + GUF_ASSERT(str); + return !str->allocator && !str->data.lng.size && !str->data.lng.capacity && str->data.lng.c_str == NULL; +} + + +GUF_STR_KWRDS guf_str *guf_str_init_empty(guf_str *str, guf_allocator *allocator) +{ + GUF_ASSERT_RELEASE(str && allocator); + GUF_ASSERT_RELEASE(allocator->alloc && allocator->realloc && allocator->free); + str->allocator = allocator; + guf_str_set_shrt_size_(str, 1); + str->data.shrt.c_str[0] = '\0'; + GUF_ASSERT(guf_str_is_valid(str)); + return str; +} + +GUF_STR_KWRDS guf_str *guf_str_try_init(guf_str *str, guf_str_view str_view, guf_allocator *alloc, guf_err *err) +{ + if (!str) { + guf_err_set_or_panic(err, GUF_ERR_NULL_PTR, GUF_ERR_MSG("in guf_str_try_init: str is NULL")); + return NULL; + } else if (!alloc || !alloc->alloc || !alloc->realloc || !alloc->free) { + guf_err_set_or_panic(err, GUF_ERR_NULL_PTR, GUF_ERR_MSG("in guf_str_try_init: alloc (or allocs function pointers) is/are NULL")); + return NULL; + } + + if (!guf_str_view_is_valid(str_view)) { + guf_err_set_or_panic(err, GUF_ERR_NULL_PTR, GUF_ERR_MSG("in guf_str_try_init: invalid str_view")); + return NULL; + } + + guf_str_init_empty(str, alloc); + + if (str_view.len == 0) { + GUF_ASSERT(!guf_str_is_readonly(str)); + GUF_ASSERT(guf_str_is_valid(str)); + guf_err_set_if_not_null(err, GUF_ERR_NONE); + return str; + } + GUF_ASSERT(str_view.str && str_view.len > 0); + + guf_str_try_reserve(str, str_view.len, err); + GUF_ASSERT(guf_str_is_valid(str)); + + if (err && *err != GUF_ERR_NONE) { + guf_panic(*err, GUF_ERR_MSG("in guf_str_try_init: Initial allocation failed")); + return NULL; + } + GUF_ASSERT(guf_str_len_internal_(str) == 0); + GUF_ASSERT(guf_str_cap_internal_(str) >= (size_t)str_view.len); + GUF_ASSERT(!guf_str_is_readonly(str)); + + char *c_str_dst = guf_str_get_cstr_internal_(str); + GUF_ASSERT(c_str_dst); + memcpy(c_str_dst, str_view.str, str_view.len); + c_str_dst[str_view.len] = '\0'; + guf_str_set_len_internal_(str, str_view.len); + + GUF_ASSERT(!guf_str_is_readonly(str)); + GUF_ASSERT(guf_str_is_valid(str)); + guf_err_set_if_not_null(err, GUF_ERR_NONE); + return str; +} + +GUF_STR_KWRDS guf_str *guf_str_init(guf_str *str, guf_str_view str_view, guf_allocator *alloc) +{ + return guf_str_try_init(str, str_view, alloc, NULL); +} + +GUF_STR_KWRDS guf_str guf_str_try_new(guf_str_view str_view, guf_allocator *alloc, guf_err *err) +{ + guf_str str = guf_str_new_uninitialised(); + guf_str_try_init(&str, str_view, alloc, err); + if (err && *err != GUF_ERR_NONE) { + guf_err_set_or_panic(err, *err, GUF_ERR_MSG("in guf_str_try_new: failed init")); + return guf_str_new_uninitialised(); + } else { + GUF_ASSERT(!guf_str_is_uninit(&str)); + guf_err_set_if_not_null(err, GUF_ERR_NONE); + return str; + } +} + +GUF_STR_KWRDS guf_str guf_str_new(guf_str_view str_view, guf_allocator *alloc) +{ + return guf_str_try_new(str_view, alloc, NULL); +} + +GUF_STR_KWRDS guf_str *guf_str_try_init_from_cstr(guf_str *str, const char* c_str, guf_allocator *alloc, guf_err *err) +{ + GUF_ASSERT(str); + if (!str) { + guf_err_set_or_panic(err, GUF_ERR_NULL_PTR, GUF_ERR_MSG("in guf_str_try_init_from_cstr: str is NULL")); + return NULL; + } else if (!c_str) { + guf_err_set_or_panic(err, GUF_ERR_NULL_PTR, GUF_ERR_MSG("in guf_str_try_init_from_cstr: c_str is NULL")); + return NULL; + } else if (!alloc || !alloc->alloc || !alloc->realloc || !alloc->free) { + guf_err_set_or_panic(err, GUF_ERR_NULL_PTR, GUF_ERR_MSG("in guf_str_try_init_from_cstr: alloc (or allocs function pointers) is/are NULL")); + return NULL; + } + + const size_t len = strlen(c_str); + if (len >= PTRDIFF_MAX) { + guf_err_set_or_panic(err, GUF_ERR_INT_OVERFLOW, GUF_ERR_MSG("in guf_str_try_init_from_cstr: stlen(c_str) >= PTRDIFF_MAX")); + return NULL; + } + + guf_str_try_init(str, (guf_str_view){.str = c_str, .len = (ptrdiff_t)len}, alloc, err); + if (err && *err != GUF_ERR_NONE) { + guf_err_set_or_panic(err, *err, GUF_ERR_MSG("in guf_str_try_init_from_cstr: guf_str_try_init failed")); + return NULL; + } else { + guf_err_set_if_not_null(err, GUF_ERR_NONE); + return str; + } +} + +GUF_STR_KWRDS guf_str *guf_str_init_from_cstr(guf_str *str, const char* c_str, guf_allocator *alloc) +{ + return guf_str_try_init_from_cstr(str, c_str, alloc, NULL); +} + +GUF_STR_KWRDS guf_str *guf_str_init_readonly(guf_str *str, guf_str_view sv) +{ + // #define GUF_STR_UNINITIALISED (guf_str){.allocator = NULL, .data.lng.size = 0, .data.lng.capacity = 0, .data.lng.c_str = NULL} + // #define GUF_CSTR_TO_READONLY_STR(CSTR) ((guf_str){.allocator = NULL, .data.lng.c_str = (CSTR), .data.lng.size = strlen(CSTR) + 1, .data.lng.capacity = 0}) + GUF_ASSERT(str); + GUF_ASSERT(guf_str_view_is_valid(sv)); + str->allocator = NULL; + str->data.lng.c_str = (char*)sv.str; // TODO: is this actually legal as long as we don't modify the pointer's contents? + str->data.lng.size = sv.len + 1; + str->data.lng.capacity = 0; + GUF_ASSERT(guf_str_is_readonly(str)); + GUF_ASSERT(guf_str_is_valid(str)); + return str; +} + +GUF_STR_KWRDS guf_str guf_str_new_readonly(guf_str_view sv) +{ + guf_str s; + guf_str_init_readonly(&s, sv); + return s; +} + + +GUF_STR_KWRDS guf_str_view guf_str_to_view(const guf_str *str) +{ + GUF_ASSERT(guf_str_is_valid(str)); + guf_str_view sv = { + .str = guf_str_const_cstr(str), + .len = guf_str_len(str) + }; + GUF_ASSERT(guf_str_view_is_valid(sv)); + return sv; +} + +GUF_STR_KWRDS char *guf_str_try_at(guf_str *str, ptrdiff_t idx, guf_err *err) +{ + GUF_ASSERT(guf_str_is_valid(str)); + + const ptrdiff_t len = guf_str_len(str); + + if (idx < 0) { + guf_err_set_or_panic(err, GUF_ERR_IDX_RANGE, GUF_ERR_MSG("in guf_str_try_at: idx < 0")); + return NULL; + } else if (idx >= len) { + guf_err_set_or_panic(err, GUF_ERR_IDX_RANGE, GUF_ERR_MSG("in guf_str_try_at: idx out of range (idx >= len)")); + return NULL; + } else { + char *c_str = guf_str_try_get_cstr(str, err); + if (err && *err != GUF_ERR_NONE) { + guf_err_set_or_panic(err, *err, GUF_ERR_MSG("in guf_str_try_at: guf_str_try_get_cstr failed (guf_str is readonly)")); + return NULL; + } + GUF_ASSERT(c_str); + guf_err_set_if_not_null(err, GUF_ERR_NONE); + return c_str + idx; + } +} + +GUF_STR_KWRDS char *guf_str_at(guf_str *str, ptrdiff_t idx) +{ + return guf_str_try_at(str, idx, NULL); +} + +GUF_STR_KWRDS char *guf_str_try_back(guf_str *str, guf_err *err) +{ + GUF_ASSERT(guf_str_is_valid(str)); + const ptrdiff_t len = guf_str_len(str); + if (len == 0) { + guf_err_set_or_panic(err, GUF_ERR_IDX_RANGE, GUF_ERR_MSG("in guf_str_try_back: len == 0")); + return NULL; + } else { + guf_err_set_if_not_null(err, GUF_ERR_NONE); + return guf_str_try_at(str, len - 1, err); + } +} + +GUF_STR_KWRDS char *guf_str_back(guf_str *str) +{ + return guf_str_try_back(str, NULL); +} + +GUF_STR_KWRDS char *guf_str_try_front(guf_str *str, guf_err *err) +{ + GUF_ASSERT(guf_str_is_valid(str)); + const ptrdiff_t len = guf_str_len(str); + if (len == 0) { + guf_err_set_or_panic(err, GUF_ERR_IDX_RANGE, GUF_ERR_MSG("in guf_str_try_front: len == 0")); + return NULL; + } else { + guf_err_set_if_not_null(err, GUF_ERR_NONE); + return guf_str_try_at(str, 0, err); + } +} + +GUF_STR_KWRDS char *guf_str_front(guf_str *str) +{ + return guf_str_try_front(str, NULL); +} + +GUF_STR_KWRDS char guf_str_try_at_cpy(const guf_str *str, ptrdiff_t idx, guf_err *err) +{ + GUF_ASSERT(guf_str_is_valid(str)); + const ptrdiff_t len = guf_str_len(str); + if (idx < 0) { + guf_err_set_or_panic(err, GUF_ERR_IDX_RANGE, GUF_ERR_MSG("in guf_str_try_at_cpy: idx < 0")); + return '\0'; + } else if (idx >= len) { + guf_err_set_or_panic(err, GUF_ERR_IDX_RANGE, GUF_ERR_MSG("in guf_str_try_at_cpy: idx out of range (idx >= len)")); + return '\0'; + } else { + const char *c_str = guf_str_const_cstr(str); + GUF_ASSERT(c_str); + guf_err_set_if_not_null(err, GUF_ERR_NONE); + return c_str[idx]; + } +} + +GUF_STR_KWRDS char guf_str_at_cpy(const guf_str *str, ptrdiff_t idx) +{ + return guf_str_try_at_cpy(str, idx, NULL); +} + +GUF_STR_KWRDS char guf_str_try_back_cpy(const guf_str *str, guf_err *err) +{ + GUF_ASSERT(guf_str_is_valid(str)); + const ptrdiff_t len = guf_str_len(str); + if (len == 0) { + guf_err_set_or_panic(err, GUF_ERR_IDX_RANGE, GUF_ERR_MSG("in guf_str_try_back_cpy: len == 0")); + return '\0'; + } else { + guf_err_set_if_not_null(err, GUF_ERR_NONE); + return guf_str_try_at_cpy(str, len - 1, err); + } +} + +GUF_STR_KWRDS char guf_str_back_cpy(const guf_str *str) +{ + return guf_str_try_back_cpy(str, NULL); +} + +GUF_STR_KWRDS char guf_str_try_front_cpy(const guf_str *str, guf_err *err) +{ + GUF_ASSERT(guf_str_is_valid(str)); + const ptrdiff_t len = guf_str_len(str); + if (len == 0) { + guf_err_set_or_panic(err, GUF_ERR_IDX_RANGE, GUF_ERR_MSG("in guf_str_try_front_cpy: len == 0")); + return '\0'; + } else { + guf_err_set_if_not_null(err, GUF_ERR_NONE); + return guf_str_try_at_cpy(str, 0, err); + } +} + +GUF_STR_KWRDS char guf_str_front_cpy(const guf_str *str) +{ + return guf_str_try_front_cpy(str, NULL); +} + + +GUF_STR_KWRDS void guf_str_free(guf_str *str, void *ctx) +{ + (void)ctx; + + if (!str || guf_str_is_uninit(str)) { + return; + } else if (guf_str_is_readonly(str)) { // Don't need to de-allocate anything for read-only strings. + *str = guf_str_new_uninitialised(); + return; + } else if (!guf_str_is_short(str)) { // Need to de-allocate. + GUF_ASSERT(guf_str_capacity(str) < PTRDIFF_MAX); + const ptrdiff_t cap_with_null = guf_str_capacity(str) + 1; + GUF_ASSERT((cap_with_null % 2) == 0); + char *c_str = guf_str_cstr(str); + GUF_ASSERT(str->allocator->free); + if (str->allocator->free) { + str->allocator->free(c_str, cap_with_null, str->allocator->ctx); + } + *str = guf_str_new_uninitialised(); + return; + } else { + GUF_ASSERT(guf_str_is_short(str)); + *str = guf_str_new_uninitialised(); + } +} + +GUF_STR_KWRDS guf_str *guf_str_copy(guf_str *dst, const guf_str *src, void *ctx) +{ + (void)ctx; + GUF_ASSERT_RELEASE(dst); + GUF_ASSERT_RELEASE(guf_str_is_valid(src)); + GUF_ASSERT_RELEASE(!guf_str_is_readonly(src)); // // Doesn't make sense to deep-cpy in readonly mode (I think). + + guf_str_init_empty(dst, src->allocator); + GUF_ASSERT(guf_str_is_short_internal_(dst)); + + char *dst_cstr = NULL; + if (!guf_str_is_short_internal_(src)) { + const size_t src_cap_with_null = guf_str_cap_internal_(src) + 1; + dst_cstr = src->allocator->alloc(src_cap_with_null, src->allocator->ctx); + if (!dst_cstr) { + *dst = guf_str_new_uninitialised(); + return NULL; + } + dst->data.lng.c_str = dst_cstr; + dst->data.lng.capacity = src->data.lng.capacity; + dst->data.lng.size = src->data.lng.size; + } else { + dst->data.shrt.size = src->data.shrt.size; + dst_cstr = dst->data.shrt.c_str; + } + GUF_ASSERT(dst_cstr); + + const size_t src_len_with_null = guf_str_len_internal_(src) + 1; + GUF_ASSERT(src_len_with_null == (guf_str_len_internal_(dst) + 1)); + GUF_ASSERT(guf_str_is_short_internal_(dst) == guf_str_is_short_internal_(src)); + + const char *src_cstr = guf_str_const_cstr(src); + GUF_ASSERT(src_cstr); + memcpy(dst_cstr, src_cstr, src_len_with_null); + + GUF_ASSERT(guf_str_is_valid(dst)); + return dst; +} + +GUF_STR_KWRDS guf_str *guf_str_move(guf_str *dst, guf_str *src, void *ctx) +{ + (void)ctx; + GUF_ASSERT_RELEASE(dst); + GUF_ASSERT_RELEASE(guf_str_is_valid(src)); + GUF_ASSERT_RELEASE(!guf_str_is_readonly(src)); // Doesn't make sense to move in readonly mode (I think). + + *dst = *src; + *src = guf_str_new_uninitialised(); + return dst; +} + +GUF_STR_KWRDS 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)); + + if (guf_str_len(a) != guf_str_len(b)) { + return false; + } + const char *a_cstr = guf_str_const_cstr(a); + const char *b_cstr = guf_str_const_cstr(b); + GUF_ASSERT(a_cstr && b_cstr); + + return 0 == memcmp(a_cstr, b_cstr, guf_str_len(a)); +} + +GUF_STR_KWRDS int guf_str_cmp(const guf_str *a, const guf_str *b) +{ + GUF_ASSERT_RELEASE(guf_str_is_valid(a) && guf_str_is_valid(b)); + GUF_ASSERT_RELEASE(guf_str_is_valid(a) && guf_str_is_valid(b)); + + + const ptrdiff_t shorter_len = guf_min_ptrdiff_t(guf_str_len(a), guf_str_len(b)); + const char *a_cstr = guf_str_const_cstr(a); + const char *b_cstr = guf_str_const_cstr(b); + GUF_ASSERT(a_cstr && b_cstr); + + return memcmp(a_cstr, b_cstr, shorter_len); +} + +GUF_STR_KWRDS guf_str *guf_str_try_set(guf_str *str, guf_str_view sv, guf_err *err) +{ + GUF_ASSERT(guf_str_is_valid(str)); + if (guf_str_is_readonly(str)) { + guf_err_set_or_panic(err, GUF_ERR_INVALID_ARG, GUF_ERR_MSG("in guf_str_try_set: guf_str is readonly")); + return NULL; + } else if (!guf_str_view_is_valid(sv)) { + guf_err_set_or_panic(err, GUF_ERR_INVALID_ARG, GUF_ERR_MSG("in guf_str_try_set: str_view is invalid")); + return NULL; + } + + guf_str_try_reserve(str, sv.len, err); + if (err && *err != GUF_ERR_NONE) { + guf_err_set_or_panic(err, *err, GUF_ERR_MSG("in guf_str_try_set: guf_str_try_reserve failed")); + return NULL; + } + + char *c_str_dst = guf_str_cstr(str); + GUF_ASSERT(c_str_dst); + if (sv.len > 0) { + GUF_ASSERT(sv.str); + memcpy(c_str_dst, sv.str, sv.len); + } + c_str_dst[sv.len] = '\0'; + guf_str_set_len_internal_(str, sv.len); + + GUF_ASSERT(guf_str_is_valid(str)); + guf_err_set_if_not_null(err, GUF_ERR_NONE); + return str; +} + +GUF_STR_KWRDS guf_str *guf_str_set(guf_str *str, guf_str_view sv) +{ + return guf_str_try_set(str, sv, NULL); +} + + + +GUF_STR_KWRDS guf_str *guf_str_try_append_char(guf_str *str, char c, ptrdiff_t times, guf_err *err) +{ + GUF_ASSERT(guf_str_is_valid(str)); + + if (guf_str_is_readonly(str)) { + guf_err_set_or_panic(err, GUF_ERR_NULL_PTR, GUF_ERR_MSG("in guf_str_try_append_char: str is readonly")); + return NULL; + } + + if (times < 0) { + guf_err_set_or_panic(err, GUF_ERR_INVALID_ARG, GUF_ERR_MSG("in guf_str_try_append_char: repeats < 0")); + return NULL; + } else if (times == 0) { + guf_err_set_if_not_null(err, GUF_ERR_NONE); + return str; + } + + GUF_ASSERT(guf_str_len_internal_(str) <= guf_str_cap_internal_(str)); + + const size_t old_cap = guf_str_cap_internal_(str); + const size_t old_len = guf_str_len_internal_(str); + + const size_t new_len = old_len + (size_t)times; + if (new_len <= old_len || new_len >= (size_t)PTRDIFF_MAX) { // Handle overflow. + guf_err_set_or_panic(err, GUF_ERR_INT_OVERFLOW, GUF_ERR_MSG("in guf_str_try_append_char: new length would overflow ptrdiff_t")); + return NULL; + } else if (new_len > old_cap) { // Need to grow capacity. + guf_str_try_reserve(str, new_len, err); + if (err && *err != GUF_ERR_NONE) { + guf_err_set_or_panic(err, *err, GUF_ERR_MSG("in guf_str_try_append_char: failed to reserve capacity")); + return NULL; + } + } + const size_t new_cap = guf_str_cap_internal_(str); + GUF_ASSERT_RELEASE(new_cap >= new_len && new_cap >= old_cap); + GUF_ASSERT(guf_str_len_internal_(str) == old_len); + GUF_ASSERT(((ptrdiff_t)new_cap - (ptrdiff_t)old_len) >= times); + + char *c_str = guf_str_get_cstr_internal_(str); + for (size_t i = old_len; i < new_len; ++i) { + c_str[i] = c; + } + guf_str_set_len_internal_(str, new_len); + c_str[new_len] = '\0'; + + guf_err_set_if_not_null(err, GUF_ERR_NONE); + return str; +} + +GUF_STR_KWRDS guf_str *guf_str_append_char(guf_str *str, char c, ptrdiff_t times) +{ + return guf_str_try_append_char(str, c, times, NULL); +} + +GUF_STR_KWRDS guf_str *guf_str_try_append_one_char(guf_str *str, char c, guf_err *err) +{ + return guf_str_try_append_char(str, c, 1, err); +} + +GUF_STR_KWRDS guf_str *guf_str_append_one_char(guf_str *str, char c) +{ + return guf_str_try_append_one_char(str, c, NULL); +} + + +GUF_STR_KWRDS guf_str *guf_str_try_append(guf_str *str, guf_str_view sv, guf_err *err) +{ + GUF_ASSERT(guf_str_is_valid(str)); + + if (!guf_str_view_is_valid(sv)) { + guf_err_set_or_panic(err, GUF_ERR_INVALID_ARG, GUF_ERR_MSG("in guf_str_try_append_view: str_view is invalid")); + return NULL; + } else if (guf_str_is_readonly(str)) { + guf_err_set_or_panic(err, GUF_ERR_NULL_PTR, GUF_ERR_MSG("in in guf_str_try_append_view: str is readonly")); + return NULL; + } + + if (sv.len == 0) { + guf_err_set_if_not_null(err, GUF_ERR_NONE); + return str; + } + GUF_ASSERT(sv.str && sv.len > 0); + + const size_t old_cap = guf_str_cap_internal_(str); + const size_t old_len = guf_str_len_internal_(str); + const size_t new_len = old_len + (size_t)sv.len; + if (new_len <= old_len || new_len >= (size_t)PTRDIFF_MAX) { // Handle overflow. + guf_err_set_or_panic(err, GUF_ERR_INT_OVERFLOW, GUF_ERR_MSG("in guf_str_try_append_view: new length would overflow ptrdiff_t")); + return NULL; + } else if (new_len > old_cap) { // Growth necessary. + guf_str_try_reserve(str, new_len, err); + if (err && *err != GUF_ERR_NONE) { + guf_err_set_or_panic(err, *err, GUF_ERR_MSG("in guf_str_try_append_view: failed to reserve capacity")); + return NULL; + } + } + + const size_t new_cap = guf_str_cap_internal_(str); + GUF_ASSERT_RELEASE(new_cap >= old_cap && new_cap >= new_len); + GUF_ASSERT(((ptrdiff_t)new_cap - (ptrdiff_t)old_len) >= sv.len); + + char *c_str_dst = guf_str_get_cstr_internal_(str); + for (size_t dst_i = old_len, src_i = 0; dst_i < new_len; ++dst_i, ++src_i) { + GUF_ASSERT(src_i < (size_t)sv.len); + c_str_dst[dst_i] = sv.str[src_i]; + } + c_str_dst[new_len] = '\0'; + guf_str_set_len_internal_(str, new_len); + + GUF_ASSERT(guf_str_is_valid(str)); + guf_err_set_if_not_null(err, GUF_ERR_NONE); + return str; +} + +GUF_STR_KWRDS guf_str *guf_str_append(guf_str *str, guf_str_view sv) +{ + return guf_str_try_append(str, sv, NULL); +} + +GUF_STR_KWRDS ptrdiff_t guf_str_try_append_file(guf_str *to_append, const char *fname, guf_err *err) +{ + if (!fname) { + guf_err_set_or_panic(err, GUF_ERR_NULL_PTR, GUF_ERR_MSG("guf_str_read_file: fname is NULL")); + return 0; + } + + FILE *f = fopen(fname, "r"); + if (!f) { + guf_err_set_or_panic(err, GUF_ERR_IO, GUF_ERR_MSG("guf_str_read_file:: Failed to open file (fopen returned NULL)")); + return 0; + } + + size_t chars_read = 0, total_chars_read = 0; + char buf[GUF_READ_FILE_BUFSIZE] = {'\0'}; + while ((chars_read = fread(buf, sizeof buf[0], GUF_ARR_SIZE(buf), f)) && !ferror(f)) { + guf_str_append(to_append, (guf_str_view){.len = chars_read, .str = buf}); + total_chars_read += chars_read; + if (feof(f)) { + break; + } + } + + if (ferror(f)) { + fclose(f); + guf_err_set_or_panic(err, GUF_ERR_IO, GUF_ERR_MSG("read_file: Unexpected error while reading file")); + } else { + GUF_ASSERT(feof(f)); + fclose(f); + } + + return total_chars_read; +} + +GUF_STR_KWRDS ptrdiff_t guf_str_append_file(guf_str *to_append, const char *fname) { + return guf_str_try_append_file(to_append, fname, NULL); +} + + +GUF_STR_KWRDS guf_str *guf_str_try_substr(guf_str *str, ptrdiff_t pos, ptrdiff_t count, guf_err *err) +{ + GUF_ASSERT(guf_str_is_valid(str)); + + const ptrdiff_t len = guf_str_len(str); + if (pos < 0 || pos >= len) { + guf_err_set_or_panic(err, GUF_ERR_IDX_RANGE, GUF_ERR_MSG("in guf_str_try_substr: pos out of range")); + return NULL; + } else if (count < 0) { + guf_err_set_or_panic(err, GUF_ERR_IDX_RANGE, GUF_ERR_MSG("in guf_str_try_substr: count < 0")); + return NULL; + } + + char *c_str = guf_str_try_get_cstr(str, err); + if (err && *err != GUF_ERR_NONE) { + guf_err_set_or_panic(err, *err, GUF_ERR_MSG("in guf_str_try_substr: string is readonly (guf_str_try_get_cstr() failed)")); + return NULL; + } + GUF_ASSERT(c_str); + + ptrdiff_t pos_plus_count = 0; + guf_saturating_add_ptrdiff_t(pos, count, &pos_plus_count); + const ptrdiff_t substr_len = pos_plus_count > len ? len - pos : count; + GUF_ASSERT(substr_len >= 0 && substr_len <= len && substr_len <= guf_str_capacity(str)); + GUF_ASSERT((size_t)pos + (size_t)(substr_len) <= (size_t)len); // [*] + + if (pos > 0) { + for (ptrdiff_t i = 0; i < substr_len; ++i) { + // GUF_ASSERT(pos + i < len); // cf. [*] + c_str[i] = c_str[pos + i]; + } + } + c_str[substr_len] = '\0'; + guf_str_set_len_internal_(str, substr_len); + + GUF_ASSERT(guf_str_is_valid(str)); + guf_err_set_if_not_null(err, GUF_ERR_NONE); + return str; +} + +GUF_STR_KWRDS guf_str *guf_str_substr(guf_str *str, ptrdiff_t pos, ptrdiff_t count) +{ + return guf_str_try_substr(str, pos, count, NULL); +} + +GUF_STR_KWRDS char guf_str_try_pop_back(guf_str *str, guf_err *err) +{ + GUF_ASSERT(guf_str_is_valid(str)); + + const ptrdiff_t len = guf_str_len(str); + if (len <= 0) { + guf_err_set_or_panic(err, GUF_ERR_IDX_RANGE, GUF_ERR_MSG("in guf_str_try_pop_back: len <= 0")); + return '\0'; + } + GUF_ASSERT(len - 1 >= 0); + const char last = guf_str_at_cpy(str, len - 1); + guf_str_try_substr(str, 0, len - 1, err); + if (err && *err != GUF_ERR_NONE) { + guf_err_set_or_panic(err, *err, GUF_ERR_MSG("in guf_str_try_pop_back: guf_str_try_substr failed")); + return '\0'; + } else { + GUF_ASSERT(guf_str_is_valid(str)); + GUF_ASSERT(guf_str_len(str) == len - 1); + guf_err_set_if_not_null(err, GUF_ERR_NONE); + return last; + } +} + +GUF_STR_KWRDS char guf_str_pop_back(guf_str *str) +{ + return guf_str_try_pop_back(str, NULL); +} + +GUF_STR_KWRDS guf_hash_size_t guf_str_hash(const guf_str *str) +{ + GUF_ASSERT(guf_str_is_valid(str)); + return guf_str_view_hash(&(guf_str_view){.str = guf_str_const_cstr(str), .len = guf_str_len(str)}); +} + +GUF_STR_KWRDS uint_fast64_t guf_str_hash64(const guf_str *str) +{ + GUF_ASSERT(guf_str_is_valid(str)); + return guf_str_view_hash64(&(guf_str_view){.str = guf_str_const_cstr(str), .len = guf_str_len(str)}); +} + +GUF_STR_KWRDS uint_fast32_t guf_str_hash32(const guf_str *str) +{ + GUF_ASSERT(guf_str_is_valid(str)); + return guf_str_view_hash32(&(guf_str_view){.str = guf_str_const_cstr(str), .len = guf_str_len(str)}); +} + + + + +// guf_str_view: + +GUF_STR_KWRDS bool guf_str_view_is_valid(guf_str_view sv) +{ + if (sv.str) { + return sv.len >= 0; + } else { + return sv.len == 0; + } +} + +GUF_STR_KWRDS guf_str_view guf_str_view_from_str(const guf_str *str) +{ + GUF_ASSERT(str); + GUF_ASSERT(guf_str_is_valid(str)); + + return (guf_str_view){.str = guf_str_const_cstr(str), .len = guf_str_len(str)}; + +} + +/* + cf. "str_pop_first_split": + - https://accu.org/conf-docs/PDFs_2021/luca_sass_modern_c_and_what_we_can_learn_from_it.pdf ("String handling in Modern C", page 128 of the pdf) + - https://youtu.be/QpAhX-gsHMs?si=lCvm6o60LrYHaAHc&t=3059 (last-retrieved 2025-04-30) +*/ + +GUF_STR_KWRDS guf_str_view guf_str_view_pop_split(guf_str_view *src, guf_str_view delim) +{ + GUF_ASSERT(src); + GUF_ASSERT_RELEASE(guf_str_view_is_valid(*src)); + GUF_ASSERT_RELEASE(guf_str_view_is_valid(delim)); + + if (delim.len <= 0) { + goto delim_not_found; + } + + for (ptrdiff_t src_idx = 0; src_idx < src->len; ++src_idx) { + ptrdiff_t num_matched = 0; + for (ptrdiff_t delim_idx = 0; delim_idx < delim.len && (src_idx + delim.len <= src->len); ++delim_idx) { + if (delim.str[delim_idx] != src->str[src_idx + delim_idx]) { + break; + } + ++num_matched; + } + if (num_matched == delim.len) { // Delimiter found in interval [src_idx, src_idx + delim.len) + const guf_str_view popped = guf_str_view_substr(*src, 0, src_idx); + const ptrdiff_t advance_len = popped.len + delim.len; + GUF_ASSERT(advance_len > 0 && advance_len >= delim.len); + src->len -= advance_len; + GUF_ASSERT(src->len >= 0); + src->str = src->len > 0 ? src->str + advance_len : NULL; + return popped; + } + } + +delim_not_found:; + const guf_str_view popped = *src; + src->str = NULL; + src->len = 0; + return popped; +} + +static inline int guf_str_view_cmp_asc_by_len_(const void *a, const void *b) +{ + const guf_str_view *asv = (const guf_str_view*)a; + const guf_str_view *bsv = (const guf_str_view*)b; + if (asv->len < bsv->len) { + return -1; + } else if (asv->len > bsv->len) { + return 1; + } else { + return 0; + } +} + +static inline int guf_str_view_cmp_desc_by_len_(const void *a, const void *b) +{ + return -guf_str_view_cmp_asc_by_len_(a, b); +} + +GUF_STR_KWRDS guf_str_tok_state guf_str_tok_state_new(guf_str_view str, guf_str_view *delims, ptrdiff_t delim_count, guf_str_tok_delim_opt delim_match_opt) +{ + GUF_ASSERT_RELEASE(guf_str_view_is_valid(str)); + GUF_ASSERT_RELEASE(delim_count > 0 ? delims != NULL : true); + + ptrdiff_t max_delim_len = 0; + if (delims && delim_count > 0) { + for (ptrdiff_t i = 0; i < delim_count; ++i) { + GUF_ASSERT_RELEASE(guf_str_view_is_valid(delims[i])); + max_delim_len = guf_max_ptrdiff_t(max_delim_len, delims[i].len); + } + } + if (max_delim_len <= 0 || delim_count <= 0 || delims == NULL) { + delims = NULL; + delim_count = 0; + } else { + switch (delim_match_opt) { + case GUF_STR_TOK_DELIM_OPT_MATCH_LONGEST: + qsort(delims, delim_count, sizeof(delims[0]), guf_str_view_cmp_desc_by_len_); + break; + case GUF_STR_TOK_DELIM_OPT_MATCH_SHORTEST: + qsort(delims, delim_count, sizeof(delims[0]), guf_str_view_cmp_asc_by_len_); + break; + case GUF_STR_TOK_DELIM_OPT_MATCH_IN_ORDER: + break; + default: + GUF_ASSERT(false); + break; + } + } + return (guf_str_tok_state) { + .input = str, + .delims = delims, + .delim_count = delim_count, + .num_toks_read = 0, + .num_delims_read = 0, + .cur_tok = (guf_str_view){.len = 0, .str = NULL}, + .cur_delim = (guf_str_view){.len = 0, .str = NULL}, + .done = false + }; +} + +GUF_STR_KWRDS bool guf_str_tok_next(guf_str_tok_state *state, bool preserve_delims) +{ + GUF_ASSERT(state); + GUF_ASSERT(guf_str_view_is_valid(state->input)); + GUF_ASSERT(state->num_toks_read >= 0 && state->num_delims_read >= 0 && state->delim_count >= 0); + GUF_ASSERT(state->delim_count > 0 ? state->delims != NULL : true); + + if (state->done || state->input.len <= 0 || state->input.str == NULL) { + state->done = true; + state->cur_tok = (guf_str_view){.str = NULL, .len = 0}; + state->cur_delim = (guf_str_view){.str = NULL, .len = 0}; + return false; + } else if (state->delim_count <= 0 || state->delims == NULL) { + state->done = true; + state->cur_tok = state->input; + state->cur_delim = (guf_str_view){.str = NULL, .len = 0}; + return state->cur_tok.len > 0; + } + +find_next_delim_begin: + state->cur_tok = state->cur_delim = (guf_str_view){.str = state->input.str, .len = 0}; + while (state->input.len > 0) { + GUF_ASSERT(state->input.str != NULL); + for (ptrdiff_t delim_idx = 0; delim_idx < state->delim_count; ++delim_idx) { // If state->delims is sorted descending/ascending by length, match the longest/shortest possible delim + const guf_str_view delim = state->delims[delim_idx]; + GUF_ASSERT(guf_str_view_is_valid(delim)); + if (delim.len > state->input.len || delim.len <= 0) { // Current delim cannot possibly match. + continue; + } + const guf_str_view delim_candidate = guf_str_view_substr(state->input, 0, delim.len); + if (guf_str_view_equal(&delim_candidate, &delim)) { // a) Matched the current delim: + GUF_ASSERT(state->input.len >= delim.len); + GUF_ASSERT(state->cur_tok.len >= 0); + state->cur_delim = delim; + state->num_delims_read += 1; + + state->input.len -= delim.len; + state->input.str = state->input.len > 0 ? state->input.str + delim.len : NULL; + + if (!preserve_delims && state->cur_tok.len == 0) { + goto find_next_delim_begin; + } + + state->num_toks_read += state->cur_tok.len > 0 ? 1 : 0; + state->done = state->input.len <= 0; + GUF_ASSERT(state->cur_tok.len > 0 || state->cur_delim.len > 0); + return true; + } + } + // b) Could not match any of the delims: + state->cur_tok.len += 1; + state->input.len -= 1; + state->input.str = state->input.len > 0 ? state->input.str + 1 : NULL; + } + + state->done = true; + GUF_ASSERT(state->cur_tok.len >= 0); + state->cur_delim = (guf_str_view){.str = NULL, .len = 0}; + if (state->cur_tok.len > 0) { + state->num_toks_read += 1; + } + return state->cur_tok.len > 0; +} + +GUF_STR_KWRDS guf_str_view guf_str_view_trim_left_ascii(guf_str_view sv) +{ + if (sv.len <= 0 || sv.str == NULL) { + return sv; + } + + for (; sv.len > 0 && guf_char_isspace_ascii(*sv.str); --sv.len, ++sv.str) + ; + + GUF_ASSERT(sv.len >= 0); + GUF_ASSERT(sv.len == 0 || !guf_char_isspace_ascii(*sv.str)); + + return sv; +} + +GUF_STR_KWRDS guf_str_view guf_str_view_trim_right_ascii(guf_str_view sv) +{ + if (sv.len <= 0 || sv.str == NULL) { + return sv; + } + + for (; sv.len > 0 && guf_char_isspace_ascii(sv.str[sv.len - 1]); --sv.len) + ; + + GUF_ASSERT(sv.len >= 0); + GUF_ASSERT(sv.len == 0 || !guf_char_isspace_ascii(sv.str[sv.len - 1])); + + return sv; +} + +GUF_STR_KWRDS guf_str_view guf_str_view_substr(guf_str_view str, ptrdiff_t pos, ptrdiff_t count) +{ + GUF_ASSERT_RELEASE(str.str); + GUF_ASSERT_RELEASE(pos >= 0); + GUF_ASSERT_RELEASE(count >= 0); + + if (str.len == 0 || count == 0 || pos >= str.len || pos < 0 || str.str == NULL) { + return (guf_str_view){.str = str.str, .len = 0}; + } + + ptrdiff_t pos_plus_count = 0; + guf_saturating_add_ptrdiff_t(pos, count, &pos_plus_count); + const ptrdiff_t substr_len = pos_plus_count > str.len ? str.len - pos : count; + GUF_ASSERT(substr_len >= 0); + GUF_ASSERT(substr_len <= str.len); + + const guf_str_view sub_sv = {.str = str.str + pos, .len = substr_len}; + GUF_ASSERT(guf_str_view_is_valid(sub_sv)); + return sub_sv; +} + +GUF_STR_KWRDS guf_hash_size_t guf_str_view_hash(const guf_str_view *sv) +{ + GUF_ASSERT(sv); + if (!sv->str || sv->len <= 0) { + return GUF_HASH_INIT; + } + return guf_hash(sv->str, sv->len, GUF_HASH_INIT); +} + +GUF_STR_KWRDS uint_fast64_t guf_str_view_hash64(const guf_str_view *sv) +{ + GUF_ASSERT(sv); + if (!sv->str || sv->len <= 0) { + return GUF_HASH64_INIT; + } + return guf_hash64(sv->str, sv->len, GUF_HASH64_INIT); +} + +GUF_STR_KWRDS uint_fast32_t guf_str_view_hash32(const guf_str_view *sv) +{ + GUF_ASSERT(sv); + if (!sv->str || sv->len <= 0) { + return GUF_HASH32_INIT; + } + return guf_hash32(sv->str, sv->len, GUF_HASH32_INIT); +} + +GUF_STR_KWRDS bool guf_str_view_equal(const guf_str_view* a, const guf_str_view* b) +{ + GUF_ASSERT(a && b); + if (a->len != b->len) { + return false; + } + + if ((!a->str && b->str) || (!b->str && a->str)) { + return false; + } else if (!a->str && !b->str) { + return a->len == b->len; + } + GUF_ASSERT(a->str && b->str); + + if (a->len <= 0) { + return true; + } + + return 0 == memcmp(a->str, b->str, a->len); +} + +GUF_STR_KWRDS bool guf_str_view_equal_val_arg(guf_str_view a_val, guf_str_view b_val) +{ + return guf_str_view_equal(&a_val, &b_val); +} + +GUF_STR_KWRDS unsigned guf_str_view_read_uint(guf_str_view *sv) +{ // TODO: Handle overflow etc. + unsigned res = 0; + while (sv->len && sv->str[0] >= '0' && sv->str[0] <= '9') { + res *= 10; + res += (sv->str[0] - '0'); + sv->len -= 1; + sv->str = sv->len > 0 ? sv->str + 1 : NULL; + } + return res; +} + +#undef GUF_STR_IMPL +#undef GUF_STR_IMPL_STATIC +#endif /* end impl */ + +#undef GUF_STR_KWRDS diff --git a/libguf/src/guf_str_view_type.h b/libguf/src/guf_str_view_type.h new file mode 100755 index 0000000..9922b34 --- /dev/null +++ b/libguf/src/guf_str_view_type.h @@ -0,0 +1,14 @@ +/* + is parametrized: no +*/ + +#ifndef GUF_STR_VIEW_TYPE_H +#define GUF_STR_VIEW_TYPE_H +#include + +typedef struct guf_str_view { + const char *str; + ptrdiff_t len; +} guf_str_view; + +#endif diff --git a/libguf/src/guf_utf8.h b/libguf/src/guf_utf8.h new file mode 100755 index 0000000..eb6253b --- /dev/null +++ b/libguf/src/guf_utf8.h @@ -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 "�" (U+FFFD) +#define GUF_UTF8_REPLACEMENT_CHAR_CODEPOINT UINT32_C(0xFFFD) + +#endif + +#if defined(GUF_UTF8_IMPL) || defined(GUF_UTF8_IMPL_STATIC) +#include + +// 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] = 0xC0; + first_byte_bits = 5; + } else if (cp >= 0x800 && cp <= 0xFFFF) { // binary: 1110.xxxx 10xx.xxxx 10xx.xxxx + num_bytes = 3; + result->bytes[0] = 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] = 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] = 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 && bytes[i] <= 0xFF)) { + 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 diff --git a/libguf/src/guf_utils.h b/libguf/src/guf_utils.h new file mode 100755 index 0000000..9b85090 --- /dev/null +++ b/libguf/src/guf_utils.h @@ -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 diff --git a/libguf_impls/guf_alloc_libc_impl.c b/libguf_impls/guf_alloc_libc_impl.c new file mode 100644 index 0000000..813fd0e --- /dev/null +++ b/libguf_impls/guf_alloc_libc_impl.c @@ -0,0 +1,2 @@ +#define GUF_ALLOC_LIBC_IMPL +#include "guf_alloc_libc.h" diff --git a/libguf_impls/guf_init.c b/libguf_impls/guf_init.c new file mode 100644 index 0000000..e69de29 diff --git a/libguf_impls/guf_init_impl.c b/libguf_impls/guf_init_impl.c new file mode 100755 index 0000000..7261d21 --- /dev/null +++ b/libguf_impls/guf_init_impl.c @@ -0,0 +1 @@ +#include "guf_init.h" diff --git a/libguf_impls/guf_str_impl.c b/libguf_impls/guf_str_impl.c new file mode 100644 index 0000000..8114902 --- /dev/null +++ b/libguf_impls/guf_str_impl.c @@ -0,0 +1,2 @@ +#define GUF_STR_IMPL +#include "guf_str.h"