Initial commit, yay

This commit is contained in:
VegOwOtenks 2024-06-13 15:28:21 +02:00
commit 25e26756cd
85 changed files with 7077 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
build/

18
CMakeLists.txt Normal file
View file

@ -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/)

18
src/CMakeLists.txt Normal file
View file

@ -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)

View file

@ -0,0 +1 @@
add_library(FixedBuffer STATIC FixedBuffer.c)

View file

@ -0,0 +1,81 @@
#include "FixedBuffer.h"
#include "../pointers/pointers.h"
#include <stdlib.h>
#include <string.h>
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;
}

View file

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

View file

@ -0,0 +1 @@
add_library(FreeList STATIC FreeList.c)

234
src/FreeList/FreeList.c Normal file
View file

@ -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 <stdlib.h>
#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);
}

55
src/FreeList/FreeList.h Normal file
View file

@ -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_ */

View file

@ -0,0 +1 @@
add_library(QuadTree STATIC QuadTree.c)

1
src/QuadTree/QuadTree.c Normal file
View file

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

8
src/QuadTree/QuadTree.h Normal file
View file

@ -0,0 +1,8 @@
#ifndef UTILITIEC_QUADTREE_H
#define UTILITIEC_QUADTREE_H
typedef struct QuadTree_s {
} QuadTree;
#endif

View file

@ -0,0 +1 @@
add_library(Scratchpad STATIC Scratchpad.c)

126
src/Scratchpad/Scratchpad.c Normal file
View file

@ -0,0 +1,126 @@
#include "Scratchpad.h"
#include "../pointers/pointers.h"
#include <stdlib.h>
#include <string.h>
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;
}

View file

@ -0,0 +1,29 @@
#ifndef SCRATCHPAD_H
#define SCRATCHPAD_H
#include "../allocator-interface/allocator-interface.h"
#include <stddef.h>
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

View file

@ -0,0 +1 @@
add_library(StringView STATIC StringView.c)

206
src/StringView/StringView.c Normal file
View file

@ -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 <ctype.h>
#include <string.h>
#include <stdint.h>
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;
}

View file

@ -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 <stdbool.h>
#include <stddef.h>
// 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_ */

View file

@ -0,0 +1 @@
add_library(ThreadPool STATIC ThreadPool.c)

412
src/ThreadPool/ThreadPool.c Normal file
View file

@ -0,0 +1,412 @@
#include "ThreadPool.h"
#include <pthread.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
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;
}

114
src/ThreadPool/ThreadPool.h Normal file
View file

@ -0,0 +1,114 @@
#ifndef V_THREADPOOL_H
#define V_THREADPOOL_H
#include <stdint.h> // int32_t, size_t, int8_t
#include <stdbool.h> // 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

View file

@ -0,0 +1 @@
add_library(allocator-interface STATIC allocator-interface.c)

View file

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

View file

@ -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 <stddef.h>
#include <stdlib.h>
#include <string.h>
#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

View file

@ -0,0 +1 @@
add_library(argumentc STATIC argumentc.c)

304
src/argumentc/argumentc.c Normal file
View file

@ -0,0 +1,304 @@
#include "argumentc.h"
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
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";
}
}

57
src/argumentc/argumentc.h Normal file
View file

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

View file

@ -0,0 +1 @@
add_library(arraylist STATIC arraylist.c)

433
src/arraylist/arraylist.c Normal file
View file

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

207
src/arraylist/arraylist.h Normal file
View file

@ -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 <stdbool.h>
#include <stdlib.h>
#include <string.h>
#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

9
src/cmakegen.sh Normal file
View file

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

View file

@ -0,0 +1 @@
add_library(dynamicarray STATIC dynamicarray.c)

View file

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

View file

@ -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_ */

View file

@ -0,0 +1 @@
add_library(dynamicbuffer STATIC dynamicbuffer.c)

View file

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

View file

@ -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 <stdlib.h>
#include <string.h>
#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

83
src/errorcodes.h Normal file
View file

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

View file

@ -0,0 +1 @@
add_library(hashmap STATIC hashmap.c)

359
src/hashmap/hashmap.c Normal file
View file

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

67
src/hashmap/hashmap.h Normal file
View file

@ -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 <stdbool.h>
#include <stddef.h>
#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_ */

View file

@ -0,0 +1 @@
add_library(pointers STATIC pointers.c)

31
src/pointers/pointers.c Normal file
View file

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

28
src/pointers/pointers.h Normal file
View file

@ -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 <stdint.h>
void* advancep(void* pointer, uintptr_t bytes);
void* rewindp(void* pointer, uintptr_t bytes);
#endif /* SRC_POINTERS_H_ */

2
src/rand/CMakeLists.txt Normal file
View file

@ -0,0 +1,2 @@
add_library(rand STATIC opensimplex.c
xoshiro256.c)

175
src/rand/opensimplex.c Normal file
View file

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

52
src/rand/opensimplex.h Normal file
View file

@ -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 <stdint.h>
#include <math.h>
#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

106
src/rand/xoshiro256.c Normal file
View file

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

33
src/rand/xoshiro256.h Normal file
View file

@ -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 <stdint.h>
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

5
src/regex/CMakeLists.txt Normal file
View file

@ -0,0 +1,5 @@
add_library(regex STATIC compile.c
machine_state.c
match.c
match_struct.c
regex.c)

517
src/regex/compile.c Normal file
View file

@ -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 <ctype.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#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(
&regex->machine_memory,
sizeof(RegexMachineStateAccept)
);
if (accept == NULL) {
return NULL;
}
RegexMachineStateAccept_Init(accept);
// Copy the sequence to an owned buffer
char* accept_sequence = Scratchpad_Reserve(
&regex->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(&regex->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(&regex->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(&regex->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(&regex->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(&regex->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(&regex->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;
}

8
src/regex/compile.h Normal file
View file

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

37
src/regex/machine_state.c Normal file
View file

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

12
src/regex/machine_state.h Normal file
View file

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

View file

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

624
src/regex/match.c Normal file
View file

@ -0,0 +1,624 @@
#include "match.h"
#include "machine_state.h"
#include "match_struct.h"
#include "regex_struct.h"
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
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;
}

62
src/regex/match.h Normal file
View file

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

45
src/regex/match_struct.c Normal file
View file

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

29
src/regex/match_struct.h Normal file
View file

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

61
src/regex/regex.c Normal file
View file

@ -0,0 +1,61 @@
#include "regex.h"
#include "compile.h"
#include "match.h"
#include <stdbool.h>
#include <stdlib.h>
#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(&regex->machine_memory);
return;
}
static void _Regex_Reset(Regex* regex)
{
Scratchpad_Reset(&regex->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;
}

32
src/regex/regex.h Normal file
View file

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

19
src/regex/regex_struct.h Normal file
View file

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

View file

@ -0,0 +1 @@
add_library(siphash STATIC siphash.c)

128
src/siphash/siphash.c Normal file
View file

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

43
src/siphash/siphash.h Normal file
View file

@ -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 <stdint.h>
#include <string.h>
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*/

View file

@ -0,0 +1,4 @@
add_library(threading STATIC os_mutex.c
os_rwlock.c
os_semaphore.c
os_thread.c)

61
src/threading/os_mutex.c Normal file
View file

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

23
src/threading/os_mutex.h Normal file
View file

@ -0,0 +1,23 @@
#ifndef OS_MUTEX_H
#define OS_MUTEX_H
#if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__)) // Fuck you, apple
#include <pthread.h>
typedef pthread_mutex_t os_mutex_t;
#elif defined(_WIN32)
#include <windows.h>
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

90
src/threading/os_rwlock.c Normal file
View file

@ -0,0 +1,90 @@
#include "os_rwlock.h"
#include <pthread.h>
#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

29
src/threading/os_rwlock.h Normal file
View file

@ -0,0 +1,29 @@
#ifndef UTILITIEC_RWLOCK_H
#define UTILITIEC_RWLOCK_H
#if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__)) // Fuck you, apple
#include <pthread.h>
typedef pthread_rwlock_t OSRWLock;
#elif defined(_WIN32)
#include <syncapi.h>
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

View file

@ -0,0 +1,92 @@
#include "os_semaphore.h"
#include <semaphore.h>
#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

View file

@ -0,0 +1,59 @@
#ifndef OS_SEMAPHORE_H
#define OS_SEMAPHORE_H
#if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__)) // Fuck you, apple
#include <semaphore.h>
typedef sem_t os_semaphore_t;
#elif defined(_WIN32)
#include <windows.h>
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

89
src/threading/os_thread.c Normal file
View file

@ -0,0 +1,89 @@
#include "os_thread.h"
#include <stdlib.h>
#if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__))
#include <pthread.h>
#include <signal.h>
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

28
src/threading/os_thread.h Normal file
View file

@ -0,0 +1,28 @@
#ifndef OS_THREAD_H
#define OS_THREAD_H
#if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__)) // Fuck you, apple
#include <pthread.h>
typedef pthread_t os_thread_t;
#elif defined(_WIN32)
#include <windows.h>
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

1
src/utf8/CMakeLists.txt Normal file
View file

@ -0,0 +1 @@
add_library(utf8 STATIC utf-8.c)

60
src/utf8/utf-8.c Normal file
View file

@ -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 <stddef.h>
#include <stdint.h>
#include <string.h>
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);
}

28
src/utf8/utf-8.h Normal file
View file

@ -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 <stddef.h>
size_t UTF8_CodepointLength(const char* string);
const char* UTF8_NextCodepoint(const char* string);
#endif /* SRC_COMMON_UTF_UTF_8_H_ */

16
tests/.cproject Normal file
View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<?fileVersion 4.0.0?><cproject storage_type_id="org.eclipse.cdt.core.XmlProjectDescriptionStorage">
<storageModule moduleId="org.eclipse.cdt.core.settings">
<cconfiguration id="org.eclipse.cdt.core.default.config.1524259863">
<storageModule buildSystemId="org.eclipse.cdt.core.defaultConfigDataProvider" id="org.eclipse.cdt.core.default.config.1524259863" moduleId="org.eclipse.cdt.core.settings" name="Configuration">
<externalSettings/>
<extensions/>
</storageModule>
<storageModule moduleId="org.eclipse.cdt.core.externalSettings"/>
</cconfiguration>
</storageModule>
<storageModule moduleId="org.eclipse.cdt.core.pathentry">
<pathentry excluding="**/CMakeFiles/**" kind="out" path="build"/>
</storageModule>
<storageModule moduleId="org.eclipse.cdt.core.LanguageSettingsProviders"/>
</cproject>

20
tests/.project Normal file
View file

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>tests</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.cdt.core.cBuilder</name>
<triggers>clean,full,incremental,</triggers>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.cdt.core.cnature</nature>
<nature>org.eclipse.cdt.core.ccnature</nature>
<nature>org.eclipse.cdt.cmake.core.cmakeNature</nature>
</natures>
</projectDescription>

38
tests/CMakeLists.txt Normal file
View file

@ -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
)

24
tests/Scratchpad.test.c Normal file
View file

@ -0,0 +1,24 @@
#include "../src/Scratchpad/Scratchpad.h"
#include <assert.h>
#include <stdlib.h>
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;
}

36
tests/StringView.test.c Normal file
View file

@ -0,0 +1,36 @@
#include "../src/StringView/StringView.h"
#include <assert.h>
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;
}

88
tests/ThreadPool.test.c Normal file
View file

@ -0,0 +1,88 @@
#include "../src/ThreadPool/ThreadPool.h"
#include <assert.h>
#include <stdbool.h>
#include <stdlib.h>
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;
}

139
tests/argumentc.test.c Normal file
View file

@ -0,0 +1,139 @@
#include "../src/argumentc/argumentc.h"
#include <assert.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
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);
}

119
tests/regex.test.c Normal file
View file

@ -0,0 +1,119 @@
#include "../src/regex/regex.h"
#include <assert.h>
#include <stdlib.h>
void testLifetime(void)
{
allocator_t allocator;
Allocator_CreateSystemAllocator(&allocator);
Regex regex;
Regex_Create(&regex, &allocator);
Regex_Destroy(&regex);
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(&regex, &allocator) == EXIT_SUCCESS);
assert(Regex_Compile(&regex, test_string_0) == EXIT_SUCCESS);
RegexMatch match;
assert(Regex_FirstMatch(&regex, test_string_0, &match) == EXIT_SUCCESS);
assert(StringView_Equal(match.match, test_string_0));
Regex_Destroy(&regex);
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(&regex, &allocator) == EXIT_SUCCESS);
assert(Regex_Compile(&regex, regex_string) == EXIT_SUCCESS);
RegexMatch match;
assert(Regex_FirstMatch(&regex, match_string, &match) == EXIT_SUCCESS);
assert(StringView_Equal(match.match, match_string));
Regex_Destroy(&regex);
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(&regex, &allocator) == EXIT_SUCCESS);
assert(Regex_Compile(&regex, regex_string) == EXIT_SUCCESS);
RegexMatch match;
assert(Regex_FirstMatch(&regex, match_string, &match) == EXIT_SUCCESS);
assert(StringView_Equal(match.match, match_string));
assert(RegexMatch_HaveNumberedCapture(&match, 1));
assert(RegexMatch_HaveNumberedCapture(&match, 2));
Regex_Destroy(&regex);
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(&regex, &allocator) == EXIT_SUCCESS);
assert(Regex_Compile(&regex, regex_string) == EXIT_SUCCESS);
RegexMatch match;
assert(Regex_FirstMatch(&regex, match_string_0, &match) == EXIT_SUCCESS);
assert(StringView_Equal(match.match, match_string_0));
assert(Regex_FirstMatch(&regex, match_string_1, &match) == EXIT_SUCCESS);
assert(StringView_Equal(match.match, match_string_1));
Regex_Destroy(&regex);
assert(allocator.reserved == 0);
Allocator_DestroySystemAllocator(&allocator);
return;
}
int main()
{
testLifetime();
testLiteral();
testBackslash();
testGroup();
testQuantifier();
return 0;
}