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(""tag">")
+
+#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("- "));
+ markdown__parse_line(md, strvRemovePrefix(line, istrTell(in)), out, false, false);
+ ostrPuts(out, strv("
"));
+
+ 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, "
');
+ 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]  -> 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