From 5db8a6001443816f3bd55c3b843512b82874a6df Mon Sep 17 00:00:00 2001 From: doyle Date: Wed, 28 Jul 2021 21:10:25 +1000 Subject: [PATCH] Add some more helper libraries --- Code/Dqn.h | 1833 ++++++++++++++++++------------------------- Code/Dqn_Curl.h | 80 ++ Code/Dqn_Jsmn.h | 684 ++++++++++++++++ Code/Dqn_MetaDesk.h | 948 ++++++++++++++++++++++ Code/Dqn_Money.h | 95 +++ Code/Dqn_Tests.cpp | 370 ++++++--- Code/Dqn_U128.h | 1039 ++++++++++++++++++++++++ 7 files changed, 3904 insertions(+), 1145 deletions(-) create mode 100644 Code/Dqn_Curl.h create mode 100644 Code/Dqn_Jsmn.h create mode 100644 Code/Dqn_MetaDesk.h create mode 100644 Code/Dqn_Money.h create mode 100644 Code/Dqn_U128.h diff --git a/Code/Dqn.h b/Code/Dqn.h index 313fe99..492a014 100644 --- a/Code/Dqn.h +++ b/Code/Dqn.h @@ -1,6 +1,6 @@ -// ------------------------------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- // NOTE: Dqn -// ------------------------------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- // General all-purpose utility library. // // ----------------------------------------------------------------------------- @@ -51,26 +51,6 @@ // the descriptor/name to describe the allocation. All extra parameters and // tags are compiled out when tracing is disabled. // -// #define DQN_ALLOCATOR_DEFAULT_TO_NULL -// If defined, zero initialising an allocator uses the null allocator (i.e. -// crash on allocation). This forces the user to specify explicitly which -// allocator to use, for example. -// -// Dqn_Allocator allocator = {}; -// Dqn_Allocator_Allocate(allocator, ...) // <- This asserts and crashes -// -// So you have to explicitly do -// -// Dqn_Allocator allocator = {}; -// allocator.type = Dqn_AllocatorType::Heap; -// -// or -// -// Dqn_Allocator allocator = Dqn_AllocatorHeap() -// -// When not defined a zero initialised allocator will use the Heap -// Dqn_Allocator via malloc, realloc, free. -// // ------------------------------------------------------------------------------------------------- // NOTE: Library Config // ------------------------------------------------------------------------------------------------- @@ -475,7 +455,7 @@ DQN_API void Dqn__ZeroMemBytes(void *ptr, Dqn_usize count, Dqn_ZeroMem zero_mem) #error "Compiler not supported" #endif -// TODO(doyle): Force inline +// TODO(dqn): Force inline inline Dqn_i64 Dqn_AtomicSetValue64(Dqn_i64 volatile *target, Dqn_i64 value) { #if defined(DQN_COMPILER_W32_MSVC) || defined(DQN_COMPILER_W32_CLANG) @@ -864,38 +844,6 @@ DQN_API char *Dqn_PointerMetadata_Init (void *ptr, Dqn_isi DQN_API Dqn_PointerMetadata Dqn_PointerMetadata_Get (void *ptr); DQN_API char *Dqn_PointerMetadata_GetRawPointer(void *ptr); -// ------------------------------------------------------------------------------------------------- -// NOTE: Dqn_Map -// ------------------------------------------------------------------------------------------------- -using Dqn_MapUsageBitset = Dqn_usize; - -template -struct Dqn_Map -{ - Dqn_isize count; // Count of actual 'values' stored in the map so far. - Dqn_isize size; // Total 'values' the map can store - T *values; // Storage of the 'values' a key maps to - Dqn_MapUsageBitset *usage_bitsets; // Array of numbers where each bit represents if the map slot in 'data' is occupied or not. - Dqn_isize usage_bitsets_size; // The number of usage bitsets -}; - -// Internal structure for finding a key in the Map -struct Dqn__MapKeyLookup -{ - Dqn_isize index; // Index into the 'values' array containing the value for the 'key' - Dqn_isize bitset_index; // Index into the 'usage_bitset' array for the usage of the - Dqn_usize bit_index; // The N'th bit in the 'usage_bitset' - Dqn_usize bitset_bit; // The N'th bit, bit-shifted into bitset position -}; - -template Dqn_isize Dqn_Map_MemoryRequired(Dqn_isize num_values); -template Dqn_Map Dqn_Map_InitWithMemory(void *mem, Dqn_isize mem_size); -template Dqn__MapKeyLookup Dqn_Map__GetKeyLookup (Dqn_Map const *map, Dqn_u64 key); -template T *Dqn_Map_FindOrMake (Dqn_Map *map, Dqn_u64 key, Dqn_b32 *found = nullptr); -template Dqn_b32 Dqn_Map_Add (Dqn_Map *map, Dqn_u64 key, T const &value); -template T *Dqn_Map_Get (Dqn_Map *map, Dqn_u64 key); -template Dqn_b32 Dqn_Map_Erase (Dqn_Map *map, Dqn_u64 key); - // ------------------------------------------------------------------------------------------------- // NOTE: Dqn_AllocationTracer // ------------------------------------------------------------------------------------------------- @@ -921,9 +869,9 @@ struct Dqn_AllocationTrace struct Dqn_AllocationTracer { - // NOTE: Read Only Fields + // NOTE: Read only fields Dqn_TicketMutex mutex; - Dqn_Map map; + // Dqn_Map map; }; void Dqn_AllocationTracer_Add (Dqn_AllocationTracer *tracer, void *ptr, Dqn_usize size DQN_CALL_SITE_ARGS); @@ -933,19 +881,15 @@ void Dqn_AllocationTracer_Remove(Dqn_AllocationTracer *tracer, void *ptr); // NOTE: Dqn_CRTAllocator // ------------------------------------------------------------------------------------------------- // -// CRT Style allocators unrelated to Dqn_Allocator that are simply for -// interfacing with foreign libraries that allow you to override malloc, realloc -// and free. +// CRT Style allocators that are for interfacing with foreign libraries that +// allow you to override malloc, realloc and free. // -// Dqn_Allocator and friends (Dqn_ArenaAllocator, ... etc) are not designed to -// be used for replacing library allocation stubs that expect to use CRT style -// allocation, i.e. malloc and friends. This is by design, C libraries designed -// around that paradigm should not be shoe-horned into another allocation scheme -// as the library you're interfacing with has been designed with the liberal -// allocating and freeing style encouraged by the CRT. -// -// In response, Dqn_Allocator itself does not implement realloc to discourage -// its use. (Use virtual memory tricks to avoid reallocation altogether). +// Dqn_ArenaAllocator is not designed to be used for replacing library +// allocation stubs that expect to use CRT style allocation, i.e. malloc and +// friends. This is by design, C libraries designed around that paradigm should +// not be shoe-horned into another allocation scheme as the library you're +// interfacing with has been designed with the liberal allocating and freeing +// style encouraged by the CRT. // #define DQN_CRT_ALLOCATOR_MALLOC(name) void *name(size_t size) #define DQN_CRT_ALLOCATOR_REALLOC(name) void *name(void *ptr, size_t new_size) @@ -984,136 +928,80 @@ DQN_API void *Dqn_CRTAllocator__Malloc (Dqn_CRTAllocator *alloc DQN_API void *Dqn_CRTAllocator__Realloc (Dqn_CRTAllocator *allocator, void *ptr, Dqn_usize size DQN_CALL_SITE_ARGS); // ------------------------------------------------------------------------------------------------- -// NOTE: Dqn_Allocator +// NOTE: Dqn_Map // ------------------------------------------------------------------------------------------------- -enum struct Dqn_AllocatorType -{ -#if defined(DQN_ALLOCATOR_DEFAULT_TO_NULL) - Null, - Heap, // Malloc, free -#else - Heap, // Malloc, free - Null, -#endif +struct Dqn_ArenaAllocator; // Foward declare - XHeap, // Malloc free, crash on failure - Arena, - Custom, -}; - -// For a custom free routine, return the number of bytes freed. Though this is -// optional and only for making sure the allocator updates it's allocation stats -// accordingly. -#define DQN_ALLOCATOR_CUSTOM_ALLOCATE_PROC(name) void *name(Dqn_isize size, Dqn_u8 alignment, void *user_context DQN_CALL_SITE_ARGS) -#define DQN_ALLOCATOR_CUSTOM_FREE_PROC(name) Dqn_isize name(void *ptr, void *user_context) -typedef DQN_ALLOCATOR_CUSTOM_ALLOCATE_PROC(Dqn_Allocator_CustomAllocateProc); -typedef DQN_ALLOCATOR_CUSTOM_FREE_PROC(Dqn_Allocator_CustomFreeProc); - -// Example of a custom allocator proc -/* -DQN_ALLOCATOR_CUSTOM_ALLOCATE_PROC(Dqn_Allocator_MallocProc) -{ - Dqn_usize allocation_size = Dqn_PointerMetadata_SizeRequired(size, alignment); - void * result = malloc(allocation_size); - return result; -} - -DQN_ALLOCATOR_CUSTOM_FREE_PROC(Dqn_Allocator_FreeProc) -{ - Dqn_PointerMetadata meta = Dqn_PointerMetadata_Get(ptr); - void * raw_ptr = Dqn_PointerMetadata_GetRawPointer(ptr); - free(raw_ptr, meta.size); - return meta.size; -} -*/ - -struct Dqn_Allocator -{ - Dqn_AllocatorType type; - union - { - void *user; - struct Dqn_ArenaAllocator *arena; - } context; - - Dqn_AllocationTracer *tracer; - Dqn_isize bytes_allocated; - Dqn_isize allocations; - Dqn_isize total_bytes_allocated; - Dqn_isize total_allocations; - - // NOTE: Only required if type == Dqn_AllocatorType::Custom - struct - { - Dqn_Allocator_CustomAllocateProc *allocate; - Dqn_Allocator_CustomFreeProc *free; - } custom; -}; - -DQN_API Dqn_Allocator Dqn_Allocator_InitWithNull (); -DQN_API Dqn_Allocator Dqn_Allocator_InitWithHeap (); -DQN_API Dqn_Allocator Dqn_Allocator_InitWithXHeap (); -DQN_API Dqn_Allocator Dqn_Allocator_InitWithArena (Dqn_ArenaAllocator *arena); -DQN_API Dqn_Allocator Dqn_Allocator_InitWithProcs (Dqn_Allocator_CustomAllocateProc *allocate_proc, Dqn_Allocator_CustomFreeProc *free_proc); -DQN_API void Dqn_Allocator_Free (Dqn_Allocator *allocator, void *ptr); - -// A tagged allocation is annotated with a defined string (the tag) which will be associated with -// the allocation if the allocator was initialised with a MemoryTracer and DQN_ALLOCATION_TRACING is -// defined. It is compiled out otherwise leaving no impact on the binary. - -// Allocate some bytes aligned to a custom alignment. -#define Dqn_Allocator_TaggedAllocate(allocator, size, alignment, zero_mem, tag) Dqn_Allocator__Allocate(allocator, size, alignment, zero_mem DQN_CALL_SITE(tag)) -#define Dqn_Allocator_Allocate( allocator, size, alignment, zero_mem) Dqn_Allocator__Allocate(allocator, size, alignment, zero_mem DQN_CALL_SITE("")) - -// Allocate a 'type' aligned to a custom alignment. -#define Dqn_Allocator_TaggedNew( allocator, Type, zero_mem, tag) (Type *)Dqn_Allocator__Allocate(allocator, sizeof(Type), alignof(Type), zero_mem DQN_CALL_SITE(tag)) -#define Dqn_Allocator_New( allocator, Type, zero_mem) (Type *)Dqn_Allocator__Allocate(allocator, sizeof(Type), alignof(Type), zero_mem DQN_CALL_SITE("")) - -// Allocate an array of 'type' aligned to the type's natural alignment using alignof -#define Dqn_Allocator_TaggedNewArray(allocator, Type, count, zero_mem, tag) (Type *)Dqn_Allocator__Allocate(allocator, sizeof(Type) * count, alignof(Type), zero_mem DQN_CALL_SITE(tag)) -#define Dqn_Allocator_NewArray( allocator, Type, count, zero_mem) (Type *)Dqn_Allocator__Allocate(allocator, sizeof(Type) * count, alignof(Type), zero_mem DQN_CALL_SITE("")) - -// Internal API. Avoid using, and prefer the macros above. -DQN_API void *Dqn_Allocator__Allocate (Dqn_Allocator *allocator, Dqn_isize size, Dqn_u8 alignment, Dqn_ZeroMem zero_mem DQN_CALL_SITE_ARGS); - -// ------------------------------------------------------------------------------------------------- -// NOTE: Dqn_Slices -// ------------------------------------------------------------------------------------------------- -#define DQN_SLICE_FMT(slice) (slice).size, (slice).data template -struct Dqn_Slice +struct Dqn_MapEntry { - T *data; - Dqn_isize size; - - T const &operator[] (Dqn_isize i) const { DQN_ASSERT_MSG(i >= 0 && i < size, "%d >= 0 && %d < %d", i, size); return data[i]; } - T &operator[] (Dqn_isize i) { DQN_ASSERT_MSG(i >= 0 && i < size, "%d >= 0 && %d < %d", i, size); return data[i]; } - T const *begin () const { return data; } - T const *end () const { return data + size; } - T *begin () { return data; } - T *end () { return data + size; } - T const *operator+ (Dqn_isize i) const { DQN_ASSERT_MSG(i >= 0 && i < size, "%d >= 0 && %d < %d", i, size); return data + i; } - T *operator+ (Dqn_isize i) { DQN_ASSERT_MSG(i >= 0 && i < size, "%d >= 0 && %d < %d", i, size); return data + i; } + Dqn_u64 hash; + T value; + Dqn_MapEntry *next; }; -template DQN_API Dqn_Slice Dqn_Slice_Init (T *data, Dqn_isize size); -template DQN_API Dqn_Slice Dqn_Slice_InitWithArray (T (&array)[N]); +template +struct Dqn_Map +{ + Dqn_ArenaAllocator *arena; + Dqn_MapEntry **values; + Dqn_isize size; // The size of the 'values' list -#define Dqn_Slice_TaggedAllocate( allocator, Type, size, zero_mem, tag) Dqn_Slice__Allocate(allocator, size, zero_mem DQN_CALL_SITE(tag)) -#define Dqn_Slice_Allocate( allocator, Type, size, zero_mem) Dqn_Slice__Allocate(allocator, size, zero_mem DQN_CALL_SITE("")) -#define Dqn_Slice_ArenaTaggedAllocate(allocator, Type, size, zero_mem, tag) Dqn_Slice__ArenaAllocate(allocator, size, zero_mem DQN_CALL_SITE(tag)) -#define Dqn_Slice_ArenaAllocate( allocator, Type, size, zero_mem) Dqn_Slice__ArenaAllocate(allocator, size, zero_mem DQN_CALL_SITE("")) + // NOTE: Sum count and chain_count for total items in the list. + Dqn_isize count; // The total number of top-level slots in the 'values' list occupied + Dqn_isize chain_count; // The total number of chained elements in 'values' + Dqn_MapEntry *free_list; +}; -// Allocate and copy the bytes/slice into a new slice. The null terminated variants are for byte -// arrays and allocate an extra byte to ensure that the last byte is 0. -template DQN_API Dqn_Slice Dqn_Slice_CopyNullTerminated (struct Dqn_Allocator *allocator, T const *src, Dqn_isize size); -template DQN_API Dqn_Slice Dqn_Slice_CopyNullTerminated (struct Dqn_Allocator *allocator, Dqn_Slice const src); -template DQN_API Dqn_Slice Dqn_Slice_Copy (struct Dqn_Allocator *allocator, T const *src, Dqn_isize size); -template DQN_API Dqn_Slice Dqn_Slice_Copy (struct Dqn_Allocator *allocator, Dqn_Slice const src); +enum struct Dqn_MapCollideRule +{ + Overwrite, + Chain, + Fail, +}; -// Check equality of two slices using memcmp, operator== checks slice equality if size and pointers are the same -template DQN_API Dqn_b32 Dqn_Slice_Memcmp (Dqn_Slice const a, Dqn_Slice const b); -template DQN_API Dqn_b32 operator== (Dqn_Slice const &lhs, Dqn_Slice const &rhs); +template Dqn_Map Dqn_Map_InitWithArena(Dqn_ArenaAllocator *arena, Dqn_isize size = 0); +template Dqn_MapEntry *Dqn_Map_FindOrAdd (Dqn_Map *map, Dqn_u64 hash, Dqn_MapCollideRule rule); +template Dqn_MapEntry *Dqn_Map_Add (Dqn_Map *map, Dqn_u64 hash, T *value, Dqn_MapCollideRule rule); +template Dqn_MapEntry *Dqn_Map_AddCopy (Dqn_Map *map, Dqn_u64 hash, const T &value, Dqn_MapCollideRule rule); +template Dqn_MapEntry *Dqn_Map_Get (Dqn_Map *map, Dqn_u64 hash); +template void Dqn_Map_Erase (Dqn_Map *map, Dqn_u64 hash, Dqn_ZeroMem zero_mem); + +// ------------------------------------------------------------------------------------------------- +// NOTE: Dqn_Array +// ------------------------------------------------------------------------------------------------- +template struct Dqn_Array +{ + Dqn_ArenaAllocator *arena; + T *data; + Dqn_isize size; + Dqn_isize max; + + T const operator[](Dqn_isize i) const { DQN_ASSERT_MSG(i >= 0 && i < size, "%d >= 0 && %d < %d", i, size); return data[i]; } + T operator[](Dqn_isize i) { DQN_ASSERT_MSG(i >= 0 && i < size, "%d >= 0 && %d < %d", i, size); return data[i]; } + T const *begin () const { return data; } + T const *end () const { return data + size; } + T *begin () { return data; } + T *end () { return data + size; } + T const *operator+(Dqn_isize i) const { DQN_ASSERT_MSG(i >= 0 && i < size, "%d >= 0 && %d < %d", i, size); return data + i; } + T *operator+(Dqn_isize i) { DQN_ASSERT_MSG(i >= 0 && i < size, "%d >= 0 && %d < %d", i, size); return data + i; } +}; + +template DQN_API Dqn_Array Dqn_Array_InitWithMemory (T *memory, Dqn_isize max, Dqn_isize size = 0); +#define Dqn_Array_InitWithArenaNoGrow( arena, Type, max, size, zero_mem) Dqn_Array__InitWithArenaNoGrow( arena, max, size, zero_mem DQN_CALL_SITE("")) + +#define Dqn_Array_Reserve( array, size) Dqn_Array__Reserve(array, size DQN_CALL_SITE("")) + +#define Dqn_Array_AddArray( array, items, num) Dqn_Array__AddArray(array, items, num DQN_CALL_SITE("")) +#define Dqn_Array_Add( array, item) Dqn_Array__Add(array, item DQN_CALL_SITE("")) +#define Dqn_Array_Make( array, num) Dqn_Array__Make(array, num DQN_CALL_SITE("")) +template DQN_API void Dqn_Array_Clear (Dqn_Array *a, Dqn_ZeroMem zero_mem = Dqn_ZeroMem::No); + +template DQN_API void Dqn_Array_EraseStable (Dqn_Array *a, Dqn_isize index); +template DQN_API void Dqn_Array_EraseUnstable (Dqn_Array *a, Dqn_isize index); + +template DQN_API void Dqn_Array_Pop (Dqn_Array *a, Dqn_isize num, Dqn_ZeroMem zero_mem = Dqn_ZeroMem::No); +template DQN_API T * Dqn_Array_Peek (Dqn_Array *a); // ------------------------------------------------------------------------------------------------- // NOTE: Dqn_String @@ -1122,14 +1010,9 @@ template DQN_API Dqn_b32 operator== #define DQN_STRING_FMT(string) (int)((string).size), (string).str struct Dqn_String { - union { - // NOTE: To appease GCC, Clang can't assign C string literal to char * - // Only UB if you try modify a string originally declared const - char const *str_; - char *str; - }; - Dqn_isize size; - Dqn_isize cap; + char *str; + Dqn_isize size; + Dqn_isize cap; char const *begin() const { return str; } char const *end () const { return str + size; } @@ -1137,52 +1020,62 @@ struct Dqn_String char *end () { return str + size; } }; -DQN_API Dqn_String Dqn_String_Init (char const *str, Dqn_isize size); +struct Dqn_StringW +{ + wchar_t *str; + Dqn_isize size; + Dqn_isize cap; -// return: The allocated string. When allocation fails, str returned is nullptr, size is set to the length required NOT INCLUDING the null terminator. -// i.e. the required buffer length for generating the string is (result.size + 1). -#define Dqn_String_InitTaggedFmtV( allocator, fmt, va, tag) Dqn_String__InitFmtV(allocator, fmt, va DQN_CALL_SITE(tag)) -#define Dqn_String_InitFmtV( allocator, fmt, va) Dqn_String__InitFmtV(allocator, fmt, va DQN_CALL_SITE("")) -DQN_API Dqn_String Dqn_String__InitFmtV (Dqn_Allocator *allocator, char const *fmt, va_list va DQN_CALL_SITE_ARGS); + wchar_t const *begin() const { return str; } + wchar_t const *end () const { return str + size; } + wchar_t *begin() { return str; } + wchar_t *end () { return str + size; } +}; -#define Dqn_String_InitTaggedFmt( allocator, tag, fmt, ...) Dqn_String__InitFmt(allocator DQN_CALL_SITE(tag), fmt, ## __VA_ARGS__) -#define Dqn_String_InitFmt( allocator, fmt, ...) Dqn_String__InitFmt(allocator DQN_CALL_SITE(""), fmt, ## __VA_ARGS__) -DQN_API Dqn_String Dqn_String__InitFmt (Dqn_Allocator *allocator DQN_CALL_SITE_ARGS, char const *fmt, ...); +// Make a string from a pre-existing string. +DQN_API Dqn_String Dqn_String_Init (char const *string, Dqn_isize size); -#define Dqn_String_InitArenaTaggedFmt( arena, tag, fmt, ...) Dqn_String__InitArenaFmt(arena DQN_CALL_SITE(tag), fmt, ## __VA_ARGS__) -#define Dqn_String_InitArenaFmt( arena, fmt, ...) Dqn_String__InitArenaFmt(arena DQN_CALL_SITE(""), fmt, ## __VA_ARGS__) -DQN_API Dqn_String Dqn_String__InitArenaFmt (Dqn_ArenaAllocator *arena DQN_CALL_SITE_ARGS, char const *fmt, ...); +// Make an empty string from a the buffer. 1 byte is reserved for the null-terminator +DQN_API Dqn_String Dqn_String_InitMemory(char *buf, Dqn_isize capacity); -#define Dqn_String_InitArenaTaggedFmtV( arena, tag, fmt, ...) Dqn_String__InitArenaFmtV(arena DQN_CALL_SITE(tag), fmt, ## __VA_ARGS__) -#define Dqn_String_InitArenaFmtV( arena, fmt, ...) Dqn_String__InitArenaFmtV(arena DQN_CALL_SITE(""), fmt, ## __VA_ARGS__) -DQN_API Dqn_String Dqn_String__InitArenaFmtV (Dqn_ArenaAllocator *arena, char const *fmt, va_list va DQN_CALL_SITE_ARGS); +#define Dqn_String_TaggedFmt(arena, tag, fmt, ...) Dqn_String__Fmt(arena DQN_CALL_SITE(tag), fmt, ## __VA_ARGS__) +#define Dqn_String_Fmt(arena, fmt, ...) Dqn_String__Fmt(arena DQN_CALL_SITE(""), fmt, ## __VA_ARGS__) -DQN_API Dqn_String Dqn_String_Allocate (Dqn_Allocator *allocator, Dqn_isize size, Dqn_ZeroMem zero_mem); -DQN_API Dqn_String Dqn_String_ArenaAllocate (Dqn_ArenaAllocator *arena, Dqn_isize size, Dqn_ZeroMem zero_mem); -DQN_API Dqn_b32 Dqn_String_Compare (Dqn_String const lhs, Dqn_String const rhs); -DQN_API Dqn_b32 Dqn_String_CompareCaseInsensitive(Dqn_String const lhs, Dqn_String const rhs); +#define Dqn_String_TaggedFmtV(arena, tag, fmt, ...) Dqn_String__FmtV(arena DQN_CALL_SITE(tag), fmt, ## __VA_ARGS__) +#define Dqn_String_FmtV(arena, fmt, ...) Dqn_String__FmtV(arena DQN_CALL_SITE(""), fmt, ## __VA_ARGS__) -// allocator: (Optional) When null, the string is allocated with DQN_MALLOC, result should be freed with DQN_FREE. -#define Dqn_String_Copy( src, allocator) Dqn_String__Copy(src, allocator DQN_CALL_SITE("")) -#define Dqn_String_ArenaCopy( src, arena) Dqn_String__ArenaCopy(src, arena DQN_CALL_SITE("")) +#define Dqn_String_TaggedAllocate(arena, size, zero_mem, tag) Dqn_String__Allocate(arena, size, zero_mem DQN_CALL_SITE(tag)); +#define Dqn_String_Allocate(arena, size, zero_mem) Dqn_String__Allocate(arena, size, zero_mem DQN_CALL_SITE("")); -DQN_API Dqn_String Dqn_String__Copy (Dqn_String const src, Dqn_Allocator *allocator DQN_CALL_SITE_ARGS); -DQN_API Dqn_String Dqn_String__ArenaCopy (Dqn_String const src, Dqn_ArenaAllocator *arena DQN_CALL_SITE_ARGS); -DQN_API Dqn_String Dqn_String_TrimWhitespaceAround (Dqn_String src); -DQN_API Dqn_b32 operator== (Dqn_String const &lhs, Dqn_String const &rhs); +#define Dqn_String_TaggedCopy(src, arena, tag) Dqn_String__Copy(src, arena DQN_CALL_SITE(tag)) +#define Dqn_String_Copy(src, arena) Dqn_String__Copy(src, arena DQN_CALL_SITE("")) + +DQN_API Dqn_String Dqn_String__Fmt(Dqn_ArenaAllocator *arena DQN_CALL_SITE_ARGS, char const *fmt, ...); +DQN_API Dqn_String Dqn_String__FmtV(Dqn_ArenaAllocator *arena, char const *fmt, va_list va DQN_CALL_SITE_ARGS); +DQN_API Dqn_String Dqn_String__Allocate(Dqn_ArenaAllocator *arena, Dqn_isize size, Dqn_ZeroMem zero_mem); +DQN_API Dqn_String Dqn_String__Copy(Dqn_String const src, Dqn_ArenaAllocator *arena DQN_CALL_SITE_ARGS); + +DQN_API Dqn_String Dqn_String_TrimWhitespaceAround(Dqn_String src); +DQN_API Dqn_b32 operator== (Dqn_String const &lhs, Dqn_String const &rhs); // Append to the string if there's enough capacity. No reallocation is permitted, fails if not enough space DQN_API Dqn_b32 Dqn_String_AppendFmtV (Dqn_String *str, char const *fmt, va_list va); DQN_API Dqn_b32 Dqn_String_AppendFmt (Dqn_String *str, char const *fmt, ...); -// Free a string allocated with `Dqn_String_Copy`, `Dqn_String_InitFmtV` `Dqn_String_InitFmt` -// allocator: The same allocator specified when `Dqn_String_Copy` was called. -DQN_API void Dqn_String_Free (Dqn_String *string, Dqn_Allocator *allocator); -DQN_API Dqn_b32 Dqn_String_StartsWith(Dqn_String string, Dqn_String prefix); -DQN_API Dqn_Slice Dqn_String_Split (Dqn_String src, Dqn_Allocator *allocator); +enum struct Dqn_StringEqCase +{ + Sensitive, + Insensitive, +}; -DQN_API Dqn_u64 Dqn_String_ToU64 (Dqn_String str); -DQN_API Dqn_i64 Dqn_String_ToI64 (Dqn_String str); +DQN_API Dqn_b32 Dqn_String_Eq (Dqn_String const lhs, Dqn_String const rhs, Dqn_StringEqCase eq_case = Dqn_StringEqCase::Sensitive); +DQN_API Dqn_b32 Dqn_String_EqInsensitive (Dqn_String const lhs, Dqn_String const rhs); +DQN_API Dqn_b32 Dqn_String_StartsWith (Dqn_String string, Dqn_String prefix, Dqn_StringEqCase eq_case = Dqn_StringEqCase::Sensitive); +DQN_API Dqn_b32 Dqn_String_StartsWithInsensitive(Dqn_String string, Dqn_String prefix); +DQN_API Dqn_Array Dqn_String_Split (Dqn_String src, Dqn_ArenaAllocator *arena); + +DQN_API Dqn_u64 Dqn_String_ToU64 (Dqn_String str); +DQN_API Dqn_i64 Dqn_String_ToI64 (Dqn_String str); // ------------------------------------------------------------------------------------------------- @@ -1211,7 +1104,7 @@ struct Dqn_FixedString char *end () { return data + size; } }; -template DQN_API Dqn_FixedString Dqn_FixedString_InitFmt (char const *fmt, ...); +template DQN_API Dqn_FixedString Dqn_FixedString_Fmt (char const *fmt, ...); template DQN_API Dqn_isize Dqn_FixedString_Max (Dqn_FixedString *); template DQN_API void Dqn_FixedString_Clear (Dqn_FixedString *str); template DQN_API Dqn_b32 Dqn_FixedString_AppendFmtV(Dqn_FixedString *str, char const *fmt, va_list va); @@ -1434,39 +1327,6 @@ DQN_API Dqn_V2I Dqn_RectI32_Size (Dqn_RectI32 rect); DQN_API Dqn_V2 Dqn_LerpV2 (Dqn_V2 a, Dqn_f32 t, Dqn_V2 b); DQN_API Dqn_f32 Dqn_LerpF32(Dqn_f32 a, Dqn_f32 t, Dqn_f32 b); -// ------------------------------------------------------------------------------------------------- -// NOTE: Dqn_StringMap -// ------------------------------------------------------------------------------------------------- -template -struct Dqn_StringMapEntry -{ - Dqn_String key; - T *value; - Dqn_StringMapEntry *next; -}; - -template -struct Dqn_StringMap -{ - Dqn_Allocator backup_allocator; - Dqn_u32 hashing_seed; - Dqn_Allocator *allocator; - Dqn_StringMapEntry **values; - Dqn_isize size; -}; - -enum struct Dqn_StringMapCollisionRule -{ - Stop, - Chain, -}; - -template void Dqn_StringMap__InitializeSize(Dqn_StringMap *map, Dqn_isize size = 0); -template Dqn_StringMap Dqn_StringMap_InitWithArena (Dqn_ArenaAllocator *arena, Dqn_isize size = 0, Dqn_u32 hashing_seed = 0); -template Dqn_u64 Dqn_StringMap_Hash (Dqn_StringMap *map, Dqn_String key); -template Dqn_StringMapEntry *Dqn_StringMap_Add (Dqn_StringMap *map, Dqn_String key, T *value, Dqn_StringMapCollisionRule rule); -template Dqn_StringMapEntry *Dqn_StringMap_Get (Dqn_StringMap *map, Dqn_String key); - // ------------------------------------------------------------------------------------------------- // NOTE: Dqn_ArenaAllocator // ------------------------------------------------------------------------------------------------- @@ -1480,20 +1340,28 @@ struct Dqn_ArenaAllocatorBlock Dqn_ArenaAllocatorBlock *next; }; +enum struct Dqn_ArenaAllocatorMemProvider +{ + CRT, + Virtual, + UserMemory, +}; + Dqn_usize const DQN_MEM_ARENA_DEFAULT_MIN_BLOCK_SIZE = DQN_KILOBYTES(4); struct Dqn_ArenaAllocator { - // NOTE: Manual Configuration - // (Fill once after zero init OR not needed if Init(...) calls are used) - Dqn_isize min_block_size; - Dqn_Allocator backup_allocator; - Dqn_Allocator *allocator; + Dqn_ArenaAllocatorMemProvider mem_provider; + + // NOTE: Read/Write + Dqn_isize min_block_size; // (Optional): When 0, DQN_MEM_ARENA_DEFAULT_MIN_BLOCK_SIZE is used. Otherwise every new block will at minimum be sized to this value. + + // The following fields are should be set once after zero initialisation Dqn_AllocationTracer *tracer; // NOTE: Read Only Dqn_ArenaAllocatorBlock *curr_mem_block; Dqn_ArenaAllocatorBlock *top_mem_block; - Dqn_isize highest_used_mark; // TODO(doyle): This is not implemented yet + Dqn_isize highest_used_mark; // TODO(dqn): This is not implemented yet int total_allocated_mem_blocks; // Total throughout the life-time of the arena Dqn_isize usage_before_last_reset; Dqn_isize wastage_before_last_reset; @@ -1507,10 +1375,10 @@ struct Dqn_ArenaAllocatorRegion Dqn_ArenaAllocatorBlock *top_mem_block; }; -struct Dqn_ArenaAllocatorScopedRegion +struct Dqn_ArenaAllocatorAutoRegion { - Dqn_ArenaAllocatorScopedRegion(Dqn_ArenaAllocator *arena); - ~Dqn_ArenaAllocatorScopedRegion(); + Dqn_ArenaAllocatorAutoRegion(Dqn_ArenaAllocator *arena); + ~Dqn_ArenaAllocatorAutoRegion(); Dqn_ArenaAllocatorRegion region; }; @@ -1522,25 +1390,32 @@ struct Dqn_ArenaAllocatorStats Dqn_isize total_blocks; }; -DQN_API Dqn_ArenaAllocator Dqn_ArenaAllocator_InitWithNewAllocator(Dqn_Allocator allocator, Dqn_isize size, Dqn_AllocationTracer *tracer DQN_CALL_SITE_ARGS); -DQN_API Dqn_ArenaAllocator Dqn_ArenaAllocator_InitWithAllocator (Dqn_Allocator *allocator, Dqn_isize size, Dqn_AllocationTracer *tracer DQN_CALL_SITE_ARGS); -DQN_API Dqn_ArenaAllocator Dqn_ArenaAllocator_InitWithMemory (void *memory, Dqn_isize size, Dqn_AllocationTracer *tracer = nullptr); -DQN_API void Dqn_ArenaAllocator_Free (Dqn_ArenaAllocator *arena); -DQN_API Dqn_b32 Dqn_ArenaAllocator_Reserve (Dqn_ArenaAllocator *arena, Dqn_isize size DQN_CALL_SITE_ARGS); -DQN_API void Dqn_ArenaAllocator_ResetUsage (Dqn_ArenaAllocator *arena, Dqn_ZeroMem zero_mem); -DQN_API Dqn_ArenaAllocatorRegion Dqn_ArenaAllocator_BeginRegion (Dqn_ArenaAllocator *arena); -DQN_API void Dqn_ArenaAllocator_EndRegion (Dqn_ArenaAllocatorRegion region); -DQN_API Dqn_ArenaAllocatorScopedRegion Dqn_ArenaAllocator_MakeScopedRegion (Dqn_ArenaAllocator *arena); +// NOTE: Dqn_ArenaAllocator can also be zero initialised and will default to the heap allocator with 0 size. +DQN_API Dqn_ArenaAllocator Dqn_ArenaAllocator_InitWithMemory(void *memory, Dqn_isize size); +DQN_API Dqn_ArenaAllocator Dqn_ArenaAllocator_InitWithCRT (Dqn_isize size DQN_CALL_SITE_ARGS); +DQN_API void Dqn_ArenaAllocator_Free (Dqn_ArenaAllocator *arena); +DQN_API Dqn_b32 Dqn_ArenaAllocator_Reserve (Dqn_ArenaAllocator *arena, Dqn_isize size DQN_CALL_SITE_ARGS); +DQN_API void Dqn_ArenaAllocator_ResetUsage (Dqn_ArenaAllocator *arena, Dqn_ZeroMem zero_mem); +DQN_API Dqn_ArenaAllocatorRegion Dqn_ArenaAllocator_BeginRegion (Dqn_ArenaAllocator *arena); +DQN_API void Dqn_ArenaAllocator_EndRegion (Dqn_ArenaAllocatorRegion region); +DQN_API Dqn_ArenaAllocatorAutoRegion Dqn_ArenaAllocator_AutoRegion (Dqn_ArenaAllocator *arena); -#define Dqn_ArenaAllocator_TaggedAllocate( arena, size, alignment, zero_mem, tag) Dqn_ArenaAllocator__Allocate(arena, size, alignment, zero_mem DQN_CALL_SITE(tag)) -#define Dqn_ArenaAllocator_Allocate( arena, size, alignment, zero_mem) Dqn_ArenaAllocator__Allocate(arena, size, alignment, zero_mem DQN_CALL_SITE("")) +#define Dqn_ArenaAllocator_TaggedAllocate(arena, size, alignment, zero_mem, tag) Dqn_ArenaAllocator__Allocate(arena, size, alignment, zero_mem DQN_CALL_SITE(tag)) +#define Dqn_ArenaAllocator_Allocate(arena, size, alignment, zero_mem) Dqn_ArenaAllocator__Allocate(arena, size, alignment, zero_mem DQN_CALL_SITE("")) -#define Dqn_ArenaAllocator_TaggedNew( arena, Type, zero_mem, tag) (Type *)Dqn_ArenaAllocator__Allocate(arena, sizeof(Type), alignof(Type), zero_mem DQN_CALL_SITE(tag)) -#define Dqn_ArenaAllocator_New( arena, Type, zero_mem) (Type *)Dqn_ArenaAllocator__Allocate(arena, sizeof(Type), alignof(Type), zero_mem DQN_CALL_SITE("")) +#define Dqn_ArenaAllocator_TaggedNew(arena, Type, zero_mem, tag) (Type *)Dqn_ArenaAllocator__Allocate(arena, sizeof(Type), alignof(Type), zero_mem DQN_CALL_SITE(tag)) +#define Dqn_ArenaAllocator_New(arena, Type, zero_mem) (Type *)Dqn_ArenaAllocator__Allocate(arena, sizeof(Type), alignof(Type), zero_mem DQN_CALL_SITE("")) -#define Dqn_ArenaAllocator_TaggedNewArray( arena, Type, count, zero_mem, tag) (Type *)Dqn_ArenaAllocator__Allocate(arena, sizeof(Type) * count, alignof(Type), zero_mem DQN_CALL_SITE(tag)) -#define Dqn_ArenaAllocator_NewArray( arena, Type, count, zero_mem) (Type *)Dqn_ArenaAllocator__Allocate(arena, sizeof(Type) * count, alignof(Type), zero_mem DQN_CALL_SITE("")) +#define Dqn_ArenaAllocator_TaggedNewArray(arena, Type, count, zero_mem, tag) (Type *)Dqn_ArenaAllocator__Allocate(arena, sizeof(Type) * count, alignof(Type), zero_mem DQN_CALL_SITE(tag)) +#define Dqn_ArenaAllocator_NewArray(arena, Type, count, zero_mem) (Type *)Dqn_ArenaAllocator__Allocate(arena, sizeof(Type) * count, alignof(Type), zero_mem DQN_CALL_SITE("")) +#define Dqn_ArenaAllocator_TaggedCopyNullTerminate(arena, Type, src, count, tag) (Type *)Dqn_ArenaAllocator__CopyNullTerminate(arena, src, sizeof(*src) * count, alignof(Type) DQN_CALL_SITE(tag)) +#define Dqn_ArenaAllocator_CopyNullTerminate(arena, Type, src, count) (Type *)Dqn_ArenaAllocator__CopyNullTerminate(arena, src, sizeof(*src) * count, alignof(Type) DQN_CALL_SITE("")) + +#define Dqn_ArenaAllocator_TaggedCopy(arena, Type, src, count, tag) (Type *)Dqn_ArenaAllocator__Copy(arena, src, sizeof(*src) * count, alignof(Type) DQN_CALL_SITE(tag)) +#define Dqn_ArenaAllocator_Copy(arena, Type, src, count) (Type *)Dqn_ArenaAllocator__Copy(arena, src, sizeof(*src) * count, alignof(Type) DQN_CALL_SITE("")) +DQN_API void *Dqn_ArenaAllocator__Copy (Dqn_ArenaAllocator *arena, void *src, Dqn_isize size, Dqn_u8 alignment DQN_CALL_SITE_ARGS); +DQN_API void *Dqn_ArenaAllocator__CopyNullTerminate (Dqn_ArenaAllocator *arena, void *src, Dqn_isize size, Dqn_u8 alignment DQN_CALL_SITE_ARGS); DQN_API void *Dqn_ArenaAllocator__Allocate (Dqn_ArenaAllocator *arena, Dqn_isize size, Dqn_u8 alignment, Dqn_ZeroMem zero_mem DQN_CALL_SITE_ARGS); DQN_API Dqn_ArenaAllocatorStats Dqn_ArenaAllocator_GetStats (Dqn_ArenaAllocator const *arena); DQN_API void Dqn_ArenaAllocator_DumpStatsToLog (Dqn_ArenaAllocator const *arena, char const *label); @@ -1592,14 +1467,29 @@ DQN_API char Dqn_Char_ToHex (char ch); DQN_API char Dqn_Char_ToHexUnchecked(char ch); DQN_API char Dqn_Char_ToLower (char ch); +// ------------------------------------------------------------------------------------------------- +// NOTE: Dqn_Hex +// ------------------------------------------------------------------------------------------------- +DQN_API char const *Dqn_Hex_CStringTrimSpaceAnd0xPrefix(char const *hex, Dqn_isize size, Dqn_isize *real_size); +DQN_API Dqn_String Dqn_Hex_StringTrimSpaceAnd0xPrefix (Dqn_String const string); + +// Convert a char string to a binary representation without any checks except assertions in debug. +DQN_API Dqn_u8 *Dqn_Hex_CStringToU8BytesUnchecked(char const *hex, Dqn_isize size, Dqn_isize *real_size, Dqn_ArenaAllocator *arena); +DQN_API Dqn_Array Dqn_Hex_CStringToU8ArrayUnchecked(char const *hex, Dqn_isize size, Dqn_ArenaAllocator *arena); +DQN_API Dqn_Array Dqn_Hex_StringToU8ArrayUnchecked (Dqn_String const hex, Dqn_ArenaAllocator *arena); + +// Convert a series of bytes into a string +DQN_API char *Dqn_Hex_U8BytesToCString(char const *bytes, Dqn_isize size, Dqn_ArenaAllocator *arena); +DQN_API Dqn_String Dqn_Hex_U8ArrayToString (Dqn_Array const bytes, Dqn_ArenaAllocator *arena); + // ------------------------------------------------------------------------------------------------- // NOTE: Dqn_Str // ------------------------------------------------------------------------------------------------- DQN_API Dqn_b32 Dqn_Str_Equals (char const *a, char const *b, Dqn_isize a_len = -1, Dqn_isize b_len = -1); DQN_API char const *Dqn_Str_FindMulti (char const *buf, char const *find_list[], Dqn_isize const *find_string_lens, Dqn_isize find_len, Dqn_isize *match_index, Dqn_isize buf_len = -1); DQN_API char const *Dqn_Str_Find (char const *buf, char const *find, Dqn_isize buf_len = -1, Dqn_isize find_len = -1, Dqn_b32 case_insensitive = false); -DQN_API char const *Dqn_Str_FileNameFromPath (char const *path, int len = -1, int *file_name_len = nullptr); -DQN_API Dqn_b32 Dqn_Str_Len (char const *a); +DQN_API char const *Dqn_Str_FileNameFromPath (char const *path, Dqn_isize len = -1, Dqn_isize *file_name_len = nullptr); +DQN_API Dqn_isize Dqn_Str_Size (char const *a); DQN_API Dqn_b32 Dqn_Str_Match (char const *src, char const *find, int find_len); DQN_API char const *Dqn_Str_SkipToChar (char const *src, char ch); DQN_API char const *Dqn_Str_SkipToNextAlphaNum (char const *src); @@ -1621,6 +1511,8 @@ DQN_API char const *Dqn_Str_TrimPrefix (char const *src, Dqn_isi DQN_API Dqn_u64 Dqn_Str_ToU64 (char const *buf, int len = -1, char separator = ','); DQN_API Dqn_i64 Dqn_Str_ToI64 (char const *buf, int len = -1, char separator = ','); +DQN_API Dqn_isize Dqn_StrW_Size (wchar_t const *a); + // ------------------------------------------------------------------------------------------------- // NOTE: Dqn_File // ------------------------------------------------------------------------------------------------- @@ -1640,12 +1532,12 @@ DQN_API Dqn_FileInfo Dqn_File_Info (char const *path); // file_size: (Optional) The size of the file in bytes, the allocated buffer is (file_size + 1 [null terminator]) in bytes. // allocator: (Optional) When null, the buffer is allocated with DQN_MALLOC, result should be freed with DQN_FREE. // return: nullptr if allocation failed. -#define Dqn_File_TaggedReadEntireFile(file, file_size, allocator, tag) Dqn_File__ReadEntireFile(file, file_size, allocator DQN_CALL_SITE(tag)) -#define Dqn_File_ReadEntireFile( file, file_size, allocator) Dqn_File__ReadEntireFile(file, file_size, allocator DQN_CALL_SITE("")) -#define Dqn_File_ArenaReadEntireFile( file, file_size, allocator) Dqn_File__ArenaReadEntireFile(file, file_size, allocator DQN_CALL_SITE("")) -DQN_API char *Dqn_File__ReadEntireFile (char const *file, Dqn_isize *file_size, Dqn_Allocator *allocator DQN_CALL_SITE_ARGS); -DQN_API char *Dqn_File__ArenaReadEntireFile(char const *file, Dqn_isize *file_size, Dqn_ArenaAllocator *arena DQN_CALL_SITE_ARGS); -DQN_API Dqn_b32 Dqn_File_WriteEntireFile (char const *file, char const *buffer, Dqn_isize buffer_size); +#define Dqn_File_ArenaReadFile( file, file_size, arena) Dqn_File__ArenaReadFile(file, file_size, arena DQN_CALL_SITE("")) +#define Dqn_File_TaggedArenaReadFileToString(file, arena, tag) Dqn_File__ArenaReadFileToString(file, arena DQN_CALL_SITE(tag)) +#define Dqn_File_ArenaReadFileToString(file, arena) Dqn_File__ArenaReadFileToString(file, arena DQN_CALL_SITE("")) +DQN_API char *Dqn_File__ArenaReadFile (char const *file, Dqn_isize *file_size, Dqn_ArenaAllocator *arena DQN_CALL_SITE_ARGS); +DQN_API Dqn_String Dqn_File__ArenaReadFileToString(char const *file, Dqn_ArenaAllocator *arena DQN_CALL_SITE_ARGS); +DQN_API Dqn_b32 Dqn_File_WriteFile (char const *file, char const *buffer, Dqn_isize buffer_size); // ------------------------------------------------------------------------------------------------- // NOTE: Utiltiies @@ -1655,7 +1547,7 @@ struct Dqn_U64Str // Points to the start of the str in the buffer, not necessarily buf since // we write into the buffer in reverse char *str; - char buf[27]; // NOTE(doyle): 27 is the maximum size of Dqn_u64 including commas + char buf[27]; // NOTE(dqn): 27 is the maximum size of Dqn_u64 including commas int len; }; @@ -1692,10 +1584,21 @@ DQN_API char *Dqn_U64ToStr (Dqn_u64 val, Dqn_U64Str *result, Dqn_b32 DQN_API char *Dqn_U64ToTempStr (Dqn_u64 val, Dqn_b32 comma_sep = true); // ------------------------------------------------------------------------------------------------- -// NOTE: Dqn_Win32 +// NOTE: Dqn_Win // ------------------------------------------------------------------------------------------------- -DQN_API Dqn_FixedString<1024> Dqn_Win32_LastError(); -DQN_API wchar_t *Dqn_Win32_ArenaToWChar(Dqn_ArenaAllocator *arena, Dqn_String src, int *wchar_size); +// last_error: (Optional) The error code associated with the last error will be written into this value +DQN_API Dqn_String Dqn_Win_LastError (Dqn_ArenaAllocator *arena, int *last_error); +DQN_API void Dqn_Win_DumpLastError(Dqn_ArenaAllocator *tmp_arena, Dqn_String prefix); +DQN_API Dqn_StringW Dqn_Win_UTF8ToWChar (Dqn_String src, Dqn_ArenaAllocator *arena); +DQN_API Dqn_String Dqn_Win_WCharToUTF8 (Dqn_StringW src, Dqn_ArenaAllocator *arena); + +// size: (Optional) The size of the current directory string returned +// suffix: (Optional) A suffix to append to the current working directory +// suffix_size: (Optional) The size of the suffix to append +DQN_API Dqn_String Dqn_Win_CurrentDir (Dqn_ArenaAllocator *arena, Dqn_ArenaAllocator *tmp_arena, Dqn_String suffix); +DQN_API Dqn_StringW Dqn_Win_CurrentDirW (Dqn_ArenaAllocator *arena, Dqn_StringW suffix); +DQN_API Dqn_Array Dqn_Win_FolderFiles (Dqn_String path, Dqn_ArenaAllocator *arena, Dqn_ArenaAllocator *tmp_arena); +DQN_API Dqn_Array Dqn_Win_FolderFilesW(Dqn_StringW path, Dqn_ArenaAllocator *arena); // ------------------------------------------------------------------------------------------------- // NOTE: Dqn_TimedBlock @@ -1770,34 +1673,31 @@ struct Dqn_StringBuilderBlock Dqn_StringBuilderBlock *next; }; +// TODO(dqn): Rewrite this as string lists Dqn_isize constexpr DQN_STRING_BUILDER_MIN_BLOCK_SIZE = DQN_KILOBYTES(4); template struct Dqn_StringBuilder { - Dqn_Allocator backup_allocator; - Dqn_Allocator *allocator; + Dqn_ArenaAllocator *arena; char fixed_mem[N]; Dqn_StringBuilderBlock fixed_mem_block; Dqn_StringBuilderBlock *last_mem_block; }; template DQN_API void Dqn_StringBuilder_InitWithArena (Dqn_StringBuilder *builder, Dqn_ArenaAllocator *arena); -template DQN_API void Dqn_StringBuilder_InitWithAllocator (Dqn_StringBuilder *builder, Dqn_Allocator *allocator); // Get the size required to build the string, it returns the length not including the null-terminator. template DQN_API Dqn_isize Dqn_StringBuilder_GetSize (Dqn_StringBuilder const *builder); template DQN_API void Dqn_StringBuilder_BuildToDest (Dqn_StringBuilder const *builder, char *dest, Dqn_usize dest_size); -template DQN_API Dqn_String Dqn_StringBuilder_BuildStringWithAllocator(Dqn_StringBuilder *builder, Dqn_Allocator *allocator); template DQN_API Dqn_String Dqn_StringBuilder_BuildStringWithArena (Dqn_StringBuilder *builder, Dqn_ArenaAllocator *arena); -#define Dqn_StringBuilder_Build( builder, allocator, len) Dqn_StringBuilder__Build(builder, allocator, len DQN_CALL_SITE("")) +#define Dqn_StringBuilder_Build( builder, arena, len) Dqn_StringBuilder__Build(builder, arena, len DQN_CALL_SITE("")) template DQN_API void Dqn_StringBuilder_AppendFmtV (Dqn_StringBuilder *builder, char const *fmt, va_list va); template DQN_API void Dqn_StringBuilder_AppendFmt (Dqn_StringBuilder *builder, char const *fmt, ...); template DQN_API void Dqn_StringBuilder_Append (Dqn_StringBuilder *builder, char const *str, Dqn_isize len = -1); template DQN_API void Dqn_StringBuilder_AppendString (Dqn_StringBuilder *builder, Dqn_String const string); template DQN_API void Dqn_StringBuilder_AppendChar (Dqn_StringBuilder *builder, char ch); -template DQN_API void Dqn_StringBuilder_Free (Dqn_StringBuilder *builder); // ------------------------------------------------------------------------------------------------- // NOTE: Dqn_FixedArray @@ -1822,7 +1722,6 @@ DQN_FIXED_ARRAY_TEMPLATE struct Dqn_FixedArray }; DQN_FIXED_ARRAY_TEMPLATE DQN_API DQN_FIXED_ARRAY_TEMPLATE_DECL Dqn_FixedArray_Init (T const *item, int num); -DQN_FIXED_ARRAY_TEMPLATE DQN_API Dqn_Slice Dqn_FixedArray_Slice (DQN_FIXED_ARRAY_TEMPLATE_DECL *a); DQN_FIXED_ARRAY_TEMPLATE DQN_API Dqn_isize Dqn_FixedArray_Max (DQN_FIXED_ARRAY_TEMPLATE_DECL const *); // Calculate the index of a item from the its pointer @@ -1847,55 +1746,9 @@ template DQN_API T *Dqn_FixedArra template DQN_API Dqn_b32 Dqn_FixedArray_FindElseMake (DQN_FIXED_ARRAY_TEMPLATE_DECL *a, T **entry, IsEqual IsEqualProc); DQN_FIXED_ARRAY_TEMPLATE DQN_API T *Dqn_FixedArray_Find (DQN_FIXED_ARRAY_TEMPLATE_DECL *a, T *find); -// ------------------------------------------------------------------------------------------------- -// NOTE: Dqn_Array -// ------------------------------------------------------------------------------------------------- -template struct Dqn_Array -{ - Dqn_Allocator allocator; - T *data; - Dqn_isize size; - Dqn_isize max; - - T const operator[](Dqn_isize i) const { DQN_ASSERT_MSG(i >= 0 && i < size, "%d >= 0 && %d < %d", i, size); return data[i]; } - T operator[](Dqn_isize i) { DQN_ASSERT_MSG(i >= 0 && i < size, "%d >= 0 && %d < %d", i, size); return data[i]; } - T const *begin () const { return data; } - T const *end () const { return data + size; } - T *begin () { return data; } - T *end () { return data + size; } - T const *operator+(Dqn_isize i) const { DQN_ASSERT_MSG(i >= 0 && i < size, "%d >= 0 && %d < %d", i, size); return data + i; } - T *operator+(Dqn_isize i) { DQN_ASSERT_MSG(i >= 0 && i < size, "%d >= 0 && %d < %d", i, size); return data + i; } -}; - -template DQN_API Dqn_Array Dqn_Array_InitWithMemory (T *memory, Dqn_isize max, Dqn_isize size = 0); -#define Dqn_Array_InitWithAllocatorNoGrow(allocator, Type, max, size, zero_mem) Dqn_Array__InitWithAllocatorNoGrow(allocator, max, size, zero_mem DQN_CALL_SITE("")) -#define Dqn_Array_InitWithArenaNoGrow( arena, Type, max, size, zero_mem) Dqn_Array__InitWithArenaNoGrow( arena, max, size, zero_mem DQN_CALL_SITE("")) - -#define Dqn_Array_Reserve( array, size) Dqn_Array__Reserve(array, size DQN_CALL_SITE("")) -template DQN_API void Dqn_Array_Free (Dqn_Array *a); - -#define Dqn_Array_AddArray( array, items, num) Dqn_Array__AddArray(array, items, num DQN_CALL_SITE("")) -#define Dqn_Array_Add( array, item) Dqn_Array__Add(array, item DQN_CALL_SITE("")) -#define Dqn_Array_Make( array, num) Dqn_Array__Make(array, num DQN_CALL_SITE("")) -template DQN_API void Dqn_Array_Clear (Dqn_Array *a, Dqn_ZeroMem zero_mem = Dqn_ZeroMem::No); - -template DQN_API void Dqn_Array_EraseStable (Dqn_Array *a, Dqn_isize index); -template DQN_API void Dqn_Array_EraseUnstable (Dqn_Array *a, Dqn_isize index); - -template DQN_API void Dqn_Array_Pop (Dqn_Array *a, Dqn_isize num, Dqn_ZeroMem zero_mem = Dqn_ZeroMem::No); -template DQN_API T * Dqn_Array_Peek (Dqn_Array *a); - // ------------------------------------------------------------------------------------------------- // NOTE: Dqn_List - Chunked Linked Lists // ------------------------------------------------------------------------------------------------- -/* - Dqn_List list = {}; // Zero initialise to use default heap allocator (via Dqn_Allocator). - list.chunk_size = 128; // When out of space, allocate a chunk with atleast for 128 items, or the amount the user requested, whichever is greater. - int *array = Dqn_List_Make(list, 5, Dqn_ZeroMem::Yes); // First time, allocate chunk (128 items), then bump chunk pointer to allocate 5 ints. - - // TODO(doyle): Freeing step, I only use these with arenas, so normally I just free the arena. -*/ - template struct Dqn_ListChunk { @@ -1917,19 +1770,14 @@ struct Dqn_ListIterator template struct Dqn_List { - // When 'allocator' is null, 'backup_allocator' will be used. This allows - // zero initialization usage of Dqn_List to work without configuration. - Dqn_Allocator backup_allocator; - Dqn_Allocator *allocator; - - Dqn_isize count; // Cumulative count of all items made across all list chunks - Dqn_isize chunk_size; // When new ListChunk's are required, the minimum 'data' entries to allocate for that node. - Dqn_ListChunk *head; - Dqn_ListChunk *tail; + Dqn_ArenaAllocator *arena; + Dqn_isize count; // Cumulative count of all items made across all list chunks + Dqn_isize chunk_size; // When new ListChunk's are required, the minimum 'data' entries to allocate for that node. + Dqn_ListChunk *head; + Dqn_ListChunk *tail; }; -template DQN_API Dqn_List Dqn_List_InitWithArena (Dqn_ArenaAllocator *arena, Dqn_isize chunk_size = 128); -template DQN_API Dqn_List Dqn_List_InitWithAllocator(Dqn_Allocator *allocator, Dqn_isize chunk_size = 128); +template DQN_API Dqn_List Dqn_List_InitWithArena(Dqn_ArenaAllocator *arena, Dqn_isize chunk_size = 128); // Produce an iterator for the data in the list /* @@ -1990,102 +1838,129 @@ DQN_API Dqn_MurmurHash3_128 Dqn_MurmurHash3_x64_128(void const *key, int len, Dq // NOTE: Template Implementation // ------------------------------------------------------------------------------------------------- // ------------------------------------------------------------------------------------------------- -// NOTE: Dqn_StringMap Template Implementation +// NOTE: Dqn_Map Template Implementation // ------------------------------------------------------------------------------------------------- template -void Dqn_StringMap__InitializeSize(Dqn_StringMap *map, Dqn_isize size) +Dqn_Map Dqn_Map_InitWithArena(Dqn_ArenaAllocator *arena, Dqn_isize size) { - Dqn_Allocator *allocator = map->allocator ? map->allocator : &map->backup_allocator; - if (map->size == 0) - { - Dqn_isize final_size = size == 0 ? 4096 : size; - map->values = Dqn_Allocator_NewArray(allocator, Dqn_StringMapEntry *, final_size, Dqn_ZeroMem::Yes); - if (map->values) map->size = final_size; - } -} + Dqn_Map result = {}; + result.arena = arena; -template -Dqn_StringMap Dqn_StringMap_InitWithArena(Dqn_ArenaAllocator *arena, Dqn_isize size, Dqn_u32 hashing_seed) -{ - Dqn_StringMap result = {}; - result.backup_allocator = Dqn_Allocator_InitWithArena(arena); - result.hashing_seed = hashing_seed; - Dqn_StringMap__InitializeSize(&result, size); + Dqn_isize final_size = size == 0 ? 4096 : size; + result.values = Dqn_ArenaAllocator_NewArray(arena, Dqn_MapEntry *, final_size, Dqn_ZeroMem::Yes); + if (result.values) result.size = final_size; return result; } template -Dqn_u64 Dqn_StringMap_Hash(Dqn_StringMap *map, Dqn_String key) +Dqn_MapEntry *Dqn_Map_FindOrAdd(Dqn_Map *map, Dqn_u64 hash, Dqn_MapCollideRule rule) { - Dqn_u32 const DEFAULT_SEED = 81273182; - Dqn_u64 result = - DQN_MURMUR_HASH3_U128_AS_U64(key.str, DQN_CAST(int)key.size, map->hashing_seed ? map->hashing_seed : DEFAULT_SEED); - return result; -} - -template -Dqn_StringMapEntry *Dqn_StringMap_Add(Dqn_StringMap *map, Dqn_String key, T *value, Dqn_StringMapCollisionRule rule) -{ - Dqn_StringMap__InitializeSize(map, 0 /*size*/); - Dqn_u64 hash = Dqn_StringMap_Hash(map, key); - Dqn_isize index = hash % map->size; - Dqn_StringMapEntry *result = map->values[index]; - Dqn_Allocator * allocator = map->allocator ? map->allocator : &map->backup_allocator; + Dqn_isize index = hash % map->size; + Dqn_MapEntry *result = map->values[index]; if (result) { - if (rule == Dqn_StringMapCollisionRule::Chain) + if (rule == Dqn_MapCollideRule::Chain) { - while (!Dqn_String_Compare(result->key, key)) + while (result->hash != hash) { if (result->next) result = result->next; else { - result->next = Dqn_Allocator_New(allocator, Dqn_StringMapEntry, Dqn_ZeroMem::No); + map->chain_count++; + result->next = Dqn_ArenaAllocator_New(map->arena, Dqn_MapEntry, Dqn_ZeroMem::Yes); result = result->next; break; } } } + else if (rule == Dqn_MapCollideRule::Fail) + { + result = nullptr; + } } else { - result = Dqn_Allocator_New(allocator, Dqn_StringMapEntry, Dqn_ZeroMem::No); + result = Dqn_ArenaAllocator_New(map->arena, Dqn_MapEntry, Dqn_ZeroMem::Yes); + map->count++; map->values[index] = result; } if (result) - { - result->key = key; + result->hash = hash; + + return result; +} + +template +Dqn_MapEntry *Dqn_Map_Add(Dqn_Map *map, Dqn_u64 hash, T &value, Dqn_MapCollideRule rule) +{ + Dqn_MapEntry *result = Dqn_Map_FindOrAdd(map, hash, rule); + if (result) result->value = value; - result->next = nullptr; + + return result; +} + +template +Dqn_MapEntry *Dqn_Map_AddCopy(Dqn_Map *map, Dqn_u64 hash, T const &value, Dqn_MapCollideRule rule) +{ + Dqn_MapEntry *result = Dqn_Map_FindOrAdd(map, hash, rule); + if (result) + result->value = value; + + return result; +} + +template +Dqn_MapEntry *Dqn_Map_Get(Dqn_Map *map, Dqn_u64 hash) +{ + Dqn_isize index = hash % map->size; + Dqn_MapEntry *result = nullptr; + + for (Dqn_MapEntry *entry = map->values[index]; entry; entry = entry->next) + { + if (entry->hash == hash) + { + result = entry; + break; + } } return result; } template -Dqn_StringMapEntry *Dqn_StringMap_Get(Dqn_StringMap *map, Dqn_String key) +void Dqn_Map_Erase(Dqn_Map *map, Dqn_u64 hash, Dqn_ZeroMem zero_mem) { - Dqn_u64 hash = Dqn_StringMap_Hash(map, key); - Dqn_isize index = hash % map->size; - Dqn_StringMapEntry *result = nullptr; + Dqn_isize index = hash % map->size; + Dqn_MapEntry **entry = &(map->values[index]); + Dqn_b32 is_chain_entry = *entry && (*entry)->next; - for (result = map->values[index]; result; result = result->next) + while ((*entry) && (*entry)->hash != hash) + entry = &((*entry)->next); + + if ((*entry) && (*entry)->hash == hash) { - if (result->key == key) - break; - } + Dqn_MapEntry *erase_entry = *entry; + Dqn_MapEntry *next = erase_entry->next; + (*entry) = next; - return result; + Dqn__ZeroMemBytes(erase_entry, sizeof(*erase_entry), zero_mem); + erase_entry->next = map->free_list; + map->free_list = erase_entry; + + if (is_chain_entry) map->chain_count--; + else map->count--; + } } // ------------------------------------------------------------------------------------------------- // NOTE: Dqn_FixedString Template Implementation // ------------------------------------------------------------------------------------------------- template -DQN_API Dqn_FixedString Dqn_FixedString_InitFmt(char const *fmt, ...) +DQN_API Dqn_FixedString Dqn_FixedString_Fmt(char const *fmt, ...) { Dqn_FixedString result = {}; va_list va; @@ -2138,8 +2013,8 @@ DQN_API Dqn_b32 Dqn_FixedString_AppendFmt(Dqn_FixedString *str, char const template DQN_API Dqn_b32 Dqn_FixedString_Append(Dqn_FixedString *str, char const *src, Dqn_isize size) { - if (size == -1) size = DQN_CAST(Dqn_isize)Dqn_Str_Len(src); - Dqn_isize space = MAX_ - str->size; + if (size == -1) size = DQN_CAST(Dqn_isize)Dqn_Str_Size(src); + Dqn_isize space = (MAX_ - 1 /*reserve byte for null terminator*/) - str->size; Dqn_b32 result = size <= space; if (!result) @@ -2168,236 +2043,9 @@ DQN_API Dqn_String Dqn_FixedString_ToString(Dqn_FixedString const *str) return result; } -// ------------------------------------------------------------------------------------------------- -// NOTE: Dqn_Slice Template Implementation -// ------------------------------------------------------------------------------------------------- -template -DQN_API Dqn_Slice Dqn_Slice_Init(T *data, Dqn_isize size) -{ - Dqn_Slice result = {}; - result.data = data; - result.size = size; - return result; -} - -template -DQN_API Dqn_Slice Dqn_Slice_InitWithArray(T (&array)[N]) -{ - Dqn_Slice result = {}; - result.size = N; - result.data = array; - return result; -} - -template -DQN_API Dqn_Slice Dqn_Slice__Allocate(Dqn_Allocator *allocator, Dqn_isize size, Dqn_ZeroMem zero_mem DQN_CALL_SITE_ARGS) -{ - Dqn_Slice result = {}; - result.size = size; - result.data = DQN_CAST(T *) Dqn_Allocator__Allocate(allocator, (sizeof(T) * size), alignof(T), zero_mem DQN_CALL_SITE_ARGS_INPUT); - return result; -} - -template -DQN_API Dqn_Slice Dqn_Slice__ArenaAllocate(Dqn_ArenaAllocator *arena, Dqn_isize size, Dqn_ZeroMem zero_mem DQN_CALL_SITE_ARGS) -{ - Dqn_Slice result = {}; - result.size = size; - result.data = DQN_CAST(T *) Dqn_ArenaAllocator__Allocate(arena, (sizeof(T) * size), alignof(T), zero_mem DQN_CALL_SITE_ARGS_INPUT); - return result; -} - -template -DQN_API Dqn_Slice Dqn_Slice_CopyNullTerminated(Dqn_Allocator *allocator, T const *src, Dqn_isize size) -{ - Dqn_Slice result = {}; - result.size = size; - result.data = DQN_CAST(T *) Dqn_Allocator_Allocate(allocator, (sizeof(T) * size) + 1, alignof(T), Dqn_ZeroMem::No); - DQN_MEMCOPY(result.data, src, size * sizeof(T)); - result.buf[size] = 0; - return result; -} - -template -DQN_API Dqn_Slice Dqn_Slice_CopyNullTerminated(Dqn_Allocator *allocator, Dqn_Slice const src) -{ - Dqn_Slice result = Dqn_Slice_CopyNullTerminated(allocator, src.data, src.size); - return result; -} - -template -DQN_API Dqn_Slice Dqn_Slice_Copy(Dqn_Allocator *allocator, T const *src, Dqn_isize size) -{ - Dqn_Slice result = {}; - result.size = size; - result.data = DQN_CAST(T *) Dqn_Allocator_Allocate(allocator, sizeof(T) * size, alignof(T), Dqn_ZeroMem::No); - DQN_MEMCOPY(result.dat, src, size * sizeof(T)); - return result; -} - -template -DQN_API Dqn_Slice Dqn_Slice_Copy(Dqn_Allocator *allocator, Dqn_Slice const src) -{ - Dqn_Slice result = Dqn_Slice_Copy(allocator, src.data, src.size); - return result; -} - -template -DQN_API Dqn_b32 Dqn_Slice_Memcmp(Dqn_Slice const a, Dqn_Slice const b) -{ - Dqn_b32 result = false; - if (a.size != b.size) return result; - result = (DQN_MEMCMP(a.data, b.data, a.size) == 0); - return result; -} - -template -DQN_API Dqn_b32 operator==(Dqn_Slice const &lhs, Dqn_Slice const &rhs) -{ - Dqn_b32 result = lhs.size == rhs.size && lhs.data == rhs.data; - return result; -} - -// ------------------------------------------------------------------------------------------------- -// NOTE: Dqn_Map Template Implementation -// ------------------------------------------------------------------------------------------------- -template -Dqn_isize Dqn_Map_MemoryRequired(Dqn_isize num_values) -{ - Dqn_isize const BITS_PER_BITSET = sizeof(Dqn_MapUsageBitset) * 8; - - Dqn_isize bitsets_required = (num_values / BITS_PER_BITSET); - if (num_values % BITS_PER_BITSET != 0) - bitsets_required += 1; - - Dqn_isize bytes_for_values = sizeof(T) * num_values; - Dqn_isize bytes_for_bitset = bitsets_required * sizeof(Dqn_MapUsageBitset); - Dqn_isize result = bytes_for_values + bytes_for_bitset; - return result; -} - -template -Dqn_Map Dqn_Map_InitWithMemory(void *mem, Dqn_isize mem_size) -{ - // - // NOTE: Calculate how to split up the memory for maximal usage - // - Dqn_isize bytes_for_values = 0; - Dqn_isize bytes_for_usage_bitset = 0; - Dqn_isize values_count = 0; - Dqn_isize usage_bitsets_count = 0; - { - Dqn_isize max_values = mem_size / sizeof(T); - Dqn_isize mem_remaining = mem_size - (max_values * sizeof(T)); - - Dqn_isize const BITS_PER_BITSET = sizeof(Dqn_MapUsageBitset) * 8; - Dqn_isize bitsets_required = (max_values / BITS_PER_BITSET); - if (max_values % BITS_PER_BITSET != 0) - bitsets_required += 1; - - bytes_for_usage_bitset = (bitsets_required * sizeof(Dqn_MapUsageBitset)); - if (mem_remaining < bytes_for_usage_bitset) - { - Dqn_isize extra_bytes_needed = bytes_for_usage_bitset - mem_remaining; - Dqn_isize excess_items = extra_bytes_needed / sizeof(T); - if (extra_bytes_needed % sizeof(T) != 0) - excess_items += 1; - max_values -= excess_items; - } - - bytes_for_values = max_values * sizeof(T); - values_count = max_values; - usage_bitsets_count = bitsets_required; - - DQN_ASSERT(bytes_for_values > 0); - } - - // - // NOTE: Generate the map - // - Dqn_isize bytes_required = bytes_for_values + bytes_for_usage_bitset; - (void)bytes_required; - DQN_ASSERT_MSG(bytes_required <= mem_size, - "(bytes_for_values = %I64d, bytes_for_usage_bitset = %I64d, mem_size = %I64d)", - bytes_for_values, bytes_for_usage_bitset, mem_size); - DQN_ASSERT_MSG(bytes_required >= 0, "(bytes_required = %I64u)", bytes_required); - - Dqn_Map result = {}; - result.values = DQN_CAST(T *) mem; - result.usage_bitsets = DQN_CAST(Dqn_MapUsageBitset *)(DQN_CAST(Dqn_uintptr) mem + bytes_for_values); - result.size = values_count; - result.usage_bitsets_size = usage_bitsets_count; - return result; -} - -template -Dqn__MapKeyLookup Dqn_Map__GetKeyLookup(Dqn_Map const *map, Dqn_u64 key) -{ - Dqn_isize const BITS_PER_BITSET = sizeof(Dqn_MapUsageBitset) * 8; - - Dqn__MapKeyLookup result = {}; - result.index = key % map->size; - result.bitset_index = result.index / BITS_PER_BITSET; - result.bit_index = result.index % BITS_PER_BITSET; - result.bitset_bit = 1ULL << result.bit_index; - return result; -} - -template -T *Dqn_Map_FindOrMake(Dqn_Map *map, Dqn_u64 key, Dqn_b32 *found) -{ - Dqn__MapKeyLookup lookup = Dqn_Map__GetKeyLookup(map, key); - T * result = &map->values[lookup.index]; - Dqn_b32 found_ = true; - if ((map->usage_bitsets[lookup.bitset_index] & lookup.bitset_bit) == 0) - { - found_ = false; - map->count++; - map->usage_bitsets[lookup.bitset_index] |= lookup.bitset_bit; - } - - if (found) *found = found_; - return result; -} - -template -Dqn_b32 Dqn_Map_Add(Dqn_Map *map, Dqn_u64 key, T const &value) -{ - Dqn_b32 found = false; - T * entry = Dqn_Map_FindOrMake(map, key, &found); - if (!found) *entry = value; - Dqn_b32 result = (found == false); - return result; -} - -template -T *Dqn_Map_Get(Dqn_Map *map, Dqn_u64 key) -{ - Dqn__MapKeyLookup lookup = Dqn_Map__GetKeyLookup(map, key); - T *result = (map->usage_bitsets[lookup.bitset_index] & lookup.bitset_bit) ? &map->values[lookup.index] : nullptr; - return result; -} - -template -Dqn_b32 Dqn_Map_Erase(Dqn_Map *map, Dqn_u64 key) -{ - Dqn_b32 result = false; - Dqn__MapKeyLookup lookup = Dqn_Map__GetKeyLookup(map, key); - if (map->usage_bitsets[lookup.bitset_index] & lookup.bitset_bit) - { - result = true; - map->usage_bitsets[lookup.bitset_index] &= ~lookup.bitset_bit; - map->count--; - } - - DQN_ASSERT_MSG(map->count >= 0, "(count = %I64d)"); - return result; -} - // ------------------------------------------------------------------------------------------------- // NOTE: Dqn_StringBuilder Template Implementation // ------------------------------------------------------------------------------------------------- - // // NOTE: Dqn_StringBuilder Internal Functions // @@ -2424,13 +2072,12 @@ DQN_API char *Dqn_StringBuilder__AllocateWriteBuffer(Dqn_StringBuilder *build Dqn_b32 new_block_needed = (block->size - block->used) < size_required; if (new_block_needed) { - Dqn_Allocator *allocator = builder->allocator ? builder->allocator : &builder->backup_allocator; Dqn_isize allocation_size = DQN_M_MAX(size_required, DQN_STRING_BUILDER_MIN_BLOCK_SIZE); - block = Dqn_Allocator_New(allocator, Dqn_StringBuilderBlock, Dqn_ZeroMem::No); + block = Dqn_ArenaAllocator_New(builder->arena, Dqn_StringBuilderBlock, Dqn_ZeroMem::No); if (!block) return nullptr; *block = {}; - block->mem = DQN_CAST(char *)Dqn_Allocator_Allocate(allocator, allocation_size, alignof(char), Dqn_ZeroMem::No); + block->mem = DQN_CAST(char *)Dqn_ArenaAllocator_Allocate(builder->arena, allocation_size, alignof(char), Dqn_ZeroMem::No); block->size = allocation_size; builder->last_mem_block->next = block; builder->last_mem_block = builder->last_mem_block->next; @@ -2441,33 +2088,14 @@ DQN_API char *Dqn_StringBuilder__AllocateWriteBuffer(Dqn_StringBuilder *build return result; } -template -DQN_API char *Dqn_StringBuilder__Build(Dqn_StringBuilder *builder, Dqn_Allocator *allocator, Dqn_isize *len DQN_CALL_SITE_ARGS) -{ - Dqn_isize len_ = 0; - if (!len) len = &len_; - *len = Dqn_StringBuilder_GetSize(builder); - auto *result = DQN_CAST(char *)Dqn_Allocator__Allocate(allocator, *len + 1, alignof(char), Dqn_ZeroMem::No DQN_CALL_SITE_ARGS_INPUT); - Dqn_StringBuilder_BuildToDest(builder, result, *len + 1); - return result; -} - // // NOTE: Dqn_StringBuilder Functions // template DQN_API void Dqn_StringBuilder_InitWithArena(Dqn_StringBuilder *builder, Dqn_ArenaAllocator *arena) { - *builder = {}; - builder->backup_allocator = Dqn_Allocator_InitWithArena(arena); - Dqn_StringBuilder__LazyInitialise(builder); -} - -template -DQN_API void Dqn_StringBuilder_InitWithAllocator(Dqn_StringBuilder *builder, Dqn_Allocator *allocator) -{ - *builder = {}; - builder->allocator = allocator; + *builder = {}; + builder->arena = arena; Dqn_StringBuilder__LazyInitialise(builder); } @@ -2509,19 +2137,22 @@ DQN_API void Dqn_StringBuilder_BuildToDest(Dqn_StringBuilder const *builder, } template -DQN_API Dqn_String Dqn_StringBuilder_BuildStringWithAllocator(Dqn_StringBuilder *builder, Dqn_Allocator *allocator) +DQN_API char *Dqn_StringBuilder__Build(Dqn_StringBuilder *builder, Dqn_ArenaAllocator *arena, Dqn_isize *len DQN_CALL_SITE_ARGS) { - Dqn_String result = {}; - result.str = Dqn_StringBuilder_Build(builder, allocator, &result.size); - result.cap = result.size; + Dqn_isize len_ = 0; + if (!len) len = &len_; + *len = Dqn_StringBuilder_GetSize(builder); + auto *result = DQN_CAST(char *)Dqn_ArenaAllocator__Allocate(arena, *len + 1, alignof(char), Dqn_ZeroMem::No DQN_CALL_SITE_ARGS_INPUT); + Dqn_StringBuilder_BuildToDest(builder, result, *len + 1); return result; } template DQN_API Dqn_String Dqn_StringBuilder_BuildStringWithArena(Dqn_StringBuilder *builder, Dqn_ArenaAllocator *arena) { - Dqn_Allocator allocator = Dqn_Allocator_InitWithArena(arena); - Dqn_String result = Dqn_StringBuilder_BuildStringWithAllocator(builder, &allocator); + Dqn_isize size = 0; + char *string = Dqn_StringBuilder_Build(builder, arena, &size); + Dqn_String result = Dqn_String_Init(string, size); return result; } @@ -2553,7 +2184,7 @@ template DQN_API void Dqn_StringBuilder_Append(Dqn_StringBuilder *builder, char const *str, Dqn_isize len) { if (!str) return; - if (len == -1) len = DQN_CAST(Dqn_isize)Dqn_Str_Len(str); + if (len == -1) len = DQN_CAST(Dqn_isize)Dqn_Str_Size(str); if (len == 0) return; char *buf = Dqn_StringBuilder__AllocateWriteBuffer(builder, len); DQN_MEMCOPY(buf, str, len); @@ -2574,20 +2205,6 @@ DQN_API void Dqn_StringBuilder_AppendChar(Dqn_StringBuilder *builder, char ch *buf++ = ch; } -template -DQN_API void Dqn_StringBuilder_Free(Dqn_StringBuilder *builder) -{ - for (Dqn_StringBuilderBlock *block = builder->fixed_mem_block.next; - block; - block = block->next) - { - Dqn_StringBuilderBlock *block_to_free = block; - Dqn_Allocator_Free(&builder->allocator, block_to_free->mem); - Dqn_Allocator_Free(&builder->allocator, block_to_free); - } - Dqn_StringBuilder__LazyInitialise(builder); -} - // ------------------------------------------------------------------------------------------------- // NOTE: Dqn_FixedArray Template Implementation // ------------------------------------------------------------------------------------------------- @@ -2609,14 +2226,6 @@ DQN_API DQN_FIXED_ARRAY_TEMPLATE_DECL Dqn_FixedArray_Init(T const *item, int num return result; } -DQN_FIXED_ARRAY_TEMPLATE -DQN_API Dqn_Slice Dqn_FixedArray_Slice(DQN_FIXED_ARRAY_TEMPLATE_DECL *a) -{ - Dqn_Slice result = {a->data, a->size}; - return result; -} - - DQN_FIXED_ARRAY_TEMPLATE DQN_API Dqn_isize Dqn_FixedArray_Max(DQN_FIXED_ARRAY_TEMPLATE_DECL const *) { @@ -2750,21 +2359,12 @@ template DQN_API Dqn_Array Dqn_Array_InitWithMemory(T *memory, Dqn_isize max, Dqn_isize size) { Dqn_Array result = {}; - result.allocator = Dqn_Allocator_InitWithNull(); result.data = memory; result.size = size; result.max = max; return result; } -template -DQN_API Dqn_Array Dqn_Array__InitWithAllocatorNoGrow(Dqn_Allocator *allocator, Dqn_isize max, Dqn_isize size, Dqn_ZeroMem zero_mem DQN_CALL_SITE_ARGS) -{ - T *memory = DQN_CAST(T *)Dqn_Allocator__Allocate(allocator, sizeof(T) * max, alignof(T), zero_mem DQN_CALL_SITE_ARGS_INPUT); - Dqn_Array result = Dqn_Array_InitWithMemory(memory, max, size); - return result; -} - template DQN_API Dqn_Array Dqn_Array__InitWithArenaNoGrow(Dqn_ArenaAllocator *arena, Dqn_isize max, Dqn_isize size, Dqn_ZeroMem zero_mem DQN_CALL_SITE_ARGS) { @@ -2777,14 +2377,15 @@ template DQN_API bool Dqn_Array__Reserve(Dqn_Array *a, Dqn_isize size DQN_CALL_SITE_ARGS) { if (size <= a->size) return true; - T *new_ptr = DQN_CAST(T *)Dqn_Allocator__Allocate(&a->allocator, sizeof(T) * size, alignof(T), Dqn_ZeroMem::Yes DQN_CALL_SITE_ARGS_INPUT); + if (!a->arena) return false; + + T *new_ptr = DQN_CAST(T *)Dqn_ArenaAllocator__Allocate(a->arena, sizeof(T) * size, alignof(T), Dqn_ZeroMem::Yes DQN_CALL_SITE_ARGS_INPUT); if (!new_ptr) return false; if (a->data) { - // NOTE(doyle): Realloc, I don't like and don't support. Use virtual arrays + // NOTE(dqn): Realloc, I don't like and don't want to support. Use virtual arrays DQN_MEMCOPY(new_ptr, a->data, a->size * sizeof(T)); - Dqn_Allocator_Free(&a->allocator, a->data); } a->data = new_ptr; @@ -2792,12 +2393,6 @@ DQN_API bool Dqn_Array__Reserve(Dqn_Array *a, Dqn_isize size DQN_CALL_SITE_AR return true; } -template -DQN_API void Dqn_Array_Free(Dqn_Array *a) -{ - Dqn_Allocator_Free(&a->allocator, a->data); -} - template DQN_API bool Dqn_Array__GrowIfNeeded(Dqn_Array *a, Dqn_isize num_to_add DQN_CALL_SITE_ARGS) { @@ -2885,19 +2480,10 @@ DQN_API T *Dqn_Array_Peek(Dqn_Array *a) // ------------------------------------------------------------------------------------------------- template DQN_API Dqn_List Dqn_List_InitWithArena(Dqn_ArenaAllocator *arena, Dqn_isize chunk_size) -{ - Dqn_List result = {}; - result.backup_allocator = Dqn_Allocator_InitWithArena(arena); - result.chunk_size = chunk_size; - return result; -} - -template -DQN_API Dqn_List Dqn_List_InitWithAllocator(Dqn_Allocator *allocator, Dqn_isize chunk_size) { Dqn_List result = {}; + result.arena = arena; result.chunk_size = chunk_size; - result.allocator = allocator; return result; } @@ -2907,22 +2493,18 @@ DQN_API T *Dqn_List__Make(Dqn_List *list, Dqn_isize count DQN_CALL_SITE_ARGS) if (list->chunk_size == 0) list->chunk_size = 128; - Dqn_Allocator *allocator = list->allocator ? list->allocator : &list->backup_allocator; if (!list->tail || (list->tail->count + count) > list->tail->size) { - auto *tail = (Dqn_ListChunk * )Dqn_Allocator__Allocate(allocator, sizeof(Dqn_ListChunk), alignof(Dqn_ListChunk), Dqn_ZeroMem::Yes DQN_CALL_SITE_ARGS_INPUT); + auto *tail = (Dqn_ListChunk * )Dqn_ArenaAllocator__Allocate(list->arena, sizeof(Dqn_ListChunk), alignof(Dqn_ListChunk), Dqn_ZeroMem::Yes DQN_CALL_SITE_ARGS_INPUT); if (!tail) return nullptr; Dqn_isize items = DQN_M_MAX(list->chunk_size, count); - tail->data = (T * )Dqn_Allocator__Allocate(allocator, sizeof(T) * items, alignof(T), Dqn_ZeroMem::Yes DQN_CALL_SITE_ARGS_INPUT); + tail->data = (T * )Dqn_ArenaAllocator__Allocate(list->arena, sizeof(T) * items, alignof(T), Dqn_ZeroMem::Yes DQN_CALL_SITE_ARGS_INPUT); tail->size = items; if (!tail->data) - { - Dqn_Allocator_Free(allocator, tail); return nullptr; - } if (list->tail) list->tail->next = tail; @@ -3121,6 +2703,7 @@ Dqn_b32 Dqn_List_Iterate(Dqn_List *list, Dqn_ListIterator *iterator) void *LoadLibraryA (char const *file_name); void *VirtualAlloc (void *address, size_t size, DWORD allocation_type, DWORD protect); int MultiByteToWideChar (unsigned int CodePage, DWORD dwFlags, char const *lpMultiByteStr, int cbMultiByte, wchar_t *lpWideCharStr, int cchWideChar); + int WideCharToMultiByte (unsigned int CodePage, DWORD dwFlags, wchar_t const *lpWideCharStr, int cchWideChar, char *lpMultiByteStr, int cbMultiByte, char const *lpDefaultChar, bool *lpUsedDefaultChar); void GetSystemTime (SYSTEMTIME *lpSystemTime); void GetLocalTime (SYSTEMTIME *lpSystemTime); } @@ -3263,27 +2846,27 @@ DQN_API void Dqn_LogV(Dqn_LogType type, { (void)user_data; - int file_name_len = 0; - char const *file_name = Dqn_Str_FileNameFromPath(file, DQN_CAST(int) file_len, &file_name_len); + Dqn_isize file_name_len = 0; + char const *file_name = Dqn_Str_FileNameFromPath(file, file_len, &file_name_len); FILE *handle = (type == Dqn_LogType::Error) ? stderr : stdout; #if _MSC_VER SYSTEMTIME sys_time; GetLocalTime(&sys_time); fprintf(handle, - "[%02d:%02d:%02d/%s] %.*s:%05I64u:%.*s ", + "[%02d:%02d:%02d|%s|%.*s|%05I64u|%.*s] ", sys_time.wHour, sys_time.wMinute, sys_time.wSecond, Dqn_LogTypeString[DQN_CAST(int) type], - file_name_len, + DQN_CAST(int)file_name_len, file_name, line, DQN_CAST(int)func_len, func); #else fprintf(handle, - "[%s] %.*s:%05I64u:%.*s ", + "[%s|%.*s|%05I64u|%.*s] ", Dqn_LogTypeString[DQN_CAST(int) type], file_name_len, file_name, @@ -3421,13 +3004,6 @@ DQN_API char *Dqn_PointerMetadata_GetRawPointer(void *ptr) // ------------------------------------------------------------------------------------------------- // NOTE: Dqn_AllocationTracer // ------------------------------------------------------------------------------------------------- -Dqn_AllocationTracer Dqn_AllocationTracer_InitWithMemory(void *mem, Dqn_usize mem_size) -{ - Dqn_AllocationTracer result = {}; - result.map = Dqn_Map_InitWithMemory(mem, mem_size); - return result; -} - void Dqn_AllocationTracer_Add(Dqn_AllocationTracer *tracer, void *ptr, Dqn_usize size DQN_CALL_SITE_ARGS) { #if DQN_ALLOCATION_TRACING @@ -3441,7 +3017,15 @@ void Dqn_AllocationTracer_Add(Dqn_AllocationTracer *tracer, void *ptr, Dqn_usize trace.msg = msg_; Dqn_TicketMutex_Begin(&tracer->mutex); - Dqn_b32 added = Dqn_Map_Add(&tracer->table, DQN_CAST(Dqn_u64) ptr, trace); +#if 0 // TODO(dqn): We need to revisit this + if (!trace->map.values) + { + trace->arena = Dqn_ArenaAllocator_InitWithDefaultHeap(); + trace->map = Dqn_Map_InitWithArena(&trace->arena, 16192); + } +#endif + + Dqn_b32 added = Dqn_Map_Add(&tracer->map, DQN_CAST(Dqn_u64) ptr, trace, Dqn_MapCollideRule::Chain); if (!added) { // Dqn_AllocationTrace *other = Dqn_Map_Get(&tracer->table, DQN_CAST(Dqn_u64) ptr); @@ -3458,10 +3042,10 @@ void Dqn_AllocationTracer_Remove(Dqn_AllocationTracer *tracer, void *ptr) #if DQN_ALLOCATION_TRACING if (!tracer) return; Dqn_TicketMutex_Begin(&tracer->mutex); - Dqn_AllocationTrace *trace = Dqn_Map_Get(&tracer->table, DQN_CAST(Dqn_u64) ptr); + Dqn_AllocationTrace *trace = Dqn_Map_Get(&tracer->map, DQN_CAST(Dqn_u64) ptr); DQN_ASSERT_MSG(trace->ptr == ptr, "(trace->ptr = %Ix, raw_ptr = %Ix", trace->ptr, ptr); - Dqn_Map_Erase(&tracer->table, DQN_CAST(Dqn_u64) ptr); + Dqn_Map_Erase(&tracer->map, DQN_CAST(Dqn_u64) ptr); Dqn_TicketMutex_End(&tracer->mutex); #else (void)tracer; (void)ptr; @@ -3517,166 +3101,44 @@ DQN_API void Dqn_CRTAllocator__Free(Dqn_CRTAllocator *allocator, void *ptr) allocator->free ? allocator->free(ptr) : DQN_FREE(ptr); } -// ------------------------------------------------------------------------------------------------- -// NOTE: Dqn_Allocator -// ------------------------------------------------------------------------------------------------- -DQN_API Dqn_Allocator Dqn_Allocator_InitWithNull() -{ - Dqn_Allocator result = {}; - result.type = Dqn_AllocatorType::Null; - return result; -} - -DQN_API Dqn_Allocator Dqn_Allocator_InitWithHeap() -{ - Dqn_Allocator result = {}; - result.type = Dqn_AllocatorType::Heap; - return result; -} - -DQN_API Dqn_Allocator Dqn_Allocator_InitWithXHeap() -{ - Dqn_Allocator result = {}; - result.type = Dqn_AllocatorType::XHeap; - return result; -} - -DQN_API Dqn_Allocator Dqn_Allocator_InitWithArena(Dqn_ArenaAllocator *arena) -{ - Dqn_Allocator result = {}; - result.type = Dqn_AllocatorType::Arena; - result.context.arena = arena; - return result; -} - -DQN_API Dqn_Allocator Dqn_Allocator_InitWithProcs(Dqn_Allocator_CustomAllocateProc *allocate_proc, Dqn_Allocator_CustomFreeProc *free_proc) -{ - Dqn_Allocator result = {}; - result.type = Dqn_AllocatorType::Custom; - result.custom.allocate = allocate_proc; - result.custom.free = free_proc; - return result; -} - -void Dqn_Allocator_Free(Dqn_Allocator *allocator, void *ptr) -{ - Dqn_isize bytes_freed = 0; - switch (allocator->type) - { - case Dqn_AllocatorType::Null: return; - default: break; - - case Dqn_AllocatorType::Heap: - case Dqn_AllocatorType::XHeap: - { - Dqn_PointerMetadata meta = Dqn_PointerMetadata_Get(ptr); - char * raw_ptr = Dqn_PointerMetadata_GetRawPointer(ptr); - DQN_FREE(raw_ptr); - bytes_freed = meta.size; - } - break; - - case Dqn_AllocatorType::Custom: - { - if (allocator->custom.free) - bytes_freed = allocator->custom.free(ptr, allocator->context.user); - } - break; - - case Dqn_AllocatorType::Arena: - break; - } - - if (ptr) - { - allocator->allocations--; - DQN_ASSERT(allocator->allocations >= 0); - DQN_ASSERT_MSG(allocator->bytes_allocated >= bytes_freed, - "bytes_allocated = %jd, bytes_freed = %jd", - allocator->bytes_allocated, - bytes_freed); - allocator->bytes_allocated -= bytes_freed; - -#if DQN_ALLOCATION_TRACING - Dqn_AllocationTracer_Remove(allocator->tracer, ptr); -#endif - } -} - -DQN_API void *Dqn_Allocator__Allocate(Dqn_Allocator *allocator, Dqn_isize size, Dqn_u8 alignment, Dqn_ZeroMem zero_mem DQN_CALL_SITE_ARGS) -{ - char *result = nullptr; - switch (allocator->type) - { - default: - case Dqn_AllocatorType::Null: - { - DQN_LOG_W("Requested allocation on null allocator"); - return result; - } - - case Dqn_AllocatorType::Heap: - case Dqn_AllocatorType::XHeap: - { - size = Dqn_PointerMetadata_SizeRequired(size, alignment); - void *ptr = zero_mem == Dqn_ZeroMem::Yes ? DQN_CALLOC(1, DQN_CAST(size_t)size) : DQN_MALLOC(size); - result = Dqn_PointerMetadata_Init(ptr, size, alignment); - if (!result && allocator->type == Dqn_AllocatorType::XHeap) - { - DQN_ASSERT(result); - } - } - break; - - case Dqn_AllocatorType::Arena: - { - result = DQN_CAST(char *)Dqn_ArenaAllocator__Allocate(allocator->context.arena, size, alignment, zero_mem DQN_CALL_SITE_ARGS_INPUT); - } - break; - - case Dqn_AllocatorType::Custom: - { - if (allocator->custom.allocate) - result = DQN_CAST(char *)allocator->custom.allocate(size, alignment, allocator->context.user DQN_CALL_SITE_ARGS_INPUT); - } - break; - } - - if (result) - { - allocator->allocations++; - allocator->total_allocations++; - allocator->bytes_allocated += size; - allocator->total_bytes_allocated += size; - -#if DQN_ALLOCATION_TRACING - Dqn_AllocationTracer_Add(allocator->tracer, result, size DQN_CALL_SITE_ARGS_INPUT); -#endif - } - - return result; -} - // ------------------------------------------------------------------------------------------------- // NOTE: Dqn_String // ------------------------------------------------------------------------------------------------- -DQN_API Dqn_String Dqn_String_Init(char const *str, Dqn_isize size) +DQN_API Dqn_String Dqn_String_Init(char const *string, Dqn_isize size) { Dqn_String result = {}; - result.str_ = str; + result.str = DQN_CAST(char *)string; result.size = size; - result.cap = size; + result.cap = result.size; return result; } -DQN_API Dqn_String Dqn_String__InitFmtV(Dqn_Allocator *allocator, char const *fmt, va_list va DQN_CALL_SITE_ARGS) +DQN_API Dqn_String Dqn_String_InitMemory(char *buf, Dqn_isize capacity) +{ + DQN_ASSERT(capacity > 0); + Dqn_String result = {}; + result.str = buf; + result.size = 0; + result.cap = capacity - 1; // Reserve 1 byte for null terminator + return result; +} + +DQN_API Dqn_String Dqn_String__Fmt(Dqn_ArenaAllocator *arena DQN_CALL_SITE_ARGS, char const *fmt, ...) +{ + va_list va; + va_start(va, fmt); + Dqn_String result = Dqn_String__FmtV(arena, fmt, va DQN_CALL_SITE_ARGS_INPUT); + va_end(va); + return result; +} + +DQN_API Dqn_String Dqn_String__FmtV(Dqn_ArenaAllocator *arena, char const *fmt, va_list va DQN_CALL_SITE_ARGS) { Dqn_String result = {}; va_list va2; va_copy(va2, va); result.size = STB_SPRINTF_DECORATE(vsnprintf)(nullptr, 0, fmt, va); - result.str = allocator ? DQN_CAST(char *)Dqn_Allocator__Allocate(allocator, result.size + 1, alignof(char), Dqn_ZeroMem::No DQN_CALL_SITE_ARGS_INPUT) - : DQN_CAST(char *)DQN_MALLOC(result.size + 1); + result.str = DQN_CAST(char *)Dqn_ArenaAllocator__Allocate(arena, sizeof(char) * (result.size + 1), alignof(char), Dqn_ZeroMem::No DQN_CALL_SITE_ARGS_INPUT); if (result.str) { STB_SPRINTF_DECORATE(vsnprintf)(result.str, Dqn_Safe_TruncateISizeToInt(result.size + 1), fmt, va2); @@ -3687,89 +3149,29 @@ DQN_API Dqn_String Dqn_String__InitFmtV(Dqn_Allocator *allocator, char const *fm return result; } -DQN_API Dqn_String Dqn_String__InitFmt(Dqn_Allocator *allocator DQN_CALL_SITE_ARGS, char const *fmt, ...) -{ - va_list va; - va_start(va, fmt); - Dqn_String result = Dqn_String__InitFmtV(allocator, fmt, va DQN_CALL_SITE_ARGS_INPUT); - va_end(va); - return result; -} - -DQN_API Dqn_String Dqn_String__InitArenaFmt(Dqn_ArenaAllocator *arena DQN_CALL_SITE_ARGS, char const *fmt, ...) -{ - Dqn_Allocator allocator = Dqn_Allocator_InitWithArena(arena); - va_list va; - va_start(va, fmt); - Dqn_String result = Dqn_String__InitFmtV(&allocator, fmt, va DQN_CALL_SITE_ARGS_INPUT); - va_end(va); - return result; -} - -DQN_API Dqn_String Dqn_String__InitArenaFmtV(Dqn_ArenaAllocator *arena DQN_CALL_SITE_ARGS, char const *fmt, va_list va DQN_CALL_SITE_ARGS) -{ - Dqn_Allocator allocator = Dqn_Allocator_InitWithArena(arena); - Dqn_String result = Dqn_String__InitFmtV(&allocator, fmt, va DQN_CALL_SITE_ARGS_INPUT); - return result; -} - -DQN_API Dqn_String Dqn_String_Allocate(Dqn_Allocator *allocator, Dqn_isize size, Dqn_ZeroMem zero_mem) +DQN_API Dqn_String Dqn_String__Allocate(Dqn_ArenaAllocator *arena, Dqn_isize size, Dqn_ZeroMem zero_mem DQN_CALL_SITE_ARGS) { Dqn_String result = {}; - result.str = Dqn_Allocator_NewArray(allocator, char, size + 1, zero_mem); + result.str = DQN_CAST(char *)Dqn_ArenaAllocator__Allocate(arena, size + 1, alignof(char), zero_mem DQN_CALL_SITE_ARGS_INPUT); result.cap = size; return result; } -DQN_API Dqn_String Dqn_String_ArenaAllocate(Dqn_ArenaAllocator *arena, Dqn_isize size, Dqn_ZeroMem zero_mem) -{ - Dqn_String result = {}; - result.str = Dqn_ArenaAllocator_NewArray(arena, char, size + 1, zero_mem); - result.cap = size; - return result; -} - -DQN_API Dqn_b32 Dqn_String_Compare(Dqn_String const lhs, Dqn_String const rhs) -{ - Dqn_b32 result = false; - if (lhs.size == rhs.size) - result = (DQN_MEMCMP(lhs.str, rhs.str, DQN_CAST(size_t)lhs.size) == 0); - return result; -} - -DQN_API Dqn_b32 Dqn_String_CompareCaseInsensitive(Dqn_String const lhs, Dqn_String const rhs) -{ - Dqn_b32 result = (lhs.size == rhs.size); - for (Dqn_isize index = 0; index < lhs.size && result; index++) - result = (Dqn_Char_ToLower(lhs.str[index]) == Dqn_Char_ToLower(rhs.str[index])); - return result; -} - -DQN_API Dqn_String Dqn_String__ArenaCopy(Dqn_String const src, Dqn_ArenaAllocator *arena DQN_CALL_SITE_ARGS) +DQN_API Dqn_String Dqn_String__Copy(Dqn_String const src, Dqn_ArenaAllocator *arena DQN_CALL_SITE_ARGS) { Dqn_String result = src; - result.str = DQN_CAST(char *)Dqn_ArenaAllocator_Allocate(arena, result.size + 1, alignof(char), Dqn_ZeroMem::No DQN_CALL_SITE_ARGS_INPUT); + result.str = DQN_CAST(char *)Dqn_ArenaAllocator__Allocate(arena, result.size + 1, alignof(char), Dqn_ZeroMem::No DQN_CALL_SITE_ARGS_INPUT); result.cap = result.size; DQN_MEMCOPY(result.str, src.str, DQN_CAST(size_t)result.size); result.str[result.size] = 0; return result; } -DQN_API Dqn_String Dqn_String__Copy(Dqn_String const src, Dqn_Allocator *allocator DQN_CALL_SITE_ARGS) -{ - Dqn_String result = src; - result.str = allocator ? DQN_CAST(char *)Dqn_Allocator__Allocate(allocator, result.size + 1, alignof(char), Dqn_ZeroMem::No DQN_CALL_SITE_ARGS_INPUT) : - DQN_CAST(char *)DQN_MALLOC(result.size + 1); - result.cap = result.size; - result.str[result.size] = 0; - DQN_MEMCOPY(result.str, src.str, DQN_CAST(size_t)result.size); - return result; -} - DQN_API Dqn_String Dqn_String_TrimWhitespaceAround(Dqn_String const src) { Dqn_String result = {}; - result.str_ = Dqn_Str_TrimWhitespaceAround(src.str, src.size, &result.size); + result.str = DQN_CAST(char *)Dqn_Str_TrimWhitespaceAround(src.str, src.size, &result.size); + result.cap = result.size; return result; } @@ -3783,7 +3185,7 @@ DQN_API Dqn_b32 Dqn_String_AppendFmtV(Dqn_String *str, char const *fmt, va_list { va_list va2; va_copy(va2, va); - Dqn_isize require = STB_SPRINTF_DECORATE(vsnprintf)(nullptr, 0, fmt, va) + 1; + Dqn_isize require = STB_SPRINTF_DECORATE(vsnprintf)(nullptr, 0, fmt, va); Dqn_isize space = str->cap - str->size; Dqn_b32 result = require <= space; @@ -3793,7 +3195,7 @@ DQN_API Dqn_b32 Dqn_String_AppendFmtV(Dqn_String *str, char const *fmt, va_list return result; } - str->size += STB_SPRINTF_DECORATE(vsnprintf)(str->str + str->size, static_cast(space), fmt, va2); + str->size += STB_SPRINTF_DECORATE(vsnprintf)(str->str + str->size, DQN_CAST(int)(space + 1 /*null terminator*/), fmt, va2); va_end(va2); return result; } @@ -3807,24 +3209,47 @@ DQN_API Dqn_b32 Dqn_String_AppendFmt(Dqn_String *str, char const *fmt, ...) return result; } -DQN_API void Dqn_String_Free(Dqn_String *string, Dqn_Allocator *allocator) +DQN_API Dqn_b32 Dqn_String_Eq(Dqn_String const lhs, Dqn_String const rhs, Dqn_StringEqCase eq_case) { - if (allocator) Dqn_Allocator_Free(allocator, string->str); - else DQN_FREE(string->str); - *string = {}; + Dqn_b32 result = false; + if (lhs.size == rhs.size) + { + if (eq_case == Dqn_StringEqCase::Sensitive) + result = (DQN_MEMCMP(lhs.str, rhs.str, DQN_CAST(size_t)lhs.size) == 0); + else + { + result = true; + for (Dqn_isize index = 0; index < lhs.size && result; index++) + result = (Dqn_Char_ToLower(lhs.str[index]) == Dqn_Char_ToLower(rhs.str[index])); + } + } + return result; } -DQN_API Dqn_b32 Dqn_String_StartsWith(Dqn_String string, Dqn_String prefix) +DQN_API Dqn_b32 Dqn_String_EqInsensitive(Dqn_String const lhs, Dqn_String const rhs) +{ + Dqn_b32 result = Dqn_String_Eq(lhs, rhs, Dqn_StringEqCase::Insensitive); + return result; +} + +DQN_API Dqn_b32 Dqn_String_StartsWith(Dqn_String string, Dqn_String prefix, Dqn_StringEqCase eq_case) { Dqn_b32 result = false; if (prefix.size > string.size) return result; - result = DQN_MEMCMP(string.str, prefix.str, prefix.size) == 0; + Dqn_String substring = {string.str, prefix.size}; + result = Dqn_String_Eq(substring, prefix, eq_case); return result; } -DQN_API Dqn_Slice Dqn_String_Split(Dqn_String src, Dqn_Allocator *allocator) +DQN_API Dqn_b32 Dqn_String_StartsWithInsensitive(Dqn_String string, Dqn_String prefix) +{ + Dqn_b32 result = Dqn_String_StartsWith(string, prefix, Dqn_StringEqCase::Insensitive); + return result; +} + +DQN_API Dqn_Array Dqn_String_Split(Dqn_String src, Dqn_ArenaAllocator *arena) { enum StringSplitStage { @@ -3833,7 +3258,7 @@ DQN_API Dqn_Slice Dqn_String_Split(Dqn_String src, Dqn_Allocator *al StringSplitStage_Count, }; - Dqn_Slice result = {}; + Dqn_Array result = {}; int split_index = 0; int split_count = 0; @@ -3845,7 +3270,7 @@ DQN_API Dqn_Slice Dqn_String_Split(Dqn_String src, Dqn_Allocator *al char const *end = src.str; if (stage == StringSplitStage_Write) - result = Dqn_Slice_Allocate(allocator, Dqn_String, split_count, Dqn_ZeroMem::No); + result = Dqn_Array_InitWithArenaNoGrow(arena, Dqn_String, split_count, 0, Dqn_ZeroMem::No); for (;;) { @@ -3888,6 +3313,20 @@ DQN_API Dqn_i64 Dqn_String_ToI64(Dqn_String str) // ------------------------------------------------------------------------------------------------- // NOTE: Dqn_ArenaAllocator // ------------------------------------------------------------------------------------------------- +DQN_API void *Dqn_ArenaAllocator__Copy(Dqn_ArenaAllocator *arena, void *src, Dqn_isize size, Dqn_u8 alignment DQN_CALL_SITE_ARGS) +{ + void *result = Dqn_ArenaAllocator__Allocate(arena, size, alignment, Dqn_ZeroMem::No DQN_CALL_SITE_ARGS_INPUT); + DQN_MEMCOPY(result, src, size); + return result; +} + +DQN_API void *Dqn_ArenaAllocator__CopyNullTerminate(Dqn_ArenaAllocator *arena, void *src, Dqn_isize size, Dqn_u8 alignment DQN_CALL_SITE_ARGS) +{ + void *result = Dqn_ArenaAllocator__Allocate(arena, size + 1, alignment, Dqn_ZeroMem::Yes DQN_CALL_SITE_ARGS_INPUT); + DQN_MEMCOPY(result, src, size); + return result; +} + DQN_API Dqn_ArenaAllocatorBlock *Dqn_ArenaAllocator__AllocateBlock(Dqn_ArenaAllocator *arena, Dqn_isize requested_size DQN_CALL_SITE_ARGS) { Dqn_isize min_block_size = arena->min_block_size; @@ -3895,8 +3334,21 @@ DQN_API Dqn_ArenaAllocatorBlock *Dqn_ArenaAllocator__AllocateBlock(Dqn_ArenaAllo Dqn_isize mem_block_size = DQN_M_MAX(min_block_size, requested_size); auto const allocate_size = DQN_CAST(Dqn_isize)(sizeof(*arena->curr_mem_block) + mem_block_size); - Dqn_Allocator *allocator = arena->allocator ? arena->allocator : &arena->backup_allocator; - auto *result = DQN_CAST(Dqn_ArenaAllocatorBlock *)Dqn_Allocator__Allocate(allocator, allocate_size, alignof(Dqn_ArenaAllocatorBlock), Dqn_ZeroMem::No DQN_CALL_SITE_ARGS_INPUT); + + Dqn_ArenaAllocatorBlock *result = nullptr; + switch (arena->mem_provider) + { + case Dqn_ArenaAllocatorMemProvider::CRT: + { + result = DQN_CAST(Dqn_ArenaAllocatorBlock *)DQN_MALLOC(allocate_size); + } + break; + + case Dqn_ArenaAllocatorMemProvider::Virtual: + case Dqn_ArenaAllocatorMemProvider::UserMemory: + break; + } + if (!result) return result; *result = {}; @@ -3917,8 +3369,13 @@ DQN_API void Dqn_ArenaAllocator__FreeBlock(Dqn_ArenaAllocator *arena, Dqn_ArenaA if (block->prev) block->prev->next = block->next; - Dqn_Allocator *allocator = arena->allocator ? arena->allocator : &arena->backup_allocator; - Dqn_Allocator_Free(allocator, block); + switch (arena->mem_provider) + { + case Dqn_ArenaAllocatorMemProvider::CRT: DQN_FREE(block); break; + case Dqn_ArenaAllocatorMemProvider::Virtual: + case Dqn_ArenaAllocatorMemProvider::UserMemory: + break; + } } DQN_API void Dqn_ArenaAllocator__AttachBlock(Dqn_ArenaAllocator *arena, Dqn_ArenaAllocatorBlock *new_block) @@ -3937,11 +3394,10 @@ DQN_API void Dqn_ArenaAllocator__AttachBlock(Dqn_ArenaAllocator *arena, Dqn_Aren } } -DQN_API Dqn_ArenaAllocator Dqn_ArenaAllocator_InitWithNewAllocator(Dqn_Allocator allocator, Dqn_isize size, Dqn_AllocationTracer *tracer DQN_CALL_SITE_ARGS) +DQN_API Dqn_ArenaAllocator Dqn_ArenaAllocator_InitWithCRT(Dqn_isize size DQN_CALL_SITE_ARGS) { Dqn_ArenaAllocator result = {}; - result.backup_allocator = allocator; - result.tracer = tracer; + result.mem_provider = Dqn_ArenaAllocatorMemProvider::CRT; if (size > 0) { DQN_ASSERT_MSG(size >= DQN_ISIZEOF(*result.curr_mem_block), "(%I64u >= %I64u) There needs to be enough space to encode the Dqn_ArenaAllocatorBlock struct into the memory buffer", size, sizeof(*result.curr_mem_block)); @@ -3952,35 +3408,22 @@ DQN_API Dqn_ArenaAllocator Dqn_ArenaAllocator_InitWithNewAllocator(Dqn_Allocator return result; } -DQN_API Dqn_ArenaAllocator Dqn_ArenaAllocator_InitWithAllocator(Dqn_Allocator *allocator, Dqn_isize size, Dqn_AllocationTracer *tracer DQN_CALL_SITE_ARGS) +DQN_API Dqn_ArenaAllocator Dqn_ArenaAllocator_InitWithMemory(void *memory, Dqn_isize size) { Dqn_ArenaAllocator result = {}; - result.allocator = allocator; - result.tracer = tracer; + result.mem_provider = Dqn_ArenaAllocatorMemProvider::UserMemory; if (size > 0) { DQN_ASSERT_MSG(size >= DQN_ISIZEOF(*result.curr_mem_block), "(%I64u >= %I64u) There needs to be enough space to encode the Dqn_ArenaAllocatorBlock struct into the memory buffer", size, sizeof(*result.curr_mem_block)); - Dqn_ArenaAllocatorBlock *mem_block = Dqn_ArenaAllocator__AllocateBlock(&result, size DQN_CALL_SITE_ARGS_INPUT); + auto *mem_block = DQN_CAST(Dqn_ArenaAllocatorBlock *) memory; + *mem_block = {}; + mem_block->memory = DQN_CAST(Dqn_u8 *) memory + sizeof(*mem_block); + mem_block->size = size - DQN_CAST(Dqn_isize)sizeof(*mem_block); Dqn_ArenaAllocator__AttachBlock(&result, mem_block); } return result; } -DQN_API Dqn_ArenaAllocator Dqn_ArenaAllocator_InitWithMemory(void *memory, Dqn_isize size, Dqn_AllocationTracer *tracer) -{ - Dqn_ArenaAllocator result = {}; - result.tracer = tracer; - - DQN_ASSERT_MSG(size >= DQN_ISIZEOF(*result.curr_mem_block), "(%I64u >= %I64u) There needs to be enough space to encode the Dqn_ArenaAllocatorBlock struct into the memory buffer", size, sizeof(*result.curr_mem_block)); - result.backup_allocator = Dqn_Allocator_InitWithNull(); - auto *mem_block = DQN_CAST(Dqn_ArenaAllocatorBlock *) memory; - *mem_block = {}; - mem_block->memory = DQN_CAST(Dqn_u8 *) memory + sizeof(*mem_block); - mem_block->size = size - DQN_CAST(Dqn_isize)sizeof(*mem_block); - Dqn_ArenaAllocator__AttachBlock(&result, mem_block); - return result; -} - DQN_API void Dqn_ArenaAllocator_Free(Dqn_ArenaAllocator *arena) { for (Dqn_ArenaAllocatorBlock *mem_block = arena->top_mem_block; mem_block;) @@ -3990,14 +3433,14 @@ DQN_API void Dqn_ArenaAllocator_Free(Dqn_ArenaAllocator *arena) Dqn_ArenaAllocator__FreeBlock(arena, block_to_free); } - auto allocator = arena->allocator; + auto mem_provider = arena->mem_provider; auto highest_used_mark = arena->highest_used_mark; *arena = {}; arena->highest_used_mark = highest_used_mark; - arena->allocator = allocator; + arena->mem_provider = mem_provider; } -DQN_API Dqn_b32 Dqn_ArenaAllocator_Reserve_(Dqn_ArenaAllocator *arena, Dqn_isize size DQN_CALL_SITE_ARGS) +DQN_API Dqn_b32 Dqn_ArenaAllocator_Reserve(Dqn_ArenaAllocator *arena, Dqn_isize size DQN_CALL_SITE_ARGS) { if (arena->top_mem_block) { @@ -4057,17 +3500,17 @@ DQN_API void Dqn_ArenaAllocator_EndRegion(Dqn_ArenaAllocatorRegion region) region = {}; } -Dqn_ArenaAllocatorScopedRegion Dqn_ArenaAllocator_MakeScopedRegion(Dqn_ArenaAllocator *arena) +Dqn_ArenaAllocatorAutoRegion Dqn_ArenaAllocator_AutoRegion(Dqn_ArenaAllocator *arena) { - return Dqn_ArenaAllocatorScopedRegion(arena); + return Dqn_ArenaAllocatorAutoRegion(arena); } -Dqn_ArenaAllocatorScopedRegion::Dqn_ArenaAllocatorScopedRegion(Dqn_ArenaAllocator *arena) +Dqn_ArenaAllocatorAutoRegion::Dqn_ArenaAllocatorAutoRegion(Dqn_ArenaAllocator *arena) { this->region = Dqn_ArenaAllocator_BeginRegion(arena); } -Dqn_ArenaAllocatorScopedRegion::~Dqn_ArenaAllocatorScopedRegion() +Dqn_ArenaAllocatorAutoRegion::~Dqn_ArenaAllocatorAutoRegion() { Dqn_ArenaAllocator_EndRegion(this->region); } @@ -4134,7 +3577,7 @@ DQN_API void Dqn_ArenaAllocator_DumpStatsToLog(Dqn_ArenaAllocator const *arena, DQN_API Dqn_FixedString<512> Dqn_ArenaAllocator_StatsString(Dqn_ArenaAllocator const *arena, char const *label) { Dqn_ArenaAllocatorStats stats = Dqn_ArenaAllocator_GetStats(arena); - auto result = Dqn_FixedString_InitFmt<512>(DQN_ARENA_ALLOCATOR__STATS_FMT.str, + auto result = Dqn_FixedString_Fmt<512>(DQN_ARENA_ALLOCATOR__STATS_FMT.str, label, stats.total_used, stats.total_allocated, @@ -4827,22 +4270,119 @@ DQN_API char Dqn_Char_ToLower(char ch) return result; } +// ------------------------------------------------------------------------------------------------- +// NOTE: Dqn_Hex +// ------------------------------------------------------------------------------------------------- +DQN_API char const *Dqn_Hex_CStringTrimSpaceAnd0xPrefix(char const *hex, Dqn_isize size, Dqn_isize *real_size) +{ + Dqn_isize trimmed_size = 0; + char const *trimmed_hex = Dqn_Str_TrimWhitespaceAround(hex, size, &trimmed_size); + char const *result = Dqn_Str_TrimPrefix(trimmed_hex, + trimmed_size, + "0x", + 2 /*prefix_size*/, + &trimmed_size); + if (real_size) *real_size = trimmed_size; + return result; +} + +DQN_API Dqn_String Dqn_Hex_StringTrimSpaceAnd0xPrefix(Dqn_String const string) +{ + Dqn_isize trimmed_size = 0; + char const *trimmed = Dqn_Hex_CStringTrimSpaceAnd0xPrefix(string.str, string.size, &trimmed_size); + Dqn_String result = Dqn_String_Init(trimmed, trimmed_size); + return result; +} + +DQN_API Dqn_u8 *Dqn_Hex_CStringToU8BytesUnchecked(char const *hex, Dqn_isize size, Dqn_isize *real_size, Dqn_ArenaAllocator *arena) +{ + Dqn_u8 *result = Dqn_ArenaAllocator_NewArray(arena, Dqn_u8, size / 2, Dqn_ZeroMem::No); + Dqn_u8 *ptr = result; + + Dqn_b32 even_size = ((size & 1) == 0); + DQN_ASSERT_MSG(even_size, "Unexpected uneven-size given for converting hex size: %d, hex: %.*s", size, size, hex); + + for (Dqn_isize index = 0; index < size; index += 2) + { + char hex01 = hex[index + 0]; + char hex02 = hex[index + 1]; + DQN_ASSERT_MSG(Dqn_Char_IsHex(hex01), "hex01: %c", hex01); + DQN_ASSERT_MSG(Dqn_Char_IsHex(hex02), "hex02: %c", hex02); + + char value01 = Dqn_Char_HexToU8(hex01); + char value02 = Dqn_Char_HexToU8(hex02); + char value = (value01 << 4) | value02; + *ptr++ = value; + } + + Dqn_u8 const *end = result + size / 2; + DQN_ASSERT_MSG(ptr == end, "ptr: %p, end: %p", ptr, end); + + if (real_size) *real_size = (size / 2); + return result; +} + +DQN_API Dqn_Array Dqn_Hex_CStringToU8ArrayUnchecked(char const *hex, Dqn_isize size, Dqn_ArenaAllocator *arena) +{ + Dqn_isize data_size = 0; + auto *data = DQN_CAST(Dqn_u8 *)Dqn_Hex_CStringToU8BytesUnchecked(hex, size, &data_size, arena); + + Dqn_Array result = Dqn_Array_InitWithMemory(data, data_size, data_size); + return result; +} + +DQN_API Dqn_Array Dqn_Hex_StringToU8ArrayUnchecked(Dqn_String const hex, Dqn_ArenaAllocator *arena) +{ + Dqn_isize data_size = 0; + auto *data = DQN_CAST(Dqn_u8 *)Dqn_Hex_CStringToU8BytesUnchecked(hex.str, hex.size, &data_size, arena); + Dqn_Array result = Dqn_Array_InitWithMemory(data, data_size, data_size); + return result; +} + +DQN_API char *Dqn_Hex_U8BytesToCString(char const *bytes, Dqn_isize size, Dqn_ArenaAllocator *arena) +{ + char *result = size > 0 ? Dqn_ArenaAllocator_NewArray(arena, char, size * 2, Dqn_ZeroMem::No) : nullptr; + if (result) + { + char *ptr = result; + for (Dqn_isize index = 0; index < size; index++) + { + char byte = bytes[index]; + char hex01 = (byte >> 4) & 0xF; + char hex02 = byte & 0xF; + DQN_ASSERT_MSG(hex01 <= 0xF, "hex01: %d", hex01); + DQN_ASSERT_MSG(hex02 <= 0xF, "hex02: %d", hex02); + *ptr++ = Dqn_Char_ToHexUnchecked(hex01); + *ptr++ = Dqn_Char_ToHexUnchecked(hex02); + } + } + + return result; +} + +DQN_API Dqn_String Dqn_Hex_U8ArrayToString(Dqn_Array const bytes, Dqn_ArenaAllocator *arena) +{ + char *hex = Dqn_Hex_U8BytesToCString(DQN_CAST(char const *)bytes.data, bytes.size, arena); + Dqn_String result = Dqn_String_Init(hex, bytes.size * 2); + return result; +} + // ------------------------------------------------------------------------------------------------- // NOTE: Dqn_Str // ------------------------------------------------------------------------------------------------- DQN_API Dqn_b32 Dqn_Str_Equals(char const *a, char const *b, Dqn_isize a_len, Dqn_isize b_len) { - if (a_len == -1) a_len = DQN_CAST(Dqn_isize)Dqn_Str_Len(a); - if (b_len == -1) b_len = DQN_CAST(Dqn_isize)Dqn_Str_Len(b); + if (a_len == -1) a_len = DQN_CAST(Dqn_isize)Dqn_Str_Size(a); + if (b_len == -1) b_len = DQN_CAST(Dqn_isize)Dqn_Str_Size(b); if (a_len != b_len) return false; - return (strncmp(a, b, DQN_CAST(size_t)a_len) == 0); + return (DQN_MEMCMP(a, b, DQN_CAST(size_t)a_len) == 0); } DQN_API char const *Dqn_Str_FindMulti(char const *buf, char const *find_list[], Dqn_isize const *find_string_lens, Dqn_isize find_len, Dqn_isize *match_index, Dqn_isize buf_len) { char const *result = nullptr; if (find_len == 0) return result; - if (buf_len < 0) buf_len = DQN_CAST(Dqn_isize)Dqn_Str_Len(buf); + if (buf_len < 0) buf_len = DQN_CAST(Dqn_isize)Dqn_Str_Size(buf); char const *buf_end = buf + buf_len; for (; buf != buf_end; ++buf) @@ -4869,8 +4409,8 @@ DQN_API char const *Dqn_Str_FindMulti(char const *buf, char const *find_list[], DQN_API char const *Dqn_Str_Find(char const *buf, char const *find, Dqn_isize buf_len, Dqn_isize find_len, Dqn_b32 case_insensitive) { if (find_len == 0) return nullptr; - if (buf_len < 0) buf_len = DQN_CAST(Dqn_isize)Dqn_Str_Len(buf); - if (find_len < 0) find_len = DQN_CAST(Dqn_isize)Dqn_Str_Len(find); + if (buf_len < 0) buf_len = DQN_CAST(Dqn_isize)Dqn_Str_Size(buf); + if (find_len < 0) find_len = DQN_CAST(Dqn_isize)Dqn_Str_Size(find); char const *buf_end = buf + buf_len; char const *result = nullptr; @@ -4907,17 +4447,17 @@ DQN_API char const *Dqn_Str_Find(char const *buf, char const *find, Dqn_isize bu return result; } -DQN_API char const *Dqn_Str_FileNameFromPath(char const *path, int len, int *file_name_len) +DQN_API char const *Dqn_Str_FileNameFromPath(char const *path, Dqn_isize len, Dqn_isize *file_name_len) { char const *result = path; - int result_len = len == -1 ? Dqn_Str_Len(path) : len; - for (int i = (result_len - 1); i >= 0; --i) + Dqn_isize result_len = len == -1 ? Dqn_Str_Size(path) : len; + for (Dqn_isize i = (result_len - 1); i >= 0; --i) { if (result[i] == '\\' || result[i] == '/') { char const *file_end = result + result_len; result = result + (i + 1); - result_len = DQN_CAST(int)(file_end - result); + result_len = DQN_CAST(Dqn_isize)(file_end - result); break; } } @@ -4928,9 +4468,9 @@ DQN_API char const *Dqn_Str_FileNameFromPath(char const *path, int len, int *fil return result; } -DQN_API int Dqn_Str_Len(char const *src) +DQN_API Dqn_isize Dqn_Str_Size(char const *src) { - int result = 0; + Dqn_isize result = 0; while (src && src[0] != 0) { src++; @@ -4942,7 +4482,7 @@ DQN_API int Dqn_Str_Len(char const *src) DQN_API Dqn_b32 Dqn_Str_Match(char const *src, char const *find, int find_len) { - if (find_len == -1) find_len = Dqn_Safe_TruncateUSizeToInt(Dqn_Str_Len(find)); + if (find_len == -1) find_len = Dqn_Safe_TruncateUSizeToInt(Dqn_Str_Size(find)); auto result = DQN_CAST(Dqn_b32)(strncmp(src, find, DQN_CAST(size_t)find_len) == 0); return result; } @@ -5079,7 +4619,7 @@ DQN_API Dqn_u64 Dqn_Str_ToU64(char const *buf, int len, char separator) { Dqn_u64 result = 0; if (!buf) return result; - if (len == -1) len = Dqn_Safe_TruncateUSizeToInt(Dqn_Str_Len(buf)); + if (len == -1) len = Dqn_Safe_TruncateUSizeToInt(Dqn_Str_Size(buf)); if (len == 0) return result; char const *buf_ptr = Dqn_Str_SkipWhitespace(buf); @@ -5106,7 +4646,7 @@ DQN_API Dqn_i64 Dqn_Str_ToI64(char const *buf, int len, char separator) { Dqn_i64 result = 0; if (!buf) return result; - if (len == -1) len = Dqn_Safe_TruncateUSizeToInt(Dqn_Str_Len(buf)); + if (len == -1) len = Dqn_Safe_TruncateUSizeToInt(Dqn_Str_Size(buf)); if (len == 0) return result; char const *buf_ptr = Dqn_Str_SkipWhitespace(buf); @@ -5135,10 +4675,22 @@ DQN_API Dqn_i64 Dqn_Str_ToI64(char const *buf, int len, char separator) return result; } +DQN_API Dqn_isize Dqn_StrW_Size(wchar_t const *src) +{ + Dqn_isize result = 0; + while (src && src[0] != 0) + { + src++; + result++; + } + + return result; +} + // ------------------------------------------------------------------------------------------------- // NOTE: Dqn_File // ------------------------------------------------------------------------------------------------- -DQN_API char *Dqn_File__ReadEntireFile(char const *file, Dqn_isize *file_size, Dqn_Allocator *allocator DQN_CALL_SITE_ARGS) +DQN_API char *Dqn_File__ReadFile(char const *file, Dqn_isize *file_size, Dqn_ArenaAllocator *arena DQN_CALL_SITE_ARGS) { Dqn_isize file_size_ = 0; if (!file_size) @@ -5162,8 +4714,8 @@ DQN_API char *Dqn_File__ReadEntireFile(char const *file, Dqn_isize *file_size, D } rewind(file_handle); - auto *result = allocator ? DQN_CAST(char *) Dqn_Allocator__Allocate(allocator, *file_size + 1, alignof(char), Dqn_ZeroMem::No DQN_CALL_SITE_ARGS_INPUT) - : DQN_CAST(char *) DQN_MALLOC(*file_size + 1); + auto mem_region = Dqn_ArenaAllocator_BeginRegion(arena); + auto *result = DQN_CAST(char *) Dqn_ArenaAllocator__Allocate(arena, *file_size + 1, alignof(char), Dqn_ZeroMem::No DQN_CALL_SITE_ARGS_INPUT); if (!result) { DQN_LOG_M("Failed to allocate %td bytes to read file '%s'\n", *file_size + 1, file); @@ -5173,9 +4725,7 @@ DQN_API char *Dqn_File__ReadEntireFile(char const *file, Dqn_isize *file_size, D result[*file_size] = 0; if (fread(result, DQN_CAST(size_t)(*file_size), 1, file_handle) != 1) { - if (allocator) Dqn_Allocator_Free(allocator, result); - else DQN_FREE(result); - + Dqn_ArenaAllocator_EndRegion(mem_region); DQN_LOG_E("Failed to read %td bytes into buffer from '%s'\n", *file_size, file); return nullptr; } @@ -5183,14 +4733,15 @@ DQN_API char *Dqn_File__ReadEntireFile(char const *file, Dqn_isize *file_size, D return result; } -DQN_API char *Dqn_File__ArenaReadEntireFile(char const *file, Dqn_isize *file_size, Dqn_ArenaAllocator *arena DQN_CALL_SITE_ARGS) +DQN_API Dqn_String Dqn_File__ArenaReadFileToString(char const *file, Dqn_ArenaAllocator *arena DQN_CALL_SITE_ARGS) { - Dqn_Allocator allocator = Dqn_Allocator_InitWithArena(arena); - char * result = Dqn_File__ReadEntireFile(file, file_size, &allocator DQN_CALL_SITE_ARGS_INPUT); + Dqn_isize file_size = 0; + char * string = Dqn_File__ReadFile(file, &file_size, arena DQN_CALL_SITE_ARGS_INPUT); + Dqn_String result = Dqn_String_Init(string, file_size); return result; } -DQN_API Dqn_b32 Dqn_File_WriteEntireFile(char const *file, char const *buffer, Dqn_isize buffer_size) +DQN_API Dqn_b32 Dqn_File_WriteFile(char const *file, char const *buffer, Dqn_isize buffer_size) { FILE *file_handle = fopen(file, "w+b"); if (!file_handle) @@ -5215,7 +4766,7 @@ DQN_API Dqn_b32 Dqn_File_WriteEntireFile(char const *file, char const *buffer, D // NOTE: Dqn_File Implementation // ------------------------------------------------------------------------------------------------- #if defined(DQN_OS_WIN32) -DQN_API Dqn_u64 Dqn_Win32__FileTimeToSeconds(FILETIME const *time) +DQN_API Dqn_u64 Dqn_Win__FileTimeToSeconds(FILETIME const *time) { ULARGE_INTEGER time_large_int = {}; time_large_int.u.LowPart = time->dwLowDateTime; @@ -5247,9 +4798,9 @@ DQN_API Dqn_FileInfo Dqn_File_Info(char const *path) if (result) { - result.create_time_in_s = Dqn_Win32__FileTimeToSeconds(&attrib_data.ftCreationTime); - result.last_access_time_in_s = Dqn_Win32__FileTimeToSeconds(&attrib_data.ftLastAccessTime); - result.last_write_time_in_s = Dqn_Win32__FileTimeToSeconds(&attrib_data.ftLastWriteTime); + result.create_time_in_s = Dqn_Win__FileTimeToSeconds(&attrib_data.ftCreationTime); + result.last_access_time_in_s = Dqn_Win__FileTimeToSeconds(&attrib_data.ftLastAccessTime); + result.last_write_time_in_s = Dqn_Win__FileTimeToSeconds(&attrib_data.ftLastWriteTime); LARGE_INTEGER large_int = {}; large_int.u.HighPart = DQN_CAST(Dqn_i32)attrib_data.nFileSizeHigh; @@ -5257,7 +4808,7 @@ DQN_API Dqn_FileInfo Dqn_File_Info(char const *path) result.size = (Dqn_u64)large_int.QuadPart; } #else - // TODO(doyle): Implement + // TODO(dqn): Implement DQN_INVALID_CODE_PATH; #endif @@ -5424,46 +4975,218 @@ DQN_API char *Dqn_U64ToTempStr(Dqn_u64 val, Dqn_b32 comma_sep) } // ------------------------------------------------------------------------------------------------- -// NOTE: Dqn_Win32 Implementation +// NOTE: Dqn_Win Implementation // ------------------------------------------------------------------------------------------------- -DQN_API Dqn_FixedString<1024> Dqn_Win32_LastError() +DQN_API Dqn_String Dqn_Win_LastError(Dqn_ArenaAllocator *arena, int *last_error) { - Dqn_FixedString<1024> result = {}; - Dqn_FixedString_AppendFmt(&result, "(%d) ", GetLastError()); - result.size += FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, - nullptr, - GetLastError(), - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - result.str + result.size, - DQN_CAST(DWORD)(sizeof(result.str) - result.size), - nullptr); + DWORD error = GetLastError(); + int const MSG_SIZE_BYTES = DQN_KILOBYTES(64) - 1; // Maximum error size + int const MSG_SIZE = MSG_SIZE_BYTES / sizeof(char); + auto * msg = DQN_CAST(char *) Dqn_ArenaAllocator_NewArray(arena, char, MSG_SIZE_BYTES, Dqn_ZeroMem::No); + + DWORD msg_length = FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, // DWORD dwFlags, + nullptr, // LPCVOID lpSource, + error, // DWORD dwMessageId, + 0, // DWORD dwLanguageId, + msg, // LPSTR lpBuffer, + MSG_SIZE, // DWORD nSize, + nullptr // va_list * Arguments); + ); + + Dqn_String result = Dqn_String_Init(msg, msg_length); + if (last_error) *last_error = error; return result; } -DQN_API wchar_t *Dqn_Win32_ArenaToWChar(Dqn_ArenaAllocator *arena, Dqn_String src, int *wchar_size) +DQN_API void Dqn_Win_DumpLastError(Dqn_ArenaAllocator *tmp_arena, Dqn_String prefix) { - int size_int = Dqn_Safe_TruncateISizeToInt(src.size); + Dqn_ArenaAllocatorAutoRegion mem_region = Dqn_ArenaAllocator_AutoRegion(tmp_arena); + int last_error = 0; + Dqn_String msg = Dqn_Win_LastError(tmp_arena, &last_error); + + if (msg.size) + DQN_LOG_E("%.*s. Error: %.*s", DQN_STRING_FMT(prefix), DQN_STRING_FMT(msg)); + else + DQN_LOG_E("%.*s. FormatMessage failed on error: %d with error code: %d\n", DQN_STRING_FMT(prefix), last_error, GetLastError()); +} + +DQN_API Dqn_StringW Dqn_Win_UTF8ToWChar(Dqn_String src, Dqn_ArenaAllocator *arena) +{ + Dqn_StringW result = {}; + int size_int = Dqn_Safe_TruncateISizeToInt(src.size); + if (!size_int) + return result; + int required = MultiByteToWideChar(CP_UTF8, 0 /*dwFlags*/, src.str, size_int, nullptr, 0); if (required == 0) { - DQN_LOG_W("Failed to convert string '%.*s' to wide string: '%.*s'", + Dqn_ArenaAllocatorAutoRegion mem_region = Dqn_ArenaAllocator_AutoRegion(arena); + Dqn_String error = Dqn_Win_LastError(arena, nullptr); + DQN_LOG_W("Failed to convert string '%.*s' to wide string: %.*s", DQN_STRING_FMT(src), - Dqn_Win32_LastError().str); - return nullptr; + DQN_STRING_FMT(error)); + return result; } - wchar_t *result = Dqn_ArenaAllocator_NewArray(arena, wchar_t, required + 1, Dqn_ZeroMem::No); - if (result) + wchar_t *string = Dqn_ArenaAllocator_NewArray(arena, wchar_t, required + 1, Dqn_ZeroMem::No); + int string_size = 0; + if (string) { - *wchar_size = MultiByteToWideChar(CP_UTF8, 0 /*dwFlags*/, src.str, size_int, result, required); - DQN_HARD_ASSERT_MSG(*wchar_size != 0, "Error should be handled in the case above"); - result[required] = 0; - DQN_ASSERT_MSG(*wchar_size == required, "Sanity check this API"); - DQN_ASSERT_MSG(result[required - 1] != 0, + string_size = MultiByteToWideChar(CP_UTF8, 0 /*dwFlags*/, src.str, size_int, string, required); + DQN_ASSERT_MSG(string_size != 0, "Error should be handled in the case above"); + string[required] = 0; + DQN_ASSERT_MSG(string_size == required, "Sanity check this API"); + DQN_ASSERT_MSG(string[required - 1] != 0, "Sanity check that str[required] was actually the byte that you have to null-terminate, i.e. " "the character before is not a null character."); } + result = {string, string_size}; + return result; +} + +DQN_API Dqn_String Dqn_Win_WCharToUTF8(Dqn_StringW src, Dqn_ArenaAllocator *arena) +{ + Dqn_String result = {}; + int size_int = Dqn_Safe_TruncateISizeToInt(src.size); + if (!size_int) + return result; + + int required = WideCharToMultiByte(CP_UTF8, 0 /*dwFlags*/, src.str, size_int, nullptr, 0, nullptr, nullptr); + if (required == 0) + { + Dqn_ArenaAllocatorAutoRegion mem_region = Dqn_ArenaAllocator_AutoRegion(arena); + Dqn_String error = Dqn_Win_LastError(arena, nullptr); + DQN_LOG_W("Failed to convert wide string '%.*s' to UTF8 string: %.*s", + DQN_STRING_FMT(src), + DQN_STRING_FMT(error)); + return result; + } + + char *string = Dqn_ArenaAllocator_NewArray(arena, char, required + 1, Dqn_ZeroMem::No); + int string_size = 0; + if (string) + { + string_size = WideCharToMultiByte(CP_UTF8, 0 /*dwFlags*/, src.str, size_int, string, required, nullptr, nullptr); + DQN_ASSERT_MSG(string_size != 0, "Error should be handled in the case above"); + string[required] = 0; + DQN_ASSERT_MSG(string_size == required, "Sanity check this API"); + DQN_ASSERT_MSG(string[required - 1] != 0, + "Sanity check that string[required] was actually the byte that you have to null-terminate, i.e. " + "the character before is not a null character."); + } + + result = Dqn_String_Init(string, string_size); + return result; +} + +DQN_API Dqn_String Dqn_Win_CurrentDir(Dqn_ArenaAllocator *arena, Dqn_ArenaAllocator *tmp_arena, Dqn_String suffix) +{ + Dqn_StringW w_suffix = Dqn_Win_UTF8ToWChar(suffix, tmp_arena); + Dqn_StringW curr_dir = Dqn_Win_CurrentDirW(tmp_arena, w_suffix); + Dqn_String result = Dqn_Win_WCharToUTF8(curr_dir, arena); + return result; +} + +DQN_API Dqn_StringW Dqn_Win_CurrentDirW(Dqn_ArenaAllocator *arena, Dqn_StringW suffix) +{ + DQN_ASSERT(suffix.size >= 0); + Dqn_StringW result = {}; + + // NOTE: required_size is the size required *including* the null-terminator + DWORD required_size = GetCurrentDirectoryW(0, nullptr); + DWORD desired_size = required_size + DQN_CAST(DWORD) suffix.size; + Dqn_ArenaAllocatorRegion mem_region = Dqn_ArenaAllocator_BeginRegion(arena); + + wchar_t *w_path = Dqn_ArenaAllocator_NewArray(arena, wchar_t, desired_size, Dqn_ZeroMem::No); + if (!w_path) + result; + + DWORD bytes_written_wo_null_terminator = GetCurrentDirectoryW(desired_size, w_path); + if ((bytes_written_wo_null_terminator + 1) != required_size) + { + // TODO(dqn): Error + Dqn_ArenaAllocator_EndRegion(mem_region); // Undo allocations + return result; + } + + if (suffix.size) + { + DQN_MEMCOPY(w_path + bytes_written_wo_null_terminator, suffix.str, sizeof(suffix.str[0]) * suffix.size); + w_path[desired_size] = 0; + } + + result = {w_path, desired_size - 1}; + return result; +} + +DQN_API Dqn_Array Dqn_Win_FolderFilesW(Dqn_StringW path, Dqn_ArenaAllocator *arena) +{ + enum Step + { + Step_CountFiles, + Step_Allocate, + Step_Count + }; + + Dqn_Array result = {}; + Dqn_isize num_files = 0; + for (int step = Step_CountFiles; step < Step_Count; step++) + { + if (step == Step_Allocate) + result = Dqn_Array_InitWithArenaNoGrow(arena, Dqn_StringW, num_files, 0, Dqn_ZeroMem::No); + + WIN32_FIND_DATAW find_data = {}; + HANDLE find_handle = FindFirstFileExW(path.str, /*LPCWSTR lpFileName,*/ + FindExInfoStandard, /*FINDEX_INFO_LEVELS fInfoLevelId,*/ + &find_data, /*LPVOID lpFindFileData,*/ + FindExSearchNameMatch, /*FINDEX_SEARCH_OPS fSearchOp,*/ + nullptr, /*LPVOID lpSearchFilter,*/ + FIND_FIRST_EX_LARGE_FETCH /*DWORD dwAdditionalFlags)*/); + + if (find_handle == INVALID_HANDLE_VALUE) + return result; + + do + { + if (find_data.cFileName[0] == '.' || (find_data.cFileName[0] == '.' && find_data.cFileName[1] == '.')) + { + continue; + } + + if (find_data.dwFileAttributes & ~FILE_ATTRIBUTE_DIRECTORY) + { + LARGE_INTEGER file_size = {}; + file_size.LowPart = find_data.nFileSizeLow; + file_size.HighPart = find_data.nFileSizeHigh; + if (step == Step_CountFiles) + { + num_files++; + } + else + { + DQN_ASSERT(step == Step_Allocate); + Dqn_StringW *string = Dqn_Array_Make(&result, 1); + string->size = Dqn_StrW_Size(find_data.cFileName); + string->str = Dqn_ArenaAllocator_CopyNullTerminate(arena, wchar_t, find_data.cFileName, string->size); + } + } + } while (FindNextFileW(find_handle, &find_data) != 0); + } + + return result; +} + +DQN_API Dqn_Array Dqn_Win_FolderFiles(Dqn_String path, Dqn_ArenaAllocator *arena, Dqn_ArenaAllocator *tmp_arena) +{ + auto mem_region = Dqn_ArenaAllocator_AutoRegion(tmp_arena); + Dqn_StringW w_path = Dqn_Win_UTF8ToWChar(path, tmp_arena); + Dqn_Array files = Dqn_Win_FolderFilesW(w_path, tmp_arena); + + Dqn_Array result = Dqn_Array_InitWithArenaNoGrow(arena, Dqn_String, files.size, 0, Dqn_ZeroMem::No); + for (Dqn_StringW file : files) + Dqn_Array_Add(&result, Dqn_Win_WCharToUTF8(file, arena)); + return result; } diff --git a/Code/Dqn_Curl.h b/Code/Dqn_Curl.h new file mode 100644 index 0000000..75006e6 --- /dev/null +++ b/Code/Dqn_Curl.h @@ -0,0 +1,80 @@ +#ifndef DQN_CURL_H +#define DQN_CURL_H +// ----------------------------------------------------------------------------- +// NOTE: Dqn_Curl +// ----------------------------------------------------------------------------- +// A wrapper over CURL that is primarily used for hot-code reloading by +// packaging the CURL api into a struct that can be passed across DLL +// boundaries. +// +// curl_global_init(CURL_GLOBAL_ALL) must return CURLE_OK before anything +// in this file is used. You may cleanup curl on exit via curl_global_cleanup() +// if desired. +// +// An easy way to generate the curl commands required to query a url is to use +// CURL itself and the option to dump the command to a C-compatible file, i.e. +// +// curl --libcurl RequestToCCode.c -X POST -H "Content-Type: application/json" --data-binary "{\"jsonrpc\": \"2.0\", \"id\": \"0\", \"method\": \"get_service_nodes\", \"params\": []}" oxen.observer:22023/json_rpc +// +// ----------------------------------------------------------------------------- +// NOTE: Configuration +// ----------------------------------------------------------------------------- +// #define DQN_CURL_IMPLEMENTATION +// Define this in one and only one C++ file to enable the implementation +// code of the header file. + +#define CURL_STATICLIB +#define NOMINMAX +#include + +struct Dqn_CurlProcs +{ + CURL *(*curl_easy_init)(void); + CURLcode (*curl_easy_getinfo)(CURL *curl, CURLINFO info, ...); + CURLcode (*curl_easy_setopt)(CURL *handle, CURLoption option, ...); + const char *(*curl_easy_strerror)(CURLcode); + void (*curl_easy_cleanup)(CURL *curl); + + CURLM *(*curl_multi_init)(void); + CURLMcode (*curl_multi_perform)(CURLM *multi_handle, int *running_handles); + CURLMsg *(*curl_multi_info_read)(CURLM *multi_handle, int *msgs_in_queue); + CURLMcode (*curl_multi_remove_handle)(CURLM *multi_handle, CURL *curl_handle); + CURLMcode (*curl_multi_add_handle)(CURLM *multi_handle, CURL *curl_handle); + const char *(*curl_multi_strerror)(CURLMcode); + + struct curl_slist *(*curl_slist_append)(struct curl_slist *, const char *); + void (*curl_slist_free_all)(struct curl_slist *); +}; + +Dqn_CurlProcs Dqn_CurlProcs_Init(); +#endif // DQN_CURL_H + +#if defined(DQN_CURL_IMPLEMENTATION) +#pragma comment (lib, "Advapi32") +#pragma comment (lib, "Crypt32") +#pragma comment (lib, "Ws2_32") +#pragma comment (lib, "Wldap32") +#pragma comment (lib, "Normaliz") + +Dqn_CurlProcs Dqn_CurlProcs_Init() +{ + Dqn_CurlProcs result = {}; + result.curl_easy_init = curl_easy_init; + result.curl_easy_getinfo = curl_easy_getinfo; + result.curl_easy_setopt = curl_easy_setopt; + result.curl_easy_strerror = curl_easy_strerror; + result.curl_easy_cleanup = curl_easy_cleanup; + + result.curl_multi_init = curl_multi_init; + result.curl_multi_perform = curl_multi_perform; + result.curl_multi_info_read = curl_multi_info_read; + result.curl_multi_remove_handle = curl_multi_remove_handle; + result.curl_multi_add_handle = curl_multi_add_handle; + result.curl_multi_strerror = curl_multi_strerror; + + result.curl_slist_append = curl_slist_append; + result.curl_slist_free_all = curl_slist_free_all; + + return result; +} +#endif // DQN_CURL_IMPLEMENTATION diff --git a/Code/Dqn_Jsmn.h b/Code/Dqn_Jsmn.h new file mode 100644 index 0000000..0dc40e0 --- /dev/null +++ b/Code/Dqn_Jsmn.h @@ -0,0 +1,684 @@ +#ifndef DQN_JSMN_H +#define DQN_JSMN_H +// ----------------------------------------------------------------------------- +// NOTE: Dqn_Jsmn +// ----------------------------------------------------------------------------- +// Some helper functions ontop of the jsmn API to make it more ergonomic to use. +// JSMN is embedded in this source file for convenience. +// +// ----------------------------------------------------------------------------- +// NOTE: Configuration +// ----------------------------------------------------------------------------- +// #define DQN_JSMN_IMPLEMENTATION +// Define this in one and only one C++ file to enable the implementation +// code of the header file. This will also automatically enable the JSMN +// implementation. +// + +#if !defined(DQN_H) + #error You must include "Dqn.h" before including "Dqn_Jsmn.h" +#endif // DQN_H + +// ----------------------------------------------------------------------------- +// NOTE: JSMN Configuration +// ----------------------------------------------------------------------------- +// JSMN has its own set of #defines that are definable by the user. See the JSMN +// header file below for more information. +// +// ----------------------------------------------------------------------------- +// NOTE: JSMN Header File: commit 053d3cd29200edb1bfd181d917d140c16c1f8834 +// ----------------------------------------------------------------------------- +/* + * MIT License + * + * Copyright (c) 2010 Serge Zaitsev + * + * 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. + */ +#ifndef JSMN_H +#define JSMN_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef JSMN_STATIC +#define JSMN_API static +#else +#define JSMN_API extern +#endif + +/** + * JSON type identifier. Basic types are: + * o Object + * o Array + * o String + * o Other primitive: number, boolean (true/false) or null + */ +typedef enum { + JSMN_UNDEFINED = 0, + JSMN_OBJECT = 1, + JSMN_ARRAY = 2, + JSMN_STRING = 3, + JSMN_PRIMITIVE = 4 +} jsmntype_t; + +enum jsmnerr { + /* Not enough tokens were provided */ + JSMN_ERROR_NOMEM = -1, + /* Invalid character inside JSON string */ + JSMN_ERROR_INVAL = -2, + /* The string is not a full JSON packet, more bytes expected */ + JSMN_ERROR_PART = -3 +}; + +/** + * JSON token description. + * type type (object, array, string etc.) + * start start position in JSON data string + * end end position in JSON data string + */ +typedef struct jsmntok { + jsmntype_t type; + int start; + int end; + int size; +#ifdef JSMN_PARENT_LINKS + int parent; +#endif +} jsmntok_t; + +/** + * JSON parser. Contains an array of token blocks available. Also stores + * the string being parsed now and current position in that string. + */ +typedef struct jsmn_parser { + unsigned int pos; /* offset in the JSON string */ + unsigned int toknext; /* next token to allocate */ + int toksuper; /* superior token node, e.g. parent object or array */ +} jsmn_parser; + +/** + * Create JSON parser over an array of tokens + */ +JSMN_API void jsmn_init(jsmn_parser *parser); + +/** + * Run JSON parser. It parses a JSON data string into and array of tokens, each + * describing + * a single JSON object. + */ +JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len, + jsmntok_t *tokens, const unsigned int num_tokens); + + +#ifdef __cplusplus +} +#endif +#endif // JSMN_H + +// ----------------------------------------------------------------------------- +// Header File +// ----------------------------------------------------------------------------- +struct Dqn_JsmnError +{ + jsmntok_t token; + char const *cpp_func; + char const *cpp_file; // The file of the .cpp/h source code that triggered the error + int cpp_line; // The line of the .cpp/h source code that triggered the error +}; + +struct Dqn_JsmnErrorHandle +{ + Dqn_ArenaAllocator *arena; + Dqn_List list; +}; + +void Dqn_JsmnErrorHandle_AddError (Dqn_JsmnErrorHandle *err_handle, char const *func, char const *file, int line); +Dqn_String Dqn_JsmnToken_String (Dqn_String json, jsmntok_t token); +Dqn_b32 Dqn_JsmnToken_Bool (Dqn_String json, jsmntok_t token); +Dqn_u64 Dqn_JsmnToken_U64 (Dqn_String json, jsmntok_t token); +jsmntok_t *Dqn_JsmnToken_AdvanceItPastObject(jsmntok_t *start_it, Dqn_JsmnErrorHandle *err_handle, Dqn_String json); +jsmntok_t *Dqn_JsmnToken_AdvanceItPastArray (jsmntok_t *start_it, Dqn_JsmnErrorHandle *err_handle, Dqn_String json); + +#define Dqn_JsmnToken_ExpectBool(json, token, err_handle) Dqn_JsmnToken__ExpectBool(json, token, err_handle, __FILE__, __LINE__) +#define Dqn_JsmnToken_ExpectNumber(json, token, err_handle) Dqn_JsmnToken__ExpectNumber(json, token, err_handle, __FILE__, __LINE__) +#define Dqn_JsmnToken_ExpectString(token, err_handle) Dqn_JsmnToken__ExpectString(token, err_handle, __FILE__, __LINE__) +#define Dqn_JsmnToken_ExpectArray(token, err_handle) Dqn_JsmnToken__ExpectArray(token, err_handle, __FILE__, __LINE__) +#define Dqn_JsmnToken_ExpectObject(token, err_handle) Dqn_JsmnToken__ExpectObject(token, err_handle, __FILE__, __LINE__) + +#define DQN_JSMN_ERROR_HANDLE_DUMP(handle) \ + for (Dqn_ListChunk *chunk = handle.list.head; chunk; chunk = chunk->next) \ + { \ + for (auto *error = chunk->data; error != (chunk->data + chunk->count); error++) \ + { \ + DQN_LOG_E("Json parsing error at %s from %s:%d", \ + error->cpp_func, \ + Dqn_Str_FileNameFromPath(error->cpp_file), \ + error->cpp_line); \ + } \ + } + +#endif // DQN_JSMN_H + +#if defined(DQN_JSMN_IMPLEMENTATION) +// ----------------------------------------------------------------------------- +// Implementation +// ----------------------------------------------------------------------------- +void Dqn_JsmnErrorHandle_AddError(Dqn_JsmnErrorHandle *err_handle, char const *func, char const *file, int line) +{ + if (!err_handle) + return; + + if (!err_handle->list.head) + err_handle->list = Dqn_List_InitWithArena(err_handle->arena, 16); + + Dqn_JsmnError *error = Dqn_List_Make(&err_handle->list, 1); + if (error) + { + error->cpp_func = func; + error->cpp_file = file; + error->cpp_line = line; + } +} + +Dqn_b32 Dqn_JsmnToken__ExpectBool(Dqn_String json, jsmntok_t token, Dqn_JsmnErrorHandle *err_handle, char const *file, int line) +{ + DQN_ASSERT_MSG(token.start < json.size, "%I64d < %I64u", token.start, json.size); + char ch = json.str[token.start]; + Dqn_b32 result = token.type == JSMN_PRIMITIVE && (ch == 't' || ch == 'f'); + if (!result) Dqn_JsmnErrorHandle_AddError(err_handle, __func__, file, line); + return result; +} + +Dqn_b32 Dqn_JsmnToken__ExpectNumber(Dqn_String json, jsmntok_t token, Dqn_JsmnErrorHandle *err_handle, char const *file, int line) +{ + DQN_ASSERT_MSG(token.start < json.size, "%I64d < %I64u", token.start, json.size); + char ch = json.str[token.start]; + Dqn_b32 result = token.type == JSMN_PRIMITIVE && (ch == '-' || Dqn_Char_IsDigit(ch)); + if (!result) Dqn_JsmnErrorHandle_AddError(err_handle, __func__, file, line); + return result; +} + +Dqn_b32 Dqn_JsmnToken__ExpectString(jsmntok_t token, Dqn_JsmnErrorHandle *err_handle, char const *file, int line) +{ + Dqn_b32 result = token.type == JSMN_STRING; + if (!result) Dqn_JsmnErrorHandle_AddError(err_handle, __func__, file, line); + return result; +} + +Dqn_b32 Dqn_JsmnToken__ExpectArray(jsmntok_t token, Dqn_JsmnErrorHandle *err_handle, char const *file, int line) +{ + Dqn_b32 result = token.type == JSMN_ARRAY; + if (!result) Dqn_JsmnErrorHandle_AddError(err_handle, __func__, file, line); + return result; +} + +Dqn_b32 Dqn_JsmnToken__ExpectObject(jsmntok_t token, Dqn_JsmnErrorHandle *err_handle, char const *file, int line) +{ + Dqn_b32 result = token.type == JSMN_OBJECT; + if (!result) Dqn_JsmnErrorHandle_AddError(err_handle, __func__, file, line); + return result; +} + +Dqn_String Dqn_JsmnToken_String(Dqn_String json, jsmntok_t token) +{ + Dqn_String result = Dqn_String_Init(json.str + token.start, token.end - token.start); + return result; +} + +Dqn_b32 Dqn_JsmnToken_Bool(Dqn_String json, jsmntok_t token) +{ + DQN_ASSERT_MSG(token.start < json.size, "%I64d < %I64u", token.start, json.size); + char ch = json.str[token.start]; + Dqn_b32 result = ch == 't'; + if (!result) { DQN_ASSERT(ch == 'f'); } + return result; +} + +Dqn_u64 Dqn_JsmnToken_U64(Dqn_String json, jsmntok_t token) +{ + DQN_ASSERT_MSG(token.start < json.size, "%I64d < %I64u", token.start, json.size); + Dqn_String string = Dqn_JsmnToken_String(json, token); + Dqn_u64 result = Dqn_String_ToU64(string); + return result; +} + +jsmntok_t *Dqn_JsmnToken_AdvanceItPastObject(jsmntok_t *start_it, Dqn_JsmnErrorHandle *err_handle, Dqn_String json) +{ + jsmntok_t *result = start_it; + if (!Dqn_JsmnToken_ExpectObject(*result, err_handle)) return result; + jsmntok_t *object = result++; + + for (int index = 0; index < object->size; index++) + { + jsmntok_t *key = result++; + jsmntok_t *value = result++; + Dqn_String key_str = Dqn_JsmnToken_String(json, *key); + Dqn_String value_str = Dqn_JsmnToken_String(json, *value); (void)value_str; + + if (value->type == JSMN_OBJECT) + { + result = Dqn_JsmnToken_AdvanceItPastObject(value, err_handle, json); + } + else if (value->type == JSMN_ARRAY) + { + result = Dqn_JsmnToken_AdvanceItPastArray(value, err_handle, json); + } + } + + return result; +} + +jsmntok_t *Dqn_JsmnToken_AdvanceItPastArray(jsmntok_t *start_it, Dqn_JsmnErrorHandle *err_handle, Dqn_String json) +{ + jsmntok_t *result = start_it; + if (!Dqn_JsmnToken_ExpectArray(*result, err_handle)) return result; + jsmntok_t *array = result++; + + for (int index = 0; index < array->size; index++) + { + jsmntok_t *value = result++; + Dqn_String value_str = Dqn_JsmnToken_String(json, *value); (void)value_str; + if (value->type == JSMN_OBJECT) + { + Dqn_JsmnToken_AdvanceItPastObject(start_it, err_handle, json); + } + else if (value->type == JSMN_ARRAY) + { + Dqn_JsmnToken_AdvanceItPastArray(start_it, err_handle, json); + } + } + + return result; +} + +// ----------------------------------------------------------------------------- +// JSMN Implementation +// ----------------------------------------------------------------------------- +#ifdef __cplusplus +extern "C" { +#endif +/** + * Allocates a fresh unused token from the token pool. + */ +static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, jsmntok_t *tokens, + const size_t num_tokens) { + jsmntok_t *tok; + if (parser->toknext >= num_tokens) { + return NULL; + } + tok = &tokens[parser->toknext++]; + tok->start = tok->end = -1; + tok->size = 0; +#ifdef JSMN_PARENT_LINKS + tok->parent = -1; +#endif + return tok; +} + +/** + * Fills token type and boundaries. + */ +static void jsmn_fill_token(jsmntok_t *token, const jsmntype_t type, + const int start, const int end) { + token->type = type; + token->start = start; + token->end = end; + token->size = 0; +} + +/** + * Fills next available token with JSON primitive. + */ +static int jsmn_parse_primitive(jsmn_parser *parser, const char *js, + const size_t len, jsmntok_t *tokens, + const size_t num_tokens) { + jsmntok_t *token; + int start; + + start = parser->pos; + + for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { + switch (js[parser->pos]) { +#ifndef JSMN_STRICT + /* In strict mode primitive must be followed by "," or "}" or "]" */ + case ':': +#endif + case '\t': + case '\r': + case '\n': + case ' ': + case ',': + case ']': + case '}': + goto found; + default: + /* to quiet a warning from gcc*/ + break; + } + if (js[parser->pos] < 32 || js[parser->pos] >= 127) { + parser->pos = start; + return JSMN_ERROR_INVAL; + } + } +#ifdef JSMN_STRICT + /* In strict mode primitive must be followed by a comma/object/array */ + parser->pos = start; + return JSMN_ERROR_PART; +#endif + +found: + if (tokens == NULL) { + parser->pos--; + return 0; + } + token = jsmn_alloc_token(parser, tokens, num_tokens); + if (token == NULL) { + parser->pos = start; + return JSMN_ERROR_NOMEM; + } + jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos); +#ifdef JSMN_PARENT_LINKS + token->parent = parser->toksuper; +#endif + parser->pos--; + return 0; +} + +/** + * Fills next token with JSON string. + */ +static int jsmn_parse_string(jsmn_parser *parser, const char *js, + const size_t len, jsmntok_t *tokens, + const size_t num_tokens) { + jsmntok_t *token; + + int start = parser->pos; + + parser->pos++; + + /* Skip starting quote */ + for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { + char c = js[parser->pos]; + + /* Quote: end of string */ + if (c == '\"') { + if (tokens == NULL) { + return 0; + } + token = jsmn_alloc_token(parser, tokens, num_tokens); + if (token == NULL) { + parser->pos = start; + return JSMN_ERROR_NOMEM; + } + jsmn_fill_token(token, JSMN_STRING, start + 1, parser->pos); +#ifdef JSMN_PARENT_LINKS + token->parent = parser->toksuper; +#endif + return 0; + } + + /* Backslash: Quoted symbol expected */ + if (c == '\\' && parser->pos + 1 < len) { + int i; + parser->pos++; + switch (js[parser->pos]) { + /* Allowed escaped symbols */ + case '\"': + case '/': + case '\\': + case 'b': + case 'f': + case 'r': + case 'n': + case 't': + break; + /* Allows escaped symbol \uXXXX */ + case 'u': + parser->pos++; + for (i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\0'; + i++) { + /* If it isn't a hex character we have an error */ + if (!((js[parser->pos] >= 48 && js[parser->pos] <= 57) || /* 0-9 */ + (js[parser->pos] >= 65 && js[parser->pos] <= 70) || /* A-F */ + (js[parser->pos] >= 97 && js[parser->pos] <= 102))) { /* a-f */ + parser->pos = start; + return JSMN_ERROR_INVAL; + } + parser->pos++; + } + parser->pos--; + break; + /* Unexpected symbol */ + default: + parser->pos = start; + return JSMN_ERROR_INVAL; + } + } + } + parser->pos = start; + return JSMN_ERROR_PART; +} + +/** + * Parse JSON string and fill tokens. + */ +JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len, + jsmntok_t *tokens, const unsigned int num_tokens) { + int r; + int i; + jsmntok_t *token; + int count = parser->toknext; + + for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { + char c; + jsmntype_t type; + + c = js[parser->pos]; + switch (c) { + case '{': + case '[': + count++; + if (tokens == NULL) { + break; + } + token = jsmn_alloc_token(parser, tokens, num_tokens); + if (token == NULL) { + return JSMN_ERROR_NOMEM; + } + if (parser->toksuper != -1) { + jsmntok_t *t = &tokens[parser->toksuper]; +#ifdef JSMN_STRICT + /* In strict mode an object or array can't become a key */ + if (t->type == JSMN_OBJECT) { + return JSMN_ERROR_INVAL; + } +#endif + t->size++; +#ifdef JSMN_PARENT_LINKS + token->parent = parser->toksuper; +#endif + } + token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY); + token->start = parser->pos; + parser->toksuper = parser->toknext - 1; + break; + case '}': + case ']': + if (tokens == NULL) { + break; + } + type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY); +#ifdef JSMN_PARENT_LINKS + if (parser->toknext < 1) { + return JSMN_ERROR_INVAL; + } + token = &tokens[parser->toknext - 1]; + for (;;) { + if (token->start != -1 && token->end == -1) { + if (token->type != type) { + return JSMN_ERROR_INVAL; + } + token->end = parser->pos + 1; + parser->toksuper = token->parent; + break; + } + if (token->parent == -1) { + if (token->type != type || parser->toksuper == -1) { + return JSMN_ERROR_INVAL; + } + break; + } + token = &tokens[token->parent]; + } +#else + for (i = parser->toknext - 1; i >= 0; i--) { + token = &tokens[i]; + if (token->start != -1 && token->end == -1) { + if (token->type != type) { + return JSMN_ERROR_INVAL; + } + parser->toksuper = -1; + token->end = parser->pos + 1; + break; + } + } + /* Error if unmatched closing bracket */ + if (i == -1) { + return JSMN_ERROR_INVAL; + } + for (; i >= 0; i--) { + token = &tokens[i]; + if (token->start != -1 && token->end == -1) { + parser->toksuper = i; + break; + } + } +#endif + break; + case '\"': + r = jsmn_parse_string(parser, js, len, tokens, num_tokens); + if (r < 0) { + return r; + } + count++; + if (parser->toksuper != -1 && tokens != NULL) { + tokens[parser->toksuper].size++; + } + break; + case '\t': + case '\r': + case '\n': + case ' ': + break; + case ':': + parser->toksuper = parser->toknext - 1; + break; + case ',': + if (tokens != NULL && parser->toksuper != -1 && + tokens[parser->toksuper].type != JSMN_ARRAY && + tokens[parser->toksuper].type != JSMN_OBJECT) { +#ifdef JSMN_PARENT_LINKS + parser->toksuper = tokens[parser->toksuper].parent; +#else + for (i = parser->toknext - 1; i >= 0; i--) { + if (tokens[i].type == JSMN_ARRAY || tokens[i].type == JSMN_OBJECT) { + if (tokens[i].start != -1 && tokens[i].end == -1) { + parser->toksuper = i; + break; + } + } + } +#endif + } + break; +#ifdef JSMN_STRICT + /* In strict mode primitives are: numbers and booleans */ + case '-': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case 't': + case 'f': + case 'n': + /* And they must not be keys of the object */ + if (tokens != NULL && parser->toksuper != -1) { + const jsmntok_t *t = &tokens[parser->toksuper]; + if (t->type == JSMN_OBJECT || + (t->type == JSMN_STRING && t->size != 0)) { + return JSMN_ERROR_INVAL; + } + } +#else + /* In non-strict mode every unquoted value is a primitive */ + default: +#endif + r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens); + if (r < 0) { + return r; + } + count++; + if (parser->toksuper != -1 && tokens != NULL) { + tokens[parser->toksuper].size++; + } + break; + +#ifdef JSMN_STRICT + /* Unexpected char in strict mode */ + default: + return JSMN_ERROR_INVAL; +#endif + } + } + + if (tokens != NULL) { + for (i = parser->toknext - 1; i >= 0; i--) { + /* Unmatched opened object or array */ + if (tokens[i].start != -1 && tokens[i].end == -1) { + return JSMN_ERROR_PART; + } + } + } + + return count; +} + +/** + * Creates a new parser based over a given buffer with an array of tokens + * available. + */ +JSMN_API void jsmn_init(jsmn_parser *parser) { + parser->pos = 0; + parser->toknext = 0; + parser->toksuper = -1; +} +#ifdef __cplusplus +} +#endif +#endif // DQN_JSMN_IMPLEMENTATION diff --git a/Code/Dqn_MetaDesk.h b/Code/Dqn_MetaDesk.h new file mode 100644 index 0000000..095aafd --- /dev/null +++ b/Code/Dqn_MetaDesk.h @@ -0,0 +1,948 @@ +#ifndef DQN_META_DESK_H +#define DQN_META_DESK_H +// ----------------------------------------------------------------------------- +// NOTE: Dqn_MetaDesk +// ----------------------------------------------------------------------------- +// Contains helpers functions to code generate formatted CPP files using +// Dion-System's MetaDesk library. +// +// MetaDesk must be available under the include path at +// "metadesk/source/md.[c|h]" when this file is compiled. +// +// ----------------------------------------------------------------------------- +// NOTE: Configuration +// ----------------------------------------------------------------------------- +// #define DQN_META_DESK_IMPLEMENTATION +// Define this in one and only one C++ file to enable the implementation +// code of the header file. +// +// #define DQN_META_DESK_STANDALONE_PROGRAM +// Define this to enable a "main" function allowing this translation unit to +// be compiled into an executable, by default this program will generate all +// the possible code generation outputs. This file's implementation must +// also be available to this translation unit. +// +// ----------------------------------------------------------------------------- +// NOTE: Dqn_MetaDesk Header File +// ----------------------------------------------------------------------------- +#include +#include +#include "metadesk/source/md.h" + +#define DQN_MD_INVALID_CODE_PATH MD_Assert(0) +#define DQN_MD_CAST(Type) (Type) + +// ----------------------------------------------------------------------------- +// NOTE: Dqn_MDCppFile: Helper functions to generate formatted CPP files +// ----------------------------------------------------------------------------- +struct Dqn_MDCppFile +{ + FILE *file; + int indent; + int space_per_indent; + bool append_extra_new_line; +}; + +int Dqn_MDCppFile_SpacePerIndent(Dqn_MDCppFile *cpp); +void Dqn_MDCppFile_LineBeginV (Dqn_MDCppFile *cpp, char const *fmt, va_list args); +void Dqn_MDCppFile_LineBegin (Dqn_MDCppFile *cpp, char const *fmt, ...); +void Dqn_MDCppFile_LineEnd (Dqn_MDCppFile *cpp, char const *fmt, ...); +void Dqn_MDCppFile_LineAdd (Dqn_MDCppFile *cpp, char const *fmt, ...); +void Dqn_MDCppFile_LineV (Dqn_MDCppFile *cpp, char const *fmt, va_list args); +void Dqn_MDCppFile_Line (Dqn_MDCppFile *cpp, char const *fmt, ...); +void Dqn_MDCppFile_NewLine (Dqn_MDCppFile *cpp); +void Dqn_MDCppFile_Indent (Dqn_MDCppFile *cpp); +void Dqn_MDCppFile_Unindent (Dqn_MDCppFile *cpp); +void Dqn_MDCppFile_BeginBlock (Dqn_MDCppFile *cpp, char const *fmt, ...); +void Dqn_MDCppFile_EndBlock (Dqn_MDCppFile *cpp, bool trailing_semicolon, bool new_line_on_next_block); +#define Dqn_MDCppFile_EndEnumBlock(cpp) Dqn_MDCppFile_EndBlock(cpp, true /*trailing_semicolon*/, true /*new_line_on_next_block*/) +#define Dqn_MDCppFile_EndForBlock(cpp) Dqn_MDCppFile_EndBlock(cpp, false /*trailing_semicolon*/, false /*new_line_on_next_block*/) +#define Dqn_MDCppFile_EndIfBlock(cpp) Dqn_MDCppFile_EndBlock(cpp, false /*trailing_semicolon*/, false /*new_line_on_next_block*/) +#define Dqn_MDCppFile_EndFuncBlock(cpp) Dqn_MDCppFile_EndBlock(cpp, false /*trailing_semicolon*/, true /*new_line_on_next_block*/) +#define Dqn_MDCppFile_EndStructBlock(cpp) Dqn_MDCppFile_EndBlock(cpp, true /*trailing_semicolon*/, true /*new_line_on_next_block*/) + +// ----------------------------------------------------------------------------- +// NOTE: Dqn_MD: Code generating helper +// ----------------------------------------------------------------------------- +enum Dqn_MD_CodeGenFlag +{ + Dqn_MD_CodeGenFlag_CEnum = (1 << 0), + Dqn_MD_CodeGenFlag_CppStruct = (1 << 1), + Dqn_MD_CodeGenFlag_ImGuiFunction = (1 << 2), + Dqn_MD_CodeGenFlag_JsonParsingFunction = (1 << 3), + Dqn_MD_CodeGenFlag_CurlQueryFunction = (1 << 4), + Dqn_MD_CodeGenFlag_JsonEndpointURLFunction = (1 << 5), + Dqn_MD_CodeGenFlag_All = (0xFFFFFFFF), +}; + +// file_path: The path of the file to feed into the code generator to parse and spit out code. +// output_name: The name to assign the .cpp/.h file that is generated from the +// function, i.e. .h and .cpp +void Dqn_MD_CodeGen(char const *file_path, char const *output_name, unsigned int flags); + +// ----------------------------------------------------------------------------- +// NOTE: Dqn_MD: Code generating functions +// ----------------------------------------------------------------------------- +#if 0 +Generate a CPP struct for the given 'struct_node'. Children nodes of the +'struct_node' must have a 'type' child associated with it. + +Input + INS_CoinGecko_Coin: + { + id: {type: string}, + symbol: {type: string}, + name: {type: string}, + market_cap_rank: {type: u16}, + market_data: {type: INS_CoinGecko_CoinMarketData}, + } + +Output + struct CoinGecko_CoinMarketDataPrice + { + Dqn_f32 aud; + Dqn_f32 btc; + Dqn_f32 eth; + Dqn_f32 usd; + }; + +#endif +void Dqn_MD_GenerateCppStruct(Dqn_MDCppFile *cpp, MD_Node const *struct_node, bool *requires_arena); + +#if 0 +Generate for any root MetaDesk node tagged with the 'required_tag' +required_tag: The tag that must be attached to the MetaDesk node for an enum to be generated for it +name: The name of the enum to generate + +Input: (With name = "INS_QueryType", required_tag = "json_endpoint") + char const EXAMPLE[] = R"(@json_endpoint Etherscan_TXListForAddress: {} + @json_endpoint CoinGecko_Coin: {}"); + +Output + enum INS_QueryType + { + INS_QueryType_Etherscan_TXListForAddress, + INS_QueryType_CoinGecko_Coin, + INS_QueryType_Count + }; + +#endif +void Dqn_MD_GenerateCEnum(Dqn_MDCppFile *cpp, MD_ParseResult *parse, MD_String8 required_tag, char const *name); + +#if 0 +Generate ImGui widgets for visualising the given 'struct_node'. If a custom type +is provided, we assume that an ImGui function has been previously generated for +the type already. + +Input + INS_CoinGecko_Coin: + { + id: {type: string, json_type: string}, + symbol: {type: string, json_type: string}, + name: {type: string, json_type: string}, + market_cap_rank: {type: u16, json_type: number}, + market_data: {type: INS_CoinGecko_CoinMarketData, json_type: object}, + } + +Output + void INS_CoinGecko_CoinImGui(INS_CoinGecko_Coin const *val){ + ImGui::Text("id: %.*s", DQN_STRING_FMT(val->id)); + ImGui::Text("symbol: %.*s", DQN_STRING_FMT(val->symbol)); + ImGui::Text("name: %.*s", DQN_STRING_FMT(val->name)); + ImGui::Text("market_cap_rank: %I64u", val->market_cap_rank); + INS_CoinGecko_CoinMarketDataImGui(&val->market_data); + } + +#endif +void Dqn_MD_GenerateImGuiFunction(Dqn_MDCppFile *cpp, MD_Node const *struct_node, bool header_file); + +#if 0 +Generate for any root MetaDesk node tagged with 'json_endpoint'. The MetaDesk +node containing the 'json_endpoint' can specify fields that the JSON parsing +function will attempt to pull out from the provided JSON string, powered by the +library jsmn.h. This function requires "Dqn_Jsmn.h/cpp" to be present in the +project using this generated code. + +Input + char const EXAMPLE[] = R"( + Etherscan_TXListForAddressEntry: + { + block_number: {type: u64, json_key: "blockNumber", json_type: string}, + nonce: {type: u64, json_type: string}, + block_hash: {type: string, json_key: "blockHash", json_type: string}, + } + + @json_endpoint(url: "http://api.etherscan.io/api?module=account&action=txlist&address=%.*s&startblock=0&endblock=99999999&sort=asc&apikey=%.*s" + params: {eth_address:string, api_key: string}) + Etherscan_TXListForAddress: + { + status: {type: bool, json_type: string}, + message: {type: string, json_type: string}, + result: {type: Etherscan_TXListForAddressEntry[], json_type: array}, + }"); + +Output: (Truncated for brevity) + Etherscan_TXListForAddress Etherscan_TXListForAddressParseTokens(jsmntok_t **start_it, Dqn_JsmnErrorHandle *err_handle, Dqn_String json, Dqn_ArenaAllocator *arena) + { + INS_Etherscan_TXListForAddress result = {}; + jsmntok_t *it = (*start_it); + if (!Dqn_JsmnToken_ExpectObject(*it, err_handle)) return result; + jsmntok_t *object = it++; + + for (int index = 0; index < object->size; index++) + { + jsmntok_t *key = it++; + jsmntok_t *value = it++; + Dqn_String key_str = Dqn_JsmnToken_String(json, *key); + Dqn_String value_str = Dqn_JsmnToken_String(json, *value); (void)value_str; + + if (key_str == DQN_STRING("status")) + { + if (Dqn_JsmnToken_ExpectString(*value, err_handle)) + { + result.status = DQN_CAST(Dqn_b32)Dqn_String_ToU64(value_str); + } + } + ... + else if (key_str == DQN_STRING("result")) + { + if (Dqn_JsmnToken_ExpectArray(*value, err_handle)) + { + result.result = Dqn_Array_InitWithArenaNoGrow(arena, INS_Etherscan_TXListForAddressEntry, value->size, 0, Dqn_ZeroMem::Yes); + for (int array_index = 0; array_index < value->size; array_index++) + { + auto *result_entry = Dqn_Array_Make(&result.result, 1); + *result_entry = INS_Etherscan_TXListForAddressEntryParseTokens(&it, err_handle, json, arena); + } + } + } + ... + } + *start_it = it; + return result; + } + +#endif +void Dqn_MD_GenerateJsonParsingFunction(Dqn_MDCppFile *cpp, MD_Node const *struct_node, bool requires_arena, bool header_file); + +#if 0 +Generate for any root MetaDesk node with the following tags defined + @json_endpoint(url: <...>, params: {<...>}) + +Input + char const EXAMPLE[] = R"( + @json_endpoint(url: "http://api.etherscan.io/api?module=account&action=txlist&address=%.*s&startblock=0&endblock=99999999&sort=asc&apikey=%.*s" + params: {eth_address:string, api_key: string}) + Etherscan_TXListForAddress: + { + }"); + +Output + Dqn_String Etherscan_TXListForAddressURL(Dqn_ArenaAllocator *arena, Dqn_String eth_address, Dqn_String api_key) + { + Dqn_String result = Dqn_String_InitArenaFmt(arena, "http://api.etherscan.io/api?module=account&action=txlist&address=%.*s&startblock=0&endblock=99999999&sort=asc&apikey=%.*s", DQN_STRING_FMT(eth_address), DQN_STRING_FMT(api_key)); + DQN_HARD_ASSERT(result.str); + return result; + } + +#endif +void Dqn_MD_GenerateJsonEndpointURLFunction(Dqn_MDCppFile *cpp, MD_Node *struct_node, bool header_file); + +void Dqn_MD_GenerateCurlQueryFunction(Dqn_MDCppFile *cpp, MD_Node *struct_node, bool header_file); +#endif // DQN_META_DESK_H + +#if defined(DQN_META_DESK_IMPLEMENTATION) +// ----------------------------------------------------------------------------- +// NOTE: Implementation +// ----------------------------------------------------------------------------- +#pragma warning(push) +#pragma warning(disable: 4244) // '=': conversion from 'MD_u64' to 'unsigned int', possible loss of data +#pragma warning(disable: 4101) // 'consume': unreferenced local variable +#include "metadesk/source/md.c" +#pragma warning(pop) + +// ----------------------------------------------------------------------------- +// NOTE: Dqn_MDCppFile Implementation +// ----------------------------------------------------------------------------- +int Dqn_MDCppFile_SpacePerIndent(Dqn_MDCppFile *cpp) +{ + int result = cpp->space_per_indent == 0 ? 4 : cpp->space_per_indent; + return result; +} + +void Dqn_MDCppFile_LineBeginV(Dqn_MDCppFile *cpp, char const *fmt, va_list args) +{ + int spaces = cpp->indent * Dqn_MDCppFile_SpacePerIndent(cpp); + fprintf(cpp->file, "%*s", spaces, ""); + vfprintf(cpp->file, fmt, args); +} + +void Dqn_MDCppFile_LineBegin(Dqn_MDCppFile *cpp, char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + Dqn_MDCppFile_LineBeginV(cpp, fmt, args); + va_end(args); +} + +void Dqn_MDCppFile_LineEnd(Dqn_MDCppFile *cpp, char const *fmt, ...) +{ + if (fmt) + { + va_list args; + va_start(args, fmt); + vfprintf(cpp->file, fmt, args); + va_end(args); + } + + fputc('\n', cpp->file); +} + +void Dqn_MDCppFile_LineAdd(Dqn_MDCppFile *cpp, char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + vfprintf(cpp->file, fmt, args); + va_end(args); +} + +void Dqn_MDCppFile_LineV(Dqn_MDCppFile *cpp, char const *fmt, va_list args) +{ + Dqn_MDCppFile_LineBeginV(cpp, fmt, args); + Dqn_MDCppFile_LineEnd(cpp, nullptr); +} + +void Dqn_MDCppFile_Line(Dqn_MDCppFile *cpp, char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + Dqn_MDCppFile_LineBeginV(cpp, fmt, args); + Dqn_MDCppFile_LineEnd(cpp, nullptr); + va_end(args); +} + +void Dqn_MDCppFile_NewLine(Dqn_MDCppFile *cpp) +{ + fputc('\n', cpp->file); +} + +void Dqn_MDCppFile_Indent(Dqn_MDCppFile *cpp) +{ + cpp->indent++; +} + +void Dqn_MDCppFile_Unindent(Dqn_MDCppFile *cpp) +{ + cpp->indent--; + MD_Assert(cpp->indent >= 0); +} + +void Dqn_MDCppFile_BeginBlock(Dqn_MDCppFile *cpp, char const *fmt, ...) +{ + if (fmt) + { + va_list args; + va_start(args, fmt); + Dqn_MDCppFile_LineV(cpp, fmt, args); + va_end(args); + } + + Dqn_MDCppFile_Line(cpp, "{"); + Dqn_MDCppFile_Indent(cpp); +} + +void Dqn_MDCppFile_EndBlock(Dqn_MDCppFile *cpp, bool trailing_semicolon, bool new_line_on_next_block) +{ + Dqn_MDCppFile_Unindent(cpp); + Dqn_MDCppFile_Line(cpp, trailing_semicolon ? "};" : "}"); + if (new_line_on_next_block) fputc('\n', cpp->file); +} + +// ----------------------------------------------------------------------------- +// MD Helpers +// ----------------------------------------------------------------------------- +static bool MD_NodeHasArrayNotation(MD_Node const *node) +{ + unsigned node_brackets = MD_NodeFlag_HasBracketLeft | MD_NodeFlag_HasBracketRight; + bool result = (node->flags & node_brackets) == node_brackets; + return result; +} + +// ----------------------------------------------------------------------------- +// Code +// ----------------------------------------------------------------------------- +void Dqn_MD_CodeGen(char const *file_path, char const *output_name, unsigned int flags) +{ + MD_String8 h_file_name = MD_S8Fmt("%s.h", output_name); + MD_String8 cpp_file_name = MD_S8Fmt("%s.cpp", output_name); + + Dqn_MDCppFile cpp = {}; + Dqn_MDCppFile header = {}; + cpp.file = fopen(DQN_MD_CAST(char const *)cpp_file_name.str, "w+b"); + header.file = fopen(DQN_MD_CAST(char const *)h_file_name.str, "w+b"); + + fprintf(stdout, "Generating meta file from %s to %.*s and %.*s\n", file_path, MD_S8VArg(cpp_file_name), MD_S8VArg(h_file_name)); + + if (cpp.file && header.file) + { + MD_ParseResult parse = MD_ParseWholeFile(MD_S8CString(DQN_MD_CAST(char *)file_path)); + if (flags & Dqn_MD_CodeGenFlag_CEnum) + Dqn_MD_GenerateCEnum(&header, &parse, MD_S8Lit("json_endpoint"), "INS_QueryType"); + + for (MD_EachNode(struct_node, parse.node->first_child)) + { + bool requires_arena = false; + if (flags & Dqn_MD_CodeGenFlag_CppStruct) + Dqn_MD_GenerateCppStruct(&header, struct_node, &requires_arena); + + if (flags & Dqn_MD_CodeGenFlag_ImGuiFunction) + Dqn_MD_GenerateImGuiFunction(&header, struct_node, true /*header_file*/); + + if (flags & Dqn_MD_CodeGenFlag_JsonParsingFunction) + Dqn_MD_GenerateJsonParsingFunction(&header, struct_node, requires_arena, true /*header_file*/); + + if (flags & Dqn_MD_CodeGenFlag_CurlQueryFunction) + Dqn_MD_GenerateCurlQueryFunction(&header, struct_node, true /*header_file*/); + + if (flags & Dqn_MD_CodeGenFlag_JsonEndpointURLFunction) + Dqn_MD_GenerateJsonEndpointURLFunction(&header, struct_node, true /*header_file*/); + + Dqn_MDCppFile_NewLine(&header); + + if (flags & Dqn_MD_CodeGenFlag_ImGuiFunction) + Dqn_MD_GenerateImGuiFunction(&cpp, struct_node, false /*header_file*/); + + if (flags & Dqn_MD_CodeGenFlag_JsonParsingFunction) + Dqn_MD_GenerateJsonParsingFunction(&cpp, struct_node, requires_arena, false /*header_file*/); + + if (flags & Dqn_MD_CodeGenFlag_CurlQueryFunction) + Dqn_MD_GenerateCurlQueryFunction(&cpp, struct_node, false /*header_file*/); + + if (flags & Dqn_MD_CodeGenFlag_JsonEndpointURLFunction) + Dqn_MD_GenerateJsonEndpointURLFunction(&cpp, struct_node, false /*header_file*/); + } + } + + if (cpp.file) fclose(cpp.file); + if (header.file) fclose(header.file); +} + +MD_String8 Dqn_MD__ConvertTypeToCppType(MD_String8 type) +{ + MD_String8 result = type; + if (MD_S8Match(type, MD_S8Lit("u8"), 0)) { result = MD_S8Lit("Dqn_u8"); } + else if (MD_S8Match(type, MD_S8Lit("u16"), 0)) { result = MD_S8Lit("Dqn_u16"); } + else if (MD_S8Match(type, MD_S8Lit("u32"), 0)) { result = MD_S8Lit("Dqn_u32"); } + else if (MD_S8Match(type, MD_S8Lit("u64"), 0)) { result = MD_S8Lit("Dqn_u64"); } + else if (MD_S8Match(type, MD_S8Lit("u128"), 0)) { result = MD_S8Lit("intx::uint128"); } + else if (MD_S8Match(type, MD_S8Lit("i8"), 0)) { result = MD_S8Lit("Dqn_i8"); } + else if (MD_S8Match(type, MD_S8Lit("i16"), 0)) { result = MD_S8Lit("Dqn_i16"); } + else if (MD_S8Match(type, MD_S8Lit("i32"), 0)) { result = MD_S8Lit("Dqn_i32"); } + else if (MD_S8Match(type, MD_S8Lit("i64"), 0)) { result = MD_S8Lit("Dqn_i64"); } + else if (MD_S8Match(type, MD_S8Lit("f32"), 0)) { result = MD_S8Lit("Dqn_f32"); } + else if (MD_S8Match(type, MD_S8Lit("f64"), 0)) { result = MD_S8Lit("Dqn_f64"); } + else if (MD_S8Match(type, MD_S8Lit("string"), 0)) { result = MD_S8Lit("Dqn_String"); } + else if (MD_S8Match(type, MD_S8Lit("bool"), 0)) { result = MD_S8Lit("Dqn_b32"); } + return result; +} + +bool Dqn_MD__TypeIsUnsignedInt(MD_String8 type) +{ + bool result = MD_S8Match(type, MD_S8Lit("u8"), 0) || + MD_S8Match(type, MD_S8Lit("u16"), 0) || + MD_S8Match(type, MD_S8Lit("u32"), 0) || + MD_S8Match(type, MD_S8Lit("u64"), 0) || + MD_S8Match(type, MD_S8Lit("u128"), 0); + return result; +} + +bool Dqn_MD__TypeIsSignedInt(MD_String8 type) +{ + bool result = MD_S8Match(type, MD_S8Lit("i8"), 0) || + MD_S8Match(type, MD_S8Lit("i16"), 0) || + MD_S8Match(type, MD_S8Lit("i32"), 0) || + MD_S8Match(type, MD_S8Lit("i64"), 0) || + MD_S8Match(type, MD_S8Lit("i128"), 0); + return result; +} + +MD_String8 Dqn_MD__ConvertTypeToFmtString(MD_String8 type) +{ + MD_String8 result = type; + if (MD_S8Match(type, MD_S8Lit("u8"), 0)) { result = MD_S8Lit("%I64u"); } + else if (MD_S8Match(type, MD_S8Lit("u16"), 0)) { result = MD_S8Lit("%I64u"); } + else if (MD_S8Match(type, MD_S8Lit("u32"), 0)) { result = MD_S8Lit("%I64u"); } + else if (MD_S8Match(type, MD_S8Lit("u64"), 0)) { result = MD_S8Lit("%I64u"); } + else if (MD_S8Match(type, MD_S8Lit("i8"), 0)) { result = MD_S8Lit("%I64d"); } + else if (MD_S8Match(type, MD_S8Lit("i16"), 0)) { result = MD_S8Lit("%I64d"); } + else if (MD_S8Match(type, MD_S8Lit("i32"), 0)) { result = MD_S8Lit("%I64d"); } + else if (MD_S8Match(type, MD_S8Lit("i64"), 0)) { result = MD_S8Lit("%I64d"); } + else if (MD_S8Match(type, MD_S8Lit("f32"), 0)) { result = MD_S8Lit("%f"); } + else if (MD_S8Match(type, MD_S8Lit("f64"), 0)) { result = MD_S8Lit("%f"); } + else if (MD_S8Match(type, MD_S8Lit("string"), 0)) { result = MD_S8Lit("DQN_STRING_FMT(%.*s)"); } + else if (MD_S8Match(type, MD_S8Lit("bool"), 0)) { result = MD_S8Lit("%s"); } + return result; +} + +void Dqn_MD_GenerateCppStruct(Dqn_MDCppFile *cpp, MD_Node const *struct_node, bool *requires_arena) +{ + Dqn_MDCppFile_BeginBlock(cpp, "struct %.*s", MD_S8VArg(struct_node->string)); + for (MD_EachNode(field, struct_node->first_child)) + { + MD_Node *type_node = MD_NodeFromString(field->first_child, field->last_child + 1, MD_S8Lit("type"), (MD_MatchFlags)0); + MD_String8 type = type_node->first_child->string; + MD_Assert(!MD_NodeIsNil(type_node)); + + MD_String8 cpp_type = Dqn_MD__ConvertTypeToCppType(type); + if (MD_S8Match(type, MD_S8Lit("string"), 0)) *requires_arena = true; + bool const is_array = MD_NodeHasArrayNotation(type_node->first_child->next); + if (is_array) + { + *requires_arena = true; + Dqn_MDCppFile_Line(cpp, "Dqn_Array<%.*s> %.*s;", MD_S8VArg(cpp_type), MD_S8VArg(field->string)); + } + else + { + Dqn_MDCppFile_Line(cpp, "%.*s %.*s;", MD_S8VArg(cpp_type), MD_S8VArg(field->string)); + } + } + Dqn_MDCppFile_EndStructBlock(cpp); +} + +void Dqn_MD_GenerateCEnum(Dqn_MDCppFile *cpp, MD_ParseResult *parse, MD_String8 required_tag, char const *name) +{ + Dqn_MDCppFile_BeginBlock(cpp, "enum %s", name); + for (MD_EachNode(struct_node, parse->node->first_child)) + { + if (MD_NodeHasTag(struct_node, required_tag, DQN_MD_CAST(MD_MatchFlags)0)) + Dqn_MDCppFile_Line(cpp, "%s_%.*s,", name, MD_S8VArg(struct_node->string)); + } + Dqn_MDCppFile_Line(cpp, "%s_Count", name); + Dqn_MDCppFile_EndEnumBlock(cpp); +} + +void Dqn_MD_GenerateImGuiFunction(Dqn_MDCppFile *cpp, MD_Node const *struct_node, bool header_file) +{ + const MD_String8 VAL_STR = MD_S8Lit("val"); + Dqn_MDCppFile_LineBegin(cpp, + "void %.*sImGui(%.*s const *%.*s)", + MD_S8VArg(struct_node->string), + MD_S8VArg(struct_node->string), + MD_S8VArg(VAL_STR)); + + if (header_file) + { + Dqn_MDCppFile_LineEnd(cpp, ";"); + return; + } + + Dqn_MDCppFile_BeginBlock(cpp, nullptr); + for (MD_EachNode(field_node, struct_node->first_child)) + { + MD_Node *type_node = MD_NodeFromString(field_node->first_child, field_node->last_child + 1, MD_S8Lit("type"), (MD_MatchFlags)0); + MD_String8 type = type_node->first_child->string; + MD_String8 field = field_node->string; + MD_Assert(!MD_NodeIsNil(type_node)); + + bool is_array = MD_NodeHasArrayNotation(type_node->first_child->next); + MD_String8 param = VAL_STR; + if (is_array) + { + Dqn_MDCppFile_BeginBlock(cpp, "for (%.*s const &it : val->%.*s)", MD_S8VArg(type), MD_S8VArg(field)); + } + + if (is_array) + { + if (MD_S8Match(type, MD_S8Lit("u8"), 0)) Dqn_MDCppFile_Line(cpp, R"(ImGui::Text("%.*s: %%I64u", *it);)", MD_S8VArg(field)); + else if (MD_S8Match(type, MD_S8Lit("u16"), 0)) Dqn_MDCppFile_Line(cpp, R"(ImGui::Text("%.*s: %%I64u", *it);)", MD_S8VArg(field)); + else if (MD_S8Match(type, MD_S8Lit("u32"), 0)) Dqn_MDCppFile_Line(cpp, R"(ImGui::Text("%.*s: %%I64u", *it);)", MD_S8VArg(field)); + else if (MD_S8Match(type, MD_S8Lit("u64"), 0)) Dqn_MDCppFile_Line(cpp, R"(ImGui::Text("%.*s: %%I64u", *it);)", MD_S8VArg(field)); + else if (MD_S8Match(type, MD_S8Lit("u128"), 0)) Dqn_MDCppFile_Line(cpp, R"(ImGui::Text("%.*s: %%I64u", *it);)", MD_S8VArg(field)); + else if (MD_S8Match(type, MD_S8Lit("i8"), 0)) Dqn_MDCppFile_Line(cpp, R"(ImGui::Text("%.*s: %%I64d", *it);)", MD_S8VArg(field)); + else if (MD_S8Match(type, MD_S8Lit("i16"), 0)) Dqn_MDCppFile_Line(cpp, R"(ImGui::Text("%.*s: %%I64d", *it);)", MD_S8VArg(field)); + else if (MD_S8Match(type, MD_S8Lit("i32"), 0)) Dqn_MDCppFile_Line(cpp, R"(ImGui::Text("%.*s: %%I64d", *it);)", MD_S8VArg(field)); + else if (MD_S8Match(type, MD_S8Lit("i64"), 0)) Dqn_MDCppFile_Line(cpp, R"(ImGui::Text("%.*s: %%I64d", *it);)", MD_S8VArg(field)); + else if (MD_S8Match(type, MD_S8Lit("f32"), 0)) Dqn_MDCppFile_Line(cpp, R"(ImGui::Text("%.*s: %%f", *it);)", MD_S8VArg(field)); + else if (MD_S8Match(type, MD_S8Lit("f64"), 0)) Dqn_MDCppFile_Line(cpp, R"(ImGui::Text("%.*s: %%f", *it);)", MD_S8VArg(field)); + else if (MD_S8Match(type, MD_S8Lit("string"), 0)) Dqn_MDCppFile_Line(cpp, R"(ImGui::Text("%.*s: %%.*s", DQN_STRING_FMT(*it));)", MD_S8VArg(field)); + else if (MD_S8Match(type, MD_S8Lit("bool"), 0)) Dqn_MDCppFile_Line(cpp, R"(ImGui::Text("%.*s: %%s", *it ? "true" : "false");)", MD_S8VArg(field)); + else Dqn_MDCppFile_Line(cpp, R"(%.*sImGui(&it);)", MD_S8VArg(type)); + } + else + { + if (MD_S8Match(type, MD_S8Lit("u8"), 0)) Dqn_MDCppFile_Line(cpp, R"(ImGui::Text("%.*s: %%I64u", %.*s->%.*s);)", MD_S8VArg(field), MD_S8VArg(param), MD_S8VArg(field)); + else if (MD_S8Match(type, MD_S8Lit("u16"), 0)) Dqn_MDCppFile_Line(cpp, R"(ImGui::Text("%.*s: %%I64u", %.*s->%.*s);)", MD_S8VArg(field), MD_S8VArg(param), MD_S8VArg(field)); + else if (MD_S8Match(type, MD_S8Lit("u32"), 0)) Dqn_MDCppFile_Line(cpp, R"(ImGui::Text("%.*s: %%I64u", %.*s->%.*s);)", MD_S8VArg(field), MD_S8VArg(param), MD_S8VArg(field)); + else if (MD_S8Match(type, MD_S8Lit("u64"), 0)) Dqn_MDCppFile_Line(cpp, R"(ImGui::Text("%.*s: %%I64u", %.*s->%.*s);)", MD_S8VArg(field), MD_S8VArg(param), MD_S8VArg(field)); + else if (MD_S8Match(type, MD_S8Lit("u128"), 0)) Dqn_MDCppFile_Line(cpp, R"(ImGui::Text("%.*s: %%I64u", %.*s->%.*s);)", MD_S8VArg(field), MD_S8VArg(param), MD_S8VArg(field)); + else if (MD_S8Match(type, MD_S8Lit("i8"), 0)) Dqn_MDCppFile_Line(cpp, R"(ImGui::Text("%.*s: %%I64d", %.*s->%.*s);)", MD_S8VArg(field), MD_S8VArg(param), MD_S8VArg(field)); + else if (MD_S8Match(type, MD_S8Lit("i16"), 0)) Dqn_MDCppFile_Line(cpp, R"(ImGui::Text("%.*s: %%I64d", %.*s->%.*s);)", MD_S8VArg(field), MD_S8VArg(param), MD_S8VArg(field)); + else if (MD_S8Match(type, MD_S8Lit("i32"), 0)) Dqn_MDCppFile_Line(cpp, R"(ImGui::Text("%.*s: %%I64d", %.*s->%.*s);)", MD_S8VArg(field), MD_S8VArg(param), MD_S8VArg(field)); + else if (MD_S8Match(type, MD_S8Lit("i64"), 0)) Dqn_MDCppFile_Line(cpp, R"(ImGui::Text("%.*s: %%I64d", %.*s->%.*s);)", MD_S8VArg(field), MD_S8VArg(param), MD_S8VArg(field)); + else if (MD_S8Match(type, MD_S8Lit("f32"), 0)) Dqn_MDCppFile_Line(cpp, R"(ImGui::Text("%.*s: %%f", %.*s->%.*s);)", MD_S8VArg(field), MD_S8VArg(param), MD_S8VArg(field)); + else if (MD_S8Match(type, MD_S8Lit("f64"), 0)) Dqn_MDCppFile_Line(cpp, R"(ImGui::Text("%.*s: %%f", %.*s->%.*s);)", MD_S8VArg(field), MD_S8VArg(param), MD_S8VArg(field)); + else if (MD_S8Match(type, MD_S8Lit("string"), 0)) Dqn_MDCppFile_Line(cpp, R"(ImGui::Text("%.*s: %%.*s", DQN_STRING_FMT(%.*s->%.*s));)", MD_S8VArg(field), MD_S8VArg(param), MD_S8VArg(field)); + else if (MD_S8Match(type, MD_S8Lit("bool"), 0)) Dqn_MDCppFile_Line(cpp, R"(ImGui::Text("%.*s: %%s", %.*s->%.*s ? "true" : "false");)", MD_S8VArg(field), MD_S8VArg(param), MD_S8VArg(field)); + else Dqn_MDCppFile_Line(cpp, R"(%.*sImGui(&%.*s->%.*s);)", MD_S8VArg(type), MD_S8VArg(param), MD_S8VArg(field)); + } + + if (is_array) + Dqn_MDCppFile_EndForBlock(cpp); + } + Dqn_MDCppFile_EndFuncBlock(cpp); +} + +void Dqn_MD_GenerateJsonParsingFunction(Dqn_MDCppFile *cpp, MD_Node const *struct_node, bool requires_arena, bool header_file) +{ + Dqn_MDCppFile_LineBegin(cpp, "%.*s %.*sParse(Dqn_String json, Dqn_JsmnErrorHandle *err_handle, Dqn_ArenaAllocator *temp_arena", MD_S8VArg(struct_node->string), MD_S8VArg(struct_node->string)); + if (requires_arena) + Dqn_MDCppFile_LineAdd(cpp, ", Dqn_ArenaAllocator *arena"); + + if (header_file) + { + Dqn_MDCppFile_LineEnd(cpp, ");"); + } + else + { + Dqn_MDCppFile_LineEnd(cpp, ")"); + Dqn_MDCppFile_BeginBlock(cpp, nullptr); + Dqn_MDCppFile_Line(cpp, "jsmn_parser parser = {};"); + Dqn_MDCppFile_Line(cpp, "jsmn_init(&parser);"); + Dqn_MDCppFile_NewLine(cpp); + Dqn_MDCppFile_Line(cpp, "int tokens_required = jsmn_parse(&parser, json.str, json.size, nullptr, 0);"); + Dqn_MDCppFile_Line(cpp, "DQN_HARD_ASSERT(tokens_required > 0);"); + Dqn_MDCppFile_Line(cpp, "auto *tokens = Dqn_ArenaAllocator_NewArray(temp_arena, jsmntok_t, tokens_required, Dqn_ZeroMem::No);"); + Dqn_MDCppFile_NewLine(cpp); + Dqn_MDCppFile_Line(cpp, "jsmn_init(&parser);"); + Dqn_MDCppFile_Line(cpp, "jsmn_parse(&parser, json.str, json.size, tokens, tokens_required);"); + Dqn_MDCppFile_Line(cpp, "jsmntok_t *it = tokens + 0;"); + Dqn_MDCppFile_NewLine(cpp); + Dqn_MDCppFile_Line(cpp, "%.*s result = %.*sParseTokens(&it, err_handle, json, arena);", MD_S8VArg(struct_node->string), MD_S8VArg(struct_node->string)); + Dqn_MDCppFile_Line(cpp, "return result;"); + Dqn_MDCppFile_EndFuncBlock(cpp); + } + + Dqn_MDCppFile_LineBegin(cpp, "%.*s %.*sParseTokens(jsmntok_t **start_it, Dqn_JsmnErrorHandle *err_handle, Dqn_String json", MD_S8VArg(struct_node->string), MD_S8VArg(struct_node->string)); + if (requires_arena) + Dqn_MDCppFile_LineAdd(cpp, ", Dqn_ArenaAllocator *arena"); + + if (header_file) + { + Dqn_MDCppFile_LineEnd(cpp, ");"); + return; + } + + Dqn_MDCppFile_LineEnd(cpp, ")"); + Dqn_MDCppFile_BeginBlock(cpp, nullptr); + Dqn_MDCppFile_Line(cpp, "%.*s result = {};", MD_S8VArg(struct_node->string)); + Dqn_MDCppFile_Line(cpp, "jsmntok_t *it = (*start_it);"); + Dqn_MDCppFile_Line(cpp, "if (!Dqn_JsmnToken_ExpectObject(*it, err_handle)) return result;"); + Dqn_MDCppFile_Line(cpp, "jsmntok_t *object = it++;"); + Dqn_MDCppFile_NewLine(cpp); + Dqn_MDCppFile_BeginBlock(cpp, "for (int index = 0; index < object->size; index++)"); + Dqn_MDCppFile_Line(cpp, "jsmntok_t *key = it++;"); + Dqn_MDCppFile_Line(cpp, "jsmntok_t *value = it++;"); + Dqn_MDCppFile_Line(cpp, "Dqn_String key_str = Dqn_JsmnToken_String(json, *key);"); + Dqn_MDCppFile_Line(cpp, "Dqn_String value_str = Dqn_JsmnToken_String(json, *value); (void)value_str;"); + Dqn_MDCppFile_NewLine(cpp); + + MD_String8 const IF_BRANCH = MD_S8Lit("if"); + MD_String8 const ELSE_IF_BRANCH = MD_S8Lit("else if"); + MD_String8 branch_str = IF_BRANCH; + for (MD_EachNode(field_node, struct_node->first_child)) + { + MD_Node *type_node = MD_NodeFromString(field_node->first_child, field_node->last_child + 1, MD_S8Lit("type"), (MD_MatchFlags)0); + MD_Node *json_type_node = MD_NodeFromString(field_node->first_child, field_node->last_child + 1, MD_S8Lit("json_type"), (MD_MatchFlags)0); + MD_Node *json_key_node = MD_NodeFromString(field_node->first_child, field_node->last_child + 1, MD_S8Lit("json_key"), (MD_MatchFlags)0); + + MD_String8 field = field_node->string; + MD_String8 type = type_node->first_child->string; + MD_String8 json_type = json_type_node->first_child->string; + MD_String8 json_key = MD_NodeIsNil(json_key_node) ? field : json_key_node->first_child->string; + MD_Assert(!MD_NodeIsNil(type_node)); + + //------------------------------------------------------------------ + // Convert the type's in our .mdesk struct into JSON parsing code + //------------------------------------------------------------------ + Dqn_MDCppFile_BeginBlock( + cpp, "%.*s (key_str == DQN_STRING(\"%.*s\"))", MD_S8VArg(branch_str), MD_S8VArg(json_key)); + branch_str = ELSE_IF_BRANCH; + + if (MD_S8Match(json_type, MD_S8Lit("array"), (MD_MatchFlags)0)) + { + Dqn_MDCppFile_BeginBlock(cpp, "if (Dqn_JsmnToken_ExpectArray(*value, err_handle))"); + Dqn_MDCppFile_Line(cpp, R"(result.%.*s = Dqn_Array_InitWithArenaNoGrow(arena, %.*s, value->size, 0, Dqn_ZeroMem::Yes);)", MD_S8VArg(field), MD_S8VArg(type)); + Dqn_MDCppFile_BeginBlock(cpp, "for (int array_index = 0; array_index < value->size; array_index++)"); + Dqn_MDCppFile_Line(cpp, + R"(auto *%.*s_entry = Dqn_Array_Make(&result.%.*s, 1);)", + MD_S8VArg(field), + MD_S8VArg(field)); + // TODO(dqn): This doesn't work if the type is not a custom type ... + Dqn_MDCppFile_Line(cpp, + R"(*%.*s_entry = %.*sParseTokens(&it, err_handle, json, arena);)", + MD_S8VArg(field), + MD_S8VArg(type)); + Dqn_MDCppFile_EndIfBlock(cpp); + Dqn_MDCppFile_EndForBlock(cpp); + } + else if (MD_S8Match(json_type, MD_S8Lit("object"), (MD_MatchFlags)0)) + { + Dqn_MDCppFile_BeginBlock(cpp, "if (Dqn_JsmnToken_ExpectObject(*value, err_handle))"); + Dqn_MDCppFile_Line(cpp, "it = value; // Rewind the iterator to the object"); + Dqn_MDCppFile_Line(cpp, "result.%.*s = %.*sParseTokens(&it, err_handle, json);", MD_S8VArg(field), MD_S8VArg(type)); + Dqn_MDCppFile_EndIfBlock(cpp); + } + else + { + //-------------------------------------------------------------- + // Enforce that the JSON token matches the expected type + //-------------------------------------------------------------- + if (MD_S8Match(json_type, MD_S8Lit("number"), (MD_MatchFlags)0)) + Dqn_MDCppFile_BeginBlock(cpp, "if (Dqn_JsmnToken_ExpectNumber(json, *value, err_handle))"); + else if (MD_S8Match(json_type, MD_S8Lit("string"), (MD_MatchFlags)0)) + Dqn_MDCppFile_BeginBlock(cpp, "if (Dqn_JsmnToken_ExpectString(*value, err_handle))"); + else + DQN_MD_INVALID_CODE_PATH; + + //-------------------------------------------------------------- + // Convert JSON token to our struct's expecting type + //-------------------------------------------------------------- + if (MD_S8Match(type, MD_S8Lit("f32"), (MD_MatchFlags)0)) + Dqn_MDCppFile_Line(cpp, "result.%.*s = DQN_CAST(Dqn_f32)atof(value_str.str);", MD_S8VArg(field)); + else if (MD_S8Match(type, MD_S8Lit("f64"), (MD_MatchFlags)0)) + Dqn_MDCppFile_Line(cpp, "result.%.*s = atof(value_str.str);", MD_S8VArg(field)); + else if (MD_S8Match(type, MD_S8Lit("u8"), (MD_MatchFlags)0) || + MD_S8Match(type, MD_S8Lit("u16"), (MD_MatchFlags)0) || + MD_S8Match(type, MD_S8Lit("u32"), (MD_MatchFlags)0) || + MD_S8Match(type, MD_S8Lit("u64"), (MD_MatchFlags)0)) + { + if (MD_S8Match(type, MD_S8Lit("u64"), (MD_MatchFlags)0)) + { + Dqn_MDCppFile_Line( + cpp, + R"(result.%.*s = Dqn_String_ToU64(value_str);)", + MD_S8VArg(field)); + } + else + { + // We need to truncate the deserialized type to write it + // to our struct. + + // NOTE: This bit capitalises the types, i.e. turns u16 + // -> U16, which matches our function naming convention + // for Dqn_Safe_TruncateU64To and co. + char type_all_caps[4] = {}; + memcpy(type_all_caps, type.str, type.size); + type_all_caps[0] = MD_CharToUpper(type_all_caps[0]); + + Dqn_MDCppFile_Line( + cpp, + R"(result.%.*s = Dqn_Safe_TruncateU64To%s(Dqn_String_ToU64(value_str));)", + MD_S8VArg(field), + type_all_caps); + } + } + else if (MD_S8Match(type, MD_S8Lit("u128"), 0)) + { + Dqn_MDCppFile_Line( + cpp, + R"(result.%.*s = Dqn_U128_FromString(value_str.str, Dqn_Safe_TruncateU64ToInt(value_str.size));)", + MD_S8VArg(field)); + } + else if (MD_S8Match(type, MD_S8Lit("i8"), 0) || + MD_S8Match(type, MD_S8Lit("i16"), 0) || + MD_S8Match(type, MD_S8Lit("i32"), 0) || + MD_S8Match(type, MD_S8Lit("i64"), 0)) + { + char type_all_caps[4] = {}; + memcpy(type_all_caps, type.str, type.size); + type_all_caps[0] = MD_CharToUpper(type_all_caps[0]); + + Dqn_MDCppFile_Line( + cpp, + R"(result.%.*s = Dqn_Safe_TruncateU64To%s(Dqn_String_ToU64(value_str));)", + MD_S8VArg(field), + type_all_caps); + } + else if (MD_S8Match(type, MD_S8Lit("string"), 0)) + { + Dqn_MDCppFile_Line( + cpp, + R"(result.%.*s = Dqn_String_Copy(value_str, arena);)", + MD_S8VArg(field)); + } + else if (MD_S8Match(type, MD_S8Lit("bool"), 0)) + { + Dqn_MDCppFile_Line( + cpp, + R"(result.%.*s = DQN_CAST(Dqn_b32)Dqn_String_ToU64(value_str);)", + MD_S8VArg(field)); + } + else + { + // NOTE: Unhandled 'json_type' specified in the .mdesk file + DQN_MD_INVALID_CODE_PATH; + } + Dqn_MDCppFile_EndIfBlock(cpp); + } + Dqn_MDCppFile_EndIfBlock(cpp); + } + + if (struct_node->first_child) + { + Dqn_MDCppFile_BeginBlock(cpp, "else"); + Dqn_MDCppFile_Line (cpp, "// Potential unhandled json object and value, advance the iterator accordingly"); + Dqn_MDCppFile_Line (cpp, "DQN_ASSERT(key->type == JSMN_STRING);"); + Dqn_MDCppFile_BeginBlock(cpp, "if (value->type == JSMN_OBJECT)"); + Dqn_MDCppFile_Line (cpp, "it = Dqn_JsmnToken_AdvanceItPastObject(value, err_handle, json);"); + Dqn_MDCppFile_EndIfBlock(cpp); + Dqn_MDCppFile_BeginBlock(cpp, "else if (value->type == JSMN_ARRAY)"); + Dqn_MDCppFile_Line (cpp, "it = Dqn_JsmnToken_AdvanceItPastArray(value, err_handle, json);"); + Dqn_MDCppFile_EndIfBlock(cpp); + Dqn_MDCppFile_EndIfBlock(cpp); + } + + Dqn_MDCppFile_EndForBlock(cpp); + Dqn_MDCppFile_Line(cpp, "*start_it = it;"); + Dqn_MDCppFile_Line(cpp, "return result;"); + Dqn_MDCppFile_EndFuncBlock(cpp); +} + +void Dqn_MD_GenerateJsonEndpointURLFunction(Dqn_MDCppFile *cpp, MD_Node *struct_node, bool header_file) +{ + MD_Node *json_endpoint_node = MD_NodeFromString(struct_node->first_tag, struct_node->last_tag + 1, MD_S8Lit("json_endpoint"), (MD_MatchFlags)0); + if (MD_NodeIsNil(json_endpoint_node)) + return; + + if (MD_NodeIsNil(json_endpoint_node->first_child)) + { + DQN_MD_INVALID_CODE_PATH; + return; + } + + MD_Node *url_node = MD_NodeFromString(json_endpoint_node->first_child, json_endpoint_node->last_child + 1, MD_S8Lit("url"), (MD_MatchFlags)0); + MD_Node *params_node = MD_NodeFromString(json_endpoint_node->first_child, json_endpoint_node->last_child + 1, MD_S8Lit("params"), (MD_MatchFlags)0); + MD_String8 url = url_node->first_child->string; + + if (MD_NodeIsNil(url_node) || MD_NodeIsNil(params_node)) + { + DQN_MD_INVALID_CODE_PATH; + return; + } + + Dqn_MDCppFile_LineBegin(cpp, "Dqn_String %.*sURL(Dqn_ArenaAllocator *arena", MD_S8VArg(struct_node->string)); + if (!MD_NodeIsNil(params_node->first_child)) Dqn_MDCppFile_LineAdd(cpp, ", "); + for (MD_EachNode(param_node, params_node->first_child)) + { + MD_String8 name = param_node->string; + MD_String8 type = param_node->first_child->string; + MD_Assert(!MD_NodeIsNil(param_node->first_child)); + Dqn_MDCppFile_LineAdd(cpp, "%.*s %.*s", MD_S8VArg(Dqn_MD__ConvertTypeToCppType(type)), MD_S8VArg(name)); + if (!MD_NodeIsNil(param_node->next)) Dqn_MDCppFile_LineAdd(cpp, ", "); + } + + if (header_file) + { + Dqn_MDCppFile_LineEnd(cpp, ");"); + return; + } + + Dqn_MDCppFile_LineEnd(cpp, ")"); + Dqn_MDCppFile_BeginBlock(cpp, nullptr); + + Dqn_MDCppFile_LineBegin(cpp, R"(Dqn_String result = Dqn_String_Fmt(arena, "%.*s")", MD_S8VArg(url)); + if (!MD_NodeIsNil(params_node->first_child)) Dqn_MDCppFile_LineAdd(cpp, ", "); + for (MD_EachNode(param_node, params_node->first_child)) + { + MD_String8 name = param_node->string; + MD_String8 type = param_node->first_child->string; + Dqn_MD__ConvertTypeToFmtString(type); + if (MD_S8Match(type, MD_S8Lit("string"), 0)) + Dqn_MDCppFile_LineAdd(cpp, "DQN_STRING_FMT(%.*s)", MD_S8VArg(name)); + else + Dqn_MDCppFile_LineAdd(cpp, "%.*s", MD_S8VArg(name)); + + if (!MD_NodeIsNil(param_node->next)) Dqn_MDCppFile_LineAdd(cpp, ", "); + } + Dqn_MDCppFile_LineEnd(cpp, ");"); + Dqn_MDCppFile_Line(cpp, "DQN_HARD_ASSERT(result.str);"); + Dqn_MDCppFile_Line(cpp, "return result;"); + Dqn_MDCppFile_EndFuncBlock(cpp); +} + +void Dqn_MD_GenerateCurlQueryFunction(Dqn_MDCppFile *cpp, MD_Node *struct_node, bool header_file) +{ + MD_Node *json_endpoint_node = MD_NodeFromString(struct_node->first_tag, struct_node->last_tag + 1, MD_S8Lit("json_endpoint"), (MD_MatchFlags)0); + if (MD_NodeIsNil(json_endpoint_node)) + return; + + if (MD_NodeIsNil(json_endpoint_node->first_child)) + { + DQN_MD_INVALID_CODE_PATH; + return; + } + + MD_Node *url_node = MD_NodeFromString(json_endpoint_node->first_child, json_endpoint_node->last_child + 1, MD_S8Lit("url"), (MD_MatchFlags)0); + MD_Node *params_node = MD_NodeFromString(json_endpoint_node->first_child, json_endpoint_node->last_child + 1, MD_S8Lit("params"), (MD_MatchFlags)0); + MD_String8 url = url_node->first_child->string; + + if (MD_NodeIsNil(url_node) || MD_NodeIsNil(params_node)) + { + DQN_MD_INVALID_CODE_PATH; + return; + } + + Dqn_MDCppFile_LineBegin(cpp, "Dqn_b32 %.*sQuery(struct INS_Core *insight", MD_S8VArg(struct_node->string)); + if (!MD_NodeIsNil(params_node->first_child)) Dqn_MDCppFile_LineAdd(cpp, ", "); + for (MD_EachNode(param_node, params_node->first_child)) + { + MD_String8 name = param_node->string; + MD_String8 type = param_node->first_child->string; + MD_Assert(!MD_NodeIsNil(param_node->first_child)); + Dqn_MDCppFile_LineAdd(cpp, "%.*s %.*s", MD_S8VArg(Dqn_MD__ConvertTypeToCppType(type)), MD_S8VArg(name)); + if (!MD_NodeIsNil(param_node->next)) Dqn_MDCppFile_LineAdd(cpp, ", "); + } + + if (header_file) + { + Dqn_MDCppFile_LineEnd(cpp, ");"); + return; + } + + Dqn_MDCppFile_LineEnd(cpp, ")"); + Dqn_MDCppFile_BeginBlock(cpp, nullptr); + + Dqn_MDCppFile_Line(cpp, "Dqn_FixedString<1024> url = {};"); + Dqn_MDCppFile_LineBegin(cpp, R"(Dqn_FixedString_AppendFmt(&url, "%.*s")", MD_S8VArg(url)); + if (!MD_NodeIsNil(params_node->first_child)) Dqn_MDCppFile_LineAdd(cpp, ", "); + for (MD_EachNode(param_node, params_node->first_child)) + { + MD_String8 name = param_node->string; + Dqn_MDCppFile_LineAdd(cpp, "DQN_STRING_FMT(%.*s)", MD_S8VArg(name)); + if (!MD_NodeIsNil(param_node->next)) Dqn_MDCppFile_LineAdd(cpp, ", "); + } + Dqn_MDCppFile_LineEnd(cpp, ");"); + Dqn_MDCppFile_Line(cpp, "DQN_HARD_ASSERT(url.size);"); + + Dqn_MDCppFile_Line(cpp, "INS_CurlQuery *query = INS_Curl_QueueQuery(insight, INS_QueryType_%.*s, Dqn_FixedString_ToString(&url));", MD_S8VArg(struct_node->string)); + Dqn_MDCppFile_Line(cpp, "Dqn_b32 result = query != nullptr;"); + Dqn_MDCppFile_Line(cpp, "return result;"); + Dqn_MDCppFile_EndFuncBlock(cpp); +} +#endif // DQN_META_DESK_IMPLEMENTATION + +#if defined(DQN_META_DESK_STANDALONE_PROGRAM) +int main(int argc, char **argv) +{ + if (argc != 3) + { + fprintf(stdout, "Please pass a file name and output name\nUsage: metadesk \n"); + return -1; + } + + char *file = argv[1]; + char *output_name = argv[2]; + Dqn_MD_CodeGen(file, output_name, Dqn_MD_CodeGenFlag_All); + return 0; +} +#endif // DQN_META_DESK_STANDALONE_PROGRAM diff --git a/Code/Dqn_Money.h b/Code/Dqn_Money.h new file mode 100644 index 0000000..bd9a76e --- /dev/null +++ b/Code/Dqn_Money.h @@ -0,0 +1,95 @@ +#if !defined(DQN_MONEY_H) +#define DQN_MONEY_H + +#if !defined(DQN_U128_H) + #error You must include "Dqn_U128.h" before including "Dqn_Money.h" +#endif // DQN_U128_H + +struct Dqn_MoneyString +{ + char str[64]; + int size; +}; + +int const DQN_MONEY_BTC_DECIMAL_PLACES = 8; +int const DQN_MONEY_ETH_DECIMAL_PLACES = 18; +int const DQN_MONEY_BNB_DECIMAL_PLACES = DQN_MONEY_ETH_DECIMAL_PLACES; +int const DQN_MONEY_DOLLAR_DECIMAL_PLACES = 2; +int const DQN_MONEY_OXEN_DECIMAL_PLACES = 9; + +Dqn_MoneyString Dqn_MoneyU128ToString(intx::uint128 atomic_amount, intx::uint128 decimal_places, Dqn_b32 comma_sep = true); +#endif // DQN_MONEY_H + +#if defined(DQN_MONEY_IMPLEMENTATION) +Dqn_MoneyString Dqn_MoneyU128ToString(intx::uint128 atomic_amount, intx::uint128 decimal_places, Dqn_b32 comma_sep) +{ + DQN_HARD_ASSERT_MSG(decimal_places < 32, "// TODO(dqn): Verify what our limits for this are"); + + intx::uint128 atomic_units_per_currency = 1; + for (int place = 0; place < decimal_places; place++) + atomic_units_per_currency *= 10; + + intx::uint128 atomic_part = atomic_amount % atomic_units_per_currency; + intx::uint128 whole_part = atomic_amount / atomic_units_per_currency; + Dqn_MoneyString string = {}; + + if (atomic_amount == 0) + { + string.str[string.size++] = '0'; + string.str[string.size++] = '.'; + string.str[string.size++] = '0'; + } + else + { + int atomic_part_size = 0; + intx::uint128 atomic_copy = atomic_part; + + // Skip any trailing 0s in the fractional part + for (; atomic_copy > 0; atomic_copy /= 10, atomic_part_size++) + { + char digit = (char)(atomic_copy % 10); + if (digit != 0) break; + } + + // Write the fraction part into the string + for (; atomic_copy > 0; atomic_copy /= 10, atomic_part_size++) + { + char digit = (char)(atomic_copy % 10); + string.str[string.size++] = '0' + digit; + } + + // If the string has a size we have a fractional number + if (string.size) + { + // Add zeros until the fractional part is complete + for (int digits = atomic_part_size; digits < decimal_places; digits++) + string.str[string.size++] = '0'; + + string.str[string.size++] = '.'; + } + + // Write the whole part into the string + if (whole_part == 0) + { + string.str[string.size++] = '0'; + } + else + { + for (int digit_count = 0; whole_part > 0; digit_count++) + { + if (comma_sep && (digit_count != 0) && (digit_count % 3 == 0)) + string.str[string.size++] = ','; + + char digit = (char)(whole_part % 10); + string.str[string.size++] = '0' + digit; + whole_part /= 10; + } + } + } + + Dqn_MoneyString result = {}; + for (int i = string.size - 1; i >= 0; i--) + result.str[result.size++] = string.str[i]; + return result; +} +#endif // DQN_MONEY_IMPLEMENTATION diff --git a/Code/Dqn_Tests.cpp b/Code/Dqn_Tests.cpp index d437f60..c1f25e5 100644 --- a/Code/Dqn_Tests.cpp +++ b/Code/Dqn_Tests.cpp @@ -31,6 +31,9 @@ struct Dqn_TestingState Dqn_ArenaAllocator arena; }; +static int g_dqn_test_total_good_tests; +static int g_dqn_test_total_tests; + #if defined(DQN_TEST_NO_ANSI_COLORS) #define DQN_TEST_ANSI_COLOR_RED #define DQN_TEST_ANSI_COLOR_GREEN @@ -61,20 +64,12 @@ struct Dqn_TestingState testing_state.test.scope_started = true; \ testing_state.num_tests_in_group++ -// NOTE: Zero initialised allocators can be a null allocator if #define -// DQN_ALLOCATOR_DEFAULT_TO_NULL is defined, so handle this case specially -// by defaulting to the heap allocator which is the behaviour it would have -// used if the hash define was not used. - -// In the macro below we ensure that the allocator is not null, this idiom is -// repeated whereever we zero initialise an allocator. - #define DQN_TEST_DECLARE_GROUP_SCOPED(testing_state, name) \ fprintf(stdout, name "\n"); \ - if (testing_state.arena.backup_allocator.type == Dqn_AllocatorType::Null) \ - testing_state.arena.backup_allocator = Dqn_Allocator_InitWithHeap(); \ DQN_DEFER \ { \ + g_dqn_test_total_good_tests += testing_state.num_tests_ok_in_group; \ + g_dqn_test_total_tests += testing_state.num_tests_in_group; \ Dqn_TestingState_PrintGroupResult(&testing_state); \ testing_state = {}; \ fprintf(stdout, "\n\n"); \ @@ -85,7 +80,7 @@ struct Dqn_TestingState if (!(expr)) \ { \ testing_state.test.fail_expr = DQN_STRING(#expr); \ - testing_state.test.fail_msg = Dqn_String_InitArenaFmt(&testing_state.arena, msg, ##__VA_ARGS__); \ + testing_state.test.fail_msg = Dqn_String_Fmt(&testing_state.arena, msg, ##__VA_ARGS__); \ } #define DQN_TEST_EXPECT(testing_state, expr) DQN_TEST_EXPECT_MSG(testing_state, expr, "") @@ -138,6 +133,7 @@ void Dqn_TestState_PrintResult(Dqn_TestState const *result) void Dqn_Test_Allocator() { +#if 0 Dqn_TestingState testing_state = {}; DQN_TEST_DECLARE_GROUP_SCOPED(testing_state, "Dqn_Allocator"); @@ -235,6 +231,7 @@ void Dqn_Test_Allocator() DQN_TEST_EXPECT_MSG(testing_state, metadata.offset <= MAX_OFFSET, "metadata.offset: %u, MAX_OFFSET: %u", metadata.offset, MAX_OFFSET); } } +#endif } void Dqn_Test_Array() @@ -325,43 +322,34 @@ void Dqn_Test_Array() DQN_TEST_EXPECT_MSG(testing_state, array.size == 1, "array.size: %d", array.size); DQN_TEST_EXPECT_MSG(testing_state, array.max == 4, "array.max: %d", array.max); } - - { - DQN_TEST_START_SCOPE(testing_state, "Fixed Memory: Test free on fixed memory array does nothing"); - int memory[4] = {}; - Dqn_Array array = Dqn_Array_InitWithMemory(memory, Dqn_ArrayCount(memory), 0 /*size*/); - DQN_DEFER { Dqn_Array_Free(&array); }; - } } // NOTE: Dynamic Memory: Dqn_Array { - { - DQN_TEST_START_SCOPE(testing_state, "Dynamic Memory: Reserve and check over commit reallocates"); - Dqn_Array array = {}; - if (array.allocator.type == Dqn_AllocatorType::Null) - array.allocator = Dqn_Allocator_InitWithHeap(); + DQN_TEST_START_SCOPE(testing_state, "Dynamic Memory: Reserve and check over commit reallocates"); + Dqn_ArenaAllocator arena = {}; + Dqn_Array array = {}; + array.arena = &arena; - DQN_DEFER { Dqn_Array_Free(&array); }; + Dqn_Array_Reserve(&array, 4); + DQN_TEST_EXPECT_MSG(testing_state, array.size == 0, "array.size: %d", array.size); + DQN_TEST_EXPECT_MSG(testing_state, array.max == 4, "array.max: %d", array.max); - Dqn_Array_Reserve(&array, 4); - DQN_TEST_EXPECT_MSG(testing_state, array.size == 0, "array.size: %d", array.size); - DQN_TEST_EXPECT_MSG(testing_state, array.max == 4, "array.max: %d", array.max); + int DATA[] = {1, 2, 3, 4}; + Dqn_Array_AddArray(&array, DATA, Dqn_ArrayCount(DATA)); + DQN_TEST_EXPECT_MSG(testing_state, array.data[0] == 1, "array.data: %d", array.data[0]); + DQN_TEST_EXPECT_MSG(testing_state, array.data[1] == 2, "array.data: %d", array.data[1]); + DQN_TEST_EXPECT_MSG(testing_state, array.data[2] == 3, "array.data: %d", array.data[2]); + DQN_TEST_EXPECT_MSG(testing_state, array.data[3] == 4, "array.data: %d", array.data[3]); + DQN_TEST_EXPECT_MSG(testing_state, array.size == 4, "array.size: %d", array.size); - int DATA[] = {1, 2, 3, 4}; - Dqn_Array_AddArray(&array, DATA, Dqn_ArrayCount(DATA)); - DQN_TEST_EXPECT_MSG(testing_state, array.data[0] == 1, "array.data: %d", array.data[0]); - DQN_TEST_EXPECT_MSG(testing_state, array.data[1] == 2, "array.data: %d", array.data[1]); - DQN_TEST_EXPECT_MSG(testing_state, array.data[2] == 3, "array.data: %d", array.data[2]); - DQN_TEST_EXPECT_MSG(testing_state, array.data[3] == 4, "array.data: %d", array.data[3]); - DQN_TEST_EXPECT_MSG(testing_state, array.size == 4, "array.size: %d", array.size); + int *added_item = Dqn_Array_Add(&array, 5); + DQN_TEST_EXPECT_MSG(testing_state, *added_item == 5, "added_item: %d", *added_item); + DQN_TEST_EXPECT_MSG(testing_state, array.data[4] == 5, "array.data: %d", array.data[4]); + DQN_TEST_EXPECT_MSG(testing_state, array.size == 5, "array.size: %d", array.size); + DQN_TEST_EXPECT_MSG(testing_state, array.max >= 5, "array.max: %d", array.max); - int *added_item = Dqn_Array_Add(&array, 5); - DQN_TEST_EXPECT_MSG(testing_state, *added_item == 5, "added_item: %d", *added_item); - DQN_TEST_EXPECT_MSG(testing_state, array.data[4] == 5, "array.data: %d", array.data[4]); - DQN_TEST_EXPECT_MSG(testing_state, array.size == 5, "array.size: %d", array.size); - DQN_TEST_EXPECT_MSG(testing_state, array.max >= 5, "array.max: %d", array.max); - } + Dqn_ArenaAllocator_Free(&arena); } } @@ -468,9 +456,160 @@ void Dqn_Test_M4() } } +void Dqn_Test_Map() +{ + Dqn_TestingState testing_state = {}; + DQN_TEST_DECLARE_GROUP_SCOPED(testing_state, "Dqn_Map"); + Dqn_ArenaAllocator arena = {}; + { + DQN_TEST_START_SCOPE(testing_state, "Add r-value item to map"); + Dqn_Map map = Dqn_Map_InitWithArena(&arena, 1); + Dqn_MapEntry *entry = Dqn_Map_AddCopy(&map, 3 /*hash*/, 5 /*value*/, Dqn_MapCollideRule::Overwrite); + DQN_TEST_EXPECT_MSG(testing_state, map.size == 1, "size: %I64d", map.size); + DQN_TEST_EXPECT_MSG(testing_state, map.count == 1, "count: %I64u", map.count); + DQN_TEST_EXPECT_MSG(testing_state, map.chain_count == 0, "chain_count: %I64u", map.chain_count); + DQN_TEST_EXPECT_MSG(testing_state, map.free_list == nullptr, "free_list: %p", map.free_list); + DQN_TEST_EXPECT_MSG(testing_state, entry->hash == 3, "hash: %I64u", entry->hash); + DQN_TEST_EXPECT_MSG(testing_state, entry->value == 5, "value: %d", entry->value); + DQN_TEST_EXPECT_MSG(testing_state, entry->next == nullptr, "next: %p", entry->next); + Dqn_ArenaAllocator_Free(&arena); + } + + { + DQN_TEST_START_SCOPE(testing_state, "Add l-value item to map"); + Dqn_Map map = Dqn_Map_InitWithArena(&arena, 1); + int value = 5; + Dqn_MapEntry *entry = Dqn_Map_Add(&map, 3 /*hash*/, value, Dqn_MapCollideRule::Overwrite); + DQN_TEST_EXPECT_MSG(testing_state, map.size == 1, "size: %I64d", map.size); + DQN_TEST_EXPECT_MSG(testing_state, map.count == 1, "count: %I64u", map.count); + DQN_TEST_EXPECT_MSG(testing_state, map.chain_count == 0, "chain_count: %I64u", map.chain_count); + DQN_TEST_EXPECT_MSG(testing_state, map.free_list == nullptr, "free_list: %p", map.free_list); + DQN_TEST_EXPECT_MSG(testing_state, entry->hash == 3, "hash: %I64u", entry->hash); + DQN_TEST_EXPECT_MSG(testing_state, entry->value == 5, "value: %d", entry->value); + DQN_TEST_EXPECT_MSG(testing_state, entry->next == nullptr, "next: %p", entry->next); + Dqn_ArenaAllocator_Free(&arena); + } + + { + DQN_TEST_START_SCOPE(testing_state, "Add r-value item and overwrite on collision"); + Dqn_Map map = Dqn_Map_InitWithArena(&arena, 1); + Dqn_MapEntry *entry_a = Dqn_Map_AddCopy(&map, 3 /*hash*/, 5, Dqn_MapCollideRule::Overwrite); + Dqn_MapEntry *entry_b = Dqn_Map_AddCopy(&map, 4 /*hash*/, 6, Dqn_MapCollideRule::Overwrite); + DQN_TEST_EXPECT_MSG(testing_state, map.size == 1, "size: %I64u", map.size); + DQN_TEST_EXPECT_MSG(testing_state, map.count == 1, "count: %I64u", map.count); + DQN_TEST_EXPECT_MSG(testing_state, map.chain_count == 0, "chain_count: %I64u", map.chain_count); + DQN_TEST_EXPECT_MSG(testing_state, map.free_list == nullptr, "free_list: %p", map.free_list); + DQN_TEST_EXPECT_MSG(testing_state, entry_a == entry_b, "Expected entry to be overwritten"); + DQN_TEST_EXPECT_MSG(testing_state, entry_b->hash == 4, "hash: %I64u", entry_b->hash); + DQN_TEST_EXPECT_MSG(testing_state, entry_b->value == 6, "value: %d", entry_b->value); + DQN_TEST_EXPECT_MSG(testing_state, entry_b->next == nullptr, "next: %p", entry_b->next); + Dqn_ArenaAllocator_Free(&arena); + } + + { + DQN_TEST_START_SCOPE(testing_state, "Add r-value item and fail on collision"); + Dqn_Map map = Dqn_Map_InitWithArena(&arena, 1); + Dqn_Map_AddCopy(&map, 3 /*hash*/, 5, Dqn_MapCollideRule::Overwrite); + Dqn_MapEntry *entry_b = Dqn_Map_AddCopy(&map, 4 /*hash*/, 6, Dqn_MapCollideRule::Fail); + DQN_TEST_EXPECT_MSG(testing_state, entry_b == nullptr, "Expected entry to be overwritten"); + DQN_TEST_EXPECT_MSG(testing_state, map.size == 1, "size: %I64u", map.size); + DQN_TEST_EXPECT_MSG(testing_state, map.count == 1, "count: %I64u", map.count); + DQN_TEST_EXPECT_MSG(testing_state, map.chain_count == 0, "chain_count: %I64u", map.chain_count); + DQN_TEST_EXPECT_MSG(testing_state, map.free_list == nullptr, "free_list: %p", map.free_list); + Dqn_ArenaAllocator_Free(&arena); + } + + { + DQN_TEST_START_SCOPE(testing_state, "Add r-value item and chain on collision"); + Dqn_Map map = Dqn_Map_InitWithArena(&arena, 1); + Dqn_MapEntry *entry_a = Dqn_Map_AddCopy(&map, 3 /*hash*/, 5, Dqn_MapCollideRule::Overwrite); + Dqn_MapEntry *entry_b = Dqn_Map_AddCopy(&map, 4 /*hash*/, 6, Dqn_MapCollideRule::Chain); + DQN_TEST_EXPECT_MSG(testing_state, map.size == 1, "size: %I64u", map.size); + DQN_TEST_EXPECT_MSG(testing_state, map.count == 1, "count: %I64u", map.count); + DQN_TEST_EXPECT_MSG(testing_state, map.chain_count == 1, "chain_count: %I64u", map.chain_count); + DQN_TEST_EXPECT_MSG(testing_state, map.free_list == nullptr, "free_list: %p", map.free_list); + DQN_TEST_EXPECT_MSG(testing_state, entry_a != entry_b, "Expected colliding entry to be chained"); + DQN_TEST_EXPECT_MSG(testing_state, entry_a->next == entry_b, "Expected chained entry to be next to our first map entry"); + DQN_TEST_EXPECT_MSG(testing_state, entry_b->hash == 4, "hash: %I64u", entry_b->hash); + DQN_TEST_EXPECT_MSG(testing_state, entry_b->value == 6, "value: %d", entry_b->value); + DQN_TEST_EXPECT_MSG(testing_state, entry_b->next == nullptr, "next: %p", entry_b->next); + Dqn_ArenaAllocator_Free(&arena); + } + + { + DQN_TEST_START_SCOPE(testing_state, "Add r-value item and get them back out again"); + Dqn_Map map = Dqn_Map_InitWithArena(&arena, 1); + Dqn_MapEntry *entry_a = Dqn_Map_AddCopy(&map, 3 /*hash*/, 5, Dqn_MapCollideRule::Overwrite); + Dqn_MapEntry *entry_b = Dqn_Map_AddCopy(&map, 4 /*hash*/, 6, Dqn_MapCollideRule::Chain); + + Dqn_MapEntry *entry_a_copy = Dqn_Map_Get(&map, 3 /*hash*/); + Dqn_MapEntry *entry_b_copy = Dqn_Map_Get(&map, 4 /*hash*/); + DQN_TEST_EXPECT_MSG(testing_state, map.size == 1, "size: %I64u", map.size); + DQN_TEST_EXPECT_MSG(testing_state, map.count == 1, "count: %I64u", map.count); + DQN_TEST_EXPECT_MSG(testing_state, map.chain_count == 1, "chain_count: %I64u", map.chain_count); + DQN_TEST_EXPECT_MSG(testing_state, map.free_list == nullptr, "free_list: %p", map.free_list); + DQN_TEST_EXPECT(testing_state, entry_a_copy == entry_a); + DQN_TEST_EXPECT(testing_state, entry_b_copy == entry_b); + Dqn_ArenaAllocator_Free(&arena); + } + + { + DQN_TEST_START_SCOPE(testing_state, "Add r-value item and erase it"); + Dqn_Map map = Dqn_Map_InitWithArena(&arena, 1); + Dqn_Map_AddCopy(&map, 3 /*hash*/, 5, Dqn_MapCollideRule::Overwrite); + Dqn_Map_AddCopy(&map, 4 /*hash*/, 6, Dqn_MapCollideRule::Chain); + Dqn_Map_Get(&map, 3 /*hash*/); + + Dqn_Map_Erase(&map, 3 /*hash*/, Dqn_ZeroMem::No); + DQN_TEST_EXPECT_MSG(testing_state, map.size == 1, "size: %I64u", map.size); + DQN_TEST_EXPECT_MSG(testing_state, map.count == 1, "count: %I64u", map.count); + DQN_TEST_EXPECT_MSG(testing_state, map.chain_count == 0, "chain_count: %I64u", map.chain_count); + DQN_TEST_EXPECT_MSG(testing_state, map.free_list != nullptr, "free_list: %p", map.free_list); + + DQN_TEST_EXPECT_MSG(testing_state, map.free_list->hash == 3, "Entry should not be zeroed out on erase"); + DQN_TEST_EXPECT_MSG(testing_state, map.free_list->value == 5, "Entry should not be zeroed out on erase"); + DQN_TEST_EXPECT_MSG(testing_state, map.free_list->next == nullptr, "This should be the first and only entry in the free list"); + + Dqn_MapEntry *entry = Dqn_Map_Get(&map, 4 /*hash*/); + DQN_TEST_EXPECT_MSG(testing_state, entry->hash == 4, "hash: %I64u", entry->hash); + DQN_TEST_EXPECT_MSG(testing_state, entry->value == 6, "value: %d", entry->value); + DQN_TEST_EXPECT_MSG(testing_state, entry->next == nullptr, "next: %p", entry->next); + + Dqn_ArenaAllocator_Free(&arena); + } + + { + DQN_TEST_START_SCOPE(testing_state, "Add r-value item and erase it, zeroing the memory out"); + Dqn_Map map = Dqn_Map_InitWithArena(&arena, 1); + Dqn_Map_AddCopy(&map, 3 /*hash*/, 5, Dqn_MapCollideRule::Overwrite); + Dqn_Map_AddCopy(&map, 4 /*hash*/, 6, Dqn_MapCollideRule::Chain); + Dqn_Map_Get(&map, 3 /*hash*/); + + Dqn_Map_Erase(&map, 3 /*hash*/, Dqn_ZeroMem::Yes); + DQN_TEST_EXPECT_MSG(testing_state, map.size == 1, "size: %I64u", map.size); + DQN_TEST_EXPECT_MSG(testing_state, map.count == 1, "count: %I64u", map.count); + DQN_TEST_EXPECT_MSG(testing_state, map.chain_count == 0, "chain_count: %I64u", map.chain_count); + DQN_TEST_EXPECT_MSG(testing_state, map.free_list != nullptr, "free_list: %p", map.free_list); + + DQN_TEST_EXPECT_MSG(testing_state, map.free_list->hash == 0, "Entry should be zeroed out on erase"); + DQN_TEST_EXPECT_MSG(testing_state, map.free_list->value == 0, "Entry should be zeroed out on erase"); + DQN_TEST_EXPECT_MSG(testing_state, map.free_list->next == nullptr, "This should be the first and only entry in the free list"); + + Dqn_MapEntry *entry = Dqn_Map_Get(&map, 4 /*hash*/); + DQN_TEST_EXPECT_MSG(testing_state, entry->hash == 4, "hash: %I64u", entry->hash); + DQN_TEST_EXPECT_MSG(testing_state, entry->value == 6, "value: %d", entry->value); + DQN_TEST_EXPECT_MSG(testing_state, entry->next == nullptr, "next: %p", entry->next); + + Dqn_ArenaAllocator_Free(&arena); + } + + // TODO(dqn): Test free list is chained correctly + // TODO(dqn): Test deleting 'b' from the list in the situation [map] - [a]->[b], we currently only test deleting a +} + void Dqn_Test_Intrinsics() { - // TODO(doyle): We don't have meaningful tests here, but since + // TODO(dqn): We don't have meaningful tests here, but since // atomics/intrinsics are implemented using macros we ensure the macro was // written properly with these tests. @@ -812,24 +951,92 @@ void Dqn_Test_Str() } } +void Dqn_Test_String() +{ + Dqn_TestingState testing_state = {}; + DQN_TEST_DECLARE_GROUP_SCOPED(testing_state, "Dqn_String"); + { + DQN_TEST_START_SCOPE(testing_state, "Initialise with string literal w/ macro"); + Dqn_String string = DQN_STRING("AB"); + DQN_TEST_EXPECT_MSG(testing_state, string.size == 2, "size: %I64d", string.size); + DQN_TEST_EXPECT_MSG(testing_state, string.cap == 2, "cap: %I64d", string.cap); + DQN_TEST_EXPECT_MSG(testing_state, string.str[0] == 'A', "string[0]: %c", string.str[0]); + DQN_TEST_EXPECT_MSG(testing_state, string.str[1] == 'B', "string[1]: %c", string.str[1]); + } + + { + DQN_TEST_START_SCOPE(testing_state, "Initialise with format string"); + Dqn_ArenaAllocator arena = {}; + Dqn_String string = Dqn_String_Fmt(&arena, "%s", "AB"); + DQN_TEST_EXPECT_MSG(testing_state, string.size == 2, "size: %I64d", string.size); + DQN_TEST_EXPECT_MSG(testing_state, string.cap == 2, "cap: %I64d", string.cap); + DQN_TEST_EXPECT_MSG(testing_state, string.str[0] == 'A', "string[0]: %c", string.str[0]); + DQN_TEST_EXPECT_MSG(testing_state, string.str[1] == 'B', "string[1]: %c", string.str[1]); + DQN_TEST_EXPECT_MSG(testing_state, string.str[2] == 0, "string[2]: %c", string.str[2]); + Dqn_ArenaAllocator_Free(&arena); + } + + { + DQN_TEST_START_SCOPE(testing_state, "Copy string"); + Dqn_ArenaAllocator arena = {}; + Dqn_String string = DQN_STRING("AB"); + Dqn_String copy = Dqn_String_Copy(string, &arena); + DQN_TEST_EXPECT_MSG(testing_state, copy.size == 2, "size: %I64d", copy.size); + DQN_TEST_EXPECT_MSG(testing_state, copy.cap == 2, "cap: %I64d", copy.cap); + DQN_TEST_EXPECT_MSG(testing_state, copy.str[0] == 'A', "copy[0]: %c", copy.str[0]); + DQN_TEST_EXPECT_MSG(testing_state, copy.str[1] == 'B', "copy[1]: %c", copy.str[1]); + DQN_TEST_EXPECT_MSG(testing_state, copy.str[2] == 0, "copy[2]: %c", copy.str[2]); + Dqn_ArenaAllocator_Free(&arena); + } + + { + DQN_TEST_START_SCOPE(testing_state, "Trim whitespace around string"); + Dqn_String string = Dqn_String_TrimWhitespaceAround(DQN_STRING(" AB ")); + DQN_TEST_EXPECT_MSG(testing_state, string.size == 2, "size: %I64d", string.size); + DQN_TEST_EXPECT_MSG(testing_state, string.cap == 2, "cap: %I64d", string.cap); + DQN_TEST_EXPECT_MSG(testing_state, string.str[0] == 'A', "string[0]: %c", string.str[0]); + DQN_TEST_EXPECT_MSG(testing_state, string.str[1] == 'B', "string[1]: %c", string.str[1]); + DQN_TEST_EXPECT_MSG(testing_state, string.str[2] == ' ', "string[1]: %c", string.str[1]); + } + + { + DQN_TEST_START_SCOPE(testing_state, "Allocate string from arena"); + Dqn_ArenaAllocator arena = {}; + Dqn_String string = Dqn_String_Allocate(&arena, 2, Dqn_ZeroMem::No); + DQN_TEST_EXPECT_MSG(testing_state, string.size == 0, "size: %I64d", string.size); + DQN_TEST_EXPECT_MSG(testing_state, string.cap == 2, "cap: %I64d", string.cap); + Dqn_ArenaAllocator_Free(&arena); + } + + { + DQN_TEST_START_SCOPE(testing_state, "Append to allocated string"); + Dqn_ArenaAllocator arena = {}; + Dqn_String string = Dqn_String_Allocate(&arena, 2, Dqn_ZeroMem::No); + Dqn_String_AppendFmt(&string, "%c", 'A'); + Dqn_String_AppendFmt(&string, "%c", 'B'); + DQN_TEST_EXPECT_MSG(testing_state, string.size == 2, "size: %I64d", string.size); + DQN_TEST_EXPECT_MSG(testing_state, string.cap == 2, "cap: %I64d", string.cap); + DQN_TEST_EXPECT_MSG(testing_state, string.str[0] == 'A', "string[0]: %c", string.str[0]); + DQN_TEST_EXPECT_MSG(testing_state, string.str[1] == 'B', "string[1]: %c", string.str[1]); + DQN_TEST_EXPECT_MSG(testing_state, string.str[2] == 0, "string[2]: %c", string.str[2]); + } +} + void Dqn_Test_StringBuilder() { Dqn_TestingState testing_state = {}; DQN_TEST_DECLARE_GROUP_SCOPED(testing_state, "Dqn_StringBuilder"); - Dqn_Allocator allocator = Dqn_Allocator_InitWithHeap(); + Dqn_ArenaAllocator arena = {}; // NOTE: Dqn_StringBuilder_Append { { - DQN_TEST_START_SCOPE(testing_state, "Append variable size strings and build using heap allocator"); + DQN_TEST_START_SCOPE(testing_state, "Append variable size strings and build using heap arena"); Dqn_StringBuilder<> builder = {}; - if (builder.backup_allocator.type == Dqn_AllocatorType::Null) - builder.backup_allocator = Dqn_Allocator_InitWithHeap(); - Dqn_StringBuilder_Append(&builder, "Abc", 1); Dqn_StringBuilder_Append(&builder, "cd"); Dqn_isize size = 0; - char *result = Dqn_StringBuilder_Build(&builder, &allocator, &size); - DQN_DEFER { Dqn_Allocator_Free(&allocator, result); }; + char *result = Dqn_StringBuilder_Build(&builder, &arena, &size); + DQN_DEFER { Dqn_ArenaAllocator_Free(&arena); }; char const EXPECT_STR[] = "Acd"; DQN_TEST_EXPECT_MSG(testing_state, size == Dqn_CharCountI(EXPECT_STR), "size: %zd", size); @@ -837,16 +1044,13 @@ void Dqn_Test_StringBuilder() } { - DQN_TEST_START_SCOPE(testing_state, "Append empty string and build using heap allocator"); + DQN_TEST_START_SCOPE(testing_state, "Append empty string and build using heap arena"); Dqn_StringBuilder<> builder = {}; - if (builder.backup_allocator.type == Dqn_AllocatorType::Null) - builder.backup_allocator = Dqn_Allocator_InitWithHeap(); - Dqn_StringBuilder_Append(&builder, ""); Dqn_StringBuilder_Append(&builder, ""); Dqn_isize size = 0; - char *result = Dqn_StringBuilder_Build(&builder, &allocator, &size); - DQN_DEFER { Dqn_Allocator_Free(&allocator, result); }; + char *result = Dqn_StringBuilder_Build(&builder, &arena, &size); + DQN_DEFER { Dqn_ArenaAllocator_Free(&arena); }; char const EXPECT_STR[] = ""; DQN_TEST_EXPECT_MSG(testing_state, size == Dqn_CharCountI(EXPECT_STR), "size: %zd", size); @@ -854,16 +1058,13 @@ void Dqn_Test_StringBuilder() } { - DQN_TEST_START_SCOPE(testing_state, "Append empty string onto string and build using heap allocator"); + DQN_TEST_START_SCOPE(testing_state, "Append empty string onto string and build using heap arena"); Dqn_StringBuilder<> builder = {}; - if (builder.backup_allocator.type == Dqn_AllocatorType::Null) - builder.backup_allocator = Dqn_Allocator_InitWithHeap(); - Dqn_StringBuilder_Append(&builder, "Acd"); Dqn_StringBuilder_Append(&builder, ""); Dqn_isize size = 0; - char *result = Dqn_StringBuilder_Build(&builder, &allocator, &size); - DQN_DEFER { Dqn_Allocator_Free(&allocator, result); }; + char *result = Dqn_StringBuilder_Build(&builder, &arena, &size); + DQN_DEFER { Dqn_ArenaAllocator_Free(&arena); }; char const EXPECT_STR[] = "Acd"; DQN_TEST_EXPECT_MSG(testing_state, size == Dqn_CharCountI(EXPECT_STR), "size: %zd", size); @@ -871,15 +1072,12 @@ void Dqn_Test_StringBuilder() } { - DQN_TEST_START_SCOPE(testing_state, "Append nullptr and build using heap allocator"); + DQN_TEST_START_SCOPE(testing_state, "Append nullptr and build using heap arena"); Dqn_StringBuilder<> builder = {}; - if (builder.backup_allocator.type == Dqn_AllocatorType::Null) - builder.backup_allocator = Dqn_Allocator_InitWithHeap(); - Dqn_StringBuilder_Append(&builder, nullptr, 5); Dqn_isize size = 0; - char *result = Dqn_StringBuilder_Build(&builder, &allocator, &size); - DQN_DEFER { Dqn_Allocator_Free(&allocator, result); }; + char *result = Dqn_StringBuilder_Build(&builder, &arena, &size); + DQN_DEFER { Dqn_ArenaAllocator_Free(&arena); }; char const EXPECT_STR[] = ""; DQN_TEST_EXPECT_MSG(testing_state, size == Dqn_CharCountI(EXPECT_STR), "size: %zd", size); @@ -887,17 +1085,15 @@ void Dqn_Test_StringBuilder() } { - DQN_TEST_START_SCOPE(testing_state, "Append and require new linked buffer and build using heap allocator"); + DQN_TEST_START_SCOPE(testing_state, "Append and require new linked buffer and build using heap arena"); Dqn_StringBuilder<2> builder = {}; - if (builder.backup_allocator.type == Dqn_AllocatorType::Null) - builder.backup_allocator = Dqn_Allocator_InitWithHeap(); - + Dqn_StringBuilder_InitWithArena(&builder, &arena); Dqn_StringBuilder_Append(&builder, "A"); Dqn_StringBuilder_Append(&builder, "z"); // Should force a new memory block Dqn_StringBuilder_Append(&builder, "tec"); Dqn_isize size = 0; - char *result = Dqn_StringBuilder_Build(&builder, &allocator, &size); - DQN_DEFER { Dqn_Allocator_Free(&allocator, result); }; + char *result = Dqn_StringBuilder_Build(&builder, &arena, &size); + DQN_DEFER { Dqn_ArenaAllocator_Free(&arena); }; char const EXPECT_STR[] = "Aztec"; DQN_TEST_EXPECT_MSG(testing_state, size == Dqn_CharCountI(EXPECT_STR), "size: %zd", size); @@ -907,16 +1103,13 @@ void Dqn_Test_StringBuilder() // NOTE: Dqn_StringBuilder_AppendChar { - DQN_TEST_START_SCOPE(testing_state, "Append char and build using heap allocator"); + DQN_TEST_START_SCOPE(testing_state, "Append char and build using heap arena"); Dqn_StringBuilder<> builder = {}; - if (builder.backup_allocator.type == Dqn_AllocatorType::Null) - builder.backup_allocator = Dqn_Allocator_InitWithHeap(); - Dqn_StringBuilder_AppendChar(&builder, 'a'); Dqn_StringBuilder_AppendChar(&builder, 'b'); Dqn_isize size = 0; - char *result = Dqn_StringBuilder_Build(&builder, &allocator, &size); - DQN_DEFER { Dqn_Allocator_Free(&allocator, result); }; + char *result = Dqn_StringBuilder_Build(&builder, &arena, &size); + DQN_DEFER { Dqn_ArenaAllocator_Free(&arena); }; char const EXPECT_STR[] = "ab"; DQN_TEST_EXPECT_MSG(testing_state, size == Dqn_CharCountI(EXPECT_STR), "size: %zd", size); @@ -926,16 +1119,13 @@ void Dqn_Test_StringBuilder() // NOTE: Dqn_StringBuilder_AppendFmt { { - DQN_TEST_START_SCOPE(testing_state, "Append format string and build using heap allocator"); + DQN_TEST_START_SCOPE(testing_state, "Append format string and build using heap arena"); Dqn_StringBuilder<> builder = {}; - if (builder.backup_allocator.type == Dqn_AllocatorType::Null) - builder.backup_allocator = Dqn_Allocator_InitWithHeap(); - Dqn_StringBuilder_AppendFmt(&builder, "Number: %d, String: %s, ", 4, "Hello Sailor"); Dqn_StringBuilder_AppendFmt(&builder, "Extra Stuff"); Dqn_isize size = 0; - char *result = Dqn_StringBuilder_Build(&builder, &allocator, &size); - DQN_DEFER { Dqn_Allocator_Free(&allocator, result); }; + char *result = Dqn_StringBuilder_Build(&builder, &arena, &size); + DQN_DEFER { Dqn_ArenaAllocator_Free(&arena); }; char const EXPECT_STR[] = "Number: 4, String: Hello Sailor, Extra Stuff"; DQN_TEST_EXPECT_MSG(testing_state, size == Dqn_CharCountI(EXPECT_STR), "size: %zd", size); @@ -943,15 +1133,12 @@ void Dqn_Test_StringBuilder() } { - DQN_TEST_START_SCOPE(testing_state, "Append nullptr format string and build using heap allocator"); + DQN_TEST_START_SCOPE(testing_state, "Append nullptr format string and build using heap arena"); Dqn_StringBuilder<> builder = {}; - if (builder.backup_allocator.type == Dqn_AllocatorType::Null) - builder.backup_allocator = Dqn_Allocator_InitWithHeap(); - Dqn_StringBuilder_AppendFmt(&builder, nullptr); Dqn_isize size = 0; - char *result = Dqn_StringBuilder_Build(&builder, &allocator, &size); - DQN_DEFER { Dqn_Allocator_Free(&allocator, result); }; + char *result = Dqn_StringBuilder_Build(&builder, &arena, &size); + DQN_DEFER { Dqn_ArenaAllocator_Free(&arena); }; char const EXPECT_STR[] = ""; DQN_TEST_EXPECT_MSG(testing_state, size == Dqn_CharCountI(EXPECT_STR), "size: %zd", size); @@ -1001,10 +1188,13 @@ void Dqn_Test_RunSuite() Dqn_Test_FixedString(); Dqn_Test_Intrinsics(); Dqn_Test_M4(); + Dqn_Test_Map(); Dqn_Test_Rect(); Dqn_Test_Str(); + Dqn_Test_String(); Dqn_Test_StringBuilder(); Dqn_Test_TicketMutex(); + fprintf(stdout, "Summary: %d/%d tests succeeded\n", g_dqn_test_total_good_tests, g_dqn_test_total_tests); } #if defined(DQN_TEST_WITH_MAIN) diff --git a/Code/Dqn_U128.h b/Code/Dqn_U128.h new file mode 100644 index 0000000..4f458a5 --- /dev/null +++ b/Code/Dqn_U128.h @@ -0,0 +1,1039 @@ +#ifndef DQN_U128_H +#define DQN_U128_H +// ----------------------------------------------------------------------------- +// NOTE: Dqn_U128 +// ----------------------------------------------------------------------------- +// Provides some helper funtions ontop of intx that currently use heap +// allocation but don't actually need heap allocation. +// +// ----------------------------------------------------------------------------- +// NOTE: Configuration +// ----------------------------------------------------------------------------- +// #define DQN_U128_IMPLEMENTATION +// Define this in one and only one C++ file to enable the implementation +// code of the header file. + +// intx: extended precision integer library. +// Copyright 2019-2020 Pawel Bylica. +// Licensed under the Apache License, Version 2.0. + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef __has_builtin + #define __has_builtin(NAME) 0 +#endif + +#ifdef _MSC_VER + #include +#endif + +#if !defined(__has_builtin) + #define __has_builtin(NAME) 0 +#endif + +#if !defined(__has_feature) + #define __has_feature(NAME) 0 +#endif + +#if !defined(NDEBUG) + #define INTX_UNREACHABLE() assert(false) +#elif __has_builtin(__builtin_unreachable) + #define INTX_UNREACHABLE() __builtin_unreachable() +#elif defined(_MSC_VER) + #define INTX_UNREACHABLE() __assume(0) +#else + #define INTX_UNREACHABLE() (void)0 +#endif + + +#if __has_builtin(__builtin_expect) + #define INTX_UNLIKELY(EXPR) __builtin_expect(bool{EXPR}, false) +#else + #define INTX_UNLIKELY(EXPR) (bool{EXPR}) +#endif + +#if !defined(NDEBUG) + #define INTX_REQUIRE assert +#else + #define INTX_REQUIRE(X) (X) ? (void)0 : INTX_UNREACHABLE() +#endif + + +// Detect compiler support for 128-bit integer __int128 +#if defined(__SIZEOF_INT128__) + #define INTX_HAS_BUILTIN_INT128 1 +#else + #define INTX_HAS_BUILTIN_INT128 0 +#endif + + +namespace intx +{ +#if INTX_HAS_BUILTIN_INT128 + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wpedantic" // Usage of __int128 triggers a pedantic warning. + +/// Alias for the compiler supported unsigned __int128 type. +using builtin_uint128 = unsigned __int128; + + #pragma GCC diagnostic pop +#endif + +template +struct uint; + +/// The 128-bit unsigned integer. +/// +/// This type is defined as a specialization of uint<> to easier integration with full intx package, +/// however, uint128 may be used independently. +template <> +struct uint<128> +{ + using word_type = uint64_t; + static constexpr auto word_num_bits = sizeof(word_type) * 8; + static constexpr unsigned num_bits = 128; + static constexpr auto num_words = num_bits / word_num_bits; + +private: + uint64_t words_[2]{}; + +public: + constexpr uint() noexcept = default; + + constexpr uint(uint64_t low, uint64_t high) noexcept : words_{low, high} {} + + template ::value>> + constexpr uint(T x) noexcept : words_{static_cast(x), 0} // NOLINT + {} + +#if INTX_HAS_BUILTIN_INT128 + constexpr uint(builtin_uint128 x) noexcept // NOLINT + : words_{uint64_t(x), uint64_t(x >> 64)} + {} + + constexpr explicit operator builtin_uint128() const noexcept + { + return (builtin_uint128{words_[1]} << 64) | words_[0]; + } +#endif + + constexpr uint64_t& operator[](size_t i) noexcept { return words_[i]; } + constexpr const uint64_t& operator[](size_t i) const noexcept { return words_[i]; } + + constexpr explicit operator bool() const noexcept { return (words_[0] | words_[1]) != 0; } + + /// Explicit converting operator for all builtin integral types. + template ::value>::type> + constexpr explicit operator Int() const noexcept + { + return static_cast(words_[0]); + } +}; + +using uint128 = uint<128>; + + +inline constexpr bool is_constant_evaluated() noexcept +{ +#if __has_builtin(__builtin_is_constant_evaluated) || (defined(_MSC_VER) && _MSC_VER >= 1925) + return __builtin_is_constant_evaluated(); +#else + return true; +#endif +} + + +/// Contains result of add/sub/etc with a carry flag. +template +struct result_with_carry +{ + T value; + bool carry; + + /// Conversion to tuple of references, to allow usage with std::tie(). + constexpr operator std::tuple() noexcept { return {value, carry}; } +}; + + +/// Linear arithmetic operators. +/// @{ + +inline constexpr result_with_carry add_with_carry( + uint64_t x, uint64_t y, bool carry = false) noexcept +{ + const auto s = x + y; + const auto carry1 = s < x; + const auto t = s + carry; + const auto carry2 = t < s; + return {t, carry1 || carry2}; +} + +template +inline constexpr result_with_carry> add_with_carry( + const uint& x, const uint& y, bool carry = false) noexcept +{ + uint s; + bool k = carry; + for (size_t i = 0; i < uint::num_words; ++i) + { + s[i] = x[i] + y[i]; + const auto k1 = s[i] < x[i]; + s[i] += k; + k = (s[i] < uint64_t{k}) || k1; + } + return {s, k}; +} + +inline constexpr uint128 operator+(uint128 x, uint128 y) noexcept +{ + return add_with_carry(x, y).value; +} + +inline constexpr uint128 operator+(uint128 x) noexcept +{ + return x; +} + +inline constexpr result_with_carry sub_with_carry( + uint64_t x, uint64_t y, bool carry = false) noexcept +{ + const auto d = x - y; + const auto carry1 = x < y; + const auto e = d - carry; + const auto carry2 = d < uint64_t{carry}; + return {e, carry1 || carry2}; +} + +/// Performs subtraction of two unsigned numbers and returns the difference +/// and the carry bit (aka borrow, overflow). +template +inline constexpr result_with_carry> sub_with_carry( + const uint& x, const uint& y, bool carry = false) noexcept +{ + uint z; + bool k = carry; + for (size_t i = 0; i < uint::num_words; ++i) + { + z[i] = x[i] - y[i]; + const auto k1 = x[i] < y[i]; + const auto k2 = z[i] < uint64_t{k}; + z[i] -= k; + k = k1 || k2; + } + return {z, k}; +} + +inline constexpr uint128 operator-(uint128 x, uint128 y) noexcept +{ + return sub_with_carry(x, y).value; +} + +inline constexpr uint128 operator-(uint128 x) noexcept +{ + // Implementing as subtraction is better than ~x + 1. + // Clang9: Perfect. + // GCC8: Does something weird. + return 0 - x; +} + +inline uint128& operator++(uint128& x) noexcept +{ + return x = x + 1; +} + +inline uint128& operator--(uint128& x) noexcept +{ + return x = x - 1; +} + +inline uint128 operator++(uint128& x, int) noexcept +{ + auto ret = x; + ++x; + return ret; +} + +inline uint128 operator--(uint128& x, int) noexcept +{ + auto ret = x; + --x; + return ret; +} + +/// Optimized addition. +/// +/// This keeps the multiprecision addition until CodeGen so the pattern is not +/// broken during other optimizations. +inline constexpr uint128 fast_add(uint128 x, uint128 y) noexcept +{ +#if INTX_HAS_BUILTIN_INT128 + return builtin_uint128{x} + builtin_uint128{y}; +#else + return x + y; // Fallback to generic addition. +#endif +} + +/// @} + + +/// Comparison operators. +/// +/// In all implementations bitwise operators are used instead of logical ones +/// to avoid branching. +/// +/// @{ + +inline constexpr bool operator==(uint128 x, uint128 y) noexcept +{ + // Clang7: generates perfect xor based code, + // much better than __int128 where it uses vector instructions. + // GCC8: generates a bit worse cmp based code + // although it generates the xor based one for __int128. + return (x[0] == y[0]) & (x[1] == y[1]); +} + +inline constexpr bool operator!=(uint128 x, uint128 y) noexcept +{ + // Analogous to ==, but == not used directly, because that confuses GCC 8-9. + return (x[0] != y[0]) | (x[1] != y[1]); +} + +inline constexpr bool operator<(uint128 x, uint128 y) noexcept +{ + // OPT: This should be implemented by checking the borrow of x - y, + // but compilers (GCC8, Clang7) + // have problem with properly optimizing subtraction. +#if INTX_HAS_BUILTIN_INT128 + return builtin_uint128{x} < builtin_uint128{y}; +#else + return (x[1] < y[1]) | ((x[1] == y[1]) & (x[0] < y[0])); +#endif +} + +inline constexpr bool operator<=(uint128 x, uint128 y) noexcept +{ + return !(y < x); +} + +inline constexpr bool operator>(uint128 x, uint128 y) noexcept +{ + return y < x; +} + +inline constexpr bool operator>=(uint128 x, uint128 y) noexcept +{ + return !(x < y); +} + +/// @} + + +/// Bitwise operators. +/// @{ + +inline constexpr uint128 operator~(uint128 x) noexcept +{ + return {~x[0], ~x[1]}; +} + +inline constexpr uint128 operator|(uint128 x, uint128 y) noexcept +{ + // Clang7: perfect. + // GCC8: stupidly uses a vector instruction in all bitwise operators. + return {x[0] | y[0], x[1] | y[1]}; +} + +inline constexpr uint128 operator&(uint128 x, uint128 y) noexcept +{ + return {x[0] & y[0], x[1] & y[1]}; +} + +inline constexpr uint128 operator^(uint128 x, uint128 y) noexcept +{ + return {x[0] ^ y[0], x[1] ^ y[1]}; +} + +inline constexpr uint128 operator<<(uint128 x, uint64_t shift) noexcept +{ + return (shift < 64) ? + // Find the part moved from lo to hi. + // For shift == 0 right shift by (64 - shift) is invalid so + // split it into 2 shifts by 1 and (63 - shift). + uint128{x[0] << shift, (x[1] << shift) | ((x[0] >> 1) >> (63 - shift))} : + + // Guarantee "defined" behavior for shifts larger than 128. + (shift < 128) ? uint128{0, x[0] << (shift - 64)} : 0; +} + +inline constexpr uint128 operator<<(uint128 x, uint128 shift) noexcept +{ + if (INTX_UNLIKELY(shift[1] != 0)) + return 0; + + return x << shift[0]; +} + +inline constexpr uint128 operator>>(uint128 x, uint64_t shift) noexcept +{ + return (shift < 64) ? + // Find the part moved from lo to hi. + // For shift == 0 left shift by (64 - shift) is invalid so + // split it into 2 shifts by 1 and (63 - shift). + uint128{(x[0] >> shift) | ((x[1] << 1) << (63 - shift)), x[1] >> shift} : + + // Guarantee "defined" behavior for shifts larger than 128. + (shift < 128) ? uint128{x[1] >> (shift - 64)} : 0; +} + +inline constexpr uint128 operator>>(uint128 x, uint128 shift) noexcept +{ + if (INTX_UNLIKELY(shift[1] != 0)) + return 0; + + return x >> static_cast(shift); +} + +/// @} + + +/// Multiplication +/// @{ + +/// Full unsigned multiplication 64 x 64 -> 128. +inline constexpr uint128 umul(uint64_t x, uint64_t y) noexcept +{ +#if INTX_HAS_BUILTIN_INT128 + return builtin_uint128{x} * builtin_uint128{y}; +#elif defined(_MSC_VER) && _MSC_VER >= 1925 + if (!is_constant_evaluated()) + { + unsigned __int64 hi = 0; + const auto lo = _umul128(x, y, &hi); + return {lo, hi}; + } + // For constexpr fallback to portable variant. +#endif + + // Portable full unsigned multiplication 64 x 64 -> 128. + uint64_t xl = x & 0xffffffff; + uint64_t xh = x >> 32; + uint64_t yl = y & 0xffffffff; + uint64_t yh = y >> 32; + + uint64_t t0 = xl * yl; + uint64_t t1 = xh * yl; + uint64_t t2 = xl * yh; + uint64_t t3 = xh * yh; + + uint64_t u1 = t1 + (t0 >> 32); + uint64_t u2 = t2 + (u1 & 0xffffffff); + + uint64_t lo = (u2 << 32) | (t0 & 0xffffffff); + uint64_t hi = t3 + (u2 >> 32) + (u1 >> 32); + return {lo, hi}; +} + +inline constexpr uint128 operator*(uint128 x, uint128 y) noexcept +{ + auto p = umul(x[0], y[0]); + p[1] += (x[0] * y[1]) + (x[1] * y[0]); + return {p[0], p[1]}; +} + +/// @} + + +/// Assignment operators. +/// @{ + +inline constexpr uint128& operator+=(uint128& x, uint128 y) noexcept +{ + return x = x + y; +} + +inline constexpr uint128& operator-=(uint128& x, uint128 y) noexcept +{ + return x = x - y; +} + +inline uint128& operator*=(uint128& x, uint128 y) noexcept +{ + return x = x * y; +} + +inline constexpr uint128& operator|=(uint128& x, uint128 y) noexcept +{ + return x = x | y; +} + +inline constexpr uint128& operator&=(uint128& x, uint128 y) noexcept +{ + return x = x & y; +} + +inline constexpr uint128& operator^=(uint128& x, uint128 y) noexcept +{ + return x = x ^ y; +} + +inline constexpr uint128& operator<<=(uint128& x, uint64_t shift) noexcept +{ + return x = x << shift; +} + +inline constexpr uint128& operator>>=(uint128& x, uint64_t shift) noexcept +{ + return x = x >> shift; +} + +/// @} + + +inline constexpr unsigned clz_generic(uint32_t x) noexcept +{ + unsigned n = 32; + for (int i = 4; i >= 0; --i) + { + const auto s = unsigned{1} << i; + const auto hi = x >> s; + if (hi != 0) + { + n -= s; + x = hi; + } + } + return n - x; +} + +inline constexpr unsigned clz_generic(uint64_t x) noexcept +{ + unsigned n = 64; + for (int i = 5; i >= 0; --i) + { + const auto s = unsigned{1} << i; + const auto hi = x >> s; + if (hi != 0) + { + n -= s; + x = hi; + } + } + return n - static_cast(x); +} + +inline constexpr unsigned clz(uint32_t x) noexcept +{ +#ifdef _MSC_VER + return clz_generic(x); +#else + return x != 0 ? unsigned(__builtin_clz(x)) : 32; +#endif +} + +inline constexpr unsigned clz(uint64_t x) noexcept +{ +#ifdef _MSC_VER + return clz_generic(x); +#else + return x != 0 ? unsigned(__builtin_clzll(x)) : 64; +#endif +} + +inline constexpr unsigned clz(uint128 x) noexcept +{ + // In this order `h == 0` we get less instructions than in case of `h != 0`. + return x[1] == 0 ? clz(x[0]) + 64 : clz(x[1]); +} + + +inline constexpr uint64_t bswap(uint64_t x) noexcept +{ +#if __has_builtin(__builtin_bswap64) + return __builtin_bswap64(x); +#else + #ifdef _MSC_VER + if (!is_constant_evaluated()) + return _byteswap_uint64(x); + #endif + const auto a = ((x << 8) & 0xFF00FF00FF00FF00) | ((x >> 8) & 0x00FF00FF00FF00FF); + const auto b = ((a << 16) & 0xFFFF0000FFFF0000) | ((a >> 16) & 0x0000FFFF0000FFFF); + return (b << 32) | (b >> 32); +#endif +} + +inline constexpr uint128 bswap(uint128 x) noexcept +{ + return {bswap(x[1]), bswap(x[0])}; +} + + +/// Division. +/// @{ + +template +struct div_result +{ + QuotT quot; + RemT rem; + + /// Conversion to tuple of references, to allow usage with std::tie(). + constexpr operator std::tuple() noexcept { return {quot, rem}; } +}; + +namespace internal +{ +inline constexpr uint16_t reciprocal_table_item(uint8_t d9) noexcept +{ + return uint16_t(0x7fd00 / (0x100 | d9)); +} + +#define REPEAT4(x) \ + reciprocal_table_item((x) + 0), reciprocal_table_item((x) + 1), \ + reciprocal_table_item((x) + 2), reciprocal_table_item((x) + 3) + +#define REPEAT32(x) \ + REPEAT4((x) + 4 * 0), REPEAT4((x) + 4 * 1), REPEAT4((x) + 4 * 2), REPEAT4((x) + 4 * 3), \ + REPEAT4((x) + 4 * 4), REPEAT4((x) + 4 * 5), REPEAT4((x) + 4 * 6), REPEAT4((x) + 4 * 7) + +#define REPEAT256() \ + REPEAT32(32 * 0), REPEAT32(32 * 1), REPEAT32(32 * 2), REPEAT32(32 * 3), REPEAT32(32 * 4), \ + REPEAT32(32 * 5), REPEAT32(32 * 6), REPEAT32(32 * 7) + +/// Reciprocal lookup table. +constexpr uint16_t reciprocal_table[] = {REPEAT256()}; + +#undef REPEAT4 +#undef REPEAT32 +#undef REPEAT256 +} // namespace internal + +/// Computes the reciprocal (2^128 - 1) / d - 2^64 for normalized d. +/// +/// Based on Algorithm 2 from "Improved division by invariant integers". +inline uint64_t reciprocal_2by1(uint64_t d) noexcept +{ + INTX_REQUIRE(d & 0x8000000000000000); // Must be normalized. + + const uint64_t d9 = d >> 55; + const uint32_t v0 = internal::reciprocal_table[d9 - 256]; + + const uint64_t d40 = (d >> 24) + 1; + const uint64_t v1 = (v0 << 11) - uint32_t(v0 * v0 * d40 >> 40) - 1; + + const uint64_t v2 = (v1 << 13) + (v1 * (0x1000000000000000 - v1 * d40) >> 47); + + const uint64_t d0 = d & 1; + const uint64_t d63 = (d >> 1) + d0; // ceil(d/2) + const uint64_t e = ((v2 >> 1) & (0 - d0)) - v2 * d63; + const uint64_t v3 = (umul(v2, e)[1] >> 1) + (v2 << 31); + + const uint64_t v4 = v3 - (umul(v3, d) + d)[1] - d; + return v4; +} + +inline uint64_t reciprocal_3by2(uint128 d) noexcept +{ + auto v = reciprocal_2by1(d[1]); + auto p = d[1] * v; + p += d[0]; + if (p < d[0]) + { + --v; + if (p >= d[1]) + { + --v; + p -= d[1]; + } + p -= d[1]; + } + + const auto t = umul(v, d[0]); + + p += t[1]; + if (p < t[1]) + { + --v; + if (p >= d[1]) + { + if (p > d[1] || t[0] >= d[0]) + --v; + } + } + return v; +} + +inline div_result udivrem_2by1(uint128 u, uint64_t d, uint64_t v) noexcept +{ + auto q = umul(v, u[1]); + q = fast_add(q, u); + + ++q[1]; + + auto r = u[0] - q[1] * d; + + if (r > q[0]) + { + --q[1]; + r += d; + } + + if (r >= d) + { + ++q[1]; + r -= d; + } + + return {q[1], r}; +} + +inline div_result udivrem_3by2( + uint64_t u2, uint64_t u1, uint64_t u0, uint128 d, uint64_t v) noexcept +{ + auto q = umul(v, u2); + q = fast_add(q, {u1, u2}); + + auto r1 = u1 - q[1] * d[1]; + + auto t = umul(d[0], q[1]); + + auto r = uint128{u0, r1} - t - d; + r1 = r[1]; + + ++q[1]; + + if (r1 >= q[0]) + { + --q[1]; + r += d; + } + + if (r >= d) + { + ++q[1]; + r -= d; + } + + return {q[1], r}; +} + +inline div_result udivrem(uint128 x, uint128 y) noexcept +{ + if (y[1] == 0) + { + INTX_REQUIRE(y[0] != 0); // Division by 0. + + const auto lsh = clz(y[0]); + const auto rsh = (64 - lsh) % 64; + const auto rsh_mask = uint64_t{lsh == 0} - 1; + + const auto yn = y[0] << lsh; + const auto xn_lo = x[0] << lsh; + const auto xn_hi = (x[1] << lsh) | ((x[0] >> rsh) & rsh_mask); + const auto xn_ex = (x[1] >> rsh) & rsh_mask; + + const auto v = reciprocal_2by1(yn); + const auto res1 = udivrem_2by1({xn_hi, xn_ex}, yn, v); + const auto res2 = udivrem_2by1({xn_lo, res1.rem}, yn, v); + return {{res2.quot, res1.quot}, res2.rem >> lsh}; + } + + if (y[1] > x[1]) + return {0, x}; + + const auto lsh = clz(y[1]); + if (lsh == 0) + { + const auto q = unsigned{y[1] < x[1]} | unsigned{y[0] <= x[0]}; + return {q, x - (q ? y : 0)}; + } + + const auto rsh = 64 - lsh; + + const auto yn_lo = y[0] << lsh; + const auto yn_hi = (y[1] << lsh) | (y[0] >> rsh); + const auto xn_lo = x[0] << lsh; + const auto xn_hi = (x[1] << lsh) | (x[0] >> rsh); + const auto xn_ex = x[1] >> rsh; + + const auto v = reciprocal_3by2({yn_lo, yn_hi}); + const auto res = udivrem_3by2(xn_ex, xn_hi, xn_lo, {yn_lo, yn_hi}, v); + + return {res.quot, res.rem >> lsh}; +} + +inline div_result sdivrem(uint128 x, uint128 y) noexcept +{ + constexpr auto sign_mask = uint128{1} << 127; + const auto x_is_neg = (x & sign_mask) != 0; + const auto y_is_neg = (y & sign_mask) != 0; + + const auto x_abs = x_is_neg ? -x : x; + const auto y_abs = y_is_neg ? -y : y; + + const auto q_is_neg = x_is_neg ^ y_is_neg; + + const auto res = udivrem(x_abs, y_abs); + + return {q_is_neg ? -res.quot : res.quot, x_is_neg ? -res.rem : res.rem}; +} + +inline uint128 operator/(uint128 x, uint128 y) noexcept +{ + return udivrem(x, y).quot; +} + +inline uint128 operator%(uint128 x, uint128 y) noexcept +{ + return udivrem(x, y).rem; +} + +inline uint128& operator/=(uint128& x, uint128 y) noexcept +{ + return x = x / y; +} + +inline uint128& operator%=(uint128& x, uint128 y) noexcept +{ + return x = x % y; +} + +/// @} + +} // namespace intx + + +namespace std +{ +template +struct numeric_limits> +{ + using type = intx::uint; + + static constexpr bool is_specialized = true; + static constexpr bool is_integer = true; + static constexpr bool is_signed = false; + static constexpr bool is_exact = true; + static constexpr bool has_infinity = false; + static constexpr bool has_quiet_NaN = false; + static constexpr bool has_signaling_NaN = false; + static constexpr float_denorm_style has_denorm = denorm_absent; + static constexpr bool has_denorm_loss = false; + static constexpr float_round_style round_style = round_toward_zero; + static constexpr bool is_iec559 = false; + static constexpr bool is_bounded = true; + static constexpr bool is_modulo = true; + static constexpr int digits = CHAR_BIT * sizeof(type); + static constexpr int digits10 = int(0.3010299956639812 * digits); + static constexpr int max_digits10 = 0; + static constexpr int radix = 2; + static constexpr int min_exponent = 0; + static constexpr int min_exponent10 = 0; + static constexpr int max_exponent = 0; + static constexpr int max_exponent10 = 0; + static constexpr bool traps = std::numeric_limits::traps; + static constexpr bool tinyness_before = false; + + static constexpr type min() noexcept { return 0; } + static constexpr type lowest() noexcept { return min(); } + static constexpr type max() noexcept { return ~type{0}; } + static constexpr type epsilon() noexcept { return 0; } + static constexpr type round_error() noexcept { return 0; } + static constexpr type infinity() noexcept { return 0; } + static constexpr type quiet_NaN() noexcept { return 0; } + static constexpr type signaling_NaN() noexcept { return 0; } + static constexpr type denorm_min() noexcept { return 0; } +}; +} // namespace std + +namespace intx +{ +template +[[noreturn]] inline void throw_(const char* what) +{ +#if __cpp_exceptions + throw T{what}; +#else + std::fputs(what, stderr); + std::abort(); +#endif +} + +inline constexpr int from_dec_digit(char c) +{ + if (c < '0' || c > '9') + throw_("invalid digit"); + return c - '0'; +} + +inline constexpr int from_hex_digit(char c) +{ + if (c >= 'a' && c <= 'f') + return c - ('a' - 10); + if (c >= 'A' && c <= 'F') + return c - ('A' - 10); + return from_dec_digit(c); +} + +template +inline constexpr Int from_string(const char* str) +{ + auto s = str; + auto x = Int{}; + int num_digits = 0; + + if (s[0] == '0' && s[1] == 'x') + { + s += 2; + while (const auto c = *s++) + { + if (++num_digits > int{sizeof(x) * 2}) + throw_(str); + x = (x << uint64_t{4}) | from_hex_digit(c); + } + return x; + } + + while (const auto c = *s++) + { + if (num_digits++ > std::numeric_limits::digits10) + throw_(str); + + const auto d = from_dec_digit(c); + x = x * Int{10} + d; + if (x < d) + throw_(str); + } + return x; +} + +template +inline constexpr Int from_string(const std::string& s) +{ + return from_string(s.c_str()); +} + +inline constexpr uint128 operator""_u128(const char* s) +{ + return from_string(s); +} + +template +inline std::string to_string(uint x, int base = 10) +{ + if (base < 2 || base > 36) + throw_("invalid base"); + + if (x == 0) + return "0"; + + auto s = std::string{}; + while (x != 0) + { + // TODO: Use constexpr udivrem_1? + const auto res = udivrem(x, uint{base}); + const auto d = int(res.rem); + const auto c = d < 10 ? '0' + d : 'a' + d - 10; + s.push_back(char(c)); + x = res.quot; + } + std::reverse(s.begin(), s.end()); + return s; +} + +template +inline std::string hex(uint x) +{ + return to_string(x, 16); +} +} // namespace intx + +struct Dqn_U128String +{ + // NOTE: Max value 340,282,366,920,938,463,463,374,607,431,768,211,455 + char str[51 + 1]; + int size; +}; + +Dqn_U128String Dqn_U128_String (intx::uint128 x, bool separate, char separate_ch = ','); +intx::uint128 Dqn_U128_FromString(const char *str, int size); +#endif // DQN_U128_H + +#if defined(DQN_U128_IMPLEMENTATION) +Dqn_U128String Dqn_U128_String(intx::uint128 x, bool separate, char separate_ch) +{ + Dqn_U128String val = {}; + if (x == 0) + { + val.str[val.size++] = '0'; + } + else + { + int insert_count = 0; + while (x != 0) + { + // TODO: Use constexpr udivrem_1? + const auto res = udivrem(x, intx::uint128{10 /*base*/}); + const auto d = int(res.rem); + const auto c = d < 10 ? '0' + d : 'a' + d - 10; + val.str[val.size++] = char(c); + x = res.quot; + + if (separate && x != 0) + { + insert_count++; + if (insert_count % 3 == 0) + val.str[val.size++] = separate_ch; + } + } + } + + + Dqn_U128String result = {}; + for (int i = val.size - 1; i >= 0; i--) + result.str[result.size++] = val.str[i]; + return result; +} + +intx::uint128 Dqn_U128_FromString(const char *str, int size) +{ + // TODO(dqn): Don't let this throw + auto x = intx::uint128{}; + int num_digits = 0; + + if (size > 2 && (str[0] == '0' && str[1] == 'x')) + { + for (int str_index = 2; str_index < size; str_index++) + { + char c = str[str_index]; + if (++num_digits > int{sizeof(x) * 2}) + intx::throw_(str); + x = (x << uint64_t{4}) | intx::from_hex_digit(c); + } + return x; + } + + for (int str_index = 0; str_index < size; str_index++) + { + char c = str[str_index]; + if (num_digits++ > std::numeric_limits::digits10) + intx::throw_(str); + + const auto d = intx::from_dec_digit(c); + x = x * intx::uint128{10} + d; + if (x < d) + intx::throw_(str); + } + return x; +} +#endif // DQN_U128_IMPLEMENTATION