From d050fbd388dea379cf844507dd585bbd79ac663b Mon Sep 17 00:00:00 2001 From: Krzosa Karol Date: Sun, 31 Dec 2023 08:33:59 +0100 Subject: [PATCH] Split single headers, add test to make sure things compile --- arena.c | 414 ++++++++ arena.h | 438 +------- build.bat | 23 + clexer.c | 1878 +++++++++++++++++++++++++++++++++ clexer.h | 1891 ---------------------------------- core.c | 24 +- core.h | 1 + io.c | 222 ++++ io.h | 224 +--- multimedia.c | 1740 +++++++++++++++++++++++++++++++ multimedia.h | 1740 ------------------------------- regex.c | 558 ++++++++++ regex.h | 559 ---------- string.c | 493 +++++++++ string.h | 497 --------- test/main.c | 5 + test/main.cpp | 5 + test/main_core_as_header.cpp | 8 + unicode.c | 210 ++++ unicode.h | 226 ---- 20 files changed, 5569 insertions(+), 5587 deletions(-) create mode 100644 arena.c create mode 100644 build.bat create mode 100644 clexer.c create mode 100644 io.c create mode 100644 multimedia.c create mode 100644 regex.c create mode 100644 string.c create mode 100644 test/main.c create mode 100644 test/main.cpp create mode 100644 test/main_core_as_header.cpp create mode 100644 unicode.c diff --git a/arena.c b/arena.c new file mode 100644 index 0000000..17dc1dc --- /dev/null +++ b/arena.c @@ -0,0 +1,414 @@ +#include "arena.h" +#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_StaticFunc + #if defined(__GNUC__) || defined(__clang__) + #define MA_StaticFunc __attribute__((unused)) static + #else + #define MA_StaticFunc static + #endif +#endif + +MA_API size_t MA_GetAlignOffset(size_t size, size_t align) { + size_t mask = align - 1; + size_t val = size & mask; + if (val) { + val = align - val; + } + return val; +} + +MA_API size_t MA_AlignUp(size_t size, size_t align) { + size_t result = size + MA_GetAlignOffset(size, align); + return result; +} + +MA_API size_t MA_AlignDown(size_t size, size_t align) { + size += 1; // Make sure when align is 8 doesn't get rounded down to 0 + size_t result = size - (align - MA_GetAlignOffset(size, align)); + return result; +} + +MA_StaticFunc uint8_t *MV__AdvanceCommit(MV_Memory *m, size_t *commit_size, size_t page_size) { + size_t aligned_up_commit = MA_AlignUp(*commit_size, page_size); + size_t to_be_total_commited_size = aligned_up_commit + m->commit; + size_t to_be_total_commited_size_clamped_to_reserve = MA_CLAMP_TOP(to_be_total_commited_size, m->reserve); + size_t adjusted_to_boundary_commit = to_be_total_commited_size_clamped_to_reserve - m->commit; + MA_ASSERT(adjusted_to_boundary_commit && "Reached the virtual memory reserved boundary"); + *commit_size = adjusted_to_boundary_commit; + + if (adjusted_to_boundary_commit == 0) { + return 0; + } + uint8_t *result = m->data + m->commit; + return result; +} + +MA_API void MA_DeallocateStub(MA_Arena *arena, void *p) {} + +MA_API void MA_PopToPos(MA_Arena *arena, size_t pos) { + pos = MA_CLAMP_TOP(pos, arena->len); + arena->len = pos; +} + +MA_API void *MA_PopSize(MA_Arena *arena, size_t size) { + size = MA_CLAMP_TOP(size, arena->len); + arena->len -= size; + return arena->memory.data + arena->len; +} + +MA_API void MA_DeallocateArena(MA_Arena *arena) { + MV_Deallocate(&arena->memory); +} + +MA_API void MA_Reset(MA_Arena *arena) { + MA_PopToPos(arena, 0); +} + +MA_StaticFunc size_t MA__AlignLen(MA_Arena *a) { + size_t align_offset = a->alignment ? MA_GetAlignOffset((uintptr_t)a->memory.data + (uintptr_t)a->len, a->alignment) : 0; + size_t aligned = a->len + align_offset; + return aligned; +} + +MA_API void *MA__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_StaticFunc 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__ \ No newline at end of file diff --git a/arena.h b/arena.h index b91522b..a4d59e3 100644 --- a/arena.h +++ b/arena.h @@ -1,22 +1,3 @@ -/* -## 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 @@ -206,421 +187,4 @@ struct MA_Scratch { }; #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 +#endif // MA_HEADER \ No newline at end of file diff --git a/build.bat b/build.bat new file mode 100644 index 0000000..6b5f387 --- /dev/null +++ b/build.bat @@ -0,0 +1,23 @@ +@echo off +call ../misc/compile_setup.bat + +set DEBUG=-Od -RTC1 -D_DEBUG -MTd -fsanitize=address +set RELEASE=-O2 -MT -DNDEBUG -GL +set COMMON=-MP -FC -Z7 -GF -Gm- -Oi -Zo -EHa- -GR- +set WRN=-WX -W3 -wd4200 -diagnostics:column -nologo -D_CRT_SECURE_NO_WARNINGS +set LINK_DEBUG=-NODEFAULTLIB:LIBCMT +set LINK_RELEASE=-opt:ref -opt:icf -ltcg + +set DEBUG_LINE=%DEBUG% %WRN% %COMMON% -link -incremental:no %LINK_DEBUG% +set RELEASE_LINE=%RELEASE% %WRN% %COMMON% -link -incremental:no %LINK_RELEASE% + +mkdir build +cd build +cl.exe -Fe:cpp_debug.exe ../test/main.cpp %DEBUG_LINE% +cl.exe -Fe:cpp_release.exe ../test/main.cpp %RELEASE_LINE% +cl.exe -Fe:c_debug.exe ../test/main.c %DEBUG_LINE% +cl.exe -Fe:main_core_as_header.exe ../core.c ../test/main_core_as_header.cpp %DEBUG_LINE% +cd .. + +rem rtc1 - runtime error checks +rem gl - whole program optimizations diff --git a/clexer.c b/clexer.c new file mode 100644 index 0000000..dbc824b --- /dev/null +++ b/clexer.c @@ -0,0 +1,1878 @@ +#include "clexer.h" +#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__MemoryCopy + #include + #define CL__MemoryCopy(dst, src, s) memcpy(dst, src, s) +#endif + +#ifndef CL__MemoryZero + #include + #define CL__MemoryZero(p, size) memset(p, 0, size) +#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 + +#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; +} + +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; +} diff --git a/clexer.h b/clexer.h index bb7118b..1b4e0c9 100644 --- a/clexer.h +++ b/clexer.h @@ -498,1894 +498,3 @@ CL_INLINE CL_Token *CL_MatchIdentifier(CL_Tokens *tokens, char *str) { } #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 index 94ed34a..df58df3 100644 --- a/core.c +++ b/core.c @@ -1,34 +1,30 @@ #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 IO_VSNPRINTF stbsp_vsnprintf +#define IO_SNPRINTF stbsp_snprintf +#include "io.c" + #define MA_ASSERT(x) IO_Assert(x) -#include "arena.h" +#include "arena.c" -#define RE_IMPLEMENTATION #define RE_ASSERT(x) IO_Assert(x) -#include "regex.h" +#include "regex.c" -#define UTF_IMPLEMENTATION -#include "unicode.h" +#include "unicode.c" #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" +#include "string.c" -#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 index 910b91a..e6ce4a9 100644 --- a/core.h +++ b/core.h @@ -15,6 +15,7 @@ #ifdef __cplusplus #include "defer.hpp" #define TABLE_ASSERT IO_Assert + #define TABLE_ALLOCATOR_TYPE M_Allocator #define TABLE_SET_DEFAULT_ALLOCATOR \ if (!allocator.p) allocator = M_GetSystemAllocator(); #include "table.hpp" diff --git a/io.c b/io.c new file mode 100644 index 0000000..b360c3c --- /dev/null +++ b/io.c @@ -0,0 +1,222 @@ +#include "io.h" + +#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_StaticFunc + #if defined(__GNUC__) || defined(__clang__) + #define IO_StaticFunc __attribute__((unused)) static + #else + #define IO_StaticFunc static + #endif +#endif + +IO_StaticFunc int IO_Strlen(char *string) { + int len = 0; + while (*string++ != 0) len++; + return len; +} + +void (*IO_User_OutputMessage)(char *str, int len); + +IO_API bool IO__FatalErrorf(const char *file, int line, const char *msg, ...) { + va_list args1; + va_list args2; + char buff[2048]; + + va_start(args1, msg); + va_copy(args2, args1); + int size = IO_VSNPRINTF(buff, sizeof(buff), msg, args2); + va_end(args2); + + char *new_buffer = 0; + char *user_message = buff; + if (size >= sizeof(buff)) { + size += 4; + new_buffer = (char *)IO_ALLOCATE(size); + IO_VSNPRINTF(new_buffer, size, msg, args1); + user_message = new_buffer; + } + va_end(args1); + + IO_ErrorResult ret = IO_ErrorResult_Continue; + { + char buff2[2048]; + char *result = buff2; + char *b = 0; + int size = IO_SNPRINTF(buff2, sizeof(buff2), "%s(%d): error: %s \n", file, line, user_message); + if (size >= sizeof(buff2)) { + size += 4; + b = (char *)IO_ALLOCATE(size); + size = IO_SNPRINTF(b, size, "%s(%d): error: %s \n", file, line, user_message); + result = b; + } + + ret = IO_OutputError(result, size); + if (ret == IO_ErrorResult_Exit) { + IO_Exit(1); + } + + if (b) { + IO_FREE(b); + } + } + + if (new_buffer) { + IO_FREE(new_buffer); + } + + return ret == IO_ErrorResult_Break; +} + +IO_API void IO_Printf(const char *msg, ...) { + va_list args1; + va_list args2; + char buff[2048]; + + va_start(args1, msg); + va_copy(args2, args1); + int size = IO_VSNPRINTF(buff, sizeof(buff), msg, args2); + va_end(args2); + + char *new_buffer = 0; + char *result = buff; + if (size >= sizeof(buff)) { + size += 4; + new_buffer = (char *)IO_ALLOCATE(size); + IO_VSNPRINTF(new_buffer, size, msg, args1); + result = new_buffer; + } + va_end(args1); + + if (IO_User_OutputMessage) { + IO_User_OutputMessage(result, size); + } + else { + IO_OutputMessage(result, size); + } + + if (new_buffer) { + IO_FREE(new_buffer); + } +} + +IO_API bool IO__FatalError(char *msg) { + int len = IO_Strlen(msg); + IO_ErrorResult result = IO_OutputError(msg, len); + if (result == IO_ErrorResult_Exit) { + IO_Exit(1); + } + return result == IO_ErrorResult_Break; +} + +IO_API void IO_Print(char *msg) { + int len = IO_Strlen(msg); + if (IO_User_OutputMessage) { + IO_User_OutputMessage(msg, len); + } + else { + IO_OutputMessage(msg, len); + } +} +#ifdef _WIN32 + #ifndef NOMINMAX + #define NOMINMAX + #endif + #ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN + #endif + #include + + #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 diff --git a/io.h b/io.h index dd98691..12a98f9 100644 --- a/io.h +++ b/io.h @@ -2,6 +2,7 @@ #define IO_HEADER #include typedef enum IO_ErrorResult IO_ErrorResult; + #ifndef IO_API #ifdef __cplusplus #define IO_API extern "C" @@ -73,226 +74,3 @@ 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/multimedia.c b/multimedia.c new file mode 100644 index 0000000..1b49cc1 --- /dev/null +++ b/multimedia.c @@ -0,0 +1,1740 @@ +#include "multimedia.h" + +#ifndef MU_StaticFunc + #if defined(__GNUC__) || defined(__clang__) + #define MU_StaticFunc __attribute__((unused)) static + #else + #define MU_StaticFunc static + #endif +#endif + +#ifndef MU_PRIVATE_VAR + #define MU_PRIVATE_VAR MU_StaticFunc +#endif + +#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_StaticFunc void *MU_PushSize(MU_Arena *ar, size_t size); +MU_StaticFunc MU_UTF32Result MU_UTF16ToUTF32(uint16_t *c, int max_advance); +MU_StaticFunc MU_UTF8Result MU_UTF32ToUTF8(uint32_t codepoint); +MU_StaticFunc int64_t MU_CreateCharFromWidechar(char *buffer, int64_t buffer_size, wchar_t *in, int64_t inlen); +MU_StaticFunc void MU_WIN32_UpdateFocusedWindowBasedOnHandle(MU_Context *mu, HWND handle); +MU_StaticFunc void MU_WIN32_UpdateFocusedWindow(MU_Context *mu); +MU_StaticFunc void MU_Win32_GetWindowSize(HWND window, int *x, int *y); +MU_StaticFunc void MU_WIN32_GetWindowPos(HWND window, int *x, int *y); +MU_StaticFunc MU_Int2 MU_WIN32_GetMousePosition(HWND window); +MU_StaticFunc MU_Int2 MU_WIN32_GetMousePositionInverted(HWND window, int y); +MU_StaticFunc void MU_WIN32_CreateCanvas(MU_Window *window); +MU_StaticFunc void MU_WIN32_DestroyCanvas(MU_Window *window); +MU_StaticFunc void MU_WIN32_DrawCanvas(MU_Window *window); +MU_StaticFunc void MU__MemoryCopy(void *dst, const void *src, size_t size); +MU_StaticFunc void MU_PushDroppedFile(MU_Context *mu, MU_Window *window, char *str, int size); +MU_StaticFunc void MU_UpdateWindowState(MU_Window *window); +MU_StaticFunc int MU__AreStringsEqual(const char *src, const char *dst, size_t dstlen); +MU_StaticFunc void *MU_Win32_GLGetWindowProcAddressForGlad(const char *proc); +MU_StaticFunc void MU_WIN32_GetWGLFunctions(MU_Context *mu); +MU_StaticFunc void MU_WIN32_TryToInitGLContextForWindow(MU_Context *mu, MU_Win32_Window *w32_window); +MU_StaticFunc void MU_WIN32_DeinitSound(MU_Context *mu); +MU_StaticFunc void MU_WIN32_LoadCOM(MU_Context *mu); +MU_StaticFunc DWORD MU_WIN32_SoundThread(void *parameter); +MU_StaticFunc 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_StaticFunc 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_StaticFunc 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_StaticFunc 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_StaticFunc 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_StaticFunc 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_StaticFunc void MU_WIN32_UpdateFocusedWindow(MU_Context *mu) { + HWND handle = GetFocus(); + if (handle) { + MU_WIN32_UpdateFocusedWindowBasedOnHandle(mu, handle); + } +} + +MU_StaticFunc 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_StaticFunc void MU_WIN32_GetWindowPos(HWND window, int *x, int *y) { + POINT point = {0, 0}; + ClientToScreen(window, &point); + *x = point.x; + *y = point.y; +} + +MU_StaticFunc MU_Int2 MU_WIN32_GetMousePosition(HWND window) { + POINT p; + GetCursorPos(&p); + ScreenToClient(window, &p); + MU_Int2 result = {p.x, p.y}; + return result; +} + +MU_StaticFunc MU_Int2 MU_WIN32_GetMousePositionInverted(HWND window, int y) { + MU_Int2 result = MU_WIN32_GetMousePosition(window); + result.y = y - result.y; + return result; +} + +MU_StaticFunc 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_StaticFunc 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_StaticFunc 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_StaticFunc 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_StaticFunc 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_StaticFunc 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_StaticFunc 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_StaticFunc 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_StaticFunc 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_StaticFunc 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_StaticFunc 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_StaticFunc 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_StaticFunc 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_StaticFunc 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 \ No newline at end of file diff --git a/multimedia.h b/multimedia.h index 737a8e0..e070498 100644 --- a/multimedia.h +++ b/multimedia.h @@ -11,18 +11,6 @@ #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 @@ -382,1731 +370,3 @@ typedef struct 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/regex.c b/regex.c new file mode 100644 index 0000000..3fd3e4e --- /dev/null +++ b/regex.c @@ -0,0 +1,558 @@ +#include "regex.h" + +#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; +} diff --git a/regex.h b/regex.h index e11d685..31c636e 100644 --- a/regex.h +++ b/regex.h @@ -95,562 +95,3 @@ 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/string.c b/string.c new file mode 100644 index 0000000..015696b --- /dev/null +++ b/string.c @@ -0,0 +1,493 @@ +#include "string.h" +#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 + +#ifndef S8_StaticFunc + #if defined(__GNUC__) || defined(__clang__) + #define S8_StaticFunc __attribute__((unused)) static + #else + #define S8_StaticFunc static + #endif +#endif + +S8_StaticFunc int64_t S8__ClampTop(int64_t val, int64_t max) { + if (val > max) val = max; + return val; +} + +S8_API char CHAR_ToLowerCase(char a) { + if (a >= 'A' && a <= 'Z') a += 32; + return a; +} + +S8_API char CHAR_ToUpperCase(char a) { + if (a >= 'a' && a <= 'z') a -= 32; + return a; +} + +S8_API bool CHAR_IsWhitespace(char w) { + bool result = w == '\n' || w == ' ' || w == '\t' || w == '\v' || w == '\r'; + return result; +} + +S8_API bool CHAR_IsAlphabetic(char a) { + bool result = (a >= 'a' && a <= 'z') || (a >= 'A' && a <= 'Z'); + return result; +} + +S8_API bool CHAR_IsIdent(char a) { + bool result = (a >= 'a' && a <= 'z') || (a >= 'A' && a <= 'Z') || a == '_'; + return result; +} + +S8_API bool CHAR_IsDigit(char a) { + bool result = a >= '0' && a <= '9'; + return result; +} + +S8_API bool CHAR_IsAlphanumeric(char a) { + bool result = CHAR_IsDigit(a) || CHAR_IsAlphabetic(a); + return result; +} + +S8_API bool S8_AreEqual(S8_String a, S8_String b, unsigned ignore_case) { + if (a.len != b.len) return false; + for (int64_t i = 0; i < a.len; i++) { + char A = a.str[i]; + char B = b.str[i]; + if (ignore_case & S8_IGNORE_CASE) { + A = CHAR_ToLowerCase(A); + B = CHAR_ToLowerCase(B); + } + if (A != B) + return false; + } + return true; +} + +S8_API bool S8_EndsWith(S8_String a, S8_String end, unsigned ignore_case) { + S8_String a_end = S8_GetPostfix(a, end.len); + bool result = S8_AreEqual(end, a_end, ignore_case); + return result; +} + +S8_API bool S8_StartsWith(S8_String a, S8_String start, unsigned ignore_case) { + S8_String a_start = S8_GetPrefix(a, start.len); + bool result = S8_AreEqual(start, a_start, ignore_case); + return result; +} + +S8_API S8_String S8_Make(char *str, int64_t len) { + S8_String result; + result.str = (char *)str; + result.len = len; + return result; +} + +S8_API S8_String S8_Copy(S8_Allocator allocator, S8_String string) { + char *copy = (char *)S8_ALLOCATE(allocator, sizeof(char) * (string.len + 1)); + S8_MemoryCopy(copy, string.str, string.len); + copy[string.len] = 0; + S8_String result = S8_Make(copy, string.len); + return result; +} + +S8_API void S8_NormalizePath(S8_String s) { + for (int64_t i = 0; i < s.len; i++) { + if (s.str[i] == '\\') + s.str[i] = '/'; + } +} + +S8_API S8_String S8_Chop(S8_String string, int64_t len) { + len = S8__ClampTop(len, string.len); + S8_String result = S8_Make(string.str, string.len - len); + return result; +} + +S8_API S8_String S8_Skip(S8_String string, int64_t len) { + len = S8__ClampTop(len, string.len); + int64_t remain = string.len - len; + S8_String result = S8_Make(string.str + len, remain); + return result; +} + +S8_API bool S8_IsPointerInside(S8_String string, char *p) { + uintptr_t pointer = (uintptr_t)p; + uintptr_t start = (uintptr_t)string.str; + uintptr_t stop = start + (uintptr_t)string.len; + bool result = pointer >= start && pointer < stop; + return result; +} + +S8_API S8_String S8_SkipToP(S8_String string, char *p) { + if (S8_IsPointerInside(string, p)) { + S8_String result = S8_Make(p, p - string.str); + return result; + } + return string; +} + +S8_API S8_String S8_SkipPast(S8_String string, S8_String a) { + if (S8_IsPointerInside(string, a.str)) { + S8_String on_p = S8_Make(a.str, a.str - string.str); + S8_String result = S8_Skip(on_p, a.len); + return result; + } + return string; +} + +S8_API S8_String S8_GetPostfix(S8_String string, int64_t len) { + len = S8__ClampTop(len, string.len); + int64_t remain_len = string.len - len; + S8_String result = S8_Make(string.str + remain_len, len); + return result; +} + +S8_API S8_String S8_GetPrefix(S8_String string, int64_t len) { + len = S8__ClampTop(len, string.len); + S8_String result = S8_Make(string.str, len); + return result; +} + +S8_API S8_String S8_Slice(S8_String string, int64_t first_index, int64_t one_past_last_index) { + if (one_past_last_index < 0) one_past_last_index = string.len + one_past_last_index + 1; + if (first_index < 0) first_index = string.len + first_index; + S8_ASSERT(first_index < one_past_last_index && "S8_Slice, first_index is bigger then one_past_last_index"); + S8_ASSERT(string.len > 0 && "Slicing string of length 0! Might be an error!"); + S8_String result = string; + if (string.len > 0) { + if (one_past_last_index > first_index) { + first_index = S8__ClampTop(first_index, string.len - 1); + one_past_last_index = S8__ClampTop(one_past_last_index, string.len); + result.str += first_index; + result.len = one_past_last_index - first_index; + } + else { + result.len = 0; + } + } + return result; +} + +S8_API S8_String S8_Trim(S8_String string) { + if (string.len == 0) + return string; + + int64_t whitespace_begin = 0; + for (; whitespace_begin < string.len; whitespace_begin++) { + if (!CHAR_IsWhitespace(string.str[whitespace_begin])) { + break; + } + } + + int64_t whitespace_end = string.len; + for (; whitespace_end != whitespace_begin; whitespace_end--) { + if (!CHAR_IsWhitespace(string.str[whitespace_end - 1])) { + break; + } + } + + if (whitespace_begin == whitespace_end) { + string.len = 0; + } + else { + string = S8_Slice(string, whitespace_begin, whitespace_end); + } + + return string; +} + +S8_API S8_String S8_TrimEnd(S8_String string) { + int64_t whitespace_end = string.len; + for (; whitespace_end != 0; whitespace_end--) { + if (!CHAR_IsWhitespace(string.str[whitespace_end - 1])) { + break; + } + } + + S8_String result = S8_GetPrefix(string, whitespace_end); + return result; +} + +S8_API S8_String S8_ToLowerCase(S8_Allocator allocator, S8_String s) { + S8_String copy = S8_Copy(allocator, s); + for (int64_t i = 0; i < copy.len; i++) { + copy.str[i] = CHAR_ToLowerCase(copy.str[i]); + } + return copy; +} + +S8_API S8_String S8_ToUpperCase(S8_Allocator allocator, S8_String s) { + S8_String copy = S8_Copy(allocator, s); + for (int64_t i = 0; i < copy.len; i++) { + copy.str[i] = CHAR_ToUpperCase(copy.str[i]); + } + return copy; +} + +S8_API bool S8_Find(S8_String string, S8_String find, unsigned flags, int64_t *index_out) { + bool result = false; + if (flags & S8_MATCH_FIND_LAST) { + for (int64_t i = string.len; i != 0; i--) { + int64_t index = i - 1; + S8_String substring = S8_Slice(string, index, index + find.len); + if (S8_AreEqual(substring, find, flags)) { + if (index_out) + *index_out = index; + result = true; + break; + } + } + } + else { + for (int64_t i = 0; i < string.len; i++) { + S8_String substring = S8_Slice(string, i, i + find.len); + if (S8_AreEqual(substring, find, flags)) { + if (index_out) + *index_out = i; + result = true; + break; + } + } + } + + return result; +} + +S8_API S8_List S8_Split(S8_Allocator allocator, S8_String string, S8_String find, unsigned flags) { + S8_ASSERT((flags & S8_MATCH_FIND_LAST) == 0); + S8_List result = S8_MakeEmptyList(); + int64_t index = 0; + while (S8_Find(string, find, flags, &index)) { + S8_String before_match = S8_Make(string.str, index); + S8_AddNode(allocator, &result, before_match); + if (flags & S8_SPLIT_INCLUSIVE) { + S8_String match = S8_Make(string.str + index, find.len); + S8_AddNode(allocator, &result, match); + } + string = S8_Skip(string, index + find.len); + } + if (string.len) S8_AddNode(allocator, &result, string); + return result; +} + +S8_API S8_String S8_MergeWithSeparator(S8_Allocator allocator, S8_List list, S8_String separator) { + if (list.node_count == 0) return S8_MakeEmpty(); + if (list.char_count == 0) return S8_MakeEmpty(); + // S8_ASSERT((flags & S8_MATCH_FIND_LAST) == 0); + int64_t base_size = (list.char_count + 1); + int64_t sep_size = (list.node_count - 1) * separator.len; + int64_t size = base_size + sep_size; + char *buff = (char *)S8_ALLOCATE(allocator, sizeof(char) * size); + S8_String string = S8_Make(buff, 0); + for (S8_Node *it = list.first; it; it = it->next) { + S8_ASSERT(string.len + it->string.len <= size); + S8_MemoryCopy(string.str + string.len, it->string.str, it->string.len); + string.len += it->string.len; + if (it != list.last) { + S8_MemoryCopy(string.str + string.len, separator.str, separator.len); + string.len += separator.len; + } + } + S8_ASSERT(string.len == size - 1); + string.str[size] = 0; + return string; +} + +S8_API S8_String S8_Merge(S8_Allocator allocator, S8_List list) { + return S8_MergeWithSeparator(allocator, list, S8_Lit("")); +} + +S8_API S8_String S8_ReplaceAll(S8_Allocator allocator, S8_String string, S8_String replace, S8_String with, unsigned flags) { + S8_ASSERT((flags & S8_MATCH_FIND_LAST) == 0); + S8_List list = S8_Split(allocator, string, replace, flags | S8_SPLIT_INCLUSIVE); + for (S8_Node *it = list.first; it; it = it->next) { + if (S8_AreEqual(it->string, replace, flags)) { + S8_ReplaceNodeString(&list, it, with); + } + } + S8_String result = S8_Merge(allocator, list); + return result; +} + +S8_API S8_List S8_FindAll(S8_Allocator allocator, S8_String string, S8_String find, unsigned flags) { // @untested + S8_ASSERT((flags & S8_MATCH_FIND_LAST) == 0); + S8_List result = S8_MakeEmptyList(); + int64_t index = 0; + while (S8_Find(string, find, flags, &index)) { + S8_String match = S8_Make(string.str + index, find.len); + S8_AddNode(allocator, &result, match); + string = S8_Skip(string, index + find.len); + } + return result; +} + +S8_API S8_String S8_ChopLastSlash(S8_String s) { + S8_String result = s; + S8_Find(s, S8_Lit("/"), S8_MATCH_FIND_LAST, &result.len); + return result; +} + +S8_API S8_String S8_ChopLastPeriod(S8_String s) { + S8_String result = s; + S8_Find(s, S8_Lit("."), S8_MATCH_FIND_LAST, &result.len); + return result; +} + +S8_API S8_String S8_SkipToLastSlash(S8_String s) { + int64_t pos; + S8_String result = s; + if (S8_Find(s, S8_Lit("/"), S8_MATCH_FIND_LAST, &pos)) { + result = S8_Skip(result, pos + 1); + } + return result; +} + +S8_API S8_String S8_SkipToLastPeriod(S8_String s) { + int64_t pos; + S8_String result = s; + if (S8_Find(s, S8_Lit("."), S8_MATCH_FIND_LAST, &pos)) { + result = S8_Skip(result, pos + 1); + } + return result; +} + +S8_API int64_t S8_Length(char *string) { + int64_t len = 0; + while (*string++ != 0) + len++; + return len; +} + +S8_API int64_t S8_WideLength(wchar_t *string) { + int64_t len = 0; + while (*string++ != 0) + len++; + return len; +} + +S8_API S8_String S8_MakeFromChar(char *string) { + S8_String result; + result.str = (char *)string; + result.len = S8_Length(string); + return result; +} + +S8_API S8_String S8_MakeEmpty(void) { + return S8_Make(0, 0); +} + +S8_API S8_List S8_MakeEmptyList(void) { + S8_List result; + result.first = 0; + result.last = 0; + result.char_count = 0; + result.node_count = 0; + return result; +} + +S8_API S8_String S8_FormatV(S8_Allocator allocator, const char *str, va_list args1) { + va_list args2; + va_copy(args2, args1); + int64_t len = S8_VSNPRINTF(0, 0, str, args2); + va_end(args2); + + char *result = (char *)S8_ALLOCATE(allocator, sizeof(char) * (len + 1)); + S8_VSNPRINTF(result, (int)(len + 1), str, args1); + S8_String res = S8_Make(result, len); + return res; +} + +S8_API S8_String S8_Format(S8_Allocator allocator, const char *str, ...) { + S8_FORMAT(allocator, str, result); + return result; +} + +S8_API S8_Node *S8_CreateNode(S8_Allocator allocator, S8_String string) { + S8_Node *result = (S8_Node *)S8_ALLOCATE(allocator, sizeof(S8_Node)); + result->string = string; + result->next = 0; + return result; +} + +S8_API void S8_ReplaceNodeString(S8_List *list, S8_Node *node, S8_String new_string) { + list->char_count -= node->string.len; + list->char_count += new_string.len; + node->string = new_string; +} + +S8_API void S8_AddExistingNode(S8_List *list, S8_Node *node) { + if (list->first) { + list->last->next = node; + list->last = list->last->next; + } + else { + list->first = list->last = node; + } + list->node_count += 1; + list->char_count += node->string.len; +} + +S8_API void S8_AddArray(S8_Allocator allocator, S8_List *list, char **array, int count) { + for (int i = 0; i < count; i += 1) { + S8_String s = S8_MakeFromChar(array[i]); + S8_AddNode(allocator, list, s); + } +} + +S8_API void S8_AddArrayWithPrefix(S8_Allocator allocator, S8_List *list, char *prefix, char **array, int count) { + for (int i = 0; i < count; i += 1) { + S8_AddF(allocator, list, "%s%s", prefix, array[i]); + } +} + +S8_API S8_List S8_MakeList(S8_Allocator allocator, S8_String a) { + S8_List result = S8_MakeEmptyList(); + S8_AddNode(allocator, &result, a); + return result; +} + +S8_API S8_List S8_CopyList(S8_Allocator allocator, S8_List a) { + S8_List result = S8_MakeEmptyList(); + for (S8_Node *it = a.first; it; it = it->next) S8_AddNode(allocator, &result, it->string); + return result; +} + +S8_API S8_List S8_ConcatLists(S8_Allocator allocator, S8_List a, S8_List b) { + S8_List result = S8_MakeEmptyList(); + for (S8_Node *it = a.first; it; it = it->next) S8_AddNode(allocator, &result, it->string); + for (S8_Node *it = b.first; it; it = it->next) S8_AddNode(allocator, &result, it->string); + return result; +} + +S8_API S8_Node *S8_AddNode(S8_Allocator allocator, S8_List *list, S8_String string) { + S8_Node *node = S8_CreateNode(allocator, string); + S8_AddExistingNode(list, node); + return node; +} + +S8_API S8_String S8_AddF(S8_Allocator allocator, S8_List *list, const char *str, ...) { + S8_FORMAT(allocator, str, result); + S8_AddNode(allocator, list, result); + return result; +} diff --git a/string.h b/string.h index 38b6f29..3be747f 100644 --- a/string.h +++ b/string.h @@ -3,16 +3,6 @@ #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" @@ -127,490 +117,3 @@ S8_API S8_Node *S8_AddNode(S8_Allocator allocator, S8_List *list, S8_String stri 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/test/main.c b/test/main.c new file mode 100644 index 0000000..fc26919 --- /dev/null +++ b/test/main.c @@ -0,0 +1,5 @@ +#include "../core.c" +int main() { + MA_Arena arena = {0}; + return 0; +} \ No newline at end of file diff --git a/test/main.cpp b/test/main.cpp new file mode 100644 index 0000000..5f6face --- /dev/null +++ b/test/main.cpp @@ -0,0 +1,5 @@ +#include "../core.c" + +int main() { + MA_Arena arena = {}; +} \ No newline at end of file diff --git a/test/main_core_as_header.cpp b/test/main_core_as_header.cpp new file mode 100644 index 0000000..1a7145b --- /dev/null +++ b/test/main_core_as_header.cpp @@ -0,0 +1,8 @@ +#include "../core.h" + +int main() { + MA_Arena arena = {}; + int *a = MA_PushStruct(&arena, int); + *a = 10; + return 0; +} \ No newline at end of file diff --git a/unicode.c b/unicode.c new file mode 100644 index 0000000..8cc03ab --- /dev/null +++ b/unicode.c @@ -0,0 +1,210 @@ +#include "unicode.h" + +#ifndef UTF__MemoryZero + #include + #define UTF__MemoryZero(p, size) memset(p, 0, size) +#endif + +UTF_API UTF32_Result UTF_ConvertUTF16ToUTF32(uint16_t *c, int max_advance) { + UTF32_Result result; + UTF__MemoryZero(&result, sizeof(result)); + if (max_advance >= 1) { + result.advance = 1; + result.out_str = c[0]; + if (c[0] >= 0xD800 && c[0] <= 0xDBFF && c[1] >= 0xDC00 && c[1] <= 0xDFFF) { + if (max_advance >= 2) { + result.out_str = 0x10000; + result.out_str += (uint32_t)(c[0] & 0x03FF) << 10u | (c[1] & 0x03FF); + result.advance = 2; + } + else + result.error = 2; + } + } + else { + result.error = 1; + } + return result; +} + +UTF_API UTF8_Result UTF_ConvertUTF32ToUTF8(uint32_t codepoint) { + UTF8_Result result; + UTF__MemoryZero(&result, sizeof(result)); + + if (codepoint <= 0x7F) { + result.len = 1; + result.out_str[0] = (char)codepoint; + } + else if (codepoint <= 0x7FF) { + result.len = 2; + result.out_str[0] = 0xc0 | (0x1f & (codepoint >> 6)); + result.out_str[1] = 0x80 | (0x3f & codepoint); + } + else if (codepoint <= 0xFFFF) { // 16 bit word + result.len = 3; + result.out_str[0] = 0xe0 | (0xf & (codepoint >> 12)); // 4 bits + result.out_str[1] = 0x80 | (0x3f & (codepoint >> 6)); // 6 bits + result.out_str[2] = 0x80 | (0x3f & codepoint); // 6 bits + } + else if (codepoint <= 0x10FFFF) { // 21 bit word + result.len = 4; + result.out_str[0] = 0xf0 | (0x7 & (codepoint >> 18)); // 3 bits + result.out_str[1] = 0x80 | (0x3f & (codepoint >> 12)); // 6 bits + result.out_str[2] = 0x80 | (0x3f & (codepoint >> 6)); // 6 bits + result.out_str[3] = 0x80 | (0x3f & codepoint); // 6 bits + } + else { + result.error = 1; + } + + return result; +} + +UTF_API UTF32_Result UTF_ConvertUTF8ToUTF32(char *c, int max_advance) { + UTF32_Result result; + UTF__MemoryZero(&result, sizeof(result)); + + if ((c[0] & 0x80) == 0) { // Check if leftmost zero of first byte is unset + if (max_advance >= 1) { + result.out_str = c[0]; + result.advance = 1; + } + else result.error = 1; + } + + else if ((c[0] & 0xe0) == 0xc0) { + if ((c[1] & 0xc0) == 0x80) { // Continuation byte required + if (max_advance >= 2) { + result.out_str = (uint32_t)(c[0] & 0x1f) << 6u | (c[1] & 0x3f); + result.advance = 2; + } + else result.error = 2; + } + else result.error = 2; + } + + else if ((c[0] & 0xf0) == 0xe0) { + if ((c[1] & 0xc0) == 0x80 && (c[2] & 0xc0) == 0x80) { // Two continuation bytes required + if (max_advance >= 3) { + result.out_str = (uint32_t)(c[0] & 0xf) << 12u | (uint32_t)(c[1] & 0x3f) << 6u | (c[2] & 0x3f); + result.advance = 3; + } + else result.error = 3; + } + else result.error = 3; + } + + else if ((c[0] & 0xf8) == 0xf0) { + if ((c[1] & 0xc0) == 0x80 && (c[2] & 0xc0) == 0x80 && (c[3] & 0xc0) == 0x80) { // Three continuation bytes required + if (max_advance >= 4) { + result.out_str = (uint32_t)(c[0] & 0xf) << 18u | (uint32_t)(c[1] & 0x3f) << 12u | (uint32_t)(c[2] & 0x3f) << 6u | (uint32_t)(c[3] & 0x3f); + result.advance = 4; + } + else result.error = 4; + } + else result.error = 4; + } + else result.error = 4; + + return result; +} + +UTF_API UTF16_Result UTF_ConvertUTF32ToUTF16(uint32_t codepoint) { + UTF16_Result result; + UTF__MemoryZero(&result, sizeof(result)); + if (codepoint < 0x10000) { + result.out_str[0] = (uint16_t)codepoint; + result.out_str[1] = 0; + result.len = 1; + } + else if (codepoint <= 0x10FFFF) { + uint32_t code = (codepoint - 0x10000); + result.out_str[0] = (uint16_t)(0xD800 | (code >> 10)); + result.out_str[1] = (uint16_t)(0xDC00 | (code & 0x3FF)); + result.len = 2; + } + else { + result.error = 1; + } + + return result; +} + +#define UTF__HANDLE_DECODE_ERROR(question_mark) \ + { \ + if (outlen < buffer_size - 1) buffer[outlen++] = (question_mark); \ + break; \ + } + +UTF_API int64_t UTF_CreateCharFromWidechar(char *buffer, int64_t buffer_size, wchar_t *in, int64_t inlen) { + int64_t outlen = 0; + for (int64_t i = 0; i < inlen && in[i];) { + UTF32_Result decode = UTF_ConvertUTF16ToUTF32((uint16_t *)(in + i), (int)(inlen - i)); + if (!decode.error) { + i += decode.advance; + UTF8_Result encode = UTF_ConvertUTF32ToUTF8(decode.out_str); + if (!encode.error) { + for (int64_t j = 0; j < encode.len; j++) { + if (outlen < buffer_size - 1) { + buffer[outlen++] = encode.out_str[j]; + } + } + } + else UTF__HANDLE_DECODE_ERROR('?'); + } + else UTF__HANDLE_DECODE_ERROR('?'); + } + + buffer[outlen] = 0; + return outlen; +} + +UTF_API int64_t UTF_CreateWidecharFromChar(wchar_t *buffer, int64_t buffer_size, char *in, int64_t inlen) { + int64_t outlen = 0; + for (int64_t i = 0; i < inlen;) { + UTF32_Result decode = UTF_ConvertUTF8ToUTF32(in + i, (int)(inlen - i)); + if (!decode.error) { + i += decode.advance; + UTF16_Result encode = UTF_ConvertUTF32ToUTF16(decode.out_str); + if (!encode.error) { + for (int64_t j = 0; j < encode.len; j++) { + if (outlen < buffer_size - 1) { + buffer[outlen++] = encode.out_str[j]; + } + } + } + else UTF__HANDLE_DECODE_ERROR(0x003f); + } + else UTF__HANDLE_DECODE_ERROR(0x003f); + } + + buffer[outlen] = 0; + return outlen; +} + +UTF_API void UTF8_Advance(UTF8_Iter *iter) { + iter->i += iter->utf8_codepoint_byte_size; + UTF32_Result r = UTF_ConvertUTF8ToUTF32(iter->str + iter->i, iter->len - iter->i); + if (r.error) { + iter->item = 0; + return; + } + + iter->utf8_codepoint_byte_size = r.advance; + iter->item = r.out_str; +} + +UTF_API UTF8_Iter UTF8_IterateEx(char *str, int len) { + UTF8_Iter result; + UTF__MemoryZero(&result, sizeof(result)); + result.str = str; + result.len = len; + if (len) UTF8_Advance(&result); + return result; +} + +UTF_API UTF8_Iter UTF8_Iterate(char *str) { + int length = 0; + while (str[length]) length += 1; + return UTF8_IterateEx(str, length); +} diff --git a/unicode.h b/unicode.h index 7705ade..65887bd 100644 --- a/unicode.h +++ b/unicode.h @@ -6,14 +6,6 @@ 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" @@ -59,222 +51,4 @@ 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