Initial commit, yay
This commit is contained in:
commit
25e26756cd
85 changed files with 7077 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
build/
|
18
CMakeLists.txt
Normal file
18
CMakeLists.txt
Normal 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
18
src/CMakeLists.txt
Normal 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)
|
1
src/FixedBuffer/CMakeLists.txt
Normal file
1
src/FixedBuffer/CMakeLists.txt
Normal file
|
@ -0,0 +1 @@
|
||||||
|
add_library(FixedBuffer STATIC FixedBuffer.c)
|
81
src/FixedBuffer/FixedBuffer.c
Normal file
81
src/FixedBuffer/FixedBuffer.c
Normal 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;
|
||||||
|
}
|
25
src/FixedBuffer/FixedBuffer.h
Normal file
25
src/FixedBuffer/FixedBuffer.h
Normal 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
|
1
src/FreeList/CMakeLists.txt
Normal file
1
src/FreeList/CMakeLists.txt
Normal file
|
@ -0,0 +1 @@
|
||||||
|
add_library(FreeList STATIC FreeList.c)
|
234
src/FreeList/FreeList.c
Normal file
234
src/FreeList/FreeList.c
Normal 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
55
src/FreeList/FreeList.h
Normal 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_ */
|
1
src/QuadTree/CMakeLists.txt
Normal file
1
src/QuadTree/CMakeLists.txt
Normal file
|
@ -0,0 +1 @@
|
||||||
|
add_library(QuadTree STATIC QuadTree.c)
|
1
src/QuadTree/QuadTree.c
Normal file
1
src/QuadTree/QuadTree.c
Normal file
|
@ -0,0 +1 @@
|
||||||
|
#include "QuadTree.h"
|
8
src/QuadTree/QuadTree.h
Normal file
8
src/QuadTree/QuadTree.h
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
#ifndef UTILITIEC_QUADTREE_H
|
||||||
|
#define UTILITIEC_QUADTREE_H
|
||||||
|
|
||||||
|
typedef struct QuadTree_s {
|
||||||
|
|
||||||
|
} QuadTree;
|
||||||
|
|
||||||
|
#endif
|
1
src/Scratchpad/CMakeLists.txt
Normal file
1
src/Scratchpad/CMakeLists.txt
Normal file
|
@ -0,0 +1 @@
|
||||||
|
add_library(Scratchpad STATIC Scratchpad.c)
|
126
src/Scratchpad/Scratchpad.c
Normal file
126
src/Scratchpad/Scratchpad.c
Normal 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;
|
||||||
|
}
|
29
src/Scratchpad/Scratchpad.h
Normal file
29
src/Scratchpad/Scratchpad.h
Normal 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
|
1
src/StringView/CMakeLists.txt
Normal file
1
src/StringView/CMakeLists.txt
Normal file
|
@ -0,0 +1 @@
|
||||||
|
add_library(StringView STATIC StringView.c)
|
206
src/StringView/StringView.c
Normal file
206
src/StringView/StringView.c
Normal 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;
|
||||||
|
}
|
58
src/StringView/StringView.h
Normal file
58
src/StringView/StringView.h
Normal 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_ */
|
1
src/ThreadPool/CMakeLists.txt
Normal file
1
src/ThreadPool/CMakeLists.txt
Normal file
|
@ -0,0 +1 @@
|
||||||
|
add_library(ThreadPool STATIC ThreadPool.c)
|
412
src/ThreadPool/ThreadPool.c
Normal file
412
src/ThreadPool/ThreadPool.c
Normal 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
114
src/ThreadPool/ThreadPool.h
Normal 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
|
1
src/allocator-interface/CMakeLists.txt
Normal file
1
src/allocator-interface/CMakeLists.txt
Normal file
|
@ -0,0 +1 @@
|
||||||
|
add_library(allocator-interface STATIC allocator-interface.c)
|
155
src/allocator-interface/allocator-interface.c
Normal file
155
src/allocator-interface/allocator-interface.c
Normal 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;
|
||||||
|
}
|
61
src/allocator-interface/allocator-interface.h
Normal file
61
src/allocator-interface/allocator-interface.h
Normal 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
|
1
src/argumentc/CMakeLists.txt
Normal file
1
src/argumentc/CMakeLists.txt
Normal file
|
@ -0,0 +1 @@
|
||||||
|
add_library(argumentc STATIC argumentc.c)
|
304
src/argumentc/argumentc.c
Normal file
304
src/argumentc/argumentc.c
Normal 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
57
src/argumentc/argumentc.h
Normal 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
|
1
src/arraylist/CMakeLists.txt
Normal file
1
src/arraylist/CMakeLists.txt
Normal file
|
@ -0,0 +1 @@
|
||||||
|
add_library(arraylist STATIC arraylist.c)
|
433
src/arraylist/arraylist.c
Normal file
433
src/arraylist/arraylist.c
Normal 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
207
src/arraylist/arraylist.h
Normal 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
9
src/cmakegen.sh
Normal 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
|
1
src/dynamicarray/CMakeLists.txt
Normal file
1
src/dynamicarray/CMakeLists.txt
Normal file
|
@ -0,0 +1 @@
|
||||||
|
add_library(dynamicarray STATIC dynamicarray.c)
|
309
src/dynamicarray/dynamicarray.c
Normal file
309
src/dynamicarray/dynamicarray.c
Normal 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;
|
||||||
|
}
|
141
src/dynamicarray/dynamicarray.h
Normal file
141
src/dynamicarray/dynamicarray.h
Normal 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_ */
|
1
src/dynamicbuffer/CMakeLists.txt
Normal file
1
src/dynamicbuffer/CMakeLists.txt
Normal file
|
@ -0,0 +1 @@
|
||||||
|
add_library(dynamicbuffer STATIC dynamicbuffer.c)
|
217
src/dynamicbuffer/dynamicbuffer.c
Normal file
217
src/dynamicbuffer/dynamicbuffer.c
Normal 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;
|
||||||
|
}
|
116
src/dynamicbuffer/dynamicbuffer.h
Normal file
116
src/dynamicbuffer/dynamicbuffer.h
Normal 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
83
src/errorcodes.h
Normal 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
|
1
src/hashmap/CMakeLists.txt
Normal file
1
src/hashmap/CMakeLists.txt
Normal file
|
@ -0,0 +1 @@
|
||||||
|
add_library(hashmap STATIC hashmap.c)
|
359
src/hashmap/hashmap.c
Normal file
359
src/hashmap/hashmap.c
Normal 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
67
src/hashmap/hashmap.h
Normal 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_ */
|
1
src/pointers/CMakeLists.txt
Normal file
1
src/pointers/CMakeLists.txt
Normal file
|
@ -0,0 +1 @@
|
||||||
|
add_library(pointers STATIC pointers.c)
|
31
src/pointers/pointers.c
Normal file
31
src/pointers/pointers.c
Normal 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
28
src/pointers/pointers.h
Normal 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
2
src/rand/CMakeLists.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
add_library(rand STATIC opensimplex.c
|
||||||
|
xoshiro256.c)
|
175
src/rand/opensimplex.c
Normal file
175
src/rand/opensimplex.c
Normal 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
52
src/rand/opensimplex.h
Normal 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
106
src/rand/xoshiro256.c
Normal 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
33
src/rand/xoshiro256.h
Normal 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
5
src/regex/CMakeLists.txt
Normal 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
517
src/regex/compile.c
Normal 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(
|
||||||
|
®ex->machine_memory,
|
||||||
|
sizeof(RegexMachineStateAccept)
|
||||||
|
);
|
||||||
|
if (accept == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
RegexMachineStateAccept_Init(accept);
|
||||||
|
|
||||||
|
|
||||||
|
// Copy the sequence to an owned buffer
|
||||||
|
char* accept_sequence = Scratchpad_Reserve(
|
||||||
|
®ex->machine_memory,
|
||||||
|
length
|
||||||
|
);
|
||||||
|
if (accept_sequence == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
memcpy(accept_sequence, source->source, length);
|
||||||
|
|
||||||
|
// Make it a StringView for easier operation, also omits the \0
|
||||||
|
accept->sequence = StringView_FromStringSized(
|
||||||
|
accept_sequence,
|
||||||
|
length
|
||||||
|
);
|
||||||
|
|
||||||
|
// Advance source stringview
|
||||||
|
*source = StringView_Slice(*source, length, source->length);
|
||||||
|
|
||||||
|
return &accept->base;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Literals are everything except special groups, escaped characters etc...
|
||||||
|
static RegexMachineStateBase* _CompileLiteralMatch(Regex* regex, StringView* source)
|
||||||
|
{
|
||||||
|
// literal match, will be modified over time
|
||||||
|
StringView literal_match = StringView_Slice(*source, 0, 0);
|
||||||
|
|
||||||
|
bool end_reached = false;
|
||||||
|
if (SingleCharacterQuantified(*source)) {
|
||||||
|
end_reached = true;
|
||||||
|
ptrdiff_t length = UTF8_NextCodepoint(source->source) - source->source;
|
||||||
|
literal_match = StringView_Slice(*source, 0, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (! end_reached) {
|
||||||
|
if (SingleCharacterQuantified(*source)) {
|
||||||
|
/*
|
||||||
|
This break allows for regexes like "a?"
|
||||||
|
It also checks for one utf8 character (e.g. not flags)
|
||||||
|
If it wouldn't break, "var?" would be treated as an
|
||||||
|
optional word, Optional[var] not as "va" + Optional[r]
|
||||||
|
*/
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Still in a literal sequence?
|
||||||
|
if (! _IsLiteralChar(source->source[0])) {
|
||||||
|
// Literal sequence is over, break here
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Increment literal match length
|
||||||
|
source->source++;
|
||||||
|
source->length--;
|
||||||
|
literal_match.length++;
|
||||||
|
|
||||||
|
// Also terminate the loop if there are no more characters left to read
|
||||||
|
end_reached |= source->length == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _NewAcceptState(regex, &literal_match, literal_match.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
static RegexMachineStateBase* _CompileEscapedStuff(Regex* regex, StringView* source)
|
||||||
|
{
|
||||||
|
if (source->length == 1) {
|
||||||
|
// TODO: Set Error
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
*source = StringView_Slice(*source, 1, source->length);
|
||||||
|
switch (UTF8_CodepointLength(source->source)) {
|
||||||
|
case 1:
|
||||||
|
{
|
||||||
|
char next = source->source[0];
|
||||||
|
switch (next) {
|
||||||
|
case '\\':
|
||||||
|
case '(':
|
||||||
|
case ')':
|
||||||
|
case '{':
|
||||||
|
case '}':
|
||||||
|
case '[':
|
||||||
|
case ']':
|
||||||
|
case '+':
|
||||||
|
case '*':
|
||||||
|
case '?':
|
||||||
|
return _NewAcceptState(regex, source, 1);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 2:
|
||||||
|
case 3:
|
||||||
|
case 4:
|
||||||
|
case SIZE_MAX:
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Set Error
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static RegexMachineStateBase* _CompileNewState(Regex* regex, StringView* source)
|
||||||
|
{
|
||||||
|
switch (source->source[0]) {
|
||||||
|
case '(':
|
||||||
|
break;
|
||||||
|
case '[':
|
||||||
|
break;
|
||||||
|
case '\\':
|
||||||
|
return _CompileEscapedStuff(regex, source);
|
||||||
|
default:
|
||||||
|
return _CompileLiteralMatch(regex, source);
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct RegexStackEntry {
|
||||||
|
// the parent container itself (group or option)
|
||||||
|
RegexMachineStateBase* parent;
|
||||||
|
|
||||||
|
// last compiled state, next one will be appended here
|
||||||
|
RegexMachineStateBase* tail;
|
||||||
|
};
|
||||||
|
|
||||||
|
static int _PushIfGroup(DynamicArray* entry_stack, RegexMachineStateBase* new_state)
|
||||||
|
{
|
||||||
|
if (new_state->type == REGEXMACHINESTATETYPE_GROUP) {
|
||||||
|
struct RegexStackEntry* new_entry;
|
||||||
|
if (DynamicArray_AppendEmpty(entry_stack, (void**) &new_entry)) {
|
||||||
|
return ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
new_entry->parent = new_state;
|
||||||
|
new_entry->tail = new_state;
|
||||||
|
}
|
||||||
|
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _RedirectOptionTails(RegexMachineStateOption* option, RegexMachineStateBase* new_tail)
|
||||||
|
{
|
||||||
|
while (option->next_option != NULL) {
|
||||||
|
|
||||||
|
RegexMachineStateBase* tail = option->base.next;
|
||||||
|
while (tail->next != NULL) {
|
||||||
|
tail = tail->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append
|
||||||
|
tail->next = new_tail;
|
||||||
|
|
||||||
|
option = option->next_option;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int _CompileIntoGroup(Regex* regex, DynamicArray* entries, StringView* source)
|
||||||
|
{
|
||||||
|
struct RegexStackEntry* group_entry = DynamicArray_GetPointer(entries, entries->reserved - 1);
|
||||||
|
RegexMachineStateGroup* group = (RegexMachineStateGroup*) group_entry->parent;
|
||||||
|
|
||||||
|
if (source->source[0] == ')' && group->number != 0) {
|
||||||
|
// End group
|
||||||
|
RegexMachineStateGroup* group_end = Scratchpad_Reserve(®ex->machine_memory, sizeof(*group));
|
||||||
|
if (group_end == NULL) {
|
||||||
|
return ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
RegexMachineStateGroup_Init(group_end, group->name, group->number);
|
||||||
|
|
||||||
|
if (group->base.next->type == REGEXMACHINESTATETYPE_OPTION) {
|
||||||
|
_RedirectOptionTails((RegexMachineStateOption*) group->base.next, &group_end->base);
|
||||||
|
// Branching in group
|
||||||
|
} else {
|
||||||
|
// Just linear members in group
|
||||||
|
group_entry->tail->next = &group_end->base;
|
||||||
|
}
|
||||||
|
|
||||||
|
DynamicArray_RemoveFast(entries, entries->reserved - 1);
|
||||||
|
|
||||||
|
*source = StringView_Slice(*source, 1, source->length);
|
||||||
|
|
||||||
|
} else if (source->source[0] == ')' && group->number == 0) {
|
||||||
|
|
||||||
|
// Closing parenthesis for virtual zeroth capture group
|
||||||
|
// TODO: Report error
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
|
||||||
|
} else if (source->source[0] == '|') {
|
||||||
|
// Add new option as 'next' of group, embed everything parsed in it
|
||||||
|
|
||||||
|
RegexMachineStateOption* option;
|
||||||
|
option = Scratchpad_Reserve(®ex->machine_memory, sizeof(*option));
|
||||||
|
if (option == NULL) {
|
||||||
|
return ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
RegexMachineStateOption_Init(option);
|
||||||
|
option->base.next = group->base.next;
|
||||||
|
|
||||||
|
// Add to stack
|
||||||
|
{
|
||||||
|
struct RegexStackEntry* new_entry;
|
||||||
|
if (DynamicArray_AppendEmpty(entries, (void**) &new_entry)) {
|
||||||
|
return ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
new_entry->parent = &option->base;
|
||||||
|
new_entry->tail = group_entry->tail;
|
||||||
|
while (new_entry->tail->next != NULL) {
|
||||||
|
new_entry->tail = new_entry->tail->next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// redirect group next
|
||||||
|
group->base.next = &option->base;
|
||||||
|
group_entry->tail = &option->base;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
RegexMachineStateBase* new_state = _CompileNewState(regex, source);
|
||||||
|
if (new_state == NULL) {
|
||||||
|
return ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append to linked list
|
||||||
|
group_entry->tail->next = new_state;
|
||||||
|
group_entry->tail = new_state;
|
||||||
|
|
||||||
|
int add_code = _PushIfGroup(entries, new_state);
|
||||||
|
if (add_code != EXIT_SUCCESS) {
|
||||||
|
return add_code;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int _CompileIntoOption(Regex* regex, DynamicArray* entries, StringView* source)
|
||||||
|
{
|
||||||
|
struct RegexStackEntry* option_entry = DynamicArray_GetPointer(entries, entries->reserved - 1);
|
||||||
|
|
||||||
|
|
||||||
|
if (source->source[0] == '|') {
|
||||||
|
// New option
|
||||||
|
RegexMachineStateOption* new_option;
|
||||||
|
new_option = Scratchpad_Reserve(®ex->machine_memory, sizeof(*new_option));
|
||||||
|
if (new_option == NULL) {
|
||||||
|
return ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
RegexMachineStateOption_Init(new_option);
|
||||||
|
|
||||||
|
option_entry->parent = &new_option->base;
|
||||||
|
option_entry->tail = &new_option->base;
|
||||||
|
|
||||||
|
*source = StringView_Slice(*source, 1, source->length);
|
||||||
|
|
||||||
|
} else if (source->source[0] == ')') {
|
||||||
|
// Can't parse state here, finish option
|
||||||
|
DynamicArray_RemoveFast(entries, entries->reserved - 1);
|
||||||
|
} else {
|
||||||
|
// Append to current option
|
||||||
|
RegexMachineStateBase* new_state = _CompileNewState(regex, source);
|
||||||
|
|
||||||
|
option_entry->tail->next = new_state;
|
||||||
|
option_entry->tail = new_state;
|
||||||
|
|
||||||
|
int add_code = _PushIfGroup(entries, new_state);
|
||||||
|
if (add_code != EXIT_SUCCESS) {
|
||||||
|
return add_code;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int _HaveQuantifier(StringView source)
|
||||||
|
{
|
||||||
|
StringView quantifiers = StringView_FromString("+*?{");
|
||||||
|
return StringView_Contains(quantifiers, StringView_Slice(source, 0, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
static int _CompileQuantifier(Regex* regex, DynamicArray* entries, StringView* source)
|
||||||
|
{
|
||||||
|
struct RegexStackEntry* last_entry = DynamicArray_GetPointer(entries, DynamicArray_GetLength(entries) - 1);
|
||||||
|
|
||||||
|
size_t min, max;
|
||||||
|
size_t char_count = 1;
|
||||||
|
|
||||||
|
RegexMachineStateBase* repeatable = last_entry->tail;
|
||||||
|
RegexMachineStateBase* repeat_end = repeatable;
|
||||||
|
RegexMachineStateBase* previous;
|
||||||
|
|
||||||
|
previous = last_entry->parent;
|
||||||
|
while (previous->next != repeatable) {
|
||||||
|
if (previous->next->type == REGEXMACHINESTATETYPE_GROUP
|
||||||
|
&&
|
||||||
|
(((RegexMachineStateGroup*) previous->next)->number ==
|
||||||
|
((RegexMachineStateGroup*) repeatable)->number)
|
||||||
|
) {
|
||||||
|
repeatable = previous->next;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
previous = previous->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (source->source[0]) {
|
||||||
|
case '?':
|
||||||
|
min = 0;
|
||||||
|
max = 1;
|
||||||
|
break;
|
||||||
|
case '+':
|
||||||
|
min = 1;
|
||||||
|
max = SIZE_MAX;
|
||||||
|
break;
|
||||||
|
case '*':
|
||||||
|
min = 0;
|
||||||
|
max = SIZE_MAX;
|
||||||
|
break;
|
||||||
|
case '{':
|
||||||
|
// TODO: Implement
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
*source = StringView_Slice(*source, char_count, source->length);
|
||||||
|
|
||||||
|
bool greedy = true;
|
||||||
|
bool possessive = false;
|
||||||
|
if (StringView_StartsWith(*source, StringView_FromString("?"))) {
|
||||||
|
greedy = false;
|
||||||
|
*source = StringView_Slice(*source, 1, source->length);
|
||||||
|
} else if (StringView_StartsWith(*source, StringView_FromString("+"))) {
|
||||||
|
possessive = true;
|
||||||
|
*source = StringView_Slice(*source, 1, source->length);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
RegexMachineStateRepeat* repeat_state;
|
||||||
|
repeat_state = Scratchpad_Reserve(®ex->machine_memory, sizeof(*repeat_state));
|
||||||
|
if (repeat_state == NULL) {
|
||||||
|
return ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
RegexMachineStateRepeat_Init(repeat_state, min, max, greedy, possessive, repeatable);
|
||||||
|
|
||||||
|
// Insert before repeatable
|
||||||
|
// redirect repeatable end
|
||||||
|
repeat_end->next = &repeat_state->base;
|
||||||
|
previous->next = &repeat_state->base;
|
||||||
|
|
||||||
|
last_entry->tail = &repeat_state->base;
|
||||||
|
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int _CompileIntoEntries(Regex* regex, DynamicArray* entries, StringView* source)
|
||||||
|
{
|
||||||
|
struct RegexStackEntry* current = DynamicArray_GetPointer(entries, entries->reserved - 1);
|
||||||
|
|
||||||
|
|
||||||
|
if (source->length != 0) {
|
||||||
|
// Happily compile on
|
||||||
|
if (_HaveQuantifier(*source)) {
|
||||||
|
return _CompileQuantifier(regex, entries, source);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (current->parent->type) {
|
||||||
|
|
||||||
|
case REGEXMACHINESTATETYPE_GROUP:
|
||||||
|
return _CompileIntoGroup(regex, entries, source);
|
||||||
|
|
||||||
|
case REGEXMACHINESTATETYPE_OPTION:
|
||||||
|
return _CompileIntoOption(regex, entries, source);
|
||||||
|
|
||||||
|
default:
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Pop all options but not groups
|
||||||
|
// (groups need closing parentheses)
|
||||||
|
}
|
||||||
|
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Internal_Regex_Compile(Regex* regex, StringView source)
|
||||||
|
{
|
||||||
|
int return_code = EXIT_SUCCESS;
|
||||||
|
DynamicArray parent_stack;
|
||||||
|
if (DynamicArray_Create(&parent_stack, sizeof(struct RegexStackEntry), 4, regex->machine_memory.allocator)) {
|
||||||
|
// unlikely
|
||||||
|
return ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Create group zero
|
||||||
|
// Spans entire match
|
||||||
|
RegexMachineStateGroup* zero_group;
|
||||||
|
zero_group = Scratchpad_Reserve(®ex->machine_memory, sizeof(*zero_group));
|
||||||
|
if (zero_group == NULL) {
|
||||||
|
// unlikely
|
||||||
|
return_code = ENOMEM;
|
||||||
|
goto defer_stack;
|
||||||
|
}
|
||||||
|
|
||||||
|
RegexMachineStateGroup_Init(zero_group, NULL, 0);
|
||||||
|
|
||||||
|
regex->head = &zero_group->base;
|
||||||
|
|
||||||
|
struct RegexStackEntry* zero_stackentry;
|
||||||
|
// Can't fail, first push with sufficient capacity
|
||||||
|
DynamicArray_AppendEmpty(&parent_stack, (void**) &zero_stackentry);
|
||||||
|
zero_stackentry->parent = &zero_group->base;
|
||||||
|
zero_stackentry->tail = &zero_group->base;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (source.length || (parent_stack.reserved != 1)) {
|
||||||
|
int compile_code = _CompileIntoEntries(regex, &parent_stack, &source);
|
||||||
|
|
||||||
|
if (compile_code != EXIT_SUCCESS) {
|
||||||
|
return_code = compile_code;
|
||||||
|
goto defer_stack;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// End group zero
|
||||||
|
// Spans entire match
|
||||||
|
RegexMachineStateGroup* zero_group_end;
|
||||||
|
zero_group_end = Scratchpad_Reserve(®ex->machine_memory, sizeof(*zero_group_end));
|
||||||
|
if (zero_group_end == NULL) {
|
||||||
|
// unlikely
|
||||||
|
return_code = ENOMEM;
|
||||||
|
goto defer_stack;
|
||||||
|
}
|
||||||
|
|
||||||
|
RegexMachineStateGroup_Init(zero_group_end, NULL, 0);
|
||||||
|
|
||||||
|
|
||||||
|
struct RegexStackEntry* zero_stackentry = DynamicArray_GetPointer(&parent_stack, 0);
|
||||||
|
zero_stackentry->tail->next = &zero_group_end->base;
|
||||||
|
zero_stackentry->tail = &zero_group_end->base;
|
||||||
|
}
|
||||||
|
|
||||||
|
defer_stack:
|
||||||
|
DynamicArray_Destroy(&parent_stack);
|
||||||
|
|
||||||
|
return return_code;
|
||||||
|
}
|
8
src/regex/compile.h
Normal file
8
src/regex/compile.h
Normal 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
37
src/regex/machine_state.c
Normal 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
12
src/regex/machine_state.h
Normal 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
|
66
src/regex/machine_state_struct.h
Normal file
66
src/regex/machine_state_struct.h
Normal 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
624
src/regex/match.c
Normal 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
62
src/regex/match.h
Normal 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
45
src/regex/match_struct.c
Normal 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
29
src/regex/match_struct.h
Normal 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
61
src/regex/regex.c
Normal 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(®ex->machine_memory);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _Regex_Reset(Regex* regex)
|
||||||
|
{
|
||||||
|
Scratchpad_Reset(®ex->machine_memory);
|
||||||
|
regex->head = NULL;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Regex_Compile(Regex* regex, StringView source)
|
||||||
|
{
|
||||||
|
if (regex == NULL) return EDESTADDRREQ;
|
||||||
|
if (source.source == NULL) return EINVAL;
|
||||||
|
|
||||||
|
_Regex_Reset(regex);
|
||||||
|
Internal_Regex_Compile(regex, source);
|
||||||
|
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Regex_FirstMatch(Regex* regex, StringView string, RegexMatch* match)
|
||||||
|
{
|
||||||
|
if (regex == NULL) return EINVAL;
|
||||||
|
if (match == NULL) return EDESTADDRREQ;
|
||||||
|
|
||||||
|
int return_code = Regex_MatchHere(regex, string, 0, match);
|
||||||
|
|
||||||
|
return return_code;
|
||||||
|
}
|
32
src/regex/regex.h
Normal file
32
src/regex/regex.h
Normal 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
19
src/regex/regex_struct.h
Normal 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
|
1
src/siphash/CMakeLists.txt
Normal file
1
src/siphash/CMakeLists.txt
Normal file
|
@ -0,0 +1 @@
|
||||||
|
add_library(siphash STATIC siphash.c)
|
128
src/siphash/siphash.c
Normal file
128
src/siphash/siphash.c
Normal 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
43
src/siphash/siphash.h
Normal 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*/
|
4
src/threading/CMakeLists.txt
Normal file
4
src/threading/CMakeLists.txt
Normal 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
61
src/threading/os_mutex.c
Normal 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
23
src/threading/os_mutex.h
Normal 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
90
src/threading/os_rwlock.c
Normal 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
29
src/threading/os_rwlock.h
Normal 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
|
92
src/threading/os_semaphore.c
Normal file
92
src/threading/os_semaphore.c
Normal 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
|
59
src/threading/os_semaphore.h
Normal file
59
src/threading/os_semaphore.h
Normal 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
89
src/threading/os_thread.c
Normal 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
28
src/threading/os_thread.h
Normal 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
1
src/utf8/CMakeLists.txt
Normal file
|
@ -0,0 +1 @@
|
||||||
|
add_library(utf8 STATIC utf-8.c)
|
60
src/utf8/utf-8.c
Normal file
60
src/utf8/utf-8.c
Normal 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
28
src/utf8/utf-8.h
Normal 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
16
tests/.cproject
Normal 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
20
tests/.project
Normal 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
38
tests/CMakeLists.txt
Normal 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
24
tests/Scratchpad.test.c
Normal 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
36
tests/StringView.test.c
Normal 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
88
tests/ThreadPool.test.c
Normal 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
139
tests/argumentc.test.c
Normal 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
119
tests/regex.test.c
Normal 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(®ex, &allocator);
|
||||||
|
|
||||||
|
|
||||||
|
Regex_Destroy(®ex);
|
||||||
|
assert(allocator.reserved == 0);
|
||||||
|
Allocator_DestroySystemAllocator(&allocator);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
void testLiteral(void)
|
||||||
|
{
|
||||||
|
StringView test_string_0 = StringView_FromString("Hello World");
|
||||||
|
allocator_t allocator;
|
||||||
|
Allocator_CreateSystemAllocator(&allocator);
|
||||||
|
Regex regex;
|
||||||
|
assert(Regex_Create(®ex, &allocator) == EXIT_SUCCESS);
|
||||||
|
|
||||||
|
assert(Regex_Compile(®ex, test_string_0) == EXIT_SUCCESS);
|
||||||
|
|
||||||
|
RegexMatch match;
|
||||||
|
assert(Regex_FirstMatch(®ex, test_string_0, &match) == EXIT_SUCCESS);
|
||||||
|
assert(StringView_Equal(match.match, test_string_0));
|
||||||
|
|
||||||
|
Regex_Destroy(®ex);
|
||||||
|
assert(allocator.reserved == 0);
|
||||||
|
Allocator_DestroySystemAllocator(&allocator);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
void testBackslash(void)
|
||||||
|
{
|
||||||
|
StringView regex_string = StringView_FromString("Hello World\\\\");
|
||||||
|
StringView match_string = StringView_FromString("Hello World\\");
|
||||||
|
allocator_t allocator;
|
||||||
|
Allocator_CreateSystemAllocator(&allocator);
|
||||||
|
Regex regex;
|
||||||
|
assert(Regex_Create(®ex, &allocator) == EXIT_SUCCESS);
|
||||||
|
|
||||||
|
assert(Regex_Compile(®ex, regex_string) == EXIT_SUCCESS);
|
||||||
|
|
||||||
|
RegexMatch match;
|
||||||
|
assert(Regex_FirstMatch(®ex, match_string, &match) == EXIT_SUCCESS);
|
||||||
|
assert(StringView_Equal(match.match, match_string));
|
||||||
|
|
||||||
|
Regex_Destroy(®ex);
|
||||||
|
assert(allocator.reserved == 0);
|
||||||
|
Allocator_DestroySystemAllocator(&allocator);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
void testGroup(void)
|
||||||
|
{
|
||||||
|
StringView regex_string = StringView_FromString("(Hello) (World)");
|
||||||
|
StringView match_string = StringView_FromString("Hello World");
|
||||||
|
allocator_t allocator;
|
||||||
|
Allocator_CreateSystemAllocator(&allocator);
|
||||||
|
Regex regex;
|
||||||
|
assert(Regex_Create(®ex, &allocator) == EXIT_SUCCESS);
|
||||||
|
|
||||||
|
assert(Regex_Compile(®ex, regex_string) == EXIT_SUCCESS);
|
||||||
|
|
||||||
|
RegexMatch match;
|
||||||
|
assert(Regex_FirstMatch(®ex, match_string, &match) == EXIT_SUCCESS);
|
||||||
|
assert(StringView_Equal(match.match, match_string));
|
||||||
|
assert(RegexMatch_HaveNumberedCapture(&match, 1));
|
||||||
|
assert(RegexMatch_HaveNumberedCapture(&match, 2));
|
||||||
|
|
||||||
|
Regex_Destroy(®ex);
|
||||||
|
assert(allocator.reserved == 0);
|
||||||
|
Allocator_DestroySystemAllocator(&allocator);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
void testQuantifier(void)
|
||||||
|
{
|
||||||
|
StringView regex_string = StringView_FromString("\\??");
|
||||||
|
StringView match_string_0 = StringView_FromString("?");
|
||||||
|
StringView match_string_1 = StringView_FromString("");
|
||||||
|
allocator_t allocator;
|
||||||
|
Allocator_CreateSystemAllocator(&allocator);
|
||||||
|
|
||||||
|
Regex regex;
|
||||||
|
assert(Regex_Create(®ex, &allocator) == EXIT_SUCCESS);
|
||||||
|
|
||||||
|
assert(Regex_Compile(®ex, regex_string) == EXIT_SUCCESS);
|
||||||
|
|
||||||
|
RegexMatch match;
|
||||||
|
|
||||||
|
assert(Regex_FirstMatch(®ex, match_string_0, &match) == EXIT_SUCCESS);
|
||||||
|
assert(StringView_Equal(match.match, match_string_0));
|
||||||
|
|
||||||
|
assert(Regex_FirstMatch(®ex, match_string_1, &match) == EXIT_SUCCESS);
|
||||||
|
assert(StringView_Equal(match.match, match_string_1));
|
||||||
|
|
||||||
|
Regex_Destroy(®ex);
|
||||||
|
assert(allocator.reserved == 0);
|
||||||
|
Allocator_DestroySystemAllocator(&allocator);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
testLifetime();
|
||||||
|
testLiteral();
|
||||||
|
testBackslash();
|
||||||
|
testGroup();
|
||||||
|
testQuantifier();
|
||||||
|
return 0;
|
||||||
|
}
|
Loading…
Reference in a new issue