From a75c8a2e4f98acb238c0e7714e854ce7a45ee1b8 Mon Sep 17 00:00:00 2001 From: Krzosa Karol Date: Wed, 19 Jun 2024 06:51:06 +0200 Subject: [PATCH] Init repo --- .gitignore | 6 + build.bat | 10 + build_file.cpp | 123 ++ src/build_tool/build_tool_main.cpp | 90 + src/build_tool/cache.cpp | 127 ++ src/build_tool/core/allocator.c | 115 ++ src/build_tool/core/allocator.h | 44 + src/build_tool/core/array.hpp | 270 +++ src/build_tool/core/cmd.c | 186 ++ src/build_tool/core/cmd.h | 37 + src/build_tool/core/core.c | 23 + src/build_tool/core/core.cpp | 1 + src/build_tool/core/core.h | 25 + src/build_tool/core/filesystem.c | 720 +++++++ src/build_tool/core/filesystem.h | 73 + src/build_tool/core/table.hpp | 204 ++ src/build_tool/easy_strings.cpp | 116 ++ src/build_tool/library.cpp | 55 + src/build_tool/process.cpp | 153 ++ src/build_tool/standalone_libraries/arena.c | 366 ++++ src/build_tool/standalone_libraries/arena.h | 179 ++ src/build_tool/standalone_libraries/clexer.c | 1549 +++++++++++++++ src/build_tool/standalone_libraries/clexer.h | 302 +++ src/build_tool/standalone_libraries/defer.hpp | 25 + src/build_tool/standalone_libraries/hash.c | 53 + src/build_tool/standalone_libraries/hash.h | 28 + src/build_tool/standalone_libraries/io.c | 236 +++ src/build_tool/standalone_libraries/io.h | 108 + .../standalone_libraries/linked_list.h | 119 ++ .../standalone_libraries/load_library.c | 26 + .../standalone_libraries/load_library.h | 12 + .../standalone_libraries/multimedia.c | 1740 +++++++++++++++++ .../standalone_libraries/multimedia.h | 370 ++++ .../standalone_libraries/preproc_env.h | 119 ++ src/build_tool/standalone_libraries/regex.c | 558 ++++++ src/build_tool/standalone_libraries/regex.h | 95 + .../standalone_libraries/stb_sprintf.c | 1669 ++++++++++++++++ .../standalone_libraries/stb_sprintf.h | 262 +++ src/build_tool/standalone_libraries/string.c | 566 ++++++ src/build_tool/standalone_libraries/string.h | 197 ++ src/build_tool/standalone_libraries/unicode.c | 210 ++ src/build_tool/standalone_libraries/unicode.h | 55 + src/pdf_browser/basic.h | 1581 +++++++++++++++ src/pdf_browser/main.cpp | 11 + src/pdf_browser/read_pdf.cpp | 78 + src/pdf_browser/win32.cpp | 27 + src/transcript_browser/filesystem.h | 34 + src/transcript_browser/main.cpp | 377 ++++ src/transcript_browser/tests.h | 80 + src/transcript_browser/win32.cpp | 206 ++ 50 files changed, 13616 insertions(+) create mode 100644 .gitignore create mode 100644 build.bat create mode 100644 build_file.cpp create mode 100644 src/build_tool/build_tool_main.cpp create mode 100644 src/build_tool/cache.cpp create mode 100644 src/build_tool/core/allocator.c create mode 100644 src/build_tool/core/allocator.h create mode 100644 src/build_tool/core/array.hpp create mode 100644 src/build_tool/core/cmd.c create mode 100644 src/build_tool/core/cmd.h create mode 100644 src/build_tool/core/core.c create mode 100644 src/build_tool/core/core.cpp create mode 100644 src/build_tool/core/core.h create mode 100644 src/build_tool/core/filesystem.c create mode 100644 src/build_tool/core/filesystem.h create mode 100644 src/build_tool/core/table.hpp create mode 100644 src/build_tool/easy_strings.cpp create mode 100644 src/build_tool/library.cpp create mode 100644 src/build_tool/process.cpp create mode 100644 src/build_tool/standalone_libraries/arena.c create mode 100644 src/build_tool/standalone_libraries/arena.h create mode 100644 src/build_tool/standalone_libraries/clexer.c create mode 100644 src/build_tool/standalone_libraries/clexer.h create mode 100644 src/build_tool/standalone_libraries/defer.hpp create mode 100644 src/build_tool/standalone_libraries/hash.c create mode 100644 src/build_tool/standalone_libraries/hash.h create mode 100644 src/build_tool/standalone_libraries/io.c create mode 100644 src/build_tool/standalone_libraries/io.h create mode 100644 src/build_tool/standalone_libraries/linked_list.h create mode 100644 src/build_tool/standalone_libraries/load_library.c create mode 100644 src/build_tool/standalone_libraries/load_library.h create mode 100644 src/build_tool/standalone_libraries/multimedia.c create mode 100644 src/build_tool/standalone_libraries/multimedia.h create mode 100644 src/build_tool/standalone_libraries/preproc_env.h create mode 100644 src/build_tool/standalone_libraries/regex.c create mode 100644 src/build_tool/standalone_libraries/regex.h create mode 100644 src/build_tool/standalone_libraries/stb_sprintf.c create mode 100644 src/build_tool/standalone_libraries/stb_sprintf.h create mode 100644 src/build_tool/standalone_libraries/string.c create mode 100644 src/build_tool/standalone_libraries/string.h create mode 100644 src/build_tool/standalone_libraries/unicode.c create mode 100644 src/build_tool/standalone_libraries/unicode.h create mode 100644 src/pdf_browser/basic.h create mode 100644 src/pdf_browser/main.cpp create mode 100644 src/pdf_browser/read_pdf.cpp create mode 100644 src/pdf_browser/win32.cpp create mode 100644 src/transcript_browser/filesystem.h create mode 100644 src/transcript_browser/main.cpp create mode 100644 src/transcript_browser/tests.h create mode 100644 src/transcript_browser/win32.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..90e54fd --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +x64/Debug +x64/Release +.vs/ + +src/external +build/ \ No newline at end of file diff --git a/build.bat b/build.bat new file mode 100644 index 0000000..9df5cfb --- /dev/null +++ b/build.bat @@ -0,0 +1,10 @@ +@echo off + +if not exist build\build_tool.exe ( + mkdir build + cd build + cl -Fe:build_tool.exe ../src/build_tool/build_tool_main.cpp -FC -WX -W3 -wd4200 -wd4244 -diagnostics:column -nologo -Zi + cd .. +) + +build\build_tool.exe \ No newline at end of file diff --git a/build_file.cpp b/build_file.cpp new file mode 100644 index 0000000..e93a931 --- /dev/null +++ b/build_file.cpp @@ -0,0 +1,123 @@ +#include "src/build_tool/library.cpp" + +int main() { + MA_InitScratch(); + SRC_InitCache(Perm, "pdf_browser.cache"); + + Array zlibfiles = {}; + zlibfiles.add("../src/external/zlib-1.3.1/adler32.c"); + zlibfiles.add("../src/external/zlib-1.3.1/compress.c"); + zlibfiles.add("../src/external/zlib-1.3.1/crc32.c"); + zlibfiles.add("../src/external/zlib-1.3.1/deflate.c"); + zlibfiles.add("../src/external/zlib-1.3.1/gzclose.c"); + zlibfiles.add("../src/external/zlib-1.3.1/gzlib.c"); + zlibfiles.add("../src/external/zlib-1.3.1/gzread.c"); + zlibfiles.add("../src/external/zlib-1.3.1/gzwrite.c"); + zlibfiles.add("../src/external/zlib-1.3.1/inflate.c"); + zlibfiles.add("../src/external/zlib-1.3.1/infback.c"); + zlibfiles.add("../src/external/zlib-1.3.1/inftrees.c"); + zlibfiles.add("../src/external/zlib-1.3.1/inffast.c"); + zlibfiles.add("../src/external/zlib-1.3.1/trees.c"); + zlibfiles.add("../src/external/zlib-1.3.1/uncompr.c"); + zlibfiles.add("../src/external/zlib-1.3.1/zutil.c"); + + S8_String zlibinc = "../src/external/zlib-1.3.1/"; + Array zlibobj = {}; + zlibobj.add("adler32.obj"); + zlibobj.add("compress.obj"); + zlibobj.add("crc32.obj"); + zlibobj.add("deflate.obj"); + zlibobj.add("gzclose.obj"); + zlibobj.add("gzlib.obj"); + zlibobj.add("gzread.obj"); + zlibobj.add("gzwrite.obj"); + zlibobj.add("inflate.obj"); + zlibobj.add("infback.obj"); + zlibobj.add("inftrees.obj"); + zlibobj.add("inffast.obj"); + zlibobj.add("trees.obj"); + zlibobj.add("uncompr.obj"); + zlibobj.add("zutil.obj"); + + Array pdfiofiles = {}; + pdfiofiles.add("../src/external/pdfio/pdfio-aes.c"); + pdfiofiles.add("../src/external/pdfio/pdfio-array.c"); + pdfiofiles.add("../src/external/pdfio/pdfio-common.c"); + pdfiofiles.add("../src/external/pdfio/pdfio-content.c"); + pdfiofiles.add("../src/external/pdfio/pdfio-crypto.c"); + pdfiofiles.add("../src/external/pdfio/pdfio-dict.c"); + pdfiofiles.add("../src/external/pdfio/pdfio-file.c"); + pdfiofiles.add("../src/external/pdfio/pdfio-md5.c"); + pdfiofiles.add("../src/external/pdfio/pdfio-object.c"); + pdfiofiles.add("../src/external/pdfio/pdfio-page.c"); + pdfiofiles.add("../src/external/pdfio/pdfio-rc4.c"); + pdfiofiles.add("../src/external/pdfio/pdfio-sha256.c"); + pdfiofiles.add("../src/external/pdfio/pdfio-stream.c"); + pdfiofiles.add("../src/external/pdfio/pdfio-string.c"); + pdfiofiles.add("../src/external/pdfio/pdfio-token.c"); + pdfiofiles.add("../src/external/pdfio/pdfio-value.c"); + pdfiofiles.add("../src/external/pdfio/ttf.c"); + + S8_String pdfio_inc = "../src/external/pdfio"; + Array pdfio_obj = {}; + pdfio_obj.add("pdfio-aes.obj"); + pdfio_obj.add("pdfio-array.obj"); + pdfio_obj.add("pdfio-common.obj"); + pdfio_obj.add("pdfio-content.obj"); + pdfio_obj.add("pdfio-crypto.obj"); + pdfio_obj.add("pdfio-dict.obj"); + pdfio_obj.add("pdfio-file.obj"); + pdfio_obj.add("pdfio-md5.obj"); + pdfio_obj.add("pdfio-object.obj"); + pdfio_obj.add("pdfio-page.obj"); + pdfio_obj.add("pdfio-rc4.obj"); + pdfio_obj.add("pdfio-sha256.obj"); + pdfio_obj.add("pdfio-stream.obj"); + pdfio_obj.add("pdfio-string.obj"); + pdfio_obj.add("pdfio-token.obj"); + pdfio_obj.add("pdfio-value.obj"); + pdfio_obj.add("ttf.obj"); + + if (!OS_FileExists(zlibobj[0])) { + Array cmd = {}; + cmd.add("cl.exe -c -nologo -Zi -MP -FC "); + cmd.add(S8_Format(Perm, "-I %.*s ", S8_Expand(zlibinc))); + cmd += zlibfiles; + cmd += pdfiofiles; + Run(cmd); + } + + int result = 0; + + if (0) { + Array cmd = {}; + cmd.add("cl.exe -nologo -Zi -MP -FC -Fe:pdf_browser.exe "); + cmd.add(S8_Format(Perm, "-I %.*s ", S8_Expand(zlibinc))); + cmd += zlibobj; + cmd.add(S8_Format(Perm, "-I %.*s ", S8_Expand(pdfio_inc))); + cmd += pdfio_obj; + cmd += "Advapi32.lib"; + cmd.add("../src/pdf_browser/main.cpp"); + cmd.add("../src/pdf_browser/win32.cpp"); + result += Run(cmd); + } + + Array cmd = {}; + cmd.add("cl.exe -nologo -Zi -MP -FC -Fe:transcript_browser.exe"); + cmd.add("-I ../src/external/raylib/include"); + cmd.add("-std:c++20"); + cmd.add("../src/transcript_browser/main.cpp"); + cmd.add("../src/transcript_browser/win32.cpp"); + cmd.add("../src/external/raylib/lib/raylib.lib"); + cmd.add("opengl32.lib kernel32.lib user32.lib gdi32.lib winmm.lib msvcrt.lib shell32.lib"); + cmd.add("/link /NODEFAULTLIB:LIBCMT"); + result += Run(cmd); + + if (result != 0) { + OS_DeleteFile("pdf_browser.cache"); + return result; + } + + SRC_SaveCache(); + return 0; +} \ No newline at end of file diff --git a/src/build_tool/build_tool_main.cpp b/src/build_tool/build_tool_main.cpp new file mode 100644 index 0000000..c534056 --- /dev/null +++ b/src/build_tool/build_tool_main.cpp @@ -0,0 +1,90 @@ +#include "library.cpp" + +int main(int argument_count, char **arguments) { + MA_InitScratch(); + OS_MakeDir(S8_Lit("build")); + OS_SetWorkingDir(S8_Lit("build")); + S8_String working_dir = OS_GetWorkingDir(Perm); + IO_Printf("WORKING DIR: %.*s\n", S8_Expand(working_dir)); + + Array cmd = CMD_Make(arguments, argument_count); + S8_String cc = CMD_Get(cmd, "cc", IF_WINDOWS("cl") IF_MAC("clang") IF_LINUX("gcc")); + + // Search for build file in the project directory + S8_String build_file = {}; + { + for (OS_FileIter it = OS_IterateFiles(Perm, S8_Lit("..")); OS_IsValid(it); OS_Advance(&it)) { + if (S8_Seek(it.filename, S8_Lit("build_file.c"), S8_IgnoreCase)) { + build_file = it.absolute_path; + } + } + + if (build_file.str == 0) { + IO_Printf("Couldnt find build file in current dir: %.*s, exiting ... \n", S8_Expand(working_dir)); + IO_Printf("- Proper build file contains 'build_file.c' in it's name\n"); + IO_Printf("- Alternative compiler can be chosen like this: bld cc=clang\n"); + return 0; + } + } + + SRC_InitCache(Perm, "build_tool.cache"); + S8_String name_no_ext = S8_GetNameNoExt(build_file); + S8_String exe_name = S8_Format(Perm, "%.*s.exe", S8_Expand(name_no_ext)); + + // Compile the build file only if code changed + if (SRC_WasModified(build_file, exe_name)) { + double time = OS_GetTime(); + int result = 0; + if (cc == "cl") { + Array flags = {MA_GetAllocator(Perm)}; + flags += "-nologo -Zi"; + flags += "-WX -W3 -wd4200 -diagnostics:column"; + flags += Fmt("/Fe:%.*s /Fd:%.*s.pdb", S8_Expand(exe_name), S8_Expand(exe_name)); + result = Run(cc + build_file + flags); + } else if (cc == "clang") { + Array flags = {MA_GetAllocator(Perm)}; + cc = "clang++"; + + flags += "-std=c++11 -g"; + flags += "-fdiagnostics-absolute-paths"; + flags += "-Wno-writable-strings"; + flags += "-lm"; + flags += Fmt("-o %.*s", S8_Expand(exe_name)); + result = Run(cc + build_file + flags); + } else { + IO_Assert(cc == "gcc"); + cc = "g++"; + + Array flags = {MA_GetAllocator(Perm)}; + flags += "-std=c++11 -g"; + flags += "-Wno-write-strings"; + flags += "-lm"; + flags += Fmt("-o %.*s", S8_Expand(exe_name)); + result = Run(cc + build_file + flags); + } + + if (result != 0) { + IO_Printf("FAILED compilation of the build file!\n"); + OS_DeleteFile("build_tool.cache"); + return 1; + } + time = OS_GetTime() - time; + IO_Printf("TIME Build file compilation: %f\n", time); + } + + // Run the build file + double time = OS_GetTime(); + if (build_file.str) { + exe_name = OS_GetAbsolutePath(Perm, exe_name); + int result = Run(exe_name + cmd); + if (result != 0) { + IO_Printf("FAILED execution of the build file, deleting cache!\n"); + OS_DeleteFile("build_tool.cache"); + return 1; + } + } + + SRC_SaveCache(); + time = OS_GetTime() - time; + IO_Printf("TIME total build file execution: %f\n", time); +} diff --git a/src/build_tool/cache.cpp b/src/build_tool/cache.cpp new file mode 100644 index 0000000..0902057 --- /dev/null +++ b/src/build_tool/cache.cpp @@ -0,0 +1,127 @@ +#define SRC_CACHE_ENTRY_COUNT 1024 + +struct SRC_CacheEntry { + uint64_t filepath_hash; + uint64_t file_hash; + uint64_t includes_hash; + uint64_t total_hash; +}; + +struct SRC_Cache { + int entry_cap; + int entry_len; + SRC_CacheEntry entries[SRC_CACHE_ENTRY_COUNT]; +}; + +double SRC_Time; +SRC_Cache *SRC_InMemoryCache; +SRC_Cache *SRC_FromFileCache; +S8_String SRC_CacheFilename; +CL_SearchPaths SRC_SearchPaths = {}; // @todo; + +void SRC_InitCache(MA_Arena *arena, S8_String cachefilename) { + SRC_CacheFilename = cachefilename; + + SRC_InMemoryCache = MA_PushStruct(arena, SRC_Cache); + SRC_InMemoryCache->entry_cap = SRC_CACHE_ENTRY_COUNT; + + SRC_FromFileCache = MA_PushStruct(arena, SRC_Cache); + SRC_FromFileCache->entry_cap = SRC_CACHE_ENTRY_COUNT; + + S8_String cache = OS_ReadFile(arena, SRC_CacheFilename); + if (cache.len) MA_MemoryCopy(SRC_FromFileCache, cache.str, cache.len); +} + +void SRC_SaveCache() { + S8_String dump = S8_Make((char *)SRC_InMemoryCache, sizeof(SRC_Cache)); + OS_WriteFile(SRC_CacheFilename, dump); +} + +SRC_CacheEntry *SRC_AddHash(uint64_t filepath, uint64_t file, uint64_t includes) { + IO_Assert(SRC_InMemoryCache->entry_len + 1 < SRC_InMemoryCache->entry_cap); + SRC_CacheEntry *result = SRC_InMemoryCache->entries + SRC_InMemoryCache->entry_len++; + result->filepath_hash = filepath; + result->file_hash = file; + result->includes_hash = includes; + result->total_hash = HashBytes(result, sizeof(uint64_t) * 3); + return result; +} + +SRC_CacheEntry *SRC_FindCache(SRC_Cache *cache, uint64_t filepath_hash) { + for (int cache_i = 0; cache_i < cache->entry_len; cache_i += 1) { + SRC_CacheEntry *it = cache->entries + cache_i; + if (it->filepath_hash == filepath_hash) { + return it; + } + } + return 0; +} + +SRC_CacheEntry *SRC_HashFile(S8_String file, char *parent_file) { + char *resolved_file = CL_ResolveFilepath(Perm, &SRC_SearchPaths, file.str, parent_file, false); + if (!resolved_file) { + IO_Printf("Failed to resolve file: %.*s\n", S8_Expand(file)); + return 0; + } + + uint64_t filepath_hash = HashBytes(resolved_file, S8_Length(resolved_file)); + SRC_CacheEntry *entry = SRC_FindCache(SRC_InMemoryCache, filepath_hash); + if (entry) return entry; + + S8_String filecontent = OS_ReadFile(Perm, S8_MakeFromChar(resolved_file)); + IO_Assert(filecontent.str); + + uint64_t file_hash = HashBytes(filecontent.str, filecontent.len); + uint64_t includes_hash = 13; + + CL_Lexer lexer = CL_Begin(Perm, filecontent.str, resolved_file); + lexer.select_includes = true; + + for (CL_Token token = CL_Next(&lexer); token.kind != CL_EOF; token = CL_Next(&lexer)) { + if (token.is_system_include) continue; + + S8_String file_it = S8_MakeFromChar(token.string_literal); + SRC_CacheEntry *cache = SRC_HashFile(file_it, resolved_file); + if (!cache) { + // error was reported already IO_Printf("Missing cache for: %.*s\n", S8_Expand(file_it)); + continue; + } + + includes_hash = HashMix(includes_hash, cache->total_hash); + } + + SRC_CacheEntry *result = SRC_AddHash(filepath_hash, file_hash, includes_hash); + return result; +} + +bool SRC_WasModified(S8_String file, S8_String artifact_path) { + double time_start = OS_GetTime(); + + if (OS_FileExists(file) == false) { + IO_Printf("FAILED File doesnt exist: %.*s\n", S8_Expand(file)); + exit(0); + } + if (OS_IsAbsolute(file) == false) { + file = OS_GetAbsolutePath(Perm, file); + } + + S8_String without_ext = S8_ChopLastPeriod(file); + S8_String name_only = S8_SkipToLastSlash(without_ext); + + if (artifact_path.len == 0) artifact_path = S8_Format(Perm, "%.*s.%s", S8_Expand(name_only), IF_WINDOWS_ELSE("obj", "o")); + bool modified = OS_FileExists(artifact_path) == false; + + SRC_CacheEntry *in_memory = SRC_HashFile(file, 0); + IO_Assert(in_memory); + + if (modified == false) { + SRC_CacheEntry *from_file = SRC_FindCache(SRC_FromFileCache, in_memory->filepath_hash); + if (from_file == 0 || (in_memory->total_hash != from_file->total_hash)) { + modified = true; + } + } + + SRC_Time = SRC_Time + (OS_GetTime() - time_start); + + return modified; +} \ No newline at end of file diff --git a/src/build_tool/core/allocator.c b/src/build_tool/core/allocator.c new file mode 100644 index 0000000..6b9cd65 --- /dev/null +++ b/src/build_tool/core/allocator.c @@ -0,0 +1,115 @@ +#ifndef MA_CMalloc + #include + #define MA_CMalloc(x) malloc(x) + #define MA_CFree(x) free(x) + #define MA_CRealloc(p, size) realloc(p, size) +#endif + +MA_API M_Allocator MA_BootstrapExclusive(void) { + MA_Arena bootstrap_arena = {0}; + MA_Arena *arena = MA_PushStruct(&bootstrap_arena, MA_Arena); + *arena = bootstrap_arena; + arena->base_len = arena->len; + return MA_GetExclusiveAllocator(arena); +} + +MA_API void *M__AllocNonZeroed(M_Allocator allocator, size_t size) { + void *p = allocator.p(allocator.obj, M_AllocatorOp_Allocate, NULL, size, 0); + return p; +} + +MA_API void *M__Alloc(M_Allocator allocator, size_t size) { + void *p = allocator.p(allocator.obj, M_AllocatorOp_Allocate, NULL, size, 0); + MA_MemoryZero(p, size); + return p; +} + +MA_API void *M__AllocCopy(M_Allocator allocator, void *p, size_t size) { + void *copy_buffer = M__AllocNonZeroed(allocator, size); + MA_MemoryCopy(copy_buffer, p, size); + return copy_buffer; +} + +MA_API void M__Dealloc(M_Allocator allocator, void *p) { + allocator.p(allocator.obj, M_AllocatorOp_Deallocate, p, 0, 0); +} + +MA_API void *M__Realloc(M_Allocator allocator, void *p, size_t size, size_t old_size) { + void *result = allocator.p(allocator.obj, M_AllocatorOp_Reallocate, p, size, old_size); + // @todo: add old_size? because we can't zero + return result; +} + +MA_StaticFunc void *M_ClibAllocatorProc(void *allocator, M_AllocatorOp kind, void *p, size_t size, size_t old_size) { + if (kind == M_AllocatorOp_Allocate) { + return MA_CMalloc(size); + } + + if (kind == M_AllocatorOp_Deallocate) { + MA_CFree(p); + return NULL; + } + + if (kind == M_AllocatorOp_Reallocate) { + return MA_CRealloc(p, size); + } + + MA_Assertf(0, "MA_Arena invalid codepath"); + return NULL; +} + +MA_API void *MA_AllocatorProc(void *allocator, M_AllocatorOp kind, void *p, size_t size, size_t old_size) { + if (kind == M_AllocatorOp_Allocate) { + return MA__PushSizeNonZeroed((MA_Arena *)allocator, size); + } + + else if (kind == M_AllocatorOp_Reallocate) { + void *new_p = MA__PushSizeNonZeroed((MA_Arena *)allocator, size); + MA_MemoryCopy(new_p, p, old_size); + return new_p; + } + + else if (kind == M_AllocatorOp_Deallocate) { + return NULL; + } + + MA_Assertf(0, "MA_Arena invalid codepath"); + return NULL; +} + +MA_API void *MA_ExclusiveAllocatorProc(void *allocator, M_AllocatorOp kind, void *p, size_t size, size_t old_size) { + MA_Arena *arena = (MA_Arena *)allocator; + if (kind == M_AllocatorOp_Reallocate) { + if (size > old_size) { + size_t size_to_push = size - old_size; + void *result = MA__PushSizeNonZeroed(arena, size_to_push); + if (!p) p = result; + return p; + } + } + + if (kind == M_AllocatorOp_Deallocate) { + MA_DeallocateArena(arena); + return NULL; + } + + MA_Assertf(0, "MA_Arena invalid codepath"); + return NULL; +} + +MA_API M_Allocator MA_GetExclusiveAllocator(MA_Arena *arena) { + M_Allocator allocator = {(int *)arena, MA_ExclusiveAllocatorProc}; + return allocator; +} + +MA_API M_Allocator MA_GetAllocator(MA_Arena *arena) { + M_Allocator allocator = {(int *)arena, MA_AllocatorProc}; + return allocator; +} + +MA_API M_Allocator M_GetSystemAllocator(void) { + M_Allocator allocator; + allocator.obj = 0; + allocator.p = M_ClibAllocatorProc; + return allocator; +} diff --git a/src/build_tool/core/allocator.h b/src/build_tool/core/allocator.h new file mode 100644 index 0000000..a525b74 --- /dev/null +++ b/src/build_tool/core/allocator.h @@ -0,0 +1,44 @@ +typedef struct M_Allocator M_Allocator; + +typedef enum M_AllocatorOp { + M_AllocatorOp_Invalid, + M_AllocatorOp_Allocate, + M_AllocatorOp_Deallocate, + M_AllocatorOp_Reallocate, +} M_AllocatorOp; + +typedef void *M_AllocatorProc(void *allocator, M_AllocatorOp kind, void *p, size_t size, size_t old_size); +MA_API void *MA_AllocatorProc(void *allocator, M_AllocatorOp kind, void *p, size_t size, size_t old_size); +MA_API void *MA_ExclusiveAllocatorProc(void *allocator, M_AllocatorOp kind, void *p, size_t size, size_t old_size); + +struct M_Allocator { + // it's int for type safety because C++ somehow allows this: + // struct Array { M_Allocator allocator; } + // Array = {arena}; WTF???!??!? + // without actually passing M_Allocator but just a pointer + int *obj; + M_AllocatorProc *p; +}; + +#define M_AllocStruct(a, T) (T *)M_Alloc((a), sizeof(T)) +#define M_AllocArray(a, T, c) (T *)M_Alloc((a), sizeof(T) * (c)) +#define M_AllocStructNonZeroed(a, T) (T *)M_AllocNonZeroed((a), sizeof(T)) +#define M_AllocArrayNonZeroed(a, T, c) (T *)M_AllocNonZeroed((a), sizeof(T) * (c)) +#define M_AllocStructCopy(a, T, p) (T *)M_PushCopy(a, (p), sizeof(T)) + +#define M_Alloc(a, size) M__Alloc(a, size) +#define M_AllocNonZeroed(a, size) M__AllocNonZeroed(a, size) +#define M_AllocCopy(a, p, size) M__AllocCopy(a, p, size) +#define M_Realloc(a, p, size, old_size) M__Realloc(a, p, size, old_size) +#define M_Dealloc(a, p) M__Dealloc(a, p) + +MA_API void *M__AllocNonZeroed(M_Allocator allocator, size_t size); +MA_API void *M__Alloc(M_Allocator allocator, size_t size); +MA_API void *M__AllocCopy(M_Allocator allocator, void *p, size_t size); +MA_API void *M__Realloc(M_Allocator allocator, void *p, size_t size, size_t old_size); +MA_API void M__Dealloc(M_Allocator allocator, void *p); + +MA_API M_Allocator M_GetSystemAllocator(void); +MA_API M_Allocator MA_GetExclusiveAllocator(MA_Arena *arena); +MA_API M_Allocator MA_GetAllocator(MA_Arena *arena); +MA_API M_Allocator MA_BootstrapExclusive(void); diff --git a/src/build_tool/core/array.hpp b/src/build_tool/core/array.hpp new file mode 100644 index 0000000..612bcf1 --- /dev/null +++ b/src/build_tool/core/array.hpp @@ -0,0 +1,270 @@ +// Iterating and removing elements +// +// ForArrayRemovable(array) { +// ForArrayRemovablePrepare(array); +// if (it == 4) ForArrayRemovableDeclare(); +// } +// +#define ForArrayRemovable(a) for (int __i = 0; __i < (a).len; __i += 1) +#define ForArrayRemovablePrepare(a) \ + auto &it = (a)[__i]; \ + bool remove_it = false; \ + defer { \ + if (remove_it) { \ + (a).ordered_remove(it); \ + __i -= 1; \ + } \ + } +#define ForArrayRemovableDeclare() (remove_it = true) +#define For2(it, array) for (auto &it : (array)) +#define For(array) For2(it, array) + +template +struct Array { + M_Allocator allocator; + T *data; + int cap, len; + + T &operator[](int index) { + IO_Assert(index >= 0 && index < len); + return data[index]; + } + + bool is_first(T &item) { return &item == first(); } + bool is_last(T &item) { return &item == last(); } + + bool contains(T &item) { + bool result = &item >= data && &item < data + len; + return result; + } + + int get_index(T &item) { + IO_Assert((data <= &item) && ((data + len) > &item)); + size_t offset = &item - data; + return (int)offset; + } + + void add(const T &item) { + try_growing(); + data[len++] = item; + } + + // Struct needs to have 'value_to_sort_by' field + void sorted_insert_decreasing(T item) { + int insert_index = -1; + For(*this) { + if (it.value_to_sort_by <= item.value_to_sort_by) { + insert_index = get_index(it); + insert(item, insert_index); + break; + } + } + + if (insert_index == -1) { + add(item); + } + } + + void bounded_add(T item) { + IO_Assert(len + 1 <= cap); + try_growing(); // in case of error + data[len++] = item; + } + + T *alloc(const T &item) { + try_growing(); + T *ref = data + len++; + *ref = item; + return ref; + } + + T *alloc() { + try_growing(); + T *ref = data + len++; + *ref = {}; + return ref; + } + + T *alloc_multiple(int size) { + try_growing_to_fit_item_count(size); + T *result = data + len; + len += size; + return result; + } + + void add_array(T *items, int item_count) { + for (int i = 0; i < item_count; i += 1) { + add(items[i]); + } + } + + void add_array(Array items) { + add_array(items.data, items.len); + } + + void reserve(int size) { + if (size > cap) { + if (!allocator.p) allocator = M_GetSystemAllocator(); + + void *p = M_Realloc(allocator, data, size * sizeof(T), cap * sizeof(T)); + IO_Assert(p); + + data = (T *)p; + cap = size; + } + } + + void init(M_Allocator allocator, int size) { + len = 0; + cap = 0; + data = 0; + this->allocator = allocator; + reserve(size); + } + + void reset() { + len = 0; + } + + T pop() { + IO_Assert(len > 0); + return data[--len]; + } + + void unordered_remove(T &item) { // DONT USE IN LOOPS !!!! + IO_Assert(len > 0); + IO_Assert(&item >= begin() && &item < end()); + item = data[--len]; + } + + void unordered_remove_index(int index) { + IO_Assert(index >= 0 && index < len); + data[index] = data[--len]; + } + + int get_index(const T &item) { + ptrdiff_t index = (ptrdiff_t)(&item - data); + IO_Assert(index >= 0 && index < len); + // IO_Assert(index > INT_MIN && index < INT_MAX); + return (int)index; + } + + void ordered_remove(T &item) { // DONT USE IN LOOPS !!! + IO_Assert(len > 0); + IO_Assert(&item >= begin() && &item < end()); + int index = get_index(item); + ordered_remove_index(index); + } + + void ordered_remove_index(int index) { + IO_Assert(index >= 0 && index < len); + int right_len = len - index - 1; + memmove(data + index, data + index + 1, right_len * sizeof(T)); + len -= 1; + } + + void insert(T item, int index) { + if (index == len) { + add(item); + return; + } + + IO_Assert(index < len); + IO_Assert(index >= 0); + + try_growing(); + int right_len = len - index; + memmove(data + index + 1, data + index, sizeof(T) * right_len); + data[index] = item; + len += 1; + } + + void dealloc() { + if (data) M_Dealloc(allocator, data); + data = 0; + len = cap = 0; + } + + Array copy(M_Allocator allocator) { + Array result = {}; + result.allocator = allocator; + result.reserve(cap); + + memmove(result.data, data, sizeof(T) * len); + result.len = len; + return result; + } + + Array tight_copy(M_Allocator allocator) { + Array result = {}; + result.allocator = allocator; + result.reserve(len); + + memmove(result.data, data, sizeof(T) * len); + result.len = len; + return result; + } + + T *first() { + IO_Assert(len > 0); + return data; + } + T *last() { + IO_Assert(len > 0); + return data + len - 1; + } + T *front() { + IO_Assert(len > 0); + return data; + } + T *back() { + IO_Assert(len > 0); + return data + len - 1; + } + T *begin() { return data; } + T *end() { return data + len; } + + // for (auto it = integers.begin(), end = integers.end(); it != end; ++it) + struct Reverse_Iter { + T *data; + Array *arr; + + Reverse_Iter operator++(int) { + Reverse_Iter ret = *this; + data -= 1; + return ret; + } + Reverse_Iter &operator++() { + data -= 1; + return *this; + } + + T &operator*() { return data[0]; } + T *operator->() { return data; } + + friend bool operator==(const Reverse_Iter &a, const Reverse_Iter &b) { return a.data == b.data; }; + friend bool operator!=(const Reverse_Iter &a, const Reverse_Iter &b) { return a.data != b.data; }; + + Reverse_Iter begin() { return Reverse_Iter{arr->end() - 1, arr}; } + Reverse_Iter end() { return Reverse_Iter{arr->begin() - 1, arr}; } + }; + + Reverse_Iter reverse() { return {end() - 1, this}; } + + void try_growing() { + if (len + 1 > cap) { + int new_size = cap * 2; + if (new_size < 16) new_size = 16; + + reserve(new_size); + } + } + + void try_growing_to_fit_item_count(int item_count) { + if (len + item_count > cap) { + int new_size = (cap + item_count) * 2; + if (new_size < 16) new_size = 16; + reserve(new_size); + } + } +}; diff --git a/src/build_tool/core/cmd.c b/src/build_tool/core/cmd.c new file mode 100644 index 0000000..68cc10a --- /dev/null +++ b/src/build_tool/core/cmd.c @@ -0,0 +1,186 @@ + +CmdParser MakeCmdParser(MA_Arena *arena, int argc, char **argv, const char *custom_help) { + CmdParser result = {argc, argv, arena, custom_help}; + return result; +} + +void AddBool(CmdParser *p, bool *result, const char *name, const char *help) { + CmdDecl *decl = MA_PushStruct(p->arena, CmdDecl); + decl->kind = CmdDeclKind_Bool; + decl->name = S8_MakeFromChar((char *)name); + decl->help = S8_MakeFromChar((char *)help); + decl->bool_result = result; + SLL_QUEUE_ADD(p->fdecl, p->ldecl, decl); +} + +void AddInt(CmdParser *p, int *result, const char *name, const char *help) { + CmdDecl *decl = MA_PushStruct(p->arena, CmdDecl); + decl->kind = CmdDeclKind_Int; + decl->name = S8_MakeFromChar((char *)name); + decl->help = S8_MakeFromChar((char *)help); + decl->int_result = result; + SLL_QUEUE_ADD(p->fdecl, p->ldecl, decl); +} + +void AddList(CmdParser *p, S8_List *result, const char *name, const char *help) { + CmdDecl *decl = MA_PushStruct(p->arena, CmdDecl); + decl->kind = CmdDeclKind_List; + decl->name = S8_MakeFromChar((char *)name); + decl->help = S8_MakeFromChar((char *)help); + decl->list_result = result; + SLL_QUEUE_ADD(p->fdecl, p->ldecl, decl); +} + +void AddEnum(CmdParser *p, int *result, const char *name, const char *help, const char **enum_options, int enum_option_count) { + CmdDecl *decl = MA_PushStruct(p->arena, CmdDecl); + decl->kind = CmdDeclKind_Enum; + decl->name = S8_MakeFromChar((char *)name); + decl->help = S8_MakeFromChar((char *)help); + decl->enum_result = result; + decl->enum_options = enum_options; + decl->enum_option_count = enum_option_count; + SLL_QUEUE_ADD(p->fdecl, p->ldecl, decl); +} + +CmdDecl *FindDecl(CmdParser *p, S8_String name) { + for (CmdDecl *it = p->fdecl; it; it = it->next) { + if (S8_AreEqual(it->name, name, true)) { + return it; + } + } + return NULL; +} + +S8_String StrEnumValues(MA_Arena *arena, CmdDecl *decl) { + S8_List list = {0}; + S8_AddF(arena, &list, "["); + for (int i = 0; i < decl->enum_option_count; i += 1) { + S8_AddF(arena, &list, "%s", decl->enum_options[i]); + if (i != decl->enum_option_count - 1) S8_AddF(arena, &list, "|"); + } + S8_AddF(arena, &list, "]"); + return S8_Merge(arena, list); +} + +void PrintCmdUsage(CmdParser *p) { + IO_Printf("%s\nCommands:\n", p->custom_help); + for (CmdDecl *it = p->fdecl; it; it = it->next) { + IO_Printf(" "); + if (it->kind == CmdDeclKind_List) { + S8_String example = S8_Format(p->arena, "-%.*s a b c", S8_Expand(it->name)); + IO_Printf("%-30.*s %.*s\n", S8_Expand(example), S8_Expand(it->help)); + } else if (it->kind == CmdDeclKind_Bool) { + S8_String example = S8_Format(p->arena, "-%.*s", S8_Expand(it->name)); + IO_Printf("%-30.*s %.*s\n", S8_Expand(example), S8_Expand(it->help)); + } else if (it->kind == CmdDeclKind_Enum) { + S8_String enum_vals = StrEnumValues(p->arena, it); + S8_String example = S8_Format(p->arena, "-%.*s %.*s", S8_Expand(it->name), S8_Expand(enum_vals)); + IO_Printf("%-30.*s %.*s\n", S8_Expand(example), S8_Expand(it->help)); + } else if (it->kind == CmdDeclKind_Int) { + S8_String example = S8_Format(p->arena, "-%.*s 8", S8_Expand(it->name)); + IO_Printf("%-30.*s %.*s\n", S8_Expand(example), S8_Expand(it->help)); + } else IO_Todo(); + } +} + +bool InvalidCmdArg(CmdParser *p, S8_String arg) { + IO_Printf("invalid command line argument: %.*s\n", S8_Expand(arg)); + return false; +} + +bool ParseCmd(MA_Arena *arg_arena, CmdParser *p) { + for (int i = 1; i < p->argc; i += 1) { + S8_String arg = S8_MakeFromChar(p->argv[i]); + + if (S8_AreEqual(arg, S8_Lit("--help"), true) || S8_AreEqual(arg, S8_Lit("-h"), true) || S8_AreEqual(arg, S8_Lit("-help"), true)) { + PrintCmdUsage(p); + return false; + } + + if (arg.str[0] == '-') { + arg = S8_Skip(arg, 1); + if (arg.str[0] == '-') { + arg = S8_Skip(arg, 1); + } + + CmdDecl *decl = FindDecl(p, arg); + if (!decl) return InvalidCmdArg(p, arg); + + if (decl->kind == CmdDeclKind_Bool) { + *decl->bool_result = !*decl->bool_result; + } else if (decl->kind == CmdDeclKind_Int) { + if (i + 1 >= p->argc) { + IO_Printf("expected at least 1 argument after %.*s\n", S8_Expand(arg)); + return false; + } + S8_String num = S8_MakeFromChar(p->argv[++i]); + for (int i = 0; i < num.len; i += 1) { + if (!CHAR_IsDigit(num.str[i])) { + IO_Printf("expected argument to be a number, got instead: %.*s", S8_Expand(num)); + return false; + } + } + int count = atoi(num.str); + decl->int_result[0] = count; + + } else if (decl->kind == CmdDeclKind_Enum) { + if (i + 1 >= p->argc) { + IO_Printf("expected at least 1 argument after %.*s\n", S8_Expand(arg)); + return false; + } + S8_String option_from_cmd = S8_MakeFromChar(p->argv[++i]); + + bool found_option = false; + for (int i = 0; i < decl->enum_option_count; i += 1) { + S8_String option = S8_MakeFromChar((char *)decl->enum_options[i]); + if (S8_AreEqual(option, option_from_cmd, true)) { + *decl->enum_result = i; + found_option = true; + break; + } + } + + if (!found_option) { + IO_Printf("expected one of the enum values: %.*s", S8_Expand(StrEnumValues(p->arena, decl))); + IO_Printf(" got instead: %.*s\n", S8_Expand(option_from_cmd)); + return false; + } + + } else if (decl->kind == CmdDeclKind_List) { + if (i + 1 >= p->argc) { + IO_Printf("expected at least 1 argument after %.*s\n", S8_Expand(arg)); + return false; + } + + i += 1; + for (int counter = 0; i < p->argc; i += 1, counter += 1) { + S8_String arg = S8_MakeFromChar(p->argv[i]); + if (arg.str[0] == '-') { + if (counter == 0) { + IO_Printf("expected at least 1 argument after %.*s\n", S8_Expand(arg)); + return false; + } + i -= 1; + break; + } + + S8_AddNode(arg_arena, decl->list_result, arg); + } + } else IO_Todo(); + + } else { + if (p->require_one_standalone_arg && p->args.node_count == 0) { + S8_AddNode(arg_arena, &p->args, arg); + } else { + return InvalidCmdArg(p, arg); + } + } + } + + if (p->require_one_standalone_arg && p->args.node_count == 0) { + PrintCmdUsage(p); + return false; + } + + return true; +} diff --git a/src/build_tool/core/cmd.h b/src/build_tool/core/cmd.h new file mode 100644 index 0000000..e45c5de --- /dev/null +++ b/src/build_tool/core/cmd.h @@ -0,0 +1,37 @@ + +typedef enum { + CmdDeclKind_Bool, + CmdDeclKind_Int, + CmdDeclKind_List, + CmdDeclKind_Enum, +} CmdDeclKind; + +typedef struct CmdDecl CmdDecl; +struct CmdDecl { + CmdDecl *next; + CmdDeclKind kind; + S8_String name; + S8_String help; + + bool *bool_result; + S8_List *list_result; + int *int_result; + + int *enum_result; + const char **enum_options; + int enum_option_count; +}; + +typedef struct CmdParser CmdParser; +struct CmdParser { + int argc; + char **argv; + MA_Arena *arena; + const char *custom_help; + + CmdDecl *fdecl; + CmdDecl *ldecl; + + bool require_one_standalone_arg; + S8_List args; +}; diff --git a/src/build_tool/core/core.c b/src/build_tool/core/core.c new file mode 100644 index 0000000..237749d --- /dev/null +++ b/src/build_tool/core/core.c @@ -0,0 +1,23 @@ +#include "core.h" +#include "../standalone_libraries/stb_sprintf.c" +#define IO_VSNPRINTF stbsp_vsnprintf +#define IO_SNPRINTF stbsp_snprintf +#include "../standalone_libraries/io.c" +#define MA_Assertf(x, ...) IO_Assertf(x, __VA_ARGS__) +#include "../standalone_libraries/arena.c" +#define RE_ASSERT(x) IO_Assert(x) +#include "../standalone_libraries/regex.c" +#include "../standalone_libraries/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 "../standalone_libraries/string.c" +#define MU_ASSERT IO_Assert +#include "../standalone_libraries/multimedia.h" +#include "../standalone_libraries/hash.c" +#include "../standalone_libraries/load_library.c" +#include "filesystem.c" + +#include "cmd.c" +#include "allocator.c" \ No newline at end of file diff --git a/src/build_tool/core/core.cpp b/src/build_tool/core/core.cpp new file mode 100644 index 0000000..962f92f --- /dev/null +++ b/src/build_tool/core/core.cpp @@ -0,0 +1 @@ +#include "core.c" \ No newline at end of file diff --git a/src/build_tool/core/core.h b/src/build_tool/core/core.h new file mode 100644 index 0000000..b60d7f9 --- /dev/null +++ b/src/build_tool/core/core.h @@ -0,0 +1,25 @@ +#ifndef FIRST_CORE_HEADER +#define FIRST_CORE_HEADER + +#include "../standalone_libraries/preproc_env.h" +#include "../standalone_libraries/stb_sprintf.h" +#include "../standalone_libraries/io.h" +#include "../standalone_libraries/arena.h" +#include "../standalone_libraries/unicode.h" +#include "../standalone_libraries/string.h" +#include "../standalone_libraries/hash.h" +#include "../standalone_libraries/linked_list.h" +#include "../standalone_libraries/regex.h" +#include "../standalone_libraries/multimedia.h" +#include "../standalone_libraries/load_library.h" +#include "filesystem.h" +#include "cmd.h" +#include "allocator.h" + +#if LANG_CPP + #include "../standalone_libraries/defer.hpp" + #include "table.hpp" + #include "array.hpp" +#endif + +#endif \ No newline at end of file diff --git a/src/build_tool/core/filesystem.c b/src/build_tool/core/filesystem.c new file mode 100644 index 0000000..ce933f9 --- /dev/null +++ b/src/build_tool/core/filesystem.c @@ -0,0 +1,720 @@ +#ifdef _WIN32 + #ifndef NOMINMAX + #define NOMINMAX + #endif + #ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN + #endif + #include + #include + #include + +OS_API bool OS_EnableTerminalColors(void) { + // Enable color terminal output + { + // Set output mode to handle virtual terminal sequences + HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); + if (hOut != INVALID_HANDLE_VALUE) { + DWORD dwMode = 0; + if (GetConsoleMode(hOut, &dwMode)) { + dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; + if (SetConsoleMode(hOut, dwMode)) { + return true; + } else { + IO_Printf("Failed to enable colored terminal output C\n"); + } + } else { + IO_Printf("Failed to enable colored terminal output B\n"); + } + } else { + IO_Printf("Failed to enable colored terminal output A\n"); + } + } + return false; +} + +OS_API bool OS_IsAbsolute(S8_String path) { + bool result = path.len > 3 && CHAR_IsAlphabetic(path.str[0]) && path.str[1] == ':' && path.str[2] == '/'; + return result; +} + +OS_API S8_String OS_GetExePath(MA_Arena *arena) { + wchar_t wbuffer[1024]; + DWORD wsize = GetModuleFileNameW(0, wbuffer, MA_Lengthof(wbuffer)); + IO_Assert(wsize != 0); + + S8_String path = S8_FromWidecharEx(arena, wbuffer, wsize); + S8_NormalizePathUnsafe(path); + return path; +} + +OS_API S8_String OS_GetExeDir(MA_Arena *arena) { + MA_Temp scratch = MA_GetScratch(); + S8_String path = OS_GetExePath(scratch.arena); + path = S8_ChopLastSlash(path); + path = S8_Copy(arena, path); + MA_ReleaseScratch(scratch); + return path; +} + +OS_API S8_String OS_GetWorkingDir(MA_Arena *arena) { + wchar_t wbuffer[1024]; + DWORD wsize = GetCurrentDirectoryW(MA_Lengthof(wbuffer), wbuffer); + IO_Assert(wsize != 0); + IO_Assert(wsize < 1022); + wbuffer[wsize++] = '/'; + wbuffer[wsize] = 0; + + S8_String path = S8_FromWidecharEx(arena, wbuffer, wsize); + S8_NormalizePathUnsafe(path); + return path; +} + +OS_API void OS_SetWorkingDir(S8_String path) { + wchar_t wpath[1024]; + UTF_CreateWidecharFromChar(wpath, MA_Lengthof(wpath), path.str, path.len); + SetCurrentDirectoryW(wpath); +} + +OS_API S8_String OS_GetAbsolutePath(MA_Arena *arena, S8_String relative) { + wchar_t wpath[1024]; + UTF_CreateWidecharFromChar(wpath, MA_Lengthof(wpath), relative.str, relative.len); + wchar_t wpath_abs[1024]; + DWORD written = GetFullPathNameW((wchar_t *)wpath, MA_Lengthof(wpath_abs), wpath_abs, 0); + if (written == 0) + return S8_MakeEmpty(); + S8_String path = S8_FromWidecharEx(arena, wpath_abs, written); + S8_NormalizePathUnsafe(path); + return path; +} + +OS_API bool OS_FileExists(S8_String path) { + wchar_t wbuff[1024]; + UTF_CreateWidecharFromChar(wbuff, MA_Lengthof(wbuff), path.str, path.len); + DWORD attribs = GetFileAttributesW(wbuff); + bool result = attribs == INVALID_FILE_ATTRIBUTES ? false : true; + return result; +} + +OS_API bool OS_IsDir(S8_String path) { + wchar_t wbuff[1024]; + UTF_CreateWidecharFromChar(wbuff, MA_Lengthof(wbuff), path.str, path.len); + DWORD dwAttrib = GetFileAttributesW(wbuff); + return dwAttrib != INVALID_FILE_ATTRIBUTES && (dwAttrib & FILE_ATTRIBUTE_DIRECTORY); +} + +OS_API bool OS_IsFile(S8_String path) { + wchar_t wbuff[1024]; + UTF_CreateWidecharFromChar(wbuff, MA_Lengthof(wbuff), path.str, path.len); + DWORD dwAttrib = GetFileAttributesW(wbuff); + bool is_file = (dwAttrib & FILE_ATTRIBUTE_DIRECTORY) == 0; + return dwAttrib != INVALID_FILE_ATTRIBUTES && is_file; +} + +OS_API double OS_GetTime(void) { + static int64_t counts_per_second; + if (counts_per_second == 0) { + LARGE_INTEGER freq; + QueryPerformanceFrequency(&freq); + counts_per_second = freq.QuadPart; + } + + LARGE_INTEGER time; + QueryPerformanceCounter(&time); + double result = (double)time.QuadPart / (double)counts_per_second; + return result; +} + +/* +User needs to copy particular filename to keep it. + +for (OS_FileIter it = OS_IterateFiles(it); OS_IsValid(iter); OS_Advance(it)) { +} + +*/ + +typedef struct OS_Win32_FileIter { + HANDLE handle; + WIN32_FIND_DATAW data; +} OS_Win32_FileIter; + +OS_API bool OS_IsValid(OS_FileIter it) { + return it.is_valid; +} + +OS_API void OS_Advance(OS_FileIter *it) { + while (FindNextFileW(it->w32->handle, &it->w32->data) != 0) { + WIN32_FIND_DATAW *data = &it->w32->data; + + // Skip '.' and '..' + if (data->cFileName[0] == '.' && data->cFileName[1] == '.' && data->cFileName[2] == 0) continue; + if (data->cFileName[0] == '.' && data->cFileName[1] == 0) continue; + + it->is_directory = data->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY; + it->filename = S8_FromWidecharEx(it->arena, data->cFileName, S8_WideLength(data->cFileName)); + const char *is_dir = it->is_directory ? "/" : ""; + const char *separator = it->path.str[it->path.len - 1] == '/' ? "" : "/"; + it->relative_path = S8_Format(it->arena, "%.*s%s%.*s%s", S8_Expand(it->path), separator, S8_Expand(it->filename), is_dir); + it->absolute_path = OS_GetAbsolutePath(it->arena, it->relative_path); + it->is_valid = true; + + if (it->is_directory) { + IO_Assert(it->relative_path.str[it->relative_path.len - 1] == '/'); + IO_Assert(it->absolute_path.str[it->absolute_path.len - 1] == '/'); + } + return; + } + + it->is_valid = false; + DWORD error = GetLastError(); + IO_Assert(error == ERROR_NO_MORE_FILES); + FindClose(it->w32->handle); +} + +OS_API OS_FileIter OS_IterateFiles(MA_Arena *scratch_arena, S8_String path) { + OS_FileIter it = {0}; + it.arena = scratch_arena; + it.path = path; + + S8_String modified_path = S8_Format(it.arena, "%.*s\\*", S8_Expand(path)); + wchar_t *wbuff = MA_PushArray(it.arena, wchar_t, modified_path.len + 1); + int64_t wsize = UTF_CreateWidecharFromChar(wbuff, modified_path.len + 1, modified_path.str, modified_path.len); + IO_Assert(wsize); + + it.w32 = MA_PushStruct(it.arena, OS_Win32_FileIter); + it.w32->handle = FindFirstFileW(wbuff, &it.w32->data); + if (it.w32->handle == INVALID_HANDLE_VALUE) { + it.is_valid = false; + return it; + } + + IO_Assert(it.w32->data.cFileName[0] == '.' && it.w32->data.cFileName[1] == 0); + OS_Advance(&it); + return it; +} + +OS_API OS_Result OS_MakeDir(S8_String path) { + wchar_t wpath[1024]; + UTF_CreateWidecharFromChar(wpath, MA_Lengthof(wpath), path.str, path.len); + BOOL success = CreateDirectoryW(wpath, NULL); + OS_Result result = OS_SUCCESS; + if (success == 0) { + DWORD error = GetLastError(); + if (error == ERROR_ALREADY_EXISTS) { + result = OS_ALREADY_EXISTS; + } else if (error == ERROR_PATH_NOT_FOUND) { + result = OS_PATH_NOT_FOUND; + } else { + IO_Assert(0); + } + } + return result; +} + +OS_API OS_Result OS_CopyFile(S8_String from, S8_String to, bool overwrite) { + wchar_t wfrom[1024]; + UTF_CreateWidecharFromChar(wfrom, MA_Lengthof(wfrom), from.str, from.len); + + wchar_t wto[1024]; + UTF_CreateWidecharFromChar(wto, MA_Lengthof(wto), to.str, to.len); + + BOOL fail_if_exists = !overwrite; + BOOL success = CopyFileW(wfrom, wto, fail_if_exists); + + OS_Result result = OS_SUCCESS; + if (success == FALSE) + result = OS_FAILURE; + return result; +} + +OS_API OS_Result OS_DeleteFile(S8_String path) { + wchar_t wpath[1024]; + UTF_CreateWidecharFromChar(wpath, MA_Lengthof(wpath), path.str, path.len); + BOOL success = DeleteFileW(wpath); + OS_Result result = OS_SUCCESS; + if (success == 0) + result = OS_PATH_NOT_FOUND; + return result; +} + +OS_API OS_Result OS_DeleteDir(S8_String path, unsigned flags) { + IO_Todo(); + return OS_FAILURE; + #if 0 + if (flags & OS_RECURSIVE) { + MA_Temp scratch = MA_GetScratch(); + S8_List list = OS_ListDir(scratch.arena, path, OS_RECURSIVE); + S8_Node *dirs_to_remove = 0; + for (S8_Node *it = list.first; it; it = it->next) { + if (!S8_EndsWith(it->string, S8_Lit("/"), S8_IgnoreCase)) { + OS_DeleteFile(it->string); + } + else { + S8_Node *node = S8_CreateNode(scratch.arena, it->string); + SLL_STACK_ADD(dirs_to_remove, node); + } + } + for (S8_Node *it = dirs_to_remove; it; it = it->next) { + OS_DeleteDir(it->string, OS_NO_FLAGS); + } + OS_Result result = OS_DeleteDir(path, OS_NO_FLAGS); + MA_ReleaseScratch(scratch); + return result; + } + else { + wchar_t wpath[1024]; + UTF_CreateWidecharFromChar(wpath, MA_Lengthof(wpath), path.str, path.len); + BOOL success = RemoveDirectoryW(wpath); + OS_Result result = OS_SUCCESS; + if (success == 0) + result = OS_PATH_NOT_FOUND; + return result; + } + #endif +} + +static OS_Result OS__WriteFile(S8_String path, S8_String data, bool append) { + wchar_t wpath[1024]; + UTF_CreateWidecharFromChar(wpath, MA_Lengthof(wpath), path.str, path.len); + OS_Result result = OS_FAILURE; + + DWORD access = GENERIC_WRITE; + DWORD creation_disposition = CREATE_ALWAYS; + if (append) { + access = FILE_APPEND_DATA; + creation_disposition = OPEN_ALWAYS; + } + + HANDLE handle = CreateFileW(wpath, access, 0, NULL, creation_disposition, FILE_ATTRIBUTE_NORMAL, NULL); + if (handle != INVALID_HANDLE_VALUE) { + DWORD bytes_written = 0; + IO_Assert(data.len == (DWORD)data.len); // @Todo: can only read 32 byte size files? + BOOL error = WriteFile(handle, data.str, (DWORD)data.len, &bytes_written, NULL); + if (error == TRUE) { + if (bytes_written == data.len) { + result = OS_SUCCESS; + } + } + CloseHandle(handle); + } else result = OS_PATH_NOT_FOUND; + + return result; +} + +OS_API OS_Result OS_AppendFile(S8_String path, S8_String string) { + return OS__WriteFile(path, string, true); +} + +OS_API OS_Result OS_WriteFile(S8_String path, S8_String string) { + return OS__WriteFile(path, string, false); +} + +OS_API S8_String OS_ReadFile(MA_Arena *arena, S8_String path) { + bool success = false; + S8_String result = S8_MakeEmpty(); + MA_Temp checkpoint = MA_BeginTemp(arena); + + wchar_t wpath[1024]; + UTF_CreateWidecharFromChar(wpath, MA_Lengthof(wpath), path.str, path.len); + HANDLE handle = CreateFileW(wpath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (handle != INVALID_HANDLE_VALUE) { + LARGE_INTEGER file_size; + if (GetFileSizeEx(handle, &file_size)) { + if (file_size.QuadPart != 0) { + result.len = (int64_t)file_size.QuadPart; + result.str = (char *)MA_PushSizeNonZeroed(arena, result.len + 1); + DWORD read; + if (ReadFile(handle, result.str, (DWORD)result.len, &read, NULL)) { // @todo: can only read 32 byte size files? + if (read == result.len) { + success = true; + result.str[result.len] = 0; + } + } + } + } + CloseHandle(handle); + } + + if (!success) { + result = S8_MakeEmpty(); + MA_EndTemp(checkpoint); + } + + return result; +} + +OS_API int64_t OS_GetFileModTime(S8_String file) { + FILETIME time = {0}; + WIN32_FIND_DATAW data; + + wchar_t wpath[1024]; + UTF_CreateWidecharFromChar(wpath, 1024, file.str, file.len); + HANDLE handle = FindFirstFileW(wpath, &data); + if (handle != INVALID_HANDLE_VALUE) { + FindClose(handle); + time = data.ftLastWriteTime; + } else { + return -1; + } + int64_t result = (int64_t)time.dwHighDateTime << 32 | time.dwLowDateTime; + return result; +} + +OS_API OS_Date OS_GetDate(void) { + SYSTEMTIME local; + GetLocalTime(&local); + + OS_Date result = {0}; + result.year = local.wYear; + result.month = local.wMonth; + result.day = local.wDay; + result.hour = local.wHour; + result.second = local.wSecond; + // result.milliseconds = local.wMilliseconds; + return result; +} + +#elif __linux__ || __APPLE__ || __unix__ + #include + #include + #include + #include + #include + + #if OS_MAC + #include + +OS_API S8_String OS_GetExePath(MA_Arena *arena) { + char buf[PATH_MAX]; + uint32_t bufsize = PATH_MAX; + if (_NSGetExecutablePath(buf, &bufsize)) { + return S8_MakeEmpty(); + } + + S8_String result = S8_Copy(arena, S8_MakeFromChar(buf)); + return result; +} + + #else + +OS_API S8_String OS_GetExePath(MA_Arena *arena) { + char buffer[PATH_MAX] = {}; + if (readlink("/proc/self/exe", buffer, PATH_MAX) == -1) { + return S8_MakeEmpty(); + } + S8_String result = S8_Copy(arena, S8_MakeFromChar(buffer)); + return result; +} + + #endif + +OS_API bool OS_EnableTerminalColors(void) { return true; } + +OS_API bool OS_IsAbsolute(S8_String path) { + bool result = path.len >= 1 && path.str[0] == '/'; + return result; +} + +OS_API S8_String OS_GetExeDir(MA_Arena *arena) { + MA_Temp scratch = MA_GetScratch(); + S8_String path = OS_GetExePath(scratch.arena); + S8_String dir = S8_ChopLastSlash(path); + S8_String copy = S8_Copy(arena, dir); + MA_ReleaseScratch(scratch); + return copy; +} + +OS_API S8_String OS_GetWorkingDir(MA_Arena *arena) { + char *buffer = (char *)MA_PushSizeNonZeroed(arena, PATH_MAX); + char *cwd = getcwd(buffer, PATH_MAX); + S8_String result = S8_MakeFromChar(cwd); + return result; +} + +OS_API void OS_SetWorkingDir(S8_String path) { + MA_Temp scratch = MA_GetScratch(); + S8_String copy = S8_Copy(scratch.arena, path); + chdir(copy.str); + MA_ReleaseScratch(scratch); +} + +OS_API S8_String OS_GetAbsolutePath(MA_Arena *arena, S8_String relative) { + MA_Temp scratch = MA_GetScratch1(arena); + S8_String copy = S8_Copy(scratch.arena, relative); + + char *buffer = (char *)MA_PushSizeNonZeroed(arena, PATH_MAX); + realpath((char *)copy.str, buffer); + S8_String result = S8_MakeFromChar(buffer); + + MA_ReleaseScratch(scratch); + return result; +} + +OS_API bool OS_FileExists(S8_String path) { + MA_Temp scratch = MA_GetScratch(); + S8_String copy = S8_Copy(scratch.arena, path); + + bool result = false; + if (access((char *)copy.str, F_OK) == 0) { + result = true; + } + + MA_ReleaseScratch(scratch); + return result; +} + +OS_API bool OS_IsDir(S8_String path) { + MA_Temp scratch = MA_GetScratch(); + S8_String copy = S8_Copy(scratch.arena, path); + + struct stat s; + if (stat(copy.str, &s) != 0) + return false; + + bool result = S_ISDIR(s.st_mode); + MA_ReleaseScratch(scratch); + return result; +} + +OS_API bool OS_IsFile(S8_String path) { + MA_Temp scratch = MA_GetScratch(); + S8_String copy = S8_Copy(scratch.arena, path); + + struct stat s; + if (stat(copy.str, &s) != 0) + return false; + bool result = S_ISREG(s.st_mode); + MA_ReleaseScratch(scratch); + return result; +} + +OS_API double OS_GetTime(void) { + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + uint64_t timeu64 = (((uint64_t)ts.tv_sec) * 1000000ull) + ((uint64_t)ts.tv_nsec) / 1000ull; + double timef = (double)timeu64; + double result = timef / 1000000.0; // Microseconds to seconds + return result; +} + +OS_API bool OS_IsValid(OS_FileIter it) { + return it.is_valid; +} + +OS_API void OS_Advance(OS_FileIter *it) { + struct dirent *file = 0; + while ((file = readdir((DIR *)it->dir)) != NULL) { + if (file->d_name[0] == '.' && file->d_name[1] == '.' && file->d_name[2] == 0) continue; + if (file->d_name[0] == '.' && file->d_name[1] == 0) continue; + + it->is_directory = file->d_type == DT_DIR; + it->filename = S8_CopyChar(it->arena, file->d_name); + + const char *dir_char_ending = it->is_directory ? "/" : ""; + const char *separator = it->path.str[it->path.len - 1] == '/' ? "" : "/"; + it->relative_path = S8_Format(it->arena, "%.*s%s%s%s", S8_Expand(it->path), separator, file->d_name, dir_char_ending); + it->absolute_path = OS_GetAbsolutePath(it->arena, it->relative_path); + if (it->is_directory) it->absolute_path = S8_Format(it->arena, "%.*s/", S8_Expand(it->absolute_path)); + it->is_valid = true; + return; + } + it->is_valid = false; + closedir((DIR *)it->dir); +} + +OS_API OS_FileIter OS_IterateFiles(MA_Arena *arena, S8_String path) { + OS_FileIter it = {0}; + it.arena = arena; + it.path = path = S8_Copy(arena, path); + it.dir = (void *)opendir((char *)path.str); + if (!it.dir) return it; + + OS_Advance(&it); + return it; +} + +OS_API OS_Result OS_MakeDir(S8_String path) { + MA_Temp scratch = MA_GetScratch(); + path = S8_Copy(scratch.arena, path); + int error = mkdir(path.str, 0755); + MA_ReleaseScratch(scratch); + return error == 0 ? OS_SUCCESS : OS_FAILURE; +} + +OS_API OS_Result OS_CopyFile(S8_String from, S8_String to, bool overwrite) { + const char *ow = overwrite ? "-n" : ""; + int result = OS_SystemF("cp %s %.*s %.*s", ow, S8_Expand(from), S8_Expand(to)); + return result == 0 ? OS_SUCCESS : OS_FAILURE; +} + +OS_API OS_Result OS_DeleteFile(S8_String path) { + int result = OS_SystemF("rm %.*s", S8_Expand(path)); + return result == 0 ? OS_SUCCESS : OS_FAILURE; +} + +OS_API OS_Result OS_DeleteDir(S8_String path, unsigned flags) { + IO_Assert(flags & OS_RECURSIVE); + int result = OS_SystemF("rm -r %.*s", S8_Expand(path)); + return result == 0 ? OS_SUCCESS : OS_FAILURE; +} + +OS_API int64_t OS_GetFileModTime(S8_String file) { + MA_Temp scratch = MA_GetScratch(); + file = S8_Copy(scratch.arena, file); + + struct stat attrib = {}; + stat(file.str, &attrib); + struct timespec ts = attrib.IF_LINUX_ELSE(st_mtim, st_mtimespec); + int64_t result = (((int64_t)ts.tv_sec) * 1000000ll) + ((int64_t)ts.tv_nsec) / 1000ll; + + MA_ReleaseScratch(scratch); + return result; +} + +OS_API OS_Date OS_GetDate(void) { + time_t t = time(NULL); + struct tm date = *localtime(&t); + + OS_Date s = {0}; + s.second = date.tm_sec; + s.year = date.tm_year; + s.month = date.tm_mon; + s.day = date.tm_mday; + s.hour = date.tm_hour; + s.minute = date.tm_min; + + return s; +} + +OS_API OS_Result OS_AppendFile(S8_String path, S8_String string) { + MA_Temp scratch = MA_GetScratch(); + path = S8_Copy(scratch.arena, path); + + OS_Result result = OS_FAILURE; + FILE *f = fopen((const char *)path.str, "a"); + if (f) { + result = OS_SUCCESS; + + size_t written = fwrite(string.str, 1, string.len, f); + if (written < string.len) { + result = OS_FAILURE; + } + + int error = fclose(f); + if (error != 0) { + result = OS_FAILURE; + } + } + + MA_ReleaseScratch(scratch); + return result; +} + +OS_API S8_String OS_ReadFile(MA_Arena *arena, S8_String path) { + S8_String result = {}; + + // ftell returns insane size if file is + // a directory **on some machines** KEKW + if (OS_IsDir(path)) { + return result; + } + + MA_Temp scratch = MA_GetScratch1(arena); + path = S8_Copy(scratch.arena, path); + + FILE *f = fopen(path.str, "rb"); + if (f) { + fseek(f, 0, SEEK_END); + result.len = ftell(f); + fseek(f, 0, SEEK_SET); + + result.str = (char *)MA_PushSizeNonZeroed(arena, result.len + 1); + fread(result.str, result.len, 1, f); + result.str[result.len] = 0; + + fclose(f); + } + + MA_ReleaseScratch(scratch); + return result; +} + +OS_API OS_Result OS_WriteFile(S8_String path, S8_String string) { + MA_Temp scratch = MA_GetScratch(); + path = S8_Copy(scratch.arena, path); + + OS_Result result = OS_FAILURE; + FILE *f = fopen((const char *)path.str, "w"); + if (f) { + result = OS_SUCCESS; + + size_t written = fwrite(string.str, 1, string.len, f); + if (written < string.len) { + result = OS_FAILURE; + } + + int error = fclose(f); + if (error != 0) { + result = OS_FAILURE; + } + } + + MA_ReleaseScratch(scratch); + return result; +} +#endif + +#if _WIN32 || __linux__ || __APPLE__ || __unix__ +OS_API int OS_SystemF(const char *string, ...) { + MA_Temp scratch = MA_GetScratch(); + S8_FORMAT(scratch.arena, string, result); + IO_Printf("Executing: %.*s\n", S8_Expand(result)); + fflush(stdout); + int error_code = system(result.str); + MA_ReleaseScratch(scratch); + return error_code; +} + +OS_API bool OS_ExpandIncludesList(MA_Arena *arena, S8_List *out, S8_String filepath) { + S8_String c = OS_ReadFile(arena, filepath); + if (c.str == 0) return false; + S8_String path = S8_ChopLastSlash(filepath); + S8_String include = S8_Lit("#include \""); + for (;;) { + int64_t idx = -1; + if (S8_Seek(c, include, 0, &idx)) { + S8_String str_to_add = S8_GetPrefix(c, idx); + S8_AddNode(arena, out, str_to_add); + S8_String save = c; + c = S8_Skip(c, idx + include.len); + + S8_String filename = c; + filename.len = 0; + while (filename.str[filename.len] != '"' && filename.len < c.len) { + filename.len += 1; + } + + c = S8_Skip(c, filename.len + 1); + S8_String inc_path = S8_Format(arena, "%.*s/%.*s", S8_Expand(path), S8_Expand(filename)); + if (!OS_ExpandIncludesList(arena, out, inc_path)) { + S8_String s = S8_GetPrefix(save, save.len - c.len); + S8_AddNode(arena, out, s); + } + } else { + S8_AddNode(arena, out, c); + break; + } + } + return true; +} + +OS_API S8_String OS_ExpandIncludes(MA_Arena *arena, S8_String filepath) { + S8_List out = S8_MakeEmptyList(); + S8_String result = S8_MakeEmpty(); + MA_ScratchScope(s) { + OS_ExpandIncludesList(s.arena, &out, filepath); + result = S8_Merge(arena, out); + } + return result; +} +#endif \ No newline at end of file diff --git a/src/build_tool/core/filesystem.h b/src/build_tool/core/filesystem.h new file mode 100644 index 0000000..b1c158a --- /dev/null +++ b/src/build_tool/core/filesystem.h @@ -0,0 +1,73 @@ +// Quick and dirty filesystem operations + +#ifndef OS_API + #define OS_API +#endif + +typedef enum OS_Result { + OS_SUCCESS, + OS_ALREADY_EXISTS, + OS_PATH_NOT_FOUND, + OS_FAILURE, +} OS_Result; + +enum { + OS_NO_FLAGS = 0, + OS_RECURSIVE = 1, + OS_RELATIVE_PATHS = 2, +}; + +typedef struct OS_Date OS_Date; +struct OS_Date { + uint32_t year; + uint32_t month; + uint32_t day; + uint32_t hour; + uint32_t minute; + uint32_t second; +}; + +typedef struct OS_FileIter OS_FileIter; +struct OS_FileIter { + bool is_valid; + bool is_directory; + S8_String absolute_path; + S8_String relative_path; + S8_String filename; + + S8_String path; + MA_Arena *arena; + union { + struct OS_Win32_FileIter *w32; + void *dir; + }; +}; + +OS_API bool OS_IsAbsolute(S8_String path); +OS_API S8_String OS_GetExePath(MA_Arena *arena); +OS_API S8_String OS_GetExeDir(MA_Arena *arena); +OS_API S8_String OS_GetWorkingDir(MA_Arena *arena); +OS_API void OS_SetWorkingDir(S8_String path); +OS_API S8_String OS_GetAbsolutePath(MA_Arena *arena, S8_String relative); +OS_API bool OS_FileExists(S8_String path); +OS_API bool OS_IsDir(S8_String path); +OS_API bool OS_IsFile(S8_String path); +OS_API double OS_GetTime(void); +OS_API OS_Result OS_MakeDir(S8_String path); +OS_API OS_Result OS_CopyFile(S8_String from, S8_String to, bool overwrite); +OS_API OS_Result OS_DeleteFile(S8_String path); +OS_API OS_Result OS_DeleteDir(S8_String path, unsigned flags); +OS_API OS_Result OS_AppendFile(S8_String path, S8_String string); +OS_API OS_Result OS_WriteFile(S8_String path, S8_String string); +OS_API S8_String OS_ReadFile(MA_Arena *arena, S8_String path); +OS_API int OS_SystemF(const char *string, ...); +OS_API int64_t OS_GetFileModTime(S8_String file); +OS_API OS_Date OS_GetDate(void); +OS_API S8_String UTF_CreateStringFromWidechar(MA_Arena *arena, wchar_t *wstr, int64_t wsize); +OS_API bool OS_ExpandIncludesList(MA_Arena *arena, S8_List *out, S8_String filepath); +OS_API S8_String OS_ExpandIncludes(MA_Arena *arena, S8_String filepath); +OS_API bool OS_EnableTerminalColors(void); + +OS_API bool OS_IsValid(OS_FileIter it); +OS_API void OS_Advance(OS_FileIter *it); +OS_API OS_FileIter OS_IterateFiles(MA_Arena *scratch_arena, S8_String path); \ No newline at end of file diff --git a/src/build_tool/core/table.hpp b/src/build_tool/core/table.hpp new file mode 100644 index 0000000..374a803 --- /dev/null +++ b/src/build_tool/core/table.hpp @@ -0,0 +1,204 @@ +/* + Hash table implementation: + Pointers to values + Open adressing + Linear Probing + Power of 2 + Robin Hood hashing + Resizes on high probe count (min max load factor) + + Hash 0 is reserved for empty hash table entry +*/ + +template +struct Table { + struct Entry { + uint64_t hash; + uint64_t key; + size_t distance; + Value value; + }; + + M_Allocator allocator; + size_t len, cap; + Entry *values; + + static const size_t max_load_factor = 80; + static const size_t min_load_factor = 50; + static const size_t significant_distance = 8; + + // load factor calculation was rearranged + // to get rid of division: + //> 100 * len / cap = load_factor + //> len * 100 = load_factor * cap + inline bool reached_load_factor(size_t lfactor) { + return (len + 1) * 100 >= lfactor * cap; + } + + inline bool is_empty(Entry *entry) { return entry->hash == 0; } + inline bool is_occupied(Entry *entry) { return entry->hash != 0; } + + void reserve(size_t size) { + IO_Assert(size > cap && "New size is smaller then original size"); + IO_Assert(MA_IS_POW2(size)); + if (!allocator.p) allocator = M_GetSystemAllocator(); + + Entry *old_values = values; + size_t old_cap = cap; + + values = (Entry *)M_Alloc(allocator, sizeof(Entry) * size); + for (int i = 0; i < size; i += 1) values[i] = {}; + cap = size; + + IO_Assert(!(old_values == 0 && len != 0)); + if (len == 0) { + if (old_values) M_Dealloc(allocator, old_values); + return; + } + + len = 0; + for (size_t i = 0; i < old_cap; i += 1) { + Entry *it = old_values + i; + if (is_occupied(it)) { + insert(it->key, it->value); + } + } + M_Dealloc(allocator, old_values); + } + + Entry *get_table_entry(uint64_t key) { + if (len == 0) return 0; + uint64_t hash = HashBytes(&key, sizeof(key)); + if (hash == 0) hash += 1; + uint64_t index = WRAP_AROUND_POWER_OF_2(hash, cap); + uint64_t i = index; + uint64_t distance = 0; + for (;;) { + Entry *it = values + i; + if (distance > it->distance) { + return 0; + } + + if (it->hash == hash && it->key == key) { + return it; + } + + distance += 1; + i = WRAP_AROUND_POWER_OF_2(i + 1, cap); + if (i == index) return 0; + } + IO_Assert(!"Invalid codepath"); + } + + void insert(uint64_t key, const Value &value) { + if (reached_load_factor(max_load_factor)) { + if (cap == 0) cap = 16; // 32 cause cap*2 + reserve(cap * 2); + } + + uint64_t hash = HashBytes(&key, sizeof(key)); + if (hash == 0) hash += 1; + uint64_t index = WRAP_AROUND_POWER_OF_2(hash, cap); + uint64_t i = index; + Entry to_insert = {hash, key, 0, value}; + for (;;) { + Entry *it = values + i; + if (is_empty(it)) { + *it = to_insert; + len += 1; + // If we have more then 8 consecutive items we try to resize + if (to_insert.distance > 8 && reached_load_factor(min_load_factor)) { + reserve(cap * 2); + } + return; + } + if (it->hash == hash && it->key == key) { + *it = to_insert; + // If we have more then 8 consecutive items we try to resize + if (to_insert.distance > 8 && reached_load_factor(min_load_factor)) { + reserve(cap * 2); + } + return; + } + + // Robin hood hashing + if (to_insert.distance > it->distance) { + Entry temp = to_insert; + to_insert = *it; + *it = temp; + } + + to_insert.distance += 1; + i = WRAP_AROUND_POWER_OF_2(i + 1, cap); + IO_Assert(i != index && "Did a full 360 through a hash table, no good :( that shouldnt be possible"); + } + IO_Assert(!"Invalid codepath"); + } + + void remove(uint64_t key) { + Entry *entry = get_table_entry(key); + entry->hash = 0; + entry->distance = 0; + len -= 1; + } + + Value *get(uint64_t key) { + Entry *v = get_table_entry(key); + if (!v) return 0; + return &v->value; + } + + Value get(uint64_t key, Value default_value) { + Entry *v = get_table_entry(key); + if (!v) return default_value; + return v->value; + } + + Value *gets(char *str) { + int len = S8_Length(str); + uint64_t hash = HashBytes(str, len); + return get(hash); + } + + Value gets(char *str, Value default_value) { + int len = S8_Length(str); + uint64_t hash = HashBytes(str, len); + return get(hash, default_value); + } + + Value *get(S8_String s) { + uint64_t hash = HashBytes(s.str, (unsigned)s.len); + return get(hash); + } + + Value get(S8_String s, Value default_value) { + uint64_t hash = HashBytes(s.str, (unsigned)s.len); + return get(hash, default_value); + } + + void put(S8_String s, const Value &value) { + uint64_t hash = HashBytes(s.str, (unsigned)s.len); + insert(hash, value); + } + + void puts(char *str, const Value &value) { + int len = S8_Length(str); + uint64_t hash = HashBytes(str, len); + insert(hash, value); + } + + void reset() { + len = 0; + for (size_t i = 0; i < cap; i += 1) { + Entry *it = values + i; + it->hash = 0; + } + } + + void dealloc() { + M_Dealloc(allocator, values); + len = 0; + cap = 0; + values = 0; + } +}; diff --git a/src/build_tool/easy_strings.cpp b/src/build_tool/easy_strings.cpp new file mode 100644 index 0000000..12a698e --- /dev/null +++ b/src/build_tool/easy_strings.cpp @@ -0,0 +1,116 @@ +S8_String Fmt(const char *str, ...) S8__PrintfFormat(1, 2); + +Array operator+(Array a, Array b) { + Array c = a.copy(MA_GetAllocator(Perm)); + c.add_array(b); + return c; +} + +Array operator+(Array a, S8_String b) { + Array c = a.copy(MA_GetAllocator(Perm)); + c.add(b); + return c; +} + +Array operator+(S8_String a, Array b) { + Array c = b.copy(MA_GetAllocator(Perm)); + c.insert(a, 0); + return c; +} + +Array operator+(S8_String a, S8_String b) { + Array c = {MA_GetAllocator(Perm)}; + c.add(a); + c.add(b); + return c; +} + +Array &operator+=(Array &a, Array b) { + a.add_array(b); + return a; +} + +Array &operator+=(Array &a, S8_String s) { + a.add(s); + return a; +} + +//@todo: split on any whitespace instead! +Array Split(S8_String s, S8_String sep = " ") { + S8_List list = S8_Split(Perm, s, sep, 0); + Array result = {MA_GetAllocator(Perm)}; + S8_For(it, list) result.add(it->string); + return result; +} + +S8_String Merge(MA_Arena *arena, Array list, S8_String separator = " "_s) { + int64_t char_count = 0; + For(list) char_count += it.len; + if (char_count == 0) return {}; + int64_t node_count = list.len; + + int64_t base_size = (char_count + 1); + int64_t sep_size = (node_count - 1) * separator.len; + int64_t size = base_size + sep_size; + char *buff = (char *)MA_PushSize(arena, sizeof(char) * (size + 1)); + S8_String string = S8_Make(buff, 0); + For(list) { + IO_Assert(string.len + it.len <= size); + MA_MemoryCopy(string.str + string.len, it.str, it.len); + string.len += it.len; + if (!list.is_last(it)) { + MA_MemoryCopy(string.str + string.len, separator.str, separator.len); + string.len += separator.len; + } + } + IO_Assert(string.len == size - 1); + string.str[size] = 0; + return string; +} + +S8_String Merge(Array list, S8_String separator = " ") { + return Merge(Perm, list, separator); +} + +S8_String Fmt(const char *str, ...) { + S8_FORMAT(Perm, str, str_fmt); + return str_fmt; +} + +Array ListDir(char *dir) { + Array result = {MA_GetAllocator(Perm)}; + for (OS_FileIter it = OS_IterateFiles(Perm, S8_MakeFromChar(dir)); OS_IsValid(it); OS_Advance(&it)) { + result.add(S8_Copy(Perm, it.absolute_path)); + } + return result; +} + +Array CMD_Make(char **argv, int argc) { + Array result = {MA_GetAllocator(Perm)}; + for (int i = 1; i < argc; i += 1) { + S8_String it = S8_MakeFromChar(argv[i]); + result.add(it); + } + return result; +} + +S8_String CMD_Get(Array &cmd, S8_String name, S8_String default_value = "") { + For(cmd) { + int64_t idx = 0; + if (S8_Seek(it, "="_s, 0, &idx)) { + S8_String key = S8_GetPrefix(it, idx); + S8_String value = S8_Skip(it, idx + 1); + if (key == name) { + return value; + } + } + } + return default_value; +} + +bool CMD_Match(Array &cmd, S8_String name) { + For(cmd) { + if (it == name) return true; + } + return false; +} \ No newline at end of file diff --git a/src/build_tool/library.cpp b/src/build_tool/library.cpp new file mode 100644 index 0000000..2fedbd3 --- /dev/null +++ b/src/build_tool/library.cpp @@ -0,0 +1,55 @@ +#ifndef _CRT_SECURE_NO_WARNINGS + #define _CRT_SECURE_NO_WARNINGS +#endif + +#include "core/core.c" + +#define CL_Allocator MA_Arena * +#define CL_Allocate(a, s) MA_PushSizeNonZeroed(a, s) +#define CL_ASSERT IO_Assert +#define CL_VSNPRINTF stbsp_vsnprintf +#define CL_SNPRINTF stbsp_snprintf +#define AND_CL_STRING_TERMINATE_ON_NEW_LINE +#include "standalone_libraries/clexer.c" + +thread_local MA_Arena PernamentArena; +thread_local MA_Arena *Perm = &PernamentArena; + +#include "cache.cpp" +#include "easy_strings.cpp" +#include "process.cpp" + +S8_String CL_Flags = "/MP /Zi /FC /WX /W3 /wd4200 /diagnostics:column /nologo -D_CRT_SECURE_NO_WARNINGS /GF /Gm- /Oi"; +S8_String CL_Link = "/link /incremental:no"; +S8_String CL_StdOff = "/GR- /EHa-"; +S8_String CL_StdOn = "/EHsc"; +S8_String CL_Debug = "-Od -D_DEBUG -fsanitize=address -RTC1"; +S8_String CL_Release = "-O2 -MT -DNDEBUG -GL"; +S8_String CL_ReleaseLink = "-opt:ref -opt:icf"; +/* +/FC = Print full paths in diagnostics +/Gm- = Old feature, 'minimal compilation', in case it's not off by default +/GF = Pools strings as read-only. If you try to modify strings under /GF, an application error occurs. +/Oi = Replaces some function calls with intrinsic +/MP = Multithreaded compilation +/GR- = Disable runtime type information +/EHa- = Disable exceptions +/EHsc = Enable exceptions +/MT = Link static libc. The 'd' means debug version +/MD = Link dynamic libc. The 'd' means debug version +/GL = Whole program optimization +/RTC1 = runtime error checks +/opt:ref = eliminates functions and data that are never referenced +/opt:icf = eliminates redundant 'COMDAT's +*/ + +S8_String Clang_Flags = "-fdiagnostics-absolute-paths -Wno-writable-strings"; +S8_String Clang_NoStd = "-fno-exceptions"; +S8_String Clang_Debug = "-fsanitize=address -g"; +/* +-std=c++11 + */ + +S8_String GCC_Flags = "-Wno-write-strings"; +S8_String GCC_NoStd = "-fno-exceptions"; +S8_String GCC_Debug = "-fsanitize=address -g"; diff --git a/src/build_tool/process.cpp b/src/build_tool/process.cpp new file mode 100644 index 0000000..47afcbf --- /dev/null +++ b/src/build_tool/process.cpp @@ -0,0 +1,153 @@ +struct Process { + bool is_valid; + char platform[32]; +}; + +#if OS_WINDOWS +Process RunEx(S8_String in_cmd) { + MA_Scratch scratch; + wchar_t *application_name = NULL; + wchar_t *cmd = S8_ToWidechar(scratch, in_cmd); + BOOL inherit_handles = FALSE; + DWORD creation_flags = 0; + void *enviroment = NULL; + wchar_t *working_dir = NULL; + STARTUPINFOW startup_info = {}; + startup_info.cb = sizeof(STARTUPINFOW); + Process result = {}; + IO_Assert(sizeof(result.platform) >= sizeof(PROCESS_INFORMATION)); + PROCESS_INFORMATION *process_info = (PROCESS_INFORMATION *)result.platform; + BOOL success = CreateProcessW(application_name, cmd, NULL, NULL, inherit_handles, creation_flags, enviroment, working_dir, &startup_info, process_info); + result.is_valid = true; + if (!success) { + result.is_valid = false; + + LPVOID lpMsgBuf; + DWORD dw = GetLastError(); + FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, dw, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&lpMsgBuf, 0, NULL); + LocalFree(lpMsgBuf); + + IO_FatalErrorf("Failed to create process \ncmd: %.*s\nwindows_message: %s", S8_Expand(in_cmd), lpMsgBuf); + } + return result; +} + +int Wait(Process *process) { + IO_Assert(process->is_valid); + PROCESS_INFORMATION *pi = (PROCESS_INFORMATION *)process->platform; + WaitForSingleObject(pi->hProcess, INFINITE); + + DWORD exit_code; + BOOL err = GetExitCodeProcess(pi->hProcess, &exit_code); + IO_Assert(err != 0); + + CloseHandle(pi->hProcess); + CloseHandle(pi->hThread); + process[0] = {}; + return (int)exit_code; +} +#else + #include + #include + +struct TH_UnixProcess { + pid_t pid; +}; + +extern char **environ; + +Process RunEx(S8_String cmd) { + MA_Scratch scratch; + Process result = {}; + IO_Assert(sizeof(result.platform) >= sizeof(TH_UnixProcess)); + TH_UnixProcess *u = (TH_UnixProcess *)result.platform; + + S8_String exec_file = cmd; + S8_String argv = ""; + int64_t pos; + if (S8_Seek(cmd, S8_Lit(" "), 0, &pos)) { + exec_file = S8_GetPrefix(cmd, pos); + argv = S8_Skip(cmd, pos + 1); + } + + exec_file = S8_Copy(scratch, exec_file); + + // Split string on whitespace and conform with argv format + Array args = {MA_GetAllocator(scratch)}; + { + args.add(exec_file.str); + for (int64_t i = 0; i < argv.len;) { + while (i < argv.len && CHAR_IsWhitespace(argv.str[i])) { + i += 1; + } + + S8_String word = {argv.str + i, 0}; + while (i < argv.len && !CHAR_IsWhitespace(argv.str[i])) { + word.len += 1; + i += 1; + } + word = S8_Copy(scratch, word); + args.add(word.str); + } + args.add(NULL); + } + + int err = posix_spawnp(&u->pid, exec_file.str, NULL, NULL, args.data, environ); + if (err == 0) { + result.is_valid = true; + } else { + perror("posix_spawnp error"); + IO_FatalErrorf("Failed to create process, cmd: %.*s", S8_Expand(cmd)); + } + + return result; +} + +int Wait(Process *process) { + if (!process->is_valid) return 1; + TH_UnixProcess *u = (TH_UnixProcess *)process->platform; + + int status = 0; + int pid = waitpid(u->pid, &status, 0); + IO_Assert(pid != -1); + + int result = 0; + if (WIFEXITED(status)) { + result = WEXITSTATUS(status); + } else { + result = 1; + } + + process[0] = {}; + return result; +} +#endif + +Process RunEx(Array s) { + S8_String cmd = Merge(s); + Process proc = RunEx(cmd); + return proc; +} + +Process RunEx(Array s, S8_String process_start_dir) { + OS_MakeDir(process_start_dir); + S8_String working_dir = OS_GetWorkingDir(Perm); + OS_SetWorkingDir(process_start_dir); + S8_String cmd = Merge(s); + Process proc = RunEx(cmd); + OS_SetWorkingDir(working_dir); + return proc; +} + +int Run(S8_String cmd) { + Process process = RunEx(cmd); + int result = Wait(&process); + return result; +} + +int Run(Array cmd) { + S8_String cmds = Merge(cmd); + int result = Run(cmds); + return result; +} diff --git a/src/build_tool/standalone_libraries/arena.c b/src/build_tool/standalone_libraries/arena.c new file mode 100644 index 0000000..2a1db92 --- /dev/null +++ b/src/build_tool/standalone_libraries/arena.c @@ -0,0 +1,366 @@ +#include "arena.h" +#ifndef MA_Assertf + #include + #define MA_Assertf(x, ...) assert(x) +#endif + +#ifndef MA_StaticFunc + #if defined(__GNUC__) || defined(__clang__) + #define MA_StaticFunc __attribute__((unused)) static + #else + #define MA_StaticFunc static + #endif +#endif + +#if defined(MA_USE_ADDRESS_SANITIZER) + #include +#endif + +#if !defined(ASAN_POISON_MEMORY_REGION) + #define MA_ASAN_POISON_MEMORY_REGION(addr, size) ((void)(addr), (void)(size)) + #define MA_ASAN_UNPOISON_MEMORY_REGION(addr, size) ((void)(addr), (void)(size)) +#else + #define MA_ASAN_POISON_MEMORY_REGION(addr, size) ASAN_POISON_MEMORY_REGION(addr, size) + #define MA_ASAN_UNPOISON_MEMORY_REGION(addr, size) ASAN_UNPOISON_MEMORY_REGION(addr, size) +#endif + +MA_THREAD_LOCAL MA_SourceLoc MA_SavedSourceLoc; +MA_API void MA_SaveSourceLocEx(const char *file, int line) { + MA_SavedSourceLoc.file = file; + MA_SavedSourceLoc.line = line; +} + +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_Assertf(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_PopToPos(MA_Arena *arena, size_t pos) { + MA_Assertf(arena->len >= arena->base_len, "Bug: arena->len shouldn't ever be smaller then arena->base_len"); + pos = MA_CLAMP(pos, arena->base_len, arena->len); + size_t size = arena->len - pos; + arena->len = pos; + MA_ASAN_POISON_MEMORY_REGION(arena->memory.data + arena->len, size); +} + +MA_API void MA_PopSize(MA_Arena *arena, size_t size) { + MA_PopToPos(arena, arena->len - size); +} + +MA_API void MA_DeallocateArena(MA_Arena *arena) { + MV_Deallocate(&arena->memory); +} + +MA_API void MA_Reset(MA_Arena *arena) { + MA_PopToPos(arena, 0); +} + +MA_StaticFunc size_t MA__AlignLen(MA_Arena *a) { + size_t align_offset = a->alignment ? MA_GetAlignOffset((uintptr_t)a->memory.data + (uintptr_t)a->len, a->alignment) : 0; + size_t aligned = a->len + align_offset; + return aligned; +} + +MA_API void MA_SetAlignment(MA_Arena *arena, int alignment) { + arena->alignment = alignment; +} + +MA_API uint8_t *MA_GetTop(MA_Arena *a) { + MA_Assertf(a->memory.data, "Arena needs to be inited, there is no top to get!"); + 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_Assertf(0, "Pushing on uninitialized arena with zero initialization turned off"); +#endif + } + bool result = MV_Commit(&a->memory, size_with_alignment + MA_COMMIT_ADD_SIZE); + MA_Assertf(result, "%s(%d): Failed to commit memory more memory! reserve: %zu commit: %zu len: %zu size_with_alignment: %zu", MA_SavedSourceLoc.file, MA_SavedSourceLoc.line, a->memory.reserve, a->memory.commit, a->len, size_with_alignment); + (void)result; + } + + uint8_t *result = a->memory.data + aligned_len; + a->len += size_with_alignment; + MA_Assertf(a->len <= a->memory.commit, "%s(%d): Reached commit boundary! reserve: %zu commit: %zu len: %zu base_len: %zu alignment: %d size_with_alignment: %zu", MA_SavedSourceLoc.file, MA_SavedSourceLoc.line, a->memory.reserve, a->memory.commit, a->len, a->base_len, a->alignment, size_with_alignment); + MA_ASAN_UNPOISON_MEMORY_REGION(result, size); + 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 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 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_Arena *MA_PushArenaP(MA_Arena *arena, size_t size) { + MA_Arena *result = MA_PushStruct(arena, MA_Arena); + *result = MA_PushArena(arena, size); + return result; +} + +MA_API void MA_InitEx(MA_Arena *a, size_t reserve) { + a->memory = MV_Reserve(reserve); + MA_ASAN_POISON_MEMORY_REGION(a->memory.data, a->memory.reserve); + a->alignment = MA_DEFAULT_ALIGNMENT; +} + +MA_API void MA_Init(MA_Arena *a) { + MA_InitEx(a, MA_DEFAULT_RESERVE_SIZE); +} + +MA_API void MA_MakeSureInitialized(MA_Arena *a) { + if (a->memory.data == 0) { + MA_Init(a); + } +} + +MA_API MA_Arena *MA_Bootstrap(void) { + MA_Arena bootstrap_arena = {0}; + MA_Arena *arena = MA_PushStruct(&bootstrap_arena, MA_Arena); + *arena = bootstrap_arena; + arena->base_len = arena->len; + 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_ASAN_POISON_MEMORY_REGION(arena->memory.data, arena->memory.reserve); +} + +MA_API MA_Arena MA_MakeFromBuffer(void *buffer, size_t size) { + MA_Arena arena; + MA_MemoryZero(&arena, sizeof(arena)); + MA_InitFromBuffer(&arena, buffer, size); + return arena; +} + +MA_API MA_Arena MA_Create() { + MA_Arena arena = {0}; + MA_Init(&arena); + return arena; +} + +MA_API 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_Temp MA_BeginTemp(MA_Arena *arena) { + MA_Temp result; + result.pos = arena->len; + result.arena = arena; + return result; +} + +MA_API void MA_EndTemp(MA_Temp checkpoint) { + MA_PopToPos(checkpoint.arena, checkpoint.pos); +} + +MA_THREAD_LOCAL MA_Arena *MA_ScratchArenaPool[4]; + +MA_API void MA_InitScratch(void) { + for (int i = 0; i < MA_Lengthof(MA_ScratchArenaPool); i += 1) { + MA_ScratchArenaPool[i] = MA_Bootstrap(); + } +} + +MA_API MA_Temp 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_Assertf(unoccupied, "Failed to get free scratch memory, this is a fatal error, this shouldnt happen"); + MA_Temp result = MA_BeginTemp(unoccupied); + return result; +} + +MA_API MA_Temp MA_GetScratch(void) { + MA_Temp result = MA_BeginTemp(MA_ScratchArenaPool[0]); + return result; +} + +MA_API MA_Temp MA_GetScratch1(MA_Arena *conflict) { + MA_Arena *conflicts[] = {conflict}; + return MA_GetScratchEx(conflicts, 1); +} + +#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_Assertf(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_Assertf(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_Assertf(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__ || __linux__ || __APPLE__ + #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_Assertf(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_Assertf(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_Assertf(result == 0, "Failed to release virtual memory using munmap"); +} +#else +MA_API MV_Memory MV_Reserve(size_t size) { + MV_Memory result = {0}; + return result; +} + +MA_API bool MV_Commit(MV_Memory *m, size_t commit) { + return false; +} + +MA_API void MV_Deallocate(MV_Memory *m) { +} + +#endif \ No newline at end of file diff --git a/src/build_tool/standalone_libraries/arena.h b/src/build_tool/standalone_libraries/arena.h new file mode 100644 index 0000000..b7c9e87 --- /dev/null +++ b/src/build_tool/standalone_libraries/arena.h @@ -0,0 +1,179 @@ +#ifndef MA_HEADER +#define MA_HEADER +#include +#include +#include + +#define MA_KIB(x) ((x##ull) * 1024ull) +#define MA_MIB(x) (MA_KIB(x) * 1024ull) +#define MA_GIB(x) (MA_MIB(x) * 1024ull) +#define MA_TIB(x) (MA_GIB(x) * 1024ull) + +typedef struct MV_Memory MV_Memory; +typedef struct MA_Temp MA_Temp; +typedef struct MA_Arena MA_Arena; +typedef struct MA_SourceLoc MA_SourceLoc; + +#ifndef MA_DEFAULT_RESERVE_SIZE + #define MA_DEFAULT_RESERVE_SIZE MA_GIB(1) +#endif + +#ifndef MA_DEFAULT_ALIGNMENT + #define MA_DEFAULT_ALIGNMENT 8 +#endif + +#ifndef MA_COMMIT_ADD_SIZE + #define MA_COMMIT_ADD_SIZE MA_MIB(4) +#endif + +#ifndef MA_ZERO_IS_INITIALIZATION + #define MA_ZERO_IS_INITIALIZATION 1 +#endif + +#ifndef MA_API + #ifdef __cplusplus + #define MA_API extern "C" + #else + #define MA_API + #endif +#endif + +#ifndef MA_THREAD_LOCAL + #if defined(__cplusplus) && __cplusplus >= 201103L + #define MA_THREAD_LOCAL thread_local + #elif defined(__GNUC__) + #define MA_THREAD_LOCAL __thread + #elif defined(_MSC_VER) + #define MA_THREAD_LOCAL __declspec(thread) + #elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L && !defined(__STDC_NO_THREADS__) + #define MA_THREAD_LOCAL _Thread_local + #elif defined(__TINYC__) + #define MA_THREAD_LOCAL _Thread_local + #else + #error Couldnt figure out thread local, needs to be provided manually + #endif +#endif + +#ifndef MA_MemoryZero + #include + #define MA_MemoryZero(p, size) memset(p, 0, size) +#endif + +#ifndef MA_MemoryCopy + #include + #define MA_MemoryCopy(dst, src, size) memcpy(dst, src, size); +#endif + +struct MV_Memory { + size_t commit; + size_t reserve; + uint8_t *data; +}; + +struct MA_Arena { + MV_Memory memory; + int alignment; + size_t len; + size_t base_len; // When popping to 0 this is the minimum "len" value + // It's so that Bootstrapped arena won't delete itself when Reseting. +}; + +struct MA_Temp { + MA_Arena *arena; + size_t pos; +}; + +struct MA_SourceLoc { + const char *file; + int line; +}; + +extern MA_THREAD_LOCAL MA_SourceLoc MA_SavedSourceLoc; +#define MA_SaveSourceLoc() MA_SaveSourceLocEx(__FILE__, __LINE__) +MA_API void MA_SaveSourceLocEx(const char *file, int line); + +#define MA_PushSize(a, size) MA__PushSize(a, size) +#define MA_PushSizeNonZeroed(a, size) MA__PushSizeNonZeroed(a, size) +#define MA_PushCopy(a, p, size) MA__PushCopy(a, p, size) +#define MA_PushStringCopy(a, p, size) MA__PushStringCopy(a, p, size) + +#define MA_PushArrayNonZeroed(a, T, c) (T *)MA__PushSizeNonZeroed(a, sizeof(T) * (c)) +#define MA_PushStructNonZeroed(a, T) (T *)MA__PushSizeNonZeroed(a, sizeof(T)) +#define MA_PushStruct(a, T) (T *)MA__PushSize(a, sizeof(T)) +#define MA_PushArray(a, T, c) (T *)MA__PushSize(a, sizeof(T) * (c)) +#define MA_PushStructCopy(a, T, p) (T *)MA__PushCopy(a, (p), sizeof(T)) + +// clang-format off +MA_API void MA_InitEx(MA_Arena *a, size_t reserve); +MA_API void MA_Init(MA_Arena *a); +MA_API MA_Arena MA_Create(); +MA_API void MA_MakeSureInitialized(MA_Arena *a); +MA_API void MA_InitFromBuffer(MA_Arena *arena, void *buffer, size_t size); +MA_API MA_Arena MA_MakeFromBuffer(void *buffer, size_t size); +MA_API MA_Arena * MA_Bootstrap(void); +MA_API MA_Arena MA_PushArena(MA_Arena *arena, size_t size); +MA_API MA_Arena * MA_PushArenaP(MA_Arena *arena, size_t size); + +MA_API void * MA__PushSizeNonZeroed(MA_Arena *a, size_t size); +MA_API void * MA__PushSize(MA_Arena *arena, size_t size); +MA_API char * MA__PushStringCopy(MA_Arena *arena, char *p, size_t size); +MA_API void * MA__PushCopy(MA_Arena *arena, void *p, size_t size); +MA_API MA_Temp MA_BeginTemp(MA_Arena *arena); +MA_API void MA_EndTemp(MA_Temp checkpoint); + +MA_API void MA_PopToPos(MA_Arena *arena, size_t pos); +MA_API void MA_PopSize(MA_Arena *arena, size_t size); +MA_API void MA_DeallocateArena(MA_Arena *arena); +MA_API void MA_Reset(MA_Arena *arena); + +MA_API size_t MA_GetAlignOffset(size_t size, size_t align); +MA_API size_t MA_AlignUp(size_t size, size_t align); +MA_API size_t MA_AlignDown(size_t size, size_t align); + +MA_API bool MA_IsPointerInside(MA_Arena *arena, void *p); +MA_API void MA_SetAlignment(MA_Arena *arena, int alignment); +MA_API uint8_t * MA_GetTop(MA_Arena *a); + +MA_API MV_Memory MV_Reserve(size_t size); +MA_API bool MV_Commit(MV_Memory *m, size_t commit); +MA_API void MV_Deallocate(MV_Memory *m); +MA_API bool MV_DecommitPos(MV_Memory *m, size_t pos); +// clang-format on + +extern MA_THREAD_LOCAL MA_Arena *MA_ScratchArenaPool[4]; +#define MA_CheckpointScope(name, InArena) for (MA_Temp name = MA_BeginTemp(InArena); name.arena; (MA_EndTemp(name), name.arena = 0)) +#define MA_ScratchScope(x) for (MA_Temp x = MA_GetScratch(); x.arena; (MA_ReleaseScratch(x), x.arena = 0)) +#define MA_ReleaseScratch MA_EndTemp +MA_API MA_Temp MA_GetScratchEx(MA_Arena **conflicts, int conflict_count); +MA_API MA_Temp MA_GetScratch(void); +MA_API MA_Temp MA_GetScratch1(MA_Arena *conflict); + +#if defined(__cplusplus) +struct MA_Scratch { + MA_Temp checkpoint; + MA_Scratch() { this->checkpoint = MA_GetScratch(); } + MA_Scratch(MA_Temp conflict) { this->checkpoint = MA_GetScratch1(conflict.arena); } + MA_Scratch(MA_Temp c1, MA_Temp c2) { + MA_Arena *conflicts[] = {c1.arena, c2.arena}; + this->checkpoint = MA_GetScratchEx(conflicts, 2); + } + ~MA_Scratch() { MA_EndTemp(checkpoint); } + operator MA_Arena *() { return checkpoint.arena; } + + private: // @Note: Disable copy constructors, cause its error prone + MA_Scratch(MA_Scratch &arena); + MA_Scratch(MA_Scratch &arena, MA_Scratch &a2); +}; +#endif // __cplusplus + +#define MA_IS_POW2(x) (((x) & ((x)-1)) == 0) +#define MA_MIN(x, y) ((x) <= (y) ? (x) : (y)) +#define MA_MAX(x, y) ((x) >= (y) ? (x) : (y)) +#define MA_Lengthof(x) ((int64_t)((sizeof(x) / sizeof((x)[0])))) + +#define MA_CLAMP_TOP(x, max) ((x) >= (max) ? (max) : (x)) +#define MA_CLAMP_BOT(x, min) ((x) <= (min) ? (min) : (x)) +#define MA_CLAMP(x, min, max) ((x) >= (max) ? (max) : (x) <= (min) ? (min) \ + : (x)) + +#endif // MA_HEADER \ No newline at end of file diff --git a/src/build_tool/standalone_libraries/clexer.c b/src/build_tool/standalone_libraries/clexer.c new file mode 100644 index 0000000..d8fac92 --- /dev/null +++ b/src/build_tool/standalone_libraries/clexer.c @@ -0,0 +1,1549 @@ +#include "clexer.h" +#include + +/* +- I'm pretty sure I can remove allocations for most of the current functions. +- I also can fix ResolvePath stuff so that it uses string+len and doesn't need allocations +- Add lexing options like in stb_c_lexer.h + +Instead of AND_CL_STRING_TERMINATE_ON_NEW_LINE he is doing some weird cool stuff with redefining +https://github.com/nothings/stb/blob/master/stb_c_lexer.h + +CL_MULTILINE_SSTRINGS +CL_DOLLAR_IDENT + +- Add proper string parsing, as additional function, CL_ParseString() or something, this is the only one that would need allocations + +*/ + +#ifndef CL_PRIVATE_FUNCTION + #if defined(__GNUC__) || defined(__clang__) + #define CL_PRIVATE_FUNCTION __attribute__((unused)) static + #else + #define CL_PRIVATE_FUNCTION static + #endif +#endif + +#ifndef CL_Allocate + #include + #define CL_Allocate(allocator, size) malloc(size) +#endif + +#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_FileExists + #define CL_FileExists CL__FileExists + #include +CL_PRIVATE_FUNCTION bool CL_FileExists(char *name) { + bool result = false; + FILE *f = fopen(name, "rb"); + if (f) { + result = true; + fclose(f); + } + return result; +} +#endif + +CL_PRIVATE_FUNCTION void CL_ReportError(CL_Lexer *T, CL_Token *token, const char *string, ...); + +CL_PRIVATE_FUNCTION char *CL_PushStringCopy(CL_Allocator arena, char *p, int size) { + char *copy_buffer = (char *)CL_Allocate(arena, size + 1); + CL__MemoryCopy(copy_buffer, p, size); + copy_buffer[size] = 0; + return copy_buffer; +} + +CL_INLINE void CL_Advance(CL_Lexer *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 void CL_SetTokenLength(CL_Lexer *T, CL_Token *token) { + intptr_t diff = T->stream - token->str; + CL_ASSERT(diff < 2147483647); + token->len = (int)diff; +} + +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_Lexer *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 = {0}; + + 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_PRIVATE_FUNCTION void CL_ParseCharLiteral(CL_Lexer *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; + case 'x': + case 'X': CL_ASSERT(!"Not implemented"); break; // Hex constant + case 'u': CL_ASSERT(!"Not implemented"); 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); +} + +// It combines strings, verifies the escape sequences but doesn't do any allocations +// so the final string actually needs additional transformation pass. A pass +// that will combine the string snippets, replace escape sequences with actual values etc. +// +// @warning: @not_sure: we are not setting token->string_literal +// +// "String 1" "String 2" - those strings snippets are combined +// @todo: look at this again +// @todo: make a manual correct version that user can execute if he needs to +CL_PRIVATE_FUNCTION void CL_CheckString(CL_Lexer *T, CL_Token *token) { + token->kind = CL_STRINGLIT; +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 'a': + case 'b': + case 'e': + case 'f': + case 'n': + case 'r': + case 't': + case 'v': + case '\\': + case '\'': + case '?': + case '"': + case 'x': + case 'X': // Hex constant + case 'u': // Unicode constant + case 'U': + break; + case '0': // octal numbers or null + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + break; + default: { + CL_ReportError(T, token, "Invalid escape sequence"); + return; + } + } + } + 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; + } + } + CL_SetTokenLength(T, token); +} + +CL_PRIVATE_FUNCTION void CL_IsIdentifierKeyword(CL_Token *token) { + if (token->len == 1) return; + char *c = token->str; + 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; + } +} + +CL_PRIVATE_FUNCTION void CL_EatMacroWhitespace(CL_Lexer *T) { + while (T->stream[0] == ' ' || T->stream[0] == '\t') CL_Advance(T); +} + +CL_PRIVATE_FUNCTION void CL_EatUntil(CL_Lexer *T, char c) { + while (T->stream[0] != c && T->stream[0] != 0) CL_Advance(T); +} + +CL_PRIVATE_FUNCTION void CL_LexMacroInclude(CL_Lexer *T, CL_Token *token) { + token->kind = CL_PREPROC_INCLUDE; + CL_EatMacroWhitespace(T); + char end = 0; + if (*T->stream == '"') { + end = '"'; + } + else if (*T->stream == '<') { + end = '>'; + token->is_system_include = true; + } + 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); + + // @not_sure: this is because we want null terminated input into path resolution stuff + token->string_literal = CL_PushStringCopy(T->arena, token->str, token->len); +} + +CL_PRIVATE_FUNCTION bool CL_LexMacro(CL_Lexer *T, CL_Token *token) { + CL_EatMacroWhitespace(T); + token->str = T->stream; + while (CL_IsAlphabetic(*T->stream)) CL_Advance(T); + CL_SetTokenLength(T, token); + + 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; + CL_EatMacroWhitespace(T); + token->str = T->stream; + CL_EatUntil(T, '\n'); + CL_SetTokenLength(T, token); + } + 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; +} + +// 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 read only +CL_PRIVATE_FUNCTION void CL_PrepareToken(CL_Lexer *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->is_there_whitespace_before_token = true; + CL_Advance(T); +} + +CL_PRIVATE_FUNCTION void CL_DefaultTokenize(CL_Lexer *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_COLON; 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_DIV; + if (*T->stream == '/') { + token->kind = CL_COMMENT; + CL_Advance(T); + CL_EatUntil(T, '\n'); + CL_SetTokenLength(T, token); + } + 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); + } + 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_CheckString(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_CheckString(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_CheckString(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_CheckString(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_CheckString(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(token); + } break; + case '0': { + if (*T->stream == 'x' || *T->stream == 'X') { + token->kind = CL_INT; + token->is_hex = true; + 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_ReportError(T, token, "Unhandled character, skipping ..."); + } break; + } + +error_end_path:; +} + +CL_PRIVATE_FUNCTION bool CL_EatWhitespace(CL_Lexer *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_PRIVATE_FUNCTION void CL_TryToFinalizeToken(CL_Lexer *T, CL_Token *token) { + if (!token->len) { + CL_SetTokenLength(T, token); + } + if (T->inside_of_macro) { + token->is_inside_macro = true; + } +} + +CL_PRIVATE_FUNCTION void CL_InitNextToken(CL_Lexer *T, CL_Token *token) { + // Skip comments, comments get allocated on perm and gathered on the Tokenizer. + // First non comment token gets those comments attached. + for (;;) { + bool skipped = CL_EatWhitespace(T); + CL_PrepareToken(T, token, skipped); + CL_DefaultTokenize(T, token); + + if (token->kind == CL_EOF) { + break; + } + + if (T->select_includes) { + if (token->kind != CL_PREPROC_INCLUDE) continue; + } + + if (T->select_macros) { + if (!token->is_inside_macro) continue; + } + + if (T->select_comments) { + if (token->kind != CL_COMMENT) continue; + } + + if (T->skip_comments) { + if (token->kind == CL_COMMENT) continue; + } + + if (T->skip_macros) { + if (token->is_inside_macro) continue; + } + + break; + } + CL_TryToFinalizeToken(T, token); +} + +CL_API_FUNCTION CL_Token CL_Next(CL_Lexer *T) { + CL_Token result; + CL_MemoryZero(&result, sizeof(CL_Token)); + CL_InitNextToken(T, &result); + return result; +} + +CL_API_FUNCTION CL_Lexer CL_Begin(CL_Allocator arena, char *stream, char *filename) { + CL_Lexer lexer = {0}; + lexer.stream = lexer.stream_begin = stream; + lexer.file = filename; + lexer.arena = arena; + lexer.skip_comments = true; + return lexer; +} + +// +// +// + +CL_PRIVATE_FUNCTION char *CL_ChopLastSlash(CL_Allocator 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 = (char *)"./"; + } + return result; +} + +CL_PRIVATE_FUNCTION char *CL_JoinPath(CL_Allocator 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 = (char *)CL_Allocate(arena, sizeof(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; +} + +CL_PRIVATE_FUNCTION 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_Allocator arena, CL_SearchPaths *search_paths, char *filename, char *parent_file, bool is_system_include) { + CL_SearchPaths null_search_paths = {0}; + 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; +} + +// +// +// + +const char *CL_FixString[] = { + "SUFFIX_INVALID", + "SUFFIX_U", + "SUFFIX_UL", + "SUFFIX_ULL", + "SUFFIX_L", + "SUFFIX_LL", + "SUFFIX_F", + "SUFFIX_FL", + "PREFIX_U8", + "PREFIX_U16", + "PREFIX_U32", + "PREFIX_L", +}; + +const 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", +}; + +CL_API_FUNCTION void CL_StringifyMessage(char *buff, int buff_size, CL_Message *msg) { + CL_SNPRINTF(buff, buff_size, "%s(%d,%d): %15s", msg->token.file, msg->token.line + 1, msg->token.column + 1, msg->string); +} + +CL_API_FUNCTION void CL_Stringify(char *buff, int buff_size, CL_Token *token) { + const char *token_kind = "UNKNOWN"; + if (token->kind < CL_COUNT) token_kind = CL_KindString[token->kind]; + CL_SNPRINTF(buff, buff_size, "%s(%d,%d): %15s %15.*s", token->file, token->line + 1, token->column + 1, token_kind, token->len, token->str); +} + +#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_Allocate((arena), len + 1); \ + CL_VSNPRINTF(result, len + 1, string, args1); \ + va_end(args1) + +CL_PRIVATE_FUNCTION void CL_ReportError(CL_Lexer *T, CL_Token *token, const char *string, ...) { + CL__FORMAT(T->arena, string, message_string); + CL_Message *result = (CL_Message *)CL_Allocate(T->arena, sizeof(CL_Message)); + CL_MemoryZero(result, sizeof(CL_Message)); + CL_SLL_QUEUE_ADD(T->first_message, T->last_message, result); + + result->string = (char *)string; + result->token = *token; + token->kind = CL_ERROR; + token->error = result; + T->errors += 1; +} diff --git a/src/build_tool/standalone_libraries/clexer.h b/src/build_tool/standalone_libraries/clexer.h new file mode 100644 index 0000000..47179ee --- /dev/null +++ b/src/build_tool/standalone_libraries/clexer.h @@ -0,0 +1,302 @@ +#ifndef FIRST_CL_HEADER +#define FIRST_CL_HEADER + +#include +#include +#include + +#ifndef CL_API_FUNCTION + #ifdef __cplusplus + #define CL_API_FUNCTION extern "C" + #else + #define CL_API_FUNCTION + #endif +#endif + +#ifndef CL_INLINE + #ifndef _MSC_VER + #ifdef __cplusplus + #define CL_INLINE inline + #else + #define CL_INLINE + #endif + #else + #define CL_INLINE __forceinline + #endif +#endif + +#ifndef CL_Allocator +struct MA_Arena; + #define CL_Allocator MA_Arena * +#endif + +#ifndef AND_CL_STRING_TERMINATE_ON_NEW_LINE + #define AND_CL_STRING_TERMINATE_ON_NEW_LINE &&*T->stream != '\n' +#endif + +typedef enum CL_Kind { + CL_EOF, + CL_MUL, + CL_DIV, + CL_MOD, + CL_LEFTSHIFT, + CL_RIGHTSHIFT, + CL_ADD, + CL_SUB, + CL_EQUALS, + CL_LESSERTHEN, + CL_GREATERTHEN, + CL_LESSERTHEN_OR_EQUAL, + CL_GREATERTHEN_OR_EQUAL, + CL_NOTEQUALS, + CL_BITAND, + CL_BITOR, + CL_BITXOR, + CL_AND, + CL_OR, + CL_NEG, + CL_NOT, + CL_DECREMENT, + CL_INCREMENT, + CL_POSTDECREMENT, + CL_POSTINCREMENT, + CL_ASSIGN, + CL_DIVASSIGN, + CL_MULASSIGN, + CL_MODASSIGN, + CL_SUBASSIGN, + CL_ADDASSIGN, + CL_ANDASSIGN, + CL_ORASSIGN, + CL_XORASSIGN, + CL_LEFTSHIFTASSIGN, + CL_RIGHTSHIFTASSIGN, + CL_OPENPAREN, + CL_CLOSEPAREN, + CL_OPENBRACE, + CL_CLOSEBRACE, + CL_OPENBRACKET, + CL_CLOSEBRACKET, + CL_COMMA, + CL_MACRO_CONCAT, + CL_PREPROC_STRINGIFY, + CL_QUESTION, + CL_THREEDOTS, + CL_SEMICOLON, + CL_DOT, + CL_COLON, + CL_TAG, + CL_ARROW, + CL_EXPRSIZEOF, + CL_DOCCOMMENT, + CL_COMMENT, + CL_IDENTIFIER, + CL_STRINGLIT, + CL_CHARLIT, + CL_ERROR, + CL_FLOAT, + CL_INT, + CL_PREPROC_NULL, + CL_PREPROC_DEFINE, + CL_PREPROC_IFDEF, + CL_PREPROC_IFNDEF, + CL_PREPROC_INCLUDE, + CL_PREPROC_ENDIF, + CL_PREPROC_IF, + CL_PREPROC_PRAGMA, + CL_PREPROC_ERROR, + CL_PREPROC_ELSE, + CL_PREPROC_ELIF, + CL_PREPROC_UNDEF, + CL_KEYWORD_VOID, + CL_KEYWORD_INT, + CL_KEYWORD_CHAR, + CL_KEYWORD_UNSIGNED, + CL_KEYWORD_SIGNED, + CL_KEYWORD_LONG, + CL_KEYWORD_SHORT, + CL_KEYWORD_DOUBLE, + CL_KEYWORD_FLOAT, + CL_KEYWORD__BOOL, + CL_KEYWORD__COMPLEX, + CL_KEYWORD__IMAGINARY, + CL_KEYWORD_STATIC, + CL_KEYWORD_AUTO, + CL_KEYWORD_CONST, + CL_KEYWORD_EXTERN, + CL_KEYWORD_INLINE, + CL_KEYWORD_REGISTER, + CL_KEYWORD_RESTRICT, + CL_KEYWORD_VOLATILE, + CL_KEYWORD__THREAD_LOCAL, + CL_KEYWORD__ATOMIC, + CL_KEYWORD__NORETURN, + CL_KEYWORD_STRUCT, + CL_KEYWORD_UNION, + CL_KEYWORD_ENUM, + CL_KEYWORD_TYPEDEF, + CL_KEYWORD_DEFAULT, + CL_KEYWORD_BREAK, + CL_KEYWORD_RETURN, + CL_KEYWORD_SWITCH, + CL_KEYWORD_IF, + CL_KEYWORD_ELSE, + CL_KEYWORD_FOR, + CL_KEYWORD_WHILE, + CL_KEYWORD_CASE, + CL_KEYWORD_CONTINUE, + CL_KEYWORD_DO, + CL_KEYWORD_GOTO, + CL_KEYWORD_SIZEOF, + CL_KEYWORD__ALIGNAS, + CL_KEYWORD__ALIGNOF, + CL_KEYWORD__STATIC_ASSERT, + CL_KEYWORD__GENERIC, + CL_COUNT, +} CL_Kind; + +typedef enum CL_Fix { + CL_FIX_NONE, + CL_SUFFIX_U, + CL_SUFFIX_UL, + CL_SUFFIX_ULL, + CL_SUFFIX_L, + CL_SUFFIX_LL, + CL_SUFFIX_F, + CL_SUFFIX_FL, + CL_PREFIX_U8, + CL_PREFIX_U16, + CL_PREFIX_U32, + CL_PREFIX_L, +} CL_Fix; + +typedef struct CL_Token CL_Token; +struct CL_Token { + CL_Kind kind; + CL_Fix fix; + + bool is_hex : 1; + bool is_inside_macro : 1; + bool is_system_include : 1; + bool is_there_whitespace_before_token : 1; + + uint32_t id; + int len; + char *str; + + // Not storing line_begin like I would normally cause the user could + // override the line and file information using directives. + // On error need to do search if I want nice error context. + int line, column; + char *file; + + union { + double f64; + uint64_t u64; + char *intern; + char *string_literal; + struct CL_Message *error; + }; +}; + +typedef struct CL_Message CL_Message; +struct CL_Message { + CL_Message *next; + char *string; + CL_Token token; +}; + +typedef struct CL_Lexer CL_Lexer; +struct CL_Lexer { + CL_Message *first_message; + CL_Message *last_message; + int errors; + + char *stream; + char *stream_begin; + int line; + int column; + char *file; + bool inside_of_macro; + + // filters + bool skip_comments : 1; + bool skip_macros : 1; + bool select_includes : 1; + bool select_comments : 1; + bool select_macros : 1; + + CL_Allocator arena; +}; + +typedef struct CL_SearchPaths CL_SearchPaths; +struct CL_SearchPaths { + char **include_path; + int include_path_count; + + char **system_include_path; + int system_include_path_count; + + char *file_begin_to_ignore; +}; + +CL_API_FUNCTION CL_Token CL_Next(CL_Lexer *T); +CL_API_FUNCTION CL_Lexer CL_Begin(CL_Allocator arena, char *stream, char *filename); +CL_API_FUNCTION char *CL_ResolveFilepath(CL_Allocator arena, CL_SearchPaths *search_paths, char *filename, char *parent_file, bool is_system_include); +CL_API_FUNCTION void CL_StringifyMessage(char *buff, int buff_size, CL_Message *msg); +CL_API_FUNCTION void CL_Stringify(char *buff, int buff_size, CL_Token *token); + +extern const char *CL_FixString[]; +extern const char *CL_KindString[]; + +CL_INLINE int CL_StringLength(char *string) { + int len = 0; + while (*string++ != 0) len++; + return len; +} + +CL_INLINE bool CL_StringsAreEqual(char *a, int64_t alen, const char *b, int64_t blen) { + if (alen != blen) return false; + for (int i = 0; i < alen; i += 1) { + if (a[i] != b[i]) return false; + } + return true; +} + +CL_INLINE bool CL_IsIdentifier(CL_Token *token, char *str) { + int str_len = CL_StringLength(str); + bool result = token->kind == CL_IDENTIFIER && CL_StringsAreEqual(token->str, token->len, str, str_len); + return result; +} + +CL_INLINE bool CL_IsAssign(CL_Kind op) { + bool result = op >= CL_ASSIGN && op <= CL_RIGHTSHIFTASSIGN; + return result; +} + +CL_INLINE bool CL_IsKeywordType(CL_Kind op) { + bool result = op >= CL_KEYWORD_VOID && op <= CL_KEYWORD__IMAGINARY; + return result; +} + +CL_INLINE bool CL_IsKeywordTypeOrSpec(CL_Kind op) { + bool result = op >= CL_KEYWORD_VOID && op <= CL_KEYWORD_TYPEDEF; + return result; +} + +CL_INLINE bool CL_IsMacro(CL_Kind kind) { + bool result = kind >= CL_PREPROC_DEFINE && kind <= CL_PREPROC_UNDEF; + return result; +} + +CL_INLINE bool CL_IsKeyword(CL_Kind kind) { + bool result = kind >= CL_KEYWORD_VOID && kind <= CL_KEYWORD__GENERIC; + return result; +} + +CL_INLINE bool CL_IsKeywordOrIdent(CL_Kind kind) { + bool result = CL_IsKeyword(kind) || kind == CL_IDENTIFIER; + return result; +} + +#endif \ No newline at end of file diff --git a/src/build_tool/standalone_libraries/defer.hpp b/src/build_tool/standalone_libraries/defer.hpp new file mode 100644 index 0000000..463ec4a --- /dev/null +++ b/src/build_tool/standalone_libraries/defer.hpp @@ -0,0 +1,25 @@ +#ifndef FIRST_DEFER_HEADER +#define FIRST_DEFER_HEADER + +template +struct DEFER_ExitScope { + T lambda; + DEFER_ExitScope(T lambda) : lambda(lambda) {} + ~DEFER_ExitScope() { lambda(); } + DEFER_ExitScope(const DEFER_ExitScope &i) : lambda(i.lambda){}; + + private: + DEFER_ExitScope &operator=(const DEFER_ExitScope &); +}; + +class DEFER_ExitScopeHelp { + public: + template + DEFER_ExitScope operator+(T t) { return t; } +}; + +#define DEFER_CONCAT_INTERNAL(x, y) x##y +#define DEFER_CONCAT(x, y) DEFER_CONCAT_INTERNAL(x, y) +#define defer const auto DEFER_CONCAT(defer__, __LINE__) = DEFER_ExitScopeHelp() + [&]() + +#endif \ No newline at end of file diff --git a/src/build_tool/standalone_libraries/hash.c b/src/build_tool/standalone_libraries/hash.c new file mode 100644 index 0000000..8b44f00 --- /dev/null +++ b/src/build_tool/standalone_libraries/hash.c @@ -0,0 +1,53 @@ +#include "hash.h" + +// FNV HASH (1a?) +HASH_API_FUNCTION uint64_t HashBytes(void *data, uint64_t size) { + uint8_t *data8 = (uint8_t *)data; + uint64_t hash = (uint64_t)14695981039346656037ULL; + for (uint64_t i = 0; i < size; i++) { + hash = hash ^ (uint64_t)(data8[i]); + hash = hash * (uint64_t)1099511628211ULL; + } + return hash; +} + +HASH_API_FUNCTION RandomSeed MakeRandomSeed(uint64_t value) { + RandomSeed result; + result.a = value; + return result; +} + +HASH_API_FUNCTION uint64_t GetRandomU64(RandomSeed *state) { + uint64_t x = state->a; + x ^= x << 13; + x ^= x >> 7; + x ^= x << 17; + return state->a = x; +} + +HASH_API_FUNCTION int GetRandomRangeI(RandomSeed *seed, int first, int last_included) { + uint64_t random = GetRandomU64(seed); + int range = (last_included - first + 1); + int mapped = random % range; + int result = mapped + first; + return result; +} + +HASH_API_FUNCTION double GetRandomNormal(RandomSeed *series) { + uint64_t rnd = GetRandomU64(series); + double result = (double)rnd / (double)UINT64_MAX; + return result; +} + +HASH_API_FUNCTION double GetRandomNormalRange(RandomSeed *seed, double min, double max) { + double value = GetRandomNormal(seed); + double result = value * (max - min) + min; + return result; +} + +HASH_API_FUNCTION uint64_t HashMix(uint64_t x, uint64_t y) { + x ^= y; + x *= 0xff51afd7ed558ccd; + x ^= x >> 32; + return x; +} diff --git a/src/build_tool/standalone_libraries/hash.h b/src/build_tool/standalone_libraries/hash.h new file mode 100644 index 0000000..073a9d2 --- /dev/null +++ b/src/build_tool/standalone_libraries/hash.h @@ -0,0 +1,28 @@ +#ifndef FIRST_HASH_HEADER +#define FIRST_HASH_HEADER +#include + +#ifndef HASH_API_FUNCTION + #ifdef __cplusplus + #define HASH_API_FUNCTION extern "C" + #else + #define HASH_API_FUNCTION + #endif +#endif + +typedef struct RandomSeed RandomSeed; +struct RandomSeed { + uint64_t a; +}; + +HASH_API_FUNCTION uint64_t HashBytes(void *data, uint64_t size); +HASH_API_FUNCTION RandomSeed MakeRandomSeed(uint64_t value); +HASH_API_FUNCTION uint64_t GetRandomU64(RandomSeed *state); +HASH_API_FUNCTION int GetRandomRangeI(RandomSeed *seed, int first, int last_included); +HASH_API_FUNCTION double GetRandomNormal(RandomSeed *series); +HASH_API_FUNCTION double GetRandomNormalRange(RandomSeed *seed, double min, double max); +HASH_API_FUNCTION uint64_t HashMix(uint64_t x, uint64_t y); + +#define WRAP_AROUND_POWER_OF_2(x, pow2) (((x) & ((pow2)-1llu))) +static inline float GetRandomNormalF(RandomSeed *series) { return (float)GetRandomNormal(series); } +#endif \ No newline at end of file diff --git a/src/build_tool/standalone_libraries/io.c b/src/build_tool/standalone_libraries/io.c new file mode 100644 index 0000000..6f194ab --- /dev/null +++ b/src/build_tool/standalone_libraries/io.c @@ -0,0 +1,236 @@ +#include "io.h" +#include + +#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; +} + +IO_THREAD_LOCAL void (*IO_User_OutputMessage)(int kind, const char *file, int line, 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 size2 = IO_SNPRINTF(buff2, sizeof(buff2), "%s(%d): error: %s \n", file, line, user_message); + if (size2 >= sizeof(buff2)) { + size2 += 4; + b = (char *)IO_ALLOCATE(size2); + size2 = IO_SNPRINTF(b, size2, "%s(%d): error: %s \n", file, line, user_message); + result = b; + } + + ret = IO_OutputError(result, size2); + 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(int kind, const char *file, int line, const char *msg, ...) { + // First try to use a static buffer. That can fail because the message + // can be bigger then the buffer. Allocate enough memory to fit in that + // case. + 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(kind, file, line, result, size); + } else { + IO_OutputMessage(result, size); + } + + if (new_buffer) { + IO_FREE(new_buffer); + } +} + +IO_API bool IO__FatalError(const char *msg) { + int len = IO_Strlen((char *)msg); + IO_ErrorResult result = IO_OutputError((char *)msg, len); + if (result == IO_ErrorResult_Exit) { + IO_Exit(1); + } + return result == IO_ErrorResult_Break; +} + +IO_API void IO_Print(int kind, const char *file, int line, char *msg, int len) { + if (IO_User_OutputMessage) { + IO_User_OutputMessage(kind, file, line, 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); +} +#elif __linux__ || __unix__ || __APPLE__ + #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; +} +#else +IO_API IO_ErrorResult IO_OutputError(char *str, int len) { + return IO_ErrorResult_Exit; +} + +IO_API void IO_OutputMessage(char *str, int len) { +} + +IO_API void IO_Exit(int error_code) { +} + +IO_API bool IO_IsDebuggerPresent(void) { + return false; +} + +#endif // LIBC diff --git a/src/build_tool/standalone_libraries/io.h b/src/build_tool/standalone_libraries/io.h new file mode 100644 index 0000000..70f8bb8 --- /dev/null +++ b/src/build_tool/standalone_libraries/io.h @@ -0,0 +1,108 @@ +#ifndef FIRST_IO_HEADER +#define FIRST_IO_HEADER +#include + +#ifndef IO_API + #ifdef __cplusplus + #define IO_API extern "C" + #else + #define IO_API + #endif +#endif + +typedef enum IO_ErrorResult { + IO_ErrorResult_Continue, + IO_ErrorResult_Break, + IO_ErrorResult_Exit, +} IO_ErrorResult; + +#if defined(_MSC_VER) + #define IO_DebugBreak() (__debugbreak(), 0) +#else + #define IO_DebugBreak() (__builtin_trap(), 0) +#endif + +#ifndef IO_THREAD_LOCAL + #if defined(__cplusplus) && __cplusplus >= 201103L + #define IO_THREAD_LOCAL thread_local + #elif defined(__GNUC__) + #define IO_THREAD_LOCAL __thread + #elif defined(_MSC_VER) + #define IO_THREAD_LOCAL __declspec(thread) + #elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L && !defined(__STDC_NO_THREADS__) + #define IO_THREAD_LOCAL _Thread_local + #elif defined(__TINYC__) + #define IO_THREAD_LOCAL _Thread_local + #else + #error Couldnt figure out thread local, needs to be provided manually + #endif +#endif + +#if defined(__has_attribute) + #if __has_attribute(format) + #define IO__PrintfFormat(fmt, va) __attribute__((format(printf, fmt, va))) + #endif +#endif + +#ifndef IO__PrintfFormat + #define IO__PrintfFormat(fmt, va) +#endif + +typedef void IO_MessageHandler(int kind, const char *file, int line, char *str, int len); +extern IO_THREAD_LOCAL void (*IO_User_OutputMessage)(int kind, const char *file, int line, char *str, int len); + +#define IO__STRINGIFY(x) #x +#define IO__TOSTRING(x) IO__STRINGIFY(x) +#define IO_LINE IO__TOSTRING(__LINE__) + +#define IO_Assert(x) !(x) && IO__FatalError((__FILE__ "(" IO_LINE "): " \ + "error: " #x "\n")) && \ + IO_DebugBreak() +#define IO_FatalErrorf(...) \ + do { \ + bool result = IO__FatalErrorf(__FILE__, __LINE__, __VA_ARGS__); \ + if (result) IO_DebugBreak(); \ + } while (0) +#define IO_FatalError(...) \ + do { \ + bool result = IO__FatalError(__FILE__ "(" IO_LINE "): error - " __VA_ARGS__); \ + if (result) IO_DebugBreak(); \ + } while (0) +#define IO_Assertf(x, ...) \ + do { \ + if (!(x)) { \ + bool result = IO__FatalErrorf(__FILE__, __LINE__, __VA_ARGS__); \ + if (result) IO_DebugBreak(); \ + } \ + } while (0) + +#define IO_InvalidElseIf(c) \ + else if (c) { \ + IO_InvalidCodepath(); \ + } +#define IO_InvalidElse() \ + else { \ + IO_InvalidCodepath(); \ + } +#define IO_InvalidCodepath() IO_FatalError("This codepath is invalid") +#define IO_InvalidDefaultCase() \ +default: { \ + IO_FatalError("Entered invalid switch statement case"); \ +} +#define IO_Todo() IO_FatalError("This codepath is not implemented yet") + +IO_API bool IO__FatalErrorf(const char *file, int line, const char *msg, ...) IO__PrintfFormat(3, 4); +IO_API void IO__Printf(int kind, const char *file, int line, const char *msg, ...) IO__PrintfFormat(4, 5); +IO_API bool IO__FatalError(const char *msg); +IO_API void IO_Print(int kind, const char *file, int line, char *msg, int len); +IO_API void IO_OutputMessage(char *str, int len); +IO_API IO_ErrorResult IO_OutputError(char *str, int len); +IO_API void IO_Exit(int error_code); +IO_API bool IO_IsDebuggerPresent(void); + +static const int IO_KindPrintf = 1; +static const int IO_KindWarningf = 2; + +#define IO_Printf(...) IO__Printf(IO_KindPrintf, __FILE__, __LINE__, __VA_ARGS__) +#define IO_Warningf(...) IO__Printf(IO_KindWarningf, __FILE__, __LINE__, __VA_ARGS__) +#endif \ No newline at end of file diff --git a/src/build_tool/standalone_libraries/linked_list.h b/src/build_tool/standalone_libraries/linked_list.h new file mode 100644 index 0000000..7ae9625 --- /dev/null +++ b/src/build_tool/standalone_libraries/linked_list.h @@ -0,0 +1,119 @@ +#ifndef FIRST_LL_HEADER +#define FIRST_LL_HEADER +#define SLL_QUEUE_ADD_MOD(f, l, n, next) \ + do { \ + (n)->next = 0; \ + if ((f) == 0) { \ + (f) = (l) = (n); \ + } else { \ + (l) = (l)->next = (n); \ + } \ + } while (0) +#define SLL_QUEUE_ADD(f, l, n) SLL_QUEUE_ADD_MOD(f, l, n, next) + +#define SLL_QUEUE_POP_FIRST_MOD(f, l, next) \ + do { \ + if ((f) == (l)) { \ + (f) = (l) = 0; \ + } else { \ + (f) = (f)->next; \ + } \ + } while (0) +#define SLL_QUEUE_POP_FIRST(f, l) SLL_QUEUE_POP_FIRST_MOD(f, l, next) + +#define SLL_STACK_ADD_MOD(stack_base, new_stack_base, next) \ + do { \ + (new_stack_base)->next = (stack_base); \ + (stack_base) = (new_stack_base); \ + } while (0) +#define SLL_STACK_ADD(stack_base, new_stack_base) \ + SLL_STACK_ADD_MOD(stack_base, new_stack_base, next) + +#define SLL_STACK_POP_AND_STORE(stack_base, out_node) \ + do { \ + if (stack_base) { \ + (out_node) = (stack_base); \ + (stack_base) = (stack_base)->next; \ + (out_node)->next = 0; \ + } \ + } while (0) + +#define DLL_QUEUE_ADD_MOD(f, l, node, next, prev) \ + do { \ + if ((f) == 0) { \ + (f) = (l) = (node); \ + (node)->prev = 0; \ + (node)->next = 0; \ + } else { \ + (l)->next = (node); \ + (node)->prev = (l); \ + (node)->next = 0; \ + (l) = (node); \ + } \ + } while (0) +#define DLL_QUEUE_ADD(f, l, node) DLL_QUEUE_ADD_MOD(f, l, node, next, prev) +#define DLL_QUEUE_ADD_FRONT(f, l, node) DLL_QUEUE_ADD_MOD(l, f, node, prev, next) +#define DLL_QUEUE_REMOVE_MOD(first, last, node, next, prev) \ + 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; \ + (node)->next = 0; \ + } \ + } while (0) +#define DLL_QUEUE_REMOVE(first, last, node) DLL_QUEUE_REMOVE_MOD(first, last, node, next, prev) + +#define DLL_STACK_ADD_MOD(first, node, next, prev) \ + do { \ + (node)->next = (first); \ + if ((first)) \ + (first)->prev = (node); \ + (first) = (node); \ + (node)->prev = 0; \ + } while (0) +#define DLL_STACK_ADD(first, node) DLL_STACK_ADD_MOD(first, node, next, prev) +#define DLL_STACK_REMOVE_MOD(first, node, next, prev) \ + do { \ + if ((node) == (first)) { \ + (first) = (first)->next; \ + if ((first)) \ + (first)->prev = 0; \ + } else { \ + (node)->prev->next = (node)->next; \ + if ((node)->next) \ + (node)->next->prev = (node)->prev; \ + } \ + if (node) { \ + (node)->prev = 0; \ + (node)->next = 0; \ + } \ + } while (0) +#define DLL_STACK_REMOVE(first, node) DLL_STACK_REMOVE_MOD(first, node, next, prev) + +#define DLL_INSERT_NEXT_MOD(base, new, next, prev) \ + do { \ + if ((base) == 0) { \ + (base) = (new); \ + (new)->next = 0; \ + (new)->prev = 0; \ + } else { \ + (new)->next = (base)->next; \ + (base)->next = (new); \ + (new)->prev = (base); \ + if ((new)->next) (new)->next->prev = (new); \ + } \ + } while (0) +#define DLL_INSERT_NEXT(base, new) DLL_INSERT_NEXT_MOD(base, new, next, prev) +#define DLL_INSERT_PREV(base, new) DLL_INSERT_NEXT_MOD(base, new, next, prev) +#endif \ No newline at end of file diff --git a/src/build_tool/standalone_libraries/load_library.c b/src/build_tool/standalone_libraries/load_library.c new file mode 100644 index 0000000..eda26be --- /dev/null +++ b/src/build_tool/standalone_libraries/load_library.c @@ -0,0 +1,26 @@ +#include "load_library.h" +#ifdef _WIN32 + #ifndef NOMINMAX + #define NOMINMAX + #endif + #ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN + #endif + #include + +LIB_Library LIB_LoadLibrary(char *str) { + HMODULE module = LoadLibraryA(str); + return (LIB_Library)module; +} + +void *LIB_LoadSymbol(LIB_Library lib, char *symbol) { + void *result = (void *)GetProcAddress((HMODULE)lib, symbol); + return result; +} + +bool LIB_UnloadLibrary(LIB_Library lib) { + BOOL result = FreeLibrary((HMODULE)lib); + if (result == 0) return false; + return true; +} +#endif // _WIN32 diff --git a/src/build_tool/standalone_libraries/load_library.h b/src/build_tool/standalone_libraries/load_library.h new file mode 100644 index 0000000..3c98bbd --- /dev/null +++ b/src/build_tool/standalone_libraries/load_library.h @@ -0,0 +1,12 @@ +#ifndef FIRST_LIB_HEADER +#define FIRST_LIB_HEADER +typedef void *LIB_Library; + +LIB_Library LIB_LoadLibrary(char *str); +void *LIB_LoadSymbol(LIB_Library lib, char *symbol); +bool LIB_UnloadLibrary(LIB_Library lib); + +#ifndef LIB_EXPORT + #define LIB_EXPORT __declspec(dllexport) +#endif +#endif \ No newline at end of file diff --git a/src/build_tool/standalone_libraries/multimedia.c b/src/build_tool/standalone_libraries/multimedia.c new file mode 100644 index 0000000..1b49cc1 --- /dev/null +++ b/src/build_tool/standalone_libraries/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/src/build_tool/standalone_libraries/multimedia.h b/src/build_tool/standalone_libraries/multimedia.h new file mode 100644 index 0000000..241bf09 --- /dev/null +++ b/src/build_tool/standalone_libraries/multimedia.h @@ -0,0 +1,370 @@ +#ifndef FIRST_MU_HEADER +#define FIRST_MU_HEADER +#include +#include +#include +#ifndef MU_API + #ifdef __cplusplus + #define MU_API extern "C" + #else + #define MU_API + #endif +#endif + +#ifndef MU_INLINE + #ifndef _MSC_VER + #ifdef __cplusplus + #define MU_INLINE inline + #else + #define MU_INLINE + #endif + #else + #define MU_INLINE __forceinline + #endif +#endif + +#ifndef MU_Float2 + #define MU_Float2 MU__Float2 +typedef struct MU__Float2 { + float x; + float y; +} MU__Float2; +#endif + +#ifndef MU_Int2 + #define MU_Int2 MU__Int2 +typedef struct MU__Int2 { + int x; + int y; +} MU__Int2; +#endif + +//@begin gen_structs +typedef struct MU_UTF32Result MU_UTF32Result; +typedef struct MU_UTF8Result MU_UTF8Result; +typedef struct MU_Win32 MU_Win32; +typedef struct MU_Win32_Window MU_Win32_Window; +typedef struct MU_Window_Params MU_Window_Params; +typedef struct MU_Params MU_Params; +typedef struct MU_Key_State MU_Key_State; +typedef struct MU_Mouse_State MU_Mouse_State; +typedef struct MU_DroppedFile MU_DroppedFile; +typedef struct MU_Arena MU_Arena; +typedef struct MU_Window MU_Window; +typedef struct MU_Time MU_Time; +typedef struct MU_Sound MU_Sound; +typedef struct MU_Context MU_Context; +//@end gen_structs + +typedef void *MU_glGetProcAddress(const char *); + +struct MU_Window_Params { + MU_Int2 size; + MU_Int2 pos; + char *title; + bool enable_canvas; + bool resizable; + bool borderless; + bool fps_cursor; +}; + +struct MU_Params { + void *memory; + size_t cap; + + bool enable_opengl; + int opengl_major; + int opengl_minor; + + double delta_time; + MU_Window_Params window; // this controls window when calling MU_Start + void (*sound_callback)(MU_Context *mu, uint16_t *buffer, uint32_t samples_to_fill); +}; + +struct MU_Key_State { + bool down; + bool press; + bool unpress; + bool raw_press; +}; + +typedef enum MU_Key { + MU_KEY_INVALID, + MU_KEY_ESCAPE, + MU_KEY_ENTER, + MU_KEY_TAB, + MU_KEY_BACKSPACE, + MU_KEY_INSERT, + MU_KEY_DELETE, + MU_KEY_RIGHT, + MU_KEY_LEFT, + MU_KEY_DOWN, + MU_KEY_UP, + MU_KEY_PAGE_UP, + MU_KEY_PAGE_DOWN, + MU_KEY_HOME, + MU_KEY_END, + MU_KEY_F1, + MU_KEY_F2, + MU_KEY_F3, + MU_KEY_F4, + MU_KEY_F5, + MU_KEY_F6, + MU_KEY_F7, + MU_KEY_F8, + MU_KEY_F9, + MU_KEY_F10, + MU_KEY_F11, + MU_KEY_F12, + MU_KEY_SPACE = 32, + MU_KEY_APOSTROPHE = 39, + MU_KEY_PLUS = 43, + MU_KEY_COMMA = 44, + MU_KEY_MINUS = 45, + MU_KEY_PERIOD = 46, + MU_KEY_SLASH = 47, + MU_KEY_0 = 48, + MU_KEY_1 = 49, + MU_KEY_2 = 50, + MU_KEY_3 = 51, + MU_KEY_4 = 52, + MU_KEY_5 = 53, + MU_KEY_6 = 54, + MU_KEY_7 = 55, + MU_KEY_8 = 56, + MU_KEY_9 = 57, + MU_KEY_SEMICOLON = 59, + MU_KEY_EQUAL = 61, + MU_KEY_A = 65, + MU_KEY_B = 66, + MU_KEY_C = 67, + MU_KEY_D = 68, + MU_KEY_E = 69, + MU_KEY_F = 70, + MU_KEY_G = 71, + MU_KEY_H = 72, + MU_KEY_I = 73, + MU_KEY_J = 74, + MU_KEY_K = 75, + MU_KEY_L = 76, + MU_KEY_M = 77, + MU_KEY_N = 78, + MU_KEY_O = 79, + MU_KEY_P = 80, + MU_KEY_Q = 81, + MU_KEY_R = 82, + MU_KEY_S = 83, + MU_KEY_T = 84, + MU_KEY_U = 85, + MU_KEY_V = 86, + MU_KEY_W = 87, + MU_KEY_X = 88, + MU_KEY_Y = 89, + MU_KEY_Z = 90, + MU_KEY_LEFT_BRACKET = 91, + MU_KEY_BACKSLASH = 92, + MU_KEY_RIGHT_BRACKET = 93, + MU_KEY_GRAVE_ACCENT = 96, + MU_KEY_F13, + MU_KEY_F14, + MU_KEY_F15, + MU_KEY_F16, + MU_KEY_F17, + MU_KEY_F18, + MU_KEY_F19, + MU_KEY_F20, + MU_KEY_F21, + MU_KEY_F22, + MU_KEY_F23, + MU_KEY_F24, + MU_KEY_KP_0, + MU_KEY_KP_1, + MU_KEY_KP_2, + MU_KEY_KP_3, + MU_KEY_KP_4, + MU_KEY_KP_5, + MU_KEY_KP_6, + MU_KEY_KP_7, + MU_KEY_KP_8, + MU_KEY_KP_9, + MU_KEY_KP_DECIMAL, + MU_KEY_KP_DIVIDE, + MU_KEY_KP_MULTIPLY, + MU_KEY_KP_SUBTRACT, + MU_KEY_KP_ADD, + MU_KEY_KP_ENTER, + MU_KEY_LEFT_SHIFT, + MU_KEY_LEFT_CONTROL, + MU_KEY_LEFT_ALT, + MU_KEY_LEFT_SUPER, + MU_KEY_RIGHT_SHIFT, + MU_KEY_RIGHT_CONTROL, + MU_KEY_RIGHT_ALT, + MU_KEY_RIGHT_SUPER, + MU_KEY_CAPS_LOCK, + MU_KEY_SCROLL_LOCK, + MU_KEY_NUM_LOCK, + MU_KEY_PRINT_SCREEN, + MU_KEY_PAUSE, + MU_KEY_SHIFT, + MU_KEY_CONTROL, + MU_KEY_COUNT, +} MU_Key; + +struct MU_Mouse_State { + MU_Int2 pos; + MU_Float2 posf; + MU_Int2 delta_pos; + MU_Float2 delta_pos_normalized; + MU_Key_State left; + MU_Key_State middle; + MU_Key_State right; + float delta_wheel; // @todo: add smooth delta? +}; + +struct MU_DroppedFile { + MU_DroppedFile *next; + char *filename; // null terminated + int filename_size; +}; + +struct MU_Arena { + char *memory; + size_t len; + size_t cap; +}; + +// Most of the fields in the window struct are read only. They are updated +// in appropriate update functions. The window should belong to the MU_Context +// but you get access to the information. +struct MU_Window { + MU_Int2 size; + MU_Float2 sizef; + MU_Int2 pos; + MU_Float2 posf; + float dpi_scale; + bool is_fullscreen; + bool is_fps_mode; + bool is_focused; + bool change_cursor_on_mouse_hold; // @in @out + uint64_t processed_events_this_frame; + bool should_render; // @in @out this is false on first frame but it doesn't matter cause it shouldnt be rendered + + MU_DroppedFile *first_dropped_file; + + uint32_t *canvas; + bool canvas_enabled; // @in @out + + MU_Mouse_State mouse; + MU_Key_State key[MU_KEY_COUNT]; + + uint32_t user_text32[32]; + int user_text32_count; + + char user_text8[32]; + int user_text8_count; + + MU_Window *next; + void *handle; + void *platform; +}; + +struct MU_Time { + double app_start; + double frame_start; + + double update; + double update_total; + + double delta; + float deltaf; + double total; + float totalf; +}; + +struct MU_Sound { + bool initialized; + unsigned samples_per_second; + unsigned number_of_channels; + unsigned bytes_per_sample; + void (*callback)(MU_Context *mu, uint16_t *buffer, uint32_t samples_to_fill); +}; + +struct MU_Context { + bool quit; + + MU_Sound sound; + MU_Time time; + bool first_frame; + int _MU_Update_count; + size_t frame; + size_t consecutive_missed_frames; + size_t total_missed_frames; + + MU_Int2 primary_monitor_size; + bool opengl_initialized; + int opengl_major; + int opengl_minor; + void *(*gl_get_proc_address)(const char *str); + + MU_Params params; + MU_Window *window; + MU_Window *all_windows; + MU_Arena perm_arena; + MU_Arena frame_arena; // Reset at beginning of MU_Update + void *platform; +}; + +//@begin gen_api_funcs +MU_API void MU_Quit(MU_Context *mu); +MU_API void MU_DefaultSoundCallback(MU_Context *mu, uint16_t *buffer, uint32_t samples_to_fill); +MU_API double MU_GetTime(void); +MU_API void MU_ToggleFPSMode(MU_Window *window); +MU_API void MU_DisableFPSMode(MU_Window *window); +MU_API void MU_EnableFPSMode(MU_Window *window); +MU_API void MU_ToggleFullscreen(MU_Window *window); +MU_API void MU_Init(MU_Context *mu, MU_Params params, size_t len); +MU_API MU_Window *MU_AddWindow(MU_Context *mu, MU_Window_Params params); +MU_API void MU_InitWindow(MU_Context *mu, MU_Window *window, MU_Window_Params params); +MU_API MU_Context *MU_Start(MU_Params params); +MU_API bool MU_Update(MU_Context *mu); +//@end gen_api_funcs + +/* @! In the future, api for processing messages manually + + while(true) { + MU_Event event; + while (mu_get_event_blocking(&event)) { + switch(event.kind) { + + } + } + } + +typedef int MU_Modifier; +enum MU_Modifier { + MU_MODIFIER_SHIFT = 0x1, // left or right shift key + MU_MODIFIER_CTRL = 0x2, // left or right control key + MU_MODIFIER_ALT = 0x4, // left or right alt key + MU_MODIFIER_SUPER = 0x8, // left or right 'super' key + MU_MODIFIER_LMB = 0x100, // left mouse button + MU_MODIFIER_RMB = 0x200, // right mouse button + MU_MODIFIER_MMB = 0x400, // middle mouse button +}; + +typedef enum MU_Event_Kind { + MU_EVENT_KIND_INVALID, + MU_EVENT_KIND_KEY_DOWN, + MU_EVENT_KIND_KEY_UP, + MU_EVENT_KIND_MOUSE_MOVE, +} MU_Event_Kind; + +typedef struct MU_Event { + MU_Event_Kind kind; + MU_Modifier modifier; + MU_Key key; +} MU_Event; + + + */ +#endif \ No newline at end of file diff --git a/src/build_tool/standalone_libraries/preproc_env.h b/src/build_tool/standalone_libraries/preproc_env.h new file mode 100644 index 0000000..77b8084 --- /dev/null +++ b/src/build_tool/standalone_libraries/preproc_env.h @@ -0,0 +1,119 @@ +#ifndef FIRST_ENV_HEADER +#define FIRST_ENV_HEADER +#ifndef _CRT_SECURE_NO_WARNINGS + #define _CRT_SECURE_NO_WARNINGS +#endif + +#if defined(__APPLE__) && defined(__MACH__) + #define OS_MAC 1 +#elif defined(_WIN32) + #define OS_WINDOWS 1 +#elif defined(__linux__) + #define OS_POSIX 1 + #define OS_LINUX 1 +#elif OS_WASM +#else + #error Unsupported platform +#endif + +#if defined(__clang__) + #define COMPILER_CLANG 1 +#elif defined(__GNUC__) || defined(__GNUG__) + #define COMPILER_GCC 1 +#elif defined(_MSC_VER) + #define COMPILER_MSVC 1 +#elif defined(__TINYC__) + #define COMPILER_TCC 1 +#else + #error Unsupported compiler +#endif + +#ifdef __cplusplus + #define LANG_CPP 1 +#else + #define LANG_C 1 +#endif + +#ifndef OS_MAC + #define OS_MAC 0 +#endif + +#ifndef OS_WINDOWS + #define OS_WINDOWS 0 +#endif + +#ifndef OS_LINUX + #define OS_LINUX 0 +#endif + +#ifndef OS_POSIX + #define OS_POSIX 0 +#endif + +#ifndef COMPILER_MSVC + #define COMPILER_MSVC 0 +#endif + +#ifndef COMPILER_CLANG + #define COMPILER_CLANG 0 +#endif + +#ifndef COMPILER_GCC + #define COMPILER_GCC 0 +#endif + +#ifndef COMPILER_TCC + #define COMPILER_TCC 0 +#endif + +#ifndef LANG_CPP + #define LANG_CPP 0 +#endif + +#ifndef LANG_C + #define LANG_C 0 +#endif + +#if COMPILER_MSVC + #define FORCE_INLINE __forceinline +#elif COMPILER_GCC || COMPILER_CLANG + #define FORCE_INLINE __attribute__((always_inline)) inline +#else + #define FORCE_INLINE inline +#endif + +#if OS_MAC + #define IF_MAC(x) x +#else + #define IF_MAC(x) +#endif + +#if OS_WINDOWS + #define IF_WINDOWS(x) x + #define IF_WINDOWS_ELSE(x, y) x +#else + #define IF_WINDOWS(x) + #define IF_WINDOWS_ELSE(x, y) y +#endif + +#if OS_LINUX + #define IF_LINUX(x) x + #define IF_LINUX_ELSE(x, y) x +#else + #define IF_LINUX(x) + #define IF_LINUX_ELSE(x, y) y +#endif + +#if OS_WINDOWS + #define OS_NAME "windows" +#elif OS_LINUX + #define OS_NAME "linux" +#elif OS_MAC + #define OS_NAME "mac_os" +#elif OS_WASM + #define OS_NAME "wasm" +#else + #error couldnt figure out OS +#endif + +#endif \ No newline at end of file diff --git a/src/build_tool/standalone_libraries/regex.c b/src/build_tool/standalone_libraries/regex.c new file mode 100644 index 0000000..3fd3e4e --- /dev/null +++ b/src/build_tool/standalone_libraries/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/src/build_tool/standalone_libraries/regex.h b/src/build_tool/standalone_libraries/regex.h new file mode 100644 index 0000000..aca1448 --- /dev/null +++ b/src/build_tool/standalone_libraries/regex.h @@ -0,0 +1,95 @@ +#ifndef FIRST_REGEX_HEADER +#define FIRST_REGEX_HEADER +#include +#include + +#ifndef RE_Int + #define RE_Int int64_t +#endif + +#ifndef RE_API + #ifdef __cplusplus + #define RE_API extern "C" + #else + #define RE_API + #endif +#endif + +#ifndef RE_StaticFunc + #if defined(__GNUC__) || defined(__clang__) + #define RE_StaticFunc __attribute__((unused)) static + #else + #define RE_StaticFunc static + #endif +#endif + +typedef struct RE_String RE_String; +typedef struct RE_Utf32Result RE_Utf32Result; +typedef struct RE_Parser RE_Parser; +typedef struct RE_Regex RE_Regex; +typedef struct RE_Match RE_Match; + +/* @todo +Add \W \D \S oppsites +*/ + +typedef enum RE_MatchKind { + RE_MATCH_NULL, + RE_MATCH_FRONT, + RE_MATCH_BACK, + RE_MATCH_WORD, + RE_MATCH_OR, + RE_MATCH_GROUP, + RE_MATCH_SELECTED, + RE_MATCH_NOT_SELECTED, + RE_MATCH_RANGE, + RE_MATCH_ANY, + RE_MATCH_ANY_WORD, + RE_MATCH_ANY_DIGIT, + RE_MATCH_ANY_WHITESPACE, + RE_MATCH_ONE_OR_MORE, + RE_MATCH_ZERO_OR_MORE, + RE_MATCH_ZERO_OR_ONE, +} RE_MatchKind; + +struct RE_Regex { + RE_MatchKind kind; + RE_Regex *next; + RE_Regex *prev; + + union { + struct { + char word_min; + char word_max; + }; + char word; + uint32_t word32; + RE_Regex *child; + struct { + RE_Regex *left; + RE_Regex *right; + }; + struct { + RE_Regex *first; + RE_Regex *last; + } group; + }; +}; + +struct RE_Match { + RE_Int pos; + RE_Int size; +}; + +RE_API bool RE1_AreEqual(char *regex, char *string); +RE_API bool RE2_AreEqual(RE_Regex *regex, char *string); +RE_API bool RE3_AreEqual(RE_Regex *regex, char *string, RE_Int len); +RE_API RE_Match RE1_Find(char *regex, char *string); +RE_API RE_Match RE2_Find(RE_Regex *regex, char *string); +RE_API RE_Match RE3_Find(RE_Regex *regex, char *string, RE_Int len); +RE_API RE_Match RE2_FindAgain(RE_Regex *regex, char *string, RE_Match prev_match); +RE_API RE_Match RE3_FindAgain(RE_Regex *regex, char *string, RE_Int len, RE_Match prev_match); +RE_API RE_Int RE3_MatchFront(RE_Regex *regex, char *string, RE_Int len, char *string_front); +RE_API RE_Regex *RE1_Parse(char *buff, RE_Int buffsize, char *string); +RE_API RE_Regex *RE2_Parse(char *buff, RE_Int buffsize, char *string, RE_Int len); +#endif \ No newline at end of file diff --git a/src/build_tool/standalone_libraries/stb_sprintf.c b/src/build_tool/standalone_libraries/stb_sprintf.c new file mode 100644 index 0000000..c6b7a25 --- /dev/null +++ b/src/build_tool/standalone_libraries/stb_sprintf.c @@ -0,0 +1,1669 @@ + +#define stbsp__uint32 unsigned int +#define stbsp__int32 signed int + +#ifdef _MSC_VER + #define stbsp__uint64 unsigned __int64 + #define stbsp__int64 signed __int64 +#else + #define stbsp__uint64 unsigned long long + #define stbsp__int64 signed long long +#endif +#define stbsp__uint16 unsigned short + +#ifndef stbsp__uintptr + #if defined(__ppc64__) || defined(__powerpc64__) || defined(__aarch64__) || defined(_M_X64) || defined(__x86_64__) || defined(__x86_64) || defined(__s390x__) + #define stbsp__uintptr stbsp__uint64 + #else + #define stbsp__uintptr stbsp__uint32 + #endif +#endif + +#ifndef STB_SPRINTF_MSVC_MODE // used for MSVC2013 and earlier (MSVC2015 matches GCC) + #if defined(_MSC_VER) && (_MSC_VER < 1900) + #define STB_SPRINTF_MSVC_MODE + #endif +#endif + +#ifdef STB_SPRINTF_NOUNALIGNED // define this before inclusion to force stbsp_sprintf to always use aligned accesses + #define STBSP__UNALIGNED(code) +#else + #define STBSP__UNALIGNED(code) code +#endif + +#ifndef STB_SPRINTF_NOFLOAT +// internal float utility functions +static stbsp__int32 stbsp__real_to_str(char const **start, stbsp__uint32 *len, char *out, stbsp__int32 *decimal_pos, double value, stbsp__uint32 frac_digits); +static stbsp__int32 stbsp__real_to_parts(stbsp__int64 *bits, stbsp__int32 *expo, double value); + #define STBSP__SPECIAL 0x7000 +#endif + +static char stbsp__period = '.'; +static char stbsp__comma = ','; +static struct +{ + short temp; // force next field to be 2-byte aligned + char pair[201]; +} stbsp__digitpair = + { + 0, + "00010203040506070809101112131415161718192021222324" + "25262728293031323334353637383940414243444546474849" + "50515253545556575859606162636465666768697071727374" + "75767778798081828384858687888990919293949596979899"}; + +STBSP__PUBLICDEF void STB_SPRINTF_DECORATE(set_separators)(char pcomma, char pperiod) { + stbsp__period = pperiod; + stbsp__comma = pcomma; +} + +#define STBSP__LEFTJUST 1 +#define STBSP__LEADINGPLUS 2 +#define STBSP__LEADINGSPACE 4 +#define STBSP__LEADING_0X 8 +#define STBSP__LEADINGZERO 16 +#define STBSP__INTMAX 32 +#define STBSP__TRIPLET_COMMA 64 +#define STBSP__NEGATIVE 128 +#define STBSP__METRIC_SUFFIX 256 +#define STBSP__HALFWIDTH 512 +#define STBSP__METRIC_NOSPACE 1024 +#define STBSP__METRIC_1024 2048 +#define STBSP__METRIC_JEDEC 4096 + +static void stbsp__lead_sign(stbsp__uint32 fl, char *sign) { + sign[0] = 0; + if (fl & STBSP__NEGATIVE) { + sign[0] = 1; + sign[1] = '-'; + } + else if (fl & STBSP__LEADINGSPACE) { + sign[0] = 1; + sign[1] = ' '; + } + else if (fl & STBSP__LEADINGPLUS) { + sign[0] = 1; + sign[1] = '+'; + } +} + +static STBSP__ASAN stbsp__uint32 stbsp__strlen_limited(char const *s, stbsp__uint32 limit) { + char const *sn = s; + + // get up to 4-byte alignment + for (;;) { + if (((stbsp__uintptr)sn & 3) == 0) + break; + + if (!limit || *sn == 0) + return (stbsp__uint32)(sn - s); + + ++sn; + --limit; + } + + // scan over 4 bytes at a time to find terminating 0 + // this will intentionally scan up to 3 bytes past the end of buffers, + // but becase it works 4B aligned, it will never cross page boundaries + // (hence the STBSP__ASAN markup; the over-read here is intentional + // and harmless) + while (limit >= 4) { + stbsp__uint32 v = *(stbsp__uint32 *)sn; + // bit hack to find if there's a 0 byte in there + if ((v - 0x01010101) & (~v) & 0x80808080UL) + break; + + sn += 4; + limit -= 4; + } + + // handle the last few characters to find actual size + while (limit && *sn) { + ++sn; + --limit; + } + + return (stbsp__uint32)(sn - s); +} + +STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(vsprintfcb)(STBSP_SPRINTFCB *callback, void *user, char *buf, char const *fmt, va_list va) { + static char hex[] = "0123456789abcdefxp"; + static char hexu[] = "0123456789ABCDEFXP"; + char *bf; + char const *f; + int tlen = 0; + + bf = buf; + f = fmt; + for (;;) { + stbsp__int32 fw, pr, tz; + stbsp__uint32 fl; + + // macros for the callback buffer stuff +#define stbsp__chk_cb_bufL(bytes) \ + { \ + int len = (int)(bf - buf); \ + if ((len + (bytes)) >= STB_SPRINTF_MIN) { \ + tlen += len; \ + if (0 == (bf = buf = callback(buf, user, len))) \ + goto done; \ + } \ + } +#define stbsp__chk_cb_buf(bytes) \ + { \ + if (callback) { \ + stbsp__chk_cb_bufL(bytes); \ + } \ + } +#define stbsp__flush_cb() \ + { \ + stbsp__chk_cb_bufL(STB_SPRINTF_MIN - 1); \ + } // flush if there is even one byte in the buffer +#define stbsp__cb_buf_clamp(cl, v) \ + cl = v; \ + if (callback) { \ + int lg = STB_SPRINTF_MIN - (int)(bf - buf); \ + if (cl > lg) \ + cl = lg; \ + } + + // fast copy everything up to the next % (or end of string) + for (;;) { + while (((stbsp__uintptr)f) & 3) { + schk1: + if (f[0] == '%') + goto scandd; + schk2: + if (f[0] == 0) + goto endfmt; + stbsp__chk_cb_buf(1); + *bf++ = f[0]; + ++f; + } + for (;;) { + // Check if the next 4 bytes contain %(0x25) or end of string. + // Using the 'hasless' trick: + // https://graphics.stanford.edu/~seander/bithacks.html#HasLessInWord + stbsp__uint32 v, c; + v = *(stbsp__uint32 *)f; + c = (~v) & 0x80808080; + if (((v ^ 0x25252525) - 0x01010101) & c) + goto schk1; + if ((v - 0x01010101) & c) + goto schk2; + if (callback) + if ((STB_SPRINTF_MIN - (int)(bf - buf)) < 4) + goto schk1; +#ifdef STB_SPRINTF_NOUNALIGNED + if (((stbsp__uintptr)bf) & 3) { + bf[0] = f[0]; + bf[1] = f[1]; + bf[2] = f[2]; + bf[3] = f[3]; + } + else +#endif + { + *(stbsp__uint32 *)bf = v; + } + bf += 4; + f += 4; + } + } + scandd: + + ++f; + + // ok, we have a percent, read the modifiers first + fw = 0; + pr = -1; + fl = 0; + tz = 0; + + // flags + for (;;) { + switch (f[0]) { + // if we have left justify + case '-': + fl |= STBSP__LEFTJUST; + ++f; + continue; + // if we have leading plus + case '+': + fl |= STBSP__LEADINGPLUS; + ++f; + continue; + // if we have leading space + case ' ': + fl |= STBSP__LEADINGSPACE; + ++f; + continue; + // if we have leading 0x + case '#': + fl |= STBSP__LEADING_0X; + ++f; + continue; + // if we have thousand commas + case '\'': + fl |= STBSP__TRIPLET_COMMA; + ++f; + continue; + // if we have kilo marker (none->kilo->kibi->jedec) + case '$': + if (fl & STBSP__METRIC_SUFFIX) { + if (fl & STBSP__METRIC_1024) { + fl |= STBSP__METRIC_JEDEC; + } + else { + fl |= STBSP__METRIC_1024; + } + } + else { + fl |= STBSP__METRIC_SUFFIX; + } + ++f; + continue; + // if we don't want space between metric suffix and number + case '_': + fl |= STBSP__METRIC_NOSPACE; + ++f; + continue; + // if we have leading zero + case '0': + fl |= STBSP__LEADINGZERO; + ++f; + goto flags_done; + default: goto flags_done; + } + } + flags_done: + + // get the field width + if (f[0] == '*') { + fw = va_arg(va, stbsp__uint32); + ++f; + } + else { + while ((f[0] >= '0') && (f[0] <= '9')) { + fw = fw * 10 + f[0] - '0'; + f++; + } + } + // get the precision + if (f[0] == '.') { + ++f; + if (f[0] == '*') { + pr = va_arg(va, stbsp__uint32); + ++f; + } + else { + pr = 0; + while ((f[0] >= '0') && (f[0] <= '9')) { + pr = pr * 10 + f[0] - '0'; + f++; + } + } + } + + // handle integer size overrides + switch (f[0]) { + // are we halfwidth? + case 'h': + fl |= STBSP__HALFWIDTH; + ++f; + if (f[0] == 'h') + ++f; // QUARTERWIDTH + break; + // are we 64-bit (unix style) + case 'l': + fl |= ((sizeof(long) == 8) ? STBSP__INTMAX : 0); + ++f; + if (f[0] == 'l') { + fl |= STBSP__INTMAX; + ++f; + } + break; + // are we 64-bit on intmax? (c99) + case 'j': + fl |= (sizeof(size_t) == 8) ? STBSP__INTMAX : 0; + ++f; + break; + // are we 64-bit on size_t or ptrdiff_t? (c99) + case 'z': + fl |= (sizeof(ptrdiff_t) == 8) ? STBSP__INTMAX : 0; + ++f; + break; + case 't': + fl |= (sizeof(ptrdiff_t) == 8) ? STBSP__INTMAX : 0; + ++f; + break; + // are we 64-bit (msft style) + case 'I': + if ((f[1] == '6') && (f[2] == '4')) { + fl |= STBSP__INTMAX; + f += 3; + } + else if ((f[1] == '3') && (f[2] == '2')) { + f += 3; + } + else { + fl |= ((sizeof(void *) == 8) ? STBSP__INTMAX : 0); + ++f; + } + break; + default: break; + } + + // handle each replacement + switch (f[0]) { +#define STBSP__NUMSZ 512 // big enough for e308 (with commas) or e-307 + char num[STBSP__NUMSZ]; + char lead[8]; + char tail[8]; + char *s; + char const *h; + stbsp__uint32 l, n, cs; + stbsp__uint64 n64; +#ifndef STB_SPRINTF_NOFLOAT + double fv; +#endif + stbsp__int32 dp; + char const *sn; + struct STB_STRING { + char *str; + int64_t len; + }; + struct STB_STRING str; + + case 'Q': + str = va_arg(va, struct STB_STRING); + if (str.str == 0 && str.len != 0) { + str.str = (char *)"null"; + str.len = 4; + } + pr = (int)str.len; + s = (char *)str.str; + l = stbsp__strlen_limited(s, (pr >= 0) ? pr : ~0u); + lead[0] = 0; + tail[0] = 0; + pr = 0; + dp = 0; + cs = 0; + goto scopy; + + case 's': + // get the string + s = va_arg(va, char *); + if (s == 0) + s = (char *)"null"; + // get the length, limited to desired precision + // always limit to ~0u chars since our counts are 32b + l = stbsp__strlen_limited(s, (pr >= 0) ? pr : ~0u); + lead[0] = 0; + tail[0] = 0; + pr = 0; + dp = 0; + cs = 0; + // copy the string in + goto scopy; + + case 'c': // char + // get the character + s = num + STBSP__NUMSZ - 1; + *s = (char)va_arg(va, int); + l = 1; + lead[0] = 0; + tail[0] = 0; + pr = 0; + dp = 0; + cs = 0; + goto scopy; + + case 'n': // weird write-bytes specifier + { + int *d = va_arg(va, int *); + *d = tlen + (int)(bf - buf); + } break; + +#ifdef STB_SPRINTF_NOFLOAT + case 'A': // float + case 'a': // hex float + case 'G': // float + case 'g': // float + case 'E': // float + case 'e': // float + case 'f': // float + va_arg(va, double); // eat it + s = (char *)"No float"; + l = 8; + lead[0] = 0; + tail[0] = 0; + pr = 0; + cs = 0; + STBSP__NOTUSED(dp); + goto scopy; +#else + case 'A': // hex float + case 'a': // hex float + h = (f[0] == 'A') ? hexu : hex; + fv = va_arg(va, double); + if (pr == -1) + pr = 6; // default is 6 + // read the double into a string + if (stbsp__real_to_parts((stbsp__int64 *)&n64, &dp, fv)) + fl |= STBSP__NEGATIVE; + + s = num + 64; + + stbsp__lead_sign(fl, lead); + + if (dp == -1023) + dp = (n64) ? -1022 : 0; + else + n64 |= (((stbsp__uint64)1) << 52); + n64 <<= (64 - 56); + if (pr < 15) + n64 += ((((stbsp__uint64)8) << 56) >> (pr * 4)); + // add leading chars + + #ifdef STB_SPRINTF_MSVC_MODE + *s++ = '0'; + *s++ = 'x'; + #else + lead[1 + lead[0]] = '0'; + lead[2 + lead[0]] = 'x'; + lead[0] += 2; + #endif + *s++ = h[(n64 >> 60) & 15]; + n64 <<= 4; + if (pr) + *s++ = stbsp__period; + sn = s; + + // print the bits + n = pr; + if (n > 13) + n = 13; + if (pr > (stbsp__int32)n) + tz = pr - n; + pr = 0; + while (n--) { + *s++ = h[(n64 >> 60) & 15]; + n64 <<= 4; + } + + // print the expo + tail[1] = h[17]; + if (dp < 0) { + tail[2] = '-'; + dp = -dp; + } + else + tail[2] = '+'; + n = (dp >= 1000) ? 6 : ((dp >= 100) ? 5 : ((dp >= 10) ? 4 : 3)); + tail[0] = (char)n; + for (;;) { + tail[n] = '0' + dp % 10; + if (n <= 3) + break; + --n; + dp /= 10; + } + + dp = (int)(s - sn); + l = (int)(s - (num + 64)); + s = num + 64; + cs = 1 + (3 << 24); + goto scopy; + + case 'G': // float + case 'g': // float + h = (f[0] == 'G') ? hexu : hex; + fv = va_arg(va, double); + if (pr == -1) + pr = 6; + else if (pr == 0) + pr = 1; // default is 6 + // read the double into a string + if (stbsp__real_to_str(&sn, &l, num, &dp, fv, (pr - 1) | 0x80000000)) + fl |= STBSP__NEGATIVE; + + // clamp the precision and delete extra zeros after clamp + n = pr; + if (l > (stbsp__uint32)pr) + l = pr; + while ((l > 1) && (pr) && (sn[l - 1] == '0')) { + --pr; + --l; + } + + // should we use %e + if ((dp <= -4) || (dp > (stbsp__int32)n)) { + if (pr > (stbsp__int32)l) + pr = l - 1; + else if (pr) + --pr; // when using %e, there is one digit before the decimal + goto doexpfromg; + } + // this is the insane action to get the pr to match %g semantics for %f + if (dp > 0) { + pr = (dp < (stbsp__int32)l) ? l - dp : 0; + } + else { + pr = -dp + ((pr > (stbsp__int32)l) ? (stbsp__int32)l : pr); + } + goto dofloatfromg; + + case 'E': // float + case 'e': // float + h = (f[0] == 'E') ? hexu : hex; + fv = va_arg(va, double); + if (pr == -1) + pr = 6; // default is 6 + // read the double into a string + if (stbsp__real_to_str(&sn, &l, num, &dp, fv, pr | 0x80000000)) + fl |= STBSP__NEGATIVE; + doexpfromg: + tail[0] = 0; + stbsp__lead_sign(fl, lead); + if (dp == STBSP__SPECIAL) { + s = (char *)sn; + cs = 0; + pr = 0; + goto scopy; + } + s = num + 64; + // handle leading chars + *s++ = sn[0]; + + if (pr) + *s++ = stbsp__period; + + // handle after decimal + if ((l - 1) > (stbsp__uint32)pr) + l = pr + 1; + for (n = 1; n < l; n++) + *s++ = sn[n]; + // trailing zeros + tz = pr - (l - 1); + pr = 0; + // dump expo + tail[1] = h[0xe]; + dp -= 1; + if (dp < 0) { + tail[2] = '-'; + dp = -dp; + } + else + tail[2] = '+'; + #ifdef STB_SPRINTF_MSVC_MODE + n = 5; + #else + n = (dp >= 100) ? 5 : 4; + #endif + tail[0] = (char)n; + for (;;) { + tail[n] = '0' + dp % 10; + if (n <= 3) + break; + --n; + dp /= 10; + } + cs = 1 + (3 << 24); // how many tens + goto flt_lead; + + case 'f': // float + fv = va_arg(va, double); + doafloat: + // do kilos + if (fl & STBSP__METRIC_SUFFIX) { + double divisor; + divisor = 1000.0f; + if (fl & STBSP__METRIC_1024) + divisor = 1024.0; + while (fl < 0x4000000) { + if ((fv < divisor) && (fv > -divisor)) + break; + fv /= divisor; + fl += 0x1000000; + } + } + if (pr == -1) + pr = 6; // default is 6 + // read the double into a string + if (stbsp__real_to_str(&sn, &l, num, &dp, fv, pr)) + fl |= STBSP__NEGATIVE; + dofloatfromg: + tail[0] = 0; + stbsp__lead_sign(fl, lead); + if (dp == STBSP__SPECIAL) { + s = (char *)sn; + cs = 0; + pr = 0; + goto scopy; + } + s = num + 64; + + // handle the three decimal varieties + if (dp <= 0) { + stbsp__int32 i; + // handle 0.000*000xxxx + *s++ = '0'; + if (pr) + *s++ = stbsp__period; + n = -dp; + if ((stbsp__int32)n > pr) + n = pr; + i = n; + while (i) { + if ((((stbsp__uintptr)s) & 3) == 0) + break; + *s++ = '0'; + --i; + } + while (i >= 4) { + *(stbsp__uint32 *)s = 0x30303030; + s += 4; + i -= 4; + } + while (i) { + *s++ = '0'; + --i; + } + if ((stbsp__int32)(l + n) > pr) + l = pr - n; + i = l; + while (i) { + *s++ = *sn++; + --i; + } + tz = pr - (n + l); + cs = 1 + (3 << 24); // how many tens did we write (for commas below) + } + else { + cs = (fl & STBSP__TRIPLET_COMMA) ? ((600 - (stbsp__uint32)dp) % 3) : 0; + if ((stbsp__uint32)dp >= l) { + // handle xxxx000*000.0 + n = 0; + for (;;) { + if ((fl & STBSP__TRIPLET_COMMA) && (++cs == 4)) { + cs = 0; + *s++ = stbsp__comma; + } + else { + *s++ = sn[n]; + ++n; + if (n >= l) + break; + } + } + if (n < (stbsp__uint32)dp) { + n = dp - n; + if ((fl & STBSP__TRIPLET_COMMA) == 0) { + while (n) { + if ((((stbsp__uintptr)s) & 3) == 0) + break; + *s++ = '0'; + --n; + } + while (n >= 4) { + *(stbsp__uint32 *)s = 0x30303030; + s += 4; + n -= 4; + } + } + while (n) { + if ((fl & STBSP__TRIPLET_COMMA) && (++cs == 4)) { + cs = 0; + *s++ = stbsp__comma; + } + else { + *s++ = '0'; + --n; + } + } + } + cs = (int)(s - (num + 64)) + (3 << 24); // cs is how many tens + if (pr) { + *s++ = stbsp__period; + tz = pr; + } + } + else { + // handle xxxxx.xxxx000*000 + n = 0; + for (;;) { + if ((fl & STBSP__TRIPLET_COMMA) && (++cs == 4)) { + cs = 0; + *s++ = stbsp__comma; + } + else { + *s++ = sn[n]; + ++n; + if (n >= (stbsp__uint32)dp) + break; + } + } + cs = (int)(s - (num + 64)) + (3 << 24); // cs is how many tens + if (pr) + *s++ = stbsp__period; + if ((l - dp) > (stbsp__uint32)pr) + l = pr + dp; + while (n < l) { + *s++ = sn[n]; + ++n; + } + tz = pr - (l - dp); + } + } + pr = 0; + + // handle k,m,g,t + if (fl & STBSP__METRIC_SUFFIX) { + char idx; + idx = 1; + if (fl & STBSP__METRIC_NOSPACE) + idx = 0; + tail[0] = idx; + tail[1] = ' '; + { + if (fl >> 24) { // SI kilo is 'k', JEDEC and SI kibits are 'K'. + if (fl & STBSP__METRIC_1024) + tail[idx + 1] = "_KMGT"[fl >> 24]; + else + tail[idx + 1] = "_kMGT"[fl >> 24]; + idx++; + // If printing kibits and not in jedec, add the 'i'. + if (fl & STBSP__METRIC_1024 && !(fl & STBSP__METRIC_JEDEC)) { + tail[idx + 1] = 'i'; + idx++; + } + tail[0] = idx; + } + } + }; + + flt_lead: + // get the length that we copied + l = (stbsp__uint32)(s - (num + 64)); + s = num + 64; + goto scopy; +#endif + + case 'B': // upper binary + case 'b': // lower binary + h = (f[0] == 'B') ? hexu : hex; + lead[0] = 0; + if (fl & STBSP__LEADING_0X) { + lead[0] = 2; + lead[1] = '0'; + lead[2] = h[0xb]; + } + l = (8 << 4) | (1 << 8); + goto radixnum; + + case 'o': // octal + h = hexu; + lead[0] = 0; + if (fl & STBSP__LEADING_0X) { + lead[0] = 1; + lead[1] = '0'; + } + l = (3 << 4) | (3 << 8); + goto radixnum; + + case 'p': // pointer + fl |= (sizeof(void *) == 8) ? STBSP__INTMAX : 0; + pr = sizeof(void *) * 2; + fl &= ~STBSP__LEADINGZERO; // 'p' only prints the pointer with zeros + // fall through - to X + + case 'X': // upper hex + case 'x': // lower hex + h = (f[0] == 'X') ? hexu : hex; + l = (4 << 4) | (4 << 8); + lead[0] = 0; + if (fl & STBSP__LEADING_0X) { + lead[0] = 2; + lead[1] = '0'; + lead[2] = h[16]; + } + radixnum: + // get the number + if (fl & STBSP__INTMAX) + n64 = va_arg(va, stbsp__uint64); + else + n64 = va_arg(va, stbsp__uint32); + + s = num + STBSP__NUMSZ; + dp = 0; + // clear tail, and clear leading if value is zero + tail[0] = 0; + if (n64 == 0) { + lead[0] = 0; + if (pr == 0) { + l = 0; + cs = 0; + goto scopy; + } + } + // convert to string + for (;;) { + *--s = h[n64 & ((1 << (l >> 8)) - 1)]; + n64 >>= (l >> 8); + if (!((n64) || ((stbsp__int32)((num + STBSP__NUMSZ) - s) < pr))) + break; + if (fl & STBSP__TRIPLET_COMMA) { + ++l; + if ((l & 15) == ((l >> 4) & 15)) { + l &= ~15; + *--s = stbsp__comma; + } + } + }; + // get the tens and the comma pos + cs = (stbsp__uint32)((num + STBSP__NUMSZ) - s) + ((((l >> 4) & 15)) << 24); + // get the length that we copied + l = (stbsp__uint32)((num + STBSP__NUMSZ) - s); + // copy it + goto scopy; + + case 'u': // unsigned + case 'i': + case 'd': // integer + // get the integer and abs it + if (fl & STBSP__INTMAX) { + stbsp__int64 i64 = va_arg(va, stbsp__int64); + n64 = (stbsp__uint64)i64; + if ((f[0] != 'u') && (i64 < 0)) { + n64 = (stbsp__uint64)-i64; + fl |= STBSP__NEGATIVE; + } + } + else { + stbsp__int32 i = va_arg(va, stbsp__int32); + n64 = (stbsp__uint32)i; + if ((f[0] != 'u') && (i < 0)) { + n64 = (stbsp__uint32)-i; + fl |= STBSP__NEGATIVE; + } + } + +#ifndef STB_SPRINTF_NOFLOAT + if (fl & STBSP__METRIC_SUFFIX) { + if (n64 < 1024) + pr = 0; + else if (pr == -1) + pr = 1; + fv = (double)(stbsp__int64)n64; + goto doafloat; + } +#endif + + // convert to string + s = num + STBSP__NUMSZ; + l = 0; + + for (;;) { + // do in 32-bit chunks (avoid lots of 64-bit divides even with constant denominators) + char *o = s - 8; + if (n64 >= 100000000) { + n = (stbsp__uint32)(n64 % 100000000); + n64 /= 100000000; + } + else { + n = (stbsp__uint32)n64; + n64 = 0; + } + if ((fl & STBSP__TRIPLET_COMMA) == 0) { + do { + s -= 2; + *(stbsp__uint16 *)s = *(stbsp__uint16 *)&stbsp__digitpair.pair[(n % 100) * 2]; + n /= 100; + } while (n); + } + while (n) { + if ((fl & STBSP__TRIPLET_COMMA) && (l++ == 3)) { + l = 0; + *--s = stbsp__comma; + --o; + } + else { + *--s = (char)(n % 10) + '0'; + n /= 10; + } + } + if (n64 == 0) { + if ((s[0] == '0') && (s != (num + STBSP__NUMSZ))) + ++s; + break; + } + while (s != o) + if ((fl & STBSP__TRIPLET_COMMA) && (l++ == 3)) { + l = 0; + *--s = stbsp__comma; + --o; + } + else { + *--s = '0'; + } + } + + tail[0] = 0; + stbsp__lead_sign(fl, lead); + + // get the length that we copied + l = (stbsp__uint32)((num + STBSP__NUMSZ) - s); + if (l == 0) { + *--s = '0'; + l = 1; + } + cs = l + (3 << 24); + if (pr < 0) + pr = 0; + + scopy: + // get fw=leading/trailing space, pr=leading zeros + if (pr < (stbsp__int32)l) + pr = l; + n = pr + lead[0] + tail[0] + tz; + if (fw < (stbsp__int32)n) + fw = n; + fw -= n; + pr -= l; + + // handle right justify and leading zeros + if ((fl & STBSP__LEFTJUST) == 0) { + if (fl & STBSP__LEADINGZERO) // if leading zeros, everything is in pr + { + pr = (fw > pr) ? fw : pr; + fw = 0; + } + else { + fl &= ~STBSP__TRIPLET_COMMA; // if no leading zeros, then no commas + } + } + + // copy the spaces and/or zeros + if (fw + pr) { + stbsp__int32 i; + stbsp__uint32 c; + + // copy leading spaces (or when doing %8.4d stuff) + if ((fl & STBSP__LEFTJUST) == 0) + while (fw > 0) { + stbsp__cb_buf_clamp(i, fw); + fw -= i; + while (i) { + if ((((stbsp__uintptr)bf) & 3) == 0) + break; + *bf++ = ' '; + --i; + } + while (i >= 4) { + *(stbsp__uint32 *)bf = 0x20202020; + bf += 4; + i -= 4; + } + while (i) { + *bf++ = ' '; + --i; + } + stbsp__chk_cb_buf(1); + } + + // copy leader + sn = lead + 1; + while (lead[0]) { + stbsp__cb_buf_clamp(i, lead[0]); + lead[0] -= (char)i; + while (i) { + *bf++ = *sn++; + --i; + } + stbsp__chk_cb_buf(1); + } + + // copy leading zeros + c = cs >> 24; + cs &= 0xffffff; + cs = (fl & STBSP__TRIPLET_COMMA) ? ((stbsp__uint32)(c - ((pr + cs) % (c + 1)))) : 0; + while (pr > 0) { + stbsp__cb_buf_clamp(i, pr); + pr -= i; + if ((fl & STBSP__TRIPLET_COMMA) == 0) { + while (i) { + if ((((stbsp__uintptr)bf) & 3) == 0) + break; + *bf++ = '0'; + --i; + } + while (i >= 4) { + *(stbsp__uint32 *)bf = 0x30303030; + bf += 4; + i -= 4; + } + } + while (i) { + if ((fl & STBSP__TRIPLET_COMMA) && (cs++ == c)) { + cs = 0; + *bf++ = stbsp__comma; + } + else + *bf++ = '0'; + --i; + } + stbsp__chk_cb_buf(1); + } + } + + // copy leader if there is still one + sn = lead + 1; + while (lead[0]) { + stbsp__int32 i; + stbsp__cb_buf_clamp(i, lead[0]); + lead[0] -= (char)i; + while (i) { + *bf++ = *sn++; + --i; + } + stbsp__chk_cb_buf(1); + } + + // copy the string + n = l; + while (n) { + stbsp__int32 i; + stbsp__cb_buf_clamp(i, n); + n -= i; + STBSP__UNALIGNED(while (i >= 4) { + *(stbsp__uint32 volatile *)bf = *(stbsp__uint32 volatile *)s; + bf += 4; + s += 4; + i -= 4; + }) + while (i) { + *bf++ = *s++; + --i; + } + stbsp__chk_cb_buf(1); + } + + // copy trailing zeros + while (tz) { + stbsp__int32 i; + stbsp__cb_buf_clamp(i, tz); + tz -= i; + while (i) { + if ((((stbsp__uintptr)bf) & 3) == 0) + break; + *bf++ = '0'; + --i; + } + while (i >= 4) { + *(stbsp__uint32 *)bf = 0x30303030; + bf += 4; + i -= 4; + } + while (i) { + *bf++ = '0'; + --i; + } + stbsp__chk_cb_buf(1); + } + + // copy tail if there is one + sn = tail + 1; + while (tail[0]) { + stbsp__int32 i; + stbsp__cb_buf_clamp(i, tail[0]); + tail[0] -= (char)i; + while (i) { + *bf++ = *sn++; + --i; + } + stbsp__chk_cb_buf(1); + } + + // handle the left justify + if (fl & STBSP__LEFTJUST) + if (fw > 0) { + while (fw) { + stbsp__int32 i; + stbsp__cb_buf_clamp(i, fw); + fw -= i; + while (i) { + if ((((stbsp__uintptr)bf) & 3) == 0) + break; + *bf++ = ' '; + --i; + } + while (i >= 4) { + *(stbsp__uint32 *)bf = 0x20202020; + bf += 4; + i -= 4; + } + while (i--) + *bf++ = ' '; + stbsp__chk_cb_buf(1); + } + } + break; + + default: // unknown, just copy code + s = num + STBSP__NUMSZ - 1; + *s = f[0]; + l = 1; + fw = fl = 0; + lead[0] = 0; + tail[0] = 0; + pr = 0; + dp = 0; + cs = 0; + goto scopy; + } + ++f; + } +endfmt: + + if (!callback) + *bf = 0; + else + stbsp__flush_cb(); + +done: + return tlen + (int)(bf - buf); +} + +// cleanup +#undef STBSP__LEFTJUST +#undef STBSP__LEADINGPLUS +#undef STBSP__LEADINGSPACE +#undef STBSP__LEADING_0X +#undef STBSP__LEADINGZERO +#undef STBSP__INTMAX +#undef STBSP__TRIPLET_COMMA +#undef STBSP__NEGATIVE +#undef STBSP__METRIC_SUFFIX +#undef STBSP__NUMSZ +#undef stbsp__chk_cb_bufL +#undef stbsp__chk_cb_buf +#undef stbsp__flush_cb +#undef stbsp__cb_buf_clamp + +// ============================================================================ +// wrapper functions + +STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(sprintf)(char *buf, char const *fmt, ...) { + int result; + va_list va; + va_start(va, fmt); + result = STB_SPRINTF_DECORATE(vsprintfcb)(0, 0, buf, fmt, va); + va_end(va); + return result; +} + +typedef struct stbsp__context { + char *buf; + int count; + int length; + char tmp[STB_SPRINTF_MIN]; +} stbsp__context; + +static char *stbsp__clamp_callback(const char *buf, void *user, int len) { + stbsp__context *c = (stbsp__context *)user; + c->length += len; + + if (len > c->count) + len = c->count; + + if (len) { + if (buf != c->buf) { + const char *s, *se; + char *d; + d = c->buf; + s = buf; + se = buf + len; + do { + *d++ = *s++; + } while (s < se); + } + c->buf += len; + c->count -= len; + } + + if (c->count <= 0) + return c->tmp; + return (c->count >= STB_SPRINTF_MIN) ? c->buf : c->tmp; // go direct into buffer if you can +} + +static char *stbsp__count_clamp_callback(const char *buf, void *user, int len) { + stbsp__context *c = (stbsp__context *)user; + (void)sizeof(buf); + + c->length += len; + return c->tmp; // go direct into buffer if you can +} + +STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(vsnprintf)(char *buf, int count, char const *fmt, va_list va) { + stbsp__context c; + + if ((count == 0) && !buf) { + c.length = 0; + + STB_SPRINTF_DECORATE(vsprintfcb) + (stbsp__count_clamp_callback, &c, c.tmp, fmt, va); + } + else { + int l; + + c.buf = buf; + c.count = count; + c.length = 0; + + STB_SPRINTF_DECORATE(vsprintfcb) + (stbsp__clamp_callback, &c, stbsp__clamp_callback(0, &c, 0), fmt, va); + + // zero-terminate + l = (int)(c.buf - buf); + if (l >= count) // should never be greater, only equal (or less) than count + l = count - 1; + buf[l] = 0; + } + + return c.length; +} + +STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(snprintf)(char *buf, int count, char const *fmt, ...) { + int result; + va_list va; + va_start(va, fmt); + + result = STB_SPRINTF_DECORATE(vsnprintf)(buf, count, fmt, va); + va_end(va); + + return result; +} + +STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(vsprintf)(char *buf, char const *fmt, va_list va) { + return STB_SPRINTF_DECORATE(vsprintfcb)(0, 0, buf, fmt, va); +} + +// ======================================================================= +// low level float utility functions + +#ifndef STB_SPRINTF_NOFLOAT + + // copies d to bits w/ strict aliasing (this compiles to nothing on /Ox) + #define STBSP__COPYFP(dest, src) \ + { \ + int cn; \ + for (cn = 0; cn < 8; cn++) \ + ((char *)&dest)[cn] = ((char *)&src)[cn]; \ + } + +// get float info +static stbsp__int32 stbsp__real_to_parts(stbsp__int64 *bits, stbsp__int32 *expo, double value) { + double d; + stbsp__int64 b = 0; + + // load value and round at the frac_digits + d = value; + + STBSP__COPYFP(b, d); + + *bits = b & ((((stbsp__uint64)1) << 52) - 1); + *expo = (stbsp__int32)(((b >> 52) & 2047) - 1023); + + return (stbsp__int32)((stbsp__uint64)b >> 63); +} + +static double const stbsp__bot[23] = { + 1e+000, 1e+001, 1e+002, 1e+003, 1e+004, 1e+005, 1e+006, 1e+007, 1e+008, 1e+009, 1e+010, 1e+011, + 1e+012, 1e+013, 1e+014, 1e+015, 1e+016, 1e+017, 1e+018, 1e+019, 1e+020, 1e+021, 1e+022}; +static double const stbsp__negbot[22] = { + 1e-001, 1e-002, 1e-003, 1e-004, 1e-005, 1e-006, 1e-007, 1e-008, 1e-009, 1e-010, 1e-011, + 1e-012, 1e-013, 1e-014, 1e-015, 1e-016, 1e-017, 1e-018, 1e-019, 1e-020, 1e-021, 1e-022}; +static double const stbsp__negboterr[22] = { + -5.551115123125783e-018, -2.0816681711721684e-019, -2.0816681711721686e-020, -4.7921736023859299e-021, -8.1803053914031305e-022, 4.5251888174113741e-023, + 4.5251888174113739e-024, -2.0922560830128471e-025, -6.2281591457779853e-026, -3.6432197315497743e-027, 6.0503030718060191e-028, 2.0113352370744385e-029, + -3.0373745563400371e-030, 1.1806906454401013e-032, -7.7705399876661076e-032, 2.0902213275965398e-033, -7.1542424054621921e-034, -7.1542424054621926e-035, + 2.4754073164739869e-036, 5.4846728545790429e-037, 9.2462547772103625e-038, -4.8596774326570872e-039}; +static double const stbsp__top[13] = { + 1e+023, 1e+046, 1e+069, 1e+092, 1e+115, 1e+138, 1e+161, 1e+184, 1e+207, 1e+230, 1e+253, 1e+276, 1e+299}; +static double const stbsp__negtop[13] = { + 1e-023, 1e-046, 1e-069, 1e-092, 1e-115, 1e-138, 1e-161, 1e-184, 1e-207, 1e-230, 1e-253, 1e-276, 1e-299}; +static double const stbsp__toperr[13] = { + 8388608, + 6.8601809640529717e+028, + -7.253143638152921e+052, + -4.3377296974619174e+075, + -1.5559416129466825e+098, + -3.2841562489204913e+121, + -3.7745893248228135e+144, + -1.7356668416969134e+167, + -3.8893577551088374e+190, + -9.9566444326005119e+213, + 6.3641293062232429e+236, + -5.2069140800249813e+259, + -5.2504760255204387e+282}; +static double const stbsp__negtoperr[13] = { + 3.9565301985100693e-040, -2.299904345391321e-063, 3.6506201437945798e-086, 1.1875228833981544e-109, + -5.0644902316928607e-132, -6.7156837247865426e-155, -2.812077463003139e-178, -5.7778912386589953e-201, + 7.4997100559334532e-224, -4.6439668915134491e-247, -6.3691100762962136e-270, -9.436808465446358e-293, + 8.0970921678014997e-317}; + + #if defined(_MSC_VER) && (_MSC_VER <= 1200) +static stbsp__uint64 const stbsp__powten[20] = { + 1, + 10, + 100, + 1000, + 10000, + 100000, + 1000000, + 10000000, + 100000000, + 1000000000, + 10000000000, + 100000000000, + 1000000000000, + 10000000000000, + 100000000000000, + 1000000000000000, + 10000000000000000, + 100000000000000000, + 1000000000000000000, + 10000000000000000000U}; + #define stbsp__tento19th ((stbsp__uint64)1000000000000000000) + #else +static stbsp__uint64 const stbsp__powten[20] = { + 1, + 10, + 100, + 1000, + 10000, + 100000, + 1000000, + 10000000, + 100000000, + 1000000000, + 10000000000ULL, + 100000000000ULL, + 1000000000000ULL, + 10000000000000ULL, + 100000000000000ULL, + 1000000000000000ULL, + 10000000000000000ULL, + 100000000000000000ULL, + 1000000000000000000ULL, + 10000000000000000000ULL}; + #define stbsp__tento19th (1000000000000000000ULL) + #endif + + #define stbsp__ddmulthi(oh, ol, xh, yh) \ + { \ + double ahi = 0, alo, bhi = 0, blo; \ + stbsp__int64 bt; \ + oh = xh * yh; \ + STBSP__COPYFP(bt, xh); \ + bt &= ((~(stbsp__uint64)0) << 27); \ + STBSP__COPYFP(ahi, bt); \ + alo = xh - ahi; \ + STBSP__COPYFP(bt, yh); \ + bt &= ((~(stbsp__uint64)0) << 27); \ + STBSP__COPYFP(bhi, bt); \ + blo = yh - bhi; \ + ol = ((ahi * bhi - oh) + ahi * blo + alo * bhi) + alo * blo; \ + } + + #define stbsp__ddtoS64(ob, xh, xl) \ + { \ + double ahi = 0, alo, vh, t; \ + ob = (stbsp__int64)xh; \ + vh = (double)ob; \ + ahi = (xh - vh); \ + t = (ahi - xh); \ + alo = (xh - (ahi - t)) - (vh + t); \ + ob += (stbsp__int64)(ahi + alo + xl); \ + } + + #define stbsp__ddrenorm(oh, ol) \ + { \ + double s; \ + s = oh + ol; \ + ol = ol - (s - oh); \ + oh = s; \ + } + + #define stbsp__ddmultlo(oh, ol, xh, xl, yh, yl) ol = ol + (xh * yl + xl * yh); + + #define stbsp__ddmultlos(oh, ol, xh, yl) ol = ol + (xh * yl); + +static void stbsp__raise_to_power10(double *ohi, double *olo, double d, stbsp__int32 power) // power can be -323 to +350 +{ + double ph, pl; + if ((power >= 0) && (power <= 22)) { + stbsp__ddmulthi(ph, pl, d, stbsp__bot[power]); + } + else { + stbsp__int32 e, et, eb; + double p2h, p2l; + + e = power; + if (power < 0) + e = -e; + et = (e * 0x2c9) >> 14; /* %23 */ + if (et > 13) + et = 13; + eb = e - (et * 23); + + ph = d; + pl = 0.0; + if (power < 0) { + if (eb) { + --eb; + stbsp__ddmulthi(ph, pl, d, stbsp__negbot[eb]); + stbsp__ddmultlos(ph, pl, d, stbsp__negboterr[eb]); + } + if (et) { + stbsp__ddrenorm(ph, pl); + --et; + stbsp__ddmulthi(p2h, p2l, ph, stbsp__negtop[et]); + stbsp__ddmultlo(p2h, p2l, ph, pl, stbsp__negtop[et], stbsp__negtoperr[et]); + ph = p2h; + pl = p2l; + } + } + else { + if (eb) { + e = eb; + if (eb > 22) + eb = 22; + e -= eb; + stbsp__ddmulthi(ph, pl, d, stbsp__bot[eb]); + if (e) { + stbsp__ddrenorm(ph, pl); + stbsp__ddmulthi(p2h, p2l, ph, stbsp__bot[e]); + stbsp__ddmultlos(p2h, p2l, stbsp__bot[e], pl); + ph = p2h; + pl = p2l; + } + } + if (et) { + stbsp__ddrenorm(ph, pl); + --et; + stbsp__ddmulthi(p2h, p2l, ph, stbsp__top[et]); + stbsp__ddmultlo(p2h, p2l, ph, pl, stbsp__top[et], stbsp__toperr[et]); + ph = p2h; + pl = p2l; + } + } + } + stbsp__ddrenorm(ph, pl); + *ohi = ph; + *olo = pl; +} + +// given a float value, returns the significant bits in bits, and the position of the +// decimal point in decimal_pos. +/-INF and NAN are specified by special values +// returned in the decimal_pos parameter. +// frac_digits is absolute normally, but if you want from first significant digits (got %g and %e), or in 0x80000000 +static stbsp__int32 stbsp__real_to_str(char const **start, stbsp__uint32 *len, char *out, stbsp__int32 *decimal_pos, double value, stbsp__uint32 frac_digits) { + double d; + stbsp__int64 bits = 0; + stbsp__int32 expo, e, ng, tens; + + d = value; + STBSP__COPYFP(bits, d); + expo = (stbsp__int32)((bits >> 52) & 2047); + ng = (stbsp__int32)((stbsp__uint64)bits >> 63); + if (ng) + d = -d; + + if (expo == 2047) // is nan or inf? + { + *start = (bits & ((((stbsp__uint64)1) << 52) - 1)) ? "NaN" : "Inf"; + *decimal_pos = STBSP__SPECIAL; + *len = 3; + return ng; + } + + if (expo == 0) // is zero or denormal + { + if (((stbsp__uint64)bits << 1) == 0) // do zero + { + *decimal_pos = 1; + *start = out; + out[0] = '0'; + *len = 1; + return ng; + } + // find the right expo for denormals + { + stbsp__int64 v = ((stbsp__uint64)1) << 51; + while ((bits & v) == 0) { + --expo; + v >>= 1; + } + } + } + + // find the decimal exponent as well as the decimal bits of the value + { + double ph, pl; + + // log10 estimate - very specifically tweaked to hit or undershoot by no more than 1 of log10 of all expos 1..2046 + tens = expo - 1023; + tens = (tens < 0) ? ((tens * 617) / 2048) : (((tens * 1233) / 4096) + 1); + + // move the significant bits into position and stick them into an int + stbsp__raise_to_power10(&ph, &pl, d, 18 - tens); + + // get full as much precision from double-double as possible + stbsp__ddtoS64(bits, ph, pl); + + // check if we undershot + if (((stbsp__uint64)bits) >= stbsp__tento19th) + ++tens; + } + + // now do the rounding in integer land + frac_digits = (frac_digits & 0x80000000) ? ((frac_digits & 0x7ffffff) + 1) : (tens + frac_digits); + if ((frac_digits < 24)) { + stbsp__uint32 dg = 1; + if ((stbsp__uint64)bits >= stbsp__powten[9]) + dg = 10; + while ((stbsp__uint64)bits >= stbsp__powten[dg]) { + ++dg; + if (dg == 20) + goto noround; + } + if (frac_digits < dg) { + stbsp__uint64 r; + // add 0.5 at the right position and round + e = dg - frac_digits; + if ((stbsp__uint32)e >= 24) + goto noround; + r = stbsp__powten[e]; + bits = bits + (r / 2); + if ((stbsp__uint64)bits >= stbsp__powten[dg]) + ++tens; + bits /= r; + } + noround:; + } + + // kill long trailing runs of zeros + if (bits) { + stbsp__uint32 n; + for (;;) { + if (bits <= 0xffffffff) + break; + if (bits % 1000) + goto donez; + bits /= 1000; + } + n = (stbsp__uint32)bits; + while ((n % 1000) == 0) + n /= 1000; + bits = n; + donez:; + } + + // convert to string + out += 64; + e = 0; + for (;;) { + stbsp__uint32 n; + char *o = out - 8; + // do the conversion in chunks of U32s (avoid most 64-bit divides, worth it, constant denomiators be damned) + if (bits >= 100000000) { + n = (stbsp__uint32)(bits % 100000000); + bits /= 100000000; + } + else { + n = (stbsp__uint32)bits; + bits = 0; + } + while (n) { + out -= 2; + *(stbsp__uint16 *)out = *(stbsp__uint16 *)&stbsp__digitpair.pair[(n % 100) * 2]; + n /= 100; + e += 2; + } + if (bits == 0) { + if ((e) && (out[0] == '0')) { + ++out; + --e; + } + break; + } + while (out != o) { + *--out = '0'; + ++e; + } + } + + *decimal_pos = tens; + *start = out; + *len = e; + return ng; +} + + #undef stbsp__ddmulthi + #undef stbsp__ddrenorm + #undef stbsp__ddmultlo + #undef stbsp__ddmultlos + #undef STBSP__SPECIAL + #undef STBSP__COPYFP + +#endif // STB_SPRINTF_NOFLOAT + +// clean up +#undef stbsp__uint16 +#undef stbsp__uint32 +#undef stbsp__int32 +#undef stbsp__uint64 +#undef stbsp__int64 +#undef STBSP__UNALIGNED diff --git a/src/build_tool/standalone_libraries/stb_sprintf.h b/src/build_tool/standalone_libraries/stb_sprintf.h new file mode 100644 index 0000000..0300e84 --- /dev/null +++ b/src/build_tool/standalone_libraries/stb_sprintf.h @@ -0,0 +1,262 @@ +// stb_sprintf - v1.10 - public domain snprintf() implementation +// originally by Jeff Roberts / RAD Game Tools, 2015/10/20 +// http://github.com/nothings/stb +// +// allowed types: sc uidBboXx p AaGgEef n +// lengths : hh h ll j z t I64 I32 I +// +// Contributors: +// Fabian "ryg" Giesen (reformatting) +// github:aganm (attribute format) +// +// Contributors (bugfixes): +// github:d26435 +// github:trex78 +// github:account-login +// Jari Komppa (SI suffixes) +// Rohit Nirmal +// Marcin Wojdyr +// Leonard Ritter +// Stefano Zanotti +// Adam Allison +// Arvid Gerstmann +// Markus Kolb +// +// LICENSE: +// +// See end of file for license information. + +#ifndef STB_SPRINTF_H_INCLUDE + #define STB_SPRINTF_H_INCLUDE + #include +/* +Single file sprintf replacement. + +Originally written by Jeff Roberts at RAD Game Tools - 2015/10/20. +Hereby placed in public domain. + +This is a full sprintf replacement that supports everything that +the C runtime sprintfs support, including float/double, 64-bit integers, +hex floats, field parameters (%*.*d stuff), length reads backs, etc. + +Why would you need this if sprintf already exists? Well, first off, +it's *much* faster (see below). It's also much smaller than the CRT +versions code-space-wise. We've also added some simple improvements +that are super handy (commas in thousands, callbacks at buffer full, +for example). Finally, the format strings for MSVC and GCC differ +for 64-bit integers (among other small things), so this lets you use +the same format strings in cross platform code. + +It uses the standard single file trick of being both the header file +and the source itself. If you just include it normally, you just get +the header file function definitions. To get the code, you include +it from a C or C++ file and define STB_SPRINTF_IMPLEMENTATION first. + +It only uses va_args macros from the C runtime to do it's work. It +does cast doubles to S64s and shifts and divides U64s, which does +drag in CRT code on most platforms. + +It compiles to roughly 8K with float support, and 4K without. +As a comparison, when using MSVC static libs, calling sprintf drags +in 16K. + +API: +==== +int stbsp_sprintf( char * buf, char const * fmt, ... ) +int stbsp_snprintf( char * buf, int count, char const * fmt, ... ) + Convert an arg list into a buffer. stbsp_snprintf always returns + a zero-terminated string (unlike regular snprintf). + +int stbsp_vsprintf( char * buf, char const * fmt, va_list va ) +int stbsp_vsnprintf( char * buf, int count, char const * fmt, va_list va ) + Convert a va_list arg list into a buffer. stbsp_vsnprintf always returns + a zero-terminated string (unlike regular snprintf). + +int stbsp_vsprintfcb( STBSP_SPRINTFCB * callback, void * user, char * buf, char const * fmt, va_list va ) + typedef char * STBSP_SPRINTFCB( char const * buf, void * user, int len ); + Convert into a buffer, calling back every STB_SPRINTF_MIN chars. + Your callback can then copy the chars out, print them or whatever. + This function is actually the workhorse for everything else. + The buffer you pass in must hold at least STB_SPRINTF_MIN characters. + // you return the next buffer to use or 0 to stop converting + +void stbsp_set_separators( char comma, char period ) + Set the comma and period characters to use. + +FLOATS/DOUBLES: +=============== +This code uses a internal float->ascii conversion method that uses +doubles with error correction (double-doubles, for ~105 bits of +precision). This conversion is round-trip perfect - that is, an atof +of the values output here will give you the bit-exact double back. + +One difference is that our insignificant digits will be different than +with MSVC or GCC (but they don't match each other either). We also +don't attempt to find the minimum length matching float (pre-MSVC15 +doesn't either). + +If you don't need float or doubles at all, define STB_SPRINTF_NOFLOAT +and you'll save 4K of code space. + +64-BIT INTS: +============ +This library also supports 64-bit integers and you can use MSVC style or +GCC style indicators (%I64d or %lld). It supports the C99 specifiers +for size_t and ptr_diff_t (%jd %zd) as well. + +EXTRAS: +======= +Like some GCCs, for integers and floats, you can use a ' (single quote) +specifier and commas will be inserted on the thousands: "%'d" on 12345 +would print 12,345. + +For integers and floats, you can use a "$" specifier and the number +will be converted to float and then divided to get kilo, mega, giga or +tera and then printed, so "%$d" 1000 is "1.0 k", "%$.2d" 2536000 is +"2.53 M", etc. For byte values, use two $:s, like "%$$d" to turn +2536000 to "2.42 Mi". If you prefer JEDEC suffixes to SI ones, use three +$:s: "%$$$d" -> "2.42 M". To remove the space between the number and the +suffix, add "_" specifier: "%_$d" -> "2.53M". + +In addition to octal and hexadecimal conversions, you can print +integers in binary: "%b" for 256 would print 100. + +PERFORMANCE vs MSVC 2008 32-/64-bit (GCC is even slower than MSVC): +=================================================================== +"%d" across all 32-bit ints (4.8x/4.0x faster than 32-/64-bit MSVC) +"%24d" across all 32-bit ints (4.5x/4.2x faster) +"%x" across all 32-bit ints (4.5x/3.8x faster) +"%08x" across all 32-bit ints (4.3x/3.8x faster) +"%f" across e-10 to e+10 floats (7.3x/6.0x faster) +"%e" across e-10 to e+10 floats (8.1x/6.0x faster) +"%g" across e-10 to e+10 floats (10.0x/7.1x faster) +"%f" for values near e-300 (7.9x/6.5x faster) +"%f" for values near e+300 (10.0x/9.1x faster) +"%e" for values near e-300 (10.1x/7.0x faster) +"%e" for values near e+300 (9.2x/6.0x faster) +"%.320f" for values near e-300 (12.6x/11.2x faster) +"%a" for random values (8.6x/4.3x faster) +"%I64d" for 64-bits with 32-bit values (4.8x/3.4x faster) +"%I64d" for 64-bits > 32-bit values (4.9x/5.5x faster) +"%s%s%s" for 64 char strings (7.1x/7.3x faster) +"...512 char string..." ( 35.0x/32.5x faster!) +*/ + + #if defined(__clang__) + #if defined(__has_feature) && defined(__has_attribute) + #if __has_feature(address_sanitizer) + #if __has_attribute(__no_sanitize__) + #define STBSP__ASAN __attribute__((__no_sanitize__("address"))) + #elif __has_attribute(__no_sanitize_address__) + #define STBSP__ASAN __attribute__((__no_sanitize_address__)) + #elif __has_attribute(__no_address_safety_analysis__) + #define STBSP__ASAN __attribute__((__no_address_safety_analysis__)) + #endif + #endif + #endif + #elif defined(__GNUC__) && (__GNUC__ >= 5 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)) + #if defined(__SANITIZE_ADDRESS__) && __SANITIZE_ADDRESS__ + #define STBSP__ASAN __attribute__((__no_sanitize_address__)) + #endif + #elif defined(_MSC_VER) + #ifdef __SANITIZE_ADDRESS__ + #define STBSP__ASAN __declspec(no_sanitize_address) + #endif + #endif + + #ifndef STBSP__ASAN + #define STBSP__ASAN + #endif + + #ifdef STB_SPRINTF_STATIC + #define STBSP__PUBLICDEC static + #define STBSP__PUBLICDEF static STBSP__ASAN + #else + #ifdef __cplusplus + #define STBSP__PUBLICDEC extern "C" + #define STBSP__PUBLICDEF extern "C" STBSP__ASAN + #else + #define STBSP__PUBLICDEC extern + #define STBSP__PUBLICDEF STBSP__ASAN + #endif + #endif + + #if defined(__has_attribute) + #if __has_attribute(format) + #define STBSP__ATTRIBUTE_FORMAT(fmt, va) __attribute__((format(printf, fmt, va))) + #endif + #endif + + #ifndef STBSP__ATTRIBUTE_FORMAT + #define STBSP__ATTRIBUTE_FORMAT(fmt, va) + #endif + + #ifdef _MSC_VER + #define STBSP__NOTUSED(v) (void)(v) + #else + #define STBSP__NOTUSED(v) (void)sizeof(v) + #endif + + #include // for va_arg(), va_list() + #include // size_t, ptrdiff_t + + #ifndef STB_SPRINTF_MIN + #define STB_SPRINTF_MIN 512 // how many characters per callback + #endif +typedef char *STBSP_SPRINTFCB(const char *buf, void *user, int len); + + #ifndef STB_SPRINTF_DECORATE + #define STB_SPRINTF_DECORATE(name) stbsp_##name // define this before including if you want to change the names + #endif + +STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(vsprintf)(char *buf, char const *fmt, va_list va); +STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(vsnprintf)(char *buf, int count, char const *fmt, va_list va); +STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(sprintf)(char *buf, char const *fmt, ...) STBSP__ATTRIBUTE_FORMAT(2, 3); +STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(snprintf)(char *buf, int count, char const *fmt, ...) STBSP__ATTRIBUTE_FORMAT(3, 4); + +STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(vsprintfcb)(STBSP_SPRINTFCB *callback, void *user, char *buf, char const *fmt, va_list va); +STBSP__PUBLICDEC void STB_SPRINTF_DECORATE(set_separators)(char comma, char period); + +#endif // STB_SPRINTF_H_INCLUDE + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/src/build_tool/standalone_libraries/string.c b/src/build_tool/standalone_libraries/string.c new file mode 100644 index 0000000..a9ab569 --- /dev/null +++ b/src/build_tool/standalone_libraries/string.c @@ -0,0 +1,566 @@ +#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) { + 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 S8_String S8_CopyChar(S8_Allocator allocator, char *s) { + int64_t len = S8_Length(s); + char *copy = (char *)S8_ALLOCATE(allocator, sizeof(char) * (len + 1)); + S8_MemoryCopy(copy, s, len); + copy[len] = 0; + S8_String result = S8_Make(copy, len); + return result; +} + +S8_API S8_String S8_NormalizePath(S8_Allocator allocator, S8_String s) { + S8_String copy = S8_Copy(allocator, s); + for (int64_t i = 0; i < copy.len; i++) { + if (copy.str[i] == '\\') + copy.str[i] = '/'; + } + return copy; +} + +S8_API void S8_NormalizePathUnsafe(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_GetNameNoExt(S8_String s) { + return S8_SkipToLastSlash(S8_ChopLastPeriod(s)); +} + +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_Seek(S8_String string, S8_String find, S8_FindFlag flags, int64_t *index_out) { + bool ignore_case = flags & S8_FindFlag_IgnoreCase ? true : false; + bool result = false; + if (flags & S8_FindFlag_MatchFindLast) { + 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, ignore_case)) { + 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, ignore_case)) { + if (index_out) + *index_out = i; + result = true; + break; + } + } + } + + return result; +} + +S8_API int64_t S8_Find(S8_String string, S8_String find, S8_FindFlag flag) { + int64_t result = -1; + S8_Seek(string, find, flag, &result); + return result; +} + +S8_API S8_List S8_Split(S8_Allocator allocator, S8_String string, S8_String find, S8_SplitFlag flags) { + S8_List result = S8_MakeEmptyList(); + int64_t index = 0; + + S8_FindFlag find_flag = flags & S8_SplitFlag_IgnoreCase ? S8_FindFlag_IgnoreCase : S8_FindFlag_None; + while (S8_Seek(string, find, find_flag, &index)) { + S8_String before_match = S8_Make(string.str, index); + S8_AddNode(allocator, &result, before_match); + if (flags & S8_SplitFlag_SplitInclusive) { + 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(); + + 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 + 1)); + 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, bool ignore_case) { + S8_SplitFlag split_flag = ignore_case ? S8_SplitFlag_IgnoreCase : S8_SplitFlag_None; + S8_List list = S8_Split(allocator, string, replace, split_flag | S8_SplitFlag_SplitInclusive); + for (S8_Node *it = list.first; it; it = it->next) { + if (S8_AreEqual(it->string, replace, ignore_case)) { + 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, bool ignore_case) { // @untested + S8_List result = S8_MakeEmptyList(); + int64_t index = 0; + + S8_FindFlag find_flag = ignore_case ? S8_FindFlag_IgnoreCase : 0; + while (S8_Seek(string, find, find_flag, &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_Seek(s, S8_Lit("/"), S8_FindFlag_MatchFindLast, &result.len); + return result; +} + +S8_API S8_String S8_ChopLastPeriod(S8_String s) { + S8_String result = s; + S8_Seek(s, S8_Lit("."), S8_FindFlag_MatchFindLast, &result.len); + return result; +} + +S8_API S8_String S8_SkipToLastSlash(S8_String s) { + int64_t pos; + S8_String result = s; + if (S8_Seek(s, S8_Lit("/"), S8_FindFlag_MatchFindLast, &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_Seek(s, S8_Lit("."), S8_FindFlag_MatchFindLast, &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_Node *S8_Add(S8_Allocator allocator, S8_List *list, S8_String string) { + S8_String copy = S8_Copy(allocator, string); + S8_Node *node = S8_CreateNode(allocator, copy); + 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; +} + +#ifdef FIRST_UTF_HEADER + +S8_API S16_String S8_ToWidecharEx(S8_Allocator allocator, S8_String string) { + S8_ASSERT(sizeof(wchar_t) == 2); + wchar_t *buffer = (wchar_t *)S8_ALLOCATE(allocator, sizeof(wchar_t) * (string.len + 1)); + int64_t size = UTF_CreateWidecharFromChar(buffer, string.len + 1, string.str, string.len); + S16_String result = {buffer, size}; + return result; +} + +S8_API wchar_t *S8_ToWidechar(S8_Allocator allocator, S8_String string) { + S16_String result = S8_ToWidecharEx(allocator, string); + return result.str; +} + +S8_API S8_String S8_FromWidecharEx(S8_Allocator allocator, wchar_t *wstring, int64_t wsize) { + S8_ASSERT(sizeof(wchar_t) == 2); + + int64_t buffer_size = (wsize + 1) * 2; + char *buffer = (char *)S8_ALLOCATE(allocator, buffer_size); + int64_t size = UTF_CreateCharFromWidechar(buffer, buffer_size, wstring, wsize); + S8_String result = S8_Make(buffer, size); + + S8_ASSERT(size < buffer_size); + return result; +} + +S8_API S8_String S8_FromWidechar(S8_Allocator allocator, wchar_t *wstring) { + int64_t size = S8_WideLength(wstring); + S8_String result = S8_FromWidecharEx(allocator, wstring, size); + return result; +} + +#endif \ No newline at end of file diff --git a/src/build_tool/standalone_libraries/string.h b/src/build_tool/standalone_libraries/string.h new file mode 100644 index 0000000..76b1d33 --- /dev/null +++ b/src/build_tool/standalone_libraries/string.h @@ -0,0 +1,197 @@ +#ifndef FIRST_S8_STRING +#define FIRST_S8_STRING +#include +#include +#include + +#ifndef S8_API + #define S8_API +#endif + +#ifdef __cplusplus + #define S8_IF_CPP(x) x +#else + #define S8_IF_CPP(x) +#endif + +#ifndef S8_Allocator +struct MA_Arena; + #define S8_Allocator MA_Arena * +#endif + +typedef struct S8_String S8_String; +typedef struct S8_Node S8_Node; +typedef struct S8_List S8_List; +S8_API int64_t S8_Length(char *string); + +struct S8_String { + char *str; + int64_t len; +#if defined(__cplusplus) + S8_String() = default; + S8_String(char *s) : str(s), len(S8_Length(s)) {} + S8_String(char *s, int64_t l) : str(s), len(l) {} + S8_String(const char *s) : str((char *)s), len(S8_Length((char *)s)) {} + S8_String(const char *s, int64_t l) : str((char *)s), len(l) {} + #if defined(FIRST_UTF_HEADER) + struct Iter { + UTF8_Iter i; + + Iter &operator++() { + UTF8_Advance(&i); + return *this; + } + + friend bool operator!=(const Iter &a, const Iter &b) { return a.i.item != b.i.item; } + UTF8_Iter &operator*() { return i; } + }; + + Iter begin() { return {UTF8_IterateEx(str, (int)len)}; } + Iter end() { return {}; } + #endif // FIRST_UTF_HEADER +#endif // __cplusplus +}; + +struct S8_Node { + S8_Node *next; + S8_String string; +}; + +struct S8_List { + int64_t node_count; + int64_t char_count; + S8_Node *first; + S8_Node *last; + +#if defined(__cplusplus) + struct Iter { + S8_Node *it; + + Iter &operator++() { + it = it->next; + return *this; + } + + friend bool operator!=(const Iter &a, const Iter &b) { return a.it != b.it; } + S8_String &operator*() { return it->string; } + }; + + Iter begin() { return {first}; } + Iter end() { return {0}; } +#endif +}; + +typedef struct S16_String { + wchar_t *str; + int64_t len; +} S16_String; + +typedef int S8_FindFlag; +enum { + S8_FindFlag_None = 0, + S8_FindFlag_IgnoreCase = 1, + S8_FindFlag_MatchFindLast = 2, +}; + +typedef int S8_SplitFlag; +enum { + S8_SplitFlag_None = 0, + S8_SplitFlag_IgnoreCase = 1, + S8_SplitFlag_SplitInclusive = 2, +}; + +static const bool S8_IgnoreCase = true; + +#if defined(__has_attribute) + #if __has_attribute(format) + #define S8__PrintfFormat(fmt, va) __attribute__((format(printf, fmt, va))) + #endif +#endif + +#ifndef S8__PrintfFormat + #define S8__PrintfFormat(fmt, va) +#endif + +#define S8_Lit(string) S8_Make((char *)string, sizeof(string) - 1) +#define S8_ConstLit(string) \ + { string, sizeof(string) - 1 } +#define S8_Expand(string) (int)(string).len, (string).str + +#define S8_FORMAT(allocator, str, result) \ + va_list args1; \ + va_start(args1, str); \ + S8_String result = S8_FormatV(allocator, str, args1); \ + va_end(args1) + +#define S8_For(it, x) for (S8_Node *it = (x).first; it; it = it->next) + +S8_API char CHAR_ToLowerCase(char a); +S8_API char CHAR_ToUpperCase(char a); +S8_API bool CHAR_IsWhitespace(char w); +S8_API bool CHAR_IsAlphabetic(char a); +S8_API bool CHAR_IsIdent(char a); +S8_API bool CHAR_IsDigit(char a); +S8_API bool CHAR_IsAlphanumeric(char a); +S8_API bool S8_AreEqual(S8_String a, S8_String b, unsigned ignore_case S8_IF_CPP(= false)); +S8_API bool S8_EndsWith(S8_String a, S8_String end, unsigned ignore_case S8_IF_CPP(= false)); +S8_API bool S8_StartsWith(S8_String a, S8_String start, unsigned ignore_case S8_IF_CPP(= false)); +S8_API S8_String S8_Make(char *str, int64_t len); +S8_API S8_String S8_Copy(S8_Allocator allocator, S8_String string); +S8_API S8_String S8_CopyChar(S8_Allocator allocator, char *s); +S8_API S8_String S8_NormalizePath(S8_Allocator allocator, S8_String s); +S8_API void S8_NormalizePathUnsafe(S8_String s); // make sure there is no way string is const etc. +S8_API S8_String S8_Chop(S8_String string, int64_t len); +S8_API S8_String S8_Skip(S8_String string, int64_t len); +S8_API S8_String S8_GetPostfix(S8_String string, int64_t len); +S8_API S8_String S8_GetPrefix(S8_String string, int64_t len); +S8_API S8_String S8_Slice(S8_String string, int64_t first_index, int64_t one_past_last_index); +S8_API S8_String S8_Trim(S8_String string); +S8_API S8_String S8_TrimEnd(S8_String string); +S8_API S8_String S8_ToLowerCase(S8_Allocator allocator, S8_String s); +S8_API S8_String S8_ToUpperCase(S8_Allocator allocator, S8_String s); +S8_API bool S8_Seek(S8_String string, S8_String find, S8_FindFlag flags S8_IF_CPP(= S8_FindFlag_None), int64_t *index_out S8_IF_CPP(= 0)); +S8_API int64_t S8_Find(S8_String string, S8_String find, S8_FindFlag flags S8_IF_CPP(= S8_FindFlag_None)); +S8_API S8_String S8_ChopLastSlash(S8_String s); +S8_API S8_String S8_ChopLastPeriod(S8_String s); +S8_API S8_String S8_SkipToLastSlash(S8_String s); +S8_API S8_String S8_SkipToLastPeriod(S8_String s); +S8_API S8_String S8_GetNameNoExt(S8_String s); +S8_API bool S8_IsPointerInside(S8_String string, char *p); +S8_API S8_String S8_SkipToP(S8_String string, char *p); +S8_API S8_String S8_SkipPast(S8_String string, S8_String a); +S8_API int64_t S8_WideLength(wchar_t *string); +S8_API S8_String S8_MakeFromChar(char *string); +S8_API S8_String S8_MakeEmpty(void); +S8_API S8_List S8_MakeEmptyList(void); +S8_API S8_String S8_FormatV(S8_Allocator allocator, const char *str, va_list args1); +S8_API S8_String S8_Format(S8_Allocator allocator, const char *str, ...) S8__PrintfFormat(2, 3); + +S8_API S8_List S8_Split(S8_Allocator allocator, S8_String string, S8_String find, S8_SplitFlag flags S8_IF_CPP(= S8_SplitFlag_None)); +S8_API S8_String S8_MergeWithSeparator(S8_Allocator allocator, S8_List list, S8_String separator S8_IF_CPP(= S8_Lit(" "))); +S8_API S8_String S8_Merge(S8_Allocator allocator, S8_List list); +S8_API S8_String S8_ReplaceAll(S8_Allocator allocator, S8_String string, S8_String replace, S8_String with, bool ignore_case S8_IF_CPP(= false)); +S8_API S8_List S8_FindAll(S8_Allocator allocator, S8_String string, S8_String find, bool ignore_case S8_IF_CPP(= false)); + +S8_API S8_Node *S8_CreateNode(S8_Allocator allocator, S8_String string); +S8_API void S8_ReplaceNodeString(S8_List *list, S8_Node *node, S8_String new_string); +S8_API void S8_AddExistingNode(S8_List *list, S8_Node *node); +S8_API void S8_AddArray(S8_Allocator allocator, S8_List *list, char **array, int count); +S8_API void S8_AddArrayWithPrefix(S8_Allocator allocator, S8_List *list, char *prefix, char **array, int count); +S8_API S8_List S8_MakeList(S8_Allocator allocator, S8_String a); +S8_API S8_List S8_CopyList(S8_Allocator allocator, S8_List a); +S8_API S8_List S8_ConcatLists(S8_Allocator allocator, S8_List a, S8_List b); +S8_API S8_Node *S8_AddNode(S8_Allocator allocator, S8_List *list, S8_String string); +S8_API S8_Node *S8_Add(S8_Allocator allocator, S8_List *list, S8_String string); +S8_API S8_String S8_AddF(S8_Allocator allocator, S8_List *list, const char *str, ...) S8__PrintfFormat(3, 4); + +S8_API S16_String S8_ToWidecharEx(S8_Allocator allocator, S8_String string); +S8_API wchar_t *S8_ToWidechar(S8_Allocator allocator, S8_String string); +S8_API S8_String S8_FromWidecharEx(S8_Allocator allocator, wchar_t *wstring, int64_t wsize); +S8_API S8_String S8_FromWidechar(S8_Allocator allocator, wchar_t *wstring); + +#if defined(__cplusplus) +inline S8_String operator""_s(const char *str, size_t size) { return {(char *)str, (int64_t)size}; } +inline bool operator==(S8_String a, S8_String b) { return S8_AreEqual(a, b, 0); } +inline bool operator!=(S8_String a, S8_String b) { return !S8_AreEqual(a, b, 0); } +#endif +#endif \ No newline at end of file diff --git a/src/build_tool/standalone_libraries/unicode.c b/src/build_tool/standalone_libraries/unicode.c new file mode 100644 index 0000000..8cc03ab --- /dev/null +++ b/src/build_tool/standalone_libraries/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/src/build_tool/standalone_libraries/unicode.h b/src/build_tool/standalone_libraries/unicode.h new file mode 100644 index 0000000..0622e83 --- /dev/null +++ b/src/build_tool/standalone_libraries/unicode.h @@ -0,0 +1,55 @@ +#ifndef FIRST_UTF_HEADER +#define FIRST_UTF_HEADER +#define UTF_HEADER +#include +typedef struct UTF32_Result UTF32_Result; +typedef struct UTF8_Result UTF8_Result; +typedef struct UTF16_Result UTF16_Result; +typedef struct UTF8_Iter UTF8_Iter; + +#ifndef UTF_API + #ifdef __cplusplus + #define UTF_API extern "C" + #else + #define UTF_API + #endif +#endif + +struct UTF32_Result { + uint32_t out_str; + int advance; + int error; +}; + +struct UTF8_Result { + uint8_t out_str[4]; + int len; + int error; +}; + +struct UTF16_Result { + uint16_t out_str[2]; + int len; + int error; +}; + +struct UTF8_Iter { + char *str; + int len; + int utf8_codepoint_byte_size; + int i; + uint32_t item; +}; + +UTF_API UTF32_Result UTF_ConvertUTF16ToUTF32(uint16_t *c, int max_advance); +UTF_API UTF8_Result UTF_ConvertUTF32ToUTF8(uint32_t codepoint); +UTF_API UTF32_Result UTF_ConvertUTF8ToUTF32(char *c, int max_advance); +UTF_API UTF16_Result UTF_ConvertUTF32ToUTF16(uint32_t codepoint); +UTF_API int64_t UTF_CreateCharFromWidechar(char *buffer, int64_t buffer_size, wchar_t *in, int64_t inlen); +UTF_API int64_t UTF_CreateWidecharFromChar(wchar_t *buffer, int64_t buffer_size, char *in, int64_t inlen); +UTF_API void UTF8_Advance(UTF8_Iter *iter); +UTF_API UTF8_Iter UTF8_IterateEx(char *str, int len); +UTF_API UTF8_Iter UTF8_Iterate(char *str); + +#define UTF8_For(name, str, len) for (UTF8_Iter name = UTF8_IterateEx(str, (int)len); name.item; UTF8_Advance(&name)) +#endif \ No newline at end of file diff --git a/src/pdf_browser/basic.h b/src/pdf_browser/basic.h new file mode 100644 index 0000000..9b91600 --- /dev/null +++ b/src/pdf_browser/basic.h @@ -0,0 +1,1581 @@ +#pragma once +#ifndef _CRT_SECURE_NO_WARNINGS + #define _CRT_SECURE_NO_WARNINGS +#endif +#include +#include +#include +#include +#define Assert(x) assert(x) + +#if defined(__APPLE__) && defined(__MACH__) + #define OS_MAC 1 +#elif defined(_WIN32) + #define OS_WINDOWS 1 +#elif defined(__linux__) + #define OS_POSIX 1 + #define OS_LINUX 1 +#else + #error Unsupported platform +#endif + +#if defined(__clang__) + #define COMPILER_CLANG 1 +#elif defined(__GNUC__) || defined(__GNUG__) + #define COMPILER_GCC 1 +#elif defined(_MSC_VER) + #define COMPILER_MSVC 1 +#else + #error Unsupported compiler +#endif + +#ifndef OS_MAC + #define OS_MAC 0 +#endif + +#ifndef OS_WINDOWS + #define OS_WINDOWS 0 +#endif + +#ifndef OS_LINUX + #define OS_LINUX 0 +#endif + +#ifndef OS_POSIX + #define OS_POSIX 0 +#endif + +#ifndef COMPILER_MSVC + #define COMPILER_MSVC 0 +#endif + +#ifndef COMPILER_CLANG + #define COMPILER_CLANG 0 +#endif + +#ifndef COMPILER_GCC + #define COMPILER_GCC 0 +#endif + +#define KiB(x) ((x##ull) * 1024ull) +#define MiB(x) (KiB(x) * 1024ull) +#define GiB(x) (MiB(x) * 1024ull) +#define TiB(x) (GiB(x) * 1024ull) +#define Lengthof(x) ((int64_t)((sizeof(x) / sizeof((x)[0])))) + +template +T Min(T a, T b) { + if (a > b) return b; + return a; +} + +template +T ClampTop(T a, T top) { + return Min(a, top); +} + +template +T Max(T a, T b) { + if (a > b) return a; + return b; +} + +template +T ClampBottom(T bottom, T b) { + return Max(bottom, b); +} + +template +T Clamp(T value, T min, T max) { + if (value > max) return max; + if (value < min) return min; + return value; +} + +inline bool IsPowerOf2(size_t x) { + size_t result = (((x) & ((x)-1)) == 0); + return result; +} + +inline size_t WrapAroundPowerOf2(size_t x, size_t pow2) { + Assert(IsPowerOf2(pow2)); + size_t result = (((x) & ((pow2)-1llu))); + return result; +} + +inline uint64_t HashBytes(void *data, unsigned size) { + uint8_t *data8 = (uint8_t *)data; + uint64_t hash = (uint64_t)14695981039346656037ULL; + for (unsigned i = 0; i < size; i++) { + hash = hash ^ (uint64_t)(data8[i]); + hash = hash * (uint64_t)1099511628211ULL; + } + return hash; +} + +inline size_t GetAlignOffset(size_t size, size_t align) { + Assert(IsPowerOf2(align)); + size_t mask = align - 1; + size_t val = size & mask; + if (val) { + val = align - val; + } + return val; +} + +inline size_t AlignUp(size_t size, size_t align) { + size_t result = size + GetAlignOffset(size, align); + return result; +} + +inline size_t 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 - GetAlignOffset(size, align)); + return result; +} + +const int AllocatorKind_Allocate = 1; +const int AllocatorKind_Deallocate = 2; +struct Allocator { + void *(*proc)(void *object, int kind, void *p, size_t size); + void *object; +}; + +inline void *AllocSize(Allocator alo, size_t size) { + void *result = alo.proc(alo.object, AllocatorKind_Allocate, NULL, size); + memset(result, 0, size); + return result; +} +#define AllocType(alo, Type) (Type *)AllocSize(alo, sizeof(Type)) +#define AllocArray(alo, Type, count) (Type *)AllocSize(alo, sizeof(Type) * (count)) + +template +void Dealloc(Allocator alo, T **p) { + alo.proc(alo.object, AllocatorKind_Deallocate, *p, 0); + *p = NULL; +} + +Allocator GetSystemAllocator(); +#define MemoryZero(x, size) memset(x, 0, size) + +/* +Iterating and removing elements + +for (int i = 0; i < array.len; i += 1) { + auto &it = array[i]; + bool remove_item = false; + defer { + if (remove_item) { + array.ordered_remove(it); + i -= 1; + } + } +} + +IterRemove(arr) { + IterRemovePrepare(arr); + + remove_item = true; +} + +For(arr.reverse_iter()) { + defer{ arr.unordered_remove(it); }; +} + +*/ +#define IterRemove(a) for (int i = 0; i < (a).len; i += 1) +#define IterRemovePrepare(a) \ + auto &it = (a)[i]; \ + bool remove_item = false; \ + defer { \ + if (remove_item) { \ + (a).ordered_remove(it); \ + i -= 1; \ + } \ + } +#define ForItem(it, array) for (auto &it : (array)) +#define For(array) ForItem(it, array) + +constexpr int64_t SLICE_LAST = INT64_MIN; + +template +struct Slice { + T *data; + int64_t len; + + Slice() = default; + Slice(T *s, int64_t l) : data(s), len(l) {} + Slice(char *s) : data(s), len(strlen(s)) {} + Slice(const char *s) : data((char *)s), len(strlen((char *)s)) {} + Slice(const char *s, int64_t l) : data((char *)s), len(l) {} + + Slice copy(Allocator allocator) { + Slice result = {AllocArray(allocator, T, len), len}; + return result; + } + + // @copy_paste array slice + + T pop() { + Assert(len > 0); + return data[--len]; + } + + void unordered_remove(T &item) { // DONT USE IN LOOPS !!!! + Assert(&item >= begin() && &item < end()); + item = data[--len]; + } + + void unordered_remove_index(int64_t index) { + Assert(index >= 0 && index < len); + data[index] = data[--len]; + } + + void ordered_remove(T &item) { // DONT USE IN LOOPS !!! + Assert(&item >= begin() && &item < end()); + int64_t index = get_index(item); + ordered_remove_index(index); + } + + void ordered_remove_index(int64_t index) { + Assert(index >= 0 && index < len); + int64_t right_len = len - index - 1; + memmove(data + index, data + index + 1, right_len * sizeof(T)); + len -= 1; + } + + // @copy_paste array slice string + + Slice chop(int64_t ilen) { + ilen = ClampTop(ilen, len); + Slice result = {data, len - ilen}; + return result; + } + + Slice skip(int64_t ilen) { + ilen = ClampTop(ilen, len); + Slice result = {data + ilen, len - ilen}; + return result; + } + + Slice get_postfix(int64_t ilen) { + ilen = ClampTop(ilen, len); + int64_t remain_len = len - ilen; + Slice result = {data + remain_len, ilen}; + return result; + } + + Slice get_prefix(int64_t ilen) { + ilen = ClampTop(ilen, len); + Slice result = {data, ilen}; + return result; + } + + Slice slice(int64_t first_index, int64_t one_past_last_index) { + // Negative indexes work in python style, they return you the index counting from end of list + if (one_past_last_index == SLICE_LAST) one_past_last_index = len; + if (one_past_last_index < 0) one_past_last_index = len + one_past_last_index; + + if (first_index == SLICE_LAST) first_index = len; + if (first_index < 0) first_index = len + first_index; + + Slice result = *this; + if (len > 0) { + if (one_past_last_index > first_index) { + first_index = ClampTop(first_index, len - 1); + one_past_last_index = ClampTop(one_past_last_index, len); + result.data += first_index; + result.len = one_past_last_index - first_index; + } else { + result.len = 0; + } + } + return result; + } + + struct Reverse_Iter { + T *data; + Slice *arr; + + Reverse_Iter operator++(int) { + Reverse_Iter ret = *this; + data -= 1; + return ret; + } + Reverse_Iter &operator++() { + data -= 1; + return *this; + } + + T &operator*() { return data[0]; } + T *operator->() { return data; } + + friend bool operator==(const Reverse_Iter &a, const Reverse_Iter &b) { return a.data == b.data; }; + friend bool operator!=(const Reverse_Iter &a, const Reverse_Iter &b) { return a.data != b.data; }; + + Reverse_Iter begin() { return Reverse_Iter{arr->end() - 1, arr}; } + Reverse_Iter end() { return Reverse_Iter{arr->begin() - 1, arr}; } + }; + Reverse_Iter reverse_iter() { return {end() - 1, this}; } + + bool contains(T &item) { + bool result = &item >= data && &item < data + len; + return result; + } + + int64_t get_index(const T &item) { + ptrdiff_t index = (ptrdiff_t)(&item - data); + Assert(index >= 0 && index < len); + return (int64_t)index; + } + + bool is_first(T &item) { return &item == first(); } + bool is_last(T &item) { return &item == last(); } + T *first() { + Assert(len > 0); + return data; + } + T *last() { + Assert(len > 0); + return data + len - 1; + } + T *front() { + Assert(len > 0); + return data; + } + T *back() { + Assert(len > 0); + return data + len - 1; + } + T &operator[](int64_t index) { + Assert(index < len); + return data[index]; + } + T *begin() { return data; } + T *end() { return data + len; } +}; + +template +struct Array { + Allocator allocator; + T *data; + int64_t cap, len; + + void add(const T &item) { + try_growing(); + data[len++] = item; + } + + void bounded_add(T item) { + Assert(len + 1 <= cap); + try_growing(); // in case of error + data[len++] = item; + } + + T *alloc(const T &item) { + try_growing(); + T *ref = data + len++; + *ref = item; + return ref; + } + + T *alloc() { + try_growing(); + T *ref = data + len++; + *ref = {}; + return ref; + } + + T *alloc_multiple(int64_t size) { + try_growing_to_fit_item_count(size); + T *result = data + len; + len += size; + return result; + } + + void add_array(T *items, int64_t item_count) { + for (int64_t i = 0; i < item_count; i += 1) { + add(items[i]); + } + } + + void add_array(Array items) { + add_array(items.data, items.len); + } + + void reserve(int64_t size) { + if (size > cap) { + if (!allocator.proc) allocator = GetSystemAllocator(); + + void *new_data = AllocSize(allocator, size * sizeof(T)); + Assert(new_data); + memcpy(new_data, data, len * sizeof(T)); + Dealloc(allocator, &data); + + data = (T *)new_data; + cap = size; + } + } + + void init(Allocator allocator, int64_t size) { + len = 0; + cap = 0; + data = 0; + this->allocator = allocator; + reserve(size); + } + + void clear() { + len = 0; + } + + void insert(T item, int64_t index) { + if (index == len) { + add(item); + return; + } + + Assert(index < len); + Assert(index >= 0); + + try_growing(); + int64_t right_len = len - index; + memmove(data + index + 1, data + index, sizeof(T) * right_len); + data[index] = item; + len += 1; + } + + void dealloc() { + if (data) Dealloc(allocator, &data); + len = cap = 0; + } + + Array copy(Allocator allocator) { + Array result = {}; + result.allocator = allocator; + result.reserve(cap); + + memmove(result.data, data, sizeof(T) * len); + result.len = len; + return result; + } + + Array tight_copy(Allocator allocator) { + Array result = {}; + result.allocator = allocator; + result.reserve(len); + + memmove(result.data, data, sizeof(T) * len); + result.len = len; + return result; + } + + void try_growing() { + if (len + 1 > cap) { + int64_t new_size = cap * 2; + if (new_size < 16) new_size = 16; + + reserve(new_size); + } + } + + void try_growing_to_fit_item_count(int64_t item_count) { + if (len + item_count > cap) { + int64_t new_size = (cap + item_count) * 2; + if (new_size < 16) new_size = 16; + reserve(new_size); + } + } + + // @copy_paste array slice + + T pop() { + Assert(len > 0); + return data[--len]; + } + + void unordered_remove(T &item) { // DONT USE IN LOOPS !!!! + Assert(len > 0); + Assert(&item >= begin() && &item < end()); + item = data[--len]; + } + + void unordered_remove_index(int64_t index) { + Assert(index >= 0 && index < len); + data[index] = data[--len]; + } + + void ordered_remove(T &item) { + Assert(len > 0); + Assert(&item >= begin() && &item < end()); + int64_t index = get_index(item); + ordered_remove_index(index); + } + + void ordered_remove_index(int64_t index) { + Assert(index >= 0 && index < len); + int64_t right_len = len - index - 1; + memmove(data + index, data + index + 1, right_len * sizeof(T)); + len -= 1; + } + + // @copy_paste array string slice + struct Reverse_Iter { + T *data; + Array *arr; + + Reverse_Iter operator++(int) { + Reverse_Iter ret = *this; + data -= 1; + return ret; + } + Reverse_Iter &operator++() { + data -= 1; + return *this; + } + + T &operator*() { return data[0]; } + T *operator->() { return data; } + + friend bool operator==(const Reverse_Iter &a, const Reverse_Iter &b) { return a.data == b.data; }; + friend bool operator!=(const Reverse_Iter &a, const Reverse_Iter &b) { return a.data != b.data; }; + + Reverse_Iter begin() { return Reverse_Iter{arr->end() - 1, arr}; } + Reverse_Iter end() { return Reverse_Iter{arr->begin() - 1, arr}; } + }; + + Reverse_Iter reverse_iter() { return {end() - 1, this}; } + + Slice chop(int64_t ilen) { + ilen = ClampTop(ilen, len); + Slice result = {data, len - ilen}; + return result; + } + + Slice skip(int64_t ilen) { + ilen = ClampTop(ilen, len); + Slice result = {data + ilen, len - ilen}; + return result; + } + + Slice get_postfix(int64_t ilen) { + ilen = ClampTop(ilen, len); + int64_t remain_len = len - ilen; + Slice result = {data + remain_len, ilen}; + return result; + } + + Slice get_prefix(int64_t ilen) { + ilen = ClampTop(ilen, len); + Slice result = {data, ilen}; + return result; + } + + Slice slice(int64_t first_index = 0, int64_t one_past_last_index = SLICE_LAST) { + // Negative indexes work in python style, they return you the index counting from end of list + if (one_past_last_index == SLICE_LAST) one_past_last_index = len; + if (one_past_last_index < 0) one_past_last_index = len + one_past_last_index; + + if (first_index == SLICE_LAST) first_index = len; + if (first_index < 0) first_index = len + first_index; + + Slice result = {data, len}; + if (len > 0) { + if (one_past_last_index > first_index) { + first_index = ClampTop(first_index, len - 1); + one_past_last_index = ClampTop(one_past_last_index, len); + result.data += first_index; + result.len = one_past_last_index - first_index; + } else { + result.len = 0; + } + } + return result; + } + + int64_t get_index(const T &item) { + ptrdiff_t index = (ptrdiff_t)(&item - data); + Assert(index >= 0 && index < len); + // Assert(index > INT_MIN && index < INT_MAX); + return (int64_t)index; + } + + bool contains(T &item) { + bool result = &item >= data && &item < data + len; + return result; + } + + bool is_first(T &item) { return &item == first(); } + bool is_last(T &item) { return &item == last(); } + T *first() { + Assert(len > 0); + return data; + } + T *last() { + Assert(len > 0); + return data + len - 1; + } + T *front() { + Assert(len > 0); + return data; + } + T *back() { + Assert(len > 0); + return data + len - 1; + } + T &operator[](int64_t index) { + Assert(index >= 0 && index < len); + return data[index]; + } + T *begin() { return data; } + T *end() { return data + len; } +}; + +struct UTF32Result { + uint32_t out_str; + int advance; + int error; +}; + +struct UTF8Result { + uint8_t out_str[4]; + int len; + int error; +}; + +struct UTF16Result { + uint16_t out_str[2]; + int len; + int error; +}; + +struct UTF8Iter { + char *data; + int len; + int utf8_codepoint_byte_size; + int i; + uint32_t item; + + UTF8Iter &operator++() { + void Advance(UTF8Iter * iter); + Advance(this); + return *this; + } + friend bool operator!=(const UTF8Iter &a, const UTF8Iter &b) { return a.item != b.item; } + UTF8Iter &operator*() { return *this; } + UTF8Iter begin() { + UTF8Iter IterateUTF8Ex(char *data, int len); + return {IterateUTF8Ex(data, len)}; + } + UTF8Iter end() { return {}; } +}; + +using String = Slice; +using String16 = Slice; +bool IsValid(UTF8Iter &iter); +void Advance(UTF8Iter *iter); +UTF8Iter IterateUTF8Ex(char *data, int len); +UTF8Iter IterateUTF8(char *data); +UTF8Iter IterateUTF8(String string); + +#define FmtString(string) (int)(string).len, (string).data +bool AreEqual(String a, String b, unsigned ignore_case = false); +int64_t CreateWidecharFromChar(wchar_t *buffer, int64_t buffer_size, char *in, int64_t inlen); +inline bool operator==(String a, String b) { return AreEqual(a, b); } +inline bool operator!=(String a, String b) { return !AreEqual(a, b); } + +#if defined(__has_attribute) + #if __has_attribute(format) + #define PrintfFormatAttribute(fmt, va) __attribute__((format(printf, fmt, va))) + #endif +#endif + +#ifndef PrintfFormatAttribute + #define PrintfFormatAttribute(fmt, va) +#endif + +#define STRING_FORMAT(allocator, data, result) \ + va_list args1; \ + va_start(args1, data); \ + String result = FormatV(allocator, data, args1); \ + va_end(args1) + +String Format(Allocator allocator, const char *data, ...) PrintfFormatAttribute(2, 3); +String FormatV(Allocator allocator, const char *data, va_list args1); +String ToString(Allocator allocator, String16 string); +String ToString(Allocator allocator, wchar_t *string, int64_t len); +String ToString(Allocator allocator, wchar_t *wstring); +String16 ToString16(Allocator allocator, String string); +wchar_t *ToWidechar(Allocator allocator, String string); +int64_t WideLength(wchar_t *string); +void NormalizePathInPlace(String s); + +/* + Hash table implementation: + Pointers to values + Open adressing + Linear Probing + Power of 2 + Robin Hood hashing + Resizes on high probe count (min max load factor) + + Hash 0 is reserved for empty hash table entry +*/ +template +struct Table { + struct Entry { + uint64_t hash; + uint64_t key; + size_t distance; + Value value; + }; + + Allocator allocator; + size_t len, cap; + Entry *values; + + static const size_t max_load_factor = 80; + static const size_t min_load_factor = 50; + static const size_t significant_distance = 8; + + // load factor calculation was rearranged + // to get rid of division: + //> 100 * len / cap = load_factor + //> len * 100 = load_factor * cap + inline bool reached_load_factor(size_t lfactor) { + return (len + 1) * 100 >= lfactor * cap; + } + + inline bool is_empty(Entry *entry) { return entry->hash == 0; } + inline bool is_occupied(Entry *entry) { return entry->hash != 0; } + + void reserve(size_t size) { + Assert(size > cap && "New size is smaller then original size"); + Assert(IsPowerOf2(size)); + if (!allocator.proc) allocator = GetSystemAllocator(); + + Entry *old_values = values; + size_t old_cap = cap; + + values = (Entry *)AllocSize(allocator, sizeof(Entry) * size); + for (int i = 0; i < size; i += 1) values[i] = {}; + cap = size; + + Assert(!(old_values == 0 && len != 0)); + if (len == 0) { + if (old_values) Dealloc(allocator, &old_values); + return; + } + + len = 0; + for (size_t i = 0; i < old_cap; i += 1) { + Entry *it = old_values + i; + if (is_occupied(it)) { + insert(it->key, it->value); + } + } + Dealloc(allocator, &old_values); + } + + Entry *get_table_entry(uint64_t key) { + if (len == 0) return 0; + uint64_t hash = HashBytes(&key, sizeof(key)); + if (hash == 0) hash += 1; + uint64_t index = WrapAroundPowerOf2(hash, cap); + uint64_t i = index; + uint64_t distance = 0; + for (;;) { + Entry *it = values + i; + if (distance > it->distance) { + return 0; + } + + if (it->hash == hash && it->key == key) { + return it; + } + + distance += 1; + i = WrapAroundPowerOf2(i + 1, cap); + if (i == index) return 0; + } + Assert(!"Invalid codepath"); + } + + void insert(uint64_t key, const Value &value) { + if (reached_load_factor(max_load_factor)) { + if (cap == 0) cap = 16; // 32 cause cap*2 + reserve(cap * 2); + } + + uint64_t hash = HashBytes(&key, sizeof(key)); + if (hash == 0) hash += 1; + uint64_t index = WrapAroundPowerOf2(hash, cap); + uint64_t i = index; + Entry to_insert = {hash, key, 0, value}; + for (;;) { + Entry *it = values + i; + if (is_empty(it)) { + *it = to_insert; + len += 1; + // If we have more then 8 consecutive items we try to resize + if (to_insert.distance > 8 && reached_load_factor(min_load_factor)) { + reserve(cap * 2); + } + return; + } + if (it->hash == hash && it->key == key) { + *it = to_insert; + // If we have more then 8 consecutive items we try to resize + if (to_insert.distance > 8 && reached_load_factor(min_load_factor)) { + reserve(cap * 2); + } + return; + } + + // Robin hood hashing + if (to_insert.distance > it->distance) { + Entry temp = to_insert; + to_insert = *it; + *it = temp; + } + + to_insert.distance += 1; + i = WrapAroundPowerOf2(i + 1, cap); + Assert(i != index && "Did a full 360 through a hash table, no good :( that shouldnt be possible"); + } + Assert(!"Invalid codepath"); + } + + void remove(uint64_t key) { + Entry *entry = get_table_entry(key); + entry->hash = 0; + entry->distance = 0; + len -= 1; + } + + Value *get(uint64_t key) { + Entry *v = get_table_entry(key); + if (!v) return 0; + return &v->value; + } + + Value get(uint64_t key, Value default_value) { + Entry *v = get_table_entry(key); + if (!v) return default_value; + return v->value; + } + + Value *get(String s) { + uint64_t hash = HashBytes(s.data, (unsigned)s.len); + return get(hash); + } + + Value get(String s, Value default_value) { + uint64_t hash = HashBytes(s.data, (unsigned)s.len); + return get(hash, default_value); + } + + void put(String s, const Value &value) { + uint64_t hash = HashBytes(s.data, (unsigned)s.len); + insert(hash, value); + } + + void reset() { + len = 0; + for (size_t i = 0; i < cap; i += 1) { + Entry *it = values + i; + it->hash = 0; + } + } + + void dealloc() { + Dealloc(allocator, &values); + len = 0; + cap = 0; + } +}; + +template +struct DEFER_ExitScope { + T lambda; + DEFER_ExitScope(T lambda) : lambda(lambda) {} + ~DEFER_ExitScope() { lambda(); } + DEFER_ExitScope(const DEFER_ExitScope &i) : lambda(i.lambda){}; + + private: + DEFER_ExitScope &operator=(const DEFER_ExitScope &); +}; + +class DEFER_ExitScopeHelp { + public: + template + DEFER_ExitScope operator+(T t) { return t; } +}; + +#define DEFER_CONCAT_INTERNAL(x, y) x##y +#define DEFER_CONCAT(x, y) DEFER_CONCAT_INTERNAL(x, y) +#define defer const auto DEFER_CONCAT(defer__, __LINE__) = DEFER_ExitScopeHelp() + [&]() + +const int PAGE_SIZE = 4096; +const int DEFAULT_ALIGNMENT = sizeof(void *); + +struct Arena { + uint8_t *data; + size_t len; + size_t base_len; // to prevent self deleting the arena + size_t reserve; + size_t commit; + size_t align; + + operator Allocator() { + void *ArenaAllocatorProc(void *object, int kind, void *p, size_t size); + return {ArenaAllocatorProc, this}; + } +}; + +struct TempArena { + Arena *arena; + size_t len; +}; + +inline void SetLen(Arena *arena, size_t len) { arena->len = Clamp(len, arena->base_len, arena->len); } +inline void Pop(Arena *arena, size_t size) { SetLen(arena, arena->len - size); } +inline TempArena BeginTemp(Arena *arena) { return {arena, arena->len}; } +inline void EndTemp(TempArena temp) { SetLen(temp.arena, temp.len); } +inline void Clear(Arena *arena) { SetLen(arena, 0); } + +void *VReserve(size_t size); +bool VCommit(void *p, size_t size); +bool VRelease(void *p); +bool VDecommit(void *p, size_t size); + +void InitArena(Arena *arena, size_t reserve = GiB(4)); +Arena *AllocArena(size_t reserve = GiB(4)); +Arena *AllocArena(Allocator allocator, size_t size); +void *PushSize(Arena *arena, size_t size); +void Release(Arena *arena); +void InitScratch(); +TempArena GetScratchEx(Arena **conflicts, int conflict_count); + +inline TempArena GetScratch(Arena *c1 = NULL, Arena *c2 = NULL) { + int count = c1 ? 1 : 0; + count += c2 ? 1 : 0; + Arena *conflicts[] = {c1, c2}; + return GetScratchEx(conflicts, count); +} + +struct Scratch { + TempArena checkpoint; + Scratch() { this->checkpoint = GetScratch(); } + Scratch(TempArena conflict) { this->checkpoint = GetScratch(conflict.arena); } + Scratch(TempArena c1, TempArena c2) { this->checkpoint = GetScratch(c1.arena, c2.arena); } + ~Scratch() { EndTemp(checkpoint); } + operator Arena *() { return checkpoint.arena; } + operator Allocator() { return *checkpoint.arena; } + + private: // @Note: Disable copy constructors, cause its error prone + Scratch(Scratch &arena); + Scratch(Scratch &arena, Scratch &a2); +}; + +// +// Implementation +// +#ifdef BASIC_IMPL + +UTF32Result UTF16ToUTF32(uint16_t *c, int max_advance) { + UTF32Result result; + 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; +} + +UTF8Result UTF32ToUTF8(uint32_t codepoint) { + UTF8Result result; + 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; +} + +UTF32Result UTF8ToUTF32(char *c, int max_advance) { + UTF32Result result; + 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; +} + +UTF16Result UTF32ToUTF16(uint32_t codepoint) { + UTF16Result result; + 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; \ + } + +int64_t 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];) { + UTF32Result decode = UTF16ToUTF32((uint16_t *)(in + i), (int)(inlen - i)); + if (!decode.error) { + i += decode.advance; + UTF8Result encode = UTF32ToUTF8(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; +} + +int64_t CreateWidecharFromChar(wchar_t *buffer, int64_t buffer_size, char *in, int64_t inlen) { + int64_t outlen = 0; + for (int64_t i = 0; i < inlen;) { + UTF32Result decode = UTF8ToUTF32(in + i, (int)(inlen - i)); + if (!decode.error) { + i += decode.advance; + UTF16Result encode = UTF32ToUTF16(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; +} + +bool IsValid(UTF8Iter &iter) { + return iter.item; +} + +void Advance(UTF8Iter *iter) { + iter->i += iter->utf8_codepoint_byte_size; + UTF32Result r = UTF8ToUTF32(iter->data + 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; +} + +UTF8Iter IterateUTF8Ex(char *data, int len) { + UTF8Iter result; + MemoryZero(&result, sizeof(result)); + result.data = data; + result.len = len; + if (len) Advance(&result); + return result; +} + +UTF8Iter IterateUTF8(char *data) { + int length = 0; + while (data[length]) length += 1; + return IterateUTF8Ex(data, length); +} + +UTF8Iter IterateUTF8(String string) { + return IterateUTF8Ex(string.data, string.len); +} + +char ToLowerCase(char a) { + if (a >= 'A' && a <= 'Z') a += 32; + return a; +} + +char ToUpperCase(char a) { + if (a >= 'a' && a <= 'z') a -= 32; + return a; +} + +bool IsWhitespace(char w) { + bool result = w == '\n' || w == ' ' || w == '\t' || w == '\v' || w == '\r'; + return result; +} + +bool IsAlphabetic(char a) { + bool result = (a >= 'a' && a <= 'z') || (a >= 'A' && a <= 'Z'); + return result; +} + +bool IsIdent(char a) { + bool result = (a >= 'a' && a <= 'z') || (a >= 'A' && a <= 'Z') || a == '_'; + return result; +} + +bool IsDigit(char a) { + bool result = a >= '0' && a <= '9'; + return result; +} + +bool IsAlphanumeric(char a) { + bool result = IsDigit(a) || IsAlphabetic(a); + return result; +} + +bool AreEqual(String a, String b, unsigned ignore_case) { + if (a.len != b.len) return false; + for (int64_t i = 0; i < a.len; i++) { + char A = a.data[i]; + char B = b.data[i]; + if (ignore_case) { + A = ToLowerCase(A); + B = ToLowerCase(B); + } + if (A != B) + return false; + } + return true; +} + +bool EndsWith(String a, String end, unsigned ignore_case = false) { + String a_end = a.get_postfix(end.len); + bool result = AreEqual(end, a_end, ignore_case); + return result; +} + +bool StartsWith(String a, String start, unsigned ignore_case = false) { + String a_start = a.get_prefix(start.len); + bool result = AreEqual(start, a_start, ignore_case); + return result; +} + +String Trim(String string) { + if (string.len == 0) + return string; + + int64_t whitespace_begin = 0; + for (; whitespace_begin < string.len; whitespace_begin++) { + if (!IsWhitespace(string.data[whitespace_begin])) { + break; + } + } + + int64_t whitespace_end = string.len; + for (; whitespace_end != whitespace_begin; whitespace_end--) { + if (!IsWhitespace(string.data[whitespace_end - 1])) { + break; + } + } + + if (whitespace_begin == whitespace_end) { + string.len = 0; + } else { + string = string.slice(whitespace_begin, whitespace_end); + } + + return string; +} + +String TrimEnd(String string) { + int64_t whitespace_end = string.len; + for (; whitespace_end != 0; whitespace_end--) { + if (!IsWhitespace(string.data[whitespace_end - 1])) { + break; + } + } + + String result = string.get_prefix(whitespace_end); + return result; +} + +String Copy(Allocator allocator, String string) { + char *copy = (char *)AllocSize(allocator, sizeof(char) * (string.len + 1)); + memcpy(copy, string.data, string.len); + copy[string.len] = 0; + String result = {copy, string.len}; + return result; +} + +String Copy(Allocator allocator, char *string) { + return Copy(allocator, {string, (int64_t)strlen(string)}); +} + +char *NullTerminate(Allocator allocator, String string) { + if (string.data[string.len] != 0) return Copy(allocator, string).data; + return string.data; +} + +void NormalizePathInPlace(String s) { + for (int64_t i = 0; i < s.len; i++) { + if (s.data[i] == '\\') + s.data[i] = '/'; + } +} + +String NormalizePath(Allocator allocator, String s) { + String copy = Copy(allocator, s); + NormalizePathInPlace(copy); + return copy; +} + +typedef int SeekFlag; +enum { + SeekFlag_None = 0, + SeekFlag_IgnoreCase = 1, + SeekFlag_MatchFindLast = 2, +}; + +bool Seek(String string, String find, int64_t *index_out = NULL, SeekFlag flags = SeekFlag_None) { + bool ignore_case = flags & SeekFlag_IgnoreCase ? true : false; + bool result = false; + if (flags & SeekFlag_MatchFindLast) { + for (int64_t i = string.len; i != 0; i--) { + int64_t index = i - 1; + String substring = string.slice(index, index + find.len); + if (AreEqual(substring, find, ignore_case)) { + if (index_out) + *index_out = index; + result = true; + break; + } + } + } else { + for (int64_t i = 0; i < string.len; i++) { + String substring = string.slice(i, i + find.len); + if (AreEqual(substring, find, ignore_case)) { + if (index_out) + *index_out = i; + result = true; + break; + } + } + } + + return result; +} + +Array Split(Allocator allocator, String string, String delimiter) { + Array result = {allocator}; + int64_t index = 0; + while (Seek(string, delimiter, &index)) { + String before_match = {string.data, index}; + result.add(before_match); + string = string.skip(index + delimiter.len); + } + if (string.len) result.add(string); + return result; +} + +String ChopLastSlash(String s) { + String result = s; + Seek(s, "/", &result.len, SeekFlag_MatchFindLast); + return result; +} + +String ChopLastPeriod(String s) { + String result = s; + Seek(s, ".", &result.len, SeekFlag_MatchFindLast); + return result; +} + +String SkipToLastSlash(String s) { + int64_t pos; + String result = s; + if (Seek(s, "/", &pos, SeekFlag_MatchFindLast)) { + result = result.skip(pos + 1); + } + return result; +} + +String SkipToLastPeriod(String s) { + int64_t pos; + String result = s; + if (Seek(s, ".", &pos, SeekFlag_MatchFindLast)) { + result = result.skip(pos + 1); + } + return result; +} + + #include +String FormatV(Allocator allocator, const char *data, va_list args1) { + va_list args2; + va_copy(args2, args1); + int64_t len = vsnprintf(0, 0, data, args2); + va_end(args2); + + char *result = (char *)AllocSize(allocator, sizeof(char) * (len + 1)); + vsnprintf(result, (int)(len + 1), data, args1); + String res = {result, len}; + return res; +} + +String Format(Allocator allocator, const char *data, ...) PrintfFormatAttribute(2, 3) { + STRING_FORMAT(allocator, data, result); + return result; +} + +int64_t WideLength(wchar_t *string) { + int64_t len = 0; + while (*string++ != 0) + len++; + return len; +} + +String16 ToString16(Allocator allocator, String string) { + Assert(sizeof(wchar_t) == 2); + wchar_t *buffer = (wchar_t *)AllocSize(allocator, sizeof(wchar_t) * (string.len + 1)); + int64_t size = CreateWidecharFromChar(buffer, string.len + 1, string.data, string.len); + String16 result = {buffer, size}; + return result; +} + +wchar_t *ToWidechar(Allocator allocator, String string) { + String16 result = ToString16(allocator, string); + return result.data; +} + +String ToString(Allocator allocator, String16 string) { + Assert(sizeof(wchar_t) == 2); + + int64_t buffer_size = (string.len + 1) * 2; + char *buffer = (char *)AllocSize(allocator, buffer_size); + int64_t size = CreateCharFromWidechar(buffer, buffer_size, string.data, string.len); + String result = {buffer, size}; + + Assert(size < buffer_size); + return result; +} + +String ToString(Allocator allocator, wchar_t *string, int64_t len) { + return ToString(allocator, {string, len}); +} + +String ToString(Allocator allocator, wchar_t *wstring) { + int64_t size = WideLength(wstring); + String result = ToString(allocator, {wstring, size}); + return result; +} + +void InitArena(Arena *arena, size_t reserve) { + reserve = AlignUp(reserve, PAGE_SIZE); + arena->align = DEFAULT_ALIGNMENT; + arena->data = (uint8_t *)VReserve(reserve); + if (arena->data) { + arena->reserve = reserve; + } +} + +Arena *AllocArena(Allocator allocator, size_t size) { + Arena *result = AllocType(allocator, Arena); + result->data = (uint8_t *)AllocSize(allocator, size); + result->reserve = size; + result->commit = size; + result->align = DEFAULT_ALIGNMENT; + return result; +} + +Arena *AllocArena(size_t reserve) { + Arena *result = NULL; + + void *data = VReserve(reserve); + if (!data) return result; + + bool success = VCommit(data, PAGE_SIZE); + if (!success) { + VRelease(data); + return result; + } + + result = (Arena *)data; + result->data = (uint8_t *)data; + result->reserve = reserve; + result->commit = PAGE_SIZE; + result->len = result->base_len = sizeof(Arena); + result->align = DEFAULT_ALIGNMENT; + return result; +} + +void *PushSize(Arena *arena, size_t size) { + // base_len is used for bootstraping arenas, it denotes the + // space occupied by the arena. If len is smaller then base_len then + // we start to overwrite the arena itself - pure barbarism. + Assert(arena->len >= arena->base_len); + + size_t align_offset = 0; + if (arena->align) { + align_offset = GetAlignOffset((uintptr_t)arena->data + arena->len, arena->align); + } + size_t align_len = arena->len + align_offset; + size_t size_with_alignment = size + align_offset; + size_t new_len = arena->len + size_with_alignment; + if (new_len > arena->commit) { + size_t new_len_aligned_to_page_size = AlignUp(new_len, PAGE_SIZE); + size_t to_commit = new_len_aligned_to_page_size - arena->commit; + size_t to_commit_clamped = ClampTop(to_commit, arena->reserve); + if (to_commit_clamped > 0) { + bool success = VCommit(arena->data + arena->commit, to_commit_clamped); + if (success) arena->commit += to_commit_clamped; + } + if (new_len > arena->commit) { + return NULL; + } + } + uint8_t *result = arena->data + arena->len + align_offset; + arena->len = new_len; + return (void *)result; +} + +void Release(Arena *arena) { + if (arena == NULL || arena->data == NULL) return; + bool zero_memory = (uint8_t *)arena != arena->data; + VRelease(arena->data); + if (zero_memory) MemoryZero(arena, sizeof(Arena)); +} + +void *ArenaAllocatorProc(void *object, int kind, void *p, size_t size) { + if (kind == AllocatorKind_Allocate) { + return PushSize((Arena *)object, size); + } else if (AllocatorKind_Deallocate) { + } else { + Assert(!"invalid codepath"); + } + return NULL; +} + +thread_local Arena *ScratchArenaPool[4]; +void InitScratch() { + for (int i = 0; i < Lengthof(ScratchArenaPool); i += 1) { + ScratchArenaPool[i] = AllocArena(); + } +} + +TempArena GetScratchEx(Arena **conflicts, int conflict_count) { + Arena *unoccupied = 0; + for (int i = 0; i < Lengthof(ScratchArenaPool); i += 1) { + Arena *from_pool = ScratchArenaPool[i]; + unoccupied = from_pool; + for (int conflict_i = 0; conflict_i < conflict_count; conflict_i += 1) { + Arena *from_conflict = conflicts[conflict_i]; + if (from_pool == from_conflict) { + unoccupied = 0; + break; + } + } + + if (unoccupied) { + break; + } + } + + // Failed to get free scratch memory, this is a fatal error, this shouldnt happen + Assert(unoccupied); + TempArena result = BeginTemp(unoccupied); + return result; +} + + #include +void *SystemAllocator_Alloc(void *object, int kind, void *p, size_t size) { + void *result = NULL; + if (kind == AllocatorKind_Allocate) { + result = malloc(size); + assert(result); + } else if (kind == AllocatorKind_Deallocate) { + free(p); + } + return result; +} + +Allocator GetSystemAllocator() { + Allocator result = {SystemAllocator_Alloc}; + return result; +} + +#endif // BASIC_IMPL \ No newline at end of file diff --git a/src/pdf_browser/main.cpp b/src/pdf_browser/main.cpp new file mode 100644 index 0000000..ad8cf6e --- /dev/null +++ b/src/pdf_browser/main.cpp @@ -0,0 +1,11 @@ +#define BASIC_IMPL +#include "basic.h" + +#include "pdfio.h" +#include +#include "read_pdf.cpp" + +int main(int argc, char *argv[]) { + + return (0); +} \ No newline at end of file diff --git a/src/pdf_browser/read_pdf.cpp b/src/pdf_browser/read_pdf.cpp new file mode 100644 index 0000000..e0e9f7b --- /dev/null +++ b/src/pdf_browser/read_pdf.cpp @@ -0,0 +1,78 @@ +struct PdfPage { + String content; + int64_t number; +}; + +String Merge(Allocator allocator, Array list, String separator = " ") { + int64_t char_count = 0; + For(list) char_count += it.len; + if (char_count == 0) return {}; + int64_t node_count = list.len; + + int64_t base_size = (char_count + 1); + int64_t sep_size = (node_count - 1) * separator.len; + int64_t size = base_size + sep_size; + char *buff = (char *)AllocSize(allocator, sizeof(char) * (size + 1)); + String string = {buff, 0}; + For(list) { + Assert(string.len + it.len <= size); + memcpy(string.data + string.len, it.data, it.len); + string.len += it.len; + if (!list.is_last(it)) { + memcpy(string.data + string.len, separator.data, separator.len); + string.len += separator.len; + } + } + Assert(string.len == size - 1); + string.data[size] = 0; + return string; +} + +Array ReadPDF(Allocator allocator, String filename) { + Scratch scratch; + char buffer[1024]; + + char *filename_char = NullTerminate(scratch, filename); + pdfio_file_t *file = pdfioFileOpen(filename_char, NULL, NULL, NULL, NULL); + if (file == NULL) return {}; + defer { pdfioFileClose(file); }; + + Array pages = {allocator}; + for (int i = 0, num_pages = pdfioFileGetNumPages(file); i < num_pages; i++) { + pdfio_obj_t *obj = pdfioFileGetPage(file, i); + if (obj == NULL) continue; + + PdfPage *page = pages.alloc(); + page->number = i + 1; + + Array content = {scratch}; + size_t num_streams = pdfioPageGetNumStreams(obj); + for (int j = 0; j < num_streams; j++) { + pdfio_stream_t *st = pdfioPageOpenStream(obj, j, true); + if (st == NULL) continue; + defer { pdfioStreamClose(st); }; + + bool first = true; + while (pdfioStreamGetToken(st, buffer, sizeof(buffer))) { + if (buffer[0] == '(') { + if (first) { + first = false; + } else { + // content.add(" "); + } + + content.add(Copy(scratch, buffer + 1)); + } else if (!strcmp(buffer, "Td") || !strcmp(buffer, "TD") || !strcmp(buffer, "T*") || !strcmp(buffer, "\'") || !strcmp(buffer, "\"")) { + // content.add("\n"); + first = true; + } + } + + // if (!first) content.add("\n"); + } + + page->content = Merge(allocator, content, ""); + } + + return pages; +} \ No newline at end of file diff --git a/src/pdf_browser/win32.cpp b/src/pdf_browser/win32.cpp new file mode 100644 index 0000000..c7fc891 --- /dev/null +++ b/src/pdf_browser/win32.cpp @@ -0,0 +1,27 @@ +#ifndef NOMINMAX + #define NOMINMAX +#endif +#ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN +#endif +#include + +void *VReserve(size_t size) { + void *result = VirtualAlloc(0, size, MEM_RESERVE, PAGE_READWRITE); + return result; +} + +bool VCommit(void *p, size_t size) { + void *result = VirtualAlloc(p, size, MEM_COMMIT, PAGE_READWRITE); + return result ? true : false; +} + +bool VRelease(void *p) { + BOOL result = VirtualFree(p, 0, MEM_RELEASE); + return result ? true : false; +} + +bool VDecommit(void *p, size_t size) { + BOOL result = VirtualFree(p, size, MEM_DECOMMIT); + return result ? true : false; +} \ No newline at end of file diff --git a/src/transcript_browser/filesystem.h b/src/transcript_browser/filesystem.h new file mode 100644 index 0000000..bedc386 --- /dev/null +++ b/src/transcript_browser/filesystem.h @@ -0,0 +1,34 @@ +#pragma once +#include "../pdf_browser/basic.h" + +struct FileIter { + bool is_valid; + bool is_directory; + String absolute_path; + String relative_path; + String filename; + + String path; + Allocator allocator; + Arena *arena; + TempArena temp; + union { + struct Win32_FileIter *w32; + void *dir; + }; +}; + +String ReadFile(Allocator arena, String path); +String GetAbsolutePath(Allocator arena, String relative); +bool IsValid(const FileIter &it); +void Advance(FileIter *it); +FileIter IterateFiles(Allocator allocator, String path); +bool InitOS(); + +struct Process { + bool is_valid; + char platform[32]; +}; + +Process RunEx(String cmd); +int Wait(Process *process); \ No newline at end of file diff --git a/src/transcript_browser/main.cpp b/src/transcript_browser/main.cpp new file mode 100644 index 0000000..72fe745 --- /dev/null +++ b/src/transcript_browser/main.cpp @@ -0,0 +1,377 @@ +#define BASIC_IMPL +#include "../pdf_browser/basic.h" +#include "filesystem.h" +#include "tests.h" +#include "raylib.h" + +#include +#include +#include + +struct XToTimeString { + String string; // String inside transcript arena + uint16_t hour; + uint16_t minute; + uint16_t second; + String filepath; +}; + +struct TimeString { + uint16_t hour; + uint16_t minute; + uint16_t second; + String string; +}; + +struct TimeFile { + Array time_strings; + String file; +}; + +bool AppInitializedWithFolder; +Arena XArena; +Arena Perm; + +Array ParseSrtFile(Arena *arena, String filename) { + String content = ReadFile(*arena, filename); + Array lines = Split(*arena, content, "\n"); + + IterRemove(lines) { + IterRemovePrepare(lines); + it = Trim(it); + if (it.len == 0) remove_item = true; + } + + long section_number = 1; + Array time_strings = {*arena}; + for (int i = 0; i < lines.len;) { + String it0 = lines[i++]; + long num = strtol(it0.data, NULL, 10); + Assert(section_number == num); + section_number += 1; + + TimeString item = {}; + String it1 = lines[i++]; + item.hour = (uint16_t)strtol(it1.data, NULL, 10); + item.minute = (uint16_t)strtol(it1.data + 3, NULL, 10); + item.second = (uint16_t)strtol(it1.data + 6, NULL, 10); + + String next_section_number = Format(*arena, "%d", section_number); + while (i < lines.len && lines[i] != next_section_number) { + String it = lines[i]; + item.string = lines[i]; + time_strings.add(item); + i += 1; + } + } + + IterRemove(time_strings) { + IterRemovePrepare(time_strings); + if (i > 0 && AreEqual(time_strings[i - 1].string, time_strings[i].string, true)) { + remove_item = true; + } + } + + return time_strings; +} + +Array ArenasToFree; +Array TimeFiles; +std::mutex ParseThreadMutex; +void ParseThreadEntry(Array files) { + Arena *arena = AllocArena(); + ParseThreadMutex.lock(); + ArenasToFree.add(arena); + ParseThreadMutex.unlock(); + For(files) { + Array time_strings = ParseSrtFile(arena, it); + ParseThreadMutex.lock(); + TimeFiles.add({time_strings, it}); + ParseThreadMutex.unlock(); + } +} + +Array XToTimeStringArray = {Perm}; +Array InitForFolder(String folder) { + Scratch scratch; + ArenasToFree.allocator = scratch; + TimeFiles.allocator = scratch; + + int thread_count = 16; + Array filenames = {Perm}; + Array srt_files = {scratch}; + for (FileIter iter = IterateFiles(scratch, folder); IsValid(iter); Advance(&iter)) { + filenames.add(Copy(Perm, iter.absolute_path)); + if (EndsWith(iter.filename, ".srt")) { + srt_files.add(Copy(scratch, iter.absolute_path)); + } + } + + Array threads = {scratch}; + int files_per_thread = srt_files.len / thread_count; + int remainder = srt_files.len % thread_count; + int fi = 0; + for (int ti = 0; ti < thread_count; ti += 1) { + Array files = {scratch}; + for (int i = 0; fi < srt_files.len && i < files_per_thread + remainder; fi += 1, i += 1) { + files.add(srt_files[fi]); + } + if (remainder) remainder = 0; + threads.add(new std::thread(ParseThreadEntry, files)); + } + + For(threads) { + it->join(); + delete it; + } + + ForItem(file, TimeFiles) { + For(file.time_strings) { + String s = Copy(XArena, it.string); + s.data[s.len] = ' '; + XToTimeStringArray.add({s, it.hour, it.minute, it.second, file.file}); + } + } + For(ArenasToFree) Release(it); + + AppInitializedWithFolder = true; + return filenames; +} + +// +// Searching thread +// + +Arena MatchesArena; +Array Matches = {MatchesArena}; +XToTimeString *ItemFound; +Array Prompt; // system allocated + +std::mutex SearchThreadArrayMutex; +std::binary_semaphore SearchThreadSemaphore{0}; +bool SearchThreadStopSearching = false; +bool SearchThreadRunning = true; +void SearchThreadEntry() { + InitArena(&MatchesArena); + for (;;) { + SearchThreadSemaphore.acquire(); + if (!SearchThreadRunning) break; + SearchThreadStopSearching = false; + + if (Prompt.len) { + SearchThreadArrayMutex.lock(); + { + Matches.clear(); + } + SearchThreadArrayMutex.unlock(); + + String buffer = {(char *)XArena.data, (int64_t)XArena.len}; + String find = {Prompt.data, Prompt.len}; + int64_t index = 0; + while (Seek(buffer, find, &index, SeekFlag_IgnoreCase)) { + String found = {buffer.data + index, find.len}; + SearchThreadArrayMutex.lock(); + { + Matches.add(found); + } + SearchThreadArrayMutex.unlock(); + + if (SearchThreadStopSearching) break; + buffer = buffer.skip(index + find.len); + } + } + } +} + +void SearchThreadClose(std::thread &thread) { + SearchThreadRunning = false; + SearchThreadSemaphore.release(); + thread.join(); +} + +int main() { + InitOS(); + InitScratch(); + InitArena(&Perm); + InitArena(&XArena); + Arena *frame_arena = AllocArena(); + XArena.align = 0; + + String start_string = "C:/video"; + For(start_string) Prompt.add(it); + + std::thread search_thread(SearchThreadEntry); + int64_t chosen_text = 0; + int64_t match_search_offset = 0; + Array filenames = {}; + + InitWindow(1920, 1080, "Transcript Browser"); + SetWindowState(FLAG_WINDOW_RESIZABLE); + SetTargetFPS(60); + Font font = LoadFontEx("C:/Windows/Fonts/consola.ttf", 20, 0, 250); + while (!WindowShouldClose()) { + Clear(frame_arena); + + for (int key = GetCharPressed(); key; key = GetCharPressed()) { + UTF8Result utf8 = UTF32ToUTF8(key); + if (utf8.error) { + Prompt.add('?'); + continue; + } + + for (int i = 0; i < utf8.len; i += 1) { + Prompt.add(utf8.out_str[i]); + match_search_offset = 0; + SearchThreadStopSearching = true; + SearchThreadSemaphore.release(); + } + } + + if (IsKeyPressed(KEY_BACKSPACE) || IsKeyPressedRepeat(KEY_BACKSPACE)) { + if (ItemFound) { + ItemFound = NULL; + } else if (Prompt.len > 0) { + Prompt.pop(); + match_search_offset = 0; + SearchThreadStopSearching = true; + SearchThreadSemaphore.release(); + } + } + + int64_t offset_size = 1; + if (IsKeyDown(KEY_LEFT_CONTROL)) { + offset_size = 10; + } + if (IsKeyPressed(KEY_DOWN) || IsKeyPressedRepeat(KEY_DOWN)) { + chosen_text += offset_size; + if (chosen_text > 10) match_search_offset += offset_size; + } + if (IsKeyPressed(KEY_UP) || IsKeyPressedRepeat(KEY_UP)) { + chosen_text -= offset_size; + match_search_offset -= offset_size; + } + chosen_text = Clamp(chosen_text, (int64_t)0, Max(Matches.len - 1, (int64_t)0)); + match_search_offset = Clamp(match_search_offset, (int64_t)0, Max(Matches.len - 1 - 10, (int64_t)0)); + + if (IsKeyPressed(KEY_ENTER)) { + if (!AppInitializedWithFolder) { + Prompt.add('\0'); + filenames = InitForFolder(Prompt.data); + Prompt.clear(); + } else if (ItemFound) { + String base = ChopLastPeriod(ItemFound->filepath); // .srt + base = ChopLastPeriod(base); // .en + + For(filenames) { + if (StartsWith(it, base)) { + if (EndsWith(it, ".mkv") || EndsWith(it, ".webm") || EndsWith(it, ".mp4")) { + int seconds = ItemFound->hour * 60 * 60 + ItemFound->minute * 60 + ItemFound->second; + String copy = Copy(*frame_arena, it); + for (int i = 0; i < copy.len; i += 1) + if (copy.data[i] == '/') copy.data[i] = '\\'; + String args = Format(*frame_arena, "C:\\Program Files\\VideoLAN\\VLC\\vlc.exe --start-time %d \"%.*s\"", seconds, FmtString(copy)); + printf("%.*s\n", FmtString(args)); + RunEx(args); + } + } + } + } else if (Matches.len) { + String string = Matches[chosen_text]; + For(XToTimeStringArray) { + uintptr_t begin = (uintptr_t)(it.string.data); + uintptr_t end = (uintptr_t)(it.string.data + it.string.len); + uintptr_t needle = (uintptr_t)string.data; + + if (needle >= begin && needle < end) { + ItemFound = ⁢ + break; + } + } + } + } + BeginDrawing(); + ClearBackground(RAYWHITE); + + float font_size = 20; + float y = 0; + int xwidth = MeasureTextEx(font, "_", font_size, 1).x; + + if (!AppInitializedWithFolder) { + Prompt.add('\0'); + DrawTextEx(font, "> ", {0, y}, font_size, 1, BLACK); + DrawTextEx(font, Prompt.data, {(float)xwidth * 3, y}, font_size, 1, BLACK); + Prompt.len -= 1; + } else if (ItemFound) { + uintptr_t begin_region = (uintptr_t)XArena.data; + uintptr_t end_region = (uintptr_t)XArena.data + XArena.len; + + uintptr_t begin = (uintptr_t)(ItemFound->string.data - 1000); + uintptr_t end = (uintptr_t)(ItemFound->string.data + 1000); + + begin = Clamp(begin, begin_region, end_region); + end = Clamp(end, begin_region, end_region); + String string = {(char *)begin, (int64_t)(end - begin)}; + + String filename = SkipToLastSlash(ItemFound->filepath); + DrawTextEx(font, filename.data, {0, y}, font_size, 1, BLACK); + y += font_size; + + int per_line = (GetRenderWidth() / xwidth) - 20; + + for (String it = string; it.len;) { + String line = it.get_prefix(per_line); + + if (ItemFound->string.data >= line.data && ItemFound->string.data < line.data + line.len) { + DrawRectangleLines(0, y + font_size, GetRenderWidth(), 2, SKYBLUE); + } + + String line_terminated = Copy(*frame_arena, line); + DrawTextEx(font, line_terminated.data, {0, y}, font_size, 1, DARKGRAY); + + y += font_size; + it = it.skip(per_line); + } + } else { + Prompt.add('\0'); + DrawTextEx(font, "> ", {0, y}, font_size, 1, BLACK); + DrawTextEx(font, Prompt.data, {(float)xwidth * 3, y}, font_size, 1, BLACK); + Prompt.pop(); + y += font_size; + + int64_t chars_per_line = GetRenderWidth() / xwidth - Prompt.len; + + SearchThreadArrayMutex.lock(); + for (int64_t i = match_search_offset; i < Matches.len; i += 1) { + String it = Matches[i]; + uintptr_t begin_region = (uintptr_t)XArena.data; + uintptr_t end_region = (uintptr_t)XArena.data + XArena.len; + + uintptr_t begin = (uintptr_t)(it.data - chars_per_line / 2); + uintptr_t end = (uintptr_t)(it.data + chars_per_line / 2); + + begin = Clamp(begin, begin_region, end_region); + end = Clamp(end, begin_region, end_region); + String string = Copy(*frame_arena, {(char *)begin, (int64_t)(end - begin)}); + + String string_first = Copy(*frame_arena, {(char *)begin, (int64_t)(it.data - begin)}); + String string_middle = Copy(*frame_arena, it); + int width = MeasureTextEx(font, string_first.data, font_size, 1).x; + if (chosen_text == i) DrawRectangleLines(0, y + font_size, GetRenderWidth(), 2, SKYBLUE); + + String num = Format(*frame_arena, "%d", i); + DrawTextEx(font, num.data, {0, y}, font_size, 1, DARKGRAY); + DrawTextEx(font, string.data, {(float)xwidth * 4, y}, font_size, 1, DARKGRAY); + DrawTextEx(font, string_middle.data, {(float)xwidth * 4 + (float)width, y}, font_size, 1, SKYBLUE); + + y += font_size; + if (y > GetRenderHeight()) break; + } + SearchThreadArrayMutex.unlock(); + } + + if (IsKeyDown(KEY_F1)) DrawFPS(0, 0); + EndDrawing(); + } + CloseWindow(); + SearchThreadClose(search_thread); +} diff --git a/src/transcript_browser/tests.h b/src/transcript_browser/tests.h new file mode 100644 index 0000000..3f213b9 --- /dev/null +++ b/src/transcript_browser/tests.h @@ -0,0 +1,80 @@ + +void TestArena() { + Arena arena = {}; + + { + void *value = PushSize(&arena, sizeof(int)); + Assert(value == 0); + } + + InitArena(&arena, MiB(256) + PAGE_SIZE); + Assert(arena.data && arena.reserve); + + { + uint8_t *data = (uint8_t *)PushSize(&arena, sizeof(uint8_t)); + *data = 10; + data[PAGE_SIZE - 1] = 0; + // data[PAGE_SIZE] = 0; + + Assert(*data == 10); + Assert(arena.len == 1); + Assert(arena.commit == PAGE_SIZE); + + uint8_t *d2 = (uint8_t *)PushSize(&arena, sizeof(uint8_t)); + *d2 = 20; + Assert(arena.len == 9); + } + + { + int *alot_of_data = (int *)PushSize(&arena, MiB(256)); + + for (int i = 0; i < 1024 * 1024 * 1; i += 1) { + alot_of_data[i] = i; + } + for (int i = 0; i < 1024 * 1024 * 1; i += 1) { + Assert(alot_of_data[i] == i); + } + Assert(arena.len == 16 + MiB(256)); + Assert(arena.commit == AlignUp(16 + MiB(256), PAGE_SIZE)); + } + + { + void *result = PushSize(&arena, PAGE_SIZE - 16); + Assert(result); + Assert(arena.commit == arena.reserve); + Assert(arena.commit == arena.len); + + void *v = PushSize(&arena, 2423); + Assert(v == NULL); + } + + Release(&arena); + + { + Arena *arena = AllocArena(); + int *a = (int *)PushSize(arena, 1024); + for (int i = 0; i < 64; i += 1) a[i] = 0; + Clear(arena); + a = (int *)PushSize(arena, 1024); + for (int i = 0; i < 64; i += 1) a[i] = 0; + Release(arena); + } +} + +void TestArray() { + Array arr = {}; + defer { arr.dealloc(); }; + + for (int i = 0; i < 32; i += 1) arr.add(i); + + int i = 31; + For(arr.reverse_iter()) { + defer { arr.unordered_remove(it); }; + Assert(i-- == it); + } +} + +void RunTests() { + TestArena(); + TestArray(); +} \ No newline at end of file diff --git a/src/transcript_browser/win32.cpp b/src/transcript_browser/win32.cpp new file mode 100644 index 0000000..dd5af69 --- /dev/null +++ b/src/transcript_browser/win32.cpp @@ -0,0 +1,206 @@ +#include "filesystem.h" + +#ifndef NOMINMAX + #define NOMINMAX +#endif +#ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN +#endif +#include +#include + +// Basic begin +void *VReserve(size_t size) { + void *result = (uint8_t *)VirtualAlloc(0, size, MEM_RESERVE, PAGE_READWRITE); + return result; +} + +bool VCommit(void *p, size_t size) { + void *result = VirtualAlloc(p, size, MEM_COMMIT, PAGE_READWRITE); + return result ? true : false; +} + +bool VRelease(void *p) { + BOOL result = VirtualFree(p, 0, MEM_RELEASE); + return result ? true : false; +} + +bool VDecommit(void *p, size_t size) { + BOOL result = VirtualFree(p, size, MEM_DECOMMIT); + return result ? true : false; +} +// Basic end + +Process RunEx(String args) { + Scratch scratch; + wchar_t *application_name = NULL; + wchar_t *cmd = ToWidechar(scratch, args); + BOOL inherit_handles = FALSE; + DWORD creation_flags = 0; + void *enviroment = NULL; + wchar_t *working_dir = NULL; + STARTUPINFOW startup_info = {}; + startup_info.cb = sizeof(STARTUPINFOW); + Process result = {}; + Assert(sizeof(result.platform) >= sizeof(PROCESS_INFORMATION)); + PROCESS_INFORMATION *process_info = (PROCESS_INFORMATION *)result.platform; + BOOL success = CreateProcessW(application_name, cmd, NULL, NULL, inherit_handles, creation_flags, enviroment, working_dir, &startup_info, process_info); + result.is_valid = true; + if (!success) { + result.is_valid = false; + + LPVOID lpMsgBuf; + DWORD dw = GetLastError(); + FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, dw, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&lpMsgBuf, 0, NULL); + LocalFree(lpMsgBuf); + + printf("Failed to create process \ncmd: %.*s\nwindows_message: %s", FmtString(args), (char *)lpMsgBuf); + Assert(!"Failed to create process"); + } + return result; +} + +int Wait(Process *process) { + Assert(process->is_valid); + PROCESS_INFORMATION *pi = (PROCESS_INFORMATION *)process->platform; + WaitForSingleObject(pi->hProcess, INFINITE); + + DWORD exit_code; + BOOL err = GetExitCodeProcess(pi->hProcess, &exit_code); + Assert(err != 0); + + CloseHandle(pi->hProcess); + CloseHandle(pi->hThread); + process[0] = {}; + return (int)exit_code; +} + +bool InitOS() { + SetConsoleOutputCP(CP_UTF8); + + HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); + if (hOut != INVALID_HANDLE_VALUE) { + DWORD dwMode = 0; + if (GetConsoleMode(hOut, &dwMode)) { + dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; + if (SetConsoleMode(hOut, dwMode)) { + return true; + } else { + printf("Failed to enable colored terminal output C\n"); + } + } else { + printf("Failed to enable colored terminal output B\n"); + } + } else { + printf("Failed to enable colored terminal output A\n"); + } + return false; +} + +String ReadFile(Allocator arena, String path) { + bool success = false; + String result = {}; + + wchar_t wpath[1024]; + CreateWidecharFromChar(wpath, Lengthof(wpath), path.data, path.len); + HANDLE handle = CreateFileW(wpath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (handle != INVALID_HANDLE_VALUE) { + LARGE_INTEGER file_size; + if (GetFileSizeEx(handle, &file_size)) { + if (file_size.QuadPart != 0) { + result.len = (int64_t)file_size.QuadPart; + result.data = (char *)AllocSize(arena, result.len + 1); + DWORD read; + if (ReadFile(handle, result.data, (DWORD)result.len, &read, NULL)) { // @todo: can only read 32 byte size files? + if (read == result.len) { + success = true; + result.data[result.len] = 0; + } + } + } + } + CloseHandle(handle); + } + + if (!success) { + Dealloc(arena, &result.data); + result = {}; + } + + return result; +} + +typedef struct Win32_FileIter { + HANDLE handle; + WIN32_FIND_DATAW data; +} Win32_FileIter; + +String GetAbsolutePath(Allocator arena, String relative) { + wchar_t wpath[1024]; + CreateWidecharFromChar(wpath, Lengthof(wpath), relative.data, relative.len); + wchar_t wpath_abs[1024]; + DWORD written = GetFullPathNameW((wchar_t *)wpath, Lengthof(wpath_abs), wpath_abs, 0); + if (written == 0) + return {}; + String path = ToString(arena, {(wchar_t *)wpath_abs, written}); + NormalizePathInPlace(path); + return path; +} + +bool IsValid(const FileIter &it) { + return it.is_valid; +} + +void Advance(FileIter *it) { + if (it->temp.arena) EndTemp(it->temp); + it->temp = BeginTemp(it->arena); + while (FindNextFileW(it->w32->handle, &it->w32->data) != 0) { + WIN32_FIND_DATAW *data = &it->w32->data; + + // Skip '.' and '..' + if (data->cFileName[0] == '.' && data->cFileName[1] == '.' && data->cFileName[2] == 0) continue; + if (data->cFileName[0] == '.' && data->cFileName[1] == 0) continue; + + it->is_directory = data->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY; + it->filename = ToString(*it->arena, (wchar_t *)data->cFileName, WideLength(data->cFileName)); + const char *is_dir = it->is_directory ? "/" : ""; + const char *separator = it->path.data[it->path.len - 1] == '/' ? "" : "/"; + it->relative_path = Format(*it->arena, "%.*s%s%.*s%s", FmtString(it->path), separator, FmtString(it->filename), is_dir); + it->absolute_path = GetAbsolutePath(*it->arena, it->relative_path); + it->is_valid = true; + + if (it->is_directory) { + Assert(it->relative_path.data[it->relative_path.len - 1] == '/'); + Assert(it->absolute_path.data[it->absolute_path.len - 1] == '/'); + } + return; + } + + it->is_valid = false; + DWORD error = GetLastError(); + Assert(error == ERROR_NO_MORE_FILES); + FindClose(it->w32->handle); + Dealloc(it->allocator, &it->arena); +} + +FileIter IterateFiles(Allocator alo, String path) { + FileIter it = {0}; + it.allocator = alo; + it.arena = AllocArena(alo, MiB(2)); + it.path = path; + + String modified_path = Format(*it.arena, "%.*s\\*", FmtString(path)); + String16 modified_path16 = ToString16(*it.arena, modified_path); + + it.w32 = AllocType(*it.arena, Win32_FileIter); + it.w32->handle = FindFirstFileW(modified_path16.data, &it.w32->data); + if (it.w32->handle == INVALID_HANDLE_VALUE) { + it.is_valid = false; + return it; + } + + Assert(it.w32->data.cFileName[0] == '.' && it.w32->data.cFileName[1] == 0); + Advance(&it); + return it; +}