Solve Day 01

This commit is contained in:
zeichensystem 2025-12-02 21:08:15 +01:00
commit 76ea6e5d18
31 changed files with 12072 additions and 0 deletions

6
.gitignore vendored Executable file
View File

@ -0,0 +1,6 @@
input/day-??.txt
bin/
build/
.vscode/
result.txt
session.txt

82
CMakeLists.txt Executable file
View File

@ -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} $<$<CONFIG:Debug>:${DBG_FLAGS}>)
target_link_options(${current_target} PRIVATE ${WARNING_FLAGS_C} $<$<CONFIG:Debug>:${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}
)

80
aocfetch.py Executable file
View File

@ -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} :)')

82
day-01/day-01.c Normal file
View File

@ -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;
}

44
day-xy/day-xy.c Normal file
View File

@ -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;
}

10
input/day-01-example.txt Normal file
View File

@ -0,0 +1,10 @@
L68
L30
R48
L5
R60
L55
L1
L99
R14
L82

BIN
libguf/src/.DS_Store vendored Executable file

Binary file not shown.

31
libguf/src/guf_alloc.h Executable file
View File

@ -0,0 +1,31 @@
/*
is parametrized: no
*/
#ifndef GUF_ALLOC_H
#define GUF_ALLOC_H
#include "guf_common.h"
#include "guf_assert.h"
// A custom allocator interface as described in https://nullprogram.com/blog/2023/12/17/ (last-retrieved 2025-01-25)
typedef struct guf_allocator {
void *(*alloc)(ptrdiff_t size, void *ctx);
void *(*realloc)(void *ptr, ptrdiff_t old_size, ptrdiff_t new_size, void *ctx);
void (*free)(void *ptr, ptrdiff_t size, void *ctx);
void *ctx;
} guf_allocator;
// typedef enum guf_alloc_fn_type {
// GUF_ALLOC_FN_TYPE_ALLOC,
// GUF_ALLOC_FN_TYPE_REALLOC,
// GUF_ALLOC_FN_TYPE_FREE,
// } guf_alloc_fn_type;
/*
GUF_ALLOC_MAX_BYTES: Largest number of bytes an allocated buffer of elements of TYPE can have.
GUF_ALLOC_MAX_CAPACITY: Largest number of elements an allocated buffer of elements of TYPE can have.
*/
#define GUF_ALLOC_MAX_BYTES(TYPE) ( PTRDIFF_MAX - ( PTRDIFF_MAX % sizeof(TYPE) ) )
#define GUF_ALLOC_MAX_CAPACITY(TYPE) ( GUF_ALLOC_MAX_BYTES(TYPE) / sizeof(TYPE) )
#endif

121
libguf/src/guf_alloc_libc.h Executable file
View File

@ -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 <memory.h>
#include "guf_alloc.h"
#define GUF_ALLOC_TRACKER_IMPL_STATIC
#include "guf_alloc_tracker.h"
typedef struct guf_libc_alloc_ctx {
guf_alloc_tracker tracker;
bool zero_init;
} guf_libc_alloc_ctx;
#if !defined(GUF_ALLOC_LIBC_IMPL_STATIC) && !defined(GUF_ALLOC_LIBC_IMPL)
GUF_ALLOC_LIBC_KWRDS void *guf_libc_alloc(ptrdiff_t size, void *ctx);
GUF_ALLOC_LIBC_KWRDS void *guf_libc_realloc(void *ptr, ptrdiff_t old_size, ptrdiff_t new_size, void *ctx);
GUF_ALLOC_LIBC_KWRDS void *guf_libc_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

212
libguf/src/guf_alloc_tracker.h Executable file
View File

@ -0,0 +1,212 @@
#if defined(GUF_ALLOC_TRACKER_IMPL_STATIC)
#define GUF_ALLOC_TRACKER_KWRDS static inline
#else
#define GUF_ALLOC_TRACKER_KWRDS
#endif
#ifndef GUF_ALLOC_TRACKER_H
#define GUF_ALLOC_TRACKER_H
#include <stdio.h>
#include "guf_common.h"
#include "guf_str_view_type.h"
typedef struct guf_alloc_tracker {
FILE *log, *err_log;
const char *name;
size_t alloc_count, realloc_count, free_count;
ptrdiff_t allocated_bytes;
unsigned id;
bool enabled;
} guf_alloc_tracker;
#if !defined(GUF_ALLOC_TRACKER_IMPL_STATIC) && !defined(GUF_ALLOC_TRACKER_IMPL)
GUF_ALLOC_TRACKER_KWRDS guf_alloc_tracker *guf_alloc_tracker_init(guf_alloc_tracker *t, unsigned id, const char* name, FILE *log, FILE *err_log);
GUF_ALLOC_TRACKER_KWRDS guf_alloc_tracker guf_alloc_tracker_new(unsigned id, const char* name, FILE *log, FILE *err_log);
GUF_ALLOC_TRACKER_KWRDS void guf_alloc_tracker_print(const guf_alloc_tracker *t, FILE *f);
GUF_ALLOC_TRACKER_KWRDS bool guf_alloc_tracker_found_leak(const guf_alloc_tracker *t);
GUF_ALLOC_TRACKER_KWRDS bool guf_track_alloc(guf_alloc_tracker *t, ptrdiff_t size);
GUF_ALLOC_TRACKER_KWRDS bool guf_track_realloc(guf_alloc_tracker *t, ptrdiff_t old_size, ptrdiff_t new_size);
GUF_ALLOC_TRACKER_KWRDS bool guf_track_free(guf_alloc_tracker *t, ptrdiff_t size);
#endif
#if defined(GUF_ALLOC_TRACKER_IMPL_STATIC) || defined(GUF_ALLOC_TRACKER_IMPL)
#include "guf_alloc.h"
#define GUF_MATH_CKDINT_IMPL_STATIC
#include "guf_math_ckdint.h"
GUF_ALLOC_TRACKER_KWRDS guf_alloc_tracker *guf_alloc_tracker_init(guf_alloc_tracker *t, unsigned id, const char* name, FILE *log, FILE *err_log)
{
GUF_ASSERT_RELEASE(t);
t->log = log;
t->err_log = err_log;
t->alloc_count = t->realloc_count = t->free_count = 0;
t->allocated_bytes = 0;
t->name = name;
t->id = id;
t->enabled = true;
return t;
}
GUF_ALLOC_TRACKER_KWRDS guf_alloc_tracker guf_alloc_tracker_new(unsigned id, const char* name, FILE *log, FILE *err_log)
{
guf_alloc_tracker t;
guf_alloc_tracker_init(&t, id, name, log, err_log);
return t;
}
GUF_ALLOC_TRACKER_KWRDS bool guf_alloc_tracker_found_leak(const guf_alloc_tracker *t)
{
GUF_ASSERT_RELEASE(t);
return (t->allocated_bytes != 0) || (t->alloc_count != t->free_count);
}
GUF_ALLOC_TRACKER_KWRDS void guf_alloc_tracker_print(const guf_alloc_tracker *t, FILE *f)
{
GUF_ASSERT(t);
if (!f) {
f = stdout;
}
if (!t) {
fprintf(f, "guf_alloc_tracker_fprint: guf_alloc_tracker is NULL");
return;
}
fprintf(f,
"guf_alloc_tracker (name '%s' id = %u):\n"
"allocated_bytes: %td\n"
"alloc_count: %zu\n"
"realloc_count: %zu\n"
"free_count: %zu\n",
t->name ? t->name : "unnamed", t->id, t->allocated_bytes, t->alloc_count, t->realloc_count, t->free_count
);
}
GUF_ALLOC_TRACKER_KWRDS bool guf_track_alloc(guf_alloc_tracker *t, ptrdiff_t size)
{
GUF_ASSERT(t);
GUF_ASSERT(size >= 0);
bool success = true;
if (guf_saturating_add_size_t(t->alloc_count, 1, &t->alloc_count) != GUF_MATH_CKD_SUCCESS && t->err_log) {
fprintf(t->err_log, "WARNING in guf_track_alloc (name '%s' id %u): alloc_count reached SIZE_MAX\n", t->name ? t->name : "unnamed", t->id);
}
if (guf_saturating_add_ptrdiff_t(t->allocated_bytes, size, &t->allocated_bytes) != GUF_MATH_CKD_SUCCESS) {
if (t->err_log) {
fprintf(t->err_log, "ERROR in guf_track_alloc (name '%s' id %u): allocated_byte overflow\n", t->name ? t->name : "unnamed", t->id);
}
success = false;
}
if (t->allocated_bytes < 0) {
if (t->err_log) {
fprintf(t->err_log, "ERROR in guf_track_alloc (name '%s' id %u): allocated_bytes < 0\n", t->name ? t->name : "unnamed", t->id);
}
success = false;
}
if (t->log) {
fprintf(t->log, "guf_alloc_tracker (name '%s' id %u): alloc (%td bytes) %s\n", t->name ? t->name : "unnamed", t->id, size, success ? "SUCCESS" : "FAILURE");
}
return success;
}
GUF_ALLOC_TRACKER_KWRDS bool guf_track_realloc(guf_alloc_tracker *t, ptrdiff_t old_size, ptrdiff_t new_size)
{
GUF_ASSERT(t);
GUF_ASSERT(old_size >= 0 && new_size >= 0);
bool success = true;
if (guf_saturating_add_size_t(t->realloc_count, 1, &t->realloc_count) && t->err_log) {
fprintf(t->err_log, "WARNING in guf_track_realloc (name '%s' id %u): realloc_count reached SIZE_MAX\n", t->name ? t->name : "unnamed", t->id);
}
if (old_size < 0 || new_size < 0) {
success = false;
if (t->err_log) {
fprintf(t->err_log, "ERROR in guf_track_realloc (name '%s' id %u): old_size < 0 or new_size < 0\n", t->name ? t->name : "unnamed", t->id);
}
}
if (guf_saturating_sub_ptrdiff_t(t->allocated_bytes, old_size, &t->allocated_bytes)) {
success = false;
if (t->err_log) {
fprintf(t->err_log, "ERROR in guf_track_realloc (name '%s' id %u): allocated_bytes - old_size under/overflows\n", t->name ? t->name : "unnamed", t->id);
}
}
if (t->allocated_bytes < 0) {
success = false;
fprintf(t->err_log, "ERROR in guf_track_realloc (name '%s' id %u): allocated_bytes < 0 after subtracting old_size\n", t->name ? t->name : "unnamed", t->id);
}
if (guf_saturating_add_ptrdiff_t(t->allocated_bytes, new_size, &t->allocated_bytes)) {
success = false;
if (t->err_log) {
fprintf(t->err_log, "ERROR in guf_track_realloc (name '%s' id %u): allocated_bytes overflow \n", t->name ? t->name : "unnamed", t->id);
}
}
if (t->allocated_bytes < 0) {
success = false;
fprintf(t->err_log, "ERROR in guf_track_realloc (name '%s' id %u): allocated_bytes < 0 after adding new_size\n", t->name ? t->name : "unnamed", t->id);
}
if (t->log) {
fprintf(t->log, "guf_alloc_tracker (name '%s' id %u): realloc (from %td to %td bytes) %s\n", t->name ? t->name : "unnamed", t->id, old_size, new_size, (success ? "SUCCESS" : "FAILURE"));
}
return success;
}
GUF_ALLOC_TRACKER_KWRDS bool guf_track_free(guf_alloc_tracker *t, ptrdiff_t size)
{
GUF_ASSERT(t);
GUF_ASSERT(size >= 0);
bool success = true;
if (guf_saturating_add_size_t(t->free_count, 1, &t->free_count) && t->err_log) {
fprintf(t->err_log, "WARNING in guf_track_free (name '%s' id %u): free_count reached SIZE_MAX\n", t->name ? t->name : "unnamed", t->id);
}
if (size < 0) {
success = false;
if (t->err_log) {
fprintf(t->err_log, "ERROR in guf_track_free (name '%s' id %u): size < 0\n", t->name ? t->name : "unnamed", t->id);
}
}
if (t->allocated_bytes < size) {
success = false;
if (t->err_log) {
fprintf(t->err_log, "ERROR in guf_track_free (name '%s' id %u): freed more bytes than allocated\n", t->name ? t->name : "unnamed", t->id);
}
}
if (guf_saturating_sub_ptrdiff_t(t->allocated_bytes, size, &t->allocated_bytes)) {
success = false;
if (t->err_log) {
fprintf(t->err_log, "ERROR in guf_track_free (name '%s' id %u): allocated_bytes - size under/overflows\n", t->name ? t->name : "unnamed", t->id);
}
}
if (t->log) {
fprintf(t->log, "guf_alloc_tracker (name '%s' id %u): free (%td bytes) %s\n", t->name ? t->name : "unnamed", t->id, size, (success ? "SUCCESS" : "FAILURE"));
}
return success;
}
#endif /* End impl */
#endif /* End header-guard */
#undef GUF_ALLOC_TRACKER_KWRDS
#undef GUF_ALLOC_TRACKER_IMPL
#undef GUF_ALLOC_TRACKER_IMPL_STATIC

165
libguf/src/guf_assert.h Executable file
View File

@ -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 <stdio.h>
#include <assert.h>
#include "guf_common.h"
typedef enum guf_err {
GUF_ERR_NONE = 0,
GUF_ERR_UNSPECIFIED,
GUF_ERR_ALLOC_FAIL,
GUF_ERR_NULL_PTR,
GUF_ERR_INT_OVERFLOW,
GUF_ERR_DIV_BY_ZERO,
GUF_ERR_DOMAIN,
GUF_ERR_IDX_RANGE,
GUF_ERR_INVALID_ARG,
GUF_ERR_RUNTIME,
GUF_ERR_LOGIC,
GUF_ERR_NOT_FOUND,
GUF_ERR_ALREADY_EXISTS,
GUF_ERR_ASSERT_FAIL,
GUF_ERR_IO,
GUF_ERR_TYPES_NUM
} guf_err;
typedef void(*guf_panic_handler_fn)(guf_err err, const char *msg);
extern guf_panic_handler_fn guf_global_panic_handler;
extern void guf_set_global_panic_handler(guf_panic_handler_fn panic_handler);
extern void guf_panic_handler_default(guf_err err, const char *msg);
extern const char *guf_err_to_str(guf_err err);
#define GUF_FILE_LINE_STR() "file '" __FILE__ "' line " GUF_STRINGIFY(__LINE__)
#define GUF_ERR_MSG(msg) msg " (" GUF_FILE_LINE_STR() ")"
#define GUF_ERR_MSG_EMPTY() "(" GUF_FILE_LINE_STR() ")"
extern void guf_panic(guf_err err, const char *msg);
#define GUF_PANIC(err) guf_panic(err, "(" GUF_FILE_LINE_STR() ")");
// Break on debug, cf. https://nullprogram.com/blog/2022/06/26/ (last retrieved 2025-01-07)
#if __GNUC__ || __clang__
#define GUF_DEBUG_BREAK_CODE __builtin_trap()
#elif _MSC_VER
#define GUF_DEBUG_BREAK_CODE __debugbreak()
#else
#define GUF_DEBUG_BREAK_CODE abort()
#endif
#ifndef NDEBUG
#define GUF_ASSERT(COND) do { \
if (!(COND)) { \
guf_global_panic_handler(GUF_ERR_ASSERT_FAIL, "(assertion '" #COND "' " GUF_FILE_LINE_STR() ")"); \
abort(); \
}\
} while(0);
#else
#define GUF_ASSERT(COND)
#endif
#define GUF_ASSERT_RELEASE(COND) do { \
if (!(COND)) { \
guf_global_panic_handler(GUF_ERR_ASSERT_FAIL, "(release assertion '" #COND "' " GUF_FILE_LINE_STR() ")"); \
abort();\
} \
} while (0);
static inline void guf_err_set_or_panic(guf_err *err, guf_err err_val, const char *panic_msg)
{
if (!err) {
guf_panic(err_val, panic_msg);
} else {
*err = err_val;
}
}
static inline void guf_err_set_if_not_null(guf_err *err, guf_err err_val)
{
if (err) {
*err = err_val;
}
}
#endif
#ifdef GUF_INIT
#undef GUF_INIT
static const char *guf_err_type_str[] = {
[GUF_ERR_NONE] = "Not an error",
[GUF_ERR_UNSPECIFIED] = "Error",
[GUF_ERR_ALLOC_FAIL] = "Allocation error",
[GUF_ERR_NULL_PTR] = "Null pointer dereference",
[GUF_ERR_DOMAIN] = "Domain error",
[GUF_ERR_INT_OVERFLOW] = "Integer overflow",
[GUF_ERR_DIV_BY_ZERO] = "Division by zero",
[GUF_ERR_IDX_RANGE] = "Index out of range",
[GUF_ERR_INVALID_ARG] = "Invalid argument",
[GUF_ERR_NOT_FOUND] = "Not found error",
[GUF_ERR_ALREADY_EXISTS] = "Already exists error",
[GUF_ERR_RUNTIME] = "Runtime error",
[GUF_ERR_LOGIC] = "Logic error",
[GUF_ERR_ASSERT_FAIL] = "Assertion failed"
};
extern void guf_panic(guf_err err, const char *msg)
{
if (!guf_global_panic_handler) {
fputs("libguf panic (note: guf_global_panic_handler is NULL)\n", stderr);
if (msg) {
fputs(": ", stderr);
fputs(msg, stderr);
}
abort();
}
guf_global_panic_handler(err, msg);
abort(); // Just in case...
}
extern void guf_set_global_panic_handler(guf_panic_handler_fn panic_handler)
{
guf_global_panic_handler = panic_handler;
}
extern const char *guf_err_to_str(guf_err err)
{
if (GUF_ARR_SIZE(guf_err_type_str) != GUF_ERR_TYPES_NUM) {
fputs("libguf warning (in guf_assert.h): array size of guf_err_type_str != GUF_ERR_TYPES_NUM", stderr);
}
if (err < 0 || err >= GUF_ERR_TYPES_NUM) {
return "Invalid error code";
} else {
if (err > GUF_ARR_SIZE(guf_err_type_str)) {
return "Invalid error string";
}
return guf_err_type_str[err];
}
}
extern void guf_panic_handler_default(guf_err err, const char *msg)
{
fputs("libguf panic: ", stderr);
fputs(guf_err_to_str(err), stderr);
fputc(' ', stderr);
if (msg && err != GUF_ERR_NONE) {
fputs(msg, stderr);
}
fputc('\n', stderr);
#ifndef NDEBUG
GUF_DEBUG_BREAK_CODE;
#endif
abort();
}
guf_panic_handler_fn guf_global_panic_handler = guf_panic_handler_default;
#endif

130
libguf/src/guf_common.h Executable file
View File

@ -0,0 +1,130 @@
/*
is parametrized: no
*/
#ifndef GUF_COMMON_H
#define GUF_COMMON_H
#include <stdlib.h>
#include <stdbool.h>
#include <limits.h>
#include <inttypes.h>
#include <stddef.h>
#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

81
libguf/src/guf_cstr.h Executable file
View File

@ -0,0 +1,81 @@
/*
is parametrized: no
*/
#ifndef GUF_CSTR_H
#define GUF_CSTR_H
#include <string.h>
#include "guf_common.h"
#include "guf_assert.h"
#include "guf_hash.h"
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

1060
libguf/src/guf_dbuf.h Executable file

File diff suppressed because it is too large Load Diff

1053
libguf/src/guf_dict.h Executable file

File diff suppressed because it is too large Load Diff

82
libguf/src/guf_hash.h Executable file
View File

@ -0,0 +1,82 @@
/*
is parametrized: no (but recieves GUF_HASH_32_BIT from guf_common.h to set guf_hash_size_t depending on platform)
*/
#if defined(GUF_HASH_IMPL_STATIC)
#define GUF_HASH_KWRDS static
#else
#define GUF_HASH_KWRDS
#endif
#ifndef GUF_HASH_H
#define GUF_HASH_H
#include "guf_common.h"
/*
FNV-1a (32-bit and 64-bit) hash functions.
Generally, you should always call csr_hash with GUF_HASH_INIT as the hash argument, unless you want to create "chains" of hashes.
cf. http://www.isthe.com/chongo/tech/comp/fnv/ (last retrieved: 2023-11-30)
*/
#define GUF_HASH32_INIT UINT32_C(2166136261)
#define GUF_HASH64_INIT UINT64_C(14695981039346656037)
GUF_HASH_KWRDS uint_least32_t guf_hash32(const void *data, ptrdiff_t num_bytes, uint_least32_t hash); // FNV-1a (32 bit)
GUF_HASH_KWRDS uint_least64_t guf_hash64(const void *data, ptrdiff_t num_bytes, uint_least64_t hash); // FNV-1a (64 bit)
#ifdef GUF_HASH_32_BIT
typedef uint_least32_t guf_hash_size_t;
#define GUF_HASH_INIT GUF_HASH32_INIT
#define GUF_HASH_MAX GUF_UINT32_MAX
static inline guf_hash_size_t guf_hash(const void *data, ptrdiff_t num_bytes, guf_hash_size_t hash) {
return guf_hash32(data, num_bytes, hash);
}
#else
typedef uint_least64_t guf_hash_size_t;
#define GUF_HASH_INIT GUF_HASH64_INIT
#define GUF_HASH_MAX GUF_UINT64_MAX
static inline guf_hash_size_t guf_hash(const void *data, ptrdiff_t num_bytes, guf_hash_size_t hash) {
return guf_hash64(data, num_bytes, hash);
}
#endif
#endif
#if defined(GUF_HASH_IMPL) || defined(GUF_HASH_IMPL_STATIC)
#include "guf_assert.h"
GUF_HASH_KWRDS uint_least32_t guf_hash32(const void *data, ptrdiff_t num_bytes, uint_least32_t hash)
{
hash = GUF_UWRAP_32(hash);
GUF_ASSERT_RELEASE(data);
GUF_ASSERT_RELEASE(num_bytes >= 0);
const unsigned char *data_bytes = (const unsigned char*)data; // This does not break strict-aliasing rules I think...
const uint_least32_t FNV_32_PRIME = UINT32_C(16777619);
for (ptrdiff_t i = 0; i < num_bytes; ++i) {
hash = GUF_UWRAP_32(1u * hash ^ data_bytes[i]);
hash = GUF_UWRAP_32(1u * hash * FNV_32_PRIME);
}
return hash;
}
GUF_HASH_KWRDS uint_least64_t guf_hash64(const void *data, ptrdiff_t num_bytes, uint_least64_t hash)
{
hash = GUF_UWRAP_64(hash);
GUF_ASSERT_RELEASE(data);
GUF_ASSERT_RELEASE(num_bytes >= 0);
const unsigned char *data_bytes = (const unsigned char*)data; // This does not break strict-aliasing rules I think...
const uint_least64_t FNV_64_PRIME = UINT64_C(1099511628211);
for (ptrdiff_t i = 0; i < num_bytes; ++i) {
hash = GUF_UWRAP_64(1u * hash ^ data_bytes[i]);
hash = GUF_UWRAP_64(1u * hash * FNV_64_PRIME);
}
return hash;
}
#undef GUF_HASH_IMPL
#undef GUF_HASH_IMPL_STATIC
#endif /* endif GUF_IMPL/GUF_IMPL_STATIC */
#undef GUF_HASH_KWRDS

276
libguf/src/guf_id_pool.h Executable file
View File

@ -0,0 +1,276 @@
/*
is parametrized: yes
*/
#if defined(GUF_ID_POOL_IMPL_STATIC)
#define GUF_ID_POOL_KWRDS static
#else
#define GUF_ID_POOL_KWRDS
#endif
#ifndef GUF_ID_POOL_H
#define GUF_ID_POOL_H
#include "guf_common.h"
#include "guf_assert.h"
#define GUF_ID_I32_NULL (-1)
#define GUF_ID_I16_NULL (-1)
#define GUF_ID_I8_NULL (-1)
#define GUF_ID_U32_NULL (GUF_UINT32_MAX)
#define GUF_ID_U16_NULL (GUF_UINT16_MAX)
#define GUF_ID_U8_NULL (GUF_UINT8_MAX)
#endif
/*
ID-pool which allows for allocating and freeing unique IDs (useful when implementing object pools):
cf. https://github.com/erincatto/box2d/blob/main/src/id_pool.c (last-retrieved 2025-03-17)
*/
// // debug beg
// #define GUF_ID_POOL_IMPL_STATIC
// #define GUF_ID_POOL_i32
// // debug end
#if defined(GUF_ID_POOL_i32)
#undef GUF_ID_POOL_i32
#define GUF_ID_POOL_T int_least32_t
#define GUF_ID_POOL_T_MAX GUF_INT32_MAX
#define GUF_ID_POOL_NULL GUF_ID_I32_NULL
#ifndef GUF_ID_POOL_NAME
#define GUF_ID_POOL_NAME guf_idpool_i32
#endif
#elif defined(GUF_ID_POOL_i16)
#undef GUF_ID_POOL_i16
#define GUF_ID_POOL_T int_least16_t
#define GUF_ID_POOL_T_MAX GUF_INT16_MAX
#define GUF_ID_POOL_NULL GUF_ID_I16_NULL
#ifndef GUF_ID_POOL_NAME
#define GUF_ID_POOL_NAME guf_idpool_i16
#endif
#elif defined(GUF_ID_POOL_i8)
#undef GUF_ID_POOL_i8
#define GUF_ID_POOL_T int_least8_t
#define GUF_ID_POOL_T_MAX GUF_INT8_MAX
#define GUF_ID_POOL_NULL GUF_ID_I8_NULL
#ifndef GUF_ID_POOL_NAME
#define GUF_ID_POOL_NAME guf_idpool_i8
#endif
#elif defined(GUF_ID_POOL_u32)
#undef GUF_ID_POOL_u32
#define GUF_ID_POOL_T uint_least32_t
#define GUF_ID_POOL_T_MAX (GUF_UINT32_MAX - 1)
#define GUF_ID_POOL_NULL GUF_ID_U32_NULL
#ifndef GUF_ID_POOL_NAME
#define GUF_ID_POOL_NAME guf_idpool_u32
#endif
#elif defined(GUF_ID_POOL_u16)
#undef GUF_ID_POOL_u16
#define GUF_ID_POOL_T uint_least16_t
#define GUF_ID_POOL_T_MAX (GUF_UINT16_MAX - 1)
#define GUF_ID_POOL_NULL GUF_ID_U16_NULL
#ifndef GUF_ID_POOL_NAME
#define GUF_ID_POOL_NAME guf_idpool_u16
#endif
#elif defined(GUF_ID_POOL_u8)
#undef GUF_ID_POOL_u8
#define GUF_ID_POOL_T uint_least8_t
#define GUF_ID_POOL_T_MAX (GUF_UINT8_MAX - 1)
#define GUF_ID_POOL_NULL GUF_ID_U8_NULL
#ifndef GUF_ID_POOL_NAME
#define GUF_ID_POOL_NAME guf_idpool_u8
#endif
#else
#error "Must define GUF_ID_POOL_i32, GUF_ID_POOL_i16, GUF_ID_POOL_i8, GUF_ID_POOL_u32, GUF_ID_POOL_u16, or GUF_ID_POOL_u8"
#endif
#define GUF_ID_POOL_DBUF GUF_CAT(GUF_ID_POOL_NAME, _id_dbuf)
#define GUF_DBUF_NAME GUF_ID_POOL_DBUF
#define GUF_T GUF_ID_POOL_T
#define GUF_T_IS_INTEGRAL_TYPE
#define GUF_DBUF_ONLY_TYPES
#include "guf_dbuf.h"
#ifdef GUF_ID_POOL_ONLY_TYPES
#undef GUF_ID_POOL_ONLY_TYPES
typedef struct GUF_ID_POOL_NAME {
GUF_ID_POOL_DBUF free_id_dbuf;
GUF_ID_POOL_T next_id;
} GUF_ID_POOL_NAME;
#else
#ifndef GUF_ID_POOL_IMPL
typedef struct GUF_ID_POOL_NAME {
GUF_ID_POOL_DBUF free_id_dbuf; // Stores all the freed ids to reuse.
GUF_ID_POOL_T next_id; // The next-id to assign in case we don't have any freed ids to reuse.
} GUF_ID_POOL_NAME;
GUF_ID_POOL_NAME *GUF_CAT(GUF_ID_POOL_NAME, _try_init)(GUF_ID_POOL_NAME *pool, ptrdiff_t initial_cap, guf_allocator *allocator, guf_err *err);
GUF_ID_POOL_NAME *GUF_CAT(GUF_ID_POOL_NAME, _init)(GUF_ID_POOL_NAME *pool, ptrdiff_t initial_cap, guf_allocator *allocator);
void GUF_CAT(GUF_ID_POOL_NAME, _free)(GUF_ID_POOL_NAME *pool, void *ctx);
GUF_ID_POOL_T GUF_CAT(GUF_ID_POOL_NAME, _try_alloc_id)(GUF_ID_POOL_NAME *pool, guf_err *err);
GUF_ID_POOL_T GUF_CAT(GUF_ID_POOL_NAME, _alloc_id)(GUF_ID_POOL_NAME *pool);
void GUF_CAT(GUF_ID_POOL_NAME, _try_free_id)(GUF_ID_POOL_NAME *pool, GUF_ID_POOL_T id, guf_err *err);
void GUF_CAT(GUF_ID_POOL_NAME, _free_id)(GUF_ID_POOL_NAME *pool, GUF_ID_POOL_T id);
GUF_ID_POOL_NAME ptrdiff_t GUF_CAT(GUF_ID_POOL_NAME, _num_allocated)(const GUF_ID_POOL_NAME *pool); // TODO: test
#endif
#if defined(GUF_ID_POOL_IMPL) || defined(GUF_ID_POOL_IMPL_STATIC)
#define GUF_DBUF_NAME GUF_ID_POOL_DBUF
#define GUF_T GUF_ID_POOL_T
#define GUF_T_IS_INTEGRAL_TYPE
#define GUF_DBUF_WITHOUT_TYPES
#define GUF_DBUF_IMPL_STATIC
#include "guf_dbuf.h"
static bool GUF_CAT(GUF_ID_POOL_NAME, _is_valid)(const GUF_ID_POOL_NAME *pool)
{
if (!pool) {
return false;
}
bool valid_next_id = (pool->next_id == GUF_ID_POOL_NULL) || (pool->next_id >= 0 && pool->next_id <= GUF_ID_POOL_T_MAX);
bool valid_bufsize = ((pool->free_id_dbuf.size <= (ptrdiff_t)pool->next_id) || (pool->next_id == GUF_ID_POOL_NULL)) && (size_t)pool->free_id_dbuf.size <= (size_t)GUF_ID_POOL_T_MAX + 1;
return valid_next_id && valid_bufsize && GUF_CAT(GUF_ID_POOL_DBUF, _valid)(&pool->free_id_dbuf) && pool->free_id_dbuf.size >= 0;
}
GUF_ID_POOL_NAME ptrdiff_t GUF_CAT(GUF_ID_POOL_NAME, _num_allocated)(const GUF_ID_POOL_NAME *pool)
{
GUF_ASSERT((ptrdiff_t)pool->next_id >= free_id_dbuf.size)
return pool->next_id - free_id_dbuf.size;
}
GUF_ID_POOL_NAME *GUF_CAT(GUF_ID_POOL_NAME, _try_init)(GUF_ID_POOL_NAME *pool, ptrdiff_t initial_cap, guf_allocator *allocator, guf_err *err)
{
if (!pool) {
guf_err_set_or_panic(err, GUF_ERR_NULL_PTR, GUF_ERR_MSG("in guf_idpool_try_init: pool is NULL"));
return NULL;
} else if (!allocator || !allocator->alloc || !allocator->realloc || !allocator->free) {
guf_err_set_or_panic(err, GUF_ERR_NULL_PTR, GUF_ERR_MSG("in guf_idpool_try_init: allocator is NULL"));
return NULL;
} else if (initial_cap < 0) {
guf_err_set_or_panic(err, GUF_ERR_INVALID_ARG, GUF_ERR_MSG("in guf_idpool_try_init: initial_cap is < 0"));
return NULL;
}
pool->free_id_dbuf = {0};
GUF_CAT(GUF_ID_POOL_DBUF, _try_init)(&pool->free_id_dbuf, initial_cap, allocator, err);
if (err && *err != GUF_ERR_NONE) {
guf_err_set_or_panic(err, *err, GUF_ERR_MSG("in guf_idpool_try_init: failed to allocate dbuf"));
return NULL;
}
pool->next_id = 0;
guf_err_set_if_not_null(err, GUF_ERR_NONE);
return pool;
}
GUF_ID_POOL_NAME *GUF_CAT(GUF_ID_POOL_NAME, _init)(GUF_ID_POOL_NAME *pool, ptrdiff_t initial_cap, guf_allocator *allocator)
{
return GUF_CAT(GUF_ID_POOL_NAME, _try_init)(pool, initial_cap, allocator, NULL);
}
void GUF_CAT(GUF_ID_POOL_NAME, _free)(GUF_ID_POOL_NAME *pool, void *ctx)
{
(void)ctx;
if (!pool) {
return;
}
GUF_ASSERT(GUF_CAT(GUF_ID_POOL_NAME, _is_valid)(pool));
GUF_CAT(GUF_ID_POOL_DBUF, _free)(&pool->free_id_dbuf, NULL);
pool->free_id_dbuf = {0};
pool->next_id = 0;
}
GUF_ID_POOL_T GUF_CAT(GUF_ID_POOL_NAME, _try_alloc_id)(GUF_ID_POOL_NAME *pool, guf_err *err)
{
GUF_ASSERT(pool);
GUF_ASSERT(GUF_CAT(GUF_ID_POOL_NAME, _is_valid)(pool));
if (pool->free_id_dbuf.size > 0) {
GUF_ID_POOL_T id = *GUF_CAT(GUF_ID_POOL_DBUF, _back)(&pool->free_id_dbuf);
GUF_CAT(GUF_ID_POOL_DBUF, _pop)(&pool->free_id_dbuf);
GUF_ASSERT(id != GUF_ID_POOL_NULL && id <= GUF_ID_POOL_T_MAX);
guf_err_set_if_not_null(err, GUF_ERR_NONE);
return id;
}
GUF_ID_POOL_T id = pool->next_id;
if (id == GUF_ID_POOL_T_MAX || id == GUF_ID_POOL_NULL) {
pool->next_id = GUF_ID_POOL_NULL;
} else {
pool->next_id += 1;
}
if (id == GUF_ID_POOL_NULL) {
guf_err_set_or_panic(err, GUF_ERR_INT_OVERFLOW, GUF_ERR_MSG("in guf_idpool_try_alloc_id: Out of ids."));
} else {
guf_err_set_if_not_null(err, GUF_ERR_NONE);
}
return id;
}
GUF_ID_POOL_T GUF_CAT(GUF_ID_POOL_NAME, _alloc_id)(GUF_ID_POOL_NAME *pool)
{
return GUF_CAT(GUF_ID_POOL_NAME, _try_alloc_id)(pool, NULL);
}
void GUF_CAT(GUF_ID_POOL_NAME, _try_free_id)(GUF_ID_POOL_NAME *pool, GUF_ID_POOL_T id, guf_err *err)
{
GUF_ASSERT(pool);
GUF_ASSERT(GUF_CAT(GUF_ID_POOL_NAME, _is_valid)(pool));
if (id == GUF_ID_POOL_NULL) { // ID is NULL.
guf_err_set_or_panic(err, GUF_ERR_IDX_RANGE, GUF_ERR_MSG("in guf_idpool_try_free_id: id is NULL"));
return;
} else if (id < 0 || id >= pool->next_id) { // ID is beyond the allocated range.
guf_err_set_or_panic(err, GUF_ERR_IDX_RANGE, GUF_ERR_MSG("in guf_idpool_try_free_id: id < 0 or id >= pool->next_id"));
return;
} else if (pool->next_id == 0) { // There haven't been any IDs allocated yet.
GUF_ASSERT(pool->free_id_dbuf.size == 0);
guf_err_set_or_panic(err, GUF_ERR_IDX_RANGE, GUF_ERR_MSG("in guf_idpool_try_free_id: pool->next_id == 0"));
return;
}
/*
// NOTE: https://github.com/erincatto/box2d/issues/893 (last-retrieved 2025-04-09)
if (id == pool->next_id - 1) { // Optimisation in case we remove the largest allocated id so far
pool->next_id -= 1;
guf_err_set_if_not_null(err, GUF_ERR_NONE);
return;
}
*/
GUF_CAT(GUF_ID_POOL_DBUF, _try_push_val)(&pool->free_id_dbuf, id, err);
if (err && *err != GUF_ERR_NONE) {
guf_err_set_or_panic(err, *err, GUF_ERR_MSG("in guf_idpool_try_free_id: failed to push id"));
} else {
guf_err_set_if_not_null(err, GUF_ERR_NONE);
}
}
void GUF_CAT(GUF_ID_POOL_NAME, _free_id)(GUF_ID_POOL_NAME *pool, GUF_ID_POOL_T id)
{
GUF_CAT(GUF_ID_POOL_NAME, _try_free_id)(pool, id, NULL);
}
#endif /* end impl */
#endif /* end only-types */
#undef GUF_ID_POOL_KWRDS
#undef GUF_ID_POOL_IMPL
#undef GUF_ID_POOL_IMPL_STATIC
#undef GUF_ID_POOL_T
#undef GUF_ID_POOL_T_MAX
#undef GUF_ID_POOL_NULL
#undef GUF_ID_POOL_NAME

17
libguf/src/guf_init.h Executable file
View File

@ -0,0 +1,17 @@
/*
is parametrized: no, but must to be implemented once (libguf always requires implementing guf_assert and guf_hash)
*/
#ifndef GUF_INIT_H
#define GUF_INIT_H
#include "guf_common.h"
// Set up the global panic handler.
#define GUF_INIT
#include "guf_assert.h"
#define GUF_HASH_IMPL
#include "guf_hash.h"
#endif

891
libguf/src/guf_linalg.h Executable file
View File

@ -0,0 +1,891 @@
/*
is parametrized: no
*/
#if defined(GUF_LINALG_IMPL_STATIC)
#define GUF_LINALG_KWRDS static
#else
#define GUF_LINALG_KWRDS
#endif
#ifndef GUF_LINALG_H
#define GUF_LINALG_H
#include "guf_assert.h"
#include "guf_math.h"
#define GUF_QUAT_TO_EULER_EPS (1e-6f)
#define GUF_QUAT_TO_AXIS_ANGLE_EPS (1e-6f)
// #define GUF_QUAT_NORMALISE_EPS (1e-6f)
typedef struct guf_vec2 {
float x, y;
} guf_vec2;
typedef struct guf_vec3 {
float x, y, z;
} guf_vec3;
typedef struct guf_vec4 {
float x, y, z, w;
} guf_vec4;
// Row-major-matrix (vectors "are" column-vectors; correct multiplication order: mat * vec)
// (e.g. the x-basis vector is (guf_vec4f){.x = m[0][0], .y = m[1][0], .z = m[2][0], .w = m[3][0]})
typedef struct guf_mat4x4 {
float data[4][4];
} guf_mat4x4;
typedef struct guf_mat3x3 {
float data[3][3];
} guf_mat3x3;
typedef struct guf_quaternion {
float x, y, z; // Vectorial (imaginary) part.
float w; // Scalar (real) part.
} guf_quaternion;
// Convention: Tait-Bryan angles
// (order yaw-pitch-roll; intrinsic rotation; active rotation; right handed coordinate system)
typedef struct guf_euler_angles {
float yaw, pitch, roll;
} guf_euler_angles;
typedef struct guf_axis_angle {
guf_vec3 axis;
float angle;
} guf_axis_angle;
GUF_LINALG_KWRDS void guf_mat4x4_init_zero(guf_mat4x4 *m);
GUF_LINALG_KWRDS void guf_mat4x4_init_identity(guf_mat4x4 *m);
GUF_LINALG_KWRDS void guf_mat4x4_init_with_basis(guf_mat4x4 *m, guf_vec3 x_basis, guf_vec3 y_basis, guf_vec3 z_basis);
GUF_LINALG_KWRDS void guf_mat4x4_init_from_quaternion(guf_mat4x4 *rotmat, guf_quaternion q);
GUF_LINALG_KWRDS void guf_mat3x3_init_zero(guf_mat3x3 *m);
GUF_LINALG_KWRDS void guf_mat3x3_init_identity(guf_mat3x3 *m);
GUF_LINALG_KWRDS void guf_mat3x3_init_with_basis(guf_mat3x3 *m, guf_vec3 x_basis, guf_vec3 y_basis, guf_vec3 z_basis);
GUF_LINALG_KWRDS void guf_mat3x3_init_from_quaternion(guf_mat3x3 *rotmat, guf_quaternion q);
#ifdef __cplusplus
// C++ has no restrict keyword like C99...
GUF_LINALG_KWRDS void guf_mat4x4_transposed(const guf_mat4x4 *m, guf_mat4x4 *transposed);
#else
GUF_LINALG_KWRDS void guf_mat4x4_transposed(const guf_mat4x4 *m, guf_mat4x4 * restrict transposed);
#endif
#ifdef __cplusplus
// C++ has no restrict keyword like C99...
GUF_LINALG_KWRDS void guf_mat3x3_transposed(const guf_mat3x3 *m, guf_mat3x3 *transposed);
#else
GUF_LINALG_KWRDS void guf_mat3x3_transposed(const guf_mat3x3 *m, guf_mat3x3 * restrict transposed);
#endif
#ifdef __cplusplus
// C++ has no restrict keyword like C99...
GUF_LINALG_KWRDS void guf_mat4x4_mul(const guf_mat4x4 *a, const guf_mat4x4 *b, guf_mat4x4 *result);
#else
GUF_LINALG_KWRDS void guf_mat4x4_mul(const guf_mat4x4 *a, const guf_mat4x4 *b, guf_mat4x4 * restrict result);
#endif
GUF_LINALG_KWRDS bool guf_mat4x4_inverted(const guf_mat4x4 *m, guf_mat4x4 *inverted); // Using Gaussian elemination/row-reduction.
GUF_LINALG_KWRDS bool guf_mat3x3_inverted(const guf_mat3x3 *m, guf_mat3x3 *inverted); // Using a faster approach from wikipedia I don't get.
#ifdef __cplusplus
// C++ has no restrict keyword like C99...
GUF_LINALG_KWRDS void guf_mat3x3_mul(const guf_mat3x3 *a, const guf_mat3x3 *b, guf_mat3x3 *result);
#else
GUF_LINALG_KWRDS void guf_mat3x3_mul(const guf_mat3x3 *a, const guf_mat3x3 *b, guf_mat3x3 * restrict result);
#endif
static inline void guf_mat4x4_set_trans(guf_mat4x4 *m, guf_vec3 trans)
{
GUF_ASSERT(m);
m->data[0][3] = trans.x;
m->data[1][3] = trans.y;
m->data[2][3] = trans.z;
}
GUF_LINALG_KWRDS void guf_mat4x4_set_rotmat(guf_mat4x4 *m, const guf_mat3x3 *rotmat_3x3);
GUF_LINALG_KWRDS bool guf_mat4x4_is_identity(const guf_mat4x4 *m, float eps);
GUF_LINALG_KWRDS bool guf_mat3x3_is_identity(const guf_mat3x3 *m, float eps);
GUF_LINALG_KWRDS bool guf_mat4x4_nearly_equal(const guf_mat4x4 *a, const guf_mat4x4 *b, float rel_tol, float abs_tol);
GUF_LINALG_KWRDS bool guf_mat3x3_nearly_equal(const guf_mat3x3 *a, const guf_mat3x3 *b, float rel_tol, float abs_tol);
GUF_LINALG_KWRDS guf_vec3 guf_vec3_transformed(const guf_mat4x4 *mat, const guf_vec3 *v);
GUF_LINALG_KWRDS guf_vec4 guf_vec4_transformed(const guf_mat4x4 *mat, const guf_vec4 *v);
static inline guf_vec2 guf_vec2_add(guf_vec2 a, guf_vec2 b)
{
guf_vec2 sum = {
.x = a.x + b.x,
.y = a.y + b.y
};
return sum;
}
static inline guf_vec2 guf_vec2_sub(guf_vec2 a, guf_vec2 b)
{
guf_vec2 diff = {
.x = a.x - b.x,
.y = a.y - b.y
};
return diff;
}
static inline guf_vec2 guf_vec2_scaled(guf_vec2 a, float scale)
{
guf_vec2 s = {.x = a.x * scale, .y = a.y * scale};
return s;
}
static inline guf_vec3 guf_vec3_add(guf_vec3 a, guf_vec3 b)
{
guf_vec3 sum = {
.x = a.x + b.x,
.y = a.y + b.y,
.z = a.z + b.z
};
return sum;
}
static inline guf_vec3 guf_vec3_sub(guf_vec3 a, guf_vec3 b)
{
guf_vec3 diff = {
.x = a.x - b.x,
.y = a.y - b.y,
.z = a.z - b.z
};
return diff;
}
static inline guf_vec3 guf_vec3_scaled(guf_vec3 a, float scale)
{
guf_vec3 s = {.x = a.x * scale, .y = a.y * scale, .z = a.z * scale};
return s;
}
static inline float guf_vec2_mag(guf_vec2 v) {return sqrtf(v.x * v.x + v.y * v.y);}
static inline float guf_vec3_mag(guf_vec3 v) {return sqrtf(v.x * v.x + v.y * v.y + v.z * v.z);}
static inline float guf_vec2_mag_squared(guf_vec2 v) {return v.x * v.x + v.y * v.y;}
static inline float guf_vec3_mag_squared(guf_vec3 v) {return v.x * v.x + v.y * v.y + v.z * v.z;}
static inline float guf_vec2_dot(guf_vec2 a, guf_vec2 b) {return a.x * b.x + a.y * b.y;}
static inline float guf_vec3_dot(guf_vec3 a, guf_vec3 b) {return a.x * b.x + a.y * b.y + a.z * b.z;}
static inline guf_vec3 guf_vec3_cross(guf_vec3 a, guf_vec3 b)
{
guf_vec3 cross = {
.x = a.y * b.z - a.z * b.y,
.y = a.z * b.x - a.x * b.z,
.z = a.x * b.y - a.y * b.x
};
return cross;
}
static inline float guf_vec2_perpdot(guf_vec2 a, guf_vec2 b)
{
return a.x * b.y - a.y * b.x; // The z-component of a 3d cross-product of two vectors in a plane.
}
GUF_LINALG_KWRDS guf_vec2 *guf_vec2_normalise(guf_vec2 *v);
GUF_LINALG_KWRDS guf_vec3 *guf_vec3_normalise(guf_vec3 *v);
static inline guf_vec2 guf_vec2_normalised(guf_vec2 v) {return *guf_vec2_normalise(&v);}
static inline guf_vec3 guf_vec3_normalised(guf_vec3 v) {return *guf_vec3_normalise(&v);}
GUF_LINALG_KWRDS guf_vec3 guf_vec4_to_vec3(guf_vec4 v);
static inline void guf_quaternion_init_identity(guf_quaternion *q)
{
GUF_ASSERT(q);
q->w = 1;
q->x = q->y = q->z = 0;
}
static inline float guf_quaternion_mag(guf_quaternion q)
{
return sqrtf(q.w * q.w + q.x * q.x + q.y * q.y + q.z * q.z);
}
static inline float guf_quaternion_mag_squared(guf_quaternion q)
{
return q.w * q.w + q.x * q.x + q.y * q.y + q.z * q.z;
}
GUF_LINALG_KWRDS guf_quaternion *guf_quaternion_normalise(guf_quaternion *q);
static inline guf_quaternion guf_quaternion_normalised(guf_quaternion q)
{
return *guf_quaternion_normalise(&q);
}
static inline guf_quaternion guf_quaternion_inverted(guf_quaternion q)
{
q.x = -q.x;
q.y = -q.y;
q.z = -q.z;
return q;
}
#ifdef __cplusplus
// C++ has no restrict keyword like C99...
GUF_LINALG_KWRDS void guf_quaternion_mul(const guf_quaternion *a, const guf_quaternion *b, guf_quaternion *result);
#else
GUF_LINALG_KWRDS void guf_quaternion_mul(const guf_quaternion *a, const guf_quaternion *b, guf_quaternion * restrict result);
#endif
GUF_LINALG_KWRDS guf_vec3 *guf_vec3_rotate_by_quat(guf_vec3 *p, const guf_quaternion *q);
static inline guf_vec3 guf_vec3_rotated_by_quat(guf_vec3 p, const guf_quaternion *q)
{
return *guf_vec3_rotate_by_quat(&p, q);
}
GUF_LINALG_KWRDS guf_quaternion guf_quaternion_from_axis_angle(float angle, guf_vec3 unit_axis);
GUF_LINALG_KWRDS guf_quaternion guf_quaternion_from_euler(guf_euler_angles euler);
GUF_LINALG_KWRDS guf_euler_angles guf_quaternion_to_euler(guf_quaternion q);
GUF_LINALG_KWRDS void guf_mat4x4_print(const guf_mat4x4 *m, FILE *outfile);
GUF_LINALG_KWRDS void guf_mat4x4_print_with_precision(const guf_mat4x4 *m, FILE *outfile, int num_frac_digits);
#endif
#if defined(GUF_LINALG_IMPL) || defined(GUF_LINALG_IMPL_STATIC)
#include <string.h>
GUF_LINALG_KWRDS void guf_mat4x4_init_zero(guf_mat4x4 *m)
{
GUF_ASSERT(m);
for (int row = 0; row < 4; ++row) {
for (int col = 0; col < 4; ++col) {
m->data[row][col] = 0;
}
}
}
GUF_LINALG_KWRDS void guf_mat3x3_init_zero(guf_mat3x3 *m)
{
GUF_ASSERT(m);
for (int row = 0; row < 3; ++row) {
for (int col = 0; col < 3; ++col) {
m->data[row][col] = 0;
}
}
}
GUF_LINALG_KWRDS void guf_mat4x4_init_identity(guf_mat4x4 *m)
{
GUF_ASSERT(m);
guf_mat4x4_init_zero(m);
for (int i = 0; i < 4; ++i) {
m->data[i][i] = 1;
}
}
GUF_LINALG_KWRDS void guf_mat3x3_init_identity(guf_mat3x3 *m)
{
GUF_ASSERT(m);
guf_mat3x3_init_zero(m);
for (int i = 0; i < 3; ++i) {
m->data[i][i] = 1;
}
}
GUF_LINALG_KWRDS void guf_mat4x4_init_with_basis(guf_mat4x4 *m, guf_vec3 x_basis, guf_vec3 y_basis, guf_vec3 z_basis)
{
GUF_ASSERT(m);
m->data[0][0] = x_basis.x;
m->data[1][0] = x_basis.y;
m->data[2][0] = x_basis.z;
m->data[3][0] = 0;
m->data[0][1] = y_basis.x;
m->data[1][1] = y_basis.y;
m->data[2][1] = y_basis.z;
m->data[3][1] = 0;
m->data[0][2] = z_basis.x;
m->data[1][2] = z_basis.y;
m->data[2][2] = z_basis.z;
m->data[3][2] = 0;
m->data[0][3] = m->data[1][3] = m->data[2][3] = 0;
m->data[3][3] = 1;
}
GUF_LINALG_KWRDS void guf_mat3x3_init_with_basis(guf_mat3x3 *m, guf_vec3 x_basis, guf_vec3 y_basis, guf_vec3 z_basis)
{
GUF_ASSERT(m);
m->data[0][0] = x_basis.x;
m->data[1][0] = x_basis.y;
m->data[2][0] = x_basis.z;
m->data[0][1] = y_basis.x;
m->data[1][1] = y_basis.y;
m->data[2][1] = y_basis.z;
m->data[0][2] = z_basis.x;
m->data[1][2] = z_basis.y;
m->data[2][2] = z_basis.z;
}
#if defined(__cplusplus) && defined(GUF_LINALG_IMPL_STATIC)
GUF_LINALG_KWRDS void guf_mat4x4_mul(const guf_mat4x4 *a, const guf_mat4x4 *b, guf_mat4x4 *result)
#else
GUF_LINALG_KWRDS void guf_mat4x4_mul(const guf_mat4x4 *a, const guf_mat4x4 *b, guf_mat4x4 * restrict result)
#endif
{
GUF_ASSERT(a && b && result);
GUF_ASSERT(a != result && b != result);
for (int i = 0; i < 4; ++i) {
for (int j = 0; j < 4; ++j) {
result->data[j][i] = 0;
for (int k = 0; k < 4; ++k) {
result->data[j][i] += a->data[j][k] * b->data[k][i];
}
}
}
}
#if defined(__cplusplus) && defined(GUF_LINALG_IMPL_STATIC)
GUF_LINALG_KWRDS void guf_mat3x3_mul(const guf_mat3x3 *a, const guf_mat3x3 *b, guf_mat3x3 *result)
#else
GUF_LINALG_KWRDS void guf_mat3x3_mul(const guf_mat3x3 *a, const guf_mat3x3 *b, guf_mat3x3 * restrict result)
#endif
{
GUF_ASSERT(a && b && result);
GUF_ASSERT(a != result && b != result);
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 3; ++j) {
result->data[j][i] = 0;
for (int k = 0; k < 3; ++k) {
result->data[j][i] += a->data[j][k] * b->data[k][i];
}
}
}
}
#if defined(__cplusplus) && defined(GUF_LINALG_IMPL_STATIC)
GUF_LINALG_KWRDS void guf_mat4x4_transposed(const guf_mat4x4 *m, guf_mat4x4 *transposed)
#else
GUF_LINALG_KWRDS void guf_mat4x4_transposed(const guf_mat4x4 *m, guf_mat4x4 * restrict transposed)
#endif
{
GUF_ASSERT(m && transposed);
GUF_ASSERT(m != transposed);
for (int i = 0; i < 4; ++i) {
for (int j = 0; j < 4; ++j) {
transposed->data[i][j] = m->data[j][i];
}
}
}
#if defined(__cplusplus) && defined(GUF_LINALG_IMPL_STATIC)
GUF_LINALG_KWRDS void guf_mat3x3_transposed(const guf_mat3x3 *m, guf_mat3x3 *transposed)
#else
GUF_LINALG_KWRDS void guf_mat3x3_transposed(const guf_mat3x3 *m, guf_mat3x3 * restrict transposed)
#endif
{
GUF_ASSERT(m && transposed);
GUF_ASSERT(m != transposed);
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 3; ++j) {
transposed->data[i][j] = m->data[j][i];
}
}
}
GUF_LINALG_KWRDS void guf_mat4x4_set_rotmat(guf_mat4x4 *m, const guf_mat3x3 *rotmat_3x3)
{
GUF_ASSERT(m && rotmat_3x3)
for (int row = 0; row < 3; ++row) {
for (int col = 0; col < 3; ++col) {
m->data[row][col] = rotmat_3x3->data[row][col];
}
}
}
GUF_LINALG_KWRDS bool guf_mat3x3_is_identity(const guf_mat3x3 *m, float eps)
{
for (int row = 0; row < 3; ++row) {
for (int col = 0; col < 3; ++col) {
if (row == col) { // Check 1-entries.
if (!guf_nearly_one_f32(m->data[row][col], eps)) {
return false;
}
} else { // Check 0-entries.
if (!guf_nearly_zero_f32(m->data[row][col], eps)) {
return false;
}
}
}
}
return true;
}
GUF_LINALG_KWRDS bool guf_mat4x4_is_identity(const guf_mat4x4 *m, float eps)
{
for (int row = 0; row < 4; ++row) {
for (int col = 0; col < 4; ++col) {
if (row == col) { // Check 1-entries.
if (!guf_nearly_one_f32(m->data[row][col], eps)) {
return false;
}
} else { // Check 0-entries.
if (!guf_nearly_zero_f32(m->data[row][col], eps)) {
return false;
}
}
}
}
return true;
}
GUF_LINALG_KWRDS bool guf_mat3x3_nearly_equal(const guf_mat3x3 *a, const guf_mat3x3 *b, float rel_tol, float abs_tol)
{
GUF_ASSERT(a && b);
for (int row = 0; row < 3; ++row) {
for (int col = 0; col < 3; ++col) {
if (!guf_isclose_f32(a->data[row][col], b->data[row][col], rel_tol, abs_tol)) {
return false;
}
}
}
return true;
}
GUF_LINALG_KWRDS bool guf_mat4x4_nearly_equal(const guf_mat4x4 *a, const guf_mat4x4 *b, float rel_tol, float abs_tol)
{
GUF_ASSERT(a && b);
for (int row = 0; row < 4; ++row) {
for (int col = 0; col < 4; ++col) {
if (!guf_isclose_f32(a->data[row][col], b->data[row][col], rel_tol, abs_tol)) {
return false;
}
}
}
return true;
}
static void guf_mat4x4_swap_rows(guf_mat4x4 *m, int row_a_idx, int row_b_idx)
{
GUF_ASSERT(row_a_idx >= 0 && row_a_idx < 4);
GUF_ASSERT(row_b_idx >= 0 && row_b_idx < 4);
if (row_a_idx == row_b_idx) {
return;
}
const size_t ROW_BYTES = sizeof(m->data[0]);
float row_a_cpy[4];
memcpy(row_a_cpy, m->data + row_a_idx, ROW_BYTES); // row_a_cpy := row_a
memcpy(m->data + row_a_idx, m->data + row_b_idx, ROW_BYTES); // row_a := row_b
memcpy(m->data + row_b_idx, row_a_cpy, ROW_BYTES); // row_b := row_a_cpy
}
/*
Find the inverse of a 4x4 matrix using gaussian elimination (if it is invertible, returns true, false otherwise).
cf. https://en.wikipedia.org/wiki/Gaussian_elimination#Finding_the_inverse_of_a_matrix (last-retrieved 2025-03-07)
Note: In the special case of square orthonormal matrices, the matrix inverse can be calculated more efficiently
and accurately using matrix transposition (e.g. valid 3x3 rotation matrices can be inverted by transposing them).
*/
GUF_LINALG_KWRDS bool guf_mat4x4_inverted(const guf_mat4x4 *m, guf_mat4x4 *inverted)
{
GUF_ASSERT(m && inverted);
guf_mat4x4_init_identity(inverted); // This matrix starts as the identity matrix and will be transfomed into the inverse.
guf_mat4x4 rref = *m; // This initial matrix will be transformed into the identity matrix if m is invertible.
const float ASSERT_EPS = 1e-3f;
const float EPS = 1e-18f;
for (int row_start = 0; row_start < 3; ++row_start) { // 1.) Try to bring into row echelon form.
int max_pivot_row = -1;
float max_pivot_abs = -INFINITY;
for (int row = row_start; row < 4; ++row) { // Find max pivot element in the current column ("Partial pivoting")
const float candidate = fabsf(rref.data[row][row_start]);
if (!guf_nearly_zero_f32(candidate, EPS) && candidate > max_pivot_abs) {
max_pivot_abs = candidate;
max_pivot_row = row;
}
}
if (max_pivot_row == -1) { // If all potential pivots were zero, m is not invertible.
GUF_ASSERT(max_pivot_abs == -INFINITY);
GUF_ASSERT(!guf_mat4x4_is_identity(&rref, ASSERT_EPS));
return false;
}
GUF_ASSERT(max_pivot_abs > 0);
// Swap the found pivot-row with the row at row_start.
guf_mat4x4_swap_rows(&rref, row_start, max_pivot_row);
guf_mat4x4_swap_rows(inverted, row_start, max_pivot_row);
const int pivot_idx = row_start; // Same for pivot_col and pivot_row (pivot is the diagonal element)
const float pivot_val = rref.data[pivot_idx][pivot_idx];
GUF_ASSERT(!isnan(pivot_val));
GUF_ASSERT(!guf_nearly_zero_f32(pivot_val, EPS));
for (int row = pivot_idx + 1; row < 4; ++row) { // Make all values below the pivot element zero.
// We want to turn rref.data[row][pivot_idx] into zero.
// pivot_val * scale = rref.data[row][pivot_idx] <=> scale = rref.data[row][pivot_idx] / pivot_val
const float scale = rref.data[row][pivot_idx] / pivot_val; // TODO: Not sure if it's more accurate to not pre-calculate the scale (like in commit dac1d159b1cfa7d6cd71236d693b2cab6218ba51 (Fri Mar 7 15:37:55 2025 +0100))
for (int col = 0; col < 4; ++col) {
if (col >= pivot_idx) {
rref.data[row][col] -= rref.data[pivot_idx][col] * scale; // Subtract the scaled pivot-row from row
} else {
GUF_ASSERT(rref.data[pivot_idx][col] == 0);
GUF_ASSERT(rref.data[row][col] == 0);
}
inverted->data[row][col] -= inverted->data[pivot_idx][col] * scale;
}
rref.data[row][pivot_idx] = 0; // -= rref.data[pivot_idx][pivot_idx] * rref.data[row][pivot_idx] / pivot_val
}
}
for (int i = 3; i >= 0; --i) { // 2.) Try to bring into *reduced* row echelon form.
if (rref.data[i][i] == 0) {
return false;
}
for (int col = 0; col < 4; ++col) { // Scale row_i to make elem[i][i] == 1
inverted->data[i][col] /= rref.data[i][i];
GUF_ASSERT((col != i) ? rref.data[i][col] == 0 : rref.data[i][col] != 0);
}
rref.data[i][i] = 1; // rref.data[i][i] / rref.data[i][i] == 1
for (int row = i - 1; row >= 0; --row) { // Make all elements in column above rref.data[i][i] zero (by adding a scaled row_i to row).
for (int col = 0; col < 4; ++col) {
inverted->data[row][col] -= inverted->data[i][col] * rref.data[row][i];
}
rref.data[row][i] = 0; // -= rref.data[i][i] * rref.data[row][i] (== 0)
}
}
GUF_ASSERT(guf_mat4x4_is_identity(&rref, ASSERT_EPS));
(void)ASSERT_EPS;
return true;
}
GUF_LINALG_KWRDS bool guf_mat3x3_inverted(const guf_mat3x3 *m, guf_mat3x3 *inverted)
{
// TODO: Test this.
GUF_ASSERT(m && inverted);
const float EPS = 1e-12f; // TODO: Not sure about this EPS.
// cf. https://en.wikipedia.org/wiki/Determinant (last-retrieved 2025-03-08)
const float det = m->data[0][0] * m->data[1][1] * m->data[2][2] +
m->data[0][1] * m->data[1][2] * m->data[2][0] +
m->data[0][2] * m->data[1][0] * m->data[2][1] -
m->data[0][1] * m->data[1][0] * m->data[2][2] -
m->data[0][0] * m->data[1][2] * m->data[2][1];
if (guf_nearly_zero_f32(det, EPS)) { // m is only invertible if the determinant != 0
return false;
}
// cf. https://en.wikipedia.org/wiki/Invertible_matrix#Inversion_of_3_%C3%97_3_matrices (last-retrieved 2025-03-08)
// (I don't understand this method of matrix inversion, but it looks fast...)
const float A = (m->data[1][1] * m->data[2][2] - m->data[1][2] * m->data[2][1]);
const float B = -(m->data[1][0] * m->data[2][2] - m->data[1][2] * m->data[2][0]);
const float C = (m->data[1][0] * m->data[2][1] - m->data[1][1] * m->data[2][0]);
const float D = -(m->data[0][1] * m->data[2][2] - m->data[0][2] * m->data[2][1]);
const float E = (m->data[0][0] * m->data[2][2] - m->data[0][2] * m->data[2][0]);
const float F = -(m->data[0][0] * m->data[2][1] - m->data[0][1] * m->data[2][0]);
const float G = (m->data[0][1] * m->data[1][2] - m->data[0][2] * m->data[1][1]);
const float H = -(m->data[0][0] * m->data[1][2] - m->data[0][2] * m->data[1][0]);
const float I = (m->data[0][0] * m->data[1][1] - m->data[0][1] * m->data[1][0]);
inverted->data[0][0] = A / det;
inverted->data[1][0] = B / det;
inverted->data[2][0] = C / det;
inverted->data[0][1] = D / det;
inverted->data[1][1] = E / det;
inverted->data[2][1] = F / det;
inverted->data[0][2] = G / det;
inverted->data[1][2] = H / det;
inverted->data[2][2] = I / det;
return true;
}
// Assumes the last row of mat is [0, 0, 0, 1] and v->w implicitly is 1 (affine transformation)
GUF_LINALG_KWRDS guf_vec3 guf_vec3_transformed(const guf_mat4x4 *mat, const guf_vec3 *v)
{
GUF_ASSERT(mat && v);
guf_vec3 v_transformed = {
.x = mat->data[0][0] * v->x + mat->data[0][1] * v->y + mat->data[0][2] * v->z + mat->data[0][3],
.y = mat->data[1][0] * v->x + mat->data[1][1] * v->y + mat->data[1][2] * v->z + mat->data[1][3],
.z = mat->data[2][0] * v->x + mat->data[2][1] * v->y + mat->data[2][2] * v->z + mat->data[2][3]
};
return v_transformed;
}
GUF_LINALG_KWRDS guf_vec4 guf_vec4_transformed(const guf_mat4x4 *mat, const guf_vec4 *v)
{
GUF_ASSERT(mat && v);
guf_vec4 v_transformed = {
.x = mat->data[0][0] * v->x + mat->data[0][1] * v->y + mat->data[0][2] * v->z + v->w * mat->data[0][3],
.y = mat->data[1][0] * v->x + mat->data[1][1] * v->y + mat->data[1][2] * v->z + v->w * mat->data[1][3],
.z = mat->data[2][0] * v->x + mat->data[2][1] * v->y + mat->data[2][2] * v->z + v->w * mat->data[2][3],
.w = mat->data[3][0] * v->x + mat->data[3][1] * v->y + mat->data[3][2] * v->z + v->w * mat->data[3][3],
};
return v_transformed;
}
GUF_LINALG_KWRDS guf_vec2 *guf_vec2_normalise(guf_vec2 *v)
{
GUF_ASSERT(v);
const float mag = guf_vec2_mag(*v);
if (mag == 0 || isnan(mag)) {
v->x = v->y = (float)INFINITY;
} else {
v->x /= mag;
v->y /= mag;
}
return v;
}
GUF_LINALG_KWRDS guf_vec3 *guf_vec3_normalise(guf_vec3 *v)
{
GUF_ASSERT(v);
const float mag = guf_vec3_mag(*v);
if (mag == 0 || isnan(mag)) {
v->x = v->y = v->z = (float)INFINITY;
} else {
v->x /= mag;
v->y /= mag;
v->z /= mag;
}
return v;
}
GUF_LINALG_KWRDS guf_vec3 guf_vec4_to_vec3(guf_vec4 v)
{
if (guf_isclose_abstol_f32(v.w, 0, 1e-21f)) { // If w is extremely close 0, we treat it as zero.
guf_vec3 inf_vec = {(float)INFINITY, (float)INFINITY, (float)INFINITY};
return inf_vec;
} else if (guf_isclose_reltol_f32(v.w, 1, 1e-9f)) { // If w is almost 1, we treat it as exactly 1.
guf_vec3 vec = {.x = v.x, .y = v.y, .z = v.z};
return vec;
} else {
guf_vec3 vec = {.x = v.x / v.w, .y = v.y / v.w, .z = v.z / v.w};
return vec;
}
}
/*
(Unit)quaternion operations:
cf. https://danceswithcode.net/engineeringnotes/quaternions/quaternions.html (last-retrieved 2025-03-06)
*/
GUF_LINALG_KWRDS guf_quaternion guf_quaternion_from_axis_angle(float angle, guf_vec3 unit_axis)
{
const float sin_halfangle = sinf(angle / 2.f);
guf_quaternion q = {
.w = cosf(angle / 2.f),
.x = unit_axis.x * sin_halfangle,
.y = unit_axis.y * sin_halfangle,
.z = unit_axis.z * sin_halfangle,
};
return q;
}
GUF_LINALG_KWRDS guf_quaternion guf_quaternion_from_euler(guf_euler_angles euler)
{
const float r = euler.roll / 2;
const float p = euler.pitch / 2;
const float y = euler.yaw / 2;
guf_quaternion q = {
.w = cosf(r) * cosf(p) * cosf(y) + sinf(r) * sinf(p) * sinf(y),
.x = sinf(r) * cosf(p) * cosf(y) - cosf(r) * sinf(p) * sinf(y),
.y = cosf(r) * sinf(p) * cosf(y) + sinf(r) * cosf(p) * sinf(y),
.z = cosf(r) * cosf(p) * sinf(y) + sinf(r) * sinf(p) * cosf(y)
};
return q;
}
GUF_LINALG_KWRDS guf_euler_angles guf_quaternion_to_euler(guf_quaternion q)
{
guf_euler_angles euler = {0};
euler.pitch = asinf(2 * (q.w * q.y - q.x * q.z));
const float half_pi = GUF_PI_F32 / 2;
if (fabsf(euler.pitch - half_pi) <= GUF_QUAT_TO_EULER_EPS) { // Gimbal lock: pitch == pi/2
euler.roll = 0;
euler.yaw = -2 * atan2f(q.x, q.w);
} else if (fabsf(euler.pitch - (-half_pi)) <= GUF_QUAT_TO_EULER_EPS) { // Gimbal lock: pitch == -pi/2
euler.roll = 0;
euler.yaw = 2 * atan2f(q.x, q.w);
} else { // No gimbal-lock
euler.roll = atan2f(2 * (q.w * q.x + q.y * q.z), q.w*q.w - q.x*q.x - q.y*q.y + q.z*q.z);
euler.yaw = atan2f(2 * (q.w * q.z + q.x * q.y), q.w*q.w + q.x*q.x - q.y*q.y - q.z*q.z);
}
return euler;
}
GUF_LINALG_KWRDS float guf_quaternion_to_axis_angle(const guf_quaternion *q, guf_vec3 *axis_result)
{
GUF_ASSERT(q);
if (fabsf(q->w - 1) <= GUF_QUAT_TO_AXIS_ANGLE_EPS) { // Singularity: q->w is very close to 1 (identity-quaternions produce no rotation).
const float angle = 0;
if (axis_result) {
axis_result->x = axis_result->y = axis_result->z = 0;
}
return angle;
}
const float angle = 2 * acosf(q->w);
if (axis_result) {
const float sin_halfangle = sinf(angle / 2.f);
axis_result->x = q->x / sin_halfangle;
axis_result->y = q->y / sin_halfangle;
axis_result->z = q->z / sin_halfangle;
}
return angle;
}
GUF_LINALG_KWRDS void guf_mat4x4_init_from_quaternion(guf_mat4x4 *rotmat, guf_quaternion q)
{
GUF_ASSERT(rotmat);
rotmat->data[0][0] = 1 - 2 * q.y * q.y - 2 * q.z * q.z;
rotmat->data[0][1] = 2 * q.x * q.y - 2 * q.w * q.z;
rotmat->data[0][2] = 2 * q.x * q.z - 2 * q.w * q.y;
rotmat->data[0][3] = 0;
rotmat->data[1][0] = 2 * q.x * q.y + 2 * q.w * q.z;
rotmat->data[1][1] = 1 - 2 * q.x * q.x - 2 * q.z * q.z;
rotmat->data[1][2] = 2 * q.y * q.z - 2 * q.w * q.x;
rotmat->data[1][3] = 0;
rotmat->data[2][0] = 2 * q.x * q.z - 2 * q.w * q.y;
rotmat->data[2][1] = 2 * q.y * q.z + 2 * q.w * q.x;
rotmat->data[2][2] = 1 - 2 * q.x * q.x - 2 * q.y * q.y;
rotmat->data[2][3] = 0;
rotmat->data[3][0] = rotmat->data[3][1] = rotmat->data[3][2] = 0;
rotmat->data[3][3] = 1;
}
GUF_LINALG_KWRDS void guf_mat3x3_init_from_quaternion(guf_mat3x3 *rotmat, guf_quaternion q)
{
GUF_ASSERT(rotmat);
rotmat->data[0][0] = 1 - 2 * q.y * q.y - 2 * q.z * q.z;
rotmat->data[0][1] = 2 * q.x * q.y - 2 * q.w * q.z;
rotmat->data[0][2] = 2 * q.x * q.z - 2 * q.w * q.y;
rotmat->data[1][0] = 2 * q.x * q.y + 2 * q.w * q.z;
rotmat->data[1][1] = 1 - 2 * q.x * q.x - 2 * q.z * q.z;
rotmat->data[1][2] = 2 * q.y * q.z - 2 * q.w * q.x;
rotmat->data[2][0] = 2 * q.x * q.z - 2 * q.w * q.y;
rotmat->data[2][1] = 2 * q.y * q.z + 2 * q.w * q.x;
rotmat->data[2][2] = 1 - 2 * q.x * q.x - 2 * q.y * q.y;
}
GUF_LINALG_KWRDS guf_quaternion *guf_quaternion_normalise(guf_quaternion *q)
{
GUF_ASSERT(q);
const float sq_mag = guf_quaternion_mag_squared(*q);
if (sq_mag == 0 || sq_mag == 1) { // Prevent div by zero and don't normalise if already normalised.
return q;
}
const float mag = sqrtf(sq_mag);
if (mag == 0) { // Just in case.
return q;
}
q->w /= mag;
q->x /= mag;
q->y /= mag;
q->z /= mag;
return q;
}
#if defined(__cplusplus) && defined(GUF_LINALG_IMPL_STATIC)
GUF_LINALG_KWRDS void guf_quaternion_mul(const guf_quaternion *a, const guf_quaternion *b, guf_quaternion *result)
#else
GUF_LINALG_KWRDS void guf_quaternion_mul(const guf_quaternion *a, const guf_quaternion *b, guf_quaternion * restrict result)
#endif
{
GUF_ASSERT(a && b && result);
GUF_ASSERT(a != result && b != result);
result->w = a->w * b->w - a->x * b->x - a->y * b->y - a->z * b->z; // (r0s0 r1s1 r2s2 r3s3)
result->x = a->w * b->x + a->x * b->w - a->y * b->z + a->z * b->y; // (r0s1 + r1s0 r2s3 + r3s2)
result->y = a->w * b->y + a->x * b->z + a->y * b->w - a->z * b->x; // (r0s2 + r1s3 + r2s0 r3s1)
result->z = a->w * b->z - a->x * b->y + a->y * b->x + a->z * b->w; // (r0s3 r1s2 + r2s1 + r3s0)
}
GUF_LINALG_KWRDS guf_vec3 *guf_vec3_rotate_by_quat(guf_vec3 *p, const guf_quaternion *q)
{
// Active rotation of the point/vector p by the quaternion q.
GUF_ASSERT(p && q);
const guf_quaternion inv_q = guf_quaternion_inverted(*q);
guf_quaternion p_quat = {.w = 0, .x = p->x, .y = p->y, .z = p->z};
// (x) active rotation: inv(q) * p_quat * q (the point is rotated with respect to the coordinate system)
// ( ) passive rotation: q * p_quat * inv(q) (the coordinate system is rotated with respect to the point)
guf_quaternion tmp;
guf_quaternion_mul(&inv_q, &p_quat, &tmp); // tmp := inv(q) * pquat
guf_quaternion_mul(&tmp, q, &p_quat); // pquat := tmp * q
p->x = p_quat.x;
p->y = p_quat.y;
p->z = p_quat.z;
return p;
}
GUF_LINALG_KWRDS void guf_mat4x4_print_with_precision(const guf_mat4x4 *m, FILE *outfile, int num_frac_digits)
{
if (!outfile) {
outfile = stdout;
}
if (!m) {
fprintf(outfile, "guf_mat4x4 NULL\n");
return;
}
int max_digits = 1;
for (int row = 0; row < 4; ++row) { // Find how many digits before the . we need.
for (int col = 0; col < 4; ++col) {
const float elem = m->data[row][col];
int add_digits = 0;
if (isnan(elem) || isinf(elem)) {
add_digits += 3;
}
if (elem == 0) {
continue;
}
const float elem_log10 = floorf(log10f(fabsf(elem)));
int digits = (int)elem_log10 + add_digits;
digits = GUF_CLAMP(digits, 0, 32);
if (digits > max_digits) {
max_digits = digits;
}
}
}
const int whole_digits = max_digits + 1;
num_frac_digits = GUF_CLAMP(num_frac_digits, 0, 32);
for (int row = 0; row < 4; ++row) {
for (int col = 0; col < 4; ++col) {
fprintf(outfile, "% *.*f ", whole_digits + num_frac_digits + 2, num_frac_digits, (double)m->data[row][col]);
}
fputc('\n', outfile);
}
fputc('\n', outfile);
}
GUF_LINALG_KWRDS void guf_mat4x4_print(const guf_mat4x4 *m, FILE *outfile)
{
const int DEFAULT_FRAC_DIGITS = 3;
guf_mat4x4_print_with_precision(m, outfile, DEFAULT_FRAC_DIGITS);
}
#undef GUF_LINALG_IMPL
#undef GUF_LINALG_IMPL_STATIC
#endif /* end impl */
#undef GUF_LINALG_KWRDS

421
libguf/src/guf_math.h Executable file
View File

@ -0,0 +1,421 @@
/*
is parametrized: no
*/
#ifndef GUF_MATH_H
#define GUF_MATH_H
#include "guf_common.h"
#include "guf_assert.h"
#include <math.h>
#include <float.h>
#define GUF_PI 3.14159265358979323846264338327950288
#define GUF_PI_F32 3.14159265358979323846264338327950288f
// Biggest float/double less than one, cf. https://stackoverflow.com/a/33832753 (last-retrieved 2025-03-05)
#define GUF_MAX_F32_LT_ONE (1.f - FLT_EPSILON/FLT_RADIX)
#define GUF_MAX_F64_LT_ONE (1.0 - DBL_EPSILON/FLT_RADIX)
// Typesafe unsigned integer wrapping functions (generated with libguf/tools/intwrap-gen.py)
static inline uint_least8_t guf_wrap8_least_u8(uint_least8_t a) { return a & GUF_UINT8_MAX; }
static inline uint_least16_t guf_wrap16_least_u16(uint_least16_t a) { return a & GUF_UINT16_MAX; }
static inline uint_least32_t guf_wrap32_least_u32(uint_least32_t a) { return a & GUF_UINT32_MAX; }
static inline uint_least64_t guf_wrap64_least_u64(uint_least64_t a) { return a & GUF_UINT64_MAX; }
static inline unsigned char guf_wrap8_uchar(unsigned char a) { return a & GUF_UINT8_MAX; } // unsigned char: >= 8 bits
static inline unsigned short guf_wrap16_ushort(unsigned short a) { return a & GUF_UINT16_MAX; } // unsigned short: >= 16 bits
static inline unsigned long guf_wrap32_ulong(unsigned long a) { return a & GUF_UINT32_MAX; } // unsigned long: >= 32 bits
static inline unsigned long long guf_wrap64_ulong_long(unsigned long long a) { return a & GUF_UINT64_MAX; } // unsigned long long: >= 64 bits
// Rotate left.
#ifdef UINT32_MAX
static inline uint32_t guf_rotl_u32(uint32_t x, int k)
{
GUF_ASSERT(k > 0);
return (1u*x << k) | (1u*x >> (32 - k));
}
#endif
#ifdef UINT64_MAX
static inline uint64_t guf_rotl_u64(uint64_t x, int k)
{
GUF_ASSERT(k > 0);
return (1u*x << k) | (1u*x >> (64 - k));
}
#endif
static inline uint_least32_t guf_rotl32_least_u32(uint_least32_t x, int k)
{
GUF_ASSERT(k > 0);
x = guf_wrap32_least_u32(x);
return guf_wrap32_least_u32( (1u*x << k) | (1u*x >> (32 - k)) );
}
static inline uint_least64_t guf_rotl64_least_u64(uint_least64_t x, int k)
{
GUF_ASSERT(k > 0);
x = guf_wrap64_least_u64(x);
return guf_wrap64_least_u64( (1u*x << k) | (1u*x >> (64 - k)) );
}
static inline unsigned long guf_rotl32_ulong(unsigned long x, int k)
{
GUF_ASSERT(k > 0);
x = guf_wrap32_ulong(x);
return guf_wrap32_ulong( (x << k) | (x >> (32 - k)) );
}
static inline unsigned long long guf_rotl64_ulong_long(unsigned long long x, int k)
{
GUF_ASSERT(k > 0);
x = guf_wrap64_ulong_long(x);
return guf_wrap64_ulong_long( (x << k) | (x >> (64 - k)) );
}
// Signed min, max, clamp functions (generated with libguf/tools/min_max_clamp-gen.py)
static inline char guf_min_char(char a, char b) { return a < b ? a : b; }
static inline char guf_max_char(char a, char b) { return a > b ? a : b; }
static inline char guf_clamp_char(char x, char min, char max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
static inline int guf_min_int(int a, int b) { return a < b ? a : b; }
static inline int guf_max_int(int a, int b) { return a > b ? a : b; }
static inline int guf_clamp_int(int x, int min, int max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
static inline long guf_min_long(long a, long b) { return a < b ? a : b; }
static inline long guf_max_long(long a, long b) { return a > b ? a : b; }
static inline long guf_clamp_long(long x, long min, long max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
static inline long long guf_min_long_long(long long a, long long b) { return a < b ? a : b; }
static inline long long guf_max_long_long(long long a, long long b) { return a > b ? a : b; }
static inline long long guf_clamp_long_long(long long x, long long min, long long max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
static inline ptrdiff_t guf_min_ptrdiff_t(ptrdiff_t a, ptrdiff_t b) { return a < b ? a : b; }
static inline ptrdiff_t guf_max_ptrdiff_t(ptrdiff_t a, ptrdiff_t b) { return a > b ? a : b; }
static inline ptrdiff_t guf_clamp_ptrdiff_t(ptrdiff_t x, ptrdiff_t min, ptrdiff_t max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
static inline int_least8_t guf_min_least_i8(int_least8_t a, int_least8_t b) { return a < b ? a : b; }
static inline int_least8_t guf_max_least_i8(int_least8_t a, int_least8_t b) { return a > b ? a : b; }
static inline int_least8_t guf_clamp_least_i8(int_least8_t x, int_least8_t min, int_least8_t max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
static inline int_least16_t guf_min_least_i16(int_least16_t a, int_least16_t b) { return a < b ? a : b; }
static inline int_least16_t guf_max_least_i16(int_least16_t a, int_least16_t b) { return a > b ? a : b; }
static inline int_least16_t guf_clamp_least_i16(int_least16_t x, int_least16_t min, int_least16_t max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
static inline int_least32_t guf_min_least_i32(int_least32_t a, int_least32_t b) { return a < b ? a : b; }
static inline int_least32_t guf_max_least_i32(int_least32_t a, int_least32_t b) { return a > b ? a : b; }
static inline int_least32_t guf_clamp_least_i32(int_least32_t x, int_least32_t min, int_least32_t max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
static inline int_least64_t guf_min_least_i64(int_least64_t a, int_least64_t b) { return a < b ? a : b; }
static inline int_least64_t guf_max_least_i64(int_least64_t a, int_least64_t b) { return a > b ? a : b; }
static inline int_least64_t guf_clamp_least_i64(int_least64_t x, int_least64_t min, int_least64_t max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
static inline float guf_min_f32(float a, float b) { return a < b ? a : b; }
static inline float guf_max_f32(float a, float b) { return a > b ? a : b; }
static inline float guf_clamp_f32(float x, float min, float max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
static inline double guf_min_f64(double a, double b) { return a < b ? a : b; }
static inline double guf_max_f64(double a, double b) { return a > b ? a : b; }
static inline double guf_clamp_f64(double x, double min, double max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
#ifdef INT8_MAX
static inline int8_t guf_min_i8(int8_t a, int8_t b) { return a < b ? a : b; }
static inline int8_t guf_max_i8(int8_t a, int8_t b) { return a > b ? a : b; }
static inline int8_t guf_clamp_i8(int8_t x, int8_t min, int8_t max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
#endif
#ifdef INT16_MAX
static inline int16_t guf_min_i16(int16_t a, int16_t b) { return a < b ? a : b; }
static inline int16_t guf_max_i16(int16_t a, int16_t b) { return a > b ? a : b; }
static inline int16_t guf_clamp_i16(int16_t x, int16_t min, int16_t max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
#endif
#ifdef INT32_MAX
static inline int32_t guf_min_i32(int32_t a, int32_t b) { return a < b ? a : b; }
static inline int32_t guf_max_i32(int32_t a, int32_t b) { return a > b ? a : b; }
static inline int32_t guf_clamp_i32(int32_t x, int32_t min, int32_t max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
#endif
#ifdef INT64_MAX
static inline int64_t guf_min_i64(int64_t a, int64_t b) { return a < b ? a : b; }
static inline int64_t guf_max_i64(int64_t a, int64_t b) { return a > b ? a : b; }
static inline int64_t guf_clamp_i64(int64_t x, int64_t min, int64_t max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
#endif
// Unsigned min, max, clamp functions (generated with libguf/tools/min_max_clamp-gen.py)
static inline unsigned char guf_min_uchar(unsigned char a, unsigned char b) { return a < b ? a : b; }
static inline unsigned char guf_max_uchar(unsigned char a, unsigned char b) { return a > b ? a : b; }
static inline unsigned char guf_clamp_uchar(unsigned char x, unsigned char min, unsigned char max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
static inline unsigned guf_min_unsigned(unsigned a, unsigned b) { return a < b ? a : b; }
static inline unsigned guf_max_unsigned(unsigned a, unsigned b) { return a > b ? a : b; }
static inline unsigned guf_clamp_unsigned(unsigned x, unsigned min, unsigned max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
static inline unsigned long guf_min_ulong(unsigned long a, unsigned long b) { return a < b ? a : b; }
static inline unsigned long guf_max_ulong(unsigned long a, unsigned long b) { return a > b ? a : b; }
static inline unsigned long guf_clamp_ulong(unsigned long x, unsigned long min, unsigned long max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
static inline unsigned long long guf_min_ulong_long(unsigned long long a, unsigned long long b) { return a < b ? a : b; }
static inline unsigned long long guf_max_ulong_long(unsigned long long a, unsigned long long b) { return a > b ? a : b; }
static inline unsigned long long guf_clamp_ulong_long(unsigned long long x, unsigned long long min, unsigned long long max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
static inline size_t guf_min_size_t(size_t a, size_t b) { return a < b ? a : b; }
static inline size_t guf_max_size_t(size_t a, size_t b) { return a > b ? a : b; }
static inline size_t guf_clamp_size_t(size_t x, size_t min, size_t max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
static inline uint_least8_t guf_min_least_u8(uint_least8_t a, uint_least8_t b) { return a < b ? a : b; }
static inline uint_least8_t guf_max_least_u8(uint_least8_t a, uint_least8_t b) { return a > b ? a : b; }
static inline uint_least8_t guf_clamp_least_u8(uint_least8_t x, uint_least8_t min, uint_least8_t max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
static inline uint_least16_t guf_min_least_u16(uint_least16_t a, uint_least16_t b) { return a < b ? a : b; }
static inline uint_least16_t guf_max_least_u16(uint_least16_t a, uint_least16_t b) { return a > b ? a : b; }
static inline uint_least16_t guf_clamp_least_u16(uint_least16_t x, uint_least16_t min, uint_least16_t max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
static inline uint_least32_t guf_min_least_u32(uint_least32_t a, uint_least32_t b) { return a < b ? a : b; }
static inline uint_least32_t guf_max_least_u32(uint_least32_t a, uint_least32_t b) { return a > b ? a : b; }
static inline uint_least32_t guf_clamp_least_u32(uint_least32_t x, uint_least32_t min, uint_least32_t max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
static inline uint_least64_t guf_min_least_u64(uint_least64_t a, uint_least64_t b) { return a < b ? a : b; }
static inline uint_least64_t guf_max_least_u64(uint_least64_t a, uint_least64_t b) { return a > b ? a : b; }
static inline uint_least64_t guf_clamp_least_u64(uint_least64_t x, uint_least64_t min, uint_least64_t max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
#ifdef UINT8_MAX
static inline uint8_t guf_min_u8(uint8_t a, uint8_t b) { return a < b ? a : b; }
static inline uint8_t guf_max_u8(uint8_t a, uint8_t b) { return a > b ? a : b; }
static inline uint8_t guf_clamp_u8(uint8_t x, uint8_t min, uint8_t max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
#endif
#ifdef UINT16_MAX
static inline uint16_t guf_min_u16(uint16_t a, uint16_t b) { return a < b ? a : b; }
static inline uint16_t guf_max_u16(uint16_t a, uint16_t b) { return a > b ? a : b; }
static inline uint16_t guf_clamp_u16(uint16_t x, uint16_t min, uint16_t max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
#endif
#ifdef UINT32_MAX
static inline uint32_t guf_min_u32(uint32_t a, uint32_t b) { return a < b ? a : b; }
static inline uint32_t guf_max_u32(uint32_t a, uint32_t b) { return a > b ? a : b; }
static inline uint32_t guf_clamp_u32(uint32_t x, uint32_t min, uint32_t max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
#endif
#ifdef UINT64_MAX
static inline uint64_t guf_min_u64(uint64_t a, uint64_t b) { return a < b ? a : b; }
static inline uint64_t guf_max_u64(uint64_t a, uint64_t b) { return a > b ? a : b; }
static inline uint64_t guf_clamp_u64(uint64_t x, uint64_t min, uint64_t max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
#endif
// abs functions with signed result (can fail/panic for abs(INT_TYPE_MIN) for platforms with two's complement signed ints; C2X for example guarantees two's complement)
// static inline int guf_abs_int(int x) {if (x >= 0) {return x;} GUF_ASSERT_RELEASE(-INT_MAX == INT_MIN || x > INT_MIN); return -x;} // I would not drink that...
// static inline int8_t guf_abs_i8 (int8_t x) {if (x >= 0) {return x;} GUF_ASSERT_RELEASE(-INT8_MAX == INT8_MIN || x > INT8_MIN); return -x;}
// static inline int16_t guf_abs_i16(int16_t x) {if (x >= 0) {return x;} GUF_ASSERT_RELEASE(-INT16_MAX == INT16_MIN || x > INT16_MIN); return -x;}
// static inline int32_t guf_abs_i32(int32_t x) {if (x >= 0) {return x;} GUF_ASSERT_RELEASE(-INT32_MAX == INT32_MIN || x > INT32_MIN); return -x;}
// static inline int64_t guf_abs_i64(int64_t x) {if (x >= 0) {return x;} GUF_ASSERT_RELEASE(-INT64_MAX == INT64_MIN || x > INT64_MIN); return -x;}
// static inline ptrdiff_t guf_abs_ptrdiff(ptrdiff_t x) {if (x >= 0) {return x;} GUF_ASSERT_RELEASE(-PTRDIFF_MAX == PTRDIFF_MIN || x > PTRDIFF_MIN); return -x;}
// abs functions with unsigned result functions (cannot fail)
static inline unsigned guf_uabs_int(int x) {if (x >= 0) {return x;} else if (x == INT_MIN && -INT_MAX != INT_MIN) {return (unsigned)INT_MAX + 1u;} else {return -x;}}
static inline unsigned long guf_uabs_long(long x) {if (x >= 0) {return x;} else if (x == LONG_MIN && -LONG_MAX != LONG_MIN) {return (unsigned long)LONG_MAX + 1u;} else {return -x;}}
static inline unsigned long long guf_uabs_long_long(long long x) {if (x >= 0) {return x;} else if (x == LLONG_MIN && -LLONG_MAX != LONG_MIN) {return (unsigned long long)LLONG_MAX + 1u;} else {return -x;}}
static inline size_t guf_uabs_ptrdiff_t(ptrdiff_t x) {if (x >= 0) {return x;} else if (x == PTRDIFF_MIN && -PTRDIFF_MAX != PTRDIFF_MIN) {return (size_t)PTRDIFF_MAX + 1u;} else {return -x;}}
#if defined(UINT8_MAX) && defined(INT8_MAX)
static inline uint8_t guf_uabs_i8(int8_t x) {if (x >= 0) {return x;} else if (x == INT8_MIN) {return (uint8_t)INT8_MAX + 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

3959
libguf/src/guf_math_ckdint.h Executable file

File diff suppressed because it is too large Load Diff

1007
libguf/src/guf_rand.h Executable file

File diff suppressed because it is too large Load Diff

193
libguf/src/guf_sort.h Executable file
View File

@ -0,0 +1,193 @@
/*
is parametrized: yes
*/
#ifndef GUF_SORT_H
#define GUF_SORT_H
#include "guf_common.h"
typedef enum guf_sort_opt {
GUF_SORT_ASCENDING = 0,
GUF_SORT_DESCENDING = 1
} guf_sort_opt;
#endif
#ifdef GUF_T
#ifndef GUF_FN_NAME_PREFIX
#define GUF_FN_NAME_PREFIX GUF_CAT(GUF_T, _arr)
#endif
#if defined(GUF_SORT_IMPL_STATIC)
#define GUF_SORT_KWRDS static
#else
#define GUF_SORT_KWRDS
#endif
GUF_SORT_KWRDS GUF_T *GUF_CAT(GUF_FN_NAME_PREFIX, _insertion_sort)(GUF_T *arr, ptrdiff_t n, guf_sort_opt sort_opt, int(*cmp)(const GUF_T *a, const GUF_T *b));
GUF_SORT_KWRDS GUF_T *GUF_CAT(GUF_FN_NAME_PREFIX, _merge_sort)(GUF_T *restrict arr, GUF_T *restrict arr_tmp, ptrdiff_t n, guf_sort_opt sort_opt, int(*cmp)(const GUF_T *a, const GUF_T *b));
GUF_SORT_KWRDS GUF_T *GUF_CAT(GUF_FN_NAME_PREFIX, _qsort)(GUF_T *arr, ptrdiff_t n, guf_sort_opt sort_opt, int(*cmp)(const GUF_T *a, const GUF_T *b));
GUF_SORT_KWRDS bool GUF_CAT(GUF_FN_NAME_PREFIX, _is_sorted)(GUF_T *arr, ptrdiff_t n, guf_sort_opt sort_opt, int(*cmp)(const GUF_T *a, const GUF_T *b));
#if defined(GUF_SORT_IMPL) || defined(GUF_SORT_IMPL_STATIC)
#define guf_before(a_ptr, b_ptr) (cmp ? (sort_opt == GUF_SORT_ASCENDING ? 1 : -1) * cmp(a_ptr, b_ptr) == -1 : (sort_opt == GUF_SORT_ASCENDING) ? *(a_ptr) < *(b_ptr) : *(a_ptr) > *(b_ptr))
#define guf_before_or_equal(a_ptr, b_ptr) (cmp ? (sort_opt == GUF_SORT_ASCENDING ? 1 : -1) * cmp(a_ptr, b_ptr) <= 0 : (sort_opt == GUF_SORT_ASCENDING) ? *(a_ptr) <= *(b_ptr) : *(a_ptr) >= *(b_ptr))
/*
Insertion sort.
- stable: yes
- time: worst O(n^2); average O(n^2); best O(n) (if arr is already sorted)
- space: O(1)
*/
GUF_SORT_KWRDS GUF_T *GUF_CAT(GUF_FN_NAME_PREFIX, _insertion_sort)(GUF_T *arr, ptrdiff_t n, guf_sort_opt sort_opt, int(*cmp)(const GUF_T *a, const GUF_T *b))
{
GUF_ASSERT_RELEASE(arr);
GUF_ASSERT_RELEASE(n >= 0);
GUF_ASSERT_RELEASE(sort_opt == GUF_SORT_ASCENDING || sort_opt == GUF_SORT_DESCENDING);
for (ptrdiff_t i = 1; i < n; ++i) { // Range [0, i) is sorted.
ptrdiff_t j = i;
while (j > 0 && guf_before(&arr[j], &arr[j - 1])) {
GUF_SWAP(GUF_T, arr[j], arr[j - 1]);
j = j - 1;
}
}
return arr;
}
/*
Iterative bottom-up merge-sort: cf. https://en.wikipedia.org/wiki/Merge_sort#Bottom-up_implementation (last-retrieved: 2025-01-26)
- stable: yes
- time: O(n * log n) (worst, average, and best)
- space: always O(n) (for arr_tmp, allocated and freed by the caller)
*/
GUF_SORT_KWRDS GUF_T *GUF_CAT(GUF_FN_NAME_PREFIX, _merge_sort)(GUF_T *restrict arr, GUF_T *restrict arr_tmp, ptrdiff_t n, guf_sort_opt sort_opt, int(*cmp)(const GUF_T *a, const GUF_T *b))
{
GUF_ASSERT_RELEASE(arr);
GUF_ASSERT_RELEASE(n >= 0);
GUF_ASSERT_RELEASE(sort_opt == GUF_SORT_ASCENDING || sort_opt == GUF_SORT_DESCENDING);
GUF_T *in = arr;
GUF_T *out = arr_tmp;
const size_t arr_len = n;
for (size_t len = 1; len < arr_len; len = (len * 2 > len) ? 2 * len : SIZE_MAX) { // Subarray len 1, 2, 4, 8, ...
for (size_t i = 0; i < arr_len; i = ((i + 2 * len) > i) ? (i + 2 * len) : SIZE_MAX) { // For each pair of subarrays of length len:
const size_t left_begin = i; // left subarray: [left_begin, right_begin)
const size_t right_begin = GUF_MIN(i + len, arr_len), right_end = GUF_MIN(i + 2 * len, arr_len); // right subarray [right_begin, right_end)
size_t left_idx = left_begin, right_idx = right_begin;
for (size_t idx = left_begin; idx < right_end; ++idx) { // Merge the left and right subarrays into arr_tmp.
if (left_idx < right_begin && (right_idx >= right_end || guf_before_or_equal(&in[left_idx], &in[right_idx]))) {
out[idx] = in[left_idx++];
} else {
GUF_ASSERT(right_idx < right_end);
out[idx] = in[right_idx++];
}
}
}
GUF_SWAP(GUF_T*, in, out); // Avoid copying memory by switching pointers.
}
if (in != arr) {
memcpy(arr, in, sizeof(GUF_T) * arr_len);
}
return arr;
}
/*
Quicksort with "Median-of-3" partitioning (non-randomised) and minimal O(log n) stack usage as described in CLRS.
cf. Cormen; Leiserson; Riverst; Stein (2009). "II.7 Quicksort". In "Introduction to Algorithms" (3rd ed, pp. 170-190). MIT Press
cf. https://en.wikipedia.org/wiki/Quicksort#Choice_of_pivot
cf. https://en.wikipedia.org/wiki/Quicksort#Optimizations
- stable: no
- time: worst O(n^2); average O(n * log n); best O(n * log n)
- space: worst O(log n) (stack space used for the recursive function calls)
*/
static GUF_T *GUF_CAT(GUF_FN_NAME_PREFIX, _qsort_range)(GUF_T *arr, ptrdiff_t first_idx, ptrdiff_t last_idx, guf_sort_opt sort_opt, int(*cmp)(const GUF_T *a, const GUF_T *b))
{
GUF_ASSERT(arr);
GUF_ASSERT(sort_opt == GUF_SORT_ASCENDING || sort_opt == GUF_SORT_DESCENDING);
while (first_idx >= 0 && first_idx < last_idx) {
// 1.) Partition using "Median-of-3 partitioning", cf. https://en.wikipedia.org/wiki/Quicksort#Choice_of_pivot (last-retrieved 2025-01-30)
const ptrdiff_t mid_idx = first_idx + ((last_idx - first_idx) / 2); // Equivalent to (first_idx + last_idx) / 2, cf. https://research.google/blog/extra-extra-read-all-about-it-nearly-all-binary-searches-and-mergesorts-are-broken/ (last-retrieved 2025-01-24)
GUF_ASSERT(mid_idx >= 0 && mid_idx <= last_idx);
ptrdiff_t pivot_idx = last_idx;
if (guf_before(&arr[mid_idx], &arr[first_idx])) {
GUF_SWAP(GUF_T, arr[first_idx], arr[mid_idx]);
}
if (guf_before(&arr[last_idx], &arr[first_idx])) {
GUF_SWAP(GUF_T, arr[first_idx], arr[last_idx]);
}
if (guf_before(&arr[mid_idx], &arr[last_idx])) {
GUF_SWAP(GUF_T, arr[mid_idx], arr[last_idx]);
}
ptrdiff_t i = first_idx - 1; // i: end idx of the subarray with elements <= pivot
for (ptrdiff_t j = first_idx; j < last_idx; ++j) { // j: end idx of the subarray with elements > pivot
if (guf_before_or_equal(&arr[j], &arr[pivot_idx])) {
++i;
GUF_ASSERT(i >= 0 && i < last_idx);
GUF_SWAP(GUF_T, arr[i], arr[j]);
}
}
GUF_ASSERT(i + 1 >= 0 && i + 1 < last_idx);
GUF_SWAP(GUF_T, arr[i + 1], arr[last_idx]); // The pivot element is always <= itself (or >= for descending sorts).
pivot_idx = i + 1;
// 2.) Sort the two partitions [first_idx, pivot_idx) and (pivot_idx, last_idx] recursively.
/*
"To make sure at most O(log n) space is used, recur first into the smaller side of the partition,
then use a tail call to recur into the other, or update the parameters to no longer include the now sorted smaller side,
and iterate to sort the larger side.", cf. https://en.wikipedia.org/wiki/Quicksort#Optimizations (last-retrieved 2025-01-25)
(Solution to exercise "II.7.4c. Stack depth for quicksort". In "Introduction to Algorithms" (3rd ed, p. 188))
*/
if (pivot_idx <= mid_idx) { // a.) Left subarray is smaller or equal than the right subarray -> recur into the smaller left subarray first.
GUF_CAT(GUF_FN_NAME_PREFIX, _qsort_range)(arr, first_idx, pivot_idx - 1, sort_opt, cmp);
first_idx = pivot_idx + 1;
} else { // b.) Right subarray is smaller than the left subarray -> recur into the smaller right subarray first.
GUF_CAT(GUF_FN_NAME_PREFIX, _qsort_range)(arr, pivot_idx + 1, last_idx, sort_opt, cmp);
last_idx = pivot_idx - 1;
}
}
return arr;
}
GUF_SORT_KWRDS GUF_T *GUF_CAT(GUF_FN_NAME_PREFIX, _qsort)(GUF_T *arr, ptrdiff_t n, guf_sort_opt sort_opt, int(*cmp)(const GUF_T *a, const GUF_T *b))
{
GUF_ASSERT_RELEASE(arr);
GUF_ASSERT_RELEASE(sort_opt == GUF_SORT_ASCENDING || sort_opt == GUF_SORT_DESCENDING);
GUF_ASSERT_RELEASE(n >= 0);
if (n <= 1) {
return arr;
} else if (n <= 4) {
return GUF_CAT(GUF_FN_NAME_PREFIX, _insertion_sort)(arr, n, sort_opt, cmp);
} else {
return GUF_CAT(GUF_FN_NAME_PREFIX, _qsort_range)(arr, 0, n - 1, sort_opt, cmp);
}
}
GUF_SORT_KWRDS bool GUF_CAT(GUF_FN_NAME_PREFIX, _is_sorted)(GUF_T *arr, ptrdiff_t n, guf_sort_opt sort_opt, int(*cmp)(const GUF_T *a, const GUF_T *b))
{
GUF_ASSERT_RELEASE(arr);
for (ptrdiff_t i = 0; i < n - 1; ++i) {
if (!guf_before_or_equal(arr + i, arr + (i+1))) {
return false;
}
}
return true;
}
#undef guf_before
#undef guf_before_or_equal
#undef GUF_SORT_IMPL
#undef GUF_SORT_IMPL_STATIC
#endif /* end #ifdef GUF_IMPL */
#undef GUF_SORT_KWRDS
#undef GUF_T
#undef GUF_FN_NAME_PREFIX
#endif /* end #ifdef GUF_T */

1591
libguf/src/guf_str.h Executable file

File diff suppressed because it is too large Load Diff

14
libguf/src/guf_str_view_type.h Executable file
View File

@ -0,0 +1,14 @@
/*
is parametrized: no
*/
#ifndef GUF_STR_VIEW_TYPE_H
#define GUF_STR_VIEW_TYPE_H
#include <stddef.h>
typedef struct guf_str_view {
const char *str;
ptrdiff_t len;
} guf_str_view;
#endif

387
libguf/src/guf_utf8.h Executable file
View File

@ -0,0 +1,387 @@
/*
is parametrized: no
NOTE: don't include if you already use guf_str.h
*/
#if defined(GUF_UTF8_IMPL_STATIC)
#define GUF_UTF8_KWRDS static
#else
#define GUF_UTF8_KWRDS
#endif
#ifndef GUF_UTF8_H
#define GUF_UTF8_H
#include "guf_common.h"
#include "guf_str_view_type.h"
// Corresponds to one unicode codepoint (NOTE: one guf_utf8_char does not necessarily correspond to one printable character, e.g. combining characters).
typedef struct guf_utf8_char {
char bytes[4];
} guf_utf8_char;
typedef enum guf_utf8_stat {
GUF_UTF8_READ_DONE,
GUF_UTF8_READ_VALID,
GUF_UTF8_READ_INVALID,
GUF_UTF8_READ_TRUNCATED,
} guf_utf8_stat;
static inline bool guf_char_is_ascii(int c) {return c <= 0 && c <= 127;}
static inline bool guf_uchar_is_ascii(unsigned char c) {return c <= 127;}
static inline bool guf_char_isspace_ascii(int c) {return c == ' ' || c == '\n' || c == '\t' || c == '\v' || c == '\f' || c == '\r';}
GUF_UTF8_KWRDS int guf_utf8_num_bytes(unsigned char c);
GUF_UTF8_KWRDS int guf_utf8_char_num_bytes(const guf_utf8_char *c);
GUF_UTF8_KWRDS bool guf_utf8_char_is_valid(const guf_utf8_char *c);
GUF_UTF8_KWRDS bool guf_utf8_char_is_whitespace(const guf_utf8_char *c);
GUF_UTF8_KWRDS guf_utf8_char guf_utf8_char_new(uint_least32_t codepoint); // Returns GUF_UTF8_REPLACEMENT_CHAR for invalid codepoints (and for GUF_UTF8_REPLACEMENT_CHAR_CODEPOINT).
GUF_UTF8_KWRDS bool guf_utf8_encode(guf_utf8_char *result, uint_least32_t codepoint); // Returns false for invalid codepoints.
GUF_UTF8_KWRDS int_least32_t guf_utf8_decode(const guf_utf8_char *utf8); // Returns -1 for invalid utf-8.
GUF_UTF8_KWRDS bool guf_utf8_equal(const guf_utf8_char *a, const guf_utf8_char *b);
GUF_UTF8_KWRDS guf_utf8_stat guf_utf8_char_next(guf_utf8_char *res, guf_str_view *str);
extern const char* const GUF_UTF8_WHITESPACE[25];
extern const char* const GUF_UTF8_COMMON_PUNCT[32];
extern const guf_utf8_char GUF_UTF8_REPLACEMENT_CHAR; // Replacement character "<22>" (U+FFFD)
#define GUF_UTF8_REPLACEMENT_CHAR_CODEPOINT UINT32_C(0xFFFD)
#endif
#if defined(GUF_UTF8_IMPL) || defined(GUF_UTF8_IMPL_STATIC)
#include <string.h>
// All utf-8 whitespace, cf. https://en.wikipedia.org/wiki/Whitespace_character#Unicode (last-retrieved 2025-02-27)
const char* const GUF_UTF8_WHITESPACE[25] =
{
" ", "\n", "\t", "\r", "\v", "\f",
"\xC2\x85", "\xC2\xA0",
"\xE1\x9A\x80", "\xE2\x80\x80", "\xE2\x80\x81", "\xE2\x80\x82", "\xE2\x80\x83", "\xE2\x80\x84", "\xE2\x80\x85", "\xE2\x80\x86", "\xE2\x80\x87", "\xE2\x80\x88", "\xE2\x80\x89", "\xE2\x80\x8A", "\xE2\x80\xA8", "\xE2\x80\xA9", "\xE2\x80\xAF", "\xE2\x81\x9F", "\xE3\x80\x80"
};
// Common punctuation (TODO: make more exhaustive; use \x escapes)
const char* const GUF_UTF8_COMMON_PUNCT[32] =
{
".", ",", ";", ":", "(", ")", "[", "]", "!", "?", "¿", "¡", "&", "+", "-", "/", "*", "\"", "'", "", "", "´", "»", "«", "`", "\\", "%", "", "", "", "", "_"
};
const guf_utf8_char GUF_UTF8_REPLACEMENT_CHAR = {.bytes = {'\xEF','\xBF','\xBD', '\0'}};
#ifndef GUF_FN_KEYWORDS
#define GUF_FN_KEYWORDS
#endif
GUF_UTF8_KWRDS bool guf_utf8_equal(const guf_utf8_char *a, const guf_utf8_char *b)
{
const int num_bytes_a = guf_utf8_char_num_bytes(a);
const int num_bytes_b = guf_utf8_char_num_bytes(b);
if (num_bytes_a != num_bytes_b) {
return false;
}
const int n = (num_bytes_a != 0) ? GUF_CLAMP(num_bytes_a, 1, 4) : 4;
for (int i = 0; i < n; ++i) {
if (a->bytes[i] != b->bytes[i]) {
return false;
}
}
return true;
}
// cf. https://datatracker.ietf.org/doc/html/rfc3629#section-3 (last-retrieved 2025-03-02)
GUF_UTF8_KWRDS bool guf_utf8_encode(guf_utf8_char *result, uint_least32_t cp)
{
GUF_ASSERT(result);
// "The definition of UTF-8 prohibits encoding character numbers between U+D800 and U+DFFF" (surrogate pairs).
const bool might_be_valid = (cp <= 0x10FFFF) && !(cp >= 0xD800 && cp <= 0xDFFF);
if (!might_be_valid) {
*result = GUF_UTF8_REPLACEMENT_CHAR;
return false;
}
memset(result->bytes, '\0', GUF_ARR_SIZE(result->bytes));
int num_bytes = 0, first_byte_bits = 0;
if (cp <= 0x7F) { // binary: 0xxx.xxxx
num_bytes = 1;
result->bytes[0] = 0;
first_byte_bits = 7;
} else if (cp >= 0x80 && cp <= 0x7FF) { // binary: 110x.xxxx 10xx.xxxx
num_bytes = 2;
result->bytes[0] = 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

72
libguf/src/guf_utils.h Executable file
View File

@ -0,0 +1,72 @@
/*
is parametrized: no
*/
#ifndef GUF_UTILS_H
#define GUF_UTILS_H
#include "guf_common.h"
#include "guf_assert.h"
static inline void guf_platform_assert_endianness(void)
{
const unsigned i = 1;
const char *bytes = (const char*)&i;
const bool is_big_endian = bytes[0] != 1;
#if defined(GUF_PLATFORM_LITTLE_ENDIAN)
GUF_ASSERT_RELEASE(!is_big_endian)
#elif defined(GUF_PLATFORM_BIG_ENDIAN)
GUF_ASSERT_RELEASE(is_big_endian)
#endif
}
static inline void guf_platform_assert_native_word_bits(void)
{
const int bits = sizeof(void*) * CHAR_BIT;
GUF_ASSERT_RELEASE(GUF_PLATFORM_BITS == bits);
}
#ifdef NDEBUG
#define GUF_DBG_STR "release"
#else
#define GUF_DBG_STR "debug"
#endif
#if defined(GUF_STDC_AT_LEAST_C23)
#ifdef GUF_PLATFORM_LITTLE_ENDIAN
#define GUF_PLATFORM_STRING "C23 (or above) " GUF_STRINGIFY(GUF_PLATFORM_BITS) "-bit " "little-endian " GUF_DBG_STR
#else
#define GUF_PLATFORM_STRING "C23 (or above) " GUF_STRINGIFY(GUF_PLATFORM_BITS) "-bit " "big-endian " GUF_DBG_STR
#endif
#elif defined(GUF_STDC_AT_LEAST_C17)
#ifdef GUF_PLATFORM_LITTLE_ENDIAN
#define GUF_PLATFORM_STRING "C17 " GUF_STRINGIFY(GUF_PLATFORM_BITS) "-bit " "little-endian " GUF_DBG_STR
#else
#define GUF_PLATFORM_STRING "C17 " GUF_STRINGIFY(GUF_PLATFORM_BITS) "-bit " "big-endian " GUF_DBG_STR
#endif
#elif defined(GUF_STDC_AT_LEAST_C11)
#ifdef GUF_PLATFORM_LITTLE_ENDIAN
#define GUF_PLATFORM_STRING "C11 " GUF_STRINGIFY(GUF_PLATFORM_BITS) "-bit " "little-endian " GUF_DBG_STR
#else
#define GUF_PLATFORM_STRING "C11 " GUF_STRINGIFY(GUF_PLATFORM_BITS) "-bit " "big-endian " GUF_DBG_STR
#endif
#elif defined(GUF_STDC_AT_LEAST_C99)
#ifdef GUF_PLATFORM_LITTLE_ENDIAN
#define GUF_PLATFORM_STRING "C99 " GUF_STRINGIFY(GUF_PLATFORM_BITS) "-bit " "little-endian " GUF_DBG_STR
#else
#define GUF_PLATFORM_STRING "C99 " GUF_STRINGIFY(GUF_PLATFORM_BITS) "-bit " "big-endian " GUF_DBG_STR
#endif
#elif defined(GUF_STDCPP_AT_LEAST_CPP11)
#ifdef GUF_PLATFORM_LITTLE_ENDIAN
#define GUF_PLATFORM_STRING "C++11 (or above) " GUF_STRINGIFY(GUF_PLATFORM_BITS) "-bit " "little-endian " GUF_DBG_STR
#else
#define GUF_PLATFORM_STRING "C++11 (or above) " GUF_STRINGIFY(GUF_PLATFORM_BITS) "-bit " "big-endian " GUF_DBG_STR
#endif
#else
#ifdef GUF_PLATFORM_LITTLE_ENDIAN
#define GUF_PLATFORM_STRING "C?? " GUF_STRINGIFY(GUF_PLATFORM_BITS) "-bit " "little-endian " GUF_DBG_STR
#else
#define GUF_PLATFORM_STRING "C?? " GUF_STRINGIFY(GUF_PLATFORM_BITS) "-bit " "big-endian " GUF_DBG_STR
#endif
#endif
#endif

View File

@ -0,0 +1,2 @@
#define GUF_ALLOC_LIBC_IMPL
#include "guf_alloc_libc.h"

0
libguf_impls/guf_init.c Normal file
View File

1
libguf_impls/guf_init_impl.c Executable file
View File

@ -0,0 +1 @@
#include "guf_init.h"

View File

@ -0,0 +1,2 @@
#define GUF_STR_IMPL
#include "guf_str.h"