From 25e26756cd51c4f8399f127dfb9dd306880bdacd Mon Sep 17 00:00:00 2001 From: VegOwOtenks Date: Thu, 13 Jun 2024 15:28:21 +0200 Subject: [PATCH] Initial commit, yay --- .gitignore | 1 + CMakeLists.txt | 18 + src/CMakeLists.txt | 18 + src/FixedBuffer/CMakeLists.txt | 1 + src/FixedBuffer/FixedBuffer.c | 81 +++ src/FixedBuffer/FixedBuffer.h | 25 + src/FreeList/CMakeLists.txt | 1 + src/FreeList/FreeList.c | 234 +++++++ src/FreeList/FreeList.h | 55 ++ src/QuadTree/CMakeLists.txt | 1 + src/QuadTree/QuadTree.c | 1 + src/QuadTree/QuadTree.h | 8 + src/Scratchpad/CMakeLists.txt | 1 + src/Scratchpad/Scratchpad.c | 126 ++++ src/Scratchpad/Scratchpad.h | 29 + src/StringView/CMakeLists.txt | 1 + src/StringView/StringView.c | 206 ++++++ src/StringView/StringView.h | 58 ++ src/ThreadPool/CMakeLists.txt | 1 + src/ThreadPool/ThreadPool.c | 412 ++++++++++++ src/ThreadPool/ThreadPool.h | 114 ++++ src/allocator-interface/CMakeLists.txt | 1 + src/allocator-interface/allocator-interface.c | 155 +++++ src/allocator-interface/allocator-interface.h | 61 ++ src/argumentc/CMakeLists.txt | 1 + src/argumentc/argumentc.c | 304 +++++++++ src/argumentc/argumentc.h | 57 ++ src/arraylist/CMakeLists.txt | 1 + src/arraylist/arraylist.c | 433 ++++++++++++ src/arraylist/arraylist.h | 207 ++++++ src/cmakegen.sh | 9 + src/dynamicarray/CMakeLists.txt | 1 + src/dynamicarray/dynamicarray.c | 309 +++++++++ src/dynamicarray/dynamicarray.h | 141 ++++ src/dynamicbuffer/CMakeLists.txt | 1 + src/dynamicbuffer/dynamicbuffer.c | 217 ++++++ src/dynamicbuffer/dynamicbuffer.h | 116 ++++ src/errorcodes.h | 83 +++ src/hashmap/CMakeLists.txt | 1 + src/hashmap/hashmap.c | 359 ++++++++++ src/hashmap/hashmap.h | 67 ++ src/pointers/CMakeLists.txt | 1 + src/pointers/pointers.c | 31 + src/pointers/pointers.h | 28 + src/rand/CMakeLists.txt | 2 + src/rand/opensimplex.c | 175 +++++ src/rand/opensimplex.h | 52 ++ src/rand/xoshiro256.c | 106 +++ src/rand/xoshiro256.h | 33 + src/regex/CMakeLists.txt | 5 + src/regex/compile.c | 517 +++++++++++++++ src/regex/compile.h | 8 + src/regex/machine_state.c | 37 ++ src/regex/machine_state.h | 12 + src/regex/machine_state_struct.h | 66 ++ src/regex/match.c | 624 ++++++++++++++++++ src/regex/match.h | 62 ++ src/regex/match_struct.c | 45 ++ src/regex/match_struct.h | 29 + src/regex/regex.c | 61 ++ src/regex/regex.h | 32 + src/regex/regex_struct.h | 19 + src/siphash/CMakeLists.txt | 1 + src/siphash/siphash.c | 128 ++++ src/siphash/siphash.h | 43 ++ src/threading/CMakeLists.txt | 4 + src/threading/os_mutex.c | 61 ++ src/threading/os_mutex.h | 23 + src/threading/os_rwlock.c | 90 +++ src/threading/os_rwlock.h | 29 + src/threading/os_semaphore.c | 92 +++ src/threading/os_semaphore.h | 59 ++ src/threading/os_thread.c | 89 +++ src/threading/os_thread.h | 28 + src/utf8/CMakeLists.txt | 1 + src/utf8/utf-8.c | 60 ++ src/utf8/utf-8.h | 28 + tests/.cproject | 16 + tests/.project | 20 + tests/CMakeLists.txt | 38 ++ tests/Scratchpad.test.c | 24 + tests/StringView.test.c | 36 + tests/ThreadPool.test.c | 88 +++ tests/argumentc.test.c | 139 ++++ tests/regex.test.c | 119 ++++ 85 files changed, 7077 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 src/CMakeLists.txt create mode 100644 src/FixedBuffer/CMakeLists.txt create mode 100644 src/FixedBuffer/FixedBuffer.c create mode 100644 src/FixedBuffer/FixedBuffer.h create mode 100644 src/FreeList/CMakeLists.txt create mode 100644 src/FreeList/FreeList.c create mode 100644 src/FreeList/FreeList.h create mode 100644 src/QuadTree/CMakeLists.txt create mode 100644 src/QuadTree/QuadTree.c create mode 100644 src/QuadTree/QuadTree.h create mode 100644 src/Scratchpad/CMakeLists.txt create mode 100644 src/Scratchpad/Scratchpad.c create mode 100644 src/Scratchpad/Scratchpad.h create mode 100644 src/StringView/CMakeLists.txt create mode 100644 src/StringView/StringView.c create mode 100644 src/StringView/StringView.h create mode 100644 src/ThreadPool/CMakeLists.txt create mode 100644 src/ThreadPool/ThreadPool.c create mode 100644 src/ThreadPool/ThreadPool.h create mode 100644 src/allocator-interface/CMakeLists.txt create mode 100644 src/allocator-interface/allocator-interface.c create mode 100644 src/allocator-interface/allocator-interface.h create mode 100644 src/argumentc/CMakeLists.txt create mode 100644 src/argumentc/argumentc.c create mode 100644 src/argumentc/argumentc.h create mode 100644 src/arraylist/CMakeLists.txt create mode 100644 src/arraylist/arraylist.c create mode 100644 src/arraylist/arraylist.h create mode 100644 src/cmakegen.sh create mode 100644 src/dynamicarray/CMakeLists.txt create mode 100644 src/dynamicarray/dynamicarray.c create mode 100644 src/dynamicarray/dynamicarray.h create mode 100644 src/dynamicbuffer/CMakeLists.txt create mode 100644 src/dynamicbuffer/dynamicbuffer.c create mode 100644 src/dynamicbuffer/dynamicbuffer.h create mode 100644 src/errorcodes.h create mode 100644 src/hashmap/CMakeLists.txt create mode 100644 src/hashmap/hashmap.c create mode 100644 src/hashmap/hashmap.h create mode 100644 src/pointers/CMakeLists.txt create mode 100644 src/pointers/pointers.c create mode 100644 src/pointers/pointers.h create mode 100644 src/rand/CMakeLists.txt create mode 100644 src/rand/opensimplex.c create mode 100644 src/rand/opensimplex.h create mode 100644 src/rand/xoshiro256.c create mode 100644 src/rand/xoshiro256.h create mode 100644 src/regex/CMakeLists.txt create mode 100644 src/regex/compile.c create mode 100644 src/regex/compile.h create mode 100644 src/regex/machine_state.c create mode 100644 src/regex/machine_state.h create mode 100644 src/regex/machine_state_struct.h create mode 100644 src/regex/match.c create mode 100644 src/regex/match.h create mode 100644 src/regex/match_struct.c create mode 100644 src/regex/match_struct.h create mode 100644 src/regex/regex.c create mode 100644 src/regex/regex.h create mode 100644 src/regex/regex_struct.h create mode 100644 src/siphash/CMakeLists.txt create mode 100644 src/siphash/siphash.c create mode 100644 src/siphash/siphash.h create mode 100644 src/threading/CMakeLists.txt create mode 100644 src/threading/os_mutex.c create mode 100644 src/threading/os_mutex.h create mode 100644 src/threading/os_rwlock.c create mode 100644 src/threading/os_rwlock.h create mode 100644 src/threading/os_semaphore.c create mode 100644 src/threading/os_semaphore.h create mode 100644 src/threading/os_thread.c create mode 100644 src/threading/os_thread.h create mode 100644 src/utf8/CMakeLists.txt create mode 100644 src/utf8/utf-8.c create mode 100644 src/utf8/utf-8.h create mode 100644 tests/.cproject create mode 100644 tests/.project create mode 100644 tests/CMakeLists.txt create mode 100644 tests/Scratchpad.test.c create mode 100644 tests/StringView.test.c create mode 100644 tests/ThreadPool.test.c create mode 100644 tests/argumentc.test.c create mode 100644 tests/regex.test.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..567609b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..c3c09b5 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,18 @@ + +cmake_minimum_required(VERSION 3.22) +project(utilitiec C) + +set(CMAKE_BUILD_TYPE Debug) + +# Activate warnings +if (CMAKE_COMPILER_IS_GNUCC) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -pedantic") +endif() +if (MSVC) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /W4") +endif() + +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) + +add_subdirectory(src/) +add_subdirectory(tests/) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..30aeedc --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,18 @@ +add_subdirectory(./Scratchpad) +add_subdirectory(./allocator-interface) +add_subdirectory(./FixedBuffer) +add_subdirectory(./dynamicbuffer) +add_subdirectory(./FreeList) +add_subdirectory(./hashmap) +add_subdirectory(./QuadTree) +add_subdirectory(./threading) +add_subdirectory(./ThreadPool) +add_subdirectory(./regex) +add_subdirectory(./utf8) +add_subdirectory(./StringView) +add_subdirectory(./arraylist) +add_subdirectory(./argumentc) +add_subdirectory(./rand) +add_subdirectory(./siphash) +add_subdirectory(./pointers) +add_subdirectory(./dynamicarray) diff --git a/src/FixedBuffer/CMakeLists.txt b/src/FixedBuffer/CMakeLists.txt new file mode 100644 index 0000000..8333aae --- /dev/null +++ b/src/FixedBuffer/CMakeLists.txt @@ -0,0 +1 @@ +add_library(FixedBuffer STATIC FixedBuffer.c) diff --git a/src/FixedBuffer/FixedBuffer.c b/src/FixedBuffer/FixedBuffer.c new file mode 100644 index 0000000..157e0c7 --- /dev/null +++ b/src/FixedBuffer/FixedBuffer.c @@ -0,0 +1,81 @@ +#include "FixedBuffer.h" + +#include "../pointers/pointers.h" + +#include +#include + +int FixedBuffer_Create(FixedBuffer* target, size_t size, allocator_t* allocator) +{ + if (target == NULL) return EDESTADDRREQ; + + void* memory = Allocator_Allocate(allocator, size); + if (memory == NULL) return ENOMEM; + + target->memory = memory; + target->length = size; + target->reserved = 0; + + return EXIT_SUCCESS; +} + +void* FixedBuffer_GetHead(FixedBuffer* buffer) +{ + if (buffer == NULL) return NULL; + + return advancep(buffer->memory, buffer->reserved); +} + +size_t FixedBuffer_GetRemaining(FixedBuffer* buffer) +{ + if (buffer == NULL) return EINVAL; + + return buffer->length - buffer->reserved; +} + +int FixedBuffer_DiscardTail(FixedBuffer* buffer, size_t length) +{ + if (buffer == NULL) return EINVAL; + if (length > buffer->reserved) return EBOUNDS; + + memmove(buffer->memory, advancep(buffer->memory, length), buffer->reserved - length); + buffer->reserved -= length; + + return EXIT_SUCCESS; +} + +int FixedBuffer_Rewind(FixedBuffer* buffer, size_t by) +{ + if (buffer == NULL) return EINVAL; + if (buffer->reserved < by) return EBOUNDS; + + buffer->reserved -= by; + + return EXIT_SUCCESS; +} + +int FixedBuffer_Advance(FixedBuffer* buffer, size_t by) +{ + if (buffer == NULL) return EINVAL; + if (buffer->reserved + by > buffer->length) return EBOUNDS; + + buffer->reserved += by; + + return EXIT_SUCCESS; +} + +int FixedBuffer_Reset(FixedBuffer* buffer) +{ + if (buffer == NULL) return EINVAL; + + buffer->reserved = 0; + + return EXIT_SUCCESS; +} + +void FixedBuffer_Destroy(FixedBuffer* buffer, allocator_t* allocator) +{ + Allocator_Free(allocator, buffer->memory, buffer->length); + + return; +} diff --git a/src/FixedBuffer/FixedBuffer.h b/src/FixedBuffer/FixedBuffer.h new file mode 100644 index 0000000..4d2aa30 --- /dev/null +++ b/src/FixedBuffer/FixedBuffer.h @@ -0,0 +1,25 @@ +#ifndef UTILITIEC_FIXEDBUFFER_H +#define UTILITIEC_FIXEDBUFFER_H + +#include "../allocator-interface/allocator-interface.h" + +typedef struct FixedBuffer_s { + void* memory; + size_t reserved; + size_t length; +} FixedBuffer; + +int FixedBuffer_Create(FixedBuffer* target, size_t size, allocator_t* allocator); +void FixedBuffer_Destroy(FixedBuffer* buffer, allocator_t* allocator); + +void* FixedBuffer_GetHead(FixedBuffer* buffer); +size_t FixedBuffer_GetRemaining(FixedBuffer* buffer); + +int FixedBuffer_DiscardTail(FixedBuffer* buffer, size_t length); + +int FixedBuffer_Rewind(FixedBuffer* buffer, size_t by); +int FixedBuffer_Advance(FixedBuffer* buffer, size_t by); + +int FixedBuffer_Reset(FixedBuffer* buffer); + +#endif diff --git a/src/FreeList/CMakeLists.txt b/src/FreeList/CMakeLists.txt new file mode 100644 index 0000000..d0dacaa --- /dev/null +++ b/src/FreeList/CMakeLists.txt @@ -0,0 +1 @@ +add_library(FreeList STATIC FreeList.c) diff --git a/src/FreeList/FreeList.c b/src/FreeList/FreeList.c new file mode 100644 index 0000000..ef6ef47 --- /dev/null +++ b/src/FreeList/FreeList.c @@ -0,0 +1,234 @@ +/* + * This code is part of the strategy game operational-space. + * operational-space comes with ABSOLUTELY NO WARRANTY and is licensed under GPL-2.0. + * Copyright (C) 2024 VegOwOtenks, Sleppo04 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#include "FreeList.h" + +#include + +#include "../errorcodes.h" + +// fl->first_free == fl->list.capacity +// => Nothing free + +// FreeNode Pointer = 0 => No next pointer + +static inline size_t _GetSizeT(void* source) +{ + size_t target; + memcpy(&target, source, sizeof(target)); + return target; +} + +static inline void _SetSizeT(void* destination, size_t value) +{ + memcpy(destination, &value, sizeof(value)); + return; +} + +static void _FreeList_InitializePointers(FreeList* fl, size_t offset) +{ + for (size_t i = offset; i < DynamicArray_GetLength(&fl->list); i++) { + // !! Possibly unaligned pointer + size_t* element = DynamicArray_GetPointer(&fl->list, i); + _SetSizeT(element, i + 1); + } + + // !! Possibly unaligned pointer + size_t* last = DynamicArray_GetPointer(&fl->list, DynamicArray_GetLength(&fl->list) - 1); + _SetSizeT(last, fl->list.capacity); +} + +int FreeList_Create(FreeList* target, size_t object_size, size_t initial_capacity, allocator_t* allocator) +{ + if (target == NULL) return EDESTADDRREQ; + if (object_size == 0) return EINVAL; + if (initial_capacity == 0) return EINVAL; + + // Guarantee we can always fit a size_t inside the element + size_t element_size = object_size < sizeof(size_t) ? sizeof(size_t) : object_size; + + if (DynamicArray_Create(&target->list, element_size, initial_capacity, allocator)) { + return ENOMEM; + } + target->list.reserved = target->list.capacity; + target->object_size = object_size; + + // 0, init all + _FreeList_InitializePointers(target, 0); + target->first_free = 0; + + return EXIT_SUCCESS; +} + +void FreeList_Destroy(FreeList* fl) +{ + if (fl == NULL) return; + DynamicArray_Destroy(&fl->list); + + memset(fl, 0, sizeof(*fl)); + + return; +} + +int FreeList_Allocate(FreeList* fl, size_t* index) +{ + if (fl == NULL) { + return EINVAL; + } + if (index == NULL) { + return EDESTADDRREQ; + } + + if (fl->first_free == fl->list.capacity) { + if (DynamicArray_Resize(&fl->list, fl->list.capacity * 2)) { + return ENOMEM; + } + fl->list.reserved = fl->list.capacity; + _FreeList_InitializePointers(fl, fl->first_free); + } + + *index = fl->first_free; + fl->first_free = _GetSizeT(DynamicArray_GetPointer(&fl->list, fl->first_free)); + + return EXIT_SUCCESS; +} + +int FreeList_Free(FreeList* fl, size_t index) +{ + if (fl == NULL) { + return EDESTADDRREQ; + } + if (index >= fl->list.capacity) { + return EBOUNDS; + } + +#ifdef COMMON_FREELIST_CHECKFREE + // Check whether the index is freed already + + size_t current = fl->first_free; + + // There are no free elements? + if (fl->first_free == fl->list.capacity) { + goto good_free; + } + + // Iterate through the free-list + do { + if (current == index) { + // Index is already in the free-list + return EINVAL; + } + + current = _GetSizeT(DynamicArray_GetPointer(&fl->list, current)); + } while (current != 0); + +good_free: + +#endif // CHECKFREE + + // Only `fl` itself may hold the 0 reference + if (fl->first_free == 0) { + // [head] [head_next] [...] + + size_t head = fl->first_free; + // Where the first element points to + size_t head_next = _GetSizeT(DynamicArray_GetPointer(&fl->list, fl->first_free)); + + // Set the new element to point to the head_next + _SetSizeT(DynamicArray_GetPointer(&fl->list, index), head_next); + + // Head points to the new element + _SetSizeT(DynamicArray_GetPointer(&fl->list, head), index); + + // [head] [new] [head_next] [...] + } else { + // Point the new element to the previous first + _SetSizeT(DynamicArray_GetPointer(&fl->list, index), fl->first_free); + + // Point the head to the new element + fl->first_free = index; + } + + return EXIT_SUCCESS; +} + +int FreeList_FreeSorted(FreeList* fl, size_t index) +{ + if (fl == NULL) { + return EDESTADDRREQ; + } + if (index >= fl->list.capacity) { + return EBOUNDS; + } + + size_t current = fl->first_free; + size_t previous = fl->first_free; + + while (current != fl->list.capacity && current < index) { + previous = current; + current = _GetSizeT(DynamicArray_GetPointer(&fl->list, current)); + } + +#ifdef COMMON_FREELIST_CHECKFREE + if (current == index) { + return EINVAL; + } +#endif + + if (current == previous) { + // No iteration done, insert before first + _SetSizeT(DynamicArray_GetPointer(&fl->list, index), current); + fl->first_free = index; + } else { + _SetSizeT(DynamicArray_GetPointer(&fl->list, index), current); + _SetSizeT(DynamicArray_GetPointer(&fl->list, previous), index); + } + + return EXIT_SUCCESS; +} + +int FreeList_Load(FreeList* fl, void* destination, size_t index) +{ + if (fl == NULL) return EINVAL; + if (destination == NULL) return EDESTADDRREQ; + if (index >= fl->list.capacity) return EBOUNDS; + + memcpy(destination, DynamicArray_GetPointer(&fl->list, index), fl->object_size); + + return EXIT_SUCCESS; +} + +int FreeList_Store(FreeList* fl, size_t index, void* source) +{ + if (fl == NULL) return EINVAL; + if (source == NULL) return EDESTADDRREQ; + if (index >= fl->list.capacity) return EBOUNDS; + + memcpy(DynamicArray_GetPointer(&fl->list, index), source, fl->object_size); + + return EXIT_SUCCESS; +} + +void* FreeList_GetPointer(FreeList* fl, size_t index) +{ + if (fl == NULL) return NULL; + if (index >= fl->list.capacity) return NULL; + + return DynamicArray_GetPointer(&fl->list, index); +} diff --git a/src/FreeList/FreeList.h b/src/FreeList/FreeList.h new file mode 100644 index 0000000..3418c2b --- /dev/null +++ b/src/FreeList/FreeList.h @@ -0,0 +1,55 @@ +/* + * This code is part of the strategy game operational-space. + * operational-space comes with ABSOLUTELY NO WARRANTY and is licensed under GPL-2.0. + * Copyright (C) 2024 VegOwOtenks, Sleppo04 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#ifndef COMMON_FREELIST_H_ +#define COMMON_FREELIST_H_ + +#include "../dynamicarray/dynamicarray.h" + + +// Don't put packed structs in there +// List structure optimized for leaving holes inside and filling them later + +typedef struct FreeList_s { + DynamicArray list; + + size_t first_free; + // Index of the first free element + + size_t object_size; +} FreeList; + + +int FreeList_Create(FreeList* target, size_t object_size, size_t initial_capacity, allocator_t* allocator); +void FreeList_Destroy(FreeList* fl); + +int FreeList_Allocate(FreeList* fl, size_t* index); + +int FreeList_Free(FreeList* fl, size_t index); +int FreeList_FreeSorted(FreeList* fl, size_t index); + +// Store or load `object-size` bytes +int FreeList_Load(FreeList* fl, void* destination, size_t index); +int FreeList_Store(FreeList* fl, size_t index, void* source); + +// Get the pointer of the element at index +// DO NOT keep this pointer around in-between allocate() or free() calls on the FreeList +void* FreeList_GetPointer(FreeList* fl, size_t index); + +#endif /* SRC_COMMON_FREELIST_FREELIST_H_ */ diff --git a/src/QuadTree/CMakeLists.txt b/src/QuadTree/CMakeLists.txt new file mode 100644 index 0000000..ccca75a --- /dev/null +++ b/src/QuadTree/CMakeLists.txt @@ -0,0 +1 @@ +add_library(QuadTree STATIC QuadTree.c) diff --git a/src/QuadTree/QuadTree.c b/src/QuadTree/QuadTree.c new file mode 100644 index 0000000..6042bc2 --- /dev/null +++ b/src/QuadTree/QuadTree.c @@ -0,0 +1 @@ +#include "QuadTree.h" diff --git a/src/QuadTree/QuadTree.h b/src/QuadTree/QuadTree.h new file mode 100644 index 0000000..00470e8 --- /dev/null +++ b/src/QuadTree/QuadTree.h @@ -0,0 +1,8 @@ +#ifndef UTILITIEC_QUADTREE_H +#define UTILITIEC_QUADTREE_H + +typedef struct QuadTree_s { + +} QuadTree; + +#endif diff --git a/src/Scratchpad/CMakeLists.txt b/src/Scratchpad/CMakeLists.txt new file mode 100644 index 0000000..355a384 --- /dev/null +++ b/src/Scratchpad/CMakeLists.txt @@ -0,0 +1 @@ +add_library(Scratchpad STATIC Scratchpad.c) diff --git a/src/Scratchpad/Scratchpad.c b/src/Scratchpad/Scratchpad.c new file mode 100644 index 0000000..488e854 --- /dev/null +++ b/src/Scratchpad/Scratchpad.c @@ -0,0 +1,126 @@ +#include "Scratchpad.h" + +#include "../pointers/pointers.h" +#include +#include + +int Scratchpad_Create(Scratchpad* target, size_t capacity, allocator_t* allocator) +{ + if (target == NULL) return EDESTADDRREQ; + + void* memory = Allocator_Allocate(allocator, capacity); + if (memory == NULL) return ENOMEM; + + target->memory = memory; + target->capacity = capacity; + target->reserved = 0; + target->next = NULL; + target->allocator = allocator; + + return EXIT_SUCCESS; +} + +static void* _Scratchpad_ReserveHere(Scratchpad* pad, size_t size) +{ + if (size > pad->capacity - pad->reserved) return NULL; + + void* scratch_buffer = advancep(pad->memory, pad->reserved); + pad->reserved += size; + + return scratch_buffer; +} + +void* Scratchpad_Reserve(Scratchpad* pad, size_t size) +{ + if (pad == NULL) return NULL; + + Scratchpad* current = pad; + void* reserve = NULL; + while (reserve == NULL && current != NULL) { + reserve = _Scratchpad_ReserveHere(current, size); + current = current->next; + } + + if (reserve == NULL) { + Scratchpad* new = Allocator_Allocate(pad->allocator, sizeof(*pad)); + if (new != NULL) { + // Copy current pad in the *next pointer + memcpy(new, pad, sizeof(*pad)); + + // Calculate new capacity + size_t new_capacity = size > pad->capacity ? + pad->capacity * (size / pad->capacity + 1) + : pad->capacity; + + // Initialize new pad + if (Scratchpad_Create(pad, new_capacity, pad->allocator)) { + Allocator_Free(pad->allocator, new, sizeof(*new)); + } else { + reserve = Scratchpad_Reserve(pad, size); + pad->next = new; + } + } + } + + return reserve; +} + +int Scratchpad_Reclaim(Scratchpad* pad, void* pointer, size_t length) +{ + if (pad == NULL) return EDESTADDRREQ; + if (pointer == NULL) return EINVAL; + if (length > pad->reserved) return EBOUNDS; + + if (rewindp(advancep(pad->memory, pad->reserved), length) == pointer) { + pad->reserved -= length; + } + + return EXIT_SUCCESS; +} + +static void _Scratchpad_DestroyHere(Scratchpad* pad) +{ + Allocator_Free(pad->allocator, pad->memory, pad->capacity); + + return; +} + +void Scratchpad_Reset(Scratchpad* pad) +{ + if (pad == NULL) return; + + while (pad->next != NULL && pad->next->next != NULL) { + Scratchpad* current = pad->next; + pad->next = current->next; + + _Scratchpad_DestroyHere(current); + Allocator_Free(pad->allocator, current, sizeof(*current)); + } + + if (pad->next != NULL) { + Scratchpad* next = pad->next; + _Scratchpad_DestroyHere(pad); + memcpy(pad, next, sizeof(*next)); + + Allocator_Free(pad->allocator, next, sizeof(*next)); + } + + return; +} + +void Scratchpad_Destroy(Scratchpad* pad) +{ + if (pad == NULL) return; + + while (pad->next != NULL) { + Scratchpad* next_next = pad->next->next; + _Scratchpad_DestroyHere(pad->next); + Allocator_Free(pad->allocator, pad->next, sizeof(*pad)); + + pad->next = next_next; + } + + _Scratchpad_DestroyHere(pad); + + return; +} diff --git a/src/Scratchpad/Scratchpad.h b/src/Scratchpad/Scratchpad.h new file mode 100644 index 0000000..132b557 --- /dev/null +++ b/src/Scratchpad/Scratchpad.h @@ -0,0 +1,29 @@ +#ifndef SCRATCHPAD_H +#define SCRATCHPAD_H + +#include "../allocator-interface/allocator-interface.h" +#include + +typedef struct Scratchpad_s { + void* memory; + size_t capacity; + size_t reserved; + struct Scratchpad_s* next; + allocator_t* allocator; +} Scratchpad; + +/*! + @brief Create a new Scratchpad Buffer + @param target this + @param capacity How many bytes the buffer should hold initially + @return EXIT_SUCCESS, ENOMEM, EDESTADDRREQ +*/ +int Scratchpad_Create(Scratchpad* target, size_t capacity, allocator_t* allocator); + +void* Scratchpad_Reserve(Scratchpad* pad, size_t size); +int Scratchpad_Reclaim(Scratchpad* pad, void* pointer, size_t length); +void Scratchpad_Reset(Scratchpad* pad); + +void Scratchpad_Destroy(Scratchpad* pad); + +#endif diff --git a/src/StringView/CMakeLists.txt b/src/StringView/CMakeLists.txt new file mode 100644 index 0000000..eb8b34e --- /dev/null +++ b/src/StringView/CMakeLists.txt @@ -0,0 +1 @@ +add_library(StringView STATIC StringView.c) diff --git a/src/StringView/StringView.c b/src/StringView/StringView.c new file mode 100644 index 0000000..20b06ec --- /dev/null +++ b/src/StringView/StringView.c @@ -0,0 +1,206 @@ +/* + * This code is part of the programming language Ivy. + * Ivy comes with ABSOLUTELY NO WARRANTY and is licensed under AGPL-3.0 or later. + * Copyright (C) 2024 VegOwOtenks + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "StringView.h" + +#include +#include +#include + +StringView StringView_FromString(const char* source) +{ + return StringView_FromStringSized(source, strlen(source)); +} + +StringView StringView_FromStringSized(const char* source, size_t length) +{ + StringView view = { + .source = source, + .length = length, + }; + + return view; +} + +bool StringView_Equal(StringView first, StringView second) +{ + return first.length == second.length + && + strncmp(first.source, second.source, first.length) == 0; +} + +bool StringView_StartsWith(StringView string, StringView start) +{ + return strncmp(string.source, start.source, start.length) == 0; +} + +bool StringView_EndsWith(StringView string, StringView end) +{ + if (end.length > string.length) { + return false; + } + + return StringView_Equal( + end, + StringView_Slice( + string, + string.length - end.length, + string.length + ) + ); +} + +bool StringView_Contains(StringView string, StringView find) +{ + return StringView_FindString(string, find).source != NULL; +} + +size_t StringView_Count(StringView string, StringView find) +{ + size_t count = 0; + while ((find = StringView_FindString(string, find)).source != NULL) { + size_t offset = (uintptr_t) (find.source) - (uintptr_t) (string.source); + string = (StringView) {string.source + offset, string.length - offset}; + } + + return count; +} + +size_t StringView_FindStringOffset(StringView haystack, StringView needle) +{ + size_t offset = 0; + size_t index = 0; + + while (offset < haystack.length) { + while (index < needle.length && needle.source[index] == haystack.source[offset + index]) { + index++; + } + if (index == needle.length) { + return offset; + } + offset++; + } + + return SIZE_MAX; +} + +StringView StringView_FindString(StringView haystack, StringView needle) +{ + size_t offset = 0; + size_t index = 0; + + while (offset < haystack.length) { + while (index < needle.length && needle.source[index] == haystack.source[offset + index]) { + index++; + } + if (index == needle.length) { + return (StringView) {haystack.source + offset, needle.length}; + } + offset++; + } + + return (StringView) {NULL, 0}; +} + +StringView StringView_Slice(StringView string, size_t start, size_t end) +{ + if (end > string.length || start > string.length) { + return (StringView) {NULL, 0}; + } + + StringView slice = { + .length = end - start, + .source = string.source + start, + }; + + return slice; +} + +bool StringView_NextSplit(StringView* dest, StringView* source, StringView delim) +{ + if (source->length == 0) return false; + + size_t offset = StringView_FindStringOffset(*source, delim); + if (offset == SIZE_MAX) { + // No more delimiters, return entire source + offset = source->length; + } + + *dest = StringView_Slice(*source, 0, offset); + *source = StringView_Slice(*source, offset + delim.length, source->length); + + return true; +} + +StringView StringView_StripLeft(StringView sv, StringView strip) +{ + while (StringView_StartsWith(sv, strip)) { + sv = StringView_Slice(sv, strip.length, sv.length); + } + + return sv; +} + +StringView StringView_StripRight(StringView sv, StringView strip) +{ + while (StringView_EndsWith(sv, strip)) { + sv = StringView_Slice(sv, 0, sv.length - strip.length); + } + + return sv; +} + + +void StringView_Paste(char* destination, StringView source) +{ + memcpy(destination, source.source, source.length); +} + +int StringView_ParseInt(StringView source) +{ + int value = 0; + int sign = 1; + + switch (source.source[0]) { + case '+': + sign = 1; + source.source++; + source.length--; + break; + case '-': + sign = -1; + source.source++; + source.length--; + break; + default: + break; + } + + while (source.length && isdigit(source.source[0])) { + int digit = source.source[0] - '0'; + value *= 10; + value += digit; + + source.source++; + source.length--; + } + + return value * sign; +} diff --git a/src/StringView/StringView.h b/src/StringView/StringView.h new file mode 100644 index 0000000..8be710c --- /dev/null +++ b/src/StringView/StringView.h @@ -0,0 +1,58 @@ +/* + * This code is part of the programming language Ivy. + * Ivy comes with ABSOLUTELY NO WARRANTY and is licensed under AGPL-3.0 or later. + * Copyright (C) 2024 VegOwOtenks + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#ifndef STRINGVIEW_H_ +#define STRINGVIEW_H_ + +#include +#include + +// Generally, StringViews are not owned + +typedef struct StringView_s { + const char *source; + size_t length; +} StringView; + +#define STRINGVIEW_NONE ((StringView) {.source = NULL, .length = 0}) + +StringView StringView_FromString(const char* source); +StringView StringView_FromStringSized(const char* source, size_t length); + +bool StringView_Equal(StringView first, StringView second); +bool StringView_StartsWith(StringView string, StringView start); +bool StringView_EndsWith(StringView string, StringView end); +bool StringView_Contains(StringView string, StringView find); + +size_t StringView_Count(StringView string, StringView find); + +size_t StringView_FindStringOffset(StringView haystack, StringView needle); +StringView StringView_FindString(StringView haystack, StringView needle); +StringView StringView_Slice(StringView string, size_t start, size_t end); // start and end are offsets + +bool StringView_NextSplit(StringView* dest, StringView* source, StringView delim); + +StringView StringView_StripLeft(StringView sv, StringView strip); +StringView StringView_StripRight(StringView sv, StringView strip); + +void StringView_Paste(char* destination, StringView source); + +int StringView_ParseInt(StringView source); + +#endif /* SRC_STRINGVIEW_STRINGVIEW_H_ */ diff --git a/src/ThreadPool/CMakeLists.txt b/src/ThreadPool/CMakeLists.txt new file mode 100644 index 0000000..04f78c7 --- /dev/null +++ b/src/ThreadPool/CMakeLists.txt @@ -0,0 +1 @@ +add_library(ThreadPool STATIC ThreadPool.c) diff --git a/src/ThreadPool/ThreadPool.c b/src/ThreadPool/ThreadPool.c new file mode 100644 index 0000000..6da13f7 --- /dev/null +++ b/src/ThreadPool/ThreadPool.c @@ -0,0 +1,412 @@ +#include "ThreadPool.h" +#include +#include +#include +#include +#include + + +#include + +static void _ThreadPoolWorker_StoreResult(ThreadPool* pool, size_t job_id, void* result) +{ + while (OSMutex_Acquire(&pool->rw_lock) != EXIT_SUCCESS); + + ThreadPoolJob* job = FreeList_GetPointer(&pool->job_storage, job_id); + + if (job->flags & THREADPOOLJOB_DISCARD_RESULT) { + FreeList_Free(&pool->job_storage, job_id); + OSMutex_Release(&pool->rw_lock); + return; + } + + // capture result + job->result = result; + + if (pool->first_result == SIZE_MAX) { + // Initiate linked list stuff + pool->first_result = job_id; + pool->last_result = job_id; + } else { + + // Retrieve last result + ThreadPoolJob* last_result = FreeList_GetPointer( + &pool->job_storage, + pool->last_result + ); + + // Append to result linked list + last_result->next = job_id; + pool->last_result = job_id; + } + + OSMutex_Release(&pool->rw_lock); + + return; +} + +static void* ThreadPoolWorker_Main(ThreadPool* pool) +{ + +#if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__)) + pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL); +#endif + + while (true) { + // wait until job is available + int wait_code = OSSemaphore_Wait(&pool->wait_queue); + if (wait_code != EXIT_SUCCESS) { + // Can't do anything about it, try again + continue; + } + + // Get ThreadPool lock for rw + int acquire_code = OSMutex_Acquire(&pool->rw_lock); + if (acquire_code != EXIT_SUCCESS) { + // Unlocking failed, loop again + OSSemaphore_Release(&pool->wait_queue); + continue; + } + + // Grab first job + size_t job_id = pool->first_job; + ThreadPoolJob* job = FreeList_GetPointer(&pool->job_storage, job_id); + + // Make next job available + pool->first_job = job->next; + if (job->next == SIZE_MAX) pool->last_job = SIZE_MAX; + + job->next = SIZE_MAX; + + + // Copy the function and the argument to leave the critical section + ThreadFunction job_function = job->job; + void* job_argument = job->arg; + + // unlock again + OSMutex_Release(&pool->rw_lock); + + // perform job + void* result = job_function(job_argument); + + _ThreadPoolWorker_StoreResult(pool, job_id, result); + } + + return NULL; +} + +static int _ThreadPool_SpawnWorker(ThreadPool* pool, ThreadPoolWorker* worker) +{ + int create_code = OSThread_Create( + &worker->thread, + (ThreadFunction) ThreadPoolWorker_Main, + pool + ); + if (create_code != EXIT_SUCCESS) { + return EXIT_FAILURE; + } + + worker->alive = true; + worker->job = SIZE_MAX; + + return EXIT_SUCCESS; +} + +static size_t _ThreadPool_CountAliveWorkers(ThreadPool* pool) +{ + size_t sum = 0; + + for (size_t i = 0; i < pool->worker_count; i++) { + sum += pool->workers[i].alive == true; + } + + return sum; +} + +int ThreadPool_Create(ThreadPool* target, size_t worker_count, int8_t flags, allocator_t* allocator) +{ + if (target == NULL) return EDESTADDRREQ; + if (worker_count == 0) return EINVAL; + + int return_code = EXIT_SUCCESS; + + target->flags = flags; + + int freelist_code = FreeList_Create(&target->job_storage, sizeof(ThreadPoolJob), worker_count * 8, allocator); + if (freelist_code != EXIT_SUCCESS) { + return ENOMEM; + } + + int semaphore_code = OSSemaphore_Create(&target->wait_queue, 0U); + if (semaphore_code != EXIT_SUCCESS) { + return_code = ENOMEM; + goto defer_freelist; + } + + int mutex_code = OSMutex_Create(&target->rw_lock); + if (mutex_code != EXIT_SUCCESS) { + return_code = ENOMEM; + goto defer_semaphore; + } + + ThreadPoolWorker* worker_array = Allocator_AllocateArray( + allocator, + worker_count, + sizeof(ThreadPoolWorker) + ); + if (worker_array == NULL) { + return_code = ENOMEM; + goto defer_mutex; + } + target->workers = worker_array; + target->worker_count = worker_count; + + for (size_t i = 0; i < worker_count; i++) { + if (flags & THREADPOOL_KILL_WORKERS) { + worker_array[i].alive = false; + } else { + int thread_code = _ThreadPool_SpawnWorker( + target, + worker_array + i + ); + if (thread_code) { + return_code = EXIT_FAILURE; + goto defer_threads; + } + } + } + + target->first_job = SIZE_MAX; + target->first_result = SIZE_MAX; + + target->last_job = SIZE_MAX; + target->last_result = SIZE_MAX; + + target->allocator = allocator; + + return EXIT_SUCCESS; + + +defer_threads: + ;size_t i = 0; + while (i < worker_count && worker_array[i].alive) { + OSThread_Kill(&worker_array[i].thread); + i++; + } + Allocator_FreeArray( + allocator, + worker_array, + sizeof(ThreadPoolWorker), + worker_count); +defer_mutex: + OSMutex_Destroy(&target->rw_lock); +defer_semaphore: + OSSemaphore_Destroy(&target->wait_queue); +defer_freelist: + FreeList_Destroy(&target->job_storage); + return return_code; +} + +int ThreadPool_QueueJob(ThreadPool* pool, ThreadPoolJob* job, size_t* job_id_out) +{ + if (pool == NULL) return EINVAL; + if (job == NULL) return EINVAL; + + int return_code = EXIT_SUCCESS; + + int acquire_code = OSMutex_Acquire(&pool->rw_lock); + if (acquire_code != EXIT_SUCCESS) return ECANCELED; + + size_t job_id; + if (FreeList_Allocate(&pool->job_storage, &job_id) != EXIT_SUCCESS) { + return_code = ENOMEM; + goto defer_mutex; + } + ThreadPoolJob* pjob = FreeList_GetPointer(&pool->job_storage, job_id); + memcpy(pjob, job, sizeof(*job)); + pjob->next = SIZE_MAX; + + if (pool->last_job != SIZE_MAX) { + ThreadPoolJob* last_job = FreeList_GetPointer( + &pool->job_storage, + pool->last_job + ); + + last_job->next = job_id; + pool->last_job = job_id; + } else { + pool->last_job = job_id; + pool->first_job = job_id; + } + + if (pool->flags & THREADPOOL_KILL_WORKERS) { + // Spawn a new worker, if possible + for (size_t i = 0; i < pool->worker_count; i++) { + if (pool->workers[i].alive) continue; + + int create_code = _ThreadPool_SpawnWorker( + pool, + pool->workers + i + ); + if (create_code != EXIT_SUCCESS + && _ThreadPool_CountAliveWorkers(pool) == 0) { + return EXIT_FAILURE; + } else if (create_code != EXIT_SUCCESS) { + // Fail silently, there are still workers alive + break; + } else { + // create_code == EXIT_SUCCESS + + // Successfully spawned worker for job + break; + } + } + } + + OSSemaphore_Release(&pool->wait_queue); + + // This is thread-safe, i promise + if (job_id_out != NULL) job_id_out[0] = job_id; + +defer_mutex: + OSMutex_Release(&pool->rw_lock); + return return_code; +} + +int ThreadPool_UnqueueJob(ThreadPool* pool, size_t job_id) +{ + if (pool == NULL) return EINVAL; + if (job_id >= pool->job_storage.list.capacity) return EINVAL; + + int exit_code = EINPROGRESS; + + OSMutex_Acquire(&pool->rw_lock); + + + if (pool->first_job == job_id) { + ThreadPoolJob* target_job = FreeList_GetPointer(&pool->job_storage, job_id); + pool->first_job = target_job->next; + if (pool->first_job == SIZE_MAX) pool->last_job = SIZE_MAX; + + exit_code = EXIT_SUCCESS; + } else { + ThreadPoolJob* current_job = FreeList_GetPointer(&pool->job_storage, pool->first_job); + + while (current_job->next != SIZE_MAX) { + ThreadPoolJob* next = FreeList_GetPointer(&pool->job_storage, current_job->next); + if (current_job->next == job_id) { + current_job->next = next->next; + + FreeList_Free(&pool->job_storage, job_id); + + exit_code = EXIT_SUCCESS; + break; + } + + current_job = next; + } + } + + + OSMutex_Release(&pool->rw_lock); + + return exit_code; +} + +bool ThreadPool_HasFinished(ThreadPool* pool, size_t job_id) +{ + if (pool == NULL) return false; + if (job_id >= pool->job_storage.list.capacity) return false; + + OSMutex_Acquire(&pool->rw_lock); + + size_t current = pool->first_result; + while (current != job_id && current != SIZE_MAX) { + ThreadPoolJob* current_result = FreeList_GetPointer(&pool->job_storage, current); + current = current_result->next; + } + + OSMutex_Release(&pool->rw_lock); + + return current == job_id && current != SIZE_MAX; +} + +int ThreadPool_GetJobResult(ThreadPool* pool, size_t job_id, void** job_result) +{ + if (pool == NULL) return false; + if (job_id >= pool->job_storage.list.capacity) return false; + if (job_result == NULL) return EDESTADDRREQ; + + if (OSMutex_Acquire(&pool->rw_lock) != EXIT_SUCCESS) return ECANCELED; + + if (pool->first_result == job_id) { + ThreadPoolJob* job = FreeList_GetPointer(&pool->job_storage, job_id); + // store return + job_result[0] = job->result; + + // Update linked list + pool->first_result = job->next; + if (pool->first_result == SIZE_MAX) pool->last_result = SIZE_MAX; + } else { + size_t current_id = pool->first_result; + ThreadPoolJob* current_result = FreeList_GetPointer(&pool->job_storage, current_id); + while (current_result->next != job_id && current_result->next != SIZE_MAX) { + current_id = current_result->next; + current_result = FreeList_GetPointer(&pool->job_storage, current_id); + } + + if (current_result->next == job_id) { + ThreadPoolJob* job = FreeList_GetPointer(&pool->job_storage, job_id); + job_result[0] = job->result; + + current_result->next = job->next; + FreeList_Free(&pool->job_storage, job_id); + if (job_id == pool->last_result) pool->last_result = current_id; + } + } + + OSMutex_Release(&pool->rw_lock); + + // I don't want to implement error checking here, it is stated in the documentation that you have to call HasFinished() beforehand + + return EXIT_SUCCESS; +} + +size_t ThreadPool_GetNextResultID(ThreadPool* pool) +{ + if (OSMutex_Acquire(&pool->rw_lock) != EXIT_SUCCESS) return SIZE_MAX; + + size_t next_result_id = pool->first_result; + + OSMutex_Release(&pool->rw_lock); + + return next_result_id; +} + +void ThreadPool_Destroy(ThreadPool* thread_pool) +{ + OSMutex_Acquire(&thread_pool->rw_lock); + + FreeList_Destroy(&thread_pool->job_storage); + size_t i = 0; + while (i < thread_pool->worker_count && thread_pool->workers[i].alive) { + void* r; + OSThread_Kill(&thread_pool->workers[i].thread); + OSThread_Join(&thread_pool->workers[i].thread, &r); + OSThread_Destroy(&thread_pool->workers[i].thread); + i++; + } + Allocator_FreeArray( + thread_pool->allocator, + thread_pool->workers, + sizeof(ThreadPoolWorker), + thread_pool->worker_count + ); + + OSMutex_Release(&thread_pool->rw_lock); + + OSMutex_Destroy(&thread_pool->rw_lock); + OSSemaphore_Destroy(&thread_pool->wait_queue); + + return; +} diff --git a/src/ThreadPool/ThreadPool.h b/src/ThreadPool/ThreadPool.h new file mode 100644 index 0000000..329f338 --- /dev/null +++ b/src/ThreadPool/ThreadPool.h @@ -0,0 +1,114 @@ +#ifndef V_THREADPOOL_H +#define V_THREADPOOL_H + +#include // int32_t, size_t, int8_t +#include // bool + +#include "../allocator-interface/allocator-interface.h" // for allocator +#include "../FreeList/FreeList.h" + +#include "../threading/os_semaphore.h" // for os_semaphore_t +#include "../threading/os_thread.h" // for os_thread_t +#include "../threading/os_mutex.h" // for os_mutex_t + +#define THREADPOOLJOB_DISCARD_RESULT (1) + +typedef struct ThreadPoolJob_s { + ThreadFunction job; + void* result; + void* arg; + int8_t flags; + size_t next; +} ThreadPoolJob; + +typedef struct ThreadPoolWorker_s { + bool alive; + os_thread_t thread; + size_t job; +} ThreadPoolWorker; + +#define THREADPOOL_KILL_WORKERS (1) + +typedef struct ThreadPool_s { + os_mutex_t rw_lock; + os_semaphore_t wait_queue; + + ThreadPoolWorker* workers; + size_t worker_count; + + FreeList job_storage; + + size_t first_job; + size_t last_job; + + size_t first_result; + size_t last_result; + + int8_t flags; + + allocator_t* allocator; +} ThreadPool; + +/*! + @brief Create a ThreadPool and spin up the workers, unless KILL_WORKERS-flag is given + @param target this + @param worker_count How many worker threads should the thread pool use + @param flags Flag integer (use OR conjunction) + @param allocator for the thread-pool + @return EXIT_SUCCESS, ENOMEM +*/ +int ThreadPool_Create(ThreadPool* target, size_t worker_count, int8_t flags, allocator_t* allocator); + +/*! + @brief Queue a Job for completion in the ThreadPool + @param pool this + @param job The Job to queue, will be copied into ThreadPool Memory + @param job_id_out (out) The assigned job_id will be placed here + @return EXIT_SUCCESS, ENOMEM +*/ +int ThreadPool_QueueJob(ThreadPool* pool, ThreadPoolJob* job, size_t* job_id_out); + + +/*! + @brief Remove a queued job from the queue + @param pool this + @param job_id job_id of the job to cancel + @return EXIT_SUCCESS, EINVAL, EINPROGRESS +*/ +int ThreadPool_UnqueueJob(ThreadPool* pool, size_t job_id); + +/*! + @brief Query whether a job has finished already + @param pool this + @param job_id job_id of the queried job result + @return EXIT_SUCCESS, EINVAL, EINPROGRESS +*/ +bool ThreadPool_HasFinished(ThreadPool* pool, size_t job_id); + +/*! + @brief Query whether a job has finished already + @param pool this + @param job_id job_id of the queried job result + @param job_result (out) job result will be placed here + @pre the associated job must have finished + @post the job result may not be queried again + @return EXIT_SUCCESS, EINVAL, EINPROGRESS +*/ +int ThreadPool_GetJobResult(ThreadPool* pool, size_t job_id, void** job_result); + +/*! + @brief Retrieve the ID of the result finished for the longest time + @param pool this + @pre pool is not NULL + @return size_t, SIZE_MAX (no result) +*/ +size_t ThreadPool_GetNextResultID(ThreadPool* pool); + +/*! + @brief Destroy the thread-pool and all associated resources + @param thread_pool this + @return void +*/ +void ThreadPool_Destroy(ThreadPool* thread_pool); + +#endif diff --git a/src/allocator-interface/CMakeLists.txt b/src/allocator-interface/CMakeLists.txt new file mode 100644 index 0000000..704a82b --- /dev/null +++ b/src/allocator-interface/CMakeLists.txt @@ -0,0 +1 @@ +add_library(allocator-interface STATIC allocator-interface.c) diff --git a/src/allocator-interface/allocator-interface.c b/src/allocator-interface/allocator-interface.c new file mode 100644 index 0000000..350d47d --- /dev/null +++ b/src/allocator-interface/allocator-interface.c @@ -0,0 +1,155 @@ +/* + * This code is part of the strategy game operational-space. + * operational-space comes with ABSOLUTELY NO WARRANTY and is licensed under GPL-2.0. + * Copyright (C) 2024 VegOwOtenks, Sleppo04 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "allocator-interface.h" + + +// Forwarding +void* Allocator_ForwardArrayAllocation(allocator_t* allocator, size_t object_size, size_t length) +{ + return allocator->allocate(allocator, object_size * length); +} + +int Allocator_ForwardArrayFree(allocator_t* allocator, void* pointer, size_t object_size, size_t length) +{ + return allocator->free(allocator, pointer, object_size * length); +} + +void* Allocator_ForwardReallocate(allocator_t* allocator, void* pointer, size_t old_size, size_t new_size) +{ + void* new = allocator->allocate(allocator, new_size); + if (new == NULL) { + return NULL; + } + + memcpy(new, pointer, old_size); + + if (allocator->free(allocator, pointer, old_size)) { + allocator->free(allocator, new, new_size); + return NULL; + } + + return new; +} + +// Wrapping System +int Allocator_WrapSystemFree(allocator_t* allocator, void* pointer, size_t size) +{ + free(pointer); + + allocator->reserved -= size; + + return EXIT_SUCCESS; +} + +void* Allocator_WrapSystemAllocate(allocator_t* allocator, size_t size) +{ + void* result = malloc(size); + + if (result != NULL) { + allocator->reserved += size; + } + + return result; +} + + +void* Allocator_WrapSystemReallocate(allocator_t* allocator, void* pointer, size_t old_size, size_t new_size) +{ + void* result = realloc(pointer, new_size); + + if (result != NULL) { + allocator->reserved -= old_size; + allocator->reserved += new_size; + } + + return result; +} + + +void* Allocator_Allocate(allocator_t* allocator, size_t size) +{ + if (allocator == NULL) { + return malloc(size); + } + + return allocator->allocate(allocator, size); +} + +int Allocator_Free(allocator_t* allocator, void* pointer, size_t size) +{ + if (pointer == NULL) { + return EXIT_SUCCESS; + } + if (allocator == NULL) { + free(pointer); + return EXIT_SUCCESS; + } + + return allocator->free(allocator, pointer, size); +} + +void* Allocator_AllocateArray(allocator_t* allocator, size_t object_size, size_t length) +{ + if (allocator == NULL) { + return calloc(length, object_size); + } + + return allocator->allocateArray(allocator, object_size, length); +} + +int Allocator_FreeArray(allocator_t* allocator, void* pointer, size_t object_size, size_t length) +{ + if (allocator == NULL) { + free(pointer); + return EXIT_SUCCESS; + } + + return allocator->freeArray(allocator, pointer, object_size, length); +} + +void* Allocator_Reallocate(allocator_t* allocator, void* pointer, size_t old_size, size_t new_size) +{ + if (allocator == NULL) { + return realloc(pointer, new_size); + } + + return allocator->reallocate(allocator, pointer, old_size, new_size); +} + +int Allocator_CreateSystemAllocator(allocator_t* destination) +{ + destination->xdata = NULL; + destination->reserved = 0; + + destination->allocateArray = Allocator_ForwardArrayAllocation; + destination->freeArray = Allocator_ForwardArrayFree; + destination->free = Allocator_WrapSystemFree; + destination->allocate = Allocator_WrapSystemAllocate; + destination->reallocate = Allocator_WrapSystemReallocate; + + return EXIT_SUCCESS; +} + +void Allocator_DestroySystemAllocator(allocator_t* allocator) +{ + memset(allocator, 0, sizeof(allocator[0])); + return; +} diff --git a/src/allocator-interface/allocator-interface.h b/src/allocator-interface/allocator-interface.h new file mode 100644 index 0000000..d5e162d --- /dev/null +++ b/src/allocator-interface/allocator-interface.h @@ -0,0 +1,61 @@ +/* + * This code is part of the strategy game operational-space. + * operational-space comes with ABSOLUTELY NO WARRANTY and is licensed under GPL-2.0. + * Copyright (C) 2024 VegOwOtenks, Sleppo04 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#ifndef COMMONALLOCATORINTERFACE_H +#define COMMONALLOCATORINTERFACE_H + +#include +#include +#include + +#include "../errorcodes.h" + +typedef struct Allocator_s allocator_t; + +typedef void* (*AllocatorAllocateArrayFunction) (allocator_t* allocator, size_t object_size, size_t length); +typedef void* (*AllocatorReallocateFunction) (allocator_t* allocator, void* pointer, size_t old_size, size_t new_size); +typedef int (*AllocatorFreeArrayFunction) (allocator_t* allocator, void* pointer, size_t object_size, size_t length); +typedef void* (*AllocatorAllocateFunction) (allocator_t* allocator, size_t size); +typedef int (*AllocatorFreeFunction) (allocator_t* allocator, void* pointer, size_t size); + +struct Allocator_s { + AllocatorAllocateArrayFunction allocateArray; + AllocatorReallocateFunction reallocate; + AllocatorFreeArrayFunction freeArray; + AllocatorAllocateFunction allocate; + AllocatorFreeFunction free; + + size_t reserved; + + void* xdata; +}; + +void* Allocator_Allocate(allocator_t* allocator, size_t size); +int Allocator_Free(allocator_t* allocator, void* pointer, size_t size); +void* Allocator_AllocateArray(allocator_t* allocator, size_t object_size, size_t length); +int Allocator_FreeArray(allocator_t* allocator, void* pointer, size_t object_size, size_t length); +void* Allocator_Reallocate(allocator_t* allocator, void* pointer, size_t old_size, size_t new_size); + +int Allocator_CreateSystemAllocator(allocator_t* destination); +void Allocator_DestroySystemAllocator(allocator_t* allocator); + +void* Allocator_ForwardReallocate(allocator_t* allocator, void* pointer, size_t old_size, size_t new_size); + + +#endif diff --git a/src/argumentc/CMakeLists.txt b/src/argumentc/CMakeLists.txt new file mode 100644 index 0000000..b58c53f --- /dev/null +++ b/src/argumentc/CMakeLists.txt @@ -0,0 +1 @@ +add_library(argumentc STATIC argumentc.c) diff --git a/src/argumentc/argumentc.c b/src/argumentc/argumentc.c new file mode 100644 index 0000000..4a617fb --- /dev/null +++ b/src/argumentc/argumentc.c @@ -0,0 +1,304 @@ +#include "argumentc.h" +#include +#include +#include + +int Argumentc_Create(Argumentc* target, int argc, const char** argv) +{ + if (target == NULL) return EDESTADDRREQ; + if (argv == NULL) return EINVAL; + + int array_code = DynamicArray_Create( + &target->array, + sizeof(Option), + argc * 2, + NULL + ); + if (array_code != EXIT_SUCCESS) return ENOMEM; + + int i; + for (i = 0; i < argc; i++) { + StringView arg = StringView_FromString(argv[i]); + enum OptionType type; + Option opt; + + // Determine Option type + switch (arg.length) { + case 0: + case 1: + type = OPTIONTYPE_ARGUMENT; + break; + case 2: + if (StringView_Equal( + arg, + StringView_FromString("--"))) { + type = OPTIONTYPE_DELIM; + } else if (StringView_StartsWith( + arg, + StringView_FromString("-"))) { + type = OPTIONTYPE_SHORT; + } else { + type = OPTIONTYPE_ARGUMENT; + } + break; + default: + if (StringView_StartsWith( + arg, + StringView_FromString("--"))) { + type = OPTIONTYPE_LONG; + + } else if (StringView_StartsWith( + arg, + StringView_FromString("-"))) { + type = OPTIONTYPE_SHORT; + } else { + type = OPTIONTYPE_ARGUMENT; + } + break; + } + + opt.type = type; + + switch (type) { + case OPTIONTYPE_NONE: + case OPTIONTYPE_ARGUMENT: + case OPTIONTYPE_DELIM: + opt.content = arg; + if (DynamicArray_Append(&target->array, &opt)) + goto defer_arraylist; + break; + case OPTIONTYPE_LONG: + opt.content = StringView_Slice( + arg, + 2, + arg.length); + if (DynamicArray_Append(&target->array, &opt)) + goto defer_arraylist; + break; + case OPTIONTYPE_SHORT: + arg.length -= 1; + arg.source++; + while (arg.length) { + opt.content = StringView_Slice(arg, 0, 1); + if (DynamicArray_Append(&target->array, &opt)) + goto defer_arraylist; + arg.length--; + arg.source++; + } + } + + if (opt.type == OPTIONTYPE_DELIM) break; + } + + for (++i; i < argc; i++) { + Option opt; + opt.type = OPTIONTYPE_ARGUMENT; + opt.content = StringView_FromString(argv[i]); + if (DynamicArray_Append(&target->array, &opt)) + goto defer_arraylist; + } + + return EXIT_SUCCESS; +defer_arraylist: + DynamicArray_Destroy(&target->array); + return ENOMEM; +} + +void Argumentc_Destroy(Argumentc* argumentc) +{ + DynamicArray_Destroy(&argumentc->array); + return; +} + +bool Argumentc_HaveNextOption(Argumentc* argumentc) +{ + return DynamicArray_GetLength(&argumentc->array) != 0; +} + +static Option _Argumentc_PopOption(Argumentc* argumentc, size_t index) +{ + Option* next = DynamicArray_GetPointer(&argumentc->array, index); + Option local = *next; + DynamicArray_Remove(&argumentc->array, index); + + return local; +} + +Option Argumentc_PopNextOption(Argumentc* argumentc) +{ + return _Argumentc_PopOption(argumentc, 0); +} + +static size_t _Argumentc_FindOption(Argumentc* argumentc, Option option) +{ + for (size_t i = 0; i < DynamicArray_GetLength(&argumentc->array); i++) { + Option* array_option = DynamicArray_GetPointer(&argumentc->array, i); + + if (array_option->type != option.type) continue; + if (StringView_Equal(array_option->content, option.content)) { + return i; + } + } + + return SIZE_MAX; +} + +bool Argumentc_HaveOption(Argumentc* argumentc, StringView name) +{ + for (size_t i = 0; i < DynamicArray_GetLength(&argumentc->array); i++) { + Option* array_option = DynamicArray_GetPointer(&argumentc->array, i); + + if (StringView_Equal(array_option->content, name)) { + return true; + } + } + + return false; +} + +/* +static size_t _Argumentc_FindByType(Argumentc* argumentc, enum OptionType type, size_t start) +{ + for (size_t i = start; i < DynamicArray_GetLength(&argumentc->array); i++) { + Option* array_option = DynamicArray_GetPointer(&argumentc->array, i); + + if (array_option->type == type) { + return i; + } + } + + return SIZE_MAX; +} +*/ + +Option Argumentc_PopShortOption(Argumentc* argumentc, StringView name) +{ + Option short_option = { OPTIONTYPE_SHORT, name}; + size_t index = _Argumentc_FindOption(argumentc, short_option); + + if (index == SIZE_MAX) { + return (Option) {OPTIONTYPE_NONE, name}; + } + + return _Argumentc_PopOption(argumentc, index); +} + +Option Argumentc_PopLongOption(Argumentc* argumentc, StringView name) +{ + Option long_option = { OPTIONTYPE_LONG, name}; + size_t index = _Argumentc_FindOption(argumentc, long_option); + + if (index == SIZE_MAX) { + return (Option) {OPTIONTYPE_NONE, name}; + } + + return _Argumentc_PopOption(argumentc, index); +} + +OptionArgument Argumentc_PopShortArgument(Argumentc* argumentc, StringView name) +{ + Option find_option = { OPTIONTYPE_SHORT, name}; + size_t index = _Argumentc_FindOption(argumentc, find_option); + + if (index == SIZE_MAX) { + return (OptionArgument) {{OPTIONTYPE_NONE, name}, {OPTIONTYPE_NONE, name}}; + } + + Option* short_option = DynamicArray_GetPointer(&argumentc->array, index); + + size_t argument_index = index; + Option* array_current = DynamicArray_GetPointer(&argumentc->array, argument_index); + while (array_current->type == OPTIONTYPE_SHORT) { + argument_index++; + array_current++; + } + + if (array_current->type != OPTIONTYPE_ARGUMENT) { + return (OptionArgument) {{OPTIONTYPE_NONE, name}, {OPTIONTYPE_NONE, name}}; + } + + OptionArgument pair; + pair.argument = *array_current; + pair.option = *short_option; + + DynamicArray_Remove(&argumentc->array, argument_index); + DynamicArray_Remove(&argumentc->array, index); + + return pair; +} + +OptionArgument Argumentc_PopLongArgument(Argumentc* argumentc, StringView name) +{ + Option long_option = {OPTIONTYPE_LONG, name}; + size_t index = _Argumentc_FindOption(argumentc, long_option); + + if (index == SIZE_MAX) { + return (OptionArgument) {{OPTIONTYPE_NONE, name}, {OPTIONTYPE_NONE, name}}; + } + + Option* array_long = DynamicArray_GetPointer(&argumentc->array, index); + Option* array_arg = DynamicArray_GetPointer(&argumentc->array, index + 1); + + if (array_arg == NULL || array_arg->type != OPTIONTYPE_ARGUMENT) { + return (OptionArgument) {{OPTIONTYPE_NONE, name}, {OPTIONTYPE_NONE, name}}; + } + + OptionArgument pair; + pair.argument = *array_arg; + pair.option = *array_long; + + DynamicArray_Remove(&argumentc->array, index); // remove option + DynamicArray_Remove(&argumentc->array, index); // remove argument + + return pair; +} + +bool Argumentc_PopLongOptionSwitch(Argumentc* argumentc, StringView name, bool bdefault) +{ + Option switch_option = Argumentc_PopLongOption(argumentc, name); + + return switch_option.type == OPTIONTYPE_LONG ? true : bdefault; +} + +bool Argumentc_PopShortOptionSwitch(Argumentc* argumentc, StringView name, bool bdefault) +{ + Option switch_option = Argumentc_PopShortOption(argumentc, name); + + return switch_option.type == OPTIONTYPE_SHORT ? true : bdefault; +} + +int Argumentc_PopLongOptionInt(Argumentc* argumentc, StringView name, int idefault) +{ + OptionArgument int_option = Argumentc_PopLongArgument(argumentc, name); + + return int_option.option.type == OPTIONTYPE_LONG ? + StringView_ParseInt(int_option.argument.content) : + idefault; +} + +int Argumentc_PopShortOptionInt(Argumentc* argumentc, StringView name, int idefault) +{ + OptionArgument int_option = Argumentc_PopShortArgument(argumentc, name); + + return int_option.option.type == OPTIONTYPE_SHORT ? + StringView_ParseInt(int_option.argument.content) : + idefault; +} + +const char* OptionType_ToString(enum OptionType type) +{ + switch (type) { + case OPTIONTYPE_NONE: + return "OPTIONTYPE_NONE"; + case OPTIONTYPE_SHORT: + return "OPTIONTYPE_SHORT"; + case OPTIONTYPE_LONG: + return "OPTIONTYPE_LONG"; + case OPTIONTYPE_DELIM: + return "OPTIONTYPE_DELIM"; + case OPTIONTYPE_ARGUMENT: + return "OPTIONTYPE_ARGUMENT"; + default: + return "OPTIONTYPE_INVALID"; + } +} diff --git a/src/argumentc/argumentc.h b/src/argumentc/argumentc.h new file mode 100644 index 0000000..4289f72 --- /dev/null +++ b/src/argumentc/argumentc.h @@ -0,0 +1,57 @@ +#ifndef ARGUMENTC_H +#define ARGUMENTC_H + +// There is a problem with short options, if you pop them out of order +// -ab 1 -s 2 +// popshortarg s +// popshortarg a v +// popshortarg b ^ these race for the argument '1' + +#include "../dynamicarray/dynamicarray.h" +#include "../StringView/StringView.h" + +enum OptionType { + OPTIONTYPE_NONE, + OPTIONTYPE_SHORT, // -s + OPTIONTYPE_LONG, // --long + OPTIONTYPE_ARGUMENT, // value + OPTIONTYPE_DELIM, // "--" +}; + +typedef struct Option_s { + enum OptionType type; + StringView content; +} Option; + +typedef struct OptionArgument_s { + Option option; + Option argument; +} OptionArgument; + +typedef struct Argumentc_s { + DynamicArray array; +} Argumentc; + +int Argumentc_Create(Argumentc* target, int argc, const char** argv); +void Argumentc_Destroy(Argumentc* argumentc); + +bool Argumentc_HaveNextOption(Argumentc* argumentc); +Option Argumentc_PopNextOption(Argumentc* argumentc); + +bool Argumentc_HaveOption(Argumentc* argumentc, StringView name); + +Option Argumentc_PopShortOption(Argumentc* argumentc, StringView name); +Option Argumentc_PopLongOption(Argumentc* argumentc, StringView name); + +OptionArgument Argumentc_PopShortArgument(Argumentc* argumentc, StringView name); +OptionArgument Argumentc_PopLongArgument(Argumentc* argumentc, StringView name); + +bool Argumentc_PopLongOptionSwitch(Argumentc* argumentc, StringView name, bool bdefault); +bool Argumentc_PopShortOptionSwitch(Argumentc* argumentc, StringView name, bool bdefault); + +int Argumentc_PopLongOptionInt(Argumentc* argumentc, StringView name, int idefault); +int Argumentc_PopShortOptionInt(Argumentc* argumentc, StringView name, int idefault); + +const char* OptionType_ToString(enum OptionType type); + +#endif diff --git a/src/arraylist/CMakeLists.txt b/src/arraylist/CMakeLists.txt new file mode 100644 index 0000000..0d135da --- /dev/null +++ b/src/arraylist/CMakeLists.txt @@ -0,0 +1 @@ +add_library(arraylist STATIC arraylist.c) diff --git a/src/arraylist/arraylist.c b/src/arraylist/arraylist.c new file mode 100644 index 0000000..3331504 --- /dev/null +++ b/src/arraylist/arraylist.c @@ -0,0 +1,433 @@ +/* + * This code is part of the strategy game operational-space. + * operational-space comes with ABSOLUTELY NO WARRANTY and is licensed under GPL-2.0. + * Copyright (C) 2024 VegOwOtenks, Sleppo04 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#include "arraylist.h" + +/// @brief Initializes a new arraylist at the supplied memory block. +/// @param list Memory address where the list shall be initialized +/// @return NOERROR, EINVAL, ENOMEM +int ArrayListNew(arraylist_t* list) +{ + return ArrayListNewCapacity(list, ARRAYLIST_INITIAL_SIZE); +} + +/// @brief Initialize an arraylist with a given capacity in the supplied memory block. +/// @param list Memory block where the list shall be initialized +/// @param capacity Initial capacity of the new list +/// @return NOERROR, EINVAL, ENOMEM +int ArrayListNewCapacity(arraylist_t* list, size_t capacity) +{ + if (list == NULL) { + return EINVAL; + } + void** array = (void**) (malloc(sizeof(void*) * capacity)); + if (array == NULL) { + return ENOMEM; + } + list->size = 0; + list->array = array; + list->capacity = capacity; + return 0; +} + +/// @brief Copy elements from original to copy. +/// All elements previously in copy are overwritten +/// @param source list from which elements are copied +/// @param destination list to which elements are copied +/// @return NOERROR, EINVAL, EINVAL, ENOMEM +int ArrayListCopy(arraylist_t* source, arraylist_t* destination) +/* +* Copies the arraylist to new memory block, changes in the original +* list will not be reflected in the copy. +* The elements of the list are not copied because of their unknown nature. +* If you want to pass a element-copy-function, use ArrayListCopy2. +*/ +{ + if (source == NULL) { + return EINVAL; + } + if (destination == NULL) { + return EINVAL; + } + + destination->size = source->size; + destination->capacity = source->size; + destination->array = malloc(sizeof(void*) * destination->capacity); + if (destination->array == NULL) { + return ENOMEM; + } + memcpy(destination->array, source->array, source->size * sizeof(void*)); + + return NOERROR; +} + + +/// @brief Places all the return values of copyElementFunc(element) into copy for each element in original +/// Old elements of copy are removed +/// @param source +/// @param destination +/// @param copyElementFunc +/// @return NOERROR, EINVAL, EINVAL +int ArrayListCopy2(arraylist_t* source, arraylist_t* destination, void* (*copyElementFunc) (void*, size_t)) +/* Notes on ArrayListCopy also apply here +* void* (*copyElementFunc) (void*, size_t) will be passed every single +* element of the list as well as it's index in order. +* It is expected to return a void pointer which is then stored in the new +* arraylist instead of the old element. +*/ +{ + if (source == NULL) { + return EINVAL; + } + if (destination == NULL) { + return EINVAL; + } + + destination->size = source->size; + destination->capacity = source->size; + destination->array = malloc(sizeof(void*) * destination->capacity); + if (destination->array == NULL) { + return ENOMEM; + } + + for (size_t idx = 0; idx < destination->size; idx++) { + arrayListSet(destination, idx, copyElementFunc(destination->array[idx], idx)); + } + + return 0; +} + +/// @brief Resize list to newCapacity +/// @param list list to resize +/// @param newCapacity capacita to resize to +/// @return NOERROR, EINVAL, ENOMEM +int arrayListResize(arraylist_t* list, size_t newCapacity) +/* +Resizes arraylist_t* list to a new capacity of size_t newCapacity +In the case where size > newCapacity, all elements with index >= newCapacity +are omitted and not contained afterwards. Size will be changed accordingly. +*/ +{ + if (list == NULL) { + return EINVAL; + } + + void** newArray = realloc(list->array, newCapacity * sizeof(void*)); + if (newArray == NULL) { + return ENOMEM; + } + + list->array = newArray; + list->capacity = newCapacity; + list->size = min(list->size, newCapacity); + return NOERROR; +} + +/// @brief deconstructor for the arraylist_t struct +/// @param list list to destroy +/// @return NOERROR, EINVAL +int ArrayListDestroy(arraylist_t* list) +/* +Frees list->array, but not the list itself, because the struct memory management +is user-side defined. +*/ +{ + if (list == NULL) { + return EINVAL; + } + free(list->array); + return NOERROR; +} + +/// @brief append element to the end of list +/// @param list list to which element will be appended +/// @param element element to append +/// @return NOERROR, EINVAL, ENOMEM +int arrayListAppend(arraylist_t* list, void* element) +/* +Appends void* element to arraylist_t*, appended elements are at +the end of the list. +*/ +{ + if (list == NULL) { + return EINVAL; + } + if (list->size >= list->capacity) { + size_t newCapacity = max(list->capacity * 2UL, 1UL); + int resizedStatus = arrayListResize(list, newCapacity); + switch (resizedStatus) + { + case EINVAL: + return EINVAL; + case ENOMEM: + return ENOMEM; + } + } + + list->array[list->size] = element; + list->size++; + return NOERROR; +} + +/// @brief retrieves the value at position index from list and writes to element +/// @param list list to get a value from +/// @param index position at which to get the value +/// @param element where to write the value to +/// @return NOERROR, EINVAL, EBOUNDS, EINVAL +int arrayListGet(arraylist_t* list, size_t index, void** element) +{ + if (list == NULL) { + return EINVAL; + } + if (index >= list->size) { + return EBOUNDS; + } + if (element == NULL) { + return EINVAL; + } + + element[0] = list->array[index]; + return NOERROR; +} + +/// @brief Set the array at position index to the value element +/// @param list list in which to set a value +/// @param index position where to set a value +/// @param element value to set +/// @return NOERROR, EINVAL, EBOUNDS +int arrayListSet(arraylist_t* list, size_t index, void* element) +/* +Replaces the element at size_t index in arraylist_t* list with void* element +Does not free replaced elements +*/ +{ + if (list == NULL) { + return EINVAL; + } + if (index >= list->size) { + return EBOUNDS; + } + + list->array[index] = element; + return 0; +} + + +/// @brief removes the element at index out of list +/// elements further in the back will be moved to the front +/// @param list list in which to remove the element at index +/// @param index index at which to remove an element +/// @return NOERROR, EINVAL, EBOUNDS +int arrayListRemove(arraylist_t* list, size_t index) +/* +Removes the element at size_t index out of arraylist_t* list. +Does not free removed pointers. +*/ +{ + if (list == NULL) { + return EINVAL; + } + if (index >= list->size) { + return EBOUNDS; + } + + void** destination = list->array + index; + void** source = destination + 1; + size_t size = (list->size - index) * sizeof(void*); + memmove(destination, source, size); + list->size--; + return NOERROR; +} + +/// @brief Resizes the capacity of the list to fit to it's size, but at least 1 +/// @param list list to resize +/// @return NOERROR, EINVAL, ENOMEM +int arrayListFitToSize(arraylist_t* list) +/* +Because realloc(ptr, 0) differs fron platform to platform, the new capacity will be set to at least 1. +*/ +{ + if (list == NULL) { + return EINVAL; + } + return arrayListResize(list, max(list->size, 1U)); +} + +/// @brief inserts element at index into list, all elements from index on are shifted backwards +/// @param list list where element shall be inserted +/// @param index index at which to insert element +/// @param element element to insert +/// @return NOERROR, EINVAL, EBOUNDS, ENOMEM +int arrayListInsert(arraylist_t* list, size_t index, void* element) +/* +Inserts void* element to arraylist_t* list at size_t index. +The element previously at index will be moved back. +*/ +{ + if (list == NULL) { + return EINVAL; + } + if (index > list->size) { + return EBOUNDS; + } + if (list->size == list->capacity) { + size_t newCapacity = max(list->capacity * 2UL, 1UL); + int status = arrayListResize(list, newCapacity); + if (status == 1) { + return EINVAL; + } + if (status == 3) { + return ENOMEM; + } + } + + void** source = list->array + index; + void** destination = source + 1; + size_t len = (list->capacity - index) * sizeof(void*); + memmove(destination, source, len); + list->array[index] = element; + list->size++; + return 0; +} + +/// @brief Removes all elements from list +/// @param list list to clear +/// @return NOERROR, EINVAL +int arrayListClear(arraylist_t* list) +/* +Removes all elements from arraylist_t* list. +Does not free any memory, to reduce the memory usage, +take a look at arrayListFitToSize. +*/ +{ + if (list == NULL) { + return EINVAL; + } + list->size = 0UL; + return NOERROR; +} + +/// @brief checks whether list contains element +/// @param list list in which to search +/// @param element element to find in list +/// @param isContained pointer where the truth value should be written to +/// @return NOERROR, EINVAL, EINVAL +int arrayListContains(arraylist_t* list, void* element, bool* isContained) +/// Writes $(element in list) to isContained +{ + if (list == NULL) { + return EINVAL; + } + if (isContained == NULL) { + return EINVAL; + } + + void* listElement; + for (size_t index = 0; index < list->size; index++) { + arrayListGet(list, index, &listElement); + if (listElement == element) { + isContained[0] = true; + return (int) 0; + } + } + return (int) 0; +} + +/// @brief Write the first position of find in list to index +/// @param list list in which to search +/// @param find element to find +/// @param index pointer where to write the position to +/// @return EINVAL, EINVAL +int arrayListIndexOf(arraylist_t* list, void* find, size_t* index) +/* +* Writes the index of void* find in arraylist_t* list to size_t* index +* if the element is not in the list, size_t* index is left unchanged +*/ +{ + if (list == NULL) { + return EINVAL; + } + if (index == NULL) { + return EINVAL; + } + + void* item; + for (size_t i = 0; i < list->size; i++) { + arrayListGet(list, i, &item); + if (item == find) { + index[0] = 0; + return 0; + } + } + + return 0; +} + +/// @brief Replace first occurrence of target in list with replacement +/// @param list the list in which to replace target +/// @param target target element to replace +/// @param replacement what target will be replaced with +/// @return NOERROR, EINVAL, EINVAL, EBOUNDS +int arrayListReplace(arraylist_t* list, void* target, void* replacement) +/// EINVAL will be returned, if the target is not contained in the list +{ + if (list == NULL) { + return EINVAL; + } + size_t index = list->capacity; + arrayListIndexOf(list, target, &index); + // No interesting error codes returned here + if (index == list->capacity) { // list->capacity would be out of bounds + return EINVAL; + } + int rc = arrayListSet(list, index, replacement); + // list-pointer can't get invalid in-between function calls + if (rc == EBOUNDS) { + return EBOUNDS; + } + + return NOERROR; +} + +int arrayListExtend(arraylist_t* destination, arraylist_t* source) +{ + if (destination == NULL) { + return EDESTADDRREQ; + } + if (source == NULL) { + return EINVAL; + } + + if (destination->capacity < source->size + destination->size) { + size_t new_capacity = source->size + destination->size; + if (arrayListResize(destination, new_capacity)) { + return ENOMEM; + } + } + + void* destination_region = destination->array + destination->size; + void* copy_start = source->array; + size_t copy_size = sizeof(void*) * source->size; + + memcpy(destination_region, copy_start, copy_size); + + destination->size += source->size; + + return NOERROR; +} diff --git a/src/arraylist/arraylist.h b/src/arraylist/arraylist.h new file mode 100644 index 0000000..df04396 --- /dev/null +++ b/src/arraylist/arraylist.h @@ -0,0 +1,207 @@ +/* + * This code is part of the strategy game operational-space. + * operational-space comes with ABSOLUTELY NO WARRANTY and is licensed under GPL-2.0. + * Copyright (C) 2024 VegOwOtenks, Sleppo04 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#ifndef ARRAYLIST_H +#define ARRAYLIST_H + +#include +#include +#include + +#include "../errorcodes.h" + +#ifndef max +#define max(a, b) ((a) > (b) ? (a) : (b)) +#endif + +#ifndef min +#define min(a, b) ((a) < (b) ? (a) : (b)) +#endif + + +#define NOERROR EXIT_SUCCESS +#define ARRAYLIST_INITIAL_SIZE 10U + +typedef struct ArrayList { + size_t size; + size_t capacity; + void** array; +} arraylist_t; + +/// @brief Initializes a new arraylist at the supplied memory block. +/// @param list Memory address where the list shall be initialized +/// @return NOERROR, EINVAL, ENOMEM +int ArrayListNew(arraylist_t* list); + +/// @brief Initialize an arraylist with a given capacity in the supplied memory block. +/// @param list Memory block where the list shall be initialized +/// @param capacity Initial capacity of the new list +/// @return NOERROR, EINVAL, ENOMEM +int ArrayListNewCapacity(arraylist_t* list, size_t capacity); + +/// @brief Copy elements from original to copy. +/// All elements previously in copy are overwritten +/// @param source list from which elements are copied +/// @param destination list to which elements are copied +/// @return NOERROR, EINVAL, EINVAL, ENOMEM +int ArrayListCopy(arraylist_t* original, arraylist_t* copy); +/* +* Copies the arraylist to new memory block, changes in the original +* list will not be reflected in the copy. +* The elements of the list are not copied because of their unknown nature. +* If you want to pass a element-copy-function, use ArrayListCopy2. +*/ + +/// @brief Places all the return values of copyElementFunc(element) into copy for each element in original +/// Old elements of copy are removed +/// @param source +/// @param destination +/// @param copyElementFunc +/// @return NOERROR, EINVAL, EINVAL +int ArrayListCopy2(arraylist_t* original, arraylist_t* copy, void* (*copyElementFunc) (void*, size_t)); +/* Notes on ArrayListCopy also apply here +* void* (*copyElementFunc) (void*, size_t) will be passed every single +* element of the list as well as it's index in order. +* It is expected to return a void pointer which is then stored in the new +* arraylist instead of the old element. +*/ + +/// @brief Resize list to newCapacity +/// @param list list to resize +/// @param newCapacity capacita to resize to +/// @return NOERROR, EINVAL, ENOMEM +int arrayListResize(arraylist_t* list, size_t newCapacity); +/* +Resizes arraylist_t* list to a new capacity of size_t newCapacity +In the case where size > newCapacity, all elements with index >= newCapacity +are omitted and not contained afterwards. Size will be changed accordingly. +*/ + +/// @brief deconstructor for the arraylist_t struct +/// @param list list to destroy +/// @return NOERROR, EINVAL +int ArrayListDestroy(arraylist_t* list); +/* +Frees list->array, but not the list itself, because the struct memory management +is user-side defined. +*/ + +/// @brief append element to the end of list +/// @param list list to which element will be appended +/// @param element element to append +/// @return NOERROR, EINVAL, ENOMEM +int arrayListAppend(arraylist_t* list, void* element); +/* +Appends void* element to arraylist_t*, appended elements are at +the end of the list. +*/ + +/// @brief retrieves the value at position index from list and writes to element +/// @param list list to get a value from +/// @param index position at which to get the value +/// @param element where to write the value to +/// @return NOERROR, EINVAL, EBOUNDS, EINVAL +int arrayListGet(arraylist_t* list, size_t index, void** element); + +/// @brief Set the array at position index to the value element +/// @param list list in which to set a value +/// @param index position where to set a value +/// @param element value to set +/// @return NOERROR, EINVAL, EBOUNDS +int arrayListSet(arraylist_t* list, size_t index, void* element); +/* +Replaces the element at size_t index in arraylist_t* list with void* element +Does not free replaced elements +*/ + +/// @brief removes the element at index out of list +/// elements further in the back will be moved to the front +/// @param list list in which to remove the element at index +/// @param index index at which to remove an element +/// @return NOERROR, EINVAL, EBOUNDS +int arrayListRemove(arraylist_t* list, size_t index); +/* +Removes the element at size_t index out of arraylist_t* list. +Does not free removed pointers. +*/ + +/// @brief Resizes the capacity of the list to fit to it's size, but at least 1 +/// @param list list to resize +/// @return NOERROR, EINVAL, ENOMEM +int arrayListFitToSize(arraylist_t* list); +/* +Because realloc(ptr, 0) differs fron platform to platform, the new capacity will be set to at least 1. +*/ + +/// @brief inserts element at index into list, all elements from index on are shifted backwards +/// @param list list where element shall be inserted +/// @param index index at which to insert element +/// @param element element to insert +/// @return NOERROR, EINVAL, EBOUNDS, ENOMEM +int arrayListInsert(arraylist_t* list, size_t index, void* element); +/* +Inserts void* element to arraylist_t* list at size_t index. +The element previously at index will be moved back. +*/ + +/// @brief Removes all elements from list +/// @param list list to clear +/// @return NOERROR, EINVAL +int arrayListClear(arraylist_t* list); +/* +Removes all elements from arraylist_t* list. +Does not free any memory, to reduce the memory usage, +take a look at arrayListFitToSize. +*/ + +/// @brief checks whether list contains element +/// @param list list in which to search +/// @param element element to find in list +/// @param isContained pointer where the truth value should be written to +/// @return NOERROR, EINVAL, EINVAL +int arrayListContains(arraylist_t* list, void* element, bool* isContained); +/// Writes $(element in list) to isContained + +/// @brief Write the first position of find in list to index +/// @param list list in which to search +/// @param find element to find +/// @param index pointer where to write the position to +/// @return EINVAL, EINVAL +int arrayListIndexOf(arraylist_t* list, void* find, size_t* index); +/* +* Writes the index of void* find in arraylist_t* list to size_t* index +* if the element is not in the list, size_t* index is left unchanged +*/ + +/// @brief Replace first occurrence of target in list with replacement +/// @param list the list in which to replace target +/// @param target target element to replace +/// @param replacement what target will be replaced with +/// @return NOERROR, EINVAL, EINVAL, EBOUNDS +int arrayListReplace(arraylist_t* list, void* target, void* replacement); +/// EINVAL will be returned, if the target is not contained in the list + + +/// @brief extend the destination arraylist with all the elements from source +/// @param destination The elements of source will be appended to this list +/// @param source The elements of this list will be copied over to destination +/// @return NOERROR, EDESTADDRREQ, EINVAL, ENOMEM +int arrayListExtend(arraylist_t* destination, arraylist_t* source); + +#endif // ARRAYLIST_H diff --git a/src/cmakegen.sh b/src/cmakegen.sh new file mode 100644 index 0000000..490b31a --- /dev/null +++ b/src/cmakegen.sh @@ -0,0 +1,9 @@ +find -type d | xargs -n 1 basename | tail -n +2 | while read directory; +do + cd $directory; + sources=$(ls *.c) + echo "add_library($directory STATIC $sources)" > CMakeLists.txt +# echo "set_target_properties($directory PROPERTIES ARCHIVE_OUTPUT_DIRECTORY \"\${CMAKE_SOURCE_DIR}/bin\")" >> CMakeLists.txt + cd ..; +done +find -type d | tail -n +2 | xargs printf "add_subdirectory(%s)\n" > CMakeLists.txt diff --git a/src/dynamicarray/CMakeLists.txt b/src/dynamicarray/CMakeLists.txt new file mode 100644 index 0000000..4c0cb1d --- /dev/null +++ b/src/dynamicarray/CMakeLists.txt @@ -0,0 +1 @@ +add_library(dynamicarray STATIC dynamicarray.c) diff --git a/src/dynamicarray/dynamicarray.c b/src/dynamicarray/dynamicarray.c new file mode 100644 index 0000000..6a4255a --- /dev/null +++ b/src/dynamicarray/dynamicarray.c @@ -0,0 +1,309 @@ +/* + * This code is part of the strategy game operational-space. + * operational-space comes with ABSOLUTELY NO WARRANTY and is licensed under GPL-2.0. + * Copyright (C) 2024 VegOwOtenks, Sleppo04 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#include "dynamicarray.h" + +int DynamicArray_Create(DynamicArray* target, size_t object_size, size_t initial_capacity, allocator_t* allocator) +{ + if (target == NULL) { + return EDESTADDRREQ; + } + if (object_size == 0) { + return ERANGE; + } + if (initial_capacity == 0) { + return EINVAL; + } + + target->memory = Allocator_Allocate(allocator, object_size * initial_capacity); + if (target->memory == NULL) { + return ENOMEM; + } + target->object_size = object_size; + target->capacity = initial_capacity; + target->allocator = allocator; + target->reserved = 0; + + return EXIT_SUCCESS; +} + +int DynamicArray_Clone(DynamicArray* target, DynamicArray* original) +{ + if (target == NULL) { + return EDESTADDRREQ; + } + if (original == NULL) { + return EINVAL; + } + + *target = *original; + target->memory = Allocator_AllocateArray(target->allocator, target->object_size, target->capacity); + if (target->memory == NULL) { + return ENOMEM; + } + + memcpy(target->memory, original->memory, original->object_size * original->capacity); + + return EXIT_SUCCESS; +} + +int DynamicArray_DeepClone(DynamicArray* target, DynamicArray* original, DynamicArrayCloneFunction clone, void* xarg) +{ + if (target == NULL) { + return EDESTADDRREQ; + } + if (original == NULL) { + return EINVAL; + } + if (clone == NULL) { + return EINVAL; + } + + *target = *original; + target->memory = Allocator_AllocateArray(target->allocator, target->object_size, target->capacity); + if (target->memory == NULL) { + return ENOMEM; + } + + DYNAMICARRAY_FOREACH(*original, i) { + void* clone_current = DynamicArray_GetPointer(target, i); + void* original_current = DynamicArray_GetPointer(target, i); + int clone_code = clone(clone_current, original_current, xarg); + if (clone_code) { + return ECANCELED; + } + } + + return EXIT_SUCCESS; +} + +int DynamicArray_ResizeObjects(DynamicArray* array, size_t new_object_size) +{ + if (array == NULL) { + return EDESTADDRREQ; + } + if (new_object_size == 0) { + return ERANGE; + } + + size_t old_object_size = array->object_size; + size_t old_size = old_object_size * array->capacity; + size_t new_size = new_object_size * array->capacity; + void* new_array = Allocator_Reallocate(array->allocator, array->memory, old_size, new_size); + if (new_array == NULL) { + return ENOMEM; + } + array->memory = new_array; + + if (array->reserved != 0) { + // move old stuff + void* current = advancep(array->memory, old_object_size * (array->reserved - 1)); + size_t index = array->reserved - 1; + while (index != 0) { + void* location = current; + void* destination = DynamicArray_GetPointer(array, index); + memmove(destination, location, old_object_size); + + current = rewindp(current, old_object_size); + index--; + } + } + + array->object_size = new_object_size; + + return EXIT_SUCCESS; +} + +int DynamicArray_Resize(DynamicArray* array, size_t new_capacity) +{ + if (array == NULL) { + return EDESTADDRREQ; + } + if (new_capacity == 0) { + return ERANGE; + } + + size_t old_size = array->object_size * array->capacity; + size_t new_size = array->object_size * new_capacity; + void* new_array = Allocator_Reallocate(array->allocator, array->memory, old_size, new_size); + if (new_array == NULL) { + return ENOMEM; + } + array->memory = new_array; + array->capacity = new_capacity; + + return EXIT_SUCCESS; +} + +int DynamicArray_Append(DynamicArray* array, void* object) +{ + if (array == NULL) { + return EDESTADDRREQ; + } + if (object == NULL) { + return EINVAL; + } + + if (array->capacity == array->reserved) { + if (DynamicArray_Resize(array, array->capacity * 2)) { + return ENOMEM; + } + } + + void* destination = advancep(array->memory, array->reserved * array->object_size); + memcpy(destination, object, array->object_size); + array->reserved++; + + return EXIT_SUCCESS; +} + +int DynamicArray_AppendEmpty(DynamicArray* array, void** pointer) +{ + if (array == NULL) { + return EDESTADDRREQ; + } + + if (array->capacity == array->reserved) { + if (DynamicArray_Resize(array, array->capacity * 2)) { + *pointer = NULL; + return ENOMEM; + } + } + + array->reserved++; + + *pointer = DynamicArray_GetPointer(array, array->reserved - 1); + + return EXIT_SUCCESS; +} + +int DynamicArray_Remove(DynamicArray* array, size_t index) +{ + if (array == NULL) { + return EDESTADDRREQ; + } + if (index >= array->reserved) { + return EBOUNDS; + } + + if (index != array->reserved - 1) { + void* to = advancep(array->memory, array->object_size * index); + void* from = advancep(to, array->object_size); + size_t size = array->object_size * (array->reserved - index); + memmove(to, from, size); + } + array->reserved--; + + return EXIT_SUCCESS; +} + +int DynamicArray_RemoveFast(DynamicArray* array, size_t index) +{ + if (array == NULL) { + return EDESTADDRREQ; + } + if (index >= array->reserved) { + return EBOUNDS; + } + + if (array->capacity > 1) { + void* to = DynamicArray_GetPointer(array, index); + void* from = DynamicArray_GetPointer(array, array->reserved - 1); + memcpy(to, from, array->object_size); + } + array->reserved--; + + return EXIT_SUCCESS; +} + +size_t DynamicArray_FindFunction(DynamicArray* array, DynamicArrayFindFunction function, void* xarg) +{ + if (array == NULL) return SIZE_MAX; + if (function == NULL) return SIZE_MAX; + + DYNAMICARRAY_FOREACH(*array, i) { + void* current = DynamicArray_GetPointer(array, i); + if (function(current, xarg)) { + return i; + } + } + + return SIZE_MAX; +} + +size_t DynamicArray_FindFunctionLinear(DynamicArray* array, DynamicArrayLinearFindFunction function, void* xarg) +{ + if (array == NULL) return SIZE_MAX; + if (function == NULL) return SIZE_MAX; + + size_t bot = 0; + size_t top = array->reserved; + size_t mid = bot + (top - bot) / 2; + int eval = -1; + + while (bot != top) { + void* current = DynamicArray_GetPointer(array, mid); + eval = function(current, xarg); + + if (eval > 0) { + bot = mid + 1; + } else if (eval < 0) { + top = mid - 1; + } else { + bot = top; + } + } + + if (eval != 0) { + return SIZE_MAX; + } + + return mid; +} + +void* DynamicArray_GetPointer(DynamicArray* array, size_t index) +{ + if (array == NULL) { + return NULL; + } + if (index >= array->reserved) { + return NULL; + } + + size_t offset = index * array->object_size; + + return advancep(array->memory, offset); +} + +size_t DynamicArray_GetLength(DynamicArray* array) +{ + return array->reserved; +} + +void DynamicArray_Destroy(DynamicArray* array) +{ + if (array == NULL) { + return; + } + + Allocator_Free(array->allocator, array->memory, array->object_size * array->capacity); + memset(array, 0, sizeof(*array)); + + return; +} diff --git a/src/dynamicarray/dynamicarray.h b/src/dynamicarray/dynamicarray.h new file mode 100644 index 0000000..3f4c889 --- /dev/null +++ b/src/dynamicarray/dynamicarray.h @@ -0,0 +1,141 @@ +/* + * This code is part of the strategy game operational-space. + * operational-space comes with ABSOLUTELY NO WARRANTY and is licensed under GPL-2.0. + * Copyright (C) 2024 VegOwOtenks, Sleppo04 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#ifndef COMMON_DYNAMICARRAY_DYNAMICARRAY_H_ +#define COMMON_DYNAMICARRAY_DYNAMICARRAY_H_ + +#define DYNAMICARRAY_FOREACH(da, i) for (size_t i = 0; i < (da).reserved; i++) + +typedef int (*DynamicArrayFindFunction) (void* element, void* xarg); + +// -1 look to the left +// 1 look to the right +// 0 found +typedef DynamicArrayFindFunction DynamicArrayLinearFindFunction; + +typedef int (*DynamicArrayCloneFunction) (void* to, void* from, void* xarg); + +#include "../pointers/pointers.h" +#include "../allocator-interface/allocator-interface.h" + +typedef struct DynamicArray_s { + size_t object_size; + size_t capacity; + size_t reserved; + void* memory; + + allocator_t* allocator; +} DynamicArray; + + +/// @brief Initializes a new dynamic-array at the target memory location +/// @param target target memory location +/// @param object_size size of the objects this array will contain +/// @param initial_capacity expected needed capacity of the array +/// @param allocator allocator that provides memory for this array +/// @return EDESTADDRREQ, ERANGE, EINVAL, ENOMEM, EXIT_SUCCESS +int DynamicArray_Create(DynamicArray* target, size_t object_size, size_t initial_capacity, allocator_t* allocator); + +/** @brief Initializes a new dynamic-array at the target, copying data from original + * This is a 'shallow copy', the elements are by no means deep-copied themselves. + * Use DeepClone for that + **/ +/// @param target target memory location +/// @param original original dynamic array, data will be copied from here +/// @return EDESTADDRREQ, ERANGE, EINVAL, ENOMEM, EXIT_SUCCESS +int DynamicArray_Clone(DynamicArray* target, DynamicArray* original); + +/** @brief Initializes a new dynamic-array at the target, copying data from original + * This is a 'shallow copy', the elements are by no means deep-copied themselves. + * Use DeepClone for that + **/ +/// @param target target memory location +/// @param original original dynamic array, data will be copied from here +/// @param clone Function to apply on each element of the array for copying +/// @param xarg Extra Argument that will be supplied to the clone function +/// @return EDESTADDRREQ, ERANGE, EINVAL, ENOMEM, EXIT_SUCCESS +int DynamicArray_DeepClone(DynamicArray* target, DynamicArray* original, DynamicArrayCloneFunction clone, void* xarg); + +/// @brief Resize the array to a new capacity, eventually dropping elements out-of-bounds +/// @param array `this` +/// @param new_capacity Capacity the array shall have after resizing +/// @return EDESTADDRREQ, ERANGE, ENOMEM, EXIT_SUCCESS +int DynamicArray_Resize(DynamicArray* array, size_t new_capacity); + +/// @brief Resize the objects stored in the array, truncating the values or leaving it uninitialized +/// @param array `this` +/// @param new_object_size New object size to apply +/// @return EDESTADDRREQ, ERANGE, ENOMEM, EXIT_SUCCESS +int DynamicArray_ResizeObjects(DynamicArray* array, size_t new_object_size); + +/// @brief Copy object of size array->object_size into the array memory +/// @param array `this` +/// @param object Address of the object that will be copied +/// @return EDESTADDRREQ, ERANGE, ENOMEM, EXIT_SUCCESS +int DynamicArray_Append(DynamicArray* array, void* object); + +/// @brief Append a zero-ed structure and return the pointer to it +/// @param array `this` +/// @param pointer The pointer to the structure will be stored here +/// @return EDESTADDRREQ, ERANGE, ENOMEM, EXIT_SUCCESS +int DynamicArray_AppendEmpty(DynamicArray* array, void** pointer); + +/// @brief Remove the object at the index from the array, shifting all objects behind to the left +/// @param array `this` +/// @param index Index of the removed object +/// @return EDESTADDRREQ, ERANGE, ENOMEM, EXIT_SUCCESS +int DynamicArray_Remove(DynamicArray* array, size_t index); + +/// @brief Remove the object at the index from the array, moving the rightmost object to that index +/// @param array `this` +/// @param index Index to alter +/// @return EDESTADDRREQ, ERANGE, ENOMEM, EXIT_SUCCESS +int DynamicArray_RemoveFast(DynamicArray* array, size_t index); + +/// @brief Find a element by applying a function +/// @param array `this` +/// @param function function to call on each element +/// @param xarg extra argument for the called function +/// @return index, SIZE_MAX +size_t DynamicArray_FindFunction(DynamicArray* array, DynamicArrayFindFunction function, void* xarg); + +/// @brief Find a element using linear search by applying a function +/// @param array `this` +/// @param function function to call on each element for comparison +/// @param xarg extra argument for the called function, should be some sort of search key +/// @return index, SIZE_MAX +size_t DynamicArray_FindFunctionLinear(DynamicArray* array, DynamicArrayLinearFindFunction function, void* xarg); + +/// @brief Return a pointer to the object at the index +/// @param array `this` +/// @param index Retrieved objects index +/// @return NULL, Pointer +void* DynamicArray_GetPointer(DynamicArray* array, size_t index); + +/// @brief Calculate the length of the array +/// @param array `this` +/// @return array->reserved +size_t DynamicArray_GetLength(DynamicArray* array); + +/// @brief Destroy the array and all contents irreversibly +/// @param array `this` +/// @return void +void DynamicArray_Destroy(DynamicArray* array); + +#endif /* SRC_COMMON_DYNAMICARRAY_DYNAMICARRAY_H_ */ diff --git a/src/dynamicbuffer/CMakeLists.txt b/src/dynamicbuffer/CMakeLists.txt new file mode 100644 index 0000000..4a5905c --- /dev/null +++ b/src/dynamicbuffer/CMakeLists.txt @@ -0,0 +1 @@ +add_library(dynamicbuffer STATIC dynamicbuffer.c) diff --git a/src/dynamicbuffer/dynamicbuffer.c b/src/dynamicbuffer/dynamicbuffer.c new file mode 100644 index 0000000..a6e5421 --- /dev/null +++ b/src/dynamicbuffer/dynamicbuffer.c @@ -0,0 +1,217 @@ +/* + * This code is part of the strategy game operational-space. + * operational-space comes with ABSOLUTELY NO WARRANTY and is licensed under GPL-2.0. + * Copyright (C) 2024 VegOwOtenks, Sleppo04 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#include "dynamicbuffer.h" + +int DynamicBuffer_Create(dynamic_buffer_t *destination, size_t initial_capacity) +{ + return DynamicBuffer_CreateWithAllocator(destination, initial_capacity, NULL); +} + +int DynamicBuffer_CreateWithAllocator(dynamic_buffer_t *destination, size_t initial_capacity, allocator_t* allocator) +{ + if (destination == NULL) { + return EDESTADDRREQ; + } + // Some malloc() implementations return NULL and other return valid pointers on zero-byte-allocation, I'm not handling this shit + if (initial_capacity == 0) { + return EINVAL; + } + + dynamic_buffer_t local; + + local.allocator = allocator; + local.capacity = initial_capacity; + local.used = 0; + + if (allocator != NULL) { + local.array = allocator->allocate(allocator, initial_capacity); + } else { + local.array = malloc(initial_capacity); + } + if (local.array == NULL) { + return ENOMEM; + } + + destination[0] = local; + + return EXIT_SUCCESS; +} + +int DynamicBuffer_EnsureUnusedCapacity(dynamic_buffer_t* buffer, size_t needed_unused) +{ + if (buffer == NULL) { + return EINVAL; + } + + size_t unused_space = buffer->capacity - buffer->used; + + if (needed_unused < unused_space) { + return EXIT_SUCCESS; + } + + size_t needed_capacity = buffer->capacity + needed_unused; + int resize_code = DynamicBuffer_Resize(buffer, needed_capacity); + + if (resize_code) { + // ENOMEM + return resize_code; + } + + return EXIT_SUCCESS; +} + +int DynamicBuffer_EnsureCapacity(dynamic_buffer_t* buffer, size_t minimal_capacity) +{ + if (buffer == NULL) { + return EINVAL; + } + + if (minimal_capacity < buffer->capacity) { + return EXIT_SUCCESS; + } + + int resize_code = DynamicBuffer_Resize(buffer, minimal_capacity); + if (resize_code) { + // ENOMEM + return resize_code; + } + + return EXIT_SUCCESS; +} + +int DynamicBuffer_Resize(dynamic_buffer_t* buffer, size_t new_capacity) +{ + if (buffer == NULL) { + return EINVAL; + } + if (new_capacity == 0) { + return EINVAL; + } + + char* new_array; + if (buffer->allocator != NULL) { + new_array = buffer->allocator->reallocate(buffer->allocator, buffer->array, buffer->capacity, new_capacity); + } else { + new_array = realloc(buffer->array, new_capacity); + } + if (new_array == NULL) { + return ENOMEM; + } + + buffer->array = new_array; + buffer->capacity = new_capacity; + + return EXIT_SUCCESS; +} + +int DynamicBuffer_Prune(dynamic_buffer_t* buffer) +{ + if (buffer == NULL) { + return ENOMEM; + } + + return DynamicBuffer_Resize(buffer, buffer->used); +} + +int DynamicBuffer_Reset(dynamic_buffer_t* buffer) +{ + if (buffer == NULL) { + return EINVAL; + } + + buffer->used = 0; + + return EXIT_SUCCESS; +} + +int DynamicBuffer_RewindBytes(dynamic_buffer_t* buffer, size_t bytes) +{ + if (buffer == NULL) { + return EINVAL; + } + if (buffer->used < bytes) { + return EBOUNDS; + } + + buffer->used -= bytes; + + return EXIT_SUCCESS; +} + +int DynamicBuffer_Store(dynamic_buffer_t* buffer, const void* data, size_t data_size) +{ + if (buffer == NULL) { + return EINVAL; + } + if (data == NULL) { + return EINVAL; + } + if (data_size == 0) { + return EXIT_SUCCESS; + } + + int ensure_code = DynamicBuffer_EnsureUnusedCapacity(buffer, data_size); + if (ensure_code) { + // ENOMEM + return ensure_code; + } + + void* destination = ((char*) buffer->array) + buffer->used; + memcpy(destination, data, data_size); + buffer->used += data_size; + + return EXIT_SUCCESS; +} + +size_t DynamicBuffer_GetBlockCount(dynamic_buffer_t* buffer, size_t block_size) +{ + return buffer->used / block_size; +} + +void* DynamicBuffer_ReadAt(dynamic_buffer_t* buffer, size_t offset) +{ + if (offset >= buffer->used) { + return NULL; + } + + return (void*) (((char*) buffer->array) + offset); +} + +void* DynamicBuffer_ReadBlockAt(dynamic_buffer_t* buffer, size_t block_size, size_t index) +{ + return DynamicBuffer_ReadAt(buffer, block_size * index); +} + +int DynamicBuffer_Destroy(dynamic_buffer_t* buffer) +{ + if (buffer == NULL) { + return EINVAL; + } + if (buffer->allocator == NULL) { + free(buffer->array); + } else { + buffer->allocator->free(buffer->allocator, buffer->array, buffer->capacity); + } + buffer->array = NULL; + buffer->capacity = 0; + buffer->used = 0; + + return EXIT_SUCCESS; +} diff --git a/src/dynamicbuffer/dynamicbuffer.h b/src/dynamicbuffer/dynamicbuffer.h new file mode 100644 index 0000000..1ca304e --- /dev/null +++ b/src/dynamicbuffer/dynamicbuffer.h @@ -0,0 +1,116 @@ +/* + * This code is part of the strategy game operational-space. + * operational-space comes with ABSOLUTELY NO WARRANTY and is licensed under GPL-2.0. + * Copyright (C) 2024 VegOwOtenks, Sleppo04 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#ifndef DYNAMICBUFFER_H +#define DYNAMICBUFFER_H + +#include +#include + +#include "../errorcodes.h" + +#include "../allocator-interface/allocator-interface.h" + +typedef struct DynamicBuffer { + void* array; + size_t capacity; + size_t used; + + allocator_t* allocator; +} dynamic_buffer_t; + +/// @brief Create a new Dynamic Buffer at destination with initialCapacity initialCapacity +/// @param destination where the buffer will be stored +/// @param initialCapacity what it's initialCapacity will be +/// @return EINVAL, EINVAL, ENOMEM, EXIT_SUCCESS +int DynamicBuffer_Create(dynamic_buffer_t* destination, size_t initialCapacity); + + +/// @brief Create a new Dynamic Buffer at destination with initialCapacity initialCapacity +/// @param destination where the buffer will be stored +/// @param initialCapacity what it's initialCapacity will be +/// @return EINVAL, EINVAL, ENOMEM, EXIT_SUCCESS +int DynamicBuffer_CreateWithAllocator(dynamic_buffer_t* destination, size_t initialCapacity, allocator_t* allocator); + +/// @brief This function checks that there are at least needed_unused free bytes in the allocated area +/// @param buffer buffer to check +/// @param needed_unused required free array size +/// @return EINVAL, ENOMEM, EXIT_SUCCESS +int DynamicBuffer_EnsureUnusedCapacity(dynamic_buffer_t *buffer, size_t needed_unused); + +/// @brief This function resizes the buffer to minimal_capacity, if necessary +/// @param buffer buffer to check +/// @param minimal_capacity minimal capacity of the buffer array +/// @return EINVAL, ENOMEM, EXIT_SUCCESS +int DynamicBuffer_EnsureCapacity(dynamic_buffer_t *buffer, size_t minimal_capacity); + +/// @brief This function resizes the buffers array via realloc to new_capacity +/// @param buffer buffer to resize +/// @param new_capacity capacity of the new buffer array +/// @return EINVAL, ENOMEM, EXIT_SUCCESS +int DynamicBuffer_Resize(dynamic_buffer_t *buffer, size_t new_capacity); + +/// @brief This function sets the buffers capacity to what it uses +/// @param buffer buffer to prune +/// @return EINVAL, ENOMEM, EXIT_SUCCESS +int DynamicBuffer_Prune(dynamic_buffer_t* buffer); + +/// @brief Resets the count of used bytes to "clear" the buffer +/// @param buffer buffer to reset +/// @return EINVAL, EXIT_SUCCESS +int DynamicBuffer_Reset(dynamic_buffer_t *buffer); + +/// @brief Rewind the buffer pointer by bytes bytes, losing the bytes stored +/// @param buffer buffer to rewind +/// @param bytes How many bytes the buffer will lose +/// @return EINVAL, EBOUNDS, EXIT_SUCCESS +int DynamicBuffer_RewindBytes(dynamic_buffer_t* buffer, size_t bytes); + +/// @brief Stores data[.data_size] at the end of the buffer array and resizes if necessary +/// @param buffer buffer in which the data will be stored +/// @param data data to store +/// @param data_size how many bytes will be copied from data +/// @return EINVAL, EINVAL, ENOMEM, EXIT_SUCCESS +int DynamicBuffer_Store(dynamic_buffer_t *buffer, const void *data, size_t data_size); + +/// @brief Calculate how many blocks are currently in the buffer +/// @param buffer buffer to query +/// @param block_size what's the size of a single block +/// @return How many of these block_sizes does the buffer currently hold +size_t DynamicBuffer_GetBlockCount(dynamic_buffer_t* buffer, size_t block_size); + +/// @brief Get a pointer reference to the buffer contents at offset +/// @param buffer buffer to query +/// @param offset offset from the buffer start +/// @return Pointer to the address in the buffer at offset or NULL if out of Bounds +void* DynamicBuffer_ReadAt(dynamic_buffer_t* buffer, size_t offset); + +/// @brief Get a pointer reference to indexn'th block in buffer +/// @param buffer buffer to query +/// @param block_size size of a single block +/// @param index which block to read +/// @return Pointer to the block at index or NULL if out of bounds +void* DynamicBuffer_ReadBlockAt(dynamic_buffer_t* buffer, size_t block_size, size_t index); + +/// @brief Destroys the dynamic buffer and releases all resources held, the struct will not hold anything it did before +/// @param buffer buffer that shall be destroyed +/// @return EINVAL, EXIT_SUCCESS +int DynamicBuffer_Destroy(dynamic_buffer_t *buffer); + +#endif diff --git a/src/errorcodes.h b/src/errorcodes.h new file mode 100644 index 0000000..916d534 --- /dev/null +++ b/src/errorcodes.h @@ -0,0 +1,83 @@ +/* + * This code is part of the programming language Ivy. + * Ivy comes with ABSOLUTELY NO WARRANTY and is licensed under AGPL-3.0 or later. + * Copyright (C) 2024 VegOwOtenks + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#ifndef ERRORCODES_H +#define ERRORCODES_H + +// Standard codes + +#ifndef EBADFD +// File descriptor in bad state +#define EBADFD 77 +#endif + +#ifndef EINVAL +// Error Invalid Value (or parameter) +#define EINVAL 22 +#endif + +#ifndef EDESTADDRREQ +// Destination address required +#define EDESTADDRREQ 89 +#endif + +#ifndef ENOMEM +// Not enough memory +#define ENOMEM 12 +#endif + +#ifndef ECANCELED +// Not enough memory +#define ECANCELED 132 +#endif + +// Custom codes + +#ifndef EFAILED +// Operation failed +#define EFAILED 133 +#endif + +#ifndef EBADSTATE +// Operation performed on bad object state +#define EBADSTATE 131 +#endif + +#ifndef ENOTFOUND +// Object requested could not be found +#define ENOTFOUND 129 +#endif + +#ifndef ERANGE +// Value was out of range, mathematical result out of range +#define ERANGE 34 +#endif + + +#ifndef EBOUNDS +// Requested value out of array bounds +#define EBOUNDS 130 +#endif + +#ifndef EINPROGRESS +// Operation is already in progress +#define EINPROGRESS 134 +#endif + +#endif diff --git a/src/hashmap/CMakeLists.txt b/src/hashmap/CMakeLists.txt new file mode 100644 index 0000000..b1cddca --- /dev/null +++ b/src/hashmap/CMakeLists.txt @@ -0,0 +1 @@ +add_library(hashmap STATIC hashmap.c) diff --git a/src/hashmap/hashmap.c b/src/hashmap/hashmap.c new file mode 100644 index 0000000..24c0b54 --- /dev/null +++ b/src/hashmap/hashmap.c @@ -0,0 +1,359 @@ +/* + * This code is part of the strategy game operational-space. + * operational-space comes with ABSOLUTELY NO WARRANTY and is licensed under GPL-2.0. + * Copyright (C) 2024 VegOwOtenks, Sleppo04 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#include "hashmap.h" + + +// ! Caution with value_size if you care to store something else than pointers +// ! Set the key if your application is security relevant and increase c and d +struct HashMapConfig HashMap_DefaultConfig(void) +{ + struct HashMapConfig config = { + .load_factor = 0.6, + .growth_factor = 2, + .value_size = sizeof(void*), + .allocator = NULL, + .siphash_config = SipHash_DefaultConfig(), + }; + + return config; +} + +size_t _GetEntrySize(struct HashMapConfig* config) +{ + return sizeof(struct HashMapEntry) + config->value_size; +} + +int HashMap_Create(hashmap_t* restrict destination, struct HashMapConfig* restrict config, size_t initial_capacity) +{ + if (destination == NULL) { + return EDESTADDRREQ; + } + if (initial_capacity == 0) { + return EINVAL; + } + + hashmapentry_t** buckets = Allocator_AllocateArray(config->allocator, sizeof(hashmapentry_t*), initial_capacity); + if (buckets == NULL) { + return ENOMEM; + } + memset(buckets, 0, sizeof(hashmapentry_t*) * initial_capacity); + + destination->buckets = buckets; + destination->capacity = initial_capacity; + destination->used = 0; + destination->config = *config; + + return EXIT_SUCCESS; +} + +void HashMap_Destroy(hashmap_t* restrict hashmap) +{ + if (hashmap == NULL) { + return; + } + + for (size_t i = 0; i < hashmap->capacity; i++) { + hashmapentry_t* entry = hashmap->buckets[i]; + while (entry != NULL) { + hashmapentry_t* next = entry->next; + Allocator_Free(hashmap->config.allocator, entry->key, entry->key_length); + Allocator_Free(hashmap->config.allocator, entry, _GetEntrySize(&hashmap->config)); + entry = next; + } + } + + Allocator_FreeArray(hashmap->config.allocator, hashmap->buckets, sizeof(hashmapentry_t*), hashmap->capacity); + + memset(hashmap, 0, sizeof(*hashmap)); + + return; +} + +uint64_t _HashMap_GetKeyBucketIndex(hashmap_t* restrict hashmap, const void* restrict key, size_t key_length) +{ + return siphash2(key, key_length, &hashmap->config.siphash_config) % hashmap->capacity; +} + +hashmapentry_t* _HashMap_CreateEntry(hashmap_t* restrict hashmap, const void* restrict key, size_t key_length, void* restrict value) +{ + hashmapentry_t* new_entry = Allocator_Allocate(hashmap->config.allocator, _GetEntrySize(&hashmap->config)); + if (new_entry == NULL) { + return NULL; + } + + new_entry->key = Allocator_Allocate(hashmap->config.allocator, key_length); + if (new_entry->key == NULL) { + Allocator_Free(hashmap->config.allocator, new_entry, _GetEntrySize(&hashmap->config)); + return NULL; + } + memcpy(new_entry->key, key, key_length); + + new_entry->key_length = key_length; + + memcpy(new_entry->value, value, hashmap->config.value_size); + + new_entry->next = NULL; + + return new_entry; +} + +// Take care with the linking before +void _HashMap_DestroyEntry(hashmap_t* restrict hashmap, hashmapentry_t* restrict delete) +{ + Allocator_Free(hashmap->config.allocator, delete->key, delete->key_length); + delete->key = NULL; + + Allocator_Free(hashmap->config.allocator, delete, _GetEntrySize(&hashmap->config)); + + return; +} + +bool _HashMap_EntryHasKey(hashmapentry_t* entry, const void* key, size_t key_length) +{ + return entry->key_length == key_length + && memcmp(entry->key, key, key_length) == 0; +} + +hashmapentry_t** _HashMap_GetEntryWithKey(hashmap_t* restrict hashmap, const void* restrict key, size_t key_length) +{ + uint64_t bucket_index = _HashMap_GetKeyBucketIndex(hashmap, key, key_length); + hashmapentry_t** entry = hashmap->buckets + bucket_index; + + while (*entry != NULL) { + if (_HashMap_EntryHasKey(*entry, key, key_length)) { + return entry; + } + entry = &entry[0]->next; + } + + return NULL; +} + +// This operation can never fail +hashmapentry_t* _HashMap_CollectEntries(hashmap_t* restrict hashmap) +{ + hashmapentry_t first = { + .next = NULL, + .key = NULL, + .key_length = 0, + }; + + hashmapentry_t* start = &first; + hashmapentry_t* end = start; + + for (uint64_t i = 0; i < hashmap->capacity; i++) { + hashmapentry_t* current = hashmap->buckets[i]; + if (current == NULL) { + continue; + } + + end->next = current; + + while (end->next != NULL) end = end->next; + } + + return first.next; +} + +void _HashMap_EmplaceEntry(hashmap_t* restrict hashmap, hashmapentry_t* restrict entry) +{ + uint64_t bucket_index = _HashMap_GetKeyBucketIndex(hashmap, entry->key, entry->key_length); + hashmapentry_t** destination = hashmap->buckets + bucket_index; + + entry->next = *destination; + *destination = entry; + + return; +} + +int _HashMap_PreCheckLoadFactor(hashmap_t* restrict hashmap) +{ + double capacity = (double) hashmap->capacity; + double used = 1 + (double) hashmap->used; + + if ((used / capacity) < hashmap->config.load_factor) { + return EXIT_SUCCESS; + } + + size_t new_capacity = hashmap->capacity * hashmap->config.growth_factor; + + size_t old_bytes = hashmap->capacity * _GetEntrySize(&hashmap->config); + size_t new_bytes = new_capacity * _GetEntrySize(&hashmap->config); + hashmapentry_t** new_buckets = Allocator_Reallocate(hashmap->config.allocator, hashmap->buckets, old_bytes, new_bytes); + if (new_buckets == NULL) { + return ENOMEM; + } + + hashmap->buckets = new_buckets; + // Cannot fail + hashmapentry_t* entries = _HashMap_CollectEntries(hashmap); + // NULL it all + memset(new_buckets, 0, new_bytes); + + // NOW update the capacity + hashmap->capacity = new_capacity; + + while (entries != NULL) { + hashmapentry_t* next = entries->next; + _HashMap_EmplaceEntry(hashmap, entries); + + entries = next; + } + + return EXIT_SUCCESS; +} + +int HashMap_Put(hashmap_t* restrict hashmap, const void* key, size_t key_length, void* value) +{ + if (hashmap == NULL) { + return EINVAL; + } + + if (_HashMap_PreCheckLoadFactor(hashmap)) { + return ENOMEM; + } + + // I don't call the substitute single functions here because it's slightly more effective not to do so + uint64_t bucket_index = _HashMap_GetKeyBucketIndex(hashmap, key, key_length); + hashmapentry_t** entry = hashmap->buckets + bucket_index; + + while (entry[0] != NULL) { + if (_HashMap_EntryHasKey(*entry, key, key_length)) { + // Key is already present? + return EBADSTATE; + } + entry = &entry[0]->next; + } + + hashmapentry_t* new_entry = _HashMap_CreateEntry(hashmap, key, key_length, value); + if (new_entry == NULL) { + return ENOMEM; + } + + *entry = new_entry; + + hashmap->used++; + + return EXIT_SUCCESS; +} + +int HashMap_Get(hashmap_t* restrict hashmap, const void* key, size_t key_length, void* value_storage) +{ + if (hashmap == NULL) { + return EINVAL; + } + if (key == NULL) { + return EINVAL; + } + + hashmapentry_t** entry = _HashMap_GetEntryWithKey(hashmap, key, key_length); + if (entry == NULL) { + return ENOTFOUND; + } + + memcpy(value_storage, entry[0]->value, hashmap->config.value_size); + + return EXIT_SUCCESS; +} + +int HashMap_Update(hashmap_t* restrict hashmap, const void* key, size_t key_length, void* new_value) +{ + hashmapentry_t** entry = _HashMap_GetEntryWithKey(hashmap, key, key_length); + if (entry == NULL) { + return ENOTFOUND; + } + + memcpy(entry[0]->value, new_value, hashmap->config.value_size); + + return EXIT_SUCCESS; +} + +int HashMap_Remove(hashmap_t* restrict hashmap, const void* restrict key, size_t key_length) +{ + if (hashmap == NULL) { + return EINVAL; + } + if (key == NULL) { + return EINVAL; + } + + hashmapentry_t** entry = _HashMap_GetEntryWithKey(hashmap, key, key_length); + if (entry == NULL) { + return ENOTFOUND; + } + + hashmapentry_t* delete = *entry; + + // Make the linked list skip it + *entry = entry[0]->next; + + _HashMap_DestroyEntry(hashmap, delete); + hashmap->used--; + + return EXIT_SUCCESS; +} + +int HashMap_ValueByIndex(hashmap_t* restrict hashmap, size_t index, void* store_here) +{ + hashmapentry_t* entry; + int fetch_code = HashMap_EntryByIndex(hashmap, index, &entry); + if (fetch_code) { + return fetch_code; + } + + memcpy(store_here, entry->value, hashmap->config.value_size); + + return EXIT_SUCCESS; +} + +int HashMap_EntryByIndex(hashmap_t* restrict hashmap, size_t index, hashmapentry_t** store_here) +{ + if (hashmap == NULL) { + return EINVAL; + } + if (store_here == NULL) { + return EDESTADDRREQ; + } + if (index > hashmap->used) { + return EBOUNDS; + } + + size_t current_index = SIZE_MAX; + size_t bucket_index = 0; + hashmapentry_t* entry = hashmap->buckets[bucket_index]; + while (current_index != index || entry == NULL) { + if (entry != NULL) { + entry = entry->next; + } else { + bucket_index++; + entry = hashmap->buckets[bucket_index]; + } + current_index += (entry != NULL); + } + + memcpy(store_here, &entry, sizeof(entry)); + + return EXIT_SUCCESS; +} + +size_t HashMap_Size(hashmap_t* restrict hashmap) +{ + return hashmap->used; +} diff --git a/src/hashmap/hashmap.h b/src/hashmap/hashmap.h new file mode 100644 index 0000000..c60ebdf --- /dev/null +++ b/src/hashmap/hashmap.h @@ -0,0 +1,67 @@ +/* + * This code is part of the strategy game operational-space. + * operational-space comes with ABSOLUTELY NO WARRANTY and is licensed under GPL-2.0. + * Copyright (C) 2024 VegOwOtenks, Sleppo04 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#ifndef COMMONHASHMAP_H +#define COMMONHASHMAP_H + +#include +#include + +#include "../siphash/siphash.h" +#include "../allocator-interface/allocator-interface.h" + +typedef struct HashMapEntry { + void* key; + size_t key_length; + struct HashMapEntry* next; + char value[]; +} hashmapentry_t; + +typedef struct HashMapConfig { + double load_factor; + double growth_factor; + size_t value_size; + allocator_t* allocator; + siphashconfig_t siphash_config; +} hashmapconfig_t; + +typedef struct HashMap { + size_t capacity; + size_t used; + struct HashMapEntry** buckets; + struct HashMapConfig config; +} hashmap_t; + +struct HashMapConfig HashMap_DefaultConfig(void); + +int HashMap_Create(hashmap_t* restrict destination, struct HashMapConfig* restrict config, size_t initial_capacity); +void HashMap_Destroy(hashmap_t* restrict hashmap); + +int HashMap_Put(hashmap_t* restrict hashmap, const void* key, size_t key_length, void* value); +int HashMap_Remove(hashmap_t* restrict hashmap, const void* restrict key, size_t key_length); + +int HashMap_Get(hashmap_t* restrict hashmap, const void* key, size_t key_length, void* value_storage); + +int HashMap_Update(hashmap_t* restrict hashmap, const void* key, size_t key_length, void* new_value); + +int HashMap_ValueByIndex(hashmap_t* restrict hashmap, size_t index, void* store_here); +int HashMap_EntryByIndex(hashmap_t* restrict hashmap, size_t index, hashmapentry_t** store_here); +size_t HashMap_Size(hashmap_t* restrict hashmap); + +#endif /* SRC_COMMON_HASHMAP_HASHMAP_H_ */ diff --git a/src/pointers/CMakeLists.txt b/src/pointers/CMakeLists.txt new file mode 100644 index 0000000..3387a54 --- /dev/null +++ b/src/pointers/CMakeLists.txt @@ -0,0 +1 @@ +add_library(pointers STATIC pointers.c) diff --git a/src/pointers/pointers.c b/src/pointers/pointers.c new file mode 100644 index 0000000..669b285 --- /dev/null +++ b/src/pointers/pointers.c @@ -0,0 +1,31 @@ +/* + * This code is part of the programming language Ivy. + * Ivy comes with ABSOLUTELY NO WARRANTY and is licensed under AGPL-3.0 or later. + * Copyright (C) 2024 VegOwOtenks + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "pointers.h" + +void* advancep(void* pointer, uintptr_t bytes) +{ + return (void*) (((uintptr_t) (pointer)) + bytes); +} + +void* rewindp(void* pointer, uintptr_t bytes) +{ + return (void*) (((uintptr_t) (pointer)) - bytes); +} diff --git a/src/pointers/pointers.h b/src/pointers/pointers.h new file mode 100644 index 0000000..e233445 --- /dev/null +++ b/src/pointers/pointers.h @@ -0,0 +1,28 @@ +/* + * This code is part of the programming language Ivy. + * Ivy comes with ABSOLUTELY NO WARRANTY and is licensed under AGPL-3.0 or later. + * Copyright (C) 2024 VegOwOtenks + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#ifndef POINTERS_H_ +#define POINTERS_H_ + +#include + +void* advancep(void* pointer, uintptr_t bytes); +void* rewindp(void* pointer, uintptr_t bytes); + +#endif /* SRC_POINTERS_H_ */ diff --git a/src/rand/CMakeLists.txt b/src/rand/CMakeLists.txt new file mode 100644 index 0000000..d035023 --- /dev/null +++ b/src/rand/CMakeLists.txt @@ -0,0 +1,2 @@ +add_library(rand STATIC opensimplex.c +xoshiro256.c) diff --git a/src/rand/opensimplex.c b/src/rand/opensimplex.c new file mode 100644 index 0000000..5382fba --- /dev/null +++ b/src/rand/opensimplex.c @@ -0,0 +1,175 @@ +/* + * This code is part of the strategy game operational-space. + * operational-space comes with ABSOLUTELY NO WARRANTY and is licensed under GPL-2.0. + * Copyright (C) 2024 VegOwOtenks, Sleppo04 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#include "opensimplex.h" + +float GRADIENTS_2D[] = {6.9808965, 16.853374, 16.853374, 6.9808965, 16.853374, -6.9808965, 6.9808965, -16.853374, -6.9808965, -16.853374, -16.853374, -6.9808965, -16.853374, 6.9808965, -6.9808965, 16.853374, 2.3810537, 18.0859, 11.105002, 14.4723215, 14.4723215, 11.105002, 18.0859, 2.3810537, 18.0859, -2.3810537, 14.4723215, -11.105002, 11.105002, -14.4723215, 2.3810537, -18.0859, -2.3810537, -18.0859, -11.105002, -14.4723215, -14.4723215, -11.105002, -18.0859, -2.3810537, -18.0859, 2.3810537, -14.4723215, 11.105002, -11.105002, 14.4723215, -2.3810537, 18.0859, 6.9808965, 16.853374, 16.853374, 6.9808965, 16.853374, -6.9808965, 6.9808965, -16.853374, -6.9808965, -16.853374, -16.853374, -6.9808965, -16.853374, 6.9808965, -6.9808965, 16.853374, 2.3810537, 18.0859, 11.105002, 14.4723215, 14.4723215, 11.105002, 18.0859, 2.3810537, 18.0859, -2.3810537, 14.4723215, -11.105002, 11.105002, -14.4723215, 2.3810537, -18.0859, -2.3810537, -18.0859, -11.105002, -14.4723215, -14.4723215, -11.105002, -18.0859, -2.3810537, -18.0859, 2.3810537, -14.4723215, 11.105002, -11.105002, 14.4723215, -2.3810537, 18.0859, 6.9808965, 16.853374, 16.853374, 6.9808965, 16.853374, -6.9808965, 6.9808965, -16.853374, -6.9808965, -16.853374, -16.853374, -6.9808965, -16.853374, 6.9808965, -6.9808965, 16.853374, 2.3810537, 18.0859, 11.105002, 14.4723215, 14.4723215, 11.105002, 18.0859, 2.3810537, 18.0859, -2.3810537, 14.4723215, -11.105002, 11.105002, -14.4723215, 2.3810537, -18.0859, -2.3810537, -18.0859, -11.105002, -14.4723215, -14.4723215, -11.105002, -18.0859, -2.3810537, -18.0859, 2.3810537, -14.4723215, 11.105002, -11.105002, 14.4723215, -2.3810537, 18.0859, 6.9808965, 16.853374, 16.853374, 6.9808965, 16.853374, -6.9808965, 6.9808965, -16.853374, -6.9808965, -16.853374, -16.853374, -6.9808965, -16.853374, 6.9808965, -6.9808965, 16.853374, 2.3810537, 18.0859, 11.105002, 14.4723215, 14.4723215, 11.105002, 18.0859, 2.3810537, 18.0859, -2.3810537, 14.4723215, -11.105002, 11.105002, -14.4723215, 2.3810537, -18.0859, -2.3810537, -18.0859, -11.105002, -14.4723215, -14.4723215, -11.105002, -18.0859, -2.3810537, -18.0859, 2.3810537, -14.4723215, 11.105002, -11.105002, 14.4723215, -2.3810537, 18.0859, 6.9808965, 16.853374, 16.853374, 6.9808965, 16.853374, -6.9808965, 6.9808965, -16.853374, -6.9808965, -16.853374, -16.853374, -6.9808965, -16.853374, 6.9808965, -6.9808965, 16.853374, 2.3810537, 18.0859, 11.105002, 14.4723215, 14.4723215, 11.105002, 18.0859, 2.3810537, 18.0859, -2.3810537, 14.4723215, -11.105002, 11.105002, -14.4723215, 2.3810537, -18.0859, -2.3810537, -18.0859, -11.105002, -14.4723215, -14.4723215, -11.105002, -18.0859, -2.3810537, -18.0859, 2.3810537, -14.4723215, 11.105002, -11.105002, 14.4723215, -2.3810537, 18.0859, 6.9808965, 16.853374, 16.853374, 6.9808965, 16.853374, -6.9808965, 6.9808965, -16.853374, -6.9808965, -16.853374, -16.853374, -6.9808965, -16.853374, 6.9808965, -6.9808965, 16.853374}; + +int OpenSimplex_Floor(double n) +{ + return (int) floor(n); +} + +float OpenSimplex_2DGrad(int64_t seed, int64_t xsvp, int64_t ysvp, float dx, float dy) { + int64_t hash = seed ^ xsvp ^ ysvp; + hash *= HASH_MULTIPLIER; + hash ^= hash >> (64 - N_GRADS_2D_EXPONENT + 1); + int gi = (int)hash & ((N_GRADS_2D - 1) << 1); + return GRADIENTS_2D[gi | 0] * dx + GRADIENTS_2D[gi | 1] * dy; +} + +/** + * 2D OpenSimplex2S/SuperSimplex noise base. + */ +float OpenSimplex_2DNoise_UnskewedBase(int64_t seed, double xs, double ys) { + + // Get base points and offsets. + int xsb = OpenSimplex_Floor(xs), ysb = OpenSimplex_Floor(ys); + float xi = (float)(xs - xsb), yi = (float)(ys - ysb); + + // Prime pre-multiplication for hash. + int64_t xsbp = xsb * PRIME_X, ysbp = ysb * PRIME_Y; + + // Unskew. + float t = (xi + yi) * (float)UNSKEW_2D; + float dx0 = xi + t, dy0 = yi + t; + + // First vertex. + float a0 = RSQUARED_2D - dx0 * dx0 - dy0 * dy0; + float value = (a0 * a0) * (a0 * a0) * OpenSimplex_2DGrad(seed, xsbp, ysbp, dx0, dy0); + + // Second vertex. + float a1 = (float)(2 * (1 + 2 * UNSKEW_2D) * (1 / UNSKEW_2D + 2)) * t + ((float)(-2 * (1 + 2 * UNSKEW_2D) * (1 + 2 * UNSKEW_2D)) + a0); + float dx1 = dx0 - (float)(1 + 2 * UNSKEW_2D); + float dy1 = dy0 - (float)(1 + 2 * UNSKEW_2D); + value += (a1 * a1) * (a1 * a1) * OpenSimplex_2DGrad(seed, xsbp + PRIME_X, ysbp + PRIME_Y, dx1, dy1); + + // Third and fourth vertices. + // Nested conditionals were faster than compact bit logic/arithmetic. + float xmyi = xi - yi; + if (t < UNSKEW_2D) { + if (xi + xmyi > 1) { + float dx2 = dx0 - (float)(3 * UNSKEW_2D + 2); + float dy2 = dy0 - (float)(3 * UNSKEW_2D + 1); + float a2 = RSQUARED_2D - dx2 * dx2 - dy2 * dy2; + if (a2 > 0) { + value += (a2 * a2) * (a2 * a2) * OpenSimplex_2DGrad(seed, xsbp + (PRIME_X << 1), ysbp + PRIME_Y, dx2, dy2); + } + } + else + { + float dx2 = dx0 - (float)UNSKEW_2D; + float dy2 = dy0 - (float)(UNSKEW_2D + 1); + float a2 = RSQUARED_2D - dx2 * dx2 - dy2 * dy2; + if (a2 > 0) { + value += (a2 * a2) * (a2 * a2) * OpenSimplex_2DGrad(seed, xsbp, ysbp + PRIME_Y, dx2, dy2); + } + } + + if (yi - xmyi > 1) { + float dx3 = dx0 - (float)(3 * UNSKEW_2D + 1); + float dy3 = dy0 - (float)(3 * UNSKEW_2D + 2); + float a3 = RSQUARED_2D - dx3 * dx3 - dy3 * dy3; + if (a3 > 0) { + value += (a3 * a3) * (a3 * a3) * OpenSimplex_2DGrad(seed, xsbp + PRIME_X, ysbp + (PRIME_Y << 1), dx3, dy3); + } + } + else + { + float dx3 = dx0 - (float)(UNSKEW_2D + 1); + float dy3 = dy0 - (float)UNSKEW_2D; + float a3 = RSQUARED_2D - dx3 * dx3 - dy3 * dy3; + if (a3 > 0) { + value += (a3 * a3) * (a3 * a3) * OpenSimplex_2DGrad(seed, xsbp + PRIME_X, ysbp, dx3, dy3); + } + } + } + else + { + if (xi + xmyi < 0) { + float dx2 = dx0 + (float)(1 + UNSKEW_2D); + float dy2 = dy0 + (float)UNSKEW_2D; + float a2 = RSQUARED_2D - dx2 * dx2 - dy2 * dy2; + if (a2 > 0) { + value += (a2 * a2) * (a2 * a2) * OpenSimplex_2DGrad(seed, xsbp - PRIME_X, ysbp, dx2, dy2); + } + } + else + { + float dx2 = dx0 - (float)(UNSKEW_2D + 1); + float dy2 = dy0 - (float)UNSKEW_2D; + float a2 = RSQUARED_2D - dx2 * dx2 - dy2 * dy2; + if (a2 > 0) { + value += (a2 * a2) * (a2 * a2) * OpenSimplex_2DGrad(seed, xsbp + PRIME_X, ysbp, dx2, dy2); + } + } + + if (yi < xmyi) { + float dx2 = dx0 + (float)UNSKEW_2D; + float dy2 = dy0 + (float)(UNSKEW_2D + 1); + float a2 = RSQUARED_2D - dx2 * dx2 - dy2 * dy2; + if (a2 > 0) { + value += (a2 * a2) * (a2 * a2) * OpenSimplex_2DGrad(seed, xsbp, ysbp - PRIME_Y, dx2, dy2); + } + } + else + { + float dx2 = dx0 - (float)UNSKEW_2D; + float dy2 = dy0 - (float)(UNSKEW_2D + 1); + float a2 = RSQUARED_2D - dx2 * dx2 - dy2 * dy2; + if (a2 > 0) { + value += (a2 * a2) * (a2 * a2) * OpenSimplex_2DGrad(seed, xsbp, ysbp + PRIME_Y, dx2, dy2); + } + } + } + + return value; +} + +/* + * Noise Evaluators + */ + +/** + * 2D OpenSimplex2S/SuperSimplex noise, standard lattice orientation. + */ +float OpenSimplex_2DNoise(int64_t seed, double x, double y) { + + // Get points for A2* lattice + double s = SKEW_2D * (x + y); + double xs = x + s, ys = y + s; + + return OpenSimplex_2DNoise_UnskewedBase(seed, xs, ys); +} + +/** + * 2D OpenSimplex2S/SuperSimplex noise, with Y pointing down the main diagonal. + * Might be better for a 2D sandbox style game, where Y is vertical. + * Probably slightly less optimal for heightmaps or continent maps, + * unless your map is centered around an equator. It's a slight + * difference, but the option is here to make it easy. + */ +float OpenSimplex_2DNoise_ImprovedX(int64_t seed, double x, double y) { + + // Skew transform and rotation baked into one. + double xx = x * ROOT2OVER2; + double yy = y * (ROOT2OVER2 * (1 + 2 * SKEW_2D)); + + return OpenSimplex_2DNoise_UnskewedBase(seed, yy + xx, yy - xx); +} \ No newline at end of file diff --git a/src/rand/opensimplex.h b/src/rand/opensimplex.h new file mode 100644 index 0000000..0372de7 --- /dev/null +++ b/src/rand/opensimplex.h @@ -0,0 +1,52 @@ +/* + * This code is part of the strategy game operational-space. + * operational-space comes with ABSOLUTELY NO WARRANTY and is licensed under GPL-2.0. + * Copyright (C) 2024 VegOwOtenks, Sleppo04 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#ifndef OPENSIMPLEX2S_H +#define OPENSIMPLEX2S_H + +#include +#include + + +#define PRIME_X (0x5205402B9270C86FL) +#define PRIME_Y (0x598CD327003817B5L) +#define HASH_MULTIPLIER (0x53A3F72DEEC546F5L) + +#define ROOT2OVER2 (0.7071067811865476) +#define SKEW_2D (0.366025403784439) +#define UNSKEW_2D (-0.21132486540518713) + +#define N_GRADS_2D_EXPONENT (7) +#define N_GRADS_2D (1 << N_GRADS_2D_EXPONENT) + +#define NORMALIZER_2D (0.05481866495625118) + +#define RSQUARED_2D (2.0f / 3.0f) + +/// @brief Get the standard noise using the at position x and y with seed seed +/// @param seed +/// @param x +/// @param y +/// @return noise at x, y with seed seed +// TODO: Don't use this one, it has weird diagonal artifacts +float OpenSimplex_2DNoise(int64_t seed, double x, double y); + +float OpenSimplex_2DNoise_ImprovedX(int64_t seed, double x, double y); + +#endif diff --git a/src/rand/xoshiro256.c b/src/rand/xoshiro256.c new file mode 100644 index 0000000..8c720e7 --- /dev/null +++ b/src/rand/xoshiro256.c @@ -0,0 +1,106 @@ +/* + * This code is part of the strategy game operational-space. + * operational-space comes with ABSOLUTELY NO WARRANTY and is licensed under GPL-2.0. + * Copyright (C) 2024 VegOwOtenks, Sleppo04 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#include "xoshiro256.h" + +uint64_t rotate_uint64_left(uint64_t bytes, int degree) +{ + return (bytes << degree) | (bytes >> (64 - degree)); +} + + +uint64_t xoshiro256_next(xoshiro256_state_t* state) { + uint64_t* uint64_state = state->state; + const uint64_t result = rotate_uint64_left(uint64_state[0] + uint64_state[3], 23) + uint64_state[0]; + + const uint64_t t = uint64_state[1] << 17; + + uint64_state[2] ^= uint64_state[0]; + uint64_state[3] ^= uint64_state[1]; + uint64_state[1] ^= uint64_state[2]; + uint64_state[0] ^= uint64_state[3]; + + uint64_state[2] ^= t; + + uint64_state[3] = rotate_uint64_left(uint64_state[3], 45); + + return result; +} + + +/* This is the jump function for the generator. It is equivalent + to 2^128 calls to next(); it can be used to generate 2^128 + non-overlapping subsequences for parallel computations. */ + +void jump(xoshiro256_state_t* state) { + uint64_t* uint64_state = state->state; + static const uint64_t JUMP[] = { 0x180ec6d33cfd0aba, 0xd5a61266f0c9392c, 0xa9582618e03fc9aa, 0x39abdc4529b1661c }; + + uint64_t s0 = 0; + uint64_t s1 = 0; + uint64_t s2 = 0; + uint64_t s3 = 0; + for(uint64_t i = 0; i < sizeof JUMP / sizeof *JUMP; i++) + for(int b = 0; b < 64; b++) { + if (JUMP[i] & UINT64_C(1) << b) { + s0 ^= uint64_state[0]; + s1 ^= uint64_state[1]; + s2 ^= uint64_state[2]; + s3 ^= uint64_state[3]; + } + xoshiro256_next(state); + } + + uint64_state[0] = s0; + uint64_state[1] = s1; + uint64_state[2] = s2; + uint64_state[3] = s3; +} + + + +/* This is the long-jump function for the generator. It is equivalent to + 2^192 calls to next(); it can be used to generate 2^64 starting points, + from each of which jump() will generate 2^64 non-overlapping + subsequences for parallel distributed computations. */ + +void long_jump(xoshiro256_state_t* state) { + uint64_t* uint64_state = state->state; + static const uint64_t LONG_JUMP[] = { 0x76e15d3efefdcbbf, 0xc5004e441c522fb3, 0x77710069854ee241, 0x39109bb02acbe635 }; + + uint64_t s0 = 0; + uint64_t s1 = 0; + uint64_t s2 = 0; + uint64_t s3 = 0; + for(uint64_t i = 0; i < sizeof LONG_JUMP / sizeof *LONG_JUMP; i++) + for(int b = 0; b < 64; b++) { + if (LONG_JUMP[i] & UINT64_C(1) << b) { + s0 ^= uint64_state[0]; + s1 ^= uint64_state[1]; + s2 ^= uint64_state[2]; + s3 ^= uint64_state[3]; + } + xoshiro256_next(state); + } + + uint64_state[0] = s0; + uint64_state[1] = s1; + uint64_state[2] = s2; + uint64_state[3] = s3; +} \ No newline at end of file diff --git a/src/rand/xoshiro256.h b/src/rand/xoshiro256.h new file mode 100644 index 0000000..d88f3a0 --- /dev/null +++ b/src/rand/xoshiro256.h @@ -0,0 +1,33 @@ +/* + * This code is part of the strategy game operational-space. + * operational-space comes with ABSOLUTELY NO WARRANTY and is licensed under GPL-2.0. + * Copyright (C) 2024 VegOwOtenks, Sleppo04 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#ifndef XOSHIRO256_H +#define XOSHIRO256_H + +#include + +typedef struct XoshiroState { + uint64_t state[4]; +} xoshiro256_state_t; + +uint64_t rotate_uint64_left(uint64_t bytes, int degree); + +uint64_t xoshiro256_next(xoshiro256_state_t *state); + +#endif \ No newline at end of file diff --git a/src/regex/CMakeLists.txt b/src/regex/CMakeLists.txt new file mode 100644 index 0000000..d5b22a8 --- /dev/null +++ b/src/regex/CMakeLists.txt @@ -0,0 +1,5 @@ +add_library(regex STATIC compile.c +machine_state.c +match.c +match_struct.c +regex.c) diff --git a/src/regex/compile.c b/src/regex/compile.c new file mode 100644 index 0000000..b2a4aff --- /dev/null +++ b/src/regex/compile.c @@ -0,0 +1,517 @@ +#include "compile.h" +#include "machine_state.h" +#include "machine_state_struct.h" +#include "regex_struct.h" + +#include "../utf8/utf-8.h" + +#include +#include +#include +#include +#include +#include + +#define COMPILE_STATE_STACK_SIZE 16 + +static inline bool _IsSpecialChar(int character) +{ + return character == '(' || + character == ')' || + character == '[' || + character == ']' || + character == '|'; +} + +static inline bool _IsQuantifierChar(int character) +{ + return character == '?' || + character == '{' || + character == '+' || + character == '*'; +} + +static inline bool _IsLiteralChar(int character) +{ + return ! (_IsQuantifierChar(character) || _IsSpecialChar(character) || character == '\\'); +} + +static inline bool SingleCharacterQuantified(StringView source) +{ + const char* next = UTF8_NextCodepoint(source.source); + uintptr_t distance = (uintptr_t) next - (uintptr_t) source.source; + + return distance + 1 < source.length + && _IsQuantifierChar(source.source[distance]); +} + +static RegexMachineStateBase* _NewAcceptState(Regex* regex, StringView* source, size_t length) +{ + // Create new regex state + RegexMachineStateAccept* accept = Scratchpad_Reserve( + ®ex->machine_memory, + sizeof(RegexMachineStateAccept) + ); + if (accept == NULL) { + return NULL; + } + RegexMachineStateAccept_Init(accept); + + + // Copy the sequence to an owned buffer + char* accept_sequence = Scratchpad_Reserve( + ®ex->machine_memory, + length + ); + if (accept_sequence == NULL) { + return NULL; + } + memcpy(accept_sequence, source->source, length); + + // Make it a StringView for easier operation, also omits the \0 + accept->sequence = StringView_FromStringSized( + accept_sequence, + length + ); + + // Advance source stringview + *source = StringView_Slice(*source, length, source->length); + + return &accept->base; +} + +// Literals are everything except special groups, escaped characters etc... +static RegexMachineStateBase* _CompileLiteralMatch(Regex* regex, StringView* source) +{ + // literal match, will be modified over time + StringView literal_match = StringView_Slice(*source, 0, 0); + + bool end_reached = false; + if (SingleCharacterQuantified(*source)) { + end_reached = true; + ptrdiff_t length = UTF8_NextCodepoint(source->source) - source->source; + literal_match = StringView_Slice(*source, 0, length); + } + + while (! end_reached) { + if (SingleCharacterQuantified(*source)) { + /* + This break allows for regexes like "a?" + It also checks for one utf8 character (e.g. not flags) + If it wouldn't break, "var?" would be treated as an + optional word, Optional[var] not as "va" + Optional[r] + */ + break; + } + + // Still in a literal sequence? + if (! _IsLiteralChar(source->source[0])) { + // Literal sequence is over, break here + break; + } + + // Increment literal match length + source->source++; + source->length--; + literal_match.length++; + + // Also terminate the loop if there are no more characters left to read + end_reached |= source->length == 0; + } + + return _NewAcceptState(regex, &literal_match, literal_match.length); +} + +static RegexMachineStateBase* _CompileEscapedStuff(Regex* regex, StringView* source) +{ + if (source->length == 1) { + // TODO: Set Error + return NULL; + } + + *source = StringView_Slice(*source, 1, source->length); + switch (UTF8_CodepointLength(source->source)) { + case 1: + { + char next = source->source[0]; + switch (next) { + case '\\': + case '(': + case ')': + case '{': + case '}': + case '[': + case ']': + case '+': + case '*': + case '?': + return _NewAcceptState(regex, source, 1); + } + break; + } + case 2: + case 3: + case 4: + case SIZE_MAX: + return NULL; + } + + // TODO: Set Error + return NULL; +} + +static RegexMachineStateBase* _CompileNewState(Regex* regex, StringView* source) +{ + switch (source->source[0]) { + case '(': + break; + case '[': + break; + case '\\': + return _CompileEscapedStuff(regex, source); + default: + return _CompileLiteralMatch(regex, source); + } + + return NULL; +} + +struct RegexStackEntry { + // the parent container itself (group or option) + RegexMachineStateBase* parent; + + // last compiled state, next one will be appended here + RegexMachineStateBase* tail; +}; + +static int _PushIfGroup(DynamicArray* entry_stack, RegexMachineStateBase* new_state) +{ + if (new_state->type == REGEXMACHINESTATETYPE_GROUP) { + struct RegexStackEntry* new_entry; + if (DynamicArray_AppendEmpty(entry_stack, (void**) &new_entry)) { + return ENOMEM; + } + + new_entry->parent = new_state; + new_entry->tail = new_state; + } + + return EXIT_SUCCESS; +} + +void _RedirectOptionTails(RegexMachineStateOption* option, RegexMachineStateBase* new_tail) +{ + while (option->next_option != NULL) { + + RegexMachineStateBase* tail = option->base.next; + while (tail->next != NULL) { + tail = tail->next; + } + + // Append + tail->next = new_tail; + + option = option->next_option; + } + + return; +} + +static int _CompileIntoGroup(Regex* regex, DynamicArray* entries, StringView* source) +{ + struct RegexStackEntry* group_entry = DynamicArray_GetPointer(entries, entries->reserved - 1); + RegexMachineStateGroup* group = (RegexMachineStateGroup*) group_entry->parent; + + if (source->source[0] == ')' && group->number != 0) { + // End group + RegexMachineStateGroup* group_end = Scratchpad_Reserve(®ex->machine_memory, sizeof(*group)); + if (group_end == NULL) { + return ENOMEM; + } + + RegexMachineStateGroup_Init(group_end, group->name, group->number); + + if (group->base.next->type == REGEXMACHINESTATETYPE_OPTION) { + _RedirectOptionTails((RegexMachineStateOption*) group->base.next, &group_end->base); + // Branching in group + } else { + // Just linear members in group + group_entry->tail->next = &group_end->base; + } + + DynamicArray_RemoveFast(entries, entries->reserved - 1); + + *source = StringView_Slice(*source, 1, source->length); + + } else if (source->source[0] == ')' && group->number == 0) { + + // Closing parenthesis for virtual zeroth capture group + // TODO: Report error + return EXIT_FAILURE; + + } else if (source->source[0] == '|') { + // Add new option as 'next' of group, embed everything parsed in it + + RegexMachineStateOption* option; + option = Scratchpad_Reserve(®ex->machine_memory, sizeof(*option)); + if (option == NULL) { + return ENOMEM; + } + + RegexMachineStateOption_Init(option); + option->base.next = group->base.next; + + // Add to stack + { + struct RegexStackEntry* new_entry; + if (DynamicArray_AppendEmpty(entries, (void**) &new_entry)) { + return ENOMEM; + } + + new_entry->parent = &option->base; + new_entry->tail = group_entry->tail; + while (new_entry->tail->next != NULL) { + new_entry->tail = new_entry->tail->next; + } + } + + // redirect group next + group->base.next = &option->base; + group_entry->tail = &option->base; + + } else { + + RegexMachineStateBase* new_state = _CompileNewState(regex, source); + if (new_state == NULL) { + return ENOMEM; + } + + // Append to linked list + group_entry->tail->next = new_state; + group_entry->tail = new_state; + + int add_code = _PushIfGroup(entries, new_state); + if (add_code != EXIT_SUCCESS) { + return add_code; + } + } + + + return EXIT_SUCCESS; +} + +static int _CompileIntoOption(Regex* regex, DynamicArray* entries, StringView* source) +{ + struct RegexStackEntry* option_entry = DynamicArray_GetPointer(entries, entries->reserved - 1); + + + if (source->source[0] == '|') { + // New option + RegexMachineStateOption* new_option; + new_option = Scratchpad_Reserve(®ex->machine_memory, sizeof(*new_option)); + if (new_option == NULL) { + return ENOMEM; + } + + RegexMachineStateOption_Init(new_option); + + option_entry->parent = &new_option->base; + option_entry->tail = &new_option->base; + + *source = StringView_Slice(*source, 1, source->length); + + } else if (source->source[0] == ')') { + // Can't parse state here, finish option + DynamicArray_RemoveFast(entries, entries->reserved - 1); + } else { + // Append to current option + RegexMachineStateBase* new_state = _CompileNewState(regex, source); + + option_entry->tail->next = new_state; + option_entry->tail = new_state; + + int add_code = _PushIfGroup(entries, new_state); + if (add_code != EXIT_SUCCESS) { + return add_code; + } + } + + return EXIT_SUCCESS; +} + +static int _HaveQuantifier(StringView source) +{ + StringView quantifiers = StringView_FromString("+*?{"); + return StringView_Contains(quantifiers, StringView_Slice(source, 0, 1)); +} + +static int _CompileQuantifier(Regex* regex, DynamicArray* entries, StringView* source) +{ + struct RegexStackEntry* last_entry = DynamicArray_GetPointer(entries, DynamicArray_GetLength(entries) - 1); + + size_t min, max; + size_t char_count = 1; + + RegexMachineStateBase* repeatable = last_entry->tail; + RegexMachineStateBase* repeat_end = repeatable; + RegexMachineStateBase* previous; + + previous = last_entry->parent; + while (previous->next != repeatable) { + if (previous->next->type == REGEXMACHINESTATETYPE_GROUP + && + (((RegexMachineStateGroup*) previous->next)->number == + ((RegexMachineStateGroup*) repeatable)->number) + ) { + repeatable = previous->next; + break; + } + + previous = previous->next; + } + + switch (source->source[0]) { + case '?': + min = 0; + max = 1; + break; + case '+': + min = 1; + max = SIZE_MAX; + break; + case '*': + min = 0; + max = SIZE_MAX; + break; + case '{': + // TODO: Implement + return EXIT_FAILURE; + break; + } + + *source = StringView_Slice(*source, char_count, source->length); + + bool greedy = true; + bool possessive = false; + if (StringView_StartsWith(*source, StringView_FromString("?"))) { + greedy = false; + *source = StringView_Slice(*source, 1, source->length); + } else if (StringView_StartsWith(*source, StringView_FromString("+"))) { + possessive = true; + *source = StringView_Slice(*source, 1, source->length); + } + + + RegexMachineStateRepeat* repeat_state; + repeat_state = Scratchpad_Reserve(®ex->machine_memory, sizeof(*repeat_state)); + if (repeat_state == NULL) { + return ENOMEM; + } + + RegexMachineStateRepeat_Init(repeat_state, min, max, greedy, possessive, repeatable); + + // Insert before repeatable + // redirect repeatable end + repeat_end->next = &repeat_state->base; + previous->next = &repeat_state->base; + + last_entry->tail = &repeat_state->base; + + return EXIT_SUCCESS; +} + +static int _CompileIntoEntries(Regex* regex, DynamicArray* entries, StringView* source) +{ + struct RegexStackEntry* current = DynamicArray_GetPointer(entries, entries->reserved - 1); + + + if (source->length != 0) { + // Happily compile on + if (_HaveQuantifier(*source)) { + return _CompileQuantifier(regex, entries, source); + } + + switch (current->parent->type) { + + case REGEXMACHINESTATETYPE_GROUP: + return _CompileIntoGroup(regex, entries, source); + + case REGEXMACHINESTATETYPE_OPTION: + return _CompileIntoOption(regex, entries, source); + + default: + return EXIT_FAILURE; + } + + } else { + // Pop all options but not groups + // (groups need closing parentheses) + } + + return EXIT_SUCCESS; +} + +int Internal_Regex_Compile(Regex* regex, StringView source) +{ + int return_code = EXIT_SUCCESS; + DynamicArray parent_stack; + if (DynamicArray_Create(&parent_stack, sizeof(struct RegexStackEntry), 4, regex->machine_memory.allocator)) { + // unlikely + return ENOMEM; + } + + { + // Create group zero + // Spans entire match + RegexMachineStateGroup* zero_group; + zero_group = Scratchpad_Reserve(®ex->machine_memory, sizeof(*zero_group)); + if (zero_group == NULL) { + // unlikely + return_code = ENOMEM; + goto defer_stack; + } + + RegexMachineStateGroup_Init(zero_group, NULL, 0); + + regex->head = &zero_group->base; + + struct RegexStackEntry* zero_stackentry; + // Can't fail, first push with sufficient capacity + DynamicArray_AppendEmpty(&parent_stack, (void**) &zero_stackentry); + zero_stackentry->parent = &zero_group->base; + zero_stackentry->tail = &zero_group->base; + } + + while (source.length || (parent_stack.reserved != 1)) { + int compile_code = _CompileIntoEntries(regex, &parent_stack, &source); + + if (compile_code != EXIT_SUCCESS) { + return_code = compile_code; + goto defer_stack; + } + } + + { + // End group zero + // Spans entire match + RegexMachineStateGroup* zero_group_end; + zero_group_end = Scratchpad_Reserve(®ex->machine_memory, sizeof(*zero_group_end)); + if (zero_group_end == NULL) { + // unlikely + return_code = ENOMEM; + goto defer_stack; + } + + RegexMachineStateGroup_Init(zero_group_end, NULL, 0); + + + struct RegexStackEntry* zero_stackentry = DynamicArray_GetPointer(&parent_stack, 0); + zero_stackentry->tail->next = &zero_group_end->base; + zero_stackentry->tail = &zero_group_end->base; + } + +defer_stack: + DynamicArray_Destroy(&parent_stack); + + return return_code; +} diff --git a/src/regex/compile.h b/src/regex/compile.h new file mode 100644 index 0000000..df4794a --- /dev/null +++ b/src/regex/compile.h @@ -0,0 +1,8 @@ +#ifndef UTILITIEC_REGEX_COMPILE_H +#define UTILITIEC_REGEX_COMPILE_H + +#include "regex_struct.h" + +int Internal_Regex_Compile(Regex* regex, StringView source); + +#endif diff --git a/src/regex/machine_state.c b/src/regex/machine_state.c new file mode 100644 index 0000000..bc83960 --- /dev/null +++ b/src/regex/machine_state.c @@ -0,0 +1,37 @@ +#include "machine_state.h" +#include "machine_state_struct.h" + +void RegexMachineStateBase_Init(RegexMachineStateBase* base, enum RegexMachineStateType type) +{ + base->next = NULL; + base->type = type; +} + +void RegexMachineStateOption_Init(RegexMachineStateOption* option) +{ + RegexMachineStateBase_Init(&option->base, REGEXMACHINESTATETYPE_OPTION); + option->next_option = NULL; +} + +void RegexMachineStateGroup_Init(RegexMachineStateGroup* group, char* name, size_t number) +{ + RegexMachineStateBase_Init(&group->base, REGEXMACHINESTATETYPE_GROUP); + group->name = name; + group->number = number; +} + +void RegexMachineStateAccept_Init(RegexMachineStateAccept* accept) +{ + RegexMachineStateBase_Init(&accept->base, REGEXMACHINESTATETYPE_ACCEPT); + accept->sequence = (StringView) {.source = NULL, .length = 0}; +} + +void RegexMachineStateRepeat_Init(RegexMachineStateRepeat* repeat, size_t min, size_t max, bool greedy, bool possessive, RegexMachineStateBase* repeatable) +{ + RegexMachineStateBase_Init(&repeat->base, REGEXMACHINESTATETYPE_REPEAT); + repeat->min = min; + repeat->max = max; + repeat->greedy = greedy; + repeat->possessive = possessive; + repeat->repeatable = repeatable; +} diff --git a/src/regex/machine_state.h b/src/regex/machine_state.h new file mode 100644 index 0000000..ae4b27e --- /dev/null +++ b/src/regex/machine_state.h @@ -0,0 +1,12 @@ +#ifndef UTILITIEC_REGEX_MACHINE_STATE_H +#define UTILITIEC_REGEX_MACHINE_STATE_H + +#include "machine_state_struct.h" + +void RegexMachineStateBase_Init(RegexMachineStateBase* base, enum RegexMachineStateType type); +void RegexMachineStateAccept_Init(RegexMachineStateAccept* accept); +void RegexMachineStateOption_Init(RegexMachineStateOption* option); +void RegexMachineStateGroup_Init(RegexMachineStateGroup* group, char* name, size_t number); +void RegexMachineStateRepeat_Init(RegexMachineStateRepeat* repeat, size_t min, size_t max, bool greedy, bool possessive, RegexMachineStateBase* repeatable); + +#endif diff --git a/src/regex/machine_state_struct.h b/src/regex/machine_state_struct.h new file mode 100644 index 0000000..fc0234e --- /dev/null +++ b/src/regex/machine_state_struct.h @@ -0,0 +1,66 @@ +#ifndef UTILITIEC_REGEX_MACHINE_STATESTRUCT_H +#define UTILITIEC_REGEX_MACHINE_STATESTRUCT_H + +#include "../StringView/StringView.h" + +enum RegexMachineStateType { + // Accept out of a Character class + REGEXMACHINESTATETYPE_ACCEPTCLASS, + // Accept a character (sequence) + REGEXMACHINESTATETYPE_ACCEPT, + // Capture group + REGEXMACHINESTATETYPE_GROUP, + // Accept one of the following + REGEXMACHINESTATETYPE_OPTION, + // Repeat the contained node + REGEXMACHINESTATETYPE_REPEAT, +}; + +typedef struct RegexMachineStateBase_s { + // Type of this node + enum RegexMachineStateType type; + + // Next regex node, if this one matched + struct RegexMachineStateBase_s* next; +} RegexMachineStateBase; + +typedef struct RegexMachineStateOption_s { + RegexMachineStateBase base; + + // next option + struct RegexMachineStateOption_s* next_option; +} RegexMachineStateOption; + +typedef struct RegexMachineStateAccept_s { + RegexMachineStateBase base; + + // allocated in the scratchpad buffer + StringView sequence; +} RegexMachineStateAccept; + +typedef struct RegexMachineStateGroup_s { + RegexMachineStateBase base; + + // zero=root group + // SIZE_MAX=noncapturing group + size_t number; + + // NULL=unnamed + char* name; +} RegexMachineStateGroup; + +typedef struct RegexMachineStateRepeat_s { + RegexMachineStateBase base; + + size_t min; + size_t max; + + bool greedy; + bool possessive; + + RegexMachineStateBase* repeatable; +} RegexMachineStateRepeat; + + + +#endif diff --git a/src/regex/match.c b/src/regex/match.c new file mode 100644 index 0000000..18622df --- /dev/null +++ b/src/regex/match.c @@ -0,0 +1,624 @@ +#include "match.h" +#include "machine_state.h" +#include "match_struct.h" +#include "regex_struct.h" +#include +#include +#include + +typedef struct RegexMatcher_s { + RegexMatchThreadGroup top_group; + + RegexMatchThread finished_threads; +} RegexMatcher; + +static int RegexMatchThread_New(RegexMatchThread* thread, StringView string, size_t index, Regex* regex) +{ + thread->string = string; + thread->left = StringView_Slice(string, 0, index); + thread->right = StringView_Slice(string, index, string.length); + thread->regex = regex; + + thread->machine_head = regex->head; + + thread->match.match = StringView_Slice(string, index, index); + int da_code = DynamicArray_Create( + &thread->match.captures, + sizeof(RegexCapture), + 8, + regex->machine_memory.allocator + ); + if (da_code != EXIT_SUCCESS) return da_code; + + da_code = DynamicArray_Create( + &thread->repeats, + sizeof(RegexRepeatStackEntry), + 8, + regex->machine_memory.allocator + ); + if (da_code != EXIT_SUCCESS) return da_code; + + return EXIT_SUCCESS; +} + +static int _RegexMatch_Clone(RegexCapture* new, RegexCapture* orig, allocator_t* allocator) +{ + char* new_name = Allocator_Allocate(allocator, strlen(orig->name) + 1); + if (new_name == NULL) { + return ENOMEM; + } + + new->name = new_name; + + strcpy(new->name, orig->name); + + return EXIT_SUCCESS; +} + +static int RegexMatchThread_Clone(RegexMatchThread* new, RegexMatchThread* old) +{ + *new = *old; + + int clone_code; + if (new->regex->options & REGEX_MATCH_STRDUP) { + // strdup the capture names + clone_code = DynamicArray_DeepClone( + &new->match.captures, + &old->match.captures, + (DynamicArrayCloneFunction) _RegexMatch_Clone, + old->match.captures.allocator + ); + } else { + clone_code = DynamicArray_Clone(&new->match.captures, &old->match.captures); + } + if (clone_code == ECANCELED) + clone_code = ENOMEM; + if (clone_code != EXIT_SUCCESS) { + return clone_code; + } + + // Array was initialized + if (old->repeats.capacity != 0) { + DynamicArray_Clone(&new->repeats, &old->repeats); + } + + return EXIT_SUCCESS; +} + +static void RegexMatchThread_Del(RegexMatchThread* thread) +{ + if (thread->regex->options & REGEX_MATCH_STRDUP) { + DYNAMICARRAY_FOREACH(thread->match.captures, i) { + RegexCapture* capture = DynamicArray_GetPointer(&thread->match.captures, i); + Allocator_Free( + thread->match.captures.allocator, + capture->name, + strlen(capture->name) + 1 + ); + } + } + DynamicArray_Destroy(&thread->match.captures); + DynamicArray_Destroy(&thread->repeats); + memset(thread, 0, sizeof(*thread)); +} + +static int _HandleAccept(RegexMatchThread* thread, bool* discard) +{ + RegexMachineStateAccept* accept = (RegexMachineStateAccept*) thread->machine_head; + + // Matching + if (StringView_StartsWith(thread->right, accept->sequence)) { + thread->right = StringView_Slice( + thread->right, + accept->sequence.length, + thread->right.length + ); + thread->left.length += accept->sequence.length; + thread->match.match.length += accept->sequence.length; + } else { + *discard = true; + } + + return EXIT_SUCCESS; +} + +static int _HandleGroup(RegexMatchThread* thread) +{ + RegexMachineStateGroup* group = (RegexMachineStateGroup*) thread->machine_head; + + if (RegexMatch_HaveNumberedCapture(&thread->match, group->number)) { + // End of group, store match + RegexCapture* capture = RegexMatch_GetNumberedCapture(&thread->match, group->number); + + if (StringView_Equal(capture->match, STRINGVIEW_NONE)) { + // Match is finished after being reset or started + capture->match_end = thread->left.length; + capture->match = StringView_Slice(thread->left, capture->match_start, capture->match_end); + + } else { + // Recapture start: e.g. "(a)+" on string "aa", reset match + capture->match_start = thread->left.length; + capture->match = STRINGVIEW_NONE; + } + + } else { + // Start of group, start match + RegexCapture* capture = RegexMatch_MakeCapture(thread, group->name, group->number); + if (capture == NULL) { + return ENOMEM; + } + + capture->match_start = thread->left.length; + capture->match = STRINGVIEW_NONE; + } + + return EXIT_SUCCESS; +} + +static int _HandleRepeat(RegexMatchThread* thread) +{ + RegexMachineStateRepeat* repeat = (RegexMachineStateRepeat*) thread->machine_head; + RegexRepeatStackEntry* top_entry = DynamicArray_GetPointer(&thread->repeats, thread->repeats.reserved - 1); + + if (top_entry->node == repeat) { + // At least first iteration, maybe even repetitions + } else { + // Before first iteration, not matched once + + DynamicArray_AppendEmpty(&thread->repeats, (void**) &top_entry); + if (top_entry == NULL) { + return ENOMEM; + } + + top_entry->node = repeat; + top_entry->current = 0; + + // possibly create more threads + } + + return EXIT_SUCCESS; +} + +union ThreadOrGroup { + RegexMatchThread* thread; + RegexMatchThreadGroup* group; +}; + +struct GroupVisitor { + // Cast these into the types + enum RegexMatchThreadType type; + unsigned int position; + union ThreadOrGroup as; +}; + +static int RegexMatchThreadGroup_Create(RegexMatchThreadGroup* group, size_t number, allocator_t* allocator) +{ + memset(group, 0, sizeof(*group)); + group->number = number; + group->threads.allocator = allocator; + return EXIT_SUCCESS; +} + +static int _FindThreadLinear(RegexMatchThread* other, RegexMatchThread* find) +{ + if (other->number < find->number) { + return 1; + } else if (other->number > find->number) { + return -1; + } else { + return 0; + } +} + +static RegexMatchThread* _RegexMatchThreadGroup_NewThread(RegexMatchThreadGroup* group) +{ + if (DynamicArray_GetLength(&group->threads) == 0) { + int create_code = DynamicArray_Create( + &group->threads, + sizeof(RegexMatchThread), + 4, + group->threads.allocator + ); + if (create_code) return NULL; + } + + RegexMatchThread* pointer; + DynamicArray_AppendEmpty(&group->threads, (void**) &pointer); + + return pointer; +} + +static RegexMatchThreadGroup* RegexMatchThreadGroup_NewGroup(RegexMatchThreadGroup* parent_group) +{ + if (DynamicArray_GetLength(&parent_group->subgroups) == 0) { + int create_code = DynamicArray_Create( + &parent_group->subgroups, + sizeof(RegexMatchThreadGroup), + 4, + parent_group->threads.allocator + ); + if (create_code) return NULL; + } + + RegexMatchThreadGroup* pointer; + DynamicArray_AppendEmpty(&parent_group->subgroups, (void**) &pointer); + + return pointer; +} + + +static void _RegexMatchThreadGroup_ForgetThread(RegexMatchThreadGroup* group, RegexMatchThread* thread) +{ + size_t thread_index = DynamicArray_FindFunctionLinear( + &group->threads, + (DynamicArrayLinearFindFunction) _FindThreadLinear, + thread + ); + + DynamicArray_Remove(&group->threads, thread_index); + + return; +} + +static void RegexMatchThreadGroup_Destroy2(RegexMatchThreadGroup* group) +{ + DynamicArray_Destroy(&group->threads); + DynamicArray_Destroy(&group->subgroups); + return; +} + +static void RegexMatchThreadGroup_Destroy(DynamicArray* visitors) +{ + + struct GroupVisitor* current_visitor; + current_visitor = DynamicArray_GetPointer( + visitors, + visitors->reserved - 1 + ); + RegexMatchThreadGroup* group = (void*) current_visitor->type; + + if (DynamicArray_GetLength(&group->threads) != 0) { + // Append first sub thread to visitor stack + + struct GroupVisitor* new_visitor; + DynamicArray_AppendEmpty(visitors, (void**) &new_visitor); + RegexMatchThread* thread = DynamicArray_GetPointer( + &group->threads, + 0 + ); + new_visitor->type = REGEXMATCHTHREADTYPE_THREAD; + new_visitor->as.thread = thread; + + DynamicArray_RemoveFast(&group->threads, 0); + } else if (DynamicArray_GetLength(&group->subgroups) != 0) { + // Append first sub group to visitor stack + + struct GroupVisitor* new_visitor; + DynamicArray_AppendEmpty(visitors, (void**) &new_visitor); + RegexMatchThreadGroup* subgroup = DynamicArray_GetPointer( + &group->subgroups, + 0 + ); + new_visitor->type = REGEXMATCHTHREADTYPE_GROUP; + new_visitor->as.group = subgroup; + + DynamicArray_RemoveFast(&group->subgroups, 0); + + } else { + // Actually destroy the group, also pop the visitor + RegexMatchThreadGroup_Destroy2(group); + DynamicArray_RemoveFast(visitors, visitors->reserved - 1); + } + return; +} + +static int _NewRegexChild(RegexMatchThread** parent, DynamicArray* visitors, size_t* depth, RegexMachineStateBase* new_head) +{ + RegexMatchThread* child; + struct GroupVisitor* parent_visitor = DynamicArray_GetPointer( + visitors, + visitors->reserved - 2 + ); + if (parent_visitor->type != REGEXMATCHTHREADTYPE_GROUP) { + return EXIT_FAILURE; + } + + RegexMatchThreadGroup* parent_group = parent_visitor->as.group; + if (parent_group->number == (*parent)->number) { + // group is owned by parent + DynamicArray_AppendEmpty( + &parent_group->threads, + (void**)&child + ); + } else { + // New subgroup, move parent thread + + // Enlarge visitor array to provide enough space for visiting + if (visitors->reserved - 1 == *depth) { + *depth += 1; + if (visitors->capacity == *depth) { + int resize = DynamicArray_Resize( + visitors, + visitors->capacity * 2 + ); + if (resize) return resize; + } + } + + RegexMatchThreadGroup* subgroup; + subgroup = RegexMatchThreadGroup_NewGroup(parent_group); + + if (subgroup == NULL) { + return ENOMEM; + } + + // make a new group + RegexMatchThreadGroup_Create( + subgroup, + (*parent)->number, + (*parent)->regex->machine_memory.allocator + ); + + // acquire new thread memory + RegexMatchThread* nparent = _RegexMatchThreadGroup_NewThread( + subgroup + ); + child = _RegexMatchThreadGroup_NewThread(subgroup); + + // memowy + if (nparent == NULL || child == NULL) { + RegexMatchThreadGroup_Destroy2(subgroup); + DynamicArray_RemoveFast( + &parent_group->subgroups, + parent_group->subgroups.reserved - 1 + ); + return ENOMEM; + } + + // copy contents + *nparent = **parent; + + // update reference + *parent = nparent; + parent_group = subgroup; + + // remove from old group + _RegexMatchThreadGroup_ForgetThread(parent_group, *parent); + } + + if (child == NULL) { + return ENOMEM; + } + + int clone_code = RegexMatchThread_Clone(child, *parent); + if (clone_code) { + return clone_code; + } + + child->machine_head = new_head; + child->number = parent->number + DynamicArray_GetLength(&parent_group.threads) - 1; + + return EXIT_SUCCESS; +} + +static int _HandleOption(DynamicArray* visitors, size_t* depth) +{ + struct GroupVisitor* visitor = DynamicArray_GetPointer( + visitors, + visitors->reserved - 1 + ); + RegexMatchThread* thread = (void*) visitor->type; + + RegexMachineStateBase* first_head = thread->machine_head; + { + RegexMachineStateOption* opt = (void*) first_head; + thread->machine_head = &opt->next_option->base; + } + + while (thread->machine_head->type == REGEXMACHINESTATETYPE_OPTION) { + RegexMachineStateBase* option_head = thread->machine_head->next; + + int child_code = _NewRegexChild( + &thread, + visitors, + depth, + option_head->next + ); + if (child_code) { + return child_code; + } + } + + // will be auto-advanced outside of this function + thread->machine_head = first_head; + + return EXIT_SUCCESS; +} + +static int _AdvanceThread(DynamicArray* visitors, size_t* depth) +{ + struct GroupVisitor* visitor = DynamicArray_GetPointer( + visitors, + visitors->reserved - 1 + ); + RegexMatchThread* thread = (void*) visitor->type; + int code; + bool discard; + + switch (thread->machine_head->type) { + case REGEXMACHINESTATETYPE_ACCEPT: + code = _HandleAccept(thread, &discard); + break; + case REGEXMACHINESTATETYPE_GROUP: + code = _HandleGroup(thread); + break; + case REGEXMACHINESTATETYPE_OPTION: + code = _HandleOption(visitors, depth); + break; + case REGEXMACHINESTATETYPE_REPEAT: + code = _HandleRepeat(thread); + break; + default: + return EXIT_FAILURE; + } + + if (code == EXIT_SUCCESS) { + thread->machine_head = thread->machine_head->next; + } + + if (thread->machine_head == NULL) { + // Match done + + } + + return EXIT_SUCCESS; +} + +static void _HandleDestroyVisitor(DynamicArray* visitors, struct GroupVisitor* current_visitor) +{ + switch (current_visitor->type) { + case REGEXMATCHTHREADTYPE_GROUP: + RegexMatchThreadGroup_Destroy(visitors); + break; + case REGEXMATCHTHREADTYPE_THREAD: + RegexMatchThread_Del(current_visitor->as.thread); + break; + } +} + +static void _DestroyThreadHierarchy(DynamicArray* visitors, RegexMatchThreadGroup* group) +{ + visitors->reserved = 0; + { + struct GroupVisitor* zero_visitor; + DynamicArray_AppendEmpty(visitors, (void**) &zero_visitor); + zero_visitor->type = REGEXMATCHTHREADTYPE_GROUP; + zero_visitor->as.group = group; + } + + while (DynamicArray_GetLength(visitors) != 0) { + struct GroupVisitor* current_visitor; + current_visitor = DynamicArray_GetPointer( + visitors, + visitors->reserved - 1 + ); + + _HandleDestroyVisitor(visitors, current_visitor); + } + + return; +} + +static int _HandleAdvanceVisitor(DynamicArray* visitor_stack, size_t* depth) +{ + struct GroupVisitor* current_visitor = DynamicArray_GetPointer( + visitor_stack, + visitor_stack->reserved - 1 + ); + + switch (current_visitor->type) { + case REGEXMATCHTHREADTYPE_THREAD: + _AdvanceThread( + visitor_stack, + depth + ); + break; + case REGEXMATCHTHREADTYPE_GROUP: + break; + } + + return EXIT_SUCCESS; +} + +static int _WalkThreads(DynamicArray* visitor_stack, RegexMatchThreadGroup* group, size_t* depth) +{ + { + + struct GroupVisitor* zero_visitor; + DynamicArray_AppendEmpty( + visitor_stack, + (void**) &zero_visitor + ); + zero_visitor->type = REGEXMATCHTHREADTYPE_GROUP; + zero_visitor->as.group = group; + + } + + while (DynamicArray_GetLength(visitor_stack) != 0) { + int advance_code = _HandleAdvanceVisitor( + visitor_stack, + depth + ); + if (advance_code) return advance_code; + } + return EXIT_SUCCESS; +} + +int Regex_MatchHere(Regex* regex, StringView string, size_t start, RegexMatch* match) +{ + int return_code = EXIT_SUCCESS; + + // initialize variables etc.... + DynamicArray visitor_stack; + if (DynamicArray_Create( + &visitor_stack, + sizeof(struct GroupVisitor), + 16, + regex->machine_memory.allocator) + ) { + return ENOMEM; + } + + RegexMatchThreadGroup top_level_group; + RegexMatchThreadGroup_Create(&top_level_group, 0, regex->machine_memory.allocator); + size_t depth = 1; + + { + RegexMatchThread* first_thread; + first_thread = RegexMatchThreadGroup_NewThread(&top_level_group); + first_head->number = 0; + } + + bool haveFinishedThread = false; + while (! haveFinishedThread) { + _WalkThreads(&visitor_stack, &top_level_group, &depth); + } + +defer_tl_group: + _DestroyThreadHierarchy(&visitor_stack, &top_level_group); +defer_stack: + DynamicArray_Destroy(&visitor_stack); + + return return_code; +} + +RegexCapture* RegexMatch_MakeCapture(RegexMatchThread* thread, char* name, size_t number) +{ + RegexMatch* match = &thread->match; + RegexCapture* capture; + DynamicArray_AppendEmpty(&match->captures, (void**) &capture); + if (capture == NULL) { + return NULL; + } + + memset(capture, 0, sizeof(*capture)); + capture->number = number; + if (name == NULL) { + capture->name = NULL; + } else { + if (thread->regex->options & REGEX_MATCH_STRDUP) { + char* name_copy = Allocator_Allocate(match->captures.allocator, strlen(name) + 1); + if (name_copy == NULL) { + DynamicArray_RemoveFast(&match->captures, match->captures.reserved - 1); + return NULL; + } + strcpy(name_copy, name); + capture->name = name_copy; + } else { + // Unowned reference + capture->name = name; + } + } + + return capture; +} diff --git a/src/regex/match.h b/src/regex/match.h new file mode 100644 index 0000000..05278de --- /dev/null +++ b/src/regex/match.h @@ -0,0 +1,62 @@ +#ifndef UTILITIEC_REGEX_MATCH_H +#define UTILITIEC_REGEX_MATCH_H + +#include "machine_state.h" +#include "regex_struct.h" +#include "match_struct.h" + +enum RegexMatchThreadType { + REGEXMATCHTHREADTYPE_THREAD, + REGEXMATCHTHREADTYPE_GROUP, +}; + +typedef struct RegexMatchThreadGroup_s { + enum RegexMatchThreadType type; + + // thread-group number + // possibly matches with one of the threads' numbers + size_t number; + + DynamicArray threads; + DynamicArray subgroups; +} RegexMatchThreadGroup; + +typedef struct RegexMatchThread_s { + enum RegexMatchThreadType type; + + // Identifier for the thread + // if it matches the threadgroups, the thread is it's leader + size_t number; + + // Full test string + StringView string; + + // left/right from the text cursor, not necessarily part of the match + StringView right; + StringView left; + + // head node next up for evaluation + RegexMachineStateBase* machine_head; + + // stack of repeat-node states + DynamicArray repeats; + + // Immutable Reference to regex, for options + const Regex* regex; + + // Match state until now + RegexMatch match; +} RegexMatchThread; + +typedef struct RegexRepeatStackEntry_s { + // node this stack entry is for + RegexMachineStateRepeat* node; + // the current iteration + size_t current; +} RegexRepeatStackEntry; + + +int Regex_MatchHere(Regex* regex, StringView string, size_t start, RegexMatch* match); +RegexCapture* RegexMatch_MakeCapture(RegexMatchThread* thread, char* name, size_t number); + +#endif diff --git a/src/regex/match_struct.c b/src/regex/match_struct.c new file mode 100644 index 0000000..3d3024f --- /dev/null +++ b/src/regex/match_struct.c @@ -0,0 +1,45 @@ +#include "match_struct.h" + +static int _LinearFindFunction(RegexCapture* capture, size_t* find) +{ + if (capture->number == *find) { + // Equal + return 0; + } else if (capture->number < *find) { + // Look to the right + return 1; + } else { + // Look to the left + return -1; + } +} + +bool RegexMatch_HaveNumberedCapture(RegexMatch* match, size_t number) +{ + if (match == NULL) { + return false; + } + + size_t capture_index = DynamicArray_FindFunctionLinear( + &match->captures, + (DynamicArrayLinearFindFunction) _LinearFindFunction, + &number + ); + + return capture_index != SIZE_MAX; +} + +RegexCapture* RegexMatch_GetNumberedCapture(RegexMatch* match, size_t number) +{ + if (match == NULL) { + return false; + } + + size_t capture_index = DynamicArray_FindFunctionLinear( + &match->captures, + (DynamicArrayLinearFindFunction) _LinearFindFunction, + &number + ); + + return DynamicArray_GetPointer(&match->captures, capture_index); +} diff --git a/src/regex/match_struct.h b/src/regex/match_struct.h new file mode 100644 index 0000000..de03ebb --- /dev/null +++ b/src/regex/match_struct.h @@ -0,0 +1,29 @@ +#ifndef UTILITIEC_REGEX_MATCHSTRUCT_H +#define UTILITIEC_REGEX_MATCHSTRUCT_H + +#include "../StringView/StringView.h" +#include "../dynamicarray/dynamicarray.h" + +typedef struct RegexCapture_s { + // potentially unowned string, depending on Regex Options + char* name; + size_t number; + + // Reference to string passed to match() + StringView match; + size_t match_start; + size_t match_end; +} RegexCapture; + +typedef struct RegexMatch_s { + // entire match + StringView match; + + // numbered and named captures intermixed, ordered + DynamicArray captures; +} RegexMatch; + +bool RegexMatch_HaveNumberedCapture(RegexMatch* match, size_t number); +RegexCapture* RegexMatch_GetNumberedCapture(RegexMatch* match, size_t number); + +#endif diff --git a/src/regex/regex.c b/src/regex/regex.c new file mode 100644 index 0000000..e620db8 --- /dev/null +++ b/src/regex/regex.c @@ -0,0 +1,61 @@ +#include "regex.h" +#include "compile.h" +#include "match.h" +#include +#include + +#define REGEX_SCRATCHPAD_DEFAULT 4096 + +int Regex_Create(Regex* target, allocator_t* allocator) +{ + if (target == NULL) return EDESTADDRREQ; + + int scratchpad_code = Scratchpad_Create( + &target->machine_memory, + REGEX_SCRATCHPAD_DEFAULT, + allocator + ); + if (scratchpad_code != EXIT_SUCCESS) { + return ENOMEM; + } + + target->options = 0; + + return EXIT_SUCCESS; +} + +void Regex_Destroy(Regex* regex) +{ + Scratchpad_Destroy(®ex->machine_memory); + + return; +} + +static void _Regex_Reset(Regex* regex) +{ + Scratchpad_Reset(®ex->machine_memory); + regex->head = NULL; + + return; +} + +int Regex_Compile(Regex* regex, StringView source) +{ + if (regex == NULL) return EDESTADDRREQ; + if (source.source == NULL) return EINVAL; + + _Regex_Reset(regex); + Internal_Regex_Compile(regex, source); + + return EXIT_SUCCESS; +} + +int Regex_FirstMatch(Regex* regex, StringView string, RegexMatch* match) +{ + if (regex == NULL) return EINVAL; + if (match == NULL) return EDESTADDRREQ; + + int return_code = Regex_MatchHere(regex, string, 0, match); + + return return_code; +} diff --git a/src/regex/regex.h b/src/regex/regex.h new file mode 100644 index 0000000..469df8d --- /dev/null +++ b/src/regex/regex.h @@ -0,0 +1,32 @@ +#ifndef UTILITIEC_REGEX_H +#define UTILITIEC_REGEX_H + +#include "match_struct.h" +#include "regex_struct.h" + +#include "../StringView/StringView.h" + +int Regex_Create(Regex* target, allocator_t* allocator); +void Regex_Destroy(Regex* regex); + +int Regex_Compile(Regex* regex, StringView source); + +/*! + @brief Find the first match + @param regex this + @param string string + @param match out, storage for the match. Caller is responsible for deletion + @return EXIT_SUCCESS, EXIT_FAILURE, EDESTADDRREQ, ENOMEM +*/ +int Regex_FirstMatch(Regex* regex, StringView string, RegexMatch* match); + +/*! + @brief Find All matches of [regex] within [string] and place them in [matches] + @param regex this + @param string string + @param matches out, storage, caller is responsible for deletion + @return EXIT_SUCCESS, EXIT_FAILURE, EDESTADDRREQ +*/ +int Regex_FindAll(Regex* regex, StringView string, DynamicArray* matches); + +#endif diff --git a/src/regex/regex_struct.h b/src/regex/regex_struct.h new file mode 100644 index 0000000..413076b --- /dev/null +++ b/src/regex/regex_struct.h @@ -0,0 +1,19 @@ +#ifndef UTILITIEC_REGEX_STRUCT_H +#define UTILITIEC_REGEX_STRUCT_H + +#include "../Scratchpad/Scratchpad.h" +#include "../dynamicarray/dynamicarray.h" + +#include "machine_state.h" + +#define REGEX_MATCH_STRDUP (1) + +typedef struct Regex_s { + RegexMachineStateBase* head; + + Scratchpad machine_memory; + + uint64_t options; +} Regex; + +#endif diff --git a/src/siphash/CMakeLists.txt b/src/siphash/CMakeLists.txt new file mode 100644 index 0000000..f14b228 --- /dev/null +++ b/src/siphash/CMakeLists.txt @@ -0,0 +1 @@ +add_library(siphash STATIC siphash.c) diff --git a/src/siphash/siphash.c b/src/siphash/siphash.c new file mode 100644 index 0000000..56d8191 --- /dev/null +++ b/src/siphash/siphash.c @@ -0,0 +1,128 @@ +/* + * This code is part of the strategy game operational-space. + * operational-space comes with ABSOLUTELY NO WARRANTY and is licensed under GPL-2.0. + * Copyright (C) 2024 VegOwOtenks, Sleppo04 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#include "siphash.h" + +struct SipHashConfig SipHash_DefaultConfig(void) +{ + struct SipHashConfig config = { + .key = {0, 0}, + .c = 2, + .d = 4, + }; + + return config; +} + +#ifndef ROTATE64 +#define ROTATE64(b, n) ((b) << n | (b) >> (64 - n)) +#endif + +void _siphash_initialize(bit128_t* key, uint64_t* words) +{ + words[0] = key->first ^ 0x736f6d6570736575; + words[1] = key->second ^ 0x646f72616e646f6d; + words[2] = key->first ^ 0x6c7967656e657261; + words[3] = key->second ^ 0x7465646279746573; + /* I don't like it either, just look at https://cr.yp.to/siphash/siphash-20120727.pdf */ +} + +void _siphash_round(uint64_t* words) +{ + + words[0] += words[1]; words[2] += words[3]; + words[1] = ROTATE64(words[1], 13); words[3] = ROTATE64(words[3], 16); + words[1] ^= words[0]; words[3] ^= words[2]; + words[0] = ROTATE64(words[0], 32); + words[2] += words[1]; words[0] += words[3]; + words[1] = ROTATE64(words[1], 17); words[3] = ROTATE64(words[3], 21); + words[1] ^= words[2]; words[3] ^= words[0]; + words[2] = ROTATE64(words[2], 32); +} + +uint64_t _siphash_finalize(uint64_t* words, int d) +{ + uint64_t hashValue; + + words[2] ^= 0xff; + while (d) { + _siphash_round(words); + d--; + } + + // Das muss so. Echt jetzt. + hashValue = words[0] ^ words[1] ^ words[2] ^ words[3]; + return hashValue; +} + +void _perform_rounds(int times, uint64_t* words) +{ + for (int k = 0; k < times; k++) { + // Perform round on this word + _siphash_round(words); + } +} + +void _siphash_compress(int c, const char* value, unsigned int valueLength, uint64_t* words) +{ + // c is a parameter for the amount of siphash_rounds to be done + uint64_t* byteString = (uint64_t*) value; + int wordcount = (valueLength + 1 ) / 8; + uint64_t lastWord = 0; + uint64_t current; + + for (int i = 0; i < wordcount - 1; i++) { + // Minus one because the last word is somewhat special + current = *byteString; + words[3] ^= current; + + _perform_rounds(c, words); + + // Append last special byte + words[0] ^= current; + byteString += 1; // Move pointer to next 64 bits + } + + // FF 00 AB CF 00 00 00 (valueLength % 256) + int remainingBytes = valueLength % 8; + memcpy(&lastWord, byteString, remainingBytes); + uint64_t compareValue = valueLength % 256; + compareValue <<= 56; // WTF is this thing? Don't touch, it works!!elf1! + lastWord |= compareValue; //(unsigned char) (valueLength % 256); + + words[3] ^= lastWord; + _perform_rounds(c, words); + words[0] ^= lastWord; + +} + +uint64_t siphash(int c, int d, const char* key, unsigned int keyLength, bit128_t* hashkey) +{ + /* C and D are parameters of siphash */ + uint64_t words[] = {0, 0, 0, 0}; + _siphash_initialize(hashkey, words); + _siphash_compress(c, key, keyLength, words); + uint64_t hashValue = _siphash_finalize(words, d); + return hashValue; +} + +uint64_t siphash2(const char* key, unsigned int keyLength, struct SipHashConfig* config) +{ + return siphash(config->c, config->d, key, keyLength, &config->key); +} diff --git a/src/siphash/siphash.h b/src/siphash/siphash.h new file mode 100644 index 0000000..bcb507f --- /dev/null +++ b/src/siphash/siphash.h @@ -0,0 +1,43 @@ +/* + * This code is part of the strategy game operational-space. + * operational-space comes with ABSOLUTELY NO WARRANTY and is licensed under GPL-2.0. + * Copyright (C) 2024 VegOwOtenks, Sleppo04 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#ifndef SIPHASH_H +#define SIPHASH_H + +#include +#include + + +typedef struct Bit128 { + uint64_t first; + uint64_t second; +} bit128_t; + +typedef struct SipHashConfig { + bit128_t key; + int c; + int d; +} siphashconfig_t; + +struct SipHashConfig SipHash_DefaultConfig(void); + +uint64_t siphash(int c, int d, const char* key, unsigned int keyLength, bit128_t* hashkey); +uint64_t siphash2(const char* key, unsigned int keyLength, struct SipHashConfig* config); + +#endif /*SIPHASH_H*/ diff --git a/src/threading/CMakeLists.txt b/src/threading/CMakeLists.txt new file mode 100644 index 0000000..704b161 --- /dev/null +++ b/src/threading/CMakeLists.txt @@ -0,0 +1,4 @@ +add_library(threading STATIC os_mutex.c +os_rwlock.c +os_semaphore.c +os_thread.c) diff --git a/src/threading/os_mutex.c b/src/threading/os_mutex.c new file mode 100644 index 0000000..3a676d7 --- /dev/null +++ b/src/threading/os_mutex.c @@ -0,0 +1,61 @@ +#include "os_mutex.h" + + +#if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__)) // Fuck you, apple + +int OSMutex_Create(os_mutex_t* target) +{ + return pthread_mutex_init(target, NULL); +} + +int OSMutex_Acquire(os_mutex_t* mutex) +{ + return pthread_mutex_lock(mutex); +} + +int OSMutex_Release(os_mutex_t* mutex) +{ + return pthread_mutex_unlock(mutex); +} + +int OSMutex_Destroy(os_mutex_t* mutex) +{ + return pthread_mutex_destroy(mutex); +} + +#elif defined(_WIN32) + +int OSMutex_Create(os_mutex_t* target) +{ + target[0] = CreateMutex( + NULL, // default security attributes + FALSE, // initially not owned + NULL); // unnamed mutex + if (target[0] == NULL) { + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} + +int OSMutex_Acquire(os_mutex_t* mutex) +{ + WaitForSingleObject( + *mutex, // handle to mutex + INFINITE); // no time-out interval + + return EXIT_SUCCESS; +} + +int OSMutex_Release(os_mutex_t* mutex) +{ + return ReleaseMutex(mutex); +} + +int OSMutex_Destroy(os_mutex_t* mutex) +{ + CloseHandle(mutex[0]); + return EXIT_SUCCESS; +} + +#endif diff --git a/src/threading/os_mutex.h b/src/threading/os_mutex.h new file mode 100644 index 0000000..a2061e8 --- /dev/null +++ b/src/threading/os_mutex.h @@ -0,0 +1,23 @@ +#ifndef OS_MUTEX_H +#define OS_MUTEX_H + +#if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__)) // Fuck you, apple + +#include +typedef pthread_mutex_t os_mutex_t; + +#elif defined(_WIN32) + +#include +typedef HANDLE os_mutex_t; + +#else +#error "Unknown Threading Standard, feel free to add support via a fork/PR" +#endif + +int OSMutex_Create(os_mutex_t* target); +int OSMutex_Acquire(os_mutex_t* mutex); +int OSMutex_Release(os_mutex_t* mutex); +int OSMutex_Destroy(os_mutex_t* mutex); + +#endif diff --git a/src/threading/os_rwlock.c b/src/threading/os_rwlock.c new file mode 100644 index 0000000..b26fab2 --- /dev/null +++ b/src/threading/os_rwlock.c @@ -0,0 +1,90 @@ +#include "os_rwlock.h" +#include + +#if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__)) // Fuck you, apple + +int OSRWLock_Create(OSRWLock* target) +{ + return pthread_rwlock_init(target, NULL); +} + +void OSRWLock_Destroy(OSRWLock* rwlock) +{ + pthread_rwlock_destroy(rwlock); +} + +int OSRWLock_Read(OSRWLock* rwlock) +{ + return pthread_rwlock_rdlock(rwlock); +} + +int OSRWLock_Write(OSRWLock* rwlock) +{ + return pthread_rwlock_wrlock(rwlock); +} + +int OSRWLock_ReleaseRead(OSRWLock* rwlock) +{ + return pthread_rwlock_unlock(rwlock); +} + +int OSRWLock_ReleaseWrite(OSRWLock* rwlock) +{ + return pthread_rwlock_unlock(rwlock); +} + +#elif defined(_WIN32) + +int OSRWLock_Create(OSRWLock* target) +{ + InitializeSRWLock(*target); + + return EXIT_SUCCESS; +} + +void OSRWLock_Destroy(OSRWLock* rwlock) +{ + // Win32 Locks can be forgotten if there are no more threads waiting on it + OSRWLock_Write(*rwlock); + // Now, nobody else can be holding the lock + OSRWLock_ReleaseWrite(*rwlock); + + // Forget about it + return; +} + +int OSRWLock_Read(OSRWLock* rwlock) +{ + AcquireSRWLockShared(*rwlock); + + return EXIT_SUCCESS; +} + +int OSRWLock_Write(OSRWLock* rwlock) +{ + AcquireSRWLockExclusive(*rwlock); + + return EXIT_SUCCESS; +} + + +int OSRWLock_ReleaseRead(OSRWLock* rwlock) +{ + ReleaseSRWLockShared(*rwlock); + + return EXIT_SUCCESS; +} + +int OSRWLock_ReleaseWrite(OSRWLock* rwlock) +{ + ReleaseSRWLockExclusive(*rwlock); + + return EXIT_SUCCESS; +} + + +#else + +#error "Threading standard not supported" + +#endif diff --git a/src/threading/os_rwlock.h b/src/threading/os_rwlock.h new file mode 100644 index 0000000..16845a9 --- /dev/null +++ b/src/threading/os_rwlock.h @@ -0,0 +1,29 @@ +#ifndef UTILITIEC_RWLOCK_H +#define UTILITIEC_RWLOCK_H + +#if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__)) // Fuck you, apple + +#include + +typedef pthread_rwlock_t OSRWLock; + + +#elif defined(_WIN32) +#include + +typedef PSRWLOCK OSRWLock; + +#else +#error "Threading standard not supported" + +#endif + +int OSRWLock_Create(OSRWLock* target); +void OSRWLock_Destroy(OSRWLock* rwlock); + +int OSRWLock_Read(OSRWLock* rwlock); +int OSRWLock_Write(OSRWLock* rwlock); +int OSRWLock_ReleaseRead(OSRWLock* rwlock); +int OSRWLock_ReleaseWrite(OSRWLock* rwlock); + +#endif diff --git a/src/threading/os_semaphore.c b/src/threading/os_semaphore.c new file mode 100644 index 0000000..1606cd6 --- /dev/null +++ b/src/threading/os_semaphore.c @@ -0,0 +1,92 @@ +#include "os_semaphore.h" +#include + +#if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__)) // Fuck you, apple + +int OSSemaphore_Create(os_semaphore_t* target, unsigned int value) +{ + return sem_init( + target, + 0, // multi-threaded, not multiprocess + value + ); +} + +int OSSemaphore_GetValue(os_semaphore_t* semaphore) +{ + int count; + sem_getvalue(semaphore, &count); + return count; +} + +int OSSemaphore_Wait(os_semaphore_t* semaphore) +{ + return sem_wait(semaphore); +} + +int OSSemaphore_Release(os_semaphore_t* semaphore) +{ + return sem_post(semaphore); +} + +void OSSemaphore_Destroy(os_semaphore_t* semaphore) +{ + sem_close(semaphore); + return; +} + +#elif defined(_WIN32) + +int OSSemaphore_Create(os_semaphore_t* target, unsigned int value) +{ + target->sem_handle = CreateSemaphoreA( + SEMAPHORE_ALL_ACCESS, // Access attributes + value, // InitialCount + 32 767, // POSIX_SEM_VALUE_MAX + NULL // Named semaphore + ); + if (target->sem_handle != NULL) { + return ENOMEM; + } + + + target->count = value; + + return EXIT_SUCCESS; +} + +int OSSemaphore_GetValue(os_semaphore_t* semaphore) +{ + return semaphore->count; +} + +int OSSempahore_Wait(os_semaphore_t* semaphore) +{ + DWORD r = WaitForSingleObject( + semaphore->sem_handle, + INIFINITE + ); + semaphore->count -= 1; + return r != WAIT_FAILED; +} + +int OSSempahore_Release(os_semaphore_t* semaphore) +{ + if (ReleaseSemaphore( + semaphore->sem_handle, + 1, + NULL + )) { + semaphore->count++; + return EXIT_SUCCESS; + } else { + return EXIT_FAILURE; + } +} + +void OSSemaphore_Destroy(os_semaphore_t* semaphore) +{ + CloseHandle(semaphore->sem_handle); +} + +#endif diff --git a/src/threading/os_semaphore.h b/src/threading/os_semaphore.h new file mode 100644 index 0000000..b5394f9 --- /dev/null +++ b/src/threading/os_semaphore.h @@ -0,0 +1,59 @@ +#ifndef OS_SEMAPHORE_H +#define OS_SEMAPHORE_H + +#if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__)) // Fuck you, apple + +#include +typedef sem_t os_semaphore_t; + +#elif defined(_WIN32) + +#include +typedef struct Struct_OSSemaphore { + atomic int count; + HANDLE sem_handle; +} os_semaphore_t; + +#else +#error "Unknown Threading Standard, feel free to add support via a fork/PR" +#endif + +/*! + @brief Create a Operating-System-Backed Semaphore + @param target Memory area for the semaphore + @param initialValue Value the semaphore will initially hold + @pre target must be a valid pointer + @return EXIT_SUCCESS, ENOMEM +*/ +int OSSemaphore_Create(os_semaphore_t* target, unsigned int initialValue); + +/*! + @brief Rertieve the value of the semaphore + @param semaphore this + @pre Must be a valid semaphore + @return the value of the semaphore, negative if there are threads waiting +*/ +int OSSemaphore_GetValue(os_semaphore_t* semaphore); + +/*! + @brief Wait for a resource to be available on the semaphore + @param semaphore this + @return EXIT_SUCCESS, EINVAL, EINTR +*/ +int OSSemaphore_Wait(os_semaphore_t* semaphore); + +/*! + @brief Signal the release of a resource on the semaphore + @param semaphore this + @return EINVAL, EOVERFLOW +*/ +int OSSemaphore_Release(os_semaphore_t* semaphore); + +/*! + @brief Destroy the referenced semaphore resource and return it to the OS + @param semaphore this + @return void +*/ +void OSSemaphore_Destroy(os_semaphore_t* semaphore); + +#endif diff --git a/src/threading/os_thread.c b/src/threading/os_thread.c new file mode 100644 index 0000000..1b3a8b7 --- /dev/null +++ b/src/threading/os_thread.c @@ -0,0 +1,89 @@ +#include "os_thread.h" +#include + +#if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__)) + +#include +#include + +int OSThread_Create(os_thread_t* destination, ThreadFunction subroutine, void* arg) +{ + return pthread_create(destination, NULL, subroutine, arg); +} + +int OSThread_Kill(os_thread_t* thread) +{ + return pthread_cancel(*thread); +} + +int OSThread_Join(os_thread_t* thread, void** thread_return) +{ + return pthread_join(*thread, thread_return); +} + +int OSThread_Destroy(os_thread_t* thread) +{ + (void) thread; + return EXIT_SUCCESS; +} + + +#elif defined _WIN32 + +struct CaptureReturnArgument { + ThreadFunction subroutine; + void* arg; + void** return_value_storage; +}; +static void capture_return(struct CaptureReturnArgument* a) +{ + a.return_value_storage[0] = a->subroutine(a->arg); + return; +} + +int OSThread_Create(os_thread_t* destination, ThreadFunction subroutine, void* arg) +{ + destination->return_value_storage = malloc(sizeof(void*)); + if (destination->return_value_storage == NULL) { + return ENOMEM; + } + + destination->return_value_storage[0] = NULL; + destination->handle = CreateThread( + THREAD_ALL_ACCESS, // Security + 0, // default stack size + capture_return, // start address + arg, // argument + 0, // start immediately + NULL // thread id receeiver + ); + if (destination->handle == NULL) { + return GetLastError(); + } + + return EXIT_SUCCESS; +} + +int OSThread_Kill(os_thread_t* thread) +{ + return 1 == TerminateThread( + thread->thread_handle, + 0 // exit code + ); +} + +int OSThread_Join(os_thread_t* thread, void** thread_return) +{ + WaitForSingleObject(thread->thread_handle, INFINITE); + thread_return[0] = thread->return_value_storage[0]; + + return EXIT_SUCCESS; +} + +int OSThread_Destroy(os_thread_t* thread) +{ + free(thread->return_value_storage); + CloseHandle(thread->thread_handle); +} + +#endif diff --git a/src/threading/os_thread.h b/src/threading/os_thread.h new file mode 100644 index 0000000..c8acf66 --- /dev/null +++ b/src/threading/os_thread.h @@ -0,0 +1,28 @@ +#ifndef OS_THREAD_H +#define OS_THREAD_H + +#if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__)) // Fuck you, apple + +#include +typedef pthread_t os_thread_t; + +#elif defined(_WIN32) + +#include +typedef struct Struct_OSThread { + HANDLE thread_handle; + void** return_value_storage; +} os_thread_t; + +#else +#error "Unknown Threading Standard, feel free to add support via a fork/PR" +#endif + +typedef void* (*ThreadFunction) (void* arg); + +int OSThread_Create(os_thread_t* destination, ThreadFunction subroutine, void* arg); +int OSThread_Kill(os_thread_t* thread); +int OSThread_Join(os_thread_t* thread, void** thread_return); +int OSThread_Destroy(os_thread_t* thread); + +#endif diff --git a/src/utf8/CMakeLists.txt b/src/utf8/CMakeLists.txt new file mode 100644 index 0000000..d6639ce --- /dev/null +++ b/src/utf8/CMakeLists.txt @@ -0,0 +1 @@ +add_library(utf8 STATIC utf-8.c) diff --git a/src/utf8/utf-8.c b/src/utf8/utf-8.c new file mode 100644 index 0000000..84235a7 --- /dev/null +++ b/src/utf8/utf-8.c @@ -0,0 +1,60 @@ +/* + * This code is part of the strategy game operational-space. + * operational-space comes with ABSOLUTELY NO WARRANTY and is licensed under GPL-2.0. + * Copyright (C) 2024 VegOwOtenks, Sleppo04 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#include "utf-8.h" + +#include +#include +#include + +const char SINGLE_BYTE_CODEPOINT = 0x00; +const char DOUBLE_BYTE_CODEPOINT = 0x06; +const char TRIPLE_BYTE_CODEPOINT = 0x0E; +const char QUADRUPLE_BYTE_CODEPOINT = 0x1E; + +size_t UTF8_CodepointLength(const char* string) +{ + char character = *string; + character >>= 7; + if (memcmp(&character, &SINGLE_BYTE_CODEPOINT, 1) == 0) { + return 1; + } + + character = (*string) >> 5; + if (memcmp(&character, &DOUBLE_BYTE_CODEPOINT, 1) == 0) { + return 2; + } + + character = (*string) >> 4; + if (memcmp(&character, &TRIPLE_BYTE_CODEPOINT, 1) == 0) { + return 3; + } + + character = (*string) >> 3; + if (memcmp(&character, &QUADRUPLE_BYTE_CODEPOINT, 1) == 0) { + return 4; + } + + return SIZE_MAX; +} + +const char* UTF8_NextCodepoint(const char* string) +{ + return string + UTF8_CodepointLength(string); +} diff --git a/src/utf8/utf-8.h b/src/utf8/utf-8.h new file mode 100644 index 0000000..a78cd98 --- /dev/null +++ b/src/utf8/utf-8.h @@ -0,0 +1,28 @@ +/* + * This code is part of the strategy game operational-space. + * operational-space comes with ABSOLUTELY NO WARRANTY and is licensed under GPL-2.0. + * Copyright (C) 2024 VegOwOtenks, Sleppo04 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#ifndef COMMON_UTF_8_H_ +#define COMMON_UTF_8_H_ + +#include + +size_t UTF8_CodepointLength(const char* string); +const char* UTF8_NextCodepoint(const char* string); + +#endif /* SRC_COMMON_UTF_UTF_8_H_ */ diff --git a/tests/.cproject b/tests/.cproject new file mode 100644 index 0000000..8d198de --- /dev/null +++ b/tests/.cproject @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/.project b/tests/.project new file mode 100644 index 0000000..967927e --- /dev/null +++ b/tests/.project @@ -0,0 +1,20 @@ + + + tests + + + + + + org.eclipse.cdt.core.cBuilder + clean,full,incremental, + + + + + + org.eclipse.cdt.core.cnature + org.eclipse.cdt.core.ccnature + org.eclipse.cdt.cmake.core.cmakeNature + + diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..70dd5f3 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,38 @@ +add_executable(threadpool-test ThreadPool.test.c) +target_link_libraries(threadpool-test + ThreadPool + allocator-interface + FreeList + dynamicarray + threading + pointers) + + +add_executable(argumentc-test argumentc.test.c) +target_link_libraries(argumentc-test + argumentc + dynamicarray + allocator-interface + pointers + StringView) + +add_executable(StringView-test StringView.test.c) +target_link_libraries(StringView-test + StringView) + +add_executable(Scratchpad-test Scratchpad.test.c) +target_link_libraries(Scratchpad-test + Scratchpad + allocator-interface + pointers) + +add_executable(regex-test regex.test.c) +target_link_libraries(regex-test + regex + Scratchpad + dynamicarray + allocator-interface + pointers + StringView + utf8 +) diff --git a/tests/Scratchpad.test.c b/tests/Scratchpad.test.c new file mode 100644 index 0000000..ff4dac2 --- /dev/null +++ b/tests/Scratchpad.test.c @@ -0,0 +1,24 @@ +#include "../src/Scratchpad/Scratchpad.h" + +#include +#include + +void test_reserve(void) +{ + Scratchpad pad; + + assert(Scratchpad_Create(&pad, 16, NULL) == EXIT_SUCCESS); + assert(Scratchpad_Reserve(&pad, 16) != NULL); + assert(Scratchpad_Reserve(&pad, 16) != NULL); + + Scratchpad_Reset(&pad); + Scratchpad_Destroy(&pad); + + return; +} + +int main() +{ + test_reserve(); + return 0; +} diff --git a/tests/StringView.test.c b/tests/StringView.test.c new file mode 100644 index 0000000..3fd93b7 --- /dev/null +++ b/tests/StringView.test.c @@ -0,0 +1,36 @@ +#include "../src/StringView/StringView.h" +#include + +void test_split(void) +{ + const char* source = "this,is,a,csv,header"; + + StringView sourceSV = StringView_FromString(source); + StringView delim = StringView_FromString(","); + StringView split; + + assert(StringView_NextSplit(&split, &sourceSV, delim)); + assert(StringView_Equal(split, StringView_FromString("this"))); + + assert(StringView_NextSplit(&split, &sourceSV, delim)); + assert(StringView_Equal(split, StringView_FromString("is"))); + + assert(StringView_NextSplit(&split, &sourceSV, delim)); + assert(StringView_Equal(split, StringView_FromString("a"))); + + assert(StringView_NextSplit(&split, &sourceSV, delim)); + assert(StringView_Equal(split, StringView_FromString("csv"))); + + assert(StringView_NextSplit(&split, &sourceSV, delim)); + assert(StringView_Equal(split, StringView_FromString("header"))); + + assert(! StringView_NextSplit(&split, &sourceSV, delim)); + + return; +} + +int main() +{ + test_split(); + return 0; +} diff --git a/tests/ThreadPool.test.c b/tests/ThreadPool.test.c new file mode 100644 index 0000000..39cabe5 --- /dev/null +++ b/tests/ThreadPool.test.c @@ -0,0 +1,88 @@ +#include "../src/ThreadPool/ThreadPool.h" +#include +#include +#include + +void testLifetime(void) +{ + allocator_t allocator; + assert(EXIT_SUCCESS == Allocator_CreateSystemAllocator(&allocator)); + ThreadPool pool; + + assert(EXIT_SUCCESS == ThreadPool_Create(&pool, 4, 0x00, &allocator)); + ThreadPool_Destroy(&pool); + + assert(allocator.reserved == 0); + Allocator_DestroySystemAllocator(&allocator); +} + +void* testJobFunction(void* argument) +{ + assert(argument == NULL); + + return NULL; +} + +void* lastJobFunction(os_semaphore_t* semaphore) +{ + OSSemaphore_Release(semaphore); + return NULL; +} + +void testGeneric(size_t n_workers, size_t n_jobs, bool discard_result) +{ + allocator_t allocator; + assert(EXIT_SUCCESS == Allocator_CreateSystemAllocator(&allocator)); + ThreadPool pool; + + assert(EXIT_SUCCESS == ThreadPool_Create(&pool, n_workers, 0x00, &allocator)); + + os_semaphore_t semaphore; + ThreadPoolJob job; + assert(OSSemaphore_Create(&semaphore, 0) == EXIT_SUCCESS); + + + for (size_t i = 0; i < n_jobs - 1; i++) { + job.flags |= discard_result ? THREADPOOLJOB_DISCARD_RESULT : 0x00; + job.arg = NULL; + job.job = testJobFunction; + assert(ThreadPool_QueueJob(&pool, &job, NULL) == EXIT_SUCCESS); + } + + job.arg = &semaphore; + job.job = (ThreadFunction) lastJobFunction; + assert(ThreadPool_QueueJob(&pool, &job, NULL) == EXIT_SUCCESS); + + // Wait until all the jobs are done + OSSemaphore_Wait(&semaphore); + OSSemaphore_Destroy(&semaphore); + + ThreadPool_Destroy(&pool); + + assert(allocator.reserved == 0); + Allocator_DestroySystemAllocator(&allocator); +} + +size_t power2(size_t n) +{ + size_t t = 1; + for (size_t i = 0; i < n; i++) { + t *= 2; + } + + return t; +} + +int main() +{ + testGeneric(1, power2(6), false); + /* + for (size_t worker_count = 1; worker_count < 32; worker_count++) { + for (size_t job_count = 0; job_count < 16; job_count++) { + testGeneric(worker_count, power2(job_count), true); + testGeneric(worker_count, power2(job_count), false); + } + } + */ + return 0; +} diff --git a/tests/argumentc.test.c b/tests/argumentc.test.c new file mode 100644 index 0000000..e2d9d7a --- /dev/null +++ b/tests/argumentc.test.c @@ -0,0 +1,139 @@ +#include "../src/argumentc/argumentc.h" +#include +#include +#include +#include + +void test_long() +{ + int argc = 3; + const char* argv[] = {"--hello", "--world", "--!"}; + Argumentc argumentc; + assert(Argumentc_Create(&argumentc, argc, argv) == EXIT_SUCCESS); + + assert(Argumentc_PopLongOption(&argumentc, StringView_FromString("hello")).type != OPTIONTYPE_NONE); + assert(Argumentc_PopLongOption(&argumentc, StringView_FromString("!")).type != OPTIONTYPE_NONE); + assert(Argumentc_PopLongOption(&argumentc, StringView_FromString("world")).type != OPTIONTYPE_NONE); + + assert(Argumentc_HaveNextOption(&argumentc) == false); + + Argumentc_Destroy(&argumentc); + + return; +} + +void test_short() +{ + int argc = 2; + const char* argv[] = {"-abv", "-vv"}; + Argumentc argumentc; + assert(Argumentc_Create(&argumentc, argc, argv) == EXIT_SUCCESS); + + assert(Argumentc_PopShortOption(&argumentc, StringView_FromString("a")).type != OPTIONTYPE_NONE); + assert(Argumentc_PopShortOption(&argumentc, StringView_FromString("b")).type != OPTIONTYPE_NONE); + assert(Argumentc_PopShortOption(&argumentc, StringView_FromString("v")).type != OPTIONTYPE_NONE); + assert(Argumentc_PopShortOption(&argumentc, StringView_FromString("v")).type != OPTIONTYPE_NONE); + assert(Argumentc_PopShortOption(&argumentc, StringView_FromString("v")).type != OPTIONTYPE_NONE); + + assert(Argumentc_HaveNextOption(&argumentc) == false); + + Argumentc_Destroy(&argumentc); + + return; +} + +void test_long_arg() +{ + int argc = 4; + const char* argv[] = {"--long", "argument", "--longer", "argumenter"}; + Argumentc argumentc; + assert(Argumentc_Create(&argumentc, argc, argv) == EXIT_SUCCESS); + + assert( + Argumentc_PopLongArgument( + &argumentc, + StringView_FromString("longer") + ).argument.type + != + OPTIONTYPE_NONE + ); + assert( + Argumentc_PopLongArgument( + &argumentc, + StringView_FromString("long") + ).argument.type + != + OPTIONTYPE_NONE + ); + + assert(Argumentc_HaveNextOption(&argumentc) == false); + + Argumentc_Destroy(&argumentc); + + return; +} + +void test_short_arg() +{ + int argc = 4; + const char* argv[] = {"-ab", "argument", "-s", "15"}; + Argumentc argumentc; + assert(Argumentc_Create(&argumentc, argc, argv) == EXIT_SUCCESS); + + assert( + Argumentc_PopShortArgument( + &argumentc, + StringView_FromString("b") + ).argument.type + != + OPTIONTYPE_NONE + ); + assert( + Argumentc_PopShortArgument( + &argumentc, + StringView_FromString("s") + ).argument.type + != + OPTIONTYPE_NONE + ); + assert(Argumentc_PopShortOption(&argumentc, StringView_FromString("a")).type != OPTIONTYPE_NONE); + + assert(Argumentc_HaveNextOption(&argumentc) == false); + + Argumentc_Destroy(&argumentc); + + return; +} + +int main(int argc, char const *argv[]) { + Argumentc argumentc; + if (Argumentc_Create(&argumentc, argc, argv)) { + fprintf(stderr, "Failed to parse options for memory reasons"); + return EXIT_FAILURE; + } + + for (size_t i = 0; i < DynamicArray_GetLength(&argumentc.array); i++) { + Option* option = DynamicArray_GetPointer(&argumentc.array, i); + char c[option->content.length + 1]; + memset(c, 0, option->content.length + 1); + memcpy(c, option->content.source, option->content.length); + printf("%-20s: %s\n", + OptionType_ToString(option->type), + c); + } + + if (Argumentc_PopLongOption(&argumentc, StringView_FromString("long")).type != OPTIONTYPE_NONE) { + test_long(); + } + if (Argumentc_PopLongOption(&argumentc, StringView_FromString("short")).type != OPTIONTYPE_NONE) { + test_short(); + } + if (Argumentc_PopLongOption(&argumentc, StringView_FromString("long-arg")).type != OPTIONTYPE_NONE) { + test_long_arg(); + } + if (Argumentc_PopLongOption(&argumentc, StringView_FromString("short-arg")).type != OPTIONTYPE_NONE) { + test_short_arg(); + } + + Argumentc_Destroy(&argumentc); +} diff --git a/tests/regex.test.c b/tests/regex.test.c new file mode 100644 index 0000000..b830f38 --- /dev/null +++ b/tests/regex.test.c @@ -0,0 +1,119 @@ +#include "../src/regex/regex.h" + +#include +#include + +void testLifetime(void) +{ + allocator_t allocator; + Allocator_CreateSystemAllocator(&allocator); + Regex regex; + Regex_Create(®ex, &allocator); + + + Regex_Destroy(®ex); + assert(allocator.reserved == 0); + Allocator_DestroySystemAllocator(&allocator); + return; +} + +void testLiteral(void) +{ + StringView test_string_0 = StringView_FromString("Hello World"); + allocator_t allocator; + Allocator_CreateSystemAllocator(&allocator); + Regex regex; + assert(Regex_Create(®ex, &allocator) == EXIT_SUCCESS); + + assert(Regex_Compile(®ex, test_string_0) == EXIT_SUCCESS); + + RegexMatch match; + assert(Regex_FirstMatch(®ex, test_string_0, &match) == EXIT_SUCCESS); + assert(StringView_Equal(match.match, test_string_0)); + + Regex_Destroy(®ex); + assert(allocator.reserved == 0); + Allocator_DestroySystemAllocator(&allocator); + return; +} + +void testBackslash(void) +{ + StringView regex_string = StringView_FromString("Hello World\\\\"); + StringView match_string = StringView_FromString("Hello World\\"); + allocator_t allocator; + Allocator_CreateSystemAllocator(&allocator); + Regex regex; + assert(Regex_Create(®ex, &allocator) == EXIT_SUCCESS); + + assert(Regex_Compile(®ex, regex_string) == EXIT_SUCCESS); + + RegexMatch match; + assert(Regex_FirstMatch(®ex, match_string, &match) == EXIT_SUCCESS); + assert(StringView_Equal(match.match, match_string)); + + Regex_Destroy(®ex); + assert(allocator.reserved == 0); + Allocator_DestroySystemAllocator(&allocator); + return; +} + +void testGroup(void) +{ + StringView regex_string = StringView_FromString("(Hello) (World)"); + StringView match_string = StringView_FromString("Hello World"); + allocator_t allocator; + Allocator_CreateSystemAllocator(&allocator); + Regex regex; + assert(Regex_Create(®ex, &allocator) == EXIT_SUCCESS); + + assert(Regex_Compile(®ex, regex_string) == EXIT_SUCCESS); + + RegexMatch match; + assert(Regex_FirstMatch(®ex, match_string, &match) == EXIT_SUCCESS); + assert(StringView_Equal(match.match, match_string)); + assert(RegexMatch_HaveNumberedCapture(&match, 1)); + assert(RegexMatch_HaveNumberedCapture(&match, 2)); + + Regex_Destroy(®ex); + assert(allocator.reserved == 0); + Allocator_DestroySystemAllocator(&allocator); + return; +} + +void testQuantifier(void) +{ + StringView regex_string = StringView_FromString("\\??"); + StringView match_string_0 = StringView_FromString("?"); + StringView match_string_1 = StringView_FromString(""); + allocator_t allocator; + Allocator_CreateSystemAllocator(&allocator); + + Regex regex; + assert(Regex_Create(®ex, &allocator) == EXIT_SUCCESS); + + assert(Regex_Compile(®ex, regex_string) == EXIT_SUCCESS); + + RegexMatch match; + + assert(Regex_FirstMatch(®ex, match_string_0, &match) == EXIT_SUCCESS); + assert(StringView_Equal(match.match, match_string_0)); + + assert(Regex_FirstMatch(®ex, match_string_1, &match) == EXIT_SUCCESS); + assert(StringView_Equal(match.match, match_string_1)); + + Regex_Destroy(®ex); + assert(allocator.reserved == 0); + Allocator_DestroySystemAllocator(&allocator); + return; +} + +int main() +{ + testLifetime(); + testLiteral(); + testBackslash(); + testGroup(); + testQuantifier(); + return 0; +}