Solve Day 01
This commit is contained in:
commit
76ea6e5d18
6
.gitignore
vendored
Executable file
6
.gitignore
vendored
Executable file
@ -0,0 +1,6 @@
|
|||||||
|
input/day-??.txt
|
||||||
|
bin/
|
||||||
|
build/
|
||||||
|
.vscode/
|
||||||
|
result.txt
|
||||||
|
session.txt
|
||||||
82
CMakeLists.txt
Executable file
82
CMakeLists.txt
Executable 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
80
aocfetch.py
Executable 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
82
day-01/day-01.c
Normal 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
44
day-xy/day-xy.c
Normal 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
10
input/day-01-example.txt
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
L68
|
||||||
|
L30
|
||||||
|
R48
|
||||||
|
L5
|
||||||
|
R60
|
||||||
|
L55
|
||||||
|
L1
|
||||||
|
L99
|
||||||
|
R14
|
||||||
|
L82
|
||||||
BIN
libguf/src/.DS_Store
vendored
Executable file
BIN
libguf/src/.DS_Store
vendored
Executable file
Binary file not shown.
31
libguf/src/guf_alloc.h
Executable file
31
libguf/src/guf_alloc.h
Executable file
@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
is parametrized: no
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef GUF_ALLOC_H
|
||||||
|
#define GUF_ALLOC_H
|
||||||
|
#include "guf_common.h"
|
||||||
|
#include "guf_assert.h"
|
||||||
|
|
||||||
|
// A custom allocator interface as described in https://nullprogram.com/blog/2023/12/17/ (last-retrieved 2025-01-25)
|
||||||
|
typedef struct guf_allocator {
|
||||||
|
void *(*alloc)(ptrdiff_t size, void *ctx);
|
||||||
|
void *(*realloc)(void *ptr, ptrdiff_t old_size, ptrdiff_t new_size, void *ctx);
|
||||||
|
void (*free)(void *ptr, ptrdiff_t size, void *ctx);
|
||||||
|
void *ctx;
|
||||||
|
} guf_allocator;
|
||||||
|
|
||||||
|
// typedef enum guf_alloc_fn_type {
|
||||||
|
// GUF_ALLOC_FN_TYPE_ALLOC,
|
||||||
|
// GUF_ALLOC_FN_TYPE_REALLOC,
|
||||||
|
// GUF_ALLOC_FN_TYPE_FREE,
|
||||||
|
// } guf_alloc_fn_type;
|
||||||
|
|
||||||
|
/*
|
||||||
|
GUF_ALLOC_MAX_BYTES: Largest number of bytes an allocated buffer of elements of TYPE can have.
|
||||||
|
GUF_ALLOC_MAX_CAPACITY: Largest number of elements an allocated buffer of elements of TYPE can have.
|
||||||
|
*/
|
||||||
|
#define GUF_ALLOC_MAX_BYTES(TYPE) ( PTRDIFF_MAX - ( PTRDIFF_MAX % sizeof(TYPE) ) )
|
||||||
|
#define GUF_ALLOC_MAX_CAPACITY(TYPE) ( GUF_ALLOC_MAX_BYTES(TYPE) / sizeof(TYPE) )
|
||||||
|
|
||||||
|
#endif
|
||||||
121
libguf/src/guf_alloc_libc.h
Executable file
121
libguf/src/guf_alloc_libc.h
Executable 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
212
libguf/src/guf_alloc_tracker.h
Executable file
@ -0,0 +1,212 @@
|
|||||||
|
#if defined(GUF_ALLOC_TRACKER_IMPL_STATIC)
|
||||||
|
#define GUF_ALLOC_TRACKER_KWRDS static inline
|
||||||
|
#else
|
||||||
|
#define GUF_ALLOC_TRACKER_KWRDS
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef GUF_ALLOC_TRACKER_H
|
||||||
|
#define GUF_ALLOC_TRACKER_H
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include "guf_common.h"
|
||||||
|
#include "guf_str_view_type.h"
|
||||||
|
|
||||||
|
typedef struct guf_alloc_tracker {
|
||||||
|
FILE *log, *err_log;
|
||||||
|
const char *name;
|
||||||
|
size_t alloc_count, realloc_count, free_count;
|
||||||
|
ptrdiff_t allocated_bytes;
|
||||||
|
unsigned id;
|
||||||
|
bool enabled;
|
||||||
|
} guf_alloc_tracker;
|
||||||
|
|
||||||
|
#if !defined(GUF_ALLOC_TRACKER_IMPL_STATIC) && !defined(GUF_ALLOC_TRACKER_IMPL)
|
||||||
|
GUF_ALLOC_TRACKER_KWRDS guf_alloc_tracker *guf_alloc_tracker_init(guf_alloc_tracker *t, unsigned id, const char* name, FILE *log, FILE *err_log);
|
||||||
|
GUF_ALLOC_TRACKER_KWRDS guf_alloc_tracker guf_alloc_tracker_new(unsigned id, const char* name, FILE *log, FILE *err_log);
|
||||||
|
|
||||||
|
GUF_ALLOC_TRACKER_KWRDS void guf_alloc_tracker_print(const guf_alloc_tracker *t, FILE *f);
|
||||||
|
GUF_ALLOC_TRACKER_KWRDS bool guf_alloc_tracker_found_leak(const guf_alloc_tracker *t);
|
||||||
|
|
||||||
|
GUF_ALLOC_TRACKER_KWRDS bool guf_track_alloc(guf_alloc_tracker *t, ptrdiff_t size);
|
||||||
|
GUF_ALLOC_TRACKER_KWRDS bool guf_track_realloc(guf_alloc_tracker *t, ptrdiff_t old_size, ptrdiff_t new_size);
|
||||||
|
GUF_ALLOC_TRACKER_KWRDS bool guf_track_free(guf_alloc_tracker *t, ptrdiff_t size);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(GUF_ALLOC_TRACKER_IMPL_STATIC) || defined(GUF_ALLOC_TRACKER_IMPL)
|
||||||
|
|
||||||
|
#include "guf_alloc.h"
|
||||||
|
#define GUF_MATH_CKDINT_IMPL_STATIC
|
||||||
|
#include "guf_math_ckdint.h"
|
||||||
|
|
||||||
|
GUF_ALLOC_TRACKER_KWRDS guf_alloc_tracker *guf_alloc_tracker_init(guf_alloc_tracker *t, unsigned id, const char* name, FILE *log, FILE *err_log)
|
||||||
|
{
|
||||||
|
GUF_ASSERT_RELEASE(t);
|
||||||
|
t->log = log;
|
||||||
|
t->err_log = err_log;
|
||||||
|
t->alloc_count = t->realloc_count = t->free_count = 0;
|
||||||
|
t->allocated_bytes = 0;
|
||||||
|
t->name = name;
|
||||||
|
t->id = id;
|
||||||
|
t->enabled = true;
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
GUF_ALLOC_TRACKER_KWRDS guf_alloc_tracker guf_alloc_tracker_new(unsigned id, const char* name, FILE *log, FILE *err_log)
|
||||||
|
{
|
||||||
|
guf_alloc_tracker t;
|
||||||
|
guf_alloc_tracker_init(&t, id, name, log, err_log);
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
GUF_ALLOC_TRACKER_KWRDS bool guf_alloc_tracker_found_leak(const guf_alloc_tracker *t)
|
||||||
|
{
|
||||||
|
GUF_ASSERT_RELEASE(t);
|
||||||
|
return (t->allocated_bytes != 0) || (t->alloc_count != t->free_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
GUF_ALLOC_TRACKER_KWRDS void guf_alloc_tracker_print(const guf_alloc_tracker *t, FILE *f)
|
||||||
|
{
|
||||||
|
GUF_ASSERT(t);
|
||||||
|
if (!f) {
|
||||||
|
f = stdout;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!t) {
|
||||||
|
fprintf(f, "guf_alloc_tracker_fprint: guf_alloc_tracker is NULL");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fprintf(f,
|
||||||
|
"guf_alloc_tracker (name '%s' id = %u):\n"
|
||||||
|
"allocated_bytes: %td\n"
|
||||||
|
"alloc_count: %zu\n"
|
||||||
|
"realloc_count: %zu\n"
|
||||||
|
"free_count: %zu\n",
|
||||||
|
t->name ? t->name : "unnamed", t->id, t->allocated_bytes, t->alloc_count, t->realloc_count, t->free_count
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
GUF_ALLOC_TRACKER_KWRDS bool guf_track_alloc(guf_alloc_tracker *t, ptrdiff_t size)
|
||||||
|
{
|
||||||
|
GUF_ASSERT(t);
|
||||||
|
GUF_ASSERT(size >= 0);
|
||||||
|
|
||||||
|
bool success = true;
|
||||||
|
|
||||||
|
if (guf_saturating_add_size_t(t->alloc_count, 1, &t->alloc_count) != GUF_MATH_CKD_SUCCESS && t->err_log) {
|
||||||
|
fprintf(t->err_log, "WARNING in guf_track_alloc (name '%s' id %u): alloc_count reached SIZE_MAX\n", t->name ? t->name : "unnamed", t->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (guf_saturating_add_ptrdiff_t(t->allocated_bytes, size, &t->allocated_bytes) != GUF_MATH_CKD_SUCCESS) {
|
||||||
|
if (t->err_log) {
|
||||||
|
fprintf(t->err_log, "ERROR in guf_track_alloc (name '%s' id %u): allocated_byte overflow\n", t->name ? t->name : "unnamed", t->id);
|
||||||
|
}
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (t->allocated_bytes < 0) {
|
||||||
|
if (t->err_log) {
|
||||||
|
fprintf(t->err_log, "ERROR in guf_track_alloc (name '%s' id %u): allocated_bytes < 0\n", t->name ? t->name : "unnamed", t->id);
|
||||||
|
}
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (t->log) {
|
||||||
|
fprintf(t->log, "guf_alloc_tracker (name '%s' id %u): alloc (%td bytes) %s\n", t->name ? t->name : "unnamed", t->id, size, success ? "SUCCESS" : "FAILURE");
|
||||||
|
}
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
GUF_ALLOC_TRACKER_KWRDS bool guf_track_realloc(guf_alloc_tracker *t, ptrdiff_t old_size, ptrdiff_t new_size)
|
||||||
|
{
|
||||||
|
GUF_ASSERT(t);
|
||||||
|
GUF_ASSERT(old_size >= 0 && new_size >= 0);
|
||||||
|
bool success = true;
|
||||||
|
|
||||||
|
if (guf_saturating_add_size_t(t->realloc_count, 1, &t->realloc_count) && t->err_log) {
|
||||||
|
fprintf(t->err_log, "WARNING in guf_track_realloc (name '%s' id %u): realloc_count reached SIZE_MAX\n", t->name ? t->name : "unnamed", t->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (old_size < 0 || new_size < 0) {
|
||||||
|
success = false;
|
||||||
|
if (t->err_log) {
|
||||||
|
fprintf(t->err_log, "ERROR in guf_track_realloc (name '%s' id %u): old_size < 0 or new_size < 0\n", t->name ? t->name : "unnamed", t->id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (guf_saturating_sub_ptrdiff_t(t->allocated_bytes, old_size, &t->allocated_bytes)) {
|
||||||
|
success = false;
|
||||||
|
if (t->err_log) {
|
||||||
|
fprintf(t->err_log, "ERROR in guf_track_realloc (name '%s' id %u): allocated_bytes - old_size under/overflows\n", t->name ? t->name : "unnamed", t->id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (t->allocated_bytes < 0) {
|
||||||
|
success = false;
|
||||||
|
fprintf(t->err_log, "ERROR in guf_track_realloc (name '%s' id %u): allocated_bytes < 0 after subtracting old_size\n", t->name ? t->name : "unnamed", t->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (guf_saturating_add_ptrdiff_t(t->allocated_bytes, new_size, &t->allocated_bytes)) {
|
||||||
|
success = false;
|
||||||
|
if (t->err_log) {
|
||||||
|
fprintf(t->err_log, "ERROR in guf_track_realloc (name '%s' id %u): allocated_bytes overflow \n", t->name ? t->name : "unnamed", t->id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (t->allocated_bytes < 0) {
|
||||||
|
success = false;
|
||||||
|
fprintf(t->err_log, "ERROR in guf_track_realloc (name '%s' id %u): allocated_bytes < 0 after adding new_size\n", t->name ? t->name : "unnamed", t->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (t->log) {
|
||||||
|
fprintf(t->log, "guf_alloc_tracker (name '%s' id %u): realloc (from %td to %td bytes) %s\n", t->name ? t->name : "unnamed", t->id, old_size, new_size, (success ? "SUCCESS" : "FAILURE"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
GUF_ALLOC_TRACKER_KWRDS bool guf_track_free(guf_alloc_tracker *t, ptrdiff_t size)
|
||||||
|
{
|
||||||
|
GUF_ASSERT(t);
|
||||||
|
GUF_ASSERT(size >= 0);
|
||||||
|
bool success = true;
|
||||||
|
|
||||||
|
if (guf_saturating_add_size_t(t->free_count, 1, &t->free_count) && t->err_log) {
|
||||||
|
fprintf(t->err_log, "WARNING in guf_track_free (name '%s' id %u): free_count reached SIZE_MAX\n", t->name ? t->name : "unnamed", t->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (size < 0) {
|
||||||
|
success = false;
|
||||||
|
if (t->err_log) {
|
||||||
|
fprintf(t->err_log, "ERROR in guf_track_free (name '%s' id %u): size < 0\n", t->name ? t->name : "unnamed", t->id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (t->allocated_bytes < size) {
|
||||||
|
success = false;
|
||||||
|
if (t->err_log) {
|
||||||
|
fprintf(t->err_log, "ERROR in guf_track_free (name '%s' id %u): freed more bytes than allocated\n", t->name ? t->name : "unnamed", t->id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (guf_saturating_sub_ptrdiff_t(t->allocated_bytes, size, &t->allocated_bytes)) {
|
||||||
|
success = false;
|
||||||
|
if (t->err_log) {
|
||||||
|
fprintf(t->err_log, "ERROR in guf_track_free (name '%s' id %u): allocated_bytes - size under/overflows\n", t->name ? t->name : "unnamed", t->id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (t->log) {
|
||||||
|
fprintf(t->log, "guf_alloc_tracker (name '%s' id %u): free (%td bytes) %s\n", t->name ? t->name : "unnamed", t->id, size, (success ? "SUCCESS" : "FAILURE"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* End impl */
|
||||||
|
|
||||||
|
#endif /* End header-guard */
|
||||||
|
|
||||||
|
#undef GUF_ALLOC_TRACKER_KWRDS
|
||||||
|
#undef GUF_ALLOC_TRACKER_IMPL
|
||||||
|
#undef GUF_ALLOC_TRACKER_IMPL_STATIC
|
||||||
165
libguf/src/guf_assert.h
Executable file
165
libguf/src/guf_assert.h
Executable 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
130
libguf/src/guf_common.h
Executable 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
81
libguf/src/guf_cstr.h
Executable 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
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
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
82
libguf/src/guf_hash.h
Executable file
@ -0,0 +1,82 @@
|
|||||||
|
/*
|
||||||
|
is parametrized: no (but recieves GUF_HASH_32_BIT from guf_common.h to set guf_hash_size_t depending on platform)
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if defined(GUF_HASH_IMPL_STATIC)
|
||||||
|
#define GUF_HASH_KWRDS static
|
||||||
|
#else
|
||||||
|
#define GUF_HASH_KWRDS
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef GUF_HASH_H
|
||||||
|
#define GUF_HASH_H
|
||||||
|
#include "guf_common.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
FNV-1a (32-bit and 64-bit) hash functions.
|
||||||
|
Generally, you should always call csr_hash with GUF_HASH_INIT as the hash argument, unless you want to create "chains" of hashes.
|
||||||
|
cf. http://www.isthe.com/chongo/tech/comp/fnv/ (last retrieved: 2023-11-30)
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define GUF_HASH32_INIT UINT32_C(2166136261)
|
||||||
|
#define GUF_HASH64_INIT UINT64_C(14695981039346656037)
|
||||||
|
|
||||||
|
GUF_HASH_KWRDS uint_least32_t guf_hash32(const void *data, ptrdiff_t num_bytes, uint_least32_t hash); // FNV-1a (32 bit)
|
||||||
|
GUF_HASH_KWRDS uint_least64_t guf_hash64(const void *data, ptrdiff_t num_bytes, uint_least64_t hash); // FNV-1a (64 bit)
|
||||||
|
|
||||||
|
#ifdef GUF_HASH_32_BIT
|
||||||
|
typedef uint_least32_t guf_hash_size_t;
|
||||||
|
#define GUF_HASH_INIT GUF_HASH32_INIT
|
||||||
|
#define GUF_HASH_MAX GUF_UINT32_MAX
|
||||||
|
static inline guf_hash_size_t guf_hash(const void *data, ptrdiff_t num_bytes, guf_hash_size_t hash) {
|
||||||
|
return guf_hash32(data, num_bytes, hash);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
typedef uint_least64_t guf_hash_size_t;
|
||||||
|
#define GUF_HASH_INIT GUF_HASH64_INIT
|
||||||
|
#define GUF_HASH_MAX GUF_UINT64_MAX
|
||||||
|
static inline guf_hash_size_t guf_hash(const void *data, ptrdiff_t num_bytes, guf_hash_size_t hash) {
|
||||||
|
return guf_hash64(data, num_bytes, hash);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(GUF_HASH_IMPL) || defined(GUF_HASH_IMPL_STATIC)
|
||||||
|
|
||||||
|
#include "guf_assert.h"
|
||||||
|
|
||||||
|
GUF_HASH_KWRDS uint_least32_t guf_hash32(const void *data, ptrdiff_t num_bytes, uint_least32_t hash)
|
||||||
|
{
|
||||||
|
hash = GUF_UWRAP_32(hash);
|
||||||
|
GUF_ASSERT_RELEASE(data);
|
||||||
|
GUF_ASSERT_RELEASE(num_bytes >= 0);
|
||||||
|
const unsigned char *data_bytes = (const unsigned char*)data; // This does not break strict-aliasing rules I think...
|
||||||
|
const uint_least32_t FNV_32_PRIME = UINT32_C(16777619);
|
||||||
|
for (ptrdiff_t i = 0; i < num_bytes; ++i) {
|
||||||
|
hash = GUF_UWRAP_32(1u * hash ^ data_bytes[i]);
|
||||||
|
hash = GUF_UWRAP_32(1u * hash * FNV_32_PRIME);
|
||||||
|
}
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
GUF_HASH_KWRDS uint_least64_t guf_hash64(const void *data, ptrdiff_t num_bytes, uint_least64_t hash)
|
||||||
|
{
|
||||||
|
hash = GUF_UWRAP_64(hash);
|
||||||
|
GUF_ASSERT_RELEASE(data);
|
||||||
|
GUF_ASSERT_RELEASE(num_bytes >= 0);
|
||||||
|
const unsigned char *data_bytes = (const unsigned char*)data; // This does not break strict-aliasing rules I think...
|
||||||
|
const uint_least64_t FNV_64_PRIME = UINT64_C(1099511628211);
|
||||||
|
for (ptrdiff_t i = 0; i < num_bytes; ++i) {
|
||||||
|
hash = GUF_UWRAP_64(1u * hash ^ data_bytes[i]);
|
||||||
|
hash = GUF_UWRAP_64(1u * hash * FNV_64_PRIME);
|
||||||
|
}
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef GUF_HASH_IMPL
|
||||||
|
#undef GUF_HASH_IMPL_STATIC
|
||||||
|
|
||||||
|
#endif /* endif GUF_IMPL/GUF_IMPL_STATIC */
|
||||||
|
|
||||||
|
#undef GUF_HASH_KWRDS
|
||||||
276
libguf/src/guf_id_pool.h
Executable file
276
libguf/src/guf_id_pool.h
Executable file
@ -0,0 +1,276 @@
|
|||||||
|
/*
|
||||||
|
is parametrized: yes
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if defined(GUF_ID_POOL_IMPL_STATIC)
|
||||||
|
#define GUF_ID_POOL_KWRDS static
|
||||||
|
#else
|
||||||
|
#define GUF_ID_POOL_KWRDS
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef GUF_ID_POOL_H
|
||||||
|
#define GUF_ID_POOL_H
|
||||||
|
#include "guf_common.h"
|
||||||
|
#include "guf_assert.h"
|
||||||
|
|
||||||
|
#define GUF_ID_I32_NULL (-1)
|
||||||
|
#define GUF_ID_I16_NULL (-1)
|
||||||
|
#define GUF_ID_I8_NULL (-1)
|
||||||
|
|
||||||
|
#define GUF_ID_U32_NULL (GUF_UINT32_MAX)
|
||||||
|
#define GUF_ID_U16_NULL (GUF_UINT16_MAX)
|
||||||
|
#define GUF_ID_U8_NULL (GUF_UINT8_MAX)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
ID-pool which allows for allocating and freeing unique IDs (useful when implementing object pools):
|
||||||
|
cf. https://github.com/erincatto/box2d/blob/main/src/id_pool.c (last-retrieved 2025-03-17)
|
||||||
|
*/
|
||||||
|
|
||||||
|
// // debug beg
|
||||||
|
// #define GUF_ID_POOL_IMPL_STATIC
|
||||||
|
// #define GUF_ID_POOL_i32
|
||||||
|
// // debug end
|
||||||
|
|
||||||
|
#if defined(GUF_ID_POOL_i32)
|
||||||
|
#undef GUF_ID_POOL_i32
|
||||||
|
#define GUF_ID_POOL_T int_least32_t
|
||||||
|
#define GUF_ID_POOL_T_MAX GUF_INT32_MAX
|
||||||
|
#define GUF_ID_POOL_NULL GUF_ID_I32_NULL
|
||||||
|
#ifndef GUF_ID_POOL_NAME
|
||||||
|
#define GUF_ID_POOL_NAME guf_idpool_i32
|
||||||
|
#endif
|
||||||
|
#elif defined(GUF_ID_POOL_i16)
|
||||||
|
#undef GUF_ID_POOL_i16
|
||||||
|
#define GUF_ID_POOL_T int_least16_t
|
||||||
|
#define GUF_ID_POOL_T_MAX GUF_INT16_MAX
|
||||||
|
#define GUF_ID_POOL_NULL GUF_ID_I16_NULL
|
||||||
|
#ifndef GUF_ID_POOL_NAME
|
||||||
|
#define GUF_ID_POOL_NAME guf_idpool_i16
|
||||||
|
#endif
|
||||||
|
#elif defined(GUF_ID_POOL_i8)
|
||||||
|
#undef GUF_ID_POOL_i8
|
||||||
|
#define GUF_ID_POOL_T int_least8_t
|
||||||
|
#define GUF_ID_POOL_T_MAX GUF_INT8_MAX
|
||||||
|
#define GUF_ID_POOL_NULL GUF_ID_I8_NULL
|
||||||
|
#ifndef GUF_ID_POOL_NAME
|
||||||
|
#define GUF_ID_POOL_NAME guf_idpool_i8
|
||||||
|
#endif
|
||||||
|
#elif defined(GUF_ID_POOL_u32)
|
||||||
|
#undef GUF_ID_POOL_u32
|
||||||
|
#define GUF_ID_POOL_T uint_least32_t
|
||||||
|
#define GUF_ID_POOL_T_MAX (GUF_UINT32_MAX - 1)
|
||||||
|
#define GUF_ID_POOL_NULL GUF_ID_U32_NULL
|
||||||
|
#ifndef GUF_ID_POOL_NAME
|
||||||
|
#define GUF_ID_POOL_NAME guf_idpool_u32
|
||||||
|
#endif
|
||||||
|
#elif defined(GUF_ID_POOL_u16)
|
||||||
|
#undef GUF_ID_POOL_u16
|
||||||
|
#define GUF_ID_POOL_T uint_least16_t
|
||||||
|
#define GUF_ID_POOL_T_MAX (GUF_UINT16_MAX - 1)
|
||||||
|
#define GUF_ID_POOL_NULL GUF_ID_U16_NULL
|
||||||
|
#ifndef GUF_ID_POOL_NAME
|
||||||
|
#define GUF_ID_POOL_NAME guf_idpool_u16
|
||||||
|
#endif
|
||||||
|
#elif defined(GUF_ID_POOL_u8)
|
||||||
|
#undef GUF_ID_POOL_u8
|
||||||
|
#define GUF_ID_POOL_T uint_least8_t
|
||||||
|
#define GUF_ID_POOL_T_MAX (GUF_UINT8_MAX - 1)
|
||||||
|
#define GUF_ID_POOL_NULL GUF_ID_U8_NULL
|
||||||
|
#ifndef GUF_ID_POOL_NAME
|
||||||
|
#define GUF_ID_POOL_NAME guf_idpool_u8
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
#error "Must define GUF_ID_POOL_i32, GUF_ID_POOL_i16, GUF_ID_POOL_i8, GUF_ID_POOL_u32, GUF_ID_POOL_u16, or GUF_ID_POOL_u8"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define GUF_ID_POOL_DBUF GUF_CAT(GUF_ID_POOL_NAME, _id_dbuf)
|
||||||
|
|
||||||
|
#define GUF_DBUF_NAME GUF_ID_POOL_DBUF
|
||||||
|
#define GUF_T GUF_ID_POOL_T
|
||||||
|
#define GUF_T_IS_INTEGRAL_TYPE
|
||||||
|
#define GUF_DBUF_ONLY_TYPES
|
||||||
|
#include "guf_dbuf.h"
|
||||||
|
|
||||||
|
#ifdef GUF_ID_POOL_ONLY_TYPES
|
||||||
|
#undef GUF_ID_POOL_ONLY_TYPES
|
||||||
|
typedef struct GUF_ID_POOL_NAME {
|
||||||
|
GUF_ID_POOL_DBUF free_id_dbuf;
|
||||||
|
GUF_ID_POOL_T next_id;
|
||||||
|
} GUF_ID_POOL_NAME;
|
||||||
|
#else
|
||||||
|
|
||||||
|
#ifndef GUF_ID_POOL_IMPL
|
||||||
|
typedef struct GUF_ID_POOL_NAME {
|
||||||
|
GUF_ID_POOL_DBUF free_id_dbuf; // Stores all the freed ids to reuse.
|
||||||
|
GUF_ID_POOL_T next_id; // The next-id to assign in case we don't have any freed ids to reuse.
|
||||||
|
} GUF_ID_POOL_NAME;
|
||||||
|
|
||||||
|
GUF_ID_POOL_NAME *GUF_CAT(GUF_ID_POOL_NAME, _try_init)(GUF_ID_POOL_NAME *pool, ptrdiff_t initial_cap, guf_allocator *allocator, guf_err *err);
|
||||||
|
GUF_ID_POOL_NAME *GUF_CAT(GUF_ID_POOL_NAME, _init)(GUF_ID_POOL_NAME *pool, ptrdiff_t initial_cap, guf_allocator *allocator);
|
||||||
|
|
||||||
|
void GUF_CAT(GUF_ID_POOL_NAME, _free)(GUF_ID_POOL_NAME *pool, void *ctx);
|
||||||
|
|
||||||
|
GUF_ID_POOL_T GUF_CAT(GUF_ID_POOL_NAME, _try_alloc_id)(GUF_ID_POOL_NAME *pool, guf_err *err);
|
||||||
|
GUF_ID_POOL_T GUF_CAT(GUF_ID_POOL_NAME, _alloc_id)(GUF_ID_POOL_NAME *pool);
|
||||||
|
|
||||||
|
void GUF_CAT(GUF_ID_POOL_NAME, _try_free_id)(GUF_ID_POOL_NAME *pool, GUF_ID_POOL_T id, guf_err *err);
|
||||||
|
void GUF_CAT(GUF_ID_POOL_NAME, _free_id)(GUF_ID_POOL_NAME *pool, GUF_ID_POOL_T id);
|
||||||
|
|
||||||
|
GUF_ID_POOL_NAME ptrdiff_t GUF_CAT(GUF_ID_POOL_NAME, _num_allocated)(const GUF_ID_POOL_NAME *pool); // TODO: test
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(GUF_ID_POOL_IMPL) || defined(GUF_ID_POOL_IMPL_STATIC)
|
||||||
|
|
||||||
|
#define GUF_DBUF_NAME GUF_ID_POOL_DBUF
|
||||||
|
#define GUF_T GUF_ID_POOL_T
|
||||||
|
#define GUF_T_IS_INTEGRAL_TYPE
|
||||||
|
#define GUF_DBUF_WITHOUT_TYPES
|
||||||
|
#define GUF_DBUF_IMPL_STATIC
|
||||||
|
#include "guf_dbuf.h"
|
||||||
|
|
||||||
|
static bool GUF_CAT(GUF_ID_POOL_NAME, _is_valid)(const GUF_ID_POOL_NAME *pool)
|
||||||
|
{
|
||||||
|
if (!pool) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
bool valid_next_id = (pool->next_id == GUF_ID_POOL_NULL) || (pool->next_id >= 0 && pool->next_id <= GUF_ID_POOL_T_MAX);
|
||||||
|
bool valid_bufsize = ((pool->free_id_dbuf.size <= (ptrdiff_t)pool->next_id) || (pool->next_id == GUF_ID_POOL_NULL)) && (size_t)pool->free_id_dbuf.size <= (size_t)GUF_ID_POOL_T_MAX + 1;
|
||||||
|
return valid_next_id && valid_bufsize && GUF_CAT(GUF_ID_POOL_DBUF, _valid)(&pool->free_id_dbuf) && pool->free_id_dbuf.size >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
GUF_ID_POOL_NAME ptrdiff_t GUF_CAT(GUF_ID_POOL_NAME, _num_allocated)(const GUF_ID_POOL_NAME *pool)
|
||||||
|
{
|
||||||
|
GUF_ASSERT((ptrdiff_t)pool->next_id >= free_id_dbuf.size)
|
||||||
|
return pool->next_id - free_id_dbuf.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
GUF_ID_POOL_NAME *GUF_CAT(GUF_ID_POOL_NAME, _try_init)(GUF_ID_POOL_NAME *pool, ptrdiff_t initial_cap, guf_allocator *allocator, guf_err *err)
|
||||||
|
{
|
||||||
|
if (!pool) {
|
||||||
|
guf_err_set_or_panic(err, GUF_ERR_NULL_PTR, GUF_ERR_MSG("in guf_idpool_try_init: pool is NULL"));
|
||||||
|
return NULL;
|
||||||
|
} else if (!allocator || !allocator->alloc || !allocator->realloc || !allocator->free) {
|
||||||
|
guf_err_set_or_panic(err, GUF_ERR_NULL_PTR, GUF_ERR_MSG("in guf_idpool_try_init: allocator is NULL"));
|
||||||
|
return NULL;
|
||||||
|
} else if (initial_cap < 0) {
|
||||||
|
guf_err_set_or_panic(err, GUF_ERR_INVALID_ARG, GUF_ERR_MSG("in guf_idpool_try_init: initial_cap is < 0"));
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
pool->free_id_dbuf = {0};
|
||||||
|
GUF_CAT(GUF_ID_POOL_DBUF, _try_init)(&pool->free_id_dbuf, initial_cap, allocator, err);
|
||||||
|
if (err && *err != GUF_ERR_NONE) {
|
||||||
|
guf_err_set_or_panic(err, *err, GUF_ERR_MSG("in guf_idpool_try_init: failed to allocate dbuf"));
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
pool->next_id = 0;
|
||||||
|
|
||||||
|
guf_err_set_if_not_null(err, GUF_ERR_NONE);
|
||||||
|
return pool;
|
||||||
|
}
|
||||||
|
|
||||||
|
GUF_ID_POOL_NAME *GUF_CAT(GUF_ID_POOL_NAME, _init)(GUF_ID_POOL_NAME *pool, ptrdiff_t initial_cap, guf_allocator *allocator)
|
||||||
|
{
|
||||||
|
return GUF_CAT(GUF_ID_POOL_NAME, _try_init)(pool, initial_cap, allocator, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GUF_CAT(GUF_ID_POOL_NAME, _free)(GUF_ID_POOL_NAME *pool, void *ctx)
|
||||||
|
{
|
||||||
|
(void)ctx;
|
||||||
|
if (!pool) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
GUF_ASSERT(GUF_CAT(GUF_ID_POOL_NAME, _is_valid)(pool));
|
||||||
|
|
||||||
|
GUF_CAT(GUF_ID_POOL_DBUF, _free)(&pool->free_id_dbuf, NULL);
|
||||||
|
pool->free_id_dbuf = {0};
|
||||||
|
pool->next_id = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
GUF_ID_POOL_T GUF_CAT(GUF_ID_POOL_NAME, _try_alloc_id)(GUF_ID_POOL_NAME *pool, guf_err *err)
|
||||||
|
{
|
||||||
|
GUF_ASSERT(pool);
|
||||||
|
GUF_ASSERT(GUF_CAT(GUF_ID_POOL_NAME, _is_valid)(pool));
|
||||||
|
|
||||||
|
if (pool->free_id_dbuf.size > 0) {
|
||||||
|
GUF_ID_POOL_T id = *GUF_CAT(GUF_ID_POOL_DBUF, _back)(&pool->free_id_dbuf);
|
||||||
|
GUF_CAT(GUF_ID_POOL_DBUF, _pop)(&pool->free_id_dbuf);
|
||||||
|
GUF_ASSERT(id != GUF_ID_POOL_NULL && id <= GUF_ID_POOL_T_MAX);
|
||||||
|
guf_err_set_if_not_null(err, GUF_ERR_NONE);
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
GUF_ID_POOL_T id = pool->next_id;
|
||||||
|
if (id == GUF_ID_POOL_T_MAX || id == GUF_ID_POOL_NULL) {
|
||||||
|
pool->next_id = GUF_ID_POOL_NULL;
|
||||||
|
} else {
|
||||||
|
pool->next_id += 1;
|
||||||
|
}
|
||||||
|
if (id == GUF_ID_POOL_NULL) {
|
||||||
|
guf_err_set_or_panic(err, GUF_ERR_INT_OVERFLOW, GUF_ERR_MSG("in guf_idpool_try_alloc_id: Out of ids."));
|
||||||
|
} else {
|
||||||
|
guf_err_set_if_not_null(err, GUF_ERR_NONE);
|
||||||
|
}
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
GUF_ID_POOL_T GUF_CAT(GUF_ID_POOL_NAME, _alloc_id)(GUF_ID_POOL_NAME *pool)
|
||||||
|
{
|
||||||
|
return GUF_CAT(GUF_ID_POOL_NAME, _try_alloc_id)(pool, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GUF_CAT(GUF_ID_POOL_NAME, _try_free_id)(GUF_ID_POOL_NAME *pool, GUF_ID_POOL_T id, guf_err *err)
|
||||||
|
{
|
||||||
|
GUF_ASSERT(pool);
|
||||||
|
GUF_ASSERT(GUF_CAT(GUF_ID_POOL_NAME, _is_valid)(pool));
|
||||||
|
|
||||||
|
if (id == GUF_ID_POOL_NULL) { // ID is NULL.
|
||||||
|
guf_err_set_or_panic(err, GUF_ERR_IDX_RANGE, GUF_ERR_MSG("in guf_idpool_try_free_id: id is NULL"));
|
||||||
|
return;
|
||||||
|
} else if (id < 0 || id >= pool->next_id) { // ID is beyond the allocated range.
|
||||||
|
guf_err_set_or_panic(err, GUF_ERR_IDX_RANGE, GUF_ERR_MSG("in guf_idpool_try_free_id: id < 0 or id >= pool->next_id"));
|
||||||
|
return;
|
||||||
|
} else if (pool->next_id == 0) { // There haven't been any IDs allocated yet.
|
||||||
|
GUF_ASSERT(pool->free_id_dbuf.size == 0);
|
||||||
|
guf_err_set_or_panic(err, GUF_ERR_IDX_RANGE, GUF_ERR_MSG("in guf_idpool_try_free_id: pool->next_id == 0"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
// NOTE: https://github.com/erincatto/box2d/issues/893 (last-retrieved 2025-04-09)
|
||||||
|
if (id == pool->next_id - 1) { // Optimisation in case we remove the largest allocated id so far
|
||||||
|
pool->next_id -= 1;
|
||||||
|
guf_err_set_if_not_null(err, GUF_ERR_NONE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
GUF_CAT(GUF_ID_POOL_DBUF, _try_push_val)(&pool->free_id_dbuf, id, err);
|
||||||
|
if (err && *err != GUF_ERR_NONE) {
|
||||||
|
guf_err_set_or_panic(err, *err, GUF_ERR_MSG("in guf_idpool_try_free_id: failed to push id"));
|
||||||
|
} else {
|
||||||
|
guf_err_set_if_not_null(err, GUF_ERR_NONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GUF_CAT(GUF_ID_POOL_NAME, _free_id)(GUF_ID_POOL_NAME *pool, GUF_ID_POOL_T id)
|
||||||
|
{
|
||||||
|
GUF_CAT(GUF_ID_POOL_NAME, _try_free_id)(pool, id, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* end impl */
|
||||||
|
|
||||||
|
#endif /* end only-types */
|
||||||
|
|
||||||
|
#undef GUF_ID_POOL_KWRDS
|
||||||
|
#undef GUF_ID_POOL_IMPL
|
||||||
|
#undef GUF_ID_POOL_IMPL_STATIC
|
||||||
|
|
||||||
|
#undef GUF_ID_POOL_T
|
||||||
|
#undef GUF_ID_POOL_T_MAX
|
||||||
|
#undef GUF_ID_POOL_NULL
|
||||||
|
#undef GUF_ID_POOL_NAME
|
||||||
|
|
||||||
17
libguf/src/guf_init.h
Executable file
17
libguf/src/guf_init.h
Executable file
@ -0,0 +1,17 @@
|
|||||||
|
/*
|
||||||
|
is parametrized: no, but must to be implemented once (libguf always requires implementing guf_assert and guf_hash)
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef GUF_INIT_H
|
||||||
|
#define GUF_INIT_H
|
||||||
|
|
||||||
|
#include "guf_common.h"
|
||||||
|
|
||||||
|
// Set up the global panic handler.
|
||||||
|
#define GUF_INIT
|
||||||
|
#include "guf_assert.h"
|
||||||
|
|
||||||
|
#define GUF_HASH_IMPL
|
||||||
|
#include "guf_hash.h"
|
||||||
|
|
||||||
|
#endif
|
||||||
891
libguf/src/guf_linalg.h
Executable file
891
libguf/src/guf_linalg.h
Executable file
@ -0,0 +1,891 @@
|
|||||||
|
/*
|
||||||
|
is parametrized: no
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if defined(GUF_LINALG_IMPL_STATIC)
|
||||||
|
#define GUF_LINALG_KWRDS static
|
||||||
|
#else
|
||||||
|
#define GUF_LINALG_KWRDS
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef GUF_LINALG_H
|
||||||
|
#define GUF_LINALG_H
|
||||||
|
#include "guf_assert.h"
|
||||||
|
#include "guf_math.h"
|
||||||
|
|
||||||
|
#define GUF_QUAT_TO_EULER_EPS (1e-6f)
|
||||||
|
#define GUF_QUAT_TO_AXIS_ANGLE_EPS (1e-6f)
|
||||||
|
// #define GUF_QUAT_NORMALISE_EPS (1e-6f)
|
||||||
|
|
||||||
|
typedef struct guf_vec2 {
|
||||||
|
float x, y;
|
||||||
|
} guf_vec2;
|
||||||
|
|
||||||
|
typedef struct guf_vec3 {
|
||||||
|
float x, y, z;
|
||||||
|
} guf_vec3;
|
||||||
|
|
||||||
|
typedef struct guf_vec4 {
|
||||||
|
float x, y, z, w;
|
||||||
|
} guf_vec4;
|
||||||
|
|
||||||
|
// Row-major-matrix (vectors "are" column-vectors; correct multiplication order: mat * vec)
|
||||||
|
// (e.g. the x-basis vector is (guf_vec4f){.x = m[0][0], .y = m[1][0], .z = m[2][0], .w = m[3][0]})
|
||||||
|
typedef struct guf_mat4x4 {
|
||||||
|
float data[4][4];
|
||||||
|
} guf_mat4x4;
|
||||||
|
|
||||||
|
typedef struct guf_mat3x3 {
|
||||||
|
float data[3][3];
|
||||||
|
} guf_mat3x3;
|
||||||
|
|
||||||
|
typedef struct guf_quaternion {
|
||||||
|
float x, y, z; // Vectorial (imaginary) part.
|
||||||
|
float w; // Scalar (real) part.
|
||||||
|
} guf_quaternion;
|
||||||
|
|
||||||
|
// Convention: Tait-Bryan angles
|
||||||
|
// (order yaw-pitch-roll; intrinsic rotation; active rotation; right handed coordinate system)
|
||||||
|
typedef struct guf_euler_angles {
|
||||||
|
float yaw, pitch, roll;
|
||||||
|
} guf_euler_angles;
|
||||||
|
|
||||||
|
typedef struct guf_axis_angle {
|
||||||
|
guf_vec3 axis;
|
||||||
|
float angle;
|
||||||
|
} guf_axis_angle;
|
||||||
|
|
||||||
|
GUF_LINALG_KWRDS void guf_mat4x4_init_zero(guf_mat4x4 *m);
|
||||||
|
GUF_LINALG_KWRDS void guf_mat4x4_init_identity(guf_mat4x4 *m);
|
||||||
|
GUF_LINALG_KWRDS void guf_mat4x4_init_with_basis(guf_mat4x4 *m, guf_vec3 x_basis, guf_vec3 y_basis, guf_vec3 z_basis);
|
||||||
|
GUF_LINALG_KWRDS void guf_mat4x4_init_from_quaternion(guf_mat4x4 *rotmat, guf_quaternion q);
|
||||||
|
|
||||||
|
GUF_LINALG_KWRDS void guf_mat3x3_init_zero(guf_mat3x3 *m);
|
||||||
|
GUF_LINALG_KWRDS void guf_mat3x3_init_identity(guf_mat3x3 *m);
|
||||||
|
GUF_LINALG_KWRDS void guf_mat3x3_init_with_basis(guf_mat3x3 *m, guf_vec3 x_basis, guf_vec3 y_basis, guf_vec3 z_basis);
|
||||||
|
GUF_LINALG_KWRDS void guf_mat3x3_init_from_quaternion(guf_mat3x3 *rotmat, guf_quaternion q);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
// C++ has no restrict keyword like C99...
|
||||||
|
GUF_LINALG_KWRDS void guf_mat4x4_transposed(const guf_mat4x4 *m, guf_mat4x4 *transposed);
|
||||||
|
#else
|
||||||
|
GUF_LINALG_KWRDS void guf_mat4x4_transposed(const guf_mat4x4 *m, guf_mat4x4 * restrict transposed);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
// C++ has no restrict keyword like C99...
|
||||||
|
GUF_LINALG_KWRDS void guf_mat3x3_transposed(const guf_mat3x3 *m, guf_mat3x3 *transposed);
|
||||||
|
#else
|
||||||
|
GUF_LINALG_KWRDS void guf_mat3x3_transposed(const guf_mat3x3 *m, guf_mat3x3 * restrict transposed);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
// C++ has no restrict keyword like C99...
|
||||||
|
GUF_LINALG_KWRDS void guf_mat4x4_mul(const guf_mat4x4 *a, const guf_mat4x4 *b, guf_mat4x4 *result);
|
||||||
|
#else
|
||||||
|
GUF_LINALG_KWRDS void guf_mat4x4_mul(const guf_mat4x4 *a, const guf_mat4x4 *b, guf_mat4x4 * restrict result);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
GUF_LINALG_KWRDS bool guf_mat4x4_inverted(const guf_mat4x4 *m, guf_mat4x4 *inverted); // Using Gaussian elemination/row-reduction.
|
||||||
|
GUF_LINALG_KWRDS bool guf_mat3x3_inverted(const guf_mat3x3 *m, guf_mat3x3 *inverted); // Using a faster approach from wikipedia I don't get.
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
// C++ has no restrict keyword like C99...
|
||||||
|
GUF_LINALG_KWRDS void guf_mat3x3_mul(const guf_mat3x3 *a, const guf_mat3x3 *b, guf_mat3x3 *result);
|
||||||
|
#else
|
||||||
|
GUF_LINALG_KWRDS void guf_mat3x3_mul(const guf_mat3x3 *a, const guf_mat3x3 *b, guf_mat3x3 * restrict result);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static inline void guf_mat4x4_set_trans(guf_mat4x4 *m, guf_vec3 trans)
|
||||||
|
{
|
||||||
|
GUF_ASSERT(m);
|
||||||
|
m->data[0][3] = trans.x;
|
||||||
|
m->data[1][3] = trans.y;
|
||||||
|
m->data[2][3] = trans.z;
|
||||||
|
}
|
||||||
|
|
||||||
|
GUF_LINALG_KWRDS void guf_mat4x4_set_rotmat(guf_mat4x4 *m, const guf_mat3x3 *rotmat_3x3);
|
||||||
|
|
||||||
|
GUF_LINALG_KWRDS bool guf_mat4x4_is_identity(const guf_mat4x4 *m, float eps);
|
||||||
|
GUF_LINALG_KWRDS bool guf_mat3x3_is_identity(const guf_mat3x3 *m, float eps);
|
||||||
|
|
||||||
|
GUF_LINALG_KWRDS bool guf_mat4x4_nearly_equal(const guf_mat4x4 *a, const guf_mat4x4 *b, float rel_tol, float abs_tol);
|
||||||
|
GUF_LINALG_KWRDS bool guf_mat3x3_nearly_equal(const guf_mat3x3 *a, const guf_mat3x3 *b, float rel_tol, float abs_tol);
|
||||||
|
|
||||||
|
GUF_LINALG_KWRDS guf_vec3 guf_vec3_transformed(const guf_mat4x4 *mat, const guf_vec3 *v);
|
||||||
|
GUF_LINALG_KWRDS guf_vec4 guf_vec4_transformed(const guf_mat4x4 *mat, const guf_vec4 *v);
|
||||||
|
|
||||||
|
static inline guf_vec2 guf_vec2_add(guf_vec2 a, guf_vec2 b)
|
||||||
|
{
|
||||||
|
guf_vec2 sum = {
|
||||||
|
.x = a.x + b.x,
|
||||||
|
.y = a.y + b.y
|
||||||
|
};
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
static inline guf_vec2 guf_vec2_sub(guf_vec2 a, guf_vec2 b)
|
||||||
|
{
|
||||||
|
guf_vec2 diff = {
|
||||||
|
.x = a.x - b.x,
|
||||||
|
.y = a.y - b.y
|
||||||
|
};
|
||||||
|
return diff;
|
||||||
|
}
|
||||||
|
static inline guf_vec2 guf_vec2_scaled(guf_vec2 a, float scale)
|
||||||
|
{
|
||||||
|
guf_vec2 s = {.x = a.x * scale, .y = a.y * scale};
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline guf_vec3 guf_vec3_add(guf_vec3 a, guf_vec3 b)
|
||||||
|
{
|
||||||
|
guf_vec3 sum = {
|
||||||
|
.x = a.x + b.x,
|
||||||
|
.y = a.y + b.y,
|
||||||
|
.z = a.z + b.z
|
||||||
|
};
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
static inline guf_vec3 guf_vec3_sub(guf_vec3 a, guf_vec3 b)
|
||||||
|
{
|
||||||
|
guf_vec3 diff = {
|
||||||
|
.x = a.x - b.x,
|
||||||
|
.y = a.y - b.y,
|
||||||
|
.z = a.z - b.z
|
||||||
|
};
|
||||||
|
return diff;
|
||||||
|
}
|
||||||
|
static inline guf_vec3 guf_vec3_scaled(guf_vec3 a, float scale)
|
||||||
|
{
|
||||||
|
guf_vec3 s = {.x = a.x * scale, .y = a.y * scale, .z = a.z * scale};
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline float guf_vec2_mag(guf_vec2 v) {return sqrtf(v.x * v.x + v.y * v.y);}
|
||||||
|
static inline float guf_vec3_mag(guf_vec3 v) {return sqrtf(v.x * v.x + v.y * v.y + v.z * v.z);}
|
||||||
|
static inline float guf_vec2_mag_squared(guf_vec2 v) {return v.x * v.x + v.y * v.y;}
|
||||||
|
static inline float guf_vec3_mag_squared(guf_vec3 v) {return v.x * v.x + v.y * v.y + v.z * v.z;}
|
||||||
|
|
||||||
|
static inline float guf_vec2_dot(guf_vec2 a, guf_vec2 b) {return a.x * b.x + a.y * b.y;}
|
||||||
|
static inline float guf_vec3_dot(guf_vec3 a, guf_vec3 b) {return a.x * b.x + a.y * b.y + a.z * b.z;}
|
||||||
|
|
||||||
|
static inline guf_vec3 guf_vec3_cross(guf_vec3 a, guf_vec3 b)
|
||||||
|
{
|
||||||
|
guf_vec3 cross = {
|
||||||
|
.x = a.y * b.z - a.z * b.y,
|
||||||
|
.y = a.z * b.x - a.x * b.z,
|
||||||
|
.z = a.x * b.y - a.y * b.x
|
||||||
|
};
|
||||||
|
return cross;
|
||||||
|
}
|
||||||
|
static inline float guf_vec2_perpdot(guf_vec2 a, guf_vec2 b)
|
||||||
|
{
|
||||||
|
return a.x * b.y - a.y * b.x; // The z-component of a 3d cross-product of two vectors in a plane.
|
||||||
|
}
|
||||||
|
|
||||||
|
GUF_LINALG_KWRDS guf_vec2 *guf_vec2_normalise(guf_vec2 *v);
|
||||||
|
GUF_LINALG_KWRDS guf_vec3 *guf_vec3_normalise(guf_vec3 *v);
|
||||||
|
|
||||||
|
static inline guf_vec2 guf_vec2_normalised(guf_vec2 v) {return *guf_vec2_normalise(&v);}
|
||||||
|
static inline guf_vec3 guf_vec3_normalised(guf_vec3 v) {return *guf_vec3_normalise(&v);}
|
||||||
|
|
||||||
|
GUF_LINALG_KWRDS guf_vec3 guf_vec4_to_vec3(guf_vec4 v);
|
||||||
|
|
||||||
|
static inline void guf_quaternion_init_identity(guf_quaternion *q)
|
||||||
|
{
|
||||||
|
GUF_ASSERT(q);
|
||||||
|
q->w = 1;
|
||||||
|
q->x = q->y = q->z = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline float guf_quaternion_mag(guf_quaternion q)
|
||||||
|
{
|
||||||
|
return sqrtf(q.w * q.w + q.x * q.x + q.y * q.y + q.z * q.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline float guf_quaternion_mag_squared(guf_quaternion q)
|
||||||
|
{
|
||||||
|
return q.w * q.w + q.x * q.x + q.y * q.y + q.z * q.z;
|
||||||
|
}
|
||||||
|
|
||||||
|
GUF_LINALG_KWRDS guf_quaternion *guf_quaternion_normalise(guf_quaternion *q);
|
||||||
|
|
||||||
|
static inline guf_quaternion guf_quaternion_normalised(guf_quaternion q)
|
||||||
|
{
|
||||||
|
return *guf_quaternion_normalise(&q);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline guf_quaternion guf_quaternion_inverted(guf_quaternion q)
|
||||||
|
{
|
||||||
|
q.x = -q.x;
|
||||||
|
q.y = -q.y;
|
||||||
|
q.z = -q.z;
|
||||||
|
return q;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
// C++ has no restrict keyword like C99...
|
||||||
|
GUF_LINALG_KWRDS void guf_quaternion_mul(const guf_quaternion *a, const guf_quaternion *b, guf_quaternion *result);
|
||||||
|
#else
|
||||||
|
GUF_LINALG_KWRDS void guf_quaternion_mul(const guf_quaternion *a, const guf_quaternion *b, guf_quaternion * restrict result);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
GUF_LINALG_KWRDS guf_vec3 *guf_vec3_rotate_by_quat(guf_vec3 *p, const guf_quaternion *q);
|
||||||
|
static inline guf_vec3 guf_vec3_rotated_by_quat(guf_vec3 p, const guf_quaternion *q)
|
||||||
|
{
|
||||||
|
return *guf_vec3_rotate_by_quat(&p, q);
|
||||||
|
}
|
||||||
|
|
||||||
|
GUF_LINALG_KWRDS guf_quaternion guf_quaternion_from_axis_angle(float angle, guf_vec3 unit_axis);
|
||||||
|
|
||||||
|
GUF_LINALG_KWRDS guf_quaternion guf_quaternion_from_euler(guf_euler_angles euler);
|
||||||
|
GUF_LINALG_KWRDS guf_euler_angles guf_quaternion_to_euler(guf_quaternion q);
|
||||||
|
|
||||||
|
GUF_LINALG_KWRDS void guf_mat4x4_print(const guf_mat4x4 *m, FILE *outfile);
|
||||||
|
GUF_LINALG_KWRDS void guf_mat4x4_print_with_precision(const guf_mat4x4 *m, FILE *outfile, int num_frac_digits);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
#if defined(GUF_LINALG_IMPL) || defined(GUF_LINALG_IMPL_STATIC)
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
GUF_LINALG_KWRDS void guf_mat4x4_init_zero(guf_mat4x4 *m)
|
||||||
|
{
|
||||||
|
GUF_ASSERT(m);
|
||||||
|
for (int row = 0; row < 4; ++row) {
|
||||||
|
for (int col = 0; col < 4; ++col) {
|
||||||
|
m->data[row][col] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GUF_LINALG_KWRDS void guf_mat3x3_init_zero(guf_mat3x3 *m)
|
||||||
|
{
|
||||||
|
GUF_ASSERT(m);
|
||||||
|
for (int row = 0; row < 3; ++row) {
|
||||||
|
for (int col = 0; col < 3; ++col) {
|
||||||
|
m->data[row][col] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GUF_LINALG_KWRDS void guf_mat4x4_init_identity(guf_mat4x4 *m)
|
||||||
|
{
|
||||||
|
GUF_ASSERT(m);
|
||||||
|
guf_mat4x4_init_zero(m);
|
||||||
|
for (int i = 0; i < 4; ++i) {
|
||||||
|
m->data[i][i] = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GUF_LINALG_KWRDS void guf_mat3x3_init_identity(guf_mat3x3 *m)
|
||||||
|
{
|
||||||
|
GUF_ASSERT(m);
|
||||||
|
guf_mat3x3_init_zero(m);
|
||||||
|
for (int i = 0; i < 3; ++i) {
|
||||||
|
m->data[i][i] = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GUF_LINALG_KWRDS void guf_mat4x4_init_with_basis(guf_mat4x4 *m, guf_vec3 x_basis, guf_vec3 y_basis, guf_vec3 z_basis)
|
||||||
|
{
|
||||||
|
GUF_ASSERT(m);
|
||||||
|
|
||||||
|
m->data[0][0] = x_basis.x;
|
||||||
|
m->data[1][0] = x_basis.y;
|
||||||
|
m->data[2][0] = x_basis.z;
|
||||||
|
m->data[3][0] = 0;
|
||||||
|
|
||||||
|
m->data[0][1] = y_basis.x;
|
||||||
|
m->data[1][1] = y_basis.y;
|
||||||
|
m->data[2][1] = y_basis.z;
|
||||||
|
m->data[3][1] = 0;
|
||||||
|
|
||||||
|
m->data[0][2] = z_basis.x;
|
||||||
|
m->data[1][2] = z_basis.y;
|
||||||
|
m->data[2][2] = z_basis.z;
|
||||||
|
m->data[3][2] = 0;
|
||||||
|
|
||||||
|
m->data[0][3] = m->data[1][3] = m->data[2][3] = 0;
|
||||||
|
m->data[3][3] = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
GUF_LINALG_KWRDS void guf_mat3x3_init_with_basis(guf_mat3x3 *m, guf_vec3 x_basis, guf_vec3 y_basis, guf_vec3 z_basis)
|
||||||
|
{
|
||||||
|
GUF_ASSERT(m);
|
||||||
|
|
||||||
|
m->data[0][0] = x_basis.x;
|
||||||
|
m->data[1][0] = x_basis.y;
|
||||||
|
m->data[2][0] = x_basis.z;
|
||||||
|
|
||||||
|
m->data[0][1] = y_basis.x;
|
||||||
|
m->data[1][1] = y_basis.y;
|
||||||
|
m->data[2][1] = y_basis.z;
|
||||||
|
|
||||||
|
m->data[0][2] = z_basis.x;
|
||||||
|
m->data[1][2] = z_basis.y;
|
||||||
|
m->data[2][2] = z_basis.z;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(__cplusplus) && defined(GUF_LINALG_IMPL_STATIC)
|
||||||
|
GUF_LINALG_KWRDS void guf_mat4x4_mul(const guf_mat4x4 *a, const guf_mat4x4 *b, guf_mat4x4 *result)
|
||||||
|
#else
|
||||||
|
GUF_LINALG_KWRDS void guf_mat4x4_mul(const guf_mat4x4 *a, const guf_mat4x4 *b, guf_mat4x4 * restrict result)
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
GUF_ASSERT(a && b && result);
|
||||||
|
GUF_ASSERT(a != result && b != result);
|
||||||
|
for (int i = 0; i < 4; ++i) {
|
||||||
|
for (int j = 0; j < 4; ++j) {
|
||||||
|
result->data[j][i] = 0;
|
||||||
|
for (int k = 0; k < 4; ++k) {
|
||||||
|
result->data[j][i] += a->data[j][k] * b->data[k][i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(__cplusplus) && defined(GUF_LINALG_IMPL_STATIC)
|
||||||
|
GUF_LINALG_KWRDS void guf_mat3x3_mul(const guf_mat3x3 *a, const guf_mat3x3 *b, guf_mat3x3 *result)
|
||||||
|
#else
|
||||||
|
GUF_LINALG_KWRDS void guf_mat3x3_mul(const guf_mat3x3 *a, const guf_mat3x3 *b, guf_mat3x3 * restrict result)
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
GUF_ASSERT(a && b && result);
|
||||||
|
GUF_ASSERT(a != result && b != result);
|
||||||
|
for (int i = 0; i < 3; ++i) {
|
||||||
|
for (int j = 0; j < 3; ++j) {
|
||||||
|
result->data[j][i] = 0;
|
||||||
|
for (int k = 0; k < 3; ++k) {
|
||||||
|
result->data[j][i] += a->data[j][k] * b->data[k][i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(__cplusplus) && defined(GUF_LINALG_IMPL_STATIC)
|
||||||
|
GUF_LINALG_KWRDS void guf_mat4x4_transposed(const guf_mat4x4 *m, guf_mat4x4 *transposed)
|
||||||
|
#else
|
||||||
|
GUF_LINALG_KWRDS void guf_mat4x4_transposed(const guf_mat4x4 *m, guf_mat4x4 * restrict transposed)
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
GUF_ASSERT(m && transposed);
|
||||||
|
GUF_ASSERT(m != transposed);
|
||||||
|
for (int i = 0; i < 4; ++i) {
|
||||||
|
for (int j = 0; j < 4; ++j) {
|
||||||
|
transposed->data[i][j] = m->data[j][i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(__cplusplus) && defined(GUF_LINALG_IMPL_STATIC)
|
||||||
|
GUF_LINALG_KWRDS void guf_mat3x3_transposed(const guf_mat3x3 *m, guf_mat3x3 *transposed)
|
||||||
|
#else
|
||||||
|
GUF_LINALG_KWRDS void guf_mat3x3_transposed(const guf_mat3x3 *m, guf_mat3x3 * restrict transposed)
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
GUF_ASSERT(m && transposed);
|
||||||
|
GUF_ASSERT(m != transposed);
|
||||||
|
for (int i = 0; i < 3; ++i) {
|
||||||
|
for (int j = 0; j < 3; ++j) {
|
||||||
|
transposed->data[i][j] = m->data[j][i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GUF_LINALG_KWRDS void guf_mat4x4_set_rotmat(guf_mat4x4 *m, const guf_mat3x3 *rotmat_3x3)
|
||||||
|
{
|
||||||
|
GUF_ASSERT(m && rotmat_3x3)
|
||||||
|
for (int row = 0; row < 3; ++row) {
|
||||||
|
for (int col = 0; col < 3; ++col) {
|
||||||
|
m->data[row][col] = rotmat_3x3->data[row][col];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GUF_LINALG_KWRDS bool guf_mat3x3_is_identity(const guf_mat3x3 *m, float eps)
|
||||||
|
{
|
||||||
|
for (int row = 0; row < 3; ++row) {
|
||||||
|
for (int col = 0; col < 3; ++col) {
|
||||||
|
if (row == col) { // Check 1-entries.
|
||||||
|
if (!guf_nearly_one_f32(m->data[row][col], eps)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else { // Check 0-entries.
|
||||||
|
if (!guf_nearly_zero_f32(m->data[row][col], eps)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
GUF_LINALG_KWRDS bool guf_mat4x4_is_identity(const guf_mat4x4 *m, float eps)
|
||||||
|
{
|
||||||
|
for (int row = 0; row < 4; ++row) {
|
||||||
|
for (int col = 0; col < 4; ++col) {
|
||||||
|
if (row == col) { // Check 1-entries.
|
||||||
|
if (!guf_nearly_one_f32(m->data[row][col], eps)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else { // Check 0-entries.
|
||||||
|
if (!guf_nearly_zero_f32(m->data[row][col], eps)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
GUF_LINALG_KWRDS bool guf_mat3x3_nearly_equal(const guf_mat3x3 *a, const guf_mat3x3 *b, float rel_tol, float abs_tol)
|
||||||
|
{
|
||||||
|
GUF_ASSERT(a && b);
|
||||||
|
for (int row = 0; row < 3; ++row) {
|
||||||
|
for (int col = 0; col < 3; ++col) {
|
||||||
|
if (!guf_isclose_f32(a->data[row][col], b->data[row][col], rel_tol, abs_tol)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
GUF_LINALG_KWRDS bool guf_mat4x4_nearly_equal(const guf_mat4x4 *a, const guf_mat4x4 *b, float rel_tol, float abs_tol)
|
||||||
|
{
|
||||||
|
GUF_ASSERT(a && b);
|
||||||
|
for (int row = 0; row < 4; ++row) {
|
||||||
|
for (int col = 0; col < 4; ++col) {
|
||||||
|
if (!guf_isclose_f32(a->data[row][col], b->data[row][col], rel_tol, abs_tol)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void guf_mat4x4_swap_rows(guf_mat4x4 *m, int row_a_idx, int row_b_idx)
|
||||||
|
{
|
||||||
|
GUF_ASSERT(row_a_idx >= 0 && row_a_idx < 4);
|
||||||
|
GUF_ASSERT(row_b_idx >= 0 && row_b_idx < 4);
|
||||||
|
|
||||||
|
if (row_a_idx == row_b_idx) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const size_t ROW_BYTES = sizeof(m->data[0]);
|
||||||
|
float row_a_cpy[4];
|
||||||
|
memcpy(row_a_cpy, m->data + row_a_idx, ROW_BYTES); // row_a_cpy := row_a
|
||||||
|
memcpy(m->data + row_a_idx, m->data + row_b_idx, ROW_BYTES); // row_a := row_b
|
||||||
|
memcpy(m->data + row_b_idx, row_a_cpy, ROW_BYTES); // row_b := row_a_cpy
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Find the inverse of a 4x4 matrix using gaussian elimination (if it is invertible, returns true, false otherwise).
|
||||||
|
cf. https://en.wikipedia.org/wiki/Gaussian_elimination#Finding_the_inverse_of_a_matrix (last-retrieved 2025-03-07)
|
||||||
|
Note: In the special case of square orthonormal matrices, the matrix inverse can be calculated more efficiently
|
||||||
|
and accurately using matrix transposition (e.g. valid 3x3 rotation matrices can be inverted by transposing them).
|
||||||
|
*/
|
||||||
|
GUF_LINALG_KWRDS bool guf_mat4x4_inverted(const guf_mat4x4 *m, guf_mat4x4 *inverted)
|
||||||
|
{
|
||||||
|
GUF_ASSERT(m && inverted);
|
||||||
|
|
||||||
|
guf_mat4x4_init_identity(inverted); // This matrix starts as the identity matrix and will be transfomed into the inverse.
|
||||||
|
guf_mat4x4 rref = *m; // This initial matrix will be transformed into the identity matrix if m is invertible.
|
||||||
|
|
||||||
|
const float ASSERT_EPS = 1e-3f;
|
||||||
|
const float EPS = 1e-18f;
|
||||||
|
|
||||||
|
for (int row_start = 0; row_start < 3; ++row_start) { // 1.) Try to bring into row echelon form.
|
||||||
|
int max_pivot_row = -1;
|
||||||
|
float max_pivot_abs = -INFINITY;
|
||||||
|
for (int row = row_start; row < 4; ++row) { // Find max pivot element in the current column ("Partial pivoting")
|
||||||
|
const float candidate = fabsf(rref.data[row][row_start]);
|
||||||
|
if (!guf_nearly_zero_f32(candidate, EPS) && candidate > max_pivot_abs) {
|
||||||
|
max_pivot_abs = candidate;
|
||||||
|
max_pivot_row = row;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (max_pivot_row == -1) { // If all potential pivots were zero, m is not invertible.
|
||||||
|
GUF_ASSERT(max_pivot_abs == -INFINITY);
|
||||||
|
GUF_ASSERT(!guf_mat4x4_is_identity(&rref, ASSERT_EPS));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
GUF_ASSERT(max_pivot_abs > 0);
|
||||||
|
|
||||||
|
// Swap the found pivot-row with the row at row_start.
|
||||||
|
guf_mat4x4_swap_rows(&rref, row_start, max_pivot_row);
|
||||||
|
guf_mat4x4_swap_rows(inverted, row_start, max_pivot_row);
|
||||||
|
const int pivot_idx = row_start; // Same for pivot_col and pivot_row (pivot is the diagonal element)
|
||||||
|
const float pivot_val = rref.data[pivot_idx][pivot_idx];
|
||||||
|
GUF_ASSERT(!isnan(pivot_val));
|
||||||
|
GUF_ASSERT(!guf_nearly_zero_f32(pivot_val, EPS));
|
||||||
|
|
||||||
|
for (int row = pivot_idx + 1; row < 4; ++row) { // Make all values below the pivot element zero.
|
||||||
|
// We want to turn rref.data[row][pivot_idx] into zero.
|
||||||
|
// pivot_val * scale = rref.data[row][pivot_idx] <=> scale = rref.data[row][pivot_idx] / pivot_val
|
||||||
|
const float scale = rref.data[row][pivot_idx] / pivot_val; // TODO: Not sure if it's more accurate to not pre-calculate the scale (like in commit dac1d159b1cfa7d6cd71236d693b2cab6218ba51 (Fri Mar 7 15:37:55 2025 +0100))
|
||||||
|
for (int col = 0; col < 4; ++col) {
|
||||||
|
if (col >= pivot_idx) {
|
||||||
|
rref.data[row][col] -= rref.data[pivot_idx][col] * scale; // Subtract the scaled pivot-row from row
|
||||||
|
} else {
|
||||||
|
GUF_ASSERT(rref.data[pivot_idx][col] == 0);
|
||||||
|
GUF_ASSERT(rref.data[row][col] == 0);
|
||||||
|
}
|
||||||
|
inverted->data[row][col] -= inverted->data[pivot_idx][col] * scale;
|
||||||
|
}
|
||||||
|
rref.data[row][pivot_idx] = 0; // -= rref.data[pivot_idx][pivot_idx] * rref.data[row][pivot_idx] / pivot_val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 3; i >= 0; --i) { // 2.) Try to bring into *reduced* row echelon form.
|
||||||
|
if (rref.data[i][i] == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (int col = 0; col < 4; ++col) { // Scale row_i to make elem[i][i] == 1
|
||||||
|
inverted->data[i][col] /= rref.data[i][i];
|
||||||
|
GUF_ASSERT((col != i) ? rref.data[i][col] == 0 : rref.data[i][col] != 0);
|
||||||
|
}
|
||||||
|
rref.data[i][i] = 1; // rref.data[i][i] / rref.data[i][i] == 1
|
||||||
|
|
||||||
|
for (int row = i - 1; row >= 0; --row) { // Make all elements in column above rref.data[i][i] zero (by adding a scaled row_i to row).
|
||||||
|
for (int col = 0; col < 4; ++col) {
|
||||||
|
inverted->data[row][col] -= inverted->data[i][col] * rref.data[row][i];
|
||||||
|
}
|
||||||
|
rref.data[row][i] = 0; // -= rref.data[i][i] * rref.data[row][i] (== 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GUF_ASSERT(guf_mat4x4_is_identity(&rref, ASSERT_EPS));
|
||||||
|
(void)ASSERT_EPS;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
GUF_LINALG_KWRDS bool guf_mat3x3_inverted(const guf_mat3x3 *m, guf_mat3x3 *inverted)
|
||||||
|
{
|
||||||
|
// TODO: Test this.
|
||||||
|
GUF_ASSERT(m && inverted);
|
||||||
|
const float EPS = 1e-12f; // TODO: Not sure about this EPS.
|
||||||
|
|
||||||
|
// cf. https://en.wikipedia.org/wiki/Determinant (last-retrieved 2025-03-08)
|
||||||
|
const float det = m->data[0][0] * m->data[1][1] * m->data[2][2] +
|
||||||
|
m->data[0][1] * m->data[1][2] * m->data[2][0] +
|
||||||
|
m->data[0][2] * m->data[1][0] * m->data[2][1] -
|
||||||
|
m->data[0][1] * m->data[1][0] * m->data[2][2] -
|
||||||
|
m->data[0][0] * m->data[1][2] * m->data[2][1];
|
||||||
|
|
||||||
|
if (guf_nearly_zero_f32(det, EPS)) { // m is only invertible if the determinant != 0
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// cf. https://en.wikipedia.org/wiki/Invertible_matrix#Inversion_of_3_%C3%97_3_matrices (last-retrieved 2025-03-08)
|
||||||
|
// (I don't understand this method of matrix inversion, but it looks fast...)
|
||||||
|
const float A = (m->data[1][1] * m->data[2][2] - m->data[1][2] * m->data[2][1]);
|
||||||
|
const float B = -(m->data[1][0] * m->data[2][2] - m->data[1][2] * m->data[2][0]);
|
||||||
|
const float C = (m->data[1][0] * m->data[2][1] - m->data[1][1] * m->data[2][0]);
|
||||||
|
const float D = -(m->data[0][1] * m->data[2][2] - m->data[0][2] * m->data[2][1]);
|
||||||
|
const float E = (m->data[0][0] * m->data[2][2] - m->data[0][2] * m->data[2][0]);
|
||||||
|
const float F = -(m->data[0][0] * m->data[2][1] - m->data[0][1] * m->data[2][0]);
|
||||||
|
const float G = (m->data[0][1] * m->data[1][2] - m->data[0][2] * m->data[1][1]);
|
||||||
|
const float H = -(m->data[0][0] * m->data[1][2] - m->data[0][2] * m->data[1][0]);
|
||||||
|
const float I = (m->data[0][0] * m->data[1][1] - m->data[0][1] * m->data[1][0]);
|
||||||
|
|
||||||
|
inverted->data[0][0] = A / det;
|
||||||
|
inverted->data[1][0] = B / det;
|
||||||
|
inverted->data[2][0] = C / det;
|
||||||
|
|
||||||
|
inverted->data[0][1] = D / det;
|
||||||
|
inverted->data[1][1] = E / det;
|
||||||
|
inverted->data[2][1] = F / det;
|
||||||
|
|
||||||
|
inverted->data[0][2] = G / det;
|
||||||
|
inverted->data[1][2] = H / det;
|
||||||
|
inverted->data[2][2] = I / det;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assumes the last row of mat is [0, 0, 0, 1] and v->w implicitly is 1 (affine transformation)
|
||||||
|
GUF_LINALG_KWRDS guf_vec3 guf_vec3_transformed(const guf_mat4x4 *mat, const guf_vec3 *v)
|
||||||
|
{
|
||||||
|
GUF_ASSERT(mat && v);
|
||||||
|
guf_vec3 v_transformed = {
|
||||||
|
.x = mat->data[0][0] * v->x + mat->data[0][1] * v->y + mat->data[0][2] * v->z + mat->data[0][3],
|
||||||
|
.y = mat->data[1][0] * v->x + mat->data[1][1] * v->y + mat->data[1][2] * v->z + mat->data[1][3],
|
||||||
|
.z = mat->data[2][0] * v->x + mat->data[2][1] * v->y + mat->data[2][2] * v->z + mat->data[2][3]
|
||||||
|
};
|
||||||
|
return v_transformed;
|
||||||
|
}
|
||||||
|
|
||||||
|
GUF_LINALG_KWRDS guf_vec4 guf_vec4_transformed(const guf_mat4x4 *mat, const guf_vec4 *v)
|
||||||
|
{
|
||||||
|
GUF_ASSERT(mat && v);
|
||||||
|
guf_vec4 v_transformed = {
|
||||||
|
.x = mat->data[0][0] * v->x + mat->data[0][1] * v->y + mat->data[0][2] * v->z + v->w * mat->data[0][3],
|
||||||
|
.y = mat->data[1][0] * v->x + mat->data[1][1] * v->y + mat->data[1][2] * v->z + v->w * mat->data[1][3],
|
||||||
|
.z = mat->data[2][0] * v->x + mat->data[2][1] * v->y + mat->data[2][2] * v->z + v->w * mat->data[2][3],
|
||||||
|
.w = mat->data[3][0] * v->x + mat->data[3][1] * v->y + mat->data[3][2] * v->z + v->w * mat->data[3][3],
|
||||||
|
};
|
||||||
|
return v_transformed;
|
||||||
|
}
|
||||||
|
|
||||||
|
GUF_LINALG_KWRDS guf_vec2 *guf_vec2_normalise(guf_vec2 *v)
|
||||||
|
{
|
||||||
|
GUF_ASSERT(v);
|
||||||
|
const float mag = guf_vec2_mag(*v);
|
||||||
|
if (mag == 0 || isnan(mag)) {
|
||||||
|
v->x = v->y = (float)INFINITY;
|
||||||
|
} else {
|
||||||
|
v->x /= mag;
|
||||||
|
v->y /= mag;
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
GUF_LINALG_KWRDS guf_vec3 *guf_vec3_normalise(guf_vec3 *v)
|
||||||
|
{
|
||||||
|
GUF_ASSERT(v);
|
||||||
|
const float mag = guf_vec3_mag(*v);
|
||||||
|
if (mag == 0 || isnan(mag)) {
|
||||||
|
v->x = v->y = v->z = (float)INFINITY;
|
||||||
|
} else {
|
||||||
|
v->x /= mag;
|
||||||
|
v->y /= mag;
|
||||||
|
v->z /= mag;
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
GUF_LINALG_KWRDS guf_vec3 guf_vec4_to_vec3(guf_vec4 v)
|
||||||
|
{
|
||||||
|
if (guf_isclose_abstol_f32(v.w, 0, 1e-21f)) { // If w is extremely close 0, we treat it as zero.
|
||||||
|
guf_vec3 inf_vec = {(float)INFINITY, (float)INFINITY, (float)INFINITY};
|
||||||
|
return inf_vec;
|
||||||
|
} else if (guf_isclose_reltol_f32(v.w, 1, 1e-9f)) { // If w is almost 1, we treat it as exactly 1.
|
||||||
|
guf_vec3 vec = {.x = v.x, .y = v.y, .z = v.z};
|
||||||
|
return vec;
|
||||||
|
} else {
|
||||||
|
guf_vec3 vec = {.x = v.x / v.w, .y = v.y / v.w, .z = v.z / v.w};
|
||||||
|
return vec;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
(Unit)quaternion operations:
|
||||||
|
cf. https://danceswithcode.net/engineeringnotes/quaternions/quaternions.html (last-retrieved 2025-03-06)
|
||||||
|
*/
|
||||||
|
|
||||||
|
GUF_LINALG_KWRDS guf_quaternion guf_quaternion_from_axis_angle(float angle, guf_vec3 unit_axis)
|
||||||
|
{
|
||||||
|
const float sin_halfangle = sinf(angle / 2.f);
|
||||||
|
guf_quaternion q = {
|
||||||
|
.w = cosf(angle / 2.f),
|
||||||
|
.x = unit_axis.x * sin_halfangle,
|
||||||
|
.y = unit_axis.y * sin_halfangle,
|
||||||
|
.z = unit_axis.z * sin_halfangle,
|
||||||
|
};
|
||||||
|
return q;
|
||||||
|
}
|
||||||
|
|
||||||
|
GUF_LINALG_KWRDS guf_quaternion guf_quaternion_from_euler(guf_euler_angles euler)
|
||||||
|
{
|
||||||
|
const float r = euler.roll / 2;
|
||||||
|
const float p = euler.pitch / 2;
|
||||||
|
const float y = euler.yaw / 2;
|
||||||
|
guf_quaternion q = {
|
||||||
|
.w = cosf(r) * cosf(p) * cosf(y) + sinf(r) * sinf(p) * sinf(y),
|
||||||
|
.x = sinf(r) * cosf(p) * cosf(y) - cosf(r) * sinf(p) * sinf(y),
|
||||||
|
.y = cosf(r) * sinf(p) * cosf(y) + sinf(r) * cosf(p) * sinf(y),
|
||||||
|
.z = cosf(r) * cosf(p) * sinf(y) + sinf(r) * sinf(p) * cosf(y)
|
||||||
|
};
|
||||||
|
return q;
|
||||||
|
}
|
||||||
|
|
||||||
|
GUF_LINALG_KWRDS guf_euler_angles guf_quaternion_to_euler(guf_quaternion q)
|
||||||
|
{
|
||||||
|
guf_euler_angles euler = {0};
|
||||||
|
euler.pitch = asinf(2 * (q.w * q.y - q.x * q.z));
|
||||||
|
|
||||||
|
const float half_pi = GUF_PI_F32 / 2;
|
||||||
|
if (fabsf(euler.pitch - half_pi) <= GUF_QUAT_TO_EULER_EPS) { // Gimbal lock: pitch == pi/2
|
||||||
|
euler.roll = 0;
|
||||||
|
euler.yaw = -2 * atan2f(q.x, q.w);
|
||||||
|
} else if (fabsf(euler.pitch - (-half_pi)) <= GUF_QUAT_TO_EULER_EPS) { // Gimbal lock: pitch == -pi/2
|
||||||
|
euler.roll = 0;
|
||||||
|
euler.yaw = 2 * atan2f(q.x, q.w);
|
||||||
|
} else { // No gimbal-lock
|
||||||
|
euler.roll = atan2f(2 * (q.w * q.x + q.y * q.z), q.w*q.w - q.x*q.x - q.y*q.y + q.z*q.z);
|
||||||
|
euler.yaw = atan2f(2 * (q.w * q.z + q.x * q.y), q.w*q.w + q.x*q.x - q.y*q.y - q.z*q.z);
|
||||||
|
}
|
||||||
|
return euler;
|
||||||
|
}
|
||||||
|
|
||||||
|
GUF_LINALG_KWRDS float guf_quaternion_to_axis_angle(const guf_quaternion *q, guf_vec3 *axis_result)
|
||||||
|
{
|
||||||
|
GUF_ASSERT(q);
|
||||||
|
if (fabsf(q->w - 1) <= GUF_QUAT_TO_AXIS_ANGLE_EPS) { // Singularity: q->w is very close to 1 (identity-quaternions produce no rotation).
|
||||||
|
const float angle = 0;
|
||||||
|
if (axis_result) {
|
||||||
|
axis_result->x = axis_result->y = axis_result->z = 0;
|
||||||
|
}
|
||||||
|
return angle;
|
||||||
|
}
|
||||||
|
|
||||||
|
const float angle = 2 * acosf(q->w);
|
||||||
|
if (axis_result) {
|
||||||
|
const float sin_halfangle = sinf(angle / 2.f);
|
||||||
|
axis_result->x = q->x / sin_halfangle;
|
||||||
|
axis_result->y = q->y / sin_halfangle;
|
||||||
|
axis_result->z = q->z / sin_halfangle;
|
||||||
|
}
|
||||||
|
return angle;
|
||||||
|
}
|
||||||
|
|
||||||
|
GUF_LINALG_KWRDS void guf_mat4x4_init_from_quaternion(guf_mat4x4 *rotmat, guf_quaternion q)
|
||||||
|
{
|
||||||
|
GUF_ASSERT(rotmat);
|
||||||
|
|
||||||
|
rotmat->data[0][0] = 1 - 2 * q.y * q.y - 2 * q.z * q.z;
|
||||||
|
rotmat->data[0][1] = 2 * q.x * q.y - 2 * q.w * q.z;
|
||||||
|
rotmat->data[0][2] = 2 * q.x * q.z - 2 * q.w * q.y;
|
||||||
|
rotmat->data[0][3] = 0;
|
||||||
|
|
||||||
|
rotmat->data[1][0] = 2 * q.x * q.y + 2 * q.w * q.z;
|
||||||
|
rotmat->data[1][1] = 1 - 2 * q.x * q.x - 2 * q.z * q.z;
|
||||||
|
rotmat->data[1][2] = 2 * q.y * q.z - 2 * q.w * q.x;
|
||||||
|
rotmat->data[1][3] = 0;
|
||||||
|
|
||||||
|
rotmat->data[2][0] = 2 * q.x * q.z - 2 * q.w * q.y;
|
||||||
|
rotmat->data[2][1] = 2 * q.y * q.z + 2 * q.w * q.x;
|
||||||
|
rotmat->data[2][2] = 1 - 2 * q.x * q.x - 2 * q.y * q.y;
|
||||||
|
rotmat->data[2][3] = 0;
|
||||||
|
|
||||||
|
rotmat->data[3][0] = rotmat->data[3][1] = rotmat->data[3][2] = 0;
|
||||||
|
rotmat->data[3][3] = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
GUF_LINALG_KWRDS void guf_mat3x3_init_from_quaternion(guf_mat3x3 *rotmat, guf_quaternion q)
|
||||||
|
{
|
||||||
|
GUF_ASSERT(rotmat);
|
||||||
|
|
||||||
|
rotmat->data[0][0] = 1 - 2 * q.y * q.y - 2 * q.z * q.z;
|
||||||
|
rotmat->data[0][1] = 2 * q.x * q.y - 2 * q.w * q.z;
|
||||||
|
rotmat->data[0][2] = 2 * q.x * q.z - 2 * q.w * q.y;
|
||||||
|
|
||||||
|
rotmat->data[1][0] = 2 * q.x * q.y + 2 * q.w * q.z;
|
||||||
|
rotmat->data[1][1] = 1 - 2 * q.x * q.x - 2 * q.z * q.z;
|
||||||
|
rotmat->data[1][2] = 2 * q.y * q.z - 2 * q.w * q.x;
|
||||||
|
|
||||||
|
rotmat->data[2][0] = 2 * q.x * q.z - 2 * q.w * q.y;
|
||||||
|
rotmat->data[2][1] = 2 * q.y * q.z + 2 * q.w * q.x;
|
||||||
|
rotmat->data[2][2] = 1 - 2 * q.x * q.x - 2 * q.y * q.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
GUF_LINALG_KWRDS guf_quaternion *guf_quaternion_normalise(guf_quaternion *q)
|
||||||
|
{
|
||||||
|
GUF_ASSERT(q);
|
||||||
|
const float sq_mag = guf_quaternion_mag_squared(*q);
|
||||||
|
if (sq_mag == 0 || sq_mag == 1) { // Prevent div by zero and don't normalise if already normalised.
|
||||||
|
return q;
|
||||||
|
}
|
||||||
|
|
||||||
|
const float mag = sqrtf(sq_mag);
|
||||||
|
if (mag == 0) { // Just in case.
|
||||||
|
return q;
|
||||||
|
}
|
||||||
|
q->w /= mag;
|
||||||
|
q->x /= mag;
|
||||||
|
q->y /= mag;
|
||||||
|
q->z /= mag;
|
||||||
|
return q;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(__cplusplus) && defined(GUF_LINALG_IMPL_STATIC)
|
||||||
|
GUF_LINALG_KWRDS void guf_quaternion_mul(const guf_quaternion *a, const guf_quaternion *b, guf_quaternion *result)
|
||||||
|
#else
|
||||||
|
GUF_LINALG_KWRDS void guf_quaternion_mul(const guf_quaternion *a, const guf_quaternion *b, guf_quaternion * restrict result)
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
GUF_ASSERT(a && b && result);
|
||||||
|
GUF_ASSERT(a != result && b != result);
|
||||||
|
result->w = a->w * b->w - a->x * b->x - a->y * b->y - a->z * b->z; // (r0s0 − r1s1 − r2s2 − r3s3)
|
||||||
|
result->x = a->w * b->x + a->x * b->w - a->y * b->z + a->z * b->y; // (r0s1 + r1s0 − r2s3 + r3s2)
|
||||||
|
result->y = a->w * b->y + a->x * b->z + a->y * b->w - a->z * b->x; // (r0s2 + r1s3 + r2s0 − r3s1)
|
||||||
|
result->z = a->w * b->z - a->x * b->y + a->y * b->x + a->z * b->w; // (r0s3 − r1s2 + r2s1 + r3s0)
|
||||||
|
}
|
||||||
|
|
||||||
|
GUF_LINALG_KWRDS guf_vec3 *guf_vec3_rotate_by_quat(guf_vec3 *p, const guf_quaternion *q)
|
||||||
|
{
|
||||||
|
// Active rotation of the point/vector p by the quaternion q.
|
||||||
|
GUF_ASSERT(p && q);
|
||||||
|
const guf_quaternion inv_q = guf_quaternion_inverted(*q);
|
||||||
|
guf_quaternion p_quat = {.w = 0, .x = p->x, .y = p->y, .z = p->z};
|
||||||
|
|
||||||
|
// (x) active rotation: inv(q) * p_quat * q (the point is rotated with respect to the coordinate system)
|
||||||
|
// ( ) passive rotation: q * p_quat * inv(q) (the coordinate system is rotated with respect to the point)
|
||||||
|
guf_quaternion tmp;
|
||||||
|
guf_quaternion_mul(&inv_q, &p_quat, &tmp); // tmp := inv(q) * pquat
|
||||||
|
guf_quaternion_mul(&tmp, q, &p_quat); // pquat := tmp * q
|
||||||
|
|
||||||
|
p->x = p_quat.x;
|
||||||
|
p->y = p_quat.y;
|
||||||
|
p->z = p_quat.z;
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
GUF_LINALG_KWRDS void guf_mat4x4_print_with_precision(const guf_mat4x4 *m, FILE *outfile, int num_frac_digits)
|
||||||
|
{
|
||||||
|
if (!outfile) {
|
||||||
|
outfile = stdout;
|
||||||
|
}
|
||||||
|
if (!m) {
|
||||||
|
fprintf(outfile, "guf_mat4x4 NULL\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int max_digits = 1;
|
||||||
|
for (int row = 0; row < 4; ++row) { // Find how many digits before the . we need.
|
||||||
|
for (int col = 0; col < 4; ++col) {
|
||||||
|
const float elem = m->data[row][col];
|
||||||
|
int add_digits = 0;
|
||||||
|
if (isnan(elem) || isinf(elem)) {
|
||||||
|
add_digits += 3;
|
||||||
|
}
|
||||||
|
if (elem == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const float elem_log10 = floorf(log10f(fabsf(elem)));
|
||||||
|
int digits = (int)elem_log10 + add_digits;
|
||||||
|
digits = GUF_CLAMP(digits, 0, 32);
|
||||||
|
if (digits > max_digits) {
|
||||||
|
max_digits = digits;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const int whole_digits = max_digits + 1;
|
||||||
|
num_frac_digits = GUF_CLAMP(num_frac_digits, 0, 32);
|
||||||
|
|
||||||
|
for (int row = 0; row < 4; ++row) {
|
||||||
|
for (int col = 0; col < 4; ++col) {
|
||||||
|
fprintf(outfile, "% *.*f ", whole_digits + num_frac_digits + 2, num_frac_digits, (double)m->data[row][col]);
|
||||||
|
}
|
||||||
|
fputc('\n', outfile);
|
||||||
|
}
|
||||||
|
fputc('\n', outfile);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
GUF_LINALG_KWRDS void guf_mat4x4_print(const guf_mat4x4 *m, FILE *outfile)
|
||||||
|
{
|
||||||
|
const int DEFAULT_FRAC_DIGITS = 3;
|
||||||
|
guf_mat4x4_print_with_precision(m, outfile, DEFAULT_FRAC_DIGITS);
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef GUF_LINALG_IMPL
|
||||||
|
#undef GUF_LINALG_IMPL_STATIC
|
||||||
|
|
||||||
|
#endif /* end impl */
|
||||||
|
|
||||||
|
#undef GUF_LINALG_KWRDS
|
||||||
421
libguf/src/guf_math.h
Executable file
421
libguf/src/guf_math.h
Executable file
@ -0,0 +1,421 @@
|
|||||||
|
/*
|
||||||
|
is parametrized: no
|
||||||
|
*/
|
||||||
|
#ifndef GUF_MATH_H
|
||||||
|
#define GUF_MATH_H
|
||||||
|
#include "guf_common.h"
|
||||||
|
#include "guf_assert.h"
|
||||||
|
#include <math.h>
|
||||||
|
#include <float.h>
|
||||||
|
|
||||||
|
#define GUF_PI 3.14159265358979323846264338327950288
|
||||||
|
#define GUF_PI_F32 3.14159265358979323846264338327950288f
|
||||||
|
|
||||||
|
// Biggest float/double less than one, cf. https://stackoverflow.com/a/33832753 (last-retrieved 2025-03-05)
|
||||||
|
#define GUF_MAX_F32_LT_ONE (1.f - FLT_EPSILON/FLT_RADIX)
|
||||||
|
#define GUF_MAX_F64_LT_ONE (1.0 - DBL_EPSILON/FLT_RADIX)
|
||||||
|
|
||||||
|
// Typesafe unsigned integer wrapping functions (generated with libguf/tools/intwrap-gen.py)
|
||||||
|
static inline uint_least8_t guf_wrap8_least_u8(uint_least8_t a) { return a & GUF_UINT8_MAX; }
|
||||||
|
static inline uint_least16_t guf_wrap16_least_u16(uint_least16_t a) { return a & GUF_UINT16_MAX; }
|
||||||
|
static inline uint_least32_t guf_wrap32_least_u32(uint_least32_t a) { return a & GUF_UINT32_MAX; }
|
||||||
|
static inline uint_least64_t guf_wrap64_least_u64(uint_least64_t a) { return a & GUF_UINT64_MAX; }
|
||||||
|
|
||||||
|
static inline unsigned char guf_wrap8_uchar(unsigned char a) { return a & GUF_UINT8_MAX; } // unsigned char: >= 8 bits
|
||||||
|
static inline unsigned short guf_wrap16_ushort(unsigned short a) { return a & GUF_UINT16_MAX; } // unsigned short: >= 16 bits
|
||||||
|
static inline unsigned long guf_wrap32_ulong(unsigned long a) { return a & GUF_UINT32_MAX; } // unsigned long: >= 32 bits
|
||||||
|
static inline unsigned long long guf_wrap64_ulong_long(unsigned long long a) { return a & GUF_UINT64_MAX; } // unsigned long long: >= 64 bits
|
||||||
|
|
||||||
|
// Rotate left.
|
||||||
|
#ifdef UINT32_MAX
|
||||||
|
static inline uint32_t guf_rotl_u32(uint32_t x, int k)
|
||||||
|
{
|
||||||
|
GUF_ASSERT(k > 0);
|
||||||
|
return (1u*x << k) | (1u*x >> (32 - k));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef UINT64_MAX
|
||||||
|
static inline uint64_t guf_rotl_u64(uint64_t x, int k)
|
||||||
|
{
|
||||||
|
GUF_ASSERT(k > 0);
|
||||||
|
return (1u*x << k) | (1u*x >> (64 - k));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static inline uint_least32_t guf_rotl32_least_u32(uint_least32_t x, int k)
|
||||||
|
{
|
||||||
|
GUF_ASSERT(k > 0);
|
||||||
|
x = guf_wrap32_least_u32(x);
|
||||||
|
return guf_wrap32_least_u32( (1u*x << k) | (1u*x >> (32 - k)) );
|
||||||
|
}
|
||||||
|
static inline uint_least64_t guf_rotl64_least_u64(uint_least64_t x, int k)
|
||||||
|
{
|
||||||
|
GUF_ASSERT(k > 0);
|
||||||
|
x = guf_wrap64_least_u64(x);
|
||||||
|
return guf_wrap64_least_u64( (1u*x << k) | (1u*x >> (64 - k)) );
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline unsigned long guf_rotl32_ulong(unsigned long x, int k)
|
||||||
|
{
|
||||||
|
GUF_ASSERT(k > 0);
|
||||||
|
x = guf_wrap32_ulong(x);
|
||||||
|
return guf_wrap32_ulong( (x << k) | (x >> (32 - k)) );
|
||||||
|
}
|
||||||
|
static inline unsigned long long guf_rotl64_ulong_long(unsigned long long x, int k)
|
||||||
|
{
|
||||||
|
GUF_ASSERT(k > 0);
|
||||||
|
x = guf_wrap64_ulong_long(x);
|
||||||
|
return guf_wrap64_ulong_long( (x << k) | (x >> (64 - k)) );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Signed min, max, clamp functions (generated with libguf/tools/min_max_clamp-gen.py)
|
||||||
|
static inline char guf_min_char(char a, char b) { return a < b ? a : b; }
|
||||||
|
static inline char guf_max_char(char a, char b) { return a > b ? a : b; }
|
||||||
|
static inline char guf_clamp_char(char x, char min, char max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
|
||||||
|
|
||||||
|
static inline int guf_min_int(int a, int b) { return a < b ? a : b; }
|
||||||
|
static inline int guf_max_int(int a, int b) { return a > b ? a : b; }
|
||||||
|
static inline int guf_clamp_int(int x, int min, int max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
|
||||||
|
|
||||||
|
static inline long guf_min_long(long a, long b) { return a < b ? a : b; }
|
||||||
|
static inline long guf_max_long(long a, long b) { return a > b ? a : b; }
|
||||||
|
static inline long guf_clamp_long(long x, long min, long max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
|
||||||
|
|
||||||
|
static inline long long guf_min_long_long(long long a, long long b) { return a < b ? a : b; }
|
||||||
|
static inline long long guf_max_long_long(long long a, long long b) { return a > b ? a : b; }
|
||||||
|
static inline long long guf_clamp_long_long(long long x, long long min, long long max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
|
||||||
|
|
||||||
|
static inline ptrdiff_t guf_min_ptrdiff_t(ptrdiff_t a, ptrdiff_t b) { return a < b ? a : b; }
|
||||||
|
static inline ptrdiff_t guf_max_ptrdiff_t(ptrdiff_t a, ptrdiff_t b) { return a > b ? a : b; }
|
||||||
|
static inline ptrdiff_t guf_clamp_ptrdiff_t(ptrdiff_t x, ptrdiff_t min, ptrdiff_t max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
|
||||||
|
|
||||||
|
static inline int_least8_t guf_min_least_i8(int_least8_t a, int_least8_t b) { return a < b ? a : b; }
|
||||||
|
static inline int_least8_t guf_max_least_i8(int_least8_t a, int_least8_t b) { return a > b ? a : b; }
|
||||||
|
static inline int_least8_t guf_clamp_least_i8(int_least8_t x, int_least8_t min, int_least8_t max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
|
||||||
|
|
||||||
|
static inline int_least16_t guf_min_least_i16(int_least16_t a, int_least16_t b) { return a < b ? a : b; }
|
||||||
|
static inline int_least16_t guf_max_least_i16(int_least16_t a, int_least16_t b) { return a > b ? a : b; }
|
||||||
|
static inline int_least16_t guf_clamp_least_i16(int_least16_t x, int_least16_t min, int_least16_t max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
|
||||||
|
|
||||||
|
static inline int_least32_t guf_min_least_i32(int_least32_t a, int_least32_t b) { return a < b ? a : b; }
|
||||||
|
static inline int_least32_t guf_max_least_i32(int_least32_t a, int_least32_t b) { return a > b ? a : b; }
|
||||||
|
static inline int_least32_t guf_clamp_least_i32(int_least32_t x, int_least32_t min, int_least32_t max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
|
||||||
|
|
||||||
|
static inline int_least64_t guf_min_least_i64(int_least64_t a, int_least64_t b) { return a < b ? a : b; }
|
||||||
|
static inline int_least64_t guf_max_least_i64(int_least64_t a, int_least64_t b) { return a > b ? a : b; }
|
||||||
|
static inline int_least64_t guf_clamp_least_i64(int_least64_t x, int_least64_t min, int_least64_t max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
|
||||||
|
|
||||||
|
static inline float guf_min_f32(float a, float b) { return a < b ? a : b; }
|
||||||
|
static inline float guf_max_f32(float a, float b) { return a > b ? a : b; }
|
||||||
|
static inline float guf_clamp_f32(float x, float min, float max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
|
||||||
|
|
||||||
|
static inline double guf_min_f64(double a, double b) { return a < b ? a : b; }
|
||||||
|
static inline double guf_max_f64(double a, double b) { return a > b ? a : b; }
|
||||||
|
static inline double guf_clamp_f64(double x, double min, double max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
|
||||||
|
|
||||||
|
#ifdef INT8_MAX
|
||||||
|
static inline int8_t guf_min_i8(int8_t a, int8_t b) { return a < b ? a : b; }
|
||||||
|
static inline int8_t guf_max_i8(int8_t a, int8_t b) { return a > b ? a : b; }
|
||||||
|
static inline int8_t guf_clamp_i8(int8_t x, int8_t min, int8_t max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef INT16_MAX
|
||||||
|
static inline int16_t guf_min_i16(int16_t a, int16_t b) { return a < b ? a : b; }
|
||||||
|
static inline int16_t guf_max_i16(int16_t a, int16_t b) { return a > b ? a : b; }
|
||||||
|
static inline int16_t guf_clamp_i16(int16_t x, int16_t min, int16_t max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef INT32_MAX
|
||||||
|
static inline int32_t guf_min_i32(int32_t a, int32_t b) { return a < b ? a : b; }
|
||||||
|
static inline int32_t guf_max_i32(int32_t a, int32_t b) { return a > b ? a : b; }
|
||||||
|
static inline int32_t guf_clamp_i32(int32_t x, int32_t min, int32_t max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef INT64_MAX
|
||||||
|
static inline int64_t guf_min_i64(int64_t a, int64_t b) { return a < b ? a : b; }
|
||||||
|
static inline int64_t guf_max_i64(int64_t a, int64_t b) { return a > b ? a : b; }
|
||||||
|
static inline int64_t guf_clamp_i64(int64_t x, int64_t min, int64_t max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Unsigned min, max, clamp functions (generated with libguf/tools/min_max_clamp-gen.py)
|
||||||
|
static inline unsigned char guf_min_uchar(unsigned char a, unsigned char b) { return a < b ? a : b; }
|
||||||
|
static inline unsigned char guf_max_uchar(unsigned char a, unsigned char b) { return a > b ? a : b; }
|
||||||
|
static inline unsigned char guf_clamp_uchar(unsigned char x, unsigned char min, unsigned char max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
|
||||||
|
|
||||||
|
static inline unsigned guf_min_unsigned(unsigned a, unsigned b) { return a < b ? a : b; }
|
||||||
|
static inline unsigned guf_max_unsigned(unsigned a, unsigned b) { return a > b ? a : b; }
|
||||||
|
static inline unsigned guf_clamp_unsigned(unsigned x, unsigned min, unsigned max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
|
||||||
|
|
||||||
|
static inline unsigned long guf_min_ulong(unsigned long a, unsigned long b) { return a < b ? a : b; }
|
||||||
|
static inline unsigned long guf_max_ulong(unsigned long a, unsigned long b) { return a > b ? a : b; }
|
||||||
|
static inline unsigned long guf_clamp_ulong(unsigned long x, unsigned long min, unsigned long max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
|
||||||
|
|
||||||
|
static inline unsigned long long guf_min_ulong_long(unsigned long long a, unsigned long long b) { return a < b ? a : b; }
|
||||||
|
static inline unsigned long long guf_max_ulong_long(unsigned long long a, unsigned long long b) { return a > b ? a : b; }
|
||||||
|
static inline unsigned long long guf_clamp_ulong_long(unsigned long long x, unsigned long long min, unsigned long long max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
|
||||||
|
|
||||||
|
static inline size_t guf_min_size_t(size_t a, size_t b) { return a < b ? a : b; }
|
||||||
|
static inline size_t guf_max_size_t(size_t a, size_t b) { return a > b ? a : b; }
|
||||||
|
static inline size_t guf_clamp_size_t(size_t x, size_t min, size_t max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
|
||||||
|
|
||||||
|
static inline uint_least8_t guf_min_least_u8(uint_least8_t a, uint_least8_t b) { return a < b ? a : b; }
|
||||||
|
static inline uint_least8_t guf_max_least_u8(uint_least8_t a, uint_least8_t b) { return a > b ? a : b; }
|
||||||
|
static inline uint_least8_t guf_clamp_least_u8(uint_least8_t x, uint_least8_t min, uint_least8_t max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
|
||||||
|
|
||||||
|
static inline uint_least16_t guf_min_least_u16(uint_least16_t a, uint_least16_t b) { return a < b ? a : b; }
|
||||||
|
static inline uint_least16_t guf_max_least_u16(uint_least16_t a, uint_least16_t b) { return a > b ? a : b; }
|
||||||
|
static inline uint_least16_t guf_clamp_least_u16(uint_least16_t x, uint_least16_t min, uint_least16_t max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
|
||||||
|
|
||||||
|
static inline uint_least32_t guf_min_least_u32(uint_least32_t a, uint_least32_t b) { return a < b ? a : b; }
|
||||||
|
static inline uint_least32_t guf_max_least_u32(uint_least32_t a, uint_least32_t b) { return a > b ? a : b; }
|
||||||
|
static inline uint_least32_t guf_clamp_least_u32(uint_least32_t x, uint_least32_t min, uint_least32_t max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
|
||||||
|
|
||||||
|
static inline uint_least64_t guf_min_least_u64(uint_least64_t a, uint_least64_t b) { return a < b ? a : b; }
|
||||||
|
static inline uint_least64_t guf_max_least_u64(uint_least64_t a, uint_least64_t b) { return a > b ? a : b; }
|
||||||
|
static inline uint_least64_t guf_clamp_least_u64(uint_least64_t x, uint_least64_t min, uint_least64_t max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
|
||||||
|
|
||||||
|
#ifdef UINT8_MAX
|
||||||
|
static inline uint8_t guf_min_u8(uint8_t a, uint8_t b) { return a < b ? a : b; }
|
||||||
|
static inline uint8_t guf_max_u8(uint8_t a, uint8_t b) { return a > b ? a : b; }
|
||||||
|
static inline uint8_t guf_clamp_u8(uint8_t x, uint8_t min, uint8_t max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef UINT16_MAX
|
||||||
|
static inline uint16_t guf_min_u16(uint16_t a, uint16_t b) { return a < b ? a : b; }
|
||||||
|
static inline uint16_t guf_max_u16(uint16_t a, uint16_t b) { return a > b ? a : b; }
|
||||||
|
static inline uint16_t guf_clamp_u16(uint16_t x, uint16_t min, uint16_t max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef UINT32_MAX
|
||||||
|
static inline uint32_t guf_min_u32(uint32_t a, uint32_t b) { return a < b ? a : b; }
|
||||||
|
static inline uint32_t guf_max_u32(uint32_t a, uint32_t b) { return a > b ? a : b; }
|
||||||
|
static inline uint32_t guf_clamp_u32(uint32_t x, uint32_t min, uint32_t max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef UINT64_MAX
|
||||||
|
static inline uint64_t guf_min_u64(uint64_t a, uint64_t b) { return a < b ? a : b; }
|
||||||
|
static inline uint64_t guf_max_u64(uint64_t a, uint64_t b) { return a > b ? a : b; }
|
||||||
|
static inline uint64_t guf_clamp_u64(uint64_t x, uint64_t min, uint64_t max) { if (x < min) {return min;} if (x > max) {return max;} return x; }
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
// abs functions with signed result (can fail/panic for abs(INT_TYPE_MIN) for platforms with two's complement signed ints; C2X for example guarantees two's complement)
|
||||||
|
// static inline int guf_abs_int(int x) {if (x >= 0) {return x;} GUF_ASSERT_RELEASE(-INT_MAX == INT_MIN || x > INT_MIN); return -x;} // I would not drink that...
|
||||||
|
// static inline int8_t guf_abs_i8 (int8_t x) {if (x >= 0) {return x;} GUF_ASSERT_RELEASE(-INT8_MAX == INT8_MIN || x > INT8_MIN); return -x;}
|
||||||
|
// static inline int16_t guf_abs_i16(int16_t x) {if (x >= 0) {return x;} GUF_ASSERT_RELEASE(-INT16_MAX == INT16_MIN || x > INT16_MIN); return -x;}
|
||||||
|
// static inline int32_t guf_abs_i32(int32_t x) {if (x >= 0) {return x;} GUF_ASSERT_RELEASE(-INT32_MAX == INT32_MIN || x > INT32_MIN); return -x;}
|
||||||
|
// static inline int64_t guf_abs_i64(int64_t x) {if (x >= 0) {return x;} GUF_ASSERT_RELEASE(-INT64_MAX == INT64_MIN || x > INT64_MIN); return -x;}
|
||||||
|
// static inline ptrdiff_t guf_abs_ptrdiff(ptrdiff_t x) {if (x >= 0) {return x;} GUF_ASSERT_RELEASE(-PTRDIFF_MAX == PTRDIFF_MIN || x > PTRDIFF_MIN); return -x;}
|
||||||
|
|
||||||
|
// abs functions with unsigned result functions (cannot fail)
|
||||||
|
static inline unsigned guf_uabs_int(int x) {if (x >= 0) {return x;} else if (x == INT_MIN && -INT_MAX != INT_MIN) {return (unsigned)INT_MAX + 1u;} else {return -x;}}
|
||||||
|
static inline unsigned long guf_uabs_long(long x) {if (x >= 0) {return x;} else if (x == LONG_MIN && -LONG_MAX != LONG_MIN) {return (unsigned long)LONG_MAX + 1u;} else {return -x;}}
|
||||||
|
static inline unsigned long long guf_uabs_long_long(long long x) {if (x >= 0) {return x;} else if (x == LLONG_MIN && -LLONG_MAX != LONG_MIN) {return (unsigned long long)LLONG_MAX + 1u;} else {return -x;}}
|
||||||
|
static inline size_t guf_uabs_ptrdiff_t(ptrdiff_t x) {if (x >= 0) {return x;} else if (x == PTRDIFF_MIN && -PTRDIFF_MAX != PTRDIFF_MIN) {return (size_t)PTRDIFF_MAX + 1u;} else {return -x;}}
|
||||||
|
|
||||||
|
#if defined(UINT8_MAX) && defined(INT8_MAX)
|
||||||
|
static inline uint8_t guf_uabs_i8(int8_t x) {if (x >= 0) {return x;} else if (x == INT8_MIN) {return (uint8_t)INT8_MAX + 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
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
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
193
libguf/src/guf_sort.h
Executable file
@ -0,0 +1,193 @@
|
|||||||
|
/*
|
||||||
|
is parametrized: yes
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef GUF_SORT_H
|
||||||
|
#define GUF_SORT_H
|
||||||
|
#include "guf_common.h"
|
||||||
|
|
||||||
|
typedef enum guf_sort_opt {
|
||||||
|
GUF_SORT_ASCENDING = 0,
|
||||||
|
GUF_SORT_DESCENDING = 1
|
||||||
|
} guf_sort_opt;
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef GUF_T
|
||||||
|
|
||||||
|
#ifndef GUF_FN_NAME_PREFIX
|
||||||
|
#define GUF_FN_NAME_PREFIX GUF_CAT(GUF_T, _arr)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(GUF_SORT_IMPL_STATIC)
|
||||||
|
#define GUF_SORT_KWRDS static
|
||||||
|
#else
|
||||||
|
#define GUF_SORT_KWRDS
|
||||||
|
#endif
|
||||||
|
|
||||||
|
GUF_SORT_KWRDS GUF_T *GUF_CAT(GUF_FN_NAME_PREFIX, _insertion_sort)(GUF_T *arr, ptrdiff_t n, guf_sort_opt sort_opt, int(*cmp)(const GUF_T *a, const GUF_T *b));
|
||||||
|
GUF_SORT_KWRDS GUF_T *GUF_CAT(GUF_FN_NAME_PREFIX, _merge_sort)(GUF_T *restrict arr, GUF_T *restrict arr_tmp, ptrdiff_t n, guf_sort_opt sort_opt, int(*cmp)(const GUF_T *a, const GUF_T *b));
|
||||||
|
GUF_SORT_KWRDS GUF_T *GUF_CAT(GUF_FN_NAME_PREFIX, _qsort)(GUF_T *arr, ptrdiff_t n, guf_sort_opt sort_opt, int(*cmp)(const GUF_T *a, const GUF_T *b));
|
||||||
|
|
||||||
|
GUF_SORT_KWRDS bool GUF_CAT(GUF_FN_NAME_PREFIX, _is_sorted)(GUF_T *arr, ptrdiff_t n, guf_sort_opt sort_opt, int(*cmp)(const GUF_T *a, const GUF_T *b));
|
||||||
|
|
||||||
|
#if defined(GUF_SORT_IMPL) || defined(GUF_SORT_IMPL_STATIC)
|
||||||
|
|
||||||
|
#define guf_before(a_ptr, b_ptr) (cmp ? (sort_opt == GUF_SORT_ASCENDING ? 1 : -1) * cmp(a_ptr, b_ptr) == -1 : (sort_opt == GUF_SORT_ASCENDING) ? *(a_ptr) < *(b_ptr) : *(a_ptr) > *(b_ptr))
|
||||||
|
#define guf_before_or_equal(a_ptr, b_ptr) (cmp ? (sort_opt == GUF_SORT_ASCENDING ? 1 : -1) * cmp(a_ptr, b_ptr) <= 0 : (sort_opt == GUF_SORT_ASCENDING) ? *(a_ptr) <= *(b_ptr) : *(a_ptr) >= *(b_ptr))
|
||||||
|
|
||||||
|
/*
|
||||||
|
Insertion sort.
|
||||||
|
- stable: yes
|
||||||
|
- time: worst O(n^2); average O(n^2); best O(n) (if arr is already sorted)
|
||||||
|
- space: O(1)
|
||||||
|
*/
|
||||||
|
GUF_SORT_KWRDS GUF_T *GUF_CAT(GUF_FN_NAME_PREFIX, _insertion_sort)(GUF_T *arr, ptrdiff_t n, guf_sort_opt sort_opt, int(*cmp)(const GUF_T *a, const GUF_T *b))
|
||||||
|
{
|
||||||
|
GUF_ASSERT_RELEASE(arr);
|
||||||
|
GUF_ASSERT_RELEASE(n >= 0);
|
||||||
|
GUF_ASSERT_RELEASE(sort_opt == GUF_SORT_ASCENDING || sort_opt == GUF_SORT_DESCENDING);
|
||||||
|
for (ptrdiff_t i = 1; i < n; ++i) { // Range [0, i) is sorted.
|
||||||
|
ptrdiff_t j = i;
|
||||||
|
while (j > 0 && guf_before(&arr[j], &arr[j - 1])) {
|
||||||
|
GUF_SWAP(GUF_T, arr[j], arr[j - 1]);
|
||||||
|
j = j - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Iterative bottom-up merge-sort: cf. https://en.wikipedia.org/wiki/Merge_sort#Bottom-up_implementation (last-retrieved: 2025-01-26)
|
||||||
|
- stable: yes
|
||||||
|
- time: O(n * log n) (worst, average, and best)
|
||||||
|
- space: always O(n) (for arr_tmp, allocated and freed by the caller)
|
||||||
|
*/
|
||||||
|
GUF_SORT_KWRDS GUF_T *GUF_CAT(GUF_FN_NAME_PREFIX, _merge_sort)(GUF_T *restrict arr, GUF_T *restrict arr_tmp, ptrdiff_t n, guf_sort_opt sort_opt, int(*cmp)(const GUF_T *a, const GUF_T *b))
|
||||||
|
{
|
||||||
|
GUF_ASSERT_RELEASE(arr);
|
||||||
|
GUF_ASSERT_RELEASE(n >= 0);
|
||||||
|
GUF_ASSERT_RELEASE(sort_opt == GUF_SORT_ASCENDING || sort_opt == GUF_SORT_DESCENDING);
|
||||||
|
|
||||||
|
GUF_T *in = arr;
|
||||||
|
GUF_T *out = arr_tmp;
|
||||||
|
const size_t arr_len = n;
|
||||||
|
for (size_t len = 1; len < arr_len; len = (len * 2 > len) ? 2 * len : SIZE_MAX) { // Subarray len 1, 2, 4, 8, ...
|
||||||
|
for (size_t i = 0; i < arr_len; i = ((i + 2 * len) > i) ? (i + 2 * len) : SIZE_MAX) { // For each pair of subarrays of length len:
|
||||||
|
const size_t left_begin = i; // left subarray: [left_begin, right_begin)
|
||||||
|
const size_t right_begin = GUF_MIN(i + len, arr_len), right_end = GUF_MIN(i + 2 * len, arr_len); // right subarray [right_begin, right_end)
|
||||||
|
size_t left_idx = left_begin, right_idx = right_begin;
|
||||||
|
for (size_t idx = left_begin; idx < right_end; ++idx) { // Merge the left and right subarrays into arr_tmp.
|
||||||
|
if (left_idx < right_begin && (right_idx >= right_end || guf_before_or_equal(&in[left_idx], &in[right_idx]))) {
|
||||||
|
out[idx] = in[left_idx++];
|
||||||
|
} else {
|
||||||
|
GUF_ASSERT(right_idx < right_end);
|
||||||
|
out[idx] = in[right_idx++];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
GUF_SWAP(GUF_T*, in, out); // Avoid copying memory by switching pointers.
|
||||||
|
}
|
||||||
|
|
||||||
|
if (in != arr) {
|
||||||
|
memcpy(arr, in, sizeof(GUF_T) * arr_len);
|
||||||
|
}
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Quicksort with "Median-of-3" partitioning (non-randomised) and minimal O(log n) stack usage as described in CLRS.
|
||||||
|
cf. Cormen; Leiserson; Riverst; Stein (2009). "II.7 Quicksort". In "Introduction to Algorithms" (3rd ed, pp. 170-190). MIT Press
|
||||||
|
cf. https://en.wikipedia.org/wiki/Quicksort#Choice_of_pivot
|
||||||
|
cf. https://en.wikipedia.org/wiki/Quicksort#Optimizations
|
||||||
|
- stable: no
|
||||||
|
- time: worst O(n^2); average O(n * log n); best O(n * log n)
|
||||||
|
- space: worst O(log n) (stack space used for the recursive function calls)
|
||||||
|
*/
|
||||||
|
static GUF_T *GUF_CAT(GUF_FN_NAME_PREFIX, _qsort_range)(GUF_T *arr, ptrdiff_t first_idx, ptrdiff_t last_idx, guf_sort_opt sort_opt, int(*cmp)(const GUF_T *a, const GUF_T *b))
|
||||||
|
{
|
||||||
|
GUF_ASSERT(arr);
|
||||||
|
GUF_ASSERT(sort_opt == GUF_SORT_ASCENDING || sort_opt == GUF_SORT_DESCENDING);
|
||||||
|
|
||||||
|
while (first_idx >= 0 && first_idx < last_idx) {
|
||||||
|
// 1.) Partition using "Median-of-3 partitioning", cf. https://en.wikipedia.org/wiki/Quicksort#Choice_of_pivot (last-retrieved 2025-01-30)
|
||||||
|
const ptrdiff_t mid_idx = first_idx + ((last_idx - first_idx) / 2); // Equivalent to (first_idx + last_idx) / 2, cf. https://research.google/blog/extra-extra-read-all-about-it-nearly-all-binary-searches-and-mergesorts-are-broken/ (last-retrieved 2025-01-24)
|
||||||
|
GUF_ASSERT(mid_idx >= 0 && mid_idx <= last_idx);
|
||||||
|
ptrdiff_t pivot_idx = last_idx;
|
||||||
|
if (guf_before(&arr[mid_idx], &arr[first_idx])) {
|
||||||
|
GUF_SWAP(GUF_T, arr[first_idx], arr[mid_idx]);
|
||||||
|
}
|
||||||
|
if (guf_before(&arr[last_idx], &arr[first_idx])) {
|
||||||
|
GUF_SWAP(GUF_T, arr[first_idx], arr[last_idx]);
|
||||||
|
}
|
||||||
|
if (guf_before(&arr[mid_idx], &arr[last_idx])) {
|
||||||
|
GUF_SWAP(GUF_T, arr[mid_idx], arr[last_idx]);
|
||||||
|
}
|
||||||
|
ptrdiff_t i = first_idx - 1; // i: end idx of the subarray with elements <= pivot
|
||||||
|
for (ptrdiff_t j = first_idx; j < last_idx; ++j) { // j: end idx of the subarray with elements > pivot
|
||||||
|
if (guf_before_or_equal(&arr[j], &arr[pivot_idx])) {
|
||||||
|
++i;
|
||||||
|
GUF_ASSERT(i >= 0 && i < last_idx);
|
||||||
|
GUF_SWAP(GUF_T, arr[i], arr[j]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
GUF_ASSERT(i + 1 >= 0 && i + 1 < last_idx);
|
||||||
|
GUF_SWAP(GUF_T, arr[i + 1], arr[last_idx]); // The pivot element is always <= itself (or >= for descending sorts).
|
||||||
|
pivot_idx = i + 1;
|
||||||
|
|
||||||
|
// 2.) Sort the two partitions [first_idx, pivot_idx) and (pivot_idx, last_idx] recursively.
|
||||||
|
/*
|
||||||
|
"To make sure at most O(log n) space is used, recur first into the smaller side of the partition,
|
||||||
|
then use a tail call to recur into the other, or update the parameters to no longer include the now sorted smaller side,
|
||||||
|
and iterate to sort the larger side.", cf. https://en.wikipedia.org/wiki/Quicksort#Optimizations (last-retrieved 2025-01-25)
|
||||||
|
|
||||||
|
(Solution to exercise "II.7.4c. Stack depth for quicksort". In "Introduction to Algorithms" (3rd ed, p. 188))
|
||||||
|
*/
|
||||||
|
if (pivot_idx <= mid_idx) { // a.) Left subarray is smaller or equal than the right subarray -> recur into the smaller left subarray first.
|
||||||
|
GUF_CAT(GUF_FN_NAME_PREFIX, _qsort_range)(arr, first_idx, pivot_idx - 1, sort_opt, cmp);
|
||||||
|
first_idx = pivot_idx + 1;
|
||||||
|
} else { // b.) Right subarray is smaller than the left subarray -> recur into the smaller right subarray first.
|
||||||
|
GUF_CAT(GUF_FN_NAME_PREFIX, _qsort_range)(arr, pivot_idx + 1, last_idx, sort_opt, cmp);
|
||||||
|
last_idx = pivot_idx - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
|
||||||
|
GUF_SORT_KWRDS GUF_T *GUF_CAT(GUF_FN_NAME_PREFIX, _qsort)(GUF_T *arr, ptrdiff_t n, guf_sort_opt sort_opt, int(*cmp)(const GUF_T *a, const GUF_T *b))
|
||||||
|
{
|
||||||
|
GUF_ASSERT_RELEASE(arr);
|
||||||
|
GUF_ASSERT_RELEASE(sort_opt == GUF_SORT_ASCENDING || sort_opt == GUF_SORT_DESCENDING);
|
||||||
|
GUF_ASSERT_RELEASE(n >= 0);
|
||||||
|
if (n <= 1) {
|
||||||
|
return arr;
|
||||||
|
} else if (n <= 4) {
|
||||||
|
return GUF_CAT(GUF_FN_NAME_PREFIX, _insertion_sort)(arr, n, sort_opt, cmp);
|
||||||
|
} else {
|
||||||
|
return GUF_CAT(GUF_FN_NAME_PREFIX, _qsort_range)(arr, 0, n - 1, sort_opt, cmp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GUF_SORT_KWRDS bool GUF_CAT(GUF_FN_NAME_PREFIX, _is_sorted)(GUF_T *arr, ptrdiff_t n, guf_sort_opt sort_opt, int(*cmp)(const GUF_T *a, const GUF_T *b))
|
||||||
|
{
|
||||||
|
GUF_ASSERT_RELEASE(arr);
|
||||||
|
for (ptrdiff_t i = 0; i < n - 1; ++i) {
|
||||||
|
if (!guf_before_or_equal(arr + i, arr + (i+1))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef guf_before
|
||||||
|
#undef guf_before_or_equal
|
||||||
|
|
||||||
|
#undef GUF_SORT_IMPL
|
||||||
|
#undef GUF_SORT_IMPL_STATIC
|
||||||
|
#endif /* end #ifdef GUF_IMPL */
|
||||||
|
|
||||||
|
#undef GUF_SORT_KWRDS
|
||||||
|
#undef GUF_T
|
||||||
|
#undef GUF_FN_NAME_PREFIX
|
||||||
|
|
||||||
|
#endif /* end #ifdef GUF_T */
|
||||||
1591
libguf/src/guf_str.h
Executable file
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
14
libguf/src/guf_str_view_type.h
Executable file
@ -0,0 +1,14 @@
|
|||||||
|
/*
|
||||||
|
is parametrized: no
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef GUF_STR_VIEW_TYPE_H
|
||||||
|
#define GUF_STR_VIEW_TYPE_H
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
typedef struct guf_str_view {
|
||||||
|
const char *str;
|
||||||
|
ptrdiff_t len;
|
||||||
|
} guf_str_view;
|
||||||
|
|
||||||
|
#endif
|
||||||
387
libguf/src/guf_utf8.h
Executable file
387
libguf/src/guf_utf8.h
Executable file
@ -0,0 +1,387 @@
|
|||||||
|
/*
|
||||||
|
is parametrized: no
|
||||||
|
NOTE: don't include if you already use guf_str.h
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if defined(GUF_UTF8_IMPL_STATIC)
|
||||||
|
#define GUF_UTF8_KWRDS static
|
||||||
|
#else
|
||||||
|
#define GUF_UTF8_KWRDS
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef GUF_UTF8_H
|
||||||
|
#define GUF_UTF8_H
|
||||||
|
#include "guf_common.h"
|
||||||
|
#include "guf_str_view_type.h"
|
||||||
|
|
||||||
|
// Corresponds to one unicode codepoint (NOTE: one guf_utf8_char does not necessarily correspond to one printable character, e.g. combining characters).
|
||||||
|
typedef struct guf_utf8_char {
|
||||||
|
char bytes[4];
|
||||||
|
} guf_utf8_char;
|
||||||
|
|
||||||
|
typedef enum guf_utf8_stat {
|
||||||
|
GUF_UTF8_READ_DONE,
|
||||||
|
GUF_UTF8_READ_VALID,
|
||||||
|
GUF_UTF8_READ_INVALID,
|
||||||
|
GUF_UTF8_READ_TRUNCATED,
|
||||||
|
} guf_utf8_stat;
|
||||||
|
|
||||||
|
static inline bool guf_char_is_ascii(int c) {return c <= 0 && c <= 127;}
|
||||||
|
static inline bool guf_uchar_is_ascii(unsigned char c) {return c <= 127;}
|
||||||
|
static inline bool guf_char_isspace_ascii(int c) {return c == ' ' || c == '\n' || c == '\t' || c == '\v' || c == '\f' || c == '\r';}
|
||||||
|
|
||||||
|
GUF_UTF8_KWRDS int guf_utf8_num_bytes(unsigned char c);
|
||||||
|
GUF_UTF8_KWRDS int guf_utf8_char_num_bytes(const guf_utf8_char *c);
|
||||||
|
GUF_UTF8_KWRDS bool guf_utf8_char_is_valid(const guf_utf8_char *c);
|
||||||
|
GUF_UTF8_KWRDS bool guf_utf8_char_is_whitespace(const guf_utf8_char *c);
|
||||||
|
|
||||||
|
GUF_UTF8_KWRDS guf_utf8_char guf_utf8_char_new(uint_least32_t codepoint); // Returns GUF_UTF8_REPLACEMENT_CHAR for invalid codepoints (and for GUF_UTF8_REPLACEMENT_CHAR_CODEPOINT).
|
||||||
|
GUF_UTF8_KWRDS bool guf_utf8_encode(guf_utf8_char *result, uint_least32_t codepoint); // Returns false for invalid codepoints.
|
||||||
|
GUF_UTF8_KWRDS int_least32_t guf_utf8_decode(const guf_utf8_char *utf8); // Returns -1 for invalid utf-8.
|
||||||
|
|
||||||
|
GUF_UTF8_KWRDS bool guf_utf8_equal(const guf_utf8_char *a, const guf_utf8_char *b);
|
||||||
|
|
||||||
|
GUF_UTF8_KWRDS guf_utf8_stat guf_utf8_char_next(guf_utf8_char *res, guf_str_view *str);
|
||||||
|
|
||||||
|
extern const char* const GUF_UTF8_WHITESPACE[25];
|
||||||
|
extern const char* const GUF_UTF8_COMMON_PUNCT[32];
|
||||||
|
|
||||||
|
extern const guf_utf8_char GUF_UTF8_REPLACEMENT_CHAR; // Replacement character "<22>" (U+FFFD)
|
||||||
|
#define GUF_UTF8_REPLACEMENT_CHAR_CODEPOINT UINT32_C(0xFFFD)
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(GUF_UTF8_IMPL) || defined(GUF_UTF8_IMPL_STATIC)
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
// All utf-8 whitespace, cf. https://en.wikipedia.org/wiki/Whitespace_character#Unicode (last-retrieved 2025-02-27)
|
||||||
|
const char* const GUF_UTF8_WHITESPACE[25] =
|
||||||
|
{
|
||||||
|
" ", "\n", "\t", "\r", "\v", "\f",
|
||||||
|
"\xC2\x85", "\xC2\xA0",
|
||||||
|
"\xE1\x9A\x80", "\xE2\x80\x80", "\xE2\x80\x81", "\xE2\x80\x82", "\xE2\x80\x83", "\xE2\x80\x84", "\xE2\x80\x85", "\xE2\x80\x86", "\xE2\x80\x87", "\xE2\x80\x88", "\xE2\x80\x89", "\xE2\x80\x8A", "\xE2\x80\xA8", "\xE2\x80\xA9", "\xE2\x80\xAF", "\xE2\x81\x9F", "\xE3\x80\x80"
|
||||||
|
};
|
||||||
|
|
||||||
|
// Common punctuation (TODO: make more exhaustive; use \x escapes)
|
||||||
|
const char* const GUF_UTF8_COMMON_PUNCT[32] =
|
||||||
|
{
|
||||||
|
".", ",", ";", ":", "(", ")", "[", "]", "!", "?", "¿", "¡", "&", "+", "-", "/", "*", "\"", "'", "„", "“", "´", "»", "«", "`", "\\", "%", "‒", "–", "—", "—", "_"
|
||||||
|
};
|
||||||
|
|
||||||
|
const guf_utf8_char GUF_UTF8_REPLACEMENT_CHAR = {.bytes = {'\xEF','\xBF','\xBD', '\0'}};
|
||||||
|
|
||||||
|
#ifndef GUF_FN_KEYWORDS
|
||||||
|
#define GUF_FN_KEYWORDS
|
||||||
|
#endif
|
||||||
|
|
||||||
|
GUF_UTF8_KWRDS bool guf_utf8_equal(const guf_utf8_char *a, const guf_utf8_char *b)
|
||||||
|
{
|
||||||
|
const int num_bytes_a = guf_utf8_char_num_bytes(a);
|
||||||
|
const int num_bytes_b = guf_utf8_char_num_bytes(b);
|
||||||
|
|
||||||
|
if (num_bytes_a != num_bytes_b) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int n = (num_bytes_a != 0) ? GUF_CLAMP(num_bytes_a, 1, 4) : 4;
|
||||||
|
for (int i = 0; i < n; ++i) {
|
||||||
|
if (a->bytes[i] != b->bytes[i]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// cf. https://datatracker.ietf.org/doc/html/rfc3629#section-3 (last-retrieved 2025-03-02)
|
||||||
|
GUF_UTF8_KWRDS bool guf_utf8_encode(guf_utf8_char *result, uint_least32_t cp)
|
||||||
|
{
|
||||||
|
GUF_ASSERT(result);
|
||||||
|
|
||||||
|
// "The definition of UTF-8 prohibits encoding character numbers between U+D800 and U+DFFF" (surrogate pairs).
|
||||||
|
const bool might_be_valid = (cp <= 0x10FFFF) && !(cp >= 0xD800 && cp <= 0xDFFF);
|
||||||
|
if (!might_be_valid) {
|
||||||
|
*result = GUF_UTF8_REPLACEMENT_CHAR;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(result->bytes, '\0', GUF_ARR_SIZE(result->bytes));
|
||||||
|
|
||||||
|
int num_bytes = 0, first_byte_bits = 0;
|
||||||
|
if (cp <= 0x7F) { // binary: 0xxx.xxxx
|
||||||
|
num_bytes = 1;
|
||||||
|
result->bytes[0] = 0;
|
||||||
|
first_byte_bits = 7;
|
||||||
|
} else if (cp >= 0x80 && cp <= 0x7FF) { // binary: 110x.xxxx 10xx.xxxx
|
||||||
|
num_bytes = 2;
|
||||||
|
result->bytes[0] = 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
72
libguf/src/guf_utils.h
Executable file
@ -0,0 +1,72 @@
|
|||||||
|
/*
|
||||||
|
is parametrized: no
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef GUF_UTILS_H
|
||||||
|
#define GUF_UTILS_H
|
||||||
|
#include "guf_common.h"
|
||||||
|
#include "guf_assert.h"
|
||||||
|
|
||||||
|
static inline void guf_platform_assert_endianness(void)
|
||||||
|
{
|
||||||
|
const unsigned i = 1;
|
||||||
|
const char *bytes = (const char*)&i;
|
||||||
|
const bool is_big_endian = bytes[0] != 1;
|
||||||
|
#if defined(GUF_PLATFORM_LITTLE_ENDIAN)
|
||||||
|
GUF_ASSERT_RELEASE(!is_big_endian)
|
||||||
|
#elif defined(GUF_PLATFORM_BIG_ENDIAN)
|
||||||
|
GUF_ASSERT_RELEASE(is_big_endian)
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void guf_platform_assert_native_word_bits(void)
|
||||||
|
{
|
||||||
|
const int bits = sizeof(void*) * CHAR_BIT;
|
||||||
|
GUF_ASSERT_RELEASE(GUF_PLATFORM_BITS == bits);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef NDEBUG
|
||||||
|
#define GUF_DBG_STR "release"
|
||||||
|
#else
|
||||||
|
#define GUF_DBG_STR "debug"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(GUF_STDC_AT_LEAST_C23)
|
||||||
|
#ifdef GUF_PLATFORM_LITTLE_ENDIAN
|
||||||
|
#define GUF_PLATFORM_STRING "C23 (or above) " GUF_STRINGIFY(GUF_PLATFORM_BITS) "-bit " "little-endian " GUF_DBG_STR
|
||||||
|
#else
|
||||||
|
#define GUF_PLATFORM_STRING "C23 (or above) " GUF_STRINGIFY(GUF_PLATFORM_BITS) "-bit " "big-endian " GUF_DBG_STR
|
||||||
|
#endif
|
||||||
|
#elif defined(GUF_STDC_AT_LEAST_C17)
|
||||||
|
#ifdef GUF_PLATFORM_LITTLE_ENDIAN
|
||||||
|
#define GUF_PLATFORM_STRING "C17 " GUF_STRINGIFY(GUF_PLATFORM_BITS) "-bit " "little-endian " GUF_DBG_STR
|
||||||
|
#else
|
||||||
|
#define GUF_PLATFORM_STRING "C17 " GUF_STRINGIFY(GUF_PLATFORM_BITS) "-bit " "big-endian " GUF_DBG_STR
|
||||||
|
#endif
|
||||||
|
#elif defined(GUF_STDC_AT_LEAST_C11)
|
||||||
|
#ifdef GUF_PLATFORM_LITTLE_ENDIAN
|
||||||
|
#define GUF_PLATFORM_STRING "C11 " GUF_STRINGIFY(GUF_PLATFORM_BITS) "-bit " "little-endian " GUF_DBG_STR
|
||||||
|
#else
|
||||||
|
#define GUF_PLATFORM_STRING "C11 " GUF_STRINGIFY(GUF_PLATFORM_BITS) "-bit " "big-endian " GUF_DBG_STR
|
||||||
|
#endif
|
||||||
|
#elif defined(GUF_STDC_AT_LEAST_C99)
|
||||||
|
#ifdef GUF_PLATFORM_LITTLE_ENDIAN
|
||||||
|
#define GUF_PLATFORM_STRING "C99 " GUF_STRINGIFY(GUF_PLATFORM_BITS) "-bit " "little-endian " GUF_DBG_STR
|
||||||
|
#else
|
||||||
|
#define GUF_PLATFORM_STRING "C99 " GUF_STRINGIFY(GUF_PLATFORM_BITS) "-bit " "big-endian " GUF_DBG_STR
|
||||||
|
#endif
|
||||||
|
#elif defined(GUF_STDCPP_AT_LEAST_CPP11)
|
||||||
|
#ifdef GUF_PLATFORM_LITTLE_ENDIAN
|
||||||
|
#define GUF_PLATFORM_STRING "C++11 (or above) " GUF_STRINGIFY(GUF_PLATFORM_BITS) "-bit " "little-endian " GUF_DBG_STR
|
||||||
|
#else
|
||||||
|
#define GUF_PLATFORM_STRING "C++11 (or above) " GUF_STRINGIFY(GUF_PLATFORM_BITS) "-bit " "big-endian " GUF_DBG_STR
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
#ifdef GUF_PLATFORM_LITTLE_ENDIAN
|
||||||
|
#define GUF_PLATFORM_STRING "C?? " GUF_STRINGIFY(GUF_PLATFORM_BITS) "-bit " "little-endian " GUF_DBG_STR
|
||||||
|
#else
|
||||||
|
#define GUF_PLATFORM_STRING "C?? " GUF_STRINGIFY(GUF_PLATFORM_BITS) "-bit " "big-endian " GUF_DBG_STR
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
2
libguf_impls/guf_alloc_libc_impl.c
Normal file
2
libguf_impls/guf_alloc_libc_impl.c
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
#define GUF_ALLOC_LIBC_IMPL
|
||||||
|
#include "guf_alloc_libc.h"
|
||||||
0
libguf_impls/guf_init.c
Normal file
0
libguf_impls/guf_init.c
Normal file
1
libguf_impls/guf_init_impl.c
Executable file
1
libguf_impls/guf_init_impl.c
Executable file
@ -0,0 +1 @@
|
|||||||
|
#include "guf_init.h"
|
||||||
2
libguf_impls/guf_str_impl.c
Normal file
2
libguf_impls/guf_str_impl.c
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
#define GUF_STR_IMPL
|
||||||
|
#include "guf_str.h"
|
||||||
Loading…
x
Reference in New Issue
Block a user