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