From 781c2dc53ccf8979e80dc20c8d5cf07ff8ada568 Mon Sep 17 00:00:00 2001 From: Krzosa Karol Date: Thu, 27 Nov 2025 09:00:10 +0100 Subject: [PATCH] Start refactor, restructure basic --- build.bat | 22 +- src/basic/basic.cpp | 9 + src/basic/basic.h | 1788 +------------------ src/basic/basic_alloc.cpp | 248 +++ src/basic/basic_alloc.h | 95 + src/basic/basic_array.h | 497 ++++++ src/basic/basic_head.h | 215 +++ src/basic/{linked_list.h => basic_list.h} | 4 +- src/basic/basic_math.h | 59 + src/basic/basic_os.cpp | 1101 ++++++++++++ src/basic/{filesystem.h => basic_os.h} | 4 +- src/basic/basic_string.cpp | 336 ++++ src/basic/basic_string.h | 106 ++ src/basic/basic_string16.cpp | 422 +++++ src/basic/basic_string16.h | 104 ++ src/basic/basic_table.h | 185 ++ src/basic/basic_unicode.cpp | 237 +++ src/basic/basic_unicode.h | 60 + src/basic/math.cpp | 38 - src/basic/math_int.cpp | 20 - src/basic/stb_sprintf.h | 1928 +++++++++++++++++++++ src/basic/string16.cpp | 341 ---- src/basic/thread_queue.h | 33 - src/basic/unix.cpp | 417 ----- src/basic/wasm.cpp | 260 --- src/basic/win32.cpp | 487 ------ src/basic/win32_thread.cpp | 84 - src/metaprogram/metaprogram.cpp | 62 +- src/profiler/profiler.cpp | 6 +- src/text_editor/todo.txt | 38 + 30 files changed, 5716 insertions(+), 3490 deletions(-) create mode 100644 src/basic/basic.cpp create mode 100644 src/basic/basic_alloc.cpp create mode 100644 src/basic/basic_alloc.h create mode 100644 src/basic/basic_array.h create mode 100644 src/basic/basic_head.h rename src/basic/{linked_list.h => basic_list.h} (99%) create mode 100644 src/basic/basic_math.h create mode 100644 src/basic/basic_os.cpp rename src/basic/{filesystem.h => basic_os.h} (96%) create mode 100644 src/basic/basic_string.cpp create mode 100644 src/basic/basic_string.h create mode 100644 src/basic/basic_string16.cpp create mode 100644 src/basic/basic_string16.h create mode 100644 src/basic/basic_table.h create mode 100644 src/basic/basic_unicode.cpp create mode 100644 src/basic/basic_unicode.h create mode 100644 src/basic/stb_sprintf.h delete mode 100644 src/basic/string16.cpp delete mode 100644 src/basic/thread_queue.h delete mode 100644 src/basic/unix.cpp delete mode 100644 src/basic/wasm.cpp delete mode 100644 src/basic/win32.cpp delete mode 100644 src/basic/win32_thread.cpp diff --git a/build.bat b/build.bat index e7cfd21..cc35d98 100644 --- a/build.bat +++ b/build.bat @@ -13,19 +13,17 @@ if not exist "luaunity.obj" ( cl -Zi -nologo -I../src/external/lua/src -I../src/external/glad ../src/external/luaunity.c ../src/external/glad/glad.c -c ) -if not exist "metaprogram.exe" ( - cl /WX /W3 /wd4200 /diagnostics:column -FC -Zi -nologo -Fe:metaprogram.exe -I../src ../src/metaprogram/metaprogram.cpp ../src/basic/win32.cpp -) +cl /WX /W3 /wd4200 /diagnostics:column -FC -Zi -nologo -Fe:metaprogram.exe -I../src ../src/metaprogram/metaprogram.cpp metaprogram.exe -set sdl=../src/external/SDL/win32-static/SDL3-static.lib ../src/external/SDL/win32-static/SDL_uclibc.lib -cl /EHsc- /MD /Zi /FC /nologo /WX /W3 /wd4200 /diagnostics:column %profile_flags% ../src/text_editor/text_editor.cpp ../src/basic/win32.cpp -Fe:te.exe -I../src/external/SDL/include -I../src/external/lua/src -I../src/external/glad -I../src/ luaunity.obj glad.obj kernel32.lib gdi32.lib user32.lib Imm32.lib ole32.lib Shell32.lib OleAut32.lib Cfgmgr32.lib Setupapi.lib Advapi32.lib version.lib winmm.lib %sdl% -link /SUBSYSTEM:WINDOWS /NODEFAULTLIB:LIBCMT /NODEFAULTLIB:MSVCRTD +rem set sdl=../src/external/SDL/win32-static/SDL3-static.lib ../src/external/SDL/win32-static/SDL_uclibc.lib +rem cl /EHsc- /MD /Zi /FC /nologo /WX /W3 /wd4200 /diagnostics:column %profile_flags% ../src/text_editor/text_editor.cpp ../src/basic/win32.cpp -Fe:te.exe -I../src/external/SDL/include -I../src/external/lua/src -I../src/external/glad -I../src/ luaunity.obj glad.obj kernel32.lib gdi32.lib user32.lib Imm32.lib ole32.lib Shell32.lib OleAut32.lib Cfgmgr32.lib Setupapi.lib Advapi32.lib version.lib winmm.lib %sdl% -link /SUBSYSTEM:WINDOWS /NODEFAULTLIB:LIBCMT /NODEFAULTLIB:MSVCRTD -if "%1"=="release" ( - copy te.exe ..\data\te.exe - echo written ..\data\te.exe -) else ( - copy te.exe ..\data\te_debug.exe - echo written ..\data\te_debug.exe -) +rem if "%1"=="release" ( +rem copy te.exe ..\data\te.exe +rem echo written ..\data\te.exe +rem ) else ( +rem copy te.exe ..\data\te_debug.exe +rem echo written ..\data\te_debug.exe +rem ) diff --git a/src/basic/basic.cpp b/src/basic/basic.cpp new file mode 100644 index 0000000..0cfdbc6 --- /dev/null +++ b/src/basic/basic.cpp @@ -0,0 +1,9 @@ +#define STB_SPRINTF_IMPLEMENTATION +#include "stb_sprintf.h" +#include "basic_string.cpp" +#include "basic_string16.cpp" +#include "basic_unicode.cpp" +#include "basic_alloc.cpp" +#include "basic_os.cpp" +#include "math.cpp" +#include "math_int.cpp" \ No newline at end of file diff --git a/src/basic/basic.h b/src/basic/basic.h index 8e21fd6..04ae0d7 100644 --- a/src/basic/basic.h +++ b/src/basic/basic.h @@ -1,1778 +1,10 @@ -#pragma once -#ifndef _CRT_SECURE_NO_WARNINGS - #define _CRT_SECURE_NO_WARNINGS -#endif -#include -#include -#include -#include -#include -#include - - -#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 defined(__EMSCRIPTEN__) - #define OS_WASM 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_WASM - #define OS_WASM 0 -#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 - -#if OS_WINDOWS -#define BREAK() __debugbreak() -#elif OS_LINUX -#define BREAK() raise(SIGTRAP) -#elif OS_WASM -#include -extern "C" void JS_Breakpoint(void); -#define BREAK() JS_Breakpoint() -#endif - -#define Assert(x) \ - if (!(x)) { \ - BREAK(); \ - } -#define InvalidCodepath() Assert(!"invalid codepath") -#define ElseInvalidCodepath() else {InvalidCodepath()} - -#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])))) - -using U8 = uint8_t; -using U16 = uint16_t; -using U32 = uint32_t; -using U64 = uint64_t; -using S8 = int8_t; -using S16 = int16_t; -using S32 = int32_t; -using S64 = int64_t; -using Int = S64; -using UInt = U64; - -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 < min) return min; - if (value > max) return max; - return value; -} - -template -void Swap(T *a, T *b) { - T temp = *a; - *a = *b; - *b = temp; -} - -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) { - if (*p == NULL) return; - alo.proc(alo.object, AllocatorKind_Deallocate, *p, 0); - *p = NULL; -} -#define DeallocEx(alo, p) (alo).proc((alo).object, AllocatorKind_Deallocate, (p), 0); - -template -void Dealloc2(Allocator alo, T *p) { - if (*p == NULL) return; - alo.proc(alo.object, AllocatorKind_Deallocate, *p, 0); - *p = NULL; -} - -Allocator GetSystemAllocator(); -#define MemoryZero(x, size) memset(x, 0, size) -#define MemoryCopy(dst, src, size) memcpy(dst, src, size) -#define MemoryMove(dst, src, size) memmove(dst, src, 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; - } - } -} - -// Simple delete -IterRemove(arr) { - IterRemovePrepare(arr); - - remove_item = true; -} - -// Deleting backwards -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) { \ - Remove(&(a), it); \ - i -= 1; \ - } \ - } -#define ForItem(it, array) for (auto &it : (array)) -#define For(array) ForItem(it, array) - -constexpr int64_t SLICE_LAST = INT64_MIN; -inline int64_t StringLen(char *string) { - if (!string) return 0; - int64_t i = 0; - while (string[i]) i += 1; - return i; -} - -inline int64_t WideLength(char16_t *string) { - if (!string) return 0; - int64_t len = 0; - while (*string++ != 0) - len++; - return len; -} - -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(StringLen(s)) {} - Slice(const char *s) : data((char *)s), len(StringLen((char *)s)) {} - Slice(const char *s, int64_t l) : data((char *)s), len(l) {} - - Slice(char16_t *s) : data(s), len(WideLength(s)) {} - Slice(const char16_t *s) : data((char16_t *)s), len(WideLength((char16_t *)s)) {} - Slice(const char16_t *s, int64_t l) : data((char16_t *)s), len(l) {} - - T &operator[](int64_t index) { - Assert(index < len); - return data[index]; - } - T *begin() { return data; } - T *end() { return data + len; } -}; - -template -Slice Copy(Allocator alo, Slice array) { - Slice result = {}; - result.data = AllocArray(alo, T, array.len); - memcpy(result.data, array.data, sizeof(T) * array.len); - result.len = array.len; - return result; -} - -template -T Pop(Slice *arr) { - Assert(arr->len > 0); - return arr->data[--arr->len]; -} - -template -bool Contains(Slice &arr, T item) { - For(arr) if (it == item) return true; - return false; -} - -template -int64_t GetIndex(Slice &arr, const T &item) { - ptrdiff_t index = (ptrdiff_t)(&item - arr.data); - Assert(index >= 0 && index < arr.len); - return (int64_t)index; -} - -template -T Get(Slice &arr, int64_t i, T default_value = {}) { - T result = default_value; - if (i >= 0 && i < arr.len) result = arr[i]; - return result; -} - -template -bool IsLast(Slice &arr, T &item) { - bool result = arr.last() == &item; - return result; -} - -template -bool IsFirst(Slice &arr, T &item) { - bool result = arr.first() == &item; - return result; -} - -template -T *GetFirst(Slice &arr) { - Assert(arr.len > 0); - return arr.data; -} - -template -T *GetLast(Slice &arr) { - Assert(arr.len > 0); - return arr.data + arr.len - 1; -} - -template -Slice Chop(Slice &arr, int64_t len) { - len = ClampTop(len, arr.len); - Slice result = {arr.data, arr.len - len}; - return result; -} - -template -Slice Skip(Slice &arr, int64_t len) { - len = ClampTop(len, arr.len); - Slice result = {arr.data + len, arr.len - len}; - return result; -} - -template -Slice GetPostfix(Slice &arr, int64_t len) { - len = ClampTop(len, arr.len); - int64_t remain_len = arr.len - len; - Slice result = {arr.data + remain_len, len}; - return result; -} - -template -Slice GetPrefix(Slice &arr, int64_t len) { - len = ClampTop(len, arr.len); - Slice result = {arr.data, len}; - return result; -} - -template -Slice GetSlice(Slice &arr, 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 = arr.len; - if (one_past_last_index < 0) one_past_last_index = arr.len + one_past_last_index; - - if (first_index == SLICE_LAST) first_index = arr.len; - if (first_index < 0) first_index = arr.len + first_index; - - Slice result = {arr.data, arr.len}; - if (arr.len > 0) { - if (one_past_last_index > first_index) { - first_index = ClampTop(first_index, arr.len - 1); - one_past_last_index = ClampTop(one_past_last_index, arr.len); - result.data += first_index; - result.len = one_past_last_index - first_index; - } else { - result.len = 0; - } - } - return result; -} - -// Make arrays resize on every item -#define ARRAY_DEBUG 0 -#if ARRAY_DEBUG - #define ARRAY_IF_DEBUG_ELSE(IF, ELSE) IF -#else - #define ARRAY_IF_DEBUG_ELSE(IF, ELSE) ELSE -#endif - -template -struct Array { - Allocator allocator; - int64_t cap; - union { - Slice slice; - struct { - T *data; - int64_t len; - }; - }; - - T &operator[](int64_t index) { - Assert(index >= 0 && index < len); - return data[index]; - } - T *begin() { return data; } - T *end() { return data + len; } -}; - -template -T *GetFirst(Array &arr) { - Assert(arr.len > 0); - return arr.data; -} - -template -T *GetLast(Array &arr) { - Assert(arr.len > 0); - return arr.data + arr.len - 1; -} - -template -void Reserve(Array *arr, int64_t size) { - if (size > arr->cap) { - if (!arr->allocator.proc) arr->allocator = GetSystemAllocator(); - - T *new_data = AllocArray(arr->allocator, T, size); - Assert(new_data); - memcpy(new_data, arr->data, arr->len * sizeof(T)); - Dealloc(arr->allocator, &arr->data); - - arr->data = new_data; - arr->cap = size; - } -} - -template -void TryGrowing(Array *arr) { - if (arr->len + 1 > arr->cap) { - int64_t initial_size = (int64_t)ARRAY_IF_DEBUG_ELSE(1, 16); - int64_t new_size = ClampBottom(initial_size, arr->cap ARRAY_IF_DEBUG_ELSE(+1, *2)); - Reserve(arr, new_size); - } -} - -template -void TryGrowing(Array *arr, int64_t item_count) { - if (arr->len + item_count > arr->cap) { - int64_t initial_size = (int64_t)ARRAY_IF_DEBUG_ELSE(1, 16); - int64_t new_size = ClampBottom(initial_size, (arr->cap + item_count) ARRAY_IF_DEBUG_ELSE(+1, *2)); - Reserve(arr, new_size); - } -} - -template -void Add(Array *arr, T item) { - TryGrowing(arr); - arr->data[arr->len++] = item; -} - -template -void Add(Array *arr, Array &another) { - For(another) Add(arr, it); -} - -template -void Add(Array *arr, T *items, int64_t item_count) { - for (int64_t i = 0; i < item_count; i += 1) Add(arr, items[i]); -} - -template -void BoundedAdd(Array *arr, T item) { - if (arr->len + 1 <= arr->cap) arr->data[arr->len++] = item; -} - -template -void BoundedAddError(Array *arr, T item) { - Assert(arr->len + 1 <= arr->cap); - if (arr->len + 1 <= arr->cap) arr->data[arr->len++] = item; -} - -template -void Insert(Array *arr, T item, int64_t index) { - if (index == arr->len) { - Add(arr, item); - return; - } - - Assert(index < arr->len); - Assert(index >= 0); - TryGrowing(arr); - int64_t right_len = arr->len - index; - memmove(arr->data + index + 1, arr->data + index, sizeof(T) * right_len); - arr->data[index] = item; - arr->len += 1; -} - -template -Array Copy(Allocator alo, Array array) { - Array result = {alo}; - Reserve(&result, array.cap); - memcpy(result.data, array.data, sizeof(T) * array.len); - result.len = array.len; - return result; -} - -template -Array TightCopy(Allocator alo, Array array) { - Array result = {alo}; - Reserve(&result, array.len); - memcpy(result.data, array.data, sizeof(T) * array.len); - result.len = array.len; - return result; -} - -template -T Pop(Array *arr) { - Assert(arr->len > 0); - return arr->data[--arr->len]; -} - -template -bool Contains(Array &arr, T item) { - For(arr) if (it == item) return true; - return false; -} - -template -int64_t GetIndex(Array &arr, const T &item) { - ptrdiff_t index = (ptrdiff_t)(&item - arr.data); - Assert(index >= 0 && index < arr.len); - return (int64_t)index; -} - -template -void RemoveByIndex(Array *arr, int64_t index) { - Assert(index >= 0 && index < arr->len); - int64_t right_len = arr->len - index - 1; - memmove(arr->data + index, arr->data + index + 1, right_len * sizeof(T)); - arr->len -= 1; -} - -template -void RemoveManyByIndex(Array *arr, int64_t index, int64_t count) { - if (count == 0) return; - Assert(index >= 0 && index < arr->len); - Assert((index + count) > 0 && (index + count) <= arr->len); - int64_t right_len = arr->len - index - count; - memmove(arr->data + index, arr->data + index + count, right_len * sizeof(T)); - arr->len -= count; -} - -template -void RemoveMany(Array *arr, T &item, int64_t count) { - Assert(arr->len > 0); - Assert(&item >= arr->begin() && &item < arr->end()); - int64_t index = GetIndex(*arr, item); - RemoveManyByIndex(arr, index, count); -} - -template -void Remove(Array *arr, T &item) { - Assert(arr->len > 0); - Assert(&item >= arr->begin() && &item < arr->end()); - int64_t index = GetIndex(*arr, item); - RemoveByIndex(arr, index); -} - -template -void UnorderedRemove(Array *arr, T &item) { - Assert(arr->len > 0); - Assert((&item >= arr->begin()) && (&item < arr->end())); - item = arr->data[--arr->len]; -} - -template -void UnorderedRemoveByIndex(Array *arr, int64_t index) { - Assert(arr->len > 0); - Assert(index >= 0 && index < arr->len); - arr->data[index] = arr->data[--arr->len]; -} - -template -void InsertArray(Array *arr, T *items, int64_t count, int64_t index) { - if (index == arr->len) { - Add(arr, items, count); - return; - } - Assert(index < arr->len); - - TryGrowing(arr, count); - T *gap_begin = arr->data + index; - T *gap_end = gap_begin + count; - int64_t item_count = arr->len - index; - memmove(gap_end, gap_begin, item_count * sizeof(T)); - for (int64_t i = 0; i < count; i += 1) arr->data[index + i] = items[i]; - arr->len += count; -} - -template -T Get(Array &arr, int64_t i, T default_value = {}) { - T result = default_value; - if (i >= 0 && i < arr.len) result = arr[i]; - return result; -} - -template -void Dealloc(Array *arr) { - if (arr->data) Dealloc(arr->allocator, &arr->data); - arr->len = arr->cap = 0; -} - -template -bool IsLast(Array &arr, T &item) { - bool result = GetLast(arr) == &item; - return result; -} - -template -bool IsFirst(Array &arr, T &item) { - bool result = GetFirst(arr) == &item; - return result; -} - -template -Slice Chop(Array &arr, int64_t len) { - len = ClampTop(len, arr.len); - Slice result = {arr.data, arr.len - len}; - return result; -} - -template -Slice Skip(Array &arr, int64_t len) { - len = ClampTop(len, arr.len); - Slice result = {arr.data + len, arr.len - len}; - return result; -} - -template -Slice GetPostfix(Array &arr, int64_t len) { - len = ClampTop(len, arr.len); - int64_t remain_len = arr.len - len; - Slice result = {arr.data + remain_len, len}; - return result; -} - -template -Slice GetPrefix(Array &arr, int64_t len) { - len = ClampTop(len, arr.len); - Slice result = {arr.data, len}; - return result; -} - -template -Slice GetSlice(Array &arr, 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 = arr.len; - if (one_past_last_index < 0) one_past_last_index = arr.len + one_past_last_index; - - if (first_index == SLICE_LAST) first_index = arr.len; - if (first_index < 0) first_index = arr.len + first_index; - - Slice result = {arr.data, arr.len}; - if (arr.len > 0) { - if (one_past_last_index > first_index) { - first_index = ClampTop(first_index, arr.len - 1); - one_past_last_index = ClampTop(one_past_last_index, arr.len); - result.data += first_index; - result.len = one_past_last_index - first_index; - } else { - result.len = 0; - } - } - return result; -} - -template -struct ReverseIter { - T *data; - Slice *arr; - - ReverseIter operator++(int) { - ReverseIter ret = *this; - data -= 1; - return ret; - } - ReverseIter &operator++() { - data -= 1; - return *this; - } - - T &operator*() { return data[0]; } - T *operator->() { return data; } - - friend bool operator==(const ReverseIter &a, const ReverseIter &b) { return a.data == b.data; }; - friend bool operator!=(const ReverseIter &a, const ReverseIter &b) { return a.data != b.data; }; - - ReverseIter begin() { return ReverseIter{arr->end() - 1, arr}; } - ReverseIter end() { return ReverseIter{arr->begin() - 1, arr}; } -}; - -template -ReverseIter IterateInReverse(Array *arr) { - return {arr->end() - 1, &arr->slice}; -} -template -ReverseIter IterateInReverse(Slice *slice) { - return {slice->end() - 1, slice}; -} - -struct UTF32Result { - uint32_t out_str; - int64_t advance; - int64_t error; -}; - -struct UTF8Result { - uint8_t out_str[4]; - int64_t len; - int64_t error; -}; - -struct UTF16Result { - uint16_t out_str[2]; - int64_t len; - int64_t error; -}; - -struct UTF8Iter { - char *data; - int64_t len; - int64_t utf8_codepoint_byte_size; - int64_t 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, int64_t 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, int64_t len); -UTF8Iter IterateUTF8(char *data); -UTF8Iter IterateUTF8(String string); - -bool IsAlphabetic(char a); -#define FmtString(string) (int)(string).len, (string).data -bool AreEqual(String a, String b, unsigned ignore_case = false); -int64_t CreateWidecharFromChar(char16_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); } - -#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, ...); -String FormatV(Allocator allocator, const char *data, va_list args1); -String ToString(Allocator allocator, String16 string); -String ToString(Allocator allocator, char16_t *string, int64_t len); -String ToString(Allocator allocator, char16_t *wstring); -String16 ToString16(Allocator allocator, String string); -char16_t *ToWidechar(Allocator allocator, String string); -void NormalizePathInPlace(String s); -String CutLastSlash(String *s); -String ChopLastSlash(String s); -String ChopLastPeriod(String s); -String SkipToLastSlash(String s); -String SkipToLastPeriod(String s); -String Copy(Allocator allocator, String string); -Int GetSize(Array array); -Array Split(Allocator allocator, String string, String delimiter); -/* - 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 (size_t 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, size_t size); -bool VDecommit(void *p, size_t size); - -void InitArena(Arena *arena, size_t reserve = MiB(256)); -Arena *AllocArena(size_t reserve = MiB(256)); -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(Arena *conflict) { this->checkpoint = GetScratch(conflict); } - Scratch(Arena *c1, Arena *c2) { this->checkpoint = GetScratch(c1, c2); } - Scratch(Allocator conflict) { this->checkpoint = GetScratch((Arena *)conflict.object); } - Scratch(Allocator c1, Allocator c2) { this->checkpoint = GetScratch((Arena *)c1.object, (Arena *)c2.object); } - ~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); -}; - -struct RandomSeed { - uint64_t a; -}; - -inline uint64_t GetRandomU64(RandomSeed *state) { - uint64_t x = state->a; - x ^= x << 13; - x ^= x >> 7; - x ^= x << 17; - return state->a = x; -} - -// -// Implementation -// -#ifdef BASIC_IMPL - -UTF32Result UTF16ToUTF32(uint16_t *c, int64_t 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(uint8_t *c, int64_t 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, I) \ - { \ - if (outlen < buffer_size - 1) buffer[outlen++] = (question_mark); \ - i += I; \ - } - -int64_t CreateCharFromWidechar(char *buffer, int64_t buffer_size, char16_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), (int64_t)(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('?', 0); - } else UTF__HANDLE_DECODE_ERROR('?', 1); - } - - buffer[outlen] = 0; - return outlen; -} - -int64_t CreateWidecharFromChar(char16_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((uint8_t *)(in + i), (int64_t)(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, 0); - } else UTF__HANDLE_DECODE_ERROR(0x003f, 1); - } - - 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((uint8_t *)(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, int64_t len) { - UTF8Iter result; - MemoryZero(&result, sizeof(result)); - result.data = data; - result.len = len; - if (len) Advance(&result); - return result; -} - -UTF8Iter IterateUTF8(char *data) { - int64_t length = 0; - while (data[length]) length += 1; - return IterateUTF8Ex(data, length); -} - -UTF8Iter IterateUTF8(String string) { - return IterateUTF8Ex(string.data, string.len); -} - -bool IsUTF8ContinuationByte(char c) { - char result = (c & 0b11000000) == 0b10000000; - return result; -} - -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 = GetPostfix(a, 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 = GetPrefix(a, 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 = GetSlice(string, 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 = GetPrefix(string, 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 = GetSlice(string, 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 = GetSlice(string, 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}; - Add(&result, before_match); - string = Skip(string, index + delimiter.len); - } - Add(&result, string); - return result; -} - -String CutLastSlash(String *s) { - String result = *s; - Seek(*s, "/", &s->len, SeekFlag_MatchFindLast); - result = Skip(result, s->len); - 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 = Skip(result, pos + 1); - } - return result; -} - -String SkipToLastPeriod(String s) { - int64_t pos; - String result = s; - if (Seek(s, ".", &pos, SeekFlag_MatchFindLast)) { - result = Skip(result, pos + 1); - } - return result; -} - -String CutPrefix(String *string, int64_t len) { - String result = GetPrefix(*string, len); - *string = Skip(*string, len); - return result; -} - -String CutPostfix(String *string, int64_t len) { - String result = GetPostfix(*string, len); - *string = Chop(*string, len); - return result; -} - -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 (!IsLast(list, 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; -} - -Int GetSize(Array array) { - Int result = 0; - For (array) result += it.len; - 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, ...) { - STRING_FORMAT(allocator, data, result); - return result; -} - -String16 ToString16(Allocator allocator, String string) { - Assert(sizeof(char16_t) == 2); - char16_t *buffer = (char16_t *)AllocSize(allocator, sizeof(char16_t) * (string.len + 1)); - int64_t size = CreateWidecharFromChar(buffer, string.len + 1, string.data, string.len); - String16 result = {buffer, size}; - return result; -} - -char16_t *ToWidechar(Allocator allocator, String string) { - String16 result = ToString16(allocator, string); - return result.data; -} - -String ToString(Allocator allocator, String16 string) { - Assert(sizeof(char16_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, char16_t *string, int64_t len) { - return ToString(allocator, {string, len}); -} - -String ToString(Allocator allocator, char16_t *wstring) { - int64_t size = WideLength(wstring); - String result = ToString(allocator, {wstring, size}); - return result; -} - - #if defined(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 - -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, reserve); - 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 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) { - MA_ASAN_UNPOISON_MEMORY_REGION(arena->data + arena->commit, to_commit_clamped); - 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; - MA_ASAN_UNPOISON_MEMORY_REGION(result, size); - 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, arena->reserve); - if (zero_memory) MemoryZero(arena, sizeof(Arena)); -} - -void PopToPos(Arena *arena, size_t pos) { - // 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); - - pos = Clamp(pos, arena->base_len, arena->len); - size_t size = arena->len - pos; - arena->len = pos; - MA_ASAN_POISON_MEMORY_REGION(arena->data + arena->len, size); -} - -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]; - -#if OS_WASM -void InitScratch() { - Allocator sys_allocator = GetSystemAllocator(); - ScratchArenaPool[0] = AllocArena(sys_allocator, MiB(16)); - ScratchArenaPool[1] = AllocArena(sys_allocator, MiB(8)); - ScratchArenaPool[3] = AllocArena(sys_allocator, MiB(2)); - ScratchArenaPool[3] = AllocArena(sys_allocator, MiB(1)); -} -#else -void InitScratch() { - for (int i = 0; i < Lengthof(ScratchArenaPool); i += 1) { - ScratchArenaPool[i] = AllocArena(); - } -} -#endif - -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 +#include "basic_head.h" +#include "basic_alloc.h" +#include "basic_array.h" +#include "basic_string.h" +#include "basic_table.h" +#include "basic_string16.h" +#include "basic_unicode.h" +#include "basic_os.h" +#include "basic_list.h" +#include "basic_math.h" diff --git a/src/basic/basic_alloc.cpp b/src/basic/basic_alloc.cpp new file mode 100644 index 0000000..19b2c14 --- /dev/null +++ b/src/basic/basic_alloc.cpp @@ -0,0 +1,248 @@ +#if defined(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 + +#if OS_WINDOWS + +#ifndef NOMINMAX + #define NOMINMAX +#endif +#ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN +#endif +#include + +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, size_t size) { + 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; +} + +#elif OS_LINUX || OS_MAC + +void *VReserve(size_t size) { + void *result = mmap(0, size, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, (off_t)0); + return result == (void *)-1 ? 0 : result; +} + +bool VCommit(void *p, size_t size) { + int result = mprotect(p, size, PROT_READ | PROT_WRITE); + return result == 0; +} + +bool VRelease(void *p, size_t size) { + int result = munmap(p, size); + return result == 0; +} + +bool VDecommit(void *p, size_t size) { + mprotect(p, size, PROT_NONE); + madvise(p, size, MADV_DONTNEED); + return true; +} + +#else + +void *VReserve(size_t size) { + InvalidCodepath(); + return NULL; +} + +bool VCommit(void *p, size_t size) { + InvalidCodepath(); + return false; +} + +bool VRelease(void *p, size_t size) { + InvalidCodepath(); + return false; +} + +bool VDecommit(void *p, size_t size) { + InvalidCodepath(); + return false; +} + +#endif + +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, reserve); + 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 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) { + MA_ASAN_UNPOISON_MEMORY_REGION(arena->data + arena->commit, to_commit_clamped); + 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; + MA_ASAN_UNPOISON_MEMORY_REGION(result, size); + 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, arena->reserve); + if (zero_memory) MemoryZero(arena, sizeof(Arena)); +} + +void PopToPos(Arena *arena, size_t pos) { + // 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); + + pos = Clamp(pos, arena->base_len, arena->len); + size_t size = arena->len - pos; + arena->len = pos; + MA_ASAN_POISON_MEMORY_REGION(arena->data + arena->len, size); +} + +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]; + +#if OS_WASM +void InitScratch() { + Allocator sys_allocator = GetSystemAllocator(); + ScratchArenaPool[0] = AllocArena(sys_allocator, MiB(16)); + ScratchArenaPool[1] = AllocArena(sys_allocator, MiB(8)); + ScratchArenaPool[3] = AllocArena(sys_allocator, MiB(2)); + ScratchArenaPool[3] = AllocArena(sys_allocator, MiB(1)); +} +#else +void InitScratch() { + for (int i = 0; i < Lengthof(ScratchArenaPool); i += 1) { + ScratchArenaPool[i] = AllocArena(); + } +} +#endif + +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; +} \ No newline at end of file diff --git a/src/basic/basic_alloc.h b/src/basic/basic_alloc.h new file mode 100644 index 0000000..0ba11eb --- /dev/null +++ b/src/basic/basic_alloc.h @@ -0,0 +1,95 @@ +#pragma once + +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; +}; + +#define AllocType(alo, Type) (Type *)AllocSize(alo, sizeof(Type)) +#define AllocArray(alo, Type, count) (Type *)AllocSize(alo, sizeof(Type) * (count)) +inline void *AllocSize(Allocator alo, size_t size) { + void *result = alo.proc(alo.object, AllocatorKind_Allocate, NULL, size); + memset(result, 0, size); + return result; +} + +template +void Dealloc(Allocator alo, T **p) { + if (*p == NULL) return; + alo.proc(alo.object, AllocatorKind_Deallocate, *p, 0); + *p = NULL; +} +#define DeallocEx(alo, p) (alo).proc((alo).object, AllocatorKind_Deallocate, (p), 0); + +Allocator GetSystemAllocator(); +#define MemoryZero(x, size) memset(x, 0, size) +#define MemoryCopy(dst, src, size) memcpy(dst, src, size) +#define MemoryMove(dst, src, size) memmove(dst, src, size) + +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, size_t size); +bool VDecommit(void *p, size_t size); + +void InitArena(Arena *arena, size_t reserve = MiB(256)); +Arena *AllocArena(size_t reserve = MiB(256)); +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(Arena *conflict) { this->checkpoint = GetScratch(conflict); } + Scratch(Arena *c1, Arena *c2) { this->checkpoint = GetScratch(c1, c2); } + Scratch(Allocator conflict) { this->checkpoint = GetScratch((Arena *)conflict.object); } + Scratch(Allocator c1, Allocator c2) { this->checkpoint = GetScratch((Arena *)c1.object, (Arena *)c2.object); } + ~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); +}; \ No newline at end of file diff --git a/src/basic/basic_array.h b/src/basic/basic_array.h new file mode 100644 index 0000000..5842d05 --- /dev/null +++ b/src/basic/basic_array.h @@ -0,0 +1,497 @@ +#pragma once +/* +// 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; + } + } +} + +// Simple delete +IterRemove(arr) { + IterRemovePrepare(arr); + + remove_item = true; +} + +// Deleting backwards +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) { \ + Remove(&(a), it); \ + i -= 1; \ + } \ + } +#define ForItem(it, array) for (auto &it : (array)) +#define For(array) ForItem(it, array) + + + +template +struct Slice { + T *data; + int64_t len; + + Slice() = default; + Slice(T *s, int64_t l) : data(s), len(l) {} + + T &operator[](int64_t index) { + Assert(index < len); + return data[index]; + } + T *begin() { return data; } + T *end() { return data + len; } +}; + +template +Slice Copy(Allocator alo, Slice array) { + Slice result = {}; + result.data = AllocArray(alo, T, array.len); + memcpy(result.data, array.data, sizeof(T) * array.len); + result.len = array.len; + return result; +} + +template +T Pop(Slice *arr) { + Assert(arr->len > 0); + return arr->data[--arr->len]; +} + +template +bool Contains(Slice &arr, T item) { + For(arr) if (it == item) return true; + return false; +} + +template +int64_t GetIndex(Slice &arr, const T &item) { + ptrdiff_t index = (ptrdiff_t)(&item - arr.data); + Assert(index >= 0 && index < arr.len); + return (int64_t)index; +} + +template +T Get(Slice &arr, int64_t i, T default_value = {}) { + T result = default_value; + if (i >= 0 && i < arr.len) result = arr[i]; + return result; +} + +template +bool IsLast(Slice &arr, T &item) { + bool result = arr.last() == &item; + return result; +} + +template +bool IsFirst(Slice &arr, T &item) { + bool result = arr.first() == &item; + return result; +} + +template +T *GetFirst(Slice &arr) { + Assert(arr.len > 0); + return arr.data; +} + +template +T *GetLast(Slice &arr) { + Assert(arr.len > 0); + return arr.data + arr.len - 1; +} + +template +Slice Chop(Slice &arr, int64_t len) { + len = ClampTop(len, arr.len); + Slice result = {arr.data, arr.len - len}; + return result; +} + +template +Slice Skip(Slice &arr, int64_t len) { + len = ClampTop(len, arr.len); + Slice result = {arr.data + len, arr.len - len}; + return result; +} + +template +Slice GetPostfix(Slice &arr, int64_t len) { + len = ClampTop(len, arr.len); + int64_t remain_len = arr.len - len; + Slice result = {arr.data + remain_len, len}; + return result; +} + +template +Slice GetPrefix(Slice &arr, int64_t len) { + len = ClampTop(len, arr.len); + Slice result = {arr.data, len}; + return result; +} + +template +Slice GetSlice(Slice &arr, 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 = arr.len; + if (one_past_last_index < 0) one_past_last_index = arr.len + one_past_last_index; + + if (first_index == SLICE_LAST) first_index = arr.len; + if (first_index < 0) first_index = arr.len + first_index; + + Slice result = {arr.data, arr.len}; + if (arr.len > 0) { + if (one_past_last_index > first_index) { + first_index = ClampTop(first_index, arr.len - 1); + one_past_last_index = ClampTop(one_past_last_index, arr.len); + result.data += first_index; + result.len = one_past_last_index - first_index; + } else { + result.len = 0; + } + } + return result; +} + +// Make arrays resize on every item +#define ARRAY_DEBUG 0 +#if ARRAY_DEBUG + #define ARRAY_IF_DEBUG_ELSE(IF, ELSE) IF +#else + #define ARRAY_IF_DEBUG_ELSE(IF, ELSE) ELSE +#endif + +template +struct Array { + Allocator allocator; + int64_t cap; + union { + Slice slice; + struct { + T *data; + int64_t len; + }; + }; + + T &operator[](int64_t index) { + Assert(index >= 0 && index < len); + return data[index]; + } + T *begin() { return data; } + T *end() { return data + len; } +}; + +template +T *GetFirst(Array &arr) { + Assert(arr.len > 0); + return arr.data; +} + +template +T *GetLast(Array &arr) { + Assert(arr.len > 0); + return arr.data + arr.len - 1; +} + +template +void Reserve(Array *arr, int64_t size) { + if (size > arr->cap) { + if (!arr->allocator.proc) arr->allocator = GetSystemAllocator(); + + T *new_data = AllocArray(arr->allocator, T, size); + Assert(new_data); + memcpy(new_data, arr->data, arr->len * sizeof(T)); + Dealloc(arr->allocator, &arr->data); + + arr->data = new_data; + arr->cap = size; + } +} + +template +void TryGrowing(Array *arr) { + if (arr->len + 1 > arr->cap) { + int64_t initial_size = (int64_t)ARRAY_IF_DEBUG_ELSE(1, 16); + int64_t new_size = ClampBottom(initial_size, arr->cap ARRAY_IF_DEBUG_ELSE(+1, *2)); + Reserve(arr, new_size); + } +} + +template +void TryGrowing(Array *arr, int64_t item_count) { + if (arr->len + item_count > arr->cap) { + int64_t initial_size = (int64_t)ARRAY_IF_DEBUG_ELSE(1, 16); + int64_t new_size = ClampBottom(initial_size, (arr->cap + item_count) ARRAY_IF_DEBUG_ELSE(+1, *2)); + Reserve(arr, new_size); + } +} + +template +void Add(Array *arr, T item) { + TryGrowing(arr); + arr->data[arr->len++] = item; +} + +template +void Add(Array *arr, Array &another) { + For(another) Add(arr, it); +} + +template +void Add(Array *arr, T *items, int64_t item_count) { + for (int64_t i = 0; i < item_count; i += 1) Add(arr, items[i]); +} + +template +void BoundedAdd(Array *arr, T item) { + if (arr->len + 1 <= arr->cap) arr->data[arr->len++] = item; +} + +template +void BoundedAddError(Array *arr, T item) { + Assert(arr->len + 1 <= arr->cap); + if (arr->len + 1 <= arr->cap) arr->data[arr->len++] = item; +} + +template +void Insert(Array *arr, T item, int64_t index) { + if (index == arr->len) { + Add(arr, item); + return; + } + + Assert(index < arr->len); + Assert(index >= 0); + TryGrowing(arr); + int64_t right_len = arr->len - index; + memmove(arr->data + index + 1, arr->data + index, sizeof(T) * right_len); + arr->data[index] = item; + arr->len += 1; +} + +template +Array Copy(Allocator alo, Array array) { + Array result = {alo}; + Reserve(&result, array.cap); + memcpy(result.data, array.data, sizeof(T) * array.len); + result.len = array.len; + return result; +} + +template +Array TightCopy(Allocator alo, Array array) { + Array result = {alo}; + Reserve(&result, array.len); + memcpy(result.data, array.data, sizeof(T) * array.len); + result.len = array.len; + return result; +} + +template +T Pop(Array *arr) { + Assert(arr->len > 0); + return arr->data[--arr->len]; +} + +template +bool Contains(Array &arr, T item) { + For(arr) if (it == item) return true; + return false; +} + +template +int64_t GetIndex(Array &arr, const T &item) { + ptrdiff_t index = (ptrdiff_t)(&item - arr.data); + Assert(index >= 0 && index < arr.len); + return (int64_t)index; +} + +template +void RemoveByIndex(Array *arr, int64_t index) { + Assert(index >= 0 && index < arr->len); + int64_t right_len = arr->len - index - 1; + memmove(arr->data + index, arr->data + index + 1, right_len * sizeof(T)); + arr->len -= 1; +} + +template +void RemoveManyByIndex(Array *arr, int64_t index, int64_t count) { + if (count == 0) return; + Assert(index >= 0 && index < arr->len); + Assert((index + count) > 0 && (index + count) <= arr->len); + int64_t right_len = arr->len - index - count; + memmove(arr->data + index, arr->data + index + count, right_len * sizeof(T)); + arr->len -= count; +} + +template +void RemoveMany(Array *arr, T &item, int64_t count) { + Assert(arr->len > 0); + Assert(&item >= arr->begin() && &item < arr->end()); + int64_t index = GetIndex(*arr, item); + RemoveManyByIndex(arr, index, count); +} + +template +void Remove(Array *arr, T &item) { + Assert(arr->len > 0); + Assert(&item >= arr->begin() && &item < arr->end()); + int64_t index = GetIndex(*arr, item); + RemoveByIndex(arr, index); +} + +template +void UnorderedRemove(Array *arr, T &item) { + Assert(arr->len > 0); + Assert((&item >= arr->begin()) && (&item < arr->end())); + item = arr->data[--arr->len]; +} + +template +void UnorderedRemoveByIndex(Array *arr, int64_t index) { + Assert(arr->len > 0); + Assert(index >= 0 && index < arr->len); + arr->data[index] = arr->data[--arr->len]; +} + +template +void InsertArray(Array *arr, T *items, int64_t count, int64_t index) { + if (index == arr->len) { + Add(arr, items, count); + return; + } + Assert(index < arr->len); + + TryGrowing(arr, count); + T *gap_begin = arr->data + index; + T *gap_end = gap_begin + count; + int64_t item_count = arr->len - index; + memmove(gap_end, gap_begin, item_count * sizeof(T)); + for (int64_t i = 0; i < count; i += 1) arr->data[index + i] = items[i]; + arr->len += count; +} + +template +T Get(Array &arr, int64_t i, T default_value = {}) { + T result = default_value; + if (i >= 0 && i < arr.len) result = arr[i]; + return result; +} + +template +void Dealloc(Array *arr) { + if (arr->data) Dealloc(arr->allocator, &arr->data); + arr->len = arr->cap = 0; +} + +template +bool IsLast(Array &arr, T &item) { + bool result = GetLast(arr) == &item; + return result; +} + +template +bool IsFirst(Array &arr, T &item) { + bool result = GetFirst(arr) == &item; + return result; +} + +template +Slice Chop(Array &arr, int64_t len) { + len = ClampTop(len, arr.len); + Slice result = {arr.data, arr.len - len}; + return result; +} + +template +Slice Skip(Array &arr, int64_t len) { + len = ClampTop(len, arr.len); + Slice result = {arr.data + len, arr.len - len}; + return result; +} + +template +Slice GetPostfix(Array &arr, int64_t len) { + len = ClampTop(len, arr.len); + int64_t remain_len = arr.len - len; + Slice result = {arr.data + remain_len, len}; + return result; +} + +template +Slice GetPrefix(Array &arr, int64_t len) { + len = ClampTop(len, arr.len); + Slice result = {arr.data, len}; + return result; +} + +template +Slice GetSlice(Array &arr, 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 = arr.len; + if (one_past_last_index < 0) one_past_last_index = arr.len + one_past_last_index; + + if (first_index == SLICE_LAST) first_index = arr.len; + if (first_index < 0) first_index = arr.len + first_index; + + Slice result = {arr.data, arr.len}; + if (arr.len > 0) { + if (one_past_last_index > first_index) { + first_index = ClampTop(first_index, arr.len - 1); + one_past_last_index = ClampTop(one_past_last_index, arr.len); + result.data += first_index; + result.len = one_past_last_index - first_index; + } else { + result.len = 0; + } + } + return result; +} + +template +struct ReverseIter { + T *data; + Slice *arr; + + ReverseIter operator++(int) { + ReverseIter ret = *this; + data -= 1; + return ret; + } + ReverseIter &operator++() { + data -= 1; + return *this; + } + + T &operator*() { return data[0]; } + T *operator->() { return data; } + + friend bool operator==(const ReverseIter &a, const ReverseIter &b) { return a.data == b.data; }; + friend bool operator!=(const ReverseIter &a, const ReverseIter &b) { return a.data != b.data; }; + + ReverseIter begin() { return ReverseIter{arr->end() - 1, arr}; } + ReverseIter end() { return ReverseIter{arr->begin() - 1, arr}; } +}; + +template +ReverseIter IterateInReverse(Array *arr) { + return {arr->end() - 1, &arr->slice}; +} +template +ReverseIter IterateInReverse(Slice *slice) { + return {slice->end() - 1, slice}; +} + diff --git a/src/basic/basic_head.h b/src/basic/basic_head.h new file mode 100644 index 0000000..866e2ca --- /dev/null +++ b/src/basic/basic_head.h @@ -0,0 +1,215 @@ +#pragma once +#ifndef _CRT_SECURE_NO_WARNINGS + #define _CRT_SECURE_NO_WARNINGS +#endif +#include +#include +#include +#include +#include + + +#if defined(__APPLE__) && defined(__MACH__) + #define OS_POSIX 1 + #define OS_MAC 1 +#elif defined(_WIN32) + #define OS_WINDOWS 1 +#elif defined(__linux__) + #define OS_POSIX 1 + #define OS_LINUX 1 +#elif defined(__EMSCRIPTEN__) + #define OS_WASM 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_WASM + #define OS_WASM 0 +#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 + +#if OS_WINDOWS +#define BREAK() __debugbreak() +#elif OS_LINUX +#define BREAK() raise(SIGTRAP) +#elif OS_WASM +#include +EM_JS(void, JS_Breakpoint, (), { + debugger; +}) +#define BREAK() JS_Breakpoint() +#endif + +#define API +#define Assert(x) \ + if (!(x)) { \ + BREAK(); \ + } +#define InvalidCodepath() Assert(!"invalid codepath") +#define ElseInvalidCodepath() else {InvalidCodepath()} + +#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])))) +#define SLICE_LAST INT64_MAX + +using U8 = uint8_t; +using U16 = uint16_t; +using U32 = uint32_t; +using U64 = uint64_t; +using S8 = int8_t; +using S16 = int16_t; +using S32 = int32_t; +using S64 = int64_t; +using Int = S64; +using UInt = U64; + +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 < min) return min; + if (value > max) return max; + return value; +} + +template +void Swap(T *a, T *b) { + T temp = *a; + *a = *b; + *b = temp; +} + +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; +} + +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() + [&]() + +struct RandomSeed { + uint64_t a; +}; + +inline uint64_t GetRandomU64(RandomSeed *state) { + uint64_t x = state->a; + x ^= x << 13; + x ^= x >> 7; + x ^= x << 17; + return state->a = x; +} \ No newline at end of file diff --git a/src/basic/linked_list.h b/src/basic/basic_list.h similarity index 99% rename from src/basic/linked_list.h rename to src/basic/basic_list.h index 7ae9625..e9ce49e 100644 --- a/src/basic/linked_list.h +++ b/src/basic/basic_list.h @@ -1,5 +1,4 @@ -#ifndef FIRST_LL_HEADER -#define FIRST_LL_HEADER +#pragma once #define SLL_QUEUE_ADD_MOD(f, l, n, next) \ do { \ (n)->next = 0; \ @@ -116,4 +115,3 @@ } 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/basic/basic_math.h b/src/basic/basic_math.h new file mode 100644 index 0000000..640aa6b --- /dev/null +++ b/src/basic/basic_math.h @@ -0,0 +1,59 @@ +#pragma once + +struct Vec2 { + float x; + float y; +}; + +union Vec4 { + struct { + float x; + float y; + float z; + float w; + }; + struct { + float r; + float g; + float b; + float a; + }; +}; + +union Vec3 { + struct { + float x; + float y; + float z; + }; + struct { + float r; + float g; + float b; + }; +}; + +struct Rect2 { + Vec2 min; + Vec2 max; +}; + +struct Vec2I { + Int x; + Int y; +}; + +struct Rect2I { + Vec2I min; + Vec2I max; +}; + +union Color { + struct { + uint8_t r; + uint8_t g; + uint8_t b; + uint8_t a; + }; + uint32_t value; +}; \ No newline at end of file diff --git a/src/basic/basic_os.cpp b/src/basic/basic_os.cpp new file mode 100644 index 0000000..5bbff9b --- /dev/null +++ b/src/basic/basic_os.cpp @@ -0,0 +1,1101 @@ +#if OS_POSIX +#include "filesystem.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +API void (*Error)(const char *, ...); + +API void InitOS(void (*error_proc)(const char *, ...)) { + Error = error_proc; +} + +API String ReadFile(Allocator al, String path) { + Scratch scratch(al); + String null_term = Copy(scratch, path); + String result = {}; + FILE *f = fopen(null_term.data, "rb"); + if (f) { + fseek(f, 0, SEEK_END); + result.len = ftell(f); + fseek(f, 0, SEEK_SET); + + result.data = (char *)AllocSize(al, result.len + 1); + fread(result.data, result.len, 1, f); + result.data[result.len] = 0; + + fclose(f); + } + return result; +} + +API bool WriteFile(String path, String data) { + Scratch scratch; + String null_term = Copy(scratch, path); + bool result = false; + FILE *f = fopen((const char *)null_term.data, "w"); + if (f) { + size_t written = fwrite(data.data, 1, data.len, f); + if (written == data.len) { + result = true; + } + fclose(f); + } + return result; +} + +API bool DeleteFile(String path) { + Scratch scratch; + String null_term = Copy(scratch, path); + int result = unlink(null_term.data); + return result == 0; +} + +API MakeDirResult MakeDir(String path) { + Scratch scratch; + String null_term = Copy(scratch, path); + int error = mkdir(null_term.data, 0755); + MakeDirResult result = MakeDirResult_Success; + if (error != 0) { + result = MakeDirResult_ErrorOther; + if (errno == EEXIST) result = MakeDirResult_Exists; + } + return result; +} + +API int64_t GetFileModTime(String path) { + Scratch scratch; + String null_term = Copy(scratch, path); + struct stat attrib = {}; + stat(null_term.data, &attrib); + struct timespec ts = attrib.st_mtim; + int64_t result = (((int64_t)ts.tv_sec) * 1000000ll) + ((int64_t)ts.tv_nsec) / 1000ll; + return result; +} + +API String GetAbsolutePath(Allocator al, String path) { + Scratch scratch(al); + String null_term = Copy(scratch, path); + char *buffer = AllocArray(al, char, PATH_MAX); + realpath(null_term.data, buffer); + String result = buffer; + return result; +} + +API bool FileExists(String path) { + Scratch scratch; + String null_term = Copy(scratch, path); + bool result = false; + if (access((char *)null_term.data, F_OK) == 0) { + result = true; + } + return result; +} + +API bool IsDir(String path) { + Scratch scratch; + String null_term = Copy(scratch, path); + struct stat s; + if (stat(null_term.data, &s) != 0) + return false; + return S_ISDIR(s.st_mode); +} + +API bool IsFile(String path) { + Scratch scratch; + String null_term = Copy(scratch, path); + struct stat s; + if (stat(null_term.data, &s) != 0) + return false; + return S_ISREG(s.st_mode); +} + +API bool IsAbsolute(String path) { + bool result = path.len && path.data[0] == '/'; + return result; +} + +API String GetWorkingDir(Allocator al) { + char *buffer = AllocArray(al, char, PATH_MAX); + char *cwd = getcwd(buffer, PATH_MAX); + return cwd; +} + +String GetExePath(Allocator al) { + char *buffer = AllocArray(al, char, PATH_MAX); + readlink("/proc/self/exe", buffer, PATH_MAX); + return buffer; +} + +API String GetExeDir(Allocator al) { + Scratch scratch(al); + String exe_path = GetExePath(scratch); + String dir = ChopLastSlash(exe_path); + String result = Copy(al, dir); + return result; +} + +API double GetTimeMicros(void) { + struct timespec spec; + clock_gettime(CLOCK_MONOTONIC, &spec); + return (((double)spec.tv_sec) * 1000000) + (((double)spec.tv_nsec) / 1000); +} + +API bool IsValid(const FileIter &it) { + return it.is_valid; +} + +API void Advance(FileIter *it) { + struct dirent *file = NULL; + 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 = Copy(it->allocator, file->d_name); + + const char *dir_char_ending = it->is_directory ? "/" : ""; + const char *separator = it->path.data[it->path.len - 1] == '/' ? "" : "/"; + it->relative_path = Format(it->allocator, "%.*s%s%s%s", FmtString(it->path), separator, file->d_name, dir_char_ending); + it->absolute_path = GetAbsolutePath(it->allocator, it->relative_path); + if (it->is_directory) it->absolute_path = Format(it->allocator, "%.*s/", FmtString(it->absolute_path)); + it->is_valid = true; + return; + } + it->is_valid = false; + closedir((DIR *)it->dir); +} + +API FileIter IterateFiles(Allocator alo, String path) { + FileIter it = {}; + it.allocator = alo; + it.path = path; + Scratch scratch(alo); + String null_term = Copy(scratch, path); + it.dir = (void *)opendir((char *)null_term.data); + if (it.dir) { + Advance(&it); + } + return it; +} + +struct UnixProcess { + pid_t pid; + int child_stdout_read; + int stdin_write; +}; + +API Array SplitCommand(Allocator allocator, String command_line) { + Array cmd = {allocator}; + + String curr = {}; + for (int i = 0; i < command_line.len; i += 1) { + if (command_line.data[i] == ' ') { + if (curr.len > 0) { + Add(&cmd, Copy(allocator, curr).data); + curr = {}; + } + continue; + } + if (curr.len == 0) { + curr.data = command_line.data + i; + } + curr.len += 1; + } + + if (curr.len > 0) { + Add(&cmd, Copy(allocator, curr).data); + } + + return cmd; +} + +API Process SpawnProcess(String command_line, String working_dir, String write_stdin, Array enviroment) { + Scratch scratch; + const int PIPE_READ = 0; + const int PIPE_WRITE = 1; + bool error = false; + + char *buffer = AllocArray(scratch, char, 4096); + chdir(working_dir.data); + getcwd(buffer, 4096); + defer { chdir(buffer); }; + + + Process process = {}; + UnixProcess *plat = (UnixProcess *)&process.platform; + Array args = SplitCommand(scratch, command_line); + Array env = {scratch}; + + For (enviroment) { + Add(&env, Copy(scratch, it).data); + } + + int stdout_desc[2] = {}; + int stdin_desc[2] = {}; + posix_spawn_file_actions_t actions = {}; + + if (posix_spawn_file_actions_init(&actions) != 0) { + Error("Libc function failed: posix_spawn_file_actions_init, with error: %s", strerror(errno)); + return process; + } + defer { + posix_spawn_file_actions_destroy(&actions); + }; + + if (pipe(stdout_desc) == -1) { + Error("Libc function failed: pipe, with error: %s", strerror(errno)); + return process; + } + defer { + if (error) { + close(stdout_desc[PIPE_READ]); + close(stdout_desc[PIPE_WRITE]); + } else { + close(stdout_desc[PIPE_WRITE]); + } + }; + + if (pipe(stdin_desc) == -1) { + Error("Libc function failed: pipe, with error: %s", strerror(errno)); + return process; + } + defer { + if (error) { + close(stdin_desc[PIPE_READ]); + close(stdin_desc[PIPE_WRITE]); + } else { + close(stdin_desc[PIPE_READ]); + } + }; + + error = posix_spawn_file_actions_addclose(&actions, stdout_desc[PIPE_READ]) != 0; + if (error) { + Error("Libc function failed: posix_spawn_file_actions_addclose, with error: %s", strerror(errno)); + return process; + } + + error = posix_spawn_file_actions_adddup2(&actions, stdout_desc[PIPE_WRITE], STDOUT_FILENO) != 0; + if (error) { + Error("Libc function failed: posix_spawn_file_actions_adddup2 STDOUT_FILENO, with error: %s", strerror(errno)); + return process; + } + + error = posix_spawn_file_actions_adddup2(&actions, stdout_desc[PIPE_WRITE], STDERR_FILENO) != 0; + if (error) { + Error("Libc function failed: posix_spawn_file_actions_adddup2 STDERR_FILENO, with error: %s", strerror(errno)); + return process; + } + + error = posix_spawn_file_actions_addclose(&actions, stdin_desc[PIPE_WRITE]) != 0; + if (error) { + Error("Libc function failed: posix_spawn_file_actions_addclose, with error: %s", strerror(errno)); + return process; + } + + error = posix_spawn_file_actions_adddup2(&actions, stdin_desc[PIPE_READ], STDIN_FILENO) != 0; + if (error) { + Error("Libc function failed: posix_spawn_file_actions_adddup2 STDIN_FILENO, with error: %s", strerror(errno)); + return process; + } + + pid_t process_pid = 0; + error = posix_spawnp(&process_pid, args[0], &actions, NULL, args.data, env.data) != 0; + if (error) { + Error("Libc function failed: failed to create process\n, with error: %s", strerror(errno)); + return process; + } + + + plat->child_stdout_read = stdout_desc[PIPE_READ]; + plat->stdin_write = stdin_desc[PIPE_WRITE]; + plat->pid = process_pid; + + if (write_stdin.len) { + WriteStdin(&process, write_stdin); + CloseStdin(&process); + } + + process.id = process_pid; + process.is_valid = true; + return process; +} + +API bool IsValid(Process *process) { + UnixProcess *plat = (UnixProcess *)&process->platform; + if (process->is_valid == false) { + return false; + } + + int status = 0; + pollfd p = {}; + p.fd = plat->child_stdout_read; + p.events = POLLRDHUP | POLLERR | POLLHUP | POLLNVAL; + int res = poll(&p, 1, 0); + if (res > 0) { + pid_t result = waitpid(plat->pid, &status, 0); + process->exit_code = WEXITSTATUS(status); + return false; + } + + return true; +} + +API void KillProcess(Process *process) { + Assert(process->is_valid); + UnixProcess *plat = (UnixProcess *)process->platform; + kill(plat->pid, SIGKILL); + process->exit_code = -1; +} + +API String PollStdout(Allocator allocator, Process *process, bool force_read) { + Assert(process->is_valid); + UnixProcess *plat = (UnixProcess *)process->platform; + + String result = {}; + result.data = AllocArray(allocator, char, 16 * 4096); + + pollfd p = {}; + p.fd = plat->child_stdout_read; + p.events = POLLIN; + int res = poll(&p, 1, 0); + if (res > 0 || force_read) { + result.len = read(plat->child_stdout_read, result.data, 4 * 4096); + } + return result; +} + +API void WriteStdin(Process *process, String string) { + if (string.len == 0) return; + Assert(process->is_valid); + + UnixProcess *plat = (UnixProcess *)process->platform; + ssize_t size = write(plat->stdin_write, string.data, string.len); + + Assert(size == string.len); +} + +API void CloseStdin(Process *process) { + UnixProcess *plat = (UnixProcess *)process->platform; + close(plat->stdin_write); +} + +#elif OS_WINDOWS + +#ifndef NOMINMAX + #define NOMINMAX +#endif +#ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN +#endif +#include +#include +#include + +API void (*Error)(const char *, ...); + +API void InitOS(void (*error_proc)(const char *, ...)) { + Error = error_proc; + + SetConsoleOutputCP(65001); + SetConsoleCP(65001); +} + +API String ReadFile(Allocator arena, String path) { + bool success = false; + String result = {}; + + Scratch scratch(arena); + String16 string16 = ToString16(scratch, path); + HANDLE handle = CreateFileW((wchar_t *)string16.data, 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; + +API String GetAbsolutePath(Allocator arena, String relative) { + Scratch scratch(arena); + String16 wpath = ToString16(scratch, relative); + wchar_t *wpath_abs = AllocArray(scratch, wchar_t, 4096); + DWORD written = GetFullPathNameW((wchar_t *)wpath.data, 4096, wpath_abs, 0); + if (written == 0) + return {}; + String path = ToString(arena, {(char16_t *)wpath_abs, written}); + NormalizePathInPlace(path); + return path; +} + +API bool IsValid(const FileIter &it) { + return it.is_valid; +} + +API void Advance(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 = ToString(it->allocator, (char16_t *)data->cFileName, WideLength((char16_t *)data->cFileName)); + const char *is_dir = it->is_directory ? "/" : ""; + const char *separator = it->path.data[it->path.len - 1] == '/' ? "" : "/"; + it->relative_path = Format(it->allocator, "%.*s%s%.*s%s", FmtString(it->path), separator, FmtString(it->filename), is_dir); + it->absolute_path = GetAbsolutePath(it->allocator, 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); +} + +API FileIter IterateFiles(Allocator alo, String path) { + FileIter it = {0}; + it.allocator = alo; + it.path = path; + + String modified_path = Format(it.allocator, "%.*s\\*", FmtString(path)); + String16 modified_path16 = ToString16(it.allocator, modified_path); + + it.w32 = AllocType(it.allocator, Win32_FileIter); + it.w32->handle = FindFirstFileW((wchar_t *)modified_path16.data, &it.w32->data); + if (it.w32->handle == INVALID_HANDLE_VALUE) { + it.is_valid = false; + return it; + } + + Advance(&it); + return it; +} + +API double GetTimeMicros(void) { + static double invfreq; + if (!invfreq) { + LARGE_INTEGER frequency; + QueryPerformanceFrequency(&frequency); + invfreq = 1000000.0 / frequency.QuadPart; + } + LARGE_INTEGER counter; + QueryPerformanceCounter(&counter); + return counter.QuadPart * invfreq; +} + +API bool WriteFile(String path, String data) { + bool result = false; + + Scratch scratch; + String16 wpath = ToString16(scratch, path); + + DWORD access = GENERIC_WRITE; + DWORD creation_disposition = CREATE_ALWAYS; + HANDLE handle = CreateFileW((wchar_t *)wpath.data, access, 0, NULL, creation_disposition, FILE_ATTRIBUTE_NORMAL, NULL); + if (handle != INVALID_HANDLE_VALUE) { + DWORD bytes_written = 0; + Assert(data.len == (DWORD)data.len); // @Todo: can only read 32 byte size files? + BOOL error = WriteFile(handle, data.data, (DWORD)data.len, &bytes_written, NULL); + if (error == TRUE) { + if (bytes_written == data.len) { + result = true; + } + } + CloseHandle(handle); + } + + return result; +} + +API String GetExePath(Allocator allocator) { + Scratch scratch(allocator); + wchar_t *wbuffer = AllocArray(scratch, wchar_t, 4096); + DWORD wsize = GetModuleFileNameW(0, wbuffer, 4096); + Assert(wsize != 0); + + String path = ToString(allocator, (char16_t *)wbuffer, wsize); + NormalizePathInPlace(path); + return path; +} + +API String GetExeDir(Allocator allocator) { + Scratch scratch((Arena *)allocator.object); + String path = GetExePath(scratch); + path = ChopLastSlash(path); + path = Copy(allocator, path); + return path; +} + +API bool FileExists(String path) { + Scratch scratch; + String16 wbuff = ToString16(scratch, path); + DWORD attribs = GetFileAttributesW((wchar_t *)wbuff.data); + bool result = attribs == INVALID_FILE_ATTRIBUTES ? false : true; + return result; +} + +API bool IsDir(String path) { + Scratch scratch; + String16 wbuff = ToString16(scratch, path); + DWORD dwAttrib = GetFileAttributesW((wchar_t *)wbuff.data); + return dwAttrib != INVALID_FILE_ATTRIBUTES && (dwAttrib & FILE_ATTRIBUTE_DIRECTORY); +} + +API bool IsFile(String path) { + Scratch scratch; + String16 wbuff = ToString16(scratch, path); + DWORD dwAttrib = GetFileAttributesW((wchar_t *)wbuff.data); + bool is_file = (dwAttrib & FILE_ATTRIBUTE_DIRECTORY) == 0; + return dwAttrib != INVALID_FILE_ATTRIBUTES && is_file; +} + +API String GetWorkingDir(Allocator arena) { + Scratch scratch(arena); + wchar_t *wbuffer = AllocArray(scratch, wchar_t, 4096); + DWORD wsize = GetCurrentDirectoryW(4096, wbuffer); + Assert(wsize != 0); + wbuffer[wsize] = 0; + + String path = ToString(arena, (char16_t *)wbuffer, wsize); + NormalizePathInPlace(path); + return path; +} + +API bool IsAbsolute(String path) { + bool result = path.len > 3 && IsAlphabetic(path.data[0]) && path.data[1] == ':' && (path.data[2] == '/' || path.data[2] == '\\'); + return result; +} + +API bool DeleteFile(String path) { + Scratch scratch; + String16 wpath = ToString16(scratch, path); + + BOOL success = DeleteFileW((wchar_t *)wpath.data); + bool result = true; + if (success == 0) result = false; + return result; +} + +API int64_t GetFileModTime(String file) { + Scratch scratch; + String16 string16 = ToString16(scratch, file); + WIN32_FIND_DATAW data; + HANDLE handle = FindFirstFileW((wchar_t *)string16.data, &data); + if (handle != INVALID_HANDLE_VALUE) { + FindClose(handle); + FILETIME time = data.ftLastWriteTime; + int64_t result = (int64_t)time.dwHighDateTime << 32 | time.dwLowDateTime; + return result; + } else { + return -1; + } +} + +API MakeDirResult MakeDir(String path) { + Scratch scratch; + MakeDirResult result = MakeDirResult_Success; + String16 string16 = ToString16(scratch, path); + BOOL success = CreateDirectoryW((wchar_t *)string16.data, NULL); + if (success == 0) { + DWORD error = GetLastError(); + if (error == ERROR_ALREADY_EXISTS) { + result = MakeDirResult_Exists; + } else if (error == ERROR_PATH_NOT_FOUND) { + result = MakeDirResult_NotFound; + } else { + result = MakeDirResult_ErrorOther; + } + } + return result; +} + +struct Win32Process { + HANDLE handle; + HANDLE child_stdout_read; + HANDLE child_stdout_write; + HANDLE child_stdin_read; + HANDLE child_stdin_write; +}; +// static_assert(sizeof(Win32Process) < sizeof(Process::platform)); + +static void Win32ReportError(String msg, String cmd) { + LPVOID lpMsgBuf; + FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&lpMsgBuf, 0, NULL); + defer { LocalFree(lpMsgBuf); }; + char *buff = (char *)lpMsgBuf; + size_t buffLen = strlen((const char *)buff); + if (buffLen > 0 && buff[buffLen - 1] == '\n') buff[buffLen - 1] = 0; + Error("%.*s: %.*s! %s", FmtString(msg), FmtString(cmd), (char *)lpMsgBuf); +} + +static void Win32CloseProcess(Process *process) { + Win32Process *p = (Win32Process *)process->platform; + if (p->handle != INVALID_HANDLE_VALUE) CloseHandle(p->handle); + if (p->child_stdout_write != INVALID_HANDLE_VALUE) CloseHandle(p->child_stdout_write); + if (p->child_stdout_read != INVALID_HANDLE_VALUE) CloseHandle(p->child_stdout_read); + if (p->child_stdin_read != INVALID_HANDLE_VALUE) CloseHandle(p->child_stdin_read); + if (p->child_stdin_write != INVALID_HANDLE_VALUE) CloseHandle(p->child_stdin_write); + process->is_valid = false; +} + +static void Win32ProcessError(Process *process, String msg, String cmd) { + Win32ReportError(msg, cmd); + Win32CloseProcess(process); +} + +API void WriteStdin(Process *process, String string) { + if (string.len == 0) return; + Assert(process->is_valid); + + Win32Process *p = (Win32Process *)process->platform; + Assert(p->child_stdin_write != INVALID_HANDLE_VALUE); + + DWORD written = 0; + bool write_error = WriteFile(p->child_stdin_write, string.data, (DWORD)string.len, &written, NULL) == 0; + + Assert(write_error == false); + Assert(written == string.len); +} + +API void CloseStdin(Process *process) { + Win32Process *p = (Win32Process *)process->platform; + CloseHandle(p->child_stdin_write); + p->child_stdin_write = INVALID_HANDLE_VALUE; +} + +API Process SpawnProcess(String command_line, String working_dir, String write_stdin, Array enviroment) { + Process process = {}; + Win32Process *p = (Win32Process *)process.platform; + + Scratch scratch; + command_line = Format(scratch, "/C %.*s", FmtString(command_line)); + + p->handle = INVALID_HANDLE_VALUE; + p->child_stdout_write = INVALID_HANDLE_VALUE; + p->child_stdout_read = INVALID_HANDLE_VALUE; + p->child_stdin_read = INVALID_HANDLE_VALUE; + p->child_stdin_write = INVALID_HANDLE_VALUE; + + SECURITY_ATTRIBUTES security_atrb = {}; + security_atrb.nLength = sizeof(SECURITY_ATTRIBUTES); + security_atrb.bInheritHandle = TRUE; + + if (!CreatePipe(&p->child_stdout_read, &p->child_stdout_write, &security_atrb, 0)) { + Win32ProcessError(&process, "Failed to create process at create pipe stage", command_line); + return process; + } + + if (!SetHandleInformation(p->child_stdout_read, HANDLE_FLAG_INHERIT, 0)) { + Win32ProcessError(&process, "Failed to create process at create pipe stage", command_line); + return process; + } + + if (!CreatePipe(&p->child_stdin_read, &p->child_stdin_write, &security_atrb, 0)) { + Win32ProcessError(&process, "Failed to create process at create pipe stage", command_line); + return process; + } + + if (!SetHandleInformation(p->child_stdin_write, HANDLE_FLAG_INHERIT, 0)) { + Win32ProcessError(&process, "Failed to create process at create pipe stage", command_line); + return process; + } + + STARTUPINFOW startup = {}; + startup.cb = sizeof(STARTUPINFO); + startup.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES; + startup.hStdInput = p->child_stdin_read; + startup.hStdOutput = p->child_stdout_write; + startup.hStdError = p->child_stdout_write; + startup.wShowWindow = SW_HIDE; + + String16 cwd = ToString16(scratch, working_dir); + String16 cmd = ToString16(scratch, command_line); + + char *env = NULL; + if (enviroment.len) { + Int size = GetSize(enviroment) + enviroment.len + 1; + env = (char *)PushSize(scratch, size); + Int i = 0; + For(enviroment) { + MemoryCopy(env + i, it.data, it.len); + i += it.len; + + env[i++] = 0; + } + env[i++] = 0; + } + + DWORD dwCreationFlags = 0; + BOOL bInheritHandles = TRUE; + + PROCESS_INFORMATION info = {}; + if (!CreateProcessW(L"c:\\windows\\system32\\cmd.exe", (wchar_t *)cmd.data, 0, 0, bInheritHandles, dwCreationFlags, env, (wchar_t *)cwd.data, &startup, &info)) { + Win32ProcessError(&process, "failed to create process", command_line); + return process; + } + + // Close handles to the stdin and stdout pipes no longer needed by the child process. + // If they are not explicitly closed, there is no way to recognize that the child process has ended. + CloseHandle(info.hThread); + CloseHandle(p->child_stdin_read); + CloseHandle(p->child_stdout_write); + p->child_stdin_read = INVALID_HANDLE_VALUE; + p->child_stdout_write = INVALID_HANDLE_VALUE; + + p->handle = info.hProcess; + process.is_valid = true; + process.id = (int64_t)p->handle; + + if (write_stdin.len) { + WriteStdin(&process, write_stdin); + CloseStdin(&process); + } + + return process; +} + +API bool IsValid(Process *process) { + if (process->is_valid == false) return false; + Win32Process *p = (Win32Process *)process->platform; + + if (WaitForSingleObject(p->handle, 0) != WAIT_OBJECT_0) { + return true; + } + + DWORD exit_code; + bool get_exit_code_failed = GetExitCodeProcess(p->handle, &exit_code) == 0; + if (get_exit_code_failed) { + exit_code = -1; + } + + process->exit_code = (int)exit_code; + Win32CloseProcess(process); + return false; +} + +API void KillProcess(Process *process) { + Assert(process->is_valid); + Win32Process *p = (Win32Process *)process->platform; + + bool terminate_process_error = TerminateProcess(p->handle, -1) == 0; + if (terminate_process_error) { + Assert(0); + } + Win32CloseProcess(process); + process->exit_code = -1; +} + +API String PollStdout(Allocator allocator, Process *process, bool force_read) { + Assert(process->is_valid); + Win32Process *p = (Win32Process *)process->platform; + + DWORD bytes_avail = 0; + bool peek_error = PeekNamedPipe(p->child_stdout_read, NULL, 0, NULL, &bytes_avail, NULL) == 0; + if (peek_error) { + return {}; + } else if (bytes_avail == 0) { + return {}; + } + + size_t buffer_size = ClampTop(bytes_avail, (DWORD)(4096 * 16)); + char *buffer = AllocArray(allocator, char, buffer_size); + + DWORD bytes_read = 0; + bool read_error = ReadFile(p->child_stdout_read, buffer, (DWORD)buffer_size, &bytes_read, 0) == 0; + if (read_error) { + Win32ReportError("Failed to read the stdout of child process", "ReadFile"); + Dealloc(allocator, &buffer); + Win32CloseProcess(process); + return {}; + } + + String result = {buffer, bytes_read}; + return result; +} + +#elif OS_WASM +#include "filesystem.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define PATH_MAX 1024 +#include + +API void (*Error)(const char *, ...); + +API void InitOS(void (*error_proc)(const char *, ...)) { + Error = error_proc; +} + +API String ReadFile(Allocator al, String path) { + Scratch scratch(al); + String null_term = Copy(scratch, path); + String result = {}; + FILE *f = fopen(null_term.data, "rb"); + if (f) { + fseek(f, 0, SEEK_END); + result.len = ftell(f); + fseek(f, 0, SEEK_SET); + + result.data = (char *)AllocSize(al, result.len + 1); + fread(result.data, result.len, 1, f); + result.data[result.len] = 0; + + fclose(f); + } + return result; +} + +API bool WriteFile(String path, String data) { + Scratch scratch; + String null_term = Copy(scratch, path); + bool result = false; + FILE *f = fopen((const char *)null_term.data, "w"); + if (f) { + size_t written = fwrite(data.data, 1, data.len, f); + if (written == data.len) { + result = true; + } + fclose(f); + } + return result; +} + +API bool DeleteFile(String path) { + Scratch scratch; + String null_term = Copy(scratch, path); + int result = unlink(null_term.data); + return result == 0; +} + +API MakeDirResult MakeDir(String path) { + Scratch scratch; + String null_term = Copy(scratch, path); + int error = mkdir(null_term.data, 0755); + MakeDirResult result = MakeDirResult_Success; + if (error != 0) { + result = MakeDirResult_ErrorOther; + if (errno == EEXIST) result = MakeDirResult_Exists; + } + return result; +} + +API int64_t GetFileModTime(String path) { + Scratch scratch; + String null_term = Copy(scratch, path); + struct stat attrib = {}; + stat(null_term.data, &attrib); + struct timespec ts = attrib.st_mtim; + int64_t result = (((int64_t)ts.tv_sec) * 1000000ll) + ((int64_t)ts.tv_nsec) / 1000ll; + return result; +} + +API String GetAbsolutePath(Allocator al, String path) { + return path; +} + +API bool FileExists(String path) { + Scratch scratch; + String null_term = Copy(scratch, path); + bool result = false; + if (access((char *)null_term.data, F_OK) == 0) { + result = true; + } + return result; +} + +API bool IsDir(String path) { + Scratch scratch; + String null_term = Copy(scratch, path); + struct stat s; + if (stat(null_term.data, &s) != 0) + return false; + return S_ISDIR(s.st_mode); +} + +API bool IsFile(String path) { + Scratch scratch; + String null_term = Copy(scratch, path); + struct stat s; + if (stat(null_term.data, &s) != 0) + return false; + return S_ISREG(s.st_mode); +} + +API bool IsAbsolute(String path) { + bool result = path.len && path.data[0] == '/'; + return result; +} + +API String GetWorkingDir(Allocator al) { + return Copy(al, "/workingdir"); +} + +API String GetExePath(Allocator al) { + return Copy(al, "/text_editor"); +} + +API String GetExeDir(Allocator al) { + Scratch scratch(al); + String exe_path = GetExePath(scratch); + String dir = ChopLastSlash(exe_path); + String result = Copy(al, dir); + return result; +} + +API double GetTimeMicros(void) { + struct timespec spec; + clock_gettime(CLOCK_MONOTONIC, &spec); + return (((double)spec.tv_sec) * 1000000) + (((double)spec.tv_nsec) / 1000); +} + +API bool IsValid(const FileIter &it) { + return it.is_valid; +} + +API void Advance(FileIter *it) { + struct dirent *file = NULL; + 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 = Copy(it->allocator, file->d_name); + + const char *dir_char_ending = it->is_directory ? "/" : ""; + const char *separator = it->path.data[it->path.len - 1] == '/' ? "" : "/"; + it->relative_path = Format(it->allocator, "%.*s%s%s%s", FmtString(it->path), separator, file->d_name, dir_char_ending); + it->absolute_path = GetAbsolutePath(it->allocator, it->relative_path); + if (it->is_directory) it->absolute_path = Format(it->allocator, "%.*s/", FmtString(it->absolute_path)); + it->is_valid = true; + return; + } + it->is_valid = false; + closedir((DIR *)it->dir); +} + +API FileIter IterateFiles(Allocator alo, String path) { + FileIter it = {}; + it.allocator = alo; + it.path = path; + Scratch scratch(alo); + String null_term = Copy(scratch, path); + it.dir = (void *)opendir((char *)null_term.data); + if (it.dir) { + Advance(&it); + } + return it; +} + +API Array SplitCommand(Allocator allocator, String command_line) { + Array cmd = {allocator}; + + String curr = {}; + for (int i = 0; i < command_line.len; i += 1) { + if (command_line.data[i] == ' ') { + if (curr.len > 0) { + Add(&cmd, Copy(allocator, curr).data); + curr = {}; + } + continue; + } + if (curr.len == 0) { + curr.data = command_line.data + i; + } + curr.len += 1; + } + + if (curr.len > 0) { + Add(&cmd, Copy(allocator, curr).data); + } + + return cmd; +} + +API Process SpawnProcess(String command_line, String working_dir, String write_stdin, Array enviroment) { + return {}; +} + +API bool IsValid(Process *process) { + return false; +} + +API void KillProcess(Process *process) { +} + +API String PollStdout(Allocator allocator, Process *process, bool force_read) { + return {}; +} + +API void WriteStdin(Process *process, String string) { + return; +} + +API void CloseStdin(Process *process) { + return; +} + +#endif diff --git a/src/basic/filesystem.h b/src/basic/basic_os.h similarity index 96% rename from src/basic/filesystem.h rename to src/basic/basic_os.h index de9d740..f5ad275 100644 --- a/src/basic/filesystem.h +++ b/src/basic/basic_os.h @@ -1,5 +1,4 @@ #pragma once -#include "../basic/basic.h" struct FileIter { bool is_valid; @@ -50,8 +49,7 @@ void KillProcess(Process *process); String PollStdout(Allocator allocator, Process *process, bool force_read); void WriteStdin(Process *process, String string); void CloseStdin(Process *process); - -double get_time_in_micros(void); +double GetTimeMicros(void); enum MakeDirResult { MakeDirResult_Success, diff --git a/src/basic/basic_string.cpp b/src/basic/basic_string.cpp new file mode 100644 index 0000000..e71f9a0 --- /dev/null +++ b/src/basic/basic_string.cpp @@ -0,0 +1,336 @@ +API char ToLowerCase(char a) { + if (a >= 'A' && a <= 'Z') a += 32; + return a; +} + +API char ToUpperCase(char a) { + if (a >= 'a' && a <= 'z') a -= 32; + return a; +} + +API bool IsWhitespace(char w) { + bool result = w == '\n' || w == ' ' || w == '\t' || w == '\v' || w == '\r'; + return result; +} + +API bool IsAlphabetic(char a) { + bool result = (a >= 'a' && a <= 'z') || (a >= 'A' && a <= 'Z'); + return result; +} + +API bool IsIdent(char a) { + bool result = (a >= 'a' && a <= 'z') || (a >= 'A' && a <= 'Z') || a == '_'; + return result; +} + +API bool IsDigit(char a) { + bool result = a >= '0' && a <= '9'; + return result; +} + +API bool IsAlphanumeric(char a) { + bool result = IsDigit(a) || IsAlphabetic(a); + return result; +} + +API bool IsSymbol(char w) { + bool result = (w >= '!' && w <= '/') || (w >= ':' && w <= '@') || (w >= '[' && w <= '`') || (w >= '{' && w <= '~'); + return result; +} + +API bool IsNonWord(char w) { + if (w == '_') return false; + bool result = IsSymbol(w) || IsWhitespace(w); + return result; +} + +API bool IsWord(char w) { + bool result = !IsNonWord(w); + return result; +} + +API bool IsBrace(char c) { + bool result = c == '{' || c == '}'; + return result; +} + +API bool IsParen(char c) { + bool result = c == '(' || c == ')'; + return result; +} + +API String Chop(String a, int64_t len) { + len = ClampTop(len, a.len); + String result = {a.data, a.len - len}; + return result; +} + +API String Skip(String a, int64_t len) { + len = ClampTop(len, a.len); + String result = {a.data + len, a.len - len}; + return result; +} + +API String GetPostfix(String a, int64_t len) { + len = ClampTop(len, a.len); + int64_t remain_len = a.len - len; + String result = {a.data + remain_len, len}; + return result; +} + +API String GetPrefix(String a, int64_t len) { + len = ClampTop(len, a.len); + String result = {a.data, len}; + return result; +} + +API String GetSlice(String arr, 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 = arr.len; + if (one_past_last_index < 0) one_past_last_index = arr.len + one_past_last_index; + + if (first_index == SLICE_LAST) first_index = arr.len; + if (first_index < 0) first_index = arr.len + first_index; + + String result = {arr.data, arr.len}; + if (arr.len > 0) { + if (one_past_last_index > first_index) { + first_index = ClampTop(first_index, arr.len - 1); + one_past_last_index = ClampTop(one_past_last_index, arr.len); + result.data += first_index; + result.len = one_past_last_index - first_index; + } else { + result.len = 0; + } + } + return result; +} + +API 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; +} + +API bool EndsWith(String a, String end, unsigned ignore_case) { + String a_end = GetPostfix(a, end.len); + bool result = AreEqual(end, a_end, ignore_case); + return result; +} + +API bool StartsWith(String a, String start, unsigned ignore_case) { + String a_start = GetPrefix(a, start.len); + bool result = AreEqual(start, a_start, ignore_case); + return result; +} + +API 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 = GetSlice(string, whitespace_begin, whitespace_end); + } + + return string; +} + +API 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 = GetPrefix(string, whitespace_end); + return result; +} + +API 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; +} + +API String Copy(Allocator allocator, char *string) { + return Copy(allocator, {string, (int64_t)strlen(string)}); +} + +API void NormalizePathInPlace(String s) { + for (int64_t i = 0; i < s.len; i++) { + if (s.data[i] == '\\') + s.data[i] = '/'; + } +} + +API String NormalizePath(Allocator allocator, String s) { + String copy = Copy(allocator, s); + NormalizePathInPlace(copy); + return copy; +} + +API bool Seek(String string, String find, int64_t *index_out, SeekFlag flags) { + 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 = GetSlice(string, 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 = GetSlice(string, i, i + find.len); + if (AreEqual(substring, find, ignore_case)) { + if (index_out) + *index_out = i; + result = true; + break; + } + } + } + + return result; +} + +API String CutLastSlash(String *s) { + String result = *s; + Seek(*s, "/", &s->len, SeekFlag_MatchFindLast); + result = Skip(result, s->len); + return result; +} + +API String ChopLastSlash(String s) { + String result = s; + Seek(s, "/", &result.len, SeekFlag_MatchFindLast); + return result; +} + +API String ChopLastPeriod(String s) { + String result = s; + Seek(s, ".", &result.len, SeekFlag_MatchFindLast); + return result; +} + +API String SkipToLastSlash(String s) { + int64_t pos; + String result = s; + if (Seek(s, "/", &pos, SeekFlag_MatchFindLast)) { + result = Skip(result, pos + 1); + } + return result; +} + +API String SkipToLastPeriod(String s) { + int64_t pos; + String result = s; + if (Seek(s, ".", &pos, SeekFlag_MatchFindLast)) { + result = Skip(result, pos + 1); + } + return result; +} + +API String CutPrefix(String *string, int64_t len) { + String result = GetPrefix(*string, len); + *string = Skip(*string, len); + return result; +} + +API String CutPostfix(String *string, int64_t len) { + String result = GetPostfix(*string, len); + *string = Chop(*string, len); + return result; +} + +API String FormatV(Allocator allocator, const char *data, va_list args1) { + va_list args2; + va_copy(args2, args1); + int64_t len = stbsp_vsnprintf(0, 0, data, args2); + va_end(args2); + + char *result = (char *)AllocSize(allocator, sizeof(char) * (len + 1)); + stbsp_vsnprintf(result, (int)(len + 1), data, args1); + String res = {result, len}; + return res; +} + +API String Format(Allocator allocator, const char *data, ...) { + STRING_FORMAT(allocator, data, result); + return result; +} + +API 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}; + Add(&result, before_match); + string = Skip(string, index + delimiter.len); + } + Add(&result, string); + return result; +} + +API 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 (!IsLast(list, 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; +} + +API Int GetSize(Array array) { + Int result = 0; + For (array) result += it.len; + return result; +} \ No newline at end of file diff --git a/src/basic/basic_string.h b/src/basic/basic_string.h new file mode 100644 index 0000000..702ad67 --- /dev/null +++ b/src/basic/basic_string.h @@ -0,0 +1,106 @@ +#pragma once + +struct String { + char *data; + int64_t len; + + String() = default; + String(char *s) : data(s), len(strlen(s)) {} + String(char *s, int64_t l) : data((char *)s), len(l) {} + String(const char *s) : data((char *)s), len(strlen((char *)s)) {} + String(const char *s, int64_t l) : data((char *)s), len(l) {} + + + char &operator[](int64_t index) { + Assert(index < len); + return data[index]; + } + char *begin() { return data; } + char *end() { return data + len; } + + struct ReverseIter { + char *data; + String *arr; + + ReverseIter operator++(int) { + ReverseIter ret = *this; + data -= 1; + return ret; + } + ReverseIter &operator++() { + data -= 1; + return *this; + } + + char &operator*() { return data[0]; } + char *operator->() { return data; } + + friend bool operator==(const ReverseIter &a, const ReverseIter &b) { return a.data == b.data; }; + friend bool operator!=(const ReverseIter &a, const ReverseIter &b) { return a.data != b.data; }; + + ReverseIter begin() { return ReverseIter{arr->end() - 1, arr}; } + ReverseIter end() { return ReverseIter{arr->begin() - 1, arr}; } + }; + ReverseIter reverse() { return {this->end() - 1, this}; } +}; + +API char ToLowerCase(char a); +API char ToUpperCase(char a); +API bool IsWhitespace(char w); +API bool IsAlphabetic(char a); +API bool IsIdent(char a); +API bool IsDigit(char a); +API bool IsAlphanumeric(char a); +API bool IsSymbol(char w); +API bool IsNonWord(char w); +API bool IsWord(char w); +API bool IsBrace(char c); +API bool IsParen(char c); + +API bool EndsWith(String a, String end, unsigned ignore_case = false); +API bool StartsWith(String a, String start, unsigned ignore_case = false); +API bool AreEqual(String a, String b, unsigned ignore_case = false); +inline bool operator==(String a, String b) { return AreEqual(a, b); } +inline bool operator!=(String a, String b) { return !AreEqual(a, b); } + +API String Trim(String string); +API String TrimEnd(String string); +API String Chop(String a, int64_t len); +API String Skip(String a, int64_t len); +API String GetPostfix(String a, int64_t len); +API String GetPrefix(String a, int64_t len); +API String GetSlice(String arr, int64_t first_index = 0, int64_t one_past_last_index = SLICE_LAST); +API String CutLastSlash(String *s); +API String ChopLastSlash(String s); +API String ChopLastPeriod(String s); +API String SkipToLastSlash(String s); +API String SkipToLastPeriod(String s); +API String CutPrefix(String *string, int64_t len); +API String CutPostfix(String *string, int64_t len); + +API String Copy(Allocator allocator, String string); +API String Copy(Allocator allocator, char *string); +API void NormalizePathInPlace(String s); +API String NormalizePath(Allocator allocator, String s); + +#define FmtString(string) (int)(string).len, (string).data +API String FormatV(Allocator allocator, const char *data, va_list args1); +API String Format(Allocator allocator, const char *data, ...); +#define STRING_FORMAT(allocator, data, result) \ + va_list args1; \ + va_start(args1, data); \ + String result = FormatV(allocator, data, args1); \ + va_end(args1) + + +typedef int SeekFlag; +enum { + SeekFlag_None = 0, + SeekFlag_IgnoreCase = 1, + SeekFlag_MatchFindLast = 2, +}; +API bool Seek(String string, String find, int64_t *index_out = NULL, SeekFlag flags = SeekFlag_None); + +API Array Split(Allocator allocator, String string, String delimiter = " "); +API String Merge(Allocator allocator, Array list, String separator); +API Int GetSize(Array array); \ No newline at end of file diff --git a/src/basic/basic_string16.cpp b/src/basic/basic_string16.cpp new file mode 100644 index 0000000..8fe3a02 --- /dev/null +++ b/src/basic/basic_string16.cpp @@ -0,0 +1,422 @@ +#include + +API int64_t WideLength(char16_t *string) { + if (!string) return 0; + int64_t len = 0; + while (*string++ != 0) + len++; + return len; +} + +API char16_t ToLowerCase(char16_t a) { + if (a >= u'A' && a <= u'Z') a += 32; + return a; +} + +API char16_t ToUpperCase(char16_t a) { + if (a >= u'a' && a <= u'z') a -= 32; + return a; +} + +API bool IsWhitespace(char16_t w) { + bool result = w == u'\n' || w == u' ' || w == u'\t' || w == u'\v' || w == u'\r'; + return result; +} + +API bool IsAlphabetic(char16_t a) { + bool result = (a >= u'a' && a <= u'z') || (a >= u'A' && a <= u'Z'); + return result; +} + +API bool IsIdent(char16_t a) { + bool result = (a >= u'a' && a <= u'z') || (a >= u'A' && a <= u'Z') || a == u'_'; + return result; +} + +API bool IsDigit(char16_t a) { + bool result = a >= u'0' && a <= u'9'; + return result; +} + +API bool IsAlphanumeric(char16_t a) { + bool result = IsDigit(a) || IsAlphabetic(a); + return result; +} + +API bool IsSymbol(char16_t w) { + bool result = (w >= u'!' && w <= u'/') || (w >= u':' && w <= u'@') || (w >= u'[' && w <= u'`') || (w >= u'{' && w <= u'~'); + return result; +} + +API bool IsNonWord(char16_t w) { + if (w == u'_') return false; + bool result = IsSymbol(w) || IsWhitespace(w); + return result; +} + +API bool IsWord(char16_t w) { + bool result = !IsNonWord(w); + return result; +} + +API bool IsBrace(char16_t c) { + bool result = c == u'{' || c == u'}'; + return result; +} + +API bool IsParen(char16_t c) { + bool result = c == u'(' || c == u')'; + return result; +} + + +API String16 Chop(String16 a, int64_t len) { + len = ClampTop(len, a.len); + String16 result = {a.data, a.len - len}; + return result; +} + +API String16 Skip(String16 a, int64_t len) { + len = ClampTop(len, a.len); + String16 result = {a.data + len, a.len - len}; + return result; +} + +API String16 GetPostfix(String16 a, int64_t len) { + len = ClampTop(len, a.len); + int64_t remain_len = a.len - len; + String16 result = {a.data + remain_len, len}; + return result; +} + +API String16 GetPrefix(String16 a, int64_t len) { + len = ClampTop(len, a.len); + String16 result = {a.data, len}; + return result; +} + +API String16 GetSlice(String16 arr, 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 = arr.len; + if (one_past_last_index < 0) one_past_last_index = arr.len + one_past_last_index; + + if (first_index == SLICE_LAST) first_index = arr.len; + if (first_index < 0) first_index = arr.len + first_index; + + String16 result = {arr.data, arr.len}; + if (arr.len > 0) { + if (one_past_last_index > first_index) { + first_index = ClampTop(first_index, arr.len - 1); + one_past_last_index = ClampTop(one_past_last_index, arr.len); + result.data += first_index; + result.len = one_past_last_index - first_index; + } else { + result.len = 0; + } + } + return result; +} + +API bool AreEqual(String16 a, String16 b, unsigned ignore_case) { + if (a.len != b.len) return false; + for (int64_t i = 0; i < a.len; i++) { + char16_t A = a.data[i]; + char16_t B = b.data[i]; + if (ignore_case) { + A = ToLowerCase(A); + B = ToLowerCase(B); + } + if (A != B) + return false; + } + return true; +} + +API bool EndsWith(String16 a, String16 end, unsigned ignore_case) { + String16 a_end = GetPostfix(a, end.len); + bool result = AreEqual(end, a_end, ignore_case); + return result; +} + +API bool StartsWith(String16 a, String16 start, unsigned ignore_case) { + String16 a_start = GetPrefix(a, start.len); + bool result = AreEqual(start, a_start, ignore_case); + return result; +} + +API String16 Trim(String16 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 = GetSlice(string, whitespace_begin, whitespace_end); + } + + return string; +} + +API String16 TrimEnd(String16 string) { + int64_t whitespace_end = string.len; + for (; whitespace_end != 0; whitespace_end--) { + if (!IsWhitespace(string.data[whitespace_end - 1])) { + break; + } + } + + String16 result = GetPrefix(string, whitespace_end); + return result; +} + +API String16 Copy16(Allocator allocator, String16 string) { + char16_t *copy = (char16_t *)AllocSize(allocator, sizeof(char16_t) * (string.len + 1)); + memcpy(copy, string.data, string.len); + copy[string.len] = 0; + String16 result = {copy, string.len}; + return result; +} + +API String16 Copy16(Allocator allocator, char16_t *string) { + return Copy16(allocator, {string, (int64_t)WideLength(string)}); +} + +API void NormalizePathInPlace(String16 s) { + for (int64_t i = 0; i < s.len; i++) { + if (s.data[i] == u'\\') + s.data[i] = u'/'; + } +} + +API String16 NormalizePath(Allocator allocator, String16 s) { + String16 copy = Copy16(allocator, s); + NormalizePathInPlace(copy); + return copy; +} + +API bool Seek(String16 string, String16 find, int64_t *index_out, SeekFlag flags) { + 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; + String16 substring = GetSlice(string, 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++) { + String16 substring = GetSlice(string, i, i + find.len); + if (AreEqual(substring, find, ignore_case)) { + if (index_out) + *index_out = i; + result = true; + break; + } + } + } + + return result; +} + +API String16 CutLastSlash(String16 *s) { + String16 result = *s; + Seek(*s, u"/", &s->len, SeekFlag_MatchFindLast); + result = Skip(result, s->len); + return result; +} + +API String16 ChopLastSlash(String16 s) { + String16 result = s; + Seek(s, u"/", &result.len, SeekFlag_MatchFindLast); + return result; +} + +API String16 ChopLastPeriod(String16 s) { + String16 result = s; + Seek(s, u".", &result.len, SeekFlag_MatchFindLast); + return result; +} + +API String16 SkipToLastSlash(String16 s) { + int64_t pos; + String16 result = s; + if (Seek(s, u"/", &pos, SeekFlag_MatchFindLast)) { + result = Skip(result, pos + 1); + } + return result; +} + +API String16 SkipToLastPeriod(String16 s) { + int64_t pos; + String16 result = s; + if (Seek(s, u".", &pos, SeekFlag_MatchFindLast)) { + result = Skip(result, pos + 1); + } + return result; +} + +API String16 CutPrefix(String16 *string, int64_t len) { + String16 result = GetPrefix(*string, len); + *string = Skip(*string, len); + return result; +} + +API String16 CutPostfix(String16 *string, int64_t len) { + String16 result = GetPostfix(*string, len); + *string = Chop(*string, len); + return result; +} + +API String16 Format16V(Allocator allocator, const char *data, va_list args1) { + Scratch scratch; + va_list args2; + va_copy(args2, args1); + int64_t len = stbsp_vsnprintf(0, 0, data, args2); + va_end(args2); + + char *result = (char *)AllocSize(scratch, sizeof(char) * (len + 1)); + stbsp_vsnprintf(result, (int)(len + 1), data, args1); + String res = {result, len}; + String16 res16 = ToString16(allocator, res); + return res16; +} + +API String16 Format16(Allocator allocator, const char *data, ...) { + Scratch scratch; + STRING_FORMAT(scratch, data, result); + String16 result16 = ToString16(allocator, result); + return result16; +} + +API Array Split(Allocator allocator, String16 string, String16 delimiter) { + Array result = {allocator}; + int64_t index = 0; + while (Seek(string, delimiter, &index)) { + String16 before_match = {string.data, index}; + Add(&result, before_match); + string = Skip(string, index + delimiter.len); + } + Add(&result, string); + return result; +} + +API String16 Merge(Allocator allocator, Array list, String16 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; + char16_t *buff = (char16_t *)AllocSize(allocator, sizeof(char16_t) * (size + 1)); + String16 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 (!IsLast(list, 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; +} + +API Int GetSize(Array array) { + Int result = 0; + For (array) result += it.len; + return result; +} + + +API String16 SkipNumberEx(String16 *string) { + String16 col = {string->data, 0}; + for (int64_t i = 0; i < string->len; i += 1) { + if (IsDigit(string->data[i])) { + col.len += 1; + } else { + break; + } + } + *string = Skip(*string, col.len); + return col; +} + +API Int SkipNumber(String16 *string) { + String16 col = SkipNumberEx(string); + if (col.len == 0) return -1; + Scratch scratch; + String num_string = ToString(scratch, col); + Int result = strtoll(num_string.data, NULL, 10); + return result; +} + +API String16 SkipUntil(String16 *string, String16 str) { + String16 begin = *string; + begin.len = 0; + for (; string->len; begin.len += 1) { + String16 match = GetPrefix(*string, str.len); + if (StartsWith(match, str)) break; + *string = Skip(*string, 1); + } + return begin; +} + +API String16 SkipWhitespace(String16 *string) { + String16 begin = {string->data, 0}; + for (Int i = 0; i < string->len; i += 1) { + if (!IsWhitespace(string->data[i])) break; + *string = Skip(*string, 1); + begin.len += 1; + } + return begin; +} + +// chop this - :324 +API String16 ChopNumberEx(String16 *string) { + String16 col = {}; + for (int64_t i = string->len - 1; i >= 0; i -= 1) { + if (IsDigit(string->data[i])) { + col.data = string->data + i; + col.len += 1; + } else if (string->data[i] == L':') { + break; + } else { + return {}; + } + } + *string = Chop(*string, col.len + 1); + return col; +} + +API Int ChopNumber(String16 *string) { + Scratch scratch; + String16 col = ChopNumberEx(string); + if (col.len == 0) return -1; + String num_string = ToString(scratch, col); + Int result = strtoll(num_string.data, NULL, 10) - 1; + + return result; +} \ No newline at end of file diff --git a/src/basic/basic_string16.h b/src/basic/basic_string16.h new file mode 100644 index 0000000..ab381f5 --- /dev/null +++ b/src/basic/basic_string16.h @@ -0,0 +1,104 @@ +#pragma once + +API int64_t WideLength(char16_t *string); + +struct String16 { + char16_t *data; + int64_t len; + + String16() = default; + String16(char16_t *s) : data(s), len(WideLength(s)) {} + String16(char16_t *s, int64_t l) : data((char16_t *)s), len(l) {} + String16(const char16_t *s) : data((char16_t *)s), len(WideLength((char16_t *)s)) {} + String16(const char16_t *s, int64_t l) : data((char16_t *)s), len(l) {} + + + char16_t &operator[](int64_t index) { + Assert(index < len); + return data[index]; + } + char16_t *begin() { return data; } + char16_t *end() { return data + len; } + + struct ReverseIter { + char16_t *data; + String16 *arr; + + ReverseIter operator++(int) { + ReverseIter ret = *this; + data -= 1; + return ret; + } + ReverseIter &operator++() { + data -= 1; + return *this; + } + + char16_t &operator*() { return data[0]; } + char16_t *operator->() { return data; } + + friend bool operator==(const ReverseIter &a, const ReverseIter &b) { return a.data == b.data; }; + friend bool operator!=(const ReverseIter &a, const ReverseIter &b) { return a.data != b.data; }; + + ReverseIter begin() { return ReverseIter{arr->end() - 1, arr}; } + ReverseIter end() { return ReverseIter{arr->begin() - 1, arr}; } + }; + ReverseIter reverse() { return {this->end() - 1, this}; } +}; + +API char16_t ToLowerCase(char16_t a); +API char16_t ToUpperCase(char16_t a); +API bool IsWhitespace(char16_t w); +API bool IsAlphabetic(char16_t a); +API bool IsIdent(char16_t a); +API bool IsDigit(char16_t a); +API bool IsAlphanumeric(char16_t a); +API bool IsSymbol(char16_t w); +API bool IsNonWord(char16_t w); +API bool IsWord(char16_t w); +API bool IsBrace(char16_t c); +API bool IsParen(char16_t c); + +API bool EndsWith(String16 a, String16 end, unsigned ignore_case = false); +API bool StartsWith(String16 a, String16 start, unsigned ignore_case = false); +API bool AreEqual(String16 a, String16 b, unsigned ignore_case = false); +inline bool operator==(String16 a, String16 b) { return AreEqual(a, b); } +inline bool operator!=(String16 a, String16 b) { return !AreEqual(a, b); } + +API String16 Trim(String16 string); +API String16 TrimEnd(String16 string); +API String16 Chop(String16 a, int64_t len); +API String16 Skip(String16 a, int64_t len); +API String16 GetPostfix(String16 a, int64_t len); +API String16 GetPrefix(String16 a, int64_t len); +API String16 GetSlice(String16 arr, int64_t first_index = 0, int64_t one_past_last_index = SLICE_LAST); +API String16 CutLastSlash(String16 *s); +API String16 ChopLastSlash(String16 s); +API String16 ChopLastPeriod(String16 s); +API String16 SkipToLastSlash(String16 s); +API String16 SkipToLastPeriod(String16 s); +API String16 CutPrefix(String16 *string, int64_t len); +API String16 CutPostfix(String16 *string, int64_t len); + +// @todo: think about this API, for parsing, maybe redesign or something +API String16 SkipNumberEx(String16 *string); +API Int SkipNumber(String16 *string); +API String16 SkipUntil(String16 *string, String16 str); +API String16 SkipWhitespace(String16 *string); +API String16 ChopNumberEx(String16 *string); +API Int ChopNumber(String16 *string); + +API String16 Copy16(Allocator allocator, String16 string); +API String16 Copy16(Allocator allocator, char16_t *string, int64_t len); +API String16 Copy16(Allocator allocator, char *string); +API void NormalizePathInPlace(String16 s); +API String16 NormalizePath(Allocator allocator, String16 s); + +API String16 Format16V(Allocator allocator, const char *data, va_list args1); +API String16 Format16(Allocator allocator, const char *data, ...); + +API bool Seek(String16 string, String16 find, int64_t *index_out = NULL, SeekFlag flags = SeekFlag_None); + +API Array Split(Allocator allocator, String16 string, String16 delimiter = u" "); +API String16 Merge(Allocator allocator, Array list, String16 separator); +API Int GetSize(Array array); \ No newline at end of file diff --git a/src/basic/basic_table.h b/src/basic/basic_table.h new file mode 100644 index 0000000..ed4813f --- /dev/null +++ b/src/basic/basic_table.h @@ -0,0 +1,185 @@ + +/* + 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 (size_t 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; + } +}; diff --git a/src/basic/basic_unicode.cpp b/src/basic/basic_unicode.cpp new file mode 100644 index 0000000..1c84992 --- /dev/null +++ b/src/basic/basic_unicode.cpp @@ -0,0 +1,237 @@ +API UTF32Result UTF16ToUTF32(uint16_t *c, int64_t 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; +} + +API 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; +} + +API UTF32Result UTF8ToUTF32(uint8_t *c, int64_t 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; +} + +API 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, I) \ + { \ + if (outlen < buffer_size - 1) buffer[outlen++] = (question_mark); \ + i += I; \ + } + +API int64_t CreateCharFromWidechar(char *buffer, int64_t buffer_size, char16_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), (int64_t)(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('?', 0); + } else UTF__HANDLE_DECODE_ERROR('?', 1); + } + + buffer[outlen] = 0; + return outlen; +} + +API int64_t CreateWidecharFromChar(char16_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((uint8_t *)(in + i), (int64_t)(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, 0); + } else UTF__HANDLE_DECODE_ERROR(0x003f, 1); + } + + buffer[outlen] = 0; + return outlen; +} + +API bool IsValid(UTF8Iter &iter) { + return iter.item; +} + +API void Advance(UTF8Iter *iter) { + iter->i += iter->utf8_codepoint_byte_size; + UTF32Result r = UTF8ToUTF32((uint8_t *)(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; +} + +API UTF8Iter IterateUTF8Ex(char *data, int64_t len) { + UTF8Iter result; + MemoryZero(&result, sizeof(result)); + result.data = data; + result.len = len; + if (len) Advance(&result); + return result; +} + +API UTF8Iter IterateUTF8(char *data) { + int64_t length = 0; + while (data[length]) length += 1; + return IterateUTF8Ex(data, length); +} + +API UTF8Iter IterateUTF8(String string) { + return IterateUTF8Ex(string.data, string.len); +} + +API bool IsUTF8ContinuationByte(char c) { + char result = (c & 0b11000000) == 0b10000000; + return result; +} + +API String16 ToString16(Allocator allocator, String string) { + char16_t *buffer = (char16_t *)AllocSize(allocator, sizeof(char16_t) * (string.len + 1)); + int64_t size = CreateWidecharFromChar(buffer, string.len + 1, string.data, string.len); + String16 result = {buffer, size}; + return result; +} + +API String16 ToString16(Allocator allocator, char *in_string) { + String string(in_string); + String16 result = ToString16(allocator, string); + return result; +} + +API char16_t *ToWidechar(Allocator allocator, String string) { + String16 result = ToString16(allocator, string); + return result.data; +} + +API String ToString(Allocator allocator, String16 string) { + Assert(sizeof(char16_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; +} + +API String ToString(Allocator allocator, char16_t *string, int64_t len) { + return ToString(allocator, {string, len}); +} + +API String ToString(Allocator allocator, char16_t *wstring) { + int64_t size = WideLength(wstring); + String result = ToString(allocator, {wstring, size}); + return result; +} + diff --git a/src/basic/basic_unicode.h b/src/basic/basic_unicode.h new file mode 100644 index 0000000..1637b39 --- /dev/null +++ b/src/basic/basic_unicode.h @@ -0,0 +1,60 @@ +#pragma once + +struct UTF32Result { + uint32_t out_str; + int64_t advance; + int64_t error; +}; + +struct UTF8Result { + uint8_t out_str[4]; + int64_t len; + int64_t error; +}; + +struct UTF16Result { + uint16_t out_str[2]; + int64_t len; + int64_t error; +}; + +struct UTF8Iter { + char *data; + int64_t len; + int64_t utf8_codepoint_byte_size; + int64_t 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, int64_t len); + return {IterateUTF8Ex(data, len)}; + } + UTF8Iter end() { return {}; } +}; + +API UTF32Result UTF16ToUTF32(uint16_t *c, int64_t max_advance); +API UTF8Result UTF32ToUTF8(uint32_t codepoint); +API UTF32Result UTF8ToUTF32(uint8_t *c, int64_t max_advance); +API UTF16Result UTF32ToUTF16(uint32_t codepoint); + +API int64_t CreateCharFromWidechar(char *buffer, int64_t buffer_size, char16_t *in, int64_t inlen); +API int64_t CreateWidecharFromChar(char16_t *buffer, int64_t buffer_size, char *in, int64_t inlen); +API char16_t *ToWidechar(Allocator allocator, String string); +API String16 ToString16(Allocator allocator, String string); +API String ToString(Allocator allocator, String16 string); +API String ToString(Allocator allocator, char16_t *string, int64_t len); +API String ToString(Allocator allocator, char16_t *wstring); + +API bool IsValid(UTF8Iter &iter); +API void Advance(UTF8Iter *iter); +API UTF8Iter IterateUTF8Ex(char *data, int64_t len); +API UTF8Iter IterateUTF8(char *data); +API UTF8Iter IterateUTF8(String string); +API bool IsUTF8ContinuationByte(char c); diff --git a/src/basic/math.cpp b/src/basic/math.cpp index 1bafff2..1c58ee8 100644 --- a/src/basic/math.cpp +++ b/src/basic/math.cpp @@ -1,41 +1,3 @@ -struct Vec2 { - float x; - float y; -}; - -union Vec4 { - struct { - float x; - float y; - float z; - float w; - }; - struct { - float r; - float g; - float b; - float a; - }; -}; - -union Vec3 { - struct { - float x; - float y; - float z; - }; - struct { - float r; - float g; - float b; - }; -}; - -struct Rect2 { - Vec2 min; - Vec2 max; -}; - Vec2 GetSize(Rect2 r) { Vec2 result = {r.max.x - r.min.x, r.max.y - r.min.y}; return result; diff --git a/src/basic/math_int.cpp b/src/basic/math_int.cpp index e95e88e..25fffcb 100644 --- a/src/basic/math_int.cpp +++ b/src/basic/math_int.cpp @@ -1,23 +1,3 @@ -struct Vec2I { - Int x; - Int y; -}; - -struct Rect2I { - Vec2I min; - Vec2I max; -}; - -union Color { - struct { - uint8_t r; - uint8_t g; - uint8_t b; - uint8_t a; - }; - uint32_t value; -}; - Vec2I GetSize(Rect2I r) { Vec2I result = {r.max.x - r.min.x, r.max.y - r.min.y}; return result; diff --git a/src/basic/stb_sprintf.h b/src/basic/stb_sprintf.h new file mode 100644 index 0000000..29f88b6 --- /dev/null +++ b/src/basic/stb_sprintf.h @@ -0,0 +1,1928 @@ +// 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 + +/* +Single file sprintf replacement. + +Originally written by Jeff Roberts at RAD Game Tools - 2015/10/20. +Hereby placed in public domain. + +This is a full sprintf replacement that supports everything that +the C runtime sprintfs support, including float/double, 64-bit integers, +hex floats, field parameters (%*.*d stuff), length reads backs, etc. + +Why would you need this if sprintf already exists? Well, first off, +it's *much* faster (see below). It's also much smaller than the CRT +versions code-space-wise. We've also added some simple improvements +that are super handy (commas in thousands, callbacks at buffer full, +for example). Finally, the format strings for MSVC and GCC differ +for 64-bit integers (among other small things), so this lets you use +the same format strings in cross platform code. + +It uses the standard single file trick of being both the header file +and the source itself. If you just include it normally, you just get +the header file function definitions. To get the code, you include +it from a C or C++ file and define STB_SPRINTF_IMPLEMENTATION first. + +It only uses va_args macros from the C runtime to do it's work. It +does cast doubles to S64s and shifts and divides U64s, which does +drag in CRT code on most platforms. + +It compiles to roughly 8K with float support, and 4K without. +As a comparison, when using MSVC static libs, calling sprintf drags +in 16K. + +API: +==== +int stbsp_sprintf( char * buf, char const * fmt, ... ) +int stbsp_snprintf( char * buf, int count, char const * fmt, ... ) + Convert an arg list into a buffer. stbsp_snprintf always returns + a zero-terminated string (unlike regular snprintf). + +int stbsp_vsprintf( char * buf, char const * fmt, va_list va ) +int stbsp_vsnprintf( char * buf, int count, char const * fmt, va_list va ) + Convert a va_list arg list into a buffer. stbsp_vsnprintf always returns + a zero-terminated string (unlike regular snprintf). + +int stbsp_vsprintfcb( STBSP_SPRINTFCB * callback, void * user, char * buf, char const * fmt, va_list va ) + typedef char * STBSP_SPRINTFCB( char const * buf, void * user, int len ); + Convert into a buffer, calling back every STB_SPRINTF_MIN chars. + Your callback can then copy the chars out, print them or whatever. + This function is actually the workhorse for everything else. + The buffer you pass in must hold at least STB_SPRINTF_MIN characters. + // you return the next buffer to use or 0 to stop converting + +void stbsp_set_separators( char comma, char period ) + Set the comma and period characters to use. + +FLOATS/DOUBLES: +=============== +This code uses a internal float->ascii conversion method that uses +doubles with error correction (double-doubles, for ~105 bits of +precision). This conversion is round-trip perfect - that is, an atof +of the values output here will give you the bit-exact double back. + +One difference is that our insignificant digits will be different than +with MSVC or GCC (but they don't match each other either). We also +don't attempt to find the minimum length matching float (pre-MSVC15 +doesn't either). + +If you don't need float or doubles at all, define STB_SPRINTF_NOFLOAT +and you'll save 4K of code space. + +64-BIT INTS: +============ +This library also supports 64-bit integers and you can use MSVC style or +GCC style indicators (%I64d or %lld). It supports the C99 specifiers +for size_t and ptr_diff_t (%jd %zd) as well. + +EXTRAS: +======= +Like some GCCs, for integers and floats, you can use a ' (single quote) +specifier and commas will be inserted on the thousands: "%'d" on 12345 +would print 12,345. + +For integers and floats, you can use a "$" specifier and the number +will be converted to float and then divided to get kilo, mega, giga or +tera and then printed, so "%$d" 1000 is "1.0 k", "%$.2d" 2536000 is +"2.53 M", etc. For byte values, use two $:s, like "%$$d" to turn +2536000 to "2.42 Mi". If you prefer JEDEC suffixes to SI ones, use three +$:s: "%$$$d" -> "2.42 M". To remove the space between the number and the +suffix, add "_" specifier: "%_$d" -> "2.53M". + +In addition to octal and hexadecimal conversions, you can print +integers in binary: "%b" for 256 would print 100. + +PERFORMANCE vs MSVC 2008 32-/64-bit (GCC is even slower than MSVC): +=================================================================== +"%d" across all 32-bit ints (4.8x/4.0x faster than 32-/64-bit MSVC) +"%24d" across all 32-bit ints (4.5x/4.2x faster) +"%x" across all 32-bit ints (4.5x/3.8x faster) +"%08x" across all 32-bit ints (4.3x/3.8x faster) +"%f" across e-10 to e+10 floats (7.3x/6.0x faster) +"%e" across e-10 to e+10 floats (8.1x/6.0x faster) +"%g" across e-10 to e+10 floats (10.0x/7.1x faster) +"%f" for values near e-300 (7.9x/6.5x faster) +"%f" for values near e+300 (10.0x/9.1x faster) +"%e" for values near e-300 (10.1x/7.0x faster) +"%e" for values near e+300 (9.2x/6.0x faster) +"%.320f" for values near e-300 (12.6x/11.2x faster) +"%a" for random values (8.6x/4.3x faster) +"%I64d" for 64-bits with 32-bit values (4.8x/3.4x faster) +"%I64d" for 64-bits > 32-bit values (4.9x/5.5x faster) +"%s%s%s" for 64 char strings (7.1x/7.3x faster) +"...512 char string..." ( 35.0x/32.5x faster!) +*/ + +#if defined(__clang__) + #if defined(__has_feature) && defined(__has_attribute) + #if __has_feature(address_sanitizer) + #if __has_attribute(__no_sanitize__) + #define STBSP__ASAN __attribute__((__no_sanitize__("address"))) + #elif __has_attribute(__no_sanitize_address__) + #define STBSP__ASAN __attribute__((__no_sanitize_address__)) + #elif __has_attribute(__no_address_safety_analysis__) + #define STBSP__ASAN __attribute__((__no_address_safety_analysis__)) + #endif + #endif + #endif +#elif defined(__GNUC__) && (__GNUC__ >= 5 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)) + #if defined(__SANITIZE_ADDRESS__) && __SANITIZE_ADDRESS__ + #define STBSP__ASAN __attribute__((__no_sanitize_address__)) + #endif +#endif + +#ifndef STBSP__ASAN +#define STBSP__ASAN +#endif + +#ifdef STB_SPRINTF_STATIC +#define STBSP__PUBLICDEC static +#define STBSP__PUBLICDEF static STBSP__ASAN +#else +#ifdef __cplusplus +#define STBSP__PUBLICDEC extern "C" +#define STBSP__PUBLICDEF extern "C" STBSP__ASAN +#else +#define STBSP__PUBLICDEC extern +#define STBSP__PUBLICDEF STBSP__ASAN +#endif +#endif + +#if defined(__has_attribute) + #if __has_attribute(format) + #define STBSP__ATTRIBUTE_FORMAT(fmt,va) __attribute__((format(printf,fmt,va))) + #endif +#endif + +#ifndef STBSP__ATTRIBUTE_FORMAT +#define STBSP__ATTRIBUTE_FORMAT(fmt,va) +#endif + +#ifdef _MSC_VER +#define STBSP__NOTUSED(v) (void)(v) +#else +#define STBSP__NOTUSED(v) (void)sizeof(v) +#endif + +#include // for va_arg(), va_list() +#include // size_t, ptrdiff_t + +#ifndef STB_SPRINTF_MIN +#define STB_SPRINTF_MIN 512 // how many characters per callback +#endif +typedef char *STBSP_SPRINTFCB(const char *buf, void *user, int len); + +#ifndef STB_SPRINTF_DECORATE +#define STB_SPRINTF_DECORATE(name) stbsp_##name // define this before including if you want to change the names +#endif + +STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(vsprintf)(char *buf, char const *fmt, va_list va); +STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(vsnprintf)(char *buf, int count, char const *fmt, va_list va); +STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(sprintf)(char *buf, char const *fmt, ...) STBSP__ATTRIBUTE_FORMAT(2,3); +STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(snprintf)(char *buf, int count, char const *fmt, ...) STBSP__ATTRIBUTE_FORMAT(3,4); + +STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(vsprintfcb)(STBSP_SPRINTFCB *callback, void *user, char *buf, char const *fmt, va_list va); +STBSP__PUBLICDEC void STB_SPRINTF_DECORATE(set_separators)(char comma, char period); + +#endif // STB_SPRINTF_H_INCLUDE + +#ifdef STB_SPRINTF_IMPLEMENTATION + +#define stbsp__uint32 unsigned int +#define stbsp__int32 signed int + +#ifdef _MSC_VER +#define stbsp__uint64 unsigned __int64 +#define stbsp__int64 signed __int64 +#else +#define stbsp__uint64 unsigned long long +#define stbsp__int64 signed long long +#endif +#define stbsp__uint16 unsigned short + +#ifndef stbsp__uintptr +#if defined(__ppc64__) || defined(__powerpc64__) || defined(__aarch64__) || defined(_M_X64) || defined(__x86_64__) || defined(__x86_64) || defined(__s390x__) +#define stbsp__uintptr stbsp__uint64 +#else +#define stbsp__uintptr stbsp__uint32 +#endif +#endif + +#ifndef STB_SPRINTF_MSVC_MODE // used for MSVC2013 and earlier (MSVC2015 matches GCC) +#if defined(_MSC_VER) && (_MSC_VER < 1900) +#define STB_SPRINTF_MSVC_MODE +#endif +#endif + +#ifdef STB_SPRINTF_NOUNALIGNED // define this before inclusion to force stbsp_sprintf to always use aligned accesses +#define STBSP__UNALIGNED(code) +#else +#define STBSP__UNALIGNED(code) code +#endif + +#ifndef STB_SPRINTF_NOFLOAT +// internal float utility functions +static stbsp__int32 stbsp__real_to_str(char const **start, stbsp__uint32 *len, char *out, stbsp__int32 *decimal_pos, double value, stbsp__uint32 frac_digits); +static stbsp__int32 stbsp__real_to_parts(stbsp__int64 *bits, stbsp__int32 *expo, double value); +#define STBSP__SPECIAL 0x7000 +#endif + +static char stbsp__period = '.'; +static char stbsp__comma = ','; +static struct +{ + short temp; // force next field to be 2-byte aligned + char pair[201]; +} stbsp__digitpair = +{ + 0, + "00010203040506070809101112131415161718192021222324" + "25262728293031323334353637383940414243444546474849" + "50515253545556575859606162636465666768697071727374" + "75767778798081828384858687888990919293949596979899" +}; + +STBSP__PUBLICDEF void STB_SPRINTF_DECORATE(set_separators)(char pcomma, char pperiod) +{ + stbsp__period = pperiod; + stbsp__comma = pcomma; +} + +#define STBSP__LEFTJUST 1 +#define STBSP__LEADINGPLUS 2 +#define STBSP__LEADINGSPACE 4 +#define STBSP__LEADING_0X 8 +#define STBSP__LEADINGZERO 16 +#define STBSP__INTMAX 32 +#define STBSP__TRIPLET_COMMA 64 +#define STBSP__NEGATIVE 128 +#define STBSP__METRIC_SUFFIX 256 +#define STBSP__HALFWIDTH 512 +#define STBSP__METRIC_NOSPACE 1024 +#define STBSP__METRIC_1024 2048 +#define STBSP__METRIC_JEDEC 4096 + +static void stbsp__lead_sign(stbsp__uint32 fl, char *sign) +{ + sign[0] = 0; + if (fl & STBSP__NEGATIVE) { + sign[0] = 1; + sign[1] = '-'; + } else if (fl & STBSP__LEADINGSPACE) { + sign[0] = 1; + sign[1] = ' '; + } else if (fl & STBSP__LEADINGPLUS) { + sign[0] = 1; + sign[1] = '+'; + } +} + +static STBSP__ASAN stbsp__uint32 stbsp__strlen_limited(char const *s, stbsp__uint32 limit) +{ + char const * sn = s; + + // get up to 4-byte alignment + for (;;) { + if (((stbsp__uintptr)sn & 3) == 0) + break; + + if (!limit || *sn == 0) + return (stbsp__uint32)(sn - s); + + ++sn; + --limit; + } + + // scan over 4 bytes at a time to find terminating 0 + // this will intentionally scan up to 3 bytes past the end of buffers, + // but becase it works 4B aligned, it will never cross page boundaries + // (hence the STBSP__ASAN markup; the over-read here is intentional + // and harmless) + while (limit >= 4) { + stbsp__uint32 v = *(stbsp__uint32 *)sn; + // bit hack to find if there's a 0 byte in there + if ((v - 0x01010101) & (~v) & 0x80808080UL) + break; + + sn += 4; + limit -= 4; + } + + // handle the last few characters to find actual size + while (limit && *sn) { + ++sn; + --limit; + } + + return (stbsp__uint32)(sn - s); +} + +STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(vsprintfcb)(STBSP_SPRINTFCB *callback, void *user, char *buf, char const *fmt, va_list va) +{ + static char hex[] = "0123456789abcdefxp"; + static char hexu[] = "0123456789ABCDEFXP"; + char *bf; + char const *f; + int tlen = 0; + + bf = buf; + f = fmt; + for (;;) { + stbsp__int32 fw, pr, tz; + stbsp__uint32 fl; + + // macros for the callback buffer stuff + #define stbsp__chk_cb_bufL(bytes) \ + { \ + int len = (int)(bf - buf); \ + if ((len + (bytes)) >= STB_SPRINTF_MIN) { \ + tlen += len; \ + if (0 == (bf = buf = callback(buf, user, len))) \ + goto done; \ + } \ + } + #define stbsp__chk_cb_buf(bytes) \ + { \ + if (callback) { \ + stbsp__chk_cb_bufL(bytes); \ + } \ + } + #define stbsp__flush_cb() \ + { \ + stbsp__chk_cb_bufL(STB_SPRINTF_MIN - 1); \ + } // flush if there is even one byte in the buffer + #define stbsp__cb_buf_clamp(cl, v) \ + cl = v; \ + if (callback) { \ + int lg = STB_SPRINTF_MIN - (int)(bf - buf); \ + if (cl > lg) \ + cl = lg; \ + } + + // fast copy everything up to the next % (or end of string) + for (;;) { + while (((stbsp__uintptr)f) & 3) { + schk1: + if (f[0] == '%') + goto scandd; + schk2: + if (f[0] == 0) + goto endfmt; + stbsp__chk_cb_buf(1); + *bf++ = f[0]; + ++f; + } + for (;;) { + // Check if the next 4 bytes contain %(0x25) or end of string. + // Using the 'hasless' trick: + // https://graphics.stanford.edu/~seander/bithacks.html#HasLessInWord + stbsp__uint32 v, c; + v = *(stbsp__uint32 *)f; + c = (~v) & 0x80808080; + if (((v ^ 0x25252525) - 0x01010101) & c) + goto schk1; + if ((v - 0x01010101) & c) + goto schk2; + if (callback) + if ((STB_SPRINTF_MIN - (int)(bf - buf)) < 4) + goto schk1; + #ifdef STB_SPRINTF_NOUNALIGNED + if(((stbsp__uintptr)bf) & 3) { + bf[0] = f[0]; + bf[1] = f[1]; + bf[2] = f[2]; + bf[3] = f[3]; + } else + #endif + { + *(stbsp__uint32 *)bf = v; + } + bf += 4; + f += 4; + } + } + scandd: + + ++f; + + // ok, we have a percent, read the modifiers first + fw = 0; + pr = -1; + fl = 0; + tz = 0; + + // flags + for (;;) { + switch (f[0]) { + // if we have left justify + case '-': + fl |= STBSP__LEFTJUST; + ++f; + continue; + // if we have leading plus + case '+': + fl |= STBSP__LEADINGPLUS; + ++f; + continue; + // if we have leading space + case ' ': + fl |= STBSP__LEADINGSPACE; + ++f; + continue; + // if we have leading 0x + case '#': + fl |= STBSP__LEADING_0X; + ++f; + continue; + // if we have thousand commas + case '\'': + fl |= STBSP__TRIPLET_COMMA; + ++f; + continue; + // if we have kilo marker (none->kilo->kibi->jedec) + case '$': + if (fl & STBSP__METRIC_SUFFIX) { + if (fl & STBSP__METRIC_1024) { + fl |= STBSP__METRIC_JEDEC; + } else { + fl |= STBSP__METRIC_1024; + } + } else { + fl |= STBSP__METRIC_SUFFIX; + } + ++f; + continue; + // if we don't want space between metric suffix and number + case '_': + fl |= STBSP__METRIC_NOSPACE; + ++f; + continue; + // if we have leading zero + case '0': + fl |= STBSP__LEADINGZERO; + ++f; + goto flags_done; + default: goto flags_done; + } + } + flags_done: + + // get the field width + if (f[0] == '*') { + fw = va_arg(va, stbsp__uint32); + ++f; + } else { + while ((f[0] >= '0') && (f[0] <= '9')) { + fw = fw * 10 + f[0] - '0'; + f++; + } + } + // get the precision + if (f[0] == '.') { + ++f; + if (f[0] == '*') { + pr = va_arg(va, stbsp__uint32); + ++f; + } else { + pr = 0; + while ((f[0] >= '0') && (f[0] <= '9')) { + pr = pr * 10 + f[0] - '0'; + f++; + } + } + } + + // handle integer size overrides + switch (f[0]) { + // are we halfwidth? + case 'h': + fl |= STBSP__HALFWIDTH; + ++f; + if (f[0] == 'h') + ++f; // QUARTERWIDTH + break; + // are we 64-bit (unix style) + case 'l': + fl |= ((sizeof(long) == 8) ? STBSP__INTMAX : 0); + ++f; + if (f[0] == 'l') { + fl |= STBSP__INTMAX; + ++f; + } + break; + // are we 64-bit on intmax? (c99) + case 'j': + fl |= (sizeof(size_t) == 8) ? STBSP__INTMAX : 0; + ++f; + break; + // are we 64-bit on size_t or ptrdiff_t? (c99) + case 'z': + fl |= (sizeof(ptrdiff_t) == 8) ? STBSP__INTMAX : 0; + ++f; + break; + case 't': + fl |= (sizeof(ptrdiff_t) == 8) ? STBSP__INTMAX : 0; + ++f; + break; + // are we 64-bit (msft style) + case 'I': + if ((f[1] == '6') && (f[2] == '4')) { + fl |= STBSP__INTMAX; + f += 3; + } else if ((f[1] == '3') && (f[2] == '2')) { + f += 3; + } else { + fl |= ((sizeof(void *) == 8) ? STBSP__INTMAX : 0); + ++f; + } + break; + default: break; + } + + // handle each replacement + switch (f[0]) { + #define STBSP__NUMSZ 512 // big enough for e308 (with commas) or e-307 + char num[STBSP__NUMSZ]; + char lead[8]; + char tail[8]; + struct stb__string { + char *data; + int64_t len; + } S; + + 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; + + case 'S': + // get the string + S = va_arg(va, struct stb__string); + if (S.data == 0) { + S.data = (char *)"null"; + S.len = 4; + } + l = (unsigned int)S.len; + lead[0] = 0; + tail[0] = 0; + pr = 0; + dp = 0; + cs = 0; + // copy the string in + goto scopy; + + + case 's': + // get the string + s = va_arg(va, char *); + if (s == 0) + s = (char *)"null"; + // get the length, limited to desired precision + // always limit to ~0u chars since our counts are 32b + l = stbsp__strlen_limited(s, (pr >= 0) ? pr : ~0u); + lead[0] = 0; + tail[0] = 0; + pr = 0; + dp = 0; + cs = 0; + // copy the string in + goto scopy; + + case 'c': // char + // get the character + s = num + STBSP__NUMSZ - 1; + *s = (char)va_arg(va, int); + l = 1; + lead[0] = 0; + tail[0] = 0; + pr = 0; + dp = 0; + cs = 0; + goto scopy; + + case 'n': // weird write-bytes specifier + { + int *d = va_arg(va, int *); + *d = tlen + (int)(bf - buf); + } break; + +#ifdef STB_SPRINTF_NOFLOAT + case 'A': // float + case 'a': // hex float + case 'G': // float + case 'g': // float + case 'E': // float + case 'e': // float + case 'f': // float + va_arg(va, double); // eat it + s = (char *)"No float"; + l = 8; + lead[0] = 0; + tail[0] = 0; + pr = 0; + cs = 0; + STBSP__NOTUSED(dp); + goto scopy; +#else + case 'A': // hex float + case 'a': // hex float + h = (f[0] == 'A') ? hexu : hex; + fv = va_arg(va, double); + if (pr == -1) + pr = 6; // default is 6 + // read the double into a string + if (stbsp__real_to_parts((stbsp__int64 *)&n64, &dp, fv)) + fl |= STBSP__NEGATIVE; + + s = num + 64; + + stbsp__lead_sign(fl, lead); + + if (dp == -1023) + dp = (n64) ? -1022 : 0; + else + n64 |= (((stbsp__uint64)1) << 52); + n64 <<= (64 - 56); + if (pr < 15) + n64 += ((((stbsp__uint64)8) << 56) >> (pr * 4)); +// add leading chars + +#ifdef STB_SPRINTF_MSVC_MODE + *s++ = '0'; + *s++ = 'x'; +#else + lead[1 + lead[0]] = '0'; + lead[2 + lead[0]] = 'x'; + lead[0] += 2; +#endif + *s++ = h[(n64 >> 60) & 15]; + n64 <<= 4; + if (pr) + *s++ = stbsp__period; + sn = s; + + // print the bits + n = pr; + if (n > 13) + n = 13; + if (pr > (stbsp__int32)n) + tz = pr - n; + pr = 0; + while (n--) { + *s++ = h[(n64 >> 60) & 15]; + n64 <<= 4; + } + + // print the expo + tail[1] = h[17]; + if (dp < 0) { + tail[2] = '-'; + dp = -dp; + } else + tail[2] = '+'; + n = (dp >= 1000) ? 6 : ((dp >= 100) ? 5 : ((dp >= 10) ? 4 : 3)); + tail[0] = (char)n; + for (;;) { + tail[n] = '0' + dp % 10; + if (n <= 3) + break; + --n; + dp /= 10; + } + + dp = (int)(s - sn); + l = (int)(s - (num + 64)); + s = num + 64; + cs = 1 + (3 << 24); + goto scopy; + + case 'G': // float + case 'g': // float + h = (f[0] == 'G') ? hexu : hex; + fv = va_arg(va, double); + if (pr == -1) + pr = 6; + else if (pr == 0) + pr = 1; // default is 6 + // read the double into a string + if (stbsp__real_to_str(&sn, &l, num, &dp, fv, (pr - 1) | 0x80000000)) + fl |= STBSP__NEGATIVE; + + // clamp the precision and delete extra zeros after clamp + n = pr; + if (l > (stbsp__uint32)pr) + l = pr; + while ((l > 1) && (pr) && (sn[l - 1] == '0')) { + --pr; + --l; + } + + // should we use %e + if ((dp <= -4) || (dp > (stbsp__int32)n)) { + if (pr > (stbsp__int32)l) + pr = l - 1; + else if (pr) + --pr; // when using %e, there is one digit before the decimal + goto doexpfromg; + } + // this is the insane action to get the pr to match %g semantics for %f + if (dp > 0) { + pr = (dp < (stbsp__int32)l) ? l - dp : 0; + } else { + pr = -dp + ((pr > (stbsp__int32)l) ? (stbsp__int32) l : pr); + } + goto dofloatfromg; + + case 'E': // float + case 'e': // float + h = (f[0] == 'E') ? hexu : hex; + fv = va_arg(va, double); + if (pr == -1) + pr = 6; // default is 6 + // read the double into a string + if (stbsp__real_to_str(&sn, &l, num, &dp, fv, pr | 0x80000000)) + fl |= STBSP__NEGATIVE; + doexpfromg: + tail[0] = 0; + stbsp__lead_sign(fl, lead); + if (dp == STBSP__SPECIAL) { + s = (char *)sn; + cs = 0; + pr = 0; + goto scopy; + } + s = num + 64; + // handle leading chars + *s++ = sn[0]; + + if (pr) + *s++ = stbsp__period; + + // handle after decimal + if ((l - 1) > (stbsp__uint32)pr) + l = pr + 1; + for (n = 1; n < l; n++) + *s++ = sn[n]; + // trailing zeros + tz = pr - (l - 1); + pr = 0; + // dump expo + tail[1] = h[0xe]; + dp -= 1; + if (dp < 0) { + tail[2] = '-'; + dp = -dp; + } else + tail[2] = '+'; +#ifdef STB_SPRINTF_MSVC_MODE + n = 5; +#else + n = (dp >= 100) ? 5 : 4; +#endif + tail[0] = (char)n; + for (;;) { + tail[n] = '0' + dp % 10; + if (n <= 3) + break; + --n; + dp /= 10; + } + cs = 1 + (3 << 24); // how many tens + goto flt_lead; + + case 'f': // float + fv = va_arg(va, double); + doafloat: + // do kilos + if (fl & STBSP__METRIC_SUFFIX) { + double divisor; + divisor = 1000.0f; + if (fl & STBSP__METRIC_1024) + divisor = 1024.0; + while (fl < 0x4000000) { + if ((fv < divisor) && (fv > -divisor)) + break; + fv /= divisor; + fl += 0x1000000; + } + } + if (pr == -1) + pr = 6; // default is 6 + // read the double into a string + if (stbsp__real_to_str(&sn, &l, num, &dp, fv, pr)) + fl |= STBSP__NEGATIVE; + dofloatfromg: + tail[0] = 0; + stbsp__lead_sign(fl, lead); + if (dp == STBSP__SPECIAL) { + s = (char *)sn; + cs = 0; + pr = 0; + goto scopy; + } + s = num + 64; + + // handle the three decimal varieties + if (dp <= 0) { + stbsp__int32 i; + // handle 0.000*000xxxx + *s++ = '0'; + if (pr) + *s++ = stbsp__period; + n = -dp; + if ((stbsp__int32)n > pr) + n = pr; + i = n; + while (i) { + if ((((stbsp__uintptr)s) & 3) == 0) + break; + *s++ = '0'; + --i; + } + while (i >= 4) { + *(stbsp__uint32 *)s = 0x30303030; + s += 4; + i -= 4; + } + while (i) { + *s++ = '0'; + --i; + } + if ((stbsp__int32)(l + n) > pr) + l = pr - n; + i = l; + while (i) { + *s++ = *sn++; + --i; + } + tz = pr - (n + l); + cs = 1 + (3 << 24); // how many tens did we write (for commas below) + } else { + cs = (fl & STBSP__TRIPLET_COMMA) ? ((600 - (stbsp__uint32)dp) % 3) : 0; + if ((stbsp__uint32)dp >= l) { + // handle xxxx000*000.0 + n = 0; + for (;;) { + if ((fl & STBSP__TRIPLET_COMMA) && (++cs == 4)) { + cs = 0; + *s++ = stbsp__comma; + } else { + *s++ = sn[n]; + ++n; + if (n >= l) + break; + } + } + if (n < (stbsp__uint32)dp) { + n = dp - n; + if ((fl & STBSP__TRIPLET_COMMA) == 0) { + while (n) { + if ((((stbsp__uintptr)s) & 3) == 0) + break; + *s++ = '0'; + --n; + } + while (n >= 4) { + *(stbsp__uint32 *)s = 0x30303030; + s += 4; + n -= 4; + } + } + while (n) { + if ((fl & STBSP__TRIPLET_COMMA) && (++cs == 4)) { + cs = 0; + *s++ = stbsp__comma; + } else { + *s++ = '0'; + --n; + } + } + } + cs = (int)(s - (num + 64)) + (3 << 24); // cs is how many tens + if (pr) { + *s++ = stbsp__period; + tz = pr; + } + } else { + // handle xxxxx.xxxx000*000 + n = 0; + for (;;) { + if ((fl & STBSP__TRIPLET_COMMA) && (++cs == 4)) { + cs = 0; + *s++ = stbsp__comma; + } else { + *s++ = sn[n]; + ++n; + if (n >= (stbsp__uint32)dp) + break; + } + } + cs = (int)(s - (num + 64)) + (3 << 24); // cs is how many tens + if (pr) + *s++ = stbsp__period; + if ((l - dp) > (stbsp__uint32)pr) + l = pr + dp; + while (n < l) { + *s++ = sn[n]; + ++n; + } + tz = pr - (l - dp); + } + } + pr = 0; + + // handle k,m,g,t + if (fl & STBSP__METRIC_SUFFIX) { + char idx; + idx = 1; + if (fl & STBSP__METRIC_NOSPACE) + idx = 0; + tail[0] = idx; + tail[1] = ' '; + { + if (fl >> 24) { // SI kilo is 'k', JEDEC and SI kibits are 'K'. + if (fl & STBSP__METRIC_1024) + tail[idx + 1] = "_KMGT"[fl >> 24]; + else + tail[idx + 1] = "_kMGT"[fl >> 24]; + idx++; + // If printing kibits and not in jedec, add the 'i'. + if (fl & STBSP__METRIC_1024 && !(fl & STBSP__METRIC_JEDEC)) { + tail[idx + 1] = 'i'; + idx++; + } + tail[0] = idx; + } + } + }; + + flt_lead: + // get the length that we copied + l = (stbsp__uint32)(s - (num + 64)); + s = num + 64; + goto scopy; +#endif + + case 'B': // upper binary + case 'b': // lower binary + h = (f[0] == 'B') ? hexu : hex; + lead[0] = 0; + if (fl & STBSP__LEADING_0X) { + lead[0] = 2; + lead[1] = '0'; + lead[2] = h[0xb]; + } + l = (8 << 4) | (1 << 8); + goto radixnum; + + case 'o': // octal + h = hexu; + lead[0] = 0; + if (fl & STBSP__LEADING_0X) { + lead[0] = 1; + lead[1] = '0'; + } + l = (3 << 4) | (3 << 8); + goto radixnum; + + case 'p': // pointer + fl |= (sizeof(void *) == 8) ? STBSP__INTMAX : 0; + pr = sizeof(void *) * 2; + fl &= ~STBSP__LEADINGZERO; // 'p' only prints the pointer with zeros + // fall through - to X + + case 'X': // upper hex + case 'x': // lower hex + h = (f[0] == 'X') ? hexu : hex; + l = (4 << 4) | (4 << 8); + lead[0] = 0; + if (fl & STBSP__LEADING_0X) { + lead[0] = 2; + lead[1] = '0'; + lead[2] = h[16]; + } + radixnum: + // get the number + if (fl & STBSP__INTMAX) + n64 = va_arg(va, stbsp__uint64); + else + n64 = va_arg(va, stbsp__uint32); + + s = num + STBSP__NUMSZ; + dp = 0; + // clear tail, and clear leading if value is zero + tail[0] = 0; + if (n64 == 0) { + lead[0] = 0; + if (pr == 0) { + l = 0; + cs = 0; + goto scopy; + } + } + // convert to string + for (;;) { + *--s = h[n64 & ((1 << (l >> 8)) - 1)]; + n64 >>= (l >> 8); + if (!((n64) || ((stbsp__int32)((num + STBSP__NUMSZ) - s) < pr))) + break; + if (fl & STBSP__TRIPLET_COMMA) { + ++l; + if ((l & 15) == ((l >> 4) & 15)) { + l &= ~15; + *--s = stbsp__comma; + } + } + }; + // get the tens and the comma pos + cs = (stbsp__uint32)((num + STBSP__NUMSZ) - s) + ((((l >> 4) & 15)) << 24); + // get the length that we copied + l = (stbsp__uint32)((num + STBSP__NUMSZ) - s); + // copy it + goto scopy; + + case 'u': // unsigned + case 'i': + case 'd': // integer + // get the integer and abs it + if (fl & STBSP__INTMAX) { + stbsp__int64 i64 = va_arg(va, stbsp__int64); + n64 = (stbsp__uint64)i64; + if ((f[0] != 'u') && (i64 < 0)) { + n64 = (stbsp__uint64)-i64; + fl |= STBSP__NEGATIVE; + } + } else { + stbsp__int32 i = va_arg(va, stbsp__int32); + n64 = (stbsp__uint32)i; + if ((f[0] != 'u') && (i < 0)) { + n64 = (stbsp__uint32)-i; + fl |= STBSP__NEGATIVE; + } + } + +#ifndef STB_SPRINTF_NOFLOAT + if (fl & STBSP__METRIC_SUFFIX) { + if (n64 < 1024) + pr = 0; + else if (pr == -1) + pr = 1; + fv = (double)(stbsp__int64)n64; + goto doafloat; + } +#endif + + // convert to string + s = num + STBSP__NUMSZ; + l = 0; + + for (;;) { + // do in 32-bit chunks (avoid lots of 64-bit divides even with constant denominators) + char *o = s - 8; + if (n64 >= 100000000) { + n = (stbsp__uint32)(n64 % 100000000); + n64 /= 100000000; + } else { + n = (stbsp__uint32)n64; + n64 = 0; + } + if ((fl & STBSP__TRIPLET_COMMA) == 0) { + do { + s -= 2; + *(stbsp__uint16 *)s = *(stbsp__uint16 *)&stbsp__digitpair.pair[(n % 100) * 2]; + n /= 100; + } while (n); + } + while (n) { + if ((fl & STBSP__TRIPLET_COMMA) && (l++ == 3)) { + l = 0; + *--s = stbsp__comma; + --o; + } else { + *--s = (char)(n % 10) + '0'; + n /= 10; + } + } + if (n64 == 0) { + if ((s[0] == '0') && (s != (num + STBSP__NUMSZ))) + ++s; + break; + } + while (s != o) + if ((fl & STBSP__TRIPLET_COMMA) && (l++ == 3)) { + l = 0; + *--s = stbsp__comma; + --o; + } else { + *--s = '0'; + } + } + + tail[0] = 0; + stbsp__lead_sign(fl, lead); + + // get the length that we copied + l = (stbsp__uint32)((num + STBSP__NUMSZ) - s); + if (l == 0) { + *--s = '0'; + l = 1; + } + cs = l + (3 << 24); + if (pr < 0) + pr = 0; + + scopy: + // get fw=leading/trailing space, pr=leading zeros + if (pr < (stbsp__int32)l) + pr = l; + n = pr + lead[0] + tail[0] + tz; + if (fw < (stbsp__int32)n) + fw = n; + fw -= n; + pr -= l; + + // handle right justify and leading zeros + if ((fl & STBSP__LEFTJUST) == 0) { + if (fl & STBSP__LEADINGZERO) // if leading zeros, everything is in pr + { + pr = (fw > pr) ? fw : pr; + fw = 0; + } else { + fl &= ~STBSP__TRIPLET_COMMA; // if no leading zeros, then no commas + } + } + + // copy the spaces and/or zeros + if (fw + pr) { + stbsp__int32 i; + stbsp__uint32 c; + + // copy leading spaces (or when doing %8.4d stuff) + if ((fl & STBSP__LEFTJUST) == 0) + while (fw > 0) { + stbsp__cb_buf_clamp(i, fw); + fw -= i; + while (i) { + if ((((stbsp__uintptr)bf) & 3) == 0) + break; + *bf++ = ' '; + --i; + } + while (i >= 4) { + *(stbsp__uint32 *)bf = 0x20202020; + bf += 4; + i -= 4; + } + while (i) { + *bf++ = ' '; + --i; + } + stbsp__chk_cb_buf(1); + } + + // copy leader + sn = lead + 1; + while (lead[0]) { + stbsp__cb_buf_clamp(i, lead[0]); + lead[0] -= (char)i; + while (i) { + *bf++ = *sn++; + --i; + } + stbsp__chk_cb_buf(1); + } + + // copy leading zeros + c = cs >> 24; + cs &= 0xffffff; + cs = (fl & STBSP__TRIPLET_COMMA) ? ((stbsp__uint32)(c - ((pr + cs) % (c + 1)))) : 0; + while (pr > 0) { + stbsp__cb_buf_clamp(i, pr); + pr -= i; + if ((fl & STBSP__TRIPLET_COMMA) == 0) { + while (i) { + if ((((stbsp__uintptr)bf) & 3) == 0) + break; + *bf++ = '0'; + --i; + } + while (i >= 4) { + *(stbsp__uint32 *)bf = 0x30303030; + bf += 4; + i -= 4; + } + } + while (i) { + if ((fl & STBSP__TRIPLET_COMMA) && (cs++ == c)) { + cs = 0; + *bf++ = stbsp__comma; + } else + *bf++ = '0'; + --i; + } + stbsp__chk_cb_buf(1); + } + } + + // copy leader if there is still one + sn = lead + 1; + while (lead[0]) { + stbsp__int32 i; + stbsp__cb_buf_clamp(i, lead[0]); + lead[0] -= (char)i; + while (i) { + *bf++ = *sn++; + --i; + } + stbsp__chk_cb_buf(1); + } + + // copy the string + n = l; + while (n) { + stbsp__int32 i; + stbsp__cb_buf_clamp(i, n); + n -= i; + STBSP__UNALIGNED(while (i >= 4) { + *(stbsp__uint32 volatile *)bf = *(stbsp__uint32 volatile *)s; + bf += 4; + s += 4; + i -= 4; + }) + while (i) { + *bf++ = *s++; + --i; + } + stbsp__chk_cb_buf(1); + } + + // copy trailing zeros + while (tz) { + stbsp__int32 i; + stbsp__cb_buf_clamp(i, tz); + tz -= i; + while (i) { + if ((((stbsp__uintptr)bf) & 3) == 0) + break; + *bf++ = '0'; + --i; + } + while (i >= 4) { + *(stbsp__uint32 *)bf = 0x30303030; + bf += 4; + i -= 4; + } + while (i) { + *bf++ = '0'; + --i; + } + stbsp__chk_cb_buf(1); + } + + // copy tail if there is one + sn = tail + 1; + while (tail[0]) { + stbsp__int32 i; + stbsp__cb_buf_clamp(i, tail[0]); + tail[0] -= (char)i; + while (i) { + *bf++ = *sn++; + --i; + } + stbsp__chk_cb_buf(1); + } + + // handle the left justify + if (fl & STBSP__LEFTJUST) + if (fw > 0) { + while (fw) { + stbsp__int32 i; + stbsp__cb_buf_clamp(i, fw); + fw -= i; + while (i) { + if ((((stbsp__uintptr)bf) & 3) == 0) + break; + *bf++ = ' '; + --i; + } + while (i >= 4) { + *(stbsp__uint32 *)bf = 0x20202020; + bf += 4; + i -= 4; + } + while (i--) + *bf++ = ' '; + stbsp__chk_cb_buf(1); + } + } + break; + + default: // unknown, just copy code + s = num + STBSP__NUMSZ - 1; + *s = f[0]; + l = 1; + fw = fl = 0; + lead[0] = 0; + tail[0] = 0; + pr = 0; + dp = 0; + cs = 0; + goto scopy; + } + ++f; + } +endfmt: + + if (!callback) + *bf = 0; + else + stbsp__flush_cb(); + +done: + return tlen + (int)(bf - buf); +} + +// cleanup +#undef STBSP__LEFTJUST +#undef STBSP__LEADINGPLUS +#undef STBSP__LEADINGSPACE +#undef STBSP__LEADING_0X +#undef STBSP__LEADINGZERO +#undef STBSP__INTMAX +#undef STBSP__TRIPLET_COMMA +#undef STBSP__NEGATIVE +#undef STBSP__METRIC_SUFFIX +#undef STBSP__NUMSZ +#undef stbsp__chk_cb_bufL +#undef stbsp__chk_cb_buf +#undef stbsp__flush_cb +#undef stbsp__cb_buf_clamp + +// ============================================================================ +// wrapper functions + +STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(sprintf)(char *buf, char const *fmt, ...) +{ + int result; + va_list va; + va_start(va, fmt); + result = STB_SPRINTF_DECORATE(vsprintfcb)(0, 0, buf, fmt, va); + va_end(va); + return result; +} + +typedef struct stbsp__context { + char *buf; + int count; + int length; + char tmp[STB_SPRINTF_MIN]; +} stbsp__context; + +static char *stbsp__clamp_callback(const char *buf, void *user, int len) +{ + stbsp__context *c = (stbsp__context *)user; + c->length += len; + + if (len > c->count) + len = c->count; + + if (len) { + if (buf != c->buf) { + const char *s, *se; + char *d; + d = c->buf; + s = buf; + se = buf + len; + do { + *d++ = *s++; + } while (s < se); + } + c->buf += len; + c->count -= len; + } + + if (c->count <= 0) + return c->tmp; + return (c->count >= STB_SPRINTF_MIN) ? c->buf : c->tmp; // go direct into buffer if you can +} + +static char * stbsp__count_clamp_callback( const char * buf, void * user, int len ) +{ + stbsp__context * c = (stbsp__context*)user; + (void) sizeof(buf); + + c->length += len; + return c->tmp; // go direct into buffer if you can +} + +STBSP__PUBLICDEF int STB_SPRINTF_DECORATE( vsnprintf )( char * buf, int count, char const * fmt, va_list va ) +{ + stbsp__context c; + + if ( (count == 0) && !buf ) + { + c.length = 0; + + STB_SPRINTF_DECORATE( vsprintfcb )( stbsp__count_clamp_callback, &c, c.tmp, fmt, va ); + } + else + { + int l; + + c.buf = buf; + c.count = count; + c.length = 0; + + STB_SPRINTF_DECORATE( vsprintfcb )( stbsp__clamp_callback, &c, stbsp__clamp_callback(0,&c,0), fmt, va ); + + // zero-terminate + l = (int)( c.buf - buf ); + if ( l >= count ) // should never be greater, only equal (or less) than count + l = count - 1; + buf[l] = 0; + } + + return c.length; +} + +STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(snprintf)(char *buf, int count, char const *fmt, ...) +{ + int result; + va_list va; + va_start(va, fmt); + + result = STB_SPRINTF_DECORATE(vsnprintf)(buf, count, fmt, va); + va_end(va); + + return result; +} + +STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(vsprintf)(char *buf, char const *fmt, va_list va) +{ + return STB_SPRINTF_DECORATE(vsprintfcb)(0, 0, buf, fmt, va); +} + +// ======================================================================= +// low level float utility functions + +#ifndef STB_SPRINTF_NOFLOAT + +// copies d to bits w/ strict aliasing (this compiles to nothing on /Ox) +#define STBSP__COPYFP(dest, src) \ + { \ + int cn; \ + for (cn = 0; cn < 8; cn++) \ + ((char *)&dest)[cn] = ((char *)&src)[cn]; \ + } + +// get float info +static stbsp__int32 stbsp__real_to_parts(stbsp__int64 *bits, stbsp__int32 *expo, double value) +{ + double d; + stbsp__int64 b = 0; + + // load value and round at the frac_digits + d = value; + + STBSP__COPYFP(b, d); + + *bits = b & ((((stbsp__uint64)1) << 52) - 1); + *expo = (stbsp__int32)(((b >> 52) & 2047) - 1023); + + return (stbsp__int32)((stbsp__uint64) b >> 63); +} + +static double const stbsp__bot[23] = { + 1e+000, 1e+001, 1e+002, 1e+003, 1e+004, 1e+005, 1e+006, 1e+007, 1e+008, 1e+009, 1e+010, 1e+011, + 1e+012, 1e+013, 1e+014, 1e+015, 1e+016, 1e+017, 1e+018, 1e+019, 1e+020, 1e+021, 1e+022 +}; +static double const stbsp__negbot[22] = { + 1e-001, 1e-002, 1e-003, 1e-004, 1e-005, 1e-006, 1e-007, 1e-008, 1e-009, 1e-010, 1e-011, + 1e-012, 1e-013, 1e-014, 1e-015, 1e-016, 1e-017, 1e-018, 1e-019, 1e-020, 1e-021, 1e-022 +}; +static double const stbsp__negboterr[22] = { + -5.551115123125783e-018, -2.0816681711721684e-019, -2.0816681711721686e-020, -4.7921736023859299e-021, -8.1803053914031305e-022, 4.5251888174113741e-023, + 4.5251888174113739e-024, -2.0922560830128471e-025, -6.2281591457779853e-026, -3.6432197315497743e-027, 6.0503030718060191e-028, 2.0113352370744385e-029, + -3.0373745563400371e-030, 1.1806906454401013e-032, -7.7705399876661076e-032, 2.0902213275965398e-033, -7.1542424054621921e-034, -7.1542424054621926e-035, + 2.4754073164739869e-036, 5.4846728545790429e-037, 9.2462547772103625e-038, -4.8596774326570872e-039 +}; +static double const stbsp__top[13] = { + 1e+023, 1e+046, 1e+069, 1e+092, 1e+115, 1e+138, 1e+161, 1e+184, 1e+207, 1e+230, 1e+253, 1e+276, 1e+299 +}; +static double const stbsp__negtop[13] = { + 1e-023, 1e-046, 1e-069, 1e-092, 1e-115, 1e-138, 1e-161, 1e-184, 1e-207, 1e-230, 1e-253, 1e-276, 1e-299 +}; +static double const stbsp__toperr[13] = { + 8388608, + 6.8601809640529717e+028, + -7.253143638152921e+052, + -4.3377296974619174e+075, + -1.5559416129466825e+098, + -3.2841562489204913e+121, + -3.7745893248228135e+144, + -1.7356668416969134e+167, + -3.8893577551088374e+190, + -9.9566444326005119e+213, + 6.3641293062232429e+236, + -5.2069140800249813e+259, + -5.2504760255204387e+282 +}; +static double const stbsp__negtoperr[13] = { + 3.9565301985100693e-040, -2.299904345391321e-063, 3.6506201437945798e-086, 1.1875228833981544e-109, + -5.0644902316928607e-132, -6.7156837247865426e-155, -2.812077463003139e-178, -5.7778912386589953e-201, + 7.4997100559334532e-224, -4.6439668915134491e-247, -6.3691100762962136e-270, -9.436808465446358e-293, + 8.0970921678014997e-317 +}; + +#if defined(_MSC_VER) && (_MSC_VER <= 1200) +static stbsp__uint64 const stbsp__powten[20] = { + 1, + 10, + 100, + 1000, + 10000, + 100000, + 1000000, + 10000000, + 100000000, + 1000000000, + 10000000000, + 100000000000, + 1000000000000, + 10000000000000, + 100000000000000, + 1000000000000000, + 10000000000000000, + 100000000000000000, + 1000000000000000000, + 10000000000000000000U +}; +#define stbsp__tento19th ((stbsp__uint64)1000000000000000000) +#else +static stbsp__uint64 const stbsp__powten[20] = { + 1, + 10, + 100, + 1000, + 10000, + 100000, + 1000000, + 10000000, + 100000000, + 1000000000, + 10000000000ULL, + 100000000000ULL, + 1000000000000ULL, + 10000000000000ULL, + 100000000000000ULL, + 1000000000000000ULL, + 10000000000000000ULL, + 100000000000000000ULL, + 1000000000000000000ULL, + 10000000000000000000ULL +}; +#define stbsp__tento19th (1000000000000000000ULL) +#endif + +#define stbsp__ddmulthi(oh, ol, xh, yh) \ + { \ + double ahi = 0, alo, bhi = 0, blo; \ + stbsp__int64 bt; \ + oh = xh * yh; \ + STBSP__COPYFP(bt, xh); \ + bt &= ((~(stbsp__uint64)0) << 27); \ + STBSP__COPYFP(ahi, bt); \ + alo = xh - ahi; \ + STBSP__COPYFP(bt, yh); \ + bt &= ((~(stbsp__uint64)0) << 27); \ + STBSP__COPYFP(bhi, bt); \ + blo = yh - bhi; \ + ol = ((ahi * bhi - oh) + ahi * blo + alo * bhi) + alo * blo; \ + } + +#define stbsp__ddtoS64(ob, xh, xl) \ + { \ + double ahi = 0, alo, vh, t; \ + ob = (stbsp__int64)xh; \ + vh = (double)ob; \ + ahi = (xh - vh); \ + t = (ahi - xh); \ + alo = (xh - (ahi - t)) - (vh + t); \ + ob += (stbsp__int64)(ahi + alo + xl); \ + } + +#define stbsp__ddrenorm(oh, ol) \ + { \ + double s; \ + s = oh + ol; \ + ol = ol - (s - oh); \ + oh = s; \ + } + +#define stbsp__ddmultlo(oh, ol, xh, xl, yh, yl) ol = ol + (xh * yl + xl * yh); + +#define stbsp__ddmultlos(oh, ol, xh, yl) ol = ol + (xh * yl); + +static void stbsp__raise_to_power10(double *ohi, double *olo, double d, stbsp__int32 power) // power can be -323 to +350 +{ + double ph, pl; + if ((power >= 0) && (power <= 22)) { + stbsp__ddmulthi(ph, pl, d, stbsp__bot[power]); + } else { + stbsp__int32 e, et, eb; + double p2h, p2l; + + e = power; + if (power < 0) + e = -e; + et = (e * 0x2c9) >> 14; /* %23 */ + if (et > 13) + et = 13; + eb = e - (et * 23); + + ph = d; + pl = 0.0; + if (power < 0) { + if (eb) { + --eb; + stbsp__ddmulthi(ph, pl, d, stbsp__negbot[eb]); + stbsp__ddmultlos(ph, pl, d, stbsp__negboterr[eb]); + } + if (et) { + stbsp__ddrenorm(ph, pl); + --et; + stbsp__ddmulthi(p2h, p2l, ph, stbsp__negtop[et]); + stbsp__ddmultlo(p2h, p2l, ph, pl, stbsp__negtop[et], stbsp__negtoperr[et]); + ph = p2h; + pl = p2l; + } + } else { + if (eb) { + e = eb; + if (eb > 22) + eb = 22; + e -= eb; + stbsp__ddmulthi(ph, pl, d, stbsp__bot[eb]); + if (e) { + stbsp__ddrenorm(ph, pl); + stbsp__ddmulthi(p2h, p2l, ph, stbsp__bot[e]); + stbsp__ddmultlos(p2h, p2l, stbsp__bot[e], pl); + ph = p2h; + pl = p2l; + } + } + if (et) { + stbsp__ddrenorm(ph, pl); + --et; + stbsp__ddmulthi(p2h, p2l, ph, stbsp__top[et]); + stbsp__ddmultlo(p2h, p2l, ph, pl, stbsp__top[et], stbsp__toperr[et]); + ph = p2h; + pl = p2l; + } + } + } + stbsp__ddrenorm(ph, pl); + *ohi = ph; + *olo = pl; +} + +// given a float value, returns the significant bits in bits, and the position of the +// decimal point in decimal_pos. +/-INF and NAN are specified by special values +// returned in the decimal_pos parameter. +// frac_digits is absolute normally, but if you want from first significant digits (got %g and %e), or in 0x80000000 +static stbsp__int32 stbsp__real_to_str(char const **start, stbsp__uint32 *len, char *out, stbsp__int32 *decimal_pos, double value, stbsp__uint32 frac_digits) +{ + double d; + stbsp__int64 bits = 0; + stbsp__int32 expo, e, ng, tens; + + d = value; + STBSP__COPYFP(bits, d); + expo = (stbsp__int32)((bits >> 52) & 2047); + ng = (stbsp__int32)((stbsp__uint64) bits >> 63); + if (ng) + d = -d; + + if (expo == 2047) // is nan or inf? + { + *start = (bits & ((((stbsp__uint64)1) << 52) - 1)) ? "NaN" : "Inf"; + *decimal_pos = STBSP__SPECIAL; + *len = 3; + return ng; + } + + if (expo == 0) // is zero or denormal + { + if (((stbsp__uint64) bits << 1) == 0) // do zero + { + *decimal_pos = 1; + *start = out; + out[0] = '0'; + *len = 1; + return ng; + } + // find the right expo for denormals + { + stbsp__int64 v = ((stbsp__uint64)1) << 51; + while ((bits & v) == 0) { + --expo; + v >>= 1; + } + } + } + + // find the decimal exponent as well as the decimal bits of the value + { + double ph, pl; + + // log10 estimate - very specifically tweaked to hit or undershoot by no more than 1 of log10 of all expos 1..2046 + tens = expo - 1023; + tens = (tens < 0) ? ((tens * 617) / 2048) : (((tens * 1233) / 4096) + 1); + + // move the significant bits into position and stick them into an int + stbsp__raise_to_power10(&ph, &pl, d, 18 - tens); + + // get full as much precision from double-double as possible + stbsp__ddtoS64(bits, ph, pl); + + // check if we undershot + if (((stbsp__uint64)bits) >= stbsp__tento19th) + ++tens; + } + + // now do the rounding in integer land + frac_digits = (frac_digits & 0x80000000) ? ((frac_digits & 0x7ffffff) + 1) : (tens + frac_digits); + if ((frac_digits < 24)) { + stbsp__uint32 dg = 1; + if ((stbsp__uint64)bits >= stbsp__powten[9]) + dg = 10; + while ((stbsp__uint64)bits >= stbsp__powten[dg]) { + ++dg; + if (dg == 20) + goto noround; + } + if (frac_digits < dg) { + stbsp__uint64 r; + // add 0.5 at the right position and round + e = dg - frac_digits; + if ((stbsp__uint32)e >= 24) + goto noround; + r = stbsp__powten[e]; + bits = bits + (r / 2); + if ((stbsp__uint64)bits >= stbsp__powten[dg]) + ++tens; + bits /= r; + } + noround:; + } + + // kill long trailing runs of zeros + if (bits) { + stbsp__uint32 n; + for (;;) { + if (bits <= 0xffffffff) + break; + if (bits % 1000) + goto donez; + bits /= 1000; + } + n = (stbsp__uint32)bits; + while ((n % 1000) == 0) + n /= 1000; + bits = n; + donez:; + } + + // convert to string + out += 64; + e = 0; + for (;;) { + stbsp__uint32 n; + char *o = out - 8; + // do the conversion in chunks of U32s (avoid most 64-bit divides, worth it, constant denomiators be damned) + if (bits >= 100000000) { + n = (stbsp__uint32)(bits % 100000000); + bits /= 100000000; + } else { + n = (stbsp__uint32)bits; + bits = 0; + } + while (n) { + out -= 2; + *(stbsp__uint16 *)out = *(stbsp__uint16 *)&stbsp__digitpair.pair[(n % 100) * 2]; + n /= 100; + e += 2; + } + if (bits == 0) { + if ((e) && (out[0] == '0')) { + ++out; + --e; + } + break; + } + while (out != o) { + *--out = '0'; + ++e; + } + } + + *decimal_pos = tens; + *start = out; + *len = e; + return ng; +} + +#undef stbsp__ddmulthi +#undef stbsp__ddrenorm +#undef stbsp__ddmultlo +#undef stbsp__ddmultlos +#undef STBSP__SPECIAL +#undef STBSP__COPYFP + +#endif // STB_SPRINTF_NOFLOAT + +// clean up +#undef stbsp__uint16 +#undef stbsp__uint32 +#undef stbsp__int32 +#undef stbsp__uint64 +#undef stbsp__int64 +#undef STBSP__UNALIGNED + +#endif // STB_SPRINTF_IMPLEMENTATION + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ \ No newline at end of file diff --git a/src/basic/string16.cpp b/src/basic/string16.cpp deleted file mode 100644 index 4852aff..0000000 --- a/src/basic/string16.cpp +++ /dev/null @@ -1,341 +0,0 @@ -char16_t ToLowerCase(char16_t a) { - if (a >= 'A' && a <= 'Z') a += 32; - return a; -} - -char16_t ToUpperCase(char16_t a) { - if (a >= 'a' && a <= 'z') a -= 32; - return a; -} - -bool IsWhitespace(char16_t w) { - bool result = w == '\n' || w == ' ' || w == '\t' || w == '\v' || w == '\r'; - return result; -} - -bool IsSymbol(char16_t w) { - bool result = (w >= '!' && w <= '/') || (w >= ':' && w <= '@') || (w >= '[' && w <= '`') || (w >= '{' && w <= '~'); - return result; -} - -bool IsNonWord(char16_t w) { - if (w == '_') return false; - bool result = IsSymbol(w) || IsWhitespace(w); - return result; -} - -bool IsWord(char16_t w) { - bool result = !IsNonWord(w); - return result; -} - -bool IsBrace(char16_t c) { - bool result = c == '{' || c == '}'; - return result; -} - -bool IsParen(char16_t c) { - bool result = c == '(' || c == ')'; - return result; -} - -bool IsAlphabetic(char16_t a) { - bool result = (a >= 'a' && a <= 'z') || (a >= 'A' && a <= 'Z'); - return result; -} - -bool IsIdent(char16_t a) { - bool result = (a >= 'a' && a <= 'z') || (a >= 'A' && a <= 'Z') || a == '_'; - return result; -} - -bool IsDigit(char16_t a) { - bool result = a >= '0' && a <= '9'; - return result; -} - -bool IsAlphanumeric(char16_t a) { - bool result = IsDigit(a) || IsAlphabetic(a); - return result; -} - -bool AreEqual(String16 a, String16 b, unsigned ignore_case = false) { - if (a.len != b.len) return false; - for (int64_t i = 0; i < a.len; i++) { - char16_t A = a.data[i]; - char16_t B = b.data[i]; - if (ignore_case) { - A = ToLowerCase(A); - B = ToLowerCase(B); - } - if (A != B) - return false; - } - return true; -} -inline bool operator==(String16 a, String16 b) { return AreEqual(a, b); } -inline bool operator!=(String16 a, String16 b) { return !AreEqual(a, b); } - -char16_t GetChar(String16 string, int64_t i) { - char16_t result = 0; - if (i < string.len) result = string.data[i]; - return result; -} - -String16 Merge(Allocator allocator, Array list, String16 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; - char16_t *buff = (char16_t *)AllocSize(allocator, sizeof(char16_t) * (size + 1)); - String16 string = {buff, 0}; - For(list) { - Assert(string.len + it.len <= size); - memcpy(string.data + string.len, it.data, it.len * sizeof(char16_t)); - string.len += it.len; - if (!IsLast(list, it)) { - memcpy(string.data + string.len, separator.data, separator.len * sizeof(char16_t)); - string.len += separator.len; - } - } - Assert(string.len == size - 1); - string.data[size] = 0; - return string; -} - -bool Seek(String16 string, String16 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; - String16 substring = GetSlice(string, 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++) { - String16 substring = GetSlice(string, i, i + find.len); - if (AreEqual(substring, find, ignore_case)) { - if (index_out) - *index_out = i; - result = true; - break; - } - } - } - - return result; -} - -String16 CutLastSlash(String16 *s) { - String16 result = *s; - Seek(*s, u"/", &s->len, SeekFlag_MatchFindLast); - result = Skip(result, s->len); - return result; -} - -String16 ChopLastSlash(String16 s) { - String16 result = s; - Seek(s, u"/", &result.len, SeekFlag_MatchFindLast); - return result; -} - -String16 ChopLastPeriod(String16 s) { - String16 result = s; - Seek(s, u".", &result.len, SeekFlag_MatchFindLast); - return result; -} - -String16 SkipToLastSlash(String16 s) { - int64_t pos; - String16 result = s; - if (Seek(s, u"/", &pos, SeekFlag_MatchFindLast)) { - result = Skip(result, pos + 1); - } - return result; -} - -String16 SkipToLastPeriod(String16 s) { - int64_t pos; - String16 result = s; - if (Seek(s, u".", &pos, SeekFlag_MatchFindLast)) { - result = Skip(result, pos + 1); - } - return result; -} - -String16 CutPrefix(String16 *string, int64_t len) { - String16 result = GetPrefix(*string, len); - *string = Skip(*string, len); - return result; -} - -String16 CutPostfix(String16 *string, int64_t len) { - String16 result = GetPostfix(*string, len); - *string = Chop(*string, len); - return result; -} - -String16 Trim(String16 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 = GetSlice(string, whitespace_begin, whitespace_end); - } - - return string; -} - -String16 TrimEnd(String16 string) { - int64_t whitespace_end = string.len; - for (; whitespace_end != 0; whitespace_end--) { - if (!IsWhitespace(string.data[whitespace_end - 1])) { - break; - } - } - - String16 result = GetPrefix(string, whitespace_end); - return result; -} - -bool EndsWith(String16 a, String16 end, unsigned ignore_case = false) { - String16 a_end = GetPostfix(a, end.len); - bool result = AreEqual(end, a_end, ignore_case); - return result; -} - -bool StartsWith(String16 a, String16 start, unsigned ignore_case = false) { - String16 a_start = GetPrefix(a, start.len); - bool result = AreEqual(start, a_start, ignore_case); - return result; -} - -String16 Copy16(Allocator allocator, String16 string) { - char16_t *copy = (char16_t *)AllocSize(allocator, sizeof(char16_t) * (string.len + 1)); - memcpy(copy, string.data, string.len); - copy[string.len] = 0; - String16 result = {copy, string.len}; - return result; -} - -String16 Copy16(Allocator allocator, char16_t *string) { - String16 s = {string, (int64_t)WideLength(string)}; - return Copy(allocator, s); -} - -String16 Concat(Allocator allocator, String16 a, String16 b) { - char16_t *str = AllocArray(allocator, char16_t, a.len + b.len + 1); - MemoryCopy(str, a.data, a.len * 2); - MemoryCopy(str + a.len, b.data, b.len * 2); - str[a.len + b.len] = 0; - return {str, a.len + b.len}; -} - -void NormalizePathInPlace(String16 s) { - for (int64_t i = 0; i < s.len; i++) { - if (s.data[i] == u'\\') - s.data[i] = u'/'; - } -} - -String16 NormalizePath(Allocator allocator, String16 s) { - String16 copy = Copy(allocator, s); - NormalizePathInPlace(copy); - return copy; -} - -String16 SkipNumberEx(String16 *string) { - String16 col = {string->data, 0}; - for (int64_t i = 0; i < string->len; i += 1) { - if (IsDigit(string->data[i])) { - col.len += 1; - } else { - break; - } - } - *string = Skip(*string, col.len); - return col; -} - -Int SkipNumber(String16 *string) { - String16 col = SkipNumberEx(string); - if (col.len == 0) return -1; - Scratch scratch; - String num_string = ToString(scratch, col); - Int result = strtoll(num_string.data, NULL, 10); - return result; -} - -String16 SkipUntil(String16 *string, String16 str) { - String16 begin = *string; - begin.len = 0; - for (; string->len; begin.len += 1) { - String16 match = GetPrefix(*string, str.len); - if (StartsWith(match, str)) break; - *string = Skip(*string, 1); - } - return begin; -} - -String16 SkipWhitespace(String16 *string) { - String16 begin = {string->data, 0}; - for (Int i = 0; i < string->len; i += 1) { - if (!IsWhitespace(string->data[i])) break; - *string = Skip(*string, 1); - begin.len += 1; - } - return begin; -} - -// chop this - :324 -String16 ChopNumberEx(String16 *string) { - String16 col = {}; - for (int64_t i = string->len - 1; i >= 0; i -= 1) { - if (IsDigit(string->data[i])) { - col.data = string->data + i; - col.len += 1; - } else if (string->data[i] == L':') { - break; - } else { - return {}; - } - } - *string = Chop(*string, col.len + 1); - return col; -} - -Int ChopNumber(String16 *string) { - Scratch scratch; - String16 col = ChopNumberEx(string); - if (col.len == 0) return -1; - String num_string = ToString(scratch, col); - Int result = strtoll(num_string.data, NULL, 10) - 1; - - return result; -} \ No newline at end of file diff --git a/src/basic/thread_queue.h b/src/basic/thread_queue.h deleted file mode 100644 index f37b553..0000000 --- a/src/basic/thread_queue.h +++ /dev/null @@ -1,33 +0,0 @@ -#define WORK_FUNCTION(name) void name(void *data) -typedef WORK_FUNCTION(WorkQueueCallback); - -struct WorkQueueEntry { - WorkQueueCallback *callback; - void *data; -}; - -struct ThreadCtx { - int thread_index; -}; - -struct WorkQueue { - int32_t thread_count; - WorkQueueEntry entries[256]; - int64_t volatile index_to_write; - int64_t volatile index_to_read; - int64_t volatile completion_index; - int64_t volatile completion_goal; - void *semaphore; -}; - -struct ThreadStartupInfo { - uint32_t thread_id; - int32_t thread_index; - WorkQueue *queue; -}; - -void PushWork(WorkQueue *wq, void *data, WorkQueueCallback *callback); -void InitWorkQueue(WorkQueue *queue, uint32_t thread_count, ThreadStartupInfo *info); -void WaitUntilCompletion(WorkQueue *wq); - -int64_t AtomicIncrement(volatile int64_t *i); \ No newline at end of file diff --git a/src/basic/unix.cpp b/src/basic/unix.cpp deleted file mode 100644 index 6bb00b5..0000000 --- a/src/basic/unix.cpp +++ /dev/null @@ -1,417 +0,0 @@ -#include "filesystem.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -void (*Error)(const char *, ...); - -void *VReserve(size_t size) { - void *result = mmap(0, size, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, (off_t)0); - return result == (void *)-1 ? 0 : result; -} - -bool VCommit(void *p, size_t size) { - int result = mprotect(p, size, PROT_READ | PROT_WRITE); - return result == 0; -} - -bool VRelease(void *p, size_t size) { - int result = munmap(p, size); - return result == 0; -} - -bool VDecommit(void *p, size_t size) { - mprotect(p, size, PROT_NONE); - madvise(p, size, MADV_DONTNEED); - return true; -} - -void InitOS(void (*error_proc)(const char *, ...)) { - Error = error_proc; -} - -String ReadFile(Allocator al, String path) { - Scratch scratch(al); - String null_term = Copy(scratch, path); - String result = {}; - FILE *f = fopen(null_term.data, "rb"); - if (f) { - fseek(f, 0, SEEK_END); - result.len = ftell(f); - fseek(f, 0, SEEK_SET); - - result.data = (char *)AllocSize(al, result.len + 1); - fread(result.data, result.len, 1, f); - result.data[result.len] = 0; - - fclose(f); - } - return result; -} - -bool WriteFile(String path, String data) { - Scratch scratch; - String null_term = Copy(scratch, path); - bool result = false; - FILE *f = fopen((const char *)null_term.data, "w"); - if (f) { - size_t written = fwrite(data.data, 1, data.len, f); - if (written == data.len) { - result = true; - } - fclose(f); - } - return result; -} - -bool DeleteFile(String path) { - Scratch scratch; - String null_term = Copy(scratch, path); - int result = unlink(null_term.data); - return result == 0; -} - -MakeDirResult MakeDir(String path) { - Scratch scratch; - String null_term = Copy(scratch, path); - int error = mkdir(null_term.data, 0755); - MakeDirResult result = MakeDirResult_Success; - if (error != 0) { - result = MakeDirResult_ErrorOther; - if (errno == EEXIST) result = MakeDirResult_Exists; - } - return result; -} - -int64_t GetFileModTime(String path) { - Scratch scratch; - String null_term = Copy(scratch, path); - struct stat attrib = {}; - stat(null_term.data, &attrib); - struct timespec ts = attrib.st_mtim; - int64_t result = (((int64_t)ts.tv_sec) * 1000000ll) + ((int64_t)ts.tv_nsec) / 1000ll; - return result; -} - -String GetAbsolutePath(Allocator al, String path) { - Scratch scratch(al); - String null_term = Copy(scratch, path); - char *buffer = AllocArray(al, char, PATH_MAX); - realpath(null_term.data, buffer); - String result = buffer; - return result; -} - -bool FileExists(String path) { - Scratch scratch; - String null_term = Copy(scratch, path); - bool result = false; - if (access((char *)null_term.data, F_OK) == 0) { - result = true; - } - return result; -} - -bool IsDir(String path) { - Scratch scratch; - String null_term = Copy(scratch, path); - struct stat s; - if (stat(null_term.data, &s) != 0) - return false; - return S_ISDIR(s.st_mode); -} - -bool IsFile(String path) { - Scratch scratch; - String null_term = Copy(scratch, path); - struct stat s; - if (stat(null_term.data, &s) != 0) - return false; - return S_ISREG(s.st_mode); -} - -bool IsAbsolute(String path) { - bool result = path.len && path.data[0] == '/'; - return result; -} - -String GetWorkingDir(Allocator al) { - char *buffer = AllocArray(al, char, PATH_MAX); - char *cwd = getcwd(buffer, PATH_MAX); - return cwd; -} - -String GetExePath(Allocator al) { - char *buffer = AllocArray(al, char, PATH_MAX); - readlink("/proc/self/exe", buffer, PATH_MAX); - return buffer; -} - -String GetExeDir(Allocator al) { - Scratch scratch(al); - String exe_path = GetExePath(scratch); - String dir = ChopLastSlash(exe_path); - String result = Copy(al, dir); - return result; -} - -double get_time_in_micros(void) { - struct timespec spec; - clock_gettime(CLOCK_MONOTONIC, &spec); - return (((double)spec.tv_sec) * 1000000) + (((double)spec.tv_nsec) / 1000); -} - -bool IsValid(const FileIter &it) { - return it.is_valid; -} - -void Advance(FileIter *it) { - struct dirent *file = NULL; - 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 = Copy(it->allocator, file->d_name); - - const char *dir_char_ending = it->is_directory ? "/" : ""; - const char *separator = it->path.data[it->path.len - 1] == '/' ? "" : "/"; - it->relative_path = Format(it->allocator, "%.*s%s%s%s", FmtString(it->path), separator, file->d_name, dir_char_ending); - it->absolute_path = GetAbsolutePath(it->allocator, it->relative_path); - if (it->is_directory) it->absolute_path = Format(it->allocator, "%.*s/", FmtString(it->absolute_path)); - it->is_valid = true; - return; - } - it->is_valid = false; - closedir((DIR *)it->dir); -} - -FileIter IterateFiles(Allocator alo, String path) { - FileIter it = {}; - it.allocator = alo; - it.path = path; - Scratch scratch(alo); - String null_term = Copy(scratch, path); - it.dir = (void *)opendir((char *)null_term.data); - if (it.dir) { - Advance(&it); - } - return it; -} - -struct UnixProcess { - pid_t pid; - int child_stdout_read; - int stdin_write; -}; - -Array SplitCommand(Allocator allocator, String command_line) { - Array cmd = {allocator}; - - String curr = {}; - for (int i = 0; i < command_line.len; i += 1) { - if (command_line.data[i] == ' ') { - if (curr.len > 0) { - Add(&cmd, Copy(allocator, curr).data); - curr = {}; - } - continue; - } - if (curr.len == 0) { - curr.data = command_line.data + i; - } - curr.len += 1; - } - - if (curr.len > 0) { - Add(&cmd, Copy(allocator, curr).data); - } - - return cmd; -} - -Process SpawnProcess(String command_line, String working_dir, String write_stdin, Array enviroment) { - Scratch scratch; - const int PIPE_READ = 0; - const int PIPE_WRITE = 1; - bool error = false; - - char *buffer = AllocArray(scratch, char, 4096); - chdir(working_dir.data); - getcwd(buffer, 4096); - defer { chdir(buffer); }; - - - Process process = {}; - UnixProcess *plat = (UnixProcess *)&process.platform; - Array args = SplitCommand(scratch, command_line); - Array env = {scratch}; - - For (enviroment) { - Add(&env, Copy(scratch, it).data); - } - - int stdout_desc[2] = {}; - int stdin_desc[2] = {}; - posix_spawn_file_actions_t actions = {}; - - if (posix_spawn_file_actions_init(&actions) != 0) { - Error("Libc function failed: posix_spawn_file_actions_init, with error: %s", strerror(errno)); - return process; - } - defer { - posix_spawn_file_actions_destroy(&actions); - }; - - if (pipe(stdout_desc) == -1) { - Error("Libc function failed: pipe, with error: %s", strerror(errno)); - return process; - } - defer { - if (error) { - close(stdout_desc[PIPE_READ]); - close(stdout_desc[PIPE_WRITE]); - } else { - close(stdout_desc[PIPE_WRITE]); - } - }; - - if (pipe(stdin_desc) == -1) { - Error("Libc function failed: pipe, with error: %s", strerror(errno)); - return process; - } - defer { - if (error) { - close(stdin_desc[PIPE_READ]); - close(stdin_desc[PIPE_WRITE]); - } else { - close(stdin_desc[PIPE_READ]); - } - }; - - error = posix_spawn_file_actions_addclose(&actions, stdout_desc[PIPE_READ]) != 0; - if (error) { - Error("Libc function failed: posix_spawn_file_actions_addclose, with error: %s", strerror(errno)); - return process; - } - - error = posix_spawn_file_actions_adddup2(&actions, stdout_desc[PIPE_WRITE], STDOUT_FILENO) != 0; - if (error) { - Error("Libc function failed: posix_spawn_file_actions_adddup2 STDOUT_FILENO, with error: %s", strerror(errno)); - return process; - } - - error = posix_spawn_file_actions_adddup2(&actions, stdout_desc[PIPE_WRITE], STDERR_FILENO) != 0; - if (error) { - Error("Libc function failed: posix_spawn_file_actions_adddup2 STDERR_FILENO, with error: %s", strerror(errno)); - return process; - } - - error = posix_spawn_file_actions_addclose(&actions, stdin_desc[PIPE_WRITE]) != 0; - if (error) { - Error("Libc function failed: posix_spawn_file_actions_addclose, with error: %s", strerror(errno)); - return process; - } - - error = posix_spawn_file_actions_adddup2(&actions, stdin_desc[PIPE_READ], STDIN_FILENO) != 0; - if (error) { - Error("Libc function failed: posix_spawn_file_actions_adddup2 STDIN_FILENO, with error: %s", strerror(errno)); - return process; - } - - pid_t process_pid = 0; - error = posix_spawnp(&process_pid, args[0], &actions, NULL, args.data, env.data) != 0; - if (error) { - Error("Libc function failed: failed to create process\n, with error: %s", strerror(errno)); - return process; - } - - - plat->child_stdout_read = stdout_desc[PIPE_READ]; - plat->stdin_write = stdin_desc[PIPE_WRITE]; - plat->pid = process_pid; - - if (write_stdin.len) { - WriteStdin(&process, write_stdin); - CloseStdin(&process); - } - - process.id = process_pid; - process.is_valid = true; - return process; -} - -bool IsValid(Process *process) { - UnixProcess *plat = (UnixProcess *)&process->platform; - if (process->is_valid == false) { - return false; - } - - int status = 0; - pollfd p = {}; - p.fd = plat->child_stdout_read; - p.events = POLLRDHUP | POLLERR | POLLHUP | POLLNVAL; - int res = poll(&p, 1, 0); - if (res > 0) { - pid_t result = waitpid(plat->pid, &status, 0); - process->exit_code = WEXITSTATUS(status); - return false; - } - - return true; -} - -void KillProcess(Process *process) { - Assert(process->is_valid); - UnixProcess *plat = (UnixProcess *)process->platform; - kill(plat->pid, SIGKILL); - process->exit_code = -1; -} - -String PollStdout(Allocator allocator, Process *process, bool force_read) { - Assert(process->is_valid); - UnixProcess *plat = (UnixProcess *)process->platform; - - String result = {}; - result.data = AllocArray(allocator, char, 16 * 4096); - - pollfd p = {}; - p.fd = plat->child_stdout_read; - p.events = POLLIN; - int res = poll(&p, 1, 0); - if (res > 0 || force_read) { - result.len = read(plat->child_stdout_read, result.data, 4 * 4096); - } - return result; -} - -void WriteStdin(Process *process, String string) { - if (string.len == 0) return; - Assert(process->is_valid); - - UnixProcess *plat = (UnixProcess *)process->platform; - ssize_t size = write(plat->stdin_write, string.data, string.len); - - Assert(size == string.len); -} - -void CloseStdin(Process *process) { - UnixProcess *plat = (UnixProcess *)process->platform; - close(plat->stdin_write); -} diff --git a/src/basic/wasm.cpp b/src/basic/wasm.cpp deleted file mode 100644 index ac5c1e6..0000000 --- a/src/basic/wasm.cpp +++ /dev/null @@ -1,260 +0,0 @@ -#include "filesystem.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#define PATH_MAX 1024 - -EM_JS(void, JS_Breakpoint, (), { - debugger; -}) - -void (*Error)(const char *, ...); - -void *VReserve(size_t size) { - InvalidCodepath(); - return NULL; -} - -bool VCommit(void *p, size_t size) { - InvalidCodepath(); - return false; -} - -bool VRelease(void *p, size_t size) { - InvalidCodepath(); - return false; -} - -bool VDecommit(void *p, size_t size) { - InvalidCodepath(); - return false; -} - -void InitOS(void (*error_proc)(const char *, ...)) { - Error = error_proc; -} - -String ReadFile(Allocator al, String path) { - Scratch scratch(al); - String null_term = Copy(scratch, path); - String result = {}; - FILE *f = fopen(null_term.data, "rb"); - if (f) { - fseek(f, 0, SEEK_END); - result.len = ftell(f); - fseek(f, 0, SEEK_SET); - - result.data = (char *)AllocSize(al, result.len + 1); - fread(result.data, result.len, 1, f); - result.data[result.len] = 0; - - fclose(f); - } - return result; -} - -bool WriteFile(String path, String data) { - Scratch scratch; - String null_term = Copy(scratch, path); - bool result = false; - FILE *f = fopen((const char *)null_term.data, "w"); - if (f) { - size_t written = fwrite(data.data, 1, data.len, f); - if (written == data.len) { - result = true; - } - fclose(f); - } - return result; -} - -bool DeleteFile(String path) { - Scratch scratch; - String null_term = Copy(scratch, path); - int result = unlink(null_term.data); - return result == 0; -} - -MakeDirResult MakeDir(String path) { - Scratch scratch; - String null_term = Copy(scratch, path); - int error = mkdir(null_term.data, 0755); - MakeDirResult result = MakeDirResult_Success; - if (error != 0) { - result = MakeDirResult_ErrorOther; - if (errno == EEXIST) result = MakeDirResult_Exists; - } - return result; -} - -int64_t GetFileModTime(String path) { - Scratch scratch; - String null_term = Copy(scratch, path); - struct stat attrib = {}; - stat(null_term.data, &attrib); - struct timespec ts = attrib.st_mtim; - int64_t result = (((int64_t)ts.tv_sec) * 1000000ll) + ((int64_t)ts.tv_nsec) / 1000ll; - return result; -} - -String GetAbsolutePath(Allocator al, String path) { - return path; -} - -bool FileExists(String path) { - Scratch scratch; - String null_term = Copy(scratch, path); - bool result = false; - if (access((char *)null_term.data, F_OK) == 0) { - result = true; - } - return result; -} - -bool IsDir(String path) { - Scratch scratch; - String null_term = Copy(scratch, path); - struct stat s; - if (stat(null_term.data, &s) != 0) - return false; - return S_ISDIR(s.st_mode); -} - -bool IsFile(String path) { - Scratch scratch; - String null_term = Copy(scratch, path); - struct stat s; - if (stat(null_term.data, &s) != 0) - return false; - return S_ISREG(s.st_mode); -} - -bool IsAbsolute(String path) { - bool result = path.len && path.data[0] == '/'; - return result; -} - -String GetWorkingDir(Allocator al) { - return Copy(al, "/workingdir"); -} - -String GetExePath(Allocator al) { - return Copy(al, "/text_editor"); -} - -String GetExeDir(Allocator al) { - Scratch scratch(al); - String exe_path = GetExePath(scratch); - String dir = ChopLastSlash(exe_path); - String result = Copy(al, dir); - return result; -} - -double get_time_in_micros(void) { - struct timespec spec; - clock_gettime(CLOCK_MONOTONIC, &spec); - return (((double)spec.tv_sec) * 1000000) + (((double)spec.tv_nsec) / 1000); -} - -bool IsValid(const FileIter &it) { - return it.is_valid; -} - -void Advance(FileIter *it) { - struct dirent *file = NULL; - 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 = Copy(it->allocator, file->d_name); - - const char *dir_char_ending = it->is_directory ? "/" : ""; - const char *separator = it->path.data[it->path.len - 1] == '/' ? "" : "/"; - it->relative_path = Format(it->allocator, "%.*s%s%s%s", FmtString(it->path), separator, file->d_name, dir_char_ending); - it->absolute_path = GetAbsolutePath(it->allocator, it->relative_path); - if (it->is_directory) it->absolute_path = Format(it->allocator, "%.*s/", FmtString(it->absolute_path)); - it->is_valid = true; - return; - } - it->is_valid = false; - closedir((DIR *)it->dir); -} - -FileIter IterateFiles(Allocator alo, String path) { - FileIter it = {}; - it.allocator = alo; - it.path = path; - Scratch scratch(alo); - String null_term = Copy(scratch, path); - it.dir = (void *)opendir((char *)null_term.data); - if (it.dir) { - Advance(&it); - } - return it; -} - -Array SplitCommand(Allocator allocator, String command_line) { - Array cmd = {allocator}; - - String curr = {}; - for (int i = 0; i < command_line.len; i += 1) { - if (command_line.data[i] == ' ') { - if (curr.len > 0) { - Add(&cmd, Copy(allocator, curr).data); - curr = {}; - } - continue; - } - if (curr.len == 0) { - curr.data = command_line.data + i; - } - curr.len += 1; - } - - if (curr.len > 0) { - Add(&cmd, Copy(allocator, curr).data); - } - - return cmd; -} - -Process SpawnProcess(String command_line, String working_dir, String write_stdin, Array enviroment) { - return {}; -} - -bool IsValid(Process *process) { - return false; -} - -void KillProcess(Process *process) { - -} - -String PollStdout(Allocator allocator, Process *process, bool force_read) { - return {}; -} - -void WriteStdin(Process *process, String string) { - return; -} - -void CloseStdin(Process *process) { - return; -} diff --git a/src/basic/win32.cpp b/src/basic/win32.cpp deleted file mode 100644 index f3ea54e..0000000 --- a/src/basic/win32.cpp +++ /dev/null @@ -1,487 +0,0 @@ -#include "filesystem.h" -#include "thread_queue.h" - -#ifndef NOMINMAX - #define NOMINMAX -#endif -#ifndef WIN32_LEAN_AND_MEAN - #define WIN32_LEAN_AND_MEAN -#endif -#include -#include -#include - -#include "win32_thread.cpp" - -void (*Error)(const char *, ...); - -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, size_t size) { - 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; -} - -void InitOS(void (*error_proc)(const char *, ...)) { - Error = error_proc; - - SetConsoleOutputCP(65001); - SetConsoleCP(65001); -} - -String ReadFile(Allocator arena, String path) { - bool success = false; - String result = {}; - - Scratch scratch(arena); - String16 string16 = ToString16(scratch, path); - HANDLE handle = CreateFileW((wchar_t *)string16.data, 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) { - Scratch scratch(arena); - String16 wpath = ToString16(scratch, relative); - wchar_t *wpath_abs = AllocArray(scratch, wchar_t, 4096); - DWORD written = GetFullPathNameW((wchar_t *)wpath.data, 4096, wpath_abs, 0); - if (written == 0) - return {}; - String path = ToString(arena, {(char16_t *)wpath_abs, written}); - NormalizePathInPlace(path); - return path; -} - -bool IsValid(const FileIter &it) { - return it.is_valid; -} - -void Advance(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 = ToString(it->allocator, (char16_t *)data->cFileName, WideLength((char16_t *)data->cFileName)); - const char *is_dir = it->is_directory ? "/" : ""; - const char *separator = it->path.data[it->path.len - 1] == '/' ? "" : "/"; - it->relative_path = Format(it->allocator, "%.*s%s%.*s%s", FmtString(it->path), separator, FmtString(it->filename), is_dir); - it->absolute_path = GetAbsolutePath(it->allocator, 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); -} - -FileIter IterateFiles(Allocator alo, String path) { - FileIter it = {0}; - it.allocator = alo; - it.path = path; - - String modified_path = Format(it.allocator, "%.*s\\*", FmtString(path)); - String16 modified_path16 = ToString16(it.allocator, modified_path); - - it.w32 = AllocType(it.allocator, Win32_FileIter); - it.w32->handle = FindFirstFileW((wchar_t *)modified_path16.data, &it.w32->data); - if (it.w32->handle == INVALID_HANDLE_VALUE) { - it.is_valid = false; - return it; - } - - Advance(&it); - return it; -} - -double get_time_in_micros(void) { - static double invfreq; - if (!invfreq) { - LARGE_INTEGER frequency; - QueryPerformanceFrequency(&frequency); - invfreq = 1000000.0 / frequency.QuadPart; - } - LARGE_INTEGER counter; - QueryPerformanceCounter(&counter); - return counter.QuadPart * invfreq; -} - -bool WriteFile(String path, String data) { - bool result = false; - - Scratch scratch; - String16 wpath = ToString16(scratch, path); - - DWORD access = GENERIC_WRITE; - DWORD creation_disposition = CREATE_ALWAYS; - HANDLE handle = CreateFileW((wchar_t *)wpath.data, access, 0, NULL, creation_disposition, FILE_ATTRIBUTE_NORMAL, NULL); - if (handle != INVALID_HANDLE_VALUE) { - DWORD bytes_written = 0; - Assert(data.len == (DWORD)data.len); // @Todo: can only read 32 byte size files? - BOOL error = WriteFile(handle, data.data, (DWORD)data.len, &bytes_written, NULL); - if (error == TRUE) { - if (bytes_written == data.len) { - result = true; - } - } - CloseHandle(handle); - } - - return result; -} - -String GetExePath(Allocator allocator) { - Scratch scratch(allocator); - wchar_t *wbuffer = AllocArray(scratch, wchar_t, 4096); - DWORD wsize = GetModuleFileNameW(0, wbuffer, 4096); - Assert(wsize != 0); - - String path = ToString(allocator, (char16_t *)wbuffer, wsize); - NormalizePathInPlace(path); - return path; -} - -String GetExeDir(Allocator allocator) { - Scratch scratch((Arena *)allocator.object); - String path = GetExePath(scratch); - path = ChopLastSlash(path); - path = Copy(allocator, path); - return path; -} - -bool FileExists(String path) { - Scratch scratch; - String16 wbuff = ToString16(scratch, path); - DWORD attribs = GetFileAttributesW((wchar_t *)wbuff.data); - bool result = attribs == INVALID_FILE_ATTRIBUTES ? false : true; - return result; -} - -bool IsDir(String path) { - Scratch scratch; - String16 wbuff = ToString16(scratch, path); - DWORD dwAttrib = GetFileAttributesW((wchar_t *)wbuff.data); - return dwAttrib != INVALID_FILE_ATTRIBUTES && (dwAttrib & FILE_ATTRIBUTE_DIRECTORY); -} - -bool IsFile(String path) { - Scratch scratch; - String16 wbuff = ToString16(scratch, path); - DWORD dwAttrib = GetFileAttributesW((wchar_t *)wbuff.data); - bool is_file = (dwAttrib & FILE_ATTRIBUTE_DIRECTORY) == 0; - return dwAttrib != INVALID_FILE_ATTRIBUTES && is_file; -} - -String GetWorkingDir(Allocator arena) { - Scratch scratch(arena); - wchar_t *wbuffer = AllocArray(scratch, wchar_t, 4096); - DWORD wsize = GetCurrentDirectoryW(4096, wbuffer); - Assert(wsize != 0); - wbuffer[wsize] = 0; - - String path = ToString(arena, (char16_t *)wbuffer, wsize); - NormalizePathInPlace(path); - return path; -} - -bool IsAbsolute(String path) { - bool result = path.len > 3 && IsAlphabetic(path.data[0]) && path.data[1] == ':' && (path.data[2] == '/' || path.data[2] == '\\'); - return result; -} - -bool DeleteFile(String path) { - Scratch scratch; - String16 wpath = ToString16(scratch, path); - - BOOL success = DeleteFileW((wchar_t *)wpath.data); - bool result = true; - if (success == 0) result = false; - return result; -} - -int64_t GetFileModTime(String file) { - Scratch scratch; - String16 string16 = ToString16(scratch, file); - WIN32_FIND_DATAW data; - HANDLE handle = FindFirstFileW((wchar_t *)string16.data, &data); - if (handle != INVALID_HANDLE_VALUE) { - FindClose(handle); - FILETIME time = data.ftLastWriteTime; - int64_t result = (int64_t)time.dwHighDateTime << 32 | time.dwLowDateTime; - return result; - } else { - return -1; - } -} - -MakeDirResult MakeDir(String path) { - Scratch scratch; - MakeDirResult result = MakeDirResult_Success; - String16 string16 = ToString16(scratch, path); - BOOL success = CreateDirectoryW((wchar_t *)string16.data, NULL); - if (success == 0) { - DWORD error = GetLastError(); - if (error == ERROR_ALREADY_EXISTS) { - result = MakeDirResult_Exists; - } else if (error == ERROR_PATH_NOT_FOUND) { - result = MakeDirResult_NotFound; - } else { - result = MakeDirResult_ErrorOther; - } - } - return result; -} - -struct Win32Process { - HANDLE handle; - HANDLE child_stdout_read; - HANDLE child_stdout_write; - HANDLE child_stdin_read; - HANDLE child_stdin_write; -}; -// static_assert(sizeof(Win32Process) < sizeof(Process::platform)); - -void Win32ReportError(String msg, String cmd) { - LPVOID lpMsgBuf; - FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&lpMsgBuf, 0, NULL); - defer { LocalFree(lpMsgBuf); }; - char *buff = (char *)lpMsgBuf; - size_t buffLen = strlen((const char *)buff); - if (buffLen > 0 && buff[buffLen - 1] == '\n') buff[buffLen - 1] = 0; - Error("%.*s: %.*s! %s", FmtString(msg), FmtString(cmd), (char *)lpMsgBuf); -} - -void Win32CloseProcess(Process *process) { - Win32Process *p = (Win32Process *)process->platform; - if (p->handle != INVALID_HANDLE_VALUE) CloseHandle(p->handle); - if (p->child_stdout_write != INVALID_HANDLE_VALUE) CloseHandle(p->child_stdout_write); - if (p->child_stdout_read != INVALID_HANDLE_VALUE) CloseHandle(p->child_stdout_read); - if (p->child_stdin_read != INVALID_HANDLE_VALUE) CloseHandle(p->child_stdin_read); - if (p->child_stdin_write != INVALID_HANDLE_VALUE) CloseHandle(p->child_stdin_write); - process->is_valid = false; -} - -static void Win32ProcessError(Process *process, String msg, String cmd) { - Win32ReportError(msg, cmd); - Win32CloseProcess(process); -} - -void WriteStdin(Process *process, String string) { - if (string.len == 0) return; - Assert(process->is_valid); - - Win32Process *p = (Win32Process *)process->platform; - Assert(p->child_stdin_write != INVALID_HANDLE_VALUE); - - DWORD written = 0; - bool write_error = WriteFile(p->child_stdin_write, string.data, (DWORD)string.len, &written, NULL) == 0; - - Assert(write_error == false); - Assert(written == string.len); -} - -void CloseStdin(Process *process) { - Win32Process *p = (Win32Process *)process->platform; - CloseHandle(p->child_stdin_write); - p->child_stdin_write = INVALID_HANDLE_VALUE; -} - -Process SpawnProcess(String command_line, String working_dir, String write_stdin, Array enviroment) { - Process process = {}; - Win32Process *p = (Win32Process *)process.platform; - - Scratch scratch; - command_line = Format(scratch, "/C %.*s", FmtString(command_line)); - - p->handle = INVALID_HANDLE_VALUE; - p->child_stdout_write = INVALID_HANDLE_VALUE; - p->child_stdout_read = INVALID_HANDLE_VALUE; - p->child_stdin_read = INVALID_HANDLE_VALUE; - p->child_stdin_write = INVALID_HANDLE_VALUE; - - SECURITY_ATTRIBUTES security_atrb = {}; - security_atrb.nLength = sizeof(SECURITY_ATTRIBUTES); - security_atrb.bInheritHandle = TRUE; - - if (!CreatePipe(&p->child_stdout_read, &p->child_stdout_write, &security_atrb, 0)) { - Win32ProcessError(&process, "Failed to create process at create pipe stage", command_line); - return process; - } - - if (!SetHandleInformation(p->child_stdout_read, HANDLE_FLAG_INHERIT, 0)) { - Win32ProcessError(&process, "Failed to create process at create pipe stage", command_line); - return process; - } - - if (!CreatePipe(&p->child_stdin_read, &p->child_stdin_write, &security_atrb, 0)) { - Win32ProcessError(&process, "Failed to create process at create pipe stage", command_line); - return process; - } - - if (!SetHandleInformation(p->child_stdin_write, HANDLE_FLAG_INHERIT, 0)) { - Win32ProcessError(&process, "Failed to create process at create pipe stage", command_line); - return process; - } - - STARTUPINFOW startup = {}; - startup.cb = sizeof(STARTUPINFO); - startup.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES; - startup.hStdInput = p->child_stdin_read; - startup.hStdOutput = p->child_stdout_write; - startup.hStdError = p->child_stdout_write; - startup.wShowWindow = SW_HIDE; - - String16 cwd = ToString16(scratch, working_dir); - String16 cmd = ToString16(scratch, command_line); - - char *env = NULL; - if (enviroment.len) { - Int size = GetSize(enviroment) + enviroment.len + 1; - env = (char *)PushSize(scratch, size); - Int i = 0; - For(enviroment) { - MemoryCopy(env + i, it.data, it.len); - i += it.len; - - env[i++] = 0; - } - env[i++] = 0; - } - - DWORD dwCreationFlags = 0; - BOOL bInheritHandles = TRUE; - - PROCESS_INFORMATION info = {}; - if (!CreateProcessW(L"c:\\windows\\system32\\cmd.exe", (wchar_t *)cmd.data, 0, 0, bInheritHandles, dwCreationFlags, env, (wchar_t *)cwd.data, &startup, &info)) { - Win32ProcessError(&process, "failed to create process", command_line); - return process; - } - - // Close handles to the stdin and stdout pipes no longer needed by the child process. - // If they are not explicitly closed, there is no way to recognize that the child process has ended. - CloseHandle(info.hThread); - CloseHandle(p->child_stdin_read); - CloseHandle(p->child_stdout_write); - p->child_stdin_read = INVALID_HANDLE_VALUE; - p->child_stdout_write = INVALID_HANDLE_VALUE; - - p->handle = info.hProcess; - process.is_valid = true; - process.id = (int64_t)p->handle; - - if (write_stdin.len) { - WriteStdin(&process, write_stdin); - CloseStdin(&process); - } - - return process; -} - -bool IsValid(Process *process) { - if (process->is_valid == false) return false; - Win32Process *p = (Win32Process *)process->platform; - - if (WaitForSingleObject(p->handle, 0) != WAIT_OBJECT_0) { - return true; - } - - DWORD exit_code; - bool get_exit_code_failed = GetExitCodeProcess(p->handle, &exit_code) == 0; - if (get_exit_code_failed) { - exit_code = -1; - } - - process->exit_code = (int)exit_code; - Win32CloseProcess(process); - return false; -} - -void KillProcess(Process *process) { - Assert(process->is_valid); - Win32Process *p = (Win32Process *)process->platform; - - bool terminate_process_error = TerminateProcess(p->handle, -1) == 0; - if (terminate_process_error) { - Assert(0); - } - Win32CloseProcess(process); - process->exit_code = -1; -} - -String PollStdout(Allocator allocator, Process *process, bool force_read) { - Assert(process->is_valid); - Win32Process *p = (Win32Process *)process->platform; - - DWORD bytes_avail = 0; - bool peek_error = PeekNamedPipe(p->child_stdout_read, NULL, 0, NULL, &bytes_avail, NULL) == 0; - if (peek_error) { - return {}; - } else if (bytes_avail == 0) { - return {}; - } - - size_t buffer_size = ClampTop(bytes_avail, (DWORD)(4096 * 16)); - char *buffer = AllocArray(allocator, char, buffer_size); - - DWORD bytes_read = 0; - bool read_error = ReadFile(p->child_stdout_read, buffer, (DWORD)buffer_size, &bytes_read, 0) == 0; - if (read_error) { - Win32ReportError("Failed to read the stdout of child process", "ReadFile"); - Dealloc(allocator, &buffer); - Win32CloseProcess(process); - return {}; - } - - String result = {buffer, bytes_read}; - return result; -} diff --git a/src/basic/win32_thread.cpp b/src/basic/win32_thread.cpp deleted file mode 100644 index d51bc4a..0000000 --- a/src/basic/win32_thread.cpp +++ /dev/null @@ -1,84 +0,0 @@ -int64_t AtomicIncrement(volatile int64_t *i) { - return InterlockedIncrement64(i); -} - -int64_t AtomicCompareAndSwap(volatile int64_t *dst, int64_t exchange, int64_t comperand) { - return InterlockedCompareExchange64(dst, exchange, comperand); -} - -void PushWork(WorkQueue *wq, void *data, WorkQueueCallback *callback) { - uint32_t new_index = (wq->index_to_write + 1) % Lengthof(wq->entries); - assert(new_index != wq->index_to_read); - - WorkQueueEntry *entry = wq->entries + wq->index_to_write; - entry->data = data; - entry->callback = callback; - - wq->completion_goal += 1; - _WriteBarrier(); - wq->index_to_write = new_index; - ReleaseSemaphore(wq->semaphore, 1, 0); -} - -bool TryDoingWork(WorkQueue *wq) { - bool should_sleep = false; - int64_t original_index_to_read = wq->index_to_read; - int64_t new_index_to_read = (original_index_to_read + 1) % Lengthof(wq->entries); - if (original_index_to_read != wq->index_to_write) { - int64_t index = AtomicCompareAndSwap(&wq->index_to_read, new_index_to_read, original_index_to_read); - if (index == original_index_to_read) { - WorkQueueEntry *entry = wq->entries + index; - entry->callback(entry->data); - AtomicIncrement(&wq->completion_index); - } - } else { - should_sleep = true; - } - return should_sleep; -} - -DWORD WINAPI WorkQueueThreadEntry(LPVOID param) { - auto ti = (ThreadStartupInfo *)param; - - ThreadCtx ctx = {}; - ctx.thread_index = ti->thread_index; - InitScratch(); - for (;;) { - if (TryDoingWork(ti->queue)) { - WaitForSingleObject(ti->queue->semaphore, INFINITE); - } - } -} - -void InitWorkQueue(WorkQueue *queue, uint32_t thread_count, ThreadStartupInfo *info) { - queue->thread_count = thread_count; - queue->index_to_read = 0; - queue->index_to_write = 0; - queue->completion_index = 0; - queue->completion_goal = 0; - queue->semaphore = CreateSemaphoreExA(0, 0, thread_count, 0, 0, SEMAPHORE_ALL_ACCESS); - Assert(queue->semaphore != INVALID_HANDLE_VALUE); - - for (uint32_t i = 0; i < thread_count; i++) { - ThreadStartupInfo *ti = info + i; - ti->thread_index = i; - ti->queue = queue; - - DWORD thread_id = 0; - HANDLE thread_handle = CreateThread(0, 0, WorkQueueThreadEntry, ti, 0, &thread_id); - Assert(thread_handle != INVALID_HANDLE_VALUE); - ti->thread_id = thread_id; - CloseHandle(thread_handle); - } -} - -void WaitUntilCompletion(WorkQueue *wq) { - while (wq->completion_goal != wq->completion_index) { - TryDoingWork(wq); - } -} - -bool IsWorkCompleted(WorkQueue *wq) { - bool result = wq->completion_goal == wq->completion_index; - return result; -} \ No newline at end of file diff --git a/src/metaprogram/metaprogram.cpp b/src/metaprogram/metaprogram.cpp index 7854362..457c0b2 100644 --- a/src/metaprogram/metaprogram.cpp +++ b/src/metaprogram/metaprogram.cpp @@ -1,18 +1,58 @@ -#define BASIC_IMPL #include "basic/basic.h" -#include "basic/filesystem.h" +#include "basic/basic.cpp" int main() { InitScratch(); + // Basic constructors + { + String a = "thing"; + String b("thing"); + Assert(a == b); + String c = {}; + Assert(c.len == 0 && c.data == NULL); + Assert(a != c); + } - Scratch scratch; - String data = ReadFile(scratch, "../data/init.lua"); - Array array = {scratch}; + // Accessors + { + String a = "thing"; + Assert(Skip(a, 1) == "hing"); + Assert(Chop(a, 1) == "thin"); + Assert(GetPrefix(a, 1) == "t"); + Assert(GetPostfix(a, 1) == "g"); + Assert(GetSlice(a, 0) == "thing"); + Assert(GetSlice(a, 0, -1) == "thin"); + Assert(GetSlice(a, 1, -1) == "hin"); + Assert(GetSlice(a, -2, -1) == "n"); + } - Add(&array, String{"String BaseLuaConfig = R\"==(\n"}); - Add(&array, data); - Add(&array, String{"\n)==\";\n"}); - String result = Merge(scratch, array, ""); - WriteFile("../src/text_editor/generated_config.cpp", result); + { + Scratch scratch; + String16 a = Format16(scratch, "Memes %d", 30); + Assert(a == u"Memes 30"); -} \ No newline at end of file + } + + + printf("hello world\n"); +} + + +// #define BASIC_IMPL +// #include "basic/basic.h" +// #include "basic/filesystem.h" + +// int main() { +// InitScratch(); + +// Scratch scratch; +// String data = ReadFile(scratch, "../data/init.lua"); +// Array array = {scratch}; + +// Add(&array, String{"String BaseLuaConfig = R\"==(\n"}); +// Add(&array, data); +// Add(&array, String{"\n)==\";\n"}); +// String result = Merge(scratch, array, ""); +// WriteFile("../src/text_editor/generated_config.cpp", result); + +// } \ No newline at end of file diff --git a/src/profiler/profiler.cpp b/src/profiler/profiler.cpp index 68f3025..e5eb1ed 100644 --- a/src/profiler/profiler.cpp +++ b/src/profiler/profiler.cpp @@ -3,7 +3,7 @@ static SpallProfile spall_ctx; static SpallBuffer spall_buffer; -double get_time_in_micros(void); +double GetTimeMicros(void); void BeginProfiler() { spall_ctx = spall_init_file("hello_world.spall", 1); @@ -25,14 +25,14 @@ void _BeginProfileScope(const char *name, int len) { spall_buffer_begin(&spall_ctx, &spall_buffer, name, // name of your name len, // name len minus the null terminator - get_time_in_micros() // timestamp in microseconds -- start of your timing block + GetTimeMicros() // timestamp in microseconds -- start of your timing block ); } #define BeginProfileScope(name) _BeginProfileScope(#name, sizeof(#name) - 1) void EndProfileScope() { spall_buffer_end(&spall_ctx, &spall_buffer, - get_time_in_micros() // timestamp in microseconds -- end of your timing block + GetTimeMicros() // timestamp in microseconds -- end of your timing block ); } #define ProfileScope(name) ProfileScope_ PROFILE_SCOPE_VAR_##name(#name, sizeof(#name) - 1) diff --git a/src/text_editor/todo.txt b/src/text_editor/todo.txt index 942b7dc..b43f3da 100644 --- a/src/text_editor/todo.txt +++ b/src/text_editor/todo.txt @@ -1,3 +1,41 @@ +REDESIGN and DELETE CODE +- Reduce the amount of actions needed to navigate using keyboard +- Make mouse important but much less so + +Needs to change: +- Make it more similar to sublime like editors +- Need Ctrl + P +- Clickable title bar may be cool or whatever but it's pretty bad +- Executing lua commands is clunky, need a real Ctrl+P and keybind actions, popups: remove a lot of the lua functionality, just for config files +- Window, View, Buffer + flags design (or is completely new kind based approach needed for Windows/Views?) + - How to make non-editable, informative, with different font size, title bar. Which might also contain tabs + - How to design clickable tree view in this way? + - How to design Command view? + - How to design popup view (input field)? + - How to design search view? or search and replace view? +- Window management, splitting, GC +- add modified stb_sprintf + +Things I like: +- Basic editing +- Configurable Open +- Lua config files work pretty well + + + + + + + + + + + + + + + + DESIGN try to make console less special, make stuff reusable etc. DESIGN Config file versions, when loading should be checked, at the top of the file, what to do when old version? ISSUE Ctrl+Alt+Down (DuplicateLine) doesn't work on ubuntu