Start refactor, restructure basic
This commit is contained in:
22
build.bat
22
build.bat
@@ -13,19 +13,17 @@ if not exist "luaunity.obj" (
|
|||||||
cl -Zi -nologo -I../src/external/lua/src -I../src/external/glad ../src/external/luaunity.c ../src/external/glad/glad.c -c
|
cl -Zi -nologo -I../src/external/lua/src -I../src/external/glad ../src/external/luaunity.c ../src/external/glad/glad.c -c
|
||||||
)
|
)
|
||||||
|
|
||||||
if not exist "metaprogram.exe" (
|
cl /WX /W3 /wd4200 /diagnostics:column -FC -Zi -nologo -Fe:metaprogram.exe -I../src ../src/metaprogram/metaprogram.cpp
|
||||||
cl /WX /W3 /wd4200 /diagnostics:column -FC -Zi -nologo -Fe:metaprogram.exe -I../src ../src/metaprogram/metaprogram.cpp ../src/basic/win32.cpp
|
|
||||||
)
|
|
||||||
metaprogram.exe
|
metaprogram.exe
|
||||||
|
|
||||||
|
|
||||||
set sdl=../src/external/SDL/win32-static/SDL3-static.lib ../src/external/SDL/win32-static/SDL_uclibc.lib
|
rem set sdl=../src/external/SDL/win32-static/SDL3-static.lib ../src/external/SDL/win32-static/SDL_uclibc.lib
|
||||||
cl /EHsc- /MD /Zi /FC /nologo /WX /W3 /wd4200 /diagnostics:column %profile_flags% ../src/text_editor/text_editor.cpp ../src/basic/win32.cpp -Fe:te.exe -I../src/external/SDL/include -I../src/external/lua/src -I../src/external/glad -I../src/ luaunity.obj glad.obj kernel32.lib gdi32.lib user32.lib Imm32.lib ole32.lib Shell32.lib OleAut32.lib Cfgmgr32.lib Setupapi.lib Advapi32.lib version.lib winmm.lib %sdl% -link /SUBSYSTEM:WINDOWS /NODEFAULTLIB:LIBCMT /NODEFAULTLIB:MSVCRTD
|
rem cl /EHsc- /MD /Zi /FC /nologo /WX /W3 /wd4200 /diagnostics:column %profile_flags% ../src/text_editor/text_editor.cpp ../src/basic/win32.cpp -Fe:te.exe -I../src/external/SDL/include -I../src/external/lua/src -I../src/external/glad -I../src/ luaunity.obj glad.obj kernel32.lib gdi32.lib user32.lib Imm32.lib ole32.lib Shell32.lib OleAut32.lib Cfgmgr32.lib Setupapi.lib Advapi32.lib version.lib winmm.lib %sdl% -link /SUBSYSTEM:WINDOWS /NODEFAULTLIB:LIBCMT /NODEFAULTLIB:MSVCRTD
|
||||||
|
|
||||||
if "%1"=="release" (
|
rem if "%1"=="release" (
|
||||||
copy te.exe ..\data\te.exe
|
rem copy te.exe ..\data\te.exe
|
||||||
echo written ..\data\te.exe
|
rem echo written ..\data\te.exe
|
||||||
) else (
|
rem ) else (
|
||||||
copy te.exe ..\data\te_debug.exe
|
rem copy te.exe ..\data\te_debug.exe
|
||||||
echo written ..\data\te_debug.exe
|
rem echo written ..\data\te_debug.exe
|
||||||
)
|
rem )
|
||||||
|
|||||||
9
src/basic/basic.cpp
Normal file
9
src/basic/basic.cpp
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
#define STB_SPRINTF_IMPLEMENTATION
|
||||||
|
#include "stb_sprintf.h"
|
||||||
|
#include "basic_string.cpp"
|
||||||
|
#include "basic_string16.cpp"
|
||||||
|
#include "basic_unicode.cpp"
|
||||||
|
#include "basic_alloc.cpp"
|
||||||
|
#include "basic_os.cpp"
|
||||||
|
#include "math.cpp"
|
||||||
|
#include "math_int.cpp"
|
||||||
1788
src/basic/basic.h
1788
src/basic/basic.h
File diff suppressed because it is too large
Load Diff
248
src/basic/basic_alloc.cpp
Normal file
248
src/basic/basic_alloc.cpp
Normal file
@@ -0,0 +1,248 @@
|
|||||||
|
#if defined(USE_ADDRESS_SANITIZER)
|
||||||
|
#include <sanitizer/asan_interface.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if !defined(ASAN_POISON_MEMORY_REGION)
|
||||||
|
#define MA_ASAN_POISON_MEMORY_REGION(addr, size) ((void)(addr), (void)(size))
|
||||||
|
#define MA_ASAN_UNPOISON_MEMORY_REGION(addr, size) ((void)(addr), (void)(size))
|
||||||
|
#else
|
||||||
|
#define MA_ASAN_POISON_MEMORY_REGION(addr, size) ASAN_POISON_MEMORY_REGION(addr, size)
|
||||||
|
#define MA_ASAN_UNPOISON_MEMORY_REGION(addr, size) ASAN_UNPOISON_MEMORY_REGION(addr, size)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if OS_WINDOWS
|
||||||
|
|
||||||
|
#ifndef NOMINMAX
|
||||||
|
#define NOMINMAX
|
||||||
|
#endif
|
||||||
|
#ifndef WIN32_LEAN_AND_MEAN
|
||||||
|
#define WIN32_LEAN_AND_MEAN
|
||||||
|
#endif
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
void *VReserve(size_t size) {
|
||||||
|
void *result = (uint8_t *)VirtualAlloc(0, size, MEM_RESERVE, PAGE_READWRITE);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VCommit(void *p, size_t size) {
|
||||||
|
void *result = VirtualAlloc(p, size, MEM_COMMIT, PAGE_READWRITE);
|
||||||
|
return result ? true : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VRelease(void *p, size_t size) {
|
||||||
|
BOOL result = VirtualFree(p, 0, MEM_RELEASE);
|
||||||
|
return result ? true : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VDecommit(void *p, size_t size) {
|
||||||
|
BOOL result = VirtualFree(p, size, MEM_DECOMMIT);
|
||||||
|
return result ? true : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#elif OS_LINUX || OS_MAC
|
||||||
|
|
||||||
|
void *VReserve(size_t size) {
|
||||||
|
void *result = mmap(0, size, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, (off_t)0);
|
||||||
|
return result == (void *)-1 ? 0 : result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VCommit(void *p, size_t size) {
|
||||||
|
int result = mprotect(p, size, PROT_READ | PROT_WRITE);
|
||||||
|
return result == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VRelease(void *p, size_t size) {
|
||||||
|
int result = munmap(p, size);
|
||||||
|
return result == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VDecommit(void *p, size_t size) {
|
||||||
|
mprotect(p, size, PROT_NONE);
|
||||||
|
madvise(p, size, MADV_DONTNEED);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
void *VReserve(size_t size) {
|
||||||
|
InvalidCodepath();
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VCommit(void *p, size_t size) {
|
||||||
|
InvalidCodepath();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VRelease(void *p, size_t size) {
|
||||||
|
InvalidCodepath();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VDecommit(void *p, size_t size) {
|
||||||
|
InvalidCodepath();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void InitArena(Arena *arena, size_t reserve) {
|
||||||
|
reserve = AlignUp(reserve, PAGE_SIZE);
|
||||||
|
arena->align = DEFAULT_ALIGNMENT;
|
||||||
|
arena->data = (uint8_t *)VReserve(reserve);
|
||||||
|
if (arena->data) {
|
||||||
|
arena->reserve = reserve;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Arena *AllocArena(Allocator allocator, size_t size) {
|
||||||
|
Arena *result = AllocType(allocator, Arena);
|
||||||
|
result->data = (uint8_t *)AllocSize(allocator, size);
|
||||||
|
result->reserve = size;
|
||||||
|
result->commit = size;
|
||||||
|
result->align = DEFAULT_ALIGNMENT;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Arena *AllocArena(size_t reserve) {
|
||||||
|
Arena *result = NULL;
|
||||||
|
|
||||||
|
void *data = VReserve(reserve);
|
||||||
|
if (!data) return result;
|
||||||
|
|
||||||
|
bool success = VCommit(data, PAGE_SIZE);
|
||||||
|
if (!success) {
|
||||||
|
VRelease(data, reserve);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = (Arena *)data;
|
||||||
|
result->data = (uint8_t *)data;
|
||||||
|
result->reserve = reserve;
|
||||||
|
result->commit = PAGE_SIZE;
|
||||||
|
result->len = result->base_len = sizeof(Arena);
|
||||||
|
result->align = DEFAULT_ALIGNMENT;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void *PushSize(Arena *arena, size_t size) {
|
||||||
|
// base_len is used for bootstraping arenas, it denotes the
|
||||||
|
// space occupied by the arena. If len is smaller then base_len then
|
||||||
|
// we start to overwrite the arena itself - pure barbarism.
|
||||||
|
Assert(arena->len >= arena->base_len);
|
||||||
|
|
||||||
|
size_t align_offset = 0;
|
||||||
|
if (arena->align) {
|
||||||
|
align_offset = GetAlignOffset((uintptr_t)arena->data + arena->len, arena->align);
|
||||||
|
}
|
||||||
|
size_t size_with_alignment = size + align_offset;
|
||||||
|
size_t new_len = arena->len + size_with_alignment;
|
||||||
|
if (new_len > arena->commit) {
|
||||||
|
size_t new_len_aligned_to_page_size = AlignUp(new_len, PAGE_SIZE);
|
||||||
|
size_t to_commit = new_len_aligned_to_page_size - arena->commit;
|
||||||
|
size_t to_commit_clamped = ClampTop(to_commit, arena->reserve);
|
||||||
|
if (to_commit_clamped > 0) {
|
||||||
|
bool success = VCommit(arena->data + arena->commit, to_commit_clamped);
|
||||||
|
if (success) {
|
||||||
|
MA_ASAN_UNPOISON_MEMORY_REGION(arena->data + arena->commit, to_commit_clamped);
|
||||||
|
arena->commit += to_commit_clamped;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (new_len > arena->commit) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
uint8_t *result = arena->data + arena->len + align_offset;
|
||||||
|
arena->len = new_len;
|
||||||
|
MA_ASAN_UNPOISON_MEMORY_REGION(result, size);
|
||||||
|
return (void *)result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Release(Arena *arena) {
|
||||||
|
if (arena == NULL || arena->data == NULL) return;
|
||||||
|
bool zero_memory = (uint8_t *)arena != arena->data;
|
||||||
|
VRelease(arena->data, arena->reserve);
|
||||||
|
if (zero_memory) MemoryZero(arena, sizeof(Arena));
|
||||||
|
}
|
||||||
|
|
||||||
|
void PopToPos(Arena *arena, size_t pos) {
|
||||||
|
// base_len is used for bootstraping arenas, it denotes the
|
||||||
|
// space occupied by the arena. If len is smaller then base_len then
|
||||||
|
// we start to overwrite the arena itself - pure barbarism.
|
||||||
|
Assert(arena->len >= arena->base_len);
|
||||||
|
|
||||||
|
pos = Clamp(pos, arena->base_len, arena->len);
|
||||||
|
size_t size = arena->len - pos;
|
||||||
|
arena->len = pos;
|
||||||
|
MA_ASAN_POISON_MEMORY_REGION(arena->data + arena->len, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
void *ArenaAllocatorProc(void *object, int kind, void *p, size_t size) {
|
||||||
|
if (kind == AllocatorKind_Allocate) {
|
||||||
|
return PushSize((Arena *)object, size);
|
||||||
|
} else if (AllocatorKind_Deallocate) {
|
||||||
|
} else {
|
||||||
|
Assert(!"invalid codepath");
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
thread_local Arena *ScratchArenaPool[4];
|
||||||
|
|
||||||
|
#if OS_WASM
|
||||||
|
void InitScratch() {
|
||||||
|
Allocator sys_allocator = GetSystemAllocator();
|
||||||
|
ScratchArenaPool[0] = AllocArena(sys_allocator, MiB(16));
|
||||||
|
ScratchArenaPool[1] = AllocArena(sys_allocator, MiB(8));
|
||||||
|
ScratchArenaPool[3] = AllocArena(sys_allocator, MiB(2));
|
||||||
|
ScratchArenaPool[3] = AllocArena(sys_allocator, MiB(1));
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
void InitScratch() {
|
||||||
|
for (int i = 0; i < Lengthof(ScratchArenaPool); i += 1) {
|
||||||
|
ScratchArenaPool[i] = AllocArena();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
TempArena GetScratchEx(Arena **conflicts, int conflict_count) {
|
||||||
|
Arena *unoccupied = 0;
|
||||||
|
for (int i = 0; i < Lengthof(ScratchArenaPool); i += 1) {
|
||||||
|
Arena *from_pool = ScratchArenaPool[i];
|
||||||
|
unoccupied = from_pool;
|
||||||
|
for (int conflict_i = 0; conflict_i < conflict_count; conflict_i += 1) {
|
||||||
|
Arena *from_conflict = conflicts[conflict_i];
|
||||||
|
if (from_pool == from_conflict) {
|
||||||
|
unoccupied = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unoccupied) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Failed to get free scratch memory, this is a fatal error, this shouldnt happen
|
||||||
|
Assert(unoccupied);
|
||||||
|
TempArena result = BeginTemp(unoccupied);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
void *SystemAllocator_Alloc(void *object, int kind, void *p, size_t size) {
|
||||||
|
void *result = NULL;
|
||||||
|
if (kind == AllocatorKind_Allocate) {
|
||||||
|
result = malloc(size);
|
||||||
|
Assert(result);
|
||||||
|
} else if (kind == AllocatorKind_Deallocate) {
|
||||||
|
free(p);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Allocator GetSystemAllocator() {
|
||||||
|
Allocator result = {SystemAllocator_Alloc};
|
||||||
|
return result;
|
||||||
|
}
|
||||||
95
src/basic/basic_alloc.h
Normal file
95
src/basic/basic_alloc.h
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
const int AllocatorKind_Allocate = 1;
|
||||||
|
const int AllocatorKind_Deallocate = 2;
|
||||||
|
|
||||||
|
struct Allocator {
|
||||||
|
void *(*proc)(void *object, int kind, void *p, size_t size);
|
||||||
|
void *object;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define AllocType(alo, Type) (Type *)AllocSize(alo, sizeof(Type))
|
||||||
|
#define AllocArray(alo, Type, count) (Type *)AllocSize(alo, sizeof(Type) * (count))
|
||||||
|
inline void *AllocSize(Allocator alo, size_t size) {
|
||||||
|
void *result = alo.proc(alo.object, AllocatorKind_Allocate, NULL, size);
|
||||||
|
memset(result, 0, size);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
void Dealloc(Allocator alo, T **p) {
|
||||||
|
if (*p == NULL) return;
|
||||||
|
alo.proc(alo.object, AllocatorKind_Deallocate, *p, 0);
|
||||||
|
*p = NULL;
|
||||||
|
}
|
||||||
|
#define DeallocEx(alo, p) (alo).proc((alo).object, AllocatorKind_Deallocate, (p), 0);
|
||||||
|
|
||||||
|
Allocator GetSystemAllocator();
|
||||||
|
#define MemoryZero(x, size) memset(x, 0, size)
|
||||||
|
#define MemoryCopy(dst, src, size) memcpy(dst, src, size)
|
||||||
|
#define MemoryMove(dst, src, size) memmove(dst, src, size)
|
||||||
|
|
||||||
|
const int PAGE_SIZE = 4096;
|
||||||
|
const int DEFAULT_ALIGNMENT = sizeof(void *);
|
||||||
|
|
||||||
|
struct Arena {
|
||||||
|
uint8_t *data;
|
||||||
|
size_t len;
|
||||||
|
size_t base_len; // to prevent self deleting the arena
|
||||||
|
size_t reserve;
|
||||||
|
size_t commit;
|
||||||
|
size_t align;
|
||||||
|
|
||||||
|
operator Allocator() {
|
||||||
|
void *ArenaAllocatorProc(void *object, int kind, void *p, size_t size);
|
||||||
|
return {ArenaAllocatorProc, this};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct TempArena {
|
||||||
|
Arena *arena;
|
||||||
|
size_t len;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline void SetLen(Arena *arena, size_t len) { arena->len = Clamp(len, arena->base_len, arena->len); }
|
||||||
|
inline void Pop(Arena *arena, size_t size) { SetLen(arena, arena->len - size); }
|
||||||
|
inline TempArena BeginTemp(Arena *arena) { return {arena, arena->len}; }
|
||||||
|
inline void EndTemp(TempArena temp) { SetLen(temp.arena, temp.len); }
|
||||||
|
inline void Clear(Arena *arena) { SetLen(arena, 0); }
|
||||||
|
|
||||||
|
void *VReserve(size_t size);
|
||||||
|
bool VCommit(void *p, size_t size);
|
||||||
|
bool VRelease(void *p, size_t size);
|
||||||
|
bool VDecommit(void *p, size_t size);
|
||||||
|
|
||||||
|
void InitArena(Arena *arena, size_t reserve = MiB(256));
|
||||||
|
Arena *AllocArena(size_t reserve = MiB(256));
|
||||||
|
Arena *AllocArena(Allocator allocator, size_t size);
|
||||||
|
void *PushSize(Arena *arena, size_t size);
|
||||||
|
void Release(Arena *arena);
|
||||||
|
void InitScratch();
|
||||||
|
TempArena GetScratchEx(Arena **conflicts, int conflict_count);
|
||||||
|
|
||||||
|
inline TempArena GetScratch(Arena *c1 = NULL, Arena *c2 = NULL) {
|
||||||
|
int count = c1 ? 1 : 0;
|
||||||
|
count += c2 ? 1 : 0;
|
||||||
|
Arena *conflicts[] = {c1, c2};
|
||||||
|
return GetScratchEx(conflicts, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Scratch {
|
||||||
|
TempArena checkpoint;
|
||||||
|
Scratch() { this->checkpoint = GetScratch(); }
|
||||||
|
|
||||||
|
Scratch(Arena *conflict) { this->checkpoint = GetScratch(conflict); }
|
||||||
|
Scratch(Arena *c1, Arena *c2) { this->checkpoint = GetScratch(c1, c2); }
|
||||||
|
Scratch(Allocator conflict) { this->checkpoint = GetScratch((Arena *)conflict.object); }
|
||||||
|
Scratch(Allocator c1, Allocator c2) { this->checkpoint = GetScratch((Arena *)c1.object, (Arena *)c2.object); }
|
||||||
|
~Scratch() { EndTemp(checkpoint); }
|
||||||
|
operator Arena *() { return checkpoint.arena; }
|
||||||
|
operator Allocator() { return *checkpoint.arena; }
|
||||||
|
|
||||||
|
private: // @Note: Disable copy constructors, cause its error prone
|
||||||
|
Scratch(Scratch &arena);
|
||||||
|
Scratch(Scratch &arena, Scratch &a2);
|
||||||
|
};
|
||||||
497
src/basic/basic_array.h
Normal file
497
src/basic/basic_array.h
Normal file
@@ -0,0 +1,497 @@
|
|||||||
|
#pragma once
|
||||||
|
/*
|
||||||
|
// Iterating and removing elements
|
||||||
|
for (int i = 0; i < array.len; i += 1) {
|
||||||
|
auto &it = array[i];
|
||||||
|
bool remove_item = false;
|
||||||
|
defer {
|
||||||
|
if (remove_item) {
|
||||||
|
array.ordered_remove(it);
|
||||||
|
i -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simple delete
|
||||||
|
IterRemove(arr) {
|
||||||
|
IterRemovePrepare(arr);
|
||||||
|
|
||||||
|
remove_item = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deleting backwards
|
||||||
|
For(arr.reverse_iter()) {
|
||||||
|
defer{ arr.unordered_remove(it); };
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
#define IterRemove(a) for (int i = 0; i < (a).len; i += 1)
|
||||||
|
#define IterRemovePrepare(a) \
|
||||||
|
auto &it = (a)[i]; \
|
||||||
|
bool remove_item = false; \
|
||||||
|
defer { \
|
||||||
|
if (remove_item) { \
|
||||||
|
Remove(&(a), it); \
|
||||||
|
i -= 1; \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
#define ForItem(it, array) for (auto &it : (array))
|
||||||
|
#define For(array) ForItem(it, array)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
struct Slice {
|
||||||
|
T *data;
|
||||||
|
int64_t len;
|
||||||
|
|
||||||
|
Slice() = default;
|
||||||
|
Slice(T *s, int64_t l) : data(s), len(l) {}
|
||||||
|
|
||||||
|
T &operator[](int64_t index) {
|
||||||
|
Assert(index < len);
|
||||||
|
return data[index];
|
||||||
|
}
|
||||||
|
T *begin() { return data; }
|
||||||
|
T *end() { return data + len; }
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
Slice<T> Copy(Allocator alo, Slice<T> array) {
|
||||||
|
Slice<T> result = {};
|
||||||
|
result.data = AllocArray(alo, T, array.len);
|
||||||
|
memcpy(result.data, array.data, sizeof(T) * array.len);
|
||||||
|
result.len = array.len;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
T Pop(Slice<T> *arr) {
|
||||||
|
Assert(arr->len > 0);
|
||||||
|
return arr->data[--arr->len];
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
bool Contains(Slice<T> &arr, T item) {
|
||||||
|
For(arr) if (it == item) return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
int64_t GetIndex(Slice<T> &arr, const T &item) {
|
||||||
|
ptrdiff_t index = (ptrdiff_t)(&item - arr.data);
|
||||||
|
Assert(index >= 0 && index < arr.len);
|
||||||
|
return (int64_t)index;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
T Get(Slice<T> &arr, int64_t i, T default_value = {}) {
|
||||||
|
T result = default_value;
|
||||||
|
if (i >= 0 && i < arr.len) result = arr[i];
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
bool IsLast(Slice<T> &arr, T &item) {
|
||||||
|
bool result = arr.last() == &item;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
bool IsFirst(Slice<T> &arr, T &item) {
|
||||||
|
bool result = arr.first() == &item;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
T *GetFirst(Slice<T> &arr) {
|
||||||
|
Assert(arr.len > 0);
|
||||||
|
return arr.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
T *GetLast(Slice<T> &arr) {
|
||||||
|
Assert(arr.len > 0);
|
||||||
|
return arr.data + arr.len - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
Slice<T> Chop(Slice<T> &arr, int64_t len) {
|
||||||
|
len = ClampTop(len, arr.len);
|
||||||
|
Slice<T> result = {arr.data, arr.len - len};
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
Slice<T> Skip(Slice<T> &arr, int64_t len) {
|
||||||
|
len = ClampTop(len, arr.len);
|
||||||
|
Slice<T> result = {arr.data + len, arr.len - len};
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
Slice<T> GetPostfix(Slice<T> &arr, int64_t len) {
|
||||||
|
len = ClampTop(len, arr.len);
|
||||||
|
int64_t remain_len = arr.len - len;
|
||||||
|
Slice<T> result = {arr.data + remain_len, len};
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
Slice<T> GetPrefix(Slice<T> &arr, int64_t len) {
|
||||||
|
len = ClampTop(len, arr.len);
|
||||||
|
Slice<T> result = {arr.data, len};
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
Slice<T> GetSlice(Slice<T> &arr, int64_t first_index = 0, int64_t one_past_last_index = SLICE_LAST) {
|
||||||
|
// Negative indexes work in python style, they return you the index counting from end of list
|
||||||
|
if (one_past_last_index == SLICE_LAST) one_past_last_index = arr.len;
|
||||||
|
if (one_past_last_index < 0) one_past_last_index = arr.len + one_past_last_index;
|
||||||
|
|
||||||
|
if (first_index == SLICE_LAST) first_index = arr.len;
|
||||||
|
if (first_index < 0) first_index = arr.len + first_index;
|
||||||
|
|
||||||
|
Slice<T> result = {arr.data, arr.len};
|
||||||
|
if (arr.len > 0) {
|
||||||
|
if (one_past_last_index > first_index) {
|
||||||
|
first_index = ClampTop(first_index, arr.len - 1);
|
||||||
|
one_past_last_index = ClampTop(one_past_last_index, arr.len);
|
||||||
|
result.data += first_index;
|
||||||
|
result.len = one_past_last_index - first_index;
|
||||||
|
} else {
|
||||||
|
result.len = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make arrays resize on every item
|
||||||
|
#define ARRAY_DEBUG 0
|
||||||
|
#if ARRAY_DEBUG
|
||||||
|
#define ARRAY_IF_DEBUG_ELSE(IF, ELSE) IF
|
||||||
|
#else
|
||||||
|
#define ARRAY_IF_DEBUG_ELSE(IF, ELSE) ELSE
|
||||||
|
#endif
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
struct Array {
|
||||||
|
Allocator allocator;
|
||||||
|
int64_t cap;
|
||||||
|
union {
|
||||||
|
Slice<T> slice;
|
||||||
|
struct {
|
||||||
|
T *data;
|
||||||
|
int64_t len;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
T &operator[](int64_t index) {
|
||||||
|
Assert(index >= 0 && index < len);
|
||||||
|
return data[index];
|
||||||
|
}
|
||||||
|
T *begin() { return data; }
|
||||||
|
T *end() { return data + len; }
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
T *GetFirst(Array<T> &arr) {
|
||||||
|
Assert(arr.len > 0);
|
||||||
|
return arr.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
T *GetLast(Array<T> &arr) {
|
||||||
|
Assert(arr.len > 0);
|
||||||
|
return arr.data + arr.len - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
void Reserve(Array<T> *arr, int64_t size) {
|
||||||
|
if (size > arr->cap) {
|
||||||
|
if (!arr->allocator.proc) arr->allocator = GetSystemAllocator();
|
||||||
|
|
||||||
|
T *new_data = AllocArray(arr->allocator, T, size);
|
||||||
|
Assert(new_data);
|
||||||
|
memcpy(new_data, arr->data, arr->len * sizeof(T));
|
||||||
|
Dealloc(arr->allocator, &arr->data);
|
||||||
|
|
||||||
|
arr->data = new_data;
|
||||||
|
arr->cap = size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
void TryGrowing(Array<T> *arr) {
|
||||||
|
if (arr->len + 1 > arr->cap) {
|
||||||
|
int64_t initial_size = (int64_t)ARRAY_IF_DEBUG_ELSE(1, 16);
|
||||||
|
int64_t new_size = ClampBottom(initial_size, arr->cap ARRAY_IF_DEBUG_ELSE(+1, *2));
|
||||||
|
Reserve(arr, new_size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
void TryGrowing(Array<T> *arr, int64_t item_count) {
|
||||||
|
if (arr->len + item_count > arr->cap) {
|
||||||
|
int64_t initial_size = (int64_t)ARRAY_IF_DEBUG_ELSE(1, 16);
|
||||||
|
int64_t new_size = ClampBottom(initial_size, (arr->cap + item_count) ARRAY_IF_DEBUG_ELSE(+1, *2));
|
||||||
|
Reserve(arr, new_size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
void Add(Array<T> *arr, T item) {
|
||||||
|
TryGrowing(arr);
|
||||||
|
arr->data[arr->len++] = item;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
void Add(Array<T> *arr, Array<T> &another) {
|
||||||
|
For(another) Add(arr, it);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
void Add(Array<T> *arr, T *items, int64_t item_count) {
|
||||||
|
for (int64_t i = 0; i < item_count; i += 1) Add(arr, items[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
void BoundedAdd(Array<T> *arr, T item) {
|
||||||
|
if (arr->len + 1 <= arr->cap) arr->data[arr->len++] = item;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
void BoundedAddError(Array<T> *arr, T item) {
|
||||||
|
Assert(arr->len + 1 <= arr->cap);
|
||||||
|
if (arr->len + 1 <= arr->cap) arr->data[arr->len++] = item;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
void Insert(Array<T> *arr, T item, int64_t index) {
|
||||||
|
if (index == arr->len) {
|
||||||
|
Add(arr, item);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert(index < arr->len);
|
||||||
|
Assert(index >= 0);
|
||||||
|
TryGrowing(arr);
|
||||||
|
int64_t right_len = arr->len - index;
|
||||||
|
memmove(arr->data + index + 1, arr->data + index, sizeof(T) * right_len);
|
||||||
|
arr->data[index] = item;
|
||||||
|
arr->len += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
Array<T> Copy(Allocator alo, Array<T> array) {
|
||||||
|
Array<T> result = {alo};
|
||||||
|
Reserve(&result, array.cap);
|
||||||
|
memcpy(result.data, array.data, sizeof(T) * array.len);
|
||||||
|
result.len = array.len;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
Array<T> TightCopy(Allocator alo, Array<T> array) {
|
||||||
|
Array<T> result = {alo};
|
||||||
|
Reserve(&result, array.len);
|
||||||
|
memcpy(result.data, array.data, sizeof(T) * array.len);
|
||||||
|
result.len = array.len;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
T Pop(Array<T> *arr) {
|
||||||
|
Assert(arr->len > 0);
|
||||||
|
return arr->data[--arr->len];
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
bool Contains(Array<T> &arr, T item) {
|
||||||
|
For(arr) if (it == item) return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
int64_t GetIndex(Array<T> &arr, const T &item) {
|
||||||
|
ptrdiff_t index = (ptrdiff_t)(&item - arr.data);
|
||||||
|
Assert(index >= 0 && index < arr.len);
|
||||||
|
return (int64_t)index;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
void RemoveByIndex(Array<T> *arr, int64_t index) {
|
||||||
|
Assert(index >= 0 && index < arr->len);
|
||||||
|
int64_t right_len = arr->len - index - 1;
|
||||||
|
memmove(arr->data + index, arr->data + index + 1, right_len * sizeof(T));
|
||||||
|
arr->len -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
void RemoveManyByIndex(Array<T> *arr, int64_t index, int64_t count) {
|
||||||
|
if (count == 0) return;
|
||||||
|
Assert(index >= 0 && index < arr->len);
|
||||||
|
Assert((index + count) > 0 && (index + count) <= arr->len);
|
||||||
|
int64_t right_len = arr->len - index - count;
|
||||||
|
memmove(arr->data + index, arr->data + index + count, right_len * sizeof(T));
|
||||||
|
arr->len -= count;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
void RemoveMany(Array<T> *arr, T &item, int64_t count) {
|
||||||
|
Assert(arr->len > 0);
|
||||||
|
Assert(&item >= arr->begin() && &item < arr->end());
|
||||||
|
int64_t index = GetIndex(*arr, item);
|
||||||
|
RemoveManyByIndex(arr, index, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
void Remove(Array<T> *arr, T &item) {
|
||||||
|
Assert(arr->len > 0);
|
||||||
|
Assert(&item >= arr->begin() && &item < arr->end());
|
||||||
|
int64_t index = GetIndex(*arr, item);
|
||||||
|
RemoveByIndex(arr, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
void UnorderedRemove(Array<T> *arr, T &item) {
|
||||||
|
Assert(arr->len > 0);
|
||||||
|
Assert((&item >= arr->begin()) && (&item < arr->end()));
|
||||||
|
item = arr->data[--arr->len];
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
void UnorderedRemoveByIndex(Array<T> *arr, int64_t index) {
|
||||||
|
Assert(arr->len > 0);
|
||||||
|
Assert(index >= 0 && index < arr->len);
|
||||||
|
arr->data[index] = arr->data[--arr->len];
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
void InsertArray(Array<T> *arr, T *items, int64_t count, int64_t index) {
|
||||||
|
if (index == arr->len) {
|
||||||
|
Add(arr, items, count);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Assert(index < arr->len);
|
||||||
|
|
||||||
|
TryGrowing(arr, count);
|
||||||
|
T *gap_begin = arr->data + index;
|
||||||
|
T *gap_end = gap_begin + count;
|
||||||
|
int64_t item_count = arr->len - index;
|
||||||
|
memmove(gap_end, gap_begin, item_count * sizeof(T));
|
||||||
|
for (int64_t i = 0; i < count; i += 1) arr->data[index + i] = items[i];
|
||||||
|
arr->len += count;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
T Get(Array<T> &arr, int64_t i, T default_value = {}) {
|
||||||
|
T result = default_value;
|
||||||
|
if (i >= 0 && i < arr.len) result = arr[i];
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
void Dealloc(Array<T> *arr) {
|
||||||
|
if (arr->data) Dealloc(arr->allocator, &arr->data);
|
||||||
|
arr->len = arr->cap = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
bool IsLast(Array<T> &arr, T &item) {
|
||||||
|
bool result = GetLast(arr) == &item;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
bool IsFirst(Array<T> &arr, T &item) {
|
||||||
|
bool result = GetFirst(arr) == &item;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
Slice<T> Chop(Array<T> &arr, int64_t len) {
|
||||||
|
len = ClampTop(len, arr.len);
|
||||||
|
Slice<T> result = {arr.data, arr.len - len};
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
Slice<T> Skip(Array<T> &arr, int64_t len) {
|
||||||
|
len = ClampTop(len, arr.len);
|
||||||
|
Slice<T> result = {arr.data + len, arr.len - len};
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
Slice<T> GetPostfix(Array<T> &arr, int64_t len) {
|
||||||
|
len = ClampTop(len, arr.len);
|
||||||
|
int64_t remain_len = arr.len - len;
|
||||||
|
Slice<T> result = {arr.data + remain_len, len};
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
Slice<T> GetPrefix(Array<T> &arr, int64_t len) {
|
||||||
|
len = ClampTop(len, arr.len);
|
||||||
|
Slice<T> result = {arr.data, len};
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
Slice<T> GetSlice(Array<T> &arr, int64_t first_index = 0, int64_t one_past_last_index = SLICE_LAST) {
|
||||||
|
// Negative indexes work in python style, they return you the index counting from end of list
|
||||||
|
if (one_past_last_index == SLICE_LAST) one_past_last_index = arr.len;
|
||||||
|
if (one_past_last_index < 0) one_past_last_index = arr.len + one_past_last_index;
|
||||||
|
|
||||||
|
if (first_index == SLICE_LAST) first_index = arr.len;
|
||||||
|
if (first_index < 0) first_index = arr.len + first_index;
|
||||||
|
|
||||||
|
Slice<T> result = {arr.data, arr.len};
|
||||||
|
if (arr.len > 0) {
|
||||||
|
if (one_past_last_index > first_index) {
|
||||||
|
first_index = ClampTop(first_index, arr.len - 1);
|
||||||
|
one_past_last_index = ClampTop(one_past_last_index, arr.len);
|
||||||
|
result.data += first_index;
|
||||||
|
result.len = one_past_last_index - first_index;
|
||||||
|
} else {
|
||||||
|
result.len = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
struct ReverseIter {
|
||||||
|
T *data;
|
||||||
|
Slice<T> *arr;
|
||||||
|
|
||||||
|
ReverseIter operator++(int) {
|
||||||
|
ReverseIter ret = *this;
|
||||||
|
data -= 1;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
ReverseIter &operator++() {
|
||||||
|
data -= 1;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
T &operator*() { return data[0]; }
|
||||||
|
T *operator->() { return data; }
|
||||||
|
|
||||||
|
friend bool operator==(const ReverseIter &a, const ReverseIter &b) { return a.data == b.data; };
|
||||||
|
friend bool operator!=(const ReverseIter &a, const ReverseIter &b) { return a.data != b.data; };
|
||||||
|
|
||||||
|
ReverseIter begin() { return ReverseIter{arr->end() - 1, arr}; }
|
||||||
|
ReverseIter end() { return ReverseIter{arr->begin() - 1, arr}; }
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
ReverseIter<T> IterateInReverse(Array<T> *arr) {
|
||||||
|
return {arr->end() - 1, &arr->slice};
|
||||||
|
}
|
||||||
|
template <class T>
|
||||||
|
ReverseIter<T> IterateInReverse(Slice<T> *slice) {
|
||||||
|
return {slice->end() - 1, slice};
|
||||||
|
}
|
||||||
|
|
||||||
215
src/basic/basic_head.h
Normal file
215
src/basic/basic_head.h
Normal file
@@ -0,0 +1,215 @@
|
|||||||
|
#pragma once
|
||||||
|
#ifndef _CRT_SECURE_NO_WARNINGS
|
||||||
|
#define _CRT_SECURE_NO_WARNINGS
|
||||||
|
#endif
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
|
||||||
|
#if defined(__APPLE__) && defined(__MACH__)
|
||||||
|
#define OS_POSIX 1
|
||||||
|
#define OS_MAC 1
|
||||||
|
#elif defined(_WIN32)
|
||||||
|
#define OS_WINDOWS 1
|
||||||
|
#elif defined(__linux__)
|
||||||
|
#define OS_POSIX 1
|
||||||
|
#define OS_LINUX 1
|
||||||
|
#elif defined(__EMSCRIPTEN__)
|
||||||
|
#define OS_WASM 1
|
||||||
|
#else
|
||||||
|
#error Unsupported platform
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(__clang__)
|
||||||
|
#define COMPILER_CLANG 1
|
||||||
|
#elif defined(__GNUC__) || defined(__GNUG__)
|
||||||
|
#define COMPILER_GCC 1
|
||||||
|
#elif defined(_MSC_VER)
|
||||||
|
#define COMPILER_MSVC 1
|
||||||
|
#else
|
||||||
|
#error Unsupported compiler
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef OS_WASM
|
||||||
|
#define OS_WASM 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef OS_MAC
|
||||||
|
#define OS_MAC 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef OS_WINDOWS
|
||||||
|
#define OS_WINDOWS 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef OS_LINUX
|
||||||
|
#define OS_LINUX 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef OS_POSIX
|
||||||
|
#define OS_POSIX 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef COMPILER_MSVC
|
||||||
|
#define COMPILER_MSVC 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef COMPILER_CLANG
|
||||||
|
#define COMPILER_CLANG 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef COMPILER_GCC
|
||||||
|
#define COMPILER_GCC 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if OS_WINDOWS
|
||||||
|
#define BREAK() __debugbreak()
|
||||||
|
#elif OS_LINUX
|
||||||
|
#define BREAK() raise(SIGTRAP)
|
||||||
|
#elif OS_WASM
|
||||||
|
#include <emscripten.h>
|
||||||
|
EM_JS(void, JS_Breakpoint, (), {
|
||||||
|
debugger;
|
||||||
|
})
|
||||||
|
#define BREAK() JS_Breakpoint()
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define API
|
||||||
|
#define Assert(x) \
|
||||||
|
if (!(x)) { \
|
||||||
|
BREAK(); \
|
||||||
|
}
|
||||||
|
#define InvalidCodepath() Assert(!"invalid codepath")
|
||||||
|
#define ElseInvalidCodepath() else {InvalidCodepath()}
|
||||||
|
|
||||||
|
#define KiB(x) ((x##ull) * 1024ull)
|
||||||
|
#define MiB(x) (KiB(x) * 1024ull)
|
||||||
|
#define GiB(x) (MiB(x) * 1024ull)
|
||||||
|
#define TiB(x) (GiB(x) * 1024ull)
|
||||||
|
#define Lengthof(x) ((int64_t)((sizeof(x) / sizeof((x)[0]))))
|
||||||
|
#define SLICE_LAST INT64_MAX
|
||||||
|
|
||||||
|
using U8 = uint8_t;
|
||||||
|
using U16 = uint16_t;
|
||||||
|
using U32 = uint32_t;
|
||||||
|
using U64 = uint64_t;
|
||||||
|
using S8 = int8_t;
|
||||||
|
using S16 = int16_t;
|
||||||
|
using S32 = int32_t;
|
||||||
|
using S64 = int64_t;
|
||||||
|
using Int = S64;
|
||||||
|
using UInt = U64;
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
T Min(T a, T b) {
|
||||||
|
if (a > b) return b;
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
T ClampTop(T a, T top) {
|
||||||
|
return Min(a, top);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
T Max(T a, T b) {
|
||||||
|
if (a > b) return a;
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
T ClampBottom(T bottom, T b) {
|
||||||
|
return Max(bottom, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
T Clamp(T value, T min, T max) {
|
||||||
|
if (value < min) return min;
|
||||||
|
if (value > max) return max;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
void Swap(T *a, T *b) {
|
||||||
|
T temp = *a;
|
||||||
|
*a = *b;
|
||||||
|
*b = temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool IsPowerOf2(size_t x) {
|
||||||
|
size_t result = (((x) & ((x)-1)) == 0);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline size_t WrapAroundPowerOf2(size_t x, size_t pow2) {
|
||||||
|
Assert(IsPowerOf2(pow2));
|
||||||
|
size_t result = (((x) & ((pow2)-1llu)));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline uint64_t HashBytes(void *data, unsigned size) {
|
||||||
|
uint8_t *data8 = (uint8_t *)data;
|
||||||
|
uint64_t hash = (uint64_t)14695981039346656037ULL;
|
||||||
|
for (unsigned i = 0; i < size; i++) {
|
||||||
|
hash = hash ^ (uint64_t)(data8[i]);
|
||||||
|
hash = hash * (uint64_t)1099511628211ULL;
|
||||||
|
}
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline size_t GetAlignOffset(size_t size, size_t align) {
|
||||||
|
Assert(IsPowerOf2(align));
|
||||||
|
size_t mask = align - 1;
|
||||||
|
size_t val = size & mask;
|
||||||
|
if (val) {
|
||||||
|
val = align - val;
|
||||||
|
}
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline size_t AlignUp(size_t size, size_t align) {
|
||||||
|
size_t result = size + GetAlignOffset(size, align);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline size_t AlignDown(size_t size, size_t align) {
|
||||||
|
size += 1; // Make sure when align is 8 doesn't get rounded down to 0
|
||||||
|
size_t result = size - (align - GetAlignOffset(size, align));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct DEFER_ExitScope {
|
||||||
|
T lambda;
|
||||||
|
DEFER_ExitScope(T lambda) : lambda(lambda) {}
|
||||||
|
~DEFER_ExitScope() { lambda(); }
|
||||||
|
DEFER_ExitScope(const DEFER_ExitScope &i) : lambda(i.lambda){};
|
||||||
|
|
||||||
|
private:
|
||||||
|
DEFER_ExitScope &operator=(const DEFER_ExitScope &);
|
||||||
|
};
|
||||||
|
|
||||||
|
class DEFER_ExitScopeHelp {
|
||||||
|
public:
|
||||||
|
template <typename T>
|
||||||
|
DEFER_ExitScope<T> operator+(T t) { return t; }
|
||||||
|
};
|
||||||
|
|
||||||
|
#define DEFER_CONCAT_INTERNAL(x, y) x##y
|
||||||
|
#define DEFER_CONCAT(x, y) DEFER_CONCAT_INTERNAL(x, y)
|
||||||
|
#define defer const auto DEFER_CONCAT(defer__, __LINE__) = DEFER_ExitScopeHelp() + [&]()
|
||||||
|
|
||||||
|
struct RandomSeed {
|
||||||
|
uint64_t a;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline uint64_t GetRandomU64(RandomSeed *state) {
|
||||||
|
uint64_t x = state->a;
|
||||||
|
x ^= x << 13;
|
||||||
|
x ^= x >> 7;
|
||||||
|
x ^= x << 17;
|
||||||
|
return state->a = x;
|
||||||
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
#ifndef FIRST_LL_HEADER
|
#pragma once
|
||||||
#define FIRST_LL_HEADER
|
|
||||||
#define SLL_QUEUE_ADD_MOD(f, l, n, next) \
|
#define SLL_QUEUE_ADD_MOD(f, l, n, next) \
|
||||||
do { \
|
do { \
|
||||||
(n)->next = 0; \
|
(n)->next = 0; \
|
||||||
@@ -116,4 +115,3 @@
|
|||||||
} while (0)
|
} while (0)
|
||||||
#define DLL_INSERT_NEXT(base, new) DLL_INSERT_NEXT_MOD(base, new, next, prev)
|
#define DLL_INSERT_NEXT(base, new) DLL_INSERT_NEXT_MOD(base, new, next, prev)
|
||||||
#define DLL_INSERT_PREV(base, new) DLL_INSERT_NEXT_MOD(base, new, next, prev)
|
#define DLL_INSERT_PREV(base, new) DLL_INSERT_NEXT_MOD(base, new, next, prev)
|
||||||
#endif
|
|
||||||
59
src/basic/basic_math.h
Normal file
59
src/basic/basic_math.h
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
struct Vec2 {
|
||||||
|
float x;
|
||||||
|
float y;
|
||||||
|
};
|
||||||
|
|
||||||
|
union Vec4 {
|
||||||
|
struct {
|
||||||
|
float x;
|
||||||
|
float y;
|
||||||
|
float z;
|
||||||
|
float w;
|
||||||
|
};
|
||||||
|
struct {
|
||||||
|
float r;
|
||||||
|
float g;
|
||||||
|
float b;
|
||||||
|
float a;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
union Vec3 {
|
||||||
|
struct {
|
||||||
|
float x;
|
||||||
|
float y;
|
||||||
|
float z;
|
||||||
|
};
|
||||||
|
struct {
|
||||||
|
float r;
|
||||||
|
float g;
|
||||||
|
float b;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Rect2 {
|
||||||
|
Vec2 min;
|
||||||
|
Vec2 max;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Vec2I {
|
||||||
|
Int x;
|
||||||
|
Int y;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Rect2I {
|
||||||
|
Vec2I min;
|
||||||
|
Vec2I max;
|
||||||
|
};
|
||||||
|
|
||||||
|
union Color {
|
||||||
|
struct {
|
||||||
|
uint8_t r;
|
||||||
|
uint8_t g;
|
||||||
|
uint8_t b;
|
||||||
|
uint8_t a;
|
||||||
|
};
|
||||||
|
uint32_t value;
|
||||||
|
};
|
||||||
1101
src/basic/basic_os.cpp
Normal file
1101
src/basic/basic_os.cpp
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,4 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "../basic/basic.h"
|
|
||||||
|
|
||||||
struct FileIter {
|
struct FileIter {
|
||||||
bool is_valid;
|
bool is_valid;
|
||||||
@@ -50,8 +49,7 @@ void KillProcess(Process *process);
|
|||||||
String PollStdout(Allocator allocator, Process *process, bool force_read);
|
String PollStdout(Allocator allocator, Process *process, bool force_read);
|
||||||
void WriteStdin(Process *process, String string);
|
void WriteStdin(Process *process, String string);
|
||||||
void CloseStdin(Process *process);
|
void CloseStdin(Process *process);
|
||||||
|
double GetTimeMicros(void);
|
||||||
double get_time_in_micros(void);
|
|
||||||
|
|
||||||
enum MakeDirResult {
|
enum MakeDirResult {
|
||||||
MakeDirResult_Success,
|
MakeDirResult_Success,
|
||||||
336
src/basic/basic_string.cpp
Normal file
336
src/basic/basic_string.cpp
Normal file
@@ -0,0 +1,336 @@
|
|||||||
|
API char ToLowerCase(char a) {
|
||||||
|
if (a >= 'A' && a <= 'Z') a += 32;
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
API char ToUpperCase(char a) {
|
||||||
|
if (a >= 'a' && a <= 'z') a -= 32;
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
API bool IsWhitespace(char w) {
|
||||||
|
bool result = w == '\n' || w == ' ' || w == '\t' || w == '\v' || w == '\r';
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
API bool IsAlphabetic(char a) {
|
||||||
|
bool result = (a >= 'a' && a <= 'z') || (a >= 'A' && a <= 'Z');
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
API bool IsIdent(char a) {
|
||||||
|
bool result = (a >= 'a' && a <= 'z') || (a >= 'A' && a <= 'Z') || a == '_';
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
API bool IsDigit(char a) {
|
||||||
|
bool result = a >= '0' && a <= '9';
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
API bool IsAlphanumeric(char a) {
|
||||||
|
bool result = IsDigit(a) || IsAlphabetic(a);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
API bool IsSymbol(char w) {
|
||||||
|
bool result = (w >= '!' && w <= '/') || (w >= ':' && w <= '@') || (w >= '[' && w <= '`') || (w >= '{' && w <= '~');
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
API bool IsNonWord(char w) {
|
||||||
|
if (w == '_') return false;
|
||||||
|
bool result = IsSymbol(w) || IsWhitespace(w);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
API bool IsWord(char w) {
|
||||||
|
bool result = !IsNonWord(w);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
API bool IsBrace(char c) {
|
||||||
|
bool result = c == '{' || c == '}';
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
API bool IsParen(char c) {
|
||||||
|
bool result = c == '(' || c == ')';
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
API String Chop(String a, int64_t len) {
|
||||||
|
len = ClampTop(len, a.len);
|
||||||
|
String result = {a.data, a.len - len};
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
API String Skip(String a, int64_t len) {
|
||||||
|
len = ClampTop(len, a.len);
|
||||||
|
String result = {a.data + len, a.len - len};
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
API String GetPostfix(String a, int64_t len) {
|
||||||
|
len = ClampTop(len, a.len);
|
||||||
|
int64_t remain_len = a.len - len;
|
||||||
|
String result = {a.data + remain_len, len};
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
API String GetPrefix(String a, int64_t len) {
|
||||||
|
len = ClampTop(len, a.len);
|
||||||
|
String result = {a.data, len};
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
API String GetSlice(String arr, int64_t first_index, int64_t one_past_last_index) {
|
||||||
|
// Negative indexes work in python style, they return you the index counting from end of list
|
||||||
|
if (one_past_last_index == SLICE_LAST) one_past_last_index = arr.len;
|
||||||
|
if (one_past_last_index < 0) one_past_last_index = arr.len + one_past_last_index;
|
||||||
|
|
||||||
|
if (first_index == SLICE_LAST) first_index = arr.len;
|
||||||
|
if (first_index < 0) first_index = arr.len + first_index;
|
||||||
|
|
||||||
|
String result = {arr.data, arr.len};
|
||||||
|
if (arr.len > 0) {
|
||||||
|
if (one_past_last_index > first_index) {
|
||||||
|
first_index = ClampTop(first_index, arr.len - 1);
|
||||||
|
one_past_last_index = ClampTop(one_past_last_index, arr.len);
|
||||||
|
result.data += first_index;
|
||||||
|
result.len = one_past_last_index - first_index;
|
||||||
|
} else {
|
||||||
|
result.len = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
API bool AreEqual(String a, String b, unsigned ignore_case) {
|
||||||
|
if (a.len != b.len) return false;
|
||||||
|
for (int64_t i = 0; i < a.len; i++) {
|
||||||
|
char A = a.data[i];
|
||||||
|
char B = b.data[i];
|
||||||
|
if (ignore_case) {
|
||||||
|
A = ToLowerCase(A);
|
||||||
|
B = ToLowerCase(B);
|
||||||
|
}
|
||||||
|
if (A != B)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
API bool EndsWith(String a, String end, unsigned ignore_case) {
|
||||||
|
String a_end = GetPostfix(a, end.len);
|
||||||
|
bool result = AreEqual(end, a_end, ignore_case);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
API bool StartsWith(String a, String start, unsigned ignore_case) {
|
||||||
|
String a_start = GetPrefix(a, start.len);
|
||||||
|
bool result = AreEqual(start, a_start, ignore_case);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
API String Trim(String string) {
|
||||||
|
if (string.len == 0)
|
||||||
|
return string;
|
||||||
|
|
||||||
|
int64_t whitespace_begin = 0;
|
||||||
|
for (; whitespace_begin < string.len; whitespace_begin++) {
|
||||||
|
if (!IsWhitespace(string.data[whitespace_begin])) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t whitespace_end = string.len;
|
||||||
|
for (; whitespace_end != whitespace_begin; whitespace_end--) {
|
||||||
|
if (!IsWhitespace(string.data[whitespace_end - 1])) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (whitespace_begin == whitespace_end) {
|
||||||
|
string.len = 0;
|
||||||
|
} else {
|
||||||
|
string = GetSlice(string, whitespace_begin, whitespace_end);
|
||||||
|
}
|
||||||
|
|
||||||
|
return string;
|
||||||
|
}
|
||||||
|
|
||||||
|
API String TrimEnd(String string) {
|
||||||
|
int64_t whitespace_end = string.len;
|
||||||
|
for (; whitespace_end != 0; whitespace_end--) {
|
||||||
|
if (!IsWhitespace(string.data[whitespace_end - 1])) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String result = GetPrefix(string, whitespace_end);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
API String Copy(Allocator allocator, String string) {
|
||||||
|
char *copy = (char *)AllocSize(allocator, sizeof(char) * (string.len + 1));
|
||||||
|
memcpy(copy, string.data, string.len);
|
||||||
|
copy[string.len] = 0;
|
||||||
|
String result = {copy, string.len};
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
API String Copy(Allocator allocator, char *string) {
|
||||||
|
return Copy(allocator, {string, (int64_t)strlen(string)});
|
||||||
|
}
|
||||||
|
|
||||||
|
API void NormalizePathInPlace(String s) {
|
||||||
|
for (int64_t i = 0; i < s.len; i++) {
|
||||||
|
if (s.data[i] == '\\')
|
||||||
|
s.data[i] = '/';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
API String NormalizePath(Allocator allocator, String s) {
|
||||||
|
String copy = Copy(allocator, s);
|
||||||
|
NormalizePathInPlace(copy);
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
|
||||||
|
API bool Seek(String string, String find, int64_t *index_out, SeekFlag flags) {
|
||||||
|
bool ignore_case = flags & SeekFlag_IgnoreCase ? true : false;
|
||||||
|
bool result = false;
|
||||||
|
if (flags & SeekFlag_MatchFindLast) {
|
||||||
|
for (int64_t i = string.len; i != 0; i--) {
|
||||||
|
int64_t index = i - 1;
|
||||||
|
String substring = GetSlice(string, index, index + find.len);
|
||||||
|
if (AreEqual(substring, find, ignore_case)) {
|
||||||
|
if (index_out)
|
||||||
|
*index_out = index;
|
||||||
|
result = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (int64_t i = 0; i < string.len; i++) {
|
||||||
|
String substring = GetSlice(string, i, i + find.len);
|
||||||
|
if (AreEqual(substring, find, ignore_case)) {
|
||||||
|
if (index_out)
|
||||||
|
*index_out = i;
|
||||||
|
result = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
API String CutLastSlash(String *s) {
|
||||||
|
String result = *s;
|
||||||
|
Seek(*s, "/", &s->len, SeekFlag_MatchFindLast);
|
||||||
|
result = Skip(result, s->len);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
API String ChopLastSlash(String s) {
|
||||||
|
String result = s;
|
||||||
|
Seek(s, "/", &result.len, SeekFlag_MatchFindLast);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
API String ChopLastPeriod(String s) {
|
||||||
|
String result = s;
|
||||||
|
Seek(s, ".", &result.len, SeekFlag_MatchFindLast);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
API String SkipToLastSlash(String s) {
|
||||||
|
int64_t pos;
|
||||||
|
String result = s;
|
||||||
|
if (Seek(s, "/", &pos, SeekFlag_MatchFindLast)) {
|
||||||
|
result = Skip(result, pos + 1);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
API String SkipToLastPeriod(String s) {
|
||||||
|
int64_t pos;
|
||||||
|
String result = s;
|
||||||
|
if (Seek(s, ".", &pos, SeekFlag_MatchFindLast)) {
|
||||||
|
result = Skip(result, pos + 1);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
API String CutPrefix(String *string, int64_t len) {
|
||||||
|
String result = GetPrefix(*string, len);
|
||||||
|
*string = Skip(*string, len);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
API String CutPostfix(String *string, int64_t len) {
|
||||||
|
String result = GetPostfix(*string, len);
|
||||||
|
*string = Chop(*string, len);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
API String FormatV(Allocator allocator, const char *data, va_list args1) {
|
||||||
|
va_list args2;
|
||||||
|
va_copy(args2, args1);
|
||||||
|
int64_t len = stbsp_vsnprintf(0, 0, data, args2);
|
||||||
|
va_end(args2);
|
||||||
|
|
||||||
|
char *result = (char *)AllocSize(allocator, sizeof(char) * (len + 1));
|
||||||
|
stbsp_vsnprintf(result, (int)(len + 1), data, args1);
|
||||||
|
String res = {result, len};
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
API String Format(Allocator allocator, const char *data, ...) {
|
||||||
|
STRING_FORMAT(allocator, data, result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
API Array<String> Split(Allocator allocator, String string, String delimiter) {
|
||||||
|
Array<String> result = {allocator};
|
||||||
|
int64_t index = 0;
|
||||||
|
while (Seek(string, delimiter, &index)) {
|
||||||
|
String before_match = {string.data, index};
|
||||||
|
Add(&result, before_match);
|
||||||
|
string = Skip(string, index + delimiter.len);
|
||||||
|
}
|
||||||
|
Add(&result, string);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
API String Merge(Allocator allocator, Array<String> list, String separator) {
|
||||||
|
int64_t char_count = 0;
|
||||||
|
For(list) char_count += it.len;
|
||||||
|
if (char_count == 0) return {};
|
||||||
|
int64_t node_count = list.len;
|
||||||
|
|
||||||
|
int64_t base_size = (char_count + 1);
|
||||||
|
int64_t sep_size = (node_count - 1) * separator.len;
|
||||||
|
int64_t size = base_size + sep_size;
|
||||||
|
char *buff = (char *)AllocSize(allocator, sizeof(char) * (size + 1));
|
||||||
|
String string = {buff, 0};
|
||||||
|
For(list) {
|
||||||
|
Assert(string.len + it.len <= size);
|
||||||
|
memcpy(string.data + string.len, it.data, it.len);
|
||||||
|
string.len += it.len;
|
||||||
|
if (!IsLast(list, it)) {
|
||||||
|
memcpy(string.data + string.len, separator.data, separator.len);
|
||||||
|
string.len += separator.len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Assert(string.len == size - 1);
|
||||||
|
string.data[size] = 0;
|
||||||
|
return string;
|
||||||
|
}
|
||||||
|
|
||||||
|
API Int GetSize(Array<String> array) {
|
||||||
|
Int result = 0;
|
||||||
|
For (array) result += it.len;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
106
src/basic/basic_string.h
Normal file
106
src/basic/basic_string.h
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
struct String {
|
||||||
|
char *data;
|
||||||
|
int64_t len;
|
||||||
|
|
||||||
|
String() = default;
|
||||||
|
String(char *s) : data(s), len(strlen(s)) {}
|
||||||
|
String(char *s, int64_t l) : data((char *)s), len(l) {}
|
||||||
|
String(const char *s) : data((char *)s), len(strlen((char *)s)) {}
|
||||||
|
String(const char *s, int64_t l) : data((char *)s), len(l) {}
|
||||||
|
|
||||||
|
|
||||||
|
char &operator[](int64_t index) {
|
||||||
|
Assert(index < len);
|
||||||
|
return data[index];
|
||||||
|
}
|
||||||
|
char *begin() { return data; }
|
||||||
|
char *end() { return data + len; }
|
||||||
|
|
||||||
|
struct ReverseIter {
|
||||||
|
char *data;
|
||||||
|
String *arr;
|
||||||
|
|
||||||
|
ReverseIter operator++(int) {
|
||||||
|
ReverseIter ret = *this;
|
||||||
|
data -= 1;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
ReverseIter &operator++() {
|
||||||
|
data -= 1;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
char &operator*() { return data[0]; }
|
||||||
|
char *operator->() { return data; }
|
||||||
|
|
||||||
|
friend bool operator==(const ReverseIter &a, const ReverseIter &b) { return a.data == b.data; };
|
||||||
|
friend bool operator!=(const ReverseIter &a, const ReverseIter &b) { return a.data != b.data; };
|
||||||
|
|
||||||
|
ReverseIter begin() { return ReverseIter{arr->end() - 1, arr}; }
|
||||||
|
ReverseIter end() { return ReverseIter{arr->begin() - 1, arr}; }
|
||||||
|
};
|
||||||
|
ReverseIter reverse() { return {this->end() - 1, this}; }
|
||||||
|
};
|
||||||
|
|
||||||
|
API char ToLowerCase(char a);
|
||||||
|
API char ToUpperCase(char a);
|
||||||
|
API bool IsWhitespace(char w);
|
||||||
|
API bool IsAlphabetic(char a);
|
||||||
|
API bool IsIdent(char a);
|
||||||
|
API bool IsDigit(char a);
|
||||||
|
API bool IsAlphanumeric(char a);
|
||||||
|
API bool IsSymbol(char w);
|
||||||
|
API bool IsNonWord(char w);
|
||||||
|
API bool IsWord(char w);
|
||||||
|
API bool IsBrace(char c);
|
||||||
|
API bool IsParen(char c);
|
||||||
|
|
||||||
|
API bool EndsWith(String a, String end, unsigned ignore_case = false);
|
||||||
|
API bool StartsWith(String a, String start, unsigned ignore_case = false);
|
||||||
|
API bool AreEqual(String a, String b, unsigned ignore_case = false);
|
||||||
|
inline bool operator==(String a, String b) { return AreEqual(a, b); }
|
||||||
|
inline bool operator!=(String a, String b) { return !AreEqual(a, b); }
|
||||||
|
|
||||||
|
API String Trim(String string);
|
||||||
|
API String TrimEnd(String string);
|
||||||
|
API String Chop(String a, int64_t len);
|
||||||
|
API String Skip(String a, int64_t len);
|
||||||
|
API String GetPostfix(String a, int64_t len);
|
||||||
|
API String GetPrefix(String a, int64_t len);
|
||||||
|
API String GetSlice(String arr, int64_t first_index = 0, int64_t one_past_last_index = SLICE_LAST);
|
||||||
|
API String CutLastSlash(String *s);
|
||||||
|
API String ChopLastSlash(String s);
|
||||||
|
API String ChopLastPeriod(String s);
|
||||||
|
API String SkipToLastSlash(String s);
|
||||||
|
API String SkipToLastPeriod(String s);
|
||||||
|
API String CutPrefix(String *string, int64_t len);
|
||||||
|
API String CutPostfix(String *string, int64_t len);
|
||||||
|
|
||||||
|
API String Copy(Allocator allocator, String string);
|
||||||
|
API String Copy(Allocator allocator, char *string);
|
||||||
|
API void NormalizePathInPlace(String s);
|
||||||
|
API String NormalizePath(Allocator allocator, String s);
|
||||||
|
|
||||||
|
#define FmtString(string) (int)(string).len, (string).data
|
||||||
|
API String FormatV(Allocator allocator, const char *data, va_list args1);
|
||||||
|
API String Format(Allocator allocator, const char *data, ...);
|
||||||
|
#define STRING_FORMAT(allocator, data, result) \
|
||||||
|
va_list args1; \
|
||||||
|
va_start(args1, data); \
|
||||||
|
String result = FormatV(allocator, data, args1); \
|
||||||
|
va_end(args1)
|
||||||
|
|
||||||
|
|
||||||
|
typedef int SeekFlag;
|
||||||
|
enum {
|
||||||
|
SeekFlag_None = 0,
|
||||||
|
SeekFlag_IgnoreCase = 1,
|
||||||
|
SeekFlag_MatchFindLast = 2,
|
||||||
|
};
|
||||||
|
API bool Seek(String string, String find, int64_t *index_out = NULL, SeekFlag flags = SeekFlag_None);
|
||||||
|
|
||||||
|
API Array<String> Split(Allocator allocator, String string, String delimiter = " ");
|
||||||
|
API String Merge(Allocator allocator, Array<String> list, String separator);
|
||||||
|
API Int GetSize(Array<String> array);
|
||||||
422
src/basic/basic_string16.cpp
Normal file
422
src/basic/basic_string16.cpp
Normal file
@@ -0,0 +1,422 @@
|
|||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
API int64_t WideLength(char16_t *string) {
|
||||||
|
if (!string) return 0;
|
||||||
|
int64_t len = 0;
|
||||||
|
while (*string++ != 0)
|
||||||
|
len++;
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
API char16_t ToLowerCase(char16_t a) {
|
||||||
|
if (a >= u'A' && a <= u'Z') a += 32;
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
API char16_t ToUpperCase(char16_t a) {
|
||||||
|
if (a >= u'a' && a <= u'z') a -= 32;
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
API bool IsWhitespace(char16_t w) {
|
||||||
|
bool result = w == u'\n' || w == u' ' || w == u'\t' || w == u'\v' || w == u'\r';
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
API bool IsAlphabetic(char16_t a) {
|
||||||
|
bool result = (a >= u'a' && a <= u'z') || (a >= u'A' && a <= u'Z');
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
API bool IsIdent(char16_t a) {
|
||||||
|
bool result = (a >= u'a' && a <= u'z') || (a >= u'A' && a <= u'Z') || a == u'_';
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
API bool IsDigit(char16_t a) {
|
||||||
|
bool result = a >= u'0' && a <= u'9';
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
API bool IsAlphanumeric(char16_t a) {
|
||||||
|
bool result = IsDigit(a) || IsAlphabetic(a);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
API bool IsSymbol(char16_t w) {
|
||||||
|
bool result = (w >= u'!' && w <= u'/') || (w >= u':' && w <= u'@') || (w >= u'[' && w <= u'`') || (w >= u'{' && w <= u'~');
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
API bool IsNonWord(char16_t w) {
|
||||||
|
if (w == u'_') return false;
|
||||||
|
bool result = IsSymbol(w) || IsWhitespace(w);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
API bool IsWord(char16_t w) {
|
||||||
|
bool result = !IsNonWord(w);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
API bool IsBrace(char16_t c) {
|
||||||
|
bool result = c == u'{' || c == u'}';
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
API bool IsParen(char16_t c) {
|
||||||
|
bool result = c == u'(' || c == u')';
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
API String16 Chop(String16 a, int64_t len) {
|
||||||
|
len = ClampTop(len, a.len);
|
||||||
|
String16 result = {a.data, a.len - len};
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
API String16 Skip(String16 a, int64_t len) {
|
||||||
|
len = ClampTop(len, a.len);
|
||||||
|
String16 result = {a.data + len, a.len - len};
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
API String16 GetPostfix(String16 a, int64_t len) {
|
||||||
|
len = ClampTop(len, a.len);
|
||||||
|
int64_t remain_len = a.len - len;
|
||||||
|
String16 result = {a.data + remain_len, len};
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
API String16 GetPrefix(String16 a, int64_t len) {
|
||||||
|
len = ClampTop(len, a.len);
|
||||||
|
String16 result = {a.data, len};
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
API String16 GetSlice(String16 arr, int64_t first_index, int64_t one_past_last_index) {
|
||||||
|
// Negative indexes work in python style, they return you the index counting from end of list
|
||||||
|
if (one_past_last_index == SLICE_LAST) one_past_last_index = arr.len;
|
||||||
|
if (one_past_last_index < 0) one_past_last_index = arr.len + one_past_last_index;
|
||||||
|
|
||||||
|
if (first_index == SLICE_LAST) first_index = arr.len;
|
||||||
|
if (first_index < 0) first_index = arr.len + first_index;
|
||||||
|
|
||||||
|
String16 result = {arr.data, arr.len};
|
||||||
|
if (arr.len > 0) {
|
||||||
|
if (one_past_last_index > first_index) {
|
||||||
|
first_index = ClampTop(first_index, arr.len - 1);
|
||||||
|
one_past_last_index = ClampTop(one_past_last_index, arr.len);
|
||||||
|
result.data += first_index;
|
||||||
|
result.len = one_past_last_index - first_index;
|
||||||
|
} else {
|
||||||
|
result.len = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
API bool AreEqual(String16 a, String16 b, unsigned ignore_case) {
|
||||||
|
if (a.len != b.len) return false;
|
||||||
|
for (int64_t i = 0; i < a.len; i++) {
|
||||||
|
char16_t A = a.data[i];
|
||||||
|
char16_t B = b.data[i];
|
||||||
|
if (ignore_case) {
|
||||||
|
A = ToLowerCase(A);
|
||||||
|
B = ToLowerCase(B);
|
||||||
|
}
|
||||||
|
if (A != B)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
API bool EndsWith(String16 a, String16 end, unsigned ignore_case) {
|
||||||
|
String16 a_end = GetPostfix(a, end.len);
|
||||||
|
bool result = AreEqual(end, a_end, ignore_case);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
API bool StartsWith(String16 a, String16 start, unsigned ignore_case) {
|
||||||
|
String16 a_start = GetPrefix(a, start.len);
|
||||||
|
bool result = AreEqual(start, a_start, ignore_case);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
API String16 Trim(String16 string) {
|
||||||
|
if (string.len == 0)
|
||||||
|
return string;
|
||||||
|
|
||||||
|
int64_t whitespace_begin = 0;
|
||||||
|
for (; whitespace_begin < string.len; whitespace_begin++) {
|
||||||
|
if (!IsWhitespace(string.data[whitespace_begin])) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t whitespace_end = string.len;
|
||||||
|
for (; whitespace_end != whitespace_begin; whitespace_end--) {
|
||||||
|
if (!IsWhitespace(string.data[whitespace_end - 1])) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (whitespace_begin == whitespace_end) {
|
||||||
|
string.len = 0;
|
||||||
|
} else {
|
||||||
|
string = GetSlice(string, whitespace_begin, whitespace_end);
|
||||||
|
}
|
||||||
|
|
||||||
|
return string;
|
||||||
|
}
|
||||||
|
|
||||||
|
API String16 TrimEnd(String16 string) {
|
||||||
|
int64_t whitespace_end = string.len;
|
||||||
|
for (; whitespace_end != 0; whitespace_end--) {
|
||||||
|
if (!IsWhitespace(string.data[whitespace_end - 1])) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String16 result = GetPrefix(string, whitespace_end);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
API String16 Copy16(Allocator allocator, String16 string) {
|
||||||
|
char16_t *copy = (char16_t *)AllocSize(allocator, sizeof(char16_t) * (string.len + 1));
|
||||||
|
memcpy(copy, string.data, string.len);
|
||||||
|
copy[string.len] = 0;
|
||||||
|
String16 result = {copy, string.len};
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
API String16 Copy16(Allocator allocator, char16_t *string) {
|
||||||
|
return Copy16(allocator, {string, (int64_t)WideLength(string)});
|
||||||
|
}
|
||||||
|
|
||||||
|
API void NormalizePathInPlace(String16 s) {
|
||||||
|
for (int64_t i = 0; i < s.len; i++) {
|
||||||
|
if (s.data[i] == u'\\')
|
||||||
|
s.data[i] = u'/';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
API String16 NormalizePath(Allocator allocator, String16 s) {
|
||||||
|
String16 copy = Copy16(allocator, s);
|
||||||
|
NormalizePathInPlace(copy);
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
|
||||||
|
API bool Seek(String16 string, String16 find, int64_t *index_out, SeekFlag flags) {
|
||||||
|
bool ignore_case = flags & SeekFlag_IgnoreCase ? true : false;
|
||||||
|
bool result = false;
|
||||||
|
if (flags & SeekFlag_MatchFindLast) {
|
||||||
|
for (int64_t i = string.len; i != 0; i--) {
|
||||||
|
int64_t index = i - 1;
|
||||||
|
String16 substring = GetSlice(string, index, index + find.len);
|
||||||
|
if (AreEqual(substring, find, ignore_case)) {
|
||||||
|
if (index_out)
|
||||||
|
*index_out = index;
|
||||||
|
result = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (int64_t i = 0; i < string.len; i++) {
|
||||||
|
String16 substring = GetSlice(string, i, i + find.len);
|
||||||
|
if (AreEqual(substring, find, ignore_case)) {
|
||||||
|
if (index_out)
|
||||||
|
*index_out = i;
|
||||||
|
result = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
API String16 CutLastSlash(String16 *s) {
|
||||||
|
String16 result = *s;
|
||||||
|
Seek(*s, u"/", &s->len, SeekFlag_MatchFindLast);
|
||||||
|
result = Skip(result, s->len);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
API String16 ChopLastSlash(String16 s) {
|
||||||
|
String16 result = s;
|
||||||
|
Seek(s, u"/", &result.len, SeekFlag_MatchFindLast);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
API String16 ChopLastPeriod(String16 s) {
|
||||||
|
String16 result = s;
|
||||||
|
Seek(s, u".", &result.len, SeekFlag_MatchFindLast);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
API String16 SkipToLastSlash(String16 s) {
|
||||||
|
int64_t pos;
|
||||||
|
String16 result = s;
|
||||||
|
if (Seek(s, u"/", &pos, SeekFlag_MatchFindLast)) {
|
||||||
|
result = Skip(result, pos + 1);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
API String16 SkipToLastPeriod(String16 s) {
|
||||||
|
int64_t pos;
|
||||||
|
String16 result = s;
|
||||||
|
if (Seek(s, u".", &pos, SeekFlag_MatchFindLast)) {
|
||||||
|
result = Skip(result, pos + 1);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
API String16 CutPrefix(String16 *string, int64_t len) {
|
||||||
|
String16 result = GetPrefix(*string, len);
|
||||||
|
*string = Skip(*string, len);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
API String16 CutPostfix(String16 *string, int64_t len) {
|
||||||
|
String16 result = GetPostfix(*string, len);
|
||||||
|
*string = Chop(*string, len);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
API String16 Format16V(Allocator allocator, const char *data, va_list args1) {
|
||||||
|
Scratch scratch;
|
||||||
|
va_list args2;
|
||||||
|
va_copy(args2, args1);
|
||||||
|
int64_t len = stbsp_vsnprintf(0, 0, data, args2);
|
||||||
|
va_end(args2);
|
||||||
|
|
||||||
|
char *result = (char *)AllocSize(scratch, sizeof(char) * (len + 1));
|
||||||
|
stbsp_vsnprintf(result, (int)(len + 1), data, args1);
|
||||||
|
String res = {result, len};
|
||||||
|
String16 res16 = ToString16(allocator, res);
|
||||||
|
return res16;
|
||||||
|
}
|
||||||
|
|
||||||
|
API String16 Format16(Allocator allocator, const char *data, ...) {
|
||||||
|
Scratch scratch;
|
||||||
|
STRING_FORMAT(scratch, data, result);
|
||||||
|
String16 result16 = ToString16(allocator, result);
|
||||||
|
return result16;
|
||||||
|
}
|
||||||
|
|
||||||
|
API Array<String16> Split(Allocator allocator, String16 string, String16 delimiter) {
|
||||||
|
Array<String16> result = {allocator};
|
||||||
|
int64_t index = 0;
|
||||||
|
while (Seek(string, delimiter, &index)) {
|
||||||
|
String16 before_match = {string.data, index};
|
||||||
|
Add(&result, before_match);
|
||||||
|
string = Skip(string, index + delimiter.len);
|
||||||
|
}
|
||||||
|
Add(&result, string);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
API String16 Merge(Allocator allocator, Array<String16> list, String16 separator) {
|
||||||
|
int64_t char_count = 0;
|
||||||
|
For(list) char_count += it.len;
|
||||||
|
if (char_count == 0) return {};
|
||||||
|
int64_t node_count = list.len;
|
||||||
|
|
||||||
|
int64_t base_size = (char_count + 1);
|
||||||
|
int64_t sep_size = (node_count - 1) * separator.len;
|
||||||
|
int64_t size = base_size + sep_size;
|
||||||
|
char16_t *buff = (char16_t *)AllocSize(allocator, sizeof(char16_t) * (size + 1));
|
||||||
|
String16 string = {buff, 0};
|
||||||
|
For(list) {
|
||||||
|
Assert(string.len + it.len <= size);
|
||||||
|
memcpy(string.data + string.len, it.data, it.len);
|
||||||
|
string.len += it.len;
|
||||||
|
if (!IsLast(list, it)) {
|
||||||
|
memcpy(string.data + string.len, separator.data, separator.len);
|
||||||
|
string.len += separator.len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Assert(string.len == size - 1);
|
||||||
|
string.data[size] = 0;
|
||||||
|
return string;
|
||||||
|
}
|
||||||
|
|
||||||
|
API Int GetSize(Array<String16> array) {
|
||||||
|
Int result = 0;
|
||||||
|
For (array) result += it.len;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
API String16 SkipNumberEx(String16 *string) {
|
||||||
|
String16 col = {string->data, 0};
|
||||||
|
for (int64_t i = 0; i < string->len; i += 1) {
|
||||||
|
if (IsDigit(string->data[i])) {
|
||||||
|
col.len += 1;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*string = Skip(*string, col.len);
|
||||||
|
return col;
|
||||||
|
}
|
||||||
|
|
||||||
|
API Int SkipNumber(String16 *string) {
|
||||||
|
String16 col = SkipNumberEx(string);
|
||||||
|
if (col.len == 0) return -1;
|
||||||
|
Scratch scratch;
|
||||||
|
String num_string = ToString(scratch, col);
|
||||||
|
Int result = strtoll(num_string.data, NULL, 10);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
API String16 SkipUntil(String16 *string, String16 str) {
|
||||||
|
String16 begin = *string;
|
||||||
|
begin.len = 0;
|
||||||
|
for (; string->len; begin.len += 1) {
|
||||||
|
String16 match = GetPrefix(*string, str.len);
|
||||||
|
if (StartsWith(match, str)) break;
|
||||||
|
*string = Skip(*string, 1);
|
||||||
|
}
|
||||||
|
return begin;
|
||||||
|
}
|
||||||
|
|
||||||
|
API String16 SkipWhitespace(String16 *string) {
|
||||||
|
String16 begin = {string->data, 0};
|
||||||
|
for (Int i = 0; i < string->len; i += 1) {
|
||||||
|
if (!IsWhitespace(string->data[i])) break;
|
||||||
|
*string = Skip(*string, 1);
|
||||||
|
begin.len += 1;
|
||||||
|
}
|
||||||
|
return begin;
|
||||||
|
}
|
||||||
|
|
||||||
|
// chop this - :324
|
||||||
|
API String16 ChopNumberEx(String16 *string) {
|
||||||
|
String16 col = {};
|
||||||
|
for (int64_t i = string->len - 1; i >= 0; i -= 1) {
|
||||||
|
if (IsDigit(string->data[i])) {
|
||||||
|
col.data = string->data + i;
|
||||||
|
col.len += 1;
|
||||||
|
} else if (string->data[i] == L':') {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*string = Chop(*string, col.len + 1);
|
||||||
|
return col;
|
||||||
|
}
|
||||||
|
|
||||||
|
API Int ChopNumber(String16 *string) {
|
||||||
|
Scratch scratch;
|
||||||
|
String16 col = ChopNumberEx(string);
|
||||||
|
if (col.len == 0) return -1;
|
||||||
|
String num_string = ToString(scratch, col);
|
||||||
|
Int result = strtoll(num_string.data, NULL, 10) - 1;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
104
src/basic/basic_string16.h
Normal file
104
src/basic/basic_string16.h
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
API int64_t WideLength(char16_t *string);
|
||||||
|
|
||||||
|
struct String16 {
|
||||||
|
char16_t *data;
|
||||||
|
int64_t len;
|
||||||
|
|
||||||
|
String16() = default;
|
||||||
|
String16(char16_t *s) : data(s), len(WideLength(s)) {}
|
||||||
|
String16(char16_t *s, int64_t l) : data((char16_t *)s), len(l) {}
|
||||||
|
String16(const char16_t *s) : data((char16_t *)s), len(WideLength((char16_t *)s)) {}
|
||||||
|
String16(const char16_t *s, int64_t l) : data((char16_t *)s), len(l) {}
|
||||||
|
|
||||||
|
|
||||||
|
char16_t &operator[](int64_t index) {
|
||||||
|
Assert(index < len);
|
||||||
|
return data[index];
|
||||||
|
}
|
||||||
|
char16_t *begin() { return data; }
|
||||||
|
char16_t *end() { return data + len; }
|
||||||
|
|
||||||
|
struct ReverseIter {
|
||||||
|
char16_t *data;
|
||||||
|
String16 *arr;
|
||||||
|
|
||||||
|
ReverseIter operator++(int) {
|
||||||
|
ReverseIter ret = *this;
|
||||||
|
data -= 1;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
ReverseIter &operator++() {
|
||||||
|
data -= 1;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
char16_t &operator*() { return data[0]; }
|
||||||
|
char16_t *operator->() { return data; }
|
||||||
|
|
||||||
|
friend bool operator==(const ReverseIter &a, const ReverseIter &b) { return a.data == b.data; };
|
||||||
|
friend bool operator!=(const ReverseIter &a, const ReverseIter &b) { return a.data != b.data; };
|
||||||
|
|
||||||
|
ReverseIter begin() { return ReverseIter{arr->end() - 1, arr}; }
|
||||||
|
ReverseIter end() { return ReverseIter{arr->begin() - 1, arr}; }
|
||||||
|
};
|
||||||
|
ReverseIter reverse() { return {this->end() - 1, this}; }
|
||||||
|
};
|
||||||
|
|
||||||
|
API char16_t ToLowerCase(char16_t a);
|
||||||
|
API char16_t ToUpperCase(char16_t a);
|
||||||
|
API bool IsWhitespace(char16_t w);
|
||||||
|
API bool IsAlphabetic(char16_t a);
|
||||||
|
API bool IsIdent(char16_t a);
|
||||||
|
API bool IsDigit(char16_t a);
|
||||||
|
API bool IsAlphanumeric(char16_t a);
|
||||||
|
API bool IsSymbol(char16_t w);
|
||||||
|
API bool IsNonWord(char16_t w);
|
||||||
|
API bool IsWord(char16_t w);
|
||||||
|
API bool IsBrace(char16_t c);
|
||||||
|
API bool IsParen(char16_t c);
|
||||||
|
|
||||||
|
API bool EndsWith(String16 a, String16 end, unsigned ignore_case = false);
|
||||||
|
API bool StartsWith(String16 a, String16 start, unsigned ignore_case = false);
|
||||||
|
API bool AreEqual(String16 a, String16 b, unsigned ignore_case = false);
|
||||||
|
inline bool operator==(String16 a, String16 b) { return AreEqual(a, b); }
|
||||||
|
inline bool operator!=(String16 a, String16 b) { return !AreEqual(a, b); }
|
||||||
|
|
||||||
|
API String16 Trim(String16 string);
|
||||||
|
API String16 TrimEnd(String16 string);
|
||||||
|
API String16 Chop(String16 a, int64_t len);
|
||||||
|
API String16 Skip(String16 a, int64_t len);
|
||||||
|
API String16 GetPostfix(String16 a, int64_t len);
|
||||||
|
API String16 GetPrefix(String16 a, int64_t len);
|
||||||
|
API String16 GetSlice(String16 arr, int64_t first_index = 0, int64_t one_past_last_index = SLICE_LAST);
|
||||||
|
API String16 CutLastSlash(String16 *s);
|
||||||
|
API String16 ChopLastSlash(String16 s);
|
||||||
|
API String16 ChopLastPeriod(String16 s);
|
||||||
|
API String16 SkipToLastSlash(String16 s);
|
||||||
|
API String16 SkipToLastPeriod(String16 s);
|
||||||
|
API String16 CutPrefix(String16 *string, int64_t len);
|
||||||
|
API String16 CutPostfix(String16 *string, int64_t len);
|
||||||
|
|
||||||
|
// @todo: think about this API, for parsing, maybe redesign or something
|
||||||
|
API String16 SkipNumberEx(String16 *string);
|
||||||
|
API Int SkipNumber(String16 *string);
|
||||||
|
API String16 SkipUntil(String16 *string, String16 str);
|
||||||
|
API String16 SkipWhitespace(String16 *string);
|
||||||
|
API String16 ChopNumberEx(String16 *string);
|
||||||
|
API Int ChopNumber(String16 *string);
|
||||||
|
|
||||||
|
API String16 Copy16(Allocator allocator, String16 string);
|
||||||
|
API String16 Copy16(Allocator allocator, char16_t *string, int64_t len);
|
||||||
|
API String16 Copy16(Allocator allocator, char *string);
|
||||||
|
API void NormalizePathInPlace(String16 s);
|
||||||
|
API String16 NormalizePath(Allocator allocator, String16 s);
|
||||||
|
|
||||||
|
API String16 Format16V(Allocator allocator, const char *data, va_list args1);
|
||||||
|
API String16 Format16(Allocator allocator, const char *data, ...);
|
||||||
|
|
||||||
|
API bool Seek(String16 string, String16 find, int64_t *index_out = NULL, SeekFlag flags = SeekFlag_None);
|
||||||
|
|
||||||
|
API Array<String16> Split(Allocator allocator, String16 string, String16 delimiter = u" ");
|
||||||
|
API String16 Merge(Allocator allocator, Array<String16> list, String16 separator);
|
||||||
|
API Int GetSize(Array<String16> array);
|
||||||
185
src/basic/basic_table.h
Normal file
185
src/basic/basic_table.h
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
|
||||||
|
/*
|
||||||
|
Hash table implementation:
|
||||||
|
Pointers to values
|
||||||
|
Open adressing
|
||||||
|
Linear Probing
|
||||||
|
Power of 2
|
||||||
|
Robin Hood hashing
|
||||||
|
Resizes on high probe count (min max load factor)
|
||||||
|
|
||||||
|
Hash 0 is reserved for empty hash table entry
|
||||||
|
*/
|
||||||
|
template <class Value>
|
||||||
|
struct Table {
|
||||||
|
struct Entry {
|
||||||
|
uint64_t hash;
|
||||||
|
uint64_t key;
|
||||||
|
size_t distance;
|
||||||
|
Value value;
|
||||||
|
};
|
||||||
|
|
||||||
|
Allocator allocator;
|
||||||
|
size_t len, cap;
|
||||||
|
Entry *values;
|
||||||
|
|
||||||
|
static const size_t max_load_factor = 80;
|
||||||
|
static const size_t min_load_factor = 50;
|
||||||
|
static const size_t significant_distance = 8;
|
||||||
|
|
||||||
|
// load factor calculation was rearranged
|
||||||
|
// to get rid of division:
|
||||||
|
//> 100 * len / cap = load_factor
|
||||||
|
//> len * 100 = load_factor * cap
|
||||||
|
inline bool reached_load_factor(size_t lfactor) {
|
||||||
|
return (len + 1) * 100 >= lfactor * cap;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool is_empty(Entry *entry) { return entry->hash == 0; }
|
||||||
|
inline bool is_occupied(Entry *entry) { return entry->hash != 0; }
|
||||||
|
|
||||||
|
void reserve(size_t size) {
|
||||||
|
Assert(size > cap && "New size is smaller then original size");
|
||||||
|
Assert(IsPowerOf2(size));
|
||||||
|
if (!allocator.proc) allocator = GetSystemAllocator();
|
||||||
|
|
||||||
|
Entry *old_values = values;
|
||||||
|
size_t old_cap = cap;
|
||||||
|
|
||||||
|
values = (Entry *)AllocSize(allocator, sizeof(Entry) * size);
|
||||||
|
for (size_t i = 0; i < size; i += 1) values[i] = {};
|
||||||
|
cap = size;
|
||||||
|
|
||||||
|
Assert(!(old_values == 0 && len != 0));
|
||||||
|
if (len == 0) {
|
||||||
|
if (old_values) Dealloc(allocator, &old_values);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
len = 0;
|
||||||
|
for (size_t i = 0; i < old_cap; i += 1) {
|
||||||
|
Entry *it = old_values + i;
|
||||||
|
if (is_occupied(it)) {
|
||||||
|
insert(it->key, it->value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Dealloc(allocator, &old_values);
|
||||||
|
}
|
||||||
|
|
||||||
|
Entry *get_table_entry(uint64_t key) {
|
||||||
|
if (len == 0) return 0;
|
||||||
|
uint64_t hash = HashBytes(&key, sizeof(key));
|
||||||
|
if (hash == 0) hash += 1;
|
||||||
|
uint64_t index = WrapAroundPowerOf2(hash, cap);
|
||||||
|
uint64_t i = index;
|
||||||
|
uint64_t distance = 0;
|
||||||
|
for (;;) {
|
||||||
|
Entry *it = values + i;
|
||||||
|
if (distance > it->distance) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (it->hash == hash && it->key == key) {
|
||||||
|
return it;
|
||||||
|
}
|
||||||
|
|
||||||
|
distance += 1;
|
||||||
|
i = WrapAroundPowerOf2(i + 1, cap);
|
||||||
|
if (i == index) return 0;
|
||||||
|
}
|
||||||
|
Assert(!"Invalid codepath");
|
||||||
|
}
|
||||||
|
|
||||||
|
void insert(uint64_t key, const Value &value) {
|
||||||
|
if (reached_load_factor(max_load_factor)) {
|
||||||
|
if (cap == 0) cap = 16; // 32 cause cap*2
|
||||||
|
reserve(cap * 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t hash = HashBytes(&key, sizeof(key));
|
||||||
|
if (hash == 0) hash += 1;
|
||||||
|
uint64_t index = WrapAroundPowerOf2(hash, cap);
|
||||||
|
uint64_t i = index;
|
||||||
|
Entry to_insert = {hash, key, 0, value};
|
||||||
|
for (;;) {
|
||||||
|
Entry *it = values + i;
|
||||||
|
if (is_empty(it)) {
|
||||||
|
*it = to_insert;
|
||||||
|
len += 1;
|
||||||
|
// If we have more then 8 consecutive items we try to resize
|
||||||
|
if (to_insert.distance > 8 && reached_load_factor(min_load_factor)) {
|
||||||
|
reserve(cap * 2);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (it->hash == hash && it->key == key) {
|
||||||
|
*it = to_insert;
|
||||||
|
// If we have more then 8 consecutive items we try to resize
|
||||||
|
if (to_insert.distance > 8 && reached_load_factor(min_load_factor)) {
|
||||||
|
reserve(cap * 2);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Robin hood hashing
|
||||||
|
if (to_insert.distance > it->distance) {
|
||||||
|
Entry temp = to_insert;
|
||||||
|
to_insert = *it;
|
||||||
|
*it = temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
to_insert.distance += 1;
|
||||||
|
i = WrapAroundPowerOf2(i + 1, cap);
|
||||||
|
Assert(i != index && "Did a full 360 through a hash table, no good :( that shouldnt be possible");
|
||||||
|
}
|
||||||
|
Assert(!"Invalid codepath");
|
||||||
|
}
|
||||||
|
|
||||||
|
void remove(uint64_t key) {
|
||||||
|
Entry *entry = get_table_entry(key);
|
||||||
|
entry->hash = 0;
|
||||||
|
entry->distance = 0;
|
||||||
|
len -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Value *get(uint64_t key) {
|
||||||
|
Entry *v = get_table_entry(key);
|
||||||
|
if (!v) return 0;
|
||||||
|
return &v->value;
|
||||||
|
}
|
||||||
|
|
||||||
|
Value get(uint64_t key, Value default_value) {
|
||||||
|
Entry *v = get_table_entry(key);
|
||||||
|
if (!v) return default_value;
|
||||||
|
return v->value;
|
||||||
|
}
|
||||||
|
|
||||||
|
Value *get(String s) {
|
||||||
|
uint64_t hash = HashBytes(s.data, (unsigned)s.len);
|
||||||
|
return get(hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
Value get(String s, Value default_value) {
|
||||||
|
uint64_t hash = HashBytes(s.data, (unsigned)s.len);
|
||||||
|
return get(hash, default_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void put(String s, const Value &value) {
|
||||||
|
uint64_t hash = HashBytes(s.data, (unsigned)s.len);
|
||||||
|
insert(hash, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset() {
|
||||||
|
len = 0;
|
||||||
|
for (size_t i = 0; i < cap; i += 1) {
|
||||||
|
Entry *it = values + i;
|
||||||
|
it->hash = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void dealloc() {
|
||||||
|
Dealloc(allocator, &values);
|
||||||
|
len = 0;
|
||||||
|
cap = 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
237
src/basic/basic_unicode.cpp
Normal file
237
src/basic/basic_unicode.cpp
Normal file
@@ -0,0 +1,237 @@
|
|||||||
|
API UTF32Result UTF16ToUTF32(uint16_t *c, int64_t max_advance) {
|
||||||
|
UTF32Result result;
|
||||||
|
MemoryZero(&result, sizeof(result));
|
||||||
|
if (max_advance >= 1) {
|
||||||
|
result.advance = 1;
|
||||||
|
result.out_str = c[0];
|
||||||
|
if (c[0] >= 0xD800 && c[0] <= 0xDBFF && c[1] >= 0xDC00 && c[1] <= 0xDFFF) {
|
||||||
|
if (max_advance >= 2) {
|
||||||
|
result.out_str = 0x10000;
|
||||||
|
result.out_str += (uint32_t)(c[0] & 0x03FF) << 10u | (c[1] & 0x03FF);
|
||||||
|
result.advance = 2;
|
||||||
|
} else
|
||||||
|
result.error = 2;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result.error = 1;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
API UTF8Result UTF32ToUTF8(uint32_t codepoint) {
|
||||||
|
UTF8Result result;
|
||||||
|
MemoryZero(&result, sizeof(result));
|
||||||
|
|
||||||
|
if (codepoint <= 0x7F) {
|
||||||
|
result.len = 1;
|
||||||
|
result.out_str[0] = (char)codepoint;
|
||||||
|
} else if (codepoint <= 0x7FF) {
|
||||||
|
result.len = 2;
|
||||||
|
result.out_str[0] = 0xc0 | (0x1f & (codepoint >> 6));
|
||||||
|
result.out_str[1] = 0x80 | (0x3f & codepoint);
|
||||||
|
} else if (codepoint <= 0xFFFF) { // 16 bit word
|
||||||
|
result.len = 3;
|
||||||
|
result.out_str[0] = 0xe0 | (0xf & (codepoint >> 12)); // 4 bits
|
||||||
|
result.out_str[1] = 0x80 | (0x3f & (codepoint >> 6)); // 6 bits
|
||||||
|
result.out_str[2] = 0x80 | (0x3f & codepoint); // 6 bits
|
||||||
|
} else if (codepoint <= 0x10FFFF) { // 21 bit word
|
||||||
|
result.len = 4;
|
||||||
|
result.out_str[0] = 0xf0 | (0x7 & (codepoint >> 18)); // 3 bits
|
||||||
|
result.out_str[1] = 0x80 | (0x3f & (codepoint >> 12)); // 6 bits
|
||||||
|
result.out_str[2] = 0x80 | (0x3f & (codepoint >> 6)); // 6 bits
|
||||||
|
result.out_str[3] = 0x80 | (0x3f & codepoint); // 6 bits
|
||||||
|
} else {
|
||||||
|
result.error = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
API UTF32Result UTF8ToUTF32(uint8_t *c, int64_t max_advance) {
|
||||||
|
UTF32Result result;
|
||||||
|
MemoryZero(&result, sizeof(result));
|
||||||
|
|
||||||
|
if ((c[0] & 0x80) == 0) { // Check if leftmost zero of first byte is unset
|
||||||
|
if (max_advance >= 1) {
|
||||||
|
result.out_str = c[0];
|
||||||
|
result.advance = 1;
|
||||||
|
} else result.error = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
else if ((c[0] & 0xe0) == 0xc0) {
|
||||||
|
if ((c[1] & 0xc0) == 0x80) { // Continuation byte required
|
||||||
|
if (max_advance >= 2) {
|
||||||
|
result.out_str = (uint32_t)(c[0] & 0x1f) << 6u | (c[1] & 0x3f);
|
||||||
|
result.advance = 2;
|
||||||
|
} else result.error = 2;
|
||||||
|
} else result.error = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
else if ((c[0] & 0xf0) == 0xe0) {
|
||||||
|
if ((c[1] & 0xc0) == 0x80 && (c[2] & 0xc0) == 0x80) { // Two continuation bytes required
|
||||||
|
if (max_advance >= 3) {
|
||||||
|
result.out_str = (uint32_t)(c[0] & 0xf) << 12u | (uint32_t)(c[1] & 0x3f) << 6u | (c[2] & 0x3f);
|
||||||
|
result.advance = 3;
|
||||||
|
} else result.error = 3;
|
||||||
|
} else result.error = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
else if ((c[0] & 0xf8) == 0xf0) {
|
||||||
|
if ((c[1] & 0xc0) == 0x80 && (c[2] & 0xc0) == 0x80 && (c[3] & 0xc0) == 0x80) { // Three continuation bytes required
|
||||||
|
if (max_advance >= 4) {
|
||||||
|
result.out_str = (uint32_t)(c[0] & 0xf) << 18u | (uint32_t)(c[1] & 0x3f) << 12u | (uint32_t)(c[2] & 0x3f) << 6u | (uint32_t)(c[3] & 0x3f);
|
||||||
|
result.advance = 4;
|
||||||
|
} else result.error = 4;
|
||||||
|
} else result.error = 4;
|
||||||
|
} else result.error = 4;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
API UTF16Result UTF32ToUTF16(uint32_t codepoint) {
|
||||||
|
UTF16Result result;
|
||||||
|
MemoryZero(&result, sizeof(result));
|
||||||
|
if (codepoint < 0x10000) {
|
||||||
|
result.out_str[0] = (uint16_t)codepoint;
|
||||||
|
result.out_str[1] = 0;
|
||||||
|
result.len = 1;
|
||||||
|
} else if (codepoint <= 0x10FFFF) {
|
||||||
|
uint32_t code = (codepoint - 0x10000);
|
||||||
|
result.out_str[0] = (uint16_t)(0xD800 | (code >> 10));
|
||||||
|
result.out_str[1] = (uint16_t)(0xDC00 | (code & 0x3FF));
|
||||||
|
result.len = 2;
|
||||||
|
} else {
|
||||||
|
result.error = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define UTF__HANDLE_DECODE_ERROR(question_mark, I) \
|
||||||
|
{ \
|
||||||
|
if (outlen < buffer_size - 1) buffer[outlen++] = (question_mark); \
|
||||||
|
i += I; \
|
||||||
|
}
|
||||||
|
|
||||||
|
API int64_t CreateCharFromWidechar(char *buffer, int64_t buffer_size, char16_t *in, int64_t inlen) {
|
||||||
|
int64_t outlen = 0;
|
||||||
|
for (int64_t i = 0; i < inlen && in[i];) {
|
||||||
|
UTF32Result decode = UTF16ToUTF32((uint16_t *)(in + i), (int64_t)(inlen - i));
|
||||||
|
if (!decode.error) {
|
||||||
|
i += decode.advance;
|
||||||
|
UTF8Result encode = UTF32ToUTF8(decode.out_str);
|
||||||
|
if (!encode.error) {
|
||||||
|
for (int64_t j = 0; j < encode.len; j++) {
|
||||||
|
if (outlen < buffer_size - 1) {
|
||||||
|
buffer[outlen++] = encode.out_str[j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else UTF__HANDLE_DECODE_ERROR('?', 0);
|
||||||
|
} else UTF__HANDLE_DECODE_ERROR('?', 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer[outlen] = 0;
|
||||||
|
return outlen;
|
||||||
|
}
|
||||||
|
|
||||||
|
API int64_t CreateWidecharFromChar(char16_t *buffer, int64_t buffer_size, char *in, int64_t inlen) {
|
||||||
|
int64_t outlen = 0;
|
||||||
|
for (int64_t i = 0; i < inlen;) {
|
||||||
|
UTF32Result decode = UTF8ToUTF32((uint8_t *)(in + i), (int64_t)(inlen - i));
|
||||||
|
if (!decode.error) {
|
||||||
|
i += decode.advance;
|
||||||
|
UTF16Result encode = UTF32ToUTF16(decode.out_str);
|
||||||
|
if (!encode.error) {
|
||||||
|
for (int64_t j = 0; j < encode.len; j++) {
|
||||||
|
if (outlen < buffer_size - 1) {
|
||||||
|
buffer[outlen++] = encode.out_str[j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else UTF__HANDLE_DECODE_ERROR(0x003f, 0);
|
||||||
|
} else UTF__HANDLE_DECODE_ERROR(0x003f, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer[outlen] = 0;
|
||||||
|
return outlen;
|
||||||
|
}
|
||||||
|
|
||||||
|
API bool IsValid(UTF8Iter &iter) {
|
||||||
|
return iter.item;
|
||||||
|
}
|
||||||
|
|
||||||
|
API void Advance(UTF8Iter *iter) {
|
||||||
|
iter->i += iter->utf8_codepoint_byte_size;
|
||||||
|
UTF32Result r = UTF8ToUTF32((uint8_t *)(iter->data + iter->i), iter->len - iter->i);
|
||||||
|
if (r.error) {
|
||||||
|
iter->item = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
iter->utf8_codepoint_byte_size = r.advance;
|
||||||
|
iter->item = r.out_str;
|
||||||
|
}
|
||||||
|
|
||||||
|
API UTF8Iter IterateUTF8Ex(char *data, int64_t len) {
|
||||||
|
UTF8Iter result;
|
||||||
|
MemoryZero(&result, sizeof(result));
|
||||||
|
result.data = data;
|
||||||
|
result.len = len;
|
||||||
|
if (len) Advance(&result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
API UTF8Iter IterateUTF8(char *data) {
|
||||||
|
int64_t length = 0;
|
||||||
|
while (data[length]) length += 1;
|
||||||
|
return IterateUTF8Ex(data, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
API UTF8Iter IterateUTF8(String string) {
|
||||||
|
return IterateUTF8Ex(string.data, string.len);
|
||||||
|
}
|
||||||
|
|
||||||
|
API bool IsUTF8ContinuationByte(char c) {
|
||||||
|
char result = (c & 0b11000000) == 0b10000000;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
API String16 ToString16(Allocator allocator, String string) {
|
||||||
|
char16_t *buffer = (char16_t *)AllocSize(allocator, sizeof(char16_t) * (string.len + 1));
|
||||||
|
int64_t size = CreateWidecharFromChar(buffer, string.len + 1, string.data, string.len);
|
||||||
|
String16 result = {buffer, size};
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
API String16 ToString16(Allocator allocator, char *in_string) {
|
||||||
|
String string(in_string);
|
||||||
|
String16 result = ToString16(allocator, string);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
API char16_t *ToWidechar(Allocator allocator, String string) {
|
||||||
|
String16 result = ToString16(allocator, string);
|
||||||
|
return result.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
API String ToString(Allocator allocator, String16 string) {
|
||||||
|
Assert(sizeof(char16_t) == 2);
|
||||||
|
|
||||||
|
int64_t buffer_size = (string.len + 1) * 2;
|
||||||
|
char *buffer = (char *)AllocSize(allocator, buffer_size);
|
||||||
|
int64_t size = CreateCharFromWidechar(buffer, buffer_size, string.data, string.len);
|
||||||
|
String result = {buffer, size};
|
||||||
|
|
||||||
|
Assert(size < buffer_size);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
API String ToString(Allocator allocator, char16_t *string, int64_t len) {
|
||||||
|
return ToString(allocator, {string, len});
|
||||||
|
}
|
||||||
|
|
||||||
|
API String ToString(Allocator allocator, char16_t *wstring) {
|
||||||
|
int64_t size = WideLength(wstring);
|
||||||
|
String result = ToString(allocator, {wstring, size});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
60
src/basic/basic_unicode.h
Normal file
60
src/basic/basic_unicode.h
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
struct UTF32Result {
|
||||||
|
uint32_t out_str;
|
||||||
|
int64_t advance;
|
||||||
|
int64_t error;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct UTF8Result {
|
||||||
|
uint8_t out_str[4];
|
||||||
|
int64_t len;
|
||||||
|
int64_t error;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct UTF16Result {
|
||||||
|
uint16_t out_str[2];
|
||||||
|
int64_t len;
|
||||||
|
int64_t error;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct UTF8Iter {
|
||||||
|
char *data;
|
||||||
|
int64_t len;
|
||||||
|
int64_t utf8_codepoint_byte_size;
|
||||||
|
int64_t i;
|
||||||
|
uint32_t item;
|
||||||
|
|
||||||
|
UTF8Iter &operator++() {
|
||||||
|
void Advance(UTF8Iter * iter);
|
||||||
|
Advance(this);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
friend bool operator!=(const UTF8Iter &a, const UTF8Iter &b) { return a.item != b.item; }
|
||||||
|
UTF8Iter &operator*() { return *this; }
|
||||||
|
UTF8Iter begin() {
|
||||||
|
UTF8Iter IterateUTF8Ex(char *data, int64_t len);
|
||||||
|
return {IterateUTF8Ex(data, len)};
|
||||||
|
}
|
||||||
|
UTF8Iter end() { return {}; }
|
||||||
|
};
|
||||||
|
|
||||||
|
API UTF32Result UTF16ToUTF32(uint16_t *c, int64_t max_advance);
|
||||||
|
API UTF8Result UTF32ToUTF8(uint32_t codepoint);
|
||||||
|
API UTF32Result UTF8ToUTF32(uint8_t *c, int64_t max_advance);
|
||||||
|
API UTF16Result UTF32ToUTF16(uint32_t codepoint);
|
||||||
|
|
||||||
|
API int64_t CreateCharFromWidechar(char *buffer, int64_t buffer_size, char16_t *in, int64_t inlen);
|
||||||
|
API int64_t CreateWidecharFromChar(char16_t *buffer, int64_t buffer_size, char *in, int64_t inlen);
|
||||||
|
API char16_t *ToWidechar(Allocator allocator, String string);
|
||||||
|
API String16 ToString16(Allocator allocator, String string);
|
||||||
|
API String ToString(Allocator allocator, String16 string);
|
||||||
|
API String ToString(Allocator allocator, char16_t *string, int64_t len);
|
||||||
|
API String ToString(Allocator allocator, char16_t *wstring);
|
||||||
|
|
||||||
|
API bool IsValid(UTF8Iter &iter);
|
||||||
|
API void Advance(UTF8Iter *iter);
|
||||||
|
API UTF8Iter IterateUTF8Ex(char *data, int64_t len);
|
||||||
|
API UTF8Iter IterateUTF8(char *data);
|
||||||
|
API UTF8Iter IterateUTF8(String string);
|
||||||
|
API bool IsUTF8ContinuationByte(char c);
|
||||||
@@ -1,41 +1,3 @@
|
|||||||
struct Vec2 {
|
|
||||||
float x;
|
|
||||||
float y;
|
|
||||||
};
|
|
||||||
|
|
||||||
union Vec4 {
|
|
||||||
struct {
|
|
||||||
float x;
|
|
||||||
float y;
|
|
||||||
float z;
|
|
||||||
float w;
|
|
||||||
};
|
|
||||||
struct {
|
|
||||||
float r;
|
|
||||||
float g;
|
|
||||||
float b;
|
|
||||||
float a;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
union Vec3 {
|
|
||||||
struct {
|
|
||||||
float x;
|
|
||||||
float y;
|
|
||||||
float z;
|
|
||||||
};
|
|
||||||
struct {
|
|
||||||
float r;
|
|
||||||
float g;
|
|
||||||
float b;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Rect2 {
|
|
||||||
Vec2 min;
|
|
||||||
Vec2 max;
|
|
||||||
};
|
|
||||||
|
|
||||||
Vec2 GetSize(Rect2 r) {
|
Vec2 GetSize(Rect2 r) {
|
||||||
Vec2 result = {r.max.x - r.min.x, r.max.y - r.min.y};
|
Vec2 result = {r.max.x - r.min.x, r.max.y - r.min.y};
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@@ -1,23 +1,3 @@
|
|||||||
struct Vec2I {
|
|
||||||
Int x;
|
|
||||||
Int y;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Rect2I {
|
|
||||||
Vec2I min;
|
|
||||||
Vec2I max;
|
|
||||||
};
|
|
||||||
|
|
||||||
union Color {
|
|
||||||
struct {
|
|
||||||
uint8_t r;
|
|
||||||
uint8_t g;
|
|
||||||
uint8_t b;
|
|
||||||
uint8_t a;
|
|
||||||
};
|
|
||||||
uint32_t value;
|
|
||||||
};
|
|
||||||
|
|
||||||
Vec2I GetSize(Rect2I r) {
|
Vec2I GetSize(Rect2I r) {
|
||||||
Vec2I result = {r.max.x - r.min.x, r.max.y - r.min.y};
|
Vec2I result = {r.max.x - r.min.x, r.max.y - r.min.y};
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
1928
src/basic/stb_sprintf.h
Normal file
1928
src/basic/stb_sprintf.h
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,341 +0,0 @@
|
|||||||
char16_t ToLowerCase(char16_t a) {
|
|
||||||
if (a >= 'A' && a <= 'Z') a += 32;
|
|
||||||
return a;
|
|
||||||
}
|
|
||||||
|
|
||||||
char16_t ToUpperCase(char16_t a) {
|
|
||||||
if (a >= 'a' && a <= 'z') a -= 32;
|
|
||||||
return a;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsWhitespace(char16_t w) {
|
|
||||||
bool result = w == '\n' || w == ' ' || w == '\t' || w == '\v' || w == '\r';
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsSymbol(char16_t w) {
|
|
||||||
bool result = (w >= '!' && w <= '/') || (w >= ':' && w <= '@') || (w >= '[' && w <= '`') || (w >= '{' && w <= '~');
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsNonWord(char16_t w) {
|
|
||||||
if (w == '_') return false;
|
|
||||||
bool result = IsSymbol(w) || IsWhitespace(w);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsWord(char16_t w) {
|
|
||||||
bool result = !IsNonWord(w);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsBrace(char16_t c) {
|
|
||||||
bool result = c == '{' || c == '}';
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsParen(char16_t c) {
|
|
||||||
bool result = c == '(' || c == ')';
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsAlphabetic(char16_t a) {
|
|
||||||
bool result = (a >= 'a' && a <= 'z') || (a >= 'A' && a <= 'Z');
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsIdent(char16_t a) {
|
|
||||||
bool result = (a >= 'a' && a <= 'z') || (a >= 'A' && a <= 'Z') || a == '_';
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsDigit(char16_t a) {
|
|
||||||
bool result = a >= '0' && a <= '9';
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsAlphanumeric(char16_t a) {
|
|
||||||
bool result = IsDigit(a) || IsAlphabetic(a);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AreEqual(String16 a, String16 b, unsigned ignore_case = false) {
|
|
||||||
if (a.len != b.len) return false;
|
|
||||||
for (int64_t i = 0; i < a.len; i++) {
|
|
||||||
char16_t A = a.data[i];
|
|
||||||
char16_t B = b.data[i];
|
|
||||||
if (ignore_case) {
|
|
||||||
A = ToLowerCase(A);
|
|
||||||
B = ToLowerCase(B);
|
|
||||||
}
|
|
||||||
if (A != B)
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
inline bool operator==(String16 a, String16 b) { return AreEqual(a, b); }
|
|
||||||
inline bool operator!=(String16 a, String16 b) { return !AreEqual(a, b); }
|
|
||||||
|
|
||||||
char16_t GetChar(String16 string, int64_t i) {
|
|
||||||
char16_t result = 0;
|
|
||||||
if (i < string.len) result = string.data[i];
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
String16 Merge(Allocator allocator, Array<String16> list, String16 separator = " ") {
|
|
||||||
int64_t char_count = 0;
|
|
||||||
For(list) char_count += it.len;
|
|
||||||
if (char_count == 0) return {};
|
|
||||||
int64_t node_count = list.len;
|
|
||||||
|
|
||||||
int64_t base_size = (char_count + 1);
|
|
||||||
int64_t sep_size = (node_count - 1) * separator.len;
|
|
||||||
int64_t size = base_size + sep_size;
|
|
||||||
char16_t *buff = (char16_t *)AllocSize(allocator, sizeof(char16_t) * (size + 1));
|
|
||||||
String16 string = {buff, 0};
|
|
||||||
For(list) {
|
|
||||||
Assert(string.len + it.len <= size);
|
|
||||||
memcpy(string.data + string.len, it.data, it.len * sizeof(char16_t));
|
|
||||||
string.len += it.len;
|
|
||||||
if (!IsLast(list, it)) {
|
|
||||||
memcpy(string.data + string.len, separator.data, separator.len * sizeof(char16_t));
|
|
||||||
string.len += separator.len;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Assert(string.len == size - 1);
|
|
||||||
string.data[size] = 0;
|
|
||||||
return string;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Seek(String16 string, String16 find, int64_t *index_out = NULL, SeekFlag flags = SeekFlag_None) {
|
|
||||||
bool ignore_case = flags & SeekFlag_IgnoreCase ? true : false;
|
|
||||||
bool result = false;
|
|
||||||
if (flags & SeekFlag_MatchFindLast) {
|
|
||||||
for (int64_t i = string.len; i != 0; i--) {
|
|
||||||
int64_t index = i - 1;
|
|
||||||
String16 substring = GetSlice(string, index, index + find.len);
|
|
||||||
if (AreEqual(substring, find, ignore_case)) {
|
|
||||||
if (index_out)
|
|
||||||
*index_out = index;
|
|
||||||
result = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (int64_t i = 0; i < string.len; i++) {
|
|
||||||
String16 substring = GetSlice(string, i, i + find.len);
|
|
||||||
if (AreEqual(substring, find, ignore_case)) {
|
|
||||||
if (index_out)
|
|
||||||
*index_out = i;
|
|
||||||
result = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
String16 CutLastSlash(String16 *s) {
|
|
||||||
String16 result = *s;
|
|
||||||
Seek(*s, u"/", &s->len, SeekFlag_MatchFindLast);
|
|
||||||
result = Skip(result, s->len);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
String16 ChopLastSlash(String16 s) {
|
|
||||||
String16 result = s;
|
|
||||||
Seek(s, u"/", &result.len, SeekFlag_MatchFindLast);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
String16 ChopLastPeriod(String16 s) {
|
|
||||||
String16 result = s;
|
|
||||||
Seek(s, u".", &result.len, SeekFlag_MatchFindLast);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
String16 SkipToLastSlash(String16 s) {
|
|
||||||
int64_t pos;
|
|
||||||
String16 result = s;
|
|
||||||
if (Seek(s, u"/", &pos, SeekFlag_MatchFindLast)) {
|
|
||||||
result = Skip(result, pos + 1);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
String16 SkipToLastPeriod(String16 s) {
|
|
||||||
int64_t pos;
|
|
||||||
String16 result = s;
|
|
||||||
if (Seek(s, u".", &pos, SeekFlag_MatchFindLast)) {
|
|
||||||
result = Skip(result, pos + 1);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
String16 CutPrefix(String16 *string, int64_t len) {
|
|
||||||
String16 result = GetPrefix(*string, len);
|
|
||||||
*string = Skip(*string, len);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
String16 CutPostfix(String16 *string, int64_t len) {
|
|
||||||
String16 result = GetPostfix(*string, len);
|
|
||||||
*string = Chop(*string, len);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
String16 Trim(String16 string) {
|
|
||||||
if (string.len == 0)
|
|
||||||
return string;
|
|
||||||
|
|
||||||
int64_t whitespace_begin = 0;
|
|
||||||
for (; whitespace_begin < string.len; whitespace_begin++) {
|
|
||||||
if (!IsWhitespace(string.data[whitespace_begin])) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int64_t whitespace_end = string.len;
|
|
||||||
for (; whitespace_end != whitespace_begin; whitespace_end--) {
|
|
||||||
if (!IsWhitespace(string.data[whitespace_end - 1])) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (whitespace_begin == whitespace_end) {
|
|
||||||
string.len = 0;
|
|
||||||
} else {
|
|
||||||
string = GetSlice(string, whitespace_begin, whitespace_end);
|
|
||||||
}
|
|
||||||
|
|
||||||
return string;
|
|
||||||
}
|
|
||||||
|
|
||||||
String16 TrimEnd(String16 string) {
|
|
||||||
int64_t whitespace_end = string.len;
|
|
||||||
for (; whitespace_end != 0; whitespace_end--) {
|
|
||||||
if (!IsWhitespace(string.data[whitespace_end - 1])) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String16 result = GetPrefix(string, whitespace_end);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool EndsWith(String16 a, String16 end, unsigned ignore_case = false) {
|
|
||||||
String16 a_end = GetPostfix(a, end.len);
|
|
||||||
bool result = AreEqual(end, a_end, ignore_case);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool StartsWith(String16 a, String16 start, unsigned ignore_case = false) {
|
|
||||||
String16 a_start = GetPrefix(a, start.len);
|
|
||||||
bool result = AreEqual(start, a_start, ignore_case);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
String16 Copy16(Allocator allocator, String16 string) {
|
|
||||||
char16_t *copy = (char16_t *)AllocSize(allocator, sizeof(char16_t) * (string.len + 1));
|
|
||||||
memcpy(copy, string.data, string.len);
|
|
||||||
copy[string.len] = 0;
|
|
||||||
String16 result = {copy, string.len};
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
String16 Copy16(Allocator allocator, char16_t *string) {
|
|
||||||
String16 s = {string, (int64_t)WideLength(string)};
|
|
||||||
return Copy(allocator, s);
|
|
||||||
}
|
|
||||||
|
|
||||||
String16 Concat(Allocator allocator, String16 a, String16 b) {
|
|
||||||
char16_t *str = AllocArray(allocator, char16_t, a.len + b.len + 1);
|
|
||||||
MemoryCopy(str, a.data, a.len * 2);
|
|
||||||
MemoryCopy(str + a.len, b.data, b.len * 2);
|
|
||||||
str[a.len + b.len] = 0;
|
|
||||||
return {str, a.len + b.len};
|
|
||||||
}
|
|
||||||
|
|
||||||
void NormalizePathInPlace(String16 s) {
|
|
||||||
for (int64_t i = 0; i < s.len; i++) {
|
|
||||||
if (s.data[i] == u'\\')
|
|
||||||
s.data[i] = u'/';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String16 NormalizePath(Allocator allocator, String16 s) {
|
|
||||||
String16 copy = Copy(allocator, s);
|
|
||||||
NormalizePathInPlace(copy);
|
|
||||||
return copy;
|
|
||||||
}
|
|
||||||
|
|
||||||
String16 SkipNumberEx(String16 *string) {
|
|
||||||
String16 col = {string->data, 0};
|
|
||||||
for (int64_t i = 0; i < string->len; i += 1) {
|
|
||||||
if (IsDigit(string->data[i])) {
|
|
||||||
col.len += 1;
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*string = Skip(*string, col.len);
|
|
||||||
return col;
|
|
||||||
}
|
|
||||||
|
|
||||||
Int SkipNumber(String16 *string) {
|
|
||||||
String16 col = SkipNumberEx(string);
|
|
||||||
if (col.len == 0) return -1;
|
|
||||||
Scratch scratch;
|
|
||||||
String num_string = ToString(scratch, col);
|
|
||||||
Int result = strtoll(num_string.data, NULL, 10);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
String16 SkipUntil(String16 *string, String16 str) {
|
|
||||||
String16 begin = *string;
|
|
||||||
begin.len = 0;
|
|
||||||
for (; string->len; begin.len += 1) {
|
|
||||||
String16 match = GetPrefix(*string, str.len);
|
|
||||||
if (StartsWith(match, str)) break;
|
|
||||||
*string = Skip(*string, 1);
|
|
||||||
}
|
|
||||||
return begin;
|
|
||||||
}
|
|
||||||
|
|
||||||
String16 SkipWhitespace(String16 *string) {
|
|
||||||
String16 begin = {string->data, 0};
|
|
||||||
for (Int i = 0; i < string->len; i += 1) {
|
|
||||||
if (!IsWhitespace(string->data[i])) break;
|
|
||||||
*string = Skip(*string, 1);
|
|
||||||
begin.len += 1;
|
|
||||||
}
|
|
||||||
return begin;
|
|
||||||
}
|
|
||||||
|
|
||||||
// chop this - :324
|
|
||||||
String16 ChopNumberEx(String16 *string) {
|
|
||||||
String16 col = {};
|
|
||||||
for (int64_t i = string->len - 1; i >= 0; i -= 1) {
|
|
||||||
if (IsDigit(string->data[i])) {
|
|
||||||
col.data = string->data + i;
|
|
||||||
col.len += 1;
|
|
||||||
} else if (string->data[i] == L':') {
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*string = Chop(*string, col.len + 1);
|
|
||||||
return col;
|
|
||||||
}
|
|
||||||
|
|
||||||
Int ChopNumber(String16 *string) {
|
|
||||||
Scratch scratch;
|
|
||||||
String16 col = ChopNumberEx(string);
|
|
||||||
if (col.len == 0) return -1;
|
|
||||||
String num_string = ToString(scratch, col);
|
|
||||||
Int result = strtoll(num_string.data, NULL, 10) - 1;
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
#define WORK_FUNCTION(name) void name(void *data)
|
|
||||||
typedef WORK_FUNCTION(WorkQueueCallback);
|
|
||||||
|
|
||||||
struct WorkQueueEntry {
|
|
||||||
WorkQueueCallback *callback;
|
|
||||||
void *data;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ThreadCtx {
|
|
||||||
int thread_index;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct WorkQueue {
|
|
||||||
int32_t thread_count;
|
|
||||||
WorkQueueEntry entries[256];
|
|
||||||
int64_t volatile index_to_write;
|
|
||||||
int64_t volatile index_to_read;
|
|
||||||
int64_t volatile completion_index;
|
|
||||||
int64_t volatile completion_goal;
|
|
||||||
void *semaphore;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ThreadStartupInfo {
|
|
||||||
uint32_t thread_id;
|
|
||||||
int32_t thread_index;
|
|
||||||
WorkQueue *queue;
|
|
||||||
};
|
|
||||||
|
|
||||||
void PushWork(WorkQueue *wq, void *data, WorkQueueCallback *callback);
|
|
||||||
void InitWorkQueue(WorkQueue *queue, uint32_t thread_count, ThreadStartupInfo *info);
|
|
||||||
void WaitUntilCompletion(WorkQueue *wq);
|
|
||||||
|
|
||||||
int64_t AtomicIncrement(volatile int64_t *i);
|
|
||||||
@@ -1,417 +0,0 @@
|
|||||||
#include "filesystem.h"
|
|
||||||
#include <dirent.h>
|
|
||||||
#include <sys/stat.h>
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <sys/syscall.h>
|
|
||||||
#include <sys/wait.h>
|
|
||||||
#include <sys/mman.h>
|
|
||||||
#include <linux/limits.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <time.h>
|
|
||||||
#include <errno.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <spawn.h>
|
|
||||||
#include <poll.h>
|
|
||||||
|
|
||||||
void (*Error)(const char *, ...);
|
|
||||||
|
|
||||||
void *VReserve(size_t size) {
|
|
||||||
void *result = mmap(0, size, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, (off_t)0);
|
|
||||||
return result == (void *)-1 ? 0 : result;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool VCommit(void *p, size_t size) {
|
|
||||||
int result = mprotect(p, size, PROT_READ | PROT_WRITE);
|
|
||||||
return result == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool VRelease(void *p, size_t size) {
|
|
||||||
int result = munmap(p, size);
|
|
||||||
return result == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool VDecommit(void *p, size_t size) {
|
|
||||||
mprotect(p, size, PROT_NONE);
|
|
||||||
madvise(p, size, MADV_DONTNEED);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void InitOS(void (*error_proc)(const char *, ...)) {
|
|
||||||
Error = error_proc;
|
|
||||||
}
|
|
||||||
|
|
||||||
String ReadFile(Allocator al, String path) {
|
|
||||||
Scratch scratch(al);
|
|
||||||
String null_term = Copy(scratch, path);
|
|
||||||
String result = {};
|
|
||||||
FILE *f = fopen(null_term.data, "rb");
|
|
||||||
if (f) {
|
|
||||||
fseek(f, 0, SEEK_END);
|
|
||||||
result.len = ftell(f);
|
|
||||||
fseek(f, 0, SEEK_SET);
|
|
||||||
|
|
||||||
result.data = (char *)AllocSize(al, result.len + 1);
|
|
||||||
fread(result.data, result.len, 1, f);
|
|
||||||
result.data[result.len] = 0;
|
|
||||||
|
|
||||||
fclose(f);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WriteFile(String path, String data) {
|
|
||||||
Scratch scratch;
|
|
||||||
String null_term = Copy(scratch, path);
|
|
||||||
bool result = false;
|
|
||||||
FILE *f = fopen((const char *)null_term.data, "w");
|
|
||||||
if (f) {
|
|
||||||
size_t written = fwrite(data.data, 1, data.len, f);
|
|
||||||
if (written == data.len) {
|
|
||||||
result = true;
|
|
||||||
}
|
|
||||||
fclose(f);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DeleteFile(String path) {
|
|
||||||
Scratch scratch;
|
|
||||||
String null_term = Copy(scratch, path);
|
|
||||||
int result = unlink(null_term.data);
|
|
||||||
return result == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
MakeDirResult MakeDir(String path) {
|
|
||||||
Scratch scratch;
|
|
||||||
String null_term = Copy(scratch, path);
|
|
||||||
int error = mkdir(null_term.data, 0755);
|
|
||||||
MakeDirResult result = MakeDirResult_Success;
|
|
||||||
if (error != 0) {
|
|
||||||
result = MakeDirResult_ErrorOther;
|
|
||||||
if (errno == EEXIST) result = MakeDirResult_Exists;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
int64_t GetFileModTime(String path) {
|
|
||||||
Scratch scratch;
|
|
||||||
String null_term = Copy(scratch, path);
|
|
||||||
struct stat attrib = {};
|
|
||||||
stat(null_term.data, &attrib);
|
|
||||||
struct timespec ts = attrib.st_mtim;
|
|
||||||
int64_t result = (((int64_t)ts.tv_sec) * 1000000ll) + ((int64_t)ts.tv_nsec) / 1000ll;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
String GetAbsolutePath(Allocator al, String path) {
|
|
||||||
Scratch scratch(al);
|
|
||||||
String null_term = Copy(scratch, path);
|
|
||||||
char *buffer = AllocArray(al, char, PATH_MAX);
|
|
||||||
realpath(null_term.data, buffer);
|
|
||||||
String result = buffer;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool FileExists(String path) {
|
|
||||||
Scratch scratch;
|
|
||||||
String null_term = Copy(scratch, path);
|
|
||||||
bool result = false;
|
|
||||||
if (access((char *)null_term.data, F_OK) == 0) {
|
|
||||||
result = true;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsDir(String path) {
|
|
||||||
Scratch scratch;
|
|
||||||
String null_term = Copy(scratch, path);
|
|
||||||
struct stat s;
|
|
||||||
if (stat(null_term.data, &s) != 0)
|
|
||||||
return false;
|
|
||||||
return S_ISDIR(s.st_mode);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsFile(String path) {
|
|
||||||
Scratch scratch;
|
|
||||||
String null_term = Copy(scratch, path);
|
|
||||||
struct stat s;
|
|
||||||
if (stat(null_term.data, &s) != 0)
|
|
||||||
return false;
|
|
||||||
return S_ISREG(s.st_mode);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsAbsolute(String path) {
|
|
||||||
bool result = path.len && path.data[0] == '/';
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
String GetWorkingDir(Allocator al) {
|
|
||||||
char *buffer = AllocArray(al, char, PATH_MAX);
|
|
||||||
char *cwd = getcwd(buffer, PATH_MAX);
|
|
||||||
return cwd;
|
|
||||||
}
|
|
||||||
|
|
||||||
String GetExePath(Allocator al) {
|
|
||||||
char *buffer = AllocArray(al, char, PATH_MAX);
|
|
||||||
readlink("/proc/self/exe", buffer, PATH_MAX);
|
|
||||||
return buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
String GetExeDir(Allocator al) {
|
|
||||||
Scratch scratch(al);
|
|
||||||
String exe_path = GetExePath(scratch);
|
|
||||||
String dir = ChopLastSlash(exe_path);
|
|
||||||
String result = Copy(al, dir);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
double get_time_in_micros(void) {
|
|
||||||
struct timespec spec;
|
|
||||||
clock_gettime(CLOCK_MONOTONIC, &spec);
|
|
||||||
return (((double)spec.tv_sec) * 1000000) + (((double)spec.tv_nsec) / 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsValid(const FileIter &it) {
|
|
||||||
return it.is_valid;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Advance(FileIter *it) {
|
|
||||||
struct dirent *file = NULL;
|
|
||||||
while ((file = readdir((DIR *)it->dir)) != NULL) {
|
|
||||||
if (file->d_name[0] == '.' && file->d_name[1] == '.' && file->d_name[2] == 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (file->d_name[0] == '.' && file->d_name[1] == 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
it->is_directory = file->d_type == DT_DIR;
|
|
||||||
it->filename = Copy(it->allocator, file->d_name);
|
|
||||||
|
|
||||||
const char *dir_char_ending = it->is_directory ? "/" : "";
|
|
||||||
const char *separator = it->path.data[it->path.len - 1] == '/' ? "" : "/";
|
|
||||||
it->relative_path = Format(it->allocator, "%.*s%s%s%s", FmtString(it->path), separator, file->d_name, dir_char_ending);
|
|
||||||
it->absolute_path = GetAbsolutePath(it->allocator, it->relative_path);
|
|
||||||
if (it->is_directory) it->absolute_path = Format(it->allocator, "%.*s/", FmtString(it->absolute_path));
|
|
||||||
it->is_valid = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
it->is_valid = false;
|
|
||||||
closedir((DIR *)it->dir);
|
|
||||||
}
|
|
||||||
|
|
||||||
FileIter IterateFiles(Allocator alo, String path) {
|
|
||||||
FileIter it = {};
|
|
||||||
it.allocator = alo;
|
|
||||||
it.path = path;
|
|
||||||
Scratch scratch(alo);
|
|
||||||
String null_term = Copy(scratch, path);
|
|
||||||
it.dir = (void *)opendir((char *)null_term.data);
|
|
||||||
if (it.dir) {
|
|
||||||
Advance(&it);
|
|
||||||
}
|
|
||||||
return it;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct UnixProcess {
|
|
||||||
pid_t pid;
|
|
||||||
int child_stdout_read;
|
|
||||||
int stdin_write;
|
|
||||||
};
|
|
||||||
|
|
||||||
Array<char *> SplitCommand(Allocator allocator, String command_line) {
|
|
||||||
Array<char *> cmd = {allocator};
|
|
||||||
|
|
||||||
String curr = {};
|
|
||||||
for (int i = 0; i < command_line.len; i += 1) {
|
|
||||||
if (command_line.data[i] == ' ') {
|
|
||||||
if (curr.len > 0) {
|
|
||||||
Add(&cmd, Copy(allocator, curr).data);
|
|
||||||
curr = {};
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (curr.len == 0) {
|
|
||||||
curr.data = command_line.data + i;
|
|
||||||
}
|
|
||||||
curr.len += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (curr.len > 0) {
|
|
||||||
Add(&cmd, Copy(allocator, curr).data);
|
|
||||||
}
|
|
||||||
|
|
||||||
return cmd;
|
|
||||||
}
|
|
||||||
|
|
||||||
Process SpawnProcess(String command_line, String working_dir, String write_stdin, Array<String> enviroment) {
|
|
||||||
Scratch scratch;
|
|
||||||
const int PIPE_READ = 0;
|
|
||||||
const int PIPE_WRITE = 1;
|
|
||||||
bool error = false;
|
|
||||||
|
|
||||||
char *buffer = AllocArray(scratch, char, 4096);
|
|
||||||
chdir(working_dir.data);
|
|
||||||
getcwd(buffer, 4096);
|
|
||||||
defer { chdir(buffer); };
|
|
||||||
|
|
||||||
|
|
||||||
Process process = {};
|
|
||||||
UnixProcess *plat = (UnixProcess *)&process.platform;
|
|
||||||
Array<char *> args = SplitCommand(scratch, command_line);
|
|
||||||
Array<char *> env = {scratch};
|
|
||||||
|
|
||||||
For (enviroment) {
|
|
||||||
Add(&env, Copy(scratch, it).data);
|
|
||||||
}
|
|
||||||
|
|
||||||
int stdout_desc[2] = {};
|
|
||||||
int stdin_desc[2] = {};
|
|
||||||
posix_spawn_file_actions_t actions = {};
|
|
||||||
|
|
||||||
if (posix_spawn_file_actions_init(&actions) != 0) {
|
|
||||||
Error("Libc function failed: posix_spawn_file_actions_init, with error: %s", strerror(errno));
|
|
||||||
return process;
|
|
||||||
}
|
|
||||||
defer {
|
|
||||||
posix_spawn_file_actions_destroy(&actions);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (pipe(stdout_desc) == -1) {
|
|
||||||
Error("Libc function failed: pipe, with error: %s", strerror(errno));
|
|
||||||
return process;
|
|
||||||
}
|
|
||||||
defer {
|
|
||||||
if (error) {
|
|
||||||
close(stdout_desc[PIPE_READ]);
|
|
||||||
close(stdout_desc[PIPE_WRITE]);
|
|
||||||
} else {
|
|
||||||
close(stdout_desc[PIPE_WRITE]);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (pipe(stdin_desc) == -1) {
|
|
||||||
Error("Libc function failed: pipe, with error: %s", strerror(errno));
|
|
||||||
return process;
|
|
||||||
}
|
|
||||||
defer {
|
|
||||||
if (error) {
|
|
||||||
close(stdin_desc[PIPE_READ]);
|
|
||||||
close(stdin_desc[PIPE_WRITE]);
|
|
||||||
} else {
|
|
||||||
close(stdin_desc[PIPE_READ]);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
error = posix_spawn_file_actions_addclose(&actions, stdout_desc[PIPE_READ]) != 0;
|
|
||||||
if (error) {
|
|
||||||
Error("Libc function failed: posix_spawn_file_actions_addclose, with error: %s", strerror(errno));
|
|
||||||
return process;
|
|
||||||
}
|
|
||||||
|
|
||||||
error = posix_spawn_file_actions_adddup2(&actions, stdout_desc[PIPE_WRITE], STDOUT_FILENO) != 0;
|
|
||||||
if (error) {
|
|
||||||
Error("Libc function failed: posix_spawn_file_actions_adddup2 STDOUT_FILENO, with error: %s", strerror(errno));
|
|
||||||
return process;
|
|
||||||
}
|
|
||||||
|
|
||||||
error = posix_spawn_file_actions_adddup2(&actions, stdout_desc[PIPE_WRITE], STDERR_FILENO) != 0;
|
|
||||||
if (error) {
|
|
||||||
Error("Libc function failed: posix_spawn_file_actions_adddup2 STDERR_FILENO, with error: %s", strerror(errno));
|
|
||||||
return process;
|
|
||||||
}
|
|
||||||
|
|
||||||
error = posix_spawn_file_actions_addclose(&actions, stdin_desc[PIPE_WRITE]) != 0;
|
|
||||||
if (error) {
|
|
||||||
Error("Libc function failed: posix_spawn_file_actions_addclose, with error: %s", strerror(errno));
|
|
||||||
return process;
|
|
||||||
}
|
|
||||||
|
|
||||||
error = posix_spawn_file_actions_adddup2(&actions, stdin_desc[PIPE_READ], STDIN_FILENO) != 0;
|
|
||||||
if (error) {
|
|
||||||
Error("Libc function failed: posix_spawn_file_actions_adddup2 STDIN_FILENO, with error: %s", strerror(errno));
|
|
||||||
return process;
|
|
||||||
}
|
|
||||||
|
|
||||||
pid_t process_pid = 0;
|
|
||||||
error = posix_spawnp(&process_pid, args[0], &actions, NULL, args.data, env.data) != 0;
|
|
||||||
if (error) {
|
|
||||||
Error("Libc function failed: failed to create process\n, with error: %s", strerror(errno));
|
|
||||||
return process;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
plat->child_stdout_read = stdout_desc[PIPE_READ];
|
|
||||||
plat->stdin_write = stdin_desc[PIPE_WRITE];
|
|
||||||
plat->pid = process_pid;
|
|
||||||
|
|
||||||
if (write_stdin.len) {
|
|
||||||
WriteStdin(&process, write_stdin);
|
|
||||||
CloseStdin(&process);
|
|
||||||
}
|
|
||||||
|
|
||||||
process.id = process_pid;
|
|
||||||
process.is_valid = true;
|
|
||||||
return process;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsValid(Process *process) {
|
|
||||||
UnixProcess *plat = (UnixProcess *)&process->platform;
|
|
||||||
if (process->is_valid == false) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
int status = 0;
|
|
||||||
pollfd p = {};
|
|
||||||
p.fd = plat->child_stdout_read;
|
|
||||||
p.events = POLLRDHUP | POLLERR | POLLHUP | POLLNVAL;
|
|
||||||
int res = poll(&p, 1, 0);
|
|
||||||
if (res > 0) {
|
|
||||||
pid_t result = waitpid(plat->pid, &status, 0);
|
|
||||||
process->exit_code = WEXITSTATUS(status);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void KillProcess(Process *process) {
|
|
||||||
Assert(process->is_valid);
|
|
||||||
UnixProcess *plat = (UnixProcess *)process->platform;
|
|
||||||
kill(plat->pid, SIGKILL);
|
|
||||||
process->exit_code = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
String PollStdout(Allocator allocator, Process *process, bool force_read) {
|
|
||||||
Assert(process->is_valid);
|
|
||||||
UnixProcess *plat = (UnixProcess *)process->platform;
|
|
||||||
|
|
||||||
String result = {};
|
|
||||||
result.data = AllocArray(allocator, char, 16 * 4096);
|
|
||||||
|
|
||||||
pollfd p = {};
|
|
||||||
p.fd = plat->child_stdout_read;
|
|
||||||
p.events = POLLIN;
|
|
||||||
int res = poll(&p, 1, 0);
|
|
||||||
if (res > 0 || force_read) {
|
|
||||||
result.len = read(plat->child_stdout_read, result.data, 4 * 4096);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
void WriteStdin(Process *process, String string) {
|
|
||||||
if (string.len == 0) return;
|
|
||||||
Assert(process->is_valid);
|
|
||||||
|
|
||||||
UnixProcess *plat = (UnixProcess *)process->platform;
|
|
||||||
ssize_t size = write(plat->stdin_write, string.data, string.len);
|
|
||||||
|
|
||||||
Assert(size == string.len);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CloseStdin(Process *process) {
|
|
||||||
UnixProcess *plat = (UnixProcess *)process->platform;
|
|
||||||
close(plat->stdin_write);
|
|
||||||
}
|
|
||||||
@@ -1,260 +0,0 @@
|
|||||||
#include "filesystem.h"
|
|
||||||
#include <dirent.h>
|
|
||||||
#include <sys/stat.h>
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <sys/syscall.h>
|
|
||||||
#include <sys/wait.h>
|
|
||||||
#include <sys/mman.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <time.h>
|
|
||||||
#include <errno.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <spawn.h>
|
|
||||||
#include <poll.h>
|
|
||||||
#include <emscripten.h>
|
|
||||||
#define PATH_MAX 1024
|
|
||||||
|
|
||||||
EM_JS(void, JS_Breakpoint, (), {
|
|
||||||
debugger;
|
|
||||||
})
|
|
||||||
|
|
||||||
void (*Error)(const char *, ...);
|
|
||||||
|
|
||||||
void *VReserve(size_t size) {
|
|
||||||
InvalidCodepath();
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool VCommit(void *p, size_t size) {
|
|
||||||
InvalidCodepath();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool VRelease(void *p, size_t size) {
|
|
||||||
InvalidCodepath();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool VDecommit(void *p, size_t size) {
|
|
||||||
InvalidCodepath();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void InitOS(void (*error_proc)(const char *, ...)) {
|
|
||||||
Error = error_proc;
|
|
||||||
}
|
|
||||||
|
|
||||||
String ReadFile(Allocator al, String path) {
|
|
||||||
Scratch scratch(al);
|
|
||||||
String null_term = Copy(scratch, path);
|
|
||||||
String result = {};
|
|
||||||
FILE *f = fopen(null_term.data, "rb");
|
|
||||||
if (f) {
|
|
||||||
fseek(f, 0, SEEK_END);
|
|
||||||
result.len = ftell(f);
|
|
||||||
fseek(f, 0, SEEK_SET);
|
|
||||||
|
|
||||||
result.data = (char *)AllocSize(al, result.len + 1);
|
|
||||||
fread(result.data, result.len, 1, f);
|
|
||||||
result.data[result.len] = 0;
|
|
||||||
|
|
||||||
fclose(f);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WriteFile(String path, String data) {
|
|
||||||
Scratch scratch;
|
|
||||||
String null_term = Copy(scratch, path);
|
|
||||||
bool result = false;
|
|
||||||
FILE *f = fopen((const char *)null_term.data, "w");
|
|
||||||
if (f) {
|
|
||||||
size_t written = fwrite(data.data, 1, data.len, f);
|
|
||||||
if (written == data.len) {
|
|
||||||
result = true;
|
|
||||||
}
|
|
||||||
fclose(f);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DeleteFile(String path) {
|
|
||||||
Scratch scratch;
|
|
||||||
String null_term = Copy(scratch, path);
|
|
||||||
int result = unlink(null_term.data);
|
|
||||||
return result == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
MakeDirResult MakeDir(String path) {
|
|
||||||
Scratch scratch;
|
|
||||||
String null_term = Copy(scratch, path);
|
|
||||||
int error = mkdir(null_term.data, 0755);
|
|
||||||
MakeDirResult result = MakeDirResult_Success;
|
|
||||||
if (error != 0) {
|
|
||||||
result = MakeDirResult_ErrorOther;
|
|
||||||
if (errno == EEXIST) result = MakeDirResult_Exists;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
int64_t GetFileModTime(String path) {
|
|
||||||
Scratch scratch;
|
|
||||||
String null_term = Copy(scratch, path);
|
|
||||||
struct stat attrib = {};
|
|
||||||
stat(null_term.data, &attrib);
|
|
||||||
struct timespec ts = attrib.st_mtim;
|
|
||||||
int64_t result = (((int64_t)ts.tv_sec) * 1000000ll) + ((int64_t)ts.tv_nsec) / 1000ll;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
String GetAbsolutePath(Allocator al, String path) {
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool FileExists(String path) {
|
|
||||||
Scratch scratch;
|
|
||||||
String null_term = Copy(scratch, path);
|
|
||||||
bool result = false;
|
|
||||||
if (access((char *)null_term.data, F_OK) == 0) {
|
|
||||||
result = true;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsDir(String path) {
|
|
||||||
Scratch scratch;
|
|
||||||
String null_term = Copy(scratch, path);
|
|
||||||
struct stat s;
|
|
||||||
if (stat(null_term.data, &s) != 0)
|
|
||||||
return false;
|
|
||||||
return S_ISDIR(s.st_mode);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsFile(String path) {
|
|
||||||
Scratch scratch;
|
|
||||||
String null_term = Copy(scratch, path);
|
|
||||||
struct stat s;
|
|
||||||
if (stat(null_term.data, &s) != 0)
|
|
||||||
return false;
|
|
||||||
return S_ISREG(s.st_mode);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsAbsolute(String path) {
|
|
||||||
bool result = path.len && path.data[0] == '/';
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
String GetWorkingDir(Allocator al) {
|
|
||||||
return Copy(al, "/workingdir");
|
|
||||||
}
|
|
||||||
|
|
||||||
String GetExePath(Allocator al) {
|
|
||||||
return Copy(al, "/text_editor");
|
|
||||||
}
|
|
||||||
|
|
||||||
String GetExeDir(Allocator al) {
|
|
||||||
Scratch scratch(al);
|
|
||||||
String exe_path = GetExePath(scratch);
|
|
||||||
String dir = ChopLastSlash(exe_path);
|
|
||||||
String result = Copy(al, dir);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
double get_time_in_micros(void) {
|
|
||||||
struct timespec spec;
|
|
||||||
clock_gettime(CLOCK_MONOTONIC, &spec);
|
|
||||||
return (((double)spec.tv_sec) * 1000000) + (((double)spec.tv_nsec) / 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsValid(const FileIter &it) {
|
|
||||||
return it.is_valid;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Advance(FileIter *it) {
|
|
||||||
struct dirent *file = NULL;
|
|
||||||
while ((file = readdir((DIR *)it->dir)) != NULL) {
|
|
||||||
if (file->d_name[0] == '.' && file->d_name[1] == '.' && file->d_name[2] == 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (file->d_name[0] == '.' && file->d_name[1] == 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
it->is_directory = file->d_type == DT_DIR;
|
|
||||||
it->filename = Copy(it->allocator, file->d_name);
|
|
||||||
|
|
||||||
const char *dir_char_ending = it->is_directory ? "/" : "";
|
|
||||||
const char *separator = it->path.data[it->path.len - 1] == '/' ? "" : "/";
|
|
||||||
it->relative_path = Format(it->allocator, "%.*s%s%s%s", FmtString(it->path), separator, file->d_name, dir_char_ending);
|
|
||||||
it->absolute_path = GetAbsolutePath(it->allocator, it->relative_path);
|
|
||||||
if (it->is_directory) it->absolute_path = Format(it->allocator, "%.*s/", FmtString(it->absolute_path));
|
|
||||||
it->is_valid = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
it->is_valid = false;
|
|
||||||
closedir((DIR *)it->dir);
|
|
||||||
}
|
|
||||||
|
|
||||||
FileIter IterateFiles(Allocator alo, String path) {
|
|
||||||
FileIter it = {};
|
|
||||||
it.allocator = alo;
|
|
||||||
it.path = path;
|
|
||||||
Scratch scratch(alo);
|
|
||||||
String null_term = Copy(scratch, path);
|
|
||||||
it.dir = (void *)opendir((char *)null_term.data);
|
|
||||||
if (it.dir) {
|
|
||||||
Advance(&it);
|
|
||||||
}
|
|
||||||
return it;
|
|
||||||
}
|
|
||||||
|
|
||||||
Array<char *> SplitCommand(Allocator allocator, String command_line) {
|
|
||||||
Array<char *> cmd = {allocator};
|
|
||||||
|
|
||||||
String curr = {};
|
|
||||||
for (int i = 0; i < command_line.len; i += 1) {
|
|
||||||
if (command_line.data[i] == ' ') {
|
|
||||||
if (curr.len > 0) {
|
|
||||||
Add(&cmd, Copy(allocator, curr).data);
|
|
||||||
curr = {};
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (curr.len == 0) {
|
|
||||||
curr.data = command_line.data + i;
|
|
||||||
}
|
|
||||||
curr.len += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (curr.len > 0) {
|
|
||||||
Add(&cmd, Copy(allocator, curr).data);
|
|
||||||
}
|
|
||||||
|
|
||||||
return cmd;
|
|
||||||
}
|
|
||||||
|
|
||||||
Process SpawnProcess(String command_line, String working_dir, String write_stdin, Array<String> enviroment) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsValid(Process *process) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void KillProcess(Process *process) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
String PollStdout(Allocator allocator, Process *process, bool force_read) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
void WriteStdin(Process *process, String string) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
void CloseStdin(Process *process) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
@@ -1,487 +0,0 @@
|
|||||||
#include "filesystem.h"
|
|
||||||
#include "thread_queue.h"
|
|
||||||
|
|
||||||
#ifndef NOMINMAX
|
|
||||||
#define NOMINMAX
|
|
||||||
#endif
|
|
||||||
#ifndef WIN32_LEAN_AND_MEAN
|
|
||||||
#define WIN32_LEAN_AND_MEAN
|
|
||||||
#endif
|
|
||||||
#include <windows.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <intrin.h>
|
|
||||||
|
|
||||||
#include "win32_thread.cpp"
|
|
||||||
|
|
||||||
void (*Error)(const char *, ...);
|
|
||||||
|
|
||||||
void *VReserve(size_t size) {
|
|
||||||
void *result = (uint8_t *)VirtualAlloc(0, size, MEM_RESERVE, PAGE_READWRITE);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool VCommit(void *p, size_t size) {
|
|
||||||
void *result = VirtualAlloc(p, size, MEM_COMMIT, PAGE_READWRITE);
|
|
||||||
return result ? true : false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool VRelease(void *p, size_t size) {
|
|
||||||
BOOL result = VirtualFree(p, 0, MEM_RELEASE);
|
|
||||||
return result ? true : false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool VDecommit(void *p, size_t size) {
|
|
||||||
BOOL result = VirtualFree(p, size, MEM_DECOMMIT);
|
|
||||||
return result ? true : false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void InitOS(void (*error_proc)(const char *, ...)) {
|
|
||||||
Error = error_proc;
|
|
||||||
|
|
||||||
SetConsoleOutputCP(65001);
|
|
||||||
SetConsoleCP(65001);
|
|
||||||
}
|
|
||||||
|
|
||||||
String ReadFile(Allocator arena, String path) {
|
|
||||||
bool success = false;
|
|
||||||
String result = {};
|
|
||||||
|
|
||||||
Scratch scratch(arena);
|
|
||||||
String16 string16 = ToString16(scratch, path);
|
|
||||||
HANDLE handle = CreateFileW((wchar_t *)string16.data, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
|
|
||||||
if (handle != INVALID_HANDLE_VALUE) {
|
|
||||||
LARGE_INTEGER file_size;
|
|
||||||
if (GetFileSizeEx(handle, &file_size)) {
|
|
||||||
if (file_size.QuadPart != 0) {
|
|
||||||
result.len = (int64_t)file_size.QuadPart;
|
|
||||||
result.data = (char *)AllocSize(arena, result.len + 1);
|
|
||||||
DWORD read;
|
|
||||||
if (ReadFile(handle, result.data, (DWORD)result.len, &read, NULL)) { // @todo: can only read 32 byte size files?
|
|
||||||
if (read == result.len) {
|
|
||||||
success = true;
|
|
||||||
result.data[result.len] = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
CloseHandle(handle);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!success) {
|
|
||||||
Dealloc(arena, &result.data);
|
|
||||||
result = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
typedef struct Win32_FileIter {
|
|
||||||
HANDLE handle;
|
|
||||||
WIN32_FIND_DATAW data;
|
|
||||||
} Win32_FileIter;
|
|
||||||
|
|
||||||
String GetAbsolutePath(Allocator arena, String relative) {
|
|
||||||
Scratch scratch(arena);
|
|
||||||
String16 wpath = ToString16(scratch, relative);
|
|
||||||
wchar_t *wpath_abs = AllocArray(scratch, wchar_t, 4096);
|
|
||||||
DWORD written = GetFullPathNameW((wchar_t *)wpath.data, 4096, wpath_abs, 0);
|
|
||||||
if (written == 0)
|
|
||||||
return {};
|
|
||||||
String path = ToString(arena, {(char16_t *)wpath_abs, written});
|
|
||||||
NormalizePathInPlace(path);
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsValid(const FileIter &it) {
|
|
||||||
return it.is_valid;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Advance(FileIter *it) {
|
|
||||||
while (FindNextFileW(it->w32->handle, &it->w32->data) != 0) {
|
|
||||||
WIN32_FIND_DATAW *data = &it->w32->data;
|
|
||||||
|
|
||||||
// Skip '.' and '..'
|
|
||||||
if (data->cFileName[0] == '.' && data->cFileName[1] == '.' && data->cFileName[2] == 0) continue;
|
|
||||||
if (data->cFileName[0] == '.' && data->cFileName[1] == 0) continue;
|
|
||||||
|
|
||||||
it->is_directory = data->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
|
|
||||||
it->filename = ToString(it->allocator, (char16_t *)data->cFileName, WideLength((char16_t *)data->cFileName));
|
|
||||||
const char *is_dir = it->is_directory ? "/" : "";
|
|
||||||
const char *separator = it->path.data[it->path.len - 1] == '/' ? "" : "/";
|
|
||||||
it->relative_path = Format(it->allocator, "%.*s%s%.*s%s", FmtString(it->path), separator, FmtString(it->filename), is_dir);
|
|
||||||
it->absolute_path = GetAbsolutePath(it->allocator, it->relative_path);
|
|
||||||
it->is_valid = true;
|
|
||||||
|
|
||||||
if (it->is_directory) {
|
|
||||||
Assert(it->relative_path.data[it->relative_path.len - 1] == '/');
|
|
||||||
Assert(it->absolute_path.data[it->absolute_path.len - 1] == '/');
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
it->is_valid = false;
|
|
||||||
DWORD error = GetLastError();
|
|
||||||
Assert(error == ERROR_NO_MORE_FILES);
|
|
||||||
FindClose(it->w32->handle);
|
|
||||||
}
|
|
||||||
|
|
||||||
FileIter IterateFiles(Allocator alo, String path) {
|
|
||||||
FileIter it = {0};
|
|
||||||
it.allocator = alo;
|
|
||||||
it.path = path;
|
|
||||||
|
|
||||||
String modified_path = Format(it.allocator, "%.*s\\*", FmtString(path));
|
|
||||||
String16 modified_path16 = ToString16(it.allocator, modified_path);
|
|
||||||
|
|
||||||
it.w32 = AllocType(it.allocator, Win32_FileIter);
|
|
||||||
it.w32->handle = FindFirstFileW((wchar_t *)modified_path16.data, &it.w32->data);
|
|
||||||
if (it.w32->handle == INVALID_HANDLE_VALUE) {
|
|
||||||
it.is_valid = false;
|
|
||||||
return it;
|
|
||||||
}
|
|
||||||
|
|
||||||
Advance(&it);
|
|
||||||
return it;
|
|
||||||
}
|
|
||||||
|
|
||||||
double get_time_in_micros(void) {
|
|
||||||
static double invfreq;
|
|
||||||
if (!invfreq) {
|
|
||||||
LARGE_INTEGER frequency;
|
|
||||||
QueryPerformanceFrequency(&frequency);
|
|
||||||
invfreq = 1000000.0 / frequency.QuadPart;
|
|
||||||
}
|
|
||||||
LARGE_INTEGER counter;
|
|
||||||
QueryPerformanceCounter(&counter);
|
|
||||||
return counter.QuadPart * invfreq;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WriteFile(String path, String data) {
|
|
||||||
bool result = false;
|
|
||||||
|
|
||||||
Scratch scratch;
|
|
||||||
String16 wpath = ToString16(scratch, path);
|
|
||||||
|
|
||||||
DWORD access = GENERIC_WRITE;
|
|
||||||
DWORD creation_disposition = CREATE_ALWAYS;
|
|
||||||
HANDLE handle = CreateFileW((wchar_t *)wpath.data, access, 0, NULL, creation_disposition, FILE_ATTRIBUTE_NORMAL, NULL);
|
|
||||||
if (handle != INVALID_HANDLE_VALUE) {
|
|
||||||
DWORD bytes_written = 0;
|
|
||||||
Assert(data.len == (DWORD)data.len); // @Todo: can only read 32 byte size files?
|
|
||||||
BOOL error = WriteFile(handle, data.data, (DWORD)data.len, &bytes_written, NULL);
|
|
||||||
if (error == TRUE) {
|
|
||||||
if (bytes_written == data.len) {
|
|
||||||
result = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
CloseHandle(handle);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
String GetExePath(Allocator allocator) {
|
|
||||||
Scratch scratch(allocator);
|
|
||||||
wchar_t *wbuffer = AllocArray(scratch, wchar_t, 4096);
|
|
||||||
DWORD wsize = GetModuleFileNameW(0, wbuffer, 4096);
|
|
||||||
Assert(wsize != 0);
|
|
||||||
|
|
||||||
String path = ToString(allocator, (char16_t *)wbuffer, wsize);
|
|
||||||
NormalizePathInPlace(path);
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
|
|
||||||
String GetExeDir(Allocator allocator) {
|
|
||||||
Scratch scratch((Arena *)allocator.object);
|
|
||||||
String path = GetExePath(scratch);
|
|
||||||
path = ChopLastSlash(path);
|
|
||||||
path = Copy(allocator, path);
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool FileExists(String path) {
|
|
||||||
Scratch scratch;
|
|
||||||
String16 wbuff = ToString16(scratch, path);
|
|
||||||
DWORD attribs = GetFileAttributesW((wchar_t *)wbuff.data);
|
|
||||||
bool result = attribs == INVALID_FILE_ATTRIBUTES ? false : true;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsDir(String path) {
|
|
||||||
Scratch scratch;
|
|
||||||
String16 wbuff = ToString16(scratch, path);
|
|
||||||
DWORD dwAttrib = GetFileAttributesW((wchar_t *)wbuff.data);
|
|
||||||
return dwAttrib != INVALID_FILE_ATTRIBUTES && (dwAttrib & FILE_ATTRIBUTE_DIRECTORY);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsFile(String path) {
|
|
||||||
Scratch scratch;
|
|
||||||
String16 wbuff = ToString16(scratch, path);
|
|
||||||
DWORD dwAttrib = GetFileAttributesW((wchar_t *)wbuff.data);
|
|
||||||
bool is_file = (dwAttrib & FILE_ATTRIBUTE_DIRECTORY) == 0;
|
|
||||||
return dwAttrib != INVALID_FILE_ATTRIBUTES && is_file;
|
|
||||||
}
|
|
||||||
|
|
||||||
String GetWorkingDir(Allocator arena) {
|
|
||||||
Scratch scratch(arena);
|
|
||||||
wchar_t *wbuffer = AllocArray(scratch, wchar_t, 4096);
|
|
||||||
DWORD wsize = GetCurrentDirectoryW(4096, wbuffer);
|
|
||||||
Assert(wsize != 0);
|
|
||||||
wbuffer[wsize] = 0;
|
|
||||||
|
|
||||||
String path = ToString(arena, (char16_t *)wbuffer, wsize);
|
|
||||||
NormalizePathInPlace(path);
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsAbsolute(String path) {
|
|
||||||
bool result = path.len > 3 && IsAlphabetic(path.data[0]) && path.data[1] == ':' && (path.data[2] == '/' || path.data[2] == '\\');
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DeleteFile(String path) {
|
|
||||||
Scratch scratch;
|
|
||||||
String16 wpath = ToString16(scratch, path);
|
|
||||||
|
|
||||||
BOOL success = DeleteFileW((wchar_t *)wpath.data);
|
|
||||||
bool result = true;
|
|
||||||
if (success == 0) result = false;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
int64_t GetFileModTime(String file) {
|
|
||||||
Scratch scratch;
|
|
||||||
String16 string16 = ToString16(scratch, file);
|
|
||||||
WIN32_FIND_DATAW data;
|
|
||||||
HANDLE handle = FindFirstFileW((wchar_t *)string16.data, &data);
|
|
||||||
if (handle != INVALID_HANDLE_VALUE) {
|
|
||||||
FindClose(handle);
|
|
||||||
FILETIME time = data.ftLastWriteTime;
|
|
||||||
int64_t result = (int64_t)time.dwHighDateTime << 32 | time.dwLowDateTime;
|
|
||||||
return result;
|
|
||||||
} else {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MakeDirResult MakeDir(String path) {
|
|
||||||
Scratch scratch;
|
|
||||||
MakeDirResult result = MakeDirResult_Success;
|
|
||||||
String16 string16 = ToString16(scratch, path);
|
|
||||||
BOOL success = CreateDirectoryW((wchar_t *)string16.data, NULL);
|
|
||||||
if (success == 0) {
|
|
||||||
DWORD error = GetLastError();
|
|
||||||
if (error == ERROR_ALREADY_EXISTS) {
|
|
||||||
result = MakeDirResult_Exists;
|
|
||||||
} else if (error == ERROR_PATH_NOT_FOUND) {
|
|
||||||
result = MakeDirResult_NotFound;
|
|
||||||
} else {
|
|
||||||
result = MakeDirResult_ErrorOther;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Win32Process {
|
|
||||||
HANDLE handle;
|
|
||||||
HANDLE child_stdout_read;
|
|
||||||
HANDLE child_stdout_write;
|
|
||||||
HANDLE child_stdin_read;
|
|
||||||
HANDLE child_stdin_write;
|
|
||||||
};
|
|
||||||
// static_assert(sizeof(Win32Process) < sizeof(Process::platform));
|
|
||||||
|
|
||||||
void Win32ReportError(String msg, String cmd) {
|
|
||||||
LPVOID lpMsgBuf;
|
|
||||||
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&lpMsgBuf, 0, NULL);
|
|
||||||
defer { LocalFree(lpMsgBuf); };
|
|
||||||
char *buff = (char *)lpMsgBuf;
|
|
||||||
size_t buffLen = strlen((const char *)buff);
|
|
||||||
if (buffLen > 0 && buff[buffLen - 1] == '\n') buff[buffLen - 1] = 0;
|
|
||||||
Error("%.*s: %.*s! %s", FmtString(msg), FmtString(cmd), (char *)lpMsgBuf);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Win32CloseProcess(Process *process) {
|
|
||||||
Win32Process *p = (Win32Process *)process->platform;
|
|
||||||
if (p->handle != INVALID_HANDLE_VALUE) CloseHandle(p->handle);
|
|
||||||
if (p->child_stdout_write != INVALID_HANDLE_VALUE) CloseHandle(p->child_stdout_write);
|
|
||||||
if (p->child_stdout_read != INVALID_HANDLE_VALUE) CloseHandle(p->child_stdout_read);
|
|
||||||
if (p->child_stdin_read != INVALID_HANDLE_VALUE) CloseHandle(p->child_stdin_read);
|
|
||||||
if (p->child_stdin_write != INVALID_HANDLE_VALUE) CloseHandle(p->child_stdin_write);
|
|
||||||
process->is_valid = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void Win32ProcessError(Process *process, String msg, String cmd) {
|
|
||||||
Win32ReportError(msg, cmd);
|
|
||||||
Win32CloseProcess(process);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WriteStdin(Process *process, String string) {
|
|
||||||
if (string.len == 0) return;
|
|
||||||
Assert(process->is_valid);
|
|
||||||
|
|
||||||
Win32Process *p = (Win32Process *)process->platform;
|
|
||||||
Assert(p->child_stdin_write != INVALID_HANDLE_VALUE);
|
|
||||||
|
|
||||||
DWORD written = 0;
|
|
||||||
bool write_error = WriteFile(p->child_stdin_write, string.data, (DWORD)string.len, &written, NULL) == 0;
|
|
||||||
|
|
||||||
Assert(write_error == false);
|
|
||||||
Assert(written == string.len);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CloseStdin(Process *process) {
|
|
||||||
Win32Process *p = (Win32Process *)process->platform;
|
|
||||||
CloseHandle(p->child_stdin_write);
|
|
||||||
p->child_stdin_write = INVALID_HANDLE_VALUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
Process SpawnProcess(String command_line, String working_dir, String write_stdin, Array<String> enviroment) {
|
|
||||||
Process process = {};
|
|
||||||
Win32Process *p = (Win32Process *)process.platform;
|
|
||||||
|
|
||||||
Scratch scratch;
|
|
||||||
command_line = Format(scratch, "/C %.*s", FmtString(command_line));
|
|
||||||
|
|
||||||
p->handle = INVALID_HANDLE_VALUE;
|
|
||||||
p->child_stdout_write = INVALID_HANDLE_VALUE;
|
|
||||||
p->child_stdout_read = INVALID_HANDLE_VALUE;
|
|
||||||
p->child_stdin_read = INVALID_HANDLE_VALUE;
|
|
||||||
p->child_stdin_write = INVALID_HANDLE_VALUE;
|
|
||||||
|
|
||||||
SECURITY_ATTRIBUTES security_atrb = {};
|
|
||||||
security_atrb.nLength = sizeof(SECURITY_ATTRIBUTES);
|
|
||||||
security_atrb.bInheritHandle = TRUE;
|
|
||||||
|
|
||||||
if (!CreatePipe(&p->child_stdout_read, &p->child_stdout_write, &security_atrb, 0)) {
|
|
||||||
Win32ProcessError(&process, "Failed to create process at create pipe stage", command_line);
|
|
||||||
return process;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!SetHandleInformation(p->child_stdout_read, HANDLE_FLAG_INHERIT, 0)) {
|
|
||||||
Win32ProcessError(&process, "Failed to create process at create pipe stage", command_line);
|
|
||||||
return process;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!CreatePipe(&p->child_stdin_read, &p->child_stdin_write, &security_atrb, 0)) {
|
|
||||||
Win32ProcessError(&process, "Failed to create process at create pipe stage", command_line);
|
|
||||||
return process;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!SetHandleInformation(p->child_stdin_write, HANDLE_FLAG_INHERIT, 0)) {
|
|
||||||
Win32ProcessError(&process, "Failed to create process at create pipe stage", command_line);
|
|
||||||
return process;
|
|
||||||
}
|
|
||||||
|
|
||||||
STARTUPINFOW startup = {};
|
|
||||||
startup.cb = sizeof(STARTUPINFO);
|
|
||||||
startup.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
|
|
||||||
startup.hStdInput = p->child_stdin_read;
|
|
||||||
startup.hStdOutput = p->child_stdout_write;
|
|
||||||
startup.hStdError = p->child_stdout_write;
|
|
||||||
startup.wShowWindow = SW_HIDE;
|
|
||||||
|
|
||||||
String16 cwd = ToString16(scratch, working_dir);
|
|
||||||
String16 cmd = ToString16(scratch, command_line);
|
|
||||||
|
|
||||||
char *env = NULL;
|
|
||||||
if (enviroment.len) {
|
|
||||||
Int size = GetSize(enviroment) + enviroment.len + 1;
|
|
||||||
env = (char *)PushSize(scratch, size);
|
|
||||||
Int i = 0;
|
|
||||||
For(enviroment) {
|
|
||||||
MemoryCopy(env + i, it.data, it.len);
|
|
||||||
i += it.len;
|
|
||||||
|
|
||||||
env[i++] = 0;
|
|
||||||
}
|
|
||||||
env[i++] = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
DWORD dwCreationFlags = 0;
|
|
||||||
BOOL bInheritHandles = TRUE;
|
|
||||||
|
|
||||||
PROCESS_INFORMATION info = {};
|
|
||||||
if (!CreateProcessW(L"c:\\windows\\system32\\cmd.exe", (wchar_t *)cmd.data, 0, 0, bInheritHandles, dwCreationFlags, env, (wchar_t *)cwd.data, &startup, &info)) {
|
|
||||||
Win32ProcessError(&process, "failed to create process", command_line);
|
|
||||||
return process;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close handles to the stdin and stdout pipes no longer needed by the child process.
|
|
||||||
// If they are not explicitly closed, there is no way to recognize that the child process has ended.
|
|
||||||
CloseHandle(info.hThread);
|
|
||||||
CloseHandle(p->child_stdin_read);
|
|
||||||
CloseHandle(p->child_stdout_write);
|
|
||||||
p->child_stdin_read = INVALID_HANDLE_VALUE;
|
|
||||||
p->child_stdout_write = INVALID_HANDLE_VALUE;
|
|
||||||
|
|
||||||
p->handle = info.hProcess;
|
|
||||||
process.is_valid = true;
|
|
||||||
process.id = (int64_t)p->handle;
|
|
||||||
|
|
||||||
if (write_stdin.len) {
|
|
||||||
WriteStdin(&process, write_stdin);
|
|
||||||
CloseStdin(&process);
|
|
||||||
}
|
|
||||||
|
|
||||||
return process;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsValid(Process *process) {
|
|
||||||
if (process->is_valid == false) return false;
|
|
||||||
Win32Process *p = (Win32Process *)process->platform;
|
|
||||||
|
|
||||||
if (WaitForSingleObject(p->handle, 0) != WAIT_OBJECT_0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
DWORD exit_code;
|
|
||||||
bool get_exit_code_failed = GetExitCodeProcess(p->handle, &exit_code) == 0;
|
|
||||||
if (get_exit_code_failed) {
|
|
||||||
exit_code = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
process->exit_code = (int)exit_code;
|
|
||||||
Win32CloseProcess(process);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void KillProcess(Process *process) {
|
|
||||||
Assert(process->is_valid);
|
|
||||||
Win32Process *p = (Win32Process *)process->platform;
|
|
||||||
|
|
||||||
bool terminate_process_error = TerminateProcess(p->handle, -1) == 0;
|
|
||||||
if (terminate_process_error) {
|
|
||||||
Assert(0);
|
|
||||||
}
|
|
||||||
Win32CloseProcess(process);
|
|
||||||
process->exit_code = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
String PollStdout(Allocator allocator, Process *process, bool force_read) {
|
|
||||||
Assert(process->is_valid);
|
|
||||||
Win32Process *p = (Win32Process *)process->platform;
|
|
||||||
|
|
||||||
DWORD bytes_avail = 0;
|
|
||||||
bool peek_error = PeekNamedPipe(p->child_stdout_read, NULL, 0, NULL, &bytes_avail, NULL) == 0;
|
|
||||||
if (peek_error) {
|
|
||||||
return {};
|
|
||||||
} else if (bytes_avail == 0) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t buffer_size = ClampTop(bytes_avail, (DWORD)(4096 * 16));
|
|
||||||
char *buffer = AllocArray(allocator, char, buffer_size);
|
|
||||||
|
|
||||||
DWORD bytes_read = 0;
|
|
||||||
bool read_error = ReadFile(p->child_stdout_read, buffer, (DWORD)buffer_size, &bytes_read, 0) == 0;
|
|
||||||
if (read_error) {
|
|
||||||
Win32ReportError("Failed to read the stdout of child process", "ReadFile");
|
|
||||||
Dealloc(allocator, &buffer);
|
|
||||||
Win32CloseProcess(process);
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
String result = {buffer, bytes_read};
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
int64_t AtomicIncrement(volatile int64_t *i) {
|
|
||||||
return InterlockedIncrement64(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
int64_t AtomicCompareAndSwap(volatile int64_t *dst, int64_t exchange, int64_t comperand) {
|
|
||||||
return InterlockedCompareExchange64(dst, exchange, comperand);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PushWork(WorkQueue *wq, void *data, WorkQueueCallback *callback) {
|
|
||||||
uint32_t new_index = (wq->index_to_write + 1) % Lengthof(wq->entries);
|
|
||||||
assert(new_index != wq->index_to_read);
|
|
||||||
|
|
||||||
WorkQueueEntry *entry = wq->entries + wq->index_to_write;
|
|
||||||
entry->data = data;
|
|
||||||
entry->callback = callback;
|
|
||||||
|
|
||||||
wq->completion_goal += 1;
|
|
||||||
_WriteBarrier();
|
|
||||||
wq->index_to_write = new_index;
|
|
||||||
ReleaseSemaphore(wq->semaphore, 1, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool TryDoingWork(WorkQueue *wq) {
|
|
||||||
bool should_sleep = false;
|
|
||||||
int64_t original_index_to_read = wq->index_to_read;
|
|
||||||
int64_t new_index_to_read = (original_index_to_read + 1) % Lengthof(wq->entries);
|
|
||||||
if (original_index_to_read != wq->index_to_write) {
|
|
||||||
int64_t index = AtomicCompareAndSwap(&wq->index_to_read, new_index_to_read, original_index_to_read);
|
|
||||||
if (index == original_index_to_read) {
|
|
||||||
WorkQueueEntry *entry = wq->entries + index;
|
|
||||||
entry->callback(entry->data);
|
|
||||||
AtomicIncrement(&wq->completion_index);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
should_sleep = true;
|
|
||||||
}
|
|
||||||
return should_sleep;
|
|
||||||
}
|
|
||||||
|
|
||||||
DWORD WINAPI WorkQueueThreadEntry(LPVOID param) {
|
|
||||||
auto ti = (ThreadStartupInfo *)param;
|
|
||||||
|
|
||||||
ThreadCtx ctx = {};
|
|
||||||
ctx.thread_index = ti->thread_index;
|
|
||||||
InitScratch();
|
|
||||||
for (;;) {
|
|
||||||
if (TryDoingWork(ti->queue)) {
|
|
||||||
WaitForSingleObject(ti->queue->semaphore, INFINITE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void InitWorkQueue(WorkQueue *queue, uint32_t thread_count, ThreadStartupInfo *info) {
|
|
||||||
queue->thread_count = thread_count;
|
|
||||||
queue->index_to_read = 0;
|
|
||||||
queue->index_to_write = 0;
|
|
||||||
queue->completion_index = 0;
|
|
||||||
queue->completion_goal = 0;
|
|
||||||
queue->semaphore = CreateSemaphoreExA(0, 0, thread_count, 0, 0, SEMAPHORE_ALL_ACCESS);
|
|
||||||
Assert(queue->semaphore != INVALID_HANDLE_VALUE);
|
|
||||||
|
|
||||||
for (uint32_t i = 0; i < thread_count; i++) {
|
|
||||||
ThreadStartupInfo *ti = info + i;
|
|
||||||
ti->thread_index = i;
|
|
||||||
ti->queue = queue;
|
|
||||||
|
|
||||||
DWORD thread_id = 0;
|
|
||||||
HANDLE thread_handle = CreateThread(0, 0, WorkQueueThreadEntry, ti, 0, &thread_id);
|
|
||||||
Assert(thread_handle != INVALID_HANDLE_VALUE);
|
|
||||||
ti->thread_id = thread_id;
|
|
||||||
CloseHandle(thread_handle);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void WaitUntilCompletion(WorkQueue *wq) {
|
|
||||||
while (wq->completion_goal != wq->completion_index) {
|
|
||||||
TryDoingWork(wq);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsWorkCompleted(WorkQueue *wq) {
|
|
||||||
bool result = wq->completion_goal == wq->completion_index;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
@@ -1,18 +1,58 @@
|
|||||||
#define BASIC_IMPL
|
|
||||||
#include "basic/basic.h"
|
#include "basic/basic.h"
|
||||||
#include "basic/filesystem.h"
|
#include "basic/basic.cpp"
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
InitScratch();
|
InitScratch();
|
||||||
|
// Basic constructors
|
||||||
|
{
|
||||||
|
String a = "thing";
|
||||||
|
String b("thing");
|
||||||
|
Assert(a == b);
|
||||||
|
String c = {};
|
||||||
|
Assert(c.len == 0 && c.data == NULL);
|
||||||
|
Assert(a != c);
|
||||||
|
}
|
||||||
|
|
||||||
Scratch scratch;
|
// Accessors
|
||||||
String data = ReadFile(scratch, "../data/init.lua");
|
{
|
||||||
Array<String> array = {scratch};
|
String a = "thing";
|
||||||
|
Assert(Skip(a, 1) == "hing");
|
||||||
|
Assert(Chop(a, 1) == "thin");
|
||||||
|
Assert(GetPrefix(a, 1) == "t");
|
||||||
|
Assert(GetPostfix(a, 1) == "g");
|
||||||
|
Assert(GetSlice(a, 0) == "thing");
|
||||||
|
Assert(GetSlice(a, 0, -1) == "thin");
|
||||||
|
Assert(GetSlice(a, 1, -1) == "hin");
|
||||||
|
Assert(GetSlice(a, -2, -1) == "n");
|
||||||
|
}
|
||||||
|
|
||||||
Add(&array, String{"String BaseLuaConfig = R\"==(\n"});
|
{
|
||||||
Add(&array, data);
|
Scratch scratch;
|
||||||
Add(&array, String{"\n)==\";\n"});
|
String16 a = Format16(scratch, "Memes %d", 30);
|
||||||
String result = Merge(scratch, array, "");
|
Assert(a == u"Memes 30");
|
||||||
WriteFile("../src/text_editor/generated_config.cpp", result);
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
printf("hello world\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// #define BASIC_IMPL
|
||||||
|
// #include "basic/basic.h"
|
||||||
|
// #include "basic/filesystem.h"
|
||||||
|
|
||||||
|
// int main() {
|
||||||
|
// InitScratch();
|
||||||
|
|
||||||
|
// Scratch scratch;
|
||||||
|
// String data = ReadFile(scratch, "../data/init.lua");
|
||||||
|
// Array<String> array = {scratch};
|
||||||
|
|
||||||
|
// Add(&array, String{"String BaseLuaConfig = R\"==(\n"});
|
||||||
|
// Add(&array, data);
|
||||||
|
// Add(&array, String{"\n)==\";\n"});
|
||||||
|
// String result = Merge(scratch, array, "");
|
||||||
|
// WriteFile("../src/text_editor/generated_config.cpp", result);
|
||||||
|
|
||||||
|
// }
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
static SpallProfile spall_ctx;
|
static SpallProfile spall_ctx;
|
||||||
static SpallBuffer spall_buffer;
|
static SpallBuffer spall_buffer;
|
||||||
double get_time_in_micros(void);
|
double GetTimeMicros(void);
|
||||||
|
|
||||||
void BeginProfiler() {
|
void BeginProfiler() {
|
||||||
spall_ctx = spall_init_file("hello_world.spall", 1);
|
spall_ctx = spall_init_file("hello_world.spall", 1);
|
||||||
@@ -25,14 +25,14 @@ void _BeginProfileScope(const char *name, int len) {
|
|||||||
spall_buffer_begin(&spall_ctx, &spall_buffer,
|
spall_buffer_begin(&spall_ctx, &spall_buffer,
|
||||||
name, // name of your name
|
name, // name of your name
|
||||||
len, // name len minus the null terminator
|
len, // name len minus the null terminator
|
||||||
get_time_in_micros() // timestamp in microseconds -- start of your timing block
|
GetTimeMicros() // timestamp in microseconds -- start of your timing block
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
#define BeginProfileScope(name) _BeginProfileScope(#name, sizeof(#name) - 1)
|
#define BeginProfileScope(name) _BeginProfileScope(#name, sizeof(#name) - 1)
|
||||||
|
|
||||||
void EndProfileScope() {
|
void EndProfileScope() {
|
||||||
spall_buffer_end(&spall_ctx, &spall_buffer,
|
spall_buffer_end(&spall_ctx, &spall_buffer,
|
||||||
get_time_in_micros() // timestamp in microseconds -- end of your timing block
|
GetTimeMicros() // timestamp in microseconds -- end of your timing block
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
#define ProfileScope(name) ProfileScope_ PROFILE_SCOPE_VAR_##name(#name, sizeof(#name) - 1)
|
#define ProfileScope(name) ProfileScope_ PROFILE_SCOPE_VAR_##name(#name, sizeof(#name) - 1)
|
||||||
|
|||||||
@@ -1,3 +1,41 @@
|
|||||||
|
REDESIGN and DELETE CODE
|
||||||
|
- Reduce the amount of actions needed to navigate using keyboard
|
||||||
|
- Make mouse important but much less so
|
||||||
|
|
||||||
|
Needs to change:
|
||||||
|
- Make it more similar to sublime like editors
|
||||||
|
- Need Ctrl + P
|
||||||
|
- Clickable title bar may be cool or whatever but it's pretty bad
|
||||||
|
- Executing lua commands is clunky, need a real Ctrl+P and keybind actions, popups: remove a lot of the lua functionality, just for config files
|
||||||
|
- Window, View, Buffer + flags design (or is completely new kind based approach needed for Windows/Views?)
|
||||||
|
- How to make non-editable, informative, with different font size, title bar. Which might also contain tabs
|
||||||
|
- How to design clickable tree view in this way?
|
||||||
|
- How to design Command view?
|
||||||
|
- How to design popup view (input field)?
|
||||||
|
- How to design search view? or search and replace view?
|
||||||
|
- Window management, splitting, GC
|
||||||
|
- add modified stb_sprintf
|
||||||
|
|
||||||
|
Things I like:
|
||||||
|
- Basic editing
|
||||||
|
- Configurable Open
|
||||||
|
- Lua config files work pretty well
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
DESIGN try to make console less special, make stuff reusable etc.
|
DESIGN try to make console less special, make stuff reusable etc.
|
||||||
DESIGN Config file versions, when loading should be checked, at the top of the file, what to do when old version?
|
DESIGN Config file versions, when loading should be checked, at the top of the file, what to do when old version?
|
||||||
ISSUE Ctrl+Alt+Down (DuplicateLine) doesn't work on ubuntu
|
ISSUE Ctrl+Alt+Down (DuplicateLine) doesn't work on ubuntu
|
||||||
|
|||||||
Reference in New Issue
Block a user