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 0000000..0f45b2c Binary files /dev/null and b/docs/docs.com differ 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")); + } + else if (md->list.indent > cur_indent) { + markdown__close_list(md, out); + } + + md->list.indent = cur_indent; + ostrPuts(out, strv("
  • ")); + markdown__parse_line(md, strvRemovePrefix(line, 2), out, false, false); + ostrPuts(out, strv("
  • ")); + goto read_whole_line; + } + + // check if it is an
    + char hr_char = strvFront(line); + strview_t hr = strvTrim(line); + bool is_hr = true; + for (usize i = 0; i < hr.len; ++i) { + if (hr.buf[i] != hr_char) { + is_hr = false; + break; + } + } + + if (is_hr) { + ostrPuts(out, strv("
    ")); + goto read_whole_line; + } + else { + strview_t to_print = line; + int n = markdown__count_chars(&line, strvFront(line)); + to_print = strvSub(to_print, 0, n); + line = strvSub(line, n, SIZE_MAX); + ostrPuts(out, to_print); + } + + return line; +read_whole_line: + return STRV_EMPTY; +} + +static strview_t markdown__parse_olist(markdown_ctx_t *md, strview_t line, outstream_t *out) { + instream_t in = istrInitLen(line.buf, line.len); + + int32 number = 0; + if (!istrGetI32(&in, &number)) { + return line; + } + + if (istrPeek(&in) != '.') { + return line; + } + + istrSkip(&in, 1); + + if (istrPeek(&in) != ' ') { + return line; + } + + istrSkip(&in, 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++] = true; + ostrPuts(out, strv("
      \n")); + } + else if (md->list.indent > cur_indent) { + markdown__close_list(md, out); + } + + md->list.indent = cur_indent; + ostrPuts(out, strv("
    1. ")); + markdown__parse_line(md, strvRemovePrefix(line, istrTell(in)), out, false, false); + ostrPuts(out, strv("
    2. ")); + + return STRV_EMPTY; +} + +static strview_t markdown__parse_code_block(markdown_ctx_t *md, strview_t line, outstream_t *out) { + strview_t line_copy = line; + int ticks = markdown__count_chars(&line_copy, '`'); + + if (ticks != 3) { + goto finish; + } + + if (md->code.is_in_block) { + md->code.is_in_block = false; + if (md->curparser) { + md_parser_t *p = md->curparser; + if (p->finish) { + p->finish(p->userdata); + } + } + ostrPuts(out, strv("
    \n")); + line = line_copy; + goto finish; + } + + instream_t in = istrInitLen(line_copy.buf, line_copy.len); + strview_t lang = istrGetLine(&in); + + if (!strvIsEmpty(lang)) { + md->curparser = NULL; + md_options_t *opt = md->options; + if (opt) { + for (int i = 0; i < opt->parsers_count; ++i) { + if (strvEquals(lang, opt->parsers->lang)) { + md->curparser = &opt->parsers[i]; + break; + } + } + } + + if (!md->curparser) { + warn("markdown: no parser found for code block in language (%v)", lang); + } + else { + md_parser_t *p = md->curparser; + if (p->init) { + p->init(p->userdata); + } + } + } + + ostrPuts(out, strv("
      ")); + md->code.is_in_block = true; + + return STRV_EMPTY; +finish: + return line; +} + +static bool markdown__try_parse_url(instream_t *in, strview_t *out_url, strview_t *out_text) { + istrSkip(in, 1); // skip [ + + strview_t text = istrGetView(in, ']'); + istrSkip(in, 1); // skip ] + + strview_t url = STRV_EMPTY; + if (istrPeek(in) == '(') { + istrSkip(in, 1); // skip ( + url = istrGetView(in, ')'); + istrSkip(in, 1); // skip ) + } + + bool success = !strvIsEmpty(url); + + if (success) { + *out_url = url; + *out_text = text; + } + + return success; +} + +static void markdown__parse_line(markdown_ctx_t *md, strview_t line, outstream_t *out, bool add_newline, bool is_line_start) { + if (md->code.is_in_block && strvFront(line) != '`') { + md_parser_t *p = md->curparser; + if (p && p->callback) { + p->callback(md->raw_line, out, p->userdata); + } + else { + ostrPrintf(out, "%v\n", md->raw_line); + } + return; + } + + if (strvIsEmpty(line)) { + markdown__empty_line(md, out); + return; + } + + switch (strvFront(line)) { + // header + case '#': + line = markdown__parse_header(md, line, out); + break; + // unordered list or
      + case '-': case '*': case '_': + line = markdown__parse_ulist_or_line(md, line, out); + break; + // ordered list + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + line = markdown__parse_olist(md, line, out); + break; + // code block + case '`': + line = markdown__parse_code_block(md, line, out); + break; + default: + break; + } + + if (!strvIsEmpty(line) && is_line_start && !md->is_in_paragraph) { + md->is_in_paragraph = true; + ostrPuts(out, strv("

      \n")); + } + + for (usize i = 0; i < line.len; ++i) { + switch (line.buf[i]) { + // escape next character + case '\\': + if (++i < line.len) { + ostrPutc(out, line.buf[i]); + } + break; + // bold or italic + case '*': + { + strview_t sub = strvSub(line, i, SIZE_MAX); + int n = markdown__count_chars(&sub, '*'); + + bool is_both = n >= 3; + bool is_italic = n == 1 || is_both; + bool is_bold = n == 2 || is_both; + + if (is_italic) { + ostrPrintf(out, "<%s>", md->is_italic ? "/i" : "i"); + md->is_italic = !md->is_italic; + } + if (is_bold) { + ostrPrintf(out, "<%s>", md->is_bold ? "/b" : "b"); + md->is_bold = !md->is_bold; + } + if (is_both) { + for (int k = 3; k < n; ++k) { + ostrPutc(out, '*'); + } + } + i += n - 1; + break; + } + // url + case '[': + { + instream_t in = istrInitLen(line.buf + i, line.len - i); + strview_t url = STRV_EMPTY; + strview_t text = STRV_EMPTY; + if (markdown__try_parse_url(&in, &url, &text)) { + ostrPrintf(out, "%v", url, strvIsEmpty(text) ? url : text); + i += istrTell(in) - 1; + } + else{ + ostrPutc(out, line.buf[i]); + } + break; + } + // image + case '!': + { + instream_t in = istrInitLen(line.buf + i, line.len - i); + strview_t url = STRV_EMPTY; + strview_t text = STRV_EMPTY; + + istrSkip(&in, 1); // skip ! + + if (markdown__try_parse_url(&in, &url, &text)) { + ostrPrintf(out, "\"%v\"",'); + i += istrTell(in) - 1; + } + else{ + ostrPutc(out, line.buf[i]); + } + break; + } + // code block + case '`': + { + bool is_escaped = false; + if ((i + 1) < line.len) { + is_escaped = line.buf[i + 1] == '`'; + } + instream_t in = istrInitLen(line.buf + i, line.len - i); + + istrSkip(&in, is_escaped ? 2 : 1); // skip ` + ostrPuts(out, strv("")); + while (!istrIsFinished(in)) { + strview_t code = istrGetView(&in, '`'); + markdown__escape(code, out); + if (!is_escaped || istrPeek(&in) == '`') { + break; + } + ostrPutc(out, '`'); + } + ostrPuts(out, strv("")); + i += istrTell(in); + break; + } + default: + ostrPutc(out, line.buf[i]); + break; + } + } + + if (add_newline && !md->code.is_in_block) { + ostrPutc(out, '\n'); + } +} + +static void markdown__empty_line(markdown_ctx_t *md, outstream_t *out) { + // close lists + while (md->list.count > 0) { + if (md->list.list_is_ordered[--md->list.count]) { + ostrPuts(out, strv("

    \n")); + } + else { + 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