From a92b1195494133ebc9dfcc6492ef3614092d0638 Mon Sep 17 00:00:00 2001 From: snarmph <43290148+snarmph@users.noreply.github.com> Date: Fri, 29 Nov 2024 16:10:48 +0100 Subject: [PATCH] mmmmh --- colla/arena.c => arena.c | 25 +- colla/arena.h => arena.h | 10 +- colla/base64.c => base64.c | 2 +- colla/base64.h => base64.h | 0 colla/bits.h => bits.h | 2 +- build.c | 51 +- colla/colladefines.h => colladefines.h | 22 + colla/collatypes.h => collatypes.h | 0 colla/cthreads.c => cthreads.c | 563 ++++---- colla/cthreads.h => cthreads.h | 112 +- deprecated/coropool.c | 322 ----- deprecated/coropool.h | 17 - deprecated/coroutine.c | 11 - deprecated/coroutine.h | 133 -- deprecated/dir.c | 204 --- deprecated/dir.h | 34 - deprecated/dirwatch.c | 295 ---- deprecated/dirwatch.h | 67 - deprecated/fs.c | 134 -- deprecated/fs.h | 43 - deprecated/hashmap.c | 130 -- deprecated/hashmap.h | 49 - deprecated/jobpool.c | 144 -- deprecated/jobpool.h | 12 - deprecated/os.c | 107 -- deprecated/os.h | 30 - dir.c | 163 +++ dir.h | 25 + docs/arena.md | 131 ++ docs/base64.md | 32 + docs/cthreads.md | 49 + docs/dir.md | 52 + docs/docs.com | Bin 0 -> 315392 bytes docs/file.md | 41 + docs/format.md | 28 + docs/highlight.md | 37 + docs/hot_reload.md | 79 + docs/html.md | 45 + docs/http.md | 5 + docs/ini.md | 5 + docs/json.md | 5 + docs/markdown.md | 5 + docs/readme.md | 5 + docs/server.md | 5 + docs/sha1.md | 5 + docs/socket.md | 5 + docs/str.md | 5 + docs/strstream.md | 5 + docs/tracelog.md | 5 + docs/utf8.md | 5 + docs/vec.md | 5 + docs/vmem.md | 5 + docs/websocket.md | 5 + docs/xml.md | 5 + colla/file.c => file.c | 727 +++++----- colla/file.h => file.h | 108 +- colla/format.c => format.c | 2 +- colla/format.h => format.h | 0 highlight.c | 621 ++++++++ highlight.h | 49 + colla/hot_reload.c => hot_reload.c | 170 ++- colla/hot_reload.h => hot_reload.h | 0 html.h | 77 + colla/http.c => http.c | 1112 +++++++------- colla/http.h => http.h | 166 +-- colla/ini.c => ini.c | 558 +++---- colla/ini.h => ini.h | 106 +- colla/json.c => json.c | 3 +- colla/json.h => json.h | 0 markdown.c | 503 +++++++ markdown.h | 59 + colla/server.c => server.c | 32 +- colla/server.h => server.h | 2 +- sha1.c | 120 ++ sha1.h | 18 + colla/socket.c => socket.c | 570 ++++---- colla/socket.h => socket.h | 186 +-- {colla/stb => stb}/stb_sprintf.h | 15 + colla/str.c => str.c | 807 ++++++----- colla/str.h => str.h | 228 +-- colla/strstream.c => strstream.c | 1282 +++++++++-------- colla/strstream.h => strstream.h | 315 ++-- {colla/tcc => tcc}/colla.def | 0 {colla/tcc => tcc}/colla_tcc.h | 2 +- tools/build | 8 + tools/docs.c | 429 ++++++ colla/tracelog.c => tracelog.c | 429 +++--- colla/tracelog.h => tracelog.h | 118 +- colla/utf8.c => utf8.c | 344 ++--- colla/utf8.h => utf8.h | 38 +- colla/vec.h => vec.h | 0 colla/vmem.c => vmem.c | 3 +- colla/vmem.h => vmem.h | 0 {colla/warnings => warnings}/colla_warn_beg.h | 0 {colla/warnings => warnings}/colla_warn_end.h | 0 websocket.c | 142 ++ websocket.h | 13 + colla/xml.c => xml.c | 2 +- colla/xml.h => xml.h | 0 99 files changed, 6922 insertions(+), 5723 deletions(-) rename colla/arena.c => arena.c (88%) rename colla/arena.h => arena.h (91%) rename colla/base64.c => base64.c (98%) rename colla/base64.h => base64.h (100%) rename colla/bits.h => bits.h (88%) rename colla/colladefines.h => colladefines.h (80%) rename colla/collatypes.h => collatypes.h (100%) rename colla/cthreads.c => cthreads.c (94%) rename colla/cthreads.h => cthreads.h (95%) delete mode 100644 deprecated/coropool.c delete mode 100644 deprecated/coropool.h delete mode 100644 deprecated/coroutine.c delete mode 100644 deprecated/coroutine.h delete mode 100644 deprecated/dir.c delete mode 100644 deprecated/dir.h delete mode 100644 deprecated/dirwatch.c delete mode 100644 deprecated/dirwatch.h delete mode 100644 deprecated/fs.c delete mode 100644 deprecated/fs.h delete mode 100644 deprecated/hashmap.c delete mode 100644 deprecated/hashmap.h delete mode 100644 deprecated/jobpool.c delete mode 100644 deprecated/jobpool.h delete mode 100644 deprecated/os.c delete mode 100644 deprecated/os.h create mode 100644 dir.c create mode 100644 dir.h create mode 100644 docs/arena.md create mode 100644 docs/base64.md create mode 100644 docs/cthreads.md create mode 100644 docs/dir.md create mode 100644 docs/docs.com create mode 100644 docs/file.md create mode 100644 docs/format.md create mode 100644 docs/highlight.md create mode 100644 docs/hot_reload.md create mode 100644 docs/html.md create mode 100644 docs/http.md create mode 100644 docs/ini.md create mode 100644 docs/json.md create mode 100644 docs/markdown.md create mode 100644 docs/readme.md create mode 100644 docs/server.md create mode 100644 docs/sha1.md create mode 100644 docs/socket.md create mode 100644 docs/str.md create mode 100644 docs/strstream.md create mode 100644 docs/tracelog.md create mode 100644 docs/utf8.md create mode 100644 docs/vec.md create mode 100644 docs/vmem.md create mode 100644 docs/websocket.md create mode 100644 docs/xml.md rename colla/file.c => file.c (95%) rename colla/file.h => file.h (96%) rename colla/format.c => format.c (97%) rename colla/format.h => format.h (100%) create mode 100644 highlight.c create mode 100644 highlight.h rename colla/hot_reload.c => hot_reload.c (88%) rename colla/hot_reload.h => hot_reload.h (100%) create mode 100644 html.h rename colla/http.c => http.c (95%) rename colla/http.h => http.h (96%) rename colla/ini.c => ini.c (92%) rename colla/ini.h => ini.h (94%) rename colla/json.c => json.c (99%) rename colla/json.h => json.h (100%) create mode 100644 markdown.c create mode 100644 markdown.h rename colla/server.c => server.c (95%) rename colla/server.h => server.h (93%) create mode 100644 sha1.c create mode 100644 sha1.h rename colla/socket.c => socket.c (97%) rename colla/socket.h => socket.h (97%) rename {colla/stb => stb}/stb_sprintf.h (99%) rename colla/str.c => str.c (91%) rename colla/str.h => str.h (86%) rename colla/strstream.c => strstream.c (93%) rename colla/strstream.h => strstream.h (95%) rename {colla/tcc => tcc}/colla.def (100%) rename {colla/tcc => tcc}/colla_tcc.h (99%) create mode 100644 tools/build create mode 100644 tools/docs.c rename colla/tracelog.c => tracelog.c (82%) rename colla/tracelog.h => tracelog.h (96%) rename colla/utf8.c => utf8.c (94%) rename colla/utf8.h => utf8.h (95%) rename colla/vec.h => vec.h (100%) rename colla/vmem.c => vmem.c (98%) rename colla/vmem.h => vmem.h (100%) rename {colla/warnings => warnings}/colla_warn_beg.h (100%) rename {colla/warnings => warnings}/colla_warn_end.h (100%) create mode 100644 websocket.c create mode 100644 websocket.h rename colla/xml.c => xml.c (99%) rename colla/xml.h => xml.h (100%) diff --git a/colla/arena.c b/arena.c similarity index 88% rename from colla/arena.c rename to arena.c index fdf09da..5262568 100644 --- a/colla/arena.c +++ b/arena.c @@ -20,16 +20,17 @@ static void arena__free_virtual(arena_t *arena); static void arena__free_malloc(arena_t *arena); arena_t arenaInit(const arena_desc_t *desc) { + arena_t out = {0}; + if (desc) { switch (desc->type) { - case ARENA_VIRTUAL: return arena__make_virtual(desc->allocation); - case ARENA_MALLOC: return arena__make_malloc(desc->allocation); - case ARENA_STATIC: return arena__make_static(desc->static_buffer, desc->allocation); + case ARENA_VIRTUAL: out = arena__make_virtual(desc->allocation); break; + case ARENA_MALLOC: out = arena__make_malloc(desc->allocation); break; + case ARENA_STATIC: out = arena__make_static(desc->static_buffer, desc->allocation); break; } } - debug("couldn't init arena: %p %d\n", desc, desc ? desc->type : 0); - return (arena_t){0}; + return out; } void arenaCleanup(arena_t *arena) { @@ -50,17 +51,9 @@ void arenaCleanup(arena_t *arena) { arena->type = 0; } -arena_t arenaScratch(arena_t *arena) { - if (!arena) { - return (arena_t){0}; - } - - return (arena_t) { - .start = arena->current, - .current = arena->current, - .end = arena->end, - .type = arena->type, - }; +arena_t arenaScratch(arena_t *arena, usize size) { + uint8 *buffer = alloc(arena, uint8, size, ALLOC_SOFT_FAIL | ALLOC_NOZERO); + return arena__make_static(buffer, buffer ? size : 0); } void *arenaAlloc(const arena_alloc_desc_t *desc) { diff --git a/colla/arena.h b/arena.h similarity index 91% rename from colla/arena.h rename to arena.h index 829cf6f..b398d67 100644 --- a/colla/arena.h +++ b/arena.h @@ -2,7 +2,7 @@ #include "collatypes.h" -#ifdef __TINYC__ +#if COLLA_TCC #define alignof __alignof__ #else #define alignof _Alignof @@ -37,23 +37,27 @@ typedef struct { arena_t *arena; usize count; alloc_flags_e flags; - usize size; usize align; + usize size; } arena_alloc_desc_t; +#ifndef ARENA_NO_SIZE_HELPERS #define KB(count) ( (count) * 1024) #define MB(count) (KB(count) * 1024) #define GB(count) (MB(count) * 1024) +#endif // arena_type_e type, usize allocation, [ byte *static_buffer ] #define arenaMake(...) arenaInit(&(arena_desc_t){ __VA_ARGS__ }) -// arena_t *arena, T type, [ usize count, alloc_flags_e flags, usize size, usize align ] +// arena_t *arena, T type, [ usize count, alloc_flags_e flags, usize align, usize size ] #define alloc(arenaptr, type, ...) arenaAlloc(&(arena_alloc_desc_t){ .size = sizeof(type), .count = 1, .align = alignof(type), .arena = arenaptr, __VA_ARGS__ }) arena_t arenaInit(const arena_desc_t *desc); void arenaCleanup(arena_t *arena); +arena_t arenaScratch(arena_t *arena, usize size); + void *arenaAlloc(const arena_alloc_desc_t *desc); usize arenaTell(arena_t *arena); usize arenaRemaining(arena_t *arena); diff --git a/colla/base64.c b/base64.c similarity index 98% rename from colla/base64.c rename to base64.c index eaccb62..4c78ba2 100644 --- a/colla/base64.c +++ b/base64.c @@ -4,7 +4,7 @@ #include "arena.h" -static char encoding_table[] = { +static unsigned char encoding_table[] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', diff --git a/colla/base64.h b/base64.h similarity index 100% rename from colla/base64.h rename to base64.h diff --git a/colla/bits.h b/bits.h similarity index 88% rename from colla/bits.h rename to bits.h index 30b44c8..1e79ef2 100644 --- a/colla/bits.h +++ b/bits.h @@ -24,7 +24,7 @@ inline uint32 bitsCtz(uint32 v) { return v ? __builtin_ctz(v) : 0; #elif BITS_WIN uint32 trailing = 0; - return _BitScanForward(&trailing, v) ? trailing : 0; + return _BitScanForward((unsigned long *)&trailing, v) ? trailing : 0; #else return 0; #endif diff --git a/build.c b/build.c index 8d32fe9..a98ac51 100644 --- a/build.c +++ b/build.c @@ -1,8 +1,9 @@ #if COLLA_ONLYCORE - #define COLLA_NOTHREADS 1 - #define COLLA_NOSOCKETS 1 - #define COLLA_NOHTTP 1 - #define COLLA_NOSERVER 1 + #define COLLA_NOTHREADS 1 + #define COLLA_NOSOCKETS 1 + #define COLLA_NOHTTP 1 + #define COLLA_NOSERVER 1 + #define COLLA_NOHOTRELOAD 1 #endif #if COLLA_NOSOCKETS @@ -12,32 +13,40 @@ #define COLLA_NOSERVER 1 #endif -#include "src/arena.c" -#include "src/base64.c" -#include "src/file.c" -#include "src/format.c" -#include "src/ini.c" -#include "src/json.c" -#include "src/str.c" -#include "src/strstream.c" -#include "src/tracelog.c" -#include "src/utf8.c" -#include "src/vmem.c" -#include "src/xml.c" -#include "src/hot_reload.c" +#include "arena.c" +#include "base64.c" +#include "file.c" +#include "format.c" +#include "ini.c" +#include "json.c" +#include "str.c" +#include "strstream.c" +#include "tracelog.c" +#include "utf8.c" +#include "vmem.c" +#include "xml.c" +#include "sha1.c" +#include "markdown.c" +#include "highlight.c" +#include "dir.c" #if !COLLA_NOTHREADS -#include "src/cthreads.c" +#include "cthreads.c" #endif #if !COLLA_NOSOCKETS -#include "src/socket.c" +#include "socket.c" +#include "websocket.c" #endif #if !COLLA_NOHTTP -#include "src/http.c" +#include "http.c" #endif #if !COLLA_NOSERVER -#include "src/server.c" +#include "server.c" #endif + +#if !COLLA_NOHOTRELOAD +#include "hot_reload.c" +#endif \ No newline at end of file diff --git a/colla/colladefines.h b/colladefines.h similarity index 80% rename from colla/colladefines.h rename to colladefines.h index a9d58e6..840d7bd 100644 --- a/colla/colladefines.h +++ b/colladefines.h @@ -41,6 +41,14 @@ #endif +#if defined(__COSMOPOLITAN__) +#define COLLA_COSMO 1 +#else +#define COLLA_COSMO 0 +#endif + +#define COLLA_POSIX (COLLA_OSX || COLLA_LIN || COLLA_COSMO) + #if defined(__clang__) #define COLLA_CLANG 1 @@ -88,3 +96,17 @@ #define COLLA_CMT_LIB 0 #endif + + +#if COLLA_WIN + +#define WIN32_LEAN_AND_MEAN +#define NOMINMAX + +#endif + +#undef min +#undef max + +#define min(a, b) ((a) < (b) ? (a) : (b)) +#define max(a, b) ((a) > (b) ? (a) : (b)) \ No newline at end of file diff --git a/colla/collatypes.h b/collatypes.h similarity index 100% rename from colla/collatypes.h rename to collatypes.h diff --git a/colla/cthreads.c b/cthreads.c similarity index 94% rename from colla/cthreads.c rename to cthreads.c index 9208231..b910438 100644 --- a/colla/cthreads.c +++ b/cthreads.c @@ -1,281 +1,282 @@ -#include "cthreads.h" - -#include - -typedef struct { - cthread_func_t func; - void *arg; -} _thr_internal_t; - -#if COLLA_WIN -#define WIN32_LEAN_AND_MEAN -#include - -// == THREAD =========================================== - -static DWORD _thrFuncInternal(void *arg) { - _thr_internal_t *params = (_thr_internal_t *)arg; - cthread_func_t func = params->func; - void *argument = params->arg; - free(params); - return (DWORD)func(argument); -} - -cthread_t thrCreate(cthread_func_t func, void *arg) { - HANDLE thread = INVALID_HANDLE_VALUE; - _thr_internal_t *params = calloc(1, sizeof(_thr_internal_t)); - - if(params) { - params->func = func; - params->arg = arg; - - thread = CreateThread(NULL, 0, _thrFuncInternal, params, 0, NULL); - } - - return (cthread_t)thread; -} - -bool thrValid(cthread_t ctx) { - return (HANDLE)ctx != INVALID_HANDLE_VALUE; -} - -bool thrDetach(cthread_t ctx) { - return CloseHandle((HANDLE)ctx); -} - -cthread_t thrCurrent(void) { - return (cthread_t)GetCurrentThread(); -} - -int thrCurrentId(void) { - return GetCurrentThreadId(); -} - -int thrGetId(cthread_t ctx) { -#if COLLA_TCC - return 0; -#endif - return GetThreadId((HANDLE)ctx); -} - -void thrExit(int code) { - ExitThread(code); -} - -bool thrJoin(cthread_t ctx, int *code) { - if(!ctx) return false; - int return_code = WaitForSingleObject((HANDLE)ctx, INFINITE); - if(code) *code = return_code; - BOOL success = CloseHandle((HANDLE)ctx); - return return_code != WAIT_FAILED && success; -} - -// == MUTEX ============================================ - -cmutex_t mtxInit(void) { - CRITICAL_SECTION *crit_sec = calloc(1, sizeof(CRITICAL_SECTION)); - if(crit_sec) { - InitializeCriticalSection(crit_sec); - } - return (cmutex_t)crit_sec; -} - -void mtxFree(cmutex_t ctx) { - DeleteCriticalSection((CRITICAL_SECTION *)ctx); - free((CRITICAL_SECTION *)ctx); -} - -bool mtxValid(cmutex_t ctx) { - return (void *)ctx != NULL; -} - -bool mtxLock(cmutex_t ctx) { - EnterCriticalSection((CRITICAL_SECTION *)ctx); - return true; -} - -bool mtxTryLock(cmutex_t ctx) { - return TryEnterCriticalSection((CRITICAL_SECTION *)ctx); -} - -bool mtxUnlock(cmutex_t ctx) { - LeaveCriticalSection((CRITICAL_SECTION *)ctx); - return true; -} - -#if !COLLA_NO_CONDITION_VAR -// == CONDITION VARIABLE =============================== - -#include "tracelog.h" - -condvar_t condInit(void) { - CONDITION_VARIABLE *cond = calloc(1, sizeof(CONDITION_VARIABLE)); - InitializeConditionVariable(cond); - return (condvar_t)cond; -} - -void condFree(condvar_t cond) { - free((CONDITION_VARIABLE *)cond); -} - -void condWake(condvar_t cond) { - WakeConditionVariable((CONDITION_VARIABLE *)cond); -} - -void condWakeAll(condvar_t cond) { - WakeAllConditionVariable((CONDITION_VARIABLE *)cond); -} - -void condWait(condvar_t cond, cmutex_t mtx) { - SleepConditionVariableCS((CONDITION_VARIABLE *)cond, (CRITICAL_SECTION *)mtx, INFINITE); -} - -void condWaitTimed(condvar_t cond, cmutex_t mtx, int milliseconds) { - SleepConditionVariableCS((CONDITION_VARIABLE *)cond, (CRITICAL_SECTION *)mtx, milliseconds); -} - -#endif - -#else -#include -#include -#include -#include - -// == THREAD =========================================== - -#define INT_TO_VOIDP(a) ((void *)((uintptr_t)(a))) - -static void *_thrFuncInternal(void *arg) { - _thr_internal_t *params = (_thr_internal_t *)arg; - cthread_func_t func = params->func; - void *argument = params->arg; - free(params); - return INT_TO_VOIDP(func(argument)); -} - -cthread_t thrCreate(cthread_func_t func, void *arg) { - pthread_t handle = (pthread_t)NULL; - - _thr_internal_t *params = calloc(1, sizeof(_thr_internal_t)); - - if(params) { - params->func = func; - params->arg = arg; - - int result = pthread_create(&handle, NULL, _thrFuncInternal, params); - if(result) handle = (pthread_t)NULL; - } - - return (cthread_t)handle; -} - -bool thrValid(cthread_t ctx) { - return (void *)ctx != NULL; -} - -bool thrDetach(cthread_t ctx) { - return pthread_detach((pthread_t)ctx) == 0; -} - -cthread_t thrCurrent(void) { - return (cthread_t)pthread_self(); -} - -int thrCurrentId(void) { - return (int)pthread_self(); -} - -int thrGetId(cthread_t ctx) { - return (int)ctx; -} - -void thrExit(int code) { - pthread_exit(INT_TO_VOIDP(code)); -} - -bool thrJoin(cthread_t ctx, int *code) { - void *result = code; - return pthread_join((pthread_t)ctx, &result) != 0; -} - -// == MUTEX ============================================ - -cmutex_t mtxInit(void) { - pthread_mutex_t *mutex = calloc(1, sizeof(pthread_mutex_t)); - - if(mutex) { - if(pthread_mutex_init(mutex, NULL)) { - free(mutex); - mutex = NULL; - } - } - - return (cmutex_t)mutex; -} - -void mtxFree(cmutex_t ctx) { - pthread_mutex_destroy((pthread_mutex_t *)ctx); -} - -bool mtxValid(cmutex_t ctx) { - return (void *)ctx != NULL; -} - -bool mtxLock(cmutex_t ctx) { - return pthread_mutex_lock((pthread_mutex_t *)ctx) == 0; -} - -bool mtxTryLock(cmutex_t ctx) { - return pthread_mutex_trylock((pthread_mutex_t *)ctx) == 0; -} - -bool mtxUnlock(cmutex_t ctx) { - return pthread_mutex_unlock((pthread_mutex_t *)ctx) == 0; -} - -#if !COLLA_NO_CONDITION_VAR - -// == CONDITION VARIABLE =============================== - -condvar_t condInit(void) { - pthread_cond_t *cond = calloc(1, sizeof(pthread_cond_t)); - - if(cond) { - if(pthread_cond_init(cond, NULL)) { - free(cond); - cond = NULL; - } - } - - return (condvar_t)cond; -} - -void condFree(condvar_t cond) { - if (!cond) return; - pthread_cond_destroy((pthread_cond_t *)cond); - free((pthread_cond_t *)cond); -} - -void condWake(condvar_t cond) { - pthread_cond_signal((pthread_cond_t *)cond); -} - -void condWakeAll(condvar_t cond) { - pthread_cond_broadcast((pthread_cond_t *)cond); -} - -void condWait(condvar_t cond, cmutex_t mtx) { - pthread_cond_wait((pthread_cond_t *)cond, (pthread_mutex_t *)mtx); -} - -void condWaitTimed(condvar_t cond, cmutex_t mtx, int milliseconds) { - struct timespec timeout; - time(&timeout.tv_sec); - timeout.tv_nsec += milliseconds * 1000000; - pthread_cond_timedwait((pthread_cond_t *)cond, (pthread_mutex_t *)mtx, &timeout); -} - -#endif - -#endif +#include "cthreads.h" + +#include + +// TODO: swap calloc for arenas + +typedef struct { + cthread_func_t func; + void *arg; +} _thr_internal_t; + +#if COLLA_WIN +#include + +// == THREAD =========================================== + +static DWORD WINAPI _thrFuncInternal(void *arg) { + _thr_internal_t *params = (_thr_internal_t *)arg; + cthread_func_t func = params->func; + void *argument = params->arg; + free(params); + return (DWORD)func(argument); +} + +cthread_t thrCreate(cthread_func_t func, void *arg) { + HANDLE thread = INVALID_HANDLE_VALUE; + _thr_internal_t *params = calloc(1, sizeof(_thr_internal_t)); + + if(params) { + params->func = func; + params->arg = arg; + + thread = CreateThread(NULL, 0, _thrFuncInternal, params, 0, NULL); + } + + return (cthread_t)thread; +} + +bool thrValid(cthread_t ctx) { + return (HANDLE)ctx != INVALID_HANDLE_VALUE; +} + +bool thrDetach(cthread_t ctx) { + return CloseHandle((HANDLE)ctx); +} + +cthread_t thrCurrent(void) { + return (cthread_t)GetCurrentThread(); +} + +int thrCurrentId(void) { + return GetCurrentThreadId(); +} + +int thrGetId(cthread_t ctx) { +#if COLLA_TCC + return 0; +#endif + return GetThreadId((HANDLE)ctx); +} + +void thrExit(int code) { + ExitThread(code); +} + +bool thrJoin(cthread_t ctx, int *code) { + if(!ctx) return false; + int return_code = WaitForSingleObject((HANDLE)ctx, INFINITE); + if(code) *code = return_code; + BOOL success = CloseHandle((HANDLE)ctx); + return return_code != WAIT_FAILED && success; +} + +// == MUTEX ============================================ + +cmutex_t mtxInit(void) { + CRITICAL_SECTION *crit_sec = calloc(1, sizeof(CRITICAL_SECTION)); + if(crit_sec) { + InitializeCriticalSection(crit_sec); + } + return (cmutex_t)crit_sec; +} + +void mtxFree(cmutex_t ctx) { + DeleteCriticalSection((CRITICAL_SECTION *)ctx); + free((CRITICAL_SECTION *)ctx); +} + +bool mtxValid(cmutex_t ctx) { + return (void *)ctx != NULL; +} + +bool mtxLock(cmutex_t ctx) { + EnterCriticalSection((CRITICAL_SECTION *)ctx); + return true; +} + +bool mtxTryLock(cmutex_t ctx) { + return TryEnterCriticalSection((CRITICAL_SECTION *)ctx); +} + +bool mtxUnlock(cmutex_t ctx) { + LeaveCriticalSection((CRITICAL_SECTION *)ctx); + return true; +} + +#if !COLLA_NO_CONDITION_VAR +// == CONDITION VARIABLE =============================== + +#include "tracelog.h" + +condvar_t condInit(void) { + CONDITION_VARIABLE *cond = calloc(1, sizeof(CONDITION_VARIABLE)); + InitializeConditionVariable(cond); + return (condvar_t)cond; +} + +void condFree(condvar_t cond) { + free((CONDITION_VARIABLE *)cond); +} + +void condWake(condvar_t cond) { + WakeConditionVariable((CONDITION_VARIABLE *)cond); +} + +void condWakeAll(condvar_t cond) { + WakeAllConditionVariable((CONDITION_VARIABLE *)cond); +} + +void condWait(condvar_t cond, cmutex_t mtx) { + SleepConditionVariableCS((CONDITION_VARIABLE *)cond, (CRITICAL_SECTION *)mtx, INFINITE); +} + +void condWaitTimed(condvar_t cond, cmutex_t mtx, int milliseconds) { + SleepConditionVariableCS((CONDITION_VARIABLE *)cond, (CRITICAL_SECTION *)mtx, milliseconds); +} + +#endif + +#else +#include +#include +#include +#include + +// == THREAD =========================================== + +#define INT_TO_VOIDP(a) ((void *)((uintptr_t)(a))) + +static void *_thrFuncInternal(void *arg) { + _thr_internal_t *params = (_thr_internal_t *)arg; + cthread_func_t func = params->func; + void *argument = params->arg; + free(params); + return INT_TO_VOIDP(func(argument)); +} + +cthread_t thrCreate(cthread_func_t func, void *arg) { + pthread_t handle = (pthread_t)NULL; + + _thr_internal_t *params = calloc(1, sizeof(_thr_internal_t)); + + if(params) { + params->func = func; + params->arg = arg; + + int result = pthread_create(&handle, NULL, _thrFuncInternal, params); + if(result) handle = (pthread_t)NULL; + } + + return (cthread_t)handle; +} + +bool thrValid(cthread_t ctx) { + return (void *)ctx != NULL; +} + +bool thrDetach(cthread_t ctx) { + return pthread_detach((pthread_t)ctx) == 0; +} + +cthread_t thrCurrent(void) { + return (cthread_t)pthread_self(); +} + +int thrCurrentId(void) { + return (int)pthread_self(); +} + +int thrGetId(cthread_t ctx) { + return (int)ctx; +} + +void thrExit(int code) { + pthread_exit(INT_TO_VOIDP(code)); +} + +bool thrJoin(cthread_t ctx, int *code) { + void *result = code; + return pthread_join((pthread_t)ctx, &result) != 0; +} + +// == MUTEX ============================================ + +cmutex_t mtxInit(void) { + pthread_mutex_t *mutex = calloc(1, sizeof(pthread_mutex_t)); + + if(mutex) { + if(pthread_mutex_init(mutex, NULL)) { + free(mutex); + mutex = NULL; + } + } + + return (cmutex_t)mutex; +} + +void mtxFree(cmutex_t ctx) { + pthread_mutex_destroy((pthread_mutex_t *)ctx); +} + +bool mtxValid(cmutex_t ctx) { + return (void *)ctx != NULL; +} + +bool mtxLock(cmutex_t ctx) { + return pthread_mutex_lock((pthread_mutex_t *)ctx) == 0; +} + +bool mtxTryLock(cmutex_t ctx) { + return pthread_mutex_trylock((pthread_mutex_t *)ctx) == 0; +} + +bool mtxUnlock(cmutex_t ctx) { + return pthread_mutex_unlock((pthread_mutex_t *)ctx) == 0; +} + +#if !COLLA_NO_CONDITION_VAR + +// == CONDITION VARIABLE =============================== + +condvar_t condInit(void) { + pthread_cond_t *cond = calloc(1, sizeof(pthread_cond_t)); + + if(cond) { + if(pthread_cond_init(cond, NULL)) { + free(cond); + cond = NULL; + } + } + + return (condvar_t)cond; +} + +void condFree(condvar_t cond) { + if (!cond) return; + pthread_cond_destroy((pthread_cond_t *)cond); + free((pthread_cond_t *)cond); +} + +void condWake(condvar_t cond) { + pthread_cond_signal((pthread_cond_t *)cond); +} + +void condWakeAll(condvar_t cond) { + pthread_cond_broadcast((pthread_cond_t *)cond); +} + +void condWait(condvar_t cond, cmutex_t mtx) { + pthread_cond_wait((pthread_cond_t *)cond, (pthread_mutex_t *)mtx); +} + +void condWaitTimed(condvar_t cond, cmutex_t mtx, int milliseconds) { + struct timespec timeout; + time(&timeout.tv_sec); + timeout.tv_nsec += milliseconds * 1000000; + pthread_cond_timedwait((pthread_cond_t *)cond, (pthread_mutex_t *)mtx, &timeout); +} + +#endif + +#endif diff --git a/colla/cthreads.h b/cthreads.h similarity index 95% rename from colla/cthreads.h rename to cthreads.h index 9e8ecd3..03202f2 100644 --- a/colla/cthreads.h +++ b/cthreads.h @@ -1,57 +1,57 @@ -#pragma once - -#include "collatypes.h" - -#if COLLA_TCC -#define COLLA_NO_CONDITION_VAR 1 -#endif - -typedef struct arena_t arena_t; - -// == THREAD =========================================== - -typedef uintptr_t cthread_t; - -typedef int (*cthread_func_t)(void *); - -cthread_t thrCreate(cthread_func_t func, void *arg); -bool thrValid(cthread_t ctx); -bool thrDetach(cthread_t ctx); - -cthread_t thrCurrent(void); -int thrCurrentId(void); -int thrGetId(cthread_t ctx); - -void thrExit(int code); -bool thrJoin(cthread_t ctx, int *code); - -// == MUTEX ============================================ - -typedef uintptr_t cmutex_t; - -cmutex_t mtxInit(void); -void mtxFree(cmutex_t ctx); - -bool mtxValid(cmutex_t ctx); - -bool mtxLock(cmutex_t ctx); -bool mtxTryLock(cmutex_t ctx); -bool mtxUnlock(cmutex_t ctx); - -#if !COLLA_NO_CONDITION_VAR -// == CONDITION VARIABLE =============================== - -typedef uintptr_t condvar_t; - -#define COND_WAIT_INFINITE 0xFFFFFFFF - -condvar_t condInit(void); -void condFree(condvar_t cond); - -void condWake(condvar_t cond); -void condWakeAll(condvar_t cond); - -void condWait(condvar_t cond, cmutex_t mtx); -void condWaitTimed(condvar_t cond, cmutex_t mtx, int milliseconds); - +#pragma once + +#include "collatypes.h" + +#if COLLA_TCC +#define COLLA_NO_CONDITION_VAR 1 +#endif + +typedef struct arena_t arena_t; + +// == THREAD =========================================== + +typedef uintptr_t cthread_t; + +typedef int (*cthread_func_t)(void *); + +cthread_t thrCreate(cthread_func_t func, void *arg); +bool thrValid(cthread_t ctx); +bool thrDetach(cthread_t ctx); + +cthread_t thrCurrent(void); +int thrCurrentId(void); +int thrGetId(cthread_t ctx); + +void thrExit(int code); +bool thrJoin(cthread_t ctx, int *code); + +// == MUTEX ============================================ + +typedef uintptr_t cmutex_t; + +cmutex_t mtxInit(void); +void mtxFree(cmutex_t ctx); + +bool mtxValid(cmutex_t ctx); + +bool mtxLock(cmutex_t ctx); +bool mtxTryLock(cmutex_t ctx); +bool mtxUnlock(cmutex_t ctx); + +#if !COLLA_NO_CONDITION_VAR +// == CONDITION VARIABLE =============================== + +typedef uintptr_t condvar_t; + +#define COND_WAIT_INFINITE 0xFFFFFFFF + +condvar_t condInit(void); +void condFree(condvar_t cond); + +void condWake(condvar_t cond); +void condWakeAll(condvar_t cond); + +void condWait(condvar_t cond, cmutex_t mtx); +void condWaitTimed(condvar_t cond, cmutex_t mtx, int milliseconds); + #endif \ No newline at end of file diff --git a/deprecated/coropool.c b/deprecated/coropool.c deleted file mode 100644 index 9d169e4..0000000 --- a/deprecated/coropool.c +++ /dev/null @@ -1,322 +0,0 @@ -#include "coropool.h" - -#if 0 -#include -#include - -#include "jobpool.h" - -enum { - NUM_JOBS = 50 -}; - -struct _pool_internal_t; - -typedef union job_t { - struct { - mco_coro *co; - struct _pool_internal_t *pool; - }; - struct { - union job_t *next_free; - void *__padding; - }; -} job_t; - -static inline bool _isJobValid(job_t *job) { - return job->pool != NULL; -} - -typedef struct jobchunk_t { - job_t jobs[NUM_JOBS]; - job_t *first_free; - struct jobchunk_t *next; -} jobchunk_t; - -void _chunkInit(jobchunk_t *chunk) { - if (!chunk) return; - chunk->first_free = chunk->jobs; - for (int i = 0; i < (NUM_JOBS - 1); ++i) { - chunk->jobs[i].next_free = chunk->jobs + i + 1; - } -} - -jobchunk_t *_chunkNew(jobchunk_t *prev) { - jobchunk_t *chunk = calloc(1, sizeof(jobchunk_t)); - _chunkInit(chunk); - prev->next = chunk; - return chunk; -} - -job_t *_chunkGetFirstFree(jobchunk_t *chunk) { - job_t *first_free = chunk->first_free; - if (first_free) { - chunk->first_free = first_free->next_free; - } - return first_free; -} - -void _chunkSetFree(jobchunk_t *chunk, job_t *job) { - job->pool = NULL; - job->next_free = chunk->first_free; - chunk->first_free = job; -} - -typedef struct _pool_internal_t { - jobpool_t pool; - jobchunk_t head_chunk; - cmutex_t chunk_mtx; -} _pool_internal_t; - -static int _worker(void *arg); - -coropool_t copoInit(uint32 num) { - _pool_internal_t *pool = calloc(1, sizeof(_pool_internal_t)); - pool->pool = poolInit(num); - _chunkInit(&pool->head_chunk); - pool->chunk_mtx = mtxInit(); - return pool; -} - -void copoFree(coropool_t copo) { - _pool_internal_t *pool = copo; - - poolWait(pool->pool); - poolFree(pool->pool); - - jobchunk_t *chunk = &pool->head_chunk; - while (chunk) { - jobchunk_t *next = chunk->next; - free(chunk); - chunk = next; - } - - mtxFree(pool->chunk_mtx); - free(pool); -} - -bool copoAdd(coropool_t copo, copo_func_f fn) { - mco_desc desc = mco_desc_init(fn, 0); - return copoAddDesc(copo, &desc); -} - -bool copoAddDesc(coropool_t copo, mco_desc *desc) { - mco_coro *co; - mco_create(&co, desc); - return copoAddCo(copo, co); -} - -static bool _copoAddJob(job_t *job) { - //return poolAdd(job->pool->pool, _worker, job); - return true; -} - -static bool _copoRemoveJob(job_t *job) { - _pool_internal_t *pool = job->pool; - // find chunk - jobchunk_t *chunk = &pool->head_chunk; - while (chunk) { - if (chunk->jobs < job && (chunk->jobs + NUM_JOBS) > job) { - break; - } - chunk = chunk->next; - } - if (!chunk) return false; - mtxLock(pool->chunk_mtx); - _chunkSetFree(chunk, job); - mtxUnlock(pool->chunk_mtx); -} - -bool copoAddCo(coropool_t copo, mco_coro *co) { - _pool_internal_t *pool = copo; - - job_t *new_job = NULL; - jobchunk_t *chunk = &pool->head_chunk; - - mtxLock(pool->chunk_mtx); - while (!new_job) { - new_job = _chunkGetFirstFree(chunk); - if (!new_job) { - if (!chunk->next) { - info("adding new chunk"); - chunk->next = _chunkNew(chunk); - } - chunk = chunk->next; - } - } - mtxUnlock(pool->chunk_mtx); - - new_job->co = co; - new_job->pool = pool; - - //return poolAdd(pool->pool, _worker, new_job); - return _copoAddJob(new_job); -} - -void copoWait(coropool_t copo) { - _pool_internal_t *pool = copo; - poolWait(pool->pool); -} - -static int _worker(void *arg) { - job_t *job = arg; - mco_result res = mco_resume(job->co); - if (res != MCO_DEAD) { - _copoAddJob(job); - } - else { - _copoRemoveJob(job); - } - return 0; -} -#endif - -#include - -typedef struct { - vec(mco_coro *) jobs; - uint32 head; - cmutex_t work_mutex; - condvar_t work_cond; - condvar_t working_cond; - int32 working_count; - int32 thread_count; - bool stop; -} _copo_internal_t; - -static mco_coro *_getJob(_copo_internal_t *copo); -static int _copoWorker(void *arg); - -coropool_t copoInit(uint32 num) { - if (!num) num = 2; - - _copo_internal_t *copo = malloc(sizeof(_copo_internal_t)); - *copo = (_copo_internal_t){ - .work_mutex = mtxInit(), - .work_cond = condInit(), - .working_cond = condInit(), - .thread_count = (int32)num - }; - - for (usize i = 0; i < num; ++i) { - thrDetach(thrCreate(_copoWorker, copo)); - } - - return copo; -} - -void copoFree(coropool_t copo_in) { - _copo_internal_t *copo = copo_in; - if (!copo) return; - - mtxLock(copo->work_mutex); - copo->stop = true; - condWakeAll(copo->work_cond); - mtxUnlock(copo->work_mutex); - - copoWait(copo); - - vecFree(copo->jobs); - mtxFree(copo->work_mutex); - condFree(copo->work_cond); - condFree(copo->working_cond); - - free(copo); -} - -bool copoAdd(coropool_t copo, copo_func_f fn) { - mco_desc desc = mco_desc_init(fn, 0); - return copoAddDesc(copo, &desc); -} - -bool copoAddDesc(coropool_t copo, mco_desc *desc) { - mco_coro *co; - mco_create(&co, desc); - return copoAddCo(copo, co); -} - -bool copoAddCo(coropool_t copo_in, mco_coro *co) { - _copo_internal_t *copo = copo_in; - if (!copo) return false; - - mtxLock(copo->work_mutex); - - if (copo->head > vecLen(copo->jobs)) { - vecClear(copo->jobs); - copo->head = 0; - } - - vecAppend(copo->jobs, co); - - condWake(copo->work_cond); - mtxUnlock(copo->work_mutex); - - return true; -} - -void copoWait(coropool_t copo_in) { - _copo_internal_t *copo = copo_in; - if (!copo) return; - - mtxLock(copo->work_mutex); - // while its either - // - working and there's still some threads doing some work - // - not working and there's still some threads exiting - while ((!copo->stop && copo->working_count > 0) || - (copo->stop && copo->thread_count > 0) - ) { - condWait(copo->working_cond, copo->work_mutex); - } - mtxUnlock(copo->work_mutex); -} - -// == PRIVATE FUNCTIONS =================================== - -static mco_coro *_getJob(_copo_internal_t *copo) { - if (copo->head >= vecLen(copo->jobs)) { - copo->head = 0; - } - return copo->jobs[copo->head++]; -} - -static int _copoWorker(void *arg) { - _copo_internal_t *copo = arg; - - while (true) { - mtxLock(copo->work_mutex); - // wait for a new job - while (copo->head >= vecLen(copo->jobs) && !copo->stop) { - condWait(copo->work_cond, copo->work_mutex); - } - - if (copo->stop) { - break; - } - - mco_coro *job = _getJob(copo); - copo->working_count++; - mtxUnlock(copo->work_mutex); - - if (job && mco_status(job) != MCO_DEAD) { - mco_resume(job); - if (mco_status(job) != MCO_DEAD) { - copoAddCo(copo, job); - } - } - - mtxLock(copo->work_mutex); - copo->working_count--; - if (!copo->stop && - copo->working_count == 0 && - copo->head == vecLen(copo->jobs) - ) { - condWake(copo->working_cond); - } - mtxUnlock(copo->work_mutex); - } - - copo->thread_count--; - condWake(copo->working_cond); - mtxUnlock(copo->work_mutex); - return 0; -} \ No newline at end of file diff --git a/deprecated/coropool.h b/deprecated/coropool.h deleted file mode 100644 index 2bbdfc5..0000000 --- a/deprecated/coropool.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -#include -#include - -#include "minicoro.h" - -typedef void *coropool_t; -typedef void (*copo_func_f)(mco_coro *co); - -coropool_t copoInit(uint32 num); -void copoFree(coropool_t copo); - -bool copoAdd(coropool_t copo, copo_func_f fn); -bool copoAddDesc(coropool_t copo, mco_desc *desc); -bool copoAddCo(coropool_t copo, mco_coro *co); -void copoWait(coropool_t copo); diff --git a/deprecated/coroutine.c b/deprecated/coroutine.c deleted file mode 100644 index 661b143..0000000 --- a/deprecated/coroutine.c +++ /dev/null @@ -1,11 +0,0 @@ -#include "coroutine.h" - -coroutine_t coInit() { - return (coroutine_t) { - .state = 0 - }; -} - -bool coIsDead(coroutine_t co) { - return co.state == -1; -} diff --git a/deprecated/coroutine.h b/deprecated/coroutine.h deleted file mode 100644 index 7a24984..0000000 --- a/deprecated/coroutine.h +++ /dev/null @@ -1,133 +0,0 @@ -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -#include // memset -#include "collatypes.h" -#include "tracelog.h" // fatal - -// heavily inspired by https://gist.github.com/Enichan/5f01140530ff0133fde19c9549a2a973 - -#if 0 -// Usage example: -typedef struct { - int result; - coroutine_t co; -} co_int_t; - -bool coVoid(co_void_t *co) { - costate(co_nostate); - coroutine({ - printf("hello"); yield(); - printf("world"); yield(); - printf("how"); yield(); - printf("is"); yield(); - printf("it"); yield(); - printf("going?\n"); - }); -} - -bool countToTen(co_int_t *co) { - costate(int i; int ii;); - coroutine({ - for(self.i = 0; self.i < 10; ++self.i) { - self.ii += self.i; - yieldVal(self.ii); - } - }); -} - -int main() { - co_void_t covoid = {0}; - while(coVoid(&covoid)) { - printf(" "); - } - - co_int_t coint; - coint.co = coInit(); - while(countToTen(&coint)) { - printf("%d ", coint.result); - } - printf("\n"); - // reset coroutine for next call - coint.co = coInit(); - while(countToTen(&coint)) { - printf("%d ", coint.result); - } - printf("\n"); -} -#endif - -typedef struct { - int state; -} coroutine_t; - -typedef struct { - coroutine_t co; -} co_void_t; - -coroutine_t coInit(); -bool coIsDead(coroutine_t co); - -#define COVAR co -#define COSELF self - -#define costate(...) \ - typedef struct { bool init; __VA_ARGS__ } COSTATE; \ - static COSTATE self = {0} - -#define co_nostate { char __dummy__; } - -#define yieldBreak() \ - COVAR->co.state = -1; \ - self.init = false; \ - return false; - -#define coroutine(...) \ - if(!self.init) { \ - if(COVAR->co.state != 0) { \ - fatal("coroutine not initialized in %s:%d,\n" \ - "did you forget to do '= {0};'?", \ - __FILE__, __LINE__); \ - } \ - memset(&self, 0, sizeof(self)); \ - self.init = true; \ - } \ - switch(COVAR->co.state) { \ - case 0:; \ - __VA_ARGS__ \ - } \ - yieldBreak(); - -#define yield() _yield(__COUNTER__) -#define _yield(count) \ - do { \ - COVAR->co.state = count; \ - return true; \ - case count:; \ - } while(0); - -#define yieldVal(v) _yieldVal(v, __COUNTER__) -#define _yieldVal(v, count) \ - do { \ - COVAR->co.state = count; \ - COVAR->result = v; \ - return true; \ - case count:; \ - } while(0); - -// increment __COUNTER__ past 0 so we don't get double case errors -#define CONCAT_IMPL(x, y) x##y -#define MACRO_CONCAT(x, y) CONCAT_IMPL(x, y) -#define __INC_COUNTER int MACRO_CONCAT(__counter_tmp_, __COUNTER__) -__INC_COUNTER; - -#undef CONCAT_IMPL -#undef MACRO_CONCAT -#undef __INC_COUNTER - -#ifdef __cplusplus -} // extern "C" -#endif diff --git a/deprecated/dir.c b/deprecated/dir.c deleted file mode 100644 index 5a4c2a5..0000000 --- a/deprecated/dir.c +++ /dev/null @@ -1,204 +0,0 @@ -#include "dir.h" -#include "tracelog.h" - -#if COLLA_WIN -#include "win32_slim.h" -#include -#include - -#include "strstream.h" - -typedef struct { - dir_entry_t cur; - dir_entry_t next; - HANDLE handle; -} _dir_internal_t; - -static dir_entry_t _fillDirEntry(WIN32_FIND_DATAW *data) { - return (dir_entry_t) { - .type = - data->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ? - FS_TYPE_DIR : FS_TYPE_FILE, - .name = strFromWChar(data->cFileName, 0) - }; -} - -static _dir_internal_t _getFirst(const wchar_t *path) { - _dir_internal_t res = {0}; - WIN32_FIND_DATAW data = {0}; - - res.handle = FindFirstFileW(path, &data); - - if(res.handle != INVALID_HANDLE_VALUE) { - res.next = _fillDirEntry(&data); - } - - return res; -} - -static void _getNext(_dir_internal_t *ctx) { - WIN32_FIND_DATAW data = {0}; - - BOOL result = FindNextFileW(ctx->handle, &data); - if(!result) { - if(GetLastError() == ERROR_NO_MORE_FILES) { - FindClose(ctx->handle); - ctx->handle = NULL; - return; - } - } - ctx->next = _fillDirEntry(&data); -} - -dir_t dirOpen(const char *path) { - DWORD n = GetFullPathName(path, 0, NULL, NULL); - outstream_t out = ostrInitLen(n + 3); - n = GetFullPathName(path, n, out.buf, NULL); - assert(n > 0); - out.len += n; - switch(ostrBack(out)) { - case '\\': - case '/': - case ':': - // directory ends in path separator - // NOP - break; - default: - ostrPutc(&out, '\\'); - } - ostrPutc(&out, '*'); - - _dir_internal_t *dir = malloc(sizeof(_dir_internal_t)); - if(dir) { - wchar_t *wpath = strToWCHAR(ostrAsStr(out)); - assert(wpath); - *dir = _getFirst(wpath); - free(wpath); - } - ostrFree(out); - - return dir; -} - -void dirClose(dir_t ctx) { - free(ctx); -} - -bool dirValid(dir_t ctx) { - _dir_internal_t *dir = (_dir_internal_t*)ctx; - return dir->handle != INVALID_HANDLE_VALUE; -} - -dir_entry_t *dirNext(dir_t ctx) { - _dir_internal_t *dir = (_dir_internal_t*)ctx; - strFree(dir->cur.name); - if(!dir->handle) return NULL; - dir->cur = dir->next; - _getNext(dir); - return &dir->cur; -} - -void dirCreate(const char *path) { - CreateDirectoryA(path, NULL); -} - -#else - -#include -#include -#include -#include - -// taken from https://sites.uclouvain.be/SystInfo/usr/include/dirent.h.html -// hopefully shouldn't be needed -#ifndef DT_DIR - #define DT_DIR 4 -#endif -#ifndef DT_REG - #define DT_REG 8 -#endif - -typedef struct { - DIR *dir; - dir_entry_t next; -} _dir_internal_t; - -dir_t dirOpen(const char *path) { - _dir_internal_t *ctx = (_dir_internal_t *)calloc(1, sizeof(_dir_internal_t)); - if(ctx) ctx->dir = opendir(path); - return ctx; -} - -void dirClose(dir_t ctx) { - if(ctx) { - _dir_internal_t *in = (_dir_internal_t *)ctx; - closedir(in->dir); - free(in); - } -} - -bool dirValid(dir_t ctx) { - _dir_internal_t *dir = (_dir_internal_t*)ctx; - return dir->dir != NULL; -} - -dir_entry_t *dirNext(dir_t ctx) { - if(!ctx) return NULL; - _dir_internal_t *in = (_dir_internal_t *)ctx; - strFree(in->next.name); - struct dirent *dp = readdir(in->dir); - if(!dp) return NULL; - - switch(dp->d_type) { - case DT_DIR: in->next.type = FS_TYPE_DIR; break; - case DT_REG: in->next.type = FS_TYPE_FILE; break; - default: in->next.type = FS_TYPE_UNKNOWN; break; - } - - in->next.name = strFromStr(dp->d_name); - return &in->next; -} - -void dirCreate(const char *path) { - mkdir(path, 0700); -} - -#endif - -#include - -bool dirRemove(const char *path) { - dir_t dir = dirOpen(path); - if (!dirValid(dir)) return false; - dir_entry_t *it = NULL; - while((it = dirNext(dir))) { - if (it->type == FS_TYPE_FILE) { - str_t file_path = strFromFmt("%s/%s", path, it->name.buf); - if (remove(file_path.buf)) { - err("couldn't remove %s > %s", file_path.buf, strerror(errno)); - } - strFree(file_path); - } - else if (it->type == FS_TYPE_DIR) { - if (strcmp(it->name.buf, ".") == 0 || strcmp(it->name.buf, "..") == 0) { - continue; - } - str_t new_path = strFromFmt("%s/%s", path, it->name.buf); - info("new path: %s--%s -> %s", path, it->name.buf, new_path.buf); - if (!dirRemove(new_path.buf)) { - err("couldn't delete folder %s", new_path.buf); - break; - } - strFree(new_path); - } - else { - err("%d -> %s", it->type, it->name.buf); - } - } - dirClose(dir); -#if COLLA_WIN - return RemoveDirectoryA(path); -#else - return rmdir(path) == 0; -#endif -} diff --git a/deprecated/dir.h b/deprecated/dir.h deleted file mode 100644 index 90a5871..0000000 --- a/deprecated/dir.h +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -#include "str.h" - -typedef void *dir_t; - -typedef enum { - FS_TYPE_UNKNOWN, - FS_TYPE_FILE, - FS_TYPE_DIR, -} fs_type_t; - -typedef struct { - fs_type_t type; - str_t name; -} dir_entry_t; - -dir_t dirOpen(const char *path); -void dirClose(dir_t ctx); - -bool dirValid(dir_t ctx); - -dir_entry_t *dirNext(dir_t ctx); - -void dirCreate(const char *path); -bool dirRemove(const char *path); - -#ifdef __cplusplus -} // extern "C" -#endif diff --git a/deprecated/dirwatch.c b/deprecated/dirwatch.c deleted file mode 100644 index 49ff44e..0000000 --- a/deprecated/dirwatch.c +++ /dev/null @@ -1,295 +0,0 @@ -#include "dirwatch.h" - -#include -#include -#include -#include "tracelog.h" - -#if COLLA_WIN -#include "win32_slim.h" -#include "str.h" - -typedef struct { - const char *path; - dirwatch_cb_t on_event; - BOOL should_continue; - HANDLE stop_event; -} __dirwatch_internal_t; - -static int watchDirThread(void *cdata) { - __dirwatch_internal_t *desc = (__dirwatch_internal_t*)cdata; - - // stop_event is called from another thread when watchDirThread should exit - desc->stop_event = CreateEvent(NULL, TRUE, FALSE, NULL); - - HANDLE file = CreateFile( - desc->path, - FILE_LIST_DIRECTORY, - FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, - NULL, - OPEN_EXISTING, - FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, - NULL - ); - assert(file != INVALID_HANDLE_VALUE); - OVERLAPPED overlapped = {0}; - overlapped.hEvent = CreateEvent(NULL, FALSE, 0, NULL); - - uint8_t change_buf[1024]; - BOOL success = ReadDirectoryChangesW( - file, change_buf, sizeof(change_buf), TRUE, - FILE_NOTIFY_CHANGE_FILE_NAME | - FILE_NOTIFY_CHANGE_DIR_NAME | - FILE_NOTIFY_CHANGE_LAST_WRITE, - NULL, &overlapped, NULL - ); - assert(success); - - HANDLE events[2] = { - overlapped.hEvent, - desc->stop_event - }; - - WCHAR old_name[MAX_PATH]; - dirwatch_file_t data = {0}; - - // if the variable is 32bit aligned, a read/write is already atomice - // https://docs.microsoft.com/en-us/windows/win32/sync/interlocked-variable-access - while(desc->should_continue) { - DWORD result = WaitForMultipleObjects(2, events, FALSE, INFINITE); - - if(result == WAIT_OBJECT_0) { - DWORD bytes_transferred; - GetOverlappedResult(file, &overlapped, &bytes_transferred, FALSE); - - FILE_NOTIFY_INFORMATION *event = (FILE_NOTIFY_INFORMATION*)change_buf; - - for(;;) { - DWORD name_len = event->FileNameLength / sizeof(wchar_t); - - switch(event->Action) { - case FILE_ACTION_ADDED: - data.name = strFromWCHAR(event->FileName, name_len).buf; - data.oldname = NULL; - desc->on_event(desc->path, DIRWATCH_FILE_ADDED, data); - break; - case FILE_ACTION_REMOVED: - data.name = strFromWCHAR(event->FileName, name_len).buf; - data.oldname = NULL; - desc->on_event(desc->path, DIRWATCH_FILE_REMOVED, data); - break; - case FILE_ACTION_RENAMED_OLD_NAME: - memcpy(old_name, event->FileName, event->FileNameLength); - old_name[event->FileNameLength] = '\0'; - break; - case FILE_ACTION_RENAMED_NEW_NAME: - data.name = strFromWCHAR(event->FileName, name_len).buf; - data.oldname = strFromWCHAR(old_name, 0).buf; - desc->on_event(desc->path, DIRWATCH_FILE_RENAMED, data); - break; - } - - if(data.name) free(data.name); - if(data.oldname) free(data.oldname); - - data = (dirwatch_file_t){0}; - - if(event->NextEntryOffset) { - *((uint8_t**)&event) += event->NextEntryOffset; - } - else { - break; - } - } - - success = ReadDirectoryChangesW( - file, change_buf, sizeof(change_buf), TRUE, - FILE_NOTIFY_CHANGE_FILE_NAME | - FILE_NOTIFY_CHANGE_DIR_NAME | - FILE_NOTIFY_CHANGE_LAST_WRITE, - NULL, &overlapped, NULL - ); - assert(success); - } - // stop_event - else if(result == WAIT_OBJECT_0 + 1) { - warn("stopping"); - break; - } - } - - return 0; -} - -dirwatch_t watchDir(dirwatch_desc_t desc) { - dirwatch_t dir = {0}; - - __dirwatch_internal_t *opts = HeapAlloc( - GetProcessHeap(), - 0, - sizeof(__dirwatch_internal_t) - ); - - opts->path = desc.path; - opts->on_event = desc.on_event; - opts->should_continue = true; - - dir.desc = (dirwatch_desc_t *)opts; - - dir.handle = thrCreate(watchDirThread, (void *)dir.desc); - - if(thrValid(dir.handle)) { - info("watching %s", desc.path); - } - - return dir; -} - -void waitForWatchDir(dirwatch_t *ctx) { - if(!thrValid(ctx->handle)) { - err("not valid"); - return; - } - - if(!thrJoin(ctx->handle, NULL)) { - err("dirwatch: couldn't wait for thread"); - } - info("waited"); - - HeapFree(GetProcessHeap(), 0, ctx->desc); -} - -void stopWatchDir(dirwatch_t *ctx, bool immediately) { - (void)immediately; - __dirwatch_internal_t *opts = (__dirwatch_internal_t *)ctx->desc; - opts->should_continue = false; - if(immediately) { - if(!SetEvent(opts->stop_event)) { - err("couldn't signal event stop_event: %d", GetLastError()); - } - } - if(!thrJoin(ctx->handle, NULL)) { - err("dirwatch: couldn't wait for thread"); - } - - HeapFree(GetProcessHeap(), 0, ctx->desc); -} - -#else -#include -#include // read -#include -#include -#include // MAX_PATH -#include - -#define EVENT_SIZE (sizeof(struct inotify_event)) -#define BUF_LEN (1024 * (EVENT_SIZE + 16)) - -#define ERROR(str) { err(str ": %s", strerror(errno)); goto error; } - -typedef struct { - const char *path; - dirwatch_cb_t on_event; - atomic_bool should_continue; - int fd; - int wd; -} __dirwatch_internal_t; - -static int watchDirThread(void *cdata) { - __dirwatch_internal_t *desc = (__dirwatch_internal_t *)cdata; - info("watching %s", desc->path); - - int length/*, fd, wd*/; - char buffer[BUF_LEN]; - - desc->fd = inotify_init(); - - if(desc->fd < 0) { - ERROR("inotify_init failed"); - } - - desc->wd = inotify_add_watch(desc->fd, desc->path, IN_MOVE | IN_CREATE | IN_DELETE); - - char old_path[PATH_MAX] = {0}; - dirwatch_file_t data = {0}; - - while(desc->should_continue) { - length = (int)read(desc->fd, buffer, BUF_LEN); - - if(length < 0) { - ERROR("couldn't read"); - } - - for(int i = 0; i < length;) { - struct inotify_event *event = (struct inotify_event *) &buffer[i]; - if(event->len) { - uint32_t e = event->mask; - // bool is_dir = e & IN_ISDIR; - - if(e & IN_CREATE) { - data.name = event->name; - desc->on_event(desc->path, DIRWATCH_FILE_ADDED, data); - } - else if(e & IN_DELETE) { - data.name = event->name; - desc->on_event(desc->path, DIRWATCH_FILE_REMOVED, data); - } - else if(e & IN_MOVED_FROM) { - memcpy(old_path, event->name, event->len); - old_path[event->len] = '\0'; - } - else if(e & IN_MOVED_TO) { - data.oldname = old_path; - data.name = event->name; - desc->on_event(desc->path, DIRWATCH_FILE_RENAMED, data); - } - } - - i += EVENT_SIZE + event->len; - } - } - - inotify_rm_watch(desc->fd, desc->wd); - close(desc->fd); - - return 0; -error: - return 1; -} - -dirwatch_t watchDir(dirwatch_desc_t desc) { - dirwatch_t dir = {0}; - - __dirwatch_internal_t *opts = malloc(sizeof(__dirwatch_internal_t)); - opts->path = desc.path; - opts->on_event = desc.on_event; - opts->fd = 0; - opts->wd = 0; - opts->should_continue = true; - - dir.desc = (dirwatch_desc_t *)opts; - - dir.handle = thrCreate(watchDirThread, opts); - - return dir; -} - -void waitForWatchDir(dirwatch_t *ctx) { - thrJoin(ctx->handle, NULL); - free(ctx->desc); -} - -void stopWatchDir(dirwatch_t *ctx, bool immediately) { - __dirwatch_internal_t *opts = (__dirwatch_internal_t *)ctx->desc; - opts->should_continue = false; - // this one might get called in the thread first, but it doesn't matter - if(immediately) { - inotify_rm_watch(opts->fd, opts->wd); - close(opts->fd); - } - thrJoin(ctx->handle, NULL); - free(opts); -} - -#endif diff --git a/deprecated/dirwatch.h b/deprecated/dirwatch.h deleted file mode 100644 index a2d73aa..0000000 --- a/deprecated/dirwatch.h +++ /dev/null @@ -1,67 +0,0 @@ -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -/* - * Basic usage: - * - * // this function will be called from another thread every time - * // something happens to 'path' - * void onEvent(const char *path, int action, dirwatch_file_t file) { - * switch(action) { - * case DIRWATCH_FILE_ADDED: [do something] break; - * case DIRWATCH_FILE_REMOVED: [do something] break; - * case DIRWATCH_FILE_RENAMED: [do something] break; - * } - * } - * - * int main() { - * dirwatch_t dir = watchDir((dirwatch_desc_t){ - * .dirname = "watch/", - * .on_event = onEvent - * }); - * - * waitForWatchDir(&dir); - * } - */ - -#include "collatypes.h" -#include "cthreads.h" - -enum { - DIRWATCH_FILE_ADDED, - DIRWATCH_FILE_REMOVED, - DIRWATCH_FILE_RENAMED, -}; - -typedef struct { - char *name; - char *oldname; -} dirwatch_file_t; - -typedef void (*dirwatch_cb_t)(const char *path, int action, dirwatch_file_t data); - -typedef struct { - const char *path; - dirwatch_cb_t on_event; -} dirwatch_desc_t; - -typedef struct { - cthread_t handle; - dirwatch_desc_t *desc; -} dirwatch_t; - -// Creates a thread and starts watching the folder specified by desc -// if any action happend on_event will be called from this thread -dirwatch_t watchDir(dirwatch_desc_t desc); -// waits forever -void waitForWatchDir(dirwatch_t *ctx); -// stops dirwatch thread, if immediately is true, it will try to close it right away -// otherwise it might wait for one last event -void stopWatchDir(dirwatch_t *ctx, bool immediately); - -#ifdef __cplusplus -} // extern "C" -#endif diff --git a/deprecated/fs.c b/deprecated/fs.c deleted file mode 100644 index 85a6a51..0000000 --- a/deprecated/fs.c +++ /dev/null @@ -1,134 +0,0 @@ -#include "fs.h" - -#include -#include -#include -#include - -#include "tracelog.h" - -#if COLLA_WIN -#include "win32_slim.h" - -#include - -static fsmode_t _modeToType(unsigned int mode) { - switch(mode & _S_IFMT) { - case _S_IFDIR: return FS_MODE_DIR; - case _S_IFCHR: return FS_MODE_CHARACTER_DEVICE; - case _S_IFREG: return FS_MODE_FILE; - case _S_IFIFO: return FS_MODE_FIFO; - default: return FS_MODE_UKNOWN; - } -} - -fs_stat_t fsStat(file_t fp) { - TCHAR path[MAX_PATH]; - GetFinalPathNameByHandle( - (HANDLE)fp.handle, - path, - MAX_PATH, - 0 - ); - - struct stat statbuf; - int res = stat(path, &statbuf); - if(res == 0) { - return (fs_stat_t) { - .type = _modeToType(statbuf.st_mode), - .size = statbuf.st_size, - .last_access = statbuf.st_atime, - .last_modif = statbuf.st_mtime - }; - } - else { - return (fs_stat_t) { 0 }; - } -} - -fs_time_t fsAsTime(int64_t timer) { - struct tm t; - errno_t error = localtime_s(&t, &timer); - - if(error == 0) { - return (fs_time_t) { - .year = t.tm_year + 1900, - .month = t.tm_mon + 1, - .day = t.tm_mday, - .hour = t.tm_hour, - .minutes = t.tm_min, - .seconds = t.tm_sec, - .daylight_saving = t.tm_isdst > 0 - }; - } - else { - char buf[128]; - strerror_s(buf, sizeof(buf), error); - err("%s", buf); - return (fs_time_t) { 0 }; - } -} - -bool fsIsDir(const char *path) { - DWORD attr = GetFileAttributes(path); - - return attr != INVALID_FILE_ATTRIBUTES && - attr & FILE_ATTRIBUTE_DIRECTORY; -} - -#else -#include - -static fsmode_t _modeToType(unsigned int mode) { - switch(mode & __S_IFMT) { - case __S_IFDIR: return FS_MODE_DIR; - case __S_IFCHR: return FS_MODE_CHARACTER_DEVICE; - case __S_IFREG: return FS_MODE_FILE; - case __S_IFIFO: return FS_MODE_FIFO; - default: return FS_MODE_UKNOWN; - } -} - -fs_stat_t fsStat(file_t fp) { - int fd = fileno((FILE*)fp); - struct stat statbuf; - int res = fstat(fd, &statbuf); - if(res == 0) { - return (fs_stat_t) { - .type = _modeToType(statbuf.st_mode), - .size = (uint64_t)statbuf.st_size, - .last_access = statbuf.st_atime, - .last_modif = statbuf.st_mtime - }; - } - else { - return (fs_stat_t) { 0 }; - } -} - -fs_time_t fsAsTime(int64_t timer) { - struct tm *t = localtime(&timer); - - if(t) { - return (fs_time_t) { - .year = t->tm_year + 1900, - .month = t->tm_mon + 1, - .day = t->tm_mday, - .hour = t->tm_hour, - .minutes = t->tm_min, - .seconds = t->tm_sec, - .daylight_saving = t->tm_isdst > 0 - }; - } - else { - err("%s", strerror(errno)); - return (fs_time_t) { 0 }; - } -} - -bool fsIsDir(const char *path) { - struct stat statbuf; - return stat(path, &statbuf) == 0 && S_ISDIR(statbuf.st_mode); -} - -#endif diff --git a/deprecated/fs.h b/deprecated/fs.h deleted file mode 100644 index e917311..0000000 --- a/deprecated/fs.h +++ /dev/null @@ -1,43 +0,0 @@ -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -#include "collatypes.h" - -#include "file.h" - -typedef enum { - FS_MODE_FILE, - FS_MODE_DIR, - FS_MODE_CHARACTER_DEVICE, - FS_MODE_FIFO, - FS_MODE_UKNOWN, -} fsmode_t; - -typedef struct { - fsmode_t type; - uint64_t size; - int64_t last_access; - int64_t last_modif; -} fs_stat_t; - -typedef struct { - int year; - int month; - int day; - int hour; - int minutes; - int seconds; - bool daylight_saving; -} fs_time_t; - -fs_stat_t fsStat(file_t fp); -fs_time_t fsAsTime(int64_t time); - -bool fsIsDir(const char *path); - -#ifdef __cplusplus -} // extern "C" -#endif diff --git a/deprecated/hashmap.c b/deprecated/hashmap.c deleted file mode 100644 index 0bfff05..0000000 --- a/deprecated/hashmap.c +++ /dev/null @@ -1,130 +0,0 @@ -#include "hashmap.h" - -#include - -static uint64 hash_seed = 0; - -hashmap_t hmInit(usize initial_cap) { - hashmap_t map = {0}; - if (!initial_cap) initial_cap = 512; - vecReserve(map.nodes, initial_cap); - memset(map.nodes, 0, sizeof(hashnode_t) * initial_cap); - return map; -} - -void hmFree(hashmap_t map) { - vecFree(map.nodes); -} - -void hmSet(hashmap_t *map, uint64 hash, uint64 index) { - uint32 hm_index = hash % vecCap(map->nodes); - - while (map->nodes[hm_index].hash) { - hashnode_t *node = &map->nodes[hm_index]; - if (node->hash == hash) { - node->index = index; - return; - } - hm_index = (hm_index + 1) % vecCap(map->nodes); - } - - map->nodes[hm_index].hash = hash; - map->nodes[hm_index].index = index; - _veclen(map->nodes)++; - - float load_factor = (float)vecLen(map->nodes) / (float)vecCap(map->nodes); - if (load_factor > 0.75f) { - uint32 old_cap = vecCap(map->nodes); - vecReserve(map->nodes, old_cap); - for (usize i = old_cap; i < vecCap(map->nodes); ++i) { - map->nodes[i].hash = 0; - map->nodes[i].index = 0; - } - } -} - -uint64 hmGet(hashmap_t map, uint64 hash) { - uint32 hm_index = hash % vecCap(map.nodes); - - do { - hashnode_t *node = &map.nodes[hm_index]; - if (node->hash == hash) { - return node->index; - } - hm_index = (hm_index + 1) % vecCap(map.nodes); - } while (map.nodes[hm_index].hash); - - return 0; -} - -void hmDelete(hashmap_t *map, uint64 hash) { - uint32 hm_index = hash % vecCap(map->nodes); - - do { - hashnode_t *node = &map->nodes[hm_index]; - if (node->hash == hash) { - node->hash = 0; - node->index = 0; - break; - } - hm_index = (hm_index + 1) % vecCap(map->nodes); - } while (map->nodes[hm_index].hash); - - if(vecLen(map->nodes)) _veclen(map->nodes)--; -} - -void hashSetSeed(uint64 new_seed) { - hash_seed = new_seed; -} - -uint64 hash(const void *ptr, usize len) { - const uint64 m = 0xC6A4A7935BD1E995LLU; - const int r = 47; - - uint64 h = hash_seed ^ (len * m); - - const uint64 *data = (const uint64 *)ptr; - const uint64 *end = (len >> 3) + data; - - while (data != end) { - uint64 k = *data++; - - k *= m; - k ^= k >> r; - k *= m; - - h ^= k; - h *= m; - } - - const unsigned char * data2 = (const unsigned char *)data; - - switch(len & 7) { - case 7: h ^= (uint64_t)(data2[6]) << 48; - case 6: h ^= (uint64_t)(data2[5]) << 40; - case 5: h ^= (uint64_t)(data2[4]) << 32; - case 4: h ^= (uint64_t)(data2[3]) << 24; - case 3: h ^= (uint64_t)(data2[2]) << 16; - case 2: h ^= (uint64_t)(data2[1]) << 8; - case 1: h ^= (uint64_t)(data2[0]); - h *= m; - }; - - h ^= h >> r; - h *= m; - h ^= h >> r; - - return h; -} - -uint64 hashStr(str_t str) { - return hash(str.buf, str.len); -} - -uint64 hashView(strview_t view) { - return hash(view.buf, view.len); -} - -uint64 hashCStr(const char *cstr) { - return hash(cstr, strlen(cstr)); -} diff --git a/deprecated/hashmap.h b/deprecated/hashmap.h deleted file mode 100644 index 1808970..0000000 --- a/deprecated/hashmap.h +++ /dev/null @@ -1,49 +0,0 @@ -#pragma once - -#include "collatypes.h" -#include "vec.h" -#include "str.h" - -/* -Example usage: -hashSetSeed(time(NULL)); -vec(const char *) strings = NULL; -hashmap_t map = hmInit(32); - -// mapGet returns 0 in case it doesn't find anything, this way we don't need -// to check its return value -vecAppend(strings, "nil"); - -hmSet(&map, hashCStr("english"), vecAppend(strings, "hello")); -hmSet(&map, hashCStr("french"), vecAppend(strings, "bonjour")); -hmSet(&map, hashCStr("italian"), vecAppend(strings, "ciao")); - -printf("english: %s\n", strings[hmGet(map, hashCStr("english"))]); -printf("french: %s\n", strings[hmGet(map, hashCStr("french"))]); -printf("italian: %s\n", strings[hmGet(map, hashCStr("italian"))]); - -mapFree(map); -vecFree(strings); -*/ - -typedef struct { - uint64 hash; - uint64 index; -} hashnode_t; - -typedef struct { - vec(hashnode_t) nodes; -} hashmap_t; - -hashmap_t hmInit(usize initial_cap); -void hmFree(hashmap_t map); - -void hmSet(hashmap_t *map, uint64 hash, uint64 index); -uint64 hmGet(hashmap_t map, uint64 hash); -void hmDelete(hashmap_t *map, uint64 hash); - -void hashSetSeed(uint64 new_seed); -uint64 hash(const void *data, usize len); -uint64 hashStr(str_t str); -uint64 hashView(strview_t view); -uint64 hashCStr(const char *cstr); diff --git a/deprecated/jobpool.c b/deprecated/jobpool.c deleted file mode 100644 index f4e32bc..0000000 --- a/deprecated/jobpool.c +++ /dev/null @@ -1,144 +0,0 @@ -#include "jobpool.h" - -#include "vec.h" - -typedef struct { - cthread_func_t func; - void *arg; -} job_t; - -typedef struct { - vec(job_t) jobs; - uint32 head; - cmutex_t work_mutex; - condvar_t work_cond; - condvar_t working_cond; - int32 working_count; - int32 thread_count; - bool stop; -} _pool_internal_t; - -static job_t _getJob(_pool_internal_t *pool); -static int _poolWorker(void *arg); - -jobpool_t poolInit(uint32 num) { - if (!num) num = 2; - - _pool_internal_t *pool = malloc(sizeof(_pool_internal_t)); - *pool = (_pool_internal_t){ - .work_mutex = mtxInit(), - .work_cond = condInit(), - .working_cond = condInit(), - .thread_count = (int32)num - }; - - for (usize i = 0; i < num; ++i) { - thrDetach(thrCreate(_poolWorker, pool)); - } - - return pool; -} - -void poolFree(jobpool_t pool_in) { - _pool_internal_t *pool = pool_in; - if (!pool) return; - - mtxLock(pool->work_mutex); - pool->stop = true; - condWakeAll(pool->work_cond); - mtxUnlock(pool->work_mutex); - - poolWait(pool); - - vecFree(pool->jobs); - mtxFree(pool->work_mutex); - condFree(pool->work_cond); - condFree(pool->working_cond); - - free(pool); -} - -bool poolAdd(jobpool_t pool_in, cthread_func_t func, void *arg) { - _pool_internal_t *pool = pool_in; - if (!pool) return false; - - mtxLock(pool->work_mutex); - - if (pool->head > vecLen(pool->jobs)) { - vecClear(pool->jobs); - pool->head = 0; - } - - job_t job = { func, arg }; - vecAppend(pool->jobs, job); - - condWake(pool->work_cond); - mtxUnlock(pool->work_mutex); - - return true; -} - -void poolWait(jobpool_t pool_in) { - _pool_internal_t *pool = pool_in; - if (!pool) return; - - mtxLock(pool->work_mutex); - // while its either - // - working and there's still some threads doing some work - // - not working and there's still some threads exiting - while ((!pool->stop && pool->working_count > 0) || - (pool->stop && pool->thread_count > 0) - ) { - condWait(pool->working_cond, pool->work_mutex); - } - mtxUnlock(pool->work_mutex); -} - -// == PRIVATE FUNCTIONS =================================== - -static job_t _getJob(_pool_internal_t *pool) { - if (pool->head >= vecLen(pool->jobs)) { - pool->head = 0; - } - job_t job = pool->jobs[pool->head++]; - return job; -} - -static int _poolWorker(void *arg) { - _pool_internal_t *pool = arg; - - while (true) { - mtxLock(pool->work_mutex); - // wait for a new job - while (pool->head >= vecLen(pool->jobs) && !pool->stop) { - condWait(pool->work_cond, pool->work_mutex); - } - - if (pool->stop) { - break; - } - - job_t job = _getJob(pool); - pool->working_count++; - mtxUnlock(pool->work_mutex); - - if (job.func) { - job.func(job.arg); - } - - mtxLock(pool->work_mutex); - pool->working_count--; - if (!pool->stop && - pool->working_count == 0 && - pool->head == vecLen(pool->jobs) - ) { - condWake(pool->working_cond); - } - mtxUnlock(pool->work_mutex); - } - - pool->thread_count--; - condWake(pool->working_cond); - mtxUnlock(pool->work_mutex); - return 0; -} diff --git a/deprecated/jobpool.h b/deprecated/jobpool.h deleted file mode 100644 index 401aa60..0000000 --- a/deprecated/jobpool.h +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once - -#include "collatypes.h" -#include "cthreads.h" - -typedef void *jobpool_t; - -jobpool_t poolInit(uint32 num); -void poolFree(jobpool_t pool); - -bool poolAdd(jobpool_t pool, cthread_func_t func, void *arg); -void poolWait(jobpool_t pool); diff --git a/deprecated/os.c b/deprecated/os.c deleted file mode 100644 index 7586088..0000000 --- a/deprecated/os.c +++ /dev/null @@ -1,107 +0,0 @@ -#include "os.h" - -#include -#include -#include - -#if COLLA_WIN -#define _BUFSZ 128 - -#include - -// modified from netbsd source http://cvsweb.netbsd.org/bsdweb.cgi/pkgsrc/pkgtools/libnbcompat/files/getdelim.c?only_with_tag=MAIN -isize getdelim(char **buf, size_t *bufsz, int delimiter, FILE *fp) { - char *ptr, *eptr; - - if(*buf == NULL || *bufsz == 0) { - *bufsz = _BUFSZ; - if((*buf = malloc(*bufsz)) == NULL) { - return -1; - } - } - - isize result = -1; - // usually fgetc locks every read, using windows-specific - // _lock_file and _unlock_file will be faster - _lock_file(fp); - - for(ptr = *buf, eptr = *buf + *bufsz;;) { - int c = _getc_nolock(fp); - if(c == -1) { - if(feof(fp)) { - isize diff = (isize)(ptr - *buf); - if(diff != 0) { - *ptr = '\0'; - result = diff; - break; - } - } - break; - } - *ptr++ = (char)c; - if(c == delimiter) { - *ptr = '\0'; - result = ptr - *buf; - break; - } - if((ptr + 2) >= eptr) { - char *nbuf; - size_t nbufsz = *bufsz * 2; - isize d = ptr - *buf; - if((nbuf = realloc(*buf, nbufsz)) == NULL) { - break; - } - *buf = nbuf; - *bufsz = nbufsz; - eptr = nbuf + nbufsz; - ptr = nbuf + d; - } - } - - _unlock_file(fp); - return result; -} - -// taken from netbsd source http://cvsweb.netbsd.org/bsdweb.cgi/pkgsrc/pkgtools/libnbcompat/files/getline.c?only_with_tag=MAIN -isize getline(char **line_ptr, size_t *n, FILE *stream) { - return getdelim(line_ptr, n, '\n', stream); -} - -str_t getUserName() { - char buf[UNLEN + 1]; - DWORD sz = sizeof(buf); - BOOL res = GetUserNameA(buf, &sz); - if(!res) { - return strInit(); - } - return strFromBuf(buf, sz); -} - -#else - -#include -#include -#include -#include - -int stricmp(const char *a, const char *b) { - int result; - - if (a == b) { - return 0; - } - - while ((result = tolower(*a) - tolower(*b++)) == 0) { - if (*a++ == '\0') { - break; - } - } - - return result; -} - -str_t getUserName() { - return strFromStr(getlogin()); -} - -#endif diff --git a/deprecated/os.h b/deprecated/os.h deleted file mode 100644 index 160f092..0000000 --- a/deprecated/os.h +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include -#include "str.h" -#include "collatypes.h" - -#if COLLA_WIN - #include - #include "win32_slim.h" - isize getdelim(char **buf, size_t *bufsz, int delimiter, FILE *fp); - isize getline(char **line_ptr, size_t *n, FILE *stream); - #define stricmp _stricmp -#else - #ifndef _GNU_SOURCE - #define _GNU_SOURCE - #endif - #include - int stricmp(const char *a, const char *b); -#endif - -str_t getUserName(); - -#ifdef __cplusplus -} // extern "C" -#endif diff --git a/dir.c b/dir.c new file mode 100644 index 0000000..3dd1b35 --- /dev/null +++ b/dir.c @@ -0,0 +1,163 @@ +#include "dir.h" + +#if COLLA_WIN + +#include + +typedef struct dir_t { + WIN32_FIND_DATA find_data; + HANDLE handle; + dir_entry_t cur_entry; + dir_entry_t next_entry; +} dir_t; + +static dir_entry_t dir__entry_from_find_data(arena_t *arena, WIN32_FIND_DATA *fd) { + dir_entry_t out = {0}; + + out.name = str(arena, fd->cFileName); + + if (fd->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + out.type = DIRTYPE_DIR; + } + else { + LARGE_INTEGER filesize = { + .LowPart = fd->nFileSizeLow, + .HighPart = fd->nFileSizeHigh, + }; + out.filesize = filesize.QuadPart; + } + + return out; +} + +dir_t *dirOpen(arena_t *arena, strview_t path) { + uint8 tmpbuf[1024] = {0}; + arena_t scratch = arenaMake(ARENA_STATIC, sizeof(tmpbuf), tmpbuf); + + TCHAR *winpath = strvToTChar(&scratch, path); + // get a little extra leeway + TCHAR fullpath[MAX_PATH + 16] = {0}; + DWORD pathlen = GetFullPathName(winpath, MAX_PATH, fullpath, NULL); + // add asterisk at the end of the path + if (fullpath[pathlen ] != '\\' && fullpath[pathlen] != '/') { + fullpath[pathlen++] = '\\'; + } + fullpath[pathlen++] = '*'; + fullpath[pathlen++] = '\0'; + + dir_t *ctx = alloc(arena, dir_t); + ctx->handle = FindFirstFile(fullpath, &ctx->find_data); + + if (ctx->handle == INVALID_HANDLE_VALUE) { + arenaPop(arena, sizeof(dir_t)); + return NULL; + } + + ctx->next_entry = dir__entry_from_find_data(arena, &ctx->find_data); + + return ctx; +} + +void dirClose(dir_t *ctx) { + FindClose(ctx->handle); + ctx->handle = INVALID_HANDLE_VALUE; +} + +bool dirIsValid(dir_t *ctx) { + return ctx && ctx->handle != INVALID_HANDLE_VALUE; +} + +dir_entry_t *dirNext(arena_t *arena, dir_t *ctx) { + if (!dirIsValid(ctx)) { + return NULL; + } + + ctx->cur_entry = ctx->next_entry; + + ctx->next_entry = (dir_entry_t){0}; + + if (FindNextFile(ctx->handle, &ctx->find_data)) { + ctx->next_entry = dir__entry_from_find_data(arena, &ctx->find_data); + } + else { + dirClose(ctx); + } + + return &ctx->cur_entry; +} + +#elif COLLA_POSIX + +#include +#include +#include +#include + +// taken from https://sites.uclouvain.be/SystInfo/usr/include/dirent.h.html +// hopefully shouldn't be needed +#ifndef DT_DIR + #define DT_DIR 4 +#endif +#ifndef DT_REG + #define DT_REG 8 +#endif + +typedef struct dir_t { + DIR *dir; + dir_entry_t next; +} dir_t; + +dir_t *dirOpen(arena_t *arena, strview_t path) { + if (strvIsEmpty(path)) { + err("path cannot be null"); + return NULL; + } + + uint8 tmpbuf[1024]; + arena_t scratch = arenaMake(ARENA_STATIC, sizeof(tmpbuf), tmpbuf); + str_t dirpath = str(&scratch, path); + + DIR *dir = opendir(dirpath.buf); + if (!dir) { + err("could not open dir (%v)", path); + return NULL; + } + + dir_t *ctx = alloc(arena, dir_t); + ctx->dir = dir; + return ctx; +} + +void dirClose(dir_t *ctx) { + if (ctx) { + closedir(ctx->dir); + } +} + +bool dirIsValid(dir_t *ctx) { + return ctx && ctx->dir; +} + +dir_entry_t *dirNext(arena_t *arena, dir_t *ctx) { + if (!ctx) return NULL; + + struct dirent *dp = readdir(ctx->dir); + if (!dp) { + dirClose(ctx); + return NULL; + } + + ctx->next.name = (str_t){ dp->d_name, strlen(dp->d_name) }; + ctx->next.type = dp->d_type == DT_DIR ? DIRTYPE_DIR : DIRTYPE_FILE; + ctx->next.filesize = 0; + + if (dp->d_type == DT_REG) { + struct stat file_info = {0}; + stat(dp->d_name, &file_info); + ctx->next.filesize = file_info.st_size; + } + + return &ctx->next; +} + +#endif diff --git a/dir.h b/dir.h new file mode 100644 index 0000000..e4ed540 --- /dev/null +++ b/dir.h @@ -0,0 +1,25 @@ +#pragma once + +#include "str.h" +#include "arena.h" + +typedef struct dir_t dir_t; + +typedef enum { + DIRTYPE_FILE, + DIRTYPE_DIR, +} dir_type_e; + +typedef struct { + str_t name; + dir_type_e type; + usize filesize; +} dir_entry_t; + +dir_t *dirOpen(arena_t *arena, strview_t path); +// optional, only call this if you want to return before dirNext returns NULL +void dirClose(dir_t *ctx); + +bool dirIsValid(dir_t *ctx); + +dir_entry_t *dirNext(arena_t *arena, dir_t *ctx); diff --git a/docs/arena.md b/docs/arena.md new file mode 100644 index 0000000..fa450e8 --- /dev/null +++ b/docs/arena.md @@ -0,0 +1,131 @@ +--- +title = Arena +--- +# Arena +----------- + +An arena is a bump allocator, under the hood it can use one of 3 strategies to allocate its data: + +* `Virtual`: allocates with virtual memory, meaning that it reserves the data upfront, but only allocates one page at a time (usually 64 Kb). This is the preferred way to use the arena as it can freely allocate gigabytes of memory for free +* `Malloc`: allocates the memory upfront using malloc +* `Static`: uses a buffer passed by the user instead of allocating + +To help with allocating big chunks of data, `arena.h` defines the macros `KB`, `MB`, and `GB`. If you don't need them you can define `ARENA_NO_SIZE_HELPERS` + +To create an arena use the macro `arenaMake`: +```c +arena_t varena = arenaMake(ARENA_VIRTUAL, GB(1)); +uint8 buffer[1024]; +arena_t sarena = arenaMake(ARENA_STATIC, sizeof(buffer), buffer); +``` + +To allocate use the `alloc` macro. The parameters to allocate are: + +* A pointer to the arena +* The type to allocate +* (optional) the number of items to allocate +* (optional) flags: + * `ALLOC_NOZERO`: by default the arena sets the memory to zero before returning, use this if you want to skip it + * `ALLOC_SOFT_FAIL`: by default the arena panics when an allocation fails, meaning that it never returns `NULL`. +* (automatic) the align of the type +* (automatic) the size of the type + +Example usage: + +```c +// allocate 15 strings +str_t *string_list = alloc(arena, str_t, 15); +// allocate a 1024 bytes buffer +uint8 *buffer = alloc(arena, uint8, 1024); +// allocate a structure +game_t *game = alloc(arena, game_t); +``` + +The strength of the arena is that it makes it much easier to reason about lifetimes, for instance: + +```c +// pass a pointer to the arena for the data that we need to return +WCHAR *win32_get_full_path(arena_t *arena, const char *rel_path) { + // we need a small scratch arena for allocations that + // will be thrown away at the end of this function + uint8 scratch_buf[1024]; + arena_t scratch = arenaMake(ARENA_STATIC, sizeof(scratch_buf, scratch_buf)); + + WCHAR *win32_rel_path = str_to_wchar(&scratch, rel_path); + + DWORD pathlen = GetFullPathName(win32_rel_path, 0, NULL, NULL); + WCHAR *fullpath = alloc(arena, WCHAR, pathlen + 1); + GetFullPath(win32_rel_path, pathlen + 1, fullpath, NULL); + + // notice how we don't need to free anything at the end + return fullpath; +} +``` + +There are a few helper functions: + +* `arenaScratch`: sub allocate an arena from another arena +* `arenaTell`: returns the number of bytes allocated +* `arenaRemaining`: returns the number of bytes that can still be allocated +* `arenaRewind`: rewinds the arena to N bytes from the start (effectively "freeing" that memory) +* `arenaPop`: pops N bytes from the arena (effectively "freeing" that memory) + +Here is an example usage of a full program: + +```c +typedef struct { + char *buf; + usize len; +} str_t; + +str_t read_file(arena_t *arena, const char *filename) { + str_t out = {0}; + + FILE *fp = fopen(filename, "rb"); + if (!fp) goto error; + + fseek(fp, 0, SEEK_END); + out.len = ftell(fp); + fseek(fp, 0, SEEK_SET); + + out.buf = alloc(arena, char, out.len + 1); + + fread(out.buf, 1, out.len, fp); + +error: + return out; +} + +void write_file(const char *filename, str_t data) { + FILE *fp = fopen(filename, "wb"); + if (!fp) return; + fwrite(data.buf, 1, data.len, fp); +} + +int main() { + arena_t arena = arenaMake(ARENA_VIRTUAL, GB(1)); + str_t title = {0}; + + { + uint8 tmpbuf[KB(5)]; + arena_t scratch = arenaMake(ARENA_STATIC, sizeof(tmpbuf), tmpbuf); + str_t file = read_file(&scratch, "file.json"); + json_t json = json_parse(&scratch, file); + title = str_dup(arena, json_get(json, "title")); + } + + { + // copying an arena by value effectively makes it a scratch arena, + // as long as you don't use the original inside the same scope! + arena_t scratch = arena; + str_t to_write = str_fmt( + &scratch, + "{ \"title\": \"%s\" }", title.buf + ); + write_file("out.json", to_write); + } + + // cleanup all allocations at once + arenaCleanup(&arena); +} +``` \ No newline at end of file diff --git a/docs/base64.md b/docs/base64.md new file mode 100644 index 0000000..46128bd --- /dev/null +++ b/docs/base64.md @@ -0,0 +1,32 @@ +--- +title = Base 64 +--- +# Base 64 +---------- + +The `base64.h` header has only two functions, one to encode and one to decode to/from base 64. + +Example usage: +```c +char *recv_callback(arena_t *arena, buffer_t msg) { + buffer_t decoded = base64Decode(arena, msg); + alloc(arena, char); // allocate an extra char for the null pointer + return (char *)decoded.data; +} + +buffer_t send_callback(arena_t *arena) { + char msg[] = "Hello World!"; + buffer_t buf = { + .data = msg, + .len = arrlen(msg) - 1, // don't include the null pointer + }; + buffer_t encoded = base64Encode(arena, buf); + return encoded; +} + +int main() { + arena_t arena = arenaMake(ARENA_VIRTUAL, GB(1)); + run_server(&arena, 8080, recv_callback, send_callback); + arenaCleanup(&arena); +} +``` diff --git a/docs/cthreads.md b/docs/cthreads.md new file mode 100644 index 0000000..3f4e1ef --- /dev/null +++ b/docs/cthreads.md @@ -0,0 +1,49 @@ +--- +title = Threads +--- +# Threads +---------- + +Cross platform threads, mutexes and conditional variables. +The api is very similar to pthreads, here is a small example program that uses threads and mutexes. + +```c +struct { + bool exit; + cmutex_t mtx; +} state = {0}; + +int f1(void *) { + mtxLock(state.mtx); + state.exit = true; + mtxUnlock(state.mtx); + return 0; +} + +int f2(void *) { + while (true) { + bool exit = false; + if (mtxTryLock(state.mtx)) { + exit = state.exit; + mtxUnlock(state.mtx); + } + + if (exit) { + break; + } + } + return 0; +} + +int main() { + state.mtx = mtxInit(); + + cthread_t t1 = thrCreate(f1, NULL); + thrDetach(t1); + + cthread_t t2 = thrCreate(f2, NULL); + thrJoin(t2, NULL); + + mtxFree(state.mtx); +} +``` \ No newline at end of file diff --git a/docs/dir.md b/docs/dir.md new file mode 100644 index 0000000..35cdb4e --- /dev/null +++ b/docs/dir.md @@ -0,0 +1,52 @@ +--- +title = Dir +--- +# Dir +---------- + +This header provides a simple directory walker, here is an example usage: + +```c +typedef struct source_t { + str_t filename; + struct source_t *next; +} source_t; + +sources_t get_sources(arena_t *arena, strview_t path) { + uint8 tmpbuf[KB(5)] = {0}; + arena_t scratch = arenaMake(ARENA_STATIC, sizeof(tmpbuf), tmpbuf); + + dir_t *dir = dirOpen(&scratch, path); + dir_entry_t *entry = NULL; + + source_t *sources = NULL; + + while ((entry = dirNext(&scratch, dir))) { + if (entry->type != DIRTYPE_FILE) { + continue; + } + + strview_t ext = fileGetExtension(strv(entry->name)); + if (!strvEquals(ext, strv(".c"))) { + continue; + } + + source_t *new_source = alloc(arena, source_t); + new_source->filename = strFmt(arena, "%v/%v", path, entry->name); + new_source->next = sources; + sources = new_source; + } + + return sources; +} + +int main() { + arena_t arena = arenaMake(ARENA_VIRTUAL, GB(1)); + source_t *sources = get_sources(&arena, strv("src/colla")); + while (sources) { + info("> %v", sources->filename); + sources = sources->next; + } + arenaCleanup(&arena); +} +``` \ No newline at end of file diff --git a/docs/docs.com b/docs/docs.com new file mode 100644 index 0000000000000000000000000000000000000000..0f45b2cb88bd5ca75327076c2cedde60b1076bcd GIT binary patch literal 315392 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 literal 0 HcmV?d00001 diff --git a/docs/file.md b/docs/file.md new file mode 100644 index 0000000..8caf554 --- /dev/null +++ b/docs/file.md @@ -0,0 +1,41 @@ +--- +title = File +--- +# File +---------- + +This header provides cross platform file functionality. +It has all the basics that you can expect which work exactly like the stdio counterparts: + +* `fileOpen` +* `fileClose` +* `fileIsValid` +* `fileRead` +* `fileWrite` +* `fileSeekEnd` +* `fileRewind` +* `fileTell` + +Then there are a few helpers functions for reading / writing: + +* Writing: + * `filePutc` + * `filePuts` + * `filePrintf` + * `fileWriteWhole` +* Reading: + * `fileReadWhole` + * `fileReadWholeStr` + +There are also some functions to get info about a file without having to open it: + +* `fileExists` +* `fileSize` +* `fileGetTime` +* `fileHasChanged` + +And finally, there are some helper functions: + +* `fileGetFullPath` (for windows) +* `fileSplitPath` / `fileGetFilename` / `fileGetExtension` +* `fileDelete` diff --git a/docs/format.md b/docs/format.md new file mode 100644 index 0000000..18ab65d --- /dev/null +++ b/docs/format.md @@ -0,0 +1,28 @@ +--- +title = Format +--- +# Format +---------- + +Small formatting utility, it has 2 functions (and the `va_list` alternatives): + +* `fmtPrint`: equivalent to +* `fmtBuffer` + +It uses [stb_sprintf](https://github.com/nothings/stb/blob/master/stb_sprintf.h) under the hood, but it also has support for printing buffers using `%v` (structures that are a pair of pointer/size) + +This means it can print strings and [string views](/str). + +In + +Usage example: + +```c +int main() { + strview_t v = strv("world"); + char buffer[1024] = {0}; + + fmtPrint("Hello %v!", v); + fmtBuffer(buffer, sizeof(buffer), "Hello %v!", v); +} +``` \ No newline at end of file diff --git a/docs/highlight.md b/docs/highlight.md new file mode 100644 index 0000000..2356571 --- /dev/null +++ b/docs/highlight.md @@ -0,0 +1,37 @@ +--- +title = Highlight +--- +# Highlight +---------- + +This header provides an highlighter for c-like languages (mostly c). +The usage is simple, first create a highlight context using hlInit, for which you need an hl_config_t. The only mandatory argument is colors, which are the strings put before the keywords in the highlighted text. + +You can also pass some flags: +* `HL_FLAG_HTML`: escapes html characters + +If you're using the offline documentation, this is the code used to highlight inside the markdown code blocks (simplified to remove actual markdown parsing): + +```c +str_t highlight_code_block(arena_t *arena, strview_t code) { + uint8 tmpbuf[KB(5)]; + arena_t scratch = arenaMake(ARENA_STATIC, sizeof(tmpbuf), tmpbuf); + + hl_ctx_t *hl = hlInit(&scratch, &(hl_config_t){ + .colors = { + [HL_COLOR_NORMAL] = strv(""), + [HL_COLOR_PREPROC] = strv(""), + [HL_COLOR_TYPES] = strv(""), + [HL_COLOR_CUSTOM_TYPES] = strv(""), + [HL_COLOR_KEYWORDS] = strv(""), + [HL_COLOR_NUMBER] = strv(""), + [HL_COLOR_STRING] = strv(""), + [HL_COLOR_COMMENT] = strv(""), + [HL_COLOR_FUNC] = strv(""), + [HL_COLOR_SYMBOL] = strv(""), + [HL_COLOR_MACRO] = strv(""), + }, + .flags = HL_FLAG_HTML, + }); +} +``` \ No newline at end of file diff --git a/docs/hot_reload.md b/docs/hot_reload.md new file mode 100644 index 0000000..ba173ff --- /dev/null +++ b/docs/hot_reload.md @@ -0,0 +1,79 @@ +--- +title = Hot Reload +--- +# Hot Reload +---------- + +This header provides cross-platform library hot-reloading. To use it you need to have to entry points, one for the host and one for the client. + +In the client you can then implement these functions: + +* `hr_init`: called when the library is loaded (or reloaded) +* `hr_loop`: called every "tick" (or whenever the host decides) +* `hr_close`: called when the host finishes + +In the client you need to call these functions: + +* `hrOpen`: load the library and call `hr_init` +* `hrStep`: call `hr_loop` +* `hrReload`: check if the library has changed, and if so reload it and call `hr_init` again +* `hrClose`: call `hr_close` and cleanup + +Example usage: + +### Client + +```c +int hr_init(hr_t *ctx) { + uint8 tmpbuf[KB(5)]; + arena_t scratch = arenaMake(ARENA_STATIC, sizeof(tmpbuf), tmpbuf); + + state_t *state = ctx->userdata; + uint64 timestamp = fileGetTime(scratch, strv("sprite.png")); + if (timestamp > state->last_write) { + state->last_write = timestamp; + destroy_image(state->sprite); + state->sprite = load_image(strv("sprite.png")); + } +} + +int hr_loop(hr_t *ctx) { + state_t *state = ctx->userdata; + draw_image(state->sprite, state->posx, state->posy); +} + +int hr_close(hr_t *ctx) { + state_t *state = ctx->userdata; + destroy_image(state->sprite); +} +``` + +### Host + +```c +typedef struct { + hr_t hr; + image_t sprite; + int posx; + int posy; + uint64 last_write; +} state_t; + +int main() { + arena_t arena = arenaMake(ARENA_VIRTUAL, GB(1)); + + state_t state = {0}; + state.hr.userdata = &state; + + if (!hrOpen(&state.hr, strv("bin/client.dll"))) { + return 1; + } + + while (game_poll()) { + hrReload(&state.hr); + hrStep(&state.hr); + } + + hrClose(&state.hr, true); +} +``` \ No newline at end of file diff --git a/docs/html.md b/docs/html.md new file mode 100644 index 0000000..7dbef59 --- /dev/null +++ b/docs/html.md @@ -0,0 +1,45 @@ +--- +title = HTML +--- +# HTML +---------- + +This header provides an easy (although debatably sane) way to generate html in c. + +There are three types of tags: +* One and done tags, like `` or `
` which only have an opening tag +* Basic tags which follow this format: `content` +* Tags where the content is probably other tags + +You can open and close any tags using `tagBeg` and `tagEnd`, you can also set attributes like this: + +```c +tagBeg("div", .class="content", .id="main"); +``` + +Finally, you can use the `htmlRaw` macro to write raw html. + +Example code: +```c +str_t generate_page(arena_t *arena, page_t *data) { + str_t out = STR_EMPTY; + + htmlBeg(arena, &out); + headBeg(); + title(data->title); + htmlRaw() + style(data->css); + headEnd(); + bodyBeg(); + divBeg(.id="main"); + h1("Hello World!"); + hr(); + p("Some content blah blah"); + img(.src="image.png"); + divEnd(); + bodyEnd(); + htmlEnd(); + + return out; +} +``` \ No newline at end of file diff --git a/docs/http.md b/docs/http.md new file mode 100644 index 0000000..b783135 --- /dev/null +++ b/docs/http.md @@ -0,0 +1,5 @@ +--- +title = HTTP +--- +# HTTP +---------- \ No newline at end of file diff --git a/docs/ini.md b/docs/ini.md new file mode 100644 index 0000000..b854f8f --- /dev/null +++ b/docs/ini.md @@ -0,0 +1,5 @@ +--- +title = Ini +--- +# Ini +---------- \ No newline at end of file diff --git a/docs/json.md b/docs/json.md new file mode 100644 index 0000000..597d70d --- /dev/null +++ b/docs/json.md @@ -0,0 +1,5 @@ +--- +title = Json +--- +# Json +---------- \ No newline at end of file diff --git a/docs/markdown.md b/docs/markdown.md new file mode 100644 index 0000000..cd5c5a4 --- /dev/null +++ b/docs/markdown.md @@ -0,0 +1,5 @@ +--- +title = Markdown +--- +# Markdown +---------- \ No newline at end of file diff --git a/docs/readme.md b/docs/readme.md new file mode 100644 index 0000000..3d9e0bb --- /dev/null +++ b/docs/readme.md @@ -0,0 +1,5 @@ +--- +title = Readme +--- +# Readme +---------- \ No newline at end of file diff --git a/docs/server.md b/docs/server.md new file mode 100644 index 0000000..0644960 --- /dev/null +++ b/docs/server.md @@ -0,0 +1,5 @@ +--- +title = Server +--- +# Server +---------- \ No newline at end of file diff --git a/docs/sha1.md b/docs/sha1.md new file mode 100644 index 0000000..5c841fc --- /dev/null +++ b/docs/sha1.md @@ -0,0 +1,5 @@ +--- +title = SHA-1 +--- +# SHA-1 +---------- \ No newline at end of file diff --git a/docs/socket.md b/docs/socket.md new file mode 100644 index 0000000..5e2673d --- /dev/null +++ b/docs/socket.md @@ -0,0 +1,5 @@ +--- +title = Socket +--- +# Socket +---------- \ No newline at end of file diff --git a/docs/str.md b/docs/str.md new file mode 100644 index 0000000..2cd845a --- /dev/null +++ b/docs/str.md @@ -0,0 +1,5 @@ +--- +title = Str +--- +# Str +---------- \ No newline at end of file diff --git a/docs/strstream.md b/docs/strstream.md new file mode 100644 index 0000000..0036108 --- /dev/null +++ b/docs/strstream.md @@ -0,0 +1,5 @@ +--- +title = StrStream +--- +# StrStream +---------- \ No newline at end of file diff --git a/docs/tracelog.md b/docs/tracelog.md new file mode 100644 index 0000000..ea047b2 --- /dev/null +++ b/docs/tracelog.md @@ -0,0 +1,5 @@ +--- +title = Tracelog +--- +# Tracelog +---------- \ No newline at end of file diff --git a/docs/utf8.md b/docs/utf8.md new file mode 100644 index 0000000..57ba1e0 --- /dev/null +++ b/docs/utf8.md @@ -0,0 +1,5 @@ +--- +title = UTF-8 +--- +# UTF-8 +---------- \ No newline at end of file diff --git a/docs/vec.md b/docs/vec.md new file mode 100644 index 0000000..22bc964 --- /dev/null +++ b/docs/vec.md @@ -0,0 +1,5 @@ +--- +title = Vec +--- +# Vec +---------- \ No newline at end of file diff --git a/docs/vmem.md b/docs/vmem.md new file mode 100644 index 0000000..b930492 --- /dev/null +++ b/docs/vmem.md @@ -0,0 +1,5 @@ +--- +title = VMem +--- +# VMem +---------- \ No newline at end of file diff --git a/docs/websocket.md b/docs/websocket.md new file mode 100644 index 0000000..7fa5f6f --- /dev/null +++ b/docs/websocket.md @@ -0,0 +1,5 @@ +--- +title = WebSocket +--- +# WebSocket +---------- \ No newline at end of file diff --git a/docs/xml.md b/docs/xml.md new file mode 100644 index 0000000..d6bbafb --- /dev/null +++ b/docs/xml.md @@ -0,0 +1,5 @@ +--- +title = Xml +--- +# Xml +---------- \ No newline at end of file diff --git a/colla/file.c b/file.c similarity index 95% rename from colla/file.c rename to file.c index 7171935..fc50389 100644 --- a/colla/file.c +++ b/file.c @@ -1,360 +1,367 @@ -#include "file.h" - -#include "warnings/colla_warn_beg.h" - -#include "tracelog.h" -#include "format.h" - -#if COLLA_WIN - -#define WIN32_LEAN_AND_MEAN -#include - -#undef FILE_BEGIN -#undef FILE_CURRENT -#undef FILE_END - -#define FILE_BEGIN 0 -#define FILE_CURRENT 1 -#define FILE_END 2 - -#if COLLA_TCC -#include "tcc/colla_tcc.h" -#endif - -static DWORD file__mode_to_access(filemode_e mode) { - if (mode & FILE_APPEND) return FILE_APPEND_DATA; - - DWORD out = 0; - if (mode & FILE_READ) out |= GENERIC_READ; - if (mode & FILE_WRITE) out |= GENERIC_WRITE; - return out; -} - -static DWORD file__mode_to_creation(filemode_e mode) { - if (mode == FILE_READ) return OPEN_EXISTING; - if (mode == FILE_WRITE) return CREATE_ALWAYS; - return OPEN_ALWAYS; -} - -bool fileExists(const char *name) { - return GetFileAttributesA(name) != INVALID_FILE_ATTRIBUTES; -} - -TCHAR *fileGetFullPath(arena_t *arena, strview_t filename) { - TCHAR long_path_prefix[] = TEXT("\\\\?\\"); - const usize prefix_len = arrlen(long_path_prefix) - 1; - - uint8 tempbuf[4096]; - arena_t scratch = arenaMake(ARENA_STATIC, sizeof(tempbuf), tempbuf); - - TCHAR *rel_path = strvToTChar(&scratch, filename); - DWORD pathlen = GetFullPathName(rel_path, 0, NULL, NULL); - - TCHAR *full_path = alloc(arena, TCHAR, pathlen + prefix_len + 1); - memcpy(full_path, long_path_prefix, prefix_len * sizeof(TCHAR)); - - GetFullPathName(rel_path, pathlen + 1, full_path + prefix_len, NULL); - - return full_path; -} - -strview_t fileGetFilename(strview_t path) { - usize last_lin = strvRFind(path, '/', 0); - usize last_win = strvRFind(path, '\\', 0); - last_lin = last_lin != SIZE_MAX ? last_lin : 0; - last_win = last_win != SIZE_MAX ? last_win : 0; - usize last = max(last_lin, last_win); - return strvSub(path, last ? last + 1 : last, SIZE_MAX); -} - -strview_t fileGetExtension(strview_t path) { - usize ext_pos = strvRFind(path, '.', 0); - return strvSub(path, ext_pos, SIZE_MAX); -} - -void fileSplitPath(strview_t path, strview_t *dir, strview_t *name, strview_t *ext) { - usize dir_lin = strvRFind(path, '/', 0); - usize dir_win = strvRFind(path, '\\', 0); - dir_lin = dir_lin != STR_NONE ? dir_lin : 0; - dir_win = dir_win != STR_NONE ? dir_win : 0; - usize dir_pos = max(dir_lin, dir_win); - - usize ext_pos = strvRFind(path, '.', 0); - - if (dir) { - *dir = strvSub(path, 0, dir_pos); - } - if (name) { - *name = strvSub(path, dir_pos ? dir_pos + 1 : dir_pos, ext_pos); - } - if (ext) { - *ext = strvSub(path, ext_pos, SIZE_MAX); - } -} - -bool fileDelete(arena_t scratch, strview_t filename) { - wchar_t *wfname = strvToWChar(&scratch, filename, NULL); - return DeleteFileW(wfname); -} - -file_t fileOpen(arena_t scratch, strview_t name, filemode_e mode) { - TCHAR *full_path = fileGetFullPath(&scratch, name); - - HANDLE handle = CreateFile( - full_path, - file__mode_to_access(mode), - FILE_SHARE_READ, - NULL, - file__mode_to_creation(mode), - FILE_ATTRIBUTE_NORMAL, - NULL - ); - - return (file_t){ - .handle = (uintptr_t)handle, - }; -} - -void fileClose(file_t ctx) { - if (!fileIsValid(ctx)) return; - CloseHandle((HANDLE)ctx.handle); -} - -bool fileIsValid(file_t ctx) { - return (HANDLE)ctx.handle != 0 && - (HANDLE)ctx.handle != INVALID_HANDLE_VALUE; -} - -usize fileRead(file_t ctx, void *buf, usize len) { - if (!fileIsValid(ctx)) return 0; - DWORD read = 0; - ReadFile((HANDLE)ctx.handle, buf, (DWORD)len, &read, NULL); - return (usize)read; -} - -usize fileWrite(file_t ctx, const void *buf, usize len) { - if (!fileIsValid(ctx)) return 0; - DWORD written = 0; - WriteFile((HANDLE)ctx.handle, buf, (DWORD)len, &written, NULL); - return (usize)written; -} - -bool fileSeekEnd(file_t ctx) { - if (!fileIsValid(ctx)) return false; - DWORD result = SetFilePointer((HANDLE)ctx.handle, 0, NULL, FILE_END); - return result != INVALID_SET_FILE_POINTER; -} - -void fileRewind(file_t ctx) { - if (!fileIsValid(ctx)) return; - SetFilePointer((HANDLE)ctx.handle, 0, NULL, FILE_BEGIN); -} - -usize fileTell(file_t ctx) { - if (!fileIsValid(ctx)) return 0; - LARGE_INTEGER tell = {0}; - BOOL result = SetFilePointerEx((HANDLE)ctx.handle, (LARGE_INTEGER){0}, &tell, FILE_CURRENT); - return result == TRUE ? (usize)tell.QuadPart : 0; -} - -usize fileSize(file_t ctx) { - if (!fileIsValid(ctx)) return 0; - LARGE_INTEGER size = {0}; - BOOL result = GetFileSizeEx((HANDLE)ctx.handle, &size); - return result == TRUE ? (usize)size.QuadPart : 0; -} - -uint64 fileGetTimeFP(file_t ctx) { - if (!fileIsValid(ctx)) return 0; - FILETIME time = {0}; - GetFileTime((HANDLE)ctx.handle, NULL, NULL, &time); - ULARGE_INTEGER utime = { - .HighPart = time.dwHighDateTime, - .LowPart = time.dwLowDateTime, - }; - return (uint64)utime.QuadPart; -} - -#else - -#include - -static const char *file__mode_to_stdio(filemode_e mode) { - if (mode == FILE_READ) return "rb"; - if (mode == FILE_WRITE) return "wb"; - if (mode == FILE_APPEND) return "ab"; - if (mode == (FILE_READ | FILE_WRITE)) return "rb+"; - - return "ab+"; -} - -bool fileExists(const char *name) { - FILE *fp = fopen(name, "rb"); - bool exists = fp != NULL; - fclose(fp); - return exists; -} - -file_t fileOpen(arena_t scratch, strview_t name, filemode_e mode) { - str_t filename = str(&scratch, name); - return (file_t) { - .handle = (uintptr_t)fopen(filename.buf, file__mode_to_stdio(mode)) - }; -} - -void fileClose(file_t ctx) { - FILE *fp = (FILE *)ctx.handle; - if (fp) { - fclose(fp); - } -} - -bool fileIsValid(file_t ctx) { - bool is_valid = (FILE *)ctx.handle != NULL; - if (!is_valid) warn("file not valid"); - return is_valid; -} - -usize fileRead(file_t ctx, void *buf, usize len) { - if (!fileIsValid(ctx)) return 0; - return fread(buf, 1, len, (FILE *)ctx.handle); -} - -usize fileWrite(file_t ctx, const void *buf, usize len) { - if (!fileIsValid(ctx)) return 0; - return fwrite(buf, 1, len, (FILE *)ctx.handle); -} - -bool fileSeekEnd(file_t ctx) { - if (!fileIsValid(ctx)) return false; - return fseek((FILE *)ctx.handle, 0, SEEK_END) == 0; -} - -void fileRewind(file_t ctx) { - if (!fileIsValid(ctx)) return; - fseek((FILE *)ctx.handle, 0, SEEK_SET); -} - -usize fileTell(file_t ctx) { - if (!fileIsValid(ctx)) return 0; - return ftell((FILE *)ctx.handle); -} - -usize fileSize(file_t ctx) { - if (!fileIsValid(ctx)) return 0; - FILE *fp = (FILE *)ctx.handle; - fseek(fp, 0, SEEK_END); - long len = ftell(fp); - fseek(fp, 0, SEEK_SET); - return (usize)len; -} - -uint64 fileGetTimeFP(file_t ctx) { -#if COLLA_LIN - return 0; -#else - fatal("fileGetTime not implemented yet outside of linux and windows"); - return 0; -#endif -} - -#endif - -bool filePutc(file_t ctx, char c) { - return fileWrite(ctx, &c, 1) == 1; -} - -bool filePuts(file_t ctx, strview_t v) { - return fileWrite(ctx, v.buf, v.len) == v.len; -} - -bool filePrintf(arena_t scratch, file_t ctx, const char *fmt, ...) { - va_list args; - va_start(args, fmt); - bool result = filePrintfv(scratch, ctx, fmt, args); - va_end(args); - return result; -} - -bool filePrintfv(arena_t scratch, file_t ctx, const char *fmt, va_list args) { - str_t string = strFmtv(&scratch, fmt, args); - return fileWrite(ctx, string.buf, string.len) == string.len; -} - -buffer_t fileReadWhole(arena_t *arena, strview_t name) { - file_t fp = fileOpen(*arena, name, FILE_READ); - if (!fileIsValid(fp)) { - err("could not open file: %v", name); - return (buffer_t){0}; - } - buffer_t out = fileReadWholeFP(arena, fp); - fileClose(fp); - return out; -} - -buffer_t fileReadWholeFP(arena_t *arena, file_t ctx) { - if (!fileIsValid(ctx)) return (buffer_t){0}; - buffer_t out = {0}; - - out.len = fileSize(ctx); - out.data = alloc(arena, uint8, out.len); - usize read = fileRead(ctx, out.data, out.len); - - if (read != out.len) { - err("fileReadWholeFP: fileRead failed, should be %zu but is %zu", out.len, read); - arenaPop(arena, out.len); - return (buffer_t){0}; - } - - return out; -} - -str_t fileReadWholeStr(arena_t *arena, strview_t name) { - file_t fp = fileOpen(*arena, name, FILE_READ); - str_t out = fileReadWholeStrFP(arena, fp); - fileClose(fp); - return out; -} - -str_t fileReadWholeStrFP(arena_t *arena, file_t ctx) { - if (!fileIsValid(ctx)) return (str_t){0}; - - str_t out = {0}; - - out.len = fileSize(ctx); - out.buf = alloc(arena, uint8, out.len + 1); - usize read = fileRead(ctx, out.buf, out.len); - - if (read != out.len) { - err("fileReadWholeStrFP: fileRead failed, should be %zu but is %zu", out.len, read); - arenaPop(arena, out.len + 1); - return (str_t){0}; - } - - return out; -} - -bool fileWriteWhole(arena_t scratch, strview_t name, const void *buf, usize len) { - file_t fp = fileOpen(scratch, name, FILE_WRITE); - if (!fileIsValid(fp)) { - return false; - } - usize written = fileWrite(fp, buf, len); - fileClose(fp); - return written == len; -} - -uint64 fileGetTime(arena_t scratch, strview_t name) { - file_t fp = fileOpen(scratch, name, FILE_READ); - uint64 result = fileGetTimeFP(fp); - fileClose(fp); - return result; -} - -bool fileHasChanged(arena_t scratch, strview_t name, uint64 last_timestamp) { - uint64 timestamp = fileGetTime(scratch, name); - return timestamp > last_timestamp; -} - -#include "warnings/colla_warn_end.h" +#include "file.h" + +#include "warnings/colla_warn_beg.h" + +#include "tracelog.h" +#include "format.h" + +#if COLLA_WIN + +#include + +#undef FILE_BEGIN +#undef FILE_CURRENT +#undef FILE_END + +#define FILE_BEGIN 0 +#define FILE_CURRENT 1 +#define FILE_END 2 + +#if COLLA_TCC +#include "tcc/colla_tcc.h" +#endif + +static DWORD file__mode_to_access(filemode_e mode) { + if (mode & FILE_APPEND) return FILE_APPEND_DATA; + + DWORD out = 0; + if (mode & FILE_READ) out |= GENERIC_READ; + if (mode & FILE_WRITE) out |= GENERIC_WRITE; + return out; +} + +static DWORD file__mode_to_creation(filemode_e mode) { + if (mode == FILE_READ) return OPEN_EXISTING; + if (mode == FILE_WRITE) return CREATE_ALWAYS; + return OPEN_ALWAYS; +} + +bool fileExists(const char *name) { + return GetFileAttributesA(name) != INVALID_FILE_ATTRIBUTES; +} + +TCHAR *fileGetFullPath(arena_t *arena, strview_t filename) { + TCHAR long_path_prefix[] = TEXT("\\\\?\\"); + const usize prefix_len = arrlen(long_path_prefix) - 1; + + uint8 tempbuf[4096]; + arena_t scratch = arenaMake(ARENA_STATIC, sizeof(tempbuf), tempbuf); + + TCHAR *rel_path = strvToTChar(&scratch, filename); + DWORD pathlen = GetFullPathName(rel_path, 0, NULL, NULL); + + TCHAR *full_path = alloc(arena, TCHAR, pathlen + prefix_len + 1); + memcpy(full_path, long_path_prefix, prefix_len * sizeof(TCHAR)); + + GetFullPathName(rel_path, pathlen + 1, full_path + prefix_len, NULL); + + return full_path; +} + +bool fileDelete(arena_t scratch, strview_t filename) { + wchar_t *wfname = strvToWChar(&scratch, filename, NULL); + return DeleteFileW(wfname); +} + +file_t fileOpen(arena_t scratch, strview_t name, filemode_e mode) { + TCHAR *full_path = fileGetFullPath(&scratch, name); + + HANDLE handle = CreateFile( + full_path, + file__mode_to_access(mode), + FILE_SHARE_READ, + NULL, + file__mode_to_creation(mode), + FILE_ATTRIBUTE_NORMAL, + NULL + ); + + return (file_t){ + .handle = (uintptr_t)handle, + }; +} + +void fileClose(file_t ctx) { + if (!fileIsValid(ctx)) return; + CloseHandle((HANDLE)ctx.handle); +} + +bool fileIsValid(file_t ctx) { + return (HANDLE)ctx.handle != 0 && + (HANDLE)ctx.handle != INVALID_HANDLE_VALUE; +} + +usize fileRead(file_t ctx, void *buf, usize len) { + if (!fileIsValid(ctx)) return 0; + DWORD read = 0; + ReadFile((HANDLE)ctx.handle, buf, (DWORD)len, &read, NULL); + return (usize)read; +} + +usize fileWrite(file_t ctx, const void *buf, usize len) { + if (!fileIsValid(ctx)) return 0; + DWORD written = 0; + WriteFile((HANDLE)ctx.handle, buf, (DWORD)len, &written, NULL); + return (usize)written; +} + +bool fileSeekEnd(file_t ctx) { + if (!fileIsValid(ctx)) return false; + DWORD result = SetFilePointer((HANDLE)ctx.handle, 0, NULL, FILE_END); + return result != INVALID_SET_FILE_POINTER; +} + +void fileRewind(file_t ctx) { + if (!fileIsValid(ctx)) return; + SetFilePointer((HANDLE)ctx.handle, 0, NULL, FILE_BEGIN); +} + +usize fileTell(file_t ctx) { + if (!fileIsValid(ctx)) return 0; + LARGE_INTEGER tell = {0}; + BOOL result = SetFilePointerEx((HANDLE)ctx.handle, (LARGE_INTEGER){0}, &tell, FILE_CURRENT); + return result == TRUE ? (usize)tell.QuadPart : 0; +} + +usize fileSize(file_t ctx) { + if (!fileIsValid(ctx)) return 0; + LARGE_INTEGER size = {0}; + BOOL result = GetFileSizeEx((HANDLE)ctx.handle, &size); + return result == TRUE ? (usize)size.QuadPart : 0; +} + +uint64 fileGetTimeFP(file_t ctx) { + if (!fileIsValid(ctx)) return 0; + FILETIME time = {0}; + GetFileTime((HANDLE)ctx.handle, NULL, NULL, &time); + ULARGE_INTEGER utime = { + .HighPart = time.dwHighDateTime, + .LowPart = time.dwLowDateTime, + }; + return (uint64)utime.QuadPart; +} + +#else + +#include + +static const char *file__mode_to_stdio(filemode_e mode) { + if (mode == FILE_READ) return "rb"; + if (mode == FILE_WRITE) return "wb"; + if (mode == FILE_APPEND) return "ab"; + if (mode == (FILE_READ | FILE_WRITE)) return "rb+"; + + return "ab+"; +} + +bool fileExists(const char *name) { + FILE *fp = fopen(name, "rb"); + bool exists = fp != NULL; + fclose(fp); + return exists; +} + +bool fileDelete(arena_t scratch, strview_t filename) { + str_t name = str(&scratch, filename); + return remove(name.buf) == 0; +} + +file_t fileOpen(arena_t scratch, strview_t name, filemode_e mode) { + str_t filename = str(&scratch, name); + return (file_t) { + .handle = (uintptr_t)fopen(filename.buf, file__mode_to_stdio(mode)) + }; +} + +void fileClose(file_t ctx) { + FILE *fp = (FILE *)ctx.handle; + if (fp) { + fclose(fp); + } +} + +bool fileIsValid(file_t ctx) { + bool is_valid = (FILE *)ctx.handle != NULL; + if (!is_valid) warn("file not valid"); + return is_valid; +} + +usize fileRead(file_t ctx, void *buf, usize len) { + if (!fileIsValid(ctx)) return 0; + return fread(buf, 1, len, (FILE *)ctx.handle); +} + +usize fileWrite(file_t ctx, const void *buf, usize len) { + if (!fileIsValid(ctx)) return 0; + return fwrite(buf, 1, len, (FILE *)ctx.handle); +} + +bool fileSeekEnd(file_t ctx) { + if (!fileIsValid(ctx)) return false; + return fseek((FILE *)ctx.handle, 0, SEEK_END) == 0; +} + +void fileRewind(file_t ctx) { + if (!fileIsValid(ctx)) return; + fseek((FILE *)ctx.handle, 0, SEEK_SET); +} + +usize fileTell(file_t ctx) { + if (!fileIsValid(ctx)) return 0; + return ftell((FILE *)ctx.handle); +} + +usize fileSize(file_t ctx) { + if (!fileIsValid(ctx)) return 0; + FILE *fp = (FILE *)ctx.handle; + fseek(fp, 0, SEEK_END); + long len = ftell(fp); + fseek(fp, 0, SEEK_SET); + return (usize)len; +} + +uint64 fileGetTimeFP(file_t ctx) { +#if COLLA_LIN + return 0; +#else + fatal("fileGetTime not implemented yet outside of linux and windows"); + return 0; +#endif +} + +#endif + +strview_t fileGetFilename(strview_t path) { + usize last_lin = strvRFind(path, '/', 0); + usize last_win = strvRFind(path, '\\', 0); + last_lin = last_lin != SIZE_MAX ? last_lin : 0; + last_win = last_win != SIZE_MAX ? last_win : 0; + usize last = max(last_lin, last_win); + return strvSub(path, last ? last + 1 : last, SIZE_MAX); +} + +strview_t fileGetExtension(strview_t path) { + usize ext_pos = strvRFind(path, '.', 0); + return strvSub(path, ext_pos, SIZE_MAX); +} + +void fileSplitPath(strview_t path, strview_t *dir, strview_t *name, strview_t *ext) { + usize dir_lin = strvRFind(path, '/', 0); + usize dir_win = strvRFind(path, '\\', 0); + dir_lin = dir_lin != STR_NONE ? dir_lin : 0; + dir_win = dir_win != STR_NONE ? dir_win : 0; + usize dir_pos = max(dir_lin, dir_win); + + usize ext_pos = strvRFind(path, '.', 0); + + if (dir) { + *dir = strvSub(path, 0, dir_pos); + } + if (name) { + *name = strvSub(path, dir_pos ? dir_pos + 1 : dir_pos, ext_pos); + } + if (ext) { + *ext = strvSub(path, ext_pos, SIZE_MAX); + } +} + +bool filePutc(file_t ctx, char c) { + return fileWrite(ctx, &c, 1) == 1; +} + +bool filePuts(file_t ctx, strview_t v) { + return fileWrite(ctx, v.buf, v.len) == v.len; +} + +bool filePrintf(arena_t scratch, file_t ctx, const char *fmt, ...) { + va_list args; + va_start(args, fmt); + bool result = filePrintfv(scratch, ctx, fmt, args); + va_end(args); + return result; +} + +bool filePrintfv(arena_t scratch, file_t ctx, const char *fmt, va_list args) { + str_t string = strFmtv(&scratch, fmt, args); + return fileWrite(ctx, string.buf, string.len) == string.len; +} + +buffer_t fileReadWhole(arena_t *arena, strview_t name) { + file_t fp = fileOpen(*arena, name, FILE_READ); + if (!fileIsValid(fp)) { + err("could not open file: %v", name); + return (buffer_t){0}; + } + buffer_t out = fileReadWholeFP(arena, fp); + fileClose(fp); + return out; +} + +buffer_t fileReadWholeFP(arena_t *arena, file_t ctx) { + if (!fileIsValid(ctx)) return (buffer_t){0}; + buffer_t out = {0}; + + out.len = fileSize(ctx); + out.data = alloc(arena, uint8, out.len); + usize read = fileRead(ctx, out.data, out.len); + + if (read != out.len) { + err("fileReadWholeFP: fileRead failed, should be %zu but is %zu", out.len, read); + arenaPop(arena, out.len); + return (buffer_t){0}; + } + + return out; +} + +str_t fileReadWholeStr(arena_t *arena, strview_t name) { + file_t fp = fileOpen(*arena, name, FILE_READ); + if (!fileIsValid(fp)) { + warn("could not open file (%v)", name); + } + str_t out = fileReadWholeStrFP(arena, fp); + fileClose(fp); + return out; +} + +str_t fileReadWholeStrFP(arena_t *arena, file_t ctx) { + if (!fileIsValid(ctx)) return STR_EMPTY; + + str_t out = {0}; + + out.len = fileSize(ctx); + out.buf = alloc(arena, uint8, out.len + 1); + usize read = fileRead(ctx, out.buf, out.len); + + if (read != out.len) { + err("fileReadWholeStrFP: fileRead failed, should be %zu but is %zu", out.len, read); + arenaPop(arena, out.len + 1); + return STR_EMPTY; + } + + return out; +} + +bool fileWriteWhole(arena_t scratch, strview_t name, const void *buf, usize len) { + file_t fp = fileOpen(scratch, name, FILE_WRITE); + if (!fileIsValid(fp)) { + return false; + } + usize written = fileWrite(fp, buf, len); + fileClose(fp); + return written == len; +} + +uint64 fileGetTime(arena_t scratch, strview_t name) { + file_t fp = fileOpen(scratch, name, FILE_READ); + uint64 result = fileGetTimeFP(fp); + fileClose(fp); + return result; +} + +bool fileHasChanged(arena_t scratch, strview_t name, uint64 last_timestamp) { + uint64 timestamp = fileGetTime(scratch, name); + return timestamp > last_timestamp; +} + +#include "warnings/colla_warn_end.h" diff --git a/colla/file.h b/file.h similarity index 96% rename from colla/file.h rename to file.h index c6ca20d..ba7a3c2 100644 --- a/colla/file.h +++ b/file.h @@ -1,55 +1,55 @@ -#pragma once - -#include - -#include "collatypes.h" -#include "str.h" -#include "arena.h" - -typedef enum { - FILE_READ = 1 << 0, - FILE_WRITE = 1 << 1, - FILE_APPEND = 1 << 2, -} filemode_e; - -typedef struct { - uintptr_t handle; -} file_t; - -bool fileExists(const char *name); -TCHAR *fileGetFullPath(arena_t *arena, strview_t filename); -strview_t fileGetFilename(strview_t path); -strview_t fileGetExtension(strview_t path); -void fileSplitPath(strview_t path, strview_t *dir, strview_t *name, strview_t *ext); -bool fileDelete(arena_t scratch, strview_t filename); - -file_t fileOpen(arena_t scratch, strview_t name, filemode_e mode); -void fileClose(file_t ctx); - -bool fileIsValid(file_t ctx); - -bool filePutc(file_t ctx, char c); -bool filePuts(file_t ctx, strview_t v); -bool filePrintf(arena_t scratch, file_t ctx, const char *fmt, ...); -bool filePrintfv(arena_t scratch, file_t ctx, const char *fmt, va_list args); - -usize fileRead(file_t ctx, void *buf, usize len); -usize fileWrite(file_t ctx, const void *buf, usize len); - -bool fileSeekEnd(file_t ctx); -void fileRewind(file_t ctx); - -usize fileTell(file_t ctx); -usize fileSize(file_t ctx); - -buffer_t fileReadWhole(arena_t *arena, strview_t name); -buffer_t fileReadWholeFP(arena_t *arena, file_t ctx); - -str_t fileReadWholeStr(arena_t *arena, strview_t name); -str_t fileReadWholeStrFP(arena_t *arena, file_t ctx); - -bool fileWriteWhole(arena_t scratch, strview_t name, const void *buf, usize len); - -uint64 fileGetTime(arena_t scratch, strview_t name); -uint64 fileGetTimeFP(file_t ctx); +#pragma once + +#include + +#include "collatypes.h" +#include "str.h" +#include "arena.h" + +typedef enum { + FILE_READ = 1 << 0, + FILE_WRITE = 1 << 1, + FILE_APPEND = 1 << 2, +} filemode_e; + +typedef struct { + uintptr_t handle; +} file_t; + +bool fileExists(const char *name); +TCHAR *fileGetFullPath(arena_t *arena, strview_t filename); +strview_t fileGetFilename(strview_t path); +strview_t fileGetExtension(strview_t path); +void fileSplitPath(strview_t path, strview_t *dir, strview_t *name, strview_t *ext); +bool fileDelete(arena_t scratch, strview_t filename); + +file_t fileOpen(arena_t scratch, strview_t name, filemode_e mode); +void fileClose(file_t ctx); + +bool fileIsValid(file_t ctx); + +bool filePutc(file_t ctx, char c); +bool filePuts(file_t ctx, strview_t v); +bool filePrintf(arena_t scratch, file_t ctx, const char *fmt, ...); +bool filePrintfv(arena_t scratch, file_t ctx, const char *fmt, va_list args); + +usize fileRead(file_t ctx, void *buf, usize len); +usize fileWrite(file_t ctx, const void *buf, usize len); + +bool fileSeekEnd(file_t ctx); +void fileRewind(file_t ctx); + +usize fileTell(file_t ctx); +usize fileSize(file_t ctx); + +buffer_t fileReadWhole(arena_t *arena, strview_t name); +buffer_t fileReadWholeFP(arena_t *arena, file_t ctx); + +str_t fileReadWholeStr(arena_t *arena, strview_t name); +str_t fileReadWholeStrFP(arena_t *arena, file_t ctx); + +bool fileWriteWhole(arena_t scratch, strview_t name, const void *buf, usize len); + +uint64 fileGetTime(arena_t scratch, strview_t name); +uint64 fileGetTimeFP(file_t ctx); bool fileHasChanged(arena_t scratch, strview_t name, uint64 last_timestamp); \ No newline at end of file diff --git a/colla/format.c b/format.c similarity index 97% rename from colla/format.c rename to format.c index 9ab29d1..7ed9d21 100644 --- a/colla/format.c +++ b/format.c @@ -9,7 +9,7 @@ static char *fmt__stb_callback(const char *buf, void *ud, int len) { (void)len; - printf("%s", buf); + printf("%.*s", len, buf); return (char *)ud; } diff --git a/colla/format.h b/format.h similarity index 100% rename from colla/format.h rename to format.h diff --git a/highlight.c b/highlight.c new file mode 100644 index 0000000..aa39a14 --- /dev/null +++ b/highlight.c @@ -0,0 +1,621 @@ +#include "highlight.h" + +// based on https://github.com/Theldus/kat + +#include + +#include "arena.h" +#include "tracelog.h" +#include "strstream.h" + +typedef enum { + HL_STATE_DEFAULT, + HL_STATE_KEYWORD, + HL_STATE_NUMBER, + HL_STATE_CHAR, + HL_STATE_STRING, + HL_STATE_COMMENT_MULTI, + HL_STATE_PREPROCESSOR, + HL_STATE_PREPROCESSOR_INCLUDE, + HL_STATE_PREPROCESSOR_INCLUDE_STRING, +} hl_state_e; + +typedef enum { + HL_HTABLE_FAILED, + HL_HTABLE_REPLACED, + HL_HTABLE_ADDED, +} hl_htable_result_e; + +typedef struct hl_node_t { + strview_t key; + hl_color_e value; + struct hl_node_t *next; +} hl_node_t; + +typedef struct { + hl_node_t **buckets; + uint count; + uint used; + uint collisions; +} hl_hashtable_t; + +static hl_hashtable_t hl_htable_init(arena_t *arena, uint pow2_exp); +static hl_htable_result_e hl_htable_add(arena_t *arena, hl_hashtable_t *table, strview_t key, hl_color_e value); +static hl_node_t *hl_htable_get(hl_hashtable_t *table, strview_t key); +static uint64 hl_htable_hash(const void *bytes, usize count); + +typedef struct hl_ctx_t { + hl_state_e state; + hl_flags_e flags; + usize kw_beg; + strview_t colors[HL_COLOR__COUNT]; // todo: maybe should be str_t? + outstream_t ostr; + hl_hashtable_t kw_htable; + bool symbol_table[256]; +} hl_ctx_t; + +#define KW(str, col) { { str, sizeof(str)-1 }, HL_COLOR_##col } + +static hl_keyword_t hl_default_kwrds[] = { + /* C Types. */ + KW("double", TYPES), + KW("int", TYPES), + KW("long", TYPES), + KW("char", TYPES), + KW("float", TYPES), + KW("short", TYPES), + KW("unsigned", TYPES), + KW("signed", TYPES), + KW("bool", TYPES), + + /* Common typedefs. */ + KW("int8", TYPES), KW("uint8", TYPES), + KW("int16", TYPES), KW("uint16", TYPES), + KW("int32", TYPES), KW("uint32", TYPES), + KW("int64", TYPES), KW("uint64", TYPES), + + /* Colla keywords */ + KW("uchar", TYPES), + KW("ushort", TYPES), + KW("uint", TYPES), + KW("usize", TYPES), + KW("isize", TYPES), + KW("byte", TYPES), + + /* Other keywords. */ + KW("auto", KEYWORDS), KW("struct", KEYWORDS), KW("break", KEYWORDS), + KW("else", KEYWORDS), KW("switch", KEYWORDS), KW("case", KEYWORDS), + KW("enum", KEYWORDS), KW("register", KEYWORDS), KW("typedef", KEYWORDS), + KW("extern", KEYWORDS), KW("return", KEYWORDS), KW("union", KEYWORDS), + KW("const", KEYWORDS), KW("continue", KEYWORDS), KW("for", KEYWORDS), + KW("void", KEYWORDS), KW("default", KEYWORDS), KW("goto", KEYWORDS), + KW("sizeof", KEYWORDS), KW("volatile", KEYWORDS), KW("do", KEYWORDS), + KW("if", KEYWORDS), KW("static", KEYWORDS), KW("inline", KEYWORDS), + KW("while", KEYWORDS), +}; + +#undef KW + +static bool hl_default_symbols_table[256] = { + ['['] = true, [']'] = true, ['('] = true, + [')'] = true, ['{'] = true, ['}'] = true, + ['*'] = true, [':'] = true, ['='] = true, + [';'] = true, ['-'] = true, ['>'] = true, + ['&'] = true, ['+'] = true, ['~'] = true, + ['!'] = true, ['/'] = true, ['%'] = true, + ['<'] = true, ['^'] = true, ['|'] = true, + ['?'] = true, ['#'] = true, +}; + +static void hl_write_char(hl_ctx_t *ctx, char c); +static void hl_write(hl_ctx_t *ctx, strview_t v); +static bool hl_is_char_keyword(char c); +static bool hl_highlight_symbol(hl_ctx_t *ctx, char c); +static hl_color_e hl_get_keyword_color(hl_ctx_t *ctx, strview_t keyword); +static bool hl_is_capitalised(strview_t string); +static strview_t hl_finish_keyword(hl_ctx_t *ctx, usize beg, instream_t *in); +static void hl_print_keyword(hl_ctx_t *ctx, strview_t keyword, hl_color_e color); + +hl_ctx_t *hlInit(arena_t *arena, hl_config_t *config) { + if (!config) { + err(" cannot be null"); + return NULL; + } + + hl_ctx_t *out = alloc(arena, hl_ctx_t); + + out->flags = config->flags; + + memcpy(out->symbol_table, hl_default_symbols_table, sizeof(hl_default_symbols_table)); + memcpy(out->colors, config->colors, sizeof(config->colors)); + + int kw_count = arrlen(hl_default_kwrds); + + out->kw_htable = hl_htable_init(arena, 8); + + for (int i = 0; i < kw_count; ++i) { + hl_keyword_t *kw = &hl_default_kwrds[i]; + hl_htable_add(arena, &out->kw_htable, kw->keyword, kw->color); + } + + for (int i = 0; i < config->kwrds_count; ++i) { + hl_keyword_t *kw = &config->extra_kwrds[i]; + hl_htable_add(arena, &out->kw_htable, kw->keyword, kw->color); + } + + return out; +} + +void hl_next_char(hl_ctx_t *ctx, instream_t *in) { + char cur = istrGet(in); + bool is_last = istrIsFinished(*in); + + switch (ctx->state) { + case HL_STATE_DEFAULT: + { + /* + * If potential keyword. + * + * A valid C keyword may contain numbers, but *not* + * as a suffix. + */ + if (hl_is_char_keyword(cur) && !isdigit(cur)) { + ctx->kw_beg = istrTell(*in); + ctx->state = HL_STATE_KEYWORD; + } + + // potential number + else if (isdigit(cur)) { + ctx->kw_beg = istrTell(*in); + ctx->state = HL_STATE_NUMBER; + } + + // potential char + else if (cur == '\'') { + ctx->kw_beg = istrTell(*in); + ctx->state = HL_STATE_CHAR; + } + + // potential string + else if (cur == '"') { + ctx->kw_beg = istrTell(*in); + ctx->state = HL_STATE_STRING; + } + + // line or multiline comment + else if (cur == '/') { + // single line comment + if (istrPeek(in) == '/') { + // rewind before comment begins + istrRewindN(in, 1); + + // comment until the end of line + hl_print_keyword(ctx, istrGetLine(in), HL_COLOR_COMMENT); + } + + // multiline comment + else if (istrPeek(in) == '*') { + ctx->state = HL_STATE_COMMENT_MULTI; + ctx->kw_beg = istrTell(*in); + istrSkip(in, 1); // skip * + } + + else { + // maybe a symbol? + hl_highlight_symbol(ctx, cur); + } + } + + // preprocessor + else if (cur == '#') { + // print the # as a symbol + hl_highlight_symbol(ctx, cur); + ctx->kw_beg = istrTell(*in); + ctx->state = HL_STATE_PREPROCESSOR; + } + + // other suppored symbols + else if (hl_highlight_symbol(ctx, cur)) { + // noop + } + + else { + hl_write_char(ctx, cur); + } + + break; + } + + case HL_STATE_KEYWORD: + { + // end of keyword, check if it really is a valid keyword + if (!hl_is_char_keyword(cur)) { + strview_t keyword = hl_finish_keyword(ctx, ctx->kw_beg, in); + hl_color_e kw_color = hl_get_keyword_color(ctx, keyword); + + if (kw_color != HL_COLOR__COUNT) { + hl_print_keyword(ctx, keyword, kw_color); + + // maybe we should highlight this remaining char. + if (!hl_highlight_symbol(ctx, cur)) { + hl_write_char(ctx, cur); + } + } + + /* + * If not keyword, maybe its a function call. + * + * Important to note that this is hacky and will only work + * if there is no space between keyword and '('. + */ + else if (cur == '(') { + hl_print_keyword(ctx, keyword, HL_COLOR_FUNC); + + // Opening parenthesis will always be highlighted + hl_highlight_symbol(ctx, cur); + } + else { + if (hl_is_capitalised(keyword)) { + hl_print_keyword(ctx, keyword, HL_COLOR_MACRO); + } + else { + hl_write(ctx, keyword); + } + if (!hl_highlight_symbol(ctx, cur)) { + hl_write_char(ctx, cur); + } + } + } + break; + } + + case HL_STATE_NUMBER: + { + char c = (char)tolower(cur); + + /* + * Should we end the state?. + * + * Very important observation: + * Although the number highlight works fine for most (if not all) + * of the possible cases, it also assumes that the code is written + * correctly and the source is able to compile, meaning that: + * + * Numbers like: 123, 0xABC123, 12.3e4f, 123ULL.... + * will be correctly identified and highlighted + * + * But, 'numbers' like: 123ABC, 0xxxxABCxx123, 123UUUUU.... + * will also be highlighted. + * + * It also assumes that no keyword will start with a number + * and everything starting with a number (except inside strings or + * comments) will be a number. + */ + if (!isdigit(c) && + (c < 'a' || c > 'f') && + c != 'b' && c != 'x' && + c != 'u' && c != 'l' && + c != '.' + ) { + strview_t keyword = hl_finish_keyword(ctx, ctx->kw_beg, in); + + // if not a valid char keyword: valid number + if (!hl_is_char_keyword(cur)) { + hl_print_keyword(ctx, keyword, HL_COLOR_NUMBER); + } + else { + hl_write(ctx, keyword); + } + + // maybe we should highlight this remaining char. + if (!hl_highlight_symbol(ctx, cur)) { + hl_write_char(ctx, cur); + } + } + + break; + } + + case HL_STATE_CHAR: + { + if (is_last || (cur == '\'' && istrPeek(in) != '\'')) { + strview_t keyword = hl_finish_keyword(ctx, ctx->kw_beg, in); + keyword.len++; + + hl_print_keyword(ctx, keyword, HL_COLOR_STRING); + } + break; + } + + case HL_STATE_STRING: + { + if (is_last || (cur == '"' && istrPrevPrev(in) != '\\')) { + strview_t keyword = hl_finish_keyword(ctx, ctx->kw_beg, in); + keyword.len++; + + hl_print_keyword(ctx, keyword, HL_COLOR_STRING); + } + break; + } + + case HL_STATE_COMMENT_MULTI: + { + /* + * If we are at the end of line _or_ have identified + * an end of comment... + */ + if (is_last || (cur == '*' && istrPeek(in) == '/')) { + strview_t keyword = hl_finish_keyword(ctx, ctx->kw_beg, in); + + hl_print_keyword(ctx, keyword, HL_COLOR_COMMENT); + } + break; + } + + case HL_STATE_PREPROCESSOR: + { + + if (!hl_is_char_keyword(cur)) { + hl_write_char(ctx, cur); + break; + } + +#define hl_check(str, new_state) \ + if (cur == str[0]) { \ + instream_t temp = *in; \ + strview_t a = strvInitLen(&(str[1]), sizeof(str) - 2); \ + strview_t b = istrGetViewLen(&temp, a.len); \ + if (strvEquals(a, b)) { \ + *in = temp; \ + hl_print_keyword(ctx, strvInitLen(str, sizeof(str) - 1), HL_COLOR_PREPROC); \ + ctx->state = new_state; \ + break; \ + } \ + } + if (is_last) { + strview_t keyword = hl_finish_keyword(ctx, ctx->kw_beg, in); + hl_print_keyword(ctx, keyword, HL_COLOR_PREPROC); + break; + } + + hl_check("include", HL_STATE_PREPROCESSOR_INCLUDE) + hl_check("define", HL_STATE_DEFAULT) + hl_check("undef", HL_STATE_DEFAULT) + hl_check("ifdef", HL_STATE_DEFAULT) + hl_check("ifndef", HL_STATE_DEFAULT) + hl_check("if", HL_STATE_DEFAULT) + hl_check("endif", HL_STATE_DEFAULT) + hl_check("pragma", HL_STATE_DEFAULT) + +#undef hl_check + break; + } + + + /* + * Preprocessor/Preprocessor include + * + * This is a 'dumb' preprocessor highlighter: + * it highlights everything with the same color + * and if and only if an '#include' is detected + * the included header will be handled as string + * and thus, will have the same color as the string. + * + * In fact, it is somehow similar to what GtkSourceView + * does (Mousepad, Gedit...) but with one silly difference: + * single-line/multi-line comments will not be handled + * while inside the preprocessor state, meaning that + * comments will also have the same color as the remaining + * of the line, yeah, ugly. + */ + case HL_STATE_PREPROCESSOR_INCLUDE: + { + if (cur == '<' || cur == '"' || is_last) { + ctx->kw_beg = istrTell(*in); + ctx->state = HL_STATE_PREPROCESSOR_INCLUDE_STRING; + } + else { + hl_write_char(ctx, cur); + } + break; + } + case HL_STATE_PREPROCESSOR_INCLUDE_STRING: + { + if (cur == '>' || cur == '"' || is_last) { + strview_t keyword = hl_finish_keyword(ctx, ctx->kw_beg, in); + keyword.len += 1; + hl_print_keyword(ctx, keyword, HL_COLOR_STRING); + } + break; + } + } +} + +str_t hlHighlight(arena_t *arena, hl_ctx_t *ctx, strview_t data) { + ctx->ostr = ostrInit(arena); + + ctx->state = HL_STATE_DEFAULT; + ctx->kw_beg = 0; + + instream_t in = istrInitLen(data.buf, data.len); + + while (!istrIsFinished(in)) { + hl_next_char(ctx, &in); + } + + hl_next_char(ctx, &in); + + return ostrAsStr(&ctx->ostr); +} + +void hlSetSymbolInTable(hl_ctx_t *ctx, char symbol, bool value) { + if (!ctx) return; + ctx->symbol_table[(unsigned char)symbol] = value; +} + +void hlAddKeyword(arena_t *arena, hl_ctx_t *ctx, hl_keyword_t *keyword) { + hl_htable_add(arena, &ctx->kw_htable, keyword->keyword, keyword->color); +} + +//// HASH TABLE /////////////////////////////////////////////////// + +static hl_hashtable_t hl_htable_init(arena_t *arena, uint pow2_exp) { + uint count = 1 << pow2_exp; + return (hl_hashtable_t) { + .count = count, + .buckets = alloc(arena, hl_node_t*, count), + }; +} + +static hl_htable_result_e hl_htable_add(arena_t *arena, hl_hashtable_t *table, strview_t key, hl_color_e value) { + if (!table) { + return HL_HTABLE_FAILED; + } + + if ((float)table->used >= table->count * 0.6f) { + warn("more than 60%% of the arena is being used: %d/%d", table->used, table->count); + } + + uint64 hash = hl_htable_hash(key.buf, key.len); + usize index = hash & (table->count - 1); + hl_node_t *bucket = table->buckets[index]; + if (bucket) table->collisions++; + while (bucket) { + // already exists + if (strvEquals(bucket->key, key)) { + bucket->value = value; + return HL_HTABLE_REPLACED; + } + bucket = bucket->next; + } + + bucket = alloc(arena, hl_node_t); + + bucket->key = key; + bucket->value = value; + bucket->next = table->buckets[index]; + + table->buckets[index] = bucket; + table->used++; + + return HL_HTABLE_ADDED; +} + +static hl_node_t *hl_htable_get(hl_hashtable_t *table, strview_t key) { + if (!table || table->count == 0) { + return NULL; + } + + uint64 hash = hl_htable_hash(key.buf, key.len); + usize index = hash & (table->count - 1); + hl_node_t *bucket = table->buckets[index]; + while (bucket) { + if (strvEquals(bucket->key, key)) { + return bucket; + } + bucket = bucket->next; + } + + return NULL; +} + +// uses the sdbm algorithm +static uint64 hl_htable_hash(const void *bytes, usize count) { + const uint8 *data = bytes; + uint64 hash = 0; + + for (usize i = 0; i < count; ++i) { + hash = data[i] + (hash << 6) + (hash << 16) - hash; + } + + return hash; +} + +//// STATIC FUNCTIONS ///////////////////////////////////////////// + +static inline void hl_escape_html(outstream_t *out, char c) { + switch (c) { + case '&': + ostrPuts(out, strv("&")); + break; + case '<': + ostrPuts(out, strv("<")); + break; + case '>': + ostrPuts(out, strv(">")); + break; + default: + ostrPutc(out, c); + break; + } +} + +static void hl_write_char(hl_ctx_t *ctx, char c) { + if (ctx->flags & HL_FLAG_HTML) { + hl_escape_html(&ctx->ostr, c); + } + else { + ostrPutc(&ctx->ostr, c); + } +} + +static void hl_write(hl_ctx_t *ctx, strview_t v) { + if (ctx->flags & HL_FLAG_HTML) { + for (usize i = 0; i < v.len; ++i) { + hl_escape_html(&ctx->ostr, v.buf[i]); + } + } + else { + ostrPuts(&ctx->ostr, v); + } +} + +static bool hl_is_char_keyword(char c) { + return isalpha(c) || isdigit(c) || c == '_'; +} + +static bool hl_highlight_symbol(hl_ctx_t *ctx, char c) { + if (!ctx->symbol_table[(unsigned char)c]) { + return false; + } + + ostrPuts(&ctx->ostr, ctx->colors[HL_COLOR_SYMBOL]); + hl_write_char(ctx, c); + ostrPuts(&ctx->ostr, ctx->colors[HL_COLOR_NORMAL]); + + return true; +} + +static hl_color_e hl_get_keyword_color(hl_ctx_t *ctx, strview_t keyword) { + // todo: make this an option? + if (strvEndsWithView(keyword, strv("_t"))) { + return HL_COLOR_CUSTOM_TYPES; + } + + hl_node_t *node = hl_htable_get(&ctx->kw_htable, keyword); + return node ? node->value : HL_COLOR__COUNT; +} + +static bool hl_is_capitalised(strview_t string) { + for (usize i = 0; i < string.len; ++i) { + char c = string.buf[i]; + if (!isdigit(c) && c != '_' && (c < 'A' || c > 'Z')) { + return false; + } + } + return true; +} + +static strview_t hl_finish_keyword(hl_ctx_t *ctx, usize beg, instream_t *in) { + ctx->state = HL_STATE_DEFAULT; + beg -= 1; + usize end = istrTell(*in) - 1; + + return strv(in->start + beg, end - beg); +} + +static void hl_print_keyword(hl_ctx_t *ctx, strview_t keyword, hl_color_e color) { + ostrPuts(&ctx->ostr, ctx->colors[color]); + hl_write(ctx, keyword); + ostrPuts(&ctx->ostr, ctx->colors[HL_COLOR_NORMAL]); +} \ No newline at end of file diff --git a/highlight.h b/highlight.h new file mode 100644 index 0000000..4ef28a8 --- /dev/null +++ b/highlight.h @@ -0,0 +1,49 @@ +#pragma once + +#include "str.h" + +typedef enum { + HL_COLOR_NORMAL, + HL_COLOR_PREPROC, + HL_COLOR_TYPES, + HL_COLOR_CUSTOM_TYPES, + HL_COLOR_KEYWORDS, + HL_COLOR_NUMBER, + HL_COLOR_STRING, + HL_COLOR_COMMENT, + HL_COLOR_FUNC, + HL_COLOR_SYMBOL, + HL_COLOR_MACRO, + + HL_COLOR__COUNT, +} hl_color_e; + +typedef enum { + HL_FLAG_NONE = 0, + HL_FLAG_HTML = 1 << 0, +} hl_flags_e; + +typedef struct { + strview_t keyword; + hl_color_e color; +} hl_keyword_t; + +typedef struct { + usize idx; + usize size; +} hl_line_t; + +typedef struct { + strview_t colors[HL_COLOR__COUNT]; + hl_keyword_t *extra_kwrds; + int kwrds_count; + hl_flags_e flags; +} hl_config_t; + +typedef struct hl_ctx_t hl_ctx_t; + +hl_ctx_t *hlInit(arena_t *arena, hl_config_t *config); +str_t hlHighlight(arena_t *arena, hl_ctx_t *ctx, strview_t str); + +void hlSetSymbolInTable(hl_ctx_t *ctx, char symbol, bool value); +void hlAddKeyword(arena_t *arena, hl_ctx_t *ctx, hl_keyword_t *keyword); diff --git a/colla/hot_reload.c b/hot_reload.c similarity index 88% rename from colla/hot_reload.c rename to hot_reload.c index 9c11b30..516fd5f 100644 --- a/colla/hot_reload.c +++ b/hot_reload.c @@ -6,7 +6,6 @@ // todo linux support? #if COLLA_WIN -#define WIN32_LEAN_AND_MEAN #include #else // patch stuff up for cross platform for now, the actual program should not really call anything for now @@ -25,6 +24,9 @@ typedef struct { hr_f hr_close; } hr_internal_t; +static bool hr__os_reload(hr_internal_t *hr); +static void hr__os_free(hr_internal_t *hr); + static bool hr__file_copy(arena_t scratch, strview_t src, strview_t dst) { buffer_t srcbuf = fileReadWhole(&scratch, src); if (srcbuf.data == NULL || srcbuf.len == 0) { @@ -70,6 +72,93 @@ static bool hr__reload(hr_t *ctx) { info("loading library: %v", dll); + return hr__os_reload(hr); +} + +bool hrOpen(hr_t *ctx, strview_t path) { +#ifdef HR_DISABLE + cr_init(ctx); + return true; +#endif + arena_t arena = arenaMake(ARENA_VIRTUAL, MB(1)); + + str_t path_copy = str(&arena, path); + + if (!fileExists(path_copy.buf)) { + err("dll file: %v does not exist", path); + arenaCleanup(&arena); + return false; + } + + hr_internal_t *hr = alloc(&arena, hr_internal_t); + hr->arena = arena; + hr->path = path_copy; + + ctx->p = hr; + ctx->last_working_version = 0; + + return hr__reload(ctx); +} + +void hrClose(hr_t *ctx, bool clean_temp_files) { +#ifdef HR_DISABLE + hr_close(ctx); + return; +#endif + + hr_internal_t *hr = ctx->p; + if (hr->hr_close) { + hr->hr_close(ctx); + } + + hr__os_free(hr); + + hr->handle = NULL; + hr->hr_init = hr->hr_loop = hr->hr_close = NULL; + + if (clean_temp_files) { + arena_t scratch = hr->arena; + + strview_t dir, name, ext; + fileSplitPath(strv(hr->path), &dir, &name, &ext); + + for (int i = 0; i < ctx->last_working_version; ++i) { + str_t fname = strFmt(&scratch, "%v/%v-%d%v", dir, name, i + 1, ext); + if (!fileDelete(scratch, strv(fname))) { + err("couldn't delete %v", fname); + } + } + } + + arena_t arena = hr->arena; + arenaCleanup(&arena); + + ctx->p = NULL; +} + +int hrStep(hr_t *ctx) { +#ifdef CR_DISABLE + hr_loop(ctx); + return 0; +#endif + hr_internal_t *hr = ctx->p; + + int result = -1; + if (hr->hr_loop) { + result = hr->hr_loop(ctx); + } + return result; +} + +bool hrReload(hr_t *ctx) { + return hr__reload(ctx); +} + +//// OS SPECIFIC //////////////////////////////////////// + +#if COLLA_WIN + +static bool hr__os_reload(hr_internal_t *hr) { if (hr->handle) { FreeLibrary(hr->handle); } @@ -117,84 +206,21 @@ error: return false; } -bool hrOpen(hr_t *ctx, strview_t path) { -#ifdef HR_DISABLE - cr_init(ctx); - return true; -#endif - arena_t arena = arenaMake(ARENA_VIRTUAL, MB(1)); - - str_t path_copy = str(&arena, path); - - if (!fileExists(path_copy.buf)) { - err("dll file: %v does not exist", path); - arenaCleanup(&arena); - return false; - } - - hr_internal_t *hr = alloc(&arena, hr_internal_t); - hr->arena = arena; - hr->path = path_copy; - - ctx->p = hr; - ctx->last_working_version = 0; - - return hr__reload(ctx); -} - -void hrClose(hr_t *ctx, bool clean_temp_files) { -#ifdef HR_DISABLE - hr_close(ctx); - return; -#endif - - hr_internal_t *hr = ctx->p; - if (hr->hr_close) { - hr->hr_close(ctx); - } - +static void hr__os_free(hr_internal_t *hr) { if (hr->handle) { FreeLibrary(hr->handle); } - - hr->handle = NULL; - hr->hr_init = hr->hr_loop = hr->hr_close = NULL; - - if (clean_temp_files) { - arena_t scratch = hr->arena; - - strview_t dir, name, ext; - fileSplitPath(strv(hr->path), &dir, &name, &ext); - - for (int i = 0; i < ctx->last_working_version; ++i) { - str_t fname = strFmt(&scratch, "%v/%v-%d%v", dir, name, i + 1, ext); - if (!fileDelete(scratch, strv(fname))) { - err("couldn't delete %v: %d", fname, GetLastError()); - } - } - } - - arena_t arena = hr->arena; - arenaCleanup(&arena); - - ctx->p = NULL; } -int hrStep(hr_t *ctx) { -#ifdef CR_DISABLE - hr_loop(ctx); - return 0; -#endif - hr_internal_t *hr = ctx->p; +#elif COLLA_LIN - int result = -1; - if (hr->hr_loop) { - result = hr->hr_loop(ctx); - } - return result; +static bool hr__os_reload(hr_internal_t *hr) { + fatal("todo: linux hot reload not implemented yet"); + return true; } -bool hrReload(hr_t *ctx) { - return hr__reload(ctx); +static void hr__os_free(hr_internal_t *hr) { + fatal("todo: linux hot reload not implemented yet"); } +#endif \ No newline at end of file diff --git a/colla/hot_reload.h b/hot_reload.h similarity index 100% rename from colla/hot_reload.h rename to hot_reload.h diff --git a/html.h b/html.h new file mode 100644 index 0000000..132a9c0 --- /dev/null +++ b/html.h @@ -0,0 +1,77 @@ +#pragma once + +#include "strstream.h" + +typedef struct { + outstream_t stream; + str_t *output; +} html_context_t; + +strview_t html__strv_copy(strview_t src) { return src; } + +#define html__str_or_strv(str) _Generic(str, \ + strview_t: html__strv_copy, \ + str_t: strvInitStr, \ + const char *: strvInit, \ + char *: strvInit \ + )(str) + +#define htmlPrintf(...) ostrPrintf(&__ctx.stream, __VA_ARGS__) +#define htmlPuts(str) ostrPuts(&__ctx.stream, html__str_or_strv(str)) + +#define htmlBeg(arena_ptr, str_ptr) { \ + html_context_t __ctx = { .stream = ostrInit(arena_ptr), .output = str_ptr }; \ + htmlPrintf("\n"); +#define htmlEnd() htmlPrintf(""); *__ctx.output = ostrAsStr(&__ctx.stream); } + +#define html__args() \ + X(class) \ + X(id) \ + X(style) \ + X(onclick) \ + X(href) \ + X(src) \ + +typedef struct { +#define X(name) const char *name; + html__args() +#undef X +} html_tag_t; + +static void html__tag(html_context_t *ctx, const char *tag, html_tag_t *args) { + ostrPrintf(&ctx->stream, "<%s ", tag); + +#define X(name, ...) if (args->name) { ostrPrintf(&ctx->stream, #name "=\"%s\" ", args->name); } + html__args() +#undef X + + ostrPutc(&ctx->stream, '>'); +} + +#define tagBeg(tag, ...) do { html_tag_t args = {0, __VA_ARGS__}; html__tag(&__ctx, tag, &args); } while (0) +#define tagEnd(tag) htmlPrintf("") + +#define html__strv_or_str(s) _Generic(s, str_t: NULL) + +#define html__simple_tag(tag, text, ...) do { tagBeg(tag, __VA_ARGS__); htmlPuts(text); tagEnd(tag); } while (0) + +#define headBeg(...) tagBeg("head", __VA_ARGS__) +#define headEnd() tagEnd("head") + +#define bodyBeg(...) tagBeg("body", __VA_ARGS__) +#define bodyEnd() tagEnd("body") + +#define divBeg(...) tagBeg("div", __VA_ARGS__) +#define divEnd() tagEnd("div") + +#define htmlRaw(data) ostrPuts(&__ctx.stream, strv(#data)) + +#define title(text, ...) html__simple_tag("title", text, __VA_ARGS__) +#define h1(text, ...) html__simple_tag("h1", text, __VA_ARGS__) +#define p(text, ...) html__simple_tag("p", text, __VA_ARGS__) +#define span(text, ...) html__simple_tag("span", text, __VA_ARGS__) +#define a(text, ...) html__simple_tag("a", text, __VA_ARGS__) +#define img(...) tagBeg("img", __VA_ARGS__) +#define style(text, ...) html__simple_tag("style", text, __VA_ARGS__) + +#define hr() htmlPuts("
") diff --git a/colla/http.c b/http.c similarity index 95% rename from colla/http.c rename to http.c index 2a08cd8..4415e00 100644 --- a/colla/http.c +++ b/http.c @@ -1,556 +1,556 @@ -#include "http.h" - -#include "warnings/colla_warn_beg.h" - -#include -#include - -#include "arena.h" -#include "str.h" -#include "strstream.h" -#include "format.h" -#include "socket.h" -#include "tracelog.h" - -#if COLLA_WIN - #if COLLA_CMT_LIB - #pragma comment(lib, "Wininet") - #endif - - #include - #if !COLLA_TCC - #include - #endif -#endif - -static const TCHAR *https__get_method_str(http_method_e method); - -static http_header_t *http__parse_headers(arena_t *arena, instream_t *in) { - http_header_t *head = NULL; - strview_t line = (strview_t){0}; - - do { - line = istrGetView(in, '\r'); - - usize pos = strvFind(line, ':', 0); - if (pos != STR_NONE) { - http_header_t *h = alloc(arena, http_header_t); - - h->key = strvSub(line, 0, pos); - h->value = strvSub(line, pos + 2, SIZE_MAX); - - h->next = head; - head = h; - } - - istrSkip(in, 2); // skip \r\n - } while (line.len > 2); // while line != "\r\n" - - return head; -} - -const char *httpGetStatusString(int status) { - switch (status) { - case 200: return "OK"; - case 201: return "CREATED"; - case 202: return "ACCEPTED"; - case 204: return "NO CONTENT"; - case 205: return "RESET CONTENT"; - case 206: return "PARTIAL CONTENT"; - - case 300: return "MULTIPLE CHOICES"; - case 301: return "MOVED PERMANENTLY"; - case 302: return "MOVED TEMPORARILY"; - case 304: return "NOT MODIFIED"; - - case 400: return "BAD REQUEST"; - case 401: return "UNAUTHORIZED"; - case 403: return "FORBIDDEN"; - case 404: return "NOT FOUND"; - case 407: return "RANGE NOT SATISFIABLE"; - - case 500: return "INTERNAL SERVER_ERROR"; - case 501: return "NOT IMPLEMENTED"; - case 502: return "BAD GATEWAY"; - case 503: return "SERVICE NOT AVAILABLE"; - case 504: return "GATEWAY TIMEOUT"; - case 505: return "VERSION NOT SUPPORTED"; - } - - return "UNKNOWN"; -} - -int httpVerNumber(http_version_t ver) { - return (ver.major * 10) + ver.minor; -} - -http_req_t httpParseReq(arena_t *arena, strview_t request) { - http_req_t req = {0}; - instream_t in = istrInitLen(request.buf, request.len); - - strview_t method = strvTrim(istrGetView(&in, '/')); - istrSkip(&in, 1); // skip / - req.url = strvTrim(istrGetView(&in, ' ')); - strview_t http = strvTrim(istrGetView(&in, '\n')); - - istrSkip(&in, 1); // skip \n - - req.headers = http__parse_headers(arena, &in); - - req.body = strvTrim(istrGetViewLen(&in, SIZE_MAX)); - - strview_t methods[5] = { strv("GET"), strv("POST"), strv("HEAD"), strv("PUT"), strv("DELETE") }; - usize methods_count = arrlen(methods); - - for (usize i = 0; i < methods_count; ++i) { - if (strvEquals(method, methods[i])) { - req.method = (http_method_e)i; - break; - } - } - - in = istrInitLen(http.buf, http.len); - istrIgnoreAndSkip(&in, '/'); // skip HTTP/ - istrGetU8(&in, &req.version.major); - istrSkip(&in, 1); // skip . - istrGetU8(&in, &req.version.minor); - - return req; -} - -http_res_t httpParseRes(arena_t *arena, strview_t response) { - http_res_t res = {0}; - instream_t in = istrInitLen(response.buf, response.len); - - strview_t http = istrGetViewLen(&in, 5); - if (!strvEquals(http, strv("HTTP"))) { - err("response doesn't start with 'HTTP', instead with %v", http); - return (http_res_t){0}; - } - istrSkip(&in, 1); // skip / - istrGetU8(&in, &res.version.major); - istrSkip(&in, 1); // skip . - istrGetU8(&in, &res.version.minor); - istrGetI32(&in, (int32*)&res.status_code); - - istrIgnore(&in, '\n'); - istrSkip(&in, 1); // skip \n - - res.headers = http__parse_headers(arena, &in); - - strview_t encoding = httpGetHeader(res.headers, strv("transfer-encoding")); - if (!strvEquals(encoding, strv("chunked"))) { - res.body = istrGetViewLen(&in, SIZE_MAX); - } - else { - err("chunked encoding not implemented yet! body ignored"); - } - - return res; -} - -str_t httpReqToStr(arena_t *arena, http_req_t *req) { - outstream_t out = ostrInit(arena); - - const char *method = NULL; - switch (req->method) { - case HTTP_GET: method = "GET"; break; - case HTTP_POST: method = "POST"; break; - case HTTP_HEAD: method = "HEAD"; break; - case HTTP_PUT: method = "PUT"; break; - case HTTP_DELETE: method = "DELETE"; break; - default: err("unrecognised method: %d", method); return (str_t){0}; - } - - ostrPrintf( - &out, - "%s /%v HTTP/%hhu.%hhu\r\n", - method, req->url, req->version.major, req->version.minor - ); - - http_header_t *h = req->headers; - while (h) { - ostrPrintf(&out, "%v: %v\r\n", h->key, h->value); - h = h->next; - } - - ostrPuts(&out, strv("\r\n")); - ostrPuts(&out, req->body); - - return ostrAsStr(&out); -} - -str_t httpResToStr(arena_t *arena, http_res_t *res) { - outstream_t out = ostrInit(arena); - - ostrPrintf( - &out, - "HTTP/%hhu.%hhu %d %s\r\n", - res->version.major, - res->version.minor, - res->status_code, - httpGetStatusString(res->status_code) - ); - ostrPuts(&out, strv("\r\n")); - ostrPuts(&out, res->body); - - return ostrAsStr(&out); -} - -bool httpHasHeader(http_header_t *headers, strview_t key) { - http_header_t *h = headers; - while (h) { - if (strvEquals(h->key, key)) { - return true; - } - h = h->next; - } - return false; -} - -void httpSetHeader(http_header_t *headers, strview_t key, strview_t value) { - http_header_t *h = headers; - while (h) { - if (strvEquals(h->key, key)) { - h->value = value; - break; - } - h = h->next; - } -} - -strview_t httpGetHeader(http_header_t *headers, strview_t key) { - http_header_t *h = headers; - while (h) { - if (strvEquals(h->key, key)) { - return h->value; - } - h = h->next; - } - return (strview_t){0}; -} - -str_t httpMakeUrlSafe(arena_t *arena, strview_t string) { - strview_t chars = strv(" !\"#$%%&'()*+,/:;=?@[]"); - usize final_len = string.len; - - // find final string length first - for (usize i = 0; i < string.len; ++i) { - if (strvContains(chars, string.buf[i])) { - final_len += 2; - } - } - - str_t out = { - .buf = alloc(arena, char, final_len + 1), - .len = final_len - }; - usize cur = 0; - // substitute characters - for (usize i = 0; i < string.len; ++i) { - if (strvContains(chars, string.buf[i])) { - fmtBuffer(out.buf + cur, 4, "%%%X", string.buf[i]); - cur += 3; - } - else { - out.buf[cur++] = string.buf[i]; - } - } - - return out; -} - -str_t httpDecodeUrlSafe(arena_t *arena, strview_t string) { - usize final_len = string.len; - - for (usize i = 0; i < string.len; ++i) { - if (string.buf[i] == '%') { - final_len -= 2; - i += 2; - } - } - - assert(final_len <= string.len); - - str_t out = { - .buf = alloc(arena, char, final_len + 1), - .len = final_len - }; - - usize k = 0; - - for (usize i = 0; i < string.len; ++i) { - if (string.buf[i] == '%') { - // skip % - ++i; - - unsigned int ch = 0; - int result = sscanf(string.buf + i, "%02X", &ch); - if (result != 1 || ch > UINT8_MAX) { - err("malformed url at %zu (%s)", i, string.buf + i); - return (str_t){0}; - } - out.buf[k++] = (char)ch; - - // skip first char of hex - ++i; - } - else { - out.buf[k++] = string.buf[i]; - } - } - - return out; -} - -http_url_t httpSplitUrl(strview_t url) { - http_url_t out = {0}; - - if (strvStartsWithView(url, strv("https://"))) { - url = strvRemovePrefix(url, 8); - } - else if (strvStartsWithView(url, strv("http://"))) { - url = strvRemovePrefix(url, 7); - } - - out.host = strvSub(url, 0, strvFind(url, '/', 0)); - out.uri = strvSub(url, out.host.len, SIZE_MAX); - - return out; -} - -http_res_t httpRequest(http_request_desc_t *request) { - usize arena_begin = arenaTell(request->arena); - - http_req_t req = { - .version = (http_version_t){ 1, 1 }, - .url = request->url, - .body = request->body, - .method = request->request_type, - }; - - http_header_t *h = NULL; - - for (int i = 0; i < request->header_count; ++i) { - http_header_t *header = request->headers + i; - header->next = h; - h = header; - } - - req.headers = h; - - http_url_t url = httpSplitUrl(req.url); - - if (strvEndsWith(url.host, '/')) { - url.host = strvRemoveSuffix(url.host, 1); - } - - if (!httpHasHeader(req.headers, strv("Host"))) { - httpSetHeader(req.headers, strv("Host"), url.host); - } - if (!httpHasHeader(req.headers, strv("Content-Length"))) { - char tmp[16] = {0}; - fmtBuffer(tmp, arrlen(tmp), "%zu", req.body.len); - httpSetHeader(req.headers, strv("Content-Length"), strv(tmp)); - } - if (req.method == HTTP_POST && !httpHasHeader(req.headers, strv("Content-Type"))) { - httpSetHeader(req.headers, strv("Content-Type"), strv("application/x-www-form-urlencoded")); - } - if (!httpHasHeader(req.headers, strv("Connection"))) { - httpSetHeader(req.headers, strv("Connection"), strv("close")); - } - - if (!skInit()) { - err("couldn't initialise sockets: %s", skGetErrorString()); - goto error; - } - - socket_t sock = skOpen(SOCK_TCP); - if (!skIsValid(sock)) { - err("couldn't open socket: %s", skGetErrorString()); - goto error; - } - - char hostname[64] = {0}; - assert(url.host.len < arrlen(hostname)); - memcpy(hostname, url.host.buf, url.host.len); - - const uint16 DEFAULT_HTTP_PORT = 80; - if (!skConnect(sock, hostname, DEFAULT_HTTP_PORT)) { - err("Couldn't connect to host %s: %s", hostname, skGetErrorString()); - goto error; - } - - str_t reqstr = httpReqToStr(request->arena, &req); - if (strIsEmpty(reqstr)) { - err("couldn't get string from request"); - goto error; - } - - if (skSend(sock, reqstr.buf, (int)reqstr.len) == SOCKET_ERROR) { - err("couldn't send request to socket: %s", skGetErrorString()); - goto error; - } - - outstream_t response = ostrInit(request->arena); - char buffer[4096]; - int read = 0; - do { - read = skReceive(sock, buffer, arrlen(buffer)); - if (read == SOCKET_ERROR) { - err("couldn't get the data from the server: %s", skGetErrorString()); - goto error; - } - ostrPuts(&response, strv(buffer, read)); - } while (read != 0); - - if (!skClose(sock)) { - err("couldn't close socket: %s", skGetErrorString()); - } - - if (!skCleanup()) { - err("couldn't clean up sockets: %s", skGetErrorString()); - } - - return httpParseRes(request->arena, ostrAsView(&response)); - -error: - arenaRewind(request->arena, arena_begin); - skCleanup(); - return (http_res_t){0}; -} - -#if COLLA_WIN - -buffer_t httpsRequest(http_request_desc_t *req) { - HINTERNET internet = InternetOpenA( - TEXT("COLLA"), - INTERNET_OPEN_TYPE_PRECONFIG, - NULL, - NULL, - 0 - ); - if (!internet) { - fatal("call to InternetOpen failed: %u", GetLastError()); - } - - http_url_t split = httpSplitUrl(req->url); - strview_t server = split.host; - strview_t page = split.uri; - - if (strvStartsWithView(server, strv("http://"))) { - server = strvRemovePrefix(server, 7); - } - - if (strvStartsWithView(server, strv("https://"))) { - server = strvRemovePrefix(server, 8); - } - - arena_t scratch = *req->arena; - const TCHAR *tserver = strvToTChar(&scratch, server); - const TCHAR *tpage = strvToTChar(&scratch, page); - - HINTERNET connection = InternetConnect( - internet, - tserver, - INTERNET_DEFAULT_HTTPS_PORT, - NULL, - NULL, - INTERNET_SERVICE_HTTP, - 0, - (DWORD_PTR)NULL // userdata - ); - if (!connection) { - fatal("call to InternetConnect failed: %u", GetLastError()); - } - - const TCHAR *accepted_types[] = { TEXT("*/*"), NULL }; - - HINTERNET request = HttpOpenRequest( - connection, - https__get_method_str(req->request_type), - tpage, - TEXT("HTTP/1.1"), - NULL, - accepted_types, - INTERNET_FLAG_SECURE, - (DWORD_PTR)NULL // userdata - ); - if (!request) { - fatal("call to HttpOpenRequest failed: %u", GetLastError()); - } - - outstream_t header = ostrInit(&scratch); - - for (int i = 0; i < req->header_count; ++i) { - http_header_t *h = &req->headers[i]; - ostrClear(&header); - ostrPrintf( - &header, - "%.*s: %.*s\r\n", - h->key.len, h->key.buf, - h->value.len, h->value.buf - ); - str_t header_str = ostrAsStr(&header); - HttpAddRequestHeadersA( - request, - header_str.buf, - (DWORD)header_str.len, - 0 - ); - } - - BOOL request_sent = HttpSendRequest( - request, - NULL, - 0, - (void *)req->body.buf, - (DWORD)req->body.len - ); - if (!request_sent) { - fatal("call to HttpSendRequest failed: %u", GetLastError()); - } - - outstream_t out = ostrInit(req->arena); - - while (true) { - DWORD bytes_read = 0; - char buffer[4096]; - BOOL read = InternetReadFile( - request, - buffer, - sizeof(buffer), - &bytes_read - ); - if (!read || bytes_read == 0) { - break; - } - ostrPuts(&out, strv(buffer, bytes_read)); - } - - InternetCloseHandle(request); - InternetCloseHandle(connection); - InternetCloseHandle(internet); - - str_t outstr = ostrAsStr(&out); - - return (buffer_t) { - .data = (uint8 *)outstr.buf, - .len = outstr.len - }; -} - -static const TCHAR *https__get_method_str(http_method_e method) { - switch (method) { - case HTTP_GET: return TEXT("GET"); - case HTTP_POST: return TEXT("POST"); - case HTTP_HEAD: return TEXT("HEAD"); - case HTTP_PUT: return TEXT("PUT"); - case HTTP_DELETE: return TEXT("DELETE"); - } - // default GET - return NULL; -} -#endif - -#include "warnings/colla_warn_end.h" +#include "http.h" + +#include "warnings/colla_warn_beg.h" + +#include +#include + +#include "arena.h" +#include "str.h" +#include "strstream.h" +#include "format.h" +#include "socket.h" +#include "tracelog.h" + +#if COLLA_WIN + #if COLLA_CMT_LIB + #pragma comment(lib, "Wininet") + #endif + + #include + #if !COLLA_TCC + #include + #endif +#endif + +static const TCHAR *https__get_method_str(http_method_e method); + +static http_header_t *http__parse_headers(arena_t *arena, instream_t *in) { + http_header_t *head = NULL; + strview_t line = STRV_EMPTY; + + do { + line = istrGetView(in, '\r'); + + usize pos = strvFind(line, ':', 0); + if (pos != STR_NONE) { + http_header_t *h = alloc(arena, http_header_t); + + h->key = strvSub(line, 0, pos); + h->value = strvSub(line, pos + 2, SIZE_MAX); + + h->next = head; + head = h; + } + + istrSkip(in, 2); // skip \r\n + } while (line.len > 2); // while line != "\r\n" + + return head; +} + +const char *httpGetStatusString(int status) { + switch (status) { + case 200: return "OK"; + case 201: return "CREATED"; + case 202: return "ACCEPTED"; + case 204: return "NO CONTENT"; + case 205: return "RESET CONTENT"; + case 206: return "PARTIAL CONTENT"; + + case 300: return "MULTIPLE CHOICES"; + case 301: return "MOVED PERMANENTLY"; + case 302: return "MOVED TEMPORARILY"; + case 304: return "NOT MODIFIED"; + + case 400: return "BAD REQUEST"; + case 401: return "UNAUTHORIZED"; + case 403: return "FORBIDDEN"; + case 404: return "NOT FOUND"; + case 407: return "RANGE NOT SATISFIABLE"; + + case 500: return "INTERNAL SERVER_ERROR"; + case 501: return "NOT IMPLEMENTED"; + case 502: return "BAD GATEWAY"; + case 503: return "SERVICE NOT AVAILABLE"; + case 504: return "GATEWAY TIMEOUT"; + case 505: return "VERSION NOT SUPPORTED"; + } + + return "UNKNOWN"; +} + +int httpVerNumber(http_version_t ver) { + return (ver.major * 10) + ver.minor; +} + +http_req_t httpParseReq(arena_t *arena, strview_t request) { + http_req_t req = {0}; + instream_t in = istrInitLen(request.buf, request.len); + + strview_t method = strvTrim(istrGetView(&in, '/')); + istrSkip(&in, 1); // skip / + req.url = strvTrim(istrGetView(&in, ' ')); + strview_t http = strvTrim(istrGetView(&in, '\n')); + + istrSkip(&in, 1); // skip \n + + req.headers = http__parse_headers(arena, &in); + + req.body = strvTrim(istrGetViewLen(&in, SIZE_MAX)); + + strview_t methods[5] = { strv("GET"), strv("POST"), strv("HEAD"), strv("PUT"), strv("DELETE") }; + usize methods_count = arrlen(methods); + + for (usize i = 0; i < methods_count; ++i) { + if (strvEquals(method, methods[i])) { + req.method = (http_method_e)i; + break; + } + } + + in = istrInitLen(http.buf, http.len); + istrIgnoreAndSkip(&in, '/'); // skip HTTP/ + istrGetU8(&in, &req.version.major); + istrSkip(&in, 1); // skip . + istrGetU8(&in, &req.version.minor); + + return req; +} + +http_res_t httpParseRes(arena_t *arena, strview_t response) { + http_res_t res = {0}; + instream_t in = istrInitLen(response.buf, response.len); + + strview_t http = istrGetViewLen(&in, 5); + if (!strvEquals(http, strv("HTTP"))) { + err("response doesn't start with 'HTTP', instead with %v", http); + return (http_res_t){0}; + } + istrSkip(&in, 1); // skip / + istrGetU8(&in, &res.version.major); + istrSkip(&in, 1); // skip . + istrGetU8(&in, &res.version.minor); + istrGetI32(&in, (int32*)&res.status_code); + + istrIgnore(&in, '\n'); + istrSkip(&in, 1); // skip \n + + res.headers = http__parse_headers(arena, &in); + + strview_t encoding = httpGetHeader(res.headers, strv("transfer-encoding")); + if (!strvEquals(encoding, strv("chunked"))) { + res.body = istrGetViewLen(&in, SIZE_MAX); + } + else { + err("chunked encoding not implemented yet! body ignored"); + } + + return res; +} + +str_t httpReqToStr(arena_t *arena, http_req_t *req) { + outstream_t out = ostrInit(arena); + + const char *method = NULL; + switch (req->method) { + case HTTP_GET: method = "GET"; break; + case HTTP_POST: method = "POST"; break; + case HTTP_HEAD: method = "HEAD"; break; + case HTTP_PUT: method = "PUT"; break; + case HTTP_DELETE: method = "DELETE"; break; + default: err("unrecognised method: %d", method); return STR_EMPTY; + } + + ostrPrintf( + &out, + "%s /%v HTTP/%hhu.%hhu\r\n", + method, req->url, req->version.major, req->version.minor + ); + + http_header_t *h = req->headers; + while (h) { + ostrPrintf(&out, "%v: %v\r\n", h->key, h->value); + h = h->next; + } + + ostrPuts(&out, strv("\r\n")); + ostrPuts(&out, req->body); + + return ostrAsStr(&out); +} + +str_t httpResToStr(arena_t *arena, http_res_t *res) { + outstream_t out = ostrInit(arena); + + ostrPrintf( + &out, + "HTTP/%hhu.%hhu %d %s\r\n", + res->version.major, + res->version.minor, + res->status_code, + httpGetStatusString(res->status_code) + ); + ostrPuts(&out, strv("\r\n")); + ostrPuts(&out, res->body); + + return ostrAsStr(&out); +} + +bool httpHasHeader(http_header_t *headers, strview_t key) { + http_header_t *h = headers; + while (h) { + if (strvEquals(h->key, key)) { + return true; + } + h = h->next; + } + return false; +} + +void httpSetHeader(http_header_t *headers, strview_t key, strview_t value) { + http_header_t *h = headers; + while (h) { + if (strvEquals(h->key, key)) { + h->value = value; + break; + } + h = h->next; + } +} + +strview_t httpGetHeader(http_header_t *headers, strview_t key) { + http_header_t *h = headers; + while (h) { + if (strvEquals(h->key, key)) { + return h->value; + } + h = h->next; + } + return STRV_EMPTY; +} + +str_t httpMakeUrlSafe(arena_t *arena, strview_t string) { + strview_t chars = strv(" !\"#$%%&'()*+,/:;=?@[]"); + usize final_len = string.len; + + // find final string length first + for (usize i = 0; i < string.len; ++i) { + if (strvContains(chars, string.buf[i])) { + final_len += 2; + } + } + + str_t out = { + .buf = alloc(arena, char, final_len + 1), + .len = final_len + }; + usize cur = 0; + // substitute characters + for (usize i = 0; i < string.len; ++i) { + if (strvContains(chars, string.buf[i])) { + fmtBuffer(out.buf + cur, 4, "%%%X", string.buf[i]); + cur += 3; + } + else { + out.buf[cur++] = string.buf[i]; + } + } + + return out; +} + +str_t httpDecodeUrlSafe(arena_t *arena, strview_t string) { + usize final_len = string.len; + + for (usize i = 0; i < string.len; ++i) { + if (string.buf[i] == '%') { + final_len -= 2; + i += 2; + } + } + + assert(final_len <= string.len); + + str_t out = { + .buf = alloc(arena, char, final_len + 1), + .len = final_len + }; + + usize k = 0; + + for (usize i = 0; i < string.len; ++i) { + if (string.buf[i] == '%') { + // skip % + ++i; + + unsigned int ch = 0; + int result = sscanf(string.buf + i, "%02X", &ch); + if (result != 1 || ch > UINT8_MAX) { + err("malformed url at %zu (%s)", i, string.buf + i); + return STR_EMPTY; + } + out.buf[k++] = (char)ch; + + // skip first char of hex + ++i; + } + else { + out.buf[k++] = string.buf[i]; + } + } + + return out; +} + +http_url_t httpSplitUrl(strview_t url) { + http_url_t out = {0}; + + if (strvStartsWithView(url, strv("https://"))) { + url = strvRemovePrefix(url, 8); + } + else if (strvStartsWithView(url, strv("http://"))) { + url = strvRemovePrefix(url, 7); + } + + out.host = strvSub(url, 0, strvFind(url, '/', 0)); + out.uri = strvSub(url, out.host.len, SIZE_MAX); + + return out; +} + +http_res_t httpRequest(http_request_desc_t *request) { + usize arena_begin = arenaTell(request->arena); + + http_req_t req = { + .version = (http_version_t){ 1, 1 }, + .url = request->url, + .body = request->body, + .method = request->request_type, + }; + + http_header_t *h = NULL; + + for (int i = 0; i < request->header_count; ++i) { + http_header_t *header = request->headers + i; + header->next = h; + h = header; + } + + req.headers = h; + + http_url_t url = httpSplitUrl(req.url); + + if (strvEndsWith(url.host, '/')) { + url.host = strvRemoveSuffix(url.host, 1); + } + + if (!httpHasHeader(req.headers, strv("Host"))) { + httpSetHeader(req.headers, strv("Host"), url.host); + } + if (!httpHasHeader(req.headers, strv("Content-Length"))) { + char tmp[16] = {0}; + fmtBuffer(tmp, arrlen(tmp), "%zu", req.body.len); + httpSetHeader(req.headers, strv("Content-Length"), strv(tmp)); + } + if (req.method == HTTP_POST && !httpHasHeader(req.headers, strv("Content-Type"))) { + httpSetHeader(req.headers, strv("Content-Type"), strv("application/x-www-form-urlencoded")); + } + if (!httpHasHeader(req.headers, strv("Connection"))) { + httpSetHeader(req.headers, strv("Connection"), strv("close")); + } + + if (!skInit()) { + err("couldn't initialise sockets: %s", skGetErrorString()); + goto error; + } + + socket_t sock = skOpen(SOCK_TCP); + if (!skIsValid(sock)) { + err("couldn't open socket: %s", skGetErrorString()); + goto error; + } + + char hostname[64] = {0}; + assert(url.host.len < arrlen(hostname)); + memcpy(hostname, url.host.buf, url.host.len); + + const uint16 DEFAULT_HTTP_PORT = 80; + if (!skConnect(sock, hostname, DEFAULT_HTTP_PORT)) { + err("Couldn't connect to host %s: %s", hostname, skGetErrorString()); + goto error; + } + + str_t reqstr = httpReqToStr(request->arena, &req); + if (strIsEmpty(reqstr)) { + err("couldn't get string from request"); + goto error; + } + + if (skSend(sock, reqstr.buf, (int)reqstr.len) == SOCKET_ERROR) { + err("couldn't send request to socket: %s", skGetErrorString()); + goto error; + } + + outstream_t response = ostrInit(request->arena); + char buffer[4096]; + int read = 0; + do { + read = skReceive(sock, buffer, arrlen(buffer)); + if (read == SOCKET_ERROR) { + err("couldn't get the data from the server: %s", skGetErrorString()); + goto error; + } + ostrPuts(&response, strv(buffer, read)); + } while (read != 0); + + if (!skClose(sock)) { + err("couldn't close socket: %s", skGetErrorString()); + } + + if (!skCleanup()) { + err("couldn't clean up sockets: %s", skGetErrorString()); + } + + return httpParseRes(request->arena, ostrAsView(&response)); + +error: + arenaRewind(request->arena, arena_begin); + skCleanup(); + return (http_res_t){0}; +} + +#if COLLA_WIN + +buffer_t httpsRequest(http_request_desc_t *req) { + HINTERNET internet = InternetOpen( + TEXT("COLLA"), + INTERNET_OPEN_TYPE_PRECONFIG, + NULL, + NULL, + 0 + ); + if (!internet) { + fatal("call to InternetOpen failed: %u", GetLastError()); + } + + http_url_t split = httpSplitUrl(req->url); + strview_t server = split.host; + strview_t page = split.uri; + + if (strvStartsWithView(server, strv("http://"))) { + server = strvRemovePrefix(server, 7); + } + + if (strvStartsWithView(server, strv("https://"))) { + server = strvRemovePrefix(server, 8); + } + + arena_t scratch = *req->arena; + const TCHAR *tserver = strvToTChar(&scratch, server); + const TCHAR *tpage = strvToTChar(&scratch, page); + + HINTERNET connection = InternetConnect( + internet, + tserver, + INTERNET_DEFAULT_HTTPS_PORT, + NULL, + NULL, + INTERNET_SERVICE_HTTP, + 0, + (DWORD_PTR)NULL // userdata + ); + if (!connection) { + fatal("call to InternetConnect failed: %u", GetLastError()); + } + + const TCHAR *accepted_types[] = { TEXT("*/*"), NULL }; + + HINTERNET request = HttpOpenRequest( + connection, + https__get_method_str(req->request_type), + tpage, + TEXT("HTTP/1.1"), + NULL, + accepted_types, + INTERNET_FLAG_SECURE, + (DWORD_PTR)NULL // userdata + ); + if (!request) { + fatal("call to HttpOpenRequest failed: %u", GetLastError()); + } + + outstream_t header = ostrInit(&scratch); + + for (int i = 0; i < req->header_count; ++i) { + http_header_t *h = &req->headers[i]; + ostrClear(&header); + ostrPrintf( + &header, + "%.*s: %.*s\r\n", + h->key.len, h->key.buf, + h->value.len, h->value.buf + ); + str_t header_str = ostrAsStr(&header); + HttpAddRequestHeadersA( + request, + header_str.buf, + (DWORD)header_str.len, + 0 + ); + } + + BOOL request_sent = HttpSendRequest( + request, + NULL, + 0, + (void *)req->body.buf, + (DWORD)req->body.len + ); + if (!request_sent) { + fatal("call to HttpSendRequest failed: %u", GetLastError()); + } + + outstream_t out = ostrInit(req->arena); + + while (true) { + DWORD bytes_read = 0; + char buffer[4096]; + BOOL read = InternetReadFile( + request, + buffer, + sizeof(buffer), + &bytes_read + ); + if (!read || bytes_read == 0) { + break; + } + ostrPuts(&out, strv(buffer, bytes_read)); + } + + InternetCloseHandle(request); + InternetCloseHandle(connection); + InternetCloseHandle(internet); + + str_t outstr = ostrAsStr(&out); + + return (buffer_t) { + .data = (uint8 *)outstr.buf, + .len = outstr.len + }; +} + +static const TCHAR *https__get_method_str(http_method_e method) { + switch (method) { + case HTTP_GET: return TEXT("GET"); + case HTTP_POST: return TEXT("POST"); + case HTTP_HEAD: return TEXT("HEAD"); + case HTTP_PUT: return TEXT("PUT"); + case HTTP_DELETE: return TEXT("DELETE"); + } + // default GET + return NULL; +} +#endif + +#include "warnings/colla_warn_end.h" diff --git a/colla/http.h b/http.h similarity index 96% rename from colla/http.h rename to http.h index 4c4d210..cfb65c2 100644 --- a/colla/http.h +++ b/http.h @@ -1,83 +1,83 @@ -#pragma once - -#include "collatypes.h" -#include "str.h" - -typedef struct arena_t arena_t; -typedef uintptr_t socket_t; - -typedef enum { - HTTP_GET, - HTTP_POST, - HTTP_HEAD, - HTTP_PUT, - HTTP_DELETE -} http_method_e; - -const char *httpGetStatusString(int status); - -typedef struct { - uint8 major; - uint8 minor; -} http_version_t; - -// translates a http_version_t to a single readable number (e.g. 1.1 -> 11, 1.0 -> 10, etc) -int httpVerNumber(http_version_t ver); - -typedef struct http_header_t { - strview_t key; - strview_t value; - struct http_header_t *next; -} http_header_t; - -typedef struct { - http_method_e method; - http_version_t version; - http_header_t *headers; - strview_t url; - strview_t body; -} http_req_t; - -typedef struct { - int status_code; - http_version_t version; - http_header_t *headers; - strview_t body; -} http_res_t; - -// strview_t request needs to be valid for http_req_t to be valid! -http_req_t httpParseReq(arena_t *arena, strview_t request); -http_res_t httpParseRes(arena_t *arena, strview_t response); - -str_t httpReqToStr(arena_t *arena, http_req_t *req); -str_t httpResToStr(arena_t *arena, http_res_t *res); - -bool httpHasHeader(http_header_t *headers, strview_t key); -void httpSetHeader(http_header_t *headers, strview_t key, strview_t value); -strview_t httpGetHeader(http_header_t *headers, strview_t key); - -str_t httpMakeUrlSafe(arena_t *arena, strview_t string); -str_t httpDecodeUrlSafe(arena_t *arena, strview_t string); - -typedef struct { - strview_t host; - strview_t uri; -} http_url_t; - -http_url_t httpSplitUrl(strview_t url); - -typedef struct { - arena_t *arena; - strview_t url; - http_method_e request_type; - http_header_t *headers; - int header_count; - strview_t body; -} http_request_desc_t; - -// arena_t *arena, strview_t url, [ http_header_t *headers, int header_count, strview_t body ] -#define httpGet(arena, url, ...) httpRequest(&(http_request_desc_t){ arena, url, .request_type = HTTP_GET, __VA_ARGS__ }) -#define httpsGet(arena, url, ...) httpsRequest(&(http_request_desc_t){ arena, url, .request_type = HTTP_GET, __VA_ARGS__ }) - -http_res_t httpRequest(http_request_desc_t *request); -buffer_t httpsRequest(http_request_desc_t *request); +#pragma once + +#include "collatypes.h" +#include "str.h" + +typedef struct arena_t arena_t; +typedef uintptr_t socket_t; + +typedef enum { + HTTP_GET, + HTTP_POST, + HTTP_HEAD, + HTTP_PUT, + HTTP_DELETE +} http_method_e; + +const char *httpGetStatusString(int status); + +typedef struct { + uint8 major; + uint8 minor; +} http_version_t; + +// translates a http_version_t to a single readable number (e.g. 1.1 -> 11, 1.0 -> 10, etc) +int httpVerNumber(http_version_t ver); + +typedef struct http_header_t { + strview_t key; + strview_t value; + struct http_header_t *next; +} http_header_t; + +typedef struct { + http_method_e method; + http_version_t version; + http_header_t *headers; + strview_t url; + strview_t body; +} http_req_t; + +typedef struct { + int status_code; + http_version_t version; + http_header_t *headers; + strview_t body; +} http_res_t; + +// strview_t request needs to be valid for http_req_t to be valid! +http_req_t httpParseReq(arena_t *arena, strview_t request); +http_res_t httpParseRes(arena_t *arena, strview_t response); + +str_t httpReqToStr(arena_t *arena, http_req_t *req); +str_t httpResToStr(arena_t *arena, http_res_t *res); + +bool httpHasHeader(http_header_t *headers, strview_t key); +void httpSetHeader(http_header_t *headers, strview_t key, strview_t value); +strview_t httpGetHeader(http_header_t *headers, strview_t key); + +str_t httpMakeUrlSafe(arena_t *arena, strview_t string); +str_t httpDecodeUrlSafe(arena_t *arena, strview_t string); + +typedef struct { + strview_t host; + strview_t uri; +} http_url_t; + +http_url_t httpSplitUrl(strview_t url); + +typedef struct { + arena_t *arena; + strview_t url; + http_method_e request_type; + http_header_t *headers; + int header_count; + strview_t body; +} http_request_desc_t; + +// arena_t *arena, strview_t url, [ http_header_t *headers, int header_count, strview_t body ] +#define httpGet(arena, url, ...) httpRequest(&(http_request_desc_t){ arena, url, .request_type = HTTP_GET, __VA_ARGS__ }) +#define httpsGet(arena, url, ...) httpsRequest(&(http_request_desc_t){ arena, url, .request_type = HTTP_GET, __VA_ARGS__ }) + +http_res_t httpRequest(http_request_desc_t *request); +buffer_t httpsRequest(http_request_desc_t *request); diff --git a/colla/ini.c b/ini.c similarity index 92% rename from colla/ini.c rename to ini.c index e0e7ecc..eb853f5 100644 --- a/colla/ini.c +++ b/ini.c @@ -1,279 +1,279 @@ -#include "ini.h" - -#include "warnings/colla_warn_beg.h" - -#include - -#include "strstream.h" - -static void ini__parse(arena_t *arena, ini_t *ini, const iniopts_t *options); - -ini_t iniParse(arena_t *arena, strview_t filename, const iniopts_t *options) { - file_t fp = fileOpen(*arena, filename, FILE_READ); - ini_t out = iniParseFile(arena, fp, options); - fileClose(fp); - return out; -} - -ini_t iniParseFile(arena_t *arena, file_t file, const iniopts_t *options) { - str_t data = fileReadWholeStrFP(arena, file); - return iniParseStr(arena, strv(data), options); -} - -ini_t iniParseStr(arena_t *arena, strview_t str, const iniopts_t *options) { - ini_t out = { - .text = str, - .tables = NULL, - }; - ini__parse(arena, &out, options); - return out; -} - -bool iniIsValid(ini_t *ctx) { - return ctx && !strvIsEmpty(ctx->text); -} - -initable_t *iniGetTable(ini_t *ctx, strview_t name) { - initable_t *t = ctx ? ctx->tables : NULL; - while (t) { - if (strvEquals(t->name, name)) { - return t; - } - t = t->next; - } - return NULL; -} - -inivalue_t *iniGet(initable_t *ctx, strview_t key) { - inivalue_t *v = ctx ? ctx->values : NULL; - while (v) { - if (strvEquals(v->key, key)) { - return v; - } - v = v->next; - } - return NULL; -} - -iniarray_t iniAsArr(arena_t *arena, inivalue_t *value, char delim) { - strview_t v = value ? value->value : (strview_t){0}; - if (!delim) delim = ' '; - - strview_t *beg = (strview_t *)arena->current; - usize count = 0; - - usize start = 0; - for (usize i = 0; i < v.len; ++i) { - if (v.buf[i] == delim) { - strview_t arrval = strvTrim(strvSub(v, start, i)); - if (!strvIsEmpty(arrval)) { - strview_t *newval = alloc(arena, strview_t); - *newval = arrval; - ++count; - } - start = i + 1; - } - } - - strview_t last = strvTrim(strvSub(v, start, SIZE_MAX)); - if (!strvIsEmpty(last)) { - strview_t *newval = alloc(arena, strview_t); - *newval = last; - ++count; - } - - return (iniarray_t){ - .values = beg, - .count = count, - }; -} - -uint64 iniAsUInt(inivalue_t *value) { - strview_t v = value ? value->value : (strview_t){0}; - instream_t in = istrInitLen(v.buf, v.len); - uint64 out = 0; - if (!istrGetU64(&in, &out)) { - out = 0; - } - return out; -} - -int64 iniAsInt(inivalue_t *value) { - strview_t v = value ? value->value : (strview_t){0}; - instream_t in = istrInitLen(v.buf, v.len); - int64 out = 0; - if (!istrGetI64(&in, &out)) { - out = 0; - } - return out; -} - -double iniAsNum(inivalue_t *value) { - strview_t v = value ? value->value : (strview_t){0}; - instream_t in = istrInitLen(v.buf, v.len); - double out = 0; - if (!istrGetDouble(&in, &out)) { - out = 0.0; - } - return out; -} - -bool iniAsBool(inivalue_t *value) { - strview_t v = value ? value->value : (strview_t){0}; - instream_t in = istrInitLen(v.buf, v.len); - bool out = 0; - if (!istrGetBool(&in, &out)) { - out = false; - } - return out; -} - -// == PRIVATE FUNCTIONS ============================================================================== - -#define INIPUSH(head, tail, val) \ - do { \ - if (!head) { \ - head = val; \ - tail = val; \ - } \ - else { \ - tail->next = val; \ - val = tail; \ - } \ - } while (0) - -static iniopts_t ini__get_options(const iniopts_t *options) { - iniopts_t out = { - .key_value_divider = '=', - }; - -#define SETOPT(v) out.v = options->v ? options->v : out.v - - if (options) { - SETOPT(key_value_divider); - SETOPT(merge_duplicate_keys); - SETOPT(merge_duplicate_tables); - } - -#undef SETOPT - - return out; -} - -static void ini__add_value(arena_t *arena, initable_t *table, instream_t *in, iniopts_t *opts) { - assert(table); - - strview_t key = strvTrim(istrGetView(in, opts->key_value_divider)); - istrSkip(in, 1); - strview_t value = strvTrim(istrGetViewEither(in, strv("\n#;"))); - istrSkip(in, 1); - inivalue_t *newval = NULL; - - - if (opts->merge_duplicate_keys) { - newval = table->values; - while (newval) { - if (strvEquals(newval->key, key)) { - break; - } - newval = newval->next; - } - } - - if (newval) { - newval->value = value; - } - else { - newval = alloc(arena, inivalue_t); - newval->key = key; - newval->value = value; - - if (!table->values) { - table->values = newval; - } - else { - table->tail->next = newval; - } - - table->tail = newval; - } -} - -static void ini__add_table(arena_t *arena, ini_t *ctx, instream_t *in, iniopts_t *options) { - istrSkip(in, 1); // skip [ - strview_t name = istrGetView(in, ']'); - istrSkip(in, 1); // skip ] - initable_t *table = NULL; - - if (options->merge_duplicate_tables) { - table = ctx->tables; - while (table) { - if (strvEquals(table->name, name)) { - break; - } - table = table->next; - } - } - - if (!table) { - table = alloc(arena, initable_t); - - table->name = name; - - if (!ctx->tables) { - ctx->tables = table; - } - else { - ctx->tail->next = table; - } - - ctx->tail = table; - } - - istrIgnoreAndSkip(in, '\n'); - while (!istrIsFinished(*in)) { - switch (istrPeek(in)) { - case '\n': // fallthrough - case '\r': - return; - case '#': // fallthrough - case ';': - istrIgnoreAndSkip(in, '\n'); - break; - default: - ini__add_value(arena, table, in, options); - break; - } - } -} - -static void ini__parse(arena_t *arena, ini_t *ini, const iniopts_t *options) { - iniopts_t opts = ini__get_options(options); - - initable_t *root = alloc(arena, initable_t); - root->name = INI_ROOT; - ini->tables = root; - ini->tail = root; - - instream_t in = istrInitLen(ini->text.buf, ini->text.len); - - while (!istrIsFinished(in)) { - istrSkipWhitespace(&in); - switch (istrPeek(&in)) { - case '[': - ini__add_table(arena, ini, &in, &opts); - break; - case '#': // fallthrough - case ';': - istrIgnoreAndSkip(&in, '\n'); - break; - default: - ini__add_value(arena, ini->tables, &in, &opts); - break; - } - } -} - -#undef INIPUSH - -#include "warnings/colla_warn_end.h" +#include "ini.h" + +#include "warnings/colla_warn_beg.h" + +#include + +#include "strstream.h" + +static void ini__parse(arena_t *arena, ini_t *ini, const iniopts_t *options); + +ini_t iniParse(arena_t *arena, strview_t filename, const iniopts_t *options) { + file_t fp = fileOpen(*arena, filename, FILE_READ); + ini_t out = iniParseFile(arena, fp, options); + fileClose(fp); + return out; +} + +ini_t iniParseFile(arena_t *arena, file_t file, const iniopts_t *options) { + str_t data = fileReadWholeStrFP(arena, file); + return iniParseStr(arena, strv(data), options); +} + +ini_t iniParseStr(arena_t *arena, strview_t str, const iniopts_t *options) { + ini_t out = { + .text = str, + .tables = NULL, + }; + ini__parse(arena, &out, options); + return out; +} + +bool iniIsValid(ini_t *ctx) { + return ctx && !strvIsEmpty(ctx->text); +} + +initable_t *iniGetTable(ini_t *ctx, strview_t name) { + initable_t *t = ctx ? ctx->tables : NULL; + while (t) { + if (strvEquals(t->name, name)) { + return t; + } + t = t->next; + } + return NULL; +} + +inivalue_t *iniGet(initable_t *ctx, strview_t key) { + inivalue_t *v = ctx ? ctx->values : NULL; + while (v) { + if (strvEquals(v->key, key)) { + return v; + } + v = v->next; + } + return NULL; +} + +iniarray_t iniAsArr(arena_t *arena, inivalue_t *value, char delim) { + strview_t v = value ? value->value : STRV_EMPTY; + if (!delim) delim = ' '; + + strview_t *beg = (strview_t *)arena->current; + usize count = 0; + + usize start = 0; + for (usize i = 0; i < v.len; ++i) { + if (v.buf[i] == delim) { + strview_t arrval = strvTrim(strvSub(v, start, i)); + if (!strvIsEmpty(arrval)) { + strview_t *newval = alloc(arena, strview_t); + *newval = arrval; + ++count; + } + start = i + 1; + } + } + + strview_t last = strvTrim(strvSub(v, start, SIZE_MAX)); + if (!strvIsEmpty(last)) { + strview_t *newval = alloc(arena, strview_t); + *newval = last; + ++count; + } + + return (iniarray_t){ + .values = beg, + .count = count, + }; +} + +uint64 iniAsUInt(inivalue_t *value) { + strview_t v = value ? value->value : STRV_EMPTY; + instream_t in = istrInitLen(v.buf, v.len); + uint64 out = 0; + if (!istrGetU64(&in, &out)) { + out = 0; + } + return out; +} + +int64 iniAsInt(inivalue_t *value) { + strview_t v = value ? value->value : STRV_EMPTY; + instream_t in = istrInitLen(v.buf, v.len); + int64 out = 0; + if (!istrGetI64(&in, &out)) { + out = 0; + } + return out; +} + +double iniAsNum(inivalue_t *value) { + strview_t v = value ? value->value : STRV_EMPTY; + instream_t in = istrInitLen(v.buf, v.len); + double out = 0; + if (!istrGetDouble(&in, &out)) { + out = 0.0; + } + return out; +} + +bool iniAsBool(inivalue_t *value) { + strview_t v = value ? value->value : STRV_EMPTY; + instream_t in = istrInitLen(v.buf, v.len); + bool out = 0; + if (!istrGetBool(&in, &out)) { + out = false; + } + return out; +} + +// == PRIVATE FUNCTIONS ============================================================================== + +#define INIPUSH(head, tail, val) \ + do { \ + if (!head) { \ + head = val; \ + tail = val; \ + } \ + else { \ + tail->next = val; \ + val = tail; \ + } \ + } while (0) + +static iniopts_t ini__get_options(const iniopts_t *options) { + iniopts_t out = { + .key_value_divider = '=', + }; + +#define SETOPT(v) out.v = options->v ? options->v : out.v + + if (options) { + SETOPT(key_value_divider); + SETOPT(merge_duplicate_keys); + SETOPT(merge_duplicate_tables); + } + +#undef SETOPT + + return out; +} + +static void ini__add_value(arena_t *arena, initable_t *table, instream_t *in, iniopts_t *opts) { + assert(table); + + strview_t key = strvTrim(istrGetView(in, opts->key_value_divider)); + istrSkip(in, 1); + strview_t value = strvTrim(istrGetViewEither(in, strv("\n#;"))); + istrSkip(in, 1); + inivalue_t *newval = NULL; + + + if (opts->merge_duplicate_keys) { + newval = table->values; + while (newval) { + if (strvEquals(newval->key, key)) { + break; + } + newval = newval->next; + } + } + + if (newval) { + newval->value = value; + } + else { + newval = alloc(arena, inivalue_t); + newval->key = key; + newval->value = value; + + if (!table->values) { + table->values = newval; + } + else { + table->tail->next = newval; + } + + table->tail = newval; + } +} + +static void ini__add_table(arena_t *arena, ini_t *ctx, instream_t *in, iniopts_t *options) { + istrSkip(in, 1); // skip [ + strview_t name = istrGetView(in, ']'); + istrSkip(in, 1); // skip ] + initable_t *table = NULL; + + if (options->merge_duplicate_tables) { + table = ctx->tables; + while (table) { + if (strvEquals(table->name, name)) { + break; + } + table = table->next; + } + } + + if (!table) { + table = alloc(arena, initable_t); + + table->name = name; + + if (!ctx->tables) { + ctx->tables = table; + } + else { + ctx->tail->next = table; + } + + ctx->tail = table; + } + + istrIgnoreAndSkip(in, '\n'); + while (!istrIsFinished(*in)) { + switch (istrPeek(in)) { + case '\n': // fallthrough + case '\r': + return; + case '#': // fallthrough + case ';': + istrIgnoreAndSkip(in, '\n'); + break; + default: + ini__add_value(arena, table, in, options); + break; + } + } +} + +static void ini__parse(arena_t *arena, ini_t *ini, const iniopts_t *options) { + iniopts_t opts = ini__get_options(options); + + initable_t *root = alloc(arena, initable_t); + root->name = INI_ROOT; + ini->tables = root; + ini->tail = root; + + instream_t in = istrInitLen(ini->text.buf, ini->text.len); + + while (!istrIsFinished(in)) { + istrSkipWhitespace(&in); + switch (istrPeek(&in)) { + case '[': + ini__add_table(arena, ini, &in, &opts); + break; + case '#': // fallthrough + case ';': + istrIgnoreAndSkip(&in, '\n'); + break; + default: + ini__add_value(arena, ini->tables, &in, &opts); + break; + } + } +} + +#undef INIPUSH + +#include "warnings/colla_warn_end.h" diff --git a/colla/ini.h b/ini.h similarity index 94% rename from colla/ini.h rename to ini.h index 47ce76d..359a7b6 100644 --- a/colla/ini.h +++ b/ini.h @@ -1,54 +1,54 @@ -#pragma once - -#include "collatypes.h" -#include "str.h" -#include "file.h" - -typedef struct arena_t arena_t; - -typedef struct inivalue_t { - strview_t key; - strview_t value; - struct inivalue_t *next; -} inivalue_t; - -typedef struct initable_t { - strview_t name; - inivalue_t *values; - inivalue_t *tail; - struct initable_t *next; -} initable_t; - -typedef struct { - strview_t text; - initable_t *tables; - initable_t *tail; -} ini_t; - -typedef struct { - bool merge_duplicate_tables; // default false - bool merge_duplicate_keys; // default false - char key_value_divider; // default = -} iniopts_t; - -ini_t iniParse(arena_t *arena, strview_t filename, const iniopts_t *options); -ini_t iniParseFile(arena_t *arena, file_t file, const iniopts_t *options); -ini_t iniParseStr(arena_t *arena, strview_t str, const iniopts_t *options); - -bool iniIsValid(ini_t *ctx); - -#define INI_ROOT strv("__ROOT__") - -initable_t *iniGetTable(ini_t *ctx, strview_t name); -inivalue_t *iniGet(initable_t *ctx, strview_t key); - -typedef struct { - strview_t *values; - usize count; -} iniarray_t; - -iniarray_t iniAsArr(arena_t *arena, inivalue_t *value, char delim); -uint64 iniAsUInt(inivalue_t *value); -int64 iniAsInt(inivalue_t *value); -double iniAsNum(inivalue_t *value); +#pragma once + +#include "collatypes.h" +#include "str.h" +#include "file.h" + +typedef struct arena_t arena_t; + +typedef struct inivalue_t { + strview_t key; + strview_t value; + struct inivalue_t *next; +} inivalue_t; + +typedef struct initable_t { + strview_t name; + inivalue_t *values; + inivalue_t *tail; + struct initable_t *next; +} initable_t; + +typedef struct ini_t { + strview_t text; + initable_t *tables; + initable_t *tail; +} ini_t; + +typedef struct { + bool merge_duplicate_tables; // default false + bool merge_duplicate_keys; // default false + char key_value_divider; // default = +} iniopts_t; + +ini_t iniParse(arena_t *arena, strview_t filename, const iniopts_t *options); +ini_t iniParseFile(arena_t *arena, file_t file, const iniopts_t *options); +ini_t iniParseStr(arena_t *arena, strview_t str, const iniopts_t *options); + +bool iniIsValid(ini_t *ctx); + +#define INI_ROOT strv("__ROOT__") + +initable_t *iniGetTable(ini_t *ctx, strview_t name); +inivalue_t *iniGet(initable_t *ctx, strview_t key); + +typedef struct { + strview_t *values; + usize count; +} iniarray_t; + +iniarray_t iniAsArr(arena_t *arena, inivalue_t *value, char delim); +uint64 iniAsUInt(inivalue_t *value); +int64 iniAsInt(inivalue_t *value); +double iniAsNum(inivalue_t *value); bool iniAsBool(inivalue_t *value); \ No newline at end of file diff --git a/colla/json.c b/json.c similarity index 99% rename from colla/json.c rename to json.c index acbceaa..84ec155 100644 --- a/colla/json.c +++ b/json.c @@ -153,10 +153,9 @@ static bool json__parse_string(arena_t *arena, instream_t *in, str_t *out) { goto fail; } -success: return true; fail: - *out = (str_t){0}; + *out = STR_EMPTY; return false; } diff --git a/colla/json.h b/json.h similarity index 100% rename from colla/json.h rename to json.h diff --git a/markdown.c b/markdown.c new file mode 100644 index 0000000..4704a5d --- /dev/null +++ b/markdown.c @@ -0,0 +1,503 @@ +#include "markdown.h" + +#include "arena.h" +#include "str.h" +#include "strstream.h" +#include "file.h" +#include "ini.h" +#include "tracelog.h" + +#ifndef MD_LIST_MAX_DEPTH + #define MD_LIST_MAX_DEPTH 8 +#endif + +typedef struct { + struct { + int indent; + int count; + bool list_is_ordered[MD_LIST_MAX_DEPTH]; + } list; + struct { + bool is_in_block; + strview_t lang; + } code; + bool is_bold; + bool is_italic; + bool is_in_paragraph; + strview_t raw_line; + md_options_t *options; + md_parser_t *curparser; +} markdown_ctx_t; + +static void markdown__parse_config(arena_t *arena, instream_t *in, ini_t *out); +static int markdown__count_chars(strview_t *line, char c); +static void markdown__parse_line(markdown_ctx_t *md, strview_t line, outstream_t *out, bool add_newline, bool is_line_start); +static strview_t markdown__parse_header(markdown_ctx_t *md, strview_t line, outstream_t *out); +static strview_t markdown__parse_ulist_or_line(markdown_ctx_t *md, strview_t line, outstream_t *out); +static strview_t markdown__parse_olist(markdown_ctx_t *md, strview_t line, outstream_t *out); +static strview_t markdown__parse_code_block(markdown_ctx_t *md, strview_t line, outstream_t *out); +static bool markdown__try_parse_url(instream_t *in, strview_t *out_url, strview_t *out_text); +static void markdown__empty_line(markdown_ctx_t *md, outstream_t *out); +static void markdown__close_list(markdown_ctx_t *md, outstream_t *out); +static void markdown__escape(strview_t view, outstream_t *out); + +str_t markdown(arena_t *arena, arena_t scratch, strview_t filename, md_options_t *options) { + str_t text = fileReadWholeStr(&scratch, filename); + return markdownStr(arena, strv(text), options); +} + +str_t markdownStr(arena_t *arena, strview_t markdown_str, md_options_t *options) { + instream_t in = istrInitLen(markdown_str.buf, markdown_str.len); + + markdown__parse_config(arena, &in, options ? options->out_config : NULL); + + outstream_t out = ostrInit(arena); + + markdown_ctx_t md = { + .list = { + .indent = -1, + }, + .options = options, + }; + + while (!istrIsFinished(in)) { + md.raw_line = istrGetLine(&in); + markdown__parse_line(&md, strvTrimLeft(md.raw_line), &out, true, true); + } + + markdown__empty_line(&md, &out); + + return ostrAsStr(&out); +} + +// == PRIVATE FUNCTIONS ================================================== + +static void markdown__parse_config(arena_t *arena, instream_t *in, ini_t *out) { + strview_t first_line = strvTrim(istrGetLine(in)); + if (!strvEquals(first_line, strv("---"))) { + return; + } + + strview_t ini_data = strvInitLen(in->cur, 0); + usize data_beg = istrTell(*in); + while (!istrIsFinished(*in)) { + strview_t line = istrGetViewEither(in, strv("\r\n")); + if (strvEquals(strvTrim(line), strv("---"))) { + break; + } + istrSkipWhitespace(in); + } + usize data_end = istrTell(*in); + ini_data.len = data_end - data_beg - 3; + + if (out) { + // allocate the string as ini_t only as a copy + str_t ini_str = str(arena, ini_data); + *out = iniParseStr(arena, strv(ini_str), NULL); + } +} + +static int markdown__count_chars(strview_t *line, char c) { + strview_t temp = *line; + int n = 0; + while (strvFront(temp) == c) { + n++; + temp = strvRemovePrefix(temp, 1); + } + + *line = temp; + return n; +} + +static strview_t markdown__parse_header(markdown_ctx_t* md, strview_t line, outstream_t *out) { + int n = markdown__count_chars(&line, '#'); + line = strvTrimLeft(line); + + ostrPrintf(out, "", n); + markdown__parse_line(md, line, out, false, false); + ostrPrintf(out, "", n); + + return STRV_EMPTY; +} + +static strview_t markdown__parse_ulist_or_line(markdown_ctx_t *md, strview_t line, outstream_t *out) { + // check if there is anything before this character, if there is + // it means we're in the middle of a line and we should ignore + strview_t prev = strvSub(md->raw_line, 0, line.buf - md->raw_line.buf); + int space_count; + for (space_count = 0; space_count < prev.len; ++space_count) { + if (prev.buf[space_count] != ' ') break; + } + + if (space_count < prev.len) { + return line; + } + + // if its only * or -, this is a list + if (line.len > 1 && line.buf[1] == ' ') { + strview_t raw_line = md->raw_line; + int cur_indent = markdown__count_chars(&raw_line, ' '); + // start of list + if (md->list.indent < cur_indent) { + if (md->list.count >= MD_LIST_MAX_DEPTH) { + fatal("markdown: too many list levels, max is %d, define MD_LIST_MAX_DEPTH to an higher number", MD_LIST_MAX_DEPTH); + } + md->list.list_is_ordered[md->list.count++] = false; + ostrPuts(out, strv("
\n")); + } + } + md->list.indent = -1; + + // close paragraph + if (md->is_in_paragraph) { + ostrPuts(out, strv("

\n")); + } + md->is_in_paragraph = false; +} + +static void markdown__close_list(markdown_ctx_t *md, outstream_t *out) { + if (md->list.count > 0) { + if (md->list.list_is_ordered[--md->list.count]) { + ostrPuts(out, strv("\n")); + } + else { + ostrPuts(out, strv("\n")); + } + } +} + +static void markdown__escape(strview_t view, outstream_t *out) { + for (usize i = 0; i < view.len; ++i) { + switch (view.buf[i]){ + case '&': + ostrPuts(out, strv("&")); + break; + case '<': + ostrPuts(out, strv("<")); + break; + case '>': + ostrPuts(out, strv(">")); + break; + default: + ostrPutc(out, view.buf[i]); + break; + } + } +} \ No newline at end of file diff --git a/markdown.h b/markdown.h new file mode 100644 index 0000000..a4184ca --- /dev/null +++ b/markdown.h @@ -0,0 +1,59 @@ +#pragma once + +#include "str.h" + +typedef struct outstream_t outstream_t; + +typedef struct { + strview_t lang; + void *userdata; + void (*init)(void *userdata); + void (*finish)(void *userdata); + void (*callback)(strview_t line, outstream_t *out, void *userdata); +} md_parser_t; + +typedef struct { + md_parser_t *parsers; + int parsers_count; + ini_t *out_config; +} md_options_t; + +typedef struct ini_t ini_t; + +str_t markdown(arena_t *arena, arena_t scratch, strview_t filename, md_options_t *options); +str_t markdownStr(arena_t *arena, strview_t markdown_str, md_options_t *options); + +/* +md-lite +a subset of markdown that can be parsed line by line +rules: + begin of file: + [ ] if there are three dashes (---), everythin until the next three dashes will be read as an ini config + + begin of line: + [x] n # -> + [x] *** or --- or ___ on their own line ->
+ [x] - or * -> unordered list + [x] n. -> ordered list + [x] ```xyz and newline -> code block of language (xyz is optional) + + mid of line: + [x] * -> italic + [x] ** -> bold + [x] *** -> bold and italic + [x] [text](link) -> link + [x] ![text](link) -> image + [x] ` -> code block until next backtick + + other: + [x] empty line ->

+ [x] \ -> escape character + + todo?: + [ ] two space at end of line or \ ->
+ [ ] indent inside list -> continue in point + [ ] 4 spaces -> line code block (does NOT work with multiline, use ``` instead) + [ ] -> link + [ ] [text](link "title") -> link + [ ] fix ***both*** +*/ \ No newline at end of file diff --git a/colla/server.c b/server.c similarity index 95% rename from colla/server.c rename to server.c index 3fa277c..5a57cc3 100644 --- a/colla/server.c +++ b/server.c @@ -72,6 +72,7 @@ typedef struct server_t { server__route_t *routes_default; socket_t current_client; bool should_stop; + uint16 port; } server_t; bool server__parse_chunk(arena_t *arena, server__req_ctx_t *ctx, char buffer[SERVER_BUFSZ], usize buflen) { @@ -275,7 +276,7 @@ void server__parse_req_url(arena_t *arena, server_req_t *req) { } } -server_t *serverSetup(arena_t *arena, uint16 port) { +server_t *serverSetup(arena_t *arena, uint16 port, bool try_next_port) { if (!skInit()) { fatal("couldn't initialise sockets: %s", skGetErrorString()); } @@ -285,14 +286,24 @@ server_t *serverSetup(arena_t *arena, uint16 port) { fatal("couldn't open socket: %s", skGetErrorString()); } - skaddrin_t addr = { - .sin_family = AF_INET, - .sin_addr.s_addr = INADDR_ANY, - .sin_port = htons(port), - }; + bool bound = false; - if (!skBindPro(sk, (skaddr_t *)&addr, sizeof(addr))) { - fatal("could not bind socket: %s", skGetErrorString()); + while (!bound) { + skaddrin_t addr = { + .sin_family = AF_INET, + .sin_addr.s_addr = INADDR_ANY, + .sin_port = htons(port), + }; + + bound = skBindPro(sk, (skaddr_t *)&addr, sizeof(addr)); + + if (!bound && try_next_port) { + port++; + } + } + + if (!bound) { + fatal("couldn't open socket: %s", skGetErrorString()); } if (!skListenPro(sk, 10)) { @@ -302,6 +313,7 @@ server_t *serverSetup(arena_t *arena, uint16 port) { server_t *server = alloc(arena, server_t); server->socket = sk; + server->port = port; return server; } @@ -347,8 +359,8 @@ void serverRouteDefault(arena_t *arena, server_t *server, server_route_f cb, voi void serverStart(arena_t scratch, server_t *server) { usize start = arenaTell(&scratch); - info("Server started!"); - + info("Server started at (http://localhost:%d)!", server->port); + while (!server->should_stop) { socket_t client = skAccept(server->socket); if (!skIsValid(client)) { diff --git a/colla/server.h b/server.h similarity index 93% rename from colla/server.h rename to server.h index 28aca5e..0cd59b1 100644 --- a/colla/server.h +++ b/server.h @@ -26,7 +26,7 @@ typedef struct { typedef str_t (*server_route_f)(arena_t scratch, server_t *server, server_req_t *req, void *userdata); -server_t *serverSetup(arena_t *arena, uint16 port); +server_t *serverSetup(arena_t *arena, uint16 port, bool try_next_port); void serverRoute(arena_t *arena, server_t *server, strview_t page, server_route_f cb, void *userdata); void serverRouteDefault(arena_t *arena, server_t *server, server_route_f cb, void *userdata); void serverStart(arena_t scratch, server_t *server); diff --git a/sha1.c b/sha1.c new file mode 100644 index 0000000..498759b --- /dev/null +++ b/sha1.c @@ -0,0 +1,120 @@ +#include "sha1.h" + +sha1_t sha1_init(void) { + return (sha1_t) { + .digest = { + 0x67452301, + 0xEFCDAB89, + 0x98BADCFE, + 0x10325476, + 0xC3D2E1F0, + }, + }; +} + +uint32 sha1_left_rotate(uint32 value, uint32 count) { + return (value << count) ^ (value >> (32 - count)); +} + +void sha1_process_block(sha1_t *ctx) { + uint32 w [80]; + for (usize i = 0; i < 16; ++i) { + w[i] = ctx->block[i * 4 + 0] << 24; + w[i] |= ctx->block[i * 4 + 1] << 16; + w[i] |= ctx->block[i * 4 + 2] << 8; + w[i] |= ctx->block[i * 4 + 3] << 0; + } + + for (usize i = 16; i < 80; ++i) { + w[i] = sha1_left_rotate(w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16], 1); + } + + uint32 a = ctx->digest[0]; + uint32 b = ctx->digest[1]; + uint32 c = ctx->digest[2]; + uint32 d = ctx->digest[3]; + uint32 e = ctx->digest[4]; + + for (usize i = 0; i < 80; ++i) { + uint32 f = 0; + uint32 k = 0; + + if (i<20) { + f = (b & c) | (~b & d); + k = 0x5A827999; + } else if (i<40) { + f = b ^ c ^ d; + k = 0x6ED9EBA1; + } else if (i<60) { + f = (b & c) | (b & d) | (c & d); + k = 0x8F1BBCDC; + } else { + f = b ^ c ^ d; + k = 0xCA62C1D6; + } + uint32 temp = sha1_left_rotate(a, 5) + f + e + k + w[i]; + e = d; + d = c; + c = sha1_left_rotate(b, 30); + b = a; + a = temp; + } + + ctx->digest[0] += a; + ctx->digest[1] += b; + ctx->digest[2] += c; + ctx->digest[3] += d; + ctx->digest[4] += e; +} + +void sha1_process_byte(sha1_t *ctx, uint8 b) { + ctx->block[ctx->block_index++] = b; + ++ctx->byte_count; + if (ctx->block_index == 64) { + ctx->block_index = 0; + sha1_process_block(ctx); + } +} + +sha1_result_t sha1(sha1_t *ctx, const void *buf, usize len) { + const uint8 *block = buf; + + for (usize i = 0; i < len; ++i) { + sha1_process_byte(ctx, block[i]); + } + + usize bitcount = ctx->byte_count * 8; + sha1_process_byte(ctx, 0x80); + + if (ctx->block_index > 56) { + while (ctx->block_index != 0) { + sha1_process_byte(ctx, 0); + } + while (ctx->block_index < 56) { + sha1_process_byte(ctx, 0); + } + } else { + while (ctx->block_index < 56) { + sha1_process_byte(ctx, 0); + } + } + sha1_process_byte(ctx, 0); + sha1_process_byte(ctx, 0); + sha1_process_byte(ctx, 0); + sha1_process_byte(ctx, 0); + sha1_process_byte(ctx, (uchar)((bitcount >> 24) & 0xFF)); + sha1_process_byte(ctx, (uchar)((bitcount >> 16) & 0xFF)); + sha1_process_byte(ctx, (uchar)((bitcount >> 8 ) & 0xFF)); + sha1_process_byte(ctx, (uchar)((bitcount >> 0 ) & 0xFF)); + + // memcpy(digest, m_digest, 5 * sizeof(uint32_t));# + + sha1_result_t result = {0}; + memcpy(result.digest, ctx->digest, sizeof(result.digest)); + return result; +} + +str_t sha1Str(arena_t *arena, sha1_t *ctx, const void *buf, usize len) { + sha1_result_t result = sha1(ctx, buf, len); + return strFmt(arena, "%08x%08x%08x%08x%08x", result.digest[0], result.digest[1], result.digest[2], result.digest[3], result.digest[4]); +} \ No newline at end of file diff --git a/sha1.h b/sha1.h new file mode 100644 index 0000000..b240f84 --- /dev/null +++ b/sha1.h @@ -0,0 +1,18 @@ +#pragma once + +#include "str.h" + +typedef struct { + uint32 digest[5]; + uint8 block[64]; + usize block_index; + usize byte_count; +} sha1_t; + +typedef struct { + uint32 digest[5]; +} sha1_result_t; + +sha1_t sha1_init(void); +sha1_result_t sha1(sha1_t *ctx, const void *buf, usize len); +str_t sha1Str(arena_t *arena, sha1_t *ctx, const void *buf, usize len); diff --git a/colla/socket.c b/socket.c similarity index 97% rename from colla/socket.c rename to socket.c index dd67242..2ac3e24 100644 --- a/colla/socket.c +++ b/socket.c @@ -1,286 +1,286 @@ -#include "socket.h" - -#if COLLA_WIN && COLLA_CMT_LIB -#pragma comment(lib, "Ws2_32") -#endif - -#if COLLA_WIN - -typedef int socklen_t; - -bool skInit(void) { - WSADATA w; - int error = WSAStartup(0x0202, &w); - return error == 0; -} - -bool skCleanup(void) { - return WSACleanup() == 0; -} - -bool skClose(socket_t sock) { - return closesocket(sock) != SOCKET_ERROR; -} - -int skPoll(skpoll_t *to_poll, int num_to_poll, int timeout) { - return WSAPoll(to_poll, num_to_poll, timeout); -} - -int skGetError(void) { - return WSAGetLastError(); -} - -#else - -#include -#include -#include -#include -#include // strerror -#include - -bool skInit(void) { - return true; -} - -bool skCleanup(void) { - return true; -} - -bool skClose(socket_t sock) { - return close(sock) != SOCKET_ERROR; -} - -int skPoll(skpoll_t *to_poll, int num_to_poll, int timeout) { - return poll(to_poll, num_to_poll, timeout); -} - -int skGetError(void) { - return errno; -} - -const char *skGetErrorString(void) { - return strerror(errno); -} - -#endif - -socket_t skOpen(sktype_e type) { - int sock_type = 0; - - switch(type) { - case SOCK_TCP: sock_type = SOCK_STREAM; break; - case SOCK_UDP: sock_type = SOCK_DGRAM; break; - default: fatal("skType not recognized: %d", type); break; - } - - return skOpenPro(AF_INET, sock_type, 0); -} - -socket_t skOpenEx(const char *protocol) { - struct protoent *proto = getprotobyname(protocol); - if(!proto) { - return (socket_t){0}; - } - return skOpenPro(AF_INET, SOCK_STREAM, proto->p_proto); -} - -socket_t skOpenPro(int af, int type, int protocol) { - return socket(af, type, protocol); -} - -bool skIsValid(socket_t sock) { - return sock != SOCKET_ERROR; -} - -skaddrin_t skAddrinInit(const char *ip, uint16_t port) { - return (skaddrin_t){ - .sin_family = AF_INET, - .sin_port = htons(port), - // TODO use inet_pton instead - .sin_addr.s_addr = inet_addr(ip), - }; -} - -bool skBind(socket_t sock, const char *ip, uint16_t port) { - skaddrin_t addr = skAddrinInit(ip, port); - return skBindPro(sock, (skaddr_t *)&addr, sizeof(addr)); -} - -bool skBindPro(socket_t sock, const skaddr_t *name, int namelen) { - return bind(sock, name, namelen) != SOCKET_ERROR; -} - -bool skListen(socket_t sock) { - return skListenPro(sock, 1); -} - -bool skListenPro(socket_t sock, int backlog) { - return listen(sock, backlog) != SOCKET_ERROR; -} - -socket_t skAccept(socket_t sock) { - skaddrin_t addr = {0}; - int addr_size = sizeof(addr); - return skAcceptPro(sock, (skaddr_t *)&addr, &addr_size); -} - -socket_t skAcceptPro(socket_t sock, skaddr_t *addr, int *addrlen) { - return accept(sock, addr, (socklen_t *)addrlen); -} - -bool skConnect(socket_t sock, const char *server, unsigned short server_port) { - // TODO use getaddrinfo insetad - struct hostent *host = gethostbyname(server); - // if gethostbyname fails, inet_addr will also fail and return an easier to debug error - const char *address = server; - if(host) { - address = inet_ntoa(*(struct in_addr*)host->h_addr_list[0]); - } - - skaddrin_t addr = skAddrinInit(address, server_port); - - return skConnectPro(sock, (skaddr_t *)&addr, sizeof(addr)); -} - -bool skConnectPro(socket_t sock, const skaddr_t *name, int namelen) { - return connect(sock, name, namelen) != SOCKET_ERROR; -} - -int skSend(socket_t sock, const void *buf, int len) { - return skSendPro(sock, buf, len, 0); -} - -int skSendPro(socket_t sock, const void *buf, int len, int flags) { - return send(sock, buf, len, flags); -} - -int skSendTo(socket_t sock, const void *buf, int len, const skaddrin_t *to) { - return skSendToPro(sock, buf, len, 0, (skaddr_t*)to, sizeof(skaddrin_t)); -} - -int skSendToPro(socket_t sock, const void *buf, int len, int flags, const skaddr_t *to, int tolen) { - return sendto(sock, buf, len, flags, to, tolen); -} - -int skReceive(socket_t sock, void *buf, int len) { - return skReceivePro(sock, buf, len, 0); -} - -int skReceivePro(socket_t sock, void *buf, int len, int flags) { - return recv(sock, buf, len, flags); -} - -int skReceiveFrom(socket_t sock, void *buf, int len, skaddrin_t *from) { - int fromlen = sizeof(skaddr_t); - return skReceiveFromPro(sock, buf, len, 0, (skaddr_t*)from, &fromlen); -} - -int skReceiveFromPro(socket_t sock, void *buf, int len, int flags, skaddr_t *from, int *fromlen) { - return recvfrom(sock, buf, len, flags, from, (socklen_t *)fromlen); -} - -// put this at the end of file to not make everything else unreadable -#if COLLA_WIN -const char *skGetErrorString(void) { - switch(skGetError()) { - case WSA_INVALID_HANDLE: return "Specified event object handle is invalid."; - case WSA_NOT_ENOUGH_MEMORY: return "Insufficient memory available."; - case WSA_INVALID_PARAMETER: return "One or more parameters are invalid."; - case WSA_OPERATION_ABORTED: return "Overlapped operation aborted."; - case WSA_IO_INCOMPLETE: return "Overlapped I/O event object not in signaled state."; - case WSA_IO_PENDING: return "Overlapped operations will complete later."; - case WSAEINTR: return "Interrupted function call."; - case WSAEBADF: return "File handle is not valid."; - case WSAEACCES: return "Permission denied."; - case WSAEFAULT: return "Bad address."; - case WSAEINVAL: return "Invalid argument."; - case WSAEMFILE: return "Too many open files."; - case WSAEWOULDBLOCK: return "Resource temporarily unavailable."; - case WSAEINPROGRESS: return "Operation now in progress."; - case WSAEALREADY: return "Operation already in progress."; - case WSAENOTSOCK: return "Socket operation on nonsocket."; - case WSAEDESTADDRREQ: return "Destination address required."; - case WSAEMSGSIZE: return "Message too long."; - case WSAEPROTOTYPE: return "Protocol wrong type for socket."; - case WSAENOPROTOOPT: return "Bad protocol option."; - case WSAEPROTONOSUPPORT: return "Protocol not supported."; - case WSAESOCKTNOSUPPORT: return "Socket type not supported."; - case WSAEOPNOTSUPP: return "Operation not supported."; - case WSAEPFNOSUPPORT: return "Protocol family not supported."; - case WSAEAFNOSUPPORT: return "Address family not supported by protocol family."; - case WSAEADDRINUSE: return "Address already in use."; - case WSAEADDRNOTAVAIL: return "Cannot assign requested address."; - case WSAENETDOWN: return "Network is down."; - case WSAENETUNREACH: return "Network is unreachable."; - case WSAENETRESET: return "Network dropped connection on reset."; - case WSAECONNABORTED: return "Software caused connection abort."; - case WSAECONNRESET: return "Connection reset by peer."; - case WSAENOBUFS: return "No buffer space available."; - case WSAEISCONN: return "Socket is already connected."; - case WSAENOTCONN: return "Socket is not connected."; - case WSAESHUTDOWN: return "Cannot send after socket shutdown."; - case WSAETOOMANYREFS: return "Too many references."; - case WSAETIMEDOUT: return "Connection timed out."; - case WSAECONNREFUSED: return "Connection refused."; - case WSAELOOP: return "Cannot translate name."; - case WSAENAMETOOLONG: return "Name too long."; - case WSAEHOSTDOWN: return "Host is down."; - case WSAEHOSTUNREACH: return "No route to host."; - case WSAENOTEMPTY: return "Directory not empty."; - case WSAEPROCLIM: return "Too many processes."; - case WSAEUSERS: return "User quota exceeded."; - case WSAEDQUOT: return "Disk quota exceeded."; - case WSAESTALE: return "Stale file handle reference."; - case WSAEREMOTE: return "Item is remote."; - case WSASYSNOTREADY: return "Network subsystem is unavailable."; - case WSAVERNOTSUPPORTED: return "Winsock.dll version out of range."; - case WSANOTINITIALISED: return "Successful WSAStartup not yet performed."; - case WSAEDISCON: return "Graceful shutdown in progress."; - case WSAENOMORE: return "No more results."; - case WSAECANCELLED: return "Call has been canceled."; - case WSAEINVALIDPROCTABLE: return "Procedure call table is invalid."; - case WSAEINVALIDPROVIDER: return "Service provider is invalid."; - case WSAEPROVIDERFAILEDINIT: return "Service provider failed to initialize."; - case WSASYSCALLFAILURE: return "System call failure."; - case WSASERVICE_NOT_FOUND: return "Service not found."; - case WSATYPE_NOT_FOUND: return "Class type not found."; - case WSA_E_NO_MORE: return "No more results."; - case WSA_E_CANCELLED: return "Call was canceled."; - case WSAEREFUSED: return "Database query was refused."; - case WSAHOST_NOT_FOUND: return "Host not found."; - case WSATRY_AGAIN: return "Nonauthoritative host not found."; - case WSANO_RECOVERY: return "This is a nonrecoverable error."; - case WSANO_DATA: return "Valid name, no data record of requested type."; - case WSA_QOS_RECEIVERS: return "QoS receivers."; - case WSA_QOS_SENDERS: return "QoS senders."; - case WSA_QOS_NO_SENDERS: return "No QoS senders."; - case WSA_QOS_NO_RECEIVERS: return "QoS no receivers."; - case WSA_QOS_REQUEST_CONFIRMED: return "QoS request confirmed."; - case WSA_QOS_ADMISSION_FAILURE: return "QoS admission error."; - case WSA_QOS_POLICY_FAILURE: return "QoS policy failure."; - case WSA_QOS_BAD_STYLE: return "QoS bad style."; - case WSA_QOS_BAD_OBJECT: return "QoS bad object."; - case WSA_QOS_TRAFFIC_CTRL_ERROR: return "QoS traffic control error."; - case WSA_QOS_GENERIC_ERROR: return "QoS generic error."; - case WSA_QOS_ESERVICETYPE: return "QoS service type error."; - case WSA_QOS_EFLOWSPEC: return "QoS flowspec error."; - case WSA_QOS_EPROVSPECBUF: return "Invalid QoS provider buffer."; - case WSA_QOS_EFILTERSTYLE: return "Invalid QoS filter style."; - case WSA_QOS_EFILTERTYPE: return "Invalid QoS filter type."; - case WSA_QOS_EFILTERCOUNT: return "Incorrect QoS filter count."; - case WSA_QOS_EOBJLENGTH: return "Invalid QoS object length."; - case WSA_QOS_EFLOWCOUNT: return "Incorrect QoS flow count."; - case WSA_QOS_EUNKOWNPSOBJ: return "Unrecognized QoS object."; - case WSA_QOS_EPOLICYOBJ: return "Invalid QoS policy object."; - case WSA_QOS_EFLOWDESC: return "Invalid QoS flow descriptor."; - case WSA_QOS_EPSFLOWSPEC: return "Invalid QoS provider-specific flowspec."; - case WSA_QOS_EPSFILTERSPEC: return "Invalid QoS provider-specific filterspec."; - case WSA_QOS_ESDMODEOBJ: return "Invalid QoS shape discard mode object."; - case WSA_QOS_ESHAPERATEOBJ: return "Invalid QoS shaping rate object."; - case WSA_QOS_RESERVED_PETYPE: return "Reserved policy QoS element type."; - } - - return "(nothing)"; -} +#include "socket.h" + +#if COLLA_WIN && COLLA_CMT_LIB +#pragma comment(lib, "Ws2_32") +#endif + +#if COLLA_WIN + +typedef int socklen_t; + +bool skInit(void) { + WSADATA w; + int error = WSAStartup(0x0202, &w); + return error == 0; +} + +bool skCleanup(void) { + return WSACleanup() == 0; +} + +bool skClose(socket_t sock) { + return closesocket(sock) != SOCKET_ERROR; +} + +int skPoll(skpoll_t *to_poll, int num_to_poll, int timeout) { + return WSAPoll(to_poll, num_to_poll, timeout); +} + +int skGetError(void) { + return WSAGetLastError(); +} + +#else + +#include +#include +#include +#include +#include // strerror +#include + +bool skInit(void) { + return true; +} + +bool skCleanup(void) { + return true; +} + +bool skClose(socket_t sock) { + return close(sock) != SOCKET_ERROR; +} + +int skPoll(skpoll_t *to_poll, int num_to_poll, int timeout) { + return poll(to_poll, num_to_poll, timeout); +} + +int skGetError(void) { + return errno; +} + +const char *skGetErrorString(void) { + return strerror(errno); +} + +#endif + +socket_t skOpen(sktype_e type) { + int sock_type = 0; + + switch(type) { + case SOCK_TCP: sock_type = SOCK_STREAM; break; + case SOCK_UDP: sock_type = SOCK_DGRAM; break; + default: fatal("skType not recognized: %d", type); break; + } + + return skOpenPro(AF_INET, sock_type, 0); +} + +socket_t skOpenEx(const char *protocol) { + struct protoent *proto = getprotobyname(protocol); + if(!proto) { + return (socket_t){0}; + } + return skOpenPro(AF_INET, SOCK_STREAM, proto->p_proto); +} + +socket_t skOpenPro(int af, int type, int protocol) { + return socket(af, type, protocol); +} + +bool skIsValid(socket_t sock) { + return sock != SOCKET_ERROR; +} + +skaddrin_t skAddrinInit(const char *ip, uint16_t port) { + return (skaddrin_t){ + .sin_family = AF_INET, + .sin_port = htons(port), + // TODO use inet_pton instead + .sin_addr.s_addr = inet_addr(ip), + }; +} + +bool skBind(socket_t sock, const char *ip, uint16_t port) { + skaddrin_t addr = skAddrinInit(ip, port); + return skBindPro(sock, (skaddr_t *)&addr, sizeof(addr)); +} + +bool skBindPro(socket_t sock, const skaddr_t *name, int namelen) { + return bind(sock, name, namelen) != SOCKET_ERROR; +} + +bool skListen(socket_t sock) { + return skListenPro(sock, 1); +} + +bool skListenPro(socket_t sock, int backlog) { + return listen(sock, backlog) != SOCKET_ERROR; +} + +socket_t skAccept(socket_t sock) { + skaddrin_t addr = {0}; + int addr_size = sizeof(addr); + return skAcceptPro(sock, (skaddr_t *)&addr, &addr_size); +} + +socket_t skAcceptPro(socket_t sock, skaddr_t *addr, int *addrlen) { + return accept(sock, addr, (socklen_t *)addrlen); +} + +bool skConnect(socket_t sock, const char *server, unsigned short server_port) { + // TODO use getaddrinfo insetad + struct hostent *host = gethostbyname(server); + // if gethostbyname fails, inet_addr will also fail and return an easier to debug error + const char *address = server; + if(host) { + address = inet_ntoa(*(struct in_addr*)host->h_addr_list[0]); + } + + skaddrin_t addr = skAddrinInit(address, server_port); + + return skConnectPro(sock, (skaddr_t *)&addr, sizeof(addr)); +} + +bool skConnectPro(socket_t sock, const skaddr_t *name, int namelen) { + return connect(sock, name, namelen) != SOCKET_ERROR; +} + +int skSend(socket_t sock, const void *buf, int len) { + return skSendPro(sock, buf, len, 0); +} + +int skSendPro(socket_t sock, const void *buf, int len, int flags) { + return send(sock, buf, len, flags); +} + +int skSendTo(socket_t sock, const void *buf, int len, const skaddrin_t *to) { + return skSendToPro(sock, buf, len, 0, (skaddr_t*)to, sizeof(skaddrin_t)); +} + +int skSendToPro(socket_t sock, const void *buf, int len, int flags, const skaddr_t *to, int tolen) { + return sendto(sock, buf, len, flags, to, tolen); +} + +int skReceive(socket_t sock, void *buf, int len) { + return skReceivePro(sock, buf, len, 0); +} + +int skReceivePro(socket_t sock, void *buf, int len, int flags) { + return recv(sock, buf, len, flags); +} + +int skReceiveFrom(socket_t sock, void *buf, int len, skaddrin_t *from) { + int fromlen = sizeof(skaddr_t); + return skReceiveFromPro(sock, buf, len, 0, (skaddr_t*)from, &fromlen); +} + +int skReceiveFromPro(socket_t sock, void *buf, int len, int flags, skaddr_t *from, int *fromlen) { + return recvfrom(sock, buf, len, flags, from, (socklen_t *)fromlen); +} + +// put this at the end of file to not make everything else unreadable +#if COLLA_WIN +const char *skGetErrorString(void) { + switch(skGetError()) { + case WSA_INVALID_HANDLE: return "Specified event object handle is invalid."; + case WSA_NOT_ENOUGH_MEMORY: return "Insufficient memory available."; + case WSA_INVALID_PARAMETER: return "One or more parameters are invalid."; + case WSA_OPERATION_ABORTED: return "Overlapped operation aborted."; + case WSA_IO_INCOMPLETE: return "Overlapped I/O event object not in signaled state."; + case WSA_IO_PENDING: return "Overlapped operations will complete later."; + case WSAEINTR: return "Interrupted function call."; + case WSAEBADF: return "File handle is not valid."; + case WSAEACCES: return "Permission denied."; + case WSAEFAULT: return "Bad address."; + case WSAEINVAL: return "Invalid argument."; + case WSAEMFILE: return "Too many open files."; + case WSAEWOULDBLOCK: return "Resource temporarily unavailable."; + case WSAEINPROGRESS: return "Operation now in progress."; + case WSAEALREADY: return "Operation already in progress."; + case WSAENOTSOCK: return "Socket operation on nonsocket."; + case WSAEDESTADDRREQ: return "Destination address required."; + case WSAEMSGSIZE: return "Message too long."; + case WSAEPROTOTYPE: return "Protocol wrong type for socket."; + case WSAENOPROTOOPT: return "Bad protocol option."; + case WSAEPROTONOSUPPORT: return "Protocol not supported."; + case WSAESOCKTNOSUPPORT: return "Socket type not supported."; + case WSAEOPNOTSUPP: return "Operation not supported."; + case WSAEPFNOSUPPORT: return "Protocol family not supported."; + case WSAEAFNOSUPPORT: return "Address family not supported by protocol family."; + case WSAEADDRINUSE: return "Address already in use."; + case WSAEADDRNOTAVAIL: return "Cannot assign requested address."; + case WSAENETDOWN: return "Network is down."; + case WSAENETUNREACH: return "Network is unreachable."; + case WSAENETRESET: return "Network dropped connection on reset."; + case WSAECONNABORTED: return "Software caused connection abort."; + case WSAECONNRESET: return "Connection reset by peer."; + case WSAENOBUFS: return "No buffer space available."; + case WSAEISCONN: return "Socket is already connected."; + case WSAENOTCONN: return "Socket is not connected."; + case WSAESHUTDOWN: return "Cannot send after socket shutdown."; + case WSAETOOMANYREFS: return "Too many references."; + case WSAETIMEDOUT: return "Connection timed out."; + case WSAECONNREFUSED: return "Connection refused."; + case WSAELOOP: return "Cannot translate name."; + case WSAENAMETOOLONG: return "Name too long."; + case WSAEHOSTDOWN: return "Host is down."; + case WSAEHOSTUNREACH: return "No route to host."; + case WSAENOTEMPTY: return "Directory not empty."; + case WSAEPROCLIM: return "Too many processes."; + case WSAEUSERS: return "User quota exceeded."; + case WSAEDQUOT: return "Disk quota exceeded."; + case WSAESTALE: return "Stale file handle reference."; + case WSAEREMOTE: return "Item is remote."; + case WSASYSNOTREADY: return "Network subsystem is unavailable."; + case WSAVERNOTSUPPORTED: return "Winsock.dll version out of range."; + case WSANOTINITIALISED: return "Successful WSAStartup not yet performed."; + case WSAEDISCON: return "Graceful shutdown in progress."; + case WSAENOMORE: return "No more results."; + case WSAECANCELLED: return "Call has been canceled."; + case WSAEINVALIDPROCTABLE: return "Procedure call table is invalid."; + case WSAEINVALIDPROVIDER: return "Service provider is invalid."; + case WSAEPROVIDERFAILEDINIT: return "Service provider failed to initialize."; + case WSASYSCALLFAILURE: return "System call failure."; + case WSASERVICE_NOT_FOUND: return "Service not found."; + case WSATYPE_NOT_FOUND: return "Class type not found."; + case WSA_E_NO_MORE: return "No more results."; + case WSA_E_CANCELLED: return "Call was canceled."; + case WSAEREFUSED: return "Database query was refused."; + case WSAHOST_NOT_FOUND: return "Host not found."; + case WSATRY_AGAIN: return "Nonauthoritative host not found."; + case WSANO_RECOVERY: return "This is a nonrecoverable error."; + case WSANO_DATA: return "Valid name, no data record of requested type."; + case WSA_QOS_RECEIVERS: return "QoS receivers."; + case WSA_QOS_SENDERS: return "QoS senders."; + case WSA_QOS_NO_SENDERS: return "No QoS senders."; + case WSA_QOS_NO_RECEIVERS: return "QoS no receivers."; + case WSA_QOS_REQUEST_CONFIRMED: return "QoS request confirmed."; + case WSA_QOS_ADMISSION_FAILURE: return "QoS admission error."; + case WSA_QOS_POLICY_FAILURE: return "QoS policy failure."; + case WSA_QOS_BAD_STYLE: return "QoS bad style."; + case WSA_QOS_BAD_OBJECT: return "QoS bad object."; + case WSA_QOS_TRAFFIC_CTRL_ERROR: return "QoS traffic control error."; + case WSA_QOS_GENERIC_ERROR: return "QoS generic error."; + case WSA_QOS_ESERVICETYPE: return "QoS service type error."; + case WSA_QOS_EFLOWSPEC: return "QoS flowspec error."; + case WSA_QOS_EPROVSPECBUF: return "Invalid QoS provider buffer."; + case WSA_QOS_EFILTERSTYLE: return "Invalid QoS filter style."; + case WSA_QOS_EFILTERTYPE: return "Invalid QoS filter type."; + case WSA_QOS_EFILTERCOUNT: return "Incorrect QoS filter count."; + case WSA_QOS_EOBJLENGTH: return "Invalid QoS object length."; + case WSA_QOS_EFLOWCOUNT: return "Incorrect QoS flow count."; + case WSA_QOS_EUNKOWNPSOBJ: return "Unrecognized QoS object."; + case WSA_QOS_EPOLICYOBJ: return "Invalid QoS policy object."; + case WSA_QOS_EFLOWDESC: return "Invalid QoS flow descriptor."; + case WSA_QOS_EPSFLOWSPEC: return "Invalid QoS provider-specific flowspec."; + case WSA_QOS_EPSFILTERSPEC: return "Invalid QoS provider-specific filterspec."; + case WSA_QOS_ESDMODEOBJ: return "Invalid QoS shape discard mode object."; + case WSA_QOS_ESHAPERATEOBJ: return "Invalid QoS shaping rate object."; + case WSA_QOS_RESERVED_PETYPE: return "Reserved policy QoS element type."; + } + + return "(nothing)"; +} #endif \ No newline at end of file diff --git a/colla/socket.h b/socket.h similarity index 97% rename from colla/socket.h rename to socket.h index 5681629..024685c 100644 --- a/colla/socket.h +++ b/socket.h @@ -1,93 +1,93 @@ -#pragma once - -#include "collatypes.h" - -#if COLLA_TCC -#include "tcc/colla_tcc.h" -#elif COLLA_WIN -#define _WINSOCK_DEPRECATED_NO_WARNINGS -#include -#elif COLLA_LIN || COLLA_OSX -#include -#include -#endif - -typedef uintptr_t socket_t; -typedef struct sockaddr skaddr_t; -typedef struct sockaddr_in skaddrin_t; -typedef struct pollfd skpoll_t; - -#define SOCKET_ERROR (-1) - -typedef enum { - SOCK_TCP, - SOCK_UDP, -} sktype_e; - -// Initialize sockets, returns true on success -bool skInit(void); -// Terminates sockets, returns true on success -bool skCleanup(void); - -// Opens a socket, check socket_t with skValid -socket_t skOpen(sktype_e type); -// Opens a socket using 'protocol', options are -// ip, icmp, ggp, tcp, egp, pup, udp, hmp, xns-idp, rdp -// check socket_t with skValid -socket_t skOpenEx(const char *protocol); -// Opens a socket, check socket_t with skValid -socket_t skOpenPro(int af, int type, int protocol); - -// Checks that a opened socket is valid, returns true on success -bool skIsValid(socket_t sock); - -// Closes a socket, returns true on success -bool skClose(socket_t sock); - -// Fill out a sk_addrin_t structure with "ip" and "port" -skaddrin_t skAddrinInit(const char *ip, uint16_t port); - -// Associate a local address with a socket -bool skBind(socket_t sock, const char *ip, uint16_t port); -// Associate a local address with a socket -bool skBindPro(socket_t sock, const skaddr_t *name, int namelen); - -// Place a socket in a state in which it is listening for an incoming connection -bool skListen(socket_t sock); -// Place a socket in a state in which it is listening for an incoming connection -bool skListenPro(socket_t sock, int backlog); - -// Permits an incoming connection attempt on a socket -socket_t skAccept(socket_t sock); -// Permits an incoming connection attempt on a socket -socket_t skAcceptPro(socket_t sock, skaddr_t *addr, int *addrlen); - -// Connects to a server (e.g. "127.0.0.1" or "google.com") with a port(e.g. 1234), returns true on success -bool skConnect(socket_t sock, const char *server, unsigned short server_port); -// Connects to a server, returns true on success -bool skConnectPro(socket_t sock, const skaddr_t *name, int namelen); - -// Sends data on a socket, returns true on success -int skSend(socket_t sock, const void *buf, int len); -// Sends data on a socket, returns true on success -int skSendPro(socket_t sock, const void *buf, int len, int flags); -// Sends data to a specific destination -int skSendTo(socket_t sock, const void *buf, int len, const skaddrin_t *to); -// Sends data to a specific destination -int skSendToPro(socket_t sock, const void *buf, int len, int flags, const skaddr_t *to, int tolen); -// Receives data from a socket, returns byte count on success, 0 on connection close or -1 on error -int skReceive(socket_t sock, void *buf, int len); -// Receives data from a socket, returns byte count on success, 0 on connection close or -1 on error -int skReceivePro(socket_t sock, void *buf, int len, int flags); -// Receives a datagram and stores the source address. -int skReceiveFrom(socket_t sock, void *buf, int len, skaddrin_t *from); -// Receives a datagram and stores the source address. -int skReceiveFromPro(socket_t sock, void *buf, int len, int flags, skaddr_t *from, int *fromlen); - -// Wait for an event on some sockets -int skPoll(skpoll_t *to_poll, int num_to_poll, int timeout); - -// Returns latest socket error, returns 0 if there is no error -int skGetError(void); -// Returns a human-readable string from a skGetError -const char *skGetErrorString(void); +#pragma once + +#include "collatypes.h" + +#if COLLA_TCC +#include "tcc/colla_tcc.h" +#elif COLLA_WIN +#define _WINSOCK_DEPRECATED_NO_WARNINGS +#include +#elif COLLA_LIN || COLLA_OSX +#include +#include +#endif + +typedef uintptr_t socket_t; +typedef struct sockaddr skaddr_t; +typedef struct sockaddr_in skaddrin_t; +typedef struct pollfd skpoll_t; + +#define SOCKET_ERROR (-1) + +typedef enum { + SOCK_TCP, + SOCK_UDP, +} sktype_e; + +// Initialize sockets, returns true on success +bool skInit(void); +// Terminates sockets, returns true on success +bool skCleanup(void); + +// Opens a socket, check socket_t with skValid +socket_t skOpen(sktype_e type); +// Opens a socket using 'protocol', options are +// ip, icmp, ggp, tcp, egp, pup, udp, hmp, xns-idp, rdp +// check socket_t with skValid +socket_t skOpenEx(const char *protocol); +// Opens a socket, check socket_t with skValid +socket_t skOpenPro(int af, int type, int protocol); + +// Checks that a opened socket is valid, returns true on success +bool skIsValid(socket_t sock); + +// Closes a socket, returns true on success +bool skClose(socket_t sock); + +// Fill out a sk_addrin_t structure with "ip" and "port" +skaddrin_t skAddrinInit(const char *ip, uint16_t port); + +// Associate a local address with a socket +bool skBind(socket_t sock, const char *ip, uint16_t port); +// Associate a local address with a socket +bool skBindPro(socket_t sock, const skaddr_t *name, int namelen); + +// Place a socket in a state in which it is listening for an incoming connection +bool skListen(socket_t sock); +// Place a socket in a state in which it is listening for an incoming connection +bool skListenPro(socket_t sock, int backlog); + +// Permits an incoming connection attempt on a socket +socket_t skAccept(socket_t sock); +// Permits an incoming connection attempt on a socket +socket_t skAcceptPro(socket_t sock, skaddr_t *addr, int *addrlen); + +// Connects to a server (e.g. "127.0.0.1" or "google.com") with a port(e.g. 1234), returns true on success +bool skConnect(socket_t sock, const char *server, unsigned short server_port); +// Connects to a server, returns true on success +bool skConnectPro(socket_t sock, const skaddr_t *name, int namelen); + +// Sends data on a socket, returns true on success +int skSend(socket_t sock, const void *buf, int len); +// Sends data on a socket, returns true on success +int skSendPro(socket_t sock, const void *buf, int len, int flags); +// Sends data to a specific destination +int skSendTo(socket_t sock, const void *buf, int len, const skaddrin_t *to); +// Sends data to a specific destination +int skSendToPro(socket_t sock, const void *buf, int len, int flags, const skaddr_t *to, int tolen); +// Receives data from a socket, returns byte count on success, 0 on connection close or -1 on error +int skReceive(socket_t sock, void *buf, int len); +// Receives data from a socket, returns byte count on success, 0 on connection close or -1 on error +int skReceivePro(socket_t sock, void *buf, int len, int flags); +// Receives a datagram and stores the source address. +int skReceiveFrom(socket_t sock, void *buf, int len, skaddrin_t *from); +// Receives a datagram and stores the source address. +int skReceiveFromPro(socket_t sock, void *buf, int len, int flags, skaddr_t *from, int *fromlen); + +// Wait for an event on some sockets +int skPoll(skpoll_t *to_poll, int num_to_poll, int timeout); + +// Returns latest socket error, returns 0 if there is no error +int skGetError(void); +// Returns a human-readable string from a skGetError +const char *skGetErrorString(void); diff --git a/colla/stb/stb_sprintf.h b/stb/stb_sprintf.h similarity index 99% rename from colla/stb/stb_sprintf.h rename to stb/stb_sprintf.h index 9711697..60a81b2 100644 --- a/colla/stb/stb_sprintf.h +++ b/stb/stb_sprintf.h @@ -219,6 +219,17 @@ STBSP__PUBLICDEC void STB_SPRINTF_DECORATE(set_separators)(char comma, char peri #ifdef STB_SPRINTF_IMPLEMENTATION +#if COLLA_CLANG + +#pragma clang diagnostic push + +#pragma clang diagnostic ignored "-Wextra-semi-stmt" +#pragma clang diagnostic ignored "-Wconditional-uninitialized" +#pragma clang diagnostic ignored "-Wcast-qual" +#pragma clang diagnostic ignored "-Wimplicit-fallthrough" + +#endif + #define stbsp__uint32 unsigned int #define stbsp__int32 signed int @@ -1885,6 +1896,10 @@ static stbsp__int32 stbsp__real_to_str(char const **start, stbsp__uint32 *len, c #undef stbsp__int64 #undef STBSP__UNALIGNED +#if COLLA_CLANG +#pragma clang diagnostic pop +#endif + #endif // STB_SPRINTF_IMPLEMENTATION /* diff --git a/colla/str.c b/str.c similarity index 91% rename from colla/str.c rename to str.c index 68909c1..3ee1018 100644 --- a/colla/str.c +++ b/str.c @@ -1,399 +1,408 @@ -#include "str.h" - -#include "warnings/colla_warn_beg.h" - -#include "arena.h" -#include "format.h" -#include "tracelog.h" - -#if COLLA_WIN - -#define WIN32_LEAN_AND_MEAN -#include - -#else - -#include - -#endif - -#if COLLA_TCC -#include "tcc/colla_tcc.h" -#endif - -// == STR_T ======================================================== - -str_t strInit(arena_t *arena, const char *buf) { - return buf ? strInitLen(arena, buf, strlen(buf)) : (str_t){0}; -} - -str_t strInitLen(arena_t *arena, const char *buf, usize len) { - if (!buf || !len) return (str_t){0}; - - str_t out = { - .buf = alloc(arena, char, len + 1), - .len = len - }; - - memcpy(out.buf, buf, len); - - return out; -} - -str_t strInitView(arena_t *arena, strview_t view) { - return strInitLen(arena, view.buf, view.len); -} - -str_t strFmt(arena_t *arena, const char *fmt, ...) { - va_list args; - va_start(args, fmt); - str_t out = strFmtv(arena, fmt, args); - va_end(args); - return out; -} - -str_t strFmtv(arena_t *arena, const char *fmt, va_list args) { - va_list vcopy; - va_copy(vcopy, args); - // stb_vsnprintf returns the length + null_term - int len = fmtBufferv(NULL, 0, fmt, vcopy); - va_end(vcopy); - - char *buffer = alloc(arena, char, len + 1); - fmtBufferv(buffer, len + 1, fmt, args); - - return (str_t) { .buf = buffer, .len = (usize)len }; -} - -str_t strFromWChar(arena_t *arena, const wchar_t *src, usize srclen) { - if (!src) return (str_t){0}; - if (!srclen) srclen = wcslen(src); - - str_t out = {0}; - -#if COLLA_WIN - int outlen = WideCharToMultiByte( - CP_UTF8, 0, - src, (int)srclen, - NULL, 0, - NULL, NULL - ); - - if (outlen == 0) { - unsigned long error = GetLastError(); - if (error == ERROR_NO_UNICODE_TRANSLATION) { - err("couldn't translate wide string (%S) to utf8, no unicode translation", src); - } - else { - err("couldn't translate wide string (%S) to utf8, %u", error); - } - - return (str_t){0}; - } - - out.buf = alloc(arena, char, outlen + 1); - WideCharToMultiByte( - CP_UTF8, 0, - src, (int)srclen, - out.buf, outlen, - NULL, NULL - ); - - out.len = outlen; - -#elif COLLA_LIN - fatal("strFromWChar not implemented yet!"); -#endif - - return out; -} - -bool strEquals(str_t a, str_t b) { - return strCompare(a, b) == 0; -} - -int strCompare(str_t a, str_t b) { - return a.len == b.len ? - memcmp(a.buf, b.buf, a.len) : - (int)(a.len - b.len); -} - -str_t strDup(arena_t *arena, str_t src) { - return strInitLen(arena, src.buf, src.len); -} - -bool strIsEmpty(str_t ctx) { - return ctx.len == 0 || ctx.buf == NULL; -} - -void strReplace(str_t *ctx, char from, char to) { - if (!ctx) return; - char *buf = ctx->buf; - for (usize i = 0; i < ctx->len; ++i) { - buf[i] = buf[i] == from ? to : buf[i]; - } -} - -strview_t strSub(str_t ctx, usize from, usize to) { - if (to > ctx.len) to = ctx.len; - if (from > to) from = to; - return (strview_t){ ctx.buf + from, to - from }; -} - -void strLower(str_t *ctx) { - char *buf = ctx->buf; - for (usize i = 0; i < ctx->len; ++i) { - buf[i] = (buf[i] >= 'A' && buf[i] <= 'Z') ? - buf[i] += 'a' - 'A' : - buf[i]; - } -} - -void strUpper(str_t *ctx) { - char *buf = ctx->buf; - for (usize i = 0; i < ctx->len; ++i) { - buf[i] = (buf[i] >= 'a' && buf[i] <= 'z') ? - buf[i] -= 'a' - 'A' : - buf[i]; - } -} - -str_t strToLower(arena_t *arena, str_t ctx) { - strLower(&ctx); - return strDup(arena, ctx); -} - -str_t strToUpper(arena_t *arena, str_t ctx) { - strUpper(&ctx); - return strDup(arena, ctx); -} - - -// == STRVIEW_T ==================================================== - -strview_t strvInit(const char *cstr) { - return (strview_t){ - .buf = cstr, - .len = cstr ? strlen(cstr) : 0, - }; -} - -strview_t strvInitLen(const char *buf, usize size) { - return (strview_t){ - .buf = buf, - .len = size, - }; -} - -strview_t strvInitStr(str_t str) { - return (strview_t){ - .buf = str.buf, - .len = str.len - }; -} - - -bool strvIsEmpty(strview_t ctx) { - return ctx.len == 0 || !ctx.buf; -} - -bool strvEquals(strview_t a, strview_t b) { - return strvCompare(a, b) == 0; -} - -int strvCompare(strview_t a, strview_t b) { - return a.len == b.len ? - memcmp(a.buf, b.buf, a.len) : - (int)(a.len - b.len); -} - -wchar_t *strvToWChar(arena_t *arena, strview_t ctx, usize *outlen) { - wchar_t *out = NULL; - int len = 0; - - if (strvIsEmpty(ctx)) { - goto error; - } - -#if COLLA_WIN - len = MultiByteToWideChar( - CP_UTF8, 0, - ctx.buf, (int)ctx.len, - NULL, 0 - ); - - if (len == 0) { - unsigned long error = GetLastError(); - if (error == ERROR_NO_UNICODE_TRANSLATION) { - err("couldn't translate string (%v) to a wide string, no unicode translation", ctx); - } - else { - err("couldn't translate string (%v) to a wide string, %u", ctx, error); - } - - goto error; - } - - out = alloc(arena, wchar_t, len + 1); - - MultiByteToWideChar( - CP_UTF8, 0, - ctx.buf, (int)ctx.len, - out, len - ); - -#elif COLLA_LIN - fatal("strFromWChar not implemented yet!"); -#endif - -error: - if (outlen) { - *outlen = (usize)len; - } - return out; -} - -TCHAR *strvToTChar(arena_t *arena, strview_t str) { -#if UNICODE - return strvToWChar(arena, str, NULL); -#else - char *cstr = alloc(arena, char, str.len + 1); - memcpy(cstr, str.buf, str.len); - return cstr; -#endif -} - -strview_t strvRemovePrefix(strview_t ctx, usize n) { - if (n > ctx.len) n = ctx.len; - return (strview_t){ - .buf = ctx.buf + n, - .len = ctx.len - n, - }; -} - -strview_t strvRemoveSuffix(strview_t ctx, usize n) { - if (n > ctx.len) n = ctx.len; - return (strview_t){ - .buf = ctx.buf, - .len = ctx.len - n, - }; -} - -strview_t strvTrim(strview_t ctx) { - return strvTrimLeft(strvTrimRight(ctx)); -} - -strview_t strvTrimLeft(strview_t ctx) { - strview_t out = ctx; - for (usize i = 0; i < ctx.len; ++i) { - char c = ctx.buf[i]; - if (c != ' ' && (c < '\t' || c > '\r')) { - break; - } - out.buf++; - out.len--; - } - return out; -} - -strview_t strvTrimRight(strview_t ctx) { - strview_t out = ctx; - for (isize i = ctx.len - 1; i >= 0; --i) { - char c = ctx.buf[i]; - if (c != ' ' && (c < '\t' || c > '\r')) { - break; - } - out.len--; - } - return out; -} - -strview_t strvSub(strview_t ctx, usize from, usize to) { - if (to > ctx.len) to = ctx.len; - if (from > to) from = to; - return (strview_t){ ctx.buf + from, to - from }; -} - -bool strvStartsWith(strview_t ctx, char c) { - return ctx.len > 0 && ctx.buf[0] == c; -} - -bool strvStartsWithView(strview_t ctx, strview_t view) { - return ctx.len >= view.len && memcmp(ctx.buf, view.buf, view.len) == 0; -} - -bool strvEndsWith(strview_t ctx, char c) { - return ctx.len > 0 && ctx.buf[ctx.len - 1] == c; -} - -bool strvEndsWithView(strview_t ctx, strview_t view) { - return ctx.len >= view.len && memcmp(ctx.buf + ctx.len, view.buf, view.len) == 0; -} - -bool strvContains(strview_t ctx, char c) { - for(usize i = 0; i < ctx.len; ++i) { - if(ctx.buf[i] == c) { - return true; - } - } - return false; -} - -bool strvContainsView(strview_t ctx, strview_t view) { - if (ctx.len < view.len) return false; - usize end = ctx.len - view.len; - for (usize i = 0; i < end; ++i) { - if (memcmp(ctx.buf + i, view.buf, view.len) == 0) { - return true; - } - } - return false; -} - -usize strvFind(strview_t ctx, char c, usize from) { - for (usize i = from; i < ctx.len; ++i) { - if (ctx.buf[i] == c) { - return i; - } - } - return STR_NONE; -} - -usize strvFindView(strview_t ctx, strview_t view, usize from) { - if (ctx.len < view.len) return STR_NONE; - usize end = ctx.len - view.len; - for (usize i = from; i < end; ++i) { - if (memcmp(ctx.buf + i, view.buf, view.len) == 0) { - return i; - } - } - return STR_NONE; -} - -usize strvRFind(strview_t ctx, char c, usize from_right) { - if (from_right > ctx.len) from_right = ctx.len; - isize end = (isize)(ctx.len - from_right); - for (isize i = end; i >= 0; --i) { - if (ctx.buf[i] == c) { - return (usize)i; - } - } - return STR_NONE; -} - -usize strvRFindView(strview_t ctx, strview_t view, usize from_right) { - if (from_right > ctx.len) from_right = ctx.len; - isize end = (isize)(ctx.len - from_right); - if (end < (isize)view.len) return STR_NONE; - for (isize i = end - view.len; i >= 0; --i) { - if (memcmp(ctx.buf + i, view.buf, view.len) == 0) { - return (usize)i; - } - } - return STR_NONE; -} - -#include "warnings/colla_warn_beg.h" - -#undef CP_UTF8 -#undef ERROR_NO_UNICODE_TRANSLATION \ No newline at end of file +#include "str.h" + +#include "warnings/colla_warn_beg.h" + +#include "arena.h" +#include "format.h" +#include "tracelog.h" + +#if COLLA_WIN + +#include + +#else + +#include + +#endif + +#if COLLA_TCC +#include "tcc/colla_tcc.h" +#endif + +// == STR_T ======================================================== + +str_t strInit(arena_t *arena, const char *buf) { + return buf ? strInitLen(arena, buf, strlen(buf)) : STR_EMPTY; +} + +str_t strInitLen(arena_t *arena, const char *buf, usize len) { + if (!buf || !len) return STR_EMPTY; + + str_t out = { + .buf = alloc(arena, char, len + 1), + .len = len + }; + + memcpy(out.buf, buf, len); + + return out; +} + +str_t strInitView(arena_t *arena, strview_t view) { + return strInitLen(arena, view.buf, view.len); +} + +str_t strFmt(arena_t *arena, const char *fmt, ...) { + va_list args; + va_start(args, fmt); + str_t out = strFmtv(arena, fmt, args); + va_end(args); + return out; +} + +str_t strFmtv(arena_t *arena, const char *fmt, va_list args) { + va_list vcopy; + va_copy(vcopy, args); + // stb_vsnprintf returns the length + null_term + int len = fmtBufferv(NULL, 0, fmt, vcopy); + va_end(vcopy); + + char *buffer = alloc(arena, char, len + 1); + fmtBufferv(buffer, len + 1, fmt, args); + + return (str_t) { .buf = buffer, .len = (usize)len }; +} + +str_t strFromWChar(arena_t *arena, const wchar_t *src) { + return strFromWCharLen(arena, src, 0); +} + +str_t strFromWCharLen(arena_t *arena, const wchar_t *src, usize srclen) { + if (!src) return STR_EMPTY; + if (!srclen) srclen = wcslen(src); + + str_t out = {0}; + +#if COLLA_WIN + int outlen = WideCharToMultiByte( + CP_UTF8, 0, + src, (int)srclen, + NULL, 0, + NULL, NULL + ); + + if (outlen == 0) { + unsigned long error = GetLastError(); + if (error == ERROR_NO_UNICODE_TRANSLATION) { + err("couldn't translate wide string (%S) to utf8, no unicode translation", src); + } + else { + err("couldn't translate wide string (%S) to utf8, %u", error); + } + + return STR_EMPTY; + } + + out.buf = alloc(arena, char, outlen + 1); + WideCharToMultiByte( + CP_UTF8, 0, + src, (int)srclen, + out.buf, outlen, + NULL, NULL + ); + + out.len = outlen; + +#elif COLLA_LIN + fatal("strFromWChar not implemented yet!"); +#endif + + return out; +} + +bool strEquals(str_t a, str_t b) { + return strCompare(a, b) == 0; +} + +int strCompare(str_t a, str_t b) { + return a.len == b.len ? + memcmp(a.buf, b.buf, a.len) : + (int)(a.len - b.len); +} + +str_t strDup(arena_t *arena, str_t src) { + return strInitLen(arena, src.buf, src.len); +} + +bool strIsEmpty(str_t ctx) { + return ctx.len == 0 || ctx.buf == NULL; +} + +void strReplace(str_t *ctx, char from, char to) { + if (!ctx) return; + char *buf = ctx->buf; + for (usize i = 0; i < ctx->len; ++i) { + buf[i] = buf[i] == from ? to : buf[i]; + } +} + +strview_t strSub(str_t ctx, usize from, usize to) { + if (to > ctx.len) to = ctx.len; + if (from > to) from = to; + return (strview_t){ ctx.buf + from, to - from }; +} + +void strLower(str_t *ctx) { + char *buf = ctx->buf; + for (usize i = 0; i < ctx->len; ++i) { + buf[i] = (buf[i] >= 'A' && buf[i] <= 'Z') ? + buf[i] += 'a' - 'A' : + buf[i]; + } +} + +void strUpper(str_t *ctx) { + char *buf = ctx->buf; + for (usize i = 0; i < ctx->len; ++i) { + buf[i] = (buf[i] >= 'a' && buf[i] <= 'z') ? + buf[i] -= 'a' - 'A' : + buf[i]; + } +} + +str_t strToLower(arena_t *arena, str_t ctx) { + strLower(&ctx); + return strDup(arena, ctx); +} + +str_t strToUpper(arena_t *arena, str_t ctx) { + strUpper(&ctx); + return strDup(arena, ctx); +} + + +// == STRVIEW_T ==================================================== + +strview_t strvInit(const char *cstr) { + return (strview_t){ + .buf = cstr, + .len = cstr ? strlen(cstr) : 0, + }; +} + +strview_t strvInitLen(const char *buf, usize size) { + return (strview_t){ + .buf = buf, + .len = size, + }; +} + +strview_t strvInitStr(str_t str) { + return (strview_t){ + .buf = str.buf, + .len = str.len + }; +} + + +bool strvIsEmpty(strview_t ctx) { + return ctx.len == 0 || !ctx.buf; +} + +bool strvEquals(strview_t a, strview_t b) { + return strvCompare(a, b) == 0; +} + +int strvCompare(strview_t a, strview_t b) { + return a.len == b.len ? + memcmp(a.buf, b.buf, a.len) : + (int)(a.len - b.len); +} + +char strvFront(strview_t ctx) { + return ctx.len > 0 ? ctx.buf[0] : '\0'; +} + +char strvBack(strview_t ctx) { + return ctx.len > 0 ? ctx.buf[ctx.len - 1] : '\0'; +} + + +wchar_t *strvToWChar(arena_t *arena, strview_t ctx, usize *outlen) { + wchar_t *out = NULL; + int len = 0; + + if (strvIsEmpty(ctx)) { + goto error; + } + +#if COLLA_WIN + len = MultiByteToWideChar( + CP_UTF8, 0, + ctx.buf, (int)ctx.len, + NULL, 0 + ); + + if (len == 0) { + unsigned long error = GetLastError(); + if (error == ERROR_NO_UNICODE_TRANSLATION) { + err("couldn't translate string (%v) to a wide string, no unicode translation", ctx); + } + else { + err("couldn't translate string (%v) to a wide string, %u", ctx, error); + } + + goto error; + } + + out = alloc(arena, wchar_t, len + 1); + + MultiByteToWideChar( + CP_UTF8, 0, + ctx.buf, (int)ctx.len, + out, len + ); + +#elif COLLA_LIN + fatal("strFromWChar not implemented yet!"); +#endif + +error: + if (outlen) { + *outlen = (usize)len; + } + return out; +} + +TCHAR *strvToTChar(arena_t *arena, strview_t str) { +#if UNICODE + return strvToWChar(arena, str, NULL); +#else + char *cstr = alloc(arena, char, str.len + 1); + memcpy(cstr, str.buf, str.len); + return cstr; +#endif +} + +strview_t strvRemovePrefix(strview_t ctx, usize n) { + if (n > ctx.len) n = ctx.len; + return (strview_t){ + .buf = ctx.buf + n, + .len = ctx.len - n, + }; +} + +strview_t strvRemoveSuffix(strview_t ctx, usize n) { + if (n > ctx.len) n = ctx.len; + return (strview_t){ + .buf = ctx.buf, + .len = ctx.len - n, + }; +} + +strview_t strvTrim(strview_t ctx) { + return strvTrimLeft(strvTrimRight(ctx)); +} + +strview_t strvTrimLeft(strview_t ctx) { + strview_t out = ctx; + for (usize i = 0; i < ctx.len; ++i) { + char c = ctx.buf[i]; + if (c != ' ' && (c < '\t' || c > '\r')) { + break; + } + out.buf++; + out.len--; + } + return out; +} + +strview_t strvTrimRight(strview_t ctx) { + strview_t out = ctx; + for (isize i = ctx.len - 1; i >= 0; --i) { + char c = ctx.buf[i]; + if (c != ' ' && (c < '\t' || c > '\r')) { + break; + } + out.len--; + } + return out; +} + +strview_t strvSub(strview_t ctx, usize from, usize to) { + if (to > ctx.len) to = ctx.len; + if (from > to) from = to; + return (strview_t){ ctx.buf + from, to - from }; +} + +bool strvStartsWith(strview_t ctx, char c) { + return ctx.len > 0 && ctx.buf[0] == c; +} + +bool strvStartsWithView(strview_t ctx, strview_t view) { + return ctx.len >= view.len && memcmp(ctx.buf, view.buf, view.len) == 0; +} + +bool strvEndsWith(strview_t ctx, char c) { + return ctx.len > 0 && ctx.buf[ctx.len - 1] == c; +} + +bool strvEndsWithView(strview_t ctx, strview_t view) { + return ctx.len >= view.len && memcmp(ctx.buf + ctx.len - view.len, view.buf, view.len) == 0; +} + +bool strvContains(strview_t ctx, char c) { + for(usize i = 0; i < ctx.len; ++i) { + if(ctx.buf[i] == c) { + return true; + } + } + return false; +} + +bool strvContainsView(strview_t ctx, strview_t view) { + if (ctx.len < view.len) return false; + usize end = ctx.len - view.len; + for (usize i = 0; i < end; ++i) { + if (memcmp(ctx.buf + i, view.buf, view.len) == 0) { + return true; + } + } + return false; +} + +usize strvFind(strview_t ctx, char c, usize from) { + for (usize i = from; i < ctx.len; ++i) { + if (ctx.buf[i] == c) { + return i; + } + } + return STR_NONE; +} + +usize strvFindView(strview_t ctx, strview_t view, usize from) { + if (ctx.len < view.len) return STR_NONE; + usize end = ctx.len - view.len; + for (usize i = from; i < end; ++i) { + if (memcmp(ctx.buf + i, view.buf, view.len) == 0) { + return i; + } + } + return STR_NONE; +} + +usize strvRFind(strview_t ctx, char c, usize from_right) { + if (from_right > ctx.len) from_right = ctx.len; + isize end = (isize)(ctx.len - from_right); + for (isize i = end; i >= 0; --i) { + if (ctx.buf[i] == c) { + return (usize)i; + } + } + return STR_NONE; +} + +usize strvRFindView(strview_t ctx, strview_t view, usize from_right) { + if (from_right > ctx.len) from_right = ctx.len; + isize end = (isize)(ctx.len - from_right); + if (end < (isize)view.len) return STR_NONE; + for (isize i = end - view.len; i >= 0; --i) { + if (memcmp(ctx.buf + i, view.buf, view.len) == 0) { + return (usize)i; + } + } + return STR_NONE; +} + +#include "warnings/colla_warn_beg.h" \ No newline at end of file diff --git a/colla/str.h b/str.h similarity index 86% rename from colla/str.h rename to str.h index 5a65720..db216ae 100644 --- a/colla/str.h +++ b/str.h @@ -1,109 +1,119 @@ -#pragma once - -#include // va_list -#include // strlen - -#include "collatypes.h" - -typedef struct arena_t arena_t; - -#define STR_NONE SIZE_MAX - -typedef struct { - char *buf; - usize len; -} str_t; - -typedef struct { - const char *buf; - usize len; -} strview_t; - -// == STR_T ======================================================== - -#define str__1(arena, x) \ - _Generic((x), \ - const char *: strInit, \ - char *: strInit, \ - strview_t: strInitView \ - )(arena, x) - -#define str__2(arena, cstr, clen) strInitLen(arena, cstr, clen) -#define str__impl(_1, _2, n, ...) str__##n - -// either: -// arena_t arena, [const] char *cstr, [usize len] -// arena_t arena, strview_t view -#define str(arena, ...) str__impl(__VA_ARGS__, 2, 1, 0)(arena, __VA_ARGS__) - -str_t strInit(arena_t *arena, const char *buf); -str_t strInitLen(arena_t *arena, const char *buf, usize len); -str_t strInitView(arena_t *arena, strview_t view); -str_t strFmt(arena_t *arena, const char *fmt, ...); -str_t strFmtv(arena_t *arena, const char *fmt, va_list args); - -str_t strFromWChar(arena_t *arena, const wchar_t *src, usize srclen); - -bool strEquals(str_t a, str_t b); -int strCompare(str_t a, str_t b); - -str_t strDup(arena_t *arena, str_t src); -bool strIsEmpty(str_t ctx); - -void strReplace(str_t *ctx, char from, char to); -// if len == SIZE_MAX, copies until end -strview_t strSub(str_t ctx, usize from, usize to); - -void strLower(str_t *ctx); -void strUpper(str_t *ctx); - -str_t strToLower(arena_t *arena, str_t ctx); -str_t strToUpper(arena_t *arena, str_t ctx); - -// == STRVIEW_T ==================================================== - -#define strv__1(x) \ - _Generic((x), \ - char *: strvInit, \ - const char *: strvInit, \ - str_t: strvInitStr \ - )(x) - -#define strv__2(cstr, clen) strvInitLen(cstr, clen) -#define strv__impl(_1, _2, n, ...) strv__##n - -#define strv(...) strv__impl(__VA_ARGS__, 2, 1, 0)(__VA_ARGS__) - -strview_t strvInit(const char *cstr); -strview_t strvInitLen(const char *buf, usize size); -strview_t strvInitStr(str_t str); - -bool strvIsEmpty(strview_t ctx); -bool strvEquals(strview_t a, strview_t b); -int strvCompare(strview_t a, strview_t b); - -wchar_t *strvToWChar(arena_t *arena, strview_t ctx, usize *outlen); -TCHAR *strvToTChar(arena_t *arena, strview_t str); - -strview_t strvRemovePrefix(strview_t ctx, usize n); -strview_t strvRemoveSuffix(strview_t ctx, usize n); -strview_t strvTrim(strview_t ctx); -strview_t strvTrimLeft(strview_t ctx); -strview_t strvTrimRight(strview_t ctx); - -strview_t strvSub(strview_t ctx, usize from, usize to); - -bool strvStartsWith(strview_t ctx, char c); -bool strvStartsWithView(strview_t ctx, strview_t view); - -bool strvEndsWith(strview_t ctx, char c); -bool strvEndsWithView(strview_t ctx, strview_t view); - -bool strvContains(strview_t ctx, char c); -bool strvContainsView(strview_t ctx, strview_t view); - -usize strvFind(strview_t ctx, char c, usize from); -usize strvFindView(strview_t ctx, strview_t view, usize from); - -usize strvRFind(strview_t ctx, char c, usize from_right); -usize strvRFindView(strview_t ctx, strview_t view, usize from_right); +#pragma once + +#include // va_list +#include // strlen + +#include "collatypes.h" + +typedef struct arena_t arena_t; + +#define STR_NONE SIZE_MAX + +typedef struct { + char *buf; + usize len; +} str_t; + +typedef struct { + const char *buf; + usize len; +} strview_t; + +// == STR_T ======================================================== + +#define str__1(arena, x) \ + _Generic((x), \ + const char *: strInit, \ + char *: strInit, \ + const wchar_t *: strFromWChar, \ + wchar_t *: strFromWChar, \ + strview_t: strInitView \ + )(arena, x) + +#define str__2(arena, cstr, clen) strInitLen(arena, cstr, clen) +#define str__impl(_1, _2, n, ...) str__##n + +// either: +// arena_t arena, [const] char *cstr, [usize len] +// arena_t arena, strview_t view +#define str(arena, ...) str__impl(__VA_ARGS__, 2, 1, 0)(arena, __VA_ARGS__) + +#define STR_EMPTY (str_t){0} + +str_t strInit(arena_t *arena, const char *buf); +str_t strInitLen(arena_t *arena, const char *buf, usize len); +str_t strInitView(arena_t *arena, strview_t view); +str_t strFmt(arena_t *arena, const char *fmt, ...); +str_t strFmtv(arena_t *arena, const char *fmt, va_list args); + +str_t strFromWChar(arena_t *arena, const wchar_t *src); +str_t strFromWCharLen(arena_t *arena, const wchar_t *src, usize srclen); + +bool strEquals(str_t a, str_t b); +int strCompare(str_t a, str_t b); + +str_t strDup(arena_t *arena, str_t src); +bool strIsEmpty(str_t ctx); + +void strReplace(str_t *ctx, char from, char to); +// if len == SIZE_MAX, copies until end +strview_t strSub(str_t ctx, usize from, usize to); + +void strLower(str_t *ctx); +void strUpper(str_t *ctx); + +str_t strToLower(arena_t *arena, str_t ctx); +str_t strToUpper(arena_t *arena, str_t ctx); + +// == STRVIEW_T ==================================================== + +#define strv__1(x) \ + _Generic((x), \ + char *: strvInit, \ + const char *: strvInit, \ + str_t: strvInitStr \ + )(x) + +#define strv__2(cstr, clen) strvInitLen(cstr, clen) +#define strv__impl(_1, _2, n, ...) strv__##n + +#define strv(...) strv__impl(__VA_ARGS__, 2, 1, 0)(__VA_ARGS__) + +#define STRV_EMPTY (strview_t){0} + +strview_t strvInit(const char *cstr); +strview_t strvInitLen(const char *buf, usize size); +strview_t strvInitStr(str_t str); + +bool strvIsEmpty(strview_t ctx); +bool strvEquals(strview_t a, strview_t b); +int strvCompare(strview_t a, strview_t b); + +char strvFront(strview_t ctx); +char strvBack(strview_t ctx); + +wchar_t *strvToWChar(arena_t *arena, strview_t ctx, usize *outlen); +TCHAR *strvToTChar(arena_t *arena, strview_t str); + +strview_t strvRemovePrefix(strview_t ctx, usize n); +strview_t strvRemoveSuffix(strview_t ctx, usize n); +strview_t strvTrim(strview_t ctx); +strview_t strvTrimLeft(strview_t ctx); +strview_t strvTrimRight(strview_t ctx); + +strview_t strvSub(strview_t ctx, usize from, usize to); + +bool strvStartsWith(strview_t ctx, char c); +bool strvStartsWithView(strview_t ctx, strview_t view); + +bool strvEndsWith(strview_t ctx, char c); +bool strvEndsWithView(strview_t ctx, strview_t view); + +bool strvContains(strview_t ctx, char c); +bool strvContainsView(strview_t ctx, strview_t view); + +usize strvFind(strview_t ctx, char c, usize from); +usize strvFindView(strview_t ctx, strview_t view, usize from); + +usize strvRFind(strview_t ctx, char c, usize from_right); +usize strvRFindView(strview_t ctx, strview_t view, usize from_right); diff --git a/colla/strstream.c b/strstream.c similarity index 93% rename from colla/strstream.c rename to strstream.c index 6b5ec58..3b249fe 100644 --- a/colla/strstream.c +++ b/strstream.c @@ -1,629 +1,655 @@ -#include "strstream.h" - -#include "warnings/colla_warn_beg.h" - -#include -#include -#include -#include -#include -#include -#include // HUGE_VALF - -#include "tracelog.h" -#include "arena.h" - -#if COLLA_WIN && COLLA_TCC -#define strtoull _strtoui64 -#define strtoll _strtoi64 -#define strtof strtod -#endif - -/* == INPUT STREAM ============================================ */ - -instream_t istrInit(const char *str) { - return istrInitLen(str, strlen(str)); -} - -instream_t istrInitLen(const char *str, usize len) { - instream_t res; - res.start = res.cur = str; - res.size = len; - return res; -} - -char istrGet(instream_t *ctx) { - return ctx && ctx->cur ? *ctx->cur++ : '\0'; -} - -void istrIgnore(instream_t *ctx, char delim) { - for (; !istrIsFinished(*ctx) && *ctx->cur != delim; ++ctx->cur) { - - } -} - -void istrIgnoreAndSkip(instream_t *ctx, char delim) { - istrIgnore(ctx, delim); - istrSkip(ctx, 1); -} - -char istrPeek(instream_t *ctx) { - return ctx && ctx->cur ? *ctx->cur : '\0'; -} - -char istrPeekNext(instream_t *ctx) { - if (!ctx || !ctx->cur) return '\0'; - usize offset = (ctx->cur - ctx->start) + 1; - return offset > ctx->size ? '\0' : *(ctx->cur + 1); -} - -void istrSkip(instream_t *ctx, usize n) { - if (!ctx || !ctx->cur) return; - usize remaining = ctx->size - (ctx->cur - ctx->start); - if(n > remaining) { - return; - } - ctx->cur += n; -} - -void istrSkipWhitespace(instream_t *ctx) { - if (!ctx || !ctx->cur) return; - while (*ctx->cur && isspace(*ctx->cur)) { - ++ctx->cur; - } -} - -void istrRead(instream_t *ctx, char *buf, usize len) { - if (!ctx || !ctx->cur) return; - usize remaining = ctx->size - (ctx->cur - ctx->start); - if(len > remaining) { - warn("istrRead: trying to read len %zu from remaining %zu", len, remaining); - return; - } - memcpy(buf, ctx->cur, len); - ctx->cur += len; -} - -usize istrReadMax(instream_t *ctx, char *buf, usize len) { - if (!ctx || !ctx->cur) return 0; - usize remaining = ctx->size - (ctx->cur - ctx->start); - len = remaining < len ? remaining : len; - memcpy(buf, ctx->cur, len); - ctx->cur += len; - return len; -} - -void istrRewind(instream_t *ctx) { - ctx->cur = ctx->start; -} - -void istrRewindN(instream_t *ctx, usize amount) { - if (!ctx || !ctx->cur) return; - usize remaining = ctx->size - (ctx->cur - ctx->start); - if (amount > remaining) amount = remaining; - ctx->cur -= amount; -} - -usize istrTell(instream_t ctx) { - return ctx.cur ? ctx.cur - ctx.start : 0; -} - -usize istrRemaining(instream_t ctx) { - return ctx.cur ? ctx.size - (ctx.cur - ctx.start) : 0; -} - -bool istrIsFinished(instream_t ctx) { - return ctx.cur ? (usize)(ctx.cur - ctx.start) >= ctx.size : true; -} - -bool istrGetBool(instream_t *ctx, bool *val) { - if (!ctx || !ctx->cur) return false; - usize remaining = ctx->size - (ctx->cur - ctx->start); - if(strncmp(ctx->cur, "true", remaining) == 0) { - *val = true; - return true; - } - if(strncmp(ctx->cur, "false", remaining) == 0) { - *val = false; - return true; - } - return false; -} - -bool istrGetU8(instream_t *ctx, uint8 *val) { - if (!ctx || !ctx->cur) return false; - char *end = NULL; - *val = (uint8) strtoul(ctx->cur, &end, 0); - - if(ctx->cur == end) { - warn("istrGetU8: no valid conversion could be performed"); - return false; - } - else if(*val == UINT8_MAX) { - warn("istrGetU8: value read is out of the range of representable values"); - return false; - } - - ctx->cur = end; - return true; -} - -bool istrGetU16(instream_t *ctx, uint16 *val) { - if (!ctx || !ctx->cur) return false; - char *end = NULL; - *val = (uint16) strtoul(ctx->cur, &end, 0); - - if(ctx->cur == end) { - warn("istrGetU16: no valid conversion could be performed"); - return false; - } - else if(*val == UINT16_MAX) { - warn("istrGetU16: value read is out of the range of representable values"); - return false; - } - - ctx->cur = end; - return true; -} - -bool istrGetU32(instream_t *ctx, uint32 *val) { - if (!ctx || !ctx->cur) return false; - char *end = NULL; - *val = (uint32) strtoul(ctx->cur, &end, 0); - - if(ctx->cur == end) { - warn("istrGetU32: no valid conversion could be performed"); - return false; - } - else if(*val == UINT32_MAX) { - warn("istrGetU32: value read is out of the range of representable values"); - return false; - } - - ctx->cur = end; - return true; -} - -bool istrGetU64(instream_t *ctx, uint64 *val) { - if (!ctx || !ctx->cur) return false; - char *end = NULL; - *val = strtoull(ctx->cur, &end, 0); - - if(ctx->cur == end) { - warn("istrGetU64: no valid conversion could be performed"); - return false; - } - else if(*val == ULLONG_MAX) { - warn("istrGetU64: value read is out of the range of representable values"); - return false; - } - - ctx->cur = end; - return true; -} - -bool istrGetI8(instream_t *ctx, int8 *val) { - if (!ctx || !ctx->cur) return false; - char *end = NULL; - *val = (int8) strtol(ctx->cur, &end, 0); - - if(ctx->cur == end) { - warn("istrGetI8: no valid conversion could be performed"); - return false; - } - else if(*val == INT8_MAX || *val == INT8_MIN) { - warn("istrGetI8: value read is out of the range of representable values"); - return false; - } - - ctx->cur = end; - return true; -} - -bool istrGetI16(instream_t *ctx, int16 *val) { - if (!ctx || !ctx->cur) return false; - char *end = NULL; - *val = (int16) strtol(ctx->cur, &end, 0); - - if(ctx->cur == end) { - warn("istrGetI16: no valid conversion could be performed"); - return false; - } - else if(*val == INT16_MAX || *val == INT16_MIN) { - warn("istrGetI16: value read is out of the range of representable values"); - return false; - } - - ctx->cur = end; - return true; -} - -bool istrGetI32(instream_t *ctx, int32 *val) { - if (!ctx || !ctx->cur) return false; - char *end = NULL; - *val = (int32) strtol(ctx->cur, &end, 0); - - if(ctx->cur == end) { - warn("istrGetI32: no valid conversion could be performed"); - return false; - } - else if(*val == INT32_MAX || *val == INT32_MIN) { - warn("istrGetI32: value read is out of the range of representable values"); - return false; - } - - ctx->cur = end; - return true; -} - -bool istrGetI64(instream_t *ctx, int64 *val) { - if (!ctx || !ctx->cur) return false; - char *end = NULL; - *val = strtoll(ctx->cur, &end, 0); - - if(ctx->cur == end) { - warn("istrGetI64: no valid conversion could be performed"); - return false; - } - else if(*val == INT64_MAX || *val == INT64_MIN) { - warn("istrGetI64: value read is out of the range of representable values"); - return false; - } - - ctx->cur = end; - return true; -} - -bool istrGetFloat(instream_t *ctx, float *val) { - if (!ctx || !ctx->cur) return false; - char *end = NULL; - *val = strtof(ctx->cur, &end); - - if(ctx->cur == end) { - warn("istrGetFloat: no valid conversion could be performed"); - return false; - } - else if(*val == HUGE_VALF || *val == -HUGE_VALF) { - warn("istrGetFloat: value read is out of the range of representable values"); - return false; - } - - ctx->cur = end; - return true; -} - -bool istrGetDouble(instream_t *ctx, double *val) { - if (!ctx || !ctx->cur) return false; - char *end = NULL; - *val = strtod(ctx->cur, &end); - - if(ctx->cur == end) { - warn("istrGetDouble: no valid conversion could be performed (%.5s)", ctx->cur); - return false; - } - else if(*val == HUGE_VAL || *val == -HUGE_VAL) { - warn("istrGetDouble: value read is out of the range of representable values"); - return false; - } - - ctx->cur = end; - return true; -} - -str_t istrGetStr(arena_t *arena, instream_t *ctx, char delim) { - if (!ctx || !ctx->cur) return (str_t){0}; - const char *from = ctx->cur; - istrIgnore(ctx, delim); - // if it didn't actually find it, it just reached the end of the string - if(*ctx->cur != delim) { - return (str_t){0}; - } - usize len = ctx->cur - from; - str_t out = { - .buf = alloc(arena, char, len + 1), - .len = len - }; - memcpy(out.buf, from, len); - return out; -} - -usize istrGetBuf(instream_t *ctx, char *buf, usize buflen) { - if (!ctx || !ctx->cur) return 0; - usize remaining = ctx->size - (ctx->cur - ctx->start); - buflen -= 1; - buflen = remaining < buflen ? remaining : buflen; - memcpy(buf, ctx->cur, buflen); - buf[buflen] = '\0'; - ctx->cur += buflen; - return buflen; -} - -strview_t istrGetView(instream_t *ctx, char delim) { - if (!ctx || !ctx->cur) return (strview_t){0}; - const char *from = ctx->cur; - istrIgnore(ctx, delim); - usize len = ctx->cur - from; - return strvInitLen(from, len); -} - -strview_t istrGetViewEither(instream_t *ctx, strview_t chars) { - if (!ctx || !ctx->cur) return (strview_t){0}; - const char *from = ctx->cur; - for (; !istrIsFinished(*ctx) && !strvContains(chars, *ctx->cur); ++ctx->cur) { - - } - usize len = ctx->cur - from; - return strvInitLen(from, len); -} - -strview_t istrGetViewLen(instream_t *ctx, usize len) { - if (!ctx || !ctx->cur) return (strview_t){0}; - const char *from = ctx->cur; - istrSkip(ctx, len); - usize buflen = ctx->cur - from; - return (strview_t){ from, buflen }; -} - -/* == OUTPUT STREAM =========================================== */ - -void ostr__remove_null(outstream_t *o) { - usize len = ostrTell(o); - if (len && o->beg[len - 1] == '\0') { - arenaPop(o->arena, 1); - } -} - -outstream_t ostrInit(arena_t *arena) { - return (outstream_t){ - .beg = (char *)(arena ? arena->current : NULL), - .arena = arena, - }; -} - -void ostrClear(outstream_t *ctx) { - arenaPop(ctx->arena, ostrTell(ctx)); -} - -usize ostrTell(outstream_t *ctx) { - return ctx->arena ? (char *)ctx->arena->current - ctx->beg : 0; -} - -char ostrBack(outstream_t *ctx) { - usize len = ostrTell(ctx); - return len ? ctx->beg[len - 1] : '\0'; -} - -str_t ostrAsStr(outstream_t *ctx) { - bool is_null_terminated = ostrBack(ctx) == '\0' && false; - return (str_t){ - .buf = ctx->beg, - .len = ostrTell(ctx) - is_null_terminated - }; -} - -strview_t ostrAsView(outstream_t *ctx) { - bool is_null_terminated = ostrBack(ctx) == '\0'; - return (strview_t){ - .buf = ctx->beg, - .len = ostrTell(ctx) - is_null_terminated - }; -} - -void ostrPrintf(outstream_t *ctx, const char *fmt, ...) { - va_list args; - va_start(args, fmt); - ostrPrintfV(ctx, fmt, args); - va_end(args); -} - -void ostrPrintfV(outstream_t *ctx, const char *fmt, va_list args) { - if (!ctx->arena) return; - ostr__remove_null(ctx); - strFmtv(ctx->arena, fmt, args); - // remove null termination - arenaPop(ctx->arena, 1); -} - -void ostrPutc(outstream_t *ctx, char c) { - if (!ctx->arena) return; - ostr__remove_null(ctx); - char *newc = alloc(ctx->arena, char); - *newc = c; -} - -void ostrPuts(outstream_t *ctx, strview_t v) { - if (strvIsEmpty(v)) return; - ostr__remove_null(ctx); - str(ctx->arena, v); -} - -void ostrAppendBool(outstream_t *ctx, bool val) { - ostrPuts(ctx, val ? strv("true") : strv("false")); -} - -void ostrAppendUInt(outstream_t *ctx, uint64 val) { - ostrPrintf(ctx, "%I64u", val); -} - -void ostrAppendInt(outstream_t *ctx, int64 val) { - ostrPrintf(ctx, "%I64d", val); -} - -void ostrAppendNum(outstream_t *ctx, double val) { - ostrPrintf(ctx, "%g", val); -} - -/* == OUT BYTE STREAM ========================================= */ - -obytestream_t obstrInit(arena_t *exclusive_arena) { - return (obytestream_t){ - .beg = exclusive_arena ? exclusive_arena->current : NULL, - .arena = exclusive_arena, - }; -} - -void obstrClear(obytestream_t *ctx) { - if (ctx->arena) { - ctx->arena->current = ctx->beg; - } -} - -usize obstrTell(obytestream_t *ctx) { - return ctx->arena ? ctx->arena->current - ctx->beg : 0; -} - -buffer_t obstrAsBuf(obytestream_t *ctx) { - return (buffer_t){ .data = ctx->beg, .len = obstrTell(ctx) }; -} - -void obstrWrite(obytestream_t *ctx, const void *buf, usize buflen) { - uint8 *dst = alloc(ctx->arena, uint8, buflen); - memcpy(dst, buf, buflen); -} - -void obstrPuts(obytestream_t *ctx, strview_t str) { - obstrWrite(ctx, str.buf, str.len); -} - -void obstrAppendU8(obytestream_t *ctx, uint8 value) { - obstrWrite(ctx, &value, sizeof(value)); -} - -void obstrAppendU16(obytestream_t *ctx, uint16 value) { - obstrWrite(ctx, &value, sizeof(value)); -} - -void obstrAppendU32(obytestream_t *ctx, uint32 value) { - obstrWrite(ctx, &value, sizeof(value)); -} - -void obstrAppendU64(obytestream_t *ctx, uint64 value) { - obstrWrite(ctx, &value, sizeof(value)); -} - -void obstrAppendI8(obytestream_t *ctx, int8 value) { - obstrWrite(ctx, &value, sizeof(value)); -} - -void obstrAppendI16(obytestream_t *ctx, int16 value) { - obstrWrite(ctx, &value, sizeof(value)); -} - -void obstrAppendI32(obytestream_t *ctx, int32 value) { - obstrWrite(ctx, &value, sizeof(value)); -} - -void obstrAppendI64(obytestream_t *ctx, int64 value) { - obstrWrite(ctx, &value, sizeof(value)); -} - -/* == IN BYTE STREAM ========================================== */ - -ibytestream_t ibstrInit(const void *buf, usize len) { - return (ibytestream_t) { - .cur = buf, - .start = buf, - .size = len, - }; -} - -usize ibstrRemaining(ibytestream_t *ctx) { - return ctx->size - (ctx->cur - ctx->start); -} - -usize ibstrTell(ibytestream_t *ctx) { - return ctx->cur ? ctx->cur - ctx->start : 0; -} - -usize ibstrRead(ibytestream_t *ctx, void *buf, usize buflen) { - if (!ctx->cur) return 0; - usize remaining = ibstrRemaining(ctx); - if (buflen > remaining) buflen = remaining; - memcpy(buf, ctx->cur, buflen); - ctx->cur += buflen; - return buflen; -} - -uint8 ibstrGetU8(ibytestream_t *ctx) { - uint8 value = 0; - usize read = ibstrRead(ctx, &value, sizeof(value)); - return read == sizeof(value) ? value : 0; -} - -uint16 ibstrGetU16(ibytestream_t *ctx) { - uint16 value = 0; - usize read = ibstrRead(ctx, &value, sizeof(value)); - return read == sizeof(value) ? value : 0; -} - -uint32 ibstrGetU32(ibytestream_t *ctx) { - uint32 value = 0; - usize read = ibstrRead(ctx, &value, sizeof(value)); - return read == sizeof(value) ? value : 0; -} - -uint64 ibstrGetU64(ibytestream_t *ctx) { - uint64 value = 0; - usize read = ibstrRead(ctx, &value, sizeof(value)); - return read == sizeof(value) ? value : 0; -} - -int8 ibstrGetI8(ibytestream_t *ctx) { - int8 value = 0; - usize read = ibstrRead(ctx, &value, sizeof(value)); - return read == sizeof(value) ? value : 0; -} - -int16 ibstrGetI16(ibytestream_t *ctx) { - int16 value = 0; - usize read = ibstrRead(ctx, &value, sizeof(value)); - return read == sizeof(value) ? value : 0; -} - -int32 ibstrGetI32(ibytestream_t *ctx) { - int32 value = 0; - usize read = ibstrRead(ctx, &value, sizeof(value)); - return read == sizeof(value) ? value : 0; -} - -int64 ibstrGetI64(ibytestream_t *ctx) { - int64 value = 0; - usize read = ibstrRead(ctx, &value, sizeof(value)); - return read == sizeof(value) ? value : 0; -} - -float ibstrGetFloat(ibytestream_t *ctx) { - float value = 0; - usize read = ibstrRead(ctx, &value, sizeof(value)); - return read == sizeof(value) ? value : 0; -} - -double ibstrGetDouble(ibytestream_t *ctx) { - double value = 0; - usize read = ibstrRead(ctx, &value, sizeof(value)); - return read == sizeof(value) ? value : 0; -} - -strview_t ibstrGetView(ibytestream_t *ctx, usize lensize) { - uint64 len = 0; - usize read = ibstrRead(ctx, &len, lensize); - if (read != lensize) { - warn("couldn't read %zu bytes, instead read %zu for string", lensize, read); - return (strview_t){0}; - } - usize remaining = ibstrRemaining(ctx); - if (len > remaining) { - warn("trying to read a string of length %zu, but only %zu bytes remaining", len, remaining); - len = remaining; - } - - const char *str = (const char *)ctx->cur; - ctx->cur += len; - - return (strview_t){ - .buf = str, - .len = len, - }; -} - +#include "strstream.h" + +#include "warnings/colla_warn_beg.h" + +#include +#include +#include +#include +#include +#include +#include // HUGE_VALF + +#include "tracelog.h" +#include "arena.h" + +#if COLLA_WIN && COLLA_TCC +#define strtoull _strtoui64 +#define strtoll _strtoi64 +#define strtof strtod +#endif + +/* == INPUT STREAM ============================================ */ + +instream_t istrInit(const char *str) { + return istrInitLen(str, strlen(str)); +} + +instream_t istrInitLen(const char *str, usize len) { + instream_t res; + res.start = res.cur = str; + res.size = len; + return res; +} + +char istrGet(instream_t *ctx) { + return ctx && ctx->cur ? *ctx->cur++ : '\0'; +} + +void istrIgnore(instream_t *ctx, char delim) { + for (; !istrIsFinished(*ctx) && *ctx->cur != delim; ++ctx->cur) { + + } +} + +void istrIgnoreAndSkip(instream_t *ctx, char delim) { + istrIgnore(ctx, delim); + istrSkip(ctx, 1); +} + +char istrPeek(instream_t *ctx) { + return ctx && ctx->cur ? *ctx->cur : '\0'; +} + +char istrPeekNext(instream_t *ctx) { + if (!ctx || !ctx->cur) return '\0'; + usize offset = (ctx->cur - ctx->start) + 1; + return offset > ctx->size ? '\0' : *(ctx->cur + 1); +} + +char istrPrev(instream_t *ctx) { + if (!ctx || ctx->cur == ctx->start) return '\0'; + return *(ctx->cur - 1); +} + +char istrPrevPrev(instream_t *ctx) { + if (!ctx || (ctx->cur - 1) == ctx->start) return '\0'; + return *(ctx->cur - 2); +} + +void istrSkip(instream_t *ctx, usize n) { + if (!ctx || !ctx->cur) return; + usize remaining = ctx->size - (ctx->cur - ctx->start); + if(n > remaining) { + return; + } + ctx->cur += n; +} + +void istrSkipWhitespace(instream_t *ctx) { + if (!ctx || !ctx->cur) return; + while (*ctx->cur && isspace(*ctx->cur)) { + ++ctx->cur; + } +} + +void istrRead(instream_t *ctx, char *buf, usize len) { + if (!ctx || !ctx->cur) return; + usize remaining = ctx->size - (ctx->cur - ctx->start); + if(len > remaining) { + warn("istrRead: trying to read len %zu from remaining %zu", len, remaining); + return; + } + memcpy(buf, ctx->cur, len); + ctx->cur += len; +} + +usize istrReadMax(instream_t *ctx, char *buf, usize len) { + if (!ctx || !ctx->cur) return 0; + usize remaining = ctx->size - (ctx->cur - ctx->start); + len = remaining < len ? remaining : len; + memcpy(buf, ctx->cur, len); + ctx->cur += len; + return len; +} + +void istrRewind(instream_t *ctx) { + ctx->cur = ctx->start; +} + +void istrRewindN(instream_t *ctx, usize amount) { + if (!ctx || !ctx->cur) return; + usize remaining = ctx->size - (ctx->cur - ctx->start); + if (amount > remaining) amount = remaining; + ctx->cur -= amount; +} + +usize istrTell(instream_t ctx) { + return ctx.cur ? ctx.cur - ctx.start : 0; +} + +usize istrRemaining(instream_t ctx) { + return ctx.cur ? ctx.size - (ctx.cur - ctx.start) : 0; +} + +bool istrIsFinished(instream_t ctx) { + return ctx.cur ? (usize)(ctx.cur - ctx.start) >= ctx.size : true; +} + +bool istrGetBool(instream_t *ctx, bool *val) { + if (!ctx || !ctx->cur) return false; + usize remaining = ctx->size - (ctx->cur - ctx->start); + if(strncmp(ctx->cur, "true", remaining) == 0) { + *val = true; + return true; + } + if(strncmp(ctx->cur, "false", remaining) == 0) { + *val = false; + return true; + } + return false; +} + +bool istrGetU8(instream_t *ctx, uint8 *val) { + if (!ctx || !ctx->cur) return false; + char *end = NULL; + *val = (uint8) strtoul(ctx->cur, &end, 0); + + if(ctx->cur == end) { + warn("istrGetU8: no valid conversion could be performed"); + return false; + } + else if(*val == UINT8_MAX) { + warn("istrGetU8: value read is out of the range of representable values"); + return false; + } + + ctx->cur = end; + return true; +} + +bool istrGetU16(instream_t *ctx, uint16 *val) { + if (!ctx || !ctx->cur) return false; + char *end = NULL; + *val = (uint16) strtoul(ctx->cur, &end, 0); + + if(ctx->cur == end) { + warn("istrGetU16: no valid conversion could be performed"); + return false; + } + else if(*val == UINT16_MAX) { + warn("istrGetU16: value read is out of the range of representable values"); + return false; + } + + ctx->cur = end; + return true; +} + +bool istrGetU32(instream_t *ctx, uint32 *val) { + if (!ctx || !ctx->cur) return false; + char *end = NULL; + *val = (uint32) strtoul(ctx->cur, &end, 0); + + if(ctx->cur == end) { + warn("istrGetU32: no valid conversion could be performed"); + return false; + } + else if(*val == UINT32_MAX) { + warn("istrGetU32: value read is out of the range of representable values"); + return false; + } + + ctx->cur = end; + return true; +} + +bool istrGetU64(instream_t *ctx, uint64 *val) { + if (!ctx || !ctx->cur) return false; + char *end = NULL; + *val = strtoull(ctx->cur, &end, 0); + + if(ctx->cur == end) { + warn("istrGetU64: no valid conversion could be performed"); + return false; + } + else if(*val == ULLONG_MAX) { + warn("istrGetU64: value read is out of the range of representable values"); + return false; + } + + ctx->cur = end; + return true; +} + +bool istrGetI8(instream_t *ctx, int8 *val) { + if (!ctx || !ctx->cur) return false; + char *end = NULL; + *val = (int8) strtol(ctx->cur, &end, 0); + + if(ctx->cur == end) { + warn("istrGetI8: no valid conversion could be performed"); + return false; + } + else if(*val == INT8_MAX || *val == INT8_MIN) { + warn("istrGetI8: value read is out of the range of representable values"); + return false; + } + + ctx->cur = end; + return true; +} + +bool istrGetI16(instream_t *ctx, int16 *val) { + if (!ctx || !ctx->cur) return false; + char *end = NULL; + *val = (int16) strtol(ctx->cur, &end, 0); + + if(ctx->cur == end) { + warn("istrGetI16: no valid conversion could be performed"); + return false; + } + else if(*val == INT16_MAX || *val == INT16_MIN) { + warn("istrGetI16: value read is out of the range of representable values"); + return false; + } + + ctx->cur = end; + return true; +} + +bool istrGetI32(instream_t *ctx, int32 *val) { + if (!ctx || !ctx->cur) return false; + char *end = NULL; + *val = (int32) strtol(ctx->cur, &end, 0); + + if(ctx->cur == end) { + warn("istrGetI32: no valid conversion could be performed"); + return false; + } + else if(*val == INT32_MAX || *val == INT32_MIN) { + warn("istrGetI32: value read is out of the range of representable values"); + return false; + } + + ctx->cur = end; + return true; +} + +bool istrGetI64(instream_t *ctx, int64 *val) { + if (!ctx || !ctx->cur) return false; + char *end = NULL; + *val = strtoll(ctx->cur, &end, 0); + + if(ctx->cur == end) { + warn("istrGetI64: no valid conversion could be performed"); + return false; + } + else if(*val == INT64_MAX || *val == INT64_MIN) { + warn("istrGetI64: value read is out of the range of representable values"); + return false; + } + + ctx->cur = end; + return true; +} + +bool istrGetFloat(instream_t *ctx, float *val) { + if (!ctx || !ctx->cur) return false; + char *end = NULL; + *val = strtof(ctx->cur, &end); + + if(ctx->cur == end) { + warn("istrGetFloat: no valid conversion could be performed"); + return false; + } + else if(*val == HUGE_VALF || *val == -HUGE_VALF) { + warn("istrGetFloat: value read is out of the range of representable values"); + return false; + } + + ctx->cur = end; + return true; +} + +bool istrGetDouble(instream_t *ctx, double *val) { + if (!ctx || !ctx->cur) return false; + char *end = NULL; + *val = strtod(ctx->cur, &end); + + if(ctx->cur == end) { + warn("istrGetDouble: no valid conversion could be performed (%.5s)", ctx->cur); + return false; + } + else if(*val == HUGE_VAL || *val == -HUGE_VAL) { + warn("istrGetDouble: value read is out of the range of representable values"); + return false; + } + + ctx->cur = end; + return true; +} + +str_t istrGetStr(arena_t *arena, instream_t *ctx, char delim) { + if (!ctx || !ctx->cur) return STR_EMPTY; + const char *from = ctx->cur; + istrIgnore(ctx, delim); + // if it didn't actually find it, it just reached the end of the string + if(*ctx->cur != delim) { + return STR_EMPTY; + } + usize len = ctx->cur - from; + str_t out = { + .buf = alloc(arena, char, len + 1), + .len = len + }; + memcpy(out.buf, from, len); + return out; +} + +usize istrGetBuf(instream_t *ctx, char *buf, usize buflen) { + if (!ctx || !ctx->cur) return 0; + usize remaining = ctx->size - (ctx->cur - ctx->start); + buflen -= 1; + buflen = remaining < buflen ? remaining : buflen; + memcpy(buf, ctx->cur, buflen); + buf[buflen] = '\0'; + ctx->cur += buflen; + return buflen; +} + +strview_t istrGetView(instream_t *ctx, char delim) { + if (!ctx || !ctx->cur) return STRV_EMPTY; + const char *from = ctx->cur; + istrIgnore(ctx, delim); + usize len = ctx->cur - from; + return strvInitLen(from, len); +} + +strview_t istrGetViewEither(instream_t *ctx, strview_t chars) { + if (!ctx || !ctx->cur) return STRV_EMPTY; + const char *from = ctx->cur; + for (; !istrIsFinished(*ctx) && !strvContains(chars, *ctx->cur); ++ctx->cur) { + + } + usize len = ctx->cur - from; + return strvInitLen(from, len); +} + +strview_t istrGetViewLen(instream_t *ctx, usize len) { + if (!ctx || !ctx->cur) return STRV_EMPTY; + const char *from = ctx->cur; + istrSkip(ctx, len); + usize buflen = ctx->cur - from; + return (strview_t){ from, buflen }; +} + +strview_t istrGetLine(instream_t *ctx) { + strview_t line = istrGetView(ctx, '\n'); + istrSkip(ctx, 1); + if (strvEndsWith(line, '\r')) { + line = strvRemoveSuffix(line, 1); + } + return line; +} + +/* == OUTPUT STREAM =========================================== */ + +static void ostr__remove_null(outstream_t *o) { + usize len = ostrTell(o); + if (len && o->beg[len - 1] == '\0') { + arenaPop(o->arena, 1); + } +} + +outstream_t ostrInit(arena_t *arena) { + return (outstream_t){ + .beg = (char *)(arena ? arena->current : NULL), + .arena = arena, + }; +} + +void ostrClear(outstream_t *ctx) { + arenaPop(ctx->arena, ostrTell(ctx)); +} + +usize ostrTell(outstream_t *ctx) { + return ctx->arena ? (char *)ctx->arena->current - ctx->beg : 0; +} + +char ostrBack(outstream_t *ctx) { + usize len = ostrTell(ctx); + return len ? ctx->beg[len - 1] : '\0'; +} + +str_t ostrAsStr(outstream_t *ctx) { + if (ostrTell(ctx) == 0 || ostrBack(ctx) != '\0') { + ostrPutc(ctx, '\0'); + } + + str_t out = { + .buf = ctx->beg, + .len = ostrTell(ctx) - 1 + }; + + ctx->beg = NULL; + ctx->arena = NULL; + return out; +} + +strview_t ostrAsView(outstream_t *ctx) { + bool is_null_terminated = ostrBack(ctx) == '\0'; + return (strview_t){ + .buf = ctx->beg, + .len = ostrTell(ctx) - is_null_terminated + }; +} + +void ostrPrintf(outstream_t *ctx, const char *fmt, ...) { + va_list args; + va_start(args, fmt); + ostrPrintfV(ctx, fmt, args); + va_end(args); +} + +void ostrPrintfV(outstream_t *ctx, const char *fmt, va_list args) { + if (!ctx->arena) return; + ostr__remove_null(ctx); + strFmtv(ctx->arena, fmt, args); + // remove null termination + arenaPop(ctx->arena, 1); +} + +void ostrPutc(outstream_t *ctx, char c) { + if (!ctx->arena) return; + ostr__remove_null(ctx); + char *newc = alloc(ctx->arena, char); + *newc = c; +} + +void ostrPuts(outstream_t *ctx, strview_t v) { + if (strvIsEmpty(v)) return; + ostr__remove_null(ctx); + str(ctx->arena, v); +} + +void ostrAppendBool(outstream_t *ctx, bool val) { + ostrPuts(ctx, val ? strv("true") : strv("false")); +} + +void ostrAppendUInt(outstream_t *ctx, uint64 val) { + ostrPrintf(ctx, "%I64u", val); +} + +void ostrAppendInt(outstream_t *ctx, int64 val) { + ostrPrintf(ctx, "%I64d", val); +} + +void ostrAppendNum(outstream_t *ctx, double val) { + ostrPrintf(ctx, "%g", val); +} + +/* == OUT BYTE STREAM ========================================= */ + +obytestream_t obstrInit(arena_t *exclusive_arena) { + return (obytestream_t){ + .beg = exclusive_arena ? exclusive_arena->current : NULL, + .arena = exclusive_arena, + }; +} + +void obstrClear(obytestream_t *ctx) { + if (ctx->arena) { + ctx->arena->current = ctx->beg; + } +} + +usize obstrTell(obytestream_t *ctx) { + return ctx->arena ? ctx->arena->current - ctx->beg : 0; +} + +buffer_t obstrAsBuf(obytestream_t *ctx) { + return (buffer_t){ .data = ctx->beg, .len = obstrTell(ctx) }; +} + +void obstrWrite(obytestream_t *ctx, const void *buf, usize buflen) { + uint8 *dst = alloc(ctx->arena, uint8, buflen); + memcpy(dst, buf, buflen); +} + +void obstrPuts(obytestream_t *ctx, strview_t str) { + obstrWrite(ctx, str.buf, str.len); +} + +void obstrAppendU8(obytestream_t *ctx, uint8 value) { + obstrWrite(ctx, &value, sizeof(value)); +} + +void obstrAppendU16(obytestream_t *ctx, uint16 value) { + obstrWrite(ctx, &value, sizeof(value)); +} + +void obstrAppendU32(obytestream_t *ctx, uint32 value) { + obstrWrite(ctx, &value, sizeof(value)); +} + +void obstrAppendU64(obytestream_t *ctx, uint64 value) { + obstrWrite(ctx, &value, sizeof(value)); +} + +void obstrAppendI8(obytestream_t *ctx, int8 value) { + obstrWrite(ctx, &value, sizeof(value)); +} + +void obstrAppendI16(obytestream_t *ctx, int16 value) { + obstrWrite(ctx, &value, sizeof(value)); +} + +void obstrAppendI32(obytestream_t *ctx, int32 value) { + obstrWrite(ctx, &value, sizeof(value)); +} + +void obstrAppendI64(obytestream_t *ctx, int64 value) { + obstrWrite(ctx, &value, sizeof(value)); +} + +/* == IN BYTE STREAM ========================================== */ + +ibytestream_t ibstrInit(const void *buf, usize len) { + return (ibytestream_t) { + .cur = buf, + .start = buf, + .size = len, + }; +} + +usize ibstrRemaining(ibytestream_t *ctx) { + return ctx->size - (ctx->cur - ctx->start); +} + +usize ibstrTell(ibytestream_t *ctx) { + return ctx->cur ? ctx->cur - ctx->start : 0; +} + +usize ibstrRead(ibytestream_t *ctx, void *buf, usize buflen) { + if (!ctx->cur) return 0; + usize remaining = ibstrRemaining(ctx); + if (buflen > remaining) buflen = remaining; + memcpy(buf, ctx->cur, buflen); + ctx->cur += buflen; + return buflen; +} + +uint8 ibstrGetU8(ibytestream_t *ctx) { + uint8 value = 0; + usize read = ibstrRead(ctx, &value, sizeof(value)); + return read == sizeof(value) ? value : 0; +} + +uint16 ibstrGetU16(ibytestream_t *ctx) { + uint16 value = 0; + usize read = ibstrRead(ctx, &value, sizeof(value)); + return read == sizeof(value) ? value : 0; +} + +uint32 ibstrGetU32(ibytestream_t *ctx) { + uint32 value = 0; + usize read = ibstrRead(ctx, &value, sizeof(value)); + return read == sizeof(value) ? value : 0; +} + +uint64 ibstrGetU64(ibytestream_t *ctx) { + uint64 value = 0; + usize read = ibstrRead(ctx, &value, sizeof(value)); + return read == sizeof(value) ? value : 0; +} + +int8 ibstrGetI8(ibytestream_t *ctx) { + int8 value = 0; + usize read = ibstrRead(ctx, &value, sizeof(value)); + return read == sizeof(value) ? value : 0; +} + +int16 ibstrGetI16(ibytestream_t *ctx) { + int16 value = 0; + usize read = ibstrRead(ctx, &value, sizeof(value)); + return read == sizeof(value) ? value : 0; +} + +int32 ibstrGetI32(ibytestream_t *ctx) { + int32 value = 0; + usize read = ibstrRead(ctx, &value, sizeof(value)); + return read == sizeof(value) ? value : 0; +} + +int64 ibstrGetI64(ibytestream_t *ctx) { + int64 value = 0; + usize read = ibstrRead(ctx, &value, sizeof(value)); + return read == sizeof(value) ? value : 0; +} + +float ibstrGetFloat(ibytestream_t *ctx) { + float value = 0; + usize read = ibstrRead(ctx, &value, sizeof(value)); + return read == sizeof(value) ? value : 0; +} + +double ibstrGetDouble(ibytestream_t *ctx) { + double value = 0; + usize read = ibstrRead(ctx, &value, sizeof(value)); + return read == sizeof(value) ? value : 0; +} + +strview_t ibstrGetView(ibytestream_t *ctx, usize lensize) { + uint64 len = 0; + usize read = ibstrRead(ctx, &len, lensize); + if (read != lensize) { + warn("couldn't read %zu bytes, instead read %zu for string", lensize, read); + return STRV_EMPTY; + } + usize remaining = ibstrRemaining(ctx); + if (len > remaining) { + warn("trying to read a string of length %zu, but only %zu bytes remaining", len, remaining); + len = remaining; + } + + const char *str = (const char *)ctx->cur; + ctx->cur += len; + + return (strview_t){ + .buf = str, + .len = len, + }; +} + #include "warnings/colla_warn_end.h" \ No newline at end of file diff --git a/colla/strstream.h b/strstream.h similarity index 95% rename from colla/strstream.h rename to strstream.h index f4585d2..e0d4e26 100644 --- a/colla/strstream.h +++ b/strstream.h @@ -1,155 +1,160 @@ -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -#include - -#include "collatypes.h" -#include "str.h" - -typedef struct arena_t arena_t; - -/* == INPUT STREAM ============================================ */ - -typedef struct { - const char *start; - const char *cur; - usize size; -} instream_t; - -// initialize with null-terminated string -instream_t istrInit(const char *str); -instream_t istrInitLen(const char *str, usize len); - -// get the current character and advance -char istrGet(instream_t *ctx); -// get the current character but don't advance -char istrPeek(instream_t *ctx); -// get the next character but don't advance -char istrPeekNext(instream_t *ctx); -// ignore characters until the delimiter -void istrIgnore(instream_t *ctx, char delim); -// ignore characters until the delimiter and skip it -void istrIgnoreAndSkip(instream_t *ctx, char delim); -// skip n characters -void istrSkip(instream_t *ctx, usize n); -// skips whitespace (' ', '\\n', '\\t', '\\r') -void istrSkipWhitespace(instream_t *ctx); -// read len bytes into buffer, the buffer will not be null terminated -void istrRead(instream_t *ctx, char *buf, usize len); -// read a maximum of len bytes into buffer, the buffer will not be null terminated -// returns the number of bytes read -usize istrReadMax(instream_t *ctx, char *buf, usize len); -// returns to the beginning of the stream -void istrRewind(instream_t *ctx); -// returns back characters -void istrRewindN(instream_t *ctx, usize amount); -// returns the number of bytes read from beginning of stream -usize istrTell(instream_t ctx); -// returns the number of bytes left to read in the stream -usize istrRemaining(instream_t ctx); -// return true if the stream doesn't have any new bytes to read -bool istrIsFinished(instream_t ctx); - -bool istrGetBool(instream_t *ctx, bool *val); -bool istrGetU8(instream_t *ctx, uint8 *val); -bool istrGetU16(instream_t *ctx, uint16 *val); -bool istrGetU32(instream_t *ctx, uint32 *val); -bool istrGetU64(instream_t *ctx, uint64 *val); -bool istrGetI8(instream_t *ctx, int8 *val); -bool istrGetI16(instream_t *ctx, int16 *val); -bool istrGetI32(instream_t *ctx, int32 *val); -bool istrGetI64(instream_t *ctx, int64 *val); -bool istrGetFloat(instream_t *ctx, float *val); -bool istrGetDouble(instream_t *ctx, double *val); -str_t istrGetStr(arena_t *arena, instream_t *ctx, char delim); -// get a string of maximum size len, the string is not allocated by the function and will be null terminated -usize istrGetBuf(instream_t *ctx, char *buf, usize buflen); -strview_t istrGetView(instream_t *ctx, char delim); -strview_t istrGetViewEither(instream_t *ctx, strview_t chars); -strview_t istrGetViewLen(instream_t *ctx, usize len); - -/* == OUTPUT STREAM =========================================== */ - -typedef struct { - char *beg; - arena_t *arena; -} outstream_t; - -outstream_t ostrInit(arena_t *exclusive_arena); -void ostrClear(outstream_t *ctx); - -usize ostrTell(outstream_t *ctx); - -char ostrBack(outstream_t *ctx); -str_t ostrAsStr(outstream_t *ctx); -strview_t ostrAsView(outstream_t *ctx); - -void ostrPrintf(outstream_t *ctx, const char *fmt, ...); -void ostrPrintfV(outstream_t *ctx, const char *fmt, va_list args); -void ostrPutc(outstream_t *ctx, char c); -void ostrPuts(outstream_t *ctx, strview_t v); - -void ostrAppendBool(outstream_t *ctx, bool val); -void ostrAppendUInt(outstream_t *ctx, uint64 val); -void ostrAppendInt(outstream_t *ctx, int64 val); -void ostrAppendNum(outstream_t *ctx, double val); - -/* == OUT BYTE STREAM ========================================= */ - -typedef struct { - uint8 *beg; - arena_t *arena; -} obytestream_t; - -obytestream_t obstrInit(arena_t *exclusive_arena); -void obstrClear(obytestream_t *ctx); - -usize obstrTell(obytestream_t *ctx); -buffer_t obstrAsBuf(obytestream_t *ctx); - -void obstrWrite(obytestream_t *ctx, const void *buf, usize buflen); -void obstrPuts(obytestream_t *ctx, strview_t str); -void obstrAppendU8(obytestream_t *ctx, uint8 value); -void obstrAppendU16(obytestream_t *ctx, uint16 value); -void obstrAppendU32(obytestream_t *ctx, uint32 value); -void obstrAppendU64(obytestream_t *ctx, uint64 value); -void obstrAppendI8(obytestream_t *ctx, int8 value); -void obstrAppendI16(obytestream_t *ctx, int16 value); -void obstrAppendI32(obytestream_t *ctx, int32 value); -void obstrAppendI64(obytestream_t *ctx, int64 value); - -/* == IN BYTE STREAM ========================================== */ - -typedef struct { - const uint8 *start; - const uint8 *cur; - usize size; -} ibytestream_t; - -ibytestream_t ibstrInit(const void *buf, usize len); - -usize ibstrRemaining(ibytestream_t *ctx); -usize ibstrTell(ibytestream_t *ctx); -usize ibstrRead(ibytestream_t *ctx, void *buf, usize buflen); - -uint8 ibstrGetU8(ibytestream_t *ctx); -uint16 ibstrGetU16(ibytestream_t *ctx); -uint32 ibstrGetU32(ibytestream_t *ctx); -uint64 ibstrGetU64(ibytestream_t *ctx); -int8 ibstrGetI8(ibytestream_t *ctx); -int16 ibstrGetI16(ibytestream_t *ctx); -int32 ibstrGetI32(ibytestream_t *ctx); -int64 ibstrGetI64(ibytestream_t *ctx); -float ibstrGetFloat(ibytestream_t *ctx); -double ibstrGetDouble(ibytestream_t *ctx); - -// reads a string, before reads bytes for the length (e.g. sizeof(uint32)) -// then reads sizeof(char) * strlen -strview_t ibstrGetView(ibytestream_t *ctx, usize lensize); - -#ifdef __cplusplus -} // extern "C" -#endif +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#include "collatypes.h" +#include "str.h" + +typedef struct arena_t arena_t; + +/* == INPUT STREAM ============================================ */ + +typedef struct instream_t { + const char *start; + const char *cur; + usize size; +} instream_t; + +// initialize with null-terminated string +instream_t istrInit(const char *str); +instream_t istrInitLen(const char *str, usize len); + +// get the current character and advance +char istrGet(instream_t *ctx); +// get the current character but don't advance +char istrPeek(instream_t *ctx); +// get the next character but don't advance +char istrPeekNext(instream_t *ctx); +// returns the previous character +char istrPrev(instream_t *ctx); +// returns the character before the previous +char istrPrevPrev(instream_t *ctx); +// ignore characters until the delimiter +void istrIgnore(instream_t *ctx, char delim); +// ignore characters until the delimiter and skip it +void istrIgnoreAndSkip(instream_t *ctx, char delim); +// skip n characters +void istrSkip(instream_t *ctx, usize n); +// skips whitespace (' ', '\\n', '\\t', '\\r') +void istrSkipWhitespace(instream_t *ctx); +// read len bytes into buffer, the buffer will not be null terminated +void istrRead(instream_t *ctx, char *buf, usize len); +// read a maximum of len bytes into buffer, the buffer will not be null terminated +// returns the number of bytes read +usize istrReadMax(instream_t *ctx, char *buf, usize len); +// returns to the beginning of the stream +void istrRewind(instream_t *ctx); +// returns back characters +void istrRewindN(instream_t *ctx, usize amount); +// returns the number of bytes read from beginning of stream +usize istrTell(instream_t ctx); +// returns the number of bytes left to read in the stream +usize istrRemaining(instream_t ctx); +// return true if the stream doesn't have any new bytes to read +bool istrIsFinished(instream_t ctx); + +bool istrGetBool(instream_t *ctx, bool *val); +bool istrGetU8(instream_t *ctx, uint8 *val); +bool istrGetU16(instream_t *ctx, uint16 *val); +bool istrGetU32(instream_t *ctx, uint32 *val); +bool istrGetU64(instream_t *ctx, uint64 *val); +bool istrGetI8(instream_t *ctx, int8 *val); +bool istrGetI16(instream_t *ctx, int16 *val); +bool istrGetI32(instream_t *ctx, int32 *val); +bool istrGetI64(instream_t *ctx, int64 *val); +bool istrGetFloat(instream_t *ctx, float *val); +bool istrGetDouble(instream_t *ctx, double *val); +str_t istrGetStr(arena_t *arena, instream_t *ctx, char delim); +// get a string of maximum size len, the string is not allocated by the function and will be null terminated +usize istrGetBuf(instream_t *ctx, char *buf, usize buflen); +strview_t istrGetView(instream_t *ctx, char delim); +strview_t istrGetViewEither(instream_t *ctx, strview_t chars); +strview_t istrGetViewLen(instream_t *ctx, usize len); +strview_t istrGetLine(instream_t *ctx); + +/* == OUTPUT STREAM =========================================== */ + +typedef struct outstream_t { + char *beg; + arena_t *arena; +} outstream_t; + +outstream_t ostrInit(arena_t *exclusive_arena); +void ostrClear(outstream_t *ctx); + +usize ostrTell(outstream_t *ctx); + +char ostrBack(outstream_t *ctx); +str_t ostrAsStr(outstream_t *ctx); +strview_t ostrAsView(outstream_t *ctx); + +void ostrPrintf(outstream_t *ctx, const char *fmt, ...); +void ostrPrintfV(outstream_t *ctx, const char *fmt, va_list args); +void ostrPutc(outstream_t *ctx, char c); +void ostrPuts(outstream_t *ctx, strview_t v); + +void ostrAppendBool(outstream_t *ctx, bool val); +void ostrAppendUInt(outstream_t *ctx, uint64 val); +void ostrAppendInt(outstream_t *ctx, int64 val); +void ostrAppendNum(outstream_t *ctx, double val); + +/* == OUT BYTE STREAM ========================================= */ + +typedef struct { + uint8 *beg; + arena_t *arena; +} obytestream_t; + +obytestream_t obstrInit(arena_t *exclusive_arena); +void obstrClear(obytestream_t *ctx); + +usize obstrTell(obytestream_t *ctx); +buffer_t obstrAsBuf(obytestream_t *ctx); + +void obstrWrite(obytestream_t *ctx, const void *buf, usize buflen); +void obstrPuts(obytestream_t *ctx, strview_t str); +void obstrAppendU8(obytestream_t *ctx, uint8 value); +void obstrAppendU16(obytestream_t *ctx, uint16 value); +void obstrAppendU32(obytestream_t *ctx, uint32 value); +void obstrAppendU64(obytestream_t *ctx, uint64 value); +void obstrAppendI8(obytestream_t *ctx, int8 value); +void obstrAppendI16(obytestream_t *ctx, int16 value); +void obstrAppendI32(obytestream_t *ctx, int32 value); +void obstrAppendI64(obytestream_t *ctx, int64 value); + +/* == IN BYTE STREAM ========================================== */ + +typedef struct { + const uint8 *start; + const uint8 *cur; + usize size; +} ibytestream_t; + +ibytestream_t ibstrInit(const void *buf, usize len); + +usize ibstrRemaining(ibytestream_t *ctx); +usize ibstrTell(ibytestream_t *ctx); +usize ibstrRead(ibytestream_t *ctx, void *buf, usize buflen); + +uint8 ibstrGetU8(ibytestream_t *ctx); +uint16 ibstrGetU16(ibytestream_t *ctx); +uint32 ibstrGetU32(ibytestream_t *ctx); +uint64 ibstrGetU64(ibytestream_t *ctx); +int8 ibstrGetI8(ibytestream_t *ctx); +int16 ibstrGetI16(ibytestream_t *ctx); +int32 ibstrGetI32(ibytestream_t *ctx); +int64 ibstrGetI64(ibytestream_t *ctx); +float ibstrGetFloat(ibytestream_t *ctx); +double ibstrGetDouble(ibytestream_t *ctx); + +// reads a string, before reads bytes for the length (e.g. sizeof(uint32)) +// then reads sizeof(char) * strlen +strview_t ibstrGetView(ibytestream_t *ctx, usize lensize); + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/colla/tcc/colla.def b/tcc/colla.def similarity index 100% rename from colla/tcc/colla.def rename to tcc/colla.def diff --git a/colla/tcc/colla_tcc.h b/tcc/colla_tcc.h similarity index 99% rename from colla/tcc/colla_tcc.h rename to tcc/colla_tcc.h index e1e2fc0..f39a23d 100644 --- a/colla/tcc/colla_tcc.h +++ b/tcc/colla_tcc.h @@ -1,6 +1,6 @@ #pragma once -#if COLLA_TCC || 1 +#if COLLA_TCC #include diff --git a/tools/build b/tools/build new file mode 100644 index 0000000..bed761b --- /dev/null +++ b/tools/build @@ -0,0 +1,8 @@ +#!/bin/bash + +# cosmocc -Os -mtiny -o docs.com docs.c + +# optimised version for x86/64 only, shaves off ~300KB +x86_64-unknown-cosmo-cc -Os -mtiny -o ../docs/docs.com docs.c + +rm ../docs/docs.com.dbg \ No newline at end of file diff --git a/tools/docs.c b/tools/docs.c new file mode 100644 index 0000000..c8eb9e3 --- /dev/null +++ b/tools/docs.c @@ -0,0 +1,429 @@ +#include "../arena.c" +#include "../file.c" +#include "../format.c" +#include "../ini.c" +#include "../str.c" +#include "../strstream.c" +#include "../tracelog.c" +#include "../vmem.c" +#include "../markdown.c" +#include "../highlight.c" +#include "../dir.c" +#include "../socket.c" +#include "../http.c" +#include "../server.c" + +#include "../html.h" + +const char *raw_css; + +typedef struct page_t { + str_t title; + str_t url; + str_t data; + struct page_t *next; +} page_t; + +typedef struct { + uint8 arenabuf[KB(5)]; + arena_t arena; + hl_ctx_t *hl; + int line; +} cparser_ctx_t; + +void md_cparser_init(void *udata) { + cparser_ctx_t *cparser = udata; + cparser->line = 1; + if (cparser->hl) { + return; + } + cparser->arena = arenaMake(ARENA_STATIC, sizeof(cparser->arenabuf), cparser->arenabuf); + cparser->hl = hlInit(&cparser->arena, &(hl_config_t){ + .colors = { + [HL_COLOR_NORMAL] = strv(""), + [HL_COLOR_PREPROC] = strv(""), + [HL_COLOR_TYPES] = strv(""), + [HL_COLOR_CUSTOM_TYPES] = strv(""), + [HL_COLOR_KEYWORDS] = strv(""), + [HL_COLOR_NUMBER] = strv(""), + [HL_COLOR_STRING] = strv(""), + [HL_COLOR_COMMENT] = strv(""), + [HL_COLOR_FUNC] = strv(""), + [HL_COLOR_SYMBOL] = strv(""), + [HL_COLOR_MACRO] = strv(""), + }, + .flags = HL_FLAG_HTML, + }); +} + +void md_cparser_callback(strview_t line, outstream_t *out, void *udata) { + cparser_ctx_t *cparser = udata; + + arena_t scratch = cparser->arena; + str_t highlighted = hlHighlight(&scratch, cparser->hl, line); + ostrPrintf(out, "
  • %v
  • ", highlighted); +} + +page_t *get_pages(arena_t *arena, strview_t path, strview_t default_page) { + arena_t scratch = arenaMake(ARENA_VIRTUAL, MB(1)); + + dir_t *dir = dirOpen(&scratch, path); + dir_entry_t *entry = NULL; + + page_t *first = NULL; + page_t *head = NULL; + page_t *tail = NULL; + + cparser_ctx_t cparser = {0}; + + while ((entry = dirNext(&scratch, dir))) { + if (entry->type != DIRTYPE_FILE) { + continue; + } + + strview_t name, ext; + fileSplitPath(strv(entry->name), NULL, &name, &ext); + + if (!strvEquals(ext, strv(".md"))) { + continue; + } + + str_t fullname = strFmt(&scratch, "%v/%v", path, entry->name); + str_t markdown_str = fileReadWholeStr(&scratch, strv(fullname)); + + ini_t config = {0}; + str_t md = markdownStr(&scratch, strv(markdown_str), &(md_options_t){ + .out_config = &config, + .parsers = (md_parser_t[]){ + { + .init = md_cparser_init, + .callback = md_cparser_callback, + .userdata = &cparser, + .lang = strv("c"), + }, + }, + .parsers_count = 1, + }); + + inivalue_t *title = iniGet(iniGetTable(&config, INI_ROOT), strv("title")); + + page_t *page = alloc(arena, page_t); + page->data = md; + page->url = str(arena, name); + + if (title) { + page->title = str(arena, title->value); + } + else { + page->title = page->url; + } + + if (!first && strvEquals(name, default_page)) { + first = page; + } + else { + if (!head) head = page; + if (tail) tail->next = page; + tail = page; + } + } + + if (first) { + first->next = head; + head = first; + } + + strview_t css = strv(raw_css); + + for_each(page, head) { + str_t html = STR_EMPTY; + + htmlBeg(arena, &html); + headBeg(); + title(page->title); + style(css); + headEnd(); + bodyBeg(); + divBeg(.id="main"); + divBeg(.class="content"); + divBeg(.class="pages-container"); + divBeg(.class="pages"); + for_each(item, head) { + str_t class = strFmt(&scratch, "page-item%s", item == page ? " page-current" : ""); + str_t href = strFmt(&scratch, "/%v", item->url); + str_t onclick = strFmt(&scratch, "window.location = \"/%v\"", item->url); + + a( + item->title, + .href = href.buf, + .class = class.buf, + .onclick = onclick.buf + ); + } + divEnd(); + divEnd(); + + divBeg(.class="document-container"); + divBeg(.class="document"); + htmlPuts(page->data); + htmlRaw(
    ); + divEnd(); + divEnd(); + divEnd(); + divEnd(); + bodyEnd(); + htmlEnd(); + + page->data = html; + } + + arenaCleanup(&scratch); + + return head; +} + +str_t server_default(arena_t scratch, server_t *server, server_req_t *req, void *userdata) { + strview_t needle = strv(req->page); + if (strvFront(needle) == '/') { + needle = strvRemovePrefix(strv(req->page), 1); + } + + page_t *page = userdata; + while (page) { + if (strvEquals(strv(page->url), needle)) { + break; + } + page = page->next; + } + + // if the url is invalid, return the default page + if (!page) { + page = userdata; + } + + return serverMakeResponse(&scratch, 200, strv("text/html"), strv(page->data)); +} + +str_t server_quit(arena_t scratch, server_t *server, server_req_t *req, void *userdata) { + serverStop(server); + return STR_EMPTY; +} + +int main() { + arena_t arena = arenaMake(ARENA_VIRTUAL, MB(1)); + + page_t *pages = get_pages(&arena, strv("."), strv("readme")); + if (!pages) { + err("could not get pages"); + return 1; + } + + server_t *s = serverSetup(&arena, 8080, true); + serverRouteDefault(&arena, s, server_default, pages); + serverRoute(&arena, s, strv("/quit"), server_quit, NULL); + serverStart(arena, s); + + arenaCleanup(&arena); +} + +//// HTML GENERATION STUFF /////////////////////////////// + +const char *raw_css = "" +"html, body, #main {\n" +" margin: 0;\n" +" width: 100%;\n" +" height: 100%;\n" +" font-family: -apple-system,BlinkMacSystemFont,'Segoe UI','Noto Sans',Helvetica,Arial,sans-serif,'Apple Color Emoji','Segoe UI Emoji';\n" +" \n" +" --black: #121212;\n" +" --dark-gray: #212121;\n" +" --gray: #303030;\n" +" --light-gray: #424242;\n" +" --accent: #FFA500;\n" +" /* --accent: #F98334; */\n" +"\n" +" --blue: #45559E;\n" +" --orange: #F98334;\n" +" --yellow: #FABD2F;\n" +" --red: #FB4934;\n" +" --pink: #D3869B;\n" +" --green: #B8BB26;\n" +" --azure: #7FA375;\n" +" --white: #FBF1C7;\n" +" --light-blue: #83A598;\n" +"}\n" +"\n" +"hr {\n" +" color: white;\n" +" opacity: 0.5;\n" +"}\n" +"\n" +"a {\n" +" text-decoration: none;\n" +" font-weight: bold;\n" +" color: var(--red);\n" +"}\n" +"\n" +"a:hover {\n" +" text-decoration: underline;\n" +"}\n" +"\n" +".content {\n" +" width: 100%;\n" +" height: 100%;\n" +" background-color: var(--black);\n" +" display: flex;\n" +" gap: 20px;\n" +" align-items: stretch;\n" +" color: white;\n" +" overflow-x: scroll;\n" +"}\n" +"\n" +".pages-container {\n" +" position: sticky;\n" +" top: 0;\n" +" min-width: 200px;\n" +" background-color: var(--dark-gray);\n" +" border-right: 1px solid var(--light-gray);\n" +" box-shadow: 0 0 20px rgba(0, 0, 0, 1.0);\n" +" overflow-y: scroll;\n" +"}\n" +"\n" +".document-container {\n" +" display: flex;\n" +" justify-content: center;\n" +" width: 100%;\n" +"}\n" +"\n" +".document {\n" +" width: 100%;\n" +" max-width: 700px;\n" +" line-height: 1.5;\n" +"}\n" +"\n" +".document-padding {\n" +" height: 50px;\n" +"}\n" +"\n" +".pages {\n" +" position: relative;\n" +" background-color: var(--dark-gray);\n" +"}\n" +"\n" +".page-item:last-of-type {\n" +" border-bottom: 0;\n" +"}\n" +"\n" +".page-item {\n" +" text-decoration: none;\n" +" display: block;\n" +" color: rgba(255, 255, 255, 0.5);\n" +" padding: 0.2em 0 0.2em 1.5em;\n" +" border-bottom: 1px solid var(--light-gray);\n" +"}\n" +"\n" +".page-item:hover {\n" +" background-color: var(--light-gray);\n" +" cursor: pointer;\n" +" color: white;\n" +" opacity: 1;\n" +"}\n" +"\n" +".page-current {\n" +" color: var(--accent);\n" +" opacity: 1;\n" +"}\n" +"\n" +".page-current:hover {\n" +" color: var(--accent);\n" +"}\n" +"\n" +".page-spacing {\n" +" height: 25px; \n" +"}\n" +"\n" +"code {\n" +" color: var(--accent);\n" +" background-color: var(--dark-gray);\n" +" padding: 0.2em 0.5em 0.2em 0.5em;\n" +" border-radius: 0.5em;\n" +"}\n" +"\n" +"pre {\n" +" margin: 0;\n" +" margin-top: 2em;\n" +" background-color: var(--dark-gray);\n" +" padding: 16px;\n" +" border-radius: 0.3em;\n" +" overflow-x: scroll;\n" +"\n" +" border: 1px solid var(--light-gray);\n" +" box-shadow: 0 0 20px rgba(0, 0, 0, 1.0);\n" +"}\n" +"\n" +"pre > code {\n" +" color: #FBF1C7;\n" +" padding: 0;\n" +" background-color: transparent;\n" +"}\n" +"\n" +"pre ol {\n" +" counter-reset: item;\n" +" padding-left: 0;\n" +"}\n" +"\n" +"pre li {\n" +" display: block;\n" +" margin-left: 0em;\n" +"}\n" +"\n" +"pre li::before {\n" +" display: inline-block;\n" +" content: counter(item);\n" +" counter-increment: item;\n" +" width: 2em;\n" +" padding-right: 1.5em;\n" +" text-align: right;\n" +"}\n" +"\n" +"/* code block colors */\n" +".c-preproc {\n" +" color: #45559E;\n" +"}\n" +"\n" +".c-types {\n" +" color: #F98334;\n" +"}\n" +"\n" +".c-custom-types {\n" +" color: #FABD2F;\n" +"}\n" +"\n" +".c-kwrds {\n" +" color: #FB4934;\n" +"}\n" +"\n" +".c-num {\n" +" color: #D3869B;\n" +"}\n" +"\n" +".c-str {\n" +" color: #B8BB26;\n" +"}\n" +"\n" +".c-cmnt {\n" +" color: #928374;\n" +"}\n" +"\n" +".c-func {\n" +" color: #7FA375;\n" +"}\n" +"\n" +".c-sym {\n" +" color: #FBF1C7;\n" +"}\n" +"\n" +".c-macro {\n" +" color: #83A598;\n" +"}\n" +""; \ No newline at end of file diff --git a/colla/tracelog.c b/tracelog.c similarity index 82% rename from colla/tracelog.c rename to tracelog.c index f7830d6..9867cc7 100644 --- a/colla/tracelog.c +++ b/tracelog.c @@ -1,219 +1,212 @@ -#include "tracelog.h" - -#include -#include -#include - -#include "format.h" - -#if COLLA_WIN - #if COLLA_MSVC - #pragma warning(disable:4996) // _CRT_SECURE_NO_WARNINGS. - #endif - - #include - -#if COLLA_CMT_LIB - #pragma comment(lib, "User32") -#endif - - // avoid including windows.h - - #ifndef STD_OUTPUT_HANDLE - #define STD_OUTPUT_HANDLE ((DWORD)-11) - #endif - #ifndef CP_UTF8 - #define CP_UTF8 65001 - #endif - - #ifndef TLOG_NO_COLOURS - #define TLOG_NO_COLOURS - #endif -#endif - -#if COLLA_EMC - #define TLOG_NO_COLOURS -#endif - -#if COLLA_EMC - #define COLOUR_BLACK "" - #define COLOUR_RED "" - #define COLOUR_GREEN "" - #define COLOUR_YELLOW "" - #define COLOUR_BLUE "" - #define COLOUR_MAGENTA "" - #define COLOUR_CYAN "" - #define COLOUR_WHITE "" - #define COLOUR_RESET "
    " - #define COLOUR_BOLD "" -#elif defined(TLOG_NO_COLOURS) - #define COLOUR_BLACK "" - #define COLOUR_RED "" - #define COLOUR_GREEN "" - #define COLOUR_YELLOW "" - #define COLOUR_BLUE "" - #define COLOUR_MAGENTA "" - #define COLOUR_CYAN "" - #define COLOUR_WHITE "" - #define COLOUR_RESET "" - #define COLOUR_BOLD "" -#else - #define COLOUR_BLACK "\033[30m" - #define COLOUR_RED "\033[31m" - #define COLOUR_GREEN "\033[32m" - #define COLOUR_YELLOW "\033[33m" - #define COLOUR_BLUE "\033[22;34m" - #define COLOUR_MAGENTA "\033[35m" - #define COLOUR_CYAN "\033[36m" - #define COLOUR_WHITE "\033[37m" - #define COLOUR_RESET "\033[0m" - #define COLOUR_BOLD "\033[1m" -#endif - -#define MAX_TRACELOG_MSG_LENGTH 1024 - -bool use_newline = true; - -#if COLLA_WIN -static void setLevelColour(int level) { - WORD attribute = 15; - switch (level) { - case LogDebug: attribute = 1; break; - case LogInfo: attribute = 2; break; - case LogWarning: attribute = 6; break; - case LogError: attribute = 4; break; - case LogFatal: attribute = 4; break; - } - - HANDLE hc = GetStdHandle(STD_OUTPUT_HANDLE); - SetConsoleTextAttribute(hc, attribute); -} -#else -static void setLevelColour(int level) { - switch (level) { - case LogDebug: printf(COLOUR_BLUE); break; - case LogInfo: printf(COLOUR_GREEN); break; - case LogWarning: printf(COLOUR_YELLOW); break; - case LogError: printf(COLOUR_RED); break; - case LogFatal: printf(COLOUR_MAGENTA); break; - default: printf(COLOUR_RESET); break; - } -} -#endif - -void traceLog(int level, const char *fmt, ...) { - va_list args; - va_start(args, fmt); - traceLogVaList(level, fmt, args); - va_end(args); -} - -#ifdef TLOG_MUTEX -#include "cthreads.h" -static cmutex_t g_mtx = 0; -#endif - -void traceLogVaList(int level, const char *fmt, va_list args) { - #ifdef TLOG_MUTEX - if (!g_mtx) g_mtx = mtxInit(); - mtxLock(g_mtx); - #endif - - const char *beg = ""; - switch (level) { - case LogTrace: beg = "[TRACE" ; break; - case LogDebug: beg = "[DEBUG" ; break; - case LogInfo: beg = "[INFO" ; break; - case LogWarning: beg = "[WARNING" ; break; - case LogError: beg = "[ERROR" ; break; - case LogFatal: beg = "[FATAL" ; break; - default: break; - } - -#if COLLA_WIN - SetConsoleOutputCP(CP_UTF8); -#endif - setLevelColour(level); - printf("%s", beg); - -#if GAME_CLIENT - putchar(':'); - traceSetColour(COL_CYAN); - printf("CLIENT"); -#elif GAME_HOST - putchar(':'); - traceSetColour(COL_MAGENTA); - printf("HOST"); -#endif - - setLevelColour(level); - printf("]: "); - - // set back to white - setLevelColour(LogTrace); - // vprintf(fmt, args); - fmtPrintv(fmt, args); - - if(use_newline) { -#if COLLA_EMC - puts("
    "); -#else - puts(""); -#endif - } - -#ifndef TLOG_DONT_EXIT_ON_FATAL - if (level == LogFatal) { - -#ifndef TLOG_NO_MSGBOX - -#if COLLA_WIN - char errbuff[1024]; - fmtBufferv(errbuff, sizeof(errbuff), fmt, args); - - char captionbuf[] = -#if GAME_HOST - "Fatal Host Error"; -#elif GAME_CLIENT - "Fatal Client Error"; -#else - "Fatal Error"; -#endif - MessageBoxA( - NULL, - errbuff, captionbuf, - MB_ICONERROR | MB_OK - ); -#endif - -#endif - fflush(stdout); - abort(); - } -#endif - -#ifdef TLOG_MUTEX - mtxUnlock(g_mtx); -#endif -} - -void traceUseNewline(bool newline) { - use_newline = newline; -} - -void traceSetColour(colour_e colour) { -#if COLLA_WIN - SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), colour); -#else - switch (colour) { - case COL_RESET: printf(COLOUR_RESET); break; - case COL_BLACK: printf(COLOUR_BLACK); break; - case COL_BLUE: printf(COLOUR_BLUE); break; - case COL_GREEN: printf(COLOUR_GREEN); break; - case COL_CYAN: printf(COLOUR_CYAN); break; - case COL_RED: printf(COLOUR_RED); break; - case COL_MAGENTA: printf(COLOUR_MAGENTA); break; - case COL_YELLOW: printf(COLOUR_YELLOW); break; - } -#endif +#include "tracelog.h" + +#include +#include +#include + +#include "format.h" + +#if COLLA_WIN + #if COLLA_MSVC + #pragma warning(disable:4996) // _CRT_SECURE_NO_WARNINGS. + #endif + + #include + +#if COLLA_CMT_LIB + #pragma comment(lib, "User32") +#endif + +#if COLLA_TCC + #include "tcc/colla_tcc.h" +#endif + + //#ifndef TLOG_NO_COLOURS + // #define TLOG_NO_COLOURS + //#endif +#endif + +#if COLLA_EMC + #define TLOG_NO_COLOURS +#endif + +#if COLLA_EMC + #define COLOUR_BLACK "" + #define COLOUR_RED "" + #define COLOUR_GREEN "" + #define COLOUR_YELLOW "" + #define COLOUR_BLUE "" + #define COLOUR_MAGENTA "" + #define COLOUR_CYAN "" + #define COLOUR_WHITE "" + #define COLOUR_RESET "
    " + #define COLOUR_BOLD "" +#elif defined(TLOG_NO_COLOURS) + #define COLOUR_BLACK "" + #define COLOUR_RED "" + #define COLOUR_GREEN "" + #define COLOUR_YELLOW "" + #define COLOUR_BLUE "" + #define COLOUR_MAGENTA "" + #define COLOUR_CYAN "" + #define COLOUR_WHITE "" + #define COLOUR_RESET "" + #define COLOUR_BOLD "" +#else + #define COLOUR_BLACK "\033[30m" + #define COLOUR_RED "\033[31m" + #define COLOUR_GREEN "\033[32m" + #define COLOUR_YELLOW "\033[33m" + #define COLOUR_BLUE "\033[22;34m" + #define COLOUR_MAGENTA "\033[35m" + #define COLOUR_CYAN "\033[36m" + #define COLOUR_WHITE "\033[37m" + #define COLOUR_RESET "\033[0m" + #define COLOUR_BOLD "\033[1m" +#endif + +static bool tl_use_newline = true; + +#if COLLA_WIN +static void setLevelColour(int level) { + WORD attribute = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; + switch (level) { + case LogDebug: attribute = FOREGROUND_BLUE; break; + case LogInfo: attribute = FOREGROUND_GREEN; break; + case LogWarning: attribute = FOREGROUND_RED | FOREGROUND_GREEN; break; + case LogError: attribute = FOREGROUND_RED; break; + case LogFatal: attribute = FOREGROUND_RED; break; + } + + HANDLE hc = GetStdHandle(STD_OUTPUT_HANDLE); + SetConsoleTextAttribute(hc, attribute); +} +#else +static void setLevelColour(int level) { + switch (level) { + case LogDebug: printf(COLOUR_BLUE); break; + case LogInfo: printf(COLOUR_GREEN); break; + case LogWarning: printf(COLOUR_YELLOW); break; + case LogError: printf(COLOUR_RED); break; + case LogFatal: printf(COLOUR_MAGENTA); break; + default: printf(COLOUR_RESET); break; + } +} +#endif + +void traceLog(int level, const char *fmt, ...) { + va_list args; + va_start(args, fmt); + traceLogVaList(level, fmt, args); + va_end(args); +} + +#ifdef TLOG_MUTEX +#include "cthreads.h" +static cmutex_t g_mtx = 0; +#endif + +void traceLogVaList(int level, const char *fmt, va_list args) { + #ifdef TLOG_MUTEX + if (!g_mtx) g_mtx = mtxInit(); + mtxLock(g_mtx); + #endif + + const char *beg = ""; + switch (level) { + case LogAll: beg = "[ALL" ; break; + case LogTrace: beg = "[TRACE" ; break; + case LogDebug: beg = "[DEBUG" ; break; + case LogInfo: beg = "[INFO" ; break; + case LogWarning: beg = "[WARNING" ; break; + case LogError: beg = "[ERROR" ; break; + case LogFatal: beg = "[FATAL" ; break; + default: break; + } + +#if COLLA_WIN + SetConsoleOutputCP(CP_UTF8); +#endif + setLevelColour(level); + printf("%s", beg); + +#if GAME_CLIENT + putchar(':'); + traceSetColour(COL_CYAN); + printf("CLIENT"); +#elif GAME_HOST + putchar(':'); + traceSetColour(COL_MAGENTA); + printf("HOST"); +#endif + + setLevelColour(level); + printf("]: "); + + // set back to white + setLevelColour(LogTrace); + fmtPrintv(fmt, args); + + if(tl_use_newline) { +#if COLLA_EMC + puts("
    "); +#else + puts(""); +#endif + } + +#ifndef TLOG_DONT_EXIT_ON_FATAL + if (level == LogFatal) { + +#ifndef TLOG_NO_MSGBOX + +#if COLLA_WIN + char errbuff[1024]; + fmtBufferv(errbuff, sizeof(errbuff), fmt, args); + + char captionbuf[] = +#if GAME_HOST + "Fatal Host Error"; +#elif GAME_CLIENT + "Fatal Client Error"; +#else + "Fatal Error"; +#endif + MessageBoxA( + NULL, + errbuff, captionbuf, + MB_ICONERROR | MB_OK + ); +#endif + +#endif + fflush(stdout); + abort(); + } +#endif + +#ifdef TLOG_MUTEX + mtxUnlock(g_mtx); +#endif +} + +void traceUseNewline(bool newline) { + tl_use_newline = newline; +} + +void traceSetColour(colour_e colour) { +#if COLLA_WIN + SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), (WORD)colour); +#else + switch (colour) { + case COL_RESET: printf(COLOUR_RESET); break; + case COL_BLACK: printf(COLOUR_BLACK); break; + case COL_BLUE: printf(COLOUR_BLUE); break; + case COL_GREEN: printf(COLOUR_GREEN); break; + case COL_CYAN: printf(COLOUR_CYAN); break; + case COL_RED: printf(COLOUR_RED); break; + case COL_MAGENTA: printf(COLOUR_MAGENTA); break; + case COL_YELLOW: printf(COLOUR_YELLOW); break; + } +#endif } \ No newline at end of file diff --git a/colla/tracelog.h b/tracelog.h similarity index 96% rename from colla/tracelog.h rename to tracelog.h index 516dc3e..800fe49 100644 --- a/colla/tracelog.h +++ b/tracelog.h @@ -1,59 +1,59 @@ -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -/* Define any of this to turn on the option - * -> TLOG_NO_COLOURS: print without using colours - * -> TLOG_VS: print to visual studio console, also turns on TLOG_NO_COLOURS - * -> TLOG_DONT_EXIT_ON_FATAL: don't call 'exit(1)' when using LogFatal - * -> TLOG_DISABLE: turn off all logging, useful for release builds - * -> TLOG_MUTEX: use a mutex on every traceLog call -*/ - -#include "collatypes.h" -#include - -enum { - LogAll, LogTrace, LogDebug, LogInfo, LogWarning, LogError, LogFatal -}; - -typedef enum { - COL_RESET = 15, - COL_BLACK = 8, - COL_BLUE = 9, - COL_GREEN = 10, - COL_CYAN = 11, - COL_RED = 12, - COL_MAGENTA = 13, - COL_YELLOW = 14, - COL_WHITE = 15 -} colour_e; - -void traceLog(int level, const char *fmt, ...); -void traceLogVaList(int level, const char *fmt, va_list args); -void traceUseNewline(bool use_newline); -void traceSetColour(colour_e colour); - -#ifdef TLOG_DISABLE -#define tall(...) -#define trace(...) -#define debug(...) -#define info(...) -#define warn(...) -#define err(...) -#define fatal(...) -#else -#define tall(...) traceLog(LogAll, __VA_ARGS__) -#define trace(...) traceLog(LogTrace, __VA_ARGS__) -#define debug(...) traceLog(LogDebug, __VA_ARGS__) -#define info(...) traceLog(LogInfo, __VA_ARGS__) -#define warn(...) traceLog(LogWarning, __VA_ARGS__) -#define err(...) traceLog(LogError, __VA_ARGS__) -#define fatal(...) traceLog(LogFatal, __VA_ARGS__) -#endif - -#ifdef __cplusplus -} // extern "C" -#endif +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +/* Define any of this to turn on the option + * -> TLOG_NO_COLOURS: print without using colours + * -> TLOG_VS: print to visual studio console, also turns on TLOG_NO_COLOURS + * -> TLOG_DONT_EXIT_ON_FATAL: don't call 'exit(1)' when using LogFatal + * -> TLOG_DISABLE: turn off all logging, useful for release builds + * -> TLOG_MUTEX: use a mutex on every traceLog call +*/ + +#include "collatypes.h" +#include + +enum { + LogAll, LogTrace, LogDebug, LogInfo, LogWarning, LogError, LogFatal +}; + +typedef enum { + COL_RESET = 15, + COL_BLACK = 8, + COL_BLUE = 9, + COL_GREEN = 10, + COL_CYAN = 11, + COL_RED = 12, + COL_MAGENTA = 13, + COL_YELLOW = 14, + COL_WHITE = 15 +} colour_e; + +void traceLog(int level, const char *fmt, ...); +void traceLogVaList(int level, const char *fmt, va_list args); +void traceUseNewline(bool use_newline); +void traceSetColour(colour_e colour); + +#ifdef TLOG_DISABLE +#define tall(...) +#define trace(...) +#define debug(...) +#define info(...) +#define warn(...) +#define err(...) +#define fatal(...) +#else +#define tall(...) traceLog(LogAll, __VA_ARGS__) +#define trace(...) traceLog(LogTrace, __VA_ARGS__) +#define debug(...) traceLog(LogDebug, __VA_ARGS__) +#define info(...) traceLog(LogInfo, __VA_ARGS__) +#define warn(...) traceLog(LogWarning, __VA_ARGS__) +#define err(...) traceLog(LogError, __VA_ARGS__) +#define fatal(...) traceLog(LogFatal, __VA_ARGS__) +#endif + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/colla/utf8.c b/utf8.c similarity index 94% rename from colla/utf8.c rename to utf8.c index 8e4390e..5d47c0a 100644 --- a/colla/utf8.c +++ b/utf8.c @@ -1,172 +1,172 @@ -#include "utf8.h" - -static const uint8 masks[] = { - 0x7f, // 0111-1111 - 0x1f, // 0001-1111 - 0x0f, // 0000-1111 - 0x07, // 0000-0111 - 0x03, // 0000-0011 - 0x01 // 0000-0001 -}; - -static struct { - uint8 mask; - uint8 result; - int octets; -} sizes[] = { - { 0x80, 0x00, 1 }, // 1000-0000, 0000-0000 - { 0xE0, 0xC0, 2 }, // 1110-0000, 1100-0000 - { 0xF0, 0xE0, 3 }, // 1111-0000, 1110-0000 - { 0xF8, 0xF0, 4 }, // 1111-1000, 1111-0000 - { 0xFC, 0xF8, 5 }, // 1111-1100, 1111-1000 - { 0xFE, 0xF8, 6 }, // 1111-1110, 1111-1000 - { 0x80, 0x80, -1 }, // 1000-0000, 1000-0000 -}; - - -/* -UTF-8 codepoints are encoded using the first bits of the first character - -byte 1 | byte 2 | byte 3 | byte 4 -0xxx xxxx | | | -110x xxxx | 10xx xxxx | | -1110 xxxx | 10xx xxxx | 10xx xxxx | -1111 0xxx | 10xx xxxx | 10xx xxxx | 10xx xxxx - -so when we decode it we first find the size of the codepoint (from 1 to 4) -then we apply the mask to the first byte to get the first character -then we keep shifting the rune left 6 and applying the next byte to the mask -until the codepoint is finished (size is 0) - -## EXAMPLE - -utf8 string (€) = 1110-0010 1000-0010 1010-1100 - -cp = 0000-0000 0000-0000 0000-0000 0000-0000 -size = 3 -mask = 0x0f -> 0000-1111 -cp = *s & mask = 1110-0010 & 0000-1111 = 0000-0000 0000-0000 0000-0000 0000-0010 -++s = 1000-0010 - ---size = 2 -cp <<= 6 = 0000-0000 0000-0000 0000-0000 1000-0000 -cp |= *s & 0x3f = 1000-0010 & 0011-1111 = 0000-0000 0000-0000 0000-0000 1000-0010 -++s = 1010-1100 - ---size = 1 -cp <<= 6 = 0000-0000 0000-0000 0010-0000 1000-0000 -cp |= *s & 0x3f = 1010-1100 & 0011-1111 = 0000-0000 0000-0000 0010-0000 1010-1100 -++s = ---------- - -final codepoint = 0010-0000 1010-1100 -€ codepoint = 0010-0000 1010-1100 -*/ - -rune utf8Decode(const char **char_str) { - uint8 **s = (uint8 **)char_str; - - rune ch = 0; - // if is ascii - if (**s < 128) { - ch = **s; - ++*s; - return ch; - } - int size = utf8Size((char *)*s); - if (size == -1) { - ++*s; - return UTF8_INVALID; - } - uint8 mask = masks[size - 1]; - ch = **s & mask; - ++*s; - while(--size) { - ch <<= 6; - ch |= **s & 0x3f; // 0011-1111 - ++*s; - } - return ch; -} - - -/* -to encode a codepoint in a utf8 string we first need to find -the length of the codepoint -then we start from the rightmost byte and loop for each byte of the codepoint -using the length we got before until the first byte (which we skip) -> and (&) with 0x3f so we ignore the first to bits of the codepoint -> or (|) with 0x80 so we make sure that the first two bits are 10 -> bitshift the codepoint right 6 - -finally, we apply the correct length-mask to the first byte - -## EXAMPLE - -ch € = 0010-0000 1010-1100 -ch < 0x10000 - first = 0xe0 = 1110-0000 - len = 3 - -str[2] = (ch & 0x3f) | 0x80 = 1010-1100 & 0011-1111 | 1000-0000 - = 1010-1100 -ch >>= 6 = 0010-0000 1010-1100 >> 6 = 1000-0010 - -str[1] = (ch & 0x3f) | 0x80 = 1000-0010 & 0011-1111 | 1000-000 - = 1000-0010 -ch >>= 6 = 1000-0010 >> 6 = 0000-0010 - -str[0] = ch | first_mask = 0000-0010 | 1111-0000 - = 1111-0010 - -str = 1111-0010 1000-0010 1010-1100 -utf8 € = 1110-0010 1000-0010 1010-1100 -*/ - -usize utf8Encode(char *str, rune codepoint) { - usize len = 0; - uint8 first; - - if (codepoint < 0x80) { // 0000-0000 0000-0000 0000-0000 1000-0000 - first = 0; - len = 1; - } - else if (codepoint < 0x800) { // 0000-0000 0000-0000 0000-1000 0000-0000 - first = 0xc0; // 1100-0000 - len = 2; - } - else if (codepoint < 0x10000) { // 0000-0000 0000-0001 0000-0000 0000-0000 - first = 0xe0; // 1110-0000 - len = 3; - } - else { - first = 0xf0; // 1111-0000 - len = 4; - } - - for (usize i = len - 1; i > 0; --i) { - // 0x3f -> 0011-1111 - // 0x80 -> 1000-0000 - str[i] = (codepoint & 0x3f) | 0x80; - codepoint >>= 6; - } - - str[0] = (char)(codepoint | first); - return len; -} - -int utf8Size(const char *str) { - uint8 c = (uint8)*str; - for(usize i = 0; i < (sizeof(sizes) / sizeof(*sizes)); ++i) { - if ((c & sizes[i].mask) == sizes[i].result) { - return sizes[i].octets; - } - } - return -1; -} - -usize utf8CpSize(rune ch) { - if (ch < 0x80) return 1; - else if (ch < 0x800) return 2; - else if (ch < 0x10000) return 3; - return 4; -} +#include "utf8.h" + +static const uint8 masks[] = { + 0x7f, // 0111-1111 + 0x1f, // 0001-1111 + 0x0f, // 0000-1111 + 0x07, // 0000-0111 + 0x03, // 0000-0011 + 0x01 // 0000-0001 +}; + +static struct { + uint8 mask; + uint8 result; + int octets; +} sizes[] = { + { 0x80, 0x00, 1 }, // 1000-0000, 0000-0000 + { 0xE0, 0xC0, 2 }, // 1110-0000, 1100-0000 + { 0xF0, 0xE0, 3 }, // 1111-0000, 1110-0000 + { 0xF8, 0xF0, 4 }, // 1111-1000, 1111-0000 + { 0xFC, 0xF8, 5 }, // 1111-1100, 1111-1000 + { 0xFE, 0xF8, 6 }, // 1111-1110, 1111-1000 + { 0x80, 0x80, -1 }, // 1000-0000, 1000-0000 +}; + + +/* +UTF-8 codepoints are encoded using the first bits of the first character + +byte 1 | byte 2 | byte 3 | byte 4 +0xxx xxxx | | | +110x xxxx | 10xx xxxx | | +1110 xxxx | 10xx xxxx | 10xx xxxx | +1111 0xxx | 10xx xxxx | 10xx xxxx | 10xx xxxx + +so when we decode it we first find the size of the codepoint (from 1 to 4) +then we apply the mask to the first byte to get the first character +then we keep shifting the rune left 6 and applying the next byte to the mask +until the codepoint is finished (size is 0) + +## EXAMPLE + +utf8 string (€) = 1110-0010 1000-0010 1010-1100 + +cp = 0000-0000 0000-0000 0000-0000 0000-0000 +size = 3 +mask = 0x0f -> 0000-1111 +cp = *s & mask = 1110-0010 & 0000-1111 = 0000-0000 0000-0000 0000-0000 0000-0010 +++s = 1000-0010 + +--size = 2 +cp <<= 6 = 0000-0000 0000-0000 0000-0000 1000-0000 +cp |= *s & 0x3f = 1000-0010 & 0011-1111 = 0000-0000 0000-0000 0000-0000 1000-0010 +++s = 1010-1100 + +--size = 1 +cp <<= 6 = 0000-0000 0000-0000 0010-0000 1000-0000 +cp |= *s & 0x3f = 1010-1100 & 0011-1111 = 0000-0000 0000-0000 0010-0000 1010-1100 +++s = ---------- + +final codepoint = 0010-0000 1010-1100 +€ codepoint = 0010-0000 1010-1100 +*/ + +rune utf8Decode(const char **char_str) { + const uint8 **s = (const uint8 **)char_str; + + rune ch = 0; + // if is ascii + if (**s < 128) { + ch = **s; + ++*s; + return ch; + } + int size = utf8Size((const char *)*s); + if (size == -1) { + ++*s; + return UTF8_INVALID; + } + uint8 mask = masks[size - 1]; + ch = **s & mask; + ++*s; + while(--size) { + ch <<= 6; + ch |= **s & 0x3f; // 0011-1111 + ++*s; + } + return ch; +} + + +/* +to encode a codepoint in a utf8 string we first need to find +the length of the codepoint +then we start from the rightmost byte and loop for each byte of the codepoint +using the length we got before until the first byte (which we skip) +> and (&) with 0x3f so we ignore the first to bits of the codepoint +> or (|) with 0x80 so we make sure that the first two bits are 10 +> bitshift the codepoint right 6 + +finally, we apply the correct length-mask to the first byte + +## EXAMPLE + +ch € = 0010-0000 1010-1100 +ch < 0x10000 + first = 0xe0 = 1110-0000 + len = 3 + +str[2] = (ch & 0x3f) | 0x80 = 1010-1100 & 0011-1111 | 1000-0000 + = 1010-1100 +ch >>= 6 = 0010-0000 1010-1100 >> 6 = 1000-0010 + +str[1] = (ch & 0x3f) | 0x80 = 1000-0010 & 0011-1111 | 1000-000 + = 1000-0010 +ch >>= 6 = 1000-0010 >> 6 = 0000-0010 + +str[0] = ch | first_mask = 0000-0010 | 1111-0000 + = 1111-0010 + +str = 1111-0010 1000-0010 1010-1100 +utf8 € = 1110-0010 1000-0010 1010-1100 +*/ + +usize utf8Encode(char *str, rune codepoint) { + usize len = 0; + uint8 first; + + if (codepoint < 0x80) { // 0000-0000 0000-0000 0000-0000 1000-0000 + first = 0; + len = 1; + } + else if (codepoint < 0x800) { // 0000-0000 0000-0000 0000-1000 0000-0000 + first = 0xc0; // 1100-0000 + len = 2; + } + else if (codepoint < 0x10000) { // 0000-0000 0000-0001 0000-0000 0000-0000 + first = 0xe0; // 1110-0000 + len = 3; + } + else { + first = 0xf0; // 1111-0000 + len = 4; + } + + for (usize i = len - 1; i > 0; --i) { + // 0x3f -> 0011-1111 + // 0x80 -> 1000-0000 + str[i] = (codepoint & 0x3f) | 0x80; + codepoint >>= 6; + } + + str[0] = (char)(codepoint | first); + return len; +} + +int utf8Size(const char *str) { + uint8 c = (uint8)*str; + for(usize i = 0; i < (sizeof(sizes) / sizeof(*sizes)); ++i) { + if ((c & sizes[i].mask) == sizes[i].result) { + return sizes[i].octets; + } + } + return -1; +} + +usize utf8CpSize(rune ch) { + if (ch < 0x80) return 1; + else if (ch < 0x800) return 2; + else if (ch < 0x10000) return 3; + return 4; +} diff --git a/colla/utf8.h b/utf8.h similarity index 95% rename from colla/utf8.h rename to utf8.h index bef77a0..907e1b4 100644 --- a/colla/utf8.h +++ b/utf8.h @@ -1,19 +1,19 @@ -#pragma once - -#include "collatypes.h" - -typedef uint32 rune; - -enum { - UTF8_MAX_SIZE = 4, - UTF8_INVALID = 0x80 -}; - -// grabs the next UTF-8 codepoint and advances string ptr -rune utf8Decode(const char **str); -// encodes a codepoint as UTF-8 and returns the length -usize utf8Encode(char *str, rune ch); -// returns the size of the next UTF-8 codepoint -int utf8Size(const char *str); -// returns the size of a UTF-8 codepoint -usize utf8CpSize(rune ch); +#pragma once + +#include "collatypes.h" + +typedef uint32 rune; + +enum { + UTF8_MAX_SIZE = 4, + UTF8_INVALID = 0x80 +}; + +// grabs the next UTF-8 codepoint and advances string ptr +rune utf8Decode(const char **str); +// encodes a codepoint as UTF-8 and returns the length +usize utf8Encode(char *str, rune ch); +// returns the size of the next UTF-8 codepoint +int utf8Size(const char *str); +// returns the size of a UTF-8 codepoint +usize utf8CpSize(rune ch); diff --git a/colla/vec.h b/vec.h similarity index 100% rename from colla/vec.h rename to vec.h diff --git a/colla/vmem.c b/vmem.c similarity index 98% rename from colla/vmem.c rename to vmem.c index 264790e..b7cfd9c 100644 --- a/colla/vmem.c +++ b/vmem.c @@ -35,8 +35,7 @@ usize vmemPadToPage(usize byte_count) { #if COLLA_WIN -#define WIN32_LEAN_AND_MEAN -#include +#include void *vmemInit(usize size, usize *out_padded_size) { usize alloc_size = vmemPadToPage(size); diff --git a/colla/vmem.h b/vmem.h similarity index 100% rename from colla/vmem.h rename to vmem.h diff --git a/colla/warnings/colla_warn_beg.h b/warnings/colla_warn_beg.h similarity index 100% rename from colla/warnings/colla_warn_beg.h rename to warnings/colla_warn_beg.h diff --git a/colla/warnings/colla_warn_end.h b/warnings/colla_warn_end.h similarity index 100% rename from colla/warnings/colla_warn_end.h rename to warnings/colla_warn_end.h diff --git a/websocket.c b/websocket.c new file mode 100644 index 0000000..f370a57 --- /dev/null +++ b/websocket.c @@ -0,0 +1,142 @@ +#include "websocket.h" + +#include "arena.h" +#include "str.h" +#include "server.h" +#include "socket.h" +#include "base64.h" +#include "strstream.h" + +#include "sha1.h" + +#if !COLLA_MSVC +extern uint16_t ntohs(uint16_t hostshort); +extern uint16_t htons(uint16_t hostshort); +extern uint32_t htonl(uint32_t hostlong); + +uint64_t ntohll(uint64_t input) { + uint64_t rval = 0; + uint8_t *data = (uint8_t *)&rval; + + data[0] = input >> 56; + data[1] = input >> 48; + data[2] = input >> 40; + data[3] = input >> 32; + data[4] = input >> 24; + data[5] = input >> 16; + data[6] = input >> 8; + data[7] = input >> 0; + + return rval; +} + +uint64_t htonll(uint64_t input) { + return ntohll(input); +} + +#endif + +bool wsInitialiseSocket(arena_t scratch, socket_t websocket, strview_t key) { + str_t full_key = strFmt(&scratch, "%v" WEBSOCKET_MAGIC, key, WEBSOCKET_MAGIC); + + sha1_t sha1_ctx = sha1_init(); + sha1_result_t sha1_data = sha1(&sha1_ctx, full_key.buf, full_key.len); + + // convert to big endian for network communication + for (int i = 0; i < 5; ++i) { + sha1_data.digest[i] = htonl(sha1_data.digest[i]); + } + + buffer_t encoded_key = base64Encode(&scratch, (buffer_t){ (uint8 *)sha1_data.digest, sizeof(sha1_data.digest) }); + + str_t response = strFmt( + &scratch, + + "HTTP/1.1 101 Switching Protocols\r\n" + "Connection: Upgrade\r\n" + "Upgrade: websocket\r\n" + "Sec-WebSocket-Accept: %v\r\n" + "\r\n", + encoded_key + ); + + bool success = skSend(websocket, response.buf, response.len); + return success; +} + +buffer_t wsEncodeMessage(arena_t *arena, strview_t message) { + int extra = 6; + if (message.len > UINT16_MAX) extra += sizeof(uint64); + else if (message.len > UINT8_MAX) extra += sizeof(uint16); + uint8 *bytes = alloc(arena, uint8, message.len + extra); + bytes[0] = 0b10000001; + bytes[1] = 0b10000000; + int offset = 2; + if (message.len > UINT16_MAX) { + bytes[1] |= 0b01111111; + uint64 len = htonll(message.len); + memcpy(bytes + 2, &len, sizeof(len)); + offset += sizeof(uint64); + } + else if (message.len > UINT8_MAX) { + bytes[1] |= 0b01111110; + uint16 len = htons((uint16)message.len); + memcpy(bytes + 2, &len, sizeof(len)); + offset += sizeof(uint16); + } + else { + bytes[1] |= (uint8)message.len; + } + + uint32 mask = 0; + memcpy(bytes + offset, &mask, sizeof(mask)); + offset += sizeof(mask); + memcpy(bytes + offset, message.buf, message.len); + + return (buffer_t){ bytes, message.len + extra }; +} + +str_t wsDecodeMessage(arena_t *arena, buffer_t message) { + str_t out = STR_EMPTY; + uint8 *bytes = message.data; + + bool mask = bytes[1] & 0b10000000; + int offset = 2; + uint64 msglen = bytes[1] & 0b01111111; + + // 16bit msg len + if (msglen == 126) { + uint64 be_len = 0; + memcpy(&be_len, bytes + 2, sizeof(be_len)); + msglen = ntohs(be_len); + offset += sizeof(uint16); + } + // 64bit msg len + else if (msglen == 127) { + uint64 be_len = 0; + memcpy(&be_len, bytes + 2, sizeof(be_len)); + msglen = ntohll(be_len); + offset += sizeof(uint64); + } + + if (msglen == 0) { + warn("message length = 0"); + } + else if (mask) { + uint8 *decoded = alloc(arena, uint8, msglen + 1); + uint8 masks[4] = {0}; + memcpy(masks, bytes + offset, sizeof(masks)); + offset += 4; + + for (uint64 i = 0; i < msglen; ++i) { + decoded[i] = bytes[offset + i] ^ masks[i % 4]; + } + + out = (str_t){ (char *)decoded, msglen }; + } + else { + warn("mask bit not set!"); + } + + return out; +} diff --git a/websocket.h b/websocket.h new file mode 100644 index 0000000..3758e5f --- /dev/null +++ b/websocket.h @@ -0,0 +1,13 @@ +#pragma once + +#include "arena.h" +#include "str.h" + +#define WEBSOCKET_MAGIC "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" +#define WEBSOCKET_HTTP_KEY "Sec-WebSocket-Key" + +typedef uintptr_t socket_t; + +bool wsInitialiseSocket(arena_t scratch, socket_t websocket, strview_t key); +buffer_t wsEncodeMessage(arena_t *arena, strview_t message); +str_t wsDecodeMessage(arena_t *arena, buffer_t message); \ No newline at end of file diff --git a/colla/xml.c b/xml.c similarity index 99% rename from colla/xml.c rename to xml.c index b39cba2..0b9ab95 100644 --- a/colla/xml.c +++ b/xml.c @@ -57,7 +57,7 @@ strview_t xmlGetAttribute(xmltag_t *tag, strview_t key) { } a = a->next; } - return (strview_t){0}; + return STRV_EMPTY; } // == PRIVATE FUNCTIONS ======================================================================== diff --git a/colla/xml.h b/xml.h similarity index 100% rename from colla/xml.h rename to xml.h