Reorganization
This commit is contained in:
453
code/arena.c
Normal file
453
code/arena.c
Normal file
@@ -0,0 +1,453 @@
|
||||
#include "arena.h"
|
||||
#ifndef MA_ASSERT
|
||||
#include <assert.h>
|
||||
#define MA_ASSERT(x) assert(x)
|
||||
#endif
|
||||
|
||||
#ifndef MA_MemoryZero
|
||||
#include <string.h>
|
||||
MA_API void MA_MemoryZero(void *p, size_t size) {
|
||||
memset(p, 0, size);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef MA_MemoryCopy
|
||||
#include <string.h>
|
||||
MA_API void MA_MemoryCopy(void *dst, void *src, size_t size) {
|
||||
memcpy(dst, src, size);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef MA_CMalloc
|
||||
#include <stdlib.h>
|
||||
#define MA_CMalloc(x) malloc(x)
|
||||
#define MA_CFree(x) free(x)
|
||||
#define MA_CRealloc(p, size) realloc(p, size)
|
||||
#endif
|
||||
|
||||
#ifndef MA_StaticFunc
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
#define MA_StaticFunc __attribute__((unused)) static
|
||||
#else
|
||||
#define MA_StaticFunc static
|
||||
#endif
|
||||
#endif
|
||||
|
||||
MA_API size_t MA_GetAlignOffset(size_t size, size_t align) {
|
||||
size_t mask = align - 1;
|
||||
size_t val = size & mask;
|
||||
if (val) {
|
||||
val = align - val;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
MA_API size_t MA_AlignUp(size_t size, size_t align) {
|
||||
size_t result = size + MA_GetAlignOffset(size, align);
|
||||
return result;
|
||||
}
|
||||
|
||||
MA_API size_t MA_AlignDown(size_t size, size_t align) {
|
||||
size += 1; // Make sure when align is 8 doesn't get rounded down to 0
|
||||
size_t result = size - (align - MA_GetAlignOffset(size, align));
|
||||
return result;
|
||||
}
|
||||
|
||||
MA_StaticFunc uint8_t *MV__AdvanceCommit(MV_Memory *m, size_t *commit_size, size_t page_size) {
|
||||
size_t aligned_up_commit = MA_AlignUp(*commit_size, page_size);
|
||||
size_t to_be_total_commited_size = aligned_up_commit + m->commit;
|
||||
size_t to_be_total_commited_size_clamped_to_reserve = MA_CLAMP_TOP(to_be_total_commited_size, m->reserve);
|
||||
size_t adjusted_to_boundary_commit = to_be_total_commited_size_clamped_to_reserve - m->commit;
|
||||
MA_ASSERT(adjusted_to_boundary_commit && "Reached the virtual memory reserved boundary");
|
||||
*commit_size = adjusted_to_boundary_commit;
|
||||
|
||||
if (adjusted_to_boundary_commit == 0) {
|
||||
return 0;
|
||||
}
|
||||
uint8_t *result = m->data + m->commit;
|
||||
return result;
|
||||
}
|
||||
|
||||
MA_API void MA_DeallocateStub(MA_Arena *arena, void *p) {}
|
||||
|
||||
MA_API void MA_PopToPos(MA_Arena *arena, size_t pos) {
|
||||
pos = MA_CLAMP_TOP(pos, arena->len);
|
||||
arena->len = pos;
|
||||
}
|
||||
|
||||
MA_API void *MA_PopSize(MA_Arena *arena, size_t size) {
|
||||
size = MA_CLAMP_TOP(size, arena->len);
|
||||
arena->len -= size;
|
||||
return arena->memory.data + arena->len;
|
||||
}
|
||||
|
||||
MA_API void MA_DeallocateArena(MA_Arena *arena) {
|
||||
MV_Deallocate(&arena->memory);
|
||||
}
|
||||
|
||||
MA_API void MA_Reset(MA_Arena *arena) {
|
||||
MA_PopToPos(arena, 0);
|
||||
}
|
||||
|
||||
MA_StaticFunc size_t MA__AlignLen(MA_Arena *a) {
|
||||
size_t align_offset = a->alignment ? MA_GetAlignOffset((uintptr_t)a->memory.data + (uintptr_t)a->len, a->alignment) : 0;
|
||||
size_t aligned = a->len + align_offset;
|
||||
return aligned;
|
||||
}
|
||||
|
||||
MA_API void MA_SetAlignment(MA_Arena *arena, int alignment) {
|
||||
arena->alignment = alignment;
|
||||
}
|
||||
|
||||
MA_API uint8_t *MA_GetTop(MA_Arena *a) {
|
||||
MA_ASSERT(a->memory.data); // arena needs to be inited
|
||||
return a->memory.data + a->len;
|
||||
}
|
||||
|
||||
MA_API void *MA_PushSizeNonZeroed(MA_Arena *a, size_t size) {
|
||||
size_t align_offset = a->alignment ? MA_GetAlignOffset((uintptr_t)a->memory.data + (uintptr_t)a->len, a->alignment) : 0;
|
||||
size_t aligned_len = a->len + align_offset;
|
||||
size_t size_with_alignment = size + align_offset;
|
||||
|
||||
if (a->len + size_with_alignment > a->memory.commit) {
|
||||
if (a->memory.reserve == 0) {
|
||||
#if MA_ZERO_IS_INITIALIZATION
|
||||
MA_Init(a);
|
||||
#else
|
||||
MA_ASSERT("Pushing on uninitialized arena");
|
||||
#endif
|
||||
}
|
||||
bool result = MV_Commit(&a->memory, size_with_alignment + MA_COMMIT_ADD_SIZE);
|
||||
MA_ASSERT(result && "Failed to commit memory");
|
||||
(void)result;
|
||||
}
|
||||
|
||||
uint8_t *result = a->memory.data + aligned_len;
|
||||
a->len += size_with_alignment;
|
||||
MA_ASSERT(a->len <= a->memory.commit);
|
||||
return (void *)result;
|
||||
}
|
||||
|
||||
MA_API void *MA_PushSize(MA_Arena *arena, size_t size) {
|
||||
void *result = MA_PushSizeNonZeroed(arena, size);
|
||||
MA_MemoryZero(result, size);
|
||||
return result;
|
||||
}
|
||||
|
||||
MA_API void MA_InitEx(MA_Arena *a, size_t reserve) {
|
||||
a->memory = MV_Reserve(reserve);
|
||||
a->alignment = MA_DEFAULT_ALIGNMENT;
|
||||
}
|
||||
|
||||
MA_API void MA_Init(MA_Arena *a) {
|
||||
MA_InitEx(a, MA_DEFAULT_RESERVE_SIZE);
|
||||
}
|
||||
|
||||
MA_API void MA_MakeSureInitialized(MA_Arena *a) {
|
||||
if (a->memory.data == 0) {
|
||||
MA_Init(a);
|
||||
}
|
||||
}
|
||||
|
||||
MA_API MA_Arena *MA_Bootstrap(void) {
|
||||
MA_Arena bootstrap_arena = {0};
|
||||
MA_Arena *arena = MA_PushStruct(&bootstrap_arena, MA_Arena);
|
||||
*arena = bootstrap_arena;
|
||||
return arena;
|
||||
}
|
||||
|
||||
MA_API M_Allocator MA_BootstrapExclusive(void) {
|
||||
MA_Arena bootstrap_arena = {0};
|
||||
MA_Arena *arena = MA_PushStruct(&bootstrap_arena, MA_Arena);
|
||||
*arena = bootstrap_arena;
|
||||
return MA_GetExclusiveAllocator(arena);
|
||||
}
|
||||
|
||||
MA_API void MA_InitFromBuffer(MA_Arena *arena, void *buffer, size_t size) {
|
||||
arena->memory.data = (uint8_t *)buffer;
|
||||
arena->memory.commit = size;
|
||||
arena->memory.reserve = size;
|
||||
arena->alignment = MA_DEFAULT_ALIGNMENT;
|
||||
}
|
||||
|
||||
MA_API MA_Arena MA_MakeFromBuffer(void *buffer, size_t size) {
|
||||
MA_Arena arena;
|
||||
MA_MemoryZero(&arena, sizeof(arena));
|
||||
MA_InitFromBuffer(&arena, buffer, size);
|
||||
return arena;
|
||||
}
|
||||
|
||||
MA_API MA_Arena MA_Create() {
|
||||
MA_Arena arena = {0};
|
||||
MA_Init(&arena);
|
||||
return arena;
|
||||
}
|
||||
|
||||
MA_API char *MA_PushStringCopy(MA_Arena *arena, char *p, size_t size) {
|
||||
char *copy_buffer = (char *)MA_PushSizeNonZeroed(arena, size + 1);
|
||||
MA_MemoryCopy(copy_buffer, p, size);
|
||||
copy_buffer[size] = 0;
|
||||
return copy_buffer;
|
||||
}
|
||||
|
||||
MA_API void *MA_PushCopy(MA_Arena *arena, void *p, size_t size) {
|
||||
void *copy_buffer = MA_PushSizeNonZeroed(arena, size);
|
||||
MA_MemoryCopy(copy_buffer, p, size);
|
||||
return copy_buffer;
|
||||
}
|
||||
|
||||
MA_API bool MA_IsPointerInside(MA_Arena *arena, void *p) {
|
||||
uintptr_t pointer = (uintptr_t)p;
|
||||
uintptr_t start = (uintptr_t)arena->memory.data;
|
||||
uintptr_t stop = start + (uintptr_t)arena->len;
|
||||
bool result = pointer >= start && pointer < stop;
|
||||
return result;
|
||||
}
|
||||
|
||||
MA_API MA_Arena MA_PushArena(MA_Arena *arena, size_t size) {
|
||||
MA_Arena result;
|
||||
MA_MemoryZero(&result, sizeof(result));
|
||||
result.memory.data = MA_PushArrayNonZeroed(arena, uint8_t, size);
|
||||
result.memory.commit = size;
|
||||
result.memory.reserve = size;
|
||||
result.alignment = arena->alignment;
|
||||
return result;
|
||||
}
|
||||
|
||||
MA_API MA_Checkpoint MA_Save(MA_Arena *arena) {
|
||||
MA_Checkpoint result;
|
||||
result.pos = arena->len;
|
||||
result.arena = arena;
|
||||
return result;
|
||||
}
|
||||
|
||||
MA_API void MA_Load(MA_Checkpoint checkpoint) {
|
||||
MA_PopToPos(checkpoint.arena, checkpoint.pos);
|
||||
}
|
||||
|
||||
MA_API void *M_AllocNonZeroed(M_Allocator allocator, size_t size) {
|
||||
void *p = allocator.p(allocator.obj, M_AllocatorOp_Allocate, NULL, size, 0);
|
||||
return p;
|
||||
}
|
||||
|
||||
MA_API void *M_Alloc(M_Allocator allocator, size_t size) {
|
||||
void *p = allocator.p(allocator.obj, M_AllocatorOp_Allocate, NULL, size, 0);
|
||||
MA_MemoryZero(p, size);
|
||||
return p;
|
||||
}
|
||||
|
||||
MA_API void *M_AllocCopy(M_Allocator allocator, void *p, size_t size) {
|
||||
void *copy_buffer = M_AllocNonZeroed(allocator, size);
|
||||
MA_MemoryCopy(copy_buffer, p, size);
|
||||
return copy_buffer;
|
||||
}
|
||||
|
||||
MA_API void M_Dealloc(M_Allocator allocator, void *p) {
|
||||
allocator.p(allocator.obj, M_AllocatorOp_Deallocate, p, 0, 0);
|
||||
}
|
||||
|
||||
MA_API void *M_ReallocNonZeroed(M_Allocator allocator, void *p, size_t size, size_t old_size) {
|
||||
void *result = allocator.p(allocator.obj, M_AllocatorOp_Reallocate, p, size, old_size);
|
||||
// @todo: add old_size? because we can't zero
|
||||
return result;
|
||||
}
|
||||
|
||||
MA_StaticFunc void *M_ClibAllocatorProc(void *allocator, M_AllocatorOp kind, void *p, size_t size, size_t old_size) {
|
||||
if (kind == M_AllocatorOp_Allocate) {
|
||||
return MA_CMalloc(size);
|
||||
}
|
||||
|
||||
if (kind == M_AllocatorOp_Deallocate) {
|
||||
MA_CFree(p);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (kind == M_AllocatorOp_Reallocate) {
|
||||
return MA_CRealloc(p, size);
|
||||
}
|
||||
|
||||
MA_ASSERT("MA_Arena invalid codepath");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
MA_API void *MA_AllocatorProc(void *allocator, M_AllocatorOp kind, void *p, size_t size, size_t old_size) {
|
||||
if (kind == M_AllocatorOp_Allocate) {
|
||||
return MA_PushSizeNonZeroed((MA_Arena *)allocator, size);
|
||||
}
|
||||
|
||||
else if (kind == M_AllocatorOp_Reallocate) {
|
||||
void *new_p = MA_PushSizeNonZeroed((MA_Arena *)allocator, size);
|
||||
MA_MemoryCopy(new_p, p, old_size);
|
||||
return new_p;
|
||||
}
|
||||
|
||||
else if (kind == M_AllocatorOp_Deallocate) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
MA_ASSERT("MA_Arena invalid codepath");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
MA_API void *MA_ExclusiveAllocatorProc(void *allocator, M_AllocatorOp kind, void *p, size_t size, size_t old_size) {
|
||||
MA_Arena *arena = (MA_Arena *)allocator;
|
||||
if (kind == M_AllocatorOp_Reallocate) {
|
||||
if (size > old_size) {
|
||||
size_t size_to_push = size - old_size;
|
||||
void *result = MA_PushSizeNonZeroed(arena, size_to_push);
|
||||
if (!p) p = result;
|
||||
return p;
|
||||
}
|
||||
}
|
||||
|
||||
if (kind == M_AllocatorOp_Deallocate) {
|
||||
MA_DeallocateArena(arena);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
MA_ASSERT("MA_Arena invalid codepath");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
MA_API M_Allocator MA_GetExclusiveAllocator(MA_Arena *arena) {
|
||||
M_Allocator allocator = {arena, MA_ExclusiveAllocatorProc};
|
||||
return allocator;
|
||||
}
|
||||
|
||||
MA_API M_Allocator MA_GetAllocator(MA_Arena *arena) {
|
||||
M_Allocator allocator = {arena, MA_AllocatorProc};
|
||||
return allocator;
|
||||
}
|
||||
|
||||
MA_API M_Allocator M_GetSystemAllocator(void) {
|
||||
M_Allocator allocator;
|
||||
allocator.obj = 0;
|
||||
allocator.p = M_ClibAllocatorProc;
|
||||
return allocator;
|
||||
}
|
||||
|
||||
#ifndef MA_DISABLE_SCRATCH
|
||||
MA_THREAD_LOCAL MA_Arena MA_ScratchArenaPool[4];
|
||||
|
||||
MA_API MA_Checkpoint MA_GetScratchEx(MA_Arena **conflicts, int conflict_count) {
|
||||
MA_Arena *unoccupied = 0;
|
||||
for (int i = 0; i < MA_LENGTHOF(MA_ScratchArenaPool); i += 1) {
|
||||
MA_Arena *from_pool = MA_ScratchArenaPool + i;
|
||||
unoccupied = from_pool;
|
||||
for (int conflict_i = 0; conflict_i < conflict_count; conflict_i += 1) {
|
||||
MA_Arena *from_conflict = conflicts[conflict_i];
|
||||
if (from_pool == from_conflict) {
|
||||
unoccupied = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (unoccupied) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
MA_ASSERT(unoccupied);
|
||||
MA_Checkpoint result = MA_Save(unoccupied);
|
||||
return result;
|
||||
}
|
||||
|
||||
MA_API MA_Checkpoint MA_GetScratch(void) {
|
||||
MA_Checkpoint result = MA_Save(MA_ScratchArenaPool + 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
MA_API MA_Checkpoint MA_GetScratch1(MA_Arena *conflict) {
|
||||
MA_Arena *conflicts[] = {conflict};
|
||||
return MA_GetScratchEx(conflicts, 1);
|
||||
}
|
||||
#endif // MA_DISABLE_SCRATCH
|
||||
|
||||
#ifdef _WIN32
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#include <windows.h>
|
||||
|
||||
const size_t MV__WIN32_PAGE_SIZE = 4096;
|
||||
|
||||
MA_API MV_Memory MV_Reserve(size_t size) {
|
||||
MV_Memory result;
|
||||
MA_MemoryZero(&result, sizeof(result));
|
||||
size_t adjusted_size = MA_AlignUp(size, MV__WIN32_PAGE_SIZE);
|
||||
result.data = (uint8_t *)VirtualAlloc(0, adjusted_size, MEM_RESERVE, PAGE_READWRITE);
|
||||
MA_ASSERT(result.data && "Failed to reserve virtual memory");
|
||||
result.reserve = adjusted_size;
|
||||
return result;
|
||||
}
|
||||
|
||||
MA_API bool MV_Commit(MV_Memory *m, size_t commit) {
|
||||
uint8_t *pointer = MV__AdvanceCommit(m, &commit, MV__WIN32_PAGE_SIZE);
|
||||
if (pointer) {
|
||||
void *result = VirtualAlloc(pointer, commit, MEM_COMMIT, PAGE_READWRITE);
|
||||
MA_ASSERT(result && "Failed to commit more memory");
|
||||
if (result) {
|
||||
m->commit += commit;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
MA_API void MV_Deallocate(MV_Memory *m) {
|
||||
BOOL result = VirtualFree(m->data, 0, MEM_RELEASE);
|
||||
MA_ASSERT(result != 0 && "Failed to release MV_Memory");
|
||||
}
|
||||
|
||||
MA_API bool MV_DecommitPos(MV_Memory *m, size_t pos) {
|
||||
size_t aligned = MA_AlignDown(pos, MV__WIN32_PAGE_SIZE);
|
||||
size_t adjusted_pos = MA_CLAMP_TOP(aligned, m->commit);
|
||||
size_t size_to_decommit = m->commit - adjusted_pos;
|
||||
if (size_to_decommit) {
|
||||
uint8_t *base_address = m->data + adjusted_pos;
|
||||
BOOL result = VirtualFree(base_address, size_to_decommit, MEM_DECOMMIT);
|
||||
if (result) {
|
||||
m->commit -= size_to_decommit;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#elif __unix__ // _WIN32
|
||||
#include <sys/mman.h>
|
||||
#define MV__UNIX_PAGE_SIZE 4096
|
||||
MA_API MV_Memory MV_Reserve(size_t size) {
|
||||
MV_Memory result = {};
|
||||
size_t size_aligned = MA_AlignUp(size, MV__UNIX_PAGE_SIZE);
|
||||
result.data = (uint8_t *)mmap(0, size_aligned, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||
MA_ASSERT(result.data && "Failed to reserve memory using mmap!!");
|
||||
if (result.data) {
|
||||
result.reserve = size_aligned;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
MA_API bool MV_Commit(MV_Memory *m, size_t commit) {
|
||||
uint8_t *pointer = MV__AdvanceCommit(m, &commit, MV__UNIX_PAGE_SIZE);
|
||||
if (pointer) {
|
||||
int mprotect_result = mprotect(pointer, commit, PROT_READ | PROT_WRITE);
|
||||
MA_ASSERT(mprotect_result == 0 && "Failed to commit more memory using mmap");
|
||||
if (mprotect_result == 0) {
|
||||
m->commit += commit;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
MA_API void MV_Deallocate(MV_Memory *m) {
|
||||
int result = munmap(m->data, m->reserve);
|
||||
MA_ASSERT(result == 0 && "Failed to release virtual memory using munmap");
|
||||
}
|
||||
#else
|
||||
#error "unhandled arena platform"
|
||||
#endif // __unix__
|
||||
192
code/arena.h
Normal file
192
code/arena.h
Normal file
@@ -0,0 +1,192 @@
|
||||
#ifndef MA_HEADER
|
||||
#define MA_HEADER
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#define MA_KIB(x) ((x##ull) * 1024ull)
|
||||
#define MA_MIB(x) (MA_KIB(x) * 1024ull)
|
||||
#define MA_GIB(x) (MA_MIB(x) * 1024ull)
|
||||
#define MA_TIB(x) (MA_GIB(x) * 1024ull)
|
||||
|
||||
typedef struct MV_Memory MV_Memory;
|
||||
typedef struct MA_Checkpoint MA_Checkpoint;
|
||||
typedef struct MA_Arena MA_Arena;
|
||||
typedef struct M_Allocator M_Allocator;
|
||||
|
||||
#ifndef MA_DEFAULT_RESERVE_SIZE
|
||||
#define MA_DEFAULT_RESERVE_SIZE MA_GIB(1)
|
||||
#endif
|
||||
|
||||
#ifndef MA_DEFAULT_ALIGNMENT
|
||||
#define MA_DEFAULT_ALIGNMENT 8
|
||||
#endif
|
||||
|
||||
#ifndef MA_COMMIT_ADD_SIZE
|
||||
#define MA_COMMIT_ADD_SIZE MA_MIB(4)
|
||||
#endif
|
||||
|
||||
#ifndef MA_ZERO_IS_INITIALIZATION
|
||||
#define MA_ZERO_IS_INITIALIZATION 1
|
||||
#endif
|
||||
|
||||
#ifndef MA_API
|
||||
#ifdef __cplusplus
|
||||
#define MA_API extern "C"
|
||||
#else
|
||||
#define MA_API
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef MA_THREAD_LOCAL
|
||||
#if defined(__cplusplus) && __cplusplus >= 201103L
|
||||
#define MA_THREAD_LOCAL thread_local
|
||||
#elif defined(__GNUC__)
|
||||
#define MA_THREAD_LOCAL __thread
|
||||
#elif defined(_MSC_VER)
|
||||
#define MA_THREAD_LOCAL __declspec(thread)
|
||||
#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L && !defined(__STDC_NO_THREADS__)
|
||||
#define MA_THREAD_LOCAL _Thread_local
|
||||
#elif defined(__TINYC__)
|
||||
#define MA_THREAD_LOCAL _Thread_local
|
||||
#else
|
||||
#error Couldnt figure out thread local, needs to be provided manually
|
||||
#endif
|
||||
#endif
|
||||
|
||||
typedef enum M_AllocatorOp {
|
||||
M_AllocatorOp_Invalid,
|
||||
M_AllocatorOp_Allocate,
|
||||
M_AllocatorOp_Deallocate,
|
||||
M_AllocatorOp_Reallocate,
|
||||
} M_AllocatorOp;
|
||||
|
||||
typedef void *M_AllocatorProc(void *allocator, M_AllocatorOp kind, void *p, size_t size, size_t old_size);
|
||||
MA_API void *MA_AllocatorProc(void *allocator, M_AllocatorOp kind, void *p, size_t size, size_t old_size);
|
||||
MA_API void *MA_ExclusiveAllocatorProc(void *allocator, M_AllocatorOp kind, void *p, size_t size, size_t old_size);
|
||||
|
||||
struct M_Allocator {
|
||||
void *obj;
|
||||
M_AllocatorProc *p;
|
||||
};
|
||||
|
||||
struct MV_Memory {
|
||||
size_t commit;
|
||||
size_t reserve;
|
||||
uint8_t *data;
|
||||
};
|
||||
|
||||
struct MA_Arena {
|
||||
MV_Memory memory;
|
||||
int alignment;
|
||||
size_t len;
|
||||
|
||||
#ifdef __cplusplus
|
||||
operator M_Allocator() { return {this, MA_AllocatorProc}; }
|
||||
#endif
|
||||
};
|
||||
|
||||
struct MA_Checkpoint {
|
||||
MA_Arena *arena;
|
||||
size_t pos;
|
||||
};
|
||||
|
||||
#define MA_PushArrayNonZeroed(a, T, c) (T *)MA_PushSizeNonZeroed(a, sizeof(T) * (c))
|
||||
#define MA_PushStructNonZeroed(a, T) (T *)MA_PushSizeNonZeroed(a, sizeof(T))
|
||||
#define MA_PushStruct(a, T) (T *)MA_PushSize(a, sizeof(T))
|
||||
#define MA_PushArray(a, T, c) (T *)MA_PushSize(a, sizeof(T) * (c))
|
||||
#define MA_PushStructCopy(a, T, p) (T *)MA_PushCopy(a, (p), sizeof(T))
|
||||
#define MA_CheckpointScope(name, InArena) for (MA_Checkpoint name = MA_Save(InArena); name.arena; (MA_Load(name), name.arena = 0))
|
||||
|
||||
#define M_AllocStruct(a, T) (T *)M_Alloc((a), sizeof(T))
|
||||
#define M_AllocArray(a, T, c) (T *)M_Alloc((a), sizeof(T) * (c))
|
||||
#define M_AllocStructCopy(a, T, p) (T *)M_PushCopy(a, (p), sizeof(T))
|
||||
|
||||
#define MA_IS_POW2(x) (((x) & ((x)-1)) == 0)
|
||||
#define MA_MIN(x, y) ((x) <= (y) ? (x) : (y))
|
||||
#define MA_MAX(x, y) ((x) >= (y) ? (x) : (y))
|
||||
#define MA_LENGTHOF(x) ((int64_t)((sizeof(x) / sizeof((x)[0]))))
|
||||
|
||||
#define MA_CLAMP_TOP(x, max) ((x) >= (max) ? (max) : (x))
|
||||
#define MA_CLAMP_BOT(x, min) ((x) <= (min) ? (min) : (x))
|
||||
#define MA_CLAMP(x, min, max) ((x) >= (max) ? (max) : (x) <= (min) ? (min) \
|
||||
: (x))
|
||||
// clang-format off
|
||||
MA_API void MA_InitEx(MA_Arena *a, size_t reserve);
|
||||
MA_API void MA_Init(MA_Arena *a);
|
||||
MA_API MA_Arena MA_Create();
|
||||
MA_API void MA_MakeSureInitialized(MA_Arena *a);
|
||||
MA_API void MA_InitFromBuffer(MA_Arena *arena, void *buffer, size_t size);
|
||||
MA_API MA_Arena MA_MakeFromBuffer(void *buffer, size_t size);
|
||||
MA_API MA_Arena * MA_Bootstrap(void);
|
||||
MA_API M_Allocator MA_BootstrapExclusive(void);
|
||||
MA_API MA_Arena MA_PushArena(MA_Arena *arena, size_t size);
|
||||
|
||||
MA_API void * MA_PushSizeNonZeroed(MA_Arena *a, size_t size);
|
||||
MA_API void * MA_PushSize(MA_Arena *arena, size_t size);
|
||||
MA_API char * MA_PushStringCopy(MA_Arena *arena, char *p, size_t size);
|
||||
MA_API void * MA_PushCopy(MA_Arena *arena, void *p, size_t size);
|
||||
MA_API MA_Checkpoint MA_Save(MA_Arena *arena);
|
||||
MA_API void MA_Load(MA_Checkpoint checkpoint);
|
||||
|
||||
MA_API void MA_PopToPos(MA_Arena *arena, size_t pos);
|
||||
MA_API void * MA_PopSize(MA_Arena *arena, size_t size);
|
||||
MA_API void MA_DeallocateArena(MA_Arena *arena);
|
||||
MA_API void MA_DeallocateStub(MA_Arena *arena, void *p);
|
||||
MA_API void MA_Reset(MA_Arena *arena);
|
||||
|
||||
MA_API void MA_MemoryZero(void *p, size_t size);
|
||||
MA_API void MA_MemoryCopy(void *dst, void *src, size_t size);
|
||||
MA_API size_t MA_GetAlignOffset(size_t size, size_t align);
|
||||
MA_API size_t MA_AlignUp(size_t size, size_t align);
|
||||
MA_API size_t MA_AlignDown(size_t size, size_t align);
|
||||
|
||||
MA_API bool MA_IsPointerInside(MA_Arena *arena, void *p);
|
||||
MA_API void MA_SetAlignment(MA_Arena *arena, int alignment);
|
||||
MA_API uint8_t * MA_GetTop(MA_Arena *a);
|
||||
|
||||
MA_API MV_Memory MV_Reserve(size_t size);
|
||||
MA_API bool MV_Commit(MV_Memory *m, size_t commit);
|
||||
MA_API void MV_Deallocate(MV_Memory *m);
|
||||
MA_API bool MV_DecommitPos(MV_Memory *m, size_t pos);
|
||||
|
||||
MA_API void * M_AllocNonZeroed(M_Allocator allocator, size_t size);
|
||||
MA_API void * M_Alloc(M_Allocator allocator, size_t size);
|
||||
MA_API void * M_AllocCopy(M_Allocator allocator, void *p, size_t size);
|
||||
MA_API void * M_ReallocNonZeroed(M_Allocator allocator, void *p, size_t size, size_t old_size);
|
||||
MA_API void M_Dealloc(M_Allocator allocator, void *p);
|
||||
|
||||
MA_API M_Allocator M_GetSystemAllocator(void);
|
||||
MA_API M_Allocator MA_GetExclusiveAllocator(MA_Arena *arena);
|
||||
MA_API M_Allocator MA_GetAllocator(MA_Arena *arena);
|
||||
// clang-format on
|
||||
|
||||
#ifndef MA_DISABLE_SCRATCH
|
||||
extern MA_THREAD_LOCAL MA_Arena MA_ScratchArenaPool[];
|
||||
MA_API MA_Checkpoint MA_GetScratchEx(MA_Arena **conflicts, int conflict_count);
|
||||
MA_API MA_Checkpoint MA_GetScratch(void);
|
||||
MA_API MA_Checkpoint MA_GetScratch1(MA_Arena *conflict);
|
||||
|
||||
#define MA_ScratchScope(x) for (MA_Checkpoint x = MA_GetScratch(); x.arena; (MA_ReleaseScratch(x), x.arena = 0))
|
||||
#define MA_ReleaseScratch MA_Load
|
||||
|
||||
#if defined(__cplusplus)
|
||||
struct MA_Scratch {
|
||||
MA_Checkpoint checkpoint;
|
||||
MA_Scratch() { this->checkpoint = MA_GetScratch(); }
|
||||
MA_Scratch(MA_Checkpoint conflict) { this->checkpoint = MA_GetScratch1(conflict.arena); }
|
||||
MA_Scratch(MA_Checkpoint c1, MA_Checkpoint c2) {
|
||||
MA_Arena *conflicts[] = {c1.arena, c2.arena};
|
||||
this->checkpoint = MA_GetScratchEx(conflicts, 2);
|
||||
}
|
||||
~MA_Scratch() { MA_Load(checkpoint); }
|
||||
operator MA_Arena *() { return checkpoint.arena; }
|
||||
operator M_Allocator() { return MA_GetAllocator(checkpoint.arena); }
|
||||
|
||||
private: // @Note: Disable copy constructors, cause its error prone
|
||||
MA_Scratch(MA_Scratch &arena);
|
||||
MA_Scratch(MA_Scratch &arena, MA_Scratch &a2);
|
||||
};
|
||||
#endif // __cplusplus
|
||||
#endif // MA_DISABLE_SCRATCH
|
||||
#endif // MA_HEADER
|
||||
303
code/array.hpp
Normal file
303
code/array.hpp
Normal file
@@ -0,0 +1,303 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef ARRAY_REALLOCATE
|
||||
#include <stdlib.h>
|
||||
#define ARRAY_REALLOCATE(allocator, p, size, old_size) realloc(p, size)
|
||||
#define ARRAY_DEALLOCATE(allocator, p) free(p)
|
||||
#endif
|
||||
|
||||
#ifndef ARRAY_ASSERT
|
||||
#include <assert.h>
|
||||
#define ARRAY_ASSERT(x) assert(x)
|
||||
#endif
|
||||
|
||||
#ifndef ARRAY_MemoryMove
|
||||
#include <string.h>
|
||||
#define ARRAY_MemoryMove(dst, src, size) memmove(dst, src, size)
|
||||
#endif
|
||||
|
||||
#ifndef ARRAY_Allocator
|
||||
#define ARRAY_Allocator void *
|
||||
#endif
|
||||
|
||||
// Example:
|
||||
// #define ARRAY_SET_DEFAULT_ALLOCATOR if (!allocator) allocator = global_heap;
|
||||
#ifndef ARRAY_SET_DEFAULT_ALLOCATOR
|
||||
#define ARRAY_SET_DEFAULT_ALLOCATOR
|
||||
#endif
|
||||
|
||||
// Iterating and removing elements
|
||||
//
|
||||
// ForArrayRemovable(array) {
|
||||
// ForArrayRemovablePrepare(array);
|
||||
// if (it == 4) ForArrayRemovableDeclare();
|
||||
// }
|
||||
//
|
||||
#ifdef DEFER_HEADER
|
||||
#define ForArrayRemovable(a) for (int __i = 0; __i < (a).len; __i += 1)
|
||||
#define ForArrayRemovablePrepare(a) \
|
||||
auto &it = (a)[__i]; \
|
||||
bool remove_it = false; \
|
||||
defer { \
|
||||
if (remove_it) { \
|
||||
(a).ordered_remove(it); \
|
||||
__i -= 1; \
|
||||
} \
|
||||
}
|
||||
#define ForArrayRemovableDeclare() (remove_it = true)
|
||||
#endif
|
||||
|
||||
#ifndef For
|
||||
#define For2(it, array) for(auto &it : (array))
|
||||
#define For(array) For2(it, array)
|
||||
#endif
|
||||
|
||||
template <class T>
|
||||
struct Array {
|
||||
ARRAY_Allocator allocator;
|
||||
T *data;
|
||||
int cap, len;
|
||||
|
||||
T &operator[](int index) {
|
||||
ARRAY_ASSERT(index >= 0 && index < len);
|
||||
return data[index];
|
||||
}
|
||||
|
||||
bool is_first(T &item) { return &item == first(); }
|
||||
bool is_last(T &item) { return &item == last(); }
|
||||
|
||||
bool contains(T &item) {
|
||||
bool result = &item >= data && &item < data + len;
|
||||
return result;
|
||||
}
|
||||
|
||||
int get_index(T &item) {
|
||||
ARRAY_ASSERT((data <= &item) && ((data + len) > &item));
|
||||
size_t offset = &item - data;
|
||||
return (int)offset;
|
||||
}
|
||||
|
||||
void add(const T &item) {
|
||||
try_growing();
|
||||
data[len++] = item;
|
||||
}
|
||||
|
||||
// Struct needs to have 'value_to_sort_by' field
|
||||
void sorted_insert_decreasing(T item) {
|
||||
int insert_index = -1;
|
||||
For(*this) {
|
||||
if (it.value_to_sort_by <= item.value_to_sort_by) {
|
||||
insert_index = get_index(it);
|
||||
insert(item, insert_index);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (insert_index == -1) {
|
||||
add(item);
|
||||
}
|
||||
}
|
||||
|
||||
void bounded_add(T item) {
|
||||
ARRAY_ASSERT(len + 1 <= cap);
|
||||
try_growing(); // in case of error
|
||||
data[len++] = item;
|
||||
}
|
||||
|
||||
T *alloc(const T &item) {
|
||||
try_growing();
|
||||
T *ref = data + len++;
|
||||
*ref = item;
|
||||
return ref;
|
||||
}
|
||||
|
||||
T *alloc() {
|
||||
try_growing();
|
||||
T *ref = data + len++;
|
||||
*ref = {};
|
||||
return ref;
|
||||
}
|
||||
|
||||
T *alloc_multiple(int size) {
|
||||
try_growing_to_fit_item_count(size);
|
||||
T *result = data + len;
|
||||
len += size;
|
||||
return result;
|
||||
}
|
||||
|
||||
void add_array(T *items, int item_count) {
|
||||
for (int i = 0; i < item_count; i += 1) {
|
||||
add(items[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void add_array(Array<T> items) {
|
||||
add_array(items.data, items.len);
|
||||
}
|
||||
|
||||
void reserve(int size) {
|
||||
if (size > cap) {
|
||||
ARRAY_SET_DEFAULT_ALLOCATOR;
|
||||
|
||||
void *p = ARRAY_REALLOCATE(allocator, data, size * sizeof(T), cap * sizeof(T));
|
||||
ARRAY_ASSERT(p);
|
||||
|
||||
data = (T *)p;
|
||||
cap = size;
|
||||
}
|
||||
}
|
||||
|
||||
void init(ARRAY_Allocator allocator, int size) {
|
||||
len = 0;
|
||||
cap = 0;
|
||||
data = 0;
|
||||
this->allocator = allocator;
|
||||
reserve(size);
|
||||
}
|
||||
|
||||
void reset() {
|
||||
len = 0;
|
||||
}
|
||||
|
||||
T pop() {
|
||||
ARRAY_ASSERT(len > 0);
|
||||
return data[--len];
|
||||
}
|
||||
|
||||
void unordered_remove(T &item) { // DONT USE IN LOOPS !!!!
|
||||
ARRAY_ASSERT(len > 0);
|
||||
ARRAY_ASSERT(&item >= begin() && &item < end());
|
||||
item = data[--len];
|
||||
}
|
||||
|
||||
void unordered_remove_index(int index) {
|
||||
ARRAY_ASSERT(index >= 0 && index < len);
|
||||
data[index] = data[--len];
|
||||
}
|
||||
|
||||
int get_index(const T &item) {
|
||||
ptrdiff_t index = (ptrdiff_t)(&item - data);
|
||||
ARRAY_ASSERT(index >= 0 && index < len);
|
||||
// ARRAY_ASSERT(index > INT_MIN && index < INT_MAX);
|
||||
return (int)index;
|
||||
}
|
||||
|
||||
void ordered_remove(T &item) { // DONT USE IN LOOPS !!!
|
||||
ARRAY_ASSERT(len > 0);
|
||||
ARRAY_ASSERT(&item >= begin() && &item < end());
|
||||
int index = get_index(item);
|
||||
ordered_remove_index(index);
|
||||
}
|
||||
|
||||
void ordered_remove_index(int index) {
|
||||
ARRAY_ASSERT(index >= 0 && index < len);
|
||||
int right_len = len - index - 1;
|
||||
ARRAY_MemoryMove(data + index, data + index + 1, right_len * sizeof(T));
|
||||
len -= 1;
|
||||
}
|
||||
|
||||
void insert(T item, int index) {
|
||||
if (index == len) {
|
||||
add(item);
|
||||
return;
|
||||
}
|
||||
|
||||
ARRAY_ASSERT(index < len);
|
||||
ARRAY_ASSERT(index >= 0);
|
||||
|
||||
try_growing();
|
||||
int right_len = len - index;
|
||||
ARRAY_MemoryMove(data + index + 1, data + index, sizeof(T) * right_len);
|
||||
data[index] = item;
|
||||
len += 1;
|
||||
}
|
||||
|
||||
void dealloc() {
|
||||
if (data) ARRAY_DEALLOCATE(allocator, data);
|
||||
data = 0;
|
||||
len = cap = 0;
|
||||
}
|
||||
|
||||
Array<T> copy(ARRAY_Allocator allocator) {
|
||||
Array result = {};
|
||||
result.allocator = allocator;
|
||||
result.reserve(cap);
|
||||
|
||||
ARRAY_MemoryMove(result.data, data, sizeof(T) * len);
|
||||
result.len = len;
|
||||
return result;
|
||||
}
|
||||
|
||||
Array<T> tight_copy(ARRAY_Allocator allocator) {
|
||||
Array result = {};
|
||||
result.allocator = allocator;
|
||||
result.reserve(len);
|
||||
|
||||
ARRAY_MemoryMove(result.data, data, sizeof(T) * len);
|
||||
result.len = len;
|
||||
return result;
|
||||
}
|
||||
|
||||
T *first() {
|
||||
ARRAY_ASSERT(len > 0);
|
||||
return data;
|
||||
}
|
||||
T *last() {
|
||||
ARRAY_ASSERT(len > 0);
|
||||
return data + len - 1;
|
||||
}
|
||||
T *front() {
|
||||
ARRAY_ASSERT(len > 0);
|
||||
return data;
|
||||
}
|
||||
T *back() {
|
||||
ARRAY_ASSERT(len > 0);
|
||||
return data + len - 1;
|
||||
}
|
||||
T *begin() { return data; }
|
||||
T *end() { return data + len; }
|
||||
|
||||
// for (auto it = integers.begin(), end = integers.end(); it != end; ++it)
|
||||
struct Reverse_Iter {
|
||||
T *data;
|
||||
Array<T> *arr;
|
||||
|
||||
Reverse_Iter operator++(int) {
|
||||
Reverse_Iter ret = *this;
|
||||
data -= 1;
|
||||
return ret;
|
||||
}
|
||||
Reverse_Iter &operator++() {
|
||||
data -= 1;
|
||||
return *this;
|
||||
}
|
||||
|
||||
T &operator*() { return data[0]; }
|
||||
T *operator->() { return data; }
|
||||
|
||||
friend bool operator==(const Reverse_Iter &a, const Reverse_Iter &b) { return a.data == b.data; };
|
||||
friend bool operator!=(const Reverse_Iter &a, const Reverse_Iter &b) { return a.data != b.data; };
|
||||
|
||||
Reverse_Iter begin() { return Reverse_Iter{arr->end() - 1, arr}; }
|
||||
Reverse_Iter end() { return Reverse_Iter{arr->begin() - 1, arr}; }
|
||||
};
|
||||
|
||||
Reverse_Iter reverse() { return {end() - 1, this}; }
|
||||
|
||||
void try_growing() {
|
||||
if (len + 1 > cap) {
|
||||
int new_size = cap * 2;
|
||||
if (new_size < 16) new_size = 16;
|
||||
|
||||
reserve(new_size);
|
||||
}
|
||||
}
|
||||
|
||||
void try_growing_to_fit_item_count(int item_count) {
|
||||
if (len + item_count > cap) {
|
||||
int new_size = (cap + item_count) * 2;
|
||||
if (new_size < 16) new_size = 16;
|
||||
reserve(new_size);
|
||||
}
|
||||
}
|
||||
};
|
||||
331
code/build_lib.cpp
Normal file
331
code/build_lib.cpp
Normal file
@@ -0,0 +1,331 @@
|
||||
/*
|
||||
----------------- CL.EXE -----------------
|
||||
FLAGS = /MP /Zi /FC /WX /W3 /wd4200 /diagnostics:column /nologo -D_CRT_SECURE_NO_WARNINGS /GF /Gm- /Oi
|
||||
LINK = /link /incremental:no
|
||||
STD_OFF = /GR- /EHa-
|
||||
STD_ON = /EHsc
|
||||
DEBUG = -Od -D_DEBUG -MDd -fsanitize=address -RTC1
|
||||
DEBUG_LINK = -NODEFAULTLIB:LIBCMT
|
||||
RELEASE = -O2 -MT -DNDEBUG -GL
|
||||
RELEASE_LINK = -opt:ref -opt:icf
|
||||
----------------- CL.EXE -----------------
|
||||
/FC = Print full paths in diagnostics
|
||||
/Gm- = Old feature, 'minimal compilation', in case it's not off by default
|
||||
/GF = Pools strings as read-only. If you try to modify strings under /GF, an application error occurs.
|
||||
/Oi = Replaces some function calls with intrinsic
|
||||
/MP = Multithreaded compilation
|
||||
/GR- = Disable runtime type information
|
||||
/EHa- = Disable exceptions
|
||||
/EHsc = Enable exceptions
|
||||
/MT = Link static libc. The 'd' means debug version
|
||||
/MD = Link dynamic libc. The 'd' means debug version
|
||||
/GL = Whole program optimization
|
||||
/RTC1 = runtime error checks
|
||||
/opt:ref = eliminates functions and data that are never referenced
|
||||
/opt:icf = eliminates redundant 'COMDAT's
|
||||
*/
|
||||
|
||||
#ifndef _CRT_SECURE_NO_WARNINGS
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
#endif
|
||||
|
||||
#include "../core.c"
|
||||
|
||||
#define CL_Arena MA_Arena
|
||||
#define CL_PushSize MA_PushSizeNonZeroed
|
||||
#define CL_ASSERT IO_Assert
|
||||
#define CL_VSNPRINTF stbsp_vsnprintf
|
||||
#define CL_SNPRINTF stbsp_snprintf
|
||||
#define AND_CL_STRING_TERMINATE_ON_NEW_LINE
|
||||
#include "clexer.c"
|
||||
|
||||
#define SRC_CACHE_ENTRY_COUNT 1024
|
||||
struct SRC_CacheEntry {
|
||||
uint64_t filepath_hash;
|
||||
uint64_t file_hash;
|
||||
uint64_t includes_hash;
|
||||
uint64_t total_hash;
|
||||
};
|
||||
|
||||
struct SRC_Cache {
|
||||
int entry_cap;
|
||||
int entry_len;
|
||||
SRC_CacheEntry entries[SRC_CACHE_ENTRY_COUNT];
|
||||
};
|
||||
|
||||
double SRC_Time;
|
||||
SRC_Cache *SRC_InMemoryCache;
|
||||
SRC_Cache *SRC_FromFileCache;
|
||||
CL_ArenaTuple SRC_ArenaTuple;
|
||||
S8_String SRC_CacheFilename;
|
||||
MA_Arena PernamentArena;
|
||||
MA_Arena *Perm = &PernamentArena;
|
||||
CL_SearchPaths SRC_SearchPaths = {}; // @todo;
|
||||
|
||||
void SRC_InitCache(MA_Arena *arena, S8_String cachefilename) {
|
||||
SRC_CacheFilename = cachefilename;
|
||||
CL_InitDefaultTuple(&SRC_ArenaTuple);
|
||||
|
||||
SRC_InMemoryCache = MA_PushStruct(arena, SRC_Cache);
|
||||
SRC_InMemoryCache->entry_cap = SRC_CACHE_ENTRY_COUNT;
|
||||
|
||||
SRC_FromFileCache = MA_PushStruct(arena, SRC_Cache);
|
||||
SRC_FromFileCache->entry_cap = SRC_CACHE_ENTRY_COUNT;
|
||||
|
||||
S8_String cache = OS_ReadFile(arena, SRC_CacheFilename);
|
||||
if (cache.len) MA_MemoryCopy(SRC_FromFileCache, cache.str, cache.len);
|
||||
}
|
||||
|
||||
void SRC_SaveCache() {
|
||||
S8_String dump = S8_Make((char *)SRC_InMemoryCache, sizeof(SRC_Cache));
|
||||
OS_WriteFile(SRC_CacheFilename, dump);
|
||||
}
|
||||
|
||||
SRC_CacheEntry *SRC_AddHash(uint64_t filepath, uint64_t file, uint64_t includes) {
|
||||
IO_Assert(SRC_InMemoryCache->entry_len + 1 < SRC_InMemoryCache->entry_cap);
|
||||
SRC_CacheEntry *result = SRC_InMemoryCache->entries + SRC_InMemoryCache->entry_len++;
|
||||
result->filepath_hash = filepath;
|
||||
result->file_hash = file;
|
||||
result->includes_hash = includes;
|
||||
result->total_hash = HashBytes(result, sizeof(uint64_t) * 3);
|
||||
return result;
|
||||
}
|
||||
|
||||
SRC_CacheEntry *SRC_FindCache(SRC_Cache *cache, uint64_t filepath_hash) {
|
||||
for (int cache_i = 0; cache_i < cache->entry_len; cache_i += 1) {
|
||||
SRC_CacheEntry *it = cache->entries + cache_i;
|
||||
if (it->filepath_hash == filepath_hash) {
|
||||
return it;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
SRC_CacheEntry *SRC_HashFile(S8_String file, char *parent_file) {
|
||||
char *resolved_file = CL_ResolveFilepath(Perm, &SRC_SearchPaths, file.str, parent_file, false);
|
||||
if (!resolved_file) {
|
||||
IO_Printf("Failed to resolve file: %s\n", file.str);
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint64_t filepath_hash = HashBytes(resolved_file, S8_Length(resolved_file));
|
||||
SRC_CacheEntry *entry = SRC_FindCache(SRC_InMemoryCache, filepath_hash);
|
||||
if (entry) return entry;
|
||||
|
||||
CL_LexResult *first_lex = CL_LexFile(&SRC_ArenaTuple, resolved_file);
|
||||
IO_Assert(first_lex);
|
||||
uint64_t file_hash = HashBytes(first_lex->stream_begin, first_lex->stream - first_lex->stream_begin);
|
||||
uint64_t includes_hash = 13;
|
||||
|
||||
CL_LexList list = CL_MakeLexList(first_lex);
|
||||
for (CL_IncludeIter iter = CL_IterateIncludes(&list); iter.filename; CL_GetNextInclude(&iter)) {
|
||||
if (iter.is_system_include) continue;
|
||||
|
||||
S8_String file_it = S8_MakeFromChar(iter.filename);
|
||||
SRC_CacheEntry *cache = SRC_HashFile(file_it, resolved_file);
|
||||
if (!cache) {
|
||||
IO_Printf("Missing cache for: %s\n", file_it.str);
|
||||
continue;
|
||||
}
|
||||
|
||||
includes_hash = HashMix(includes_hash, cache->total_hash);
|
||||
}
|
||||
|
||||
SRC_CacheEntry *result = SRC_AddHash(filepath_hash, file_hash, includes_hash);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool SRC_WasModified(S8_String file, S8_String artifact_path) {
|
||||
double time_start = OS_GetTime();
|
||||
|
||||
if (OS_FileExists(file) == false) {
|
||||
IO_Printf("FAILED File doesnt exist: %.*s\n", S8_Expand(file));
|
||||
exit(0);
|
||||
}
|
||||
if (OS_IsAbsolute(file) == false) {
|
||||
file = OS_GetAbsolutePath(Perm, file);
|
||||
}
|
||||
|
||||
S8_String without_ext = S8_ChopLastPeriod(file);
|
||||
S8_String name_only = S8_SkipToLastSlash(without_ext);
|
||||
|
||||
if (artifact_path.len == 0) artifact_path = S8_Format(Perm, "%.*s.%s", S8_Expand(name_only), IF_WINDOWS_ELSE("obj", "o"));
|
||||
bool modified = OS_FileExists(artifact_path) == false;
|
||||
|
||||
SRC_CacheEntry *in_memory = SRC_HashFile(file, 0);
|
||||
IO_Assert(in_memory);
|
||||
|
||||
if (modified == false) {
|
||||
SRC_CacheEntry *from_file = SRC_FindCache(SRC_FromFileCache, in_memory->filepath_hash);
|
||||
if (from_file == 0 || (in_memory->total_hash != from_file->total_hash)) {
|
||||
modified = true;
|
||||
}
|
||||
}
|
||||
|
||||
SRC_Time = SRC_Time + (OS_GetTime() - time_start);
|
||||
|
||||
return modified;
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
|
||||
using Str = S8_String;
|
||||
struct Strs : Array<Str> {
|
||||
Strs() = default;
|
||||
Strs(char *str) {
|
||||
*this = {};
|
||||
this->add(S8_MakeFromChar(str));
|
||||
}
|
||||
Strs(char *a, char *b) {
|
||||
*this = {};
|
||||
this->add(S8_MakeFromChar(a));
|
||||
this->add(S8_MakeFromChar(b));
|
||||
}
|
||||
Strs(char *a, char *b, char *c) {
|
||||
*this = {};
|
||||
this->add(S8_MakeFromChar(a));
|
||||
this->add(S8_MakeFromChar(b));
|
||||
this->add(S8_MakeFromChar(c));
|
||||
}
|
||||
Strs(Str a) {
|
||||
*this = {};
|
||||
this->add(a);
|
||||
}
|
||||
Strs(Array<Str> a) { MA_MemoryCopy(this, &a, sizeof(a)); }
|
||||
};
|
||||
|
||||
bool operator==(Strs a, char *b) {
|
||||
if (a.len != 1) return false;
|
||||
bool result = a[0] == S8_MakeFromChar(b);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool operator==(Strs a, const char *b) {
|
||||
if (a.len != 1) return false;
|
||||
bool result = a[0] == S8_MakeFromChar((char *)b);
|
||||
return result;
|
||||
}
|
||||
|
||||
Strs operator+(Strs a, Strs b) {
|
||||
Strs c = {a.copy(*Perm)};
|
||||
c.add_array(b);
|
||||
return c;
|
||||
}
|
||||
|
||||
Strs operator+(Strs a, Str b) {
|
||||
Strs c = {a.copy(*Perm)};
|
||||
c.add(b);
|
||||
return c;
|
||||
}
|
||||
|
||||
Strs operator+(Str a, Strs b) {
|
||||
Strs c = {b.copy(*Perm)};
|
||||
c.add(a);
|
||||
return c;
|
||||
}
|
||||
|
||||
Strs &operator+=(Strs &a, Strs b) {
|
||||
a.add_array(b);
|
||||
return a;
|
||||
}
|
||||
|
||||
Strs &operator+=(Strs &a, char *str) {
|
||||
a.add(S8_MakeFromChar(str));
|
||||
return a;
|
||||
}
|
||||
|
||||
Strs &operator+=(Strs &a, Str s) {
|
||||
a.add(s);
|
||||
return a;
|
||||
}
|
||||
|
||||
Strs operator+(Strs a, char *b) {
|
||||
Strs c = {a.copy(*Perm)};
|
||||
c.add(S8_MakeFromChar(b));
|
||||
return c;
|
||||
}
|
||||
|
||||
Strs operator+(char *a, Strs b) {
|
||||
Strs c = {b.copy(*Perm)};
|
||||
c.add(S8_MakeFromChar(a));
|
||||
return c;
|
||||
}
|
||||
|
||||
Strs operator+(Str a, Str b) {
|
||||
Strs c = {};
|
||||
c.add(a);
|
||||
c.add(b);
|
||||
return c;
|
||||
}
|
||||
|
||||
//@todo: split on any whitespace instead!
|
||||
Strs Split(char *str) {
|
||||
Str s = S8_MakeFromChar(str);
|
||||
S8_List list = S8_Split(Perm, s, S8_Lit(" "), 0);
|
||||
|
||||
Strs result = {};
|
||||
S8_For(it, list) result.add(it->string);
|
||||
return result;
|
||||
}
|
||||
|
||||
Str Merge(Strs list, Str separator = " "_s) {
|
||||
int64_t char_count = 0;
|
||||
For(list) char_count += it.len;
|
||||
if (char_count == 0) return {};
|
||||
int64_t node_count = list.len;
|
||||
|
||||
int64_t base_size = (char_count + 1);
|
||||
int64_t sep_size = (node_count - 1) * separator.len;
|
||||
int64_t size = base_size + sep_size;
|
||||
char *buff = (char *)MA_PushSize(Perm, sizeof(char) * size);
|
||||
Str string = S8_Make(buff, 0);
|
||||
For(list) {
|
||||
IO_Assert(string.len + it.len <= size);
|
||||
MA_MemoryCopy(string.str + string.len, it.str, it.len);
|
||||
string.len += it.len;
|
||||
if (!list.is_last(it)) {
|
||||
MA_MemoryCopy(string.str + string.len, separator.str, separator.len);
|
||||
string.len += separator.len;
|
||||
}
|
||||
}
|
||||
IO_Assert(string.len == size - 1);
|
||||
string.str[size] = 0;
|
||||
return string;
|
||||
}
|
||||
|
||||
S8_String FilenameWithoutExt(S8_String it) { return S8_SkipToLastSlash(S8_ChopLastPeriod(it)); }
|
||||
bool CodeWasModified(char *str, char *artifact = 0) { return SRC_WasModified(S8_MakeFromChar(str), S8_MakeFromChar(artifact)); }
|
||||
bool CodeWasModified(S8_String str, S8_String artifact = {}) { return SRC_WasModified(str, artifact); }
|
||||
Strs IfCodeWasModified(char *cfile, char *objfile) {
|
||||
Strs result = {};
|
||||
S8_String s = S8_MakeFromChar(cfile);
|
||||
S8_String o = S8_MakeFromChar(objfile);
|
||||
if (SRC_WasModified(s, o)) {
|
||||
return cfile;
|
||||
}
|
||||
return objfile;
|
||||
}
|
||||
void MakeDir(char *str) { OS_MakeDir(S8_MakeFromChar(str)); }
|
||||
void ChangeDir(char *str) { OS_SetWorkingDir(S8_MakeFromChar(str)); }
|
||||
int Run(Strs s) {
|
||||
Str cmd = Merge(s);
|
||||
return OS_SystemF("%Q", cmd);
|
||||
}
|
||||
|
||||
Strs ListDir(char *dir) {
|
||||
Strs result = {};
|
||||
S8_List files = OS_ListDir(Perm, S8_MakeFromChar(dir), 0);
|
||||
S8_For(it, files) result.add(it->string);
|
||||
return result;
|
||||
}
|
||||
|
||||
#ifndef BUILD_MAIN
|
||||
int Main();
|
||||
int main() {
|
||||
SRC_InitCache(Perm, S8_Lit("build_file.cache"));
|
||||
int result = Main();
|
||||
if (result == 0) SRC_SaveCache();
|
||||
}
|
||||
#endif
|
||||
76
code/build_main.cpp
Normal file
76
code/build_main.cpp
Normal file
@@ -0,0 +1,76 @@
|
||||
#define BUILD_MAIN
|
||||
#include "build_lib.cpp"
|
||||
|
||||
int main(int argument_count, char **arguments) {
|
||||
OS_MakeDir(S8_Lit("build"));
|
||||
OS_SetWorkingDir(S8_Lit("build"));
|
||||
IO_Printf("WORKING DIR: %Q\n", OS_GetWorkingDir(Perm));
|
||||
|
||||
S8_String cc = ON_WINDOWS("cl"_s) ON_MAC("clang"_s) ON_LINUX("gcc"_s);
|
||||
|
||||
for (int i = 1; i < argument_count; i += 1) {
|
||||
S8_String arg = S8_MakeFromChar(arguments[i]);
|
||||
if (arg == "clear_cache"_s) OS_DeleteFile("build_tool.cache"_s);
|
||||
}
|
||||
|
||||
SRC_InitCache(Perm, S8_Lit("build_tool.cache"));
|
||||
|
||||
// Search for build file in the project directory
|
||||
S8_String build_file = {0};
|
||||
{
|
||||
S8_List files_current_dir = OS_ListDir(Perm, S8_Lit(".."), 0);
|
||||
for (S8_Node *it = files_current_dir.first; it; it = it->next) {
|
||||
if (S8_Find(it->string, S8_Lit("build_file.c"), S8_IGNORE_CASE, 0)) {
|
||||
build_file = it->string;
|
||||
}
|
||||
}
|
||||
|
||||
if (build_file.str == 0) {
|
||||
IO_Printf("IDLE Nothing to do, couldnt find build file in current dir: %Q, exiting ... \n", OS_GetWorkingDir(Perm));
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
S8_String a = S8_ChopLastPeriod(build_file);
|
||||
S8_String b = S8_SkipToLastSlash(a);
|
||||
S8_String exe_name = S8_Format(Perm, "%Q.exe", b);
|
||||
|
||||
// @todo: add search path?
|
||||
// Compile the build file only if code changed
|
||||
if (SRC_WasModified(build_file, exe_name)) {
|
||||
double time = OS_GetTime();
|
||||
int result = 0;
|
||||
if (cc == "cl"_s) {
|
||||
result = OS_SystemF("cl %Q -Fe:%Q -WX -W3 -wd4200 -diagnostics:column -nologo -Zi", build_file, exe_name);
|
||||
}
|
||||
else if (cc == "clang"_s) {
|
||||
result = OS_SystemF("clang -Wno-writable-strings %Q -o %Q -g", build_file, exe_name);
|
||||
}
|
||||
else {
|
||||
IO_Assert(cc == "gcc"_s);
|
||||
result = OS_SystemF("gcc -Wno-write-strings %Q -o %Q -g", build_file, exe_name);
|
||||
}
|
||||
|
||||
if (result != 0) {
|
||||
IO_Printf("FAILED compilation of the build file!\n");
|
||||
return result;
|
||||
}
|
||||
time = OS_GetTime() - time;
|
||||
IO_Printf("TIME Build file compilation: %f\n", time);
|
||||
}
|
||||
|
||||
// Run the build file
|
||||
double time = OS_GetTime();
|
||||
if (build_file.str) {
|
||||
int result = OS_SystemF("%s%Q", IF_WINDOWS_ELSE("", "./"), exe_name);
|
||||
if (result != 0) {
|
||||
printf("FAILED execution of the build file!\n");
|
||||
return result;
|
||||
}
|
||||
}
|
||||
time = OS_GetTime() - time;
|
||||
IO_Printf("TIME total build file execution: %f\n", time);
|
||||
|
||||
SRC_SaveCache();
|
||||
// OS_SetWorkingDir(S8_Lit(".."));
|
||||
}
|
||||
1878
code/clexer.c
Normal file
1878
code/clexer.c
Normal file
File diff suppressed because it is too large
Load Diff
495
code/clexer.h
Normal file
495
code/clexer.h
Normal file
@@ -0,0 +1,495 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#ifndef CL_PRIVATE_FUNCTION
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
#define CL_PRIVATE_FUNCTION __attribute__((unused)) static
|
||||
#else
|
||||
#define CL_PRIVATE_FUNCTION static
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef CL_API_FUNCTION
|
||||
#ifdef __cplusplus
|
||||
#define CL_API_FUNCTION extern "C"
|
||||
#else
|
||||
#define CL_API_FUNCTION
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef CL_INLINE
|
||||
#ifndef _MSC_VER
|
||||
#ifdef __cplusplus
|
||||
#define CL_INLINE inline
|
||||
#else
|
||||
#define CL_INLINE
|
||||
#endif
|
||||
#else
|
||||
#define CL_INLINE __forceinline
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef CL_Arena
|
||||
#define CL_Arena CL__Arena
|
||||
typedef struct CL__Arena {
|
||||
char *buff;
|
||||
int len, cap;
|
||||
} CL_Arena;
|
||||
CL_PRIVATE_FUNCTION void *CL_PushSize(CL_Arena *arena, int size);
|
||||
#else
|
||||
#define CL_CUSTOM_ARENA_TYPE
|
||||
#ifndef CL_PushSize
|
||||
#error If you use a custom Arena type, you need to implement CL_PushSize macro
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef AND_CL_STRING_TERMINATE_ON_NEW_LINE
|
||||
#define AND_CL_STRING_TERMINATE_ON_NEW_LINE &&*T->stream != '\n'
|
||||
#endif
|
||||
|
||||
typedef enum CL_Kind {
|
||||
CL_EOF,
|
||||
CL_MUL,
|
||||
CL_DIV,
|
||||
CL_MOD,
|
||||
CL_LEFTSHIFT,
|
||||
CL_RIGHTSHIFT,
|
||||
CL_ADD,
|
||||
CL_SUB,
|
||||
CL_EQUALS,
|
||||
CL_LESSERTHEN,
|
||||
CL_GREATERTHEN,
|
||||
CL_LESSERTHEN_OR_EQUAL,
|
||||
CL_GREATERTHEN_OR_EQUAL,
|
||||
CL_NOTEQUALS,
|
||||
CL_BITAND,
|
||||
CL_BITOR,
|
||||
CL_BITXOR,
|
||||
CL_AND,
|
||||
CL_OR,
|
||||
CL_NEG,
|
||||
CL_NOT,
|
||||
CL_DECREMENT,
|
||||
CL_INCREMENT,
|
||||
CL_POSTDECREMENT,
|
||||
CL_POSTINCREMENT,
|
||||
CL_ASSIGN,
|
||||
CL_DIVASSIGN,
|
||||
CL_MULASSIGN,
|
||||
CL_MODASSIGN,
|
||||
CL_SUBASSIGN,
|
||||
CL_ADDASSIGN,
|
||||
CL_ANDASSIGN,
|
||||
CL_ORASSIGN,
|
||||
CL_XORASSIGN,
|
||||
CL_LEFTSHIFTASSIGN,
|
||||
CL_RIGHTSHIFTASSIGN,
|
||||
CL_OPENPAREN,
|
||||
CL_CLOSEPAREN,
|
||||
CL_OPENBRACE,
|
||||
CL_CLOSEBRACE,
|
||||
CL_OPENBRACKET,
|
||||
CL_CLOSEBRACKET,
|
||||
CL_COMMA,
|
||||
CL_MACRO_CONCAT,
|
||||
CL_PREPROC_STRINGIFY,
|
||||
CL_QUESTION,
|
||||
CL_THREEDOTS,
|
||||
CL_SEMICOLON,
|
||||
CL_DOT,
|
||||
CL_COLON,
|
||||
CL_TAG,
|
||||
CL_ARROW,
|
||||
CL_EXPRSIZEOF,
|
||||
CL_DOCCOMMENT,
|
||||
CL_COMMENT,
|
||||
CL_IDENTIFIER,
|
||||
CL_STRINGLIT,
|
||||
CL_CHARLIT,
|
||||
CL_ERROR,
|
||||
CL_FLOAT,
|
||||
CL_INT,
|
||||
CL_PREPROC_NULL,
|
||||
CL_PREPROC_DEFINE,
|
||||
CL_PREPROC_IFDEF,
|
||||
CL_PREPROC_IFNDEF,
|
||||
CL_PREPROC_INCLUDE,
|
||||
CL_PREPROC_ENDIF,
|
||||
CL_PREPROC_IF,
|
||||
CL_PREPROC_PRAGMA,
|
||||
CL_PREPROC_ERROR,
|
||||
CL_PREPROC_ELSE,
|
||||
CL_PREPROC_ELIF,
|
||||
CL_PREPROC_UNDEF,
|
||||
CL_KEYWORD_VOID,
|
||||
CL_KEYWORD_INT,
|
||||
CL_KEYWORD_CHAR,
|
||||
CL_KEYWORD_UNSIGNED,
|
||||
CL_KEYWORD_SIGNED,
|
||||
CL_KEYWORD_LONG,
|
||||
CL_KEYWORD_SHORT,
|
||||
CL_KEYWORD_DOUBLE,
|
||||
CL_KEYWORD_FLOAT,
|
||||
CL_KEYWORD__BOOL,
|
||||
CL_KEYWORD__COMPLEX,
|
||||
CL_KEYWORD__IMAGINARY,
|
||||
CL_KEYWORD_STATIC,
|
||||
CL_KEYWORD_AUTO,
|
||||
CL_KEYWORD_CONST,
|
||||
CL_KEYWORD_EXTERN,
|
||||
CL_KEYWORD_INLINE,
|
||||
CL_KEYWORD_REGISTER,
|
||||
CL_KEYWORD_RESTRICT,
|
||||
CL_KEYWORD_VOLATILE,
|
||||
CL_KEYWORD__THREAD_LOCAL,
|
||||
CL_KEYWORD__ATOMIC,
|
||||
CL_KEYWORD__NORETURN,
|
||||
CL_KEYWORD_STRUCT,
|
||||
CL_KEYWORD_UNION,
|
||||
CL_KEYWORD_ENUM,
|
||||
CL_KEYWORD_TYPEDEF,
|
||||
CL_KEYWORD_DEFAULT,
|
||||
CL_KEYWORD_BREAK,
|
||||
CL_KEYWORD_RETURN,
|
||||
CL_KEYWORD_SWITCH,
|
||||
CL_KEYWORD_IF,
|
||||
CL_KEYWORD_ELSE,
|
||||
CL_KEYWORD_FOR,
|
||||
CL_KEYWORD_WHILE,
|
||||
CL_KEYWORD_CASE,
|
||||
CL_KEYWORD_CONTINUE,
|
||||
CL_KEYWORD_DO,
|
||||
CL_KEYWORD_GOTO,
|
||||
CL_KEYWORD_SIZEOF,
|
||||
CL_KEYWORD__ALIGNAS,
|
||||
CL_KEYWORD__ALIGNOF,
|
||||
CL_KEYWORD__STATIC_ASSERT,
|
||||
CL_KEYWORD__GENERIC,
|
||||
CL_COUNT,
|
||||
} CL_Kind;
|
||||
|
||||
typedef enum CL_Fix {
|
||||
CL_FIX_NONE,
|
||||
CL_SUFFIX_U,
|
||||
CL_SUFFIX_UL,
|
||||
CL_SUFFIX_ULL,
|
||||
CL_SUFFIX_L,
|
||||
CL_SUFFIX_LL,
|
||||
CL_SUFFIX_F,
|
||||
CL_SUFFIX_FL,
|
||||
CL_PREFIX_U8,
|
||||
CL_PREFIX_U16,
|
||||
CL_PREFIX_U32,
|
||||
CL_PREFIX_L,
|
||||
} CL_Fix;
|
||||
|
||||
typedef uint16_t CL_Flag;
|
||||
enum {
|
||||
CL_NONE,
|
||||
CL_HEX = 1,
|
||||
CL_DIGRAPH = 2,
|
||||
CL_INSIDE_OF_MACRO = 4,
|
||||
CL_SYSTEM_INCLUDE = 8,
|
||||
CL_WHITESPACE_BEFORE_TOKEN = 16,
|
||||
};
|
||||
|
||||
typedef struct CL_Hideset CL_Hideset;
|
||||
struct CL_Hideset {
|
||||
CL_Hideset *next;
|
||||
char *name;
|
||||
};
|
||||
|
||||
typedef struct CL_Token CL_Token; // 64 bytes
|
||||
struct CL_Token {
|
||||
// 16 bytes :( we want debug info etc.
|
||||
CL_Kind kind;
|
||||
CL_Flag flags;
|
||||
CL_Fix fix;
|
||||
|
||||
// 8bytes
|
||||
uint32_t id;
|
||||
int len;
|
||||
char *str; // 8bytes
|
||||
|
||||
// We dont store line_begin like I would normally cause the user could
|
||||
// override the line and file information using directives.
|
||||
// On error need to do search if I want nice error context.
|
||||
int line, column; // 8bytes
|
||||
char *file; // 8bytes
|
||||
CL_Hideset *hideset; // 8bytes
|
||||
|
||||
union { // 8bytes
|
||||
double f64;
|
||||
uint64_t u64;
|
||||
char *intern;
|
||||
char *string_literal;
|
||||
struct CL_Message *error;
|
||||
CL_Token *comment_is_attached_to_token;
|
||||
};
|
||||
};
|
||||
|
||||
typedef enum CL_MessageKind {
|
||||
CLM_ERROR,
|
||||
CLM_WARNING,
|
||||
CLM_TRACE,
|
||||
} CL_MessageKind;
|
||||
|
||||
typedef struct CL_Message CL_Message;
|
||||
struct CL_Message {
|
||||
CL_Message *next;
|
||||
CL_MessageKind kind;
|
||||
char *string;
|
||||
CL_Token token;
|
||||
};
|
||||
|
||||
typedef struct CL_Tokens CL_Tokens;
|
||||
struct CL_Tokens {
|
||||
CL_Token *data;
|
||||
int count;
|
||||
};
|
||||
|
||||
typedef char CL_Intern;
|
||||
typedef struct CL_InternEntry CL_InternEntry;
|
||||
struct CL_InternEntry {
|
||||
CL_InternEntry *next;
|
||||
char *string;
|
||||
int len;
|
||||
uint64_t hash;
|
||||
};
|
||||
|
||||
typedef struct CL_InternTable CL_InternTable;
|
||||
struct CL_InternTable {
|
||||
CL_InternEntry *entries;
|
||||
int entry_count;
|
||||
int occupied_entry_count;
|
||||
CL_Arena *arena;
|
||||
};
|
||||
|
||||
typedef struct CL_ArenaTuple CL_ArenaTuple;
|
||||
struct CL_ArenaTuple {
|
||||
|
||||
// @todo: Add TokenList and TokenNode, get rid of 1 arena ?
|
||||
CL_Arena *token;
|
||||
CL_Arena *other;
|
||||
union {
|
||||
CL_Arena *include;
|
||||
CL_Arena *macro_token;
|
||||
};
|
||||
union {
|
||||
CL_Arena *comment;
|
||||
CL_Arena *scratch2;
|
||||
};
|
||||
|
||||
CL_Arena default_comment;
|
||||
CL_Arena default_token;
|
||||
CL_Arena default_include;
|
||||
CL_Arena default_other;
|
||||
};
|
||||
|
||||
typedef struct CL_LexResult CL_LexResult;
|
||||
struct CL_LexResult {
|
||||
CL_LexResult *next_result;
|
||||
|
||||
CL_Tokens tokens;
|
||||
CL_Tokens includes;
|
||||
CL_Tokens comments;
|
||||
int attached_comment_index;
|
||||
|
||||
CL_Message *first_message;
|
||||
CL_Message *last_message;
|
||||
int errors;
|
||||
|
||||
char *stream;
|
||||
char *stream_begin;
|
||||
int line;
|
||||
int column;
|
||||
char *file;
|
||||
bool inside_of_macro;
|
||||
|
||||
CL_ArenaTuple *arena;
|
||||
};
|
||||
|
||||
typedef struct CL_SearchPaths CL_SearchPaths;
|
||||
struct CL_SearchPaths {
|
||||
char **include_path;
|
||||
int include_path_count;
|
||||
|
||||
char **system_include_path;
|
||||
int system_include_path_count;
|
||||
|
||||
char *file_begin_to_ignore;
|
||||
};
|
||||
|
||||
typedef struct CL_LexList CL_LexList;
|
||||
struct CL_LexList {
|
||||
int count;
|
||||
CL_LexResult *first_result;
|
||||
CL_LexResult *last_result;
|
||||
CL_InternTable *intern_table;
|
||||
CL_SearchPaths search_paths;
|
||||
};
|
||||
|
||||
typedef struct CL_IncludeIter CL_IncludeIter;
|
||||
struct CL_IncludeIter {
|
||||
char *filename;
|
||||
bool is_system_include;
|
||||
bool inited_with_filename;
|
||||
|
||||
CL_Token *include_token;
|
||||
|
||||
int include_index;
|
||||
CL_LexResult *parent;
|
||||
CL_LexList *lex_list;
|
||||
|
||||
CL_Arena *arena;
|
||||
CL_SearchPaths search_paths;
|
||||
bool resolve;
|
||||
};
|
||||
|
||||
//
|
||||
// Main API
|
||||
//
|
||||
CL_API_FUNCTION void CL_InitDefaultTuple(CL_ArenaTuple *tuple);
|
||||
CL_API_FUNCTION CL_LexResult *CL_LexString(CL_ArenaTuple *arena, char *filename, char *string);
|
||||
CL_API_FUNCTION CL_LexResult *CL_LexFile(CL_ArenaTuple *arena, char *filename);
|
||||
CL_API_FUNCTION CL_LexList CL_LexRecursive(CL_ArenaTuple *arena, char *filename, CL_SearchPaths paths);
|
||||
|
||||
//
|
||||
// Intern table
|
||||
//
|
||||
CL_API_FUNCTION void CL_InitInternTable(CL_Arena *arena, CL_InternTable *table, int size);
|
||||
CL_API_FUNCTION CL_InternTable *CL_CreateInternTable(CL_Arena *arena, int size);
|
||||
CL_API_FUNCTION CL_Intern *CL_InsertIntern(CL_InternTable *table, char *string, int len);
|
||||
CL_API_FUNCTION void CL_InternResult(CL_InternTable *table, CL_LexResult *result);
|
||||
|
||||
//
|
||||
// Include iteration and path resolution
|
||||
//
|
||||
CL_API_FUNCTION CL_IncludeIter CL_IterateIncludes(CL_LexList *list);
|
||||
CL_API_FUNCTION CL_IncludeIter CL_IterateResolvedIncludes(CL_Arena *arena, CL_LexList *list, CL_SearchPaths search_paths);
|
||||
CL_API_FUNCTION char *CL_ResolveFilepath(CL_Arena *arena, CL_SearchPaths *search_paths, char *filename, char *parent_file, bool is_system_include);
|
||||
CL_API_FUNCTION bool CL_IsValidFile(CL_LexList *list, char *filename);
|
||||
CL_API_FUNCTION void CL_GetNextInclude(CL_IncludeIter *iter);
|
||||
|
||||
// Token serialization
|
||||
CL_API_FUNCTION void CL_StringifyMessage(char *buff, int buff_size, CL_Message *msg);
|
||||
CL_API_FUNCTION void CL_PrintMessages(CL_LexResult *lex_result);
|
||||
CL_API_FUNCTION void CL_Stringify(char *buff, int buff_size, CL_Token *token);
|
||||
CL_API_FUNCTION void CL_PrintTokens(CL_Tokens tokens);
|
||||
//
|
||||
// Extended API for "manual" lexing with extended help
|
||||
//
|
||||
CL_API_FUNCTION void CL_ReportError(CL_LexResult *T, CL_Token *token, const char *string, ...);
|
||||
CL_API_FUNCTION bool CL_EatWhitespace(CL_LexResult *T);
|
||||
CL_API_FUNCTION void CL_SetTokenLength(CL_LexResult *T, CL_Token *token);
|
||||
CL_API_FUNCTION void CL_TryToFinalizeToken(CL_LexResult *T, CL_Token *token);
|
||||
CL_API_FUNCTION void CL_ParseCharLiteral(CL_LexResult *T, CL_Token *token);
|
||||
CL_API_FUNCTION void CL_ParseString(CL_LexResult *T, CL_Token *token);
|
||||
CL_API_FUNCTION void CL_IsIdentifierKeyword(CL_LexResult *ctx, CL_Token *token);
|
||||
CL_API_FUNCTION void CL_LexMacroInclude(CL_LexResult *T, CL_Token *token);
|
||||
CL_API_FUNCTION bool CL_LexMacro(CL_LexResult *T, CL_Token *token);
|
||||
CL_API_FUNCTION CL_LexResult *CL_CreateLexingResult(CL_ArenaTuple *arena, char *filename, char *filecontent);
|
||||
CL_API_FUNCTION void CL_PrepareToken(CL_LexResult *T, CL_Token *token, bool skipped_whitespace);
|
||||
CL_API_FUNCTION void CL_DefaultTokenize(CL_LexResult *T, CL_Token *token);
|
||||
CL_API_FUNCTION bool CL_IsComment(CL_Kind kind);
|
||||
CL_API_FUNCTION void CL_InitNextToken(CL_LexResult *T, CL_Token *token);
|
||||
CL_API_FUNCTION CL_Hideset *CL_CreateHideset(CL_Arena *arena, char *name);
|
||||
CL_API_FUNCTION CL_Token *CL_AddNextToken(CL_LexResult *T);
|
||||
CL_API_FUNCTION void CL_AddToken(CL_LexResult *T, CL_Token *token);
|
||||
CL_API_FUNCTION CL_LexList CL_MakeLexList(CL_LexResult *l);
|
||||
CL_API_FUNCTION CL_IncludeIter CL_IterateFileAndResolvedIncludes(CL_ArenaTuple *arena, char *filename, CL_SearchPaths search_paths);
|
||||
|
||||
//
|
||||
// Token iteration and utilities
|
||||
//
|
||||
CL_INLINE int CL_StringLength(char *string) {
|
||||
int len = 0;
|
||||
while (*string++ != 0) len++;
|
||||
return len;
|
||||
}
|
||||
|
||||
CL_INLINE bool CL_StringsAreEqual(char *a, int64_t alen, const char *b, int64_t blen) {
|
||||
if (alen != blen) return false;
|
||||
for (int i = 0; i < alen; i += 1) {
|
||||
if (a[i] != b[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
CL_INLINE bool CL_IsIdentifier(CL_Token *token, char *str) {
|
||||
int str_len = CL_StringLength(str);
|
||||
bool result = token->kind == CL_IDENTIFIER && CL_StringsAreEqual(token->str, token->len, str, str_len);
|
||||
return result;
|
||||
}
|
||||
|
||||
CL_INLINE bool CL_IsAssign(CL_Kind op) {
|
||||
bool result = op >= CL_ASSIGN && op <= CL_RIGHTSHIFTASSIGN;
|
||||
return result;
|
||||
}
|
||||
|
||||
CL_INLINE bool CL_IsKeywordType(CL_Kind op) {
|
||||
bool result = op >= CL_KEYWORD_VOID && op <= CL_KEYWORD__IMAGINARY;
|
||||
return result;
|
||||
}
|
||||
|
||||
CL_INLINE bool CL_IsKeywordTypeOrSpec(CL_Kind op) {
|
||||
bool result = op >= CL_KEYWORD_VOID && op <= CL_KEYWORD_TYPEDEF;
|
||||
return result;
|
||||
}
|
||||
|
||||
CL_INLINE bool CL_IsMacro(CL_Kind kind) {
|
||||
/*print(f"bool result = kind >= CL_PREPROC_{meta.preproc_keywords[0].upper()} && kind <= CL_PREPROC_{meta.preproc_keywords[-1].upper()};")*/
|
||||
bool result = kind >= CL_PREPROC_DEFINE && kind <= CL_PREPROC_UNDEF;
|
||||
/*END*/
|
||||
return result;
|
||||
}
|
||||
|
||||
CL_INLINE bool CL_IsKeyword(CL_Kind kind) {
|
||||
/*#print(f"bool result = kind >= CL_KEYWORD_{meta.keywords[0].upper()} && kind <= CL_KEYWORD_{meta.keywords[-1].upper()};")*/
|
||||
bool result = kind >= CL_KEYWORD_VOID && kind <= CL_KEYWORD__GENERIC;
|
||||
/*END*/
|
||||
return result;
|
||||
}
|
||||
|
||||
CL_INLINE bool CL_IsKeywordOrIdent(CL_Kind kind) {
|
||||
bool result = CL_IsKeyword(kind) || kind == CL_IDENTIFIER;
|
||||
return result;
|
||||
}
|
||||
|
||||
CL_Token CL_NullToken;
|
||||
CL_INLINE CL_Token *CL_Next(CL_Tokens *tokens) {
|
||||
if (tokens->count > 0) {
|
||||
CL_Token *result = tokens->data;
|
||||
tokens->data += 1;
|
||||
tokens->count -= 1;
|
||||
return result;
|
||||
}
|
||||
return &CL_NullToken;
|
||||
}
|
||||
|
||||
CL_INLINE CL_Token *CL_Get(CL_Tokens *tokens) {
|
||||
if (tokens->count > 0) {
|
||||
return tokens->data;
|
||||
}
|
||||
return &CL_NullToken;
|
||||
}
|
||||
|
||||
CL_INLINE CL_Token *CL_Match(CL_Tokens *tokens, CL_Kind kind) {
|
||||
CL_Token *result = CL_Get(tokens);
|
||||
if (result->kind == kind) {
|
||||
CL_Token *next = CL_Next(tokens);
|
||||
return next;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
CL_INLINE CL_Token *CL_MatchIdentifier(CL_Tokens *tokens, char *str) {
|
||||
CL_Token *result = CL_Get(tokens);
|
||||
if (CL_IsIdentifier(result, str)) {
|
||||
CL_Token *next = CL_Next(tokens);
|
||||
return next;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
23
code/defer.hpp
Normal file
23
code/defer.hpp
Normal file
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
#define DEFER_HEADER
|
||||
|
||||
template <typename T>
|
||||
struct DEFER_ExitScope {
|
||||
T lambda;
|
||||
DEFER_ExitScope(T lambda) : lambda(lambda) {}
|
||||
~DEFER_ExitScope() { lambda(); }
|
||||
DEFER_ExitScope(const DEFER_ExitScope &i) : lambda(i.lambda){};
|
||||
|
||||
private:
|
||||
DEFER_ExitScope &operator=(const DEFER_ExitScope &);
|
||||
};
|
||||
|
||||
class DEFER_ExitScopeHelp {
|
||||
public:
|
||||
template <typename T>
|
||||
DEFER_ExitScope<T> operator+(T t) { return t; }
|
||||
};
|
||||
|
||||
#define DEFER_CONCAT_INTERNAL(x, y) x##y
|
||||
#define DEFER_CONCAT(x, y) DEFER_CONCAT_INTERNAL(x, y)
|
||||
#define defer const auto DEFER_CONCAT(defer__, __LINE__) = DEFER_ExitScopeHelp() + [&]()
|
||||
753
code/filesystem.c
Normal file
753
code/filesystem.c
Normal file
@@ -0,0 +1,753 @@
|
||||
#include "filesystem.h"
|
||||
|
||||
#define OS_LENGTHOF(x) ((int64_t)((sizeof(x) / sizeof((x)[0]))))
|
||||
|
||||
#ifdef _WIN32
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
OS_API bool OS_EnableTerminalColors(void) {
|
||||
// Enable color terminal output
|
||||
{
|
||||
// Set output mode to handle virtual terminal sequences
|
||||
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
if (hOut != INVALID_HANDLE_VALUE) {
|
||||
DWORD dwMode = 0;
|
||||
if (GetConsoleMode(hOut, &dwMode)) {
|
||||
dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
|
||||
if (SetConsoleMode(hOut, dwMode)) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
IO_Printf("Failed to enable colored terminal output C\n");
|
||||
}
|
||||
}
|
||||
else {
|
||||
IO_Printf("Failed to enable colored terminal output B\n");
|
||||
}
|
||||
}
|
||||
else {
|
||||
IO_Printf("Failed to enable colored terminal output A\n");
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
OS_API bool OS_IsAbsolute(S8_String path) {
|
||||
bool result = path.len > 3 && CHAR_IsAlphabetic(path.str[0]) && path.str[1] == ':' && path.str[2] == '/';
|
||||
return result;
|
||||
}
|
||||
|
||||
OS_API S8_String OS_GetExePath(MA_Arena *arena) {
|
||||
wchar_t wbuffer[1024];
|
||||
DWORD wsize = GetModuleFileNameW(0, wbuffer, OS_LENGTHOF(wbuffer));
|
||||
IO_Assert(wsize != 0);
|
||||
|
||||
S8_String path = UTF_CreateStringFromWidechar(arena, wbuffer, wsize);
|
||||
S8_NormalizePath(path);
|
||||
return path;
|
||||
}
|
||||
|
||||
OS_API S8_String OS_GetExeDir(MA_Arena *arena) {
|
||||
S8_String path = OS_GetExePath(arena);
|
||||
path = S8_ChopLastSlash(path);
|
||||
path.str[path.len] = 0;
|
||||
return path;
|
||||
}
|
||||
|
||||
OS_API S8_String OS_GetWorkingDir(MA_Arena *arena) {
|
||||
wchar_t wbuffer[1024];
|
||||
DWORD wsize = GetCurrentDirectoryW(OS_LENGTHOF(wbuffer), wbuffer);
|
||||
IO_Assert(wsize != 0);
|
||||
IO_Assert(wsize < 1022);
|
||||
wbuffer[wsize++] = '/';
|
||||
wbuffer[wsize] = 0;
|
||||
|
||||
S8_String path = UTF_CreateStringFromWidechar(arena, wbuffer, wsize);
|
||||
S8_NormalizePath(path);
|
||||
return path;
|
||||
}
|
||||
|
||||
OS_API void OS_SetWorkingDir(S8_String path) {
|
||||
wchar_t wpath[1024];
|
||||
UTF_CreateWidecharFromChar(wpath, OS_LENGTHOF(wpath), path.str, path.len);
|
||||
SetCurrentDirectoryW(wpath);
|
||||
}
|
||||
|
||||
OS_API S8_String OS_GetAbsolutePath(MA_Arena *arena, S8_String relative) {
|
||||
wchar_t wpath[1024];
|
||||
UTF_CreateWidecharFromChar(wpath, OS_LENGTHOF(wpath), relative.str, relative.len);
|
||||
wchar_t wpath_abs[1024];
|
||||
DWORD written = GetFullPathNameW((wchar_t *)wpath, OS_LENGTHOF(wpath_abs), wpath_abs, 0);
|
||||
if (written == 0)
|
||||
return S8_MakeEmpty();
|
||||
S8_String path = UTF_CreateStringFromWidechar(arena, wpath_abs, written);
|
||||
S8_NormalizePath(path);
|
||||
return path;
|
||||
}
|
||||
|
||||
OS_API bool OS_FileExists(S8_String path) {
|
||||
wchar_t wbuff[1024];
|
||||
UTF_CreateWidecharFromChar(wbuff, OS_LENGTHOF(wbuff), path.str, path.len);
|
||||
DWORD attribs = GetFileAttributesW(wbuff);
|
||||
bool result = attribs == INVALID_FILE_ATTRIBUTES ? false : true;
|
||||
return result;
|
||||
}
|
||||
|
||||
OS_API bool OS_IsDir(S8_String path) {
|
||||
wchar_t wbuff[1024];
|
||||
UTF_CreateWidecharFromChar(wbuff, OS_LENGTHOF(wbuff), path.str, path.len);
|
||||
DWORD dwAttrib = GetFileAttributesW(wbuff);
|
||||
return dwAttrib != INVALID_FILE_ATTRIBUTES && (dwAttrib & FILE_ATTRIBUTE_DIRECTORY);
|
||||
}
|
||||
|
||||
OS_API bool OS_IsFile(S8_String path) {
|
||||
wchar_t wbuff[1024];
|
||||
UTF_CreateWidecharFromChar(wbuff, OS_LENGTHOF(wbuff), path.str, path.len);
|
||||
DWORD dwAttrib = GetFileAttributesW(wbuff);
|
||||
bool is_file = (dwAttrib & FILE_ATTRIBUTE_DIRECTORY) == 0;
|
||||
return dwAttrib != INVALID_FILE_ATTRIBUTES && is_file;
|
||||
}
|
||||
|
||||
OS_API double OS_GetTime(void) {
|
||||
static int64_t counts_per_second;
|
||||
if (counts_per_second == 0) {
|
||||
LARGE_INTEGER freq;
|
||||
QueryPerformanceFrequency(&freq);
|
||||
counts_per_second = freq.QuadPart;
|
||||
}
|
||||
|
||||
LARGE_INTEGER time;
|
||||
QueryPerformanceCounter(&time);
|
||||
double result = (double)time.QuadPart / (double)counts_per_second;
|
||||
return result;
|
||||
}
|
||||
|
||||
// @todo: I think we want a list with both relative + absolute + other things
|
||||
//
|
||||
// Returns directories with slash at the end '/'
|
||||
// By default returns absolute paths
|
||||
OS_API S8_List OS_ListDir(MA_Arena *arena, S8_String path, unsigned flags) {
|
||||
MA_Checkpoint scratch = MA_GetScratch1(arena);
|
||||
S8_List dirs_to_read = S8_MakeEmptyList();
|
||||
S8_List result = S8_MakeEmptyList();
|
||||
S8_AddNode(scratch.arena, &dirs_to_read, path);
|
||||
|
||||
for (S8_Node *it = dirs_to_read.first; it; it = it->next) {
|
||||
wchar_t wbuff[1024];
|
||||
S8_String modified_path = S8_Format(scratch.arena, "%.*s\\*", (int)it->string.len, it->string.str);
|
||||
IO_Assert(modified_path.len < OS_LENGTHOF(wbuff));
|
||||
int64_t wsize = UTF_CreateWidecharFromChar(wbuff, OS_LENGTHOF(wbuff), modified_path.str, modified_path.len);
|
||||
IO_Assert(wsize);
|
||||
|
||||
WIN32_FIND_DATAW ffd;
|
||||
HANDLE handle = FindFirstFileW(wbuff, &ffd);
|
||||
if (handle == INVALID_HANDLE_VALUE)
|
||||
continue;
|
||||
|
||||
do {
|
||||
|
||||
//
|
||||
// Skip '.' and '..'
|
||||
//
|
||||
if (ffd.cFileName[0] == '.') {
|
||||
if (ffd.cFileName[1] == '.') {
|
||||
if (ffd.cFileName[2] == 0)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ffd.cFileName[1] == 0)
|
||||
continue;
|
||||
}
|
||||
|
||||
bool dir = ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
|
||||
S8_String filename = UTF_CreateStringFromWidechar(scratch.arena, ffd.cFileName, S8_WideLength(ffd.cFileName));
|
||||
S8_String rel_abs_path = S8_Format(scratch.arena, "%Q/%Q%Q", it->string, filename, dir ? S8_Lit("/") : S8_Lit(""));
|
||||
if (flags & OS_RELATIVE_PATHS) {
|
||||
S8_AddNode(arena, &result, rel_abs_path);
|
||||
}
|
||||
else {
|
||||
S8_String abs_path = OS_GetAbsolutePath(arena, rel_abs_path);
|
||||
S8_AddNode(arena, &result, abs_path);
|
||||
}
|
||||
|
||||
if (dir && flags & OS_RECURSIVE) {
|
||||
S8_AddNode(scratch.arena, &dirs_to_read, rel_abs_path);
|
||||
}
|
||||
} while (FindNextFileW(handle, &ffd) != 0);
|
||||
|
||||
DWORD error = GetLastError();
|
||||
if (error != ERROR_NO_MORE_FILES) {
|
||||
// Not sure what to do here hmmm
|
||||
}
|
||||
FindClose(handle);
|
||||
}
|
||||
|
||||
MA_ReleaseScratch(scratch);
|
||||
return result;
|
||||
}
|
||||
|
||||
OS_API OS_Result OS_MakeDir(S8_String path) {
|
||||
wchar_t wpath[1024];
|
||||
UTF_CreateWidecharFromChar(wpath, OS_LENGTHOF(wpath), path.str, path.len);
|
||||
BOOL success = CreateDirectoryW(wpath, NULL);
|
||||
OS_Result result = OS_SUCCESS;
|
||||
if (success == 0) {
|
||||
DWORD error = GetLastError();
|
||||
if (error == ERROR_ALREADY_EXISTS) {
|
||||
result = OS_ALREADY_EXISTS;
|
||||
}
|
||||
else if (error == ERROR_PATH_NOT_FOUND) {
|
||||
result = OS_PATH_NOT_FOUND;
|
||||
}
|
||||
else {
|
||||
IO_Assert(0);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
OS_API OS_Result OS_CopyFile(S8_String from, S8_String to, bool overwrite) {
|
||||
wchar_t wfrom[1024];
|
||||
UTF_CreateWidecharFromChar(wfrom, OS_LENGTHOF(wfrom), from.str, from.len);
|
||||
|
||||
wchar_t wto[1024];
|
||||
UTF_CreateWidecharFromChar(wto, OS_LENGTHOF(wto), to.str, to.len);
|
||||
|
||||
BOOL fail_if_exists = !overwrite;
|
||||
BOOL success = CopyFileW(wfrom, wto, fail_if_exists);
|
||||
|
||||
OS_Result result = OS_SUCCESS;
|
||||
if (success == FALSE)
|
||||
result = OS_FAILURE;
|
||||
return result;
|
||||
}
|
||||
|
||||
OS_API OS_Result OS_DeleteFile(S8_String path) {
|
||||
wchar_t wpath[1024];
|
||||
UTF_CreateWidecharFromChar(wpath, OS_LENGTHOF(wpath), path.str, path.len);
|
||||
BOOL success = DeleteFileW(wpath);
|
||||
OS_Result result = OS_SUCCESS;
|
||||
if (success == 0)
|
||||
result = OS_PATH_NOT_FOUND;
|
||||
return result;
|
||||
}
|
||||
|
||||
OS_API OS_Result OS_DeleteDir(S8_String path, unsigned flags) {
|
||||
if (flags & OS_RECURSIVE) {
|
||||
MA_Checkpoint scratch = MA_GetScratch();
|
||||
S8_List list = OS_ListDir(scratch.arena, path, OS_RECURSIVE);
|
||||
S8_Node *dirs_to_remove = 0;
|
||||
for (S8_Node *it = list.first; it; it = it->next) {
|
||||
if (!S8_EndsWith(it->string, S8_Lit("/"), S8_IGNORE_CASE)) {
|
||||
OS_DeleteFile(it->string);
|
||||
}
|
||||
else {
|
||||
S8_Node *node = S8_CreateNode(scratch.arena, it->string);
|
||||
SLL_STACK_ADD(dirs_to_remove, node);
|
||||
}
|
||||
}
|
||||
for (S8_Node *it = dirs_to_remove; it; it = it->next) {
|
||||
OS_DeleteDir(it->string, OS_NO_FLAGS);
|
||||
}
|
||||
OS_Result result = OS_DeleteDir(path, OS_NO_FLAGS);
|
||||
MA_ReleaseScratch(scratch);
|
||||
return result;
|
||||
}
|
||||
else {
|
||||
wchar_t wpath[1024];
|
||||
UTF_CreateWidecharFromChar(wpath, OS_LENGTHOF(wpath), path.str, path.len);
|
||||
BOOL success = RemoveDirectoryW(wpath);
|
||||
OS_Result result = OS_SUCCESS;
|
||||
if (success == 0)
|
||||
result = OS_PATH_NOT_FOUND;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
static OS_Result OS__WriteFile(S8_String path, S8_String data, bool append) {
|
||||
wchar_t wpath[1024];
|
||||
UTF_CreateWidecharFromChar(wpath, OS_LENGTHOF(wpath), path.str, path.len);
|
||||
OS_Result result = OS_FAILURE;
|
||||
|
||||
DWORD access = GENERIC_WRITE;
|
||||
DWORD creation_disposition = CREATE_ALWAYS;
|
||||
if (append) {
|
||||
access = FILE_APPEND_DATA;
|
||||
creation_disposition = OPEN_ALWAYS;
|
||||
}
|
||||
|
||||
HANDLE handle = CreateFileW(wpath, access, 0, NULL, creation_disposition, FILE_ATTRIBUTE_NORMAL, NULL);
|
||||
if (handle != INVALID_HANDLE_VALUE) {
|
||||
DWORD bytes_written = 0;
|
||||
IO_Assert(data.len == (DWORD)data.len); // @Todo: can only read 32 byte size files?
|
||||
BOOL error = WriteFile(handle, data.str, (DWORD)data.len, &bytes_written, NULL);
|
||||
if (error == TRUE) {
|
||||
if (bytes_written == data.len) {
|
||||
result = OS_SUCCESS;
|
||||
}
|
||||
}
|
||||
CloseHandle(handle);
|
||||
}
|
||||
else result = OS_PATH_NOT_FOUND;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
OS_API OS_Result OS_AppendFile(S8_String path, S8_String string) {
|
||||
return OS__WriteFile(path, string, true);
|
||||
}
|
||||
|
||||
OS_API OS_Result OS_WriteFile(S8_String path, S8_String string) {
|
||||
return OS__WriteFile(path, string, false);
|
||||
}
|
||||
|
||||
OS_API S8_String OS_ReadFile(MA_Arena *arena, S8_String path) {
|
||||
bool success = false;
|
||||
S8_String result = S8_MakeEmpty();
|
||||
MA_Checkpoint checkpoint = MA_Save(arena);
|
||||
|
||||
wchar_t wpath[1024];
|
||||
UTF_CreateWidecharFromChar(wpath, OS_LENGTHOF(wpath), path.str, path.len);
|
||||
HANDLE handle = CreateFileW(wpath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
|
||||
if (handle != INVALID_HANDLE_VALUE) {
|
||||
LARGE_INTEGER file_size;
|
||||
if (GetFileSizeEx(handle, &file_size)) {
|
||||
if (file_size.QuadPart != 0) {
|
||||
result.len = (int64_t)file_size.QuadPart;
|
||||
result.str = (char *)MA_PushSizeNonZeroed(arena, result.len + 1);
|
||||
DWORD read;
|
||||
if (ReadFile(handle, result.str, (DWORD)result.len, &read, NULL)) { // @todo: can only read 32 byte size files?
|
||||
if (read == result.len) {
|
||||
success = true;
|
||||
result.str[result.len] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
CloseHandle(handle);
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
result = S8_MakeEmpty();
|
||||
MA_Load(checkpoint);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
OS_API int64_t OS_GetFileModTime(S8_String file) {
|
||||
FILETIME time = {0};
|
||||
WIN32_FIND_DATAW data;
|
||||
|
||||
wchar_t wpath[1024];
|
||||
UTF_CreateWidecharFromChar(wpath, 1024, file.str, file.len);
|
||||
HANDLE handle = FindFirstFileW(wpath, &data);
|
||||
if (handle != INVALID_HANDLE_VALUE) {
|
||||
FindClose(handle);
|
||||
time = data.ftLastWriteTime;
|
||||
}
|
||||
else {
|
||||
return -1;
|
||||
}
|
||||
int64_t result = (int64_t)time.dwHighDateTime << 32 | time.dwLowDateTime;
|
||||
return result;
|
||||
}
|
||||
|
||||
OS_API OS_Date OS_GetDate(void) {
|
||||
SYSTEMTIME local;
|
||||
GetLocalTime(&local);
|
||||
|
||||
OS_Date result = {0};
|
||||
result.year = local.wYear;
|
||||
result.month = local.wMonth;
|
||||
result.day = local.wDay;
|
||||
result.hour = local.wHour;
|
||||
result.second = local.wSecond;
|
||||
result.milliseconds = local.wMilliseconds;
|
||||
return result;
|
||||
}
|
||||
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#include <limits.h>
|
||||
#include <sys/stat.h>
|
||||
#include <time.h>
|
||||
#include <dirent.h>
|
||||
|
||||
OS_API bool OS_EnableTerminalColors(void) { return true; }
|
||||
|
||||
OS_API bool OS_IsAbsolute(S8_String path) {
|
||||
bool result = path.len >= 1 && path.str[0] == '/';
|
||||
return result;
|
||||
}
|
||||
|
||||
OS_API S8_String OS_GetExePath(MA_Arena *arena) {
|
||||
char buffer[PATH_MAX] = {};
|
||||
if (readlink("/proc/self/exe", buffer, PATH_MAX) == -1) {
|
||||
return S8_MakeEmpty();
|
||||
}
|
||||
S8_String result = S8_Copy(arena, S8_MakeFromChar(buffer));
|
||||
return result;
|
||||
}
|
||||
|
||||
OS_API S8_String OS_GetExeDir(MA_Arena *arena) {
|
||||
S8_String path = OS_GetExePath(arena);
|
||||
S8_String dir = S8_ChopLastSlash(path);
|
||||
S8_String copy = S8_Copy(arena, dir);
|
||||
return copy;
|
||||
}
|
||||
|
||||
OS_API S8_String OS_GetWorkingDir(MA_Arena *arena) {
|
||||
char *buffer = (char *)MA_PushSizeNonZeroed(arena, PATH_MAX);
|
||||
char *cwd = getcwd(buffer, PATH_MAX);
|
||||
S8_String result = S8_MakeFromChar(cwd);
|
||||
return result;
|
||||
}
|
||||
|
||||
OS_API void OS_SetWorkingDir(S8_String path) {
|
||||
MA_Checkpoint scratch = MA_GetScratch();
|
||||
S8_String copy = S8_Copy(scratch.arena, path);
|
||||
chdir(copy.str);
|
||||
MA_ReleaseScratch(scratch);
|
||||
}
|
||||
|
||||
OS_API S8_String OS_GetAbsolutePath(MA_Arena *arena, S8_String relative) {
|
||||
MA_Checkpoint scratch = MA_GetScratch1(arena);
|
||||
S8_String copy = S8_Copy(scratch.arena, relative);
|
||||
|
||||
char *buffer = (char *)MA_PushSizeNonZeroed(arena, PATH_MAX);
|
||||
realpath((char *)copy.str, buffer);
|
||||
S8_String result = S8_MakeFromChar(buffer);
|
||||
|
||||
MA_ReleaseScratch(scratch);
|
||||
return result;
|
||||
}
|
||||
|
||||
OS_API bool OS_FileExists(S8_String path) {
|
||||
MA_Checkpoint scratch = MA_GetScratch();
|
||||
S8_String copy = S8_Copy(scratch.arena, path);
|
||||
|
||||
bool result = false;
|
||||
if (access((char *)copy.str, F_OK) == 0) {
|
||||
result = true;
|
||||
}
|
||||
|
||||
MA_ReleaseScratch(scratch);
|
||||
return result;
|
||||
}
|
||||
|
||||
OS_API bool OS_IsDir(S8_String path) {
|
||||
MA_Checkpoint scratch = MA_GetScratch();
|
||||
S8_String copy = S8_Copy(scratch.arena, path);
|
||||
|
||||
struct stat s;
|
||||
if (stat(copy.str, &s) != 0)
|
||||
return false;
|
||||
|
||||
bool result = S_ISDIR(s.st_mode);
|
||||
MA_ReleaseScratch(scratch);
|
||||
return result;
|
||||
}
|
||||
|
||||
OS_API bool OS_IsFile(S8_String path) {
|
||||
MA_Checkpoint scratch = MA_GetScratch();
|
||||
S8_String copy = S8_Copy(scratch.arena, path);
|
||||
|
||||
struct stat s;
|
||||
if (stat(copy.str, &s) != 0)
|
||||
return false;
|
||||
bool result = S_ISREG(s.st_mode);
|
||||
MA_ReleaseScratch(scratch);
|
||||
return result;
|
||||
}
|
||||
|
||||
OS_API double OS_GetTime(void) {
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
uint64_t timeu64 = (((uint64_t)ts.tv_sec) * 1000000ull) + ((uint64_t)ts.tv_nsec) / 1000ull;
|
||||
double timef = (double)timeu64;
|
||||
double result = timef / 1000000.0; // Microseconds to seconds
|
||||
return result;
|
||||
}
|
||||
|
||||
OS_API S8_List OS_ListDir(MA_Arena *arena, S8_String path, unsigned flags) {
|
||||
IO_Assert((flags & OS_RECURSIVE) == 0);
|
||||
MA_Checkpoint scratch = MA_GetScratch1(arena);
|
||||
path = S8_Copy(scratch.arena, path);
|
||||
|
||||
S8_List result = {0};
|
||||
struct dirent *dir = 0;
|
||||
DIR *d = opendir((char *)path.str);
|
||||
if (d) {
|
||||
while ((dir = readdir(d)) != NULL) {
|
||||
if (dir->d_name[0] == '.') {
|
||||
if (dir->d_name[1] == '.') {
|
||||
if (dir->d_name[2] == 0)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (dir->d_name[1] == 0)
|
||||
continue;
|
||||
}
|
||||
|
||||
S8_String n = S8_Format(scratch.arena, "%Q/%s", path, dir->d_name);
|
||||
if ((flags & OS_RELATIVE_PATHS) == 0) {
|
||||
n = OS_GetAbsolutePath(scratch.arena, n);
|
||||
}
|
||||
if (dir->d_type == DT_DIR) {
|
||||
n = S8_Format(scratch.arena, "%Q/", n);
|
||||
}
|
||||
|
||||
S8_AddNode(arena, &result, S8_Copy(arena, n));
|
||||
}
|
||||
closedir(d);
|
||||
}
|
||||
|
||||
MA_ReleaseScratch(scratch);
|
||||
return result;
|
||||
}
|
||||
|
||||
OS_API OS_Result OS_MakeDir(S8_String path) {
|
||||
MA_Checkpoint scratch = MA_GetScratch();
|
||||
path = S8_Copy(scratch.arena, path);
|
||||
int error = mkdir(path.str, 0755);
|
||||
MA_ReleaseScratch(scratch);
|
||||
return error == 0 ? OS_SUCCESS : OS_FAILURE;
|
||||
}
|
||||
|
||||
OS_API OS_Result OS_CopyFile(S8_String from, S8_String to, bool overwrite) {
|
||||
const char *ow = overwrite ? "-n" : "";
|
||||
int result = OS_SystemF("cp %s %Q %Q", ow, from, to);
|
||||
return result == 0 ? OS_SUCCESS : OS_FAILURE;
|
||||
}
|
||||
|
||||
OS_API OS_Result OS_DeleteFile(S8_String path) {
|
||||
int result = OS_SystemF("rm %Q");
|
||||
return result == 0 ? OS_SUCCESS : OS_FAILURE;
|
||||
}
|
||||
|
||||
OS_API OS_Result OS_DeleteDir(S8_String path, unsigned flags) {
|
||||
IO_Assert(flags & OS_RECURSIVE);
|
||||
int result = OS_SystemF("rm -r %Q");
|
||||
return result == 0 ? OS_SUCCESS : OS_FAILURE;
|
||||
}
|
||||
|
||||
OS_API int64_t OS_GetFileModTime(S8_String file) {
|
||||
MA_Checkpoint scratch = MA_GetScratch();
|
||||
file = S8_Copy(scratch.arena, file);
|
||||
|
||||
struct stat attrib = {};
|
||||
stat(file.str, &attrib);
|
||||
struct timespec ts = attrib.st_mtim;
|
||||
int64_t result = (((int64_t)ts.tv_sec) * 1000000ll) + ((int64_t)ts.tv_nsec) / 1000ll;
|
||||
|
||||
MA_ReleaseScratch(scratch);
|
||||
return result;
|
||||
}
|
||||
|
||||
OS_API OS_Date OS_GetDate(void) {
|
||||
OS_Date s = {0};
|
||||
return s;
|
||||
}
|
||||
|
||||
OS_API OS_Result OS_AppendFile(S8_String path, S8_String string) {
|
||||
MA_Checkpoint scratch = MA_GetScratch();
|
||||
path = S8_Copy(scratch.arena, path);
|
||||
|
||||
OS_Result result = OS_FAILURE;
|
||||
FILE *f = fopen((const char *)path.str, "a");
|
||||
if (f) {
|
||||
result = OS_SUCCESS;
|
||||
|
||||
size_t written = fwrite(string.str, 1, string.len, f);
|
||||
if (written < string.len) {
|
||||
result = OS_FAILURE;
|
||||
}
|
||||
|
||||
int error = fclose(f);
|
||||
if (error != 0) {
|
||||
result = OS_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
MA_ReleaseScratch(scratch);
|
||||
return result;
|
||||
}
|
||||
|
||||
OS_API S8_String OS_ReadFile(MA_Arena *arena, S8_String path) {
|
||||
MA_Checkpoint scratch = MA_GetScratch1(arena);
|
||||
path = S8_Copy(scratch.arena, path);
|
||||
|
||||
S8_String result = {};
|
||||
FILE *f = fopen(path.str, "rb");
|
||||
if (f) {
|
||||
fseek(f, 0, SEEK_END);
|
||||
result.len = ftell(f);
|
||||
fseek(f, 0, SEEK_SET);
|
||||
|
||||
result.str = (char *)MA_PushSizeNonZeroed(arena, result.len + 1);
|
||||
fread(result.str, result.len, 1, f);
|
||||
result.str[result.len] = 0;
|
||||
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
MA_ReleaseScratch(scratch);
|
||||
return result;
|
||||
}
|
||||
|
||||
#if 0
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
OS_API OS_Result OS_WriteFile(S8_String path, S8_String string) {
|
||||
IO_Assert(path.str[path.len] == 0);
|
||||
|
||||
OS_Result result = OS_FAILURE;
|
||||
int fd = open(path.str, O_CREAT | O_WRONLY, 0644);
|
||||
if (fd != -1) {
|
||||
result = OS_SUCCESS;
|
||||
|
||||
ssize_t written = write(fd, string.str, string.len);
|
||||
if (written != string.len) result = OS_FAILURE;
|
||||
|
||||
int err = close(fd);
|
||||
if (err != 0) result = OS_FAILURE;
|
||||
}
|
||||
return OS_SUCCESS;
|
||||
}
|
||||
#else
|
||||
OS_API OS_Result OS_WriteFile(S8_String path, S8_String string) {
|
||||
MA_Checkpoint scratch = MA_GetScratch();
|
||||
path = S8_Copy(scratch.arena, path);
|
||||
|
||||
OS_Result result = OS_FAILURE;
|
||||
FILE *f = fopen((const char *)path.str, "w");
|
||||
if (f) {
|
||||
result = OS_SUCCESS;
|
||||
|
||||
size_t written = fwrite(string.str, 1, string.len, f);
|
||||
if (written < string.len) {
|
||||
result = OS_FAILURE;
|
||||
}
|
||||
|
||||
int error = fclose(f);
|
||||
if (error != 0) {
|
||||
result = OS_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
MA_ReleaseScratch(scratch);
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
OS_API int OS_SystemF(const char *string, ...) {
|
||||
MA_Checkpoint scratch = MA_GetScratch();
|
||||
S8_FORMAT(scratch.arena, string, result);
|
||||
IO_Printf("Executing: %s\n", result.str);
|
||||
fflush(stdout);
|
||||
int error_code = system(result.str);
|
||||
MA_ReleaseScratch(scratch);
|
||||
return error_code;
|
||||
}
|
||||
|
||||
OS_API S8_String UTF_CreateStringFromWidechar(MA_Arena *arena, wchar_t *wstr, int64_t wsize) {
|
||||
int64_t buffer_size = (wsize + 1) * 2;
|
||||
char *buffer = (char *)MA_PushSizeNonZeroed(arena, buffer_size);
|
||||
int64_t size = UTF_CreateCharFromWidechar(buffer, buffer_size, wstr, wsize);
|
||||
IO_Assert(size < buffer_size);
|
||||
return S8_Make(buffer, size);
|
||||
}
|
||||
|
||||
OS_API S8_List S8_SplitOnRegex(MA_Arena *arena, S8_String string, S8_String regex, unsigned flags) {
|
||||
S8_List result = S8_MakeEmptyList();
|
||||
int64_t index = 0;
|
||||
|
||||
char buff[4096];
|
||||
RE_Regex *re = RE2_Parse(buff, sizeof(buff), regex.str, regex.len);
|
||||
for (RE_Match match = RE3_Find(re, string.str, string.len); match.pos != -1; match = RE3_Find(re, string.str, string.len)) {
|
||||
S8_String before_match = S8_Make(string.str, match.pos);
|
||||
S8_String the_match = S8_Make(string.str + match.pos, match.size);
|
||||
if (before_match.len) S8_AddNode(arena, &result, before_match);
|
||||
if (flags & S8_SPLIT_INCLUSIVE) {
|
||||
if (the_match.len) S8_AddNode(arena, &result, the_match);
|
||||
}
|
||||
string = S8_Skip(string, match.pos + match.size);
|
||||
}
|
||||
S8_AddNode(arena, &result, string);
|
||||
return result;
|
||||
}
|
||||
|
||||
OS_API S8_List OS_ListDirRegex(MA_Arena *arena, S8_String path, unsigned flags, char *regex) {
|
||||
S8_List result = S8_MakeEmptyList();
|
||||
|
||||
char buff[4096];
|
||||
RE_Regex *re = RE1_Parse(buff, sizeof(buff), regex);
|
||||
S8_List files = OS_ListDir(arena, path, flags);
|
||||
for (S8_Node *it = files.first; it; it = it->next) {
|
||||
if (RE3_AreEqual(re, it->string.str, it->string.len)) {
|
||||
S8_AddNode(arena, &result, it->string);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
OS_API S8_String OS_ListDirRegexAsString(MA_Arena *arena, S8_String path, unsigned flags, char *regex) {
|
||||
S8_List files = OS_ListDirRegex(arena, path, flags, regex);
|
||||
S8_String files_str = S8_MergeWithSeparator(arena, files, S8_Lit(" "));
|
||||
return files_str;
|
||||
}
|
||||
|
||||
OS_API bool OS_ExpandIncludesList(MA_Arena *arena, S8_List *out, S8_String filepath) {
|
||||
S8_String c = OS_ReadFile(arena, filepath);
|
||||
if (c.str == 0) return false;
|
||||
S8_String path = S8_ChopLastSlash(filepath);
|
||||
S8_String include = S8_Lit("#include \"");
|
||||
for (;;) {
|
||||
int64_t idx = -1;
|
||||
if (S8_Find(c, include, 0, &idx)) {
|
||||
S8_String str_to_add = S8_GetPrefix(c, idx);
|
||||
S8_AddNode(arena, out, str_to_add);
|
||||
S8_String save = c;
|
||||
c = S8_Skip(c, idx + include.len);
|
||||
|
||||
S8_String filename = c;
|
||||
filename.len = 0;
|
||||
while (filename.str[filename.len] != '"' && filename.len < c.len) {
|
||||
filename.len += 1;
|
||||
}
|
||||
|
||||
c = S8_Skip(c, filename.len + 1);
|
||||
S8_String inc_path = S8_Format(arena, "%.*s/%.*s", S8_Expand(path), S8_Expand(filename));
|
||||
if (!OS_ExpandIncludesList(arena, out, inc_path)) {
|
||||
S8_String s = S8_GetPrefix(save, save.len - c.len);
|
||||
S8_AddNode(arena, out, s);
|
||||
}
|
||||
}
|
||||
else {
|
||||
S8_AddNode(arena, out, c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
OS_API S8_String OS_ExpandIncludes(MA_Arena *arena, S8_String filepath) {
|
||||
S8_List out = {0};
|
||||
S8_String result = {0};
|
||||
MA_ScratchScope(s) {
|
||||
OS_ExpandIncludesList(s.arena, &out, filepath);
|
||||
result = S8_Merge(arena, out);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
64
code/filesystem.h
Normal file
64
code/filesystem.h
Normal file
@@ -0,0 +1,64 @@
|
||||
#pragma once
|
||||
// Quick and dirty filesystem operations
|
||||
|
||||
#ifndef OS_API
|
||||
#ifdef __cplusplus
|
||||
#define OS_API extern "C"
|
||||
#else
|
||||
#define OS_API
|
||||
#endif
|
||||
#endif
|
||||
|
||||
typedef struct OS_Date OS_Date;
|
||||
|
||||
typedef enum OS_Result {
|
||||
OS_SUCCESS,
|
||||
OS_ALREADY_EXISTS,
|
||||
OS_PATH_NOT_FOUND,
|
||||
OS_FAILURE,
|
||||
} OS_Result;
|
||||
|
||||
enum {
|
||||
OS_NO_FLAGS = 0,
|
||||
OS_RECURSIVE = 1,
|
||||
OS_RELATIVE_PATHS = 2,
|
||||
};
|
||||
|
||||
struct OS_Date {
|
||||
uint32_t year;
|
||||
uint32_t month;
|
||||
uint32_t day;
|
||||
uint32_t hour;
|
||||
uint32_t minute;
|
||||
uint32_t second;
|
||||
uint32_t milliseconds;
|
||||
};
|
||||
|
||||
OS_API bool OS_IsAbsolute(S8_String path);
|
||||
OS_API S8_String OS_GetExePath(MA_Arena *arena);
|
||||
OS_API S8_String OS_GetExeDir(MA_Arena *arena);
|
||||
OS_API S8_String OS_GetWorkingDir(MA_Arena *arena);
|
||||
OS_API void OS_SetWorkingDir(S8_String path);
|
||||
OS_API S8_String OS_GetAbsolutePath(MA_Arena *arena, S8_String relative);
|
||||
OS_API bool OS_FileExists(S8_String path);
|
||||
OS_API bool OS_IsDir(S8_String path);
|
||||
OS_API bool OS_IsFile(S8_String path);
|
||||
OS_API double OS_GetTime(void);
|
||||
OS_API S8_List OS_ListDir(MA_Arena *arena, S8_String path, unsigned flags); // @todo: this is poor API, we want absolute, relative, bool dir
|
||||
OS_API OS_Result OS_MakeDir(S8_String path);
|
||||
OS_API OS_Result OS_CopyFile(S8_String from, S8_String to, bool overwrite);
|
||||
OS_API OS_Result OS_DeleteFile(S8_String path);
|
||||
OS_API OS_Result OS_DeleteDir(S8_String path, unsigned flags);
|
||||
OS_API OS_Result OS_AppendFile(S8_String path, S8_String string);
|
||||
OS_API OS_Result OS_WriteFile(S8_String path, S8_String string);
|
||||
OS_API S8_String OS_ReadFile(MA_Arena *arena, S8_String path);
|
||||
OS_API int OS_SystemF(const char *string, ...);
|
||||
OS_API int64_t OS_GetFileModTime(S8_String file);
|
||||
OS_API OS_Date OS_GetDate(void);
|
||||
OS_API S8_String UTF_CreateStringFromWidechar(MA_Arena *arena, wchar_t *wstr, int64_t wsize);
|
||||
OS_API S8_List S8_SplitOnRegex(MA_Arena *arena, S8_String string, S8_String regex, unsigned flags);
|
||||
OS_API S8_List OS_ListDirRegex(MA_Arena *arena, S8_String path, unsigned flags, char *regex);
|
||||
OS_API S8_String OS_ListDirRegexAsString(MA_Arena *arena, S8_String path, unsigned flags, char *regex);
|
||||
OS_API bool OS_ExpandIncludesList(MA_Arena *arena, S8_List *out, S8_String filepath);
|
||||
OS_API S8_String OS_ExpandIncludes(MA_Arena *arena, S8_String filepath);
|
||||
OS_API bool OS_EnableTerminalColors(void);
|
||||
53
code/hash.c
Normal file
53
code/hash.c
Normal file
@@ -0,0 +1,53 @@
|
||||
#include "hash.h"
|
||||
|
||||
// FNV HASH (1a?)
|
||||
HASH_API_FUNCTION uint64_t HashBytes(void *data, uint64_t size) {
|
||||
uint8_t *data8 = (uint8_t *)data;
|
||||
uint64_t hash = (uint64_t)14695981039346656037ULL;
|
||||
for (uint64_t i = 0; i < size; i++) {
|
||||
hash = hash ^ (uint64_t)(data8[i]);
|
||||
hash = hash * (uint64_t)1099511628211ULL;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
HASH_API_FUNCTION RandomSeed MakeRandomSeed(uint64_t value) {
|
||||
RandomSeed result;
|
||||
result.a = value;
|
||||
return result;
|
||||
}
|
||||
|
||||
HASH_API_FUNCTION uint64_t GetRandomU64(RandomSeed *state) {
|
||||
uint64_t x = state->a;
|
||||
x ^= x << 13;
|
||||
x ^= x >> 7;
|
||||
x ^= x << 17;
|
||||
return state->a = x;
|
||||
}
|
||||
|
||||
HASH_API_FUNCTION int GetRandomRangeI(RandomSeed *seed, int first, int last_included) {
|
||||
uint64_t random = GetRandomU64(seed);
|
||||
int range = (last_included - first + 1);
|
||||
int mapped = random % range;
|
||||
int result = mapped + first;
|
||||
return result;
|
||||
}
|
||||
|
||||
HASH_API_FUNCTION double GetRandomNormal(RandomSeed *series) {
|
||||
uint64_t rnd = GetRandomU64(series);
|
||||
double result = (double)rnd / (double)UINT64_MAX;
|
||||
return result;
|
||||
}
|
||||
|
||||
HASH_API_FUNCTION double GetRandomNormalRange(RandomSeed *seed, double min, double max) {
|
||||
double value = GetRandomNormal(seed);
|
||||
double result = value * (max - min) + min;
|
||||
return result;
|
||||
}
|
||||
|
||||
HASH_API_FUNCTION uint64_t HashMix(uint64_t x, uint64_t y) {
|
||||
x ^= y;
|
||||
x *= 0xff51afd7ed558ccd;
|
||||
x ^= x >> 32;
|
||||
return x;
|
||||
}
|
||||
31
code/hash.h
Normal file
31
code/hash.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
#ifndef HASH_API_FUNCTION
|
||||
#ifdef __cplusplus
|
||||
#define HASH_API_FUNCTION extern "C"
|
||||
#else
|
||||
#define HASH_API_FUNCTION
|
||||
#endif
|
||||
#endif
|
||||
|
||||
//@begin gen_structs
|
||||
typedef struct RandomSeed RandomSeed;
|
||||
//@end gen_structs
|
||||
|
||||
struct RandomSeed {
|
||||
uint64_t a;
|
||||
};
|
||||
|
||||
//@begin gen_api_funcs
|
||||
HASH_API_FUNCTION uint64_t HashBytes(void *data, uint64_t size);
|
||||
HASH_API_FUNCTION RandomSeed MakeRandomSeed(uint64_t value);
|
||||
HASH_API_FUNCTION uint64_t GetRandomU64(RandomSeed *state);
|
||||
HASH_API_FUNCTION int GetRandomRangeI(RandomSeed *seed, int first, int last_included);
|
||||
HASH_API_FUNCTION double GetRandomNormal(RandomSeed *series);
|
||||
HASH_API_FUNCTION double GetRandomNormalRange(RandomSeed *seed, double min, double max);
|
||||
HASH_API_FUNCTION uint64_t HashMix(uint64_t x, uint64_t y);
|
||||
//@end gen_api_funcs
|
||||
|
||||
#define WRAP_AROUND_POWER_OF_2(x, pow2) (((x) & ((pow2)-1llu)))
|
||||
static inline float GetRandomNormalF(RandomSeed *series) { return (float)GetRandomNormal(series); }
|
||||
223
code/io.c
Normal file
223
code/io.c
Normal file
@@ -0,0 +1,223 @@
|
||||
#include "io.h"
|
||||
#include <stdarg.h>
|
||||
|
||||
#ifndef IO_SNPRINTF
|
||||
#include <stdio.h>
|
||||
#define IO_SNPRINTF snprintf
|
||||
#endif
|
||||
|
||||
#ifndef IO_VSNPRINTF
|
||||
#include <stdio.h>
|
||||
#define IO_VSNPRINTF vsnprintf
|
||||
#endif
|
||||
|
||||
#ifndef IO_ALLOCATE
|
||||
#include <stdlib.h>
|
||||
#define IO_ALLOCATE(x) malloc(x)
|
||||
#define IO_FREE(x) free(x)
|
||||
#endif
|
||||
|
||||
#ifndef IO_StaticFunc
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
#define IO_StaticFunc __attribute__((unused)) static
|
||||
#else
|
||||
#define IO_StaticFunc static
|
||||
#endif
|
||||
#endif
|
||||
|
||||
IO_StaticFunc int IO_Strlen(char *string) {
|
||||
int len = 0;
|
||||
while (*string++ != 0) len++;
|
||||
return len;
|
||||
}
|
||||
|
||||
void (*IO_User_OutputMessage)(char *str, int len);
|
||||
|
||||
IO_API bool IO__FatalErrorf(const char *file, int line, const char *msg, ...) {
|
||||
va_list args1;
|
||||
va_list args2;
|
||||
char buff[2048];
|
||||
|
||||
va_start(args1, msg);
|
||||
va_copy(args2, args1);
|
||||
int size = IO_VSNPRINTF(buff, sizeof(buff), msg, args2);
|
||||
va_end(args2);
|
||||
|
||||
char *new_buffer = 0;
|
||||
char *user_message = buff;
|
||||
if (size >= sizeof(buff)) {
|
||||
size += 4;
|
||||
new_buffer = (char *)IO_ALLOCATE(size);
|
||||
IO_VSNPRINTF(new_buffer, size, msg, args1);
|
||||
user_message = new_buffer;
|
||||
}
|
||||
va_end(args1);
|
||||
|
||||
IO_ErrorResult ret = IO_ErrorResult_Continue;
|
||||
{
|
||||
char buff2[2048];
|
||||
char *result = buff2;
|
||||
char *b = 0;
|
||||
int size = IO_SNPRINTF(buff2, sizeof(buff2), "%s(%d): error: %s \n", file, line, user_message);
|
||||
if (size >= sizeof(buff2)) {
|
||||
size += 4;
|
||||
b = (char *)IO_ALLOCATE(size);
|
||||
size = IO_SNPRINTF(b, size, "%s(%d): error: %s \n", file, line, user_message);
|
||||
result = b;
|
||||
}
|
||||
|
||||
ret = IO_OutputError(result, size);
|
||||
if (ret == IO_ErrorResult_Exit) {
|
||||
IO_Exit(1);
|
||||
}
|
||||
|
||||
if (b) {
|
||||
IO_FREE(b);
|
||||
}
|
||||
}
|
||||
|
||||
if (new_buffer) {
|
||||
IO_FREE(new_buffer);
|
||||
}
|
||||
|
||||
return ret == IO_ErrorResult_Break;
|
||||
}
|
||||
|
||||
IO_API void IO_Printf(const char *msg, ...) {
|
||||
va_list args1;
|
||||
va_list args2;
|
||||
char buff[2048];
|
||||
|
||||
va_start(args1, msg);
|
||||
va_copy(args2, args1);
|
||||
int size = IO_VSNPRINTF(buff, sizeof(buff), msg, args2);
|
||||
va_end(args2);
|
||||
|
||||
char *new_buffer = 0;
|
||||
char *result = buff;
|
||||
if (size >= sizeof(buff)) {
|
||||
size += 4;
|
||||
new_buffer = (char *)IO_ALLOCATE(size);
|
||||
IO_VSNPRINTF(new_buffer, size, msg, args1);
|
||||
result = new_buffer;
|
||||
}
|
||||
va_end(args1);
|
||||
|
||||
if (IO_User_OutputMessage) {
|
||||
IO_User_OutputMessage(result, size);
|
||||
}
|
||||
else {
|
||||
IO_OutputMessage(result, size);
|
||||
}
|
||||
|
||||
if (new_buffer) {
|
||||
IO_FREE(new_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
IO_API bool IO__FatalError(char *msg) {
|
||||
int len = IO_Strlen(msg);
|
||||
IO_ErrorResult result = IO_OutputError(msg, len);
|
||||
if (result == IO_ErrorResult_Exit) {
|
||||
IO_Exit(1);
|
||||
}
|
||||
return result == IO_ErrorResult_Break;
|
||||
}
|
||||
|
||||
IO_API void IO_Print(char *msg) {
|
||||
int len = IO_Strlen(msg);
|
||||
if (IO_User_OutputMessage) {
|
||||
IO_User_OutputMessage(msg, len);
|
||||
}
|
||||
else {
|
||||
IO_OutputMessage(msg, len);
|
||||
}
|
||||
}
|
||||
#ifdef _WIN32
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#include <windows.h>
|
||||
|
||||
#pragma comment(lib, "user32")
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
IO_API bool IO_IsDebuggerPresent(void) {
|
||||
return IsDebuggerPresent();
|
||||
}
|
||||
|
||||
IO_API void IO_OutputMessage(char *str, int len) {
|
||||
if (IsDebuggerPresent()) {
|
||||
OutputDebugStringA(str);
|
||||
}
|
||||
printf("%.*s", len, str);
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
IO_API IO_ErrorResult IO_OutputError(char *str, int len) {
|
||||
IO_ErrorResult result = IO_ErrorResult_Continue;
|
||||
IO_OutputMessage(str, len);
|
||||
|
||||
char *msg = str;
|
||||
if (str[len] != 0) {
|
||||
msg = (char *)IO_ALLOCATE(len + 1);
|
||||
for (int i = 0; i < len; i += 1) msg[i] = str[i];
|
||||
msg[len] = 0;
|
||||
}
|
||||
|
||||
OutputDebugStringA(msg);
|
||||
if (!IsDebuggerPresent()) {
|
||||
|
||||
// Limit size of error output message
|
||||
char tmp = 0;
|
||||
if (len > 4096) {
|
||||
tmp = str[4096];
|
||||
str[4096] = 0;
|
||||
}
|
||||
|
||||
MessageBoxA(0, msg, "Error!", 0);
|
||||
|
||||
if (tmp != 0) {
|
||||
str[4096] = tmp;
|
||||
}
|
||||
|
||||
result = IO_ErrorResult_Exit;
|
||||
}
|
||||
else {
|
||||
result = IO_ErrorResult_Break;
|
||||
}
|
||||
|
||||
if (msg != str) {
|
||||
IO_FREE(msg);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
IO_API void IO_Exit(int error_code) {
|
||||
ExitProcess(error_code);
|
||||
}
|
||||
#else // _WIN32 else // LIBC
|
||||
#include <stdio.h>
|
||||
|
||||
IO_API IO_ErrorResult IO_OutputError(char *str, int len) {
|
||||
fprintf(stderr, "%.*s", len, str);
|
||||
return IO_ErrorResult_Exit;
|
||||
}
|
||||
|
||||
IO_API void IO_OutputMessage(char *str, int len) {
|
||||
fprintf(stdout, "%.*s", len, str);
|
||||
}
|
||||
|
||||
IO_API void IO_Exit(int error_code) {
|
||||
exit(error_code);
|
||||
}
|
||||
|
||||
IO_API bool IO_IsDebuggerPresent(void) {
|
||||
return false;
|
||||
}
|
||||
#endif // LIBC
|
||||
73
code/io.h
Normal file
73
code/io.h
Normal file
@@ -0,0 +1,73 @@
|
||||
#pragma once
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifndef IO_API
|
||||
#ifdef __cplusplus
|
||||
#define IO_API extern "C"
|
||||
#else
|
||||
#define IO_API
|
||||
#endif
|
||||
#endif
|
||||
|
||||
typedef enum IO_ErrorResult {
|
||||
IO_ErrorResult_Continue,
|
||||
IO_ErrorResult_Break,
|
||||
IO_ErrorResult_Exit,
|
||||
} IO_ErrorResult;
|
||||
|
||||
#ifdef _WIN32
|
||||
#define IO_DebugBreak() (__debugbreak(), 0)
|
||||
#else
|
||||
#define IO_DebugBreak() (IO_Exit(1), 0)
|
||||
#endif
|
||||
|
||||
extern void (*IO_User_OutputMessage)(char *str, int len);
|
||||
|
||||
#define IO__STRINGIFY(x) #x
|
||||
#define IO__TOSTRING(x) IO__STRINGIFY(x)
|
||||
#define IO_LINE IO__TOSTRING(__LINE__)
|
||||
|
||||
#define IO_Assert(x) !(x) && IO__FatalError((char *)(__FILE__ "(" IO_LINE "): " \
|
||||
"error: " #x "\n")) && \
|
||||
IO_DebugBreak()
|
||||
#define IO_FatalErrorf(...) \
|
||||
do { \
|
||||
bool result = IO__FatalErrorf(__FILE__, __LINE__, __VA_ARGS__); \
|
||||
if (result) IO_DebugBreak(); \
|
||||
} while (0)
|
||||
#define IO_FatalError(...) \
|
||||
do { \
|
||||
bool result = IO__FatalError(__FILE__ "(" IO_LINE "): error - " __VA_ARGS__); \
|
||||
if (result) IO_DebugBreak(); \
|
||||
} while (0)
|
||||
#define IO_Assertf(x, ...) \
|
||||
do { \
|
||||
if (!(x)) { \
|
||||
bool result = IO__FatalErrorf(__FILE__, __LINE__, __VA_ARGS__); \
|
||||
if (result) IO_DebugBreak(); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define IO_InvalidElseIf(c) \
|
||||
else if (c) { \
|
||||
IO_InvalidCodepath(); \
|
||||
}
|
||||
#define IO_InvalidElse() \
|
||||
else { \
|
||||
IO_InvalidCodepath(); \
|
||||
}
|
||||
#define IO_InvalidCodepath() IO_FatalError("This codepath is invalid")
|
||||
#define IO_InvalidDefaultCase() \
|
||||
default: { \
|
||||
IO_FatalError("Entered invalid switch statement case"); \
|
||||
}
|
||||
#define IO_Todo() IO_FatalError("This codepath is not implemented yet")
|
||||
|
||||
IO_API bool IO__FatalErrorf(const char *file, int line, const char *msg, ...);
|
||||
IO_API void IO_Printf(const char *msg, ...);
|
||||
IO_API bool IO__FatalError(char *msg);
|
||||
IO_API void IO_Print(char *msg);
|
||||
IO_API void IO_OutputMessage(char *str, int len);
|
||||
IO_API IO_ErrorResult IO_OutputError(char *str, int len);
|
||||
IO_API void IO_Exit(int error_code);
|
||||
IO_API bool IO_IsDebuggerPresent(void);
|
||||
125
code/linked_list.h
Normal file
125
code/linked_list.h
Normal file
@@ -0,0 +1,125 @@
|
||||
#pragma once
|
||||
#define SLL_QUEUE_ADD_MOD(f, l, n, next) \
|
||||
do { \
|
||||
(n)->next = 0; \
|
||||
if ((f) == 0) { \
|
||||
(f) = (l) = (n); \
|
||||
} \
|
||||
else { \
|
||||
(l) = (l)->next = (n); \
|
||||
} \
|
||||
} while (0)
|
||||
#define SLL_QUEUE_ADD(f, l, n) SLL_QUEUE_ADD_MOD(f, l, n, next)
|
||||
|
||||
#define SLL_QUEUE_POP_FIRST_MOD(f, l, next) \
|
||||
do { \
|
||||
if ((f) == (l)) { \
|
||||
(f) = (l) = 0; \
|
||||
} \
|
||||
else { \
|
||||
(f) = (f)->next; \
|
||||
} \
|
||||
} while (0)
|
||||
#define SLL_QUEUE_POP_FIRST(f, l) SLL_QUEUE_POP_FIRST_MOD(f, l, next)
|
||||
|
||||
#define SLL_STACK_ADD_MOD(stack_base, new_stack_base, next) \
|
||||
do { \
|
||||
(new_stack_base)->next = (stack_base); \
|
||||
(stack_base) = (new_stack_base); \
|
||||
} while (0)
|
||||
#define SLL_STACK_ADD(stack_base, new_stack_base) \
|
||||
SLL_STACK_ADD_MOD(stack_base, new_stack_base, next)
|
||||
|
||||
#define SLL_STACK_POP_AND_STORE(stack_base, out_node) \
|
||||
do { \
|
||||
if (stack_base) { \
|
||||
(out_node) = (stack_base); \
|
||||
(stack_base) = (stack_base)->next; \
|
||||
(out_node)->next = 0; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define DLL_QUEUE_ADD_MOD(f, l, node, next, prev) \
|
||||
do { \
|
||||
if ((f) == 0) { \
|
||||
(f) = (l) = (node); \
|
||||
(node)->prev = 0; \
|
||||
(node)->next = 0; \
|
||||
} \
|
||||
else { \
|
||||
(l)->next = (node); \
|
||||
(node)->prev = (l); \
|
||||
(node)->next = 0; \
|
||||
(l) = (node); \
|
||||
} \
|
||||
} while (0)
|
||||
#define DLL_QUEUE_ADD(f, l, node) DLL_QUEUE_ADD_MOD(f, l, node, next, prev)
|
||||
#define DLL_QUEUE_REMOVE_MOD(first, last, node, next, prev) \
|
||||
do { \
|
||||
if ((first) == (last)) { \
|
||||
IO_Assertf((node) == (first), "Not you are trying to remove is not in the list"); \
|
||||
(first) = (last) = 0; \
|
||||
} \
|
||||
else if ((last) == (node)) { \
|
||||
(last) = (last)->prev; \
|
||||
(last)->next = 0; \
|
||||
} \
|
||||
else if ((first) == (node)) { \
|
||||
(first) = (first)->next; \
|
||||
(first)->prev = 0; \
|
||||
} \
|
||||
else { \
|
||||
(node)->prev->next = (node)->next; \
|
||||
(node)->next->prev = (node)->prev; \
|
||||
} \
|
||||
if (node) { \
|
||||
(node)->prev = 0; \
|
||||
(node)->next = 0; \
|
||||
} \
|
||||
} while (0)
|
||||
#define DLL_QUEUE_REMOVE(first, last, node) DLL_QUEUE_REMOVE_MOD(first, last, node, next, prev)
|
||||
|
||||
#define DLL_STACK_ADD_MOD(first, node, next, prev) \
|
||||
do { \
|
||||
(node)->next = (first); \
|
||||
if ((first)) \
|
||||
(first)->prev = (node); \
|
||||
(first) = (node); \
|
||||
(node)->prev = 0; \
|
||||
} while (0)
|
||||
#define DLL_STACK_ADD(first, node) DLL_STACK_ADD_MOD(first, node, next, prev)
|
||||
#define DLL_STACK_REMOVE_MOD(first, node, next, prev) \
|
||||
do { \
|
||||
if ((node) == (first)) { \
|
||||
(first) = (first)->next; \
|
||||
if ((first)) \
|
||||
(first)->prev = 0; \
|
||||
} \
|
||||
else { \
|
||||
(node)->prev->next = (node)->next; \
|
||||
if ((node)->next) \
|
||||
(node)->next->prev = (node)->prev; \
|
||||
} \
|
||||
if (node) { \
|
||||
(node)->prev = 0; \
|
||||
(node)->next = 0; \
|
||||
} \
|
||||
} while (0)
|
||||
#define DLL_STACK_REMOVE(first, node) DLL_STACK_REMOVE_MOD(first, node, next, prev)
|
||||
|
||||
#define DLL_INSERT_NEXT_MOD(base, new, next, prev) \
|
||||
do { \
|
||||
if ((base) == 0) { \
|
||||
(base) = (new); \
|
||||
(new)->next = 0; \
|
||||
(new)->prev = 0; \
|
||||
} \
|
||||
else { \
|
||||
(new)->next = (base)->next; \
|
||||
(base)->next = (new); \
|
||||
(new)->prev = (base); \
|
||||
if ((new)->next) (new)->next->prev = (new); \
|
||||
} \
|
||||
} while (0)
|
||||
#define DLL_INSERT_NEXT(base, new) DLL_INSERT_NEXT_MOD(base, new, next, prev)
|
||||
#define DLL_INSERT_PREV(base, new) DLL_INSERT_NEXT_MOD(base, new, next, prev)
|
||||
26
code/load_library.c
Normal file
26
code/load_library.c
Normal file
@@ -0,0 +1,26 @@
|
||||
#include "load_library.h"
|
||||
#ifdef _WIN32
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#include <windows.h>
|
||||
|
||||
LIB_Library LIB_LoadLibrary(char *str) {
|
||||
HMODULE module = LoadLibraryA(str);
|
||||
return (LIB_Library)module;
|
||||
}
|
||||
|
||||
void *LIB_LoadSymbol(LIB_Library lib, char *symbol) {
|
||||
void *result = GetProcAddress((HMODULE)lib, symbol);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool LIB_UnloadLibrary(LIB_Library lib) {
|
||||
BOOL result = FreeLibrary((HMODULE)lib);
|
||||
if (result == 0) return false;
|
||||
return true;
|
||||
}
|
||||
#endif // _WIN32
|
||||
10
code/load_library.h
Normal file
10
code/load_library.h
Normal file
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
typedef void *LIB_Library;
|
||||
|
||||
LIB_Library LIB_LoadLibrary(char *str);
|
||||
void *LIB_LoadSymbol(LIB_Library lib, char *symbol);
|
||||
bool LIB_UnloadLibrary(LIB_Library lib);
|
||||
|
||||
#ifndef LIB_EXPORT
|
||||
#define LIB_EXPORT __declspec(dllexport)
|
||||
#endif
|
||||
1740
code/multimedia.c
Normal file
1740
code/multimedia.c
Normal file
File diff suppressed because it is too large
Load Diff
368
code/multimedia.h
Normal file
368
code/multimedia.h
Normal file
@@ -0,0 +1,368 @@
|
||||
#pragma once
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#ifndef MU_API
|
||||
#ifdef __cplusplus
|
||||
#define MU_API extern "C"
|
||||
#else
|
||||
#define MU_API
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef MU_INLINE
|
||||
#ifndef _MSC_VER
|
||||
#ifdef __cplusplus
|
||||
#define MU_INLINE inline
|
||||
#else
|
||||
#define MU_INLINE
|
||||
#endif
|
||||
#else
|
||||
#define MU_INLINE __forceinline
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef MU_Float2
|
||||
#define MU_Float2 MU__Float2
|
||||
typedef struct MU__Float2 {
|
||||
float x;
|
||||
float y;
|
||||
} MU__Float2;
|
||||
#endif
|
||||
|
||||
#ifndef MU_Int2
|
||||
#define MU_Int2 MU__Int2
|
||||
typedef struct MU__Int2 {
|
||||
int x;
|
||||
int y;
|
||||
} MU__Int2;
|
||||
#endif
|
||||
|
||||
//@begin gen_structs
|
||||
typedef struct MU_UTF32Result MU_UTF32Result;
|
||||
typedef struct MU_UTF8Result MU_UTF8Result;
|
||||
typedef struct MU_Win32 MU_Win32;
|
||||
typedef struct MU_Win32_Window MU_Win32_Window;
|
||||
typedef struct MU_Window_Params MU_Window_Params;
|
||||
typedef struct MU_Params MU_Params;
|
||||
typedef struct MU_Key_State MU_Key_State;
|
||||
typedef struct MU_Mouse_State MU_Mouse_State;
|
||||
typedef struct MU_DroppedFile MU_DroppedFile;
|
||||
typedef struct MU_Arena MU_Arena;
|
||||
typedef struct MU_Window MU_Window;
|
||||
typedef struct MU_Time MU_Time;
|
||||
typedef struct MU_Sound MU_Sound;
|
||||
typedef struct MU_Context MU_Context;
|
||||
//@end gen_structs
|
||||
|
||||
typedef void *MU_glGetProcAddress(const char *);
|
||||
|
||||
struct MU_Window_Params {
|
||||
MU_Int2 size;
|
||||
MU_Int2 pos;
|
||||
char *title;
|
||||
bool enable_canvas;
|
||||
bool resizable;
|
||||
bool borderless;
|
||||
bool fps_cursor;
|
||||
};
|
||||
|
||||
struct MU_Params {
|
||||
void *memory;
|
||||
size_t cap;
|
||||
|
||||
bool enable_opengl;
|
||||
int opengl_major;
|
||||
int opengl_minor;
|
||||
|
||||
double delta_time;
|
||||
MU_Window_Params window; // this controls window when calling MU_Start
|
||||
void (*sound_callback)(MU_Context *mu, uint16_t *buffer, uint32_t samples_to_fill);
|
||||
};
|
||||
|
||||
struct MU_Key_State {
|
||||
bool down;
|
||||
bool press;
|
||||
bool unpress;
|
||||
bool raw_press;
|
||||
};
|
||||
|
||||
typedef enum MU_Key {
|
||||
MU_KEY_INVALID,
|
||||
MU_KEY_ESCAPE,
|
||||
MU_KEY_ENTER,
|
||||
MU_KEY_TAB,
|
||||
MU_KEY_BACKSPACE,
|
||||
MU_KEY_INSERT,
|
||||
MU_KEY_DELETE,
|
||||
MU_KEY_RIGHT,
|
||||
MU_KEY_LEFT,
|
||||
MU_KEY_DOWN,
|
||||
MU_KEY_UP,
|
||||
MU_KEY_PAGE_UP,
|
||||
MU_KEY_PAGE_DOWN,
|
||||
MU_KEY_HOME,
|
||||
MU_KEY_END,
|
||||
MU_KEY_F1,
|
||||
MU_KEY_F2,
|
||||
MU_KEY_F3,
|
||||
MU_KEY_F4,
|
||||
MU_KEY_F5,
|
||||
MU_KEY_F6,
|
||||
MU_KEY_F7,
|
||||
MU_KEY_F8,
|
||||
MU_KEY_F9,
|
||||
MU_KEY_F10,
|
||||
MU_KEY_F11,
|
||||
MU_KEY_F12,
|
||||
MU_KEY_SPACE = 32,
|
||||
MU_KEY_APOSTROPHE = 39,
|
||||
MU_KEY_PLUS = 43,
|
||||
MU_KEY_COMMA = 44,
|
||||
MU_KEY_MINUS = 45,
|
||||
MU_KEY_PERIOD = 46,
|
||||
MU_KEY_SLASH = 47,
|
||||
MU_KEY_0 = 48,
|
||||
MU_KEY_1 = 49,
|
||||
MU_KEY_2 = 50,
|
||||
MU_KEY_3 = 51,
|
||||
MU_KEY_4 = 52,
|
||||
MU_KEY_5 = 53,
|
||||
MU_KEY_6 = 54,
|
||||
MU_KEY_7 = 55,
|
||||
MU_KEY_8 = 56,
|
||||
MU_KEY_9 = 57,
|
||||
MU_KEY_SEMICOLON = 59,
|
||||
MU_KEY_EQUAL = 61,
|
||||
MU_KEY_A = 65,
|
||||
MU_KEY_B = 66,
|
||||
MU_KEY_C = 67,
|
||||
MU_KEY_D = 68,
|
||||
MU_KEY_E = 69,
|
||||
MU_KEY_F = 70,
|
||||
MU_KEY_G = 71,
|
||||
MU_KEY_H = 72,
|
||||
MU_KEY_I = 73,
|
||||
MU_KEY_J = 74,
|
||||
MU_KEY_K = 75,
|
||||
MU_KEY_L = 76,
|
||||
MU_KEY_M = 77,
|
||||
MU_KEY_N = 78,
|
||||
MU_KEY_O = 79,
|
||||
MU_KEY_P = 80,
|
||||
MU_KEY_Q = 81,
|
||||
MU_KEY_R = 82,
|
||||
MU_KEY_S = 83,
|
||||
MU_KEY_T = 84,
|
||||
MU_KEY_U = 85,
|
||||
MU_KEY_V = 86,
|
||||
MU_KEY_W = 87,
|
||||
MU_KEY_X = 88,
|
||||
MU_KEY_Y = 89,
|
||||
MU_KEY_Z = 90,
|
||||
MU_KEY_LEFT_BRACKET = 91,
|
||||
MU_KEY_BACKSLASH = 92,
|
||||
MU_KEY_RIGHT_BRACKET = 93,
|
||||
MU_KEY_GRAVE_ACCENT = 96,
|
||||
MU_KEY_F13,
|
||||
MU_KEY_F14,
|
||||
MU_KEY_F15,
|
||||
MU_KEY_F16,
|
||||
MU_KEY_F17,
|
||||
MU_KEY_F18,
|
||||
MU_KEY_F19,
|
||||
MU_KEY_F20,
|
||||
MU_KEY_F21,
|
||||
MU_KEY_F22,
|
||||
MU_KEY_F23,
|
||||
MU_KEY_F24,
|
||||
MU_KEY_KP_0,
|
||||
MU_KEY_KP_1,
|
||||
MU_KEY_KP_2,
|
||||
MU_KEY_KP_3,
|
||||
MU_KEY_KP_4,
|
||||
MU_KEY_KP_5,
|
||||
MU_KEY_KP_6,
|
||||
MU_KEY_KP_7,
|
||||
MU_KEY_KP_8,
|
||||
MU_KEY_KP_9,
|
||||
MU_KEY_KP_DECIMAL,
|
||||
MU_KEY_KP_DIVIDE,
|
||||
MU_KEY_KP_MULTIPLY,
|
||||
MU_KEY_KP_SUBTRACT,
|
||||
MU_KEY_KP_ADD,
|
||||
MU_KEY_KP_ENTER,
|
||||
MU_KEY_LEFT_SHIFT,
|
||||
MU_KEY_LEFT_CONTROL,
|
||||
MU_KEY_LEFT_ALT,
|
||||
MU_KEY_LEFT_SUPER,
|
||||
MU_KEY_RIGHT_SHIFT,
|
||||
MU_KEY_RIGHT_CONTROL,
|
||||
MU_KEY_RIGHT_ALT,
|
||||
MU_KEY_RIGHT_SUPER,
|
||||
MU_KEY_CAPS_LOCK,
|
||||
MU_KEY_SCROLL_LOCK,
|
||||
MU_KEY_NUM_LOCK,
|
||||
MU_KEY_PRINT_SCREEN,
|
||||
MU_KEY_PAUSE,
|
||||
MU_KEY_SHIFT,
|
||||
MU_KEY_CONTROL,
|
||||
MU_KEY_COUNT,
|
||||
} MU_Key;
|
||||
|
||||
struct MU_Mouse_State {
|
||||
MU_Int2 pos;
|
||||
MU_Float2 posf;
|
||||
MU_Int2 delta_pos;
|
||||
MU_Float2 delta_pos_normalized;
|
||||
MU_Key_State left;
|
||||
MU_Key_State middle;
|
||||
MU_Key_State right;
|
||||
float delta_wheel; // @todo: add smooth delta?
|
||||
};
|
||||
|
||||
struct MU_DroppedFile {
|
||||
MU_DroppedFile *next;
|
||||
char *filename; // null terminated
|
||||
int filename_size;
|
||||
};
|
||||
|
||||
struct MU_Arena {
|
||||
char *memory;
|
||||
size_t len;
|
||||
size_t cap;
|
||||
};
|
||||
|
||||
// Most of the fields in the window struct are read only. They are updated
|
||||
// in appropriate update functions. The window should belong to the MU_Context
|
||||
// but you get access to the information.
|
||||
struct MU_Window {
|
||||
MU_Int2 size;
|
||||
MU_Float2 sizef;
|
||||
MU_Int2 pos;
|
||||
MU_Float2 posf;
|
||||
float dpi_scale;
|
||||
bool is_fullscreen;
|
||||
bool is_fps_mode;
|
||||
bool is_focused;
|
||||
bool change_cursor_on_mouse_hold; // @in @out
|
||||
uint64_t processed_events_this_frame;
|
||||
bool should_render; // @in @out this is false on first frame but it doesn't matter cause it shouldnt be rendered
|
||||
|
||||
MU_DroppedFile *first_dropped_file;
|
||||
|
||||
uint32_t *canvas;
|
||||
bool canvas_enabled; // @in @out
|
||||
|
||||
MU_Mouse_State mouse;
|
||||
MU_Key_State key[MU_KEY_COUNT];
|
||||
|
||||
uint32_t user_text32[32];
|
||||
int user_text32_count;
|
||||
|
||||
char user_text8[32];
|
||||
int user_text8_count;
|
||||
|
||||
MU_Window *next;
|
||||
void *handle;
|
||||
void *platform;
|
||||
};
|
||||
|
||||
struct MU_Time {
|
||||
double app_start;
|
||||
double frame_start;
|
||||
|
||||
double update;
|
||||
double update_total;
|
||||
|
||||
double delta;
|
||||
float deltaf;
|
||||
double total;
|
||||
float totalf;
|
||||
};
|
||||
|
||||
struct MU_Sound {
|
||||
bool initialized;
|
||||
unsigned samples_per_second;
|
||||
unsigned number_of_channels;
|
||||
unsigned bytes_per_sample;
|
||||
void (*callback)(MU_Context *mu, uint16_t *buffer, uint32_t samples_to_fill);
|
||||
};
|
||||
|
||||
struct MU_Context {
|
||||
bool quit;
|
||||
|
||||
MU_Sound sound;
|
||||
MU_Time time;
|
||||
bool first_frame;
|
||||
int _MU_Update_count;
|
||||
size_t frame;
|
||||
size_t consecutive_missed_frames;
|
||||
size_t total_missed_frames;
|
||||
|
||||
MU_Int2 primary_monitor_size;
|
||||
bool opengl_initialized;
|
||||
int opengl_major;
|
||||
int opengl_minor;
|
||||
void *(*gl_get_proc_address)(const char *str);
|
||||
|
||||
MU_Params params;
|
||||
MU_Window *window;
|
||||
MU_Window *all_windows;
|
||||
MU_Arena perm_arena;
|
||||
MU_Arena frame_arena; // Reset at beginning of MU_Update
|
||||
void *platform;
|
||||
};
|
||||
|
||||
//@begin gen_api_funcs
|
||||
MU_API void MU_Quit(MU_Context *mu);
|
||||
MU_API void MU_DefaultSoundCallback(MU_Context *mu, uint16_t *buffer, uint32_t samples_to_fill);
|
||||
MU_API double MU_GetTime(void);
|
||||
MU_API void MU_ToggleFPSMode(MU_Window *window);
|
||||
MU_API void MU_DisableFPSMode(MU_Window *window);
|
||||
MU_API void MU_EnableFPSMode(MU_Window *window);
|
||||
MU_API void MU_ToggleFullscreen(MU_Window *window);
|
||||
MU_API void MU_Init(MU_Context *mu, MU_Params params, size_t len);
|
||||
MU_API MU_Window *MU_AddWindow(MU_Context *mu, MU_Window_Params params);
|
||||
MU_API void MU_InitWindow(MU_Context *mu, MU_Window *window, MU_Window_Params params);
|
||||
MU_API MU_Context *MU_Start(MU_Params params);
|
||||
MU_API bool MU_Update(MU_Context *mu);
|
||||
//@end gen_api_funcs
|
||||
|
||||
/* @! In the future, api for processing messages manually
|
||||
|
||||
while(true) {
|
||||
MU_Event event;
|
||||
while (mu_get_event_blocking(&event)) {
|
||||
switch(event.kind) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typedef int MU_Modifier;
|
||||
enum MU_Modifier {
|
||||
MU_MODIFIER_SHIFT = 0x1, // left or right shift key
|
||||
MU_MODIFIER_CTRL = 0x2, // left or right control key
|
||||
MU_MODIFIER_ALT = 0x4, // left or right alt key
|
||||
MU_MODIFIER_SUPER = 0x8, // left or right 'super' key
|
||||
MU_MODIFIER_LMB = 0x100, // left mouse button
|
||||
MU_MODIFIER_RMB = 0x200, // right mouse button
|
||||
MU_MODIFIER_MMB = 0x400, // middle mouse button
|
||||
};
|
||||
|
||||
typedef enum MU_Event_Kind {
|
||||
MU_EVENT_KIND_INVALID,
|
||||
MU_EVENT_KIND_KEY_DOWN,
|
||||
MU_EVENT_KIND_KEY_UP,
|
||||
MU_EVENT_KIND_MOUSE_MOVE,
|
||||
} MU_Event_Kind;
|
||||
|
||||
typedef struct MU_Event {
|
||||
MU_Event_Kind kind;
|
||||
MU_Modifier modifier;
|
||||
MU_Key key;
|
||||
} MU_Event;
|
||||
|
||||
|
||||
*/
|
||||
106
code/preproc_env.h
Normal file
106
code/preproc_env.h
Normal file
@@ -0,0 +1,106 @@
|
||||
#pragma once
|
||||
#ifndef _CRT_SECURE_NO_WARNINGS
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
#endif
|
||||
|
||||
#if defined(__APPLE__) && defined(__MACH__)
|
||||
#define OS_MAC 1
|
||||
#elif defined(_WIN32)
|
||||
#define OS_WINDOWS 1
|
||||
#elif defined(__linux__)
|
||||
#define OS_POSIX 1
|
||||
#define OS_LINUX 1
|
||||
#else
|
||||
#error Unsupported platform
|
||||
#endif
|
||||
|
||||
#if defined(__clang__)
|
||||
#define COMPILER_CLANG 1
|
||||
#elif defined(__GNUC__) || defined(__GNUG__)
|
||||
#define COMPILER_GCC 1
|
||||
#elif defined(_MSC_VER)
|
||||
#define COMPILER_MSVC 1
|
||||
#elif defined(__TINYC__)
|
||||
#define COMPILER_TCC 1
|
||||
#else
|
||||
#error Unsupported compiler
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
#define LANG_CPP 1
|
||||
#else
|
||||
#define LANG_C 1
|
||||
#endif
|
||||
|
||||
#ifndef OS_MAC
|
||||
#define OS_MAC 0
|
||||
#endif
|
||||
|
||||
#ifndef OS_WINDOWS
|
||||
#define OS_WINDOWS 0
|
||||
#endif
|
||||
|
||||
#ifndef OS_LINUX
|
||||
#define OS_LINUX 0
|
||||
#endif
|
||||
|
||||
#ifndef OS_POSIX
|
||||
#define OS_POSIX 0
|
||||
#endif
|
||||
|
||||
#ifndef COMPILER_MSVC
|
||||
#define COMPILER_MSVC 0
|
||||
#endif
|
||||
|
||||
#ifndef COMPILER_CLANG
|
||||
#define COMPILER_CLANG 0
|
||||
#endif
|
||||
|
||||
#ifndef COMPILER_GCC
|
||||
#define COMPILER_GCC 0
|
||||
#endif
|
||||
|
||||
#ifndef COMPILER_TCC
|
||||
#define COMPILER_TCC 0
|
||||
#endif
|
||||
|
||||
#ifndef LANG_CPP
|
||||
#define LANG_CPP 0
|
||||
#endif
|
||||
|
||||
#ifndef LANG_C
|
||||
#define LANG_C 0
|
||||
#endif
|
||||
|
||||
#if COMPILER_MSVC
|
||||
#define FORCE_INLINE __forceinline
|
||||
#elif COMPILER_GCC || COMPILER_CLANG
|
||||
#define FORCE_INLINE __attribute__((always_inline)) inline
|
||||
#else
|
||||
#define FORCE_INLINE inline
|
||||
#endif
|
||||
|
||||
#if OS_MAC
|
||||
#define ON_MAC(x) x
|
||||
#else
|
||||
#define ON_MAC(x)
|
||||
#endif
|
||||
|
||||
#if OS_WINDOWS
|
||||
#define ON_WINDOWS(x) x
|
||||
#define IF_WINDOWS_ELSE(x, y) x
|
||||
#else
|
||||
#define ON_WINDOWS(x)
|
||||
#define IF_WINDOWS_ELSE(x, y) y
|
||||
#endif
|
||||
|
||||
#if OS_LINUX
|
||||
#define ON_LINUX(x) x
|
||||
#else
|
||||
#define ON_LINUX(x)
|
||||
#endif
|
||||
|
||||
// #if COMPILER_CLANG
|
||||
// #pragma clang diagnostic push
|
||||
// #pragma clang diagnostic ignored "-Wmicrosoft-enum-forward-reference"
|
||||
// #endif
|
||||
558
code/regex.c
Normal file
558
code/regex.c
Normal file
@@ -0,0 +1,558 @@
|
||||
#include "regex.h"
|
||||
|
||||
#ifndef RE_ASSERT
|
||||
#include <assert.h>
|
||||
#define RE_ASSERT(x) assert(x)
|
||||
#endif
|
||||
|
||||
#ifndef RE_STRICT_ASSERT
|
||||
#define RE_STRICT_ASSERT RE_ASSERT
|
||||
#endif
|
||||
|
||||
#ifndef RE_MemoryZero
|
||||
#include <string.h>
|
||||
#define RE_MemoryZero(p, size) memset(p, 0, size)
|
||||
#endif
|
||||
|
||||
typedef struct RE__Arena {
|
||||
char *buff;
|
||||
RE_Int len;
|
||||
RE_Int cap;
|
||||
} RE_Arena;
|
||||
|
||||
struct RE_String {
|
||||
char *str;
|
||||
RE_Int len;
|
||||
};
|
||||
|
||||
struct RE_Utf32Result {
|
||||
uint32_t out_str;
|
||||
int advance;
|
||||
int error;
|
||||
};
|
||||
static RE_Regex RE_NullRegex;
|
||||
static char RE_NullChar;
|
||||
|
||||
struct RE_Parser {
|
||||
RE_String string;
|
||||
RE_Int i;
|
||||
RE_Regex *first;
|
||||
RE_Regex *last;
|
||||
};
|
||||
RE_API RE_Regex *RE1_ParseEx(RE_Arena *arena, char *string);
|
||||
RE_API RE_Regex *RE2_ParseEx(RE_Arena *arena, char *string, RE_Int len);
|
||||
|
||||
RE_StaticFunc void *RE_PushSize(RE_Arena *arena, RE_Int size) {
|
||||
if (arena->len + size > arena->cap) {
|
||||
RE_ASSERT(!"RE_Regex: Not enough memory passed for this regex");
|
||||
}
|
||||
void *result = arena->buff + arena->len;
|
||||
arena->len += size;
|
||||
return result;
|
||||
}
|
||||
|
||||
RE_StaticFunc RE_Arena RE_ArenaFromBuffer(char *buff, RE_Int size) {
|
||||
RE_Arena result;
|
||||
result.len = 0;
|
||||
result.cap = size;
|
||||
result.buff = buff;
|
||||
return result;
|
||||
}
|
||||
|
||||
RE_StaticFunc RE_String RE_Skip(RE_String string, RE_Int len) {
|
||||
if (len > string.len) len = string.len;
|
||||
RE_Int remain = string.len - len;
|
||||
RE_String result;
|
||||
result.str = string.str + len;
|
||||
result.len = remain;
|
||||
return result;
|
||||
}
|
||||
|
||||
RE_StaticFunc RE_Int RE_StringLength(char *string) {
|
||||
RE_Int len = 0;
|
||||
while (*string++ != 0) len++;
|
||||
return len;
|
||||
}
|
||||
|
||||
RE_StaticFunc RE_Utf32Result RE_ConvertUTF8ToUTF32(char *c, int max_advance) {
|
||||
RE_Utf32Result result;
|
||||
RE_MemoryZero(&result, sizeof(result));
|
||||
|
||||
if ((c[0] & 0x80) == 0) { // Check if leftmost zero of first byte is unset
|
||||
if (max_advance >= 1) {
|
||||
result.out_str = c[0];
|
||||
result.advance = 1;
|
||||
}
|
||||
else result.error = 1;
|
||||
}
|
||||
|
||||
else if ((c[0] & 0xe0) == 0xc0) {
|
||||
if ((c[1] & 0xc0) == 0x80) { // Continuation byte required
|
||||
if (max_advance >= 2) {
|
||||
result.out_str = (uint32_t)(c[0] & 0x1f) << 6u | (c[1] & 0x3f);
|
||||
result.advance = 2;
|
||||
}
|
||||
else result.error = 2;
|
||||
}
|
||||
else result.error = 2;
|
||||
}
|
||||
|
||||
else if ((c[0] & 0xf0) == 0xe0) {
|
||||
if ((c[1] & 0xc0) == 0x80 && (c[2] & 0xc0) == 0x80) { // Two continuation bytes required
|
||||
if (max_advance >= 3) {
|
||||
result.out_str = (uint32_t)(c[0] & 0xf) << 12u | (uint32_t)(c[1] & 0x3f) << 6u | (c[2] & 0x3f);
|
||||
result.advance = 3;
|
||||
}
|
||||
else result.error = 3;
|
||||
}
|
||||
else result.error = 3;
|
||||
}
|
||||
|
||||
else if ((c[0] & 0xf8) == 0xf0) {
|
||||
if ((c[1] & 0xc0) == 0x80 && (c[2] & 0xc0) == 0x80 && (c[3] & 0xc0) == 0x80) { // Three continuation bytes required
|
||||
if (max_advance >= 4) {
|
||||
result.out_str = (uint32_t)(c[0] & 0xf) << 18u | (uint32_t)(c[1] & 0x3f) << 12u | (uint32_t)(c[2] & 0x3f) << 6u | (uint32_t)(c[3] & 0x3f);
|
||||
result.advance = 4;
|
||||
}
|
||||
else result.error = 4;
|
||||
}
|
||||
else result.error = 4;
|
||||
}
|
||||
else result.error = 4;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#define RE_DLL_QUEUE_REMOVE(first, last, node) \
|
||||
do { \
|
||||
if ((first) == (last)) { \
|
||||
(first) = (last) = 0; \
|
||||
} \
|
||||
else if ((last) == (node)) { \
|
||||
(last) = (last)->prev; \
|
||||
(last)->next = 0; \
|
||||
} \
|
||||
else if ((first) == (node)) { \
|
||||
(first) = (first)->next; \
|
||||
(first)->prev = 0; \
|
||||
} \
|
||||
else { \
|
||||
(node)->prev->next = (node)->next; \
|
||||
(node)->next->prev = (node)->prev; \
|
||||
} \
|
||||
if (node) (node)->prev = 0; \
|
||||
} while (0)
|
||||
|
||||
#define RE_DLL_QUEUE_ADD(f, l, node) \
|
||||
do { \
|
||||
if ((f) == 0) { \
|
||||
(f) = (l) = (node); \
|
||||
(node)->prev = 0; \
|
||||
(node)->next = 0; \
|
||||
} \
|
||||
else { \
|
||||
(l)->next = (node); \
|
||||
(node)->prev = (l); \
|
||||
(node)->next = 0; \
|
||||
(l) = (node); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
RE_StaticFunc char *RE_GetP(RE_Parser *P) {
|
||||
if (P->i >= P->string.len) return &RE_NullChar;
|
||||
return P->string.str + P->i;
|
||||
}
|
||||
|
||||
RE_StaticFunc char RE_Get(RE_Parser *P) {
|
||||
if (P->i >= P->string.len) return 0;
|
||||
return P->string.str[P->i];
|
||||
}
|
||||
|
||||
RE_StaticFunc char RE_Get1(RE_Parser *P) {
|
||||
if ((P->i + 1) >= P->string.len || P->i >= P->string.len) return 0;
|
||||
return P->string.str[P->i + 1];
|
||||
}
|
||||
|
||||
RE_StaticFunc void RE_Advance(RE_Parser *P) {
|
||||
if (P->i >= P->string.len) return;
|
||||
P->i += 1;
|
||||
}
|
||||
|
||||
RE_StaticFunc RE_Regex *RE_ParseSingle(RE_Parser *P, RE_Arena *arena, RE_Regex **first, RE_Regex **last) {
|
||||
RE_Regex *regex = (RE_Regex *)RE_PushSize(arena, sizeof(RE_Regex));
|
||||
RE_MemoryZero(regex, sizeof(*regex));
|
||||
char *c = RE_GetP(P);
|
||||
RE_Int size_left = P->string.len - P->i;
|
||||
RE_Advance(P);
|
||||
switch (*c) {
|
||||
case ')': RE_STRICT_ASSERT(regex->kind != RE_MATCH_NULL && "Invalid regex syntax, ')' appeared without matching '('"); break;
|
||||
case '\0': RE_STRICT_ASSERT(regex->kind != RE_MATCH_NULL && "Invalid regex syntax, reached end of string obruptly"); break;
|
||||
case '.': regex->kind = RE_MATCH_ANY; break;
|
||||
case '^': regex->kind = RE_MATCH_FRONT; break;
|
||||
case '$': regex->kind = RE_MATCH_BACK; break;
|
||||
|
||||
case '*': {
|
||||
if (*last) {
|
||||
regex->kind = RE_MATCH_ZERO_OR_MORE;
|
||||
RE_Regex *prev = *last;
|
||||
RE_DLL_QUEUE_REMOVE(*first, *last, *last);
|
||||
regex->child = prev;
|
||||
}
|
||||
else {
|
||||
RE_STRICT_ASSERT(!"Invalid regex syntax, '*' is not attached to anything");
|
||||
}
|
||||
} break;
|
||||
|
||||
case '+': {
|
||||
if (*last) {
|
||||
regex->kind = RE_MATCH_ONE_OR_MORE;
|
||||
RE_Regex *prev = *last;
|
||||
RE_DLL_QUEUE_REMOVE(*first, *last, *last);
|
||||
regex->child = prev;
|
||||
}
|
||||
else {
|
||||
RE_STRICT_ASSERT(!"Invalid regex syntax, '+' is not attached to anything");
|
||||
}
|
||||
} break;
|
||||
|
||||
case '?': {
|
||||
if (*last) {
|
||||
regex->kind = RE_MATCH_ZERO_OR_ONE;
|
||||
RE_Regex *prev = *last;
|
||||
RE_DLL_QUEUE_REMOVE(*first, *last, *last);
|
||||
regex->child = prev;
|
||||
}
|
||||
else {
|
||||
RE_STRICT_ASSERT(!"Invalid regex syntax, '?' is not attached to anything");
|
||||
}
|
||||
} break;
|
||||
|
||||
case '[': {
|
||||
regex->kind = RE_MATCH_SELECTED;
|
||||
if (RE_Get(P) == '^') {
|
||||
regex->kind = RE_MATCH_NOT_SELECTED;
|
||||
RE_Advance(P);
|
||||
}
|
||||
while (RE_Get(P) != 0 && RE_Get(P) != ']') {
|
||||
RE_Regex *r = RE_ParseSingle(P, arena, ®ex->group.first, ®ex->group.last);
|
||||
if (r->kind == RE_MATCH_NULL) {
|
||||
regex->kind = RE_MATCH_NULL;
|
||||
break;
|
||||
}
|
||||
if (r->kind == RE_MATCH_WORD && RE_Get(P) == '-') {
|
||||
char word = RE_Get1(P);
|
||||
if (word >= '!' && word <= '~') {
|
||||
RE_Advance(P);
|
||||
RE_Regex *right = RE_ParseSingle(P, arena, 0, 0);
|
||||
if (right->kind == RE_MATCH_NULL) {
|
||||
regex->kind = RE_MATCH_NULL;
|
||||
break;
|
||||
}
|
||||
RE_ASSERT(right->kind == RE_MATCH_WORD);
|
||||
RE_ASSERT(right->word == word);
|
||||
r->word_min = word > r->word ? r->word : word;
|
||||
r->word_max = word > r->word ? word : r->word;
|
||||
r->kind = RE_MATCH_RANGE;
|
||||
}
|
||||
}
|
||||
RE_DLL_QUEUE_ADD(regex->group.first, regex->group.last, r);
|
||||
}
|
||||
RE_Advance(P);
|
||||
} break;
|
||||
|
||||
case '(': {
|
||||
regex->kind = RE_MATCH_GROUP;
|
||||
while (RE_Get(P) != 0 && RE_Get(P) != ')') {
|
||||
RE_Regex *r = RE_ParseSingle(P, arena, ®ex->group.first, ®ex->group.last);
|
||||
if (r->kind == RE_MATCH_NULL) {
|
||||
regex->kind = RE_MATCH_NULL;
|
||||
break;
|
||||
}
|
||||
RE_DLL_QUEUE_ADD(regex->group.first, regex->group.last, r);
|
||||
}
|
||||
RE_Advance(P);
|
||||
} break;
|
||||
|
||||
case '|': {
|
||||
if (*last) {
|
||||
regex->kind = RE_MATCH_OR;
|
||||
RE_Regex *left = *last;
|
||||
RE_Regex *right = RE_ParseSingle(P, arena, first, last);
|
||||
if (right->kind == RE_MATCH_NULL) {
|
||||
regex->kind = RE_MATCH_NULL;
|
||||
RE_STRICT_ASSERT(!"Invalid regex syntax, '|' appeared but it's right option is invalid");
|
||||
}
|
||||
else {
|
||||
RE_DLL_QUEUE_REMOVE(*first, *last, left);
|
||||
regex->left = left;
|
||||
regex->right = right;
|
||||
}
|
||||
}
|
||||
} break;
|
||||
|
||||
case '\\': {
|
||||
regex->kind = RE_MATCH_WORD;
|
||||
regex->word = RE_Get(P);
|
||||
switch (regex->word) {
|
||||
case 'n': regex->word = '\n'; break;
|
||||
case 't': regex->word = '\t'; break;
|
||||
case 'r': regex->word = '\r'; break;
|
||||
case 'w': regex->kind = RE_MATCH_ANY_WORD; break;
|
||||
case 'd': regex->kind = RE_MATCH_ANY_DIGIT; break;
|
||||
case 's': regex->kind = RE_MATCH_ANY_WHITESPACE; break;
|
||||
case '\0': {
|
||||
regex->kind = RE_MATCH_NULL;
|
||||
RE_STRICT_ASSERT(!"Invalid regex syntax, escape '\\' followed by end of string");
|
||||
} break;
|
||||
}
|
||||
RE_Advance(P);
|
||||
} break;
|
||||
|
||||
default: {
|
||||
regex->kind = RE_MATCH_WORD;
|
||||
RE_Utf32Result decode = RE_ConvertUTF8ToUTF32(c, (int)size_left);
|
||||
if (decode.error) {
|
||||
regex->kind = RE_MATCH_NULL;
|
||||
RE_STRICT_ASSERT(!"Invalid regex syntax, string is an invalid utf8");
|
||||
}
|
||||
else {
|
||||
regex->word32 = decode.out_str;
|
||||
for (int i = 0; i < decode.advance - 1; i += 1)
|
||||
RE_Advance(P);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return regex;
|
||||
}
|
||||
|
||||
RE_StaticFunc RE_Int RE_MatchSingle(RE_Regex *regex, RE_String string) {
|
||||
switch (regex->kind) {
|
||||
case RE_MATCH_ZERO_OR_MORE: {
|
||||
RE_Int result = 0;
|
||||
for (; string.len;) {
|
||||
// @idea
|
||||
// In this case (asd)*(asd) we just quit with 0
|
||||
// when we meet asd
|
||||
// Maybe this should be collapsed in parsing stage/
|
||||
// asd should be combined with *asd etc. cause
|
||||
// now it's a bit weird but I dont know why you would
|
||||
// type that in the first place
|
||||
if (RE_MatchSingle(regex->next, string) != -1) break;
|
||||
RE_Int index = RE_MatchSingle(regex->child, string);
|
||||
if (index == -1) break;
|
||||
string = RE_Skip(string, index);
|
||||
result += index;
|
||||
}
|
||||
return result;
|
||||
} break;
|
||||
|
||||
case RE_MATCH_ONE_OR_MORE: {
|
||||
RE_Int result = 0;
|
||||
for (; string.len;) {
|
||||
RE_Int index = RE_MatchSingle(regex->child, string);
|
||||
if (index == -1) break;
|
||||
string = RE_Skip(string, index);
|
||||
result += index;
|
||||
}
|
||||
|
||||
if (result == 0) return -1;
|
||||
return result;
|
||||
} break;
|
||||
|
||||
case RE_MATCH_OR: {
|
||||
RE_Int right = RE_MatchSingle(regex->right, string);
|
||||
RE_Int left = RE_MatchSingle(regex->left, string);
|
||||
if (left > right) return left;
|
||||
else return right;
|
||||
} break;
|
||||
|
||||
case RE_MATCH_GROUP: {
|
||||
RE_Int result = 0;
|
||||
for (RE_Regex *it = regex->group.first; it; it = it->next) {
|
||||
if (string.len == 0) return -1;
|
||||
RE_Int index = RE_MatchSingle(it, string);
|
||||
if (index == -1) return -1;
|
||||
result += index;
|
||||
string = RE_Skip(string, index);
|
||||
}
|
||||
return result;
|
||||
} break;
|
||||
|
||||
case RE_MATCH_NOT_SELECTED: {
|
||||
for (RE_Regex *it = regex->group.first; it; it = it->next) {
|
||||
RE_Int index = RE_MatchSingle(it, string);
|
||||
if (index != -1) return -1;
|
||||
}
|
||||
RE_Utf32Result decode = RE_ConvertUTF8ToUTF32(string.str, (int)string.len);
|
||||
if (decode.error) return -1;
|
||||
return decode.advance;
|
||||
} break;
|
||||
|
||||
case RE_MATCH_SELECTED: {
|
||||
for (RE_Regex *it = regex->group.first; it; it = it->next) {
|
||||
RE_Int index = RE_MatchSingle(it, string);
|
||||
if (index != -1) return index;
|
||||
}
|
||||
return -1;
|
||||
} break;
|
||||
|
||||
case RE_MATCH_RANGE: {
|
||||
if (string.str[0] >= regex->word_min && string.str[0] <= regex->word_max)
|
||||
return 1;
|
||||
return -1;
|
||||
}
|
||||
|
||||
case RE_MATCH_ANY_WORD: {
|
||||
if ((string.str[0] >= 'a' && string.str[0] <= 'z') || (string.str[0] >= 'A' && string.str[0] <= 'Z'))
|
||||
return 1;
|
||||
return -1;
|
||||
} break;
|
||||
|
||||
case RE_MATCH_ANY_DIGIT: {
|
||||
if (string.str[0] >= '0' && string.str[0] <= '9')
|
||||
return 1;
|
||||
return -1;
|
||||
} break;
|
||||
|
||||
case RE_MATCH_ANY_WHITESPACE: {
|
||||
if (string.str[0] == ' ' || string.str[0] == '\n' || string.str[0] == '\t' || string.str[0] == '\r')
|
||||
return 1;
|
||||
return -1;
|
||||
} break;
|
||||
|
||||
case RE_MATCH_ANY: {
|
||||
if (string.str[0] != '\n') {
|
||||
return 1;
|
||||
}
|
||||
return -1;
|
||||
} break;
|
||||
|
||||
case RE_MATCH_ZERO_OR_ONE: {
|
||||
RE_Int index = RE_MatchSingle(regex->child, string);
|
||||
if (index == -1) index = 0;
|
||||
return index;
|
||||
} break;
|
||||
|
||||
case RE_MATCH_WORD: {
|
||||
RE_Utf32Result decode = RE_ConvertUTF8ToUTF32(string.str, (int)string.len);
|
||||
if (decode.error) return -1;
|
||||
if (decode.out_str == regex->word32) return decode.advance;
|
||||
return -1;
|
||||
} break;
|
||||
|
||||
case RE_MATCH_BACK:
|
||||
case RE_MATCH_NULL: return -1;
|
||||
|
||||
default: RE_ASSERT(!"Invalid codepath");
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
RE_API bool RE1_AreEqual(char *regex, char *string) {
|
||||
char buff[4096];
|
||||
RE_Regex *re = RE1_Parse(buff, sizeof(buff), regex);
|
||||
bool result = RE3_AreEqual(re, string, RE_StringLength(string));
|
||||
return result;
|
||||
}
|
||||
|
||||
RE_API bool RE2_AreEqual(RE_Regex *regex, char *string) {
|
||||
return RE3_AreEqual(regex, string, RE_StringLength(string));
|
||||
}
|
||||
|
||||
RE_API bool RE3_AreEqual(RE_Regex *regex, char *string, RE_Int len) {
|
||||
RE_Int result = RE3_MatchFront(regex, string, len, string);
|
||||
return result == len ? true : false;
|
||||
}
|
||||
|
||||
RE_API RE_Match RE1_Find(char *regex, char *string) {
|
||||
char buff[4096];
|
||||
RE_Regex *re = RE1_Parse(buff, sizeof(buff), regex);
|
||||
RE_Match result = RE2_Find(re, string);
|
||||
return result;
|
||||
}
|
||||
|
||||
RE_API RE_Match RE2_Find(RE_Regex *regex, char *string) {
|
||||
return RE3_Find(regex, string, RE_StringLength(string));
|
||||
}
|
||||
|
||||
RE_API RE_Match RE3_Find(RE_Regex *regex, char *string, RE_Int len) {
|
||||
RE_Match result;
|
||||
for (RE_Int i = 0; i < len; i += 1) {
|
||||
result.size = RE3_MatchFront(regex, string + i, len - i, string);
|
||||
if (result.size != -1) {
|
||||
result.pos = i;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
result.size = 0;
|
||||
result.pos = -1;
|
||||
return result;
|
||||
}
|
||||
|
||||
RE_API RE_Match RE2_FindAgain(RE_Regex *regex, char *string, RE_Match prev_match) {
|
||||
return RE2_Find(regex, string + prev_match.pos);
|
||||
}
|
||||
|
||||
RE_API RE_Match RE3_FindAgain(RE_Regex *regex, char *string, RE_Int len, RE_Match prev_match) {
|
||||
return RE3_Find(regex, string + prev_match.pos, len - prev_match.pos);
|
||||
}
|
||||
|
||||
RE_API RE_Int RE3_MatchFront(RE_Regex *regex, char *string, RE_Int len, char *string_front) {
|
||||
RE_String re_string;
|
||||
re_string.str = string;
|
||||
re_string.len = len;
|
||||
RE_Int submatch_len = 0;
|
||||
for (RE_Regex *it = regex; it; it = it->next) {
|
||||
if (it->kind == RE_MATCH_FRONT) {
|
||||
if (re_string.str == string_front)
|
||||
continue;
|
||||
return -1;
|
||||
}
|
||||
if (it->kind == RE_MATCH_BACK) {
|
||||
if (re_string.len == 0)
|
||||
continue;
|
||||
return -1;
|
||||
}
|
||||
|
||||
RE_Int index = RE_MatchSingle(it, re_string);
|
||||
if (index == -1) return -1;
|
||||
re_string = RE_Skip(re_string, index);
|
||||
submatch_len += index;
|
||||
}
|
||||
return submatch_len;
|
||||
}
|
||||
|
||||
RE_API RE_Regex *RE1_ParseEx(RE_Arena *arena, char *string) {
|
||||
return RE2_ParseEx(arena, string, RE_StringLength(string));
|
||||
}
|
||||
|
||||
RE_API RE_Regex *RE2_ParseEx(RE_Arena *arena, char *string, RE_Int len) {
|
||||
RE_Parser P;
|
||||
RE_MemoryZero(&P, sizeof(P));
|
||||
P.string.str = string;
|
||||
P.string.len = len;
|
||||
|
||||
for (; P.i < P.string.len;) {
|
||||
RE_Regex *regex = RE_ParseSingle(&P, arena, &P.first, &P.last);
|
||||
RE_DLL_QUEUE_ADD(P.first, P.last, regex);
|
||||
if (regex->kind == RE_MATCH_NULL) {
|
||||
P.first = &RE_NullRegex;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return P.first;
|
||||
}
|
||||
|
||||
RE_API RE_Regex *RE1_Parse(char *buff, RE_Int buffsize, char *string) {
|
||||
RE_Arena arena = RE_ArenaFromBuffer(buff, buffsize);
|
||||
RE_Regex *result = RE1_ParseEx(&arena, string);
|
||||
return result;
|
||||
}
|
||||
|
||||
RE_API RE_Regex *RE2_Parse(char *buff, RE_Int buffsize, char *string, RE_Int len) {
|
||||
RE_Arena arena = RE_ArenaFromBuffer(buff, buffsize);
|
||||
RE_Regex *result = RE2_ParseEx(&arena, string, len);
|
||||
return result;
|
||||
}
|
||||
93
code/regex.h
Normal file
93
code/regex.h
Normal file
@@ -0,0 +1,93 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifndef RE_Int
|
||||
#define RE_Int int64_t
|
||||
#endif
|
||||
|
||||
#ifndef RE_API
|
||||
#ifdef __cplusplus
|
||||
#define RE_API extern "C"
|
||||
#else
|
||||
#define RE_API
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef RE_StaticFunc
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
#define RE_StaticFunc __attribute__((unused)) static
|
||||
#else
|
||||
#define RE_StaticFunc static
|
||||
#endif
|
||||
#endif
|
||||
|
||||
typedef struct RE_String RE_String;
|
||||
typedef struct RE_Utf32Result RE_Utf32Result;
|
||||
typedef struct RE_Parser RE_Parser;
|
||||
typedef struct RE_Regex RE_Regex;
|
||||
typedef struct RE_Match RE_Match;
|
||||
|
||||
/* @todo
|
||||
Add \W \D \S oppsites
|
||||
*/
|
||||
|
||||
typedef enum RE_MatchKind {
|
||||
RE_MATCH_NULL,
|
||||
RE_MATCH_FRONT,
|
||||
RE_MATCH_BACK,
|
||||
RE_MATCH_WORD,
|
||||
RE_MATCH_OR,
|
||||
RE_MATCH_GROUP,
|
||||
RE_MATCH_SELECTED,
|
||||
RE_MATCH_NOT_SELECTED,
|
||||
RE_MATCH_RANGE,
|
||||
RE_MATCH_ANY,
|
||||
RE_MATCH_ANY_WORD,
|
||||
RE_MATCH_ANY_DIGIT,
|
||||
RE_MATCH_ANY_WHITESPACE,
|
||||
RE_MATCH_ONE_OR_MORE,
|
||||
RE_MATCH_ZERO_OR_MORE,
|
||||
RE_MATCH_ZERO_OR_ONE,
|
||||
} RE_MatchKind;
|
||||
|
||||
struct RE_Regex {
|
||||
RE_MatchKind kind;
|
||||
RE_Regex *next;
|
||||
RE_Regex *prev;
|
||||
|
||||
union {
|
||||
struct {
|
||||
char word_min;
|
||||
char word_max;
|
||||
};
|
||||
char word;
|
||||
uint32_t word32;
|
||||
RE_Regex *child;
|
||||
struct {
|
||||
RE_Regex *left;
|
||||
RE_Regex *right;
|
||||
};
|
||||
struct {
|
||||
RE_Regex *first;
|
||||
RE_Regex *last;
|
||||
} group;
|
||||
};
|
||||
};
|
||||
|
||||
struct RE_Match {
|
||||
RE_Int pos;
|
||||
RE_Int size;
|
||||
};
|
||||
|
||||
RE_API bool RE1_AreEqual(char *regex, char *string);
|
||||
RE_API bool RE2_AreEqual(RE_Regex *regex, char *string);
|
||||
RE_API bool RE3_AreEqual(RE_Regex *regex, char *string, RE_Int len);
|
||||
RE_API RE_Match RE1_Find(char *regex, char *string);
|
||||
RE_API RE_Match RE2_Find(RE_Regex *regex, char *string);
|
||||
RE_API RE_Match RE3_Find(RE_Regex *regex, char *string, RE_Int len);
|
||||
RE_API RE_Match RE2_FindAgain(RE_Regex *regex, char *string, RE_Match prev_match);
|
||||
RE_API RE_Match RE3_FindAgain(RE_Regex *regex, char *string, RE_Int len, RE_Match prev_match);
|
||||
RE_API RE_Int RE3_MatchFront(RE_Regex *regex, char *string, RE_Int len, char *string_front);
|
||||
RE_API RE_Regex *RE1_Parse(char *buff, RE_Int buffsize, char *string);
|
||||
RE_API RE_Regex *RE2_Parse(char *buff, RE_Int buffsize, char *string, RE_Int len);
|
||||
1931
code/stb_sprintf.h
Normal file
1931
code/stb_sprintf.h
Normal file
File diff suppressed because it is too large
Load Diff
493
code/string.c
Normal file
493
code/string.c
Normal file
@@ -0,0 +1,493 @@
|
||||
#include "string.h"
|
||||
#include <stdarg.h>
|
||||
|
||||
#ifndef S8_VSNPRINTF
|
||||
#include <stdio.h>
|
||||
#define S8_VSNPRINTF vsnprintf
|
||||
#endif
|
||||
|
||||
#ifndef S8_ALLOCATE
|
||||
#include <stdlib.h>
|
||||
#define S8_ALLOCATE(allocator, size) malloc(size)
|
||||
#endif
|
||||
|
||||
#ifndef S8_ASSERT
|
||||
#include <assert.h>
|
||||
#define S8_ASSERT(x) assert(x)
|
||||
#endif
|
||||
|
||||
#ifndef S8_MemoryCopy
|
||||
#include <string.h>
|
||||
#define S8_MemoryCopy(dst, src, s) memcpy(dst, src, s)
|
||||
#endif
|
||||
|
||||
#ifndef S8_StaticFunc
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
#define S8_StaticFunc __attribute__((unused)) static
|
||||
#else
|
||||
#define S8_StaticFunc static
|
||||
#endif
|
||||
#endif
|
||||
|
||||
S8_StaticFunc int64_t S8__ClampTop(int64_t val, int64_t max) {
|
||||
if (val > max) val = max;
|
||||
return val;
|
||||
}
|
||||
|
||||
S8_API char CHAR_ToLowerCase(char a) {
|
||||
if (a >= 'A' && a <= 'Z') a += 32;
|
||||
return a;
|
||||
}
|
||||
|
||||
S8_API char CHAR_ToUpperCase(char a) {
|
||||
if (a >= 'a' && a <= 'z') a -= 32;
|
||||
return a;
|
||||
}
|
||||
|
||||
S8_API bool CHAR_IsWhitespace(char w) {
|
||||
bool result = w == '\n' || w == ' ' || w == '\t' || w == '\v' || w == '\r';
|
||||
return result;
|
||||
}
|
||||
|
||||
S8_API bool CHAR_IsAlphabetic(char a) {
|
||||
bool result = (a >= 'a' && a <= 'z') || (a >= 'A' && a <= 'Z');
|
||||
return result;
|
||||
}
|
||||
|
||||
S8_API bool CHAR_IsIdent(char a) {
|
||||
bool result = (a >= 'a' && a <= 'z') || (a >= 'A' && a <= 'Z') || a == '_';
|
||||
return result;
|
||||
}
|
||||
|
||||
S8_API bool CHAR_IsDigit(char a) {
|
||||
bool result = a >= '0' && a <= '9';
|
||||
return result;
|
||||
}
|
||||
|
||||
S8_API bool CHAR_IsAlphanumeric(char a) {
|
||||
bool result = CHAR_IsDigit(a) || CHAR_IsAlphabetic(a);
|
||||
return result;
|
||||
}
|
||||
|
||||
S8_API bool S8_AreEqual(S8_String a, S8_String b, unsigned ignore_case) {
|
||||
if (a.len != b.len) return false;
|
||||
for (int64_t i = 0; i < a.len; i++) {
|
||||
char A = a.str[i];
|
||||
char B = b.str[i];
|
||||
if (ignore_case & S8_IGNORE_CASE) {
|
||||
A = CHAR_ToLowerCase(A);
|
||||
B = CHAR_ToLowerCase(B);
|
||||
}
|
||||
if (A != B)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
S8_API bool S8_EndsWith(S8_String a, S8_String end, unsigned ignore_case) {
|
||||
S8_String a_end = S8_GetPostfix(a, end.len);
|
||||
bool result = S8_AreEqual(end, a_end, ignore_case);
|
||||
return result;
|
||||
}
|
||||
|
||||
S8_API bool S8_StartsWith(S8_String a, S8_String start, unsigned ignore_case) {
|
||||
S8_String a_start = S8_GetPrefix(a, start.len);
|
||||
bool result = S8_AreEqual(start, a_start, ignore_case);
|
||||
return result;
|
||||
}
|
||||
|
||||
S8_API S8_String S8_Make(char *str, int64_t len) {
|
||||
S8_String result;
|
||||
result.str = (char *)str;
|
||||
result.len = len;
|
||||
return result;
|
||||
}
|
||||
|
||||
S8_API S8_String S8_Copy(S8_Allocator allocator, S8_String string) {
|
||||
char *copy = (char *)S8_ALLOCATE(allocator, sizeof(char) * (string.len + 1));
|
||||
S8_MemoryCopy(copy, string.str, string.len);
|
||||
copy[string.len] = 0;
|
||||
S8_String result = S8_Make(copy, string.len);
|
||||
return result;
|
||||
}
|
||||
|
||||
S8_API void S8_NormalizePath(S8_String s) {
|
||||
for (int64_t i = 0; i < s.len; i++) {
|
||||
if (s.str[i] == '\\')
|
||||
s.str[i] = '/';
|
||||
}
|
||||
}
|
||||
|
||||
S8_API S8_String S8_Chop(S8_String string, int64_t len) {
|
||||
len = S8__ClampTop(len, string.len);
|
||||
S8_String result = S8_Make(string.str, string.len - len);
|
||||
return result;
|
||||
}
|
||||
|
||||
S8_API S8_String S8_Skip(S8_String string, int64_t len) {
|
||||
len = S8__ClampTop(len, string.len);
|
||||
int64_t remain = string.len - len;
|
||||
S8_String result = S8_Make(string.str + len, remain);
|
||||
return result;
|
||||
}
|
||||
|
||||
S8_API bool S8_IsPointerInside(S8_String string, char *p) {
|
||||
uintptr_t pointer = (uintptr_t)p;
|
||||
uintptr_t start = (uintptr_t)string.str;
|
||||
uintptr_t stop = start + (uintptr_t)string.len;
|
||||
bool result = pointer >= start && pointer < stop;
|
||||
return result;
|
||||
}
|
||||
|
||||
S8_API S8_String S8_SkipToP(S8_String string, char *p) {
|
||||
if (S8_IsPointerInside(string, p)) {
|
||||
S8_String result = S8_Make(p, p - string.str);
|
||||
return result;
|
||||
}
|
||||
return string;
|
||||
}
|
||||
|
||||
S8_API S8_String S8_SkipPast(S8_String string, S8_String a) {
|
||||
if (S8_IsPointerInside(string, a.str)) {
|
||||
S8_String on_p = S8_Make(a.str, a.str - string.str);
|
||||
S8_String result = S8_Skip(on_p, a.len);
|
||||
return result;
|
||||
}
|
||||
return string;
|
||||
}
|
||||
|
||||
S8_API S8_String S8_GetPostfix(S8_String string, int64_t len) {
|
||||
len = S8__ClampTop(len, string.len);
|
||||
int64_t remain_len = string.len - len;
|
||||
S8_String result = S8_Make(string.str + remain_len, len);
|
||||
return result;
|
||||
}
|
||||
|
||||
S8_API S8_String S8_GetPrefix(S8_String string, int64_t len) {
|
||||
len = S8__ClampTop(len, string.len);
|
||||
S8_String result = S8_Make(string.str, len);
|
||||
return result;
|
||||
}
|
||||
|
||||
S8_API S8_String S8_Slice(S8_String string, int64_t first_index, int64_t one_past_last_index) {
|
||||
if (one_past_last_index < 0) one_past_last_index = string.len + one_past_last_index + 1;
|
||||
if (first_index < 0) first_index = string.len + first_index;
|
||||
S8_ASSERT(first_index < one_past_last_index && "S8_Slice, first_index is bigger then one_past_last_index");
|
||||
S8_ASSERT(string.len > 0 && "Slicing string of length 0! Might be an error!");
|
||||
S8_String result = string;
|
||||
if (string.len > 0) {
|
||||
if (one_past_last_index > first_index) {
|
||||
first_index = S8__ClampTop(first_index, string.len - 1);
|
||||
one_past_last_index = S8__ClampTop(one_past_last_index, string.len);
|
||||
result.str += first_index;
|
||||
result.len = one_past_last_index - first_index;
|
||||
}
|
||||
else {
|
||||
result.len = 0;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
S8_API S8_String S8_Trim(S8_String string) {
|
||||
if (string.len == 0)
|
||||
return string;
|
||||
|
||||
int64_t whitespace_begin = 0;
|
||||
for (; whitespace_begin < string.len; whitespace_begin++) {
|
||||
if (!CHAR_IsWhitespace(string.str[whitespace_begin])) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int64_t whitespace_end = string.len;
|
||||
for (; whitespace_end != whitespace_begin; whitespace_end--) {
|
||||
if (!CHAR_IsWhitespace(string.str[whitespace_end - 1])) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (whitespace_begin == whitespace_end) {
|
||||
string.len = 0;
|
||||
}
|
||||
else {
|
||||
string = S8_Slice(string, whitespace_begin, whitespace_end);
|
||||
}
|
||||
|
||||
return string;
|
||||
}
|
||||
|
||||
S8_API S8_String S8_TrimEnd(S8_String string) {
|
||||
int64_t whitespace_end = string.len;
|
||||
for (; whitespace_end != 0; whitespace_end--) {
|
||||
if (!CHAR_IsWhitespace(string.str[whitespace_end - 1])) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
S8_String result = S8_GetPrefix(string, whitespace_end);
|
||||
return result;
|
||||
}
|
||||
|
||||
S8_API S8_String S8_ToLowerCase(S8_Allocator allocator, S8_String s) {
|
||||
S8_String copy = S8_Copy(allocator, s);
|
||||
for (int64_t i = 0; i < copy.len; i++) {
|
||||
copy.str[i] = CHAR_ToLowerCase(copy.str[i]);
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
|
||||
S8_API S8_String S8_ToUpperCase(S8_Allocator allocator, S8_String s) {
|
||||
S8_String copy = S8_Copy(allocator, s);
|
||||
for (int64_t i = 0; i < copy.len; i++) {
|
||||
copy.str[i] = CHAR_ToUpperCase(copy.str[i]);
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
|
||||
S8_API bool S8_Find(S8_String string, S8_String find, unsigned flags, int64_t *index_out) {
|
||||
bool result = false;
|
||||
if (flags & S8_MATCH_FIND_LAST) {
|
||||
for (int64_t i = string.len; i != 0; i--) {
|
||||
int64_t index = i - 1;
|
||||
S8_String substring = S8_Slice(string, index, index + find.len);
|
||||
if (S8_AreEqual(substring, find, flags)) {
|
||||
if (index_out)
|
||||
*index_out = index;
|
||||
result = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (int64_t i = 0; i < string.len; i++) {
|
||||
S8_String substring = S8_Slice(string, i, i + find.len);
|
||||
if (S8_AreEqual(substring, find, flags)) {
|
||||
if (index_out)
|
||||
*index_out = i;
|
||||
result = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
S8_API S8_List S8_Split(S8_Allocator allocator, S8_String string, S8_String find, unsigned flags) {
|
||||
S8_ASSERT((flags & S8_MATCH_FIND_LAST) == 0);
|
||||
S8_List result = S8_MakeEmptyList();
|
||||
int64_t index = 0;
|
||||
while (S8_Find(string, find, flags, &index)) {
|
||||
S8_String before_match = S8_Make(string.str, index);
|
||||
S8_AddNode(allocator, &result, before_match);
|
||||
if (flags & S8_SPLIT_INCLUSIVE) {
|
||||
S8_String match = S8_Make(string.str + index, find.len);
|
||||
S8_AddNode(allocator, &result, match);
|
||||
}
|
||||
string = S8_Skip(string, index + find.len);
|
||||
}
|
||||
if (string.len) S8_AddNode(allocator, &result, string);
|
||||
return result;
|
||||
}
|
||||
|
||||
S8_API S8_String S8_MergeWithSeparator(S8_Allocator allocator, S8_List list, S8_String separator) {
|
||||
if (list.node_count == 0) return S8_MakeEmpty();
|
||||
if (list.char_count == 0) return S8_MakeEmpty();
|
||||
// S8_ASSERT((flags & S8_MATCH_FIND_LAST) == 0);
|
||||
int64_t base_size = (list.char_count + 1);
|
||||
int64_t sep_size = (list.node_count - 1) * separator.len;
|
||||
int64_t size = base_size + sep_size;
|
||||
char *buff = (char *)S8_ALLOCATE(allocator, sizeof(char) * size);
|
||||
S8_String string = S8_Make(buff, 0);
|
||||
for (S8_Node *it = list.first; it; it = it->next) {
|
||||
S8_ASSERT(string.len + it->string.len <= size);
|
||||
S8_MemoryCopy(string.str + string.len, it->string.str, it->string.len);
|
||||
string.len += it->string.len;
|
||||
if (it != list.last) {
|
||||
S8_MemoryCopy(string.str + string.len, separator.str, separator.len);
|
||||
string.len += separator.len;
|
||||
}
|
||||
}
|
||||
S8_ASSERT(string.len == size - 1);
|
||||
string.str[size] = 0;
|
||||
return string;
|
||||
}
|
||||
|
||||
S8_API S8_String S8_Merge(S8_Allocator allocator, S8_List list) {
|
||||
return S8_MergeWithSeparator(allocator, list, S8_Lit(""));
|
||||
}
|
||||
|
||||
S8_API S8_String S8_ReplaceAll(S8_Allocator allocator, S8_String string, S8_String replace, S8_String with, unsigned flags) {
|
||||
S8_ASSERT((flags & S8_MATCH_FIND_LAST) == 0);
|
||||
S8_List list = S8_Split(allocator, string, replace, flags | S8_SPLIT_INCLUSIVE);
|
||||
for (S8_Node *it = list.first; it; it = it->next) {
|
||||
if (S8_AreEqual(it->string, replace, flags)) {
|
||||
S8_ReplaceNodeString(&list, it, with);
|
||||
}
|
||||
}
|
||||
S8_String result = S8_Merge(allocator, list);
|
||||
return result;
|
||||
}
|
||||
|
||||
S8_API S8_List S8_FindAll(S8_Allocator allocator, S8_String string, S8_String find, unsigned flags) { // @untested
|
||||
S8_ASSERT((flags & S8_MATCH_FIND_LAST) == 0);
|
||||
S8_List result = S8_MakeEmptyList();
|
||||
int64_t index = 0;
|
||||
while (S8_Find(string, find, flags, &index)) {
|
||||
S8_String match = S8_Make(string.str + index, find.len);
|
||||
S8_AddNode(allocator, &result, match);
|
||||
string = S8_Skip(string, index + find.len);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
S8_API S8_String S8_ChopLastSlash(S8_String s) {
|
||||
S8_String result = s;
|
||||
S8_Find(s, S8_Lit("/"), S8_MATCH_FIND_LAST, &result.len);
|
||||
return result;
|
||||
}
|
||||
|
||||
S8_API S8_String S8_ChopLastPeriod(S8_String s) {
|
||||
S8_String result = s;
|
||||
S8_Find(s, S8_Lit("."), S8_MATCH_FIND_LAST, &result.len);
|
||||
return result;
|
||||
}
|
||||
|
||||
S8_API S8_String S8_SkipToLastSlash(S8_String s) {
|
||||
int64_t pos;
|
||||
S8_String result = s;
|
||||
if (S8_Find(s, S8_Lit("/"), S8_MATCH_FIND_LAST, &pos)) {
|
||||
result = S8_Skip(result, pos + 1);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
S8_API S8_String S8_SkipToLastPeriod(S8_String s) {
|
||||
int64_t pos;
|
||||
S8_String result = s;
|
||||
if (S8_Find(s, S8_Lit("."), S8_MATCH_FIND_LAST, &pos)) {
|
||||
result = S8_Skip(result, pos + 1);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
S8_API int64_t S8_Length(char *string) {
|
||||
int64_t len = 0;
|
||||
while (*string++ != 0)
|
||||
len++;
|
||||
return len;
|
||||
}
|
||||
|
||||
S8_API int64_t S8_WideLength(wchar_t *string) {
|
||||
int64_t len = 0;
|
||||
while (*string++ != 0)
|
||||
len++;
|
||||
return len;
|
||||
}
|
||||
|
||||
S8_API S8_String S8_MakeFromChar(char *string) {
|
||||
S8_String result;
|
||||
result.str = (char *)string;
|
||||
result.len = S8_Length(string);
|
||||
return result;
|
||||
}
|
||||
|
||||
S8_API S8_String S8_MakeEmpty(void) {
|
||||
return S8_Make(0, 0);
|
||||
}
|
||||
|
||||
S8_API S8_List S8_MakeEmptyList(void) {
|
||||
S8_List result;
|
||||
result.first = 0;
|
||||
result.last = 0;
|
||||
result.char_count = 0;
|
||||
result.node_count = 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
S8_API S8_String S8_FormatV(S8_Allocator allocator, const char *str, va_list args1) {
|
||||
va_list args2;
|
||||
va_copy(args2, args1);
|
||||
int64_t len = S8_VSNPRINTF(0, 0, str, args2);
|
||||
va_end(args2);
|
||||
|
||||
char *result = (char *)S8_ALLOCATE(allocator, sizeof(char) * (len + 1));
|
||||
S8_VSNPRINTF(result, (int)(len + 1), str, args1);
|
||||
S8_String res = S8_Make(result, len);
|
||||
return res;
|
||||
}
|
||||
|
||||
S8_API S8_String S8_Format(S8_Allocator allocator, const char *str, ...) {
|
||||
S8_FORMAT(allocator, str, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
S8_API S8_Node *S8_CreateNode(S8_Allocator allocator, S8_String string) {
|
||||
S8_Node *result = (S8_Node *)S8_ALLOCATE(allocator, sizeof(S8_Node));
|
||||
result->string = string;
|
||||
result->next = 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
S8_API void S8_ReplaceNodeString(S8_List *list, S8_Node *node, S8_String new_string) {
|
||||
list->char_count -= node->string.len;
|
||||
list->char_count += new_string.len;
|
||||
node->string = new_string;
|
||||
}
|
||||
|
||||
S8_API void S8_AddExistingNode(S8_List *list, S8_Node *node) {
|
||||
if (list->first) {
|
||||
list->last->next = node;
|
||||
list->last = list->last->next;
|
||||
}
|
||||
else {
|
||||
list->first = list->last = node;
|
||||
}
|
||||
list->node_count += 1;
|
||||
list->char_count += node->string.len;
|
||||
}
|
||||
|
||||
S8_API void S8_AddArray(S8_Allocator allocator, S8_List *list, char **array, int count) {
|
||||
for (int i = 0; i < count; i += 1) {
|
||||
S8_String s = S8_MakeFromChar(array[i]);
|
||||
S8_AddNode(allocator, list, s);
|
||||
}
|
||||
}
|
||||
|
||||
S8_API void S8_AddArrayWithPrefix(S8_Allocator allocator, S8_List *list, char *prefix, char **array, int count) {
|
||||
for (int i = 0; i < count; i += 1) {
|
||||
S8_AddF(allocator, list, "%s%s", prefix, array[i]);
|
||||
}
|
||||
}
|
||||
|
||||
S8_API S8_List S8_MakeList(S8_Allocator allocator, S8_String a) {
|
||||
S8_List result = S8_MakeEmptyList();
|
||||
S8_AddNode(allocator, &result, a);
|
||||
return result;
|
||||
}
|
||||
|
||||
S8_API S8_List S8_CopyList(S8_Allocator allocator, S8_List a) {
|
||||
S8_List result = S8_MakeEmptyList();
|
||||
for (S8_Node *it = a.first; it; it = it->next) S8_AddNode(allocator, &result, it->string);
|
||||
return result;
|
||||
}
|
||||
|
||||
S8_API S8_List S8_ConcatLists(S8_Allocator allocator, S8_List a, S8_List b) {
|
||||
S8_List result = S8_MakeEmptyList();
|
||||
for (S8_Node *it = a.first; it; it = it->next) S8_AddNode(allocator, &result, it->string);
|
||||
for (S8_Node *it = b.first; it; it = it->next) S8_AddNode(allocator, &result, it->string);
|
||||
return result;
|
||||
}
|
||||
|
||||
S8_API S8_Node *S8_AddNode(S8_Allocator allocator, S8_List *list, S8_String string) {
|
||||
S8_Node *node = S8_CreateNode(allocator, string);
|
||||
S8_AddExistingNode(list, node);
|
||||
return node;
|
||||
}
|
||||
|
||||
S8_API S8_String S8_AddF(S8_Allocator allocator, S8_List *list, const char *str, ...) {
|
||||
S8_FORMAT(allocator, str, result);
|
||||
S8_AddNode(allocator, list, result);
|
||||
return result;
|
||||
}
|
||||
116
code/string.h
Normal file
116
code/string.h
Normal file
@@ -0,0 +1,116 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifndef S8_API
|
||||
#ifdef __cplusplus
|
||||
#define S8_API extern "C"
|
||||
#else
|
||||
#define S8_API
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef S8_Allocator
|
||||
struct MA_Arena;
|
||||
#define S8_Allocator MA_Arena *
|
||||
#endif
|
||||
|
||||
typedef struct S8_String S8_String;
|
||||
typedef struct S8_Node S8_Node;
|
||||
typedef struct S8_List S8_List;
|
||||
|
||||
struct S8_String {
|
||||
char *str;
|
||||
int64_t len;
|
||||
};
|
||||
|
||||
struct S8_Node {
|
||||
S8_Node *next;
|
||||
S8_String string;
|
||||
};
|
||||
|
||||
struct S8_List {
|
||||
int64_t node_count;
|
||||
int64_t char_count;
|
||||
S8_Node *first;
|
||||
S8_Node *last;
|
||||
};
|
||||
|
||||
enum {
|
||||
S8_NO_FLAGS = 0,
|
||||
S8_IGNORE_CASE = 1,
|
||||
S8_SPLIT_INCLUSIVE = 4,
|
||||
S8_MATCH_FIND_LAST = 32,
|
||||
};
|
||||
|
||||
#define S8_Lit(string) S8_Make((char *)string, sizeof(string) - 1)
|
||||
#define S8_ConstLit(string) \
|
||||
{ string, sizeof(string) - 1 }
|
||||
#define S8_Expand(string) (int)(string).len, (string).str
|
||||
|
||||
#define S8_FORMAT(allocator, str, result) \
|
||||
va_list args1; \
|
||||
va_start(args1, str); \
|
||||
S8_String result = S8_FormatV(allocator, str, args1); \
|
||||
va_end(args1)
|
||||
|
||||
#define S8_For(it, x) for (S8_Node *it = (x).first; it; it = it->next)
|
||||
|
||||
#if defined(__cplusplus)
|
||||
S8_API bool S8_AreEqual(S8_String a, S8_String b, unsigned ignore_case);
|
||||
inline bool operator==(S8_String a, S8_String b) { return S8_AreEqual(a, b, false); }
|
||||
inline S8_String operator""_s(const char *str, size_t size) { return {(char *)str, (int64_t)size}; }
|
||||
#endif
|
||||
|
||||
S8_API char CHAR_ToLowerCase(char a);
|
||||
S8_API char CHAR_ToUpperCase(char a);
|
||||
S8_API bool CHAR_IsWhitespace(char w);
|
||||
S8_API bool CHAR_IsAlphabetic(char a);
|
||||
S8_API bool CHAR_IsIdent(char a);
|
||||
S8_API bool CHAR_IsDigit(char a);
|
||||
S8_API bool CHAR_IsAlphanumeric(char a);
|
||||
S8_API bool S8_AreEqual(S8_String a, S8_String b, unsigned ignore_case);
|
||||
S8_API bool S8_EndsWith(S8_String a, S8_String end, unsigned ignore_case);
|
||||
S8_API bool S8_StartsWith(S8_String a, S8_String start, unsigned ignore_case);
|
||||
S8_API S8_String S8_Make(char *str, int64_t len);
|
||||
S8_API S8_String S8_Copy(S8_Allocator allocator, S8_String string);
|
||||
S8_API void S8_NormalizePath(S8_String s);
|
||||
S8_API S8_String S8_Chop(S8_String string, int64_t len);
|
||||
S8_API S8_String S8_Skip(S8_String string, int64_t len);
|
||||
S8_API S8_String S8_GetPostfix(S8_String string, int64_t len);
|
||||
S8_API S8_String S8_GetPrefix(S8_String string, int64_t len);
|
||||
S8_API S8_String S8_Slice(S8_String string, int64_t first_index, int64_t one_past_last_index);
|
||||
S8_API S8_String S8_Trim(S8_String string);
|
||||
S8_API S8_String S8_TrimEnd(S8_String string);
|
||||
S8_API S8_String S8_ToLowerCase(S8_Allocator allocator, S8_String s);
|
||||
S8_API S8_String S8_ToUpperCase(S8_Allocator allocator, S8_String s);
|
||||
S8_API bool S8_Find(S8_String string, S8_String find, unsigned flags, int64_t *index_out);
|
||||
S8_API S8_List S8_Split(S8_Allocator allocator, S8_String string, S8_String find, unsigned flags);
|
||||
S8_API S8_String S8_MergeWithSeparator(S8_Allocator allocator, S8_List list, S8_String separator);
|
||||
S8_API S8_String S8_Merge(S8_Allocator allocator, S8_List list);
|
||||
S8_API S8_String S8_ReplaceAll(S8_Allocator allocator, S8_String string, S8_String replace, S8_String with, unsigned flags);
|
||||
S8_API S8_List S8_FindAll(S8_Allocator allocator, S8_String string, S8_String find, unsigned flags);
|
||||
S8_API S8_String S8_ChopLastSlash(S8_String s);
|
||||
S8_API S8_String S8_ChopLastPeriod(S8_String s);
|
||||
S8_API S8_String S8_SkipToLastSlash(S8_String s);
|
||||
S8_API S8_String S8_SkipToLastPeriod(S8_String s);
|
||||
S8_API bool S8_IsPointerInside(S8_String string, char *p);
|
||||
S8_API S8_String S8_SkipToP(S8_String string, char *p);
|
||||
S8_API S8_String S8_SkipPast(S8_String string, S8_String a);
|
||||
S8_API int64_t S8_Length(char *string);
|
||||
S8_API int64_t S8_WideLength(wchar_t *string);
|
||||
S8_API S8_String S8_MakeFromChar(char *string);
|
||||
S8_API S8_String S8_MakeEmpty(void);
|
||||
S8_API S8_List S8_MakeEmptyList(void);
|
||||
S8_API S8_String S8_FormatV(S8_Allocator allocator, const char *str, va_list args1);
|
||||
S8_API S8_String S8_Format(S8_Allocator allocator, const char *str, ...);
|
||||
S8_API S8_Node *S8_CreateNode(S8_Allocator allocator, S8_String string);
|
||||
S8_API void S8_ReplaceNodeString(S8_List *list, S8_Node *node, S8_String new_string);
|
||||
S8_API void S8_AddExistingNode(S8_List *list, S8_Node *node);
|
||||
S8_API void S8_AddArray(S8_Allocator allocator, S8_List *list, char **array, int count);
|
||||
S8_API void S8_AddArrayWithPrefix(S8_Allocator allocator, S8_List *list, char *prefix, char **array, int count);
|
||||
S8_API S8_List S8_MakeList(S8_Allocator allocator, S8_String a);
|
||||
S8_API S8_List S8_CopyList(S8_Allocator allocator, S8_List a);
|
||||
S8_API S8_List S8_ConcatLists(S8_Allocator allocator, S8_List a, S8_List b);
|
||||
S8_API S8_Node *S8_AddNode(S8_Allocator allocator, S8_List *list, S8_String string);
|
||||
S8_API S8_String S8_AddF(S8_Allocator allocator, S8_List *list, const char *str, ...);
|
||||
232
code/table.hpp
Normal file
232
code/table.hpp
Normal file
@@ -0,0 +1,232 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
/*
|
||||
Hash table implementation:
|
||||
Pointers to values
|
||||
Open adressing
|
||||
Linear Probing
|
||||
Power of 2
|
||||
Robin Hood hashing
|
||||
Resizes on high probe count (min max load factor)
|
||||
|
||||
Hash 0 is reserved for empty hash table entry
|
||||
*/
|
||||
#ifndef TABLE_PRIVATE_FUNCTION
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
#define TABLE_PRIVATE_FUNCTION __attribute__((unused)) static
|
||||
#else
|
||||
#define TABLE_PRIVATE_FUNCTION static
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef TABLE_Allocator
|
||||
#define TABLE_Allocator void *
|
||||
#endif
|
||||
|
||||
#ifndef TABLE_ALLOCATE
|
||||
#include <stdlib.h>
|
||||
#define TABLE_ALLOCATE(allocator, size) malloc(size)
|
||||
#endif
|
||||
|
||||
#ifndef TABLE_DEALLOCATE
|
||||
#include <stdlib.h>
|
||||
#define TABLE_DEALLOCATE(allocator, p) free(p)
|
||||
#endif
|
||||
|
||||
#ifndef TABLE_ASSERT
|
||||
#include <assert.h>
|
||||
#define TABLE_ASSERT(x) assert(x)
|
||||
#endif
|
||||
|
||||
#ifndef TABLE_SET_DEFAULT_ALLOCATOR
|
||||
#define TABLE_SET_DEFAULT_ALLOCATOR
|
||||
// Example:
|
||||
// #define TABLE_SET_DEFAULT_ALLOCATOR if (!allocator) allocator = global_heap;
|
||||
#endif
|
||||
|
||||
#ifndef TABLE_HASH_BYTES
|
||||
#define TABLE_HASH_BYTES TABLE__HashBytes
|
||||
TABLE_PRIVATE_FUNCTION uint64_t TABLE__HashBytes(void *data, unsigned size) {
|
||||
uint8_t *data8 = (uint8_t *)data;
|
||||
uint64_t hash = (uint64_t)14695981039346656037ULL;
|
||||
for (unsigned i = 0; i < size; i++) {
|
||||
hash = hash ^ (uint64_t)(data8[i]);
|
||||
hash = hash * (uint64_t)1099511628211ULL;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
#endif
|
||||
|
||||
#define TABLE__WRAP_AROUND_POWER_OF_2(x, pow2) (((x) & ((pow2)-1llu)))
|
||||
#define TABLE__IS_POW2(x) (((x) & ((x)-1)) == 0)
|
||||
|
||||
TABLE_PRIVATE_FUNCTION int TABLE_CStringLen(char *str) {
|
||||
int i = 0;
|
||||
while(str[i]) i += 1;
|
||||
return i;
|
||||
}
|
||||
|
||||
template <class Value>
|
||||
struct Table {
|
||||
struct Entry {
|
||||
uint64_t hash;
|
||||
uint64_t key;
|
||||
size_t distance;
|
||||
Value value;
|
||||
};
|
||||
|
||||
TABLE_Allocator allocator;
|
||||
size_t len, cap;
|
||||
Entry *values;
|
||||
|
||||
static const size_t max_load_factor = 80;
|
||||
static const size_t min_load_factor = 50;
|
||||
static const size_t significant_distance = 8;
|
||||
|
||||
// load factor calculation was rearranged
|
||||
// to get rid of division:
|
||||
//> 100 * len / cap = load_factor
|
||||
//> len * 100 = load_factor * cap
|
||||
inline bool reached_load_factor(size_t lfactor) {
|
||||
return (len + 1) * 100 >= lfactor * cap;
|
||||
}
|
||||
|
||||
inline bool is_empty(Entry *entry) { return entry->hash == 0; }
|
||||
inline bool is_occupied(Entry *entry) { return entry->hash != 0; }
|
||||
|
||||
void reserve(size_t size) {
|
||||
TABLE_ASSERT(size > cap && "New size is smaller then original size");
|
||||
TABLE_ASSERT(TABLE__IS_POW2(size));
|
||||
TABLE_SET_DEFAULT_ALLOCATOR;
|
||||
|
||||
Entry *old_values = values;
|
||||
size_t old_cap = cap;
|
||||
|
||||
values = (Entry *)TABLE_ALLOCATE(allocator, sizeof(Entry) * size);
|
||||
for (int i = 0; i < size; i += 1) values[i] = {};
|
||||
cap = size;
|
||||
|
||||
TABLE_ASSERT(!(old_values == 0 && len != 0));
|
||||
if (len == 0) {
|
||||
if (old_values) TABLE_DEALLOCATE(allocator, old_values);
|
||||
return;
|
||||
}
|
||||
|
||||
len = 0;
|
||||
for (size_t i = 0; i < old_cap; i += 1) {
|
||||
Entry *it = old_values + i;
|
||||
if (is_occupied(it)) {
|
||||
insert(it->key, it->value);
|
||||
}
|
||||
}
|
||||
TABLE_DEALLOCATE(allocator, old_values);
|
||||
}
|
||||
|
||||
Entry *get_table_entry(uint64_t key) {
|
||||
uint64_t hash = TABLE_HASH_BYTES(&key, sizeof(key));
|
||||
if (hash == 0) hash += 1;
|
||||
uint64_t index = TABLE__WRAP_AROUND_POWER_OF_2(hash, cap);
|
||||
uint64_t i = index;
|
||||
uint64_t distance = 0;
|
||||
for (;;) {
|
||||
Entry *it = values + i;
|
||||
if (distance > it->distance) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (it->hash == hash && it->key == key) {
|
||||
return it;
|
||||
}
|
||||
|
||||
distance += 1;
|
||||
i = TABLE__WRAP_AROUND_POWER_OF_2(i + 1, cap);
|
||||
if (i == index) return 0;
|
||||
}
|
||||
TABLE_ASSERT(!"Invalid codepath");
|
||||
}
|
||||
|
||||
void insert(uint64_t key, const Value &value) {
|
||||
if (reached_load_factor(max_load_factor)) {
|
||||
if (cap == 0) cap = 16; // 32 cause cap*2
|
||||
reserve(cap * 2);
|
||||
}
|
||||
|
||||
uint64_t hash = TABLE_HASH_BYTES(&key, sizeof(key));
|
||||
if (hash == 0) hash += 1;
|
||||
uint64_t index = TABLE__WRAP_AROUND_POWER_OF_2(hash, cap);
|
||||
uint64_t i = index;
|
||||
Entry to_insert = {hash, key, 0, value};
|
||||
for (;;) {
|
||||
Entry *it = values + i;
|
||||
if (is_empty(it)) {
|
||||
*it = to_insert;
|
||||
len += 1;
|
||||
// If we have more then 8 consecutive items we try to resize
|
||||
if (to_insert.distance > 8 && reached_load_factor(min_load_factor)) {
|
||||
reserve(cap * 2);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (it->hash == hash && it->key == key) {
|
||||
*it = to_insert;
|
||||
// If we have more then 8 consecutive items we try to resize
|
||||
if (to_insert.distance > 8 && reached_load_factor(min_load_factor)) {
|
||||
reserve(cap * 2);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Robin hood hashing
|
||||
if (to_insert.distance > it->distance) {
|
||||
Entry temp = to_insert;
|
||||
to_insert = *it;
|
||||
*it = temp;
|
||||
}
|
||||
|
||||
to_insert.distance += 1;
|
||||
i = TABLE__WRAP_AROUND_POWER_OF_2(i + 1, cap);
|
||||
TABLE_ASSERT(i != index && "Did a full 360 through a hash table, no good :( that shouldnt be possible");
|
||||
}
|
||||
TABLE_ASSERT(!"Invalid codepath");
|
||||
}
|
||||
|
||||
void remove(uint64_t key) {
|
||||
Entry *entry = get_table_entry(key);
|
||||
entry->hash = 0;
|
||||
entry->distance = 0;
|
||||
len -= 1;
|
||||
}
|
||||
|
||||
Value *get(uint64_t key) {
|
||||
Entry *v = get_table_entry(key);
|
||||
if (!v) return 0;
|
||||
return &v->value;
|
||||
}
|
||||
|
||||
Value *gets(char *str) {
|
||||
int len = TABLE_CStringLen(str);
|
||||
uint64_t hash = TABLE_HASH_BYTES(str, len);
|
||||
return get(hash);
|
||||
}
|
||||
|
||||
void puts(char *str, const Value &value) {
|
||||
int len = TABLE_CStringLen(str);
|
||||
uint64_t hash = TABLE_HASH_BYTES(str, len);
|
||||
insert(hash, value);
|
||||
}
|
||||
|
||||
void reset() {
|
||||
len = 0;
|
||||
for (size_t i = 0; i < cap; i += 1) {
|
||||
Entry *it = values + i;
|
||||
it->hash = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void dealloc() {
|
||||
TABLE_DEALLOCATE(allocator, values);
|
||||
len = 0;
|
||||
cap = 0;
|
||||
values = 0;
|
||||
}
|
||||
};
|
||||
210
code/unicode.c
Normal file
210
code/unicode.c
Normal file
@@ -0,0 +1,210 @@
|
||||
#include "unicode.h"
|
||||
|
||||
#ifndef UTF__MemoryZero
|
||||
#include <string.h>
|
||||
#define UTF__MemoryZero(p, size) memset(p, 0, size)
|
||||
#endif
|
||||
|
||||
UTF_API UTF32_Result UTF_ConvertUTF16ToUTF32(uint16_t *c, int max_advance) {
|
||||
UTF32_Result result;
|
||||
UTF__MemoryZero(&result, sizeof(result));
|
||||
if (max_advance >= 1) {
|
||||
result.advance = 1;
|
||||
result.out_str = c[0];
|
||||
if (c[0] >= 0xD800 && c[0] <= 0xDBFF && c[1] >= 0xDC00 && c[1] <= 0xDFFF) {
|
||||
if (max_advance >= 2) {
|
||||
result.out_str = 0x10000;
|
||||
result.out_str += (uint32_t)(c[0] & 0x03FF) << 10u | (c[1] & 0x03FF);
|
||||
result.advance = 2;
|
||||
}
|
||||
else
|
||||
result.error = 2;
|
||||
}
|
||||
}
|
||||
else {
|
||||
result.error = 1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
UTF_API UTF8_Result UTF_ConvertUTF32ToUTF8(uint32_t codepoint) {
|
||||
UTF8_Result result;
|
||||
UTF__MemoryZero(&result, sizeof(result));
|
||||
|
||||
if (codepoint <= 0x7F) {
|
||||
result.len = 1;
|
||||
result.out_str[0] = (char)codepoint;
|
||||
}
|
||||
else if (codepoint <= 0x7FF) {
|
||||
result.len = 2;
|
||||
result.out_str[0] = 0xc0 | (0x1f & (codepoint >> 6));
|
||||
result.out_str[1] = 0x80 | (0x3f & codepoint);
|
||||
}
|
||||
else if (codepoint <= 0xFFFF) { // 16 bit word
|
||||
result.len = 3;
|
||||
result.out_str[0] = 0xe0 | (0xf & (codepoint >> 12)); // 4 bits
|
||||
result.out_str[1] = 0x80 | (0x3f & (codepoint >> 6)); // 6 bits
|
||||
result.out_str[2] = 0x80 | (0x3f & codepoint); // 6 bits
|
||||
}
|
||||
else if (codepoint <= 0x10FFFF) { // 21 bit word
|
||||
result.len = 4;
|
||||
result.out_str[0] = 0xf0 | (0x7 & (codepoint >> 18)); // 3 bits
|
||||
result.out_str[1] = 0x80 | (0x3f & (codepoint >> 12)); // 6 bits
|
||||
result.out_str[2] = 0x80 | (0x3f & (codepoint >> 6)); // 6 bits
|
||||
result.out_str[3] = 0x80 | (0x3f & codepoint); // 6 bits
|
||||
}
|
||||
else {
|
||||
result.error = 1;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
UTF_API UTF32_Result UTF_ConvertUTF8ToUTF32(char *c, int max_advance) {
|
||||
UTF32_Result result;
|
||||
UTF__MemoryZero(&result, sizeof(result));
|
||||
|
||||
if ((c[0] & 0x80) == 0) { // Check if leftmost zero of first byte is unset
|
||||
if (max_advance >= 1) {
|
||||
result.out_str = c[0];
|
||||
result.advance = 1;
|
||||
}
|
||||
else result.error = 1;
|
||||
}
|
||||
|
||||
else if ((c[0] & 0xe0) == 0xc0) {
|
||||
if ((c[1] & 0xc0) == 0x80) { // Continuation byte required
|
||||
if (max_advance >= 2) {
|
||||
result.out_str = (uint32_t)(c[0] & 0x1f) << 6u | (c[1] & 0x3f);
|
||||
result.advance = 2;
|
||||
}
|
||||
else result.error = 2;
|
||||
}
|
||||
else result.error = 2;
|
||||
}
|
||||
|
||||
else if ((c[0] & 0xf0) == 0xe0) {
|
||||
if ((c[1] & 0xc0) == 0x80 && (c[2] & 0xc0) == 0x80) { // Two continuation bytes required
|
||||
if (max_advance >= 3) {
|
||||
result.out_str = (uint32_t)(c[0] & 0xf) << 12u | (uint32_t)(c[1] & 0x3f) << 6u | (c[2] & 0x3f);
|
||||
result.advance = 3;
|
||||
}
|
||||
else result.error = 3;
|
||||
}
|
||||
else result.error = 3;
|
||||
}
|
||||
|
||||
else if ((c[0] & 0xf8) == 0xf0) {
|
||||
if ((c[1] & 0xc0) == 0x80 && (c[2] & 0xc0) == 0x80 && (c[3] & 0xc0) == 0x80) { // Three continuation bytes required
|
||||
if (max_advance >= 4) {
|
||||
result.out_str = (uint32_t)(c[0] & 0xf) << 18u | (uint32_t)(c[1] & 0x3f) << 12u | (uint32_t)(c[2] & 0x3f) << 6u | (uint32_t)(c[3] & 0x3f);
|
||||
result.advance = 4;
|
||||
}
|
||||
else result.error = 4;
|
||||
}
|
||||
else result.error = 4;
|
||||
}
|
||||
else result.error = 4;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
UTF_API UTF16_Result UTF_ConvertUTF32ToUTF16(uint32_t codepoint) {
|
||||
UTF16_Result result;
|
||||
UTF__MemoryZero(&result, sizeof(result));
|
||||
if (codepoint < 0x10000) {
|
||||
result.out_str[0] = (uint16_t)codepoint;
|
||||
result.out_str[1] = 0;
|
||||
result.len = 1;
|
||||
}
|
||||
else if (codepoint <= 0x10FFFF) {
|
||||
uint32_t code = (codepoint - 0x10000);
|
||||
result.out_str[0] = (uint16_t)(0xD800 | (code >> 10));
|
||||
result.out_str[1] = (uint16_t)(0xDC00 | (code & 0x3FF));
|
||||
result.len = 2;
|
||||
}
|
||||
else {
|
||||
result.error = 1;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#define UTF__HANDLE_DECODE_ERROR(question_mark) \
|
||||
{ \
|
||||
if (outlen < buffer_size - 1) buffer[outlen++] = (question_mark); \
|
||||
break; \
|
||||
}
|
||||
|
||||
UTF_API int64_t UTF_CreateCharFromWidechar(char *buffer, int64_t buffer_size, wchar_t *in, int64_t inlen) {
|
||||
int64_t outlen = 0;
|
||||
for (int64_t i = 0; i < inlen && in[i];) {
|
||||
UTF32_Result decode = UTF_ConvertUTF16ToUTF32((uint16_t *)(in + i), (int)(inlen - i));
|
||||
if (!decode.error) {
|
||||
i += decode.advance;
|
||||
UTF8_Result encode = UTF_ConvertUTF32ToUTF8(decode.out_str);
|
||||
if (!encode.error) {
|
||||
for (int64_t j = 0; j < encode.len; j++) {
|
||||
if (outlen < buffer_size - 1) {
|
||||
buffer[outlen++] = encode.out_str[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
else UTF__HANDLE_DECODE_ERROR('?');
|
||||
}
|
||||
else UTF__HANDLE_DECODE_ERROR('?');
|
||||
}
|
||||
|
||||
buffer[outlen] = 0;
|
||||
return outlen;
|
||||
}
|
||||
|
||||
UTF_API int64_t UTF_CreateWidecharFromChar(wchar_t *buffer, int64_t buffer_size, char *in, int64_t inlen) {
|
||||
int64_t outlen = 0;
|
||||
for (int64_t i = 0; i < inlen;) {
|
||||
UTF32_Result decode = UTF_ConvertUTF8ToUTF32(in + i, (int)(inlen - i));
|
||||
if (!decode.error) {
|
||||
i += decode.advance;
|
||||
UTF16_Result encode = UTF_ConvertUTF32ToUTF16(decode.out_str);
|
||||
if (!encode.error) {
|
||||
for (int64_t j = 0; j < encode.len; j++) {
|
||||
if (outlen < buffer_size - 1) {
|
||||
buffer[outlen++] = encode.out_str[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
else UTF__HANDLE_DECODE_ERROR(0x003f);
|
||||
}
|
||||
else UTF__HANDLE_DECODE_ERROR(0x003f);
|
||||
}
|
||||
|
||||
buffer[outlen] = 0;
|
||||
return outlen;
|
||||
}
|
||||
|
||||
UTF_API void UTF8_Advance(UTF8_Iter *iter) {
|
||||
iter->i += iter->utf8_codepoint_byte_size;
|
||||
UTF32_Result r = UTF_ConvertUTF8ToUTF32(iter->str + iter->i, iter->len - iter->i);
|
||||
if (r.error) {
|
||||
iter->item = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
iter->utf8_codepoint_byte_size = r.advance;
|
||||
iter->item = r.out_str;
|
||||
}
|
||||
|
||||
UTF_API UTF8_Iter UTF8_IterateEx(char *str, int len) {
|
||||
UTF8_Iter result;
|
||||
UTF__MemoryZero(&result, sizeof(result));
|
||||
result.str = str;
|
||||
result.len = len;
|
||||
if (len) UTF8_Advance(&result);
|
||||
return result;
|
||||
}
|
||||
|
||||
UTF_API UTF8_Iter UTF8_Iterate(char *str) {
|
||||
int length = 0;
|
||||
while (str[length]) length += 1;
|
||||
return UTF8_IterateEx(str, length);
|
||||
}
|
||||
52
code/unicode.h
Normal file
52
code/unicode.h
Normal file
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
typedef struct UTF32_Result UTF32_Result;
|
||||
typedef struct UTF8_Result UTF8_Result;
|
||||
typedef struct UTF16_Result UTF16_Result;
|
||||
typedef struct UTF8_Iter UTF8_Iter;
|
||||
|
||||
#ifndef UTF_API
|
||||
#ifdef __cplusplus
|
||||
#define UTF_API extern "C"
|
||||
#else
|
||||
#define UTF_API
|
||||
#endif
|
||||
#endif
|
||||
|
||||
struct UTF32_Result {
|
||||
uint32_t out_str;
|
||||
int advance;
|
||||
int error;
|
||||
};
|
||||
|
||||
struct UTF8_Result {
|
||||
uint8_t out_str[4];
|
||||
int len;
|
||||
int error;
|
||||
};
|
||||
|
||||
struct UTF16_Result {
|
||||
uint16_t out_str[2];
|
||||
int len;
|
||||
int error;
|
||||
};
|
||||
|
||||
struct UTF8_Iter {
|
||||
char *str;
|
||||
int len;
|
||||
int utf8_codepoint_byte_size;
|
||||
int i;
|
||||
uint32_t item;
|
||||
};
|
||||
|
||||
UTF_API UTF32_Result UTF_ConvertUTF16ToUTF32(uint16_t *c, int max_advance);
|
||||
UTF_API UTF8_Result UTF_ConvertUTF32ToUTF8(uint32_t codepoint);
|
||||
UTF_API UTF32_Result UTF_ConvertUTF8ToUTF32(char *c, int max_advance);
|
||||
UTF_API UTF16_Result UTF_ConvertUTF32ToUTF16(uint32_t codepoint);
|
||||
UTF_API int64_t UTF_CreateCharFromWidechar(char *buffer, int64_t buffer_size, wchar_t *in, int64_t inlen);
|
||||
UTF_API int64_t UTF_CreateWidecharFromChar(wchar_t *buffer, int64_t buffer_size, char *in, int64_t inlen);
|
||||
UTF_API void UTF8_Advance(UTF8_Iter *iter);
|
||||
UTF_API UTF8_Iter UTF8_IterateEx(char *str, int len);
|
||||
UTF_API UTF8_Iter UTF8_Iterate(char *str);
|
||||
|
||||
#define UTF8_For(name, str, len) for (UTF8_Iter name = UTF8_IterateEx(str, (int)len); name.item; UTF8_Advance(&name))
|
||||
Reference in New Issue
Block a user