From 11e6559a3cc76a2930788c84e627b7b90a984cdd Mon Sep 17 00:00:00 2001 From: Krzosa Karol Date: Sun, 31 Dec 2023 07:44:32 +0100 Subject: [PATCH] Init core --- .gitignore | 30 + arena.h | 626 +++++++++++++ array.hpp | 315 +++++++ clexer.h | 2391 ++++++++++++++++++++++++++++++++++++++++++++++++ core.c | 34 + core.h | 26 + defer.hpp | 21 + filesystem.c | 513 +++++++++++ filesystem.h | 67 ++ hash.c | 53 ++ hash.h | 33 + io.h | 298 ++++++ linked_list.h | 129 +++ load_library.c | 26 + load_library.h | 14 + multimedia.h | 2112 ++++++++++++++++++++++++++++++++++++++++++ preproc_env.h | 90 ++ regex.h | 656 +++++++++++++ stb_sprintf.h | 1931 ++++++++++++++++++++++++++++++++++++++ string.h | 616 +++++++++++++ table.hpp | 228 +++++ unicode.h | 280 ++++++ 22 files changed, 10489 insertions(+) create mode 100644 .gitignore create mode 100644 arena.h create mode 100644 array.hpp create mode 100644 clexer.h create mode 100644 core.c create mode 100644 core.h create mode 100644 defer.hpp create mode 100644 filesystem.c create mode 100644 filesystem.h create mode 100644 hash.c create mode 100644 hash.h create mode 100644 io.h create mode 100644 linked_list.h create mode 100644 load_library.c create mode 100644 load_library.h create mode 100644 multimedia.h create mode 100644 preproc_env.h create mode 100644 regex.h create mode 100644 stb_sprintf.h create mode 100644 string.h create mode 100644 table.hpp create mode 100644 unicode.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fa7060f --- /dev/null +++ b/.gitignore @@ -0,0 +1,30 @@ +*.exe +*.exp +*.pdb +*.obj +*.json +*.ilk +*.lib +*.pdf +*.png +*.10x +*.sublime-project +*.sublime-workspace +backup.* + +imgui.ini + +__pycache__/ +venv/ +build/ +vendor/ +assets/ +krzlinks/ +misc/ +generated/ +html/ +backup/ +notes/ +odin/ +prototyping/ +text_editor/ \ No newline at end of file diff --git a/arena.h b/arena.h new file mode 100644 index 0000000..b91522b --- /dev/null +++ b/arena.h @@ -0,0 +1,626 @@ +/* +## MA_Arena version 2 +A public domain, single-header-file library that provides easy to use +arena data structure. + +### Features + - linear allocator (MA_Arena) + - thread local scratch allocator (MA_Scratch) *optional* + - allocator abstraction (M_Allocator) *overridable* + - virtual memory abstraction (MV_Memory) + + +### Usage, do this in *one* C or C++ file: +``` +#define MA_IMPLEMENTATION +#include "arena.h" +``` + +*/ +#ifndef MA_HEADER +#define MA_HEADER +#include +#include +#include + +#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) + +#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_INHERIT_HOOK + #define MA_INHERIT_HOOK +#endif + +#ifndef MA_C_INHERIT_HOOK + #define MA_C_INHERIT_HOOK M_Allocator allocator; +#endif + +#ifndef MA_ZERO_IS_INITIALIZATION + #define MA_ZERO_IS_INITIALIZATION 1 +#endif + +#ifndef MA_INIT_HOOK + #define MA_INIT_HOOK(arena) \ + arena->allocator.obj = (void *)arena; \ + arena->allocator.p = (M_AllocatorProc *)MA_AllocatorProc; +#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; + +typedef struct MV_Memory MV_Memory; +typedef struct MA_Checkpoint MA_Checkpoint; +typedef struct MA_Arena MA_Arena; +typedef struct M_Allocator M_Allocator; +typedef void *M_AllocatorProc(void *allocator, M_AllocatorOp kind, void *p, size_t size); + +struct M_Allocator { + void *obj; + void *(*p)(void *allocator, M_AllocatorOp kind, void *p, size_t size); +}; + +struct MV_Memory { + size_t commit; + size_t reserve; + uint8_t *data; +}; + +struct MA_Arena MA_INHERIT_HOOK { + MA_C_INHERIT_HOOK + MV_Memory memory; + int alignment; + int saved_alignment; + size_t len; + + size_t packed_array_element_size; + size_t packed_array_begin; +}; + +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_BeginPackedArray(arena, T) (T *)MA__BeginPackedArray(arena, 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_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 void MA_DeallocateStub(MA_Arena *arena, void *p); +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_Reset(MA_Arena *arena); +MA_API void * MA__BeginPackedArray(MA_Arena *arena, size_t element_size); +MA_API int MA_EndPackedArray(MA_Arena *arena); +MA_API void MA_SetAlignment(MA_Arena *arena, int alignment); +MA_API uint8_t * MA_GetTop(MA_Arena *a); +MA_API void * MA_PushSizeNonZeroed(MA_Arena *a, size_t size); +MA_API void * MA_PushSize(MA_Arena *arena, size_t size); +MA_API void MA_InitEx(MA_Arena *a, size_t reserve); +MA_API void MA_Init(MA_Arena *a); +MA_API void MA_MakeSureInitialized(MA_Arena *a); +MA_API MA_Arena * MA_Bootstrap(void); +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 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 bool MA_IsPointerInside(MA_Arena *arena, void *p); +MA_API MA_Arena MA_PushArena(MA_Arena *arena, size_t size); +MA_API MA_Checkpoint MA_Save(MA_Arena *arena); +MA_API void MA_Load(MA_Checkpoint checkpoint); +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); +MA_API void * MA_AllocatorProc(M_Allocator allocator, M_AllocatorOp kind, void *p, size_t size); + +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_Dealloc(M_Allocator allocator, void *p); +MA_API M_Allocator M_GetSystemAllocator(void); +// clang-format on + +#ifndef MA_DISABLE_SCRATCH +extern MA_THREAD_LOCAL MA_Arena MA_ScratchArenaPool[]; + #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_Load(checkpoint); } + operator MA_Arena *() { return 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 + +#ifdef MA_IMPLEMENTATION + +#ifndef MA_ASSERT + #include + #define MA_ASSERT(x) assert(x) +#endif + +#ifndef MA_MemoryZero + #include +MA_API void MA_MemoryZero(void *p, size_t size) { + memset(p, 0, size); +} +#endif + +#ifndef MA_MemoryCopy + #include +MA_API void MA_MemoryCopy(void *dst, void *src, size_t size) { + memcpy(dst, src, size); +} +#endif + +#ifndef MA_CMalloc + #include + #define MA_CMalloc(x) malloc(x) + #define MA_CFree(x) free(x) +#endif + +#ifndef MA_FN + #if defined(__GNUC__) || defined(__clang__) + #define MA_FN __attribute__((unused)) static + #else + #define MA_FN 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_FN 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_FN 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__BeginPackedArray(MA_Arena *arena, size_t element_size) { + MA_ASSERT(arena->memory.data); + arena->len = MA__AlignLen(arena); + arena->saved_alignment = arena->alignment; + arena->alignment = 0; + arena->packed_array_begin = arena->len; + arena->packed_array_element_size = element_size; + void *result = arena->memory.data + arena->len; + return result; +} + +MA_API int MA_EndPackedArray(MA_Arena *arena) { + arena->alignment = arena->saved_alignment; + size_t different = (arena->len - arena->packed_array_begin); + int result = (int)((arena->len - arena->packed_array_begin) / arena->packed_array_element_size); + return result; +} + +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); + 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_INIT_HOOK(a); +} + +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; + arena->allocator.obj = arena; + return 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_INIT_HOOK(arena); +} + +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 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); + return p; +} + +MA_API void *M_Alloc(M_Allocator allocator, size_t size) { + void *p = allocator.p(allocator.obj, M_AllocatorOp_Allocate, NULL, size); + 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); +} + +MA_FN void *M_ClibAllocatorProc(void *allocator, M_AllocatorOp kind, void *p, size_t size) { + if (kind == M_AllocatorOp_Allocate) { + return MA_CMalloc(size); + } + + if (kind == M_AllocatorOp_Deallocate) { + MA_CFree(p); + return NULL; + } + + MA_ASSERT("MA_Arena invalid codepath"); + return NULL; +} + +MA_API void *MA_AllocatorProc(M_Allocator allocator, M_AllocatorOp kind, void *p, size_t size) { + if (kind == M_AllocatorOp_Allocate) { + return MA_PushSizeNonZeroed((MA_Arena *)allocator.obj, size); + } + + if (kind == M_AllocatorOp_Deallocate) { + return NULL; + } + + MA_ASSERT("MA_Arena invalid codepath"); + return NULL; +} + +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 + +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 + #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__ +#endif // MA_IMPL \ No newline at end of file diff --git a/array.hpp b/array.hpp new file mode 100644 index 0000000..8e34edb --- /dev/null +++ b/array.hpp @@ -0,0 +1,315 @@ +// #define ARRAY_ALLOCATOR_TYPE Allocator + +#ifndef ARRAY_PRIVATE_FUNCTION + #if defined(__GNUC__) || defined(__clang__) + #define ARRAY_PRIVATE_FUNCTION __attribute__((unused)) static + #else + #define ARRAY_PRIVATE_FUNCTION static + #endif +#endif + +#ifndef ARRAY_ALLOCATE + #include + #define ARRAY_ALLOCATE(allocator, size) malloc(size) +#endif + +#ifndef ARRAY_DEALLOCATE + #include + #define ARRAY_DEALLOCATE(allocator, p) free(p) +#endif + +#ifndef ARRAY_ASSERT + #include + #define ARRAY_ASSERT(x) assert(x) +#endif + +#ifndef ARRAY_MemoryMove + #include + #define ARRAY_MemoryMove(dst, src, size) memmove(dst, src, size) +#endif + +#ifndef ARRAY_SET_DEFAULT_ALLOCATOR + #define ARRAY_SET_DEFAULT_ALLOCATOR +// Example: +// #define ARRAY_SET_DEFAULT_ALLOCATOR if (!allocator) allocator = global_heap; +#endif + +#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 + +#if !defined(ARRAY_ALLOCATOR_CODE) + #if defined(ARRAY_ALLOCATOR_TYPE) + #define ARRAY_ALLOCATOR_CODE(x) x + #define ARRAY_ALLOCATOR_PARAM ARRAY_ALLOCATOR_TYPE allocator, + #else + #define ARRAY_ALLOCATOR_CODE(x) + #define ARRAY_ALLOCATOR_PARAM + #endif +#endif + +#if !defined(For) +#define For2(array,it) for(auto &it : (array)) +#define For(array) For2(array,it) +#endif + +template +struct Array { + ARRAY_ALLOCATOR_CODE(ARRAY_ALLOCATOR_TYPE allocator;) + T *data; + int cap, len; + + T &operator[](int index) { + ARRAY_ASSERT(index >= 0 && index < len); + return data[index]; + } + T &operator[](long long 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(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 items) { + add_array(items.data, items.len); + } + + void reserve(int size) { + if (size > cap) { + ARRAY_SET_DEFAULT_ALLOCATOR; + + void *p = ARRAY_ALLOCATE(allocator, size * sizeof(T)); + ARRAY_ASSERT(p); + + if (data) { + ARRAY_MemoryMove(p, data, len * sizeof(T)); + ARRAY_DEALLOCATE(allocator, data); + } + + data = (T *)p; + cap = size; + } + } + + void init(ARRAY_ALLOCATOR_PARAM int size) { + len = 0; + cap = 0; + data = 0; + ARRAY_ALLOCATOR_CODE(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]; + } + + 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); + 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 exact_copy(ARRAY_ALLOCATOR_CODE(ARRAY_ALLOCATOR_TYPE *allocator)) { + Array result = {}; + ARRAY_ALLOCATOR_CODE(result.allocator = allocator;) + result.reserve(cap); + + ARRAY_MemoryMove(result.data, data, sizeof(T) * len); + result.len = len; + return result; + } + + Array tight_copy(ARRAY_ALLOCATOR_CODE(ARRAY_ALLOCATOR_TYPE *allocator)) { + Array result = {}; + ARRAY_ALLOCATOR_CODE(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 *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); + } + } +}; diff --git a/clexer.h b/clexer.h new file mode 100644 index 0000000..bb7118b --- /dev/null +++ b/clexer.h @@ -0,0 +1,2391 @@ +#ifndef CL_HEADER +#define CL_HEADER +#include +#include +#include + +#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_Kind; +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, +}; + +typedef enum CL_Fix CL_Fix; +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, +}; + +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, 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; +} + +#endif // CL_HEADER + +#ifdef CL_IMPLEMENTATION + +#include + +#ifndef CL_STRING_TO_DOUBLE + #include + #define CL_STRING_TO_DOUBLE(str, len) strtod(str, 0) +#endif + +#ifndef CL_ASSERT + #include + #define CL_ASSERT(x) assert(x) +#endif + +#ifndef CL_VSNPRINTF + #include + #define CL_VSNPRINTF vsnprintf +#endif + +#ifndef CL_SNPRINTF + #include + #define CL_SNPRINTF snprintf +#endif + +#ifndef CL_ReadFile + #define CL_ReadFile CL__ReadFile + #include +CL_PRIVATE_FUNCTION char *CL_ReadFile(CL_Arena *arena, char *name) { + char *result = 0; + FILE *f = fopen(name, "rb"); + if (f) { + fseek(f, 0, SEEK_END); + int len = ftell(f); + fseek(f, 0, SEEK_SET); + + result = (char *)CL_PushSize(arena, len + 1); + fread(result, len, 1, f); + fclose(f); + result[len] = 0; + } + + return result; +} +#endif + +#ifndef CL_FileExists + #define CL_FileExists CL__FileExists + #include +CL_API_FUNCTION bool CL_FileExists(char *name) { + bool result = false; + FILE *f = fopen(name, "rb"); + if (f) { + result = true; + fclose(f); + } + return result; +} +#endif + +#ifndef CL__HASH_BYTES + #define CL__HASH_BYTES CL__HashBytes +// FNV HASH (1a?) +static uint64_t CL__HashBytes(void *p, int bytes) { + uint8_t *p8 = (uint8_t *)p; + uint64_t hash = (uint64_t)14695981039346656037ULL; + for (int i = 0; i < bytes; i++) { + hash = hash ^ (uint64_t)(p8[i]); + hash = hash * (uint64_t)1099511628211ULL; + } + return hash; +} +#endif + +#ifndef CL_CUSTOM_ARENA_TYPE +CL_PRIVATE_FUNCTION void *CL_PushSize(CL_Arena *arena, int size) { + if (arena->len + size > arena->cap) { + CL_ASSERT(!"CLEX: Not enough memory"); + } + void *result = arena->buff + arena->len; + arena->len += size; + return result; +} +#endif + +#ifdef __cplusplus + #define CL_ZeroStruct() \ + {} +#else + #define CL_ZeroStruct() \ + { 0 } +#endif + +CL_PRIVATE_FUNCTION void CL__MemoryZero(void *p, size_t size) { + uint8_t *p8 = (uint8_t *)p; + while (size--) *p8++ = 0; +} + +CL_PRIVATE_FUNCTION void CL__MemoryCopy(void *dst, const void *src, size_t size) { + char *src8 = (char *)src; + char *dst8 = (char *)dst; + while (size--) *dst8++ = *src8++; +} + +#define CL_PushArray(arena, T, size) (T *)CL__PushSizeZeroed(arena, sizeof(T) * (size)) +#define CL_PushStruct(arena, T) CL_PushArray(arena, T, 1) +CL_PRIVATE_FUNCTION void *CL__PushSizeZeroed(CL_Arena *arena, int size) { + void *result = CL_PushSize(arena, size); + CL__MemoryZero(result, size); + return result; +} + +/*# +print("\nchar *CL_FixString[] = {") +for i in meta.fix: print(f""" "{i if i != "FIX_NONE" else ""}", """) +print("};") +meta.gen_enum(meta.tokens, table_name="CL_KindString", table=True) +meta.gen_enum(meta.message_kinds, table_name="CL_MessageKindString", table=True) +*/ + +char *CL_FixString[] = { + "", + "SUFFIX_U", + "SUFFIX_UL", + "SUFFIX_ULL", + "SUFFIX_L", + "SUFFIX_LL", + "SUFFIX_F", + "SUFFIX_FL", + "PREFIX_U8", + "PREFIX_U16", + "PREFIX_U32", + "PREFIX_L", +}; + +char *CL_KindString[] = { + "EOF", + "*", + "/", + "%", + "<<", + ">>", + "+", + "-", + "==", + "<", + ">", + "<=", + ">=", + "!=", + "&", + "|", + "^", + "&&", + "||", + "~", + "!", + "--", + "++", + "--", + "++", + "=", + "/=", + "*=", + "%=", + "-=", + "+=", + "&=", + "|=", + "^=", + "<<=", + ">>=", + "(", + ")", + "{", + "}", + "[", + "]", + ",", + "##", + "#Stringify", + "?", + "...", + ";", + ".", + ":", + "TAG", + "->", + "SIZEOF", + "DOCCOMMENT", + "COMMENT", + "IDENTIFIER", + "STRING_LITERAL", + "CHARACTER_LITERAL", + "ERROR TOKEN", + "FLOAT", + "INT", + "PREPROC_NULL", + "PREPROC_DEFINE", + "PREPROC_IFDEF", + "PREPROC_IFNDEF", + "PREPROC_INCLUDE", + "PREPROC_ENDIF", + "PREPROC_IF", + "PREPROC_PRAGMA", + "PREPROC_ERROR", + "PREPROC_ELSE", + "PREPROC_ELIF", + "PREPROC_UNDEF", + "KEYWORD_VOID", + "KEYWORD_INT", + "KEYWORD_CHAR", + "KEYWORD_UNSIGNED", + "KEYWORD_SIGNED", + "KEYWORD_LONG", + "KEYWORD_SHORT", + "KEYWORD_DOUBLE", + "KEYWORD_FLOAT", + "KEYWORD__BOOL", + "KEYWORD__COMPLEX", + "KEYWORD__IMAGINARY", + "KEYWORD_STATIC", + "KEYWORD_AUTO", + "KEYWORD_CONST", + "KEYWORD_EXTERN", + "KEYWORD_INLINE", + "KEYWORD_REGISTER", + "KEYWORD_RESTRICT", + "KEYWORD_VOLATILE", + "KEYWORD__THREAD_LOCAL", + "KEYWORD__ATOMIC", + "KEYWORD__NORETURN", + "KEYWORD_STRUCT", + "KEYWORD_UNION", + "KEYWORD_ENUM", + "KEYWORD_TYPEDEF", + "KEYWORD_DEFAULT", + "KEYWORD_BREAK", + "KEYWORD_RETURN", + "KEYWORD_SWITCH", + "KEYWORD_IF", + "KEYWORD_ELSE", + "KEYWORD_FOR", + "KEYWORD_WHILE", + "KEYWORD_CASE", + "KEYWORD_CONTINUE", + "KEYWORD_DO", + "KEYWORD_GOTO", + "KEYWORD_SIZEOF", + "KEYWORD__ALIGNAS", + "KEYWORD__ALIGNOF", + "KEYWORD__STATIC_ASSERT", + "KEYWORD__GENERIC", +}; + +char *CL_MessageKindString[] = { + "ERROR", + "WARNING", + "TRACE", +}; +/*END*/ + +#define CL_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 CL_DLL_QUEUE_ADD(f, l, node) CL_DLL_QUEUE_ADD_MOD(f, l, node, next, prev) + +#define CL_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 CL_SLL_QUEUE_ADD(f, l, n) CL_SLL_QUEUE_ADD_MOD(f, l, n, next) + +#define CL__FORMAT(arena, string, result) \ + va_list args1, args2; \ + va_start(args1, string); \ + va_copy(args2, args1); \ + int len = CL_VSNPRINTF(0, 0, string, args2); \ + va_end(args2); \ + char *result = (char *)CL_PushSize((arena), len + 1); \ + CL_VSNPRINTF(result, len + 1, string, args1); \ + va_end(args1) + +CL_API_FUNCTION void CL_ReportError(CL_LexResult *T, CL_Token *token, const char *string, ...) { + CL__FORMAT(T->arena->other, string, message_string); + CL_Message *result = CL_PushStruct(T->arena->other, CL_Message); + result->kind = CLM_ERROR; + result->string = (char *)string; + CL_SLL_QUEUE_ADD(T->first_message, T->last_message, result); + result->token = *token; + T->errors += 1; + token->kind = CL_ERROR; + token->error = result; +#if TEST_DEBUG + printf("%s:%d %s\n", token->file, token->line, string); + __debugbreak(); +#endif +} + +CL_PRIVATE_FUNCTION char *CL_PushStringCopy(CL_Arena *arena, char *p, int size) { + char *copy_buffer = (char *)CL_PushSize(arena, size + 1); + CL__MemoryCopy(copy_buffer, p, size); + copy_buffer[size] = 0; + return copy_buffer; +} + +CL_PRIVATE_FUNCTION CL_Token *CL_CopyToken(CL_Arena *arena, CL_Token *token) { + CL_Token *copy_buffer = (CL_Token *)CL_PushSize(arena, sizeof(CL_Token)); + CL__MemoryCopy(copy_buffer, token, sizeof(CL_Token)); + return copy_buffer; +} + +CL_API_FUNCTION void CL_StringifyMessage(char *buff, int buff_size, CL_Message *msg) { + char *kind = CL_MessageKindString[msg->kind]; + CL_SNPRINTF(buff, buff_size, "%s:%d %15s %15s", msg->token.file, msg->token.line, kind, msg->string); +} + +CL_API_FUNCTION void CL_Stringify(char *buff, int buff_size, CL_Token *token) { + char *token_kind = "UNKNOWN"; + if (token->kind < CL_COUNT) token_kind = CL_KindString[token->kind]; + CL_SNPRINTF(buff, buff_size, "%s:%d %15s %15.*s", token->file, token->line, token_kind, token->len, token->str); +} + +CL_API_FUNCTION void CL_PrintMessages(CL_LexResult *lex_result) { + char buff[1024]; + for (CL_Message *it = lex_result->first_message; it; it = it->next) { + CL_StringifyMessage(buff, sizeof(buff), it); + printf("%s\n", buff); + } +} + +CL_API_FUNCTION void CL_PrintTokens(CL_Tokens tokens) { + char buff[1024]; + for (int i = 0; i < tokens.count; i += 1) { + CL_Stringify(buff, sizeof(buff), &tokens.data[i]); + printf("%s\n", buff); + } +} + +CL_INLINE void CL_Advance(CL_LexResult *T) { + if (*T->stream == '\n') { + T->line += 1; + T->column = 0; + } + else if (*T->stream == ' ') { + T->column += 1; + } + else if (*T->stream == '\t') { + T->column += 1; + } + else if (*T->stream == 0) { + return; + } + T->stream += 1; +} + +CL_INLINE bool CL_IsAlphabetic(char c) { + bool result = (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); + return result; +} + +CL_INLINE bool CL_IsNumeric(char c) { + bool result = (c >= '0' && c <= '9'); + return result; +} + +CL_INLINE bool CL_IsHexNumeric(char c) { + bool result = (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'); + return result; +} + +CL_INLINE bool CL_IsWhitespace(char c) { + bool result = c == ' ' || c == '\n' || c == '\r' || c == '\t'; + return result; +} + +CL_INLINE bool CL_IsAlphanumeric(char c) { + bool result = CL_IsAlphabetic(c) || CL_IsNumeric(c); + return result; +} + +CL_API_FUNCTION bool CL_EatWhitespace(CL_LexResult *T) { + bool skipped = false; + for (;;) { + if (CL_IsWhitespace(*T->stream)) { + if (*T->stream == '\n') T->inside_of_macro = false; + CL_Advance(T); + skipped = true; + } + else if (T->stream[0] == '\\' && T->stream[1] == '\n') { + CL_Advance(T); + CL_Advance(T); + skipped = true; + } + else if (T->stream[0] == '\\' && T->stream[1] == '\r' && T->stream[2] == '\n') { + CL_Advance(T); + CL_Advance(T); + CL_Advance(T); + skipped = true; + } + else { + break; + } + } + return skipped; +} + +CL_API_FUNCTION void CL_SetTokenLength(CL_LexResult *T, CL_Token *token) { + intptr_t diff = T->stream - token->str; + CL_ASSERT(diff < 2147483647); + token->len = (int)diff; +} + +CL_API_FUNCTION void CL_TryToFinalizeToken(CL_LexResult *T, CL_Token *token) { + for (; T->attached_comment_index < T->comments.count; T->attached_comment_index += 1) { + CL_Token *it = T->comments.data + T->attached_comment_index; + it->comment_is_attached_to_token = token; + } + if (!token->len) { + CL_SetTokenLength(T, token); + } + if (T->inside_of_macro) { + token->flags |= CL_INSIDE_OF_MACRO; + } +} + +CL_PRIVATE_FUNCTION uint64_t CL_CharMapToNumber(char c) { + switch (c) { + case '0': return 0; break; + case '1': return 1; break; + case '2': return 2; break; + case '3': return 3; break; + case '4': return 4; break; + case '5': return 5; break; + case '6': return 6; break; + case '7': return 7; break; + case '8': return 8; break; + case '9': return 9; break; + case 'a': + case 'A': return 10; break; + case 'b': + case 'B': return 11; break; + case 'c': + case 'C': return 12; break; + case 'd': + case 'D': return 13; break; + case 'e': + case 'E': return 14; break; + case 'f': + case 'F': return 15; break; + default: return 255; + } +} + +CL_PRIVATE_FUNCTION uint64_t CL_ParseInteger(CL_LexResult *T, CL_Token *token, char *string, uint64_t len, uint64_t base) { + CL_ASSERT(base >= 2 && base <= 16); + uint64_t acc = 0; + for (uint64_t i = 0; i < len; i++) { + uint64_t num = CL_CharMapToNumber(string[i]); + if (num >= base) { + CL_ReportError(T, token, "Internal compiler error! Failed to parse a number"); + break; + } + acc *= base; + acc += num; + } + return acc; +} + +typedef struct CL_UTF32Result { + uint32_t out_str; + int advance; + int error; +} CL_UTF32Result; + +CL_PRIVATE_FUNCTION CL_UTF32Result CL_UTF8ToUTF32(char *c, int max_advance) { + CL_UTF32Result result = CL_ZeroStruct(); + + 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; +} + +// @todo I think I should look at this again +CL_API_FUNCTION void CL_ParseCharLiteral(CL_LexResult *T, CL_Token *token) { + token->kind = CL_CHARLIT; + token->str = T->stream; + while (*T->stream != '\'') { + if (*T->stream == '\\') { + CL_Advance(T); + } + if (*T->stream == 0) { + CL_ReportError(T, token, "Unclosed character literal!"); + return; + } + CL_Advance(T); + } + CL_SetTokenLength(T, token); + + if (token->str[0] == '\\') { + switch (token->str[1]) { + case '\\': token->u64 = '\\'; break; + case '\'': token->u64 = '\''; break; + case '"': token->u64 = '"'; break; + case 't': token->u64 = '\t'; break; + case 'v': token->u64 = '\v'; break; + case 'f': token->u64 = '\f'; break; + case 'n': token->u64 = '\n'; break; + case 'r': token->u64 = '\r'; break; + case 'a': token->u64 = '\a'; break; + case 'b': token->u64 = '\b'; break; + case '0': + token->u64 = '\0'; + break; + // Octal constant + case 'x': + case 'X': CL_ASSERT(0); break; // Hex constant + case 'u': CL_ASSERT(0); break; // Unicode constant + default: { + CL_ReportError(T, token, "Unknown escape code"); + } + } + } + + else { + if (token->len > 4) { + CL_ReportError(T, token, "This character literal has invalid format, it's too big"); + goto skip_utf_encode; + } + + token->u64 = 0; + int i = 0; + + for (; i < token->len;) { + CL_UTF32Result result = CL_UTF8ToUTF32(token->str + i, (int)token->len); + i += result.advance; + token->u64 |= result.out_str << (8 * (token->len - i)); + if (result.error) { + CL_ReportError(T, token, "This character literal couldnt be parsed as utf8"); + break; + } + } + if (i != token->len) { + CL_ReportError(T, token, "Character literal decode error"); + } + } + +skip_utf_encode: + CL_Advance(T); +} + +CL_PRIVATE_FUNCTION void CL_BufferWrite(char *buffer, int buffer_size, int *buffer_iter, char write) { + if (*buffer_iter < buffer_size) { + buffer[*buffer_iter] = write; + *buffer_iter += 1; + } +} + +// @todo I think I should look at this again +// Idea: Maybe try to figure out size first and then write the string +CL_API_FUNCTION void CL_ParseString(CL_LexResult *T, CL_Token *token) { + // @todo String builder here, we dont really want 4096 character limit + int buffer_iter = 0; + int buffer_size = 4096; + char buffer[4096]; + + token->kind = CL_STRINGLIT; + // First we try to parse the string normally, we write contents to scratch memory. + // Afterwards we try to seek if there are more consecutive strings. As the speak + // says, those are one string, so we combine them nicely. Then after we have written + // everything to the scratch buffer. We make a proper tight copy on the pernament + // allocator. +combine_next_string_literal: + while (*T->stream != '"' && *T->stream != 0 AND_CL_STRING_TERMINATE_ON_NEW_LINE) { + if (*T->stream == '\\') { + CL_Advance(T); + switch (*T->stream) { + case '\\': CL_BufferWrite(buffer, buffer_size, &buffer_iter, '\\'); break; + case '\'': CL_BufferWrite(buffer, buffer_size, &buffer_iter, '\''); break; + case '"': CL_BufferWrite(buffer, buffer_size, &buffer_iter, '"'); break; + case 't': CL_BufferWrite(buffer, buffer_size, &buffer_iter, '\t'); break; + case 'f': CL_BufferWrite(buffer, buffer_size, &buffer_iter, '\f'); break; + case 'n': CL_BufferWrite(buffer, buffer_size, &buffer_iter, '\n'); break; + case 'v': CL_BufferWrite(buffer, buffer_size, &buffer_iter, '\v'); break; + case 'r': CL_BufferWrite(buffer, buffer_size, &buffer_iter, '\r'); break; + case 'a': CL_BufferWrite(buffer, buffer_size, &buffer_iter, '\a'); break; + case 'b': CL_BufferWrite(buffer, buffer_size, &buffer_iter, '\b'); break; + case '0': + CL_BufferWrite(buffer, buffer_size, &buffer_iter, '\0'); + break; + + // Octal constant + case 'x': + case 'X': CL_ASSERT(0); break; // Hex constant + case 'u': CL_ASSERT(0); break; // Unicode constant + } + } + else { + CL_BufferWrite(buffer, buffer_size, &buffer_iter, *T->stream); + } + + CL_Advance(T); + } + CL_Advance(T); + + // Try to seek if there is a consecutive string. + // If there is such string we try to combine it. + char *seek_for_next_string = T->stream; + while (CL_IsWhitespace(*seek_for_next_string)) { + seek_for_next_string += 1; + } + + if (*seek_for_next_string == '"') { + seek_for_next_string += 1; + while (T->stream != seek_for_next_string) CL_Advance(T); + goto combine_next_string_literal; + } + + int len = buffer_iter + 1; + if (len > buffer_size) { + len = buffer_size; + CL_ReportError(T, token, "Truncated string! Reached 4096 character limit for string literal."); + } + + token->string_literal = CL_PushStringCopy(T->arena->other, buffer, len); +} + +CL_API_FUNCTION void CL_IsIdentifierKeyword(CL_LexResult *ctx, CL_Token *token) { + if (token->len == 1) return; + char *c = token->str; + /*import meta +meta.gen_lex_keywords()*/ + switch (c[0]) { + case 'v': { + switch (c[1]) { + case 'o': { + if (CL_StringsAreEqual(token->str, token->len, "void", 4)) { + token->kind = CL_KEYWORD_VOID; + } + else if (CL_StringsAreEqual(token->str, token->len, "volatile", 8)) { + token->kind = CL_KEYWORD_VOLATILE; + } + } break; + } + } break; + case 'i': { + switch (c[1]) { + case 'n': { + if (CL_StringsAreEqual(token->str, token->len, "int", 3)) { + token->kind = CL_KEYWORD_INT; + } + else if (CL_StringsAreEqual(token->str, token->len, "inline", 6)) { + token->kind = CL_KEYWORD_INLINE; + } + } break; + case 'f': { + if (CL_StringsAreEqual(token->str, token->len, "if", 2)) { + token->kind = CL_KEYWORD_IF; + } + } break; + } + } break; + case 'c': { + switch (c[1]) { + case 'h': { + if (CL_StringsAreEqual(token->str, token->len, "char", 4)) { + token->kind = CL_KEYWORD_CHAR; + } + } break; + case 'o': { + if (CL_StringsAreEqual(token->str, token->len, "const", 5)) { + token->kind = CL_KEYWORD_CONST; + } + else if (CL_StringsAreEqual(token->str, token->len, "continue", 8)) { + token->kind = CL_KEYWORD_CONTINUE; + } + } break; + case 'a': { + if (CL_StringsAreEqual(token->str, token->len, "case", 4)) { + token->kind = CL_KEYWORD_CASE; + } + } break; + } + } break; + case 'u': { + switch (c[1]) { + case 'n': { + if (CL_StringsAreEqual(token->str, token->len, "unsigned", 8)) { + token->kind = CL_KEYWORD_UNSIGNED; + } + else if (CL_StringsAreEqual(token->str, token->len, "union", 5)) { + token->kind = CL_KEYWORD_UNION; + } + } break; + } + } break; + case 's': { + switch (c[1]) { + case 'i': { + if (CL_StringsAreEqual(token->str, token->len, "signed", 6)) { + token->kind = CL_KEYWORD_SIGNED; + } + else if (CL_StringsAreEqual(token->str, token->len, "sizeof", 6)) { + token->kind = CL_KEYWORD_SIZEOF; + } + } break; + case 'h': { + if (CL_StringsAreEqual(token->str, token->len, "short", 5)) { + token->kind = CL_KEYWORD_SHORT; + } + } break; + case 't': { + if (CL_StringsAreEqual(token->str, token->len, "static", 6)) { + token->kind = CL_KEYWORD_STATIC; + } + else if (CL_StringsAreEqual(token->str, token->len, "struct", 6)) { + token->kind = CL_KEYWORD_STRUCT; + } + } break; + case 'w': { + if (CL_StringsAreEqual(token->str, token->len, "switch", 6)) { + token->kind = CL_KEYWORD_SWITCH; + } + } break; + } + } break; + case 'l': { + switch (c[1]) { + case 'o': { + if (CL_StringsAreEqual(token->str, token->len, "long", 4)) { + token->kind = CL_KEYWORD_LONG; + } + } break; + } + } break; + case 'd': { + switch (c[1]) { + case 'o': { + if (CL_StringsAreEqual(token->str, token->len, "double", 6)) { + token->kind = CL_KEYWORD_DOUBLE; + } + else if (CL_StringsAreEqual(token->str, token->len, "do", 2)) { + token->kind = CL_KEYWORD_DO; + } + } break; + case 'e': { + if (CL_StringsAreEqual(token->str, token->len, "default", 7)) { + token->kind = CL_KEYWORD_DEFAULT; + } + } break; + } + } break; + case 'f': { + switch (c[1]) { + case 'l': { + if (CL_StringsAreEqual(token->str, token->len, "float", 5)) { + token->kind = CL_KEYWORD_FLOAT; + } + } break; + case 'o': { + if (CL_StringsAreEqual(token->str, token->len, "for", 3)) { + token->kind = CL_KEYWORD_FOR; + } + } break; + } + } break; + case '_': { + switch (c[1]) { + case 'B': { + if (CL_StringsAreEqual(token->str, token->len, "_Bool", 5)) { + token->kind = CL_KEYWORD__BOOL; + } + } break; + case 'C': { + if (CL_StringsAreEqual(token->str, token->len, "_Complex", 8)) { + token->kind = CL_KEYWORD__COMPLEX; + } + } break; + case 'I': { + if (CL_StringsAreEqual(token->str, token->len, "_Imaginary", 10)) { + token->kind = CL_KEYWORD__IMAGINARY; + } + } break; + case 'T': { + if (CL_StringsAreEqual(token->str, token->len, "_Thread_local", 13)) { + token->kind = CL_KEYWORD__THREAD_LOCAL; + } + } break; + case 'A': { + if (CL_StringsAreEqual(token->str, token->len, "_Atomic", 7)) { + token->kind = CL_KEYWORD__ATOMIC; + } + else if (CL_StringsAreEqual(token->str, token->len, "_Alignas", 8)) { + token->kind = CL_KEYWORD__ALIGNAS; + } + else if (CL_StringsAreEqual(token->str, token->len, "_Alignof", 8)) { + token->kind = CL_KEYWORD__ALIGNOF; + } + } break; + case 'N': { + if (CL_StringsAreEqual(token->str, token->len, "_Noreturn", 9)) { + token->kind = CL_KEYWORD__NORETURN; + } + } break; + case 'S': { + if (CL_StringsAreEqual(token->str, token->len, "_Static_assert", 14)) { + token->kind = CL_KEYWORD__STATIC_ASSERT; + } + } break; + case 'G': { + if (CL_StringsAreEqual(token->str, token->len, "_Generic", 8)) { + token->kind = CL_KEYWORD__GENERIC; + } + } break; + } + } break; + case 'a': { + switch (c[1]) { + case 'u': { + if (CL_StringsAreEqual(token->str, token->len, "auto", 4)) { + token->kind = CL_KEYWORD_AUTO; + } + } break; + } + } break; + case 'e': { + switch (c[1]) { + case 'x': { + if (CL_StringsAreEqual(token->str, token->len, "extern", 6)) { + token->kind = CL_KEYWORD_EXTERN; + } + } break; + case 'n': { + if (CL_StringsAreEqual(token->str, token->len, "enum", 4)) { + token->kind = CL_KEYWORD_ENUM; + } + } break; + case 'l': { + if (CL_StringsAreEqual(token->str, token->len, "else", 4)) { + token->kind = CL_KEYWORD_ELSE; + } + } break; + } + } break; + case 'r': { + switch (c[1]) { + case 'e': { + if (CL_StringsAreEqual(token->str, token->len, "register", 8)) { + token->kind = CL_KEYWORD_REGISTER; + } + else if (CL_StringsAreEqual(token->str, token->len, "restrict", 8)) { + token->kind = CL_KEYWORD_RESTRICT; + } + else if (CL_StringsAreEqual(token->str, token->len, "return", 6)) { + token->kind = CL_KEYWORD_RETURN; + } + } break; + } + } break; + case 't': { + switch (c[1]) { + case 'y': { + if (CL_StringsAreEqual(token->str, token->len, "typedef", 7)) { + token->kind = CL_KEYWORD_TYPEDEF; + } + } break; + } + } break; + case 'b': { + switch (c[1]) { + case 'r': { + if (CL_StringsAreEqual(token->str, token->len, "break", 5)) { + token->kind = CL_KEYWORD_BREAK; + } + } break; + } + } break; + case 'w': { + switch (c[1]) { + case 'h': { + if (CL_StringsAreEqual(token->str, token->len, "while", 5)) { + token->kind = CL_KEYWORD_WHILE; + } + } break; + } + } break; + case 'g': { + switch (c[1]) { + case 'o': { + if (CL_StringsAreEqual(token->str, token->len, "goto", 4)) { + token->kind = CL_KEYWORD_GOTO; + } + } break; + } + } break; + } + /*END*/ +} + +CL_API_FUNCTION void CL_LexMacroInclude(CL_LexResult *T, CL_Token *token) { + token->kind = CL_PREPROC_INCLUDE; + while (*T->stream == ' ') CL_Advance(T); + char end = 0; + if (*T->stream == '"') { + end = '"'; + } + else if (*T->stream == '<') { + end = '>'; + token->flags |= CL_SYSTEM_INCLUDE; + } + else { + CL_ReportError(T, token, "Invalid include directive, file not specified"); + return; + } + CL_Advance(T); + + token->str = T->stream; + while (*T->stream != end) { + if (*T->stream == 0) { + CL_ReportError(T, token, "Invalid include directive, reached end of file while reading filename"); + } + if (*T->stream == '\n') { + CL_ReportError(T, token, "Invalid include directive filename, got newline character while reading filename"); + } + CL_Advance(T); + } + CL_SetTokenLength(T, token); + CL_Advance(T); + + token->str = CL_PushStringCopy(T->arena->other, token->str, token->len); + + CL_Token *include_list_item = CL_CopyToken(T->arena->include, token); + T->includes.count += 1; + if (T->includes.data == 0) T->includes.data = include_list_item; +} + +CL_API_FUNCTION bool CL_LexMacro(CL_LexResult *T, CL_Token *token) { + while (*T->stream == ' ' || T->stream[0] == '\t') CL_Advance(T); + token->str = T->stream; + while (CL_IsAlphabetic(*T->stream)) CL_Advance(T); + CL_SetTokenLength(T, token); + + /*import meta + meta.gen_lex_preproc_keywords() Need to add END*/ + switch (*token->str) { + case 'd': + if (CL_StringsAreEqual(token->str, token->len, "define", 6)) { + token->kind = CL_PREPROC_DEFINE; + } + break; + + case 'i': + if (CL_StringsAreEqual(token->str, token->len, "ifdef", 5)) { + token->kind = CL_PREPROC_IFDEF; + } + else if (CL_StringsAreEqual(token->str, token->len, "ifndef", 6)) { + token->kind = CL_PREPROC_IFNDEF; + } + else if (CL_StringsAreEqual(token->str, token->len, "include", 7)) { + token->kind = CL_PREPROC_INCLUDE; + CL_LexMacroInclude(T, token); + } + else if (CL_StringsAreEqual(token->str, token->len, "if", 2)) { + token->kind = CL_PREPROC_IF; + } + break; + + case 'e': + if (CL_StringsAreEqual(token->str, token->len, "endif", 5)) { + token->kind = CL_PREPROC_ENDIF; + } + else if (CL_StringsAreEqual(token->str, token->len, "error", 5)) { + token->kind = CL_PREPROC_ERROR; + } + else if (CL_StringsAreEqual(token->str, token->len, "else", 4)) { + token->kind = CL_PREPROC_ELSE; + } + else if (CL_StringsAreEqual(token->str, token->len, "elif", 4)) { + token->kind = CL_PREPROC_ELIF; + } + break; + + case 'p': + if (CL_StringsAreEqual(token->str, token->len, "pragma", 6)) { + token->kind = CL_PREPROC_PRAGMA; + } + break; + + case 'u': + if (CL_StringsAreEqual(token->str, token->len, "undef", 5)) { + token->kind = CL_PREPROC_UNDEF; + } + break; + default: return false; + } + return true; +} + +CL_API_FUNCTION void CL_InitLexResult(CL_LexResult *T, CL_ArenaTuple *arena, char *filename, char *filecontent) { + CL__MemoryZero(T, sizeof(CL_LexResult)); + T->arena = arena; + T->stream = filecontent; + T->stream_begin = filecontent; + T->file = filename; +} + +CL_API_FUNCTION CL_LexResult *CL_CreateLexingResult(CL_ArenaTuple *arena, char *filename, char *filecontent) { + CL_LexResult *T = CL_PushStruct(arena->other, CL_LexResult); + CL_InitLexResult(T, arena, filename, filecontent); + return T; +} + +// Skipped space here is for case #define Memes (a), this is not a function like macro because of space +static uint32_t CL_TokenID; // @todo: make it stable, thread local? +CL_API_FUNCTION void CL_PrepareToken(CL_LexResult *T, CL_Token *token, bool skipped_space) { + CL__MemoryZero(token, sizeof(*token)); + token->str = T->stream; + token->line = T->line; + token->column = T->column; + token->file = T->file; + token->id = ++CL_TokenID; + if (skipped_space) token->flags |= CL_WHITESPACE_BEFORE_TOKEN; + CL_Advance(T); +} + +CL_API_FUNCTION void CL_DefaultTokenize(CL_LexResult *T, CL_Token *token) { + char *c = token->str; + switch (*c) { + case 0: break; + case '(': token->kind = CL_OPENPAREN; break; + case ')': token->kind = CL_CLOSEPAREN; break; + case '{': token->kind = CL_OPENBRACE; break; + case '}': token->kind = CL_CLOSEBRACE; break; + case '[': token->kind = CL_OPENBRACKET; break; + case ']': token->kind = CL_CLOSEBRACKET; break; + case ',': token->kind = CL_COMMA; break; + case '~': token->kind = CL_NEG; break; + case '?': token->kind = CL_QUESTION; break; + case ';': token->kind = CL_SEMICOLON; break; + case '.': { + token->kind = CL_DOT; + if (T->stream[0] == '.' && T->stream[1] == '.') { + CL_Advance(T); + CL_Advance(T); + token->kind = CL_THREEDOTS; + } + } break; + case ':': { + token->kind = CL_COLON; + } break; + case '/': { + token->kind = CL_DIV; + if (*T->stream == '/') { + token->kind = CL_COMMENT; + CL_Advance(T); + + while (*T->stream != '\n' && *T->stream != 0) { + CL_Advance(T); + } + CL_SetTokenLength(T, token); + + CL_Token *comment_token = CL_CopyToken(T->arena->comment, token); + if (T->comments.data == 0) T->comments.data = comment_token; + T->comments.count += 1; + } + else if (*T->stream == '*') { + token->kind = CL_COMMENT; + CL_Advance(T); + for (;;) { + if (T->stream[0] == '*' && T->stream[1] == '/') { + break; + } + if (T->stream[0] == 0) { + CL_ReportError(T, token, "Unclosed block comment"); + goto error_end_path; + } + CL_Advance(T); + } + token->str += 2; + CL_SetTokenLength(T, token); + CL_Advance(T); + CL_Advance(T); + + CL_Token *comment_token = CL_CopyToken(T->arena->comment, token); + if (T->comments.data == 0) T->comments.data = comment_token; + T->comments.count += 1; + } + else if (*T->stream == '=') { + token->kind = CL_DIVASSIGN; + CL_Advance(T); + } + } break; + case '#': { + if (*T->stream == '#') { + token->kind = CL_MACRO_CONCAT; + CL_Advance(T); + } + else { + bool is_macro_directive = CL_LexMacro(T, token); + if (is_macro_directive) { + T->inside_of_macro = true; + } + else { + if (!T->inside_of_macro) { + CL_ReportError(T, token, "Invalid preprocessor directive"); + goto error_end_path; + } + + token->kind = CL_PREPROC_STRINGIFY; + token->str = T->stream; + while (*T->stream == '_' || CL_IsAlphanumeric(*T->stream)) + CL_Advance(T); + CL_SetTokenLength(T, token); + } + } + } break; + case '>': { + if (*T->stream == '=') { + token->kind = CL_GREATERTHEN_OR_EQUAL; + CL_Advance(T); + } + else if (*T->stream == '>') { + CL_Advance(T); + if (*T->stream == '=') { + CL_Advance(T); + token->kind = CL_RIGHTSHIFTASSIGN; + } + else { + token->kind = CL_RIGHTSHIFT; + } + } + else { + token->kind = CL_GREATERTHEN; + } + } break; + case '<': { + token->kind = CL_LESSERTHEN; + if (*T->stream == '=') { + token->kind = CL_LESSERTHEN_OR_EQUAL; + CL_Advance(T); + } + else if (*T->stream == '<') { + CL_Advance(T); + if (*T->stream == '=') { + CL_Advance(T); + token->kind = CL_LEFTSHIFTASSIGN; + } + else { + token->kind = CL_LEFTSHIFT; + } + } + } break; + case '&': { + if (*T->stream == '=') { + token->kind = CL_ANDASSIGN; + CL_Advance(T); + } + else if (*T->stream == '&') { + token->kind = CL_AND; + CL_Advance(T); + } + else { + token->kind = CL_BITAND; + } + } break; + case '-': { + if (*T->stream == '-') { + token->kind = CL_DECREMENT; + CL_Advance(T); + } + else if (*T->stream == '=') { + token->kind = CL_SUBASSIGN; + CL_Advance(T); + } + else { + token->kind = CL_SUB; + } + } break; + case '+': { + if (*T->stream == '+') { + token->kind = CL_INCREMENT; + CL_Advance(T); + } + else if (*T->stream == '=') { + token->kind = CL_ADDASSIGN; + CL_Advance(T); + } + else { + token->kind = CL_ADD; + } + } break; + case '|': { + if (*T->stream == '|') { + token->kind = CL_OR; + CL_Advance(T); + } + else if (*T->stream == '=') { + token->kind = CL_ORASSIGN; + CL_Advance(T); + } + else { + token->kind = CL_BITOR; + } + } break; + case '=': { + if (*T->stream != '=') { + token->kind = CL_ASSIGN; + } + else { + CL_Advance(T); + token->kind = CL_EQUALS; + } + } break; + case '!': { + if (*T->stream != '=') { + token->kind = CL_NOT; + } + else { + CL_Advance(T); + token->kind = CL_NOTEQUALS; + } + } break; + case '*': { + token->kind = CL_MUL; + if (*T->stream == '=') { + CL_Advance(T); + token->kind = CL_MULASSIGN; + } + } break; + case '%': { + token->kind = CL_MOD; + if (*T->stream == '=') { + token->kind = CL_MODASSIGN; + CL_Advance(T); + } + } break; + case '^': { + token->kind = CL_BITXOR; + if (*T->stream == '=') { + CL_Advance(T); + token->kind = CL_XORASSIGN; + } + } break; + case '"': { + CL_ParseString(T, token); + } break; + case '\'': { + CL_ParseCharLiteral(T, token); + } break; + case 'U': { // @todo Unicode32 + if (*T->stream == '"') { + token->fix = CL_PREFIX_U32; + CL_Advance(T); + CL_ParseString(T, token); + } + else if (*T->stream == '\'') { + token->fix = CL_PREFIX_U32; + CL_Advance(T); + CL_ParseCharLiteral(T, token); + } + else goto parse_regular_char; + } break; + case 'u': { // Unicode16 + if (*T->stream == '8') { // Unicode8 + if (T->stream[1] == '"') { // U8 STRING + token->fix = CL_PREFIX_U8; + CL_Advance(T); + CL_Advance(T); + CL_ParseString(T, token); + } + else if (T->stream[1] == '\'') { // U8 CHAR + token->fix = CL_PREFIX_U8; + CL_Advance(T); + CL_Advance(T); + CL_ParseCharLiteral(T, token); + } + else goto parse_regular_char; + } + else if (*T->stream == '"') { // U16 STRING + token->fix = CL_PREFIX_U16; + CL_Advance(T); + CL_ParseString(T, token); + } + else if (*T->stream == '\'') { // U16 CHAR + CL_Advance(T); + CL_ParseCharLiteral(T, token); + } + else goto parse_regular_char; + } + case 'L': { // Widechar + if (*T->stream == '"') { + token->fix = CL_PREFIX_L; + CL_Advance(T); + CL_ParseString(T, token); // @todo UTF16 + } + else if (*T->stream == '\'') { + token->fix = CL_PREFIX_L; + CL_Advance(T); + CL_ParseCharLiteral(T, token); + } + else goto parse_regular_char; + } break; + case 'A': + case 'a': + case 'B': + case 'b': + case 'C': + case 'c': + case 'D': + case 'd': + case 'E': + case 'e': + case 'F': + case 'f': + case 'G': + case 'g': + case 'H': + case 'h': + case 'I': + case 'i': + case 'J': + case 'j': + case 'K': + case 'k': + /*case 'L':*/ case 'l': + case 'M': + case 'm': + case 'N': + case 'n': + case 'O': + case 'o': + case 'P': + case 'p': + case 'Q': + case 'q': + case 'R': + case 'r': + case 'S': + case 's': + case 'T': + case 't': + // case 'U': case 'u': + case 'V': + case 'v': + case 'W': + case 'w': + case 'X': + case 'x': + case 'Y': + case 'y': + case 'Z': + case 'z': + case '_': + parse_regular_char : { + token->kind = CL_IDENTIFIER; + while (*T->stream == '_' || CL_IsAlphanumeric(*T->stream)) { + CL_Advance(T); + } + CL_SetTokenLength(T, token); + CL_IsIdentifierKeyword(T, token); + } break; + case '0': { + if (*T->stream == 'x' || *T->stream == 'X') { + token->kind = CL_INT; + token->flags |= CL_HEX; + CL_Advance(T); + while (CL_IsHexNumeric(*T->stream)) { + CL_Advance(T); + } + uint64_t len = T->stream - token->str; + CL_ASSERT(len > 2); + token->u64 = CL_ParseInteger(T, token, token->str + 2, len - 2, 16); + break; + } + } + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': { + token->kind = CL_INT; + for (;;) { + if (*T->stream == '.') { + if (token->kind == CL_FLOAT) { + CL_ReportError(T, token, "Failed to parse a floating point number, invalid format, found multiple '.'"); + } + + if (token->kind == CL_INT) { + token->kind = CL_FLOAT; + } + } + else if (CL_IsNumeric(*T->stream) == false) { + break; + } + CL_Advance(T); + } + + if (token->kind == CL_INT) { + uint64_t len = T->stream - token->str; + CL_ASSERT(len > 0); + token->u64 = CL_ParseInteger(T, token, token->str, len, 10); + } + + else if (token->kind == CL_FLOAT) { + token->f64 = CL_STRING_TO_DOUBLE(token->str, token->len); + } + + else { + CL_ASSERT(token->kind == CL_ERROR); + } + + if (*T->stream == 'f' || *T->stream == 'F') { + CL_Advance(T); + token->fix = CL_SUFFIX_F; + } + + else if (*T->stream == 'l' || *T->stream == 'L') { + CL_Advance(T); + token->fix = CL_SUFFIX_L; + if (*T->stream == 'l' || *T->stream == 'L') { + CL_Advance(T); + token->fix = CL_SUFFIX_LL; + if (*T->stream == 'u' || *T->stream == 'U') { + CL_Advance(T); + token->fix = CL_SUFFIX_ULL; + } + } + else if (*T->stream == 'u' || *T->stream == 'U') { + CL_Advance(T); + token->fix = CL_SUFFIX_UL; + } + } + + else if (*T->stream == 'u' || *T->stream == 'U') { + CL_Advance(T); + token->fix = CL_SUFFIX_U; + if (*T->stream == 'l' || *T->stream == 'L') { + CL_Advance(T); + token->fix = CL_SUFFIX_UL; + if (*T->stream == 'l' || *T->stream == 'L') { + CL_Advance(T); + token->fix = CL_SUFFIX_ULL; + } + } + } + + } break; + + default: { + CL_Message *result = CL_PushStruct(T->arena->other, CL_Message); + result->kind = CLM_WARNING; + result->string = "Unhandled character, skipping ..."; + CL_SLL_QUEUE_ADD(T->first_message, T->last_message, result); + result->token = *token; + token->kind = CL_COMMENT; + } break; + } + +error_end_path:; +} + +CL_API_FUNCTION bool CL_IsComment(CL_Kind kind) { + bool result = kind == CL_COMMENT && kind != CL_EOF; + return result; +} + +CL_API_FUNCTION void CL_InitNextToken(CL_LexResult *T, CL_Token *token) { + // Skip comments, comments get allocated on perm and gathered on the Tokenizer. + // First non comment token gets those comments attached. + do { + bool skipped = CL_EatWhitespace(T); + CL_PrepareToken(T, token, skipped); + CL_DefaultTokenize(T, token); + } while (CL_IsComment(token->kind)); + CL_TryToFinalizeToken(T, token); +} + +CL_API_FUNCTION void CL_AddToken(CL_LexResult *T, CL_Token *token) { + if (!T->tokens.data) T->tokens.data = token; + T->tokens.count += 1; +} + +CL_API_FUNCTION void CL_AddTokenEx(CL_Arena *arena, CL_Tokens *tokens, CL_Token *token_to_add) { + if (token_to_add->kind != CL_EOF) { + CL_Token *token = CL_PushStruct(arena, CL_Token); + *token = *token_to_add; + if (!tokens->data) tokens->data = token; + tokens->count += 1; + } +} + +CL_API_FUNCTION void CL_AddTokenList(CL_Arena *arena, CL_Tokens *main, CL_Tokens *tokens_to_add) { + for (int i = 0; i < tokens_to_add->count; i += 1) { + CL_Token *it = tokens_to_add->data + i; + CL_AddTokenEx(arena, main, it); + } +} + +CL_API_FUNCTION CL_Token *CL_AddNextToken(CL_LexResult *T) { + CL_Token *token = CL_PushStruct(T->arena->token, CL_Token); + CL_InitNextToken(T, token); + CL_AddToken(T, token); + return token; +} + +CL_API_FUNCTION void CL_LexStringEx(CL_LexResult *result) { + CL_Token *token; + do { + token = CL_AddNextToken(result); + } while (token->kind != CL_EOF); +} + +CL_API_FUNCTION CL_LexResult *CL_LexString(CL_ArenaTuple *arena, char *filename, char *string) { + CL_LexResult *result = CL_CreateLexingResult(arena, filename, string); + CL_LexStringEx(result); + return result; +} + +CL_API_FUNCTION CL_LexResult *CL_LexFile(CL_ArenaTuple *arena, char *filename) { + char *file = CL_ReadFile(arena->other, filename); + CL_LexResult *result = 0; + if (file) { + result = CL_LexString(arena, filename, file); + } + return result; +} + +CL_API_FUNCTION void CL_AddLexResult(CL_LexList *list, CL_LexResult *result) { + if (result == 0) return; + CL_SLL_QUEUE_ADD_MOD(list->first_result, list->last_result, result, next_result); + list->count += 1; +} + +CL_API_FUNCTION CL_LexList CL_MakeLexList(CL_LexResult *l) { + CL_LexList result = CL_ZeroStruct(); + CL_AddLexResult(&result, l); + return result; +} + +CL_PRIVATE_FUNCTION void CL__SetIncludeToken(CL_IncludeIter *iter, CL_Token *token) { + if (token) { + iter->include_token = token; + iter->filename = token->str; + iter->is_system_include = token->flags & CL_SYSTEM_INCLUDE; + } + else { + iter->include_token = 0; + iter->filename = 0; + iter->is_system_include = 0; + } +} + +CL_API_FUNCTION void CL_GetNextInclude(CL_IncludeIter *iter) { + if (iter->inited_with_filename) { + iter->parent = iter->lex_list->first_result; + iter->inited_with_filename = false; + } + + for (; iter->parent;) { + iter->include_index += 1; + if (iter->include_index >= iter->parent->includes.count) { + iter->parent = iter->parent->next_result; + CL__SetIncludeToken(iter, 0); + iter->include_index = -1; + continue; + } + + CL_Token *it = iter->parent->includes.data + iter->include_index; + CL__SetIncludeToken(iter, it); + + if (iter->resolve) { + char *filename = CL_ResolveFilepath(iter->arena, &iter->search_paths, iter->filename, iter->parent->file, iter->is_system_include); + if (CL_IsValidFile(iter->lex_list, filename)) { + iter->filename = filename; + } + else { + CL__SetIncludeToken(iter, 0); + continue; + } + } + + return; + } +} + +CL_API_FUNCTION CL_IncludeIter CL_IterateFileAndResolvedIncludes(CL_ArenaTuple *arena, char *filename, CL_SearchPaths search_paths) { + CL_IncludeIter result; + CL__MemoryZero(&result, sizeof(CL_IncludeIter)); + result.lex_list = CL_PushStruct(arena->other, CL_LexList); + if (CL_FileExists(filename)) { + result.inited_with_filename = true; + result.filename = filename; + } + result.include_index = -1; + result.resolve = true; + result.search_paths = search_paths; + result.arena = arena->other; + return result; +} + +CL_API_FUNCTION CL_IncludeIter CL_IterateIncludes(CL_LexList *list) { + CL_IncludeIter result; + CL__MemoryZero(&result, sizeof(CL_IncludeIter)); + result.lex_list = list; + result.parent = list->first_result; + result.include_index = -1; + CL_GetNextInclude(&result); + return result; +} + +CL_API_FUNCTION CL_IncludeIter CL_IterateResolvedIncludes(CL_Arena *arena, CL_LexList *list, CL_SearchPaths search_paths) { + CL_IncludeIter result; + CL__MemoryZero(&result, sizeof(CL_IncludeIter)); + result.lex_list = list; + result.parent = list->first_result; + result.include_index = -1; + result.resolve = true; + result.search_paths = search_paths; + result.arena = arena; + CL_GetNextInclude(&result); + return result; +} + +#define CL_IS_POW2(x) (((x) & ((x)-1)) == 0) +#define CL_WRAP_AROUND_POWER_OF_2(x, pow2) (((x) & ((pow2)-1llu))) + +CL_API_FUNCTION void CL_InitInternTable(CL_Arena *arena, CL_InternTable *table, int size) { + CL_ASSERT(CL_IS_POW2(size)); + table->arena = arena; + table->entries = CL_PushArray(arena, CL_InternEntry, size); + table->entry_count = size; + table->occupied_entry_count = 0; +} + +CL_API_FUNCTION CL_InternTable *CL_CreateInternTable(CL_Arena *arena, int size) { + CL_InternTable *result = CL_PushStruct(arena, CL_InternTable); + CL_InitInternTable(arena, result, size); + return result; +} + +CL_API_FUNCTION CL_Intern *CL_InsertIntern(CL_InternTable *table, char *string, int len) { + CL_ASSERT(table->arena); + uint64_t hash = CL__HASH_BYTES(string, len); + if (hash == 0) hash += 1; + + uint64_t index = CL_WRAP_AROUND_POWER_OF_2(hash, table->entry_count); + CL_InternEntry *it = table->entries + index; + for (;;) { + if (it->hash == 0) { + it->string = CL_PushStringCopy(table->arena, string, len); + it->len = len; + it->hash = hash; + table->occupied_entry_count += 1; + return it->string; + } + else if (CL_StringsAreEqual(string, len, it->string, it->len)) { + return it->string; + } + + if (!it->next) { + it->next = CL_PushStruct(table->arena, CL_InternEntry); + } + it = it->next; + } +} + +CL_API_FUNCTION void CL_InternResult(CL_InternTable *table, CL_LexResult *result) { + for (int i = 0; i < result->tokens.count; i += 1) { + CL_Token *it = result->tokens.data + i; + if (it->kind == CL_IDENTIFIER) { + it->intern = CL_InsertIntern(table, it->str, it->len); + } + } +} + +CL_API_FUNCTION void CL_InternListEx(CL_InternTable *table, CL_LexList *list) { + for (CL_LexResult *it = list->first_result; it; it = it->next_result) { + CL_InternResult(table, it); + } +} + +CL_API_FUNCTION void CL_InternList(CL_Arena *arena, CL_LexList *list) { + list->intern_table = CL_CreateInternTable(arena, 4096); + CL_InternListEx(list->intern_table, list); +} + +CL_PRIVATE_FUNCTION char *CL_ChopLastSlash(CL_Arena *arena, char *str) { + int i = 0; + int slash_pos = -1; + while (str[i]) { + if (str[i] == '/') { + slash_pos = i; + } + i += 1; + } + + char *result = str; + if (slash_pos != -1) { + result = CL_PushStringCopy(arena, str, slash_pos); + } + else { + result = "./"; + } + return result; +} + +CL_PRIVATE_FUNCTION char *CL_JoinPath(CL_Arena *arena, char *a, char *b) { + int alen = CL_StringLength(a); + int blen = CL_StringLength(b); + int additional_len = 0; + + if (alen && a[alen - 1] != '/') additional_len = 1; + char *result = CL_PushArray(arena, char, alen + blen + 1 + additional_len); + CL__MemoryCopy(result, a, alen); + if (additional_len) result[alen++] = '/'; + CL__MemoryCopy(result + alen, b, blen); + result[alen + blen] = 0; + return result; +} + +CL_PRIVATE_FUNCTION bool CL_IsAbsolutePath(char *path) { +#if _WIN32 + bool result = CL_IsAlphabetic(path[0]) && path[1] == ':' && path[2] == '/'; +#else + bool result = path[0] == '/'; +#endif + return result; +} + +char *CL_SkipToLastSlash(char *p) { + int last_slash = 0; + for (int i = 0; p[i]; i += 1) { + if (p[i] == '/') last_slash = i; + } + return p + last_slash; +} + +CL_API_FUNCTION char *CL_ResolveFilepath(CL_Arena *arena, CL_SearchPaths *search_paths, char *filename, char *parent_file, bool is_system_include) { + CL_SearchPaths null_search_paths = CL_ZeroStruct(); + if (search_paths == 0) search_paths = &null_search_paths; + + if (search_paths->file_begin_to_ignore) { + char *name = CL_SkipToLastSlash(filename); + int namelen = CL_StringLength(name); + char *ignore = search_paths->file_begin_to_ignore; + int ignorelen = CL_StringLength(ignore); + if (namelen > ignorelen) { + namelen = ignorelen; + } + if (CL_StringsAreEqual(name, namelen, search_paths->file_begin_to_ignore, ignorelen)) { + return 0; + } + } + + if (CL_IsAbsolutePath(filename) && CL_FileExists(filename)) { + return filename; + } + + if (is_system_include) { + for (int path_i = 0; path_i < search_paths->system_include_path_count; path_i += 1) { + char *path_it = search_paths->system_include_path[path_i]; + char *file = CL_JoinPath(arena, path_it, filename); + if (CL_FileExists(file)) { + return file; + } + } + } + else { + if (parent_file) { + char *parent_dir = CL_ChopLastSlash(arena, parent_file); + char *file = CL_JoinPath(arena, parent_dir, filename); + if (CL_FileExists(file)) { + return file; + } + } + + for (int path_i = 0; path_i < search_paths->include_path_count; path_i += 1) { + char *path_it = search_paths->include_path[path_i]; + char *file = CL_JoinPath(arena, path_it, filename); + if (CL_FileExists(file)) { + return file; + } + } + } + return 0; +} + +CL_API_FUNCTION bool CL_IsValidFile(CL_LexList *list, char *filename) { + if (filename == 0) return false; + int filename_len = CL_StringLength(filename); + if (filename_len == 0) return false; + + for (CL_LexResult *it = list->first_result; it; it = it->next_result) { + int file_len = CL_StringLength(it->file); + if (CL_StringsAreEqual(filename, filename_len, it->file, file_len)) { + return false; + } + } + return true; +} + +CL_API_FUNCTION CL_LexResult *CL_GetFile(CL_LexList *list, char *name) { + for (CL_LexResult *it = list->first_result; it; it = it->next_result) { + if (CL_StringsAreEqual(it->file, CL_StringLength(it->file), name, CL_StringLength(name))) { + return it; + } + } + return 0; +} + +CL_API_FUNCTION void CL_InitDefaultTuple(CL_ArenaTuple *tuple) { + CL__MemoryZero(tuple, sizeof(CL_ArenaTuple)); + tuple->comment = &tuple->default_comment; + tuple->token = &tuple->default_token; + tuple->include = &tuple->default_include; + tuple->other = &tuple->default_other; +} + +CL_API_FUNCTION CL_LexList CL_LexRecursive(CL_ArenaTuple *arena, char *filename, CL_SearchPaths paths) { + CL_LexResult *first_file = CL_LexFile(arena, filename); + CL_LexList result = CL_MakeLexList(first_file); + result.search_paths = paths; + for (CL_IncludeIter iter = CL_IterateResolvedIncludes(arena->other, &result, paths); iter.filename; CL_GetNextInclude(&iter)) { + CL_LexResult *file = CL_LexFile(arena, iter.filename); + CL_AddLexResult(&result, file); + } + return result; +} + +#endif // CL_IMPLEMENTATION \ No newline at end of file diff --git a/core.c b/core.c new file mode 100644 index 0000000..94ed34a --- /dev/null +++ b/core.c @@ -0,0 +1,34 @@ +#include "core.h" +#define S8_IMPLEMENTATION + +#define IO_IMPLEMENTATION +#define IO_VSNPRINTF stbsp_vsnprintf +#define IO_SNPRINTF stbsp_snprintf +#include "io.h" + +#define STB_SPRINTF_IMPLEMENTATION +#include "stb_sprintf.h" + +#define MA_IMPLEMENTATION +#define MA_ASSERT(x) IO_Assert(x) +#include "arena.h" + +#define RE_IMPLEMENTATION +#define RE_ASSERT(x) IO_Assert(x) +#include "regex.h" + +#define UTF_IMPLEMENTATION +#include "unicode.h" + +#define S8_VSNPRINTF stbsp_vsnprintf +#define S8_ALLOCATE(allocator, size) MA_PushSize(allocator, size) +#define S8_ASSERT(x) IO_Assert(x) +#define S8_MemoryCopy MA_MemoryCopy +#include "string.h" + +#define MU_IMPLEMENTATION +#define MU_ASSERT IO_Assert +#include "multimedia.h" +#include "hash.c" +#include "load_library.c" +#include "filesystem.c" diff --git a/core.h b/core.h new file mode 100644 index 0000000..910b91a --- /dev/null +++ b/core.h @@ -0,0 +1,26 @@ +#pragma once +#include "preproc_env.h" +#include "stb_sprintf.h" +#include "io.h" +#include "arena.h" +#include "string.h" +#include "unicode.h" +#include "hash.h" +#include "linked_list.h" +#include "regex.h" +#include "multimedia.h" +#include "load_library.h" +#include "filesystem.h" + +#ifdef __cplusplus + #include "defer.hpp" + #define TABLE_ASSERT IO_Assert + #define TABLE_SET_DEFAULT_ALLOCATOR \ + if (!allocator.p) allocator = M_GetSystemAllocator(); + #include "table.hpp" + #define ARRAY_ASSERT IO_Assert + #define ARRAY_ALLOCATOR_TYPE M_Allocator + #define ARRAY_SET_DEFAULT_ALLOCATOR \ + if (!allocator.p) allocator = M_GetSystemAllocator(); + #include "array.hpp" +#endif diff --git a/defer.hpp b/defer.hpp new file mode 100644 index 0000000..a5f1a83 --- /dev/null +++ b/defer.hpp @@ -0,0 +1,21 @@ +#define DEFER_HEADER +template +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 + DEFER_ExitScope 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() + [&]() diff --git a/filesystem.c b/filesystem.c new file mode 100644 index 0000000..3eb354e --- /dev/null +++ b/filesystem.c @@ -0,0 +1,513 @@ +#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 + #include + #include + +OS_API bool WIN_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 void DEV_SetWorkingDir(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)) { + // COLOR CODES ENABLED + } + 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"); + } + } + + MA_Checkpoint scratch = MA_GetScratch(); + S8_String working_dir = OS_GetWorkingDir(scratch.arena); + if (S8_EndsWith(working_dir, S8_Lit("build/"), S8_IGNORE_CASE)) { + OS_SetWorkingDir(S8_Lit("..")); + } + MA_ReleaseScratch(scratch); +} + +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); +} // @untested + +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 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 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; +} + +#endif + +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; +} \ No newline at end of file diff --git a/filesystem.h b/filesystem.h new file mode 100644 index 0000000..c1856c2 --- /dev/null +++ b/filesystem.h @@ -0,0 +1,67 @@ +#ifndef OS_FS_HEADER +#define OS_FS_HEADER + +#ifndef OS_API + #ifdef __cplusplus + #define OS_API extern "C" + #else + #define OS_API + #endif +#endif + +typedef enum OS_Result OS_Result; +typedef struct OS_Date OS_Date; + +enum OS_Result { + OS_SUCCESS, + OS_ALREADY_EXISTS, + OS_PATH_NOT_FOUND, + OS_FAILURE, +}; + +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 void DEV_SetWorkingDir(void); +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); +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 WIN_EnableTerminalColors(void); +#endif // OS_FS_HEADER diff --git a/hash.c b/hash.c new file mode 100644 index 0000000..8b44f00 --- /dev/null +++ b/hash.c @@ -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; +} diff --git a/hash.h b/hash.h new file mode 100644 index 0000000..35d06fd --- /dev/null +++ b/hash.h @@ -0,0 +1,33 @@ +#ifndef HASH_HEADER +#define HASH_HEADER +#include + +#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); } +#endif diff --git a/io.h b/io.h new file mode 100644 index 0000000..dd98691 --- /dev/null +++ b/io.h @@ -0,0 +1,298 @@ +#ifndef IO_HEADER +#define IO_HEADER +#include +typedef enum IO_ErrorResult IO_ErrorResult; +#ifndef IO_API + #ifdef __cplusplus + #define IO_API extern "C" + #else + #define IO_API + #endif +#endif + +enum IO_ErrorResult { + IO_ErrorResult_Continue, + IO_ErrorResult_Break, + IO_ErrorResult_Exit, +}; + +#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); +#endif + +#ifdef IO_IMPLEMENTATION +#ifndef IO_SNPRINTF + #include + #define IO_SNPRINTF snprintf +#endif + +#ifndef IO_VSNPRINTF + #include + #define IO_VSNPRINTF vsnprintf +#endif + +#ifndef IO_ALLOCATE + #include + #define IO_ALLOCATE(x) malloc(x) + #define IO_FREE(x) free(x) +#endif + +#ifndef IO_FN + #if defined(__GNUC__) || defined(__clang__) + #define IO_FN __attribute__((unused)) static + #else + #define IO_FN static + #endif +#endif + +IO_FN 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 + + #pragma comment(lib, "user32") + + #include + +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 + +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 +#endif // IO_IMPLEMENTATION \ No newline at end of file diff --git a/linked_list.h b/linked_list.h new file mode 100644 index 0000000..dc4f748 --- /dev/null +++ b/linked_list.h @@ -0,0 +1,129 @@ +#ifndef LINKED_LIST_HEADER +#define LINKED_LIST_HEADER + +#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) + +#endif \ No newline at end of file diff --git a/load_library.c b/load_library.c new file mode 100644 index 0000000..0f44622 --- /dev/null +++ b/load_library.c @@ -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 + +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 diff --git a/load_library.h b/load_library.h new file mode 100644 index 0000000..cd7782c --- /dev/null +++ b/load_library.h @@ -0,0 +1,14 @@ +#ifndef LIB_LOAD_HEADER +#define LIB_LOAD_HEADER + +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 + +#endif \ No newline at end of file diff --git a/multimedia.h b/multimedia.h new file mode 100644 index 0000000..737a8e0 --- /dev/null +++ b/multimedia.h @@ -0,0 +1,2112 @@ +#ifndef MU_HEADER +#define MU_HEADER +#include +#include +#include +#ifndef MU_API + #ifdef __cplusplus + #define MU_API extern "C" + #else + #define MU_API + #endif +#endif + +#ifndef MU_FN + #if defined(__GNUC__) || defined(__clang__) + #define MU_FN __attribute__((unused)) static + #else + #define MU_FN static + #endif +#endif + +#ifndef MU_PRIVATE_VAR + #define MU_PRIVATE_VAR MU_FN +#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 enum MU_Key MU_Key; +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; +}; + +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, +}; + +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; + + + */ + +#endif // MU_HEADER +#ifdef MU_IMPLEMENTATION +#ifdef _WIN32 + #ifndef NOMINMAX + #define NOMINMAX + #endif + #ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN + #endif + #include + #include // for handling dropped files + + #define INITGUID + #define CINTERFACE + #define COBJMACROS + #define CONST_VTABLE + #include + #include + #include + #include + + // + // Automatically linking with the libraries + // + #pragma comment(lib, "gdi32.lib") + #pragma comment(lib, "user32.lib") + #pragma comment(lib, "shell32.lib") // For handling dropping files into the app +#endif +MU_FN void *MU_PushSize(MU_Arena *ar, size_t size); +MU_FN MU_UTF32Result MU_UTF16ToUTF32(uint16_t *c, int max_advance); +MU_FN MU_UTF8Result MU_UTF32ToUTF8(uint32_t codepoint); +MU_FN int64_t MU_CreateCharFromWidechar(char *buffer, int64_t buffer_size, wchar_t *in, int64_t inlen); +MU_FN void MU_WIN32_UpdateFocusedWindowBasedOnHandle(MU_Context *mu, HWND handle); +MU_FN void MU_WIN32_UpdateFocusedWindow(MU_Context *mu); +MU_FN void MU_Win32_GetWindowSize(HWND window, int *x, int *y); +MU_FN void MU_WIN32_GetWindowPos(HWND window, int *x, int *y); +MU_FN MU_Int2 MU_WIN32_GetMousePosition(HWND window); +MU_FN MU_Int2 MU_WIN32_GetMousePositionInverted(HWND window, int y); +MU_FN void MU_WIN32_CreateCanvas(MU_Window *window); +MU_FN void MU_WIN32_DestroyCanvas(MU_Window *window); +MU_FN void MU_WIN32_DrawCanvas(MU_Window *window); +MU_FN void MU__MemoryCopy(void *dst, const void *src, size_t size); +MU_FN void MU_PushDroppedFile(MU_Context *mu, MU_Window *window, char *str, int size); +MU_FN void MU_UpdateWindowState(MU_Window *window); +MU_FN int MU__AreStringsEqual(const char *src, const char *dst, size_t dstlen); +MU_FN void *MU_Win32_GLGetWindowProcAddressForGlad(const char *proc); +MU_FN void MU_WIN32_GetWGLFunctions(MU_Context *mu); +MU_FN void MU_WIN32_TryToInitGLContextForWindow(MU_Context *mu, MU_Win32_Window *w32_window); +MU_FN void MU_WIN32_DeinitSound(MU_Context *mu); +MU_FN void MU_WIN32_LoadCOM(MU_Context *mu); +MU_FN DWORD MU_WIN32_SoundThread(void *parameter); +MU_FN void MU_WIN32_InitWasapi(MU_Context *mu); + +#ifndef MU_GL_ENABLE_MULTISAMPLING + #define MU_GL_ENABLE_MULTISAMPLING 1 +#endif + +#ifndef MU_GL_BUILD_DEBUG + #define MU_GL_BUILD_DEBUG 1 +#endif + +#ifndef MU_ASSERT_CODE + #define MU_ASSERT_CODE(x) x +#endif + +#ifndef MU_ASSERT + #include + #define MU_ASSERT(x) assert(x) +#endif + +/* Quake uses this to sleep when user is not interacting with app + void SleepUntilInput (int time) + { + MsgWaitForMultipleObjects(1, &tevent, FALSE, time, QS_ALLINPUT); + } + + if ((cl.paused && (!ActiveApp && !DDActive)) || Minimized || block_drawing) + { + SleepUntilInput (PAUSE_SLEEP); + scr_skipupdate = 1; // no point in bothering to draw + } + else if (!ActiveApp && !DDActive) + { + SleepUntilInput (NOT_FOCUS_SLEEP); + } + */ +// @! Add native handle to MU_Context for Directx 11 initialize +// @! Add option to manually blit, some manual blit param and manual blit function +// @! Add ram info? https://learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-globalmemorystatusex +// @! Maybe make the library friendly to people who dont use debuggers +// @! Set window title +// @! Good defaults for multiple windows ?? + +struct MU_UTF32Result { + uint32_t out_str; + int advance; + int error; +}; + +struct MU_UTF8Result { + char out_str[4]; + int len; + int error; +}; + +#define MU_ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) +#define MU_STACK_ADD_MOD(stack_base, new_stack_base, next) \ + do { \ + (new_stack_base)->next = (stack_base); \ + (stack_base) = (new_stack_base); \ + } while (0) +#define MU_STACK_ADD(stack_base, new_stack_base) \ + MU_STACK_ADD_MOD(stack_base, new_stack_base, next) + +MU_API void MU_Quit(MU_Context *mu) { + mu->quit = true; +} + +MU_API void MU_DefaultSoundCallback(MU_Context *mu, uint16_t *buffer, uint32_t samples_to_fill) { +} + +MU_INLINE void MU__WriteChar8(MU_Window *window, char *c, int len) { + if (window->user_text8_count + len < MU_ARRAY_SIZE(window->user_text8)) { + for (int i = 0; i < len; i += 1) { + window->user_text8[window->user_text8_count++] = c[i]; + } + } +} + +MU_INLINE void MU__WriteChar32(MU_Window *window, uint32_t c) { + if (window->user_text32_count + 1 < MU_ARRAY_SIZE(window->user_text32)) { + window->user_text32[window->user_text32_count++] = c; + } +} + +MU_INLINE void MU__KeyDown(MU_Window *window, MU_Key key) { + if (window->key[key].down == false) + window->key[key].press = true; + window->key[key].down = true; + window->key[key].raw_press = true; +} + +MU_INLINE void MU__ZeroMemory(void *p, size_t size) { + uint8_t *p8 = (uint8_t *)p; + for (size_t i = 0; i < size; i += 1) { + p8[i] = 0; + } +} + +MU_INLINE size_t MU__GetAlignOffset(size_t size, size_t align) { + size_t mask = align - 1; + size_t val = size & mask; + if (val) { + val = align - val; + } + return val; +} + +MU_INLINE void MU__KeyUP(MU_Window *window, MU_Key key) { + if (window->key[key].down == true) + window->key[key].unpress = true; + window->key[key].down = false; +} + +MU_INLINE bool MU_DoesSizeFit(MU_Arena *ar, size_t size) { + const size_t alignment = 16; + size_t align = MU__GetAlignOffset((uintptr_t)ar->memory + ar->len, alignment); + size_t cursor = ar->len + align; + bool result = cursor + size <= ar->cap; + return result; +} + +#define MU_PUSH_STRUCT(mu, T) (T *)MU_PushSize(mu, sizeof(T)) +MU_FN void *MU_PushSize(MU_Arena *ar, size_t size) { + const size_t alignment = 16; + + ar->len += MU__GetAlignOffset((uintptr_t)ar->memory + ar->len, alignment); + if (ar->len + size > ar->cap) { + MU_ASSERT(!"MU_Context has not enough memory for what you are trying to do!"); + } + + void *result = (void *)(ar->memory + ar->len); + ar->len += size; + MU_ASSERT(ar->len < ar->cap); + MU__ZeroMemory(result, size); + return result; +} + +MU_FN MU_UTF32Result MU_UTF16ToUTF32(uint16_t *c, int max_advance) { + MU_UTF32Result result; + MU__ZeroMemory(&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; +} + +MU_FN MU_UTF8Result MU_UTF32ToUTF8(uint32_t codepoint) { + MU_UTF8Result result; + MU__ZeroMemory(&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 = true; + } + + return result; +} + +// @warning: this function is a little different from usual, returns -1 on decode errors +MU_FN int64_t MU_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] != 0;) { + MU_UTF32Result decode = MU_UTF16ToUTF32((uint16_t *)(in + i), (int)(inlen - i)); + if (!decode.error) { + i += decode.advance; + MU_UTF8Result encode = MU_UTF32ToUTF8(decode.out_str); + if (!encode.error) { + for (int64_t j = 0; j < encode.len; j++) { + if (outlen >= buffer_size) { + outlen = -1; + goto failed_to_decode; + } + buffer[outlen++] = encode.out_str[j]; + } + } + else { + outlen = -1; + goto failed_to_decode; + } + } + else { + outlen = -1; + goto failed_to_decode; + } + } + + buffer[outlen] = 0; +failed_to_decode:; + return outlen; +} + +#ifdef _WIN32 + #define MU_DEFAULT_MEMORY_SIZE (1024 * 4) + +// Typedefines for the COM functions which are going to be loaded currently only required for sound +typedef HRESULT CoCreateInstanceFunction(REFCLSID rclsid, LPUNKNOWN *pUnkOuter, DWORD dwClsContext, REFIID riid, LPVOID *ppv); +typedef HRESULT CoInitializeExFunction(LPVOID pvReserved, DWORD dwCoInit); + +struct MU_Win32 { + WNDCLASSW wc; + bool good_scheduling; + MU_glGetProcAddress *wgl_get_proc_address; + HMODULE opengl32; + + HCURSOR cursor_hand; + HCURSOR cursor_arrow; + + // Sound + IMMDevice *device; + IAudioClient *audio_client; + IMMDeviceEnumerator *device_enum; + IAudioRenderClient *audio_render_client; + + uint32_t buffer_frame_count; + // IAudioCaptureClient *audio_capture_client; + + // Pointers to the functions from the dll + CoCreateInstanceFunction *CoCreateInstanceFunctionPointer; + CoInitializeExFunction *CoInitializeExFunctionPointer; +}; + +struct MU_Win32_Window { + HDC handle_dc; + HDC canvas_dc; + HBITMAP canvas_dib; + + // for fullscreen + WINDOWPLACEMENT prev_placement; + DWORD style; +}; + +MU_API double MU_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; +} + +MU_FN void MU_WIN32_UpdateFocusedWindowBasedOnHandle(MU_Context *mu, HWND handle) { + if (mu->all_windows == 0) return; + for (MU_Window *it = mu->all_windows; it; it = it->next) { + if (it->handle == handle) { + mu->window = it; + return; + } + } +} + +MU_FN void MU_WIN32_UpdateFocusedWindow(MU_Context *mu) { + HWND handle = GetFocus(); + if (handle) { + MU_WIN32_UpdateFocusedWindowBasedOnHandle(mu, handle); + } +} + +MU_FN void MU_Win32_GetWindowSize(HWND window, int *x, int *y) { + RECT window_rect; + GetClientRect(window, &window_rect); + *x = window_rect.right - window_rect.left; + *y = window_rect.bottom - window_rect.top; +} + +MU_FN void MU_WIN32_GetWindowPos(HWND window, int *x, int *y) { + POINT point = {0, 0}; + ClientToScreen(window, &point); + *x = point.x; + *y = point.y; +} + +MU_FN MU_Int2 MU_WIN32_GetMousePosition(HWND window) { + POINT p; + GetCursorPos(&p); + ScreenToClient(window, &p); + MU_Int2 result = {p.x, p.y}; + return result; +} + +MU_FN MU_Int2 MU_WIN32_GetMousePositionInverted(HWND window, int y) { + MU_Int2 result = MU_WIN32_GetMousePosition(window); + result.y = y - result.y; + return result; +} + +MU_FN void MU_WIN32_CreateCanvas(MU_Window *window) { + MU_Win32_Window *w32_window = (MU_Win32_Window *)window->platform; + + MU_ASSERT(window->canvas == 0); + + BITMAPINFO bminfo; + { + MU__ZeroMemory(&bminfo, sizeof(bminfo)); + bminfo.bmiHeader.biSize = sizeof(bminfo.bmiHeader); + bminfo.bmiHeader.biWidth = (LONG)window->size.x; + bminfo.bmiHeader.biHeight = (LONG)window->size.y; + bminfo.bmiHeader.biPlanes = 1; + bminfo.bmiHeader.biBitCount = 32; + bminfo.bmiHeader.biCompression = BI_RGB; // AA RR GG BB + bminfo.bmiHeader.biXPelsPerMeter = 1; + bminfo.bmiHeader.biYPelsPerMeter = 1; + } + w32_window->canvas_dib = CreateDIBSection(w32_window->handle_dc, &bminfo, DIB_RGB_COLORS, (void **)&window->canvas, 0, 0); + w32_window->canvas_dc = CreateCompatibleDC(w32_window->handle_dc); +} + +MU_FN void MU_WIN32_DestroyCanvas(MU_Window *window) { + MU_Win32_Window *w32_window = (MU_Win32_Window *)window->platform; + if (window->canvas) { + DeleteDC(w32_window->canvas_dc); + DeleteObject(w32_window->canvas_dib); + w32_window->canvas_dc = 0; + w32_window->canvas_dib = 0; + window->canvas = 0; + } +} + +MU_FN void MU_WIN32_DrawCanvas(MU_Window *window) { + if (window->canvas) { + MU_Win32_Window *w32_window = (MU_Win32_Window *)window->platform; + SelectObject(w32_window->canvas_dc, w32_window->canvas_dib); + BitBlt(w32_window->handle_dc, 0, 0, window->size.x, window->size.y, w32_window->canvas_dc, 0, 0, SRCCOPY); + } +} + +MU_API void MU_ToggleFPSMode(MU_Window *window) { + ShowCursor(window->is_fps_mode); + window->is_fps_mode = !window->is_fps_mode; +} + +MU_API void MU_DisableFPSMode(MU_Window *window) { + if (window->is_fps_mode == true) MU_ToggleFPSMode(window); +} + +MU_API void MU_EnableFPSMode(MU_Window *window) { + if (window->is_fps_mode == false) MU_ToggleFPSMode(window); +} + +MU_FN void MU__MemoryCopy(void *dst, const void *src, size_t size) { + char *src8 = (char *)src; + char *dst8 = (char *)dst; + while (size--) *dst8++ = *src8++; +} + +// https://devblogs.microsoft.com/oldnewthing/20100412-00/?p=14353 +MU_API void MU_ToggleFullscreen(MU_Window *window) { + MU_Win32_Window *w32_window = (MU_Win32_Window *)window->platform; + DWORD dwStyle = GetWindowLong((HWND)window->handle, GWL_STYLE); + if (window->is_fullscreen == false) { + MONITORINFO mi = {sizeof(mi)}; + if (GetWindowPlacement((HWND)window->handle, &w32_window->prev_placement) && + GetMonitorInfo(MonitorFromWindow((HWND)window->handle, MONITOR_DEFAULTTOPRIMARY), &mi)) { + SetWindowLong((HWND)window->handle, GWL_STYLE, dwStyle & ~WS_OVERLAPPEDWINDOW); + BOOL result = SetWindowPos((HWND)window->handle, HWND_TOP, + mi.rcMonitor.left, mi.rcMonitor.top, + mi.rcMonitor.right - mi.rcMonitor.left, + mi.rcMonitor.bottom - mi.rcMonitor.top, + SWP_NOOWNERZORDER | SWP_FRAMECHANGED); + if (result) window->is_fullscreen = true; + } + } + else { + SetWindowLong((HWND)window->handle, GWL_STYLE, w32_window->style); + SetWindowPlacement((HWND)window->handle, &w32_window->prev_placement); + BOOL result = SetWindowPos((HWND)window->handle, NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_FRAMECHANGED); + if (result) window->is_fullscreen = false; + } +} + +MU_FN void MU_PushDroppedFile(MU_Context *mu, MU_Window *window, char *str, int size) { + if (MU_DoesSizeFit(&mu->frame_arena, sizeof(MU_DroppedFile) + size)) { + MU_DroppedFile *result = MU_PUSH_STRUCT(&mu->frame_arena, MU_DroppedFile); + result->filename = (char *)MU_PushSize(&mu->frame_arena, size + 1); + result->filename_size = size; + MU__MemoryCopy(result->filename, str, size); + result->filename[size] = 0; + + result->next = window->first_dropped_file; + window->first_dropped_file = result; + } +} + +// Should be initialized before processing events +// Should be initialized before initializing opengl functions using GLAD +static MU_Context *MU_WIN32_ContextPointerForEventHandling = 0; +static LRESULT CALLBACK MU_WIN32_WindowProc(HWND wnd, UINT msg, WPARAM wparam, LPARAM lparam) { + MU_Context *mu = MU_WIN32_ContextPointerForEventHandling; + MU_Win32 *w32 = (MU_Win32 *)mu->platform; + + MU_WIN32_UpdateFocusedWindowBasedOnHandle(mu, wnd); + MU_Window *window = mu->window ? mu->window : 0; + if (window) window->processed_events_this_frame += 1; + + (void)w32; + switch (msg) { + + case WM_DROPFILES: { + wchar_t buffer[512]; + char buffer8[1024]; + + HDROP hdrop = (HDROP)wparam; + int file_count = (int)DragQueryFileW(hdrop, 0xffffffff, NULL, 0); + bool drop_failed = false; + for (int i = 0; i < file_count; i += 1) { + // UINT num_chars = DragQueryFileW(hdrop, i, NULL, 0) + 1; + // WCHAR* buffer = (WCHAR*) _sapp_malloc_clear(num_chars * sizeof(WCHAR)); + UINT result = DragQueryFileW(hdrop, i, buffer, MU_ARRAY_SIZE(buffer)); + MU_ASSERT(result != 0); + if (result) { + int64_t size = MU_CreateCharFromWidechar(buffer8, MU_ARRAY_SIZE(buffer8), buffer, MU_ARRAY_SIZE(buffer)); + if (size != -1) { + MU_PushDroppedFile(mu, window, buffer8, (int)size); + } + } + } + DragFinish(hdrop); + } break; + + case WM_CLOSE: { + // @! Make sure that focus works + // @! We should find the window and make sure we inform it that the user clicked the close button + PostQuitMessage(0); + mu->quit = true; + } break; + + case WM_LBUTTONDOWN: { + SetCapture(wnd); + if (window->change_cursor_on_mouse_hold) SetCursor(w32->cursor_hand); + if (window->mouse.left.down == false) + window->mouse.left.press = true; + window->mouse.left.down = true; + } break; + + case WM_LBUTTONUP: { + ReleaseCapture(); + if (window->change_cursor_on_mouse_hold) SetCursor(w32->cursor_arrow); + if (window->mouse.left.down == true) + window->mouse.left.unpress = true; + window->mouse.left.down = false; + } break; + + case WM_RBUTTONDOWN: { + SetCapture(wnd); + if (window->mouse.right.down == false) + window->mouse.right.press = true; + window->mouse.right.down = true; + } break; + + case WM_RBUTTONUP: { + ReleaseCapture(); + if (window->mouse.right.down == true) + window->mouse.right.unpress = true; + window->mouse.right.down = false; + } break; + + case WM_MBUTTONDOWN: { + SetCapture(wnd); + if (window->mouse.middle.down == false) + window->mouse.middle.press = true; + window->mouse.middle.down = true; + } break; + + case WM_MBUTTONUP: { + ReleaseCapture(); + if (window->mouse.middle.down == true) + window->mouse.middle.unpress = true; + window->mouse.middle.down = false; + } break; + + case WM_MOUSEWHEEL: { + if ((int)wparam > 0) { + window->mouse.delta_wheel += 1.0f; + } + else { + window->mouse.delta_wheel -= 1.0f; + } + } break; + + case WM_CHAR: { + MU_UTF32Result encode = MU_UTF16ToUTF32((uint16_t *)&wparam, 2); + if (encode.error) { + MU__WriteChar32(window, (uint32_t)'?'); + MU__WriteChar8(window, "?", 1); + } + else { + MU__WriteChar32(window, encode.out_str); + } + + // Utf8 encode + if (encode.error == false) { + MU_UTF8Result encode8 = MU_UTF32ToUTF8(encode.out_str); + if (encode8.error) { + MU__WriteChar8(window, "?", 1); + } + else { + MU__WriteChar8(window, encode8.out_str, encode8.len); + } + } + } break; + + case WM_KEYUP: + case WM_SYSKEYUP: { + switch (wparam) { + case VK_ESCAPE: MU__KeyUP(window, MU_KEY_ESCAPE); break; + case VK_RETURN: MU__KeyUP(window, MU_KEY_ENTER); break; + case VK_TAB: MU__KeyUP(window, MU_KEY_TAB); break; + case VK_BACK: MU__KeyUP(window, MU_KEY_BACKSPACE); break; + case VK_INSERT: MU__KeyUP(window, MU_KEY_INSERT); break; + case VK_DELETE: MU__KeyUP(window, MU_KEY_DELETE); break; + case VK_RIGHT: MU__KeyUP(window, MU_KEY_RIGHT); break; + case VK_LEFT: MU__KeyUP(window, MU_KEY_LEFT); break; + case VK_DOWN: MU__KeyUP(window, MU_KEY_DOWN); break; + case VK_UP: MU__KeyUP(window, MU_KEY_UP); break; + case VK_PRIOR: MU__KeyUP(window, MU_KEY_PAGE_UP); break; + case VK_NEXT: MU__KeyUP(window, MU_KEY_PAGE_DOWN); break; + case VK_END: MU__KeyUP(window, MU_KEY_HOME); break; + case VK_HOME: MU__KeyUP(window, MU_KEY_END); break; + case VK_F1: MU__KeyUP(window, MU_KEY_F1); break; + case VK_F2: MU__KeyUP(window, MU_KEY_F2); break; + case VK_F3: MU__KeyUP(window, MU_KEY_F3); break; + case VK_F4: MU__KeyUP(window, MU_KEY_F4); break; + case VK_F5: MU__KeyUP(window, MU_KEY_F5); break; + case VK_F6: MU__KeyUP(window, MU_KEY_F6); break; + case VK_F7: MU__KeyUP(window, MU_KEY_F7); break; + case VK_F8: MU__KeyUP(window, MU_KEY_F8); break; + case VK_F9: MU__KeyUP(window, MU_KEY_F9); break; + case VK_F10: MU__KeyUP(window, MU_KEY_F10); break; + case VK_F11: MU__KeyUP(window, MU_KEY_F11); break; + case VK_F12: MU__KeyUP(window, MU_KEY_F12); break; + case VK_SPACE: MU__KeyUP(window, MU_KEY_SPACE); break; + case VK_OEM_PLUS: MU__KeyUP(window, MU_KEY_PLUS); break; + case VK_OEM_COMMA: MU__KeyUP(window, MU_KEY_COMMA); break; + case VK_OEM_MINUS: MU__KeyUP(window, MU_KEY_MINUS); break; + case VK_OEM_PERIOD: MU__KeyUP(window, MU_KEY_PERIOD); break; + case '0': MU__KeyUP(window, MU_KEY_0); break; + case '1': MU__KeyUP(window, MU_KEY_1); break; + case '2': MU__KeyUP(window, MU_KEY_2); break; + case '3': MU__KeyUP(window, MU_KEY_3); break; + case '4': MU__KeyUP(window, MU_KEY_4); break; + case '5': MU__KeyUP(window, MU_KEY_5); break; + case '6': MU__KeyUP(window, MU_KEY_6); break; + case '7': MU__KeyUP(window, MU_KEY_7); break; + case '8': MU__KeyUP(window, MU_KEY_8); break; + case '9': MU__KeyUP(window, MU_KEY_9); break; + case ';': MU__KeyUP(window, MU_KEY_SEMICOLON); break; + case '=': MU__KeyUP(window, MU_KEY_EQUAL); break; + case 'A': MU__KeyUP(window, MU_KEY_A); break; + case 'B': MU__KeyUP(window, MU_KEY_B); break; + case 'C': MU__KeyUP(window, MU_KEY_C); break; + case 'D': MU__KeyUP(window, MU_KEY_D); break; + case 'E': MU__KeyUP(window, MU_KEY_E); break; + case 'F': MU__KeyUP(window, MU_KEY_F); break; + case 'G': MU__KeyUP(window, MU_KEY_G); break; + case 'H': MU__KeyUP(window, MU_KEY_H); break; + case 'I': MU__KeyUP(window, MU_KEY_I); break; + case 'J': MU__KeyUP(window, MU_KEY_J); break; + case 'K': MU__KeyUP(window, MU_KEY_K); break; + case 'L': MU__KeyUP(window, MU_KEY_L); break; + case 'M': MU__KeyUP(window, MU_KEY_M); break; + case 'N': MU__KeyUP(window, MU_KEY_N); break; + case 'O': MU__KeyUP(window, MU_KEY_O); break; + case 'P': MU__KeyUP(window, MU_KEY_P); break; + case 'Q': MU__KeyUP(window, MU_KEY_Q); break; + case 'R': MU__KeyUP(window, MU_KEY_R); break; + case 'S': MU__KeyUP(window, MU_KEY_S); break; + case 'T': MU__KeyUP(window, MU_KEY_T); break; + case 'U': MU__KeyUP(window, MU_KEY_U); break; + case 'V': MU__KeyUP(window, MU_KEY_V); break; + case 'W': MU__KeyUP(window, MU_KEY_W); break; + case 'X': MU__KeyUP(window, MU_KEY_X); break; + case 'Y': MU__KeyUP(window, MU_KEY_Y); break; + case 'Z': MU__KeyUP(window, MU_KEY_Z); break; + case VK_F13: MU__KeyUP(window, MU_KEY_F13); break; + case VK_F14: MU__KeyUP(window, MU_KEY_F14); break; + case VK_F15: MU__KeyUP(window, MU_KEY_F15); break; + case VK_F16: MU__KeyUP(window, MU_KEY_F16); break; + case VK_F17: MU__KeyUP(window, MU_KEY_F17); break; + case VK_F18: MU__KeyUP(window, MU_KEY_F18); break; + case VK_F19: MU__KeyUP(window, MU_KEY_F19); break; + case VK_F20: MU__KeyUP(window, MU_KEY_F20); break; + case VK_F21: MU__KeyUP(window, MU_KEY_F21); break; + case VK_F22: MU__KeyUP(window, MU_KEY_F22); break; + case VK_F23: MU__KeyUP(window, MU_KEY_F23); break; + case VK_F24: MU__KeyUP(window, MU_KEY_F24); break; + case 0x60: MU__KeyUP(window, MU_KEY_KP_0); break; + case 0x61: MU__KeyUP(window, MU_KEY_KP_1); break; + case 0x62: MU__KeyUP(window, MU_KEY_KP_2); break; + case 0x63: MU__KeyUP(window, MU_KEY_KP_3); break; + case 0x64: MU__KeyUP(window, MU_KEY_KP_4); break; + case 0x65: MU__KeyUP(window, MU_KEY_KP_5); break; + case 0x66: MU__KeyUP(window, MU_KEY_KP_6); break; + case 0x67: MU__KeyUP(window, MU_KEY_KP_7); break; + case 0x68: MU__KeyUP(window, MU_KEY_KP_8); break; + case 0x69: MU__KeyUP(window, MU_KEY_KP_9); break; + case VK_DECIMAL: MU__KeyUP(window, MU_KEY_KP_DECIMAL); break; + case VK_DIVIDE: MU__KeyUP(window, MU_KEY_KP_DIVIDE); break; + case VK_MULTIPLY: MU__KeyUP(window, MU_KEY_KP_MULTIPLY); break; + case VK_SUBTRACT: MU__KeyUP(window, MU_KEY_KP_SUBTRACT); break; + case VK_ADD: MU__KeyUP(window, MU_KEY_KP_ADD); break; + case VK_LMENU: MU__KeyUP(window, MU_KEY_LEFT_ALT); break; + case VK_LWIN: MU__KeyUP(window, MU_KEY_LEFT_SUPER); break; + case VK_CONTROL: MU__KeyUP(window, MU_KEY_CONTROL); break; + case VK_SHIFT: MU__KeyUP(window, MU_KEY_SHIFT); break; + case VK_LSHIFT: MU__KeyUP(window, MU_KEY_LEFT_SHIFT); break; + case VK_LCONTROL: MU__KeyUP(window, MU_KEY_LEFT_CONTROL); break; + case VK_RSHIFT: MU__KeyUP(window, MU_KEY_RIGHT_SHIFT); break; + case VK_RCONTROL: MU__KeyUP(window, MU_KEY_RIGHT_CONTROL); break; + case VK_RMENU: MU__KeyUP(window, MU_KEY_RIGHT_ALT); break; + case VK_RWIN: MU__KeyUP(window, MU_KEY_RIGHT_SUPER); break; + case VK_CAPITAL: MU__KeyUP(window, MU_KEY_CAPS_LOCK); break; + case VK_SCROLL: MU__KeyUP(window, MU_KEY_SCROLL_LOCK); break; + case VK_NUMLOCK: MU__KeyUP(window, MU_KEY_NUM_LOCK); break; + case VK_SNAPSHOT: MU__KeyUP(window, MU_KEY_PRINT_SCREEN); break; + case VK_PAUSE: MU__KeyUP(window, MU_KEY_PAUSE); break; + } + } break; + + case WM_KEYDOWN: + case WM_SYSKEYDOWN: { + switch (wparam) { + case VK_ESCAPE: MU__KeyDown(window, MU_KEY_ESCAPE); break; + case VK_RETURN: MU__KeyDown(window, MU_KEY_ENTER); break; + case VK_TAB: MU__KeyDown(window, MU_KEY_TAB); break; + case VK_BACK: MU__KeyDown(window, MU_KEY_BACKSPACE); break; + case VK_INSERT: MU__KeyDown(window, MU_KEY_INSERT); break; + case VK_DELETE: MU__KeyDown(window, MU_KEY_DELETE); break; + case VK_RIGHT: MU__KeyDown(window, MU_KEY_RIGHT); break; + case VK_LEFT: MU__KeyDown(window, MU_KEY_LEFT); break; + case VK_DOWN: MU__KeyDown(window, MU_KEY_DOWN); break; + case VK_UP: MU__KeyDown(window, MU_KEY_UP); break; + case VK_PRIOR: MU__KeyDown(window, MU_KEY_PAGE_UP); break; + case VK_NEXT: MU__KeyDown(window, MU_KEY_PAGE_DOWN); break; + case VK_END: MU__KeyDown(window, MU_KEY_HOME); break; + case VK_HOME: MU__KeyDown(window, MU_KEY_END); break; + case VK_F1: MU__KeyDown(window, MU_KEY_F1); break; + case VK_F2: MU__KeyDown(window, MU_KEY_F2); break; + case VK_F3: MU__KeyDown(window, MU_KEY_F3); break; + case VK_F4: MU__KeyDown(window, MU_KEY_F4); break; + case VK_F5: MU__KeyDown(window, MU_KEY_F5); break; + case VK_F6: MU__KeyDown(window, MU_KEY_F6); break; + case VK_F7: MU__KeyDown(window, MU_KEY_F7); break; + case VK_F8: MU__KeyDown(window, MU_KEY_F8); break; + case VK_F9: MU__KeyDown(window, MU_KEY_F9); break; + case VK_F10: MU__KeyDown(window, MU_KEY_F10); break; + case VK_F11: MU__KeyDown(window, MU_KEY_F11); break; + case VK_F12: MU__KeyDown(window, MU_KEY_F12); break; + case VK_SPACE: MU__KeyDown(window, MU_KEY_SPACE); break; + case VK_OEM_PLUS: MU__KeyDown(window, MU_KEY_PLUS); break; + case VK_OEM_COMMA: MU__KeyDown(window, MU_KEY_COMMA); break; + case VK_OEM_MINUS: MU__KeyDown(window, MU_KEY_MINUS); break; + case VK_OEM_PERIOD: MU__KeyDown(window, MU_KEY_PERIOD); break; + case '0': MU__KeyDown(window, MU_KEY_0); break; + case '1': MU__KeyDown(window, MU_KEY_1); break; + case '2': MU__KeyDown(window, MU_KEY_2); break; + case '3': MU__KeyDown(window, MU_KEY_3); break; + case '4': MU__KeyDown(window, MU_KEY_4); break; + case '5': MU__KeyDown(window, MU_KEY_5); break; + case '6': MU__KeyDown(window, MU_KEY_6); break; + case '7': MU__KeyDown(window, MU_KEY_7); break; + case '8': MU__KeyDown(window, MU_KEY_8); break; + case '9': MU__KeyDown(window, MU_KEY_9); break; + case ';': MU__KeyDown(window, MU_KEY_SEMICOLON); break; + case '=': MU__KeyDown(window, MU_KEY_EQUAL); break; + case 'A': MU__KeyDown(window, MU_KEY_A); break; + case 'B': MU__KeyDown(window, MU_KEY_B); break; + case 'C': MU__KeyDown(window, MU_KEY_C); break; + case 'D': MU__KeyDown(window, MU_KEY_D); break; + case 'E': MU__KeyDown(window, MU_KEY_E); break; + case 'F': MU__KeyDown(window, MU_KEY_F); break; + case 'G': MU__KeyDown(window, MU_KEY_G); break; + case 'H': MU__KeyDown(window, MU_KEY_H); break; + case 'I': MU__KeyDown(window, MU_KEY_I); break; + case 'J': MU__KeyDown(window, MU_KEY_J); break; + case 'K': MU__KeyDown(window, MU_KEY_K); break; + case 'L': MU__KeyDown(window, MU_KEY_L); break; + case 'M': MU__KeyDown(window, MU_KEY_M); break; + case 'N': MU__KeyDown(window, MU_KEY_N); break; + case 'O': MU__KeyDown(window, MU_KEY_O); break; + case 'P': MU__KeyDown(window, MU_KEY_P); break; + case 'Q': MU__KeyDown(window, MU_KEY_Q); break; + case 'R': MU__KeyDown(window, MU_KEY_R); break; + case 'S': MU__KeyDown(window, MU_KEY_S); break; + case 'T': MU__KeyDown(window, MU_KEY_T); break; + case 'U': MU__KeyDown(window, MU_KEY_U); break; + case 'V': MU__KeyDown(window, MU_KEY_V); break; + case 'W': MU__KeyDown(window, MU_KEY_W); break; + case 'X': MU__KeyDown(window, MU_KEY_X); break; + case 'Y': MU__KeyDown(window, MU_KEY_Y); break; + case 'Z': MU__KeyDown(window, MU_KEY_Z); break; + case VK_F13: MU__KeyDown(window, MU_KEY_F13); break; + case VK_F14: MU__KeyDown(window, MU_KEY_F14); break; + case VK_F15: MU__KeyDown(window, MU_KEY_F15); break; + case VK_F16: MU__KeyDown(window, MU_KEY_F16); break; + case VK_F17: MU__KeyDown(window, MU_KEY_F17); break; + case VK_F18: MU__KeyDown(window, MU_KEY_F18); break; + case VK_F19: MU__KeyDown(window, MU_KEY_F19); break; + case VK_F20: MU__KeyDown(window, MU_KEY_F20); break; + case VK_F21: MU__KeyDown(window, MU_KEY_F21); break; + case VK_F22: MU__KeyDown(window, MU_KEY_F22); break; + case VK_F23: MU__KeyDown(window, MU_KEY_F23); break; + case VK_F24: MU__KeyDown(window, MU_KEY_F24); break; + case 0x60: MU__KeyDown(window, MU_KEY_KP_0); break; + case 0x61: MU__KeyDown(window, MU_KEY_KP_1); break; + case 0x62: MU__KeyDown(window, MU_KEY_KP_2); break; + case 0x63: MU__KeyDown(window, MU_KEY_KP_3); break; + case 0x64: MU__KeyDown(window, MU_KEY_KP_4); break; + case 0x65: MU__KeyDown(window, MU_KEY_KP_5); break; + case 0x66: MU__KeyDown(window, MU_KEY_KP_6); break; + case 0x67: MU__KeyDown(window, MU_KEY_KP_7); break; + case 0x68: MU__KeyDown(window, MU_KEY_KP_8); break; + case 0x69: MU__KeyDown(window, MU_KEY_KP_9); break; + case VK_CONTROL: MU__KeyDown(window, MU_KEY_CONTROL); break; + case VK_SHIFT: MU__KeyDown(window, MU_KEY_SHIFT); break; + case VK_DECIMAL: MU__KeyDown(window, MU_KEY_KP_DECIMAL); break; + case VK_DIVIDE: MU__KeyDown(window, MU_KEY_KP_DIVIDE); break; + case VK_MULTIPLY: MU__KeyDown(window, MU_KEY_KP_MULTIPLY); break; + case VK_SUBTRACT: MU__KeyDown(window, MU_KEY_KP_SUBTRACT); break; + case VK_ADD: MU__KeyDown(window, MU_KEY_KP_ADD); break; + case VK_LSHIFT: MU__KeyDown(window, MU_KEY_LEFT_SHIFT); break; + case VK_LCONTROL: MU__KeyDown(window, MU_KEY_LEFT_CONTROL); break; + case VK_LMENU: MU__KeyDown(window, MU_KEY_LEFT_ALT); break; + case VK_LWIN: MU__KeyDown(window, MU_KEY_LEFT_SUPER); break; + case VK_RSHIFT: MU__KeyDown(window, MU_KEY_RIGHT_SHIFT); break; + case VK_RCONTROL: MU__KeyDown(window, MU_KEY_RIGHT_CONTROL); break; + case VK_RMENU: MU__KeyDown(window, MU_KEY_RIGHT_ALT); break; + case VK_RWIN: MU__KeyDown(window, MU_KEY_RIGHT_SUPER); break; + case VK_CAPITAL: MU__KeyDown(window, MU_KEY_CAPS_LOCK); break; + case VK_SCROLL: MU__KeyDown(window, MU_KEY_SCROLL_LOCK); break; + case VK_NUMLOCK: MU__KeyDown(window, MU_KEY_NUM_LOCK); break; + case VK_SNAPSHOT: MU__KeyDown(window, MU_KEY_PRINT_SCREEN); break; + case VK_PAUSE: MU__KeyDown(window, MU_KEY_PAUSE); break; + } + } break; + + default: { + return DefWindowProcW(wnd, msg, wparam, lparam); + } + } + return 0; +} + +MU_API void MU_Init(MU_Context *mu, MU_Params params, size_t len) { + MU_ASSERT(params.memory && params.cap && "Expected any kind of memory"); + + MU__ZeroMemory(mu, sizeof(*mu)); + mu->perm_arena.memory = (char *)params.memory; + mu->perm_arena.cap = params.cap; + mu->perm_arena.len = len; + MU_WIN32_ContextPointerForEventHandling = mu; + + mu->platform = MU_PUSH_STRUCT(&mu->perm_arena, MU_Win32); + MU_Win32 *w32 = (MU_Win32 *)mu->platform; + + mu->frame_arena.cap = (mu->perm_arena.cap - mu->perm_arena.len) / 2; + mu->frame_arena.memory = (char *)MU_PushSize(&mu->perm_arena, mu->frame_arena.cap); + + mu->time.delta = params.delta_time == 0.0 ? 0.0166666 : params.delta_time; + + mu->sound.callback = params.sound_callback; + mu->params = params; + + if (mu->sound.callback) { + MU_WIN32_InitWasapi(mu); + MU_ASSERT(mu->sound.initialized); + } + + typedef enum MU_PROCESS_DPI_AWARENESS { + MU_PROCESS_DPI_UNAWARE = 0, + MU_PROCESS_SYSTEM_DPI_AWARE = 1, + MU_PROCESS_PER_MONITOR_DPI_AWARE = 2 + } MU_PROCESS_DPI_AWARENESS; + typedef unsigned MU_TimeBeginPeriod(unsigned); + typedef HRESULT MU_SetProcessDpiAwareness(MU_PROCESS_DPI_AWARENESS); + + HMODULE shcore = LoadLibraryA("Shcore.dll"); + if (shcore) { + MU_SetProcessDpiAwareness *set_dpi_awr = (MU_SetProcessDpiAwareness *)GetProcAddress(shcore, "SetProcessDpiAwareness"); + if (set_dpi_awr) { + HRESULT hr = set_dpi_awr(MU_PROCESS_PER_MONITOR_DPI_AWARE); + MU_ASSERT(SUCCEEDED(hr) && "Failed to set dpi awareness"); + } + } + + HMODULE winmm = LoadLibraryA("winmm.dll"); + if (winmm) { + MU_TimeBeginPeriod *timeBeginPeriod = (MU_TimeBeginPeriod *)GetProcAddress(winmm, "timeBeginPeriod"); + if (timeBeginPeriod) { + if (timeBeginPeriod(1) == 0) { + w32->good_scheduling = true; + } + } + } + + WNDCLASSW wc; + { + MU__ZeroMemory(&wc, sizeof(wc)); + wc.lpfnWndProc = MU_WIN32_WindowProc; + wc.hInstance = GetModuleHandleW(NULL); + wc.lpszClassName = L"Multimedia_Start"; + wc.hCursor = LoadCursor(0, IDC_ARROW); + wc.hIcon = NULL; // LoadIcon(wc.hInstance, IDI_APPLICATION); + wc.style = CS_OWNDC | CS_VREDRAW | CS_HREDRAW; + MU_ASSERT_CODE(ATOM result =) + RegisterClassW(&wc); + MU_ASSERT(result != 0); + w32->wc = wc; + } + + mu->primary_monitor_size.x = GetSystemMetrics(SM_CXSCREEN); + mu->primary_monitor_size.y = GetSystemMetrics(SM_CYSCREEN); + + w32->cursor_hand = LoadCursor(0, IDC_SIZEALL); + w32->cursor_arrow = LoadCursor(0, IDC_ARROW); + + mu->time.app_start = MU_GetTime(); + mu->first_frame = true; +} + +MU_FN void MU_UpdateWindowState(MU_Window *window) { + MU_Win32_Window *w32_window = (MU_Win32_Window *)window->platform; + + UINT dpi = GetDpiForWindow((HWND)window->handle); + MU_ASSERT(dpi != 0 && "Failed to get dpi for window"); + window->dpi_scale = (float)dpi / 96.f; + + MU_Int2 size; + MU_WIN32_GetWindowPos((HWND)window->handle, &window->pos.x, &window->pos.y); + MU_Win32_GetWindowSize((HWND)window->handle, &size.x, &size.y); + + if (window->canvas_enabled == false || window->size.x != size.x || window->size.y != size.y) { + MU_WIN32_DestroyCanvas(window); + } + + window->size = size; + window->sizef.x = (float)window->size.x; + window->sizef.y = (float)window->size.y; + + window->posf.x = (float)window->pos.x; + window->posf.y = (float)window->pos.y; + + if (window->canvas_enabled && window->canvas == 0) { + MU_WIN32_CreateCanvas(window); + } +} + +MU_API MU_Window *MU_AddWindow(MU_Context *mu, MU_Window_Params params) { + MU_Window *window = MU_PUSH_STRUCT(&mu->perm_arena, MU_Window); + MU_InitWindow(mu, window, params); + return window; +} + +MU_API void MU_InitWindow(MU_Context *mu, MU_Window *window, MU_Window_Params params) { + window->platform = MU_PUSH_STRUCT(&mu->perm_arena, MU_Win32_Window); + + MU_Win32 *w32 = (MU_Win32 *)mu->platform; + MU_Win32_Window *w32_window = (MU_Win32_Window *)window->platform; + + if (params.pos.x == 0) params.pos.x = (int)((double)mu->primary_monitor_size.x * 0.1); + if (params.pos.y == 0) params.pos.y = (int)((double)mu->primary_monitor_size.y * 0.1); + if (params.size.x == 0) params.size.x = (int)((double)mu->primary_monitor_size.x * 0.8); + if (params.size.y == 0) params.size.y = (int)((double)mu->primary_monitor_size.y * 0.8); + window->canvas_enabled = params.enable_canvas; + + w32_window->style = WS_OVERLAPPEDWINDOW; + if (!params.resizable) { + w32_window->style &= ~WS_THICKFRAME & ~WS_MAXIMIZEBOX; + } + if (params.borderless) { + w32_window->style = WS_POPUP | WS_VISIBLE | WS_SYSMENU; + } + + RECT window_rect; + window_rect.left = (LONG)params.pos.x; + window_rect.top = (LONG)params.pos.y; + window_rect.right = (LONG)params.size.x + window_rect.left; + window_rect.bottom = (LONG)params.size.y + window_rect.top; + AdjustWindowRectEx(&window_rect, w32_window->style, false, 0); + + HWND handle = CreateWindowW(w32->wc.lpszClassName, L"Zzz... Window, hello!", w32_window->style, window_rect.left, window_rect.top, window_rect.right - window_rect.left, window_rect.bottom - window_rect.top, NULL, NULL, w32->wc.hInstance, NULL); + MU_ASSERT(handle); + + window->handle = handle; + w32_window->handle_dc = GetDC(handle); + MU_ASSERT(w32_window->handle_dc); + + DragAcceptFiles(handle, TRUE); + + MU_WIN32_TryToInitGLContextForWindow(mu, w32_window); + + ShowWindow(handle, SW_SHOW); + MU_UpdateWindowState(window); + MU_STACK_ADD(mu->all_windows, window); + MU_WIN32_UpdateFocusedWindow(mu); +} + +MU_API MU_Context *MU_Start(MU_Params params) { + // Bootstrap the context from user memory + // If the user didnt provide memory, allocate it ourselves + if (!params.memory) { + HANDLE process_heap = GetProcessHeap(); + params.cap = MU_DEFAULT_MEMORY_SIZE; + params.memory = HeapAlloc(process_heap, 0, params.cap); + MU_ASSERT(params.memory); + } + MU_Context *mu = (MU_Context *)params.memory; + MU_Init(mu, params, sizeof(MU_Context)); + mu->window = MU_AddWindow(mu, params.window); + + return mu; +} + +MU_API bool MU_Update(MU_Context *mu) { + MU_Win32 *w32 = (MU_Win32 *)mu->platform; + + // Since this is meant to be called in while loop + // first MU_Update happens before first frame + // therfore start of second MU_Update is end of first frame + mu->_MU_Update_count += 1; + if (mu->_MU_Update_count == 2) mu->first_frame = false; + mu->frame_arena.len = 0; + + MU_WIN32_UpdateFocusedWindow(mu); + for (MU_Window *it = mu->all_windows; it; it = it->next) { + if (it->should_render == true && mu->first_frame == false && mu->opengl_initialized) { + MU_Win32_Window *w32_window = (MU_Win32_Window *)it->platform; + MU_ASSERT_CODE(BOOL result =) + SwapBuffers(w32_window->handle_dc); + MU_ASSERT(result); + } + it->should_render = true; + + it->first_dropped_file = 0; + MU_WIN32_DrawCanvas(it); + it->processed_events_this_frame = 0; + it->user_text8_count = 0; + it->user_text32_count = 0; + it->mouse.delta_wheel = 0.0; + it->mouse.left.press = 0; + it->mouse.right.press = 0; + it->mouse.middle.press = 0; + it->mouse.left.unpress = 0; + it->mouse.right.unpress = 0; + it->mouse.middle.unpress = 0; + for (int i = 0; i < MU_KEY_COUNT; i += 1) { + it->key[i].press = 0; + it->key[i].unpress = 0; + it->key[i].raw_press = 0; + } + } + + MSG msg; + MU_WIN32_ContextPointerForEventHandling = mu; + while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + for (MU_Window *it = mu->all_windows; it; it = it->next) { + MU_UpdateWindowState(it); + } + + mu->window->user_text8[mu->window->user_text8_count] = 0; + mu->window->user_text32[mu->window->user_text32_count] = 0; + + MU_Win32_Window *w32_window = (MU_Win32_Window *)mu->window->platform; + HWND focused_window = GetFocus(); + mu->window->is_focused = focused_window == (HWND)mu->window->handle; + + // We only need to update the mouse position of a currently focused window? + { + + MU_Int2 mouse_pos = MU_WIN32_GetMousePositionInverted((HWND)mu->window->handle, mu->window->size.y); + + if (mu->window->is_focused) { + if (mu->first_frame == false) { + mu->window->mouse.delta_pos.x = mouse_pos.x - mu->window->mouse.pos.x; + mu->window->mouse.delta_pos.y = mouse_pos.y - mu->window->mouse.pos.y; + mu->window->mouse.delta_pos_normalized.x = (float)mu->window->mouse.delta_pos.x / (float)mu->window->size.x; + mu->window->mouse.delta_pos_normalized.y = (float)mu->window->mouse.delta_pos.y / (float)mu->window->size.y; + } + if (mu->window->is_fps_mode) { + SetCursorPos(mu->window->size.x / 2, mu->window->size.y / 2); + mouse_pos = MU_WIN32_GetMousePositionInverted((HWND)mu->window->handle, mu->window->size.y); + } + } + mu->window->mouse.pos = mouse_pos; + mu->window->mouse.posf.x = (float)mouse_pos.x; + mu->window->mouse.posf.y = (float)mouse_pos.y; + } + + // Timming + if (mu->first_frame == false) { + mu->time.update = MU_GetTime() - mu->time.frame_start; + if (mu->time.update < mu->time.delta) { + mu->consecutive_missed_frames = 0; + + // Try to use the Sleep, if we dont have good scheduler priority + // then we can miss framerate so need to busy loop instead + if (w32->good_scheduling) { + double time_to_sleep = mu->time.delta - mu->time.update; + double time_to_sleep_in_ms = time_to_sleep * 1000.0 - 1; + if (time_to_sleep > 0.0) { + DWORD time_to_sleep_uint = (DWORD)time_to_sleep_in_ms; + if (time_to_sleep_uint) { + Sleep(time_to_sleep_uint); + } + } + } + + // Busy loop if we dont have good scheduling + // or we woke up early + double update_time = MU_GetTime() - mu->time.frame_start; + while (update_time < mu->time.delta) { + update_time = MU_GetTime() - mu->time.frame_start; + } + } + else { + mu->consecutive_missed_frames += 1; + mu->total_missed_frames += 1; + } + + mu->frame += 1; + mu->time.update_total = MU_GetTime() - mu->time.frame_start; + mu->time.total += mu->time.delta; + } + mu->time.frame_start = MU_GetTime(); + + mu->time.deltaf = (float)mu->time.delta; + mu->time.totalf = (float)mu->time.total; + + return !mu->quit; +} + +// +// Opengl context setup +// +// @! Cleanup OpenGL - Should the user be cappable of detecting that opengl couldnt load? +// Should the layer automatically downscale? +// Should the layer inform and allow for a response? +/* + MU_Context *mu = MU_Start((MU_Params){ + .enable_opengl = true, + }); + if (mu->opengl_initialized == false) { + mu_opengl_try_initializng_context_for_window(mu->window, 3, 3); + } + if (mu->opengl_initialized == false) { + // directx + } + + + */ + +// Symbols taken from GLFW +// +// Executables (but not DLLs) exporting this symbol with this value will be +// automatically directed to the high-performance GPU on Nvidia Optimus systems +// with up-to-date drivers +// +__declspec(dllexport) DWORD NvOptimusEnablement = 1; + +// Executables (but not DLLs) exporting this symbol with this value will be +// automatically directed to the high-performance GPU on AMD PowerXpress systems +// with up-to-date drivers +// +__declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; + +typedef HGLRC MU_wglCreateContext(HDC unnamedParam1); +typedef BOOL MU_wglMakeCurrent(HDC unnamedParam1, HGLRC unnamedParam2); +typedef BOOL MU_wglDeleteContext(HGLRC unnamedParam1); +HGLRC(*mu_wglCreateContext) +(HDC unnamedParam1); +BOOL(*mu_wglMakeCurrent) +(HDC unnamedParam1, HGLRC unnamedParam2); +BOOL(*mu_wglDeleteContext) +(HGLRC unnamedParam1); + +typedef const char *MU_wglGetExtensionsStringARB(HDC hdc); +typedef BOOL MU_wglChoosePixelFormatARB(HDC hdc, const int *piAttribIList, const float *pfAttribFList, UINT nMaxFormats, int *piFormats, UINT *nNumFormats); +typedef HGLRC MU_wglCreateContextAttribsARB(HDC hDC, HGLRC hshareContext, const int *attribList); +typedef BOOL MU_wglSwapIntervalEXT(int interval); +MU_wglChoosePixelFormatARB *wglChoosePixelFormatARB; +MU_wglCreateContextAttribsARB *wglCreateContextAttribsARB; +MU_wglSwapIntervalEXT *wglSwapIntervalEXT; + + #define WGL_DRAW_TO_WINDOW_ARB 0x2001 + #define WGL_SUPPORT_OPENGL_ARB 0x2010 + #define WGL_DOUBLE_BUFFER_ARB 0x2011 + #define WGL_PIXEL_TYPE_ARB 0x2013 + #define WGL_TYPE_RGBA_ARB 0x202B + #define WGL_COLOR_BITS_ARB 0x2014 + #define WGL_DEPTH_BITS_ARB 0x2022 + #define WGL_STENCIL_BITS_ARB 0x2023 + + #define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091 + #define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092 + #define WGL_CONTEXT_PROFILE_MASK_ARB 0x9126 + #define WGL_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001 + #define WGL_CONTEXT_FLAGS_ARB 0x2094 + #define WGL_CONTEXT_DEBUG_BIT_ARB 0x00000001 + + #define WGL_SAMPLE_BUFFERS_ARB 0x2041 + #define WGL_SAMPLES_ARB 0x2042 + +// +// Below loading part largely taken from github gist of Martins Mozeiko +// + +// compares src string with dstlen characters from dst, returns 1 if they are equal, 0 if not +MU_FN int MU__AreStringsEqual(const char *src, const char *dst, size_t dstlen) { + while (*src && dstlen-- && *dst) { + if (*src++ != *dst++) { + return 0; + } + } + + return (dstlen && *src == *dst) || (!dstlen && *src == 0); +} + +MU_FN void *MU_Win32_GLGetWindowProcAddressForGlad(const char *proc) { + MU_Win32 *w32 = (MU_Win32 *)MU_WIN32_ContextPointerForEventHandling->platform; + void *func; + + func = w32->wgl_get_proc_address(proc); + if (!func) { + func = GetProcAddress(w32->opengl32, proc); + } + return func; +} + +MU_FN void MU_WIN32_GetWGLFunctions(MU_Context *mu) { + MU_Win32 *w32 = (MU_Win32 *)mu->platform; + HMODULE opengl32 = LoadLibraryA("opengl32"); + MU_ASSERT(opengl32); + if (opengl32) { + w32->opengl32 = opengl32; + w32->wgl_get_proc_address = (MU_glGetProcAddress *)GetProcAddress(opengl32, "wglGetProcAddress"); + mu->gl_get_proc_address = MU_Win32_GLGetWindowProcAddressForGlad; + mu_wglCreateContext = (MU_wglCreateContext *)GetProcAddress(opengl32, "wglCreateContext"); + mu_wglMakeCurrent = (MU_wglMakeCurrent *)GetProcAddress(opengl32, "wglMakeCurrent"); + mu_wglDeleteContext = (MU_wglDeleteContext *)GetProcAddress(opengl32, "wglDeleteContext"); + } + if (opengl32 == NULL || mu_wglCreateContext == NULL || mu->gl_get_proc_address == NULL || mu_wglMakeCurrent == NULL || mu_wglDeleteContext == NULL) { + MU_ASSERT(!"Failed to load Opengl wgl functions from opengl32.lib"); + return; + } + + // to get WGL functions we need valid GL context, so create dummy window for dummy GL contetx + HWND dummy = CreateWindowExW( + 0, L"STATIC", L"DummyWindow", WS_OVERLAPPED, + CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, + NULL, NULL, NULL, NULL); + MU_ASSERT(dummy && "Failed to create dummy window"); + + HDC dc = GetDC(dummy); + MU_ASSERT(dc && "Failed to get device context for dummy window"); + + PIXELFORMATDESCRIPTOR desc; + MU__ZeroMemory(&desc, sizeof(desc)); + { + desc.nSize = sizeof(desc); + desc.nVersion = 1; + desc.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; + desc.iPixelType = PFD_TYPE_RGBA; + desc.cColorBits = 24; + }; + + int format = ChoosePixelFormat(dc, &desc); + if (!format) { + MU_ASSERT(!"Cannot choose OpenGL pixel format for dummy window!"); + } + + int ok = DescribePixelFormat(dc, format, sizeof(desc), &desc); + MU_ASSERT(ok && "Failed to describe OpenGL pixel format"); + + // reason to create dummy window is that SetPixelFormat can be called only once for the window + if (!SetPixelFormat(dc, format, &desc)) { + MU_ASSERT(!"Cannot set OpenGL pixel format for dummy window!"); + } + + HGLRC rc = mu_wglCreateContext(dc); + MU_ASSERT(rc && "Failed to create OpenGL context for dummy window"); + + ok = mu_wglMakeCurrent(dc, rc); + MU_ASSERT(ok && "Failed to make current OpenGL context for dummy window"); + + // https://www.khronos.org/registry/OpenGL/extensions/ARB/WGL_ARB_extensions_string.txt + MU_wglGetExtensionsStringARB *wglGetExtensionsStringARB = (MU_wglGetExtensionsStringARB *)mu->gl_get_proc_address("wglGetExtensionsStringARB"); + if (!wglGetExtensionsStringARB) { + MU_ASSERT(!"OpenGL does not support WGL_ARB_extensions_string extension!"); + } + + const char *ext = wglGetExtensionsStringARB(dc); + MU_ASSERT(ext && "Failed to get OpenGL WGL extension string"); + + const char *start = ext; + for (;;) { + while (*ext != 0 && *ext != ' ') { + ext++; + } + size_t length = ext - start; + if (MU__AreStringsEqual("WGL_ARB_pixel_format", start, length)) { + // https://www.khronos.org/registry/OpenGL/extensions/ARB/WGL_ARB_pixel_format.txt + wglChoosePixelFormatARB = (MU_wglChoosePixelFormatARB *)mu->gl_get_proc_address("wglChoosePixelFormatARB"); + } + else if (MU__AreStringsEqual("WGL_ARB_create_context", start, length)) { + // https://www.khronos.org/registry/OpenGL/extensions/ARB/WGL_ARB_create_context.txt + wglCreateContextAttribsARB = (MU_wglCreateContextAttribsARB *)mu->gl_get_proc_address("wglCreateContextAttribsARB"); + } + else if (MU__AreStringsEqual("WGL_EXT_swap_control", start, length)) { + // https://www.khronos.org/registry/OpenGL/extensions/EXT/WGL_EXT_swap_control.txt + wglSwapIntervalEXT = (MU_wglSwapIntervalEXT *)mu->gl_get_proc_address("wglSwapIntervalEXT"); + } + + if (*ext == 0) { + break; + } + + ext++; + start = ext; + } + + if (!wglChoosePixelFormatARB || !wglCreateContextAttribsARB || !wglSwapIntervalEXT) { + MU_ASSERT(!"OpenGL does not support required WGL extensions for modern context!"); + } + + mu_wglMakeCurrent(NULL, NULL); + mu_wglDeleteContext(rc); + ReleaseDC(dummy, dc); + DestroyWindow(dummy); + + mu->opengl_initialized = true; +} + +MU_FN void MU_WIN32_TryToInitGLContextForWindow(MU_Context *mu, MU_Win32_Window *w32_window) { + if (mu->opengl_initialized == false && mu->params.enable_opengl) { + MU_WIN32_GetWGLFunctions(mu); + if (mu->opengl_initialized) { + mu->opengl_major = mu->params.opengl_major ? mu->params.opengl_major : 4; + mu->opengl_minor = mu->params.opengl_minor ? mu->params.opengl_minor : 5; + } + } + + if (mu->opengl_initialized) { + // set pixel format for OpenGL context + int attrib[] = + { + WGL_DRAW_TO_WINDOW_ARB, + true, + WGL_SUPPORT_OPENGL_ARB, + true, + WGL_DOUBLE_BUFFER_ARB, + true, + WGL_PIXEL_TYPE_ARB, + WGL_TYPE_RGBA_ARB, + WGL_COLOR_BITS_ARB, + 32, + WGL_DEPTH_BITS_ARB, + 24, + WGL_STENCIL_BITS_ARB, + 8, + + // uncomment for sRGB framebuffer, from WGL_ARB_framebuffer_sRGB extension + // https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_framebuffer_sRGB.txt + // WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB, GL_TRUE, + + // uncomment for multisampeld framebuffer, from WGL_ARB_multisample extension + // https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_multisample.txt + #if MU_GL_ENABLE_MULTISAMPLING + WGL_SAMPLE_BUFFERS_ARB, + 1, + WGL_SAMPLES_ARB, + 4, // 4x MSAA + #endif + + 0, + }; + + int format; + UINT formats; + if (!wglChoosePixelFormatARB(w32_window->handle_dc, attrib, 0, 1, &format, &formats) || formats == 0) { + MU_ASSERT(!"OpenGL does not support required pixel format!"); + } + + PIXELFORMATDESCRIPTOR desc; + MU__ZeroMemory(&desc, sizeof(desc)); + desc.nSize = sizeof(desc); + int ok = DescribePixelFormat(w32_window->handle_dc, format, sizeof(desc), &desc); + MU_ASSERT(ok && "Failed to describe OpenGL pixel format"); + + if (!SetPixelFormat(w32_window->handle_dc, format, &desc)) { + MU_ASSERT(!"Cannot set OpenGL selected pixel format!"); + } + + // create modern OpenGL context + { + int attrib[] = + { + WGL_CONTEXT_MAJOR_VERSION_ARB, + mu->opengl_major, + WGL_CONTEXT_MINOR_VERSION_ARB, + mu->opengl_minor, + WGL_CONTEXT_PROFILE_MASK_ARB, + WGL_CONTEXT_CORE_PROFILE_BIT_ARB, + + #if MU_GL_BUILD_DEBUG + WGL_CONTEXT_FLAGS_ARB, + WGL_CONTEXT_DEBUG_BIT_ARB, + #endif + + 0, + }; + + HGLRC rc = wglCreateContextAttribsARB(w32_window->handle_dc, 0, attrib); + if (!rc) { + MU_ASSERT(!"Cannot create modern OpenGL context! OpenGL version 4.5 not supported?"); + } + + BOOL ok = mu_wglMakeCurrent(w32_window->handle_dc, rc); + MU_ASSERT(ok && "Failed to make current OpenGL context"); + } + } +} + +// +// Sound using WASAPI +// @! Sound: Comeback to it later! I dont really know what I should expect from a sound system +// What actually in reality errors out in WASAPI, what is important when working with sound. +// As such I'm not really currently equiped to make something good / reliable. +// Probably would be nice to work with it a bit more. +// +// Sound params should probably be configurable +// but I dont really understand what I should want to expect +// from this sort of system +// +// Not sure if I should in the future implement some different non threaded api. +// +// +// Below GUID stuff taken from libsoundio +// reference: https://github.com/andrewrk/libsoundio/blob/master/src/wasapi.c +// + +// And some GUID are never implemented (Ignoring the INITGUID define) +MU_PRIVATE_VAR const CLSID MU_CLSID_MMDeviceEnumerator = { + 0xbcde0395, 0xe52f, 0x467c, {0x8e, 0x3d, 0xc4, 0x57, 0x92, 0x91, 0x69, 0x2e} +}; +MU_PRIVATE_VAR const IID MU_IID_IMMDeviceEnumerator = { + // MIDL_INTERFACE("A95664D2-9614-4F35-A746-DE8DB63617E6") + 0xa95664d2, + 0x9614, + 0x4f35, + {0xa7, 0x46, 0xde, 0x8d, 0xb6, 0x36, 0x17, 0xe6} +}; +MU_PRIVATE_VAR const IID MU_IID_IMMNotificationClient = { + // MIDL_INTERFACE("7991EEC9-7E89-4D85-8390-6C703CEC60C0") + 0x7991eec9, + 0x7e89, + 0x4d85, + {0x83, 0x90, 0x6c, 0x70, 0x3c, 0xec, 0x60, 0xc0} +}; +MU_PRIVATE_VAR const IID MU_IID_IAudioClient = { + // MIDL_INTERFACE("1CB9AD4C-DBFA-4c32-B178-C2F568A703B2") + 0x1cb9ad4c, + 0xdbfa, + 0x4c32, + {0xb1, 0x78, 0xc2, 0xf5, 0x68, 0xa7, 0x03, 0xb2} +}; +MU_PRIVATE_VAR const IID MU_IID_IAudioRenderClient = { + // MIDL_INTERFACE("F294ACFC-3146-4483-A7BF-ADDCA7C260E2") + 0xf294acfc, + 0x3146, + 0x4483, + {0xa7, 0xbf, 0xad, 0xdc, 0xa7, 0xc2, 0x60, 0xe2} +}; +MU_PRIVATE_VAR const IID MU_IID_IAudioSessionControl = { + // MIDL_INTERFACE("F4B1A599-7266-4319-A8CA-E70ACB11E8CD") + 0xf4b1a599, + 0x7266, + 0x4319, + {0xa8, 0xca, 0xe7, 0x0a, 0xcb, 0x11, 0xe8, 0xcd} +}; +MU_PRIVATE_VAR const IID MU_IID_IAudioSessionEvents = { + // MIDL_INTERFACE("24918ACC-64B3-37C1-8CA9-74A66E9957A8") + 0x24918acc, + 0x64b3, + 0x37c1, + {0x8c, 0xa9, 0x74, 0xa6, 0x6e, 0x99, 0x57, 0xa8} +}; +MU_PRIVATE_VAR const IID MU_IID_IMMEndpoint = { + // MIDL_INTERFACE("1BE09788-6894-4089-8586-9A2A6C265AC5") + 0x1be09788, + 0x6894, + 0x4089, + {0x85, 0x86, 0x9a, 0x2a, 0x6c, 0x26, 0x5a, 0xc5} +}; +MU_PRIVATE_VAR const IID MU_IID_IAudioClockAdjustment = { + // MIDL_INTERFACE("f6e4c0a0-46d9-4fb8-be21-57a3ef2b626c") + 0xf6e4c0a0, + 0x46d9, + 0x4fb8, + {0xbe, 0x21, 0x57, 0xa3, 0xef, 0x2b, 0x62, 0x6c} +}; +MU_PRIVATE_VAR const IID MU_IID_IAudioCaptureClient = { + // MIDL_INTERFACE("C8ADBD64-E71E-48a0-A4DE-185C395CD317") + 0xc8adbd64, + 0xe71e, + 0x48a0, + {0xa4, 0xde, 0x18, 0x5c, 0x39, 0x5c, 0xd3, 0x17} +}; +MU_PRIVATE_VAR const IID MU_IID_ISimpleAudioVolume = { + // MIDL_INTERFACE("87ce5498-68d6-44e5-9215-6da47ef883d8") + 0x87ce5498, + 0x68d6, + 0x44e5, + {0x92, 0x15, 0x6d, 0xa4, 0x7e, 0xf8, 0x83, 0xd8} +}; + + #ifdef __cplusplus + // In C++ mode, IsEqualGUID() takes its arguments by reference + #define IS_EQUAL_GUID(a, b) IsEqualGUID(*(a), *(b)) + #define IS_EQUAL_IID(a, b) IsEqualIID((a), *(b)) + + // And some constants are passed by reference + #define MU_IID_IAUDIOCLIENT (MU_IID_IAudioClient) + #define MU_IID_IMMENDPOINT (MU_IID_IMMEndpoint) + #define MU_IID_IAUDIOCLOCKADJUSTMENT (MU_IID_IAudioClockAdjustment) + #define MU_IID_IAUDIOSESSIONCONTROL (MU_IID_IAudioSessionControl) + #define MU_IID_IAUDIORENDERCLIENT (MU_IID_IAudioRenderClient) + #define MU_IID_IMMDEVICEENUMERATOR (MU_IID_IMMDeviceEnumerator) + #define MU_IID_IAUDIOCAPTURECLIENT (MU_IID_IAudioCaptureClient) + #define MU_IID_ISIMPLEAUDIOVOLUME (MU_IID_ISimpleAudioVolume) + #define MU_CLSID_MMDEVICEENUMERATOR (MU_CLSID_MMDeviceEnumerator) + #define MU_PKEY_DEVICE_FRIENDLYNAME (PKEY_Device_FriendlyName) + #define MU_PKEY_AUDIOENGINE_DEVICEFORMAT (PKEY_AudioEngine_DeviceFormat) + + #else + #define IS_EQUAL_GUID(a, b) IsEqualGUID((a), (b)) + #define IS_EQUAL_IID(a, b) IsEqualIID((a), (b)) + + #define MU_IID_IAUDIOCLIENT (&MU_IID_IAudioClient) + #define MU_IID_IMMENDPOINT (&MU_IID_IMMEndpoint) + #define MU_PKEY_DEVICE_FRIENDLYNAME (&PKEY_Device_FriendlyName) + #define MU_PKEY_AUDIOENGINE_DEVICEFORMAT (&PKEY_AudioEngine_DeviceFormat) + #define MU_CLSID_MMDEVICEENUMERATOR (&MU_CLSID_MMDeviceEnumerator) + #define MU_IID_IAUDIOCLOCKADJUSTMENT (&MU_IID_IAudioClockAdjustment) + #define MU_IID_IAUDIOSESSIONCONTROL (&MU_IID_IAudioSessionControl) + #define MU_IID_IAUDIORENDERCLIENT (&MU_IID_IAudioRenderClient) + #define MU_IID_IMMDEVICEENUMERATOR (&MU_IID_IMMDeviceEnumerator) + #define MU_IID_IAUDIOCAPTURECLIENT (&MU_IID_IAudioCaptureClient) + #define MU_IID_ISIMPLEAUDIOVOLUME (&MU_IID_ISimpleAudioVolume) + #endif + + // Number of REFERENCE_TIME units per second + // One unit is equal to 100 nano seconds + #define MU_REF_TIMES_PER_SECOND 10000000 + #define MU_REF_TIMES_PER_MSECOND 10000 + +// Empty functions(stubs) which are used when library fails to load +static HRESULT CoCreateInstanceStub(REFCLSID rclsid, LPUNKNOWN *pUnkOuter, DWORD dwClsContext, REFIID riid, LPVOID *ppv) { + (void)(rclsid); + (void)(pUnkOuter); + (void)(dwClsContext); + (void)(riid); + (void)(ppv); + return S_FALSE; +} + +static HRESULT CoInitializeExStub(LPVOID pvReserved, DWORD dwCoInit) { + (void)(pvReserved); + (void)(dwCoInit); + return S_FALSE; +} + +MU_FN void MU_WIN32_DeinitSound(MU_Context *mu) { + MU_Win32 *w32 = (MU_Win32 *)mu->platform; + if (w32->audio_client) w32->audio_client->lpVtbl->Stop(w32->audio_client); + if (w32->audio_client) w32->audio_client->lpVtbl->Release(w32->audio_client); + if (w32->device_enum) w32->device_enum->lpVtbl->Release(w32->device_enum); + if (w32->device) w32->device->lpVtbl->Release(w32->device); + if (w32->audio_render_client) w32->audio_render_client->lpVtbl->Release(w32->audio_render_client); + mu->sound.initialized = false; +} + +// Load COM Library functions dynamically, +// this way sound is not necessary to run the game +MU_FN void MU_WIN32_LoadCOM(MU_Context *mu) { + MU_Win32 *w32 = (MU_Win32 *)mu->platform; + + HMODULE ole32_lib = LoadLibraryA("ole32.dll"); + if (ole32_lib) { + w32->CoCreateInstanceFunctionPointer = (CoCreateInstanceFunction *)GetProcAddress(ole32_lib, "CoCreateInstance"); + w32->CoInitializeExFunctionPointer = (CoInitializeExFunction *)GetProcAddress(ole32_lib, "CoInitializeEx"); + mu->sound.initialized = true; + } + + if (ole32_lib == 0 || w32->CoCreateInstanceFunctionPointer == 0 || w32->CoInitializeExFunctionPointer == 0) { + w32->CoCreateInstanceFunctionPointer = CoCreateInstanceStub; + w32->CoInitializeExFunctionPointer = CoInitializeExStub; + mu->sound.initialized = false; + } +} + +MU_FN DWORD MU_WIN32_SoundThread(void *parameter) { + MU_Context *mu = (MU_Context *)parameter; + MU_Win32 *w32 = (MU_Win32 *)mu->platform; + + HANDLE thread_handle = GetCurrentThread(); + SetThreadPriority(thread_handle, THREAD_PRIORITY_HIGHEST); + HANDLE buffer_ready_event = CreateEvent(0, 0, 0, 0); + if (!buffer_ready_event) { + MU_ASSERT(!"Sound thread failed"); + goto error_cleanup; + } + if (FAILED(IAudioClient_SetEventHandle(w32->audio_client, buffer_ready_event))) { + MU_ASSERT(!"Sound thread failed"); + goto error_cleanup; + } + + if (FAILED(IAudioClient_Start(w32->audio_client))) { + MU_ASSERT(!"Sound thread failed"); + goto error_cleanup; + } + for (;;) { + if (WaitForSingleObject(buffer_ready_event, INFINITE) != WAIT_OBJECT_0) { + MU_ASSERT(!"Sound thread failed"); + goto error_cleanup; + } + uint32_t padding_frame_count; + if (FAILED(IAudioClient_GetCurrentPadding(w32->audio_client, &padding_frame_count))) { + MU_ASSERT(!"Sound thread failed"); + goto error_cleanup; + } + uint32_t *samples; + uint32_t fill_frame_count = w32->buffer_frame_count - padding_frame_count; + if (FAILED(IAudioRenderClient_GetBuffer(w32->audio_render_client, fill_frame_count, (BYTE **)&samples))) { + MU_ASSERT(!"Sound thread failed"); + goto error_cleanup; + } + + // Call user callback + uint32_t sample_count_to_fill = fill_frame_count * mu->sound.number_of_channels; + mu->sound.callback((MU_Context *)mu, (uint16_t *)samples, sample_count_to_fill); + + if (FAILED(IAudioRenderClient_ReleaseBuffer(w32->audio_render_client, fill_frame_count, 0))) { + MU_ASSERT(!"Sound thread failed"); + goto error_cleanup; + } + } + return 0; +error_cleanup: + MU_WIN32_DeinitSound(mu); + return -1; +} + +MU_FN void MU_WIN32_InitWasapi(MU_Context *mu) { + REFERENCE_TIME requested_buffer_duration = MU_REF_TIMES_PER_MSECOND * 40; + MU_Win32 *w32 = (MU_Win32 *)mu->platform; + + MU_WIN32_LoadCOM(mu); + MU_ASSERT(mu->sound.initialized); + if (mu->sound.initialized == false) { + return; + } + + mu->sound.bytes_per_sample = 2; + mu->sound.number_of_channels = 2; + mu->sound.samples_per_second = 44100; + + HANDLE thread_handle; + + HRESULT hr = w32->CoInitializeExFunctionPointer(0, COINITBASE_MULTITHREADED); + if (FAILED(hr)) { + MU_ASSERT(!"Failed to initialize sound"); + goto failure_path; + } + + hr = w32->CoCreateInstanceFunctionPointer(MU_CLSID_MMDEVICEENUMERATOR, NULL, CLSCTX_ALL, MU_IID_IMMDEVICEENUMERATOR, (void **)&w32->device_enum); + if (FAILED(hr)) { + MU_ASSERT(!"Failed to initialize sound"); + goto failure_path; + } + + hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(w32->device_enum, eRender, eMultimedia, &w32->device); + if (FAILED(hr)) { + MU_ASSERT(!"Failed to initialize sound"); + goto failure_path; + } + + hr = IMMDevice_Activate(w32->device, MU_IID_IAUDIOCLIENT, CLSCTX_ALL, NULL, (void **)&w32->audio_client); + if (FAILED(hr)) { + MU_ASSERT(!"Failed to initialize sound"); + goto failure_path; + } + + WAVEFORMATEX fmt; + { + MU__ZeroMemory(&fmt, sizeof(fmt)); + fmt.wFormatTag = WAVE_FORMAT_PCM; + fmt.nChannels = mu->sound.number_of_channels; + fmt.nSamplesPerSec = mu->sound.samples_per_second; + fmt.wBitsPerSample = mu->sound.bytes_per_sample * 8; + fmt.nBlockAlign = fmt.nChannels * fmt.wBitsPerSample / 8; + fmt.nAvgBytesPerSec = fmt.nSamplesPerSec * fmt.nBlockAlign; + } + + hr = IAudioClient_Initialize( + w32->audio_client, AUDCLNT_SHAREMODE_SHARED, + AUDCLNT_STREAMFLAGS_EVENTCALLBACK | AUDCLNT_STREAMFLAGS_RATEADJUST | + AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM | AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY, + requested_buffer_duration, 0, &fmt, 0); + if (FAILED(hr)) { + MU_ASSERT(!"Failed to initialize sound"); + goto failure_path; + } + + hr = IAudioClient_GetService(w32->audio_client, MU_IID_IAUDIORENDERCLIENT, (void **)&w32->audio_render_client); + if (FAILED(hr)) { + MU_ASSERT(!"Failed to initialize sound"); + goto failure_path; + } + + hr = IAudioClient_GetBufferSize(w32->audio_client, &w32->buffer_frame_count); + if (FAILED(hr)) { + MU_ASSERT(!"Failed to initialize sound"); + goto failure_path; + } + + thread_handle = CreateThread(0, 0, MU_WIN32_SoundThread, mu, 0, 0); + if (thread_handle == INVALID_HANDLE_VALUE) { + MU_ASSERT(!"Failed to create a sound thread"); + goto failure_path; + } + + return; +failure_path: + MU_WIN32_DeinitSound(mu); +} + +#endif // _WIN32 +#endif // MU_IMPLEMENTATION \ No newline at end of file diff --git a/preproc_env.h b/preproc_env.h new file mode 100644 index 0000000..96ccbbe --- /dev/null +++ b/preproc_env.h @@ -0,0 +1,90 @@ +#ifndef PREPROC_ENV_HEADER +#define PREPROC_ENV_HEADER + +#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 COMPILER_CLANG +// #pragma clang diagnostic push +// #pragma clang diagnostic ignored "-Wmicrosoft-enum-forward-reference" +// #endif + +#endif // PREPROC_ENV_HEADER \ No newline at end of file diff --git a/regex.h b/regex.h new file mode 100644 index 0000000..e11d685 --- /dev/null +++ b/regex.h @@ -0,0 +1,656 @@ +#ifndef RE_HEADER +#define RE_HEADER +#include +#include + +#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 enum RE_MatchKind RE_MatchKind; +typedef struct RE_Regex RE_Regex; +typedef struct RE_Match RE_Match; + +/* @todo +Add \W \D \S oppsites +*/ + +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, +}; + +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); + +#endif // RE_HEADER +#ifdef RE_IMPLEMENTATION +#ifndef RE_ASSERT + #include + #define RE_ASSERT(x) assert(x) +#endif + +#ifndef RE_STRICT_ASSERT + #define RE_STRICT_ASSERT RE_ASSERT +#endif + +#ifndef RE_MemoryZero + #include + #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; +} + +#endif \ No newline at end of file diff --git a/stb_sprintf.h b/stb_sprintf.h new file mode 100644 index 0000000..6ae1c48 --- /dev/null +++ b/stb_sprintf.h @@ -0,0 +1,1931 @@ +// stb_sprintf - v1.10 - public domain snprintf() implementation +// originally by Jeff Roberts / RAD Game Tools, 2015/10/20 +// http://github.com/nothings/stb +// +// allowed types: sc uidBboXx p AaGgEef n +// lengths : hh h ll j z t I64 I32 I +// +// Contributors: +// Fabian "ryg" Giesen (reformatting) +// github:aganm (attribute format) +// +// Contributors (bugfixes): +// github:d26435 +// github:trex78 +// github:account-login +// Jari Komppa (SI suffixes) +// Rohit Nirmal +// Marcin Wojdyr +// Leonard Ritter +// Stefano Zanotti +// Adam Allison +// Arvid Gerstmann +// Markus Kolb +// +// LICENSE: +// +// See end of file for license information. + +#ifndef STB_SPRINTF_H_INCLUDE + #define STB_SPRINTF_H_INCLUDE + #include +/* +Single file sprintf replacement. + +Originally written by Jeff Roberts at RAD Game Tools - 2015/10/20. +Hereby placed in public domain. + +This is a full sprintf replacement that supports everything that +the C runtime sprintfs support, including float/double, 64-bit integers, +hex floats, field parameters (%*.*d stuff), length reads backs, etc. + +Why would you need this if sprintf already exists? Well, first off, +it's *much* faster (see below). It's also much smaller than the CRT +versions code-space-wise. We've also added some simple improvements +that are super handy (commas in thousands, callbacks at buffer full, +for example). Finally, the format strings for MSVC and GCC differ +for 64-bit integers (among other small things), so this lets you use +the same format strings in cross platform code. + +It uses the standard single file trick of being both the header file +and the source itself. If you just include it normally, you just get +the header file function definitions. To get the code, you include +it from a C or C++ file and define STB_SPRINTF_IMPLEMENTATION first. + +It only uses va_args macros from the C runtime to do it's work. It +does cast doubles to S64s and shifts and divides U64s, which does +drag in CRT code on most platforms. + +It compiles to roughly 8K with float support, and 4K without. +As a comparison, when using MSVC static libs, calling sprintf drags +in 16K. + +API: +==== +int stbsp_sprintf( char * buf, char const * fmt, ... ) +int stbsp_snprintf( char * buf, int count, char const * fmt, ... ) + Convert an arg list into a buffer. stbsp_snprintf always returns + a zero-terminated string (unlike regular snprintf). + +int stbsp_vsprintf( char * buf, char const * fmt, va_list va ) +int stbsp_vsnprintf( char * buf, int count, char const * fmt, va_list va ) + Convert a va_list arg list into a buffer. stbsp_vsnprintf always returns + a zero-terminated string (unlike regular snprintf). + +int stbsp_vsprintfcb( STBSP_SPRINTFCB * callback, void * user, char * buf, char const * fmt, va_list va ) + typedef char * STBSP_SPRINTFCB( char const * buf, void * user, int len ); + Convert into a buffer, calling back every STB_SPRINTF_MIN chars. + Your callback can then copy the chars out, print them or whatever. + This function is actually the workhorse for everything else. + The buffer you pass in must hold at least STB_SPRINTF_MIN characters. + // you return the next buffer to use or 0 to stop converting + +void stbsp_set_separators( char comma, char period ) + Set the comma and period characters to use. + +FLOATS/DOUBLES: +=============== +This code uses a internal float->ascii conversion method that uses +doubles with error correction (double-doubles, for ~105 bits of +precision). This conversion is round-trip perfect - that is, an atof +of the values output here will give you the bit-exact double back. + +One difference is that our insignificant digits will be different than +with MSVC or GCC (but they don't match each other either). We also +don't attempt to find the minimum length matching float (pre-MSVC15 +doesn't either). + +If you don't need float or doubles at all, define STB_SPRINTF_NOFLOAT +and you'll save 4K of code space. + +64-BIT INTS: +============ +This library also supports 64-bit integers and you can use MSVC style or +GCC style indicators (%I64d or %lld). It supports the C99 specifiers +for size_t and ptr_diff_t (%jd %zd) as well. + +EXTRAS: +======= +Like some GCCs, for integers and floats, you can use a ' (single quote) +specifier and commas will be inserted on the thousands: "%'d" on 12345 +would print 12,345. + +For integers and floats, you can use a "$" specifier and the number +will be converted to float and then divided to get kilo, mega, giga or +tera and then printed, so "%$d" 1000 is "1.0 k", "%$.2d" 2536000 is +"2.53 M", etc. For byte values, use two $:s, like "%$$d" to turn +2536000 to "2.42 Mi". If you prefer JEDEC suffixes to SI ones, use three +$:s: "%$$$d" -> "2.42 M". To remove the space between the number and the +suffix, add "_" specifier: "%_$d" -> "2.53M". + +In addition to octal and hexadecimal conversions, you can print +integers in binary: "%b" for 256 would print 100. + +PERFORMANCE vs MSVC 2008 32-/64-bit (GCC is even slower than MSVC): +=================================================================== +"%d" across all 32-bit ints (4.8x/4.0x faster than 32-/64-bit MSVC) +"%24d" across all 32-bit ints (4.5x/4.2x faster) +"%x" across all 32-bit ints (4.5x/3.8x faster) +"%08x" across all 32-bit ints (4.3x/3.8x faster) +"%f" across e-10 to e+10 floats (7.3x/6.0x faster) +"%e" across e-10 to e+10 floats (8.1x/6.0x faster) +"%g" across e-10 to e+10 floats (10.0x/7.1x faster) +"%f" for values near e-300 (7.9x/6.5x faster) +"%f" for values near e+300 (10.0x/9.1x faster) +"%e" for values near e-300 (10.1x/7.0x faster) +"%e" for values near e+300 (9.2x/6.0x faster) +"%.320f" for values near e-300 (12.6x/11.2x faster) +"%a" for random values (8.6x/4.3x faster) +"%I64d" for 64-bits with 32-bit values (4.8x/3.4x faster) +"%I64d" for 64-bits > 32-bit values (4.9x/5.5x faster) +"%s%s%s" for 64 char strings (7.1x/7.3x faster) +"...512 char string..." ( 35.0x/32.5x faster!) +*/ + + #if defined(__clang__) + #if defined(__has_feature) && defined(__has_attribute) + #if __has_feature(address_sanitizer) + #if __has_attribute(__no_sanitize__) + #define STBSP__ASAN __attribute__((__no_sanitize__("address"))) + #elif __has_attribute(__no_sanitize_address__) + #define STBSP__ASAN __attribute__((__no_sanitize_address__)) + #elif __has_attribute(__no_address_safety_analysis__) + #define STBSP__ASAN __attribute__((__no_address_safety_analysis__)) + #endif + #endif + #endif + #elif defined(__GNUC__) && (__GNUC__ >= 5 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)) + #if defined(__SANITIZE_ADDRESS__) && __SANITIZE_ADDRESS__ + #define STBSP__ASAN __attribute__((__no_sanitize_address__)) + #endif + #endif + + #ifndef STBSP__ASAN + #define STBSP__ASAN + #endif + + #ifdef STB_SPRINTF_STATIC + #define STBSP__PUBLICDEC static + #define STBSP__PUBLICDEF static STBSP__ASAN + #else + #ifdef __cplusplus + #define STBSP__PUBLICDEC extern "C" + #define STBSP__PUBLICDEF extern "C" STBSP__ASAN + #else + #define STBSP__PUBLICDEC extern + #define STBSP__PUBLICDEF STBSP__ASAN + #endif + #endif + + #if defined(__has_attribute) + #if __has_attribute(format) + #define STBSP__ATTRIBUTE_FORMAT(fmt, va) __attribute__((format(printf, fmt, va))) + #endif + #endif + + #ifndef STBSP__ATTRIBUTE_FORMAT + #define STBSP__ATTRIBUTE_FORMAT(fmt, va) + #endif + + #ifdef _MSC_VER + #define STBSP__NOTUSED(v) (void)(v) + #else + #define STBSP__NOTUSED(v) (void)sizeof(v) + #endif + + #include // for va_arg(), va_list() + #include // size_t, ptrdiff_t + + #ifndef STB_SPRINTF_MIN + #define STB_SPRINTF_MIN 512 // how many characters per callback + #endif +typedef char *STBSP_SPRINTFCB(const char *buf, void *user, int len); + + #ifndef STB_SPRINTF_DECORATE + #define STB_SPRINTF_DECORATE(name) stbsp_##name // define this before including if you want to change the names + #endif + +STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(vsprintf)(char *buf, char const *fmt, va_list va); +STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(vsnprintf)(char *buf, int count, char const *fmt, va_list va); +STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(sprintf)(char *buf, char const *fmt, ...) STBSP__ATTRIBUTE_FORMAT(2, 3); +STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(snprintf)(char *buf, int count, char const *fmt, ...) STBSP__ATTRIBUTE_FORMAT(3, 4); + +STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(vsprintfcb)(STBSP_SPRINTFCB *callback, void *user, char *buf, char const *fmt, va_list va); +STBSP__PUBLICDEC void STB_SPRINTF_DECORATE(set_separators)(char comma, char period); + +#endif // STB_SPRINTF_H_INCLUDE + +#ifdef STB_SPRINTF_IMPLEMENTATION + + #define stbsp__uint32 unsigned int + #define stbsp__int32 signed int + + #ifdef _MSC_VER + #define stbsp__uint64 unsigned __int64 + #define stbsp__int64 signed __int64 + #else + #define stbsp__uint64 unsigned long long + #define stbsp__int64 signed long long + #endif + #define stbsp__uint16 unsigned short + + #ifndef stbsp__uintptr + #if defined(__ppc64__) || defined(__powerpc64__) || defined(__aarch64__) || defined(_M_X64) || defined(__x86_64__) || defined(__x86_64) || defined(__s390x__) + #define stbsp__uintptr stbsp__uint64 + #else + #define stbsp__uintptr stbsp__uint32 + #endif + #endif + + #ifndef STB_SPRINTF_MSVC_MODE // used for MSVC2013 and earlier (MSVC2015 matches GCC) + #if defined(_MSC_VER) && (_MSC_VER < 1900) + #define STB_SPRINTF_MSVC_MODE + #endif + #endif + + #ifdef STB_SPRINTF_NOUNALIGNED // define this before inclusion to force stbsp_sprintf to always use aligned accesses + #define STBSP__UNALIGNED(code) + #else + #define STBSP__UNALIGNED(code) code + #endif + + #ifndef STB_SPRINTF_NOFLOAT +// internal float utility functions +static stbsp__int32 stbsp__real_to_str(char const **start, stbsp__uint32 *len, char *out, stbsp__int32 *decimal_pos, double value, stbsp__uint32 frac_digits); +static stbsp__int32 stbsp__real_to_parts(stbsp__int64 *bits, stbsp__int32 *expo, double value); + #define STBSP__SPECIAL 0x7000 + #endif + +static char stbsp__period = '.'; +static char stbsp__comma = ','; +static struct +{ + short temp; // force next field to be 2-byte aligned + char pair[201]; +} stbsp__digitpair = + { + 0, + "00010203040506070809101112131415161718192021222324" + "25262728293031323334353637383940414243444546474849" + "50515253545556575859606162636465666768697071727374" + "75767778798081828384858687888990919293949596979899"}; + +STBSP__PUBLICDEF void STB_SPRINTF_DECORATE(set_separators)(char pcomma, char pperiod) { + stbsp__period = pperiod; + stbsp__comma = pcomma; +} + + #define STBSP__LEFTJUST 1 + #define STBSP__LEADINGPLUS 2 + #define STBSP__LEADINGSPACE 4 + #define STBSP__LEADING_0X 8 + #define STBSP__LEADINGZERO 16 + #define STBSP__INTMAX 32 + #define STBSP__TRIPLET_COMMA 64 + #define STBSP__NEGATIVE 128 + #define STBSP__METRIC_SUFFIX 256 + #define STBSP__HALFWIDTH 512 + #define STBSP__METRIC_NOSPACE 1024 + #define STBSP__METRIC_1024 2048 + #define STBSP__METRIC_JEDEC 4096 + +static void stbsp__lead_sign(stbsp__uint32 fl, char *sign) { + sign[0] = 0; + if (fl & STBSP__NEGATIVE) { + sign[0] = 1; + sign[1] = '-'; + } + else if (fl & STBSP__LEADINGSPACE) { + sign[0] = 1; + sign[1] = ' '; + } + else if (fl & STBSP__LEADINGPLUS) { + sign[0] = 1; + sign[1] = '+'; + } +} + +static STBSP__ASAN stbsp__uint32 stbsp__strlen_limited(char const *s, stbsp__uint32 limit) { + char const *sn = s; + + // get up to 4-byte alignment + for (;;) { + if (((stbsp__uintptr)sn & 3) == 0) + break; + + if (!limit || *sn == 0) + return (stbsp__uint32)(sn - s); + + ++sn; + --limit; + } + + // scan over 4 bytes at a time to find terminating 0 + // this will intentionally scan up to 3 bytes past the end of buffers, + // but becase it works 4B aligned, it will never cross page boundaries + // (hence the STBSP__ASAN markup; the over-read here is intentional + // and harmless) + while (limit >= 4) { + stbsp__uint32 v = *(stbsp__uint32 *)sn; + // bit hack to find if there's a 0 byte in there + if ((v - 0x01010101) & (~v) & 0x80808080UL) + break; + + sn += 4; + limit -= 4; + } + + // handle the last few characters to find actual size + while (limit && *sn) { + ++sn; + --limit; + } + + return (stbsp__uint32)(sn - s); +} + +STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(vsprintfcb)(STBSP_SPRINTFCB *callback, void *user, char *buf, char const *fmt, va_list va) { + static char hex[] = "0123456789abcdefxp"; + static char hexu[] = "0123456789ABCDEFXP"; + char *bf; + char const *f; + int tlen = 0; + + bf = buf; + f = fmt; + for (;;) { + stbsp__int32 fw, pr, tz; + stbsp__uint32 fl; + + // macros for the callback buffer stuff + #define stbsp__chk_cb_bufL(bytes) \ + { \ + int len = (int)(bf - buf); \ + if ((len + (bytes)) >= STB_SPRINTF_MIN) { \ + tlen += len; \ + if (0 == (bf = buf = callback(buf, user, len))) \ + goto done; \ + } \ + } + #define stbsp__chk_cb_buf(bytes) \ + { \ + if (callback) { \ + stbsp__chk_cb_bufL(bytes); \ + } \ + } + #define stbsp__flush_cb() \ + { \ + stbsp__chk_cb_bufL(STB_SPRINTF_MIN - 1); \ + } // flush if there is even one byte in the buffer + #define stbsp__cb_buf_clamp(cl, v) \ + cl = v; \ + if (callback) { \ + int lg = STB_SPRINTF_MIN - (int)(bf - buf); \ + if (cl > lg) \ + cl = lg; \ + } + + // fast copy everything up to the next % (or end of string) + for (;;) { + while (((stbsp__uintptr)f) & 3) { + schk1: + if (f[0] == '%') + goto scandd; + schk2: + if (f[0] == 0) + goto endfmt; + stbsp__chk_cb_buf(1); + *bf++ = f[0]; + ++f; + } + for (;;) { + // Check if the next 4 bytes contain %(0x25) or end of string. + // Using the 'hasless' trick: + // https://graphics.stanford.edu/~seander/bithacks.html#HasLessInWord + stbsp__uint32 v, c; + v = *(stbsp__uint32 *)f; + c = (~v) & 0x80808080; + if (((v ^ 0x25252525) - 0x01010101) & c) + goto schk1; + if ((v - 0x01010101) & c) + goto schk2; + if (callback) + if ((STB_SPRINTF_MIN - (int)(bf - buf)) < 4) + goto schk1; + #ifdef STB_SPRINTF_NOUNALIGNED + if (((stbsp__uintptr)bf) & 3) { + bf[0] = f[0]; + bf[1] = f[1]; + bf[2] = f[2]; + bf[3] = f[3]; + } + else + #endif + { + *(stbsp__uint32 *)bf = v; + } + bf += 4; + f += 4; + } + } + scandd: + + ++f; + + // ok, we have a percent, read the modifiers first + fw = 0; + pr = -1; + fl = 0; + tz = 0; + + // flags + for (;;) { + switch (f[0]) { + // if we have left justify + case '-': + fl |= STBSP__LEFTJUST; + ++f; + continue; + // if we have leading plus + case '+': + fl |= STBSP__LEADINGPLUS; + ++f; + continue; + // if we have leading space + case ' ': + fl |= STBSP__LEADINGSPACE; + ++f; + continue; + // if we have leading 0x + case '#': + fl |= STBSP__LEADING_0X; + ++f; + continue; + // if we have thousand commas + case '\'': + fl |= STBSP__TRIPLET_COMMA; + ++f; + continue; + // if we have kilo marker (none->kilo->kibi->jedec) + case '$': + if (fl & STBSP__METRIC_SUFFIX) { + if (fl & STBSP__METRIC_1024) { + fl |= STBSP__METRIC_JEDEC; + } + else { + fl |= STBSP__METRIC_1024; + } + } + else { + fl |= STBSP__METRIC_SUFFIX; + } + ++f; + continue; + // if we don't want space between metric suffix and number + case '_': + fl |= STBSP__METRIC_NOSPACE; + ++f; + continue; + // if we have leading zero + case '0': + fl |= STBSP__LEADINGZERO; + ++f; + goto flags_done; + default: goto flags_done; + } + } + flags_done: + + // get the field width + if (f[0] == '*') { + fw = va_arg(va, stbsp__uint32); + ++f; + } + else { + while ((f[0] >= '0') && (f[0] <= '9')) { + fw = fw * 10 + f[0] - '0'; + f++; + } + } + // get the precision + if (f[0] == '.') { + ++f; + if (f[0] == '*') { + pr = va_arg(va, stbsp__uint32); + ++f; + } + else { + pr = 0; + while ((f[0] >= '0') && (f[0] <= '9')) { + pr = pr * 10 + f[0] - '0'; + f++; + } + } + } + + // handle integer size overrides + switch (f[0]) { + // are we halfwidth? + case 'h': + fl |= STBSP__HALFWIDTH; + ++f; + if (f[0] == 'h') + ++f; // QUARTERWIDTH + break; + // are we 64-bit (unix style) + case 'l': + fl |= ((sizeof(long) == 8) ? STBSP__INTMAX : 0); + ++f; + if (f[0] == 'l') { + fl |= STBSP__INTMAX; + ++f; + } + break; + // are we 64-bit on intmax? (c99) + case 'j': + fl |= (sizeof(size_t) == 8) ? STBSP__INTMAX : 0; + ++f; + break; + // are we 64-bit on size_t or ptrdiff_t? (c99) + case 'z': + fl |= (sizeof(ptrdiff_t) == 8) ? STBSP__INTMAX : 0; + ++f; + break; + case 't': + fl |= (sizeof(ptrdiff_t) == 8) ? STBSP__INTMAX : 0; + ++f; + break; + // are we 64-bit (msft style) + case 'I': + if ((f[1] == '6') && (f[2] == '4')) { + fl |= STBSP__INTMAX; + f += 3; + } + else if ((f[1] == '3') && (f[2] == '2')) { + f += 3; + } + else { + fl |= ((sizeof(void *) == 8) ? STBSP__INTMAX : 0); + ++f; + } + break; + default: break; + } + + // handle each replacement + switch (f[0]) { + #define STBSP__NUMSZ 512 // big enough for e308 (with commas) or e-307 + char num[STBSP__NUMSZ]; + char lead[8]; + char tail[8]; + char *s; + char const *h; + stbsp__uint32 l, n, cs; + stbsp__uint64 n64; + #ifndef STB_SPRINTF_NOFLOAT + double fv; + #endif + stbsp__int32 dp; + char const *sn; + struct STB_STRING { + char *str; + int64_t len; + }; + struct STB_STRING str; + + case 'Q': + str = va_arg(va, struct STB_STRING); + if (str.str == 0 && str.len != 0) { + str.str = "null"; + str.len = 4; + } + pr = (int)str.len; + s = (char *)str.str; + l = stbsp__strlen_limited(s, (pr >= 0) ? pr : ~0u); + lead[0] = 0; + tail[0] = 0; + pr = 0; + dp = 0; + cs = 0; + goto scopy; + + case 's': + // get the string + s = va_arg(va, char *); + if (s == 0) + s = (char *)"null"; + // get the length, limited to desired precision + // always limit to ~0u chars since our counts are 32b + l = stbsp__strlen_limited(s, (pr >= 0) ? pr : ~0u); + lead[0] = 0; + tail[0] = 0; + pr = 0; + dp = 0; + cs = 0; + // copy the string in + goto scopy; + + case 'c': // char + // get the character + s = num + STBSP__NUMSZ - 1; + *s = (char)va_arg(va, int); + l = 1; + lead[0] = 0; + tail[0] = 0; + pr = 0; + dp = 0; + cs = 0; + goto scopy; + + case 'n': // weird write-bytes specifier + { + int *d = va_arg(va, int *); + *d = tlen + (int)(bf - buf); + } break; + + #ifdef STB_SPRINTF_NOFLOAT + case 'A': // float + case 'a': // hex float + case 'G': // float + case 'g': // float + case 'E': // float + case 'e': // float + case 'f': // float + va_arg(va, double); // eat it + s = (char *)"No float"; + l = 8; + lead[0] = 0; + tail[0] = 0; + pr = 0; + cs = 0; + STBSP__NOTUSED(dp); + goto scopy; + #else + case 'A': // hex float + case 'a': // hex float + h = (f[0] == 'A') ? hexu : hex; + fv = va_arg(va, double); + if (pr == -1) + pr = 6; // default is 6 + // read the double into a string + if (stbsp__real_to_parts((stbsp__int64 *)&n64, &dp, fv)) + fl |= STBSP__NEGATIVE; + + s = num + 64; + + stbsp__lead_sign(fl, lead); + + if (dp == -1023) + dp = (n64) ? -1022 : 0; + else + n64 |= (((stbsp__uint64)1) << 52); + n64 <<= (64 - 56); + if (pr < 15) + n64 += ((((stbsp__uint64)8) << 56) >> (pr * 4)); + // add leading chars + + #ifdef STB_SPRINTF_MSVC_MODE + *s++ = '0'; + *s++ = 'x'; + #else + lead[1 + lead[0]] = '0'; + lead[2 + lead[0]] = 'x'; + lead[0] += 2; + #endif + *s++ = h[(n64 >> 60) & 15]; + n64 <<= 4; + if (pr) + *s++ = stbsp__period; + sn = s; + + // print the bits + n = pr; + if (n > 13) + n = 13; + if (pr > (stbsp__int32)n) + tz = pr - n; + pr = 0; + while (n--) { + *s++ = h[(n64 >> 60) & 15]; + n64 <<= 4; + } + + // print the expo + tail[1] = h[17]; + if (dp < 0) { + tail[2] = '-'; + dp = -dp; + } + else + tail[2] = '+'; + n = (dp >= 1000) ? 6 : ((dp >= 100) ? 5 : ((dp >= 10) ? 4 : 3)); + tail[0] = (char)n; + for (;;) { + tail[n] = '0' + dp % 10; + if (n <= 3) + break; + --n; + dp /= 10; + } + + dp = (int)(s - sn); + l = (int)(s - (num + 64)); + s = num + 64; + cs = 1 + (3 << 24); + goto scopy; + + case 'G': // float + case 'g': // float + h = (f[0] == 'G') ? hexu : hex; + fv = va_arg(va, double); + if (pr == -1) + pr = 6; + else if (pr == 0) + pr = 1; // default is 6 + // read the double into a string + if (stbsp__real_to_str(&sn, &l, num, &dp, fv, (pr - 1) | 0x80000000)) + fl |= STBSP__NEGATIVE; + + // clamp the precision and delete extra zeros after clamp + n = pr; + if (l > (stbsp__uint32)pr) + l = pr; + while ((l > 1) && (pr) && (sn[l - 1] == '0')) { + --pr; + --l; + } + + // should we use %e + if ((dp <= -4) || (dp > (stbsp__int32)n)) { + if (pr > (stbsp__int32)l) + pr = l - 1; + else if (pr) + --pr; // when using %e, there is one digit before the decimal + goto doexpfromg; + } + // this is the insane action to get the pr to match %g semantics for %f + if (dp > 0) { + pr = (dp < (stbsp__int32)l) ? l - dp : 0; + } + else { + pr = -dp + ((pr > (stbsp__int32)l) ? (stbsp__int32)l : pr); + } + goto dofloatfromg; + + case 'E': // float + case 'e': // float + h = (f[0] == 'E') ? hexu : hex; + fv = va_arg(va, double); + if (pr == -1) + pr = 6; // default is 6 + // read the double into a string + if (stbsp__real_to_str(&sn, &l, num, &dp, fv, pr | 0x80000000)) + fl |= STBSP__NEGATIVE; + doexpfromg: + tail[0] = 0; + stbsp__lead_sign(fl, lead); + if (dp == STBSP__SPECIAL) { + s = (char *)sn; + cs = 0; + pr = 0; + goto scopy; + } + s = num + 64; + // handle leading chars + *s++ = sn[0]; + + if (pr) + *s++ = stbsp__period; + + // handle after decimal + if ((l - 1) > (stbsp__uint32)pr) + l = pr + 1; + for (n = 1; n < l; n++) + *s++ = sn[n]; + // trailing zeros + tz = pr - (l - 1); + pr = 0; + // dump expo + tail[1] = h[0xe]; + dp -= 1; + if (dp < 0) { + tail[2] = '-'; + dp = -dp; + } + else + tail[2] = '+'; + #ifdef STB_SPRINTF_MSVC_MODE + n = 5; + #else + n = (dp >= 100) ? 5 : 4; + #endif + tail[0] = (char)n; + for (;;) { + tail[n] = '0' + dp % 10; + if (n <= 3) + break; + --n; + dp /= 10; + } + cs = 1 + (3 << 24); // how many tens + goto flt_lead; + + case 'f': // float + fv = va_arg(va, double); + doafloat: + // do kilos + if (fl & STBSP__METRIC_SUFFIX) { + double divisor; + divisor = 1000.0f; + if (fl & STBSP__METRIC_1024) + divisor = 1024.0; + while (fl < 0x4000000) { + if ((fv < divisor) && (fv > -divisor)) + break; + fv /= divisor; + fl += 0x1000000; + } + } + if (pr == -1) + pr = 6; // default is 6 + // read the double into a string + if (stbsp__real_to_str(&sn, &l, num, &dp, fv, pr)) + fl |= STBSP__NEGATIVE; + dofloatfromg: + tail[0] = 0; + stbsp__lead_sign(fl, lead); + if (dp == STBSP__SPECIAL) { + s = (char *)sn; + cs = 0; + pr = 0; + goto scopy; + } + s = num + 64; + + // handle the three decimal varieties + if (dp <= 0) { + stbsp__int32 i; + // handle 0.000*000xxxx + *s++ = '0'; + if (pr) + *s++ = stbsp__period; + n = -dp; + if ((stbsp__int32)n > pr) + n = pr; + i = n; + while (i) { + if ((((stbsp__uintptr)s) & 3) == 0) + break; + *s++ = '0'; + --i; + } + while (i >= 4) { + *(stbsp__uint32 *)s = 0x30303030; + s += 4; + i -= 4; + } + while (i) { + *s++ = '0'; + --i; + } + if ((stbsp__int32)(l + n) > pr) + l = pr - n; + i = l; + while (i) { + *s++ = *sn++; + --i; + } + tz = pr - (n + l); + cs = 1 + (3 << 24); // how many tens did we write (for commas below) + } + else { + cs = (fl & STBSP__TRIPLET_COMMA) ? ((600 - (stbsp__uint32)dp) % 3) : 0; + if ((stbsp__uint32)dp >= l) { + // handle xxxx000*000.0 + n = 0; + for (;;) { + if ((fl & STBSP__TRIPLET_COMMA) && (++cs == 4)) { + cs = 0; + *s++ = stbsp__comma; + } + else { + *s++ = sn[n]; + ++n; + if (n >= l) + break; + } + } + if (n < (stbsp__uint32)dp) { + n = dp - n; + if ((fl & STBSP__TRIPLET_COMMA) == 0) { + while (n) { + if ((((stbsp__uintptr)s) & 3) == 0) + break; + *s++ = '0'; + --n; + } + while (n >= 4) { + *(stbsp__uint32 *)s = 0x30303030; + s += 4; + n -= 4; + } + } + while (n) { + if ((fl & STBSP__TRIPLET_COMMA) && (++cs == 4)) { + cs = 0; + *s++ = stbsp__comma; + } + else { + *s++ = '0'; + --n; + } + } + } + cs = (int)(s - (num + 64)) + (3 << 24); // cs is how many tens + if (pr) { + *s++ = stbsp__period; + tz = pr; + } + } + else { + // handle xxxxx.xxxx000*000 + n = 0; + for (;;) { + if ((fl & STBSP__TRIPLET_COMMA) && (++cs == 4)) { + cs = 0; + *s++ = stbsp__comma; + } + else { + *s++ = sn[n]; + ++n; + if (n >= (stbsp__uint32)dp) + break; + } + } + cs = (int)(s - (num + 64)) + (3 << 24); // cs is how many tens + if (pr) + *s++ = stbsp__period; + if ((l - dp) > (stbsp__uint32)pr) + l = pr + dp; + while (n < l) { + *s++ = sn[n]; + ++n; + } + tz = pr - (l - dp); + } + } + pr = 0; + + // handle k,m,g,t + if (fl & STBSP__METRIC_SUFFIX) { + char idx; + idx = 1; + if (fl & STBSP__METRIC_NOSPACE) + idx = 0; + tail[0] = idx; + tail[1] = ' '; + { + if (fl >> 24) { // SI kilo is 'k', JEDEC and SI kibits are 'K'. + if (fl & STBSP__METRIC_1024) + tail[idx + 1] = "_KMGT"[fl >> 24]; + else + tail[idx + 1] = "_kMGT"[fl >> 24]; + idx++; + // If printing kibits and not in jedec, add the 'i'. + if (fl & STBSP__METRIC_1024 && !(fl & STBSP__METRIC_JEDEC)) { + tail[idx + 1] = 'i'; + idx++; + } + tail[0] = idx; + } + } + }; + + flt_lead: + // get the length that we copied + l = (stbsp__uint32)(s - (num + 64)); + s = num + 64; + goto scopy; + #endif + + case 'B': // upper binary + case 'b': // lower binary + h = (f[0] == 'B') ? hexu : hex; + lead[0] = 0; + if (fl & STBSP__LEADING_0X) { + lead[0] = 2; + lead[1] = '0'; + lead[2] = h[0xb]; + } + l = (8 << 4) | (1 << 8); + goto radixnum; + + case 'o': // octal + h = hexu; + lead[0] = 0; + if (fl & STBSP__LEADING_0X) { + lead[0] = 1; + lead[1] = '0'; + } + l = (3 << 4) | (3 << 8); + goto radixnum; + + case 'p': // pointer + fl |= (sizeof(void *) == 8) ? STBSP__INTMAX : 0; + pr = sizeof(void *) * 2; + fl &= ~STBSP__LEADINGZERO; // 'p' only prints the pointer with zeros + // fall through - to X + + case 'X': // upper hex + case 'x': // lower hex + h = (f[0] == 'X') ? hexu : hex; + l = (4 << 4) | (4 << 8); + lead[0] = 0; + if (fl & STBSP__LEADING_0X) { + lead[0] = 2; + lead[1] = '0'; + lead[2] = h[16]; + } + radixnum: + // get the number + if (fl & STBSP__INTMAX) + n64 = va_arg(va, stbsp__uint64); + else + n64 = va_arg(va, stbsp__uint32); + + s = num + STBSP__NUMSZ; + dp = 0; + // clear tail, and clear leading if value is zero + tail[0] = 0; + if (n64 == 0) { + lead[0] = 0; + if (pr == 0) { + l = 0; + cs = 0; + goto scopy; + } + } + // convert to string + for (;;) { + *--s = h[n64 & ((1 << (l >> 8)) - 1)]; + n64 >>= (l >> 8); + if (!((n64) || ((stbsp__int32)((num + STBSP__NUMSZ) - s) < pr))) + break; + if (fl & STBSP__TRIPLET_COMMA) { + ++l; + if ((l & 15) == ((l >> 4) & 15)) { + l &= ~15; + *--s = stbsp__comma; + } + } + }; + // get the tens and the comma pos + cs = (stbsp__uint32)((num + STBSP__NUMSZ) - s) + ((((l >> 4) & 15)) << 24); + // get the length that we copied + l = (stbsp__uint32)((num + STBSP__NUMSZ) - s); + // copy it + goto scopy; + + case 'u': // unsigned + case 'i': + case 'd': // integer + // get the integer and abs it + if (fl & STBSP__INTMAX) { + stbsp__int64 i64 = va_arg(va, stbsp__int64); + n64 = (stbsp__uint64)i64; + if ((f[0] != 'u') && (i64 < 0)) { + n64 = (stbsp__uint64)-i64; + fl |= STBSP__NEGATIVE; + } + } + else { + stbsp__int32 i = va_arg(va, stbsp__int32); + n64 = (stbsp__uint32)i; + if ((f[0] != 'u') && (i < 0)) { + n64 = (stbsp__uint32)-i; + fl |= STBSP__NEGATIVE; + } + } + + #ifndef STB_SPRINTF_NOFLOAT + if (fl & STBSP__METRIC_SUFFIX) { + if (n64 < 1024) + pr = 0; + else if (pr == -1) + pr = 1; + fv = (double)(stbsp__int64)n64; + goto doafloat; + } + #endif + + // convert to string + s = num + STBSP__NUMSZ; + l = 0; + + for (;;) { + // do in 32-bit chunks (avoid lots of 64-bit divides even with constant denominators) + char *o = s - 8; + if (n64 >= 100000000) { + n = (stbsp__uint32)(n64 % 100000000); + n64 /= 100000000; + } + else { + n = (stbsp__uint32)n64; + n64 = 0; + } + if ((fl & STBSP__TRIPLET_COMMA) == 0) { + do { + s -= 2; + *(stbsp__uint16 *)s = *(stbsp__uint16 *)&stbsp__digitpair.pair[(n % 100) * 2]; + n /= 100; + } while (n); + } + while (n) { + if ((fl & STBSP__TRIPLET_COMMA) && (l++ == 3)) { + l = 0; + *--s = stbsp__comma; + --o; + } + else { + *--s = (char)(n % 10) + '0'; + n /= 10; + } + } + if (n64 == 0) { + if ((s[0] == '0') && (s != (num + STBSP__NUMSZ))) + ++s; + break; + } + while (s != o) + if ((fl & STBSP__TRIPLET_COMMA) && (l++ == 3)) { + l = 0; + *--s = stbsp__comma; + --o; + } + else { + *--s = '0'; + } + } + + tail[0] = 0; + stbsp__lead_sign(fl, lead); + + // get the length that we copied + l = (stbsp__uint32)((num + STBSP__NUMSZ) - s); + if (l == 0) { + *--s = '0'; + l = 1; + } + cs = l + (3 << 24); + if (pr < 0) + pr = 0; + + scopy: + // get fw=leading/trailing space, pr=leading zeros + if (pr < (stbsp__int32)l) + pr = l; + n = pr + lead[0] + tail[0] + tz; + if (fw < (stbsp__int32)n) + fw = n; + fw -= n; + pr -= l; + + // handle right justify and leading zeros + if ((fl & STBSP__LEFTJUST) == 0) { + if (fl & STBSP__LEADINGZERO) // if leading zeros, everything is in pr + { + pr = (fw > pr) ? fw : pr; + fw = 0; + } + else { + fl &= ~STBSP__TRIPLET_COMMA; // if no leading zeros, then no commas + } + } + + // copy the spaces and/or zeros + if (fw + pr) { + stbsp__int32 i; + stbsp__uint32 c; + + // copy leading spaces (or when doing %8.4d stuff) + if ((fl & STBSP__LEFTJUST) == 0) + while (fw > 0) { + stbsp__cb_buf_clamp(i, fw); + fw -= i; + while (i) { + if ((((stbsp__uintptr)bf) & 3) == 0) + break; + *bf++ = ' '; + --i; + } + while (i >= 4) { + *(stbsp__uint32 *)bf = 0x20202020; + bf += 4; + i -= 4; + } + while (i) { + *bf++ = ' '; + --i; + } + stbsp__chk_cb_buf(1); + } + + // copy leader + sn = lead + 1; + while (lead[0]) { + stbsp__cb_buf_clamp(i, lead[0]); + lead[0] -= (char)i; + while (i) { + *bf++ = *sn++; + --i; + } + stbsp__chk_cb_buf(1); + } + + // copy leading zeros + c = cs >> 24; + cs &= 0xffffff; + cs = (fl & STBSP__TRIPLET_COMMA) ? ((stbsp__uint32)(c - ((pr + cs) % (c + 1)))) : 0; + while (pr > 0) { + stbsp__cb_buf_clamp(i, pr); + pr -= i; + if ((fl & STBSP__TRIPLET_COMMA) == 0) { + while (i) { + if ((((stbsp__uintptr)bf) & 3) == 0) + break; + *bf++ = '0'; + --i; + } + while (i >= 4) { + *(stbsp__uint32 *)bf = 0x30303030; + bf += 4; + i -= 4; + } + } + while (i) { + if ((fl & STBSP__TRIPLET_COMMA) && (cs++ == c)) { + cs = 0; + *bf++ = stbsp__comma; + } + else + *bf++ = '0'; + --i; + } + stbsp__chk_cb_buf(1); + } + } + + // copy leader if there is still one + sn = lead + 1; + while (lead[0]) { + stbsp__int32 i; + stbsp__cb_buf_clamp(i, lead[0]); + lead[0] -= (char)i; + while (i) { + *bf++ = *sn++; + --i; + } + stbsp__chk_cb_buf(1); + } + + // copy the string + n = l; + while (n) { + stbsp__int32 i; + stbsp__cb_buf_clamp(i, n); + n -= i; + STBSP__UNALIGNED(while (i >= 4) { + *(stbsp__uint32 volatile *)bf = *(stbsp__uint32 volatile *)s; + bf += 4; + s += 4; + i -= 4; + }) + while (i) { + *bf++ = *s++; + --i; + } + stbsp__chk_cb_buf(1); + } + + // copy trailing zeros + while (tz) { + stbsp__int32 i; + stbsp__cb_buf_clamp(i, tz); + tz -= i; + while (i) { + if ((((stbsp__uintptr)bf) & 3) == 0) + break; + *bf++ = '0'; + --i; + } + while (i >= 4) { + *(stbsp__uint32 *)bf = 0x30303030; + bf += 4; + i -= 4; + } + while (i) { + *bf++ = '0'; + --i; + } + stbsp__chk_cb_buf(1); + } + + // copy tail if there is one + sn = tail + 1; + while (tail[0]) { + stbsp__int32 i; + stbsp__cb_buf_clamp(i, tail[0]); + tail[0] -= (char)i; + while (i) { + *bf++ = *sn++; + --i; + } + stbsp__chk_cb_buf(1); + } + + // handle the left justify + if (fl & STBSP__LEFTJUST) + if (fw > 0) { + while (fw) { + stbsp__int32 i; + stbsp__cb_buf_clamp(i, fw); + fw -= i; + while (i) { + if ((((stbsp__uintptr)bf) & 3) == 0) + break; + *bf++ = ' '; + --i; + } + while (i >= 4) { + *(stbsp__uint32 *)bf = 0x20202020; + bf += 4; + i -= 4; + } + while (i--) + *bf++ = ' '; + stbsp__chk_cb_buf(1); + } + } + break; + + default: // unknown, just copy code + s = num + STBSP__NUMSZ - 1; + *s = f[0]; + l = 1; + fw = fl = 0; + lead[0] = 0; + tail[0] = 0; + pr = 0; + dp = 0; + cs = 0; + goto scopy; + } + ++f; + } +endfmt: + + if (!callback) + *bf = 0; + else + stbsp__flush_cb(); + +done: + return tlen + (int)(bf - buf); +} + + // cleanup + #undef STBSP__LEFTJUST + #undef STBSP__LEADINGPLUS + #undef STBSP__LEADINGSPACE + #undef STBSP__LEADING_0X + #undef STBSP__LEADINGZERO + #undef STBSP__INTMAX + #undef STBSP__TRIPLET_COMMA + #undef STBSP__NEGATIVE + #undef STBSP__METRIC_SUFFIX + #undef STBSP__NUMSZ + #undef stbsp__chk_cb_bufL + #undef stbsp__chk_cb_buf + #undef stbsp__flush_cb + #undef stbsp__cb_buf_clamp + +// ============================================================================ +// wrapper functions + +STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(sprintf)(char *buf, char const *fmt, ...) { + int result; + va_list va; + va_start(va, fmt); + result = STB_SPRINTF_DECORATE(vsprintfcb)(0, 0, buf, fmt, va); + va_end(va); + return result; +} + +typedef struct stbsp__context { + char *buf; + int count; + int length; + char tmp[STB_SPRINTF_MIN]; +} stbsp__context; + +static char *stbsp__clamp_callback(const char *buf, void *user, int len) { + stbsp__context *c = (stbsp__context *)user; + c->length += len; + + if (len > c->count) + len = c->count; + + if (len) { + if (buf != c->buf) { + const char *s, *se; + char *d; + d = c->buf; + s = buf; + se = buf + len; + do { + *d++ = *s++; + } while (s < se); + } + c->buf += len; + c->count -= len; + } + + if (c->count <= 0) + return c->tmp; + return (c->count >= STB_SPRINTF_MIN) ? c->buf : c->tmp; // go direct into buffer if you can +} + +static char *stbsp__count_clamp_callback(const char *buf, void *user, int len) { + stbsp__context *c = (stbsp__context *)user; + (void)sizeof(buf); + + c->length += len; + return c->tmp; // go direct into buffer if you can +} + +STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(vsnprintf)(char *buf, int count, char const *fmt, va_list va) { + stbsp__context c; + + if ((count == 0) && !buf) { + c.length = 0; + + STB_SPRINTF_DECORATE(vsprintfcb) + (stbsp__count_clamp_callback, &c, c.tmp, fmt, va); + } + else { + int l; + + c.buf = buf; + c.count = count; + c.length = 0; + + STB_SPRINTF_DECORATE(vsprintfcb) + (stbsp__clamp_callback, &c, stbsp__clamp_callback(0, &c, 0), fmt, va); + + // zero-terminate + l = (int)(c.buf - buf); + if (l >= count) // should never be greater, only equal (or less) than count + l = count - 1; + buf[l] = 0; + } + + return c.length; +} + +STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(snprintf)(char *buf, int count, char const *fmt, ...) { + int result; + va_list va; + va_start(va, fmt); + + result = STB_SPRINTF_DECORATE(vsnprintf)(buf, count, fmt, va); + va_end(va); + + return result; +} + +STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(vsprintf)(char *buf, char const *fmt, va_list va) { + return STB_SPRINTF_DECORATE(vsprintfcb)(0, 0, buf, fmt, va); +} + +// ======================================================================= +// low level float utility functions + + #ifndef STB_SPRINTF_NOFLOAT + + // copies d to bits w/ strict aliasing (this compiles to nothing on /Ox) + #define STBSP__COPYFP(dest, src) \ + { \ + int cn; \ + for (cn = 0; cn < 8; cn++) \ + ((char *)&dest)[cn] = ((char *)&src)[cn]; \ + } + +// get float info +static stbsp__int32 stbsp__real_to_parts(stbsp__int64 *bits, stbsp__int32 *expo, double value) { + double d; + stbsp__int64 b = 0; + + // load value and round at the frac_digits + d = value; + + STBSP__COPYFP(b, d); + + *bits = b & ((((stbsp__uint64)1) << 52) - 1); + *expo = (stbsp__int32)(((b >> 52) & 2047) - 1023); + + return (stbsp__int32)((stbsp__uint64)b >> 63); +} + +static double const stbsp__bot[23] = { + 1e+000, 1e+001, 1e+002, 1e+003, 1e+004, 1e+005, 1e+006, 1e+007, 1e+008, 1e+009, 1e+010, 1e+011, + 1e+012, 1e+013, 1e+014, 1e+015, 1e+016, 1e+017, 1e+018, 1e+019, 1e+020, 1e+021, 1e+022}; +static double const stbsp__negbot[22] = { + 1e-001, 1e-002, 1e-003, 1e-004, 1e-005, 1e-006, 1e-007, 1e-008, 1e-009, 1e-010, 1e-011, + 1e-012, 1e-013, 1e-014, 1e-015, 1e-016, 1e-017, 1e-018, 1e-019, 1e-020, 1e-021, 1e-022}; +static double const stbsp__negboterr[22] = { + -5.551115123125783e-018, -2.0816681711721684e-019, -2.0816681711721686e-020, -4.7921736023859299e-021, -8.1803053914031305e-022, 4.5251888174113741e-023, + 4.5251888174113739e-024, -2.0922560830128471e-025, -6.2281591457779853e-026, -3.6432197315497743e-027, 6.0503030718060191e-028, 2.0113352370744385e-029, + -3.0373745563400371e-030, 1.1806906454401013e-032, -7.7705399876661076e-032, 2.0902213275965398e-033, -7.1542424054621921e-034, -7.1542424054621926e-035, + 2.4754073164739869e-036, 5.4846728545790429e-037, 9.2462547772103625e-038, -4.8596774326570872e-039}; +static double const stbsp__top[13] = { + 1e+023, 1e+046, 1e+069, 1e+092, 1e+115, 1e+138, 1e+161, 1e+184, 1e+207, 1e+230, 1e+253, 1e+276, 1e+299}; +static double const stbsp__negtop[13] = { + 1e-023, 1e-046, 1e-069, 1e-092, 1e-115, 1e-138, 1e-161, 1e-184, 1e-207, 1e-230, 1e-253, 1e-276, 1e-299}; +static double const stbsp__toperr[13] = { + 8388608, + 6.8601809640529717e+028, + -7.253143638152921e+052, + -4.3377296974619174e+075, + -1.5559416129466825e+098, + -3.2841562489204913e+121, + -3.7745893248228135e+144, + -1.7356668416969134e+167, + -3.8893577551088374e+190, + -9.9566444326005119e+213, + 6.3641293062232429e+236, + -5.2069140800249813e+259, + -5.2504760255204387e+282}; +static double const stbsp__negtoperr[13] = { + 3.9565301985100693e-040, -2.299904345391321e-063, 3.6506201437945798e-086, 1.1875228833981544e-109, + -5.0644902316928607e-132, -6.7156837247865426e-155, -2.812077463003139e-178, -5.7778912386589953e-201, + 7.4997100559334532e-224, -4.6439668915134491e-247, -6.3691100762962136e-270, -9.436808465446358e-293, + 8.0970921678014997e-317}; + + #if defined(_MSC_VER) && (_MSC_VER <= 1200) +static stbsp__uint64 const stbsp__powten[20] = { + 1, + 10, + 100, + 1000, + 10000, + 100000, + 1000000, + 10000000, + 100000000, + 1000000000, + 10000000000, + 100000000000, + 1000000000000, + 10000000000000, + 100000000000000, + 1000000000000000, + 10000000000000000, + 100000000000000000, + 1000000000000000000, + 10000000000000000000U}; + #define stbsp__tento19th ((stbsp__uint64)1000000000000000000) + #else +static stbsp__uint64 const stbsp__powten[20] = { + 1, + 10, + 100, + 1000, + 10000, + 100000, + 1000000, + 10000000, + 100000000, + 1000000000, + 10000000000ULL, + 100000000000ULL, + 1000000000000ULL, + 10000000000000ULL, + 100000000000000ULL, + 1000000000000000ULL, + 10000000000000000ULL, + 100000000000000000ULL, + 1000000000000000000ULL, + 10000000000000000000ULL}; + #define stbsp__tento19th (1000000000000000000ULL) + #endif + + #define stbsp__ddmulthi(oh, ol, xh, yh) \ + { \ + double ahi = 0, alo, bhi = 0, blo; \ + stbsp__int64 bt; \ + oh = xh * yh; \ + STBSP__COPYFP(bt, xh); \ + bt &= ((~(stbsp__uint64)0) << 27); \ + STBSP__COPYFP(ahi, bt); \ + alo = xh - ahi; \ + STBSP__COPYFP(bt, yh); \ + bt &= ((~(stbsp__uint64)0) << 27); \ + STBSP__COPYFP(bhi, bt); \ + blo = yh - bhi; \ + ol = ((ahi * bhi - oh) + ahi * blo + alo * bhi) + alo * blo; \ + } + + #define stbsp__ddtoS64(ob, xh, xl) \ + { \ + double ahi = 0, alo, vh, t; \ + ob = (stbsp__int64)xh; \ + vh = (double)ob; \ + ahi = (xh - vh); \ + t = (ahi - xh); \ + alo = (xh - (ahi - t)) - (vh + t); \ + ob += (stbsp__int64)(ahi + alo + xl); \ + } + + #define stbsp__ddrenorm(oh, ol) \ + { \ + double s; \ + s = oh + ol; \ + ol = ol - (s - oh); \ + oh = s; \ + } + + #define stbsp__ddmultlo(oh, ol, xh, xl, yh, yl) ol = ol + (xh * yl + xl * yh); + + #define stbsp__ddmultlos(oh, ol, xh, yl) ol = ol + (xh * yl); + +static void stbsp__raise_to_power10(double *ohi, double *olo, double d, stbsp__int32 power) // power can be -323 to +350 +{ + double ph, pl; + if ((power >= 0) && (power <= 22)) { + stbsp__ddmulthi(ph, pl, d, stbsp__bot[power]); + } + else { + stbsp__int32 e, et, eb; + double p2h, p2l; + + e = power; + if (power < 0) + e = -e; + et = (e * 0x2c9) >> 14; /* %23 */ + if (et > 13) + et = 13; + eb = e - (et * 23); + + ph = d; + pl = 0.0; + if (power < 0) { + if (eb) { + --eb; + stbsp__ddmulthi(ph, pl, d, stbsp__negbot[eb]); + stbsp__ddmultlos(ph, pl, d, stbsp__negboterr[eb]); + } + if (et) { + stbsp__ddrenorm(ph, pl); + --et; + stbsp__ddmulthi(p2h, p2l, ph, stbsp__negtop[et]); + stbsp__ddmultlo(p2h, p2l, ph, pl, stbsp__negtop[et], stbsp__negtoperr[et]); + ph = p2h; + pl = p2l; + } + } + else { + if (eb) { + e = eb; + if (eb > 22) + eb = 22; + e -= eb; + stbsp__ddmulthi(ph, pl, d, stbsp__bot[eb]); + if (e) { + stbsp__ddrenorm(ph, pl); + stbsp__ddmulthi(p2h, p2l, ph, stbsp__bot[e]); + stbsp__ddmultlos(p2h, p2l, stbsp__bot[e], pl); + ph = p2h; + pl = p2l; + } + } + if (et) { + stbsp__ddrenorm(ph, pl); + --et; + stbsp__ddmulthi(p2h, p2l, ph, stbsp__top[et]); + stbsp__ddmultlo(p2h, p2l, ph, pl, stbsp__top[et], stbsp__toperr[et]); + ph = p2h; + pl = p2l; + } + } + } + stbsp__ddrenorm(ph, pl); + *ohi = ph; + *olo = pl; +} + +// given a float value, returns the significant bits in bits, and the position of the +// decimal point in decimal_pos. +/-INF and NAN are specified by special values +// returned in the decimal_pos parameter. +// frac_digits is absolute normally, but if you want from first significant digits (got %g and %e), or in 0x80000000 +static stbsp__int32 stbsp__real_to_str(char const **start, stbsp__uint32 *len, char *out, stbsp__int32 *decimal_pos, double value, stbsp__uint32 frac_digits) { + double d; + stbsp__int64 bits = 0; + stbsp__int32 expo, e, ng, tens; + + d = value; + STBSP__COPYFP(bits, d); + expo = (stbsp__int32)((bits >> 52) & 2047); + ng = (stbsp__int32)((stbsp__uint64)bits >> 63); + if (ng) + d = -d; + + if (expo == 2047) // is nan or inf? + { + *start = (bits & ((((stbsp__uint64)1) << 52) - 1)) ? "NaN" : "Inf"; + *decimal_pos = STBSP__SPECIAL; + *len = 3; + return ng; + } + + if (expo == 0) // is zero or denormal + { + if (((stbsp__uint64)bits << 1) == 0) // do zero + { + *decimal_pos = 1; + *start = out; + out[0] = '0'; + *len = 1; + return ng; + } + // find the right expo for denormals + { + stbsp__int64 v = ((stbsp__uint64)1) << 51; + while ((bits & v) == 0) { + --expo; + v >>= 1; + } + } + } + + // find the decimal exponent as well as the decimal bits of the value + { + double ph, pl; + + // log10 estimate - very specifically tweaked to hit or undershoot by no more than 1 of log10 of all expos 1..2046 + tens = expo - 1023; + tens = (tens < 0) ? ((tens * 617) / 2048) : (((tens * 1233) / 4096) + 1); + + // move the significant bits into position and stick them into an int + stbsp__raise_to_power10(&ph, &pl, d, 18 - tens); + + // get full as much precision from double-double as possible + stbsp__ddtoS64(bits, ph, pl); + + // check if we undershot + if (((stbsp__uint64)bits) >= stbsp__tento19th) + ++tens; + } + + // now do the rounding in integer land + frac_digits = (frac_digits & 0x80000000) ? ((frac_digits & 0x7ffffff) + 1) : (tens + frac_digits); + if ((frac_digits < 24)) { + stbsp__uint32 dg = 1; + if ((stbsp__uint64)bits >= stbsp__powten[9]) + dg = 10; + while ((stbsp__uint64)bits >= stbsp__powten[dg]) { + ++dg; + if (dg == 20) + goto noround; + } + if (frac_digits < dg) { + stbsp__uint64 r; + // add 0.5 at the right position and round + e = dg - frac_digits; + if ((stbsp__uint32)e >= 24) + goto noround; + r = stbsp__powten[e]; + bits = bits + (r / 2); + if ((stbsp__uint64)bits >= stbsp__powten[dg]) + ++tens; + bits /= r; + } + noround:; + } + + // kill long trailing runs of zeros + if (bits) { + stbsp__uint32 n; + for (;;) { + if (bits <= 0xffffffff) + break; + if (bits % 1000) + goto donez; + bits /= 1000; + } + n = (stbsp__uint32)bits; + while ((n % 1000) == 0) + n /= 1000; + bits = n; + donez:; + } + + // convert to string + out += 64; + e = 0; + for (;;) { + stbsp__uint32 n; + char *o = out - 8; + // do the conversion in chunks of U32s (avoid most 64-bit divides, worth it, constant denomiators be damned) + if (bits >= 100000000) { + n = (stbsp__uint32)(bits % 100000000); + bits /= 100000000; + } + else { + n = (stbsp__uint32)bits; + bits = 0; + } + while (n) { + out -= 2; + *(stbsp__uint16 *)out = *(stbsp__uint16 *)&stbsp__digitpair.pair[(n % 100) * 2]; + n /= 100; + e += 2; + } + if (bits == 0) { + if ((e) && (out[0] == '0')) { + ++out; + --e; + } + break; + } + while (out != o) { + *--out = '0'; + ++e; + } + } + + *decimal_pos = tens; + *start = out; + *len = e; + return ng; +} + + #undef stbsp__ddmulthi + #undef stbsp__ddrenorm + #undef stbsp__ddmultlo + #undef stbsp__ddmultlos + #undef STBSP__SPECIAL + #undef STBSP__COPYFP + + #endif // STB_SPRINTF_NOFLOAT + + // clean up + #undef stbsp__uint16 + #undef stbsp__uint32 + #undef stbsp__int32 + #undef stbsp__uint64 + #undef stbsp__int64 + #undef STBSP__UNALIGNED + +#endif // STB_SPRINTF_IMPLEMENTATION + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/string.h b/string.h new file mode 100644 index 0000000..38b6f29 --- /dev/null +++ b/string.h @@ -0,0 +1,616 @@ +#ifndef S8_HEADER +#define S8_HEADER +#include +#include + +// Preprocessor Input: ALLOCATOR_TYPE + +#ifndef S8_FN + #if defined(__GNUC__) || defined(__clang__) + #define S8_FN __attribute__((unused)) static + #else + #define S8_FN static + #endif +#endif + +#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, ...); + +#endif // S8_HEADER +#ifdef S8_IMPLEMENTATION +#include + +#ifndef S8_VSNPRINTF + #include + #define S8_VSNPRINTF vsnprintf +#endif + +#ifndef S8_ALLOCATE + #include + #define S8_ALLOCATE(allocator, size) malloc(size) +#endif + +#ifndef S8_ASSERT + #include + #define S8_ASSERT(x) assert(x) +#endif + +#ifndef S8_MemoryCopy + #include + #define S8_MemoryCopy(dst, src, s) memcpy(dst, src, s) +#endif + +S8_FN 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; +} + +#endif // S8_IMPLEMENTATION diff --git a/table.hpp b/table.hpp new file mode 100644 index 0000000..cee9ba1 --- /dev/null +++ b/table.hpp @@ -0,0 +1,228 @@ +#include +/* + 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 +*/ + +// #define TABLE_ALLOCATOR_TYPE Allocator + +#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_ALLOCATE + #include + #define TABLE_ALLOCATE(allocator, size) malloc(size) +#endif + +#ifndef TABLE_DEALLOCATE + #include + #define TABLE_DEALLOCATE(allocator, p) free(p) +#endif + +#ifndef TABLE_ASSERT + #include + #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))) + +TABLE_PRIVATE_FUNCTION int TABLE_CStringLen(char *str) { + int i = 0; + while(str[i]) i += 1; + return i; +} + +template +struct Table { + struct Entry { + uint64_t hash; + uint64_t key; + size_t distance; + Value value; + }; + + size_t len, cap; + Entry *values; +#ifdef TABLE_ALLOCATOR_TYPE + TABLE_ALLOCATOR_TYPE allocator; +#endif + + 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_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"); + } + + // 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; + } + + 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; + } + + 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() { + COR_DEALLOCATE(allocator, values); + len = 0; + cap = 0; + values = 0; + } + static const size_t max_load_factor = 80; + static const size_t min_load_factor = 50; + static const size_t significant_distance = 8; +}; \ No newline at end of file diff --git a/unicode.h b/unicode.h new file mode 100644 index 0000000..7705ade --- /dev/null +++ b/unicode.h @@ -0,0 +1,280 @@ +#ifndef UTF_HEADER +#define UTF_HEADER +#include +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_StaticFunc + #if defined(__GNUC__) || defined(__clang__) + #define UTF_StaticFunc __attribute__((unused)) static + #else + #define UTF_StaticFunc static + #endif +#endif + +#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)) + +#endif // UTF_HEADER +#ifdef UTF_IMPLEMENTATION + +UTF_StaticFunc int UTF__StringLength(char *string) { + int len = 0; + while (*string++ != 0) + len++; + return len; +} + +UTF_StaticFunc void UTF__MemoryZero(void *p, size_t size) { + uint8_t *p8 = (uint8_t *)p; + while (size--) *p8++ = 0; +} + +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) { + return UTF8_IterateEx(str, UTF__StringLength(str)); +} + +#endif // UTF_IMPLEMENTATION \ No newline at end of file