This commit is contained in:
snarmph 2024-11-29 16:10:48 +01:00
parent 82aee127b0
commit a92b119549
99 changed files with 6922 additions and 5723 deletions

View file

@ -20,16 +20,17 @@ static void arena__free_virtual(arena_t *arena);
static void arena__free_malloc(arena_t *arena); static void arena__free_malloc(arena_t *arena);
arena_t arenaInit(const arena_desc_t *desc) { arena_t arenaInit(const arena_desc_t *desc) {
arena_t out = {0};
if (desc) { if (desc) {
switch (desc->type) { switch (desc->type) {
case ARENA_VIRTUAL: return arena__make_virtual(desc->allocation); case ARENA_VIRTUAL: out = arena__make_virtual(desc->allocation); break;
case ARENA_MALLOC: return arena__make_malloc(desc->allocation); case ARENA_MALLOC: out = arena__make_malloc(desc->allocation); break;
case ARENA_STATIC: return arena__make_static(desc->static_buffer, desc->allocation); 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 out;
return (arena_t){0};
} }
void arenaCleanup(arena_t *arena) { void arenaCleanup(arena_t *arena) {
@ -50,17 +51,9 @@ void arenaCleanup(arena_t *arena) {
arena->type = 0; arena->type = 0;
} }
arena_t arenaScratch(arena_t *arena) { arena_t arenaScratch(arena_t *arena, usize size) {
if (!arena) { uint8 *buffer = alloc(arena, uint8, size, ALLOC_SOFT_FAIL | ALLOC_NOZERO);
return (arena_t){0}; return arena__make_static(buffer, buffer ? size : 0);
}
return (arena_t) {
.start = arena->current,
.current = arena->current,
.end = arena->end,
.type = arena->type,
};
} }
void *arenaAlloc(const arena_alloc_desc_t *desc) { void *arenaAlloc(const arena_alloc_desc_t *desc) {

View file

@ -2,7 +2,7 @@
#include "collatypes.h" #include "collatypes.h"
#ifdef __TINYC__ #if COLLA_TCC
#define alignof __alignof__ #define alignof __alignof__
#else #else
#define alignof _Alignof #define alignof _Alignof
@ -37,23 +37,27 @@ typedef struct {
arena_t *arena; arena_t *arena;
usize count; usize count;
alloc_flags_e flags; alloc_flags_e flags;
usize size;
usize align; usize align;
usize size;
} arena_alloc_desc_t; } arena_alloc_desc_t;
#ifndef ARENA_NO_SIZE_HELPERS
#define KB(count) ( (count) * 1024) #define KB(count) ( (count) * 1024)
#define MB(count) (KB(count) * 1024) #define MB(count) (KB(count) * 1024)
#define GB(count) (MB(count) * 1024) #define GB(count) (MB(count) * 1024)
#endif
// arena_type_e type, usize allocation, [ byte *static_buffer ] // arena_type_e type, usize allocation, [ byte *static_buffer ]
#define arenaMake(...) arenaInit(&(arena_desc_t){ __VA_ARGS__ }) #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__ }) #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); arena_t arenaInit(const arena_desc_t *desc);
void arenaCleanup(arena_t *arena); void arenaCleanup(arena_t *arena);
arena_t arenaScratch(arena_t *arena, usize size);
void *arenaAlloc(const arena_alloc_desc_t *desc); void *arenaAlloc(const arena_alloc_desc_t *desc);
usize arenaTell(arena_t *arena); usize arenaTell(arena_t *arena);
usize arenaRemaining(arena_t *arena); usize arenaRemaining(arena_t *arena);

View file

@ -4,7 +4,7 @@
#include "arena.h" #include "arena.h"
static char encoding_table[] = { static unsigned char encoding_table[] = {
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',

View file

@ -24,7 +24,7 @@ inline uint32 bitsCtz(uint32 v) {
return v ? __builtin_ctz(v) : 0; return v ? __builtin_ctz(v) : 0;
#elif BITS_WIN #elif BITS_WIN
uint32 trailing = 0; uint32 trailing = 0;
return _BitScanForward(&trailing, v) ? trailing : 0; return _BitScanForward((unsigned long *)&trailing, v) ? trailing : 0;
#else #else
return 0; return 0;
#endif #endif

51
build.c
View file

@ -1,8 +1,9 @@
#if COLLA_ONLYCORE #if COLLA_ONLYCORE
#define COLLA_NOTHREADS 1 #define COLLA_NOTHREADS 1
#define COLLA_NOSOCKETS 1 #define COLLA_NOSOCKETS 1
#define COLLA_NOHTTP 1 #define COLLA_NOHTTP 1
#define COLLA_NOSERVER 1 #define COLLA_NOSERVER 1
#define COLLA_NOHOTRELOAD 1
#endif #endif
#if COLLA_NOSOCKETS #if COLLA_NOSOCKETS
@ -12,32 +13,40 @@
#define COLLA_NOSERVER 1 #define COLLA_NOSERVER 1
#endif #endif
#include "src/arena.c" #include "arena.c"
#include "src/base64.c" #include "base64.c"
#include "src/file.c" #include "file.c"
#include "src/format.c" #include "format.c"
#include "src/ini.c" #include "ini.c"
#include "src/json.c" #include "json.c"
#include "src/str.c" #include "str.c"
#include "src/strstream.c" #include "strstream.c"
#include "src/tracelog.c" #include "tracelog.c"
#include "src/utf8.c" #include "utf8.c"
#include "src/vmem.c" #include "vmem.c"
#include "src/xml.c" #include "xml.c"
#include "src/hot_reload.c" #include "sha1.c"
#include "markdown.c"
#include "highlight.c"
#include "dir.c"
#if !COLLA_NOTHREADS #if !COLLA_NOTHREADS
#include "src/cthreads.c" #include "cthreads.c"
#endif #endif
#if !COLLA_NOSOCKETS #if !COLLA_NOSOCKETS
#include "src/socket.c" #include "socket.c"
#include "websocket.c"
#endif #endif
#if !COLLA_NOHTTP #if !COLLA_NOHTTP
#include "src/http.c" #include "http.c"
#endif #endif
#if !COLLA_NOSERVER #if !COLLA_NOSERVER
#include "src/server.c" #include "server.c"
#endif #endif
#if !COLLA_NOHOTRELOAD
#include "hot_reload.c"
#endif

View file

@ -41,6 +41,14 @@
#endif #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__) #if defined(__clang__)
#define COLLA_CLANG 1 #define COLLA_CLANG 1
@ -88,3 +96,17 @@
#define COLLA_CMT_LIB 0 #define COLLA_CMT_LIB 0
#endif #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))

View file

@ -1,281 +1,282 @@
#include "cthreads.h" #include "cthreads.h"
#include <stdlib.h> #include <stdlib.h>
typedef struct { // TODO: swap calloc for arenas
cthread_func_t func;
void *arg; typedef struct {
} _thr_internal_t; cthread_func_t func;
void *arg;
#if COLLA_WIN } _thr_internal_t;
#define WIN32_LEAN_AND_MEAN
#include <windows.h> #if COLLA_WIN
#include <windows.h>
// == THREAD ===========================================
// == THREAD ===========================================
static DWORD _thrFuncInternal(void *arg) {
_thr_internal_t *params = (_thr_internal_t *)arg; static DWORD WINAPI _thrFuncInternal(void *arg) {
cthread_func_t func = params->func; _thr_internal_t *params = (_thr_internal_t *)arg;
void *argument = params->arg; cthread_func_t func = params->func;
free(params); void *argument = params->arg;
return (DWORD)func(argument); free(params);
} return (DWORD)func(argument);
}
cthread_t thrCreate(cthread_func_t func, void *arg) {
HANDLE thread = INVALID_HANDLE_VALUE; cthread_t thrCreate(cthread_func_t func, void *arg) {
_thr_internal_t *params = calloc(1, sizeof(_thr_internal_t)); HANDLE thread = INVALID_HANDLE_VALUE;
_thr_internal_t *params = calloc(1, sizeof(_thr_internal_t));
if(params) {
params->func = func; if(params) {
params->arg = arg; params->func = func;
params->arg = arg;
thread = CreateThread(NULL, 0, _thrFuncInternal, params, 0, NULL);
} thread = CreateThread(NULL, 0, _thrFuncInternal, params, 0, NULL);
}
return (cthread_t)thread;
} return (cthread_t)thread;
}
bool thrValid(cthread_t ctx) {
return (HANDLE)ctx != INVALID_HANDLE_VALUE; bool thrValid(cthread_t ctx) {
} return (HANDLE)ctx != INVALID_HANDLE_VALUE;
}
bool thrDetach(cthread_t ctx) {
return CloseHandle((HANDLE)ctx); bool thrDetach(cthread_t ctx) {
} return CloseHandle((HANDLE)ctx);
}
cthread_t thrCurrent(void) {
return (cthread_t)GetCurrentThread(); cthread_t thrCurrent(void) {
} return (cthread_t)GetCurrentThread();
}
int thrCurrentId(void) {
return GetCurrentThreadId(); int thrCurrentId(void) {
} return GetCurrentThreadId();
}
int thrGetId(cthread_t ctx) {
#if COLLA_TCC int thrGetId(cthread_t ctx) {
return 0; #if COLLA_TCC
#endif return 0;
return GetThreadId((HANDLE)ctx); #endif
} return GetThreadId((HANDLE)ctx);
}
void thrExit(int code) {
ExitThread(code); void thrExit(int code) {
} ExitThread(code);
}
bool thrJoin(cthread_t ctx, int *code) {
if(!ctx) return false; bool thrJoin(cthread_t ctx, int *code) {
int return_code = WaitForSingleObject((HANDLE)ctx, INFINITE); if(!ctx) return false;
if(code) *code = return_code; int return_code = WaitForSingleObject((HANDLE)ctx, INFINITE);
BOOL success = CloseHandle((HANDLE)ctx); if(code) *code = return_code;
return return_code != WAIT_FAILED && success; BOOL success = CloseHandle((HANDLE)ctx);
} return return_code != WAIT_FAILED && success;
}
// == MUTEX ============================================
// == MUTEX ============================================
cmutex_t mtxInit(void) {
CRITICAL_SECTION *crit_sec = calloc(1, sizeof(CRITICAL_SECTION)); cmutex_t mtxInit(void) {
if(crit_sec) { CRITICAL_SECTION *crit_sec = calloc(1, sizeof(CRITICAL_SECTION));
InitializeCriticalSection(crit_sec); if(crit_sec) {
} InitializeCriticalSection(crit_sec);
return (cmutex_t)crit_sec; }
} return (cmutex_t)crit_sec;
}
void mtxFree(cmutex_t ctx) {
DeleteCriticalSection((CRITICAL_SECTION *)ctx); void mtxFree(cmutex_t ctx) {
free((CRITICAL_SECTION *)ctx); DeleteCriticalSection((CRITICAL_SECTION *)ctx);
} free((CRITICAL_SECTION *)ctx);
}
bool mtxValid(cmutex_t ctx) {
return (void *)ctx != NULL; bool mtxValid(cmutex_t ctx) {
} return (void *)ctx != NULL;
}
bool mtxLock(cmutex_t ctx) {
EnterCriticalSection((CRITICAL_SECTION *)ctx); bool mtxLock(cmutex_t ctx) {
return true; EnterCriticalSection((CRITICAL_SECTION *)ctx);
} return true;
}
bool mtxTryLock(cmutex_t ctx) {
return TryEnterCriticalSection((CRITICAL_SECTION *)ctx); bool mtxTryLock(cmutex_t ctx) {
} return TryEnterCriticalSection((CRITICAL_SECTION *)ctx);
}
bool mtxUnlock(cmutex_t ctx) {
LeaveCriticalSection((CRITICAL_SECTION *)ctx); bool mtxUnlock(cmutex_t ctx) {
return true; LeaveCriticalSection((CRITICAL_SECTION *)ctx);
} return true;
}
#if !COLLA_NO_CONDITION_VAR
// == CONDITION VARIABLE =============================== #if !COLLA_NO_CONDITION_VAR
// == CONDITION VARIABLE ===============================
#include "tracelog.h"
#include "tracelog.h"
condvar_t condInit(void) {
CONDITION_VARIABLE *cond = calloc(1, sizeof(CONDITION_VARIABLE)); condvar_t condInit(void) {
InitializeConditionVariable(cond); CONDITION_VARIABLE *cond = calloc(1, sizeof(CONDITION_VARIABLE));
return (condvar_t)cond; InitializeConditionVariable(cond);
} return (condvar_t)cond;
}
void condFree(condvar_t cond) {
free((CONDITION_VARIABLE *)cond); void condFree(condvar_t cond) {
} free((CONDITION_VARIABLE *)cond);
}
void condWake(condvar_t cond) {
WakeConditionVariable((CONDITION_VARIABLE *)cond); void condWake(condvar_t cond) {
} WakeConditionVariable((CONDITION_VARIABLE *)cond);
}
void condWakeAll(condvar_t cond) {
WakeAllConditionVariable((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 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); void condWaitTimed(condvar_t cond, cmutex_t mtx, int milliseconds) {
} SleepConditionVariableCS((CONDITION_VARIABLE *)cond, (CRITICAL_SECTION *)mtx, milliseconds);
}
#endif
#endif
#else
#include <pthread.h> #else
#include <unistd.h> #include <pthread.h>
#include <sys/syscall.h> #include <unistd.h>
#include <sys/types.h> #include <sys/syscall.h>
#include <sys/types.h>
// == THREAD ===========================================
// == THREAD ===========================================
#define INT_TO_VOIDP(a) ((void *)((uintptr_t)(a)))
#define INT_TO_VOIDP(a) ((void *)((uintptr_t)(a)))
static void *_thrFuncInternal(void *arg) {
_thr_internal_t *params = (_thr_internal_t *)arg; static void *_thrFuncInternal(void *arg) {
cthread_func_t func = params->func; _thr_internal_t *params = (_thr_internal_t *)arg;
void *argument = params->arg; cthread_func_t func = params->func;
free(params); void *argument = params->arg;
return INT_TO_VOIDP(func(argument)); free(params);
} return INT_TO_VOIDP(func(argument));
}
cthread_t thrCreate(cthread_func_t func, void *arg) {
pthread_t handle = (pthread_t)NULL; 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));
_thr_internal_t *params = calloc(1, sizeof(_thr_internal_t));
if(params) {
params->func = func; if(params) {
params->arg = arg; params->func = func;
params->arg = arg;
int result = pthread_create(&handle, NULL, _thrFuncInternal, params);
if(result) handle = (pthread_t)NULL; int result = pthread_create(&handle, NULL, _thrFuncInternal, params);
} if(result) handle = (pthread_t)NULL;
}
return (cthread_t)handle;
} return (cthread_t)handle;
}
bool thrValid(cthread_t ctx) {
return (void *)ctx != NULL; bool thrValid(cthread_t ctx) {
} return (void *)ctx != NULL;
}
bool thrDetach(cthread_t ctx) {
return pthread_detach((pthread_t)ctx) == 0; bool thrDetach(cthread_t ctx) {
} return pthread_detach((pthread_t)ctx) == 0;
}
cthread_t thrCurrent(void) {
return (cthread_t)pthread_self(); cthread_t thrCurrent(void) {
} return (cthread_t)pthread_self();
}
int thrCurrentId(void) {
return (int)pthread_self(); int thrCurrentId(void) {
} return (int)pthread_self();
}
int thrGetId(cthread_t ctx) {
return (int)ctx; int thrGetId(cthread_t ctx) {
} return (int)ctx;
}
void thrExit(int code) {
pthread_exit(INT_TO_VOIDP(code)); void thrExit(int code) {
} pthread_exit(INT_TO_VOIDP(code));
}
bool thrJoin(cthread_t ctx, int *code) {
void *result = code; bool thrJoin(cthread_t ctx, int *code) {
return pthread_join((pthread_t)ctx, &result) != 0; void *result = code;
} return pthread_join((pthread_t)ctx, &result) != 0;
}
// == MUTEX ============================================
// == MUTEX ============================================
cmutex_t mtxInit(void) {
pthread_mutex_t *mutex = calloc(1, sizeof(pthread_mutex_t)); cmutex_t mtxInit(void) {
pthread_mutex_t *mutex = calloc(1, sizeof(pthread_mutex_t));
if(mutex) {
if(pthread_mutex_init(mutex, NULL)) { if(mutex) {
free(mutex); if(pthread_mutex_init(mutex, NULL)) {
mutex = NULL; free(mutex);
} mutex = NULL;
} }
}
return (cmutex_t)mutex;
} return (cmutex_t)mutex;
}
void mtxFree(cmutex_t ctx) {
pthread_mutex_destroy((pthread_mutex_t *)ctx); void mtxFree(cmutex_t ctx) {
} pthread_mutex_destroy((pthread_mutex_t *)ctx);
}
bool mtxValid(cmutex_t ctx) {
return (void *)ctx != NULL; bool mtxValid(cmutex_t ctx) {
} return (void *)ctx != NULL;
}
bool mtxLock(cmutex_t ctx) {
return pthread_mutex_lock((pthread_mutex_t *)ctx) == 0; 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 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; bool mtxUnlock(cmutex_t ctx) {
} return pthread_mutex_unlock((pthread_mutex_t *)ctx) == 0;
}
#if !COLLA_NO_CONDITION_VAR
#if !COLLA_NO_CONDITION_VAR
// == CONDITION VARIABLE ===============================
// == CONDITION VARIABLE ===============================
condvar_t condInit(void) {
pthread_cond_t *cond = calloc(1, sizeof(pthread_cond_t)); condvar_t condInit(void) {
pthread_cond_t *cond = calloc(1, sizeof(pthread_cond_t));
if(cond) {
if(pthread_cond_init(cond, NULL)) { if(cond) {
free(cond); if(pthread_cond_init(cond, NULL)) {
cond = NULL; free(cond);
} cond = NULL;
} }
}
return (condvar_t)cond;
} return (condvar_t)cond;
}
void condFree(condvar_t cond) {
if (!cond) return; void condFree(condvar_t cond) {
pthread_cond_destroy((pthread_cond_t *)cond); if (!cond) return;
free((pthread_cond_t *)cond); 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 condWake(condvar_t cond) {
} pthread_cond_signal((pthread_cond_t *)cond);
}
void condWakeAll(condvar_t cond) {
pthread_cond_broadcast((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 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; void condWaitTimed(condvar_t cond, cmutex_t mtx, int milliseconds) {
time(&timeout.tv_sec); struct timespec timeout;
timeout.tv_nsec += milliseconds * 1000000; time(&timeout.tv_sec);
pthread_cond_timedwait((pthread_cond_t *)cond, (pthread_mutex_t *)mtx, &timeout); timeout.tv_nsec += milliseconds * 1000000;
} pthread_cond_timedwait((pthread_cond_t *)cond, (pthread_mutex_t *)mtx, &timeout);
}
#endif
#endif
#endif
#endif

View file

@ -1,57 +1,57 @@
#pragma once #pragma once
#include "collatypes.h" #include "collatypes.h"
#if COLLA_TCC #if COLLA_TCC
#define COLLA_NO_CONDITION_VAR 1 #define COLLA_NO_CONDITION_VAR 1
#endif #endif
typedef struct arena_t arena_t; typedef struct arena_t arena_t;
// == THREAD =========================================== // == THREAD ===========================================
typedef uintptr_t cthread_t; typedef uintptr_t cthread_t;
typedef int (*cthread_func_t)(void *); typedef int (*cthread_func_t)(void *);
cthread_t thrCreate(cthread_func_t func, void *arg); cthread_t thrCreate(cthread_func_t func, void *arg);
bool thrValid(cthread_t ctx); bool thrValid(cthread_t ctx);
bool thrDetach(cthread_t ctx); bool thrDetach(cthread_t ctx);
cthread_t thrCurrent(void); cthread_t thrCurrent(void);
int thrCurrentId(void); int thrCurrentId(void);
int thrGetId(cthread_t ctx); int thrGetId(cthread_t ctx);
void thrExit(int code); void thrExit(int code);
bool thrJoin(cthread_t ctx, int *code); bool thrJoin(cthread_t ctx, int *code);
// == MUTEX ============================================ // == MUTEX ============================================
typedef uintptr_t cmutex_t; typedef uintptr_t cmutex_t;
cmutex_t mtxInit(void); cmutex_t mtxInit(void);
void mtxFree(cmutex_t ctx); void mtxFree(cmutex_t ctx);
bool mtxValid(cmutex_t ctx); bool mtxValid(cmutex_t ctx);
bool mtxLock(cmutex_t ctx); bool mtxLock(cmutex_t ctx);
bool mtxTryLock(cmutex_t ctx); bool mtxTryLock(cmutex_t ctx);
bool mtxUnlock(cmutex_t ctx); bool mtxUnlock(cmutex_t ctx);
#if !COLLA_NO_CONDITION_VAR #if !COLLA_NO_CONDITION_VAR
// == CONDITION VARIABLE =============================== // == CONDITION VARIABLE ===============================
typedef uintptr_t condvar_t; typedef uintptr_t condvar_t;
#define COND_WAIT_INFINITE 0xFFFFFFFF #define COND_WAIT_INFINITE 0xFFFFFFFF
condvar_t condInit(void); condvar_t condInit(void);
void condFree(condvar_t cond); void condFree(condvar_t cond);
void condWake(condvar_t cond); void condWake(condvar_t cond);
void condWakeAll(condvar_t cond); void condWakeAll(condvar_t cond);
void condWait(condvar_t cond, cmutex_t mtx); void condWait(condvar_t cond, cmutex_t mtx);
void condWaitTimed(condvar_t cond, cmutex_t mtx, int milliseconds); void condWaitTimed(condvar_t cond, cmutex_t mtx, int milliseconds);
#endif #endif

View file

@ -1,322 +0,0 @@
#include "coropool.h"
#if 0
#include <stdlib.h>
#include <tracelog.h>
#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 <vec.h>
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;
}

View file

@ -1,17 +0,0 @@
#pragma once
#include <collatypes.h>
#include <cthreads.h>
#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);

View file

@ -1,11 +0,0 @@
#include "coroutine.h"
coroutine_t coInit() {
return (coroutine_t) {
.state = 0
};
}
bool coIsDead(coroutine_t co) {
return co.state == -1;
}

View file

@ -1,133 +0,0 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <string.h> // 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

View file

@ -1,204 +0,0 @@
#include "dir.h"
#include "tracelog.h"
#if COLLA_WIN
#include "win32_slim.h"
#include <stdlib.h>
#include <assert.h>
#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 <dirent.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
// 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 <stdio.h>
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
}

View file

@ -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

View file

@ -1,295 +0,0 @@
#include "dirwatch.h"
#include <stdint.h>
#include <stdlib.h>
#include <assert.h>
#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 <sys/inotify.h>
#include <unistd.h> // read
#include <string.h>
#include <errno.h>
#include <linux/limits.h> // MAX_PATH
#include <stdatomic.h>
#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

View file

@ -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

View file

@ -1,134 +0,0 @@
#include "fs.h"
#include <time.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include "tracelog.h"
#if COLLA_WIN
#include "win32_slim.h"
#include <sys/stat.h>
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 <sys/stat.h>
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

View file

@ -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

View file

@ -1,130 +0,0 @@
#include "hashmap.h"
#include <string.h>
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));
}

View file

@ -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);

View file

@ -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;
}

View file

@ -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);

View file

@ -1,107 +0,0 @@
#include "os.h"
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#if COLLA_WIN
#define _BUFSZ 128
#include <lmcons.h>
// 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 <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <ctype.h>
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

View file

@ -1,30 +0,0 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <stddef.h>
#include <string.h>
#include "str.h"
#include "collatypes.h"
#if COLLA_WIN
#include <stdio.h>
#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 <stdio.h>
int stricmp(const char *a, const char *b);
#endif
str_t getUserName();
#ifdef __cplusplus
} // extern "C"
#endif

163
dir.c Normal file
View file

@ -0,0 +1,163 @@
#include "dir.h"
#if COLLA_WIN
#include <windows.h>
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 <dirent.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
// 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

25
dir.h Normal file
View file

@ -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);

131
docs/arena.md Normal file
View file

@ -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);
}
```

32
docs/base64.md Normal file
View file

@ -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);
}
```

49
docs/cthreads.md Normal file
View file

@ -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);
}
```

52
docs/dir.md Normal file
View file

@ -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);
}
```

BIN
docs/docs.com Normal file

Binary file not shown.

41
docs/file.md Normal file
View file

@ -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`

28
docs/format.md Normal file
View file

@ -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);
}
```

37
docs/highlight.md Normal file
View file

@ -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("</span>"),
[HL_COLOR_PREPROC] = strv("<span class=\"c-preproc\">"),
[HL_COLOR_TYPES] = strv("<span class=\"c-types\">"),
[HL_COLOR_CUSTOM_TYPES] = strv("<span class=\"c-custom-types\">"),
[HL_COLOR_KEYWORDS] = strv("<span class=\"c-kwrds\">"),
[HL_COLOR_NUMBER] = strv("<span class=\"c-num\">"),
[HL_COLOR_STRING] = strv("<span class=\"c-str\">"),
[HL_COLOR_COMMENT] = strv("<span class=\"c-cmnt\">"),
[HL_COLOR_FUNC] = strv("<span class=\"c-func\">"),
[HL_COLOR_SYMBOL] = strv("<span class=\"c-sym\">"),
[HL_COLOR_MACRO] = strv("<span class=\"c-macro\">"),
},
.flags = HL_FLAG_HTML,
});
}
```

79
docs/hot_reload.md Normal file
View file

@ -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);
}
```

45
docs/html.md Normal file
View file

@ -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 `<img>` or `<hr>` which only have an opening tag
* Basic tags which follow this format: `<tag>content</tag>`
* 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(<script src="script.js"></script>)
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;
}
```

5
docs/http.md Normal file
View file

@ -0,0 +1,5 @@
---
title = HTTP
---
# HTTP
----------

5
docs/ini.md Normal file
View file

@ -0,0 +1,5 @@
---
title = Ini
---
# Ini
----------

5
docs/json.md Normal file
View file

@ -0,0 +1,5 @@
---
title = Json
---
# Json
----------

5
docs/markdown.md Normal file
View file

@ -0,0 +1,5 @@
---
title = Markdown
---
# Markdown
----------

5
docs/readme.md Normal file
View file

@ -0,0 +1,5 @@
---
title = Readme
---
# Readme
----------

5
docs/server.md Normal file
View file

@ -0,0 +1,5 @@
---
title = Server
---
# Server
----------

5
docs/sha1.md Normal file
View file

@ -0,0 +1,5 @@
---
title = SHA-1
---
# SHA-1
----------

5
docs/socket.md Normal file
View file

@ -0,0 +1,5 @@
---
title = Socket
---
# Socket
----------

5
docs/str.md Normal file
View file

@ -0,0 +1,5 @@
---
title = Str
---
# Str
----------

5
docs/strstream.md Normal file
View file

@ -0,0 +1,5 @@
---
title = StrStream
---
# StrStream
----------

5
docs/tracelog.md Normal file
View file

@ -0,0 +1,5 @@
---
title = Tracelog
---
# Tracelog
----------

5
docs/utf8.md Normal file
View file

@ -0,0 +1,5 @@
---
title = UTF-8
---
# UTF-8
----------

5
docs/vec.md Normal file
View file

@ -0,0 +1,5 @@
---
title = Vec
---
# Vec
----------

5
docs/vmem.md Normal file
View file

@ -0,0 +1,5 @@
---
title = VMem
---
# VMem
----------

5
docs/websocket.md Normal file
View file

@ -0,0 +1,5 @@
---
title = WebSocket
---
# WebSocket
----------

5
docs/xml.md Normal file
View file

@ -0,0 +1,5 @@
---
title = Xml
---
# Xml
----------

View file

@ -1,360 +1,367 @@
#include "file.h" #include "file.h"
#include "warnings/colla_warn_beg.h" #include "warnings/colla_warn_beg.h"
#include "tracelog.h" #include "tracelog.h"
#include "format.h" #include "format.h"
#if COLLA_WIN #if COLLA_WIN
#define WIN32_LEAN_AND_MEAN #include <windows.h>
#include <windows.h>
#undef FILE_BEGIN
#undef FILE_BEGIN #undef FILE_CURRENT
#undef FILE_CURRENT #undef FILE_END
#undef FILE_END
#define FILE_BEGIN 0
#define FILE_BEGIN 0 #define FILE_CURRENT 1
#define FILE_CURRENT 1 #define FILE_END 2
#define FILE_END 2
#if COLLA_TCC
#if COLLA_TCC #include "tcc/colla_tcc.h"
#include "tcc/colla_tcc.h" #endif
#endif
static DWORD file__mode_to_access(filemode_e mode) {
static DWORD file__mode_to_access(filemode_e mode) { if (mode & FILE_APPEND) return FILE_APPEND_DATA;
if (mode & FILE_APPEND) return FILE_APPEND_DATA;
DWORD out = 0;
DWORD out = 0; if (mode & FILE_READ) out |= GENERIC_READ;
if (mode & FILE_READ) out |= GENERIC_READ; if (mode & FILE_WRITE) out |= GENERIC_WRITE;
if (mode & FILE_WRITE) out |= GENERIC_WRITE; return out;
return out; }
}
static DWORD file__mode_to_creation(filemode_e mode) {
static DWORD file__mode_to_creation(filemode_e mode) { if (mode == FILE_READ) return OPEN_EXISTING;
if (mode == FILE_READ) return OPEN_EXISTING; if (mode == FILE_WRITE) return CREATE_ALWAYS;
if (mode == FILE_WRITE) return CREATE_ALWAYS; return OPEN_ALWAYS;
return OPEN_ALWAYS; }
}
bool fileExists(const char *name) {
bool fileExists(const char *name) { return GetFileAttributesA(name) != INVALID_FILE_ATTRIBUTES;
return GetFileAttributesA(name) != INVALID_FILE_ATTRIBUTES; }
}
TCHAR *fileGetFullPath(arena_t *arena, strview_t filename) {
TCHAR *fileGetFullPath(arena_t *arena, strview_t filename) { TCHAR long_path_prefix[] = TEXT("\\\\?\\");
TCHAR long_path_prefix[] = TEXT("\\\\?\\"); const usize prefix_len = arrlen(long_path_prefix) - 1;
const usize prefix_len = arrlen(long_path_prefix) - 1;
uint8 tempbuf[4096];
uint8 tempbuf[4096]; arena_t scratch = arenaMake(ARENA_STATIC, sizeof(tempbuf), tempbuf);
arena_t scratch = arenaMake(ARENA_STATIC, sizeof(tempbuf), tempbuf);
TCHAR *rel_path = strvToTChar(&scratch, filename);
TCHAR *rel_path = strvToTChar(&scratch, filename); DWORD pathlen = GetFullPathName(rel_path, 0, NULL, NULL);
DWORD pathlen = GetFullPathName(rel_path, 0, NULL, NULL);
TCHAR *full_path = alloc(arena, TCHAR, pathlen + prefix_len + 1);
TCHAR *full_path = alloc(arena, TCHAR, pathlen + prefix_len + 1); memcpy(full_path, long_path_prefix, prefix_len * sizeof(TCHAR));
memcpy(full_path, long_path_prefix, prefix_len * sizeof(TCHAR));
GetFullPathName(rel_path, pathlen + 1, full_path + prefix_len, NULL);
GetFullPathName(rel_path, pathlen + 1, full_path + prefix_len, NULL);
return full_path;
return full_path; }
}
bool fileDelete(arena_t scratch, strview_t filename) {
strview_t fileGetFilename(strview_t path) { wchar_t *wfname = strvToWChar(&scratch, filename, NULL);
usize last_lin = strvRFind(path, '/', 0); return DeleteFileW(wfname);
usize last_win = strvRFind(path, '\\', 0); }
last_lin = last_lin != SIZE_MAX ? last_lin : 0;
last_win = last_win != SIZE_MAX ? last_win : 0; file_t fileOpen(arena_t scratch, strview_t name, filemode_e mode) {
usize last = max(last_lin, last_win); TCHAR *full_path = fileGetFullPath(&scratch, name);
return strvSub(path, last ? last + 1 : last, SIZE_MAX);
} HANDLE handle = CreateFile(
full_path,
strview_t fileGetExtension(strview_t path) { file__mode_to_access(mode),
usize ext_pos = strvRFind(path, '.', 0); FILE_SHARE_READ,
return strvSub(path, ext_pos, SIZE_MAX); NULL,
} file__mode_to_creation(mode),
FILE_ATTRIBUTE_NORMAL,
void fileSplitPath(strview_t path, strview_t *dir, strview_t *name, strview_t *ext) { NULL
usize dir_lin = strvRFind(path, '/', 0); );
usize dir_win = strvRFind(path, '\\', 0);
dir_lin = dir_lin != STR_NONE ? dir_lin : 0; return (file_t){
dir_win = dir_win != STR_NONE ? dir_win : 0; .handle = (uintptr_t)handle,
usize dir_pos = max(dir_lin, dir_win); };
}
usize ext_pos = strvRFind(path, '.', 0);
void fileClose(file_t ctx) {
if (dir) { if (!fileIsValid(ctx)) return;
*dir = strvSub(path, 0, dir_pos); CloseHandle((HANDLE)ctx.handle);
} }
if (name) {
*name = strvSub(path, dir_pos ? dir_pos + 1 : dir_pos, ext_pos); bool fileIsValid(file_t ctx) {
} return (HANDLE)ctx.handle != 0 &&
if (ext) { (HANDLE)ctx.handle != INVALID_HANDLE_VALUE;
*ext = strvSub(path, ext_pos, SIZE_MAX); }
}
} usize fileRead(file_t ctx, void *buf, usize len) {
if (!fileIsValid(ctx)) return 0;
bool fileDelete(arena_t scratch, strview_t filename) { DWORD read = 0;
wchar_t *wfname = strvToWChar(&scratch, filename, NULL); ReadFile((HANDLE)ctx.handle, buf, (DWORD)len, &read, NULL);
return DeleteFileW(wfname); return (usize)read;
} }
file_t fileOpen(arena_t scratch, strview_t name, filemode_e mode) { usize fileWrite(file_t ctx, const void *buf, usize len) {
TCHAR *full_path = fileGetFullPath(&scratch, name); if (!fileIsValid(ctx)) return 0;
DWORD written = 0;
HANDLE handle = CreateFile( WriteFile((HANDLE)ctx.handle, buf, (DWORD)len, &written, NULL);
full_path, return (usize)written;
file__mode_to_access(mode), }
FILE_SHARE_READ,
NULL, bool fileSeekEnd(file_t ctx) {
file__mode_to_creation(mode), if (!fileIsValid(ctx)) return false;
FILE_ATTRIBUTE_NORMAL, DWORD result = SetFilePointer((HANDLE)ctx.handle, 0, NULL, FILE_END);
NULL return result != INVALID_SET_FILE_POINTER;
); }
return (file_t){ void fileRewind(file_t ctx) {
.handle = (uintptr_t)handle, if (!fileIsValid(ctx)) return;
}; SetFilePointer((HANDLE)ctx.handle, 0, NULL, FILE_BEGIN);
} }
void fileClose(file_t ctx) { usize fileTell(file_t ctx) {
if (!fileIsValid(ctx)) return; if (!fileIsValid(ctx)) return 0;
CloseHandle((HANDLE)ctx.handle); LARGE_INTEGER tell = {0};
} BOOL result = SetFilePointerEx((HANDLE)ctx.handle, (LARGE_INTEGER){0}, &tell, FILE_CURRENT);
return result == TRUE ? (usize)tell.QuadPart : 0;
bool fileIsValid(file_t ctx) { }
return (HANDLE)ctx.handle != 0 &&
(HANDLE)ctx.handle != INVALID_HANDLE_VALUE; usize fileSize(file_t ctx) {
} if (!fileIsValid(ctx)) return 0;
LARGE_INTEGER size = {0};
usize fileRead(file_t ctx, void *buf, usize len) { BOOL result = GetFileSizeEx((HANDLE)ctx.handle, &size);
if (!fileIsValid(ctx)) return 0; return result == TRUE ? (usize)size.QuadPart : 0;
DWORD read = 0; }
ReadFile((HANDLE)ctx.handle, buf, (DWORD)len, &read, NULL);
return (usize)read; uint64 fileGetTimeFP(file_t ctx) {
} if (!fileIsValid(ctx)) return 0;
FILETIME time = {0};
usize fileWrite(file_t ctx, const void *buf, usize len) { GetFileTime((HANDLE)ctx.handle, NULL, NULL, &time);
if (!fileIsValid(ctx)) return 0; ULARGE_INTEGER utime = {
DWORD written = 0; .HighPart = time.dwHighDateTime,
WriteFile((HANDLE)ctx.handle, buf, (DWORD)len, &written, NULL); .LowPart = time.dwLowDateTime,
return (usize)written; };
} return (uint64)utime.QuadPart;
}
bool fileSeekEnd(file_t ctx) {
if (!fileIsValid(ctx)) return false; #else
DWORD result = SetFilePointer((HANDLE)ctx.handle, 0, NULL, FILE_END);
return result != INVALID_SET_FILE_POINTER; #include <stdio.h>
}
static const char *file__mode_to_stdio(filemode_e mode) {
void fileRewind(file_t ctx) { if (mode == FILE_READ) return "rb";
if (!fileIsValid(ctx)) return; if (mode == FILE_WRITE) return "wb";
SetFilePointer((HANDLE)ctx.handle, 0, NULL, FILE_BEGIN); if (mode == FILE_APPEND) return "ab";
} if (mode == (FILE_READ | FILE_WRITE)) return "rb+";
usize fileTell(file_t ctx) { return "ab+";
if (!fileIsValid(ctx)) return 0; }
LARGE_INTEGER tell = {0};
BOOL result = SetFilePointerEx((HANDLE)ctx.handle, (LARGE_INTEGER){0}, &tell, FILE_CURRENT); bool fileExists(const char *name) {
return result == TRUE ? (usize)tell.QuadPart : 0; FILE *fp = fopen(name, "rb");
} bool exists = fp != NULL;
fclose(fp);
usize fileSize(file_t ctx) { return exists;
if (!fileIsValid(ctx)) return 0; }
LARGE_INTEGER size = {0};
BOOL result = GetFileSizeEx((HANDLE)ctx.handle, &size); bool fileDelete(arena_t scratch, strview_t filename) {
return result == TRUE ? (usize)size.QuadPart : 0; str_t name = str(&scratch, filename);
} return remove(name.buf) == 0;
}
uint64 fileGetTimeFP(file_t ctx) {
if (!fileIsValid(ctx)) return 0; file_t fileOpen(arena_t scratch, strview_t name, filemode_e mode) {
FILETIME time = {0}; str_t filename = str(&scratch, name);
GetFileTime((HANDLE)ctx.handle, NULL, NULL, &time); return (file_t) {
ULARGE_INTEGER utime = { .handle = (uintptr_t)fopen(filename.buf, file__mode_to_stdio(mode))
.HighPart = time.dwHighDateTime, };
.LowPart = time.dwLowDateTime, }
};
return (uint64)utime.QuadPart; void fileClose(file_t ctx) {
} FILE *fp = (FILE *)ctx.handle;
if (fp) {
#else fclose(fp);
}
#include <stdio.h> }
static const char *file__mode_to_stdio(filemode_e mode) { bool fileIsValid(file_t ctx) {
if (mode == FILE_READ) return "rb"; bool is_valid = (FILE *)ctx.handle != NULL;
if (mode == FILE_WRITE) return "wb"; if (!is_valid) warn("file not valid");
if (mode == FILE_APPEND) return "ab"; return is_valid;
if (mode == (FILE_READ | FILE_WRITE)) return "rb+"; }
return "ab+"; usize fileRead(file_t ctx, void *buf, usize len) {
} if (!fileIsValid(ctx)) return 0;
return fread(buf, 1, len, (FILE *)ctx.handle);
bool fileExists(const char *name) { }
FILE *fp = fopen(name, "rb");
bool exists = fp != NULL; usize fileWrite(file_t ctx, const void *buf, usize len) {
fclose(fp); if (!fileIsValid(ctx)) return 0;
return exists; return fwrite(buf, 1, len, (FILE *)ctx.handle);
} }
file_t fileOpen(arena_t scratch, strview_t name, filemode_e mode) { bool fileSeekEnd(file_t ctx) {
str_t filename = str(&scratch, name); if (!fileIsValid(ctx)) return false;
return (file_t) { return fseek((FILE *)ctx.handle, 0, SEEK_END) == 0;
.handle = (uintptr_t)fopen(filename.buf, file__mode_to_stdio(mode)) }
};
} void fileRewind(file_t ctx) {
if (!fileIsValid(ctx)) return;
void fileClose(file_t ctx) { fseek((FILE *)ctx.handle, 0, SEEK_SET);
FILE *fp = (FILE *)ctx.handle; }
if (fp) {
fclose(fp); usize fileTell(file_t ctx) {
} if (!fileIsValid(ctx)) return 0;
} return ftell((FILE *)ctx.handle);
}
bool fileIsValid(file_t ctx) {
bool is_valid = (FILE *)ctx.handle != NULL; usize fileSize(file_t ctx) {
if (!is_valid) warn("file not valid"); if (!fileIsValid(ctx)) return 0;
return is_valid; FILE *fp = (FILE *)ctx.handle;
} fseek(fp, 0, SEEK_END);
long len = ftell(fp);
usize fileRead(file_t ctx, void *buf, usize len) { fseek(fp, 0, SEEK_SET);
if (!fileIsValid(ctx)) return 0; return (usize)len;
return fread(buf, 1, len, (FILE *)ctx.handle); }
}
uint64 fileGetTimeFP(file_t ctx) {
usize fileWrite(file_t ctx, const void *buf, usize len) { #if COLLA_LIN
if (!fileIsValid(ctx)) return 0; return 0;
return fwrite(buf, 1, len, (FILE *)ctx.handle); #else
} fatal("fileGetTime not implemented yet outside of linux and windows");
return 0;
bool fileSeekEnd(file_t ctx) { #endif
if (!fileIsValid(ctx)) return false; }
return fseek((FILE *)ctx.handle, 0, SEEK_END) == 0;
} #endif
void fileRewind(file_t ctx) { strview_t fileGetFilename(strview_t path) {
if (!fileIsValid(ctx)) return; usize last_lin = strvRFind(path, '/', 0);
fseek((FILE *)ctx.handle, 0, SEEK_SET); 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 fileTell(file_t ctx) { usize last = max(last_lin, last_win);
if (!fileIsValid(ctx)) return 0; return strvSub(path, last ? last + 1 : last, SIZE_MAX);
return ftell((FILE *)ctx.handle); }
}
strview_t fileGetExtension(strview_t path) {
usize fileSize(file_t ctx) { usize ext_pos = strvRFind(path, '.', 0);
if (!fileIsValid(ctx)) return 0; return strvSub(path, ext_pos, SIZE_MAX);
FILE *fp = (FILE *)ctx.handle; }
fseek(fp, 0, SEEK_END);
long len = ftell(fp); void fileSplitPath(strview_t path, strview_t *dir, strview_t *name, strview_t *ext) {
fseek(fp, 0, SEEK_SET); usize dir_lin = strvRFind(path, '/', 0);
return (usize)len; usize dir_win = strvRFind(path, '\\', 0);
} dir_lin = dir_lin != STR_NONE ? dir_lin : 0;
dir_win = dir_win != STR_NONE ? dir_win : 0;
uint64 fileGetTimeFP(file_t ctx) { usize dir_pos = max(dir_lin, dir_win);
#if COLLA_LIN
return 0; usize ext_pos = strvRFind(path, '.', 0);
#else
fatal("fileGetTime not implemented yet outside of linux and windows"); if (dir) {
return 0; *dir = strvSub(path, 0, dir_pos);
#endif }
} if (name) {
*name = strvSub(path, dir_pos ? dir_pos + 1 : dir_pos, ext_pos);
#endif }
if (ext) {
bool filePutc(file_t ctx, char c) { *ext = strvSub(path, ext_pos, SIZE_MAX);
return fileWrite(ctx, &c, 1) == 1; }
} }
bool filePuts(file_t ctx, strview_t v) { bool filePutc(file_t ctx, char c) {
return fileWrite(ctx, v.buf, v.len) == v.len; return fileWrite(ctx, &c, 1) == 1;
} }
bool filePrintf(arena_t scratch, file_t ctx, const char *fmt, ...) { bool filePuts(file_t ctx, strview_t v) {
va_list args; return fileWrite(ctx, v.buf, v.len) == v.len;
va_start(args, fmt); }
bool result = filePrintfv(scratch, ctx, fmt, args);
va_end(args); bool filePrintf(arena_t scratch, file_t ctx, const char *fmt, ...) {
return result; va_list args;
} va_start(args, fmt);
bool result = filePrintfv(scratch, ctx, fmt, args);
bool filePrintfv(arena_t scratch, file_t ctx, const char *fmt, va_list args) { va_end(args);
str_t string = strFmtv(&scratch, fmt, args); return result;
return fileWrite(ctx, string.buf, string.len) == string.len; }
}
bool filePrintfv(arena_t scratch, file_t ctx, const char *fmt, va_list args) {
buffer_t fileReadWhole(arena_t *arena, strview_t name) { str_t string = strFmtv(&scratch, fmt, args);
file_t fp = fileOpen(*arena, name, FILE_READ); return fileWrite(ctx, string.buf, string.len) == string.len;
if (!fileIsValid(fp)) { }
err("could not open file: %v", name);
return (buffer_t){0}; buffer_t fileReadWhole(arena_t *arena, strview_t name) {
} file_t fp = fileOpen(*arena, name, FILE_READ);
buffer_t out = fileReadWholeFP(arena, fp); if (!fileIsValid(fp)) {
fileClose(fp); err("could not open file: %v", name);
return out; return (buffer_t){0};
} }
buffer_t out = fileReadWholeFP(arena, fp);
buffer_t fileReadWholeFP(arena_t *arena, file_t ctx) { fileClose(fp);
if (!fileIsValid(ctx)) return (buffer_t){0}; return out;
buffer_t out = {0}; }
out.len = fileSize(ctx); buffer_t fileReadWholeFP(arena_t *arena, file_t ctx) {
out.data = alloc(arena, uint8, out.len); if (!fileIsValid(ctx)) return (buffer_t){0};
usize read = fileRead(ctx, out.data, out.len); buffer_t out = {0};
if (read != out.len) { out.len = fileSize(ctx);
err("fileReadWholeFP: fileRead failed, should be %zu but is %zu", out.len, read); out.data = alloc(arena, uint8, out.len);
arenaPop(arena, out.len); usize read = fileRead(ctx, out.data, out.len);
return (buffer_t){0};
} if (read != out.len) {
err("fileReadWholeFP: fileRead failed, should be %zu but is %zu", out.len, read);
return out; arenaPop(arena, out.len);
} return (buffer_t){0};
}
str_t fileReadWholeStr(arena_t *arena, strview_t name) {
file_t fp = fileOpen(*arena, name, FILE_READ); return out;
str_t out = fileReadWholeStrFP(arena, fp); }
fileClose(fp);
return out; str_t fileReadWholeStr(arena_t *arena, strview_t name) {
} file_t fp = fileOpen(*arena, name, FILE_READ);
if (!fileIsValid(fp)) {
str_t fileReadWholeStrFP(arena_t *arena, file_t ctx) { warn("could not open file (%v)", name);
if (!fileIsValid(ctx)) return (str_t){0}; }
str_t out = fileReadWholeStrFP(arena, fp);
str_t out = {0}; fileClose(fp);
return out;
out.len = fileSize(ctx); }
out.buf = alloc(arena, uint8, out.len + 1);
usize read = fileRead(ctx, out.buf, out.len); str_t fileReadWholeStrFP(arena_t *arena, file_t ctx) {
if (!fileIsValid(ctx)) return STR_EMPTY;
if (read != out.len) {
err("fileReadWholeStrFP: fileRead failed, should be %zu but is %zu", out.len, read); str_t out = {0};
arenaPop(arena, out.len + 1);
return (str_t){0}; out.len = fileSize(ctx);
} out.buf = alloc(arena, uint8, out.len + 1);
usize read = fileRead(ctx, out.buf, out.len);
return out;
} if (read != out.len) {
err("fileReadWholeStrFP: fileRead failed, should be %zu but is %zu", out.len, read);
bool fileWriteWhole(arena_t scratch, strview_t name, const void *buf, usize len) { arenaPop(arena, out.len + 1);
file_t fp = fileOpen(scratch, name, FILE_WRITE); return STR_EMPTY;
if (!fileIsValid(fp)) { }
return false;
} return out;
usize written = fileWrite(fp, buf, len); }
fileClose(fp);
return written == len; bool fileWriteWhole(arena_t scratch, strview_t name, const void *buf, usize len) {
} file_t fp = fileOpen(scratch, name, FILE_WRITE);
if (!fileIsValid(fp)) {
uint64 fileGetTime(arena_t scratch, strview_t name) { return false;
file_t fp = fileOpen(scratch, name, FILE_READ); }
uint64 result = fileGetTimeFP(fp); usize written = fileWrite(fp, buf, len);
fileClose(fp); fileClose(fp);
return result; return written == len;
} }
bool fileHasChanged(arena_t scratch, strview_t name, uint64 last_timestamp) { uint64 fileGetTime(arena_t scratch, strview_t name) {
uint64 timestamp = fileGetTime(scratch, name); file_t fp = fileOpen(scratch, name, FILE_READ);
return timestamp > last_timestamp; uint64 result = fileGetTimeFP(fp);
} fileClose(fp);
return result;
#include "warnings/colla_warn_end.h" }
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"

View file

@ -1,55 +1,55 @@
#pragma once #pragma once
#include <stdarg.h> #include <stdarg.h>
#include "collatypes.h" #include "collatypes.h"
#include "str.h" #include "str.h"
#include "arena.h" #include "arena.h"
typedef enum { typedef enum {
FILE_READ = 1 << 0, FILE_READ = 1 << 0,
FILE_WRITE = 1 << 1, FILE_WRITE = 1 << 1,
FILE_APPEND = 1 << 2, FILE_APPEND = 1 << 2,
} filemode_e; } filemode_e;
typedef struct { typedef struct {
uintptr_t handle; uintptr_t handle;
} file_t; } file_t;
bool fileExists(const char *name); bool fileExists(const char *name);
TCHAR *fileGetFullPath(arena_t *arena, strview_t filename); TCHAR *fileGetFullPath(arena_t *arena, strview_t filename);
strview_t fileGetFilename(strview_t path); strview_t fileGetFilename(strview_t path);
strview_t fileGetExtension(strview_t path); strview_t fileGetExtension(strview_t path);
void fileSplitPath(strview_t path, strview_t *dir, strview_t *name, strview_t *ext); void fileSplitPath(strview_t path, strview_t *dir, strview_t *name, strview_t *ext);
bool fileDelete(arena_t scratch, strview_t filename); bool fileDelete(arena_t scratch, strview_t filename);
file_t fileOpen(arena_t scratch, strview_t name, filemode_e mode); file_t fileOpen(arena_t scratch, strview_t name, filemode_e mode);
void fileClose(file_t ctx); void fileClose(file_t ctx);
bool fileIsValid(file_t ctx); bool fileIsValid(file_t ctx);
bool filePutc(file_t ctx, char c); bool filePutc(file_t ctx, char c);
bool filePuts(file_t ctx, strview_t v); bool filePuts(file_t ctx, strview_t v);
bool filePrintf(arena_t scratch, file_t ctx, const char *fmt, ...); 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); bool filePrintfv(arena_t scratch, file_t ctx, const char *fmt, va_list args);
usize fileRead(file_t ctx, void *buf, usize len); usize fileRead(file_t ctx, void *buf, usize len);
usize fileWrite(file_t ctx, const void *buf, usize len); usize fileWrite(file_t ctx, const void *buf, usize len);
bool fileSeekEnd(file_t ctx); bool fileSeekEnd(file_t ctx);
void fileRewind(file_t ctx); void fileRewind(file_t ctx);
usize fileTell(file_t ctx); usize fileTell(file_t ctx);
usize fileSize(file_t ctx); usize fileSize(file_t ctx);
buffer_t fileReadWhole(arena_t *arena, strview_t name); buffer_t fileReadWhole(arena_t *arena, strview_t name);
buffer_t fileReadWholeFP(arena_t *arena, file_t ctx); buffer_t fileReadWholeFP(arena_t *arena, file_t ctx);
str_t fileReadWholeStr(arena_t *arena, strview_t name); str_t fileReadWholeStr(arena_t *arena, strview_t name);
str_t fileReadWholeStrFP(arena_t *arena, file_t ctx); str_t fileReadWholeStrFP(arena_t *arena, file_t ctx);
bool fileWriteWhole(arena_t scratch, strview_t name, const void *buf, usize len); bool fileWriteWhole(arena_t scratch, strview_t name, const void *buf, usize len);
uint64 fileGetTime(arena_t scratch, strview_t name); uint64 fileGetTime(arena_t scratch, strview_t name);
uint64 fileGetTimeFP(file_t ctx); uint64 fileGetTimeFP(file_t ctx);
bool fileHasChanged(arena_t scratch, strview_t name, uint64 last_timestamp); bool fileHasChanged(arena_t scratch, strview_t name, uint64 last_timestamp);

View file

@ -9,7 +9,7 @@
static char *fmt__stb_callback(const char *buf, void *ud, int len) { static char *fmt__stb_callback(const char *buf, void *ud, int len) {
(void)len; (void)len;
printf("%s", buf); printf("%.*s", len, buf);
return (char *)ud; return (char *)ud;
} }

621
highlight.c Normal file
View file

@ -0,0 +1,621 @@
#include "highlight.h"
// based on https://github.com/Theldus/kat
#include <ctype.h>
#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("<config> 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("&amp"));
break;
case '<':
ostrPuts(out, strv("&lt"));
break;
case '>':
ostrPuts(out, strv("&gt"));
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]);
}

49
highlight.h Normal file
View file

@ -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);

View file

@ -6,7 +6,6 @@
// todo linux support? // todo linux support?
#if COLLA_WIN #if COLLA_WIN
#define WIN32_LEAN_AND_MEAN
#include <windows.h> #include <windows.h>
#else #else
// patch stuff up for cross platform for now, the actual program should not really call anything for now // 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_f hr_close;
} hr_internal_t; } 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) { static bool hr__file_copy(arena_t scratch, strview_t src, strview_t dst) {
buffer_t srcbuf = fileReadWhole(&scratch, src); buffer_t srcbuf = fileReadWhole(&scratch, src);
if (srcbuf.data == NULL || srcbuf.len == 0) { if (srcbuf.data == NULL || srcbuf.len == 0) {
@ -70,6 +72,93 @@ static bool hr__reload(hr_t *ctx) {
info("loading library: %v", dll); 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) { if (hr->handle) {
FreeLibrary(hr->handle); FreeLibrary(hr->handle);
} }
@ -117,84 +206,21 @@ error:
return false; return false;
} }
bool hrOpen(hr_t *ctx, strview_t path) { static void hr__os_free(hr_internal_t *hr) {
#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);
}
if (hr->handle) { if (hr->handle) {
FreeLibrary(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) { #elif COLLA_LIN
#ifdef CR_DISABLE
hr_loop(ctx);
return 0;
#endif
hr_internal_t *hr = ctx->p;
int result = -1; static bool hr__os_reload(hr_internal_t *hr) {
if (hr->hr_loop) { fatal("todo: linux hot reload not implemented yet");
result = hr->hr_loop(ctx); return true;
}
return result;
} }
bool hrReload(hr_t *ctx) { static void hr__os_free(hr_internal_t *hr) {
return hr__reload(ctx); fatal("todo: linux hot reload not implemented yet");
} }
#endif

77
html.h Normal file
View file

@ -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("<!DOCTYPE html>\n<html>");
#define htmlEnd() htmlPrintf("</html>"); *__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("<hr>")

File diff suppressed because it is too large Load diff

View file

@ -1,83 +1,83 @@
#pragma once #pragma once
#include "collatypes.h" #include "collatypes.h"
#include "str.h" #include "str.h"
typedef struct arena_t arena_t; typedef struct arena_t arena_t;
typedef uintptr_t socket_t; typedef uintptr_t socket_t;
typedef enum { typedef enum {
HTTP_GET, HTTP_GET,
HTTP_POST, HTTP_POST,
HTTP_HEAD, HTTP_HEAD,
HTTP_PUT, HTTP_PUT,
HTTP_DELETE HTTP_DELETE
} http_method_e; } http_method_e;
const char *httpGetStatusString(int status); const char *httpGetStatusString(int status);
typedef struct { typedef struct {
uint8 major; uint8 major;
uint8 minor; uint8 minor;
} http_version_t; } http_version_t;
// translates a http_version_t to a single readable number (e.g. 1.1 -> 11, 1.0 -> 10, etc) // 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); int httpVerNumber(http_version_t ver);
typedef struct http_header_t { typedef struct http_header_t {
strview_t key; strview_t key;
strview_t value; strview_t value;
struct http_header_t *next; struct http_header_t *next;
} http_header_t; } http_header_t;
typedef struct { typedef struct {
http_method_e method; http_method_e method;
http_version_t version; http_version_t version;
http_header_t *headers; http_header_t *headers;
strview_t url; strview_t url;
strview_t body; strview_t body;
} http_req_t; } http_req_t;
typedef struct { typedef struct {
int status_code; int status_code;
http_version_t version; http_version_t version;
http_header_t *headers; http_header_t *headers;
strview_t body; strview_t body;
} http_res_t; } http_res_t;
// strview_t request needs to be valid for http_req_t to be valid! // 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_req_t httpParseReq(arena_t *arena, strview_t request);
http_res_t httpParseRes(arena_t *arena, strview_t response); http_res_t httpParseRes(arena_t *arena, strview_t response);
str_t httpReqToStr(arena_t *arena, http_req_t *req); str_t httpReqToStr(arena_t *arena, http_req_t *req);
str_t httpResToStr(arena_t *arena, http_res_t *res); str_t httpResToStr(arena_t *arena, http_res_t *res);
bool httpHasHeader(http_header_t *headers, strview_t key); bool httpHasHeader(http_header_t *headers, strview_t key);
void httpSetHeader(http_header_t *headers, strview_t key, strview_t value); void httpSetHeader(http_header_t *headers, strview_t key, strview_t value);
strview_t httpGetHeader(http_header_t *headers, strview_t key); strview_t httpGetHeader(http_header_t *headers, strview_t key);
str_t httpMakeUrlSafe(arena_t *arena, strview_t string); str_t httpMakeUrlSafe(arena_t *arena, strview_t string);
str_t httpDecodeUrlSafe(arena_t *arena, strview_t string); str_t httpDecodeUrlSafe(arena_t *arena, strview_t string);
typedef struct { typedef struct {
strview_t host; strview_t host;
strview_t uri; strview_t uri;
} http_url_t; } http_url_t;
http_url_t httpSplitUrl(strview_t url); http_url_t httpSplitUrl(strview_t url);
typedef struct { typedef struct {
arena_t *arena; arena_t *arena;
strview_t url; strview_t url;
http_method_e request_type; http_method_e request_type;
http_header_t *headers; http_header_t *headers;
int header_count; int header_count;
strview_t body; strview_t body;
} http_request_desc_t; } http_request_desc_t;
// arena_t *arena, strview_t url, [ http_header_t *headers, int header_count, strview_t body ] // 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 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__ }) #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); http_res_t httpRequest(http_request_desc_t *request);
buffer_t httpsRequest(http_request_desc_t *request); buffer_t httpsRequest(http_request_desc_t *request);

View file

@ -1,279 +1,279 @@
#include "ini.h" #include "ini.h"
#include "warnings/colla_warn_beg.h" #include "warnings/colla_warn_beg.h"
#include <assert.h> #include <assert.h>
#include "strstream.h" #include "strstream.h"
static void ini__parse(arena_t *arena, ini_t *ini, const iniopts_t *options); 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) { ini_t iniParse(arena_t *arena, strview_t filename, const iniopts_t *options) {
file_t fp = fileOpen(*arena, filename, FILE_READ); file_t fp = fileOpen(*arena, filename, FILE_READ);
ini_t out = iniParseFile(arena, fp, options); ini_t out = iniParseFile(arena, fp, options);
fileClose(fp); fileClose(fp);
return out; return out;
} }
ini_t iniParseFile(arena_t *arena, file_t file, const iniopts_t *options) { ini_t iniParseFile(arena_t *arena, file_t file, const iniopts_t *options) {
str_t data = fileReadWholeStrFP(arena, file); str_t data = fileReadWholeStrFP(arena, file);
return iniParseStr(arena, strv(data), options); return iniParseStr(arena, strv(data), options);
} }
ini_t iniParseStr(arena_t *arena, strview_t str, const iniopts_t *options) { ini_t iniParseStr(arena_t *arena, strview_t str, const iniopts_t *options) {
ini_t out = { ini_t out = {
.text = str, .text = str,
.tables = NULL, .tables = NULL,
}; };
ini__parse(arena, &out, options); ini__parse(arena, &out, options);
return out; return out;
} }
bool iniIsValid(ini_t *ctx) { bool iniIsValid(ini_t *ctx) {
return ctx && !strvIsEmpty(ctx->text); return ctx && !strvIsEmpty(ctx->text);
} }
initable_t *iniGetTable(ini_t *ctx, strview_t name) { initable_t *iniGetTable(ini_t *ctx, strview_t name) {
initable_t *t = ctx ? ctx->tables : NULL; initable_t *t = ctx ? ctx->tables : NULL;
while (t) { while (t) {
if (strvEquals(t->name, name)) { if (strvEquals(t->name, name)) {
return t; return t;
} }
t = t->next; t = t->next;
} }
return NULL; return NULL;
} }
inivalue_t *iniGet(initable_t *ctx, strview_t key) { inivalue_t *iniGet(initable_t *ctx, strview_t key) {
inivalue_t *v = ctx ? ctx->values : NULL; inivalue_t *v = ctx ? ctx->values : NULL;
while (v) { while (v) {
if (strvEquals(v->key, key)) { if (strvEquals(v->key, key)) {
return v; return v;
} }
v = v->next; v = v->next;
} }
return NULL; return NULL;
} }
iniarray_t iniAsArr(arena_t *arena, inivalue_t *value, char delim) { iniarray_t iniAsArr(arena_t *arena, inivalue_t *value, char delim) {
strview_t v = value ? value->value : (strview_t){0}; strview_t v = value ? value->value : STRV_EMPTY;
if (!delim) delim = ' '; if (!delim) delim = ' ';
strview_t *beg = (strview_t *)arena->current; strview_t *beg = (strview_t *)arena->current;
usize count = 0; usize count = 0;
usize start = 0; usize start = 0;
for (usize i = 0; i < v.len; ++i) { for (usize i = 0; i < v.len; ++i) {
if (v.buf[i] == delim) { if (v.buf[i] == delim) {
strview_t arrval = strvTrim(strvSub(v, start, i)); strview_t arrval = strvTrim(strvSub(v, start, i));
if (!strvIsEmpty(arrval)) { if (!strvIsEmpty(arrval)) {
strview_t *newval = alloc(arena, strview_t); strview_t *newval = alloc(arena, strview_t);
*newval = arrval; *newval = arrval;
++count; ++count;
} }
start = i + 1; start = i + 1;
} }
} }
strview_t last = strvTrim(strvSub(v, start, SIZE_MAX)); strview_t last = strvTrim(strvSub(v, start, SIZE_MAX));
if (!strvIsEmpty(last)) { if (!strvIsEmpty(last)) {
strview_t *newval = alloc(arena, strview_t); strview_t *newval = alloc(arena, strview_t);
*newval = last; *newval = last;
++count; ++count;
} }
return (iniarray_t){ return (iniarray_t){
.values = beg, .values = beg,
.count = count, .count = count,
}; };
} }
uint64 iniAsUInt(inivalue_t *value) { uint64 iniAsUInt(inivalue_t *value) {
strview_t v = value ? value->value : (strview_t){0}; strview_t v = value ? value->value : STRV_EMPTY;
instream_t in = istrInitLen(v.buf, v.len); instream_t in = istrInitLen(v.buf, v.len);
uint64 out = 0; uint64 out = 0;
if (!istrGetU64(&in, &out)) { if (!istrGetU64(&in, &out)) {
out = 0; out = 0;
} }
return out; return out;
} }
int64 iniAsInt(inivalue_t *value) { int64 iniAsInt(inivalue_t *value) {
strview_t v = value ? value->value : (strview_t){0}; strview_t v = value ? value->value : STRV_EMPTY;
instream_t in = istrInitLen(v.buf, v.len); instream_t in = istrInitLen(v.buf, v.len);
int64 out = 0; int64 out = 0;
if (!istrGetI64(&in, &out)) { if (!istrGetI64(&in, &out)) {
out = 0; out = 0;
} }
return out; return out;
} }
double iniAsNum(inivalue_t *value) { double iniAsNum(inivalue_t *value) {
strview_t v = value ? value->value : (strview_t){0}; strview_t v = value ? value->value : STRV_EMPTY;
instream_t in = istrInitLen(v.buf, v.len); instream_t in = istrInitLen(v.buf, v.len);
double out = 0; double out = 0;
if (!istrGetDouble(&in, &out)) { if (!istrGetDouble(&in, &out)) {
out = 0.0; out = 0.0;
} }
return out; return out;
} }
bool iniAsBool(inivalue_t *value) { bool iniAsBool(inivalue_t *value) {
strview_t v = value ? value->value : (strview_t){0}; strview_t v = value ? value->value : STRV_EMPTY;
instream_t in = istrInitLen(v.buf, v.len); instream_t in = istrInitLen(v.buf, v.len);
bool out = 0; bool out = 0;
if (!istrGetBool(&in, &out)) { if (!istrGetBool(&in, &out)) {
out = false; out = false;
} }
return out; return out;
} }
// == PRIVATE FUNCTIONS ============================================================================== // == PRIVATE FUNCTIONS ==============================================================================
#define INIPUSH(head, tail, val) \ #define INIPUSH(head, tail, val) \
do { \ do { \
if (!head) { \ if (!head) { \
head = val; \ head = val; \
tail = val; \ tail = val; \
} \ } \
else { \ else { \
tail->next = val; \ tail->next = val; \
val = tail; \ val = tail; \
} \ } \
} while (0) } while (0)
static iniopts_t ini__get_options(const iniopts_t *options) { static iniopts_t ini__get_options(const iniopts_t *options) {
iniopts_t out = { iniopts_t out = {
.key_value_divider = '=', .key_value_divider = '=',
}; };
#define SETOPT(v) out.v = options->v ? options->v : out.v #define SETOPT(v) out.v = options->v ? options->v : out.v
if (options) { if (options) {
SETOPT(key_value_divider); SETOPT(key_value_divider);
SETOPT(merge_duplicate_keys); SETOPT(merge_duplicate_keys);
SETOPT(merge_duplicate_tables); SETOPT(merge_duplicate_tables);
} }
#undef SETOPT #undef SETOPT
return out; return out;
} }
static void ini__add_value(arena_t *arena, initable_t *table, instream_t *in, iniopts_t *opts) { static void ini__add_value(arena_t *arena, initable_t *table, instream_t *in, iniopts_t *opts) {
assert(table); assert(table);
strview_t key = strvTrim(istrGetView(in, opts->key_value_divider)); strview_t key = strvTrim(istrGetView(in, opts->key_value_divider));
istrSkip(in, 1); istrSkip(in, 1);
strview_t value = strvTrim(istrGetViewEither(in, strv("\n#;"))); strview_t value = strvTrim(istrGetViewEither(in, strv("\n#;")));
istrSkip(in, 1); istrSkip(in, 1);
inivalue_t *newval = NULL; inivalue_t *newval = NULL;
if (opts->merge_duplicate_keys) { if (opts->merge_duplicate_keys) {
newval = table->values; newval = table->values;
while (newval) { while (newval) {
if (strvEquals(newval->key, key)) { if (strvEquals(newval->key, key)) {
break; break;
} }
newval = newval->next; newval = newval->next;
} }
} }
if (newval) { if (newval) {
newval->value = value; newval->value = value;
} }
else { else {
newval = alloc(arena, inivalue_t); newval = alloc(arena, inivalue_t);
newval->key = key; newval->key = key;
newval->value = value; newval->value = value;
if (!table->values) { if (!table->values) {
table->values = newval; table->values = newval;
} }
else { else {
table->tail->next = newval; table->tail->next = newval;
} }
table->tail = newval; table->tail = newval;
} }
} }
static void ini__add_table(arena_t *arena, ini_t *ctx, instream_t *in, iniopts_t *options) { static void ini__add_table(arena_t *arena, ini_t *ctx, instream_t *in, iniopts_t *options) {
istrSkip(in, 1); // skip [ istrSkip(in, 1); // skip [
strview_t name = istrGetView(in, ']'); strview_t name = istrGetView(in, ']');
istrSkip(in, 1); // skip ] istrSkip(in, 1); // skip ]
initable_t *table = NULL; initable_t *table = NULL;
if (options->merge_duplicate_tables) { if (options->merge_duplicate_tables) {
table = ctx->tables; table = ctx->tables;
while (table) { while (table) {
if (strvEquals(table->name, name)) { if (strvEquals(table->name, name)) {
break; break;
} }
table = table->next; table = table->next;
} }
} }
if (!table) { if (!table) {
table = alloc(arena, initable_t); table = alloc(arena, initable_t);
table->name = name; table->name = name;
if (!ctx->tables) { if (!ctx->tables) {
ctx->tables = table; ctx->tables = table;
} }
else { else {
ctx->tail->next = table; ctx->tail->next = table;
} }
ctx->tail = table; ctx->tail = table;
} }
istrIgnoreAndSkip(in, '\n'); istrIgnoreAndSkip(in, '\n');
while (!istrIsFinished(*in)) { while (!istrIsFinished(*in)) {
switch (istrPeek(in)) { switch (istrPeek(in)) {
case '\n': // fallthrough case '\n': // fallthrough
case '\r': case '\r':
return; return;
case '#': // fallthrough case '#': // fallthrough
case ';': case ';':
istrIgnoreAndSkip(in, '\n'); istrIgnoreAndSkip(in, '\n');
break; break;
default: default:
ini__add_value(arena, table, in, options); ini__add_value(arena, table, in, options);
break; break;
} }
} }
} }
static void ini__parse(arena_t *arena, ini_t *ini, const iniopts_t *options) { static void ini__parse(arena_t *arena, ini_t *ini, const iniopts_t *options) {
iniopts_t opts = ini__get_options(options); iniopts_t opts = ini__get_options(options);
initable_t *root = alloc(arena, initable_t); initable_t *root = alloc(arena, initable_t);
root->name = INI_ROOT; root->name = INI_ROOT;
ini->tables = root; ini->tables = root;
ini->tail = root; ini->tail = root;
instream_t in = istrInitLen(ini->text.buf, ini->text.len); instream_t in = istrInitLen(ini->text.buf, ini->text.len);
while (!istrIsFinished(in)) { while (!istrIsFinished(in)) {
istrSkipWhitespace(&in); istrSkipWhitespace(&in);
switch (istrPeek(&in)) { switch (istrPeek(&in)) {
case '[': case '[':
ini__add_table(arena, ini, &in, &opts); ini__add_table(arena, ini, &in, &opts);
break; break;
case '#': // fallthrough case '#': // fallthrough
case ';': case ';':
istrIgnoreAndSkip(&in, '\n'); istrIgnoreAndSkip(&in, '\n');
break; break;
default: default:
ini__add_value(arena, ini->tables, &in, &opts); ini__add_value(arena, ini->tables, &in, &opts);
break; break;
} }
} }
} }
#undef INIPUSH #undef INIPUSH
#include "warnings/colla_warn_end.h" #include "warnings/colla_warn_end.h"

View file

@ -1,54 +1,54 @@
#pragma once #pragma once
#include "collatypes.h" #include "collatypes.h"
#include "str.h" #include "str.h"
#include "file.h" #include "file.h"
typedef struct arena_t arena_t; typedef struct arena_t arena_t;
typedef struct inivalue_t { typedef struct inivalue_t {
strview_t key; strview_t key;
strview_t value; strview_t value;
struct inivalue_t *next; struct inivalue_t *next;
} inivalue_t; } inivalue_t;
typedef struct initable_t { typedef struct initable_t {
strview_t name; strview_t name;
inivalue_t *values; inivalue_t *values;
inivalue_t *tail; inivalue_t *tail;
struct initable_t *next; struct initable_t *next;
} initable_t; } initable_t;
typedef struct { typedef struct ini_t {
strview_t text; strview_t text;
initable_t *tables; initable_t *tables;
initable_t *tail; initable_t *tail;
} ini_t; } ini_t;
typedef struct { typedef struct {
bool merge_duplicate_tables; // default false bool merge_duplicate_tables; // default false
bool merge_duplicate_keys; // default false bool merge_duplicate_keys; // default false
char key_value_divider; // default = char key_value_divider; // default =
} iniopts_t; } iniopts_t;
ini_t iniParse(arena_t *arena, strview_t filename, const iniopts_t *options); 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 iniParseFile(arena_t *arena, file_t file, const iniopts_t *options);
ini_t iniParseStr(arena_t *arena, strview_t str, const iniopts_t *options); ini_t iniParseStr(arena_t *arena, strview_t str, const iniopts_t *options);
bool iniIsValid(ini_t *ctx); bool iniIsValid(ini_t *ctx);
#define INI_ROOT strv("__ROOT__") #define INI_ROOT strv("__ROOT__")
initable_t *iniGetTable(ini_t *ctx, strview_t name); initable_t *iniGetTable(ini_t *ctx, strview_t name);
inivalue_t *iniGet(initable_t *ctx, strview_t key); inivalue_t *iniGet(initable_t *ctx, strview_t key);
typedef struct { typedef struct {
strview_t *values; strview_t *values;
usize count; usize count;
} iniarray_t; } iniarray_t;
iniarray_t iniAsArr(arena_t *arena, inivalue_t *value, char delim); iniarray_t iniAsArr(arena_t *arena, inivalue_t *value, char delim);
uint64 iniAsUInt(inivalue_t *value); uint64 iniAsUInt(inivalue_t *value);
int64 iniAsInt(inivalue_t *value); int64 iniAsInt(inivalue_t *value);
double iniAsNum(inivalue_t *value); double iniAsNum(inivalue_t *value);
bool iniAsBool(inivalue_t *value); bool iniAsBool(inivalue_t *value);

View file

@ -153,10 +153,9 @@ static bool json__parse_string(arena_t *arena, instream_t *in, str_t *out) {
goto fail; goto fail;
} }
success:
return true; return true;
fail: fail:
*out = (str_t){0}; *out = STR_EMPTY;
return false; return false;
} }

503
markdown.c Normal file
View file

@ -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, "<h%d>", n);
markdown__parse_line(md, line, out, false, false);
ostrPrintf(out, "</h%d>", 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("<ul>\n"));
}
else if (md->list.indent > cur_indent) {
markdown__close_list(md, out);
}
md->list.indent = cur_indent;
ostrPuts(out, strv("<li>"));
markdown__parse_line(md, strvRemovePrefix(line, 2), out, false, false);
ostrPuts(out, strv("</li>"));
goto read_whole_line;
}
// check if it is an <hr>
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("<hr>"));
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("<ol>\n"));
}
else if (md->list.indent > cur_indent) {
markdown__close_list(md, out);
}
md->list.indent = cur_indent;
ostrPuts(out, strv("<li>"));
markdown__parse_line(md, strvRemovePrefix(line, istrTell(in)), out, false, false);
ostrPuts(out, strv("</li>"));
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("</ol></code></pre>\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("<pre><code><ol>"));
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 <hr>
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("<p>\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, "<a href=\"%v\">%v</a>", 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, "<img src=\"%v\"", url);
if (!strvIsEmpty(text)) {
ostrPrintf(out, " alt=\"%v\"", text);
}
ostrPutc(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("<code>"));
while (!istrIsFinished(in)) {
strview_t code = istrGetView(&in, '`');
markdown__escape(code, out);
if (!is_escaped || istrPeek(&in) == '`') {
break;
}
ostrPutc(out, '`');
}
ostrPuts(out, strv("</code>"));
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("</ol>\n"));
}
else {
ostrPuts(out, strv("</ul>\n"));
}
}
md->list.indent = -1;
// close paragraph
if (md->is_in_paragraph) {
ostrPuts(out, strv("</p>\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("</ol>\n"));
}
else {
ostrPuts(out, strv("</ul>\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("&amp"));
break;
case '<':
ostrPuts(out, strv("&lt"));
break;
case '>':
ostrPuts(out, strv("&gt"));
break;
default:
ostrPutc(out, view.buf[i]);
break;
}
}
}

59
markdown.h Normal file
View file

@ -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 # -> <h1..n>
[x] *** or --- or ___ on their own line -> <hr>
[x] - or * -> unordered list
[x] n. -> ordered list
[x] ```xyz and newline -> code block of language <xyz> (xyz is optional)
mid of line:
[x] * -> italic
[x] ** -> bold
[x] *** -> bold and italic
[x] [text](link) -> link
[x] ![text](link) -> image
[x] ` -> code block until next backtick
other:
[x] empty line -> </p>
[x] \ -> escape character
todo?:
[ ] two space at end of line or \ -> <br>
[ ] indent inside list -> continue in point
[ ] 4 spaces -> line code block (does NOT work with multiline, use ``` instead)
[ ] <url> -> link
[ ] [text](link "title") -> link
[ ] fix ***both***
*/

View file

@ -72,6 +72,7 @@ typedef struct server_t {
server__route_t *routes_default; server__route_t *routes_default;
socket_t current_client; socket_t current_client;
bool should_stop; bool should_stop;
uint16 port;
} server_t; } server_t;
bool server__parse_chunk(arena_t *arena, server__req_ctx_t *ctx, char buffer[SERVER_BUFSZ], usize buflen) { 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()) { if (!skInit()) {
fatal("couldn't initialise sockets: %s", skGetErrorString()); 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()); fatal("couldn't open socket: %s", skGetErrorString());
} }
skaddrin_t addr = { bool bound = false;
.sin_family = AF_INET,
.sin_addr.s_addr = INADDR_ANY,
.sin_port = htons(port),
};
if (!skBindPro(sk, (skaddr_t *)&addr, sizeof(addr))) { while (!bound) {
fatal("could not bind socket: %s", skGetErrorString()); 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)) { if (!skListenPro(sk, 10)) {
@ -302,6 +313,7 @@ server_t *serverSetup(arena_t *arena, uint16 port) {
server_t *server = alloc(arena, server_t); server_t *server = alloc(arena, server_t);
server->socket = sk; server->socket = sk;
server->port = port;
return server; 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) { void serverStart(arena_t scratch, server_t *server) {
usize start = arenaTell(&scratch); usize start = arenaTell(&scratch);
info("Server started!"); info("Server started at (http://localhost:%d)!", server->port);
while (!server->should_stop) { while (!server->should_stop) {
socket_t client = skAccept(server->socket); socket_t client = skAccept(server->socket);
if (!skIsValid(client)) { if (!skIsValid(client)) {

View file

@ -26,7 +26,7 @@ typedef struct {
typedef str_t (*server_route_f)(arena_t scratch, server_t *server, server_req_t *req, void *userdata); 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 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 serverRouteDefault(arena_t *arena, server_t *server, server_route_f cb, void *userdata);
void serverStart(arena_t scratch, server_t *server); void serverStart(arena_t scratch, server_t *server);

120
sha1.c Normal file
View file

@ -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]);
}

18
sha1.h Normal file
View file

@ -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);

View file

@ -1,286 +1,286 @@
#include "socket.h" #include "socket.h"
#if COLLA_WIN && COLLA_CMT_LIB #if COLLA_WIN && COLLA_CMT_LIB
#pragma comment(lib, "Ws2_32") #pragma comment(lib, "Ws2_32")
#endif #endif
#if COLLA_WIN #if COLLA_WIN
typedef int socklen_t; typedef int socklen_t;
bool skInit(void) { bool skInit(void) {
WSADATA w; WSADATA w;
int error = WSAStartup(0x0202, &w); int error = WSAStartup(0x0202, &w);
return error == 0; return error == 0;
} }
bool skCleanup(void) { bool skCleanup(void) {
return WSACleanup() == 0; return WSACleanup() == 0;
} }
bool skClose(socket_t sock) { bool skClose(socket_t sock) {
return closesocket(sock) != SOCKET_ERROR; return closesocket(sock) != SOCKET_ERROR;
} }
int skPoll(skpoll_t *to_poll, int num_to_poll, int timeout) { int skPoll(skpoll_t *to_poll, int num_to_poll, int timeout) {
return WSAPoll(to_poll, num_to_poll, timeout); return WSAPoll(to_poll, num_to_poll, timeout);
} }
int skGetError(void) { int skGetError(void) {
return WSAGetLastError(); return WSAGetLastError();
} }
#else #else
#include <arpa/inet.h> #include <arpa/inet.h>
#include <netdb.h> #include <netdb.h>
#include <unistd.h> #include <unistd.h>
#include <errno.h> #include <errno.h>
#include <string.h> // strerror #include <string.h> // strerror
#include <poll.h> #include <poll.h>
bool skInit(void) { bool skInit(void) {
return true; return true;
} }
bool skCleanup(void) { bool skCleanup(void) {
return true; return true;
} }
bool skClose(socket_t sock) { bool skClose(socket_t sock) {
return close(sock) != SOCKET_ERROR; return close(sock) != SOCKET_ERROR;
} }
int skPoll(skpoll_t *to_poll, int num_to_poll, int timeout) { int skPoll(skpoll_t *to_poll, int num_to_poll, int timeout) {
return poll(to_poll, num_to_poll, timeout); return poll(to_poll, num_to_poll, timeout);
} }
int skGetError(void) { int skGetError(void) {
return errno; return errno;
} }
const char *skGetErrorString(void) { const char *skGetErrorString(void) {
return strerror(errno); return strerror(errno);
} }
#endif #endif
socket_t skOpen(sktype_e type) { socket_t skOpen(sktype_e type) {
int sock_type = 0; int sock_type = 0;
switch(type) { switch(type) {
case SOCK_TCP: sock_type = SOCK_STREAM; break; case SOCK_TCP: sock_type = SOCK_STREAM; break;
case SOCK_UDP: sock_type = SOCK_DGRAM; break; case SOCK_UDP: sock_type = SOCK_DGRAM; break;
default: fatal("skType not recognized: %d", type); break; default: fatal("skType not recognized: %d", type); break;
} }
return skOpenPro(AF_INET, sock_type, 0); return skOpenPro(AF_INET, sock_type, 0);
} }
socket_t skOpenEx(const char *protocol) { socket_t skOpenEx(const char *protocol) {
struct protoent *proto = getprotobyname(protocol); struct protoent *proto = getprotobyname(protocol);
if(!proto) { if(!proto) {
return (socket_t){0}; return (socket_t){0};
} }
return skOpenPro(AF_INET, SOCK_STREAM, proto->p_proto); return skOpenPro(AF_INET, SOCK_STREAM, proto->p_proto);
} }
socket_t skOpenPro(int af, int type, int protocol) { socket_t skOpenPro(int af, int type, int protocol) {
return socket(af, type, protocol); return socket(af, type, protocol);
} }
bool skIsValid(socket_t sock) { bool skIsValid(socket_t sock) {
return sock != SOCKET_ERROR; return sock != SOCKET_ERROR;
} }
skaddrin_t skAddrinInit(const char *ip, uint16_t port) { skaddrin_t skAddrinInit(const char *ip, uint16_t port) {
return (skaddrin_t){ return (skaddrin_t){
.sin_family = AF_INET, .sin_family = AF_INET,
.sin_port = htons(port), .sin_port = htons(port),
// TODO use inet_pton instead // TODO use inet_pton instead
.sin_addr.s_addr = inet_addr(ip), .sin_addr.s_addr = inet_addr(ip),
}; };
} }
bool skBind(socket_t sock, const char *ip, uint16_t port) { bool skBind(socket_t sock, const char *ip, uint16_t port) {
skaddrin_t addr = skAddrinInit(ip, port); skaddrin_t addr = skAddrinInit(ip, port);
return skBindPro(sock, (skaddr_t *)&addr, sizeof(addr)); return skBindPro(sock, (skaddr_t *)&addr, sizeof(addr));
} }
bool skBindPro(socket_t sock, const skaddr_t *name, int namelen) { bool skBindPro(socket_t sock, const skaddr_t *name, int namelen) {
return bind(sock, name, namelen) != SOCKET_ERROR; return bind(sock, name, namelen) != SOCKET_ERROR;
} }
bool skListen(socket_t sock) { bool skListen(socket_t sock) {
return skListenPro(sock, 1); return skListenPro(sock, 1);
} }
bool skListenPro(socket_t sock, int backlog) { bool skListenPro(socket_t sock, int backlog) {
return listen(sock, backlog) != SOCKET_ERROR; return listen(sock, backlog) != SOCKET_ERROR;
} }
socket_t skAccept(socket_t sock) { socket_t skAccept(socket_t sock) {
skaddrin_t addr = {0}; skaddrin_t addr = {0};
int addr_size = sizeof(addr); int addr_size = sizeof(addr);
return skAcceptPro(sock, (skaddr_t *)&addr, &addr_size); return skAcceptPro(sock, (skaddr_t *)&addr, &addr_size);
} }
socket_t skAcceptPro(socket_t sock, skaddr_t *addr, int *addrlen) { socket_t skAcceptPro(socket_t sock, skaddr_t *addr, int *addrlen) {
return accept(sock, addr, (socklen_t *)addrlen); return accept(sock, addr, (socklen_t *)addrlen);
} }
bool skConnect(socket_t sock, const char *server, unsigned short server_port) { bool skConnect(socket_t sock, const char *server, unsigned short server_port) {
// TODO use getaddrinfo insetad // TODO use getaddrinfo insetad
struct hostent *host = gethostbyname(server); struct hostent *host = gethostbyname(server);
// if gethostbyname fails, inet_addr will also fail and return an easier to debug error // if gethostbyname fails, inet_addr will also fail and return an easier to debug error
const char *address = server; const char *address = server;
if(host) { if(host) {
address = inet_ntoa(*(struct in_addr*)host->h_addr_list[0]); address = inet_ntoa(*(struct in_addr*)host->h_addr_list[0]);
} }
skaddrin_t addr = skAddrinInit(address, server_port); skaddrin_t addr = skAddrinInit(address, server_port);
return skConnectPro(sock, (skaddr_t *)&addr, sizeof(addr)); return skConnectPro(sock, (skaddr_t *)&addr, sizeof(addr));
} }
bool skConnectPro(socket_t sock, const skaddr_t *name, int namelen) { bool skConnectPro(socket_t sock, const skaddr_t *name, int namelen) {
return connect(sock, name, namelen) != SOCKET_ERROR; return connect(sock, name, namelen) != SOCKET_ERROR;
} }
int skSend(socket_t sock, const void *buf, int len) { int skSend(socket_t sock, const void *buf, int len) {
return skSendPro(sock, buf, len, 0); return skSendPro(sock, buf, len, 0);
} }
int skSendPro(socket_t sock, const void *buf, int len, int flags) { int skSendPro(socket_t sock, const void *buf, int len, int flags) {
return send(sock, buf, len, flags); return send(sock, buf, len, flags);
} }
int skSendTo(socket_t sock, const void *buf, int len, const skaddrin_t *to) { 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)); 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) { 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); return sendto(sock, buf, len, flags, to, tolen);
} }
int skReceive(socket_t sock, void *buf, int len) { int skReceive(socket_t sock, void *buf, int len) {
return skReceivePro(sock, buf, len, 0); return skReceivePro(sock, buf, len, 0);
} }
int skReceivePro(socket_t sock, void *buf, int len, int flags) { int skReceivePro(socket_t sock, void *buf, int len, int flags) {
return recv(sock, buf, len, flags); return recv(sock, buf, len, flags);
} }
int skReceiveFrom(socket_t sock, void *buf, int len, skaddrin_t *from) { int skReceiveFrom(socket_t sock, void *buf, int len, skaddrin_t *from) {
int fromlen = sizeof(skaddr_t); int fromlen = sizeof(skaddr_t);
return skReceiveFromPro(sock, buf, len, 0, (skaddr_t*)from, &fromlen); 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) { 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); return recvfrom(sock, buf, len, flags, from, (socklen_t *)fromlen);
} }
// put this at the end of file to not make everything else unreadable // put this at the end of file to not make everything else unreadable
#if COLLA_WIN #if COLLA_WIN
const char *skGetErrorString(void) { const char *skGetErrorString(void) {
switch(skGetError()) { switch(skGetError()) {
case WSA_INVALID_HANDLE: return "Specified event object handle is invalid."; case WSA_INVALID_HANDLE: return "Specified event object handle is invalid.";
case WSA_NOT_ENOUGH_MEMORY: return "Insufficient memory available."; case WSA_NOT_ENOUGH_MEMORY: return "Insufficient memory available.";
case WSA_INVALID_PARAMETER: return "One or more parameters are invalid."; case WSA_INVALID_PARAMETER: return "One or more parameters are invalid.";
case WSA_OPERATION_ABORTED: return "Overlapped operation aborted."; 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_INCOMPLETE: return "Overlapped I/O event object not in signaled state.";
case WSA_IO_PENDING: return "Overlapped operations will complete later."; case WSA_IO_PENDING: return "Overlapped operations will complete later.";
case WSAEINTR: return "Interrupted function call."; case WSAEINTR: return "Interrupted function call.";
case WSAEBADF: return "File handle is not valid."; case WSAEBADF: return "File handle is not valid.";
case WSAEACCES: return "Permission denied."; case WSAEACCES: return "Permission denied.";
case WSAEFAULT: return "Bad address."; case WSAEFAULT: return "Bad address.";
case WSAEINVAL: return "Invalid argument."; case WSAEINVAL: return "Invalid argument.";
case WSAEMFILE: return "Too many open files."; case WSAEMFILE: return "Too many open files.";
case WSAEWOULDBLOCK: return "Resource temporarily unavailable."; case WSAEWOULDBLOCK: return "Resource temporarily unavailable.";
case WSAEINPROGRESS: return "Operation now in progress."; case WSAEINPROGRESS: return "Operation now in progress.";
case WSAEALREADY: return "Operation already in progress."; case WSAEALREADY: return "Operation already in progress.";
case WSAENOTSOCK: return "Socket operation on nonsocket."; case WSAENOTSOCK: return "Socket operation on nonsocket.";
case WSAEDESTADDRREQ: return "Destination address required."; case WSAEDESTADDRREQ: return "Destination address required.";
case WSAEMSGSIZE: return "Message too long."; case WSAEMSGSIZE: return "Message too long.";
case WSAEPROTOTYPE: return "Protocol wrong type for socket."; case WSAEPROTOTYPE: return "Protocol wrong type for socket.";
case WSAENOPROTOOPT: return "Bad protocol option."; case WSAENOPROTOOPT: return "Bad protocol option.";
case WSAEPROTONOSUPPORT: return "Protocol not supported."; case WSAEPROTONOSUPPORT: return "Protocol not supported.";
case WSAESOCKTNOSUPPORT: return "Socket type not supported."; case WSAESOCKTNOSUPPORT: return "Socket type not supported.";
case WSAEOPNOTSUPP: return "Operation not supported."; case WSAEOPNOTSUPP: return "Operation not supported.";
case WSAEPFNOSUPPORT: return "Protocol family not supported."; case WSAEPFNOSUPPORT: return "Protocol family not supported.";
case WSAEAFNOSUPPORT: return "Address family not supported by protocol family."; case WSAEAFNOSUPPORT: return "Address family not supported by protocol family.";
case WSAEADDRINUSE: return "Address already in use."; case WSAEADDRINUSE: return "Address already in use.";
case WSAEADDRNOTAVAIL: return "Cannot assign requested address."; case WSAEADDRNOTAVAIL: return "Cannot assign requested address.";
case WSAENETDOWN: return "Network is down."; case WSAENETDOWN: return "Network is down.";
case WSAENETUNREACH: return "Network is unreachable."; case WSAENETUNREACH: return "Network is unreachable.";
case WSAENETRESET: return "Network dropped connection on reset."; case WSAENETRESET: return "Network dropped connection on reset.";
case WSAECONNABORTED: return "Software caused connection abort."; case WSAECONNABORTED: return "Software caused connection abort.";
case WSAECONNRESET: return "Connection reset by peer."; case WSAECONNRESET: return "Connection reset by peer.";
case WSAENOBUFS: return "No buffer space available."; case WSAENOBUFS: return "No buffer space available.";
case WSAEISCONN: return "Socket is already connected."; case WSAEISCONN: return "Socket is already connected.";
case WSAENOTCONN: return "Socket is not connected."; case WSAENOTCONN: return "Socket is not connected.";
case WSAESHUTDOWN: return "Cannot send after socket shutdown."; case WSAESHUTDOWN: return "Cannot send after socket shutdown.";
case WSAETOOMANYREFS: return "Too many references."; case WSAETOOMANYREFS: return "Too many references.";
case WSAETIMEDOUT: return "Connection timed out."; case WSAETIMEDOUT: return "Connection timed out.";
case WSAECONNREFUSED: return "Connection refused."; case WSAECONNREFUSED: return "Connection refused.";
case WSAELOOP: return "Cannot translate name."; case WSAELOOP: return "Cannot translate name.";
case WSAENAMETOOLONG: return "Name too long."; case WSAENAMETOOLONG: return "Name too long.";
case WSAEHOSTDOWN: return "Host is down."; case WSAEHOSTDOWN: return "Host is down.";
case WSAEHOSTUNREACH: return "No route to host."; case WSAEHOSTUNREACH: return "No route to host.";
case WSAENOTEMPTY: return "Directory not empty."; case WSAENOTEMPTY: return "Directory not empty.";
case WSAEPROCLIM: return "Too many processes."; case WSAEPROCLIM: return "Too many processes.";
case WSAEUSERS: return "User quota exceeded."; case WSAEUSERS: return "User quota exceeded.";
case WSAEDQUOT: return "Disk quota exceeded."; case WSAEDQUOT: return "Disk quota exceeded.";
case WSAESTALE: return "Stale file handle reference."; case WSAESTALE: return "Stale file handle reference.";
case WSAEREMOTE: return "Item is remote."; case WSAEREMOTE: return "Item is remote.";
case WSASYSNOTREADY: return "Network subsystem is unavailable."; case WSASYSNOTREADY: return "Network subsystem is unavailable.";
case WSAVERNOTSUPPORTED: return "Winsock.dll version out of range."; case WSAVERNOTSUPPORTED: return "Winsock.dll version out of range.";
case WSANOTINITIALISED: return "Successful WSAStartup not yet performed."; case WSANOTINITIALISED: return "Successful WSAStartup not yet performed.";
case WSAEDISCON: return "Graceful shutdown in progress."; case WSAEDISCON: return "Graceful shutdown in progress.";
case WSAENOMORE: return "No more results."; case WSAENOMORE: return "No more results.";
case WSAECANCELLED: return "Call has been canceled."; case WSAECANCELLED: return "Call has been canceled.";
case WSAEINVALIDPROCTABLE: return "Procedure call table is invalid."; case WSAEINVALIDPROCTABLE: return "Procedure call table is invalid.";
case WSAEINVALIDPROVIDER: return "Service provider is invalid."; case WSAEINVALIDPROVIDER: return "Service provider is invalid.";
case WSAEPROVIDERFAILEDINIT: return "Service provider failed to initialize."; case WSAEPROVIDERFAILEDINIT: return "Service provider failed to initialize.";
case WSASYSCALLFAILURE: return "System call failure."; case WSASYSCALLFAILURE: return "System call failure.";
case WSASERVICE_NOT_FOUND: return "Service not found."; case WSASERVICE_NOT_FOUND: return "Service not found.";
case WSATYPE_NOT_FOUND: return "Class type not found."; case WSATYPE_NOT_FOUND: return "Class type not found.";
case WSA_E_NO_MORE: return "No more results."; case WSA_E_NO_MORE: return "No more results.";
case WSA_E_CANCELLED: return "Call was canceled."; case WSA_E_CANCELLED: return "Call was canceled.";
case WSAEREFUSED: return "Database query was refused."; case WSAEREFUSED: return "Database query was refused.";
case WSAHOST_NOT_FOUND: return "Host not found."; case WSAHOST_NOT_FOUND: return "Host not found.";
case WSATRY_AGAIN: return "Nonauthoritative host not found."; case WSATRY_AGAIN: return "Nonauthoritative host not found.";
case WSANO_RECOVERY: return "This is a nonrecoverable error."; case WSANO_RECOVERY: return "This is a nonrecoverable error.";
case WSANO_DATA: return "Valid name, no data record of requested type."; case WSANO_DATA: return "Valid name, no data record of requested type.";
case WSA_QOS_RECEIVERS: return "QoS receivers."; case WSA_QOS_RECEIVERS: return "QoS receivers.";
case WSA_QOS_SENDERS: return "QoS senders."; case WSA_QOS_SENDERS: return "QoS senders.";
case WSA_QOS_NO_SENDERS: return "No QoS senders."; case WSA_QOS_NO_SENDERS: return "No QoS senders.";
case WSA_QOS_NO_RECEIVERS: return "QoS no receivers."; case WSA_QOS_NO_RECEIVERS: return "QoS no receivers.";
case WSA_QOS_REQUEST_CONFIRMED: return "QoS request confirmed."; case WSA_QOS_REQUEST_CONFIRMED: return "QoS request confirmed.";
case WSA_QOS_ADMISSION_FAILURE: return "QoS admission error."; case WSA_QOS_ADMISSION_FAILURE: return "QoS admission error.";
case WSA_QOS_POLICY_FAILURE: return "QoS policy failure."; case WSA_QOS_POLICY_FAILURE: return "QoS policy failure.";
case WSA_QOS_BAD_STYLE: return "QoS bad style."; case WSA_QOS_BAD_STYLE: return "QoS bad style.";
case WSA_QOS_BAD_OBJECT: return "QoS bad object."; case WSA_QOS_BAD_OBJECT: return "QoS bad object.";
case WSA_QOS_TRAFFIC_CTRL_ERROR: return "QoS traffic control error."; case WSA_QOS_TRAFFIC_CTRL_ERROR: return "QoS traffic control error.";
case WSA_QOS_GENERIC_ERROR: return "QoS generic error."; case WSA_QOS_GENERIC_ERROR: return "QoS generic error.";
case WSA_QOS_ESERVICETYPE: return "QoS service type error."; case WSA_QOS_ESERVICETYPE: return "QoS service type error.";
case WSA_QOS_EFLOWSPEC: return "QoS flowspec error."; case WSA_QOS_EFLOWSPEC: return "QoS flowspec error.";
case WSA_QOS_EPROVSPECBUF: return "Invalid QoS provider buffer."; case WSA_QOS_EPROVSPECBUF: return "Invalid QoS provider buffer.";
case WSA_QOS_EFILTERSTYLE: return "Invalid QoS filter style."; case WSA_QOS_EFILTERSTYLE: return "Invalid QoS filter style.";
case WSA_QOS_EFILTERTYPE: return "Invalid QoS filter type."; case WSA_QOS_EFILTERTYPE: return "Invalid QoS filter type.";
case WSA_QOS_EFILTERCOUNT: return "Incorrect QoS filter count."; case WSA_QOS_EFILTERCOUNT: return "Incorrect QoS filter count.";
case WSA_QOS_EOBJLENGTH: return "Invalid QoS object length."; case WSA_QOS_EOBJLENGTH: return "Invalid QoS object length.";
case WSA_QOS_EFLOWCOUNT: return "Incorrect QoS flow count."; case WSA_QOS_EFLOWCOUNT: return "Incorrect QoS flow count.";
case WSA_QOS_EUNKOWNPSOBJ: return "Unrecognized QoS object."; case WSA_QOS_EUNKOWNPSOBJ: return "Unrecognized QoS object.";
case WSA_QOS_EPOLICYOBJ: return "Invalid QoS policy object."; case WSA_QOS_EPOLICYOBJ: return "Invalid QoS policy object.";
case WSA_QOS_EFLOWDESC: return "Invalid QoS flow descriptor."; case WSA_QOS_EFLOWDESC: return "Invalid QoS flow descriptor.";
case WSA_QOS_EPSFLOWSPEC: return "Invalid QoS provider-specific flowspec."; case WSA_QOS_EPSFLOWSPEC: return "Invalid QoS provider-specific flowspec.";
case WSA_QOS_EPSFILTERSPEC: return "Invalid QoS provider-specific filterspec."; case WSA_QOS_EPSFILTERSPEC: return "Invalid QoS provider-specific filterspec.";
case WSA_QOS_ESDMODEOBJ: return "Invalid QoS shape discard mode object."; case WSA_QOS_ESDMODEOBJ: return "Invalid QoS shape discard mode object.";
case WSA_QOS_ESHAPERATEOBJ: return "Invalid QoS shaping rate object."; case WSA_QOS_ESHAPERATEOBJ: return "Invalid QoS shaping rate object.";
case WSA_QOS_RESERVED_PETYPE: return "Reserved policy QoS element type."; case WSA_QOS_RESERVED_PETYPE: return "Reserved policy QoS element type.";
} }
return "(nothing)"; return "(nothing)";
} }
#endif #endif

View file

@ -1,93 +1,93 @@
#pragma once #pragma once
#include "collatypes.h" #include "collatypes.h"
#if COLLA_TCC #if COLLA_TCC
#include "tcc/colla_tcc.h" #include "tcc/colla_tcc.h"
#elif COLLA_WIN #elif COLLA_WIN
#define _WINSOCK_DEPRECATED_NO_WARNINGS #define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <winsock2.h> #include <winsock2.h>
#elif COLLA_LIN || COLLA_OSX #elif COLLA_LIN || COLLA_OSX
#include <sys/socket.h> #include <sys/socket.h>
#include <netinet/in.h> #include <netinet/in.h>
#endif #endif
typedef uintptr_t socket_t; typedef uintptr_t socket_t;
typedef struct sockaddr skaddr_t; typedef struct sockaddr skaddr_t;
typedef struct sockaddr_in skaddrin_t; typedef struct sockaddr_in skaddrin_t;
typedef struct pollfd skpoll_t; typedef struct pollfd skpoll_t;
#define SOCKET_ERROR (-1) #define SOCKET_ERROR (-1)
typedef enum { typedef enum {
SOCK_TCP, SOCK_TCP,
SOCK_UDP, SOCK_UDP,
} sktype_e; } sktype_e;
// Initialize sockets, returns true on success // Initialize sockets, returns true on success
bool skInit(void); bool skInit(void);
// Terminates sockets, returns true on success // Terminates sockets, returns true on success
bool skCleanup(void); bool skCleanup(void);
// Opens a socket, check socket_t with skValid // Opens a socket, check socket_t with skValid
socket_t skOpen(sktype_e type); socket_t skOpen(sktype_e type);
// Opens a socket using 'protocol', options are // Opens a socket using 'protocol', options are
// ip, icmp, ggp, tcp, egp, pup, udp, hmp, xns-idp, rdp // ip, icmp, ggp, tcp, egp, pup, udp, hmp, xns-idp, rdp
// check socket_t with skValid // check socket_t with skValid
socket_t skOpenEx(const char *protocol); socket_t skOpenEx(const char *protocol);
// Opens a socket, check socket_t with skValid // Opens a socket, check socket_t with skValid
socket_t skOpenPro(int af, int type, int protocol); socket_t skOpenPro(int af, int type, int protocol);
// Checks that a opened socket is valid, returns true on success // Checks that a opened socket is valid, returns true on success
bool skIsValid(socket_t sock); bool skIsValid(socket_t sock);
// Closes a socket, returns true on success // Closes a socket, returns true on success
bool skClose(socket_t sock); bool skClose(socket_t sock);
// Fill out a sk_addrin_t structure with "ip" and "port" // Fill out a sk_addrin_t structure with "ip" and "port"
skaddrin_t skAddrinInit(const char *ip, uint16_t port); skaddrin_t skAddrinInit(const char *ip, uint16_t port);
// Associate a local address with a socket // Associate a local address with a socket
bool skBind(socket_t sock, const char *ip, uint16_t port); bool skBind(socket_t sock, const char *ip, uint16_t port);
// Associate a local address with a socket // Associate a local address with a socket
bool skBindPro(socket_t sock, const skaddr_t *name, int namelen); 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 // Place a socket in a state in which it is listening for an incoming connection
bool skListen(socket_t sock); bool skListen(socket_t sock);
// Place a socket in a state in which it is listening for an incoming connection // Place a socket in a state in which it is listening for an incoming connection
bool skListenPro(socket_t sock, int backlog); bool skListenPro(socket_t sock, int backlog);
// Permits an incoming connection attempt on a socket // Permits an incoming connection attempt on a socket
socket_t skAccept(socket_t sock); socket_t skAccept(socket_t sock);
// Permits an incoming connection attempt on a socket // Permits an incoming connection attempt on a socket
socket_t skAcceptPro(socket_t sock, skaddr_t *addr, int *addrlen); 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 // 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); bool skConnect(socket_t sock, const char *server, unsigned short server_port);
// Connects to a server, returns true on success // Connects to a server, returns true on success
bool skConnectPro(socket_t sock, const skaddr_t *name, int namelen); bool skConnectPro(socket_t sock, const skaddr_t *name, int namelen);
// Sends data on a socket, returns true on success // Sends data on a socket, returns true on success
int skSend(socket_t sock, const void *buf, int len); int skSend(socket_t sock, const void *buf, int len);
// Sends data on a socket, returns true on success // Sends data on a socket, returns true on success
int skSendPro(socket_t sock, const void *buf, int len, int flags); int skSendPro(socket_t sock, const void *buf, int len, int flags);
// Sends data to a specific destination // Sends data to a specific destination
int skSendTo(socket_t sock, const void *buf, int len, const skaddrin_t *to); int skSendTo(socket_t sock, const void *buf, int len, const skaddrin_t *to);
// Sends data to a specific destination // Sends data to a specific destination
int skSendToPro(socket_t sock, const void *buf, int len, int flags, const skaddr_t *to, int tolen); 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 // 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); 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 // 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); int skReceivePro(socket_t sock, void *buf, int len, int flags);
// Receives a datagram and stores the source address. // Receives a datagram and stores the source address.
int skReceiveFrom(socket_t sock, void *buf, int len, skaddrin_t *from); int skReceiveFrom(socket_t sock, void *buf, int len, skaddrin_t *from);
// Receives a datagram and stores the source address. // Receives a datagram and stores the source address.
int skReceiveFromPro(socket_t sock, void *buf, int len, int flags, skaddr_t *from, int *fromlen); int skReceiveFromPro(socket_t sock, void *buf, int len, int flags, skaddr_t *from, int *fromlen);
// Wait for an event on some sockets // Wait for an event on some sockets
int skPoll(skpoll_t *to_poll, int num_to_poll, int timeout); int skPoll(skpoll_t *to_poll, int num_to_poll, int timeout);
// Returns latest socket error, returns 0 if there is no error // Returns latest socket error, returns 0 if there is no error
int skGetError(void); int skGetError(void);
// Returns a human-readable string from a skGetError // Returns a human-readable string from a skGetError
const char *skGetErrorString(void); const char *skGetErrorString(void);

View file

@ -219,6 +219,17 @@ STBSP__PUBLICDEC void STB_SPRINTF_DECORATE(set_separators)(char comma, char peri
#ifdef STB_SPRINTF_IMPLEMENTATION #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__uint32 unsigned int
#define stbsp__int32 signed 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__int64
#undef STBSP__UNALIGNED #undef STBSP__UNALIGNED
#if COLLA_CLANG
#pragma clang diagnostic pop
#endif
#endif // STB_SPRINTF_IMPLEMENTATION #endif // STB_SPRINTF_IMPLEMENTATION
/* /*

View file

@ -1,399 +1,408 @@
#include "str.h" #include "str.h"
#include "warnings/colla_warn_beg.h" #include "warnings/colla_warn_beg.h"
#include "arena.h" #include "arena.h"
#include "format.h" #include "format.h"
#include "tracelog.h" #include "tracelog.h"
#if COLLA_WIN #if COLLA_WIN
#define WIN32_LEAN_AND_MEAN #include <windows.h>
#include <windows.h>
#else
#else
#include <wchar.h>
#include <wchar.h>
#endif
#endif
#if COLLA_TCC
#if COLLA_TCC #include "tcc/colla_tcc.h"
#include "tcc/colla_tcc.h" #endif
#endif
// == STR_T ========================================================
// == STR_T ========================================================
str_t strInit(arena_t *arena, const char *buf) {
str_t strInit(arena_t *arena, const char *buf) { return buf ? strInitLen(arena, buf, strlen(buf)) : STR_EMPTY;
return buf ? strInitLen(arena, buf, strlen(buf)) : (str_t){0}; }
}
str_t strInitLen(arena_t *arena, const char *buf, usize len) {
str_t strInitLen(arena_t *arena, const char *buf, usize len) { if (!buf || !len) return STR_EMPTY;
if (!buf || !len) return (str_t){0};
str_t out = {
str_t out = { .buf = alloc(arena, char, len + 1),
.buf = alloc(arena, char, len + 1), .len = len
.len = len };
};
memcpy(out.buf, buf, len);
memcpy(out.buf, buf, len);
return out;
return out; }
}
str_t strInitView(arena_t *arena, strview_t view) {
str_t strInitView(arena_t *arena, strview_t view) { return strInitLen(arena, view.buf, view.len);
return strInitLen(arena, view.buf, view.len); }
}
str_t strFmt(arena_t *arena, const char *fmt, ...) {
str_t strFmt(arena_t *arena, const char *fmt, ...) { va_list args;
va_list args; va_start(args, fmt);
va_start(args, fmt); str_t out = strFmtv(arena, fmt, args);
str_t out = strFmtv(arena, fmt, args); va_end(args);
va_end(args); return out;
return out; }
}
str_t strFmtv(arena_t *arena, const char *fmt, va_list args) {
str_t strFmtv(arena_t *arena, const char *fmt, va_list args) { va_list vcopy;
va_list vcopy; va_copy(vcopy, args);
va_copy(vcopy, args); // stb_vsnprintf returns the length + null_term
// stb_vsnprintf returns the length + null_term int len = fmtBufferv(NULL, 0, fmt, vcopy);
int len = fmtBufferv(NULL, 0, fmt, vcopy); va_end(vcopy);
va_end(vcopy);
char *buffer = alloc(arena, char, len + 1);
char *buffer = alloc(arena, char, len + 1); fmtBufferv(buffer, len + 1, fmt, args);
fmtBufferv(buffer, len + 1, fmt, args);
return (str_t) { .buf = buffer, .len = (usize)len };
return (str_t) { .buf = buffer, .len = (usize)len }; }
}
str_t strFromWChar(arena_t *arena, const wchar_t *src) {
str_t strFromWChar(arena_t *arena, const wchar_t *src, usize srclen) { return strFromWCharLen(arena, src, 0);
if (!src) return (str_t){0}; }
if (!srclen) srclen = wcslen(src);
str_t strFromWCharLen(arena_t *arena, const wchar_t *src, usize srclen) {
str_t out = {0}; if (!src) return STR_EMPTY;
if (!srclen) srclen = wcslen(src);
#if COLLA_WIN
int outlen = WideCharToMultiByte( str_t out = {0};
CP_UTF8, 0,
src, (int)srclen, #if COLLA_WIN
NULL, 0, int outlen = WideCharToMultiByte(
NULL, NULL CP_UTF8, 0,
); src, (int)srclen,
NULL, 0,
if (outlen == 0) { NULL, NULL
unsigned long error = GetLastError(); );
if (error == ERROR_NO_UNICODE_TRANSLATION) {
err("couldn't translate wide string (%S) to utf8, no unicode translation", src); if (outlen == 0) {
} unsigned long error = GetLastError();
else { if (error == ERROR_NO_UNICODE_TRANSLATION) {
err("couldn't translate wide string (%S) to utf8, %u", error); err("couldn't translate wide string (%S) to utf8, no unicode translation", src);
} }
else {
return (str_t){0}; err("couldn't translate wide string (%S) to utf8, %u", error);
} }
out.buf = alloc(arena, char, outlen + 1); return STR_EMPTY;
WideCharToMultiByte( }
CP_UTF8, 0,
src, (int)srclen, out.buf = alloc(arena, char, outlen + 1);
out.buf, outlen, WideCharToMultiByte(
NULL, NULL CP_UTF8, 0,
); src, (int)srclen,
out.buf, outlen,
out.len = outlen; NULL, NULL
);
#elif COLLA_LIN
fatal("strFromWChar not implemented yet!"); out.len = outlen;
#endif
#elif COLLA_LIN
return out; fatal("strFromWChar not implemented yet!");
} #endif
bool strEquals(str_t a, str_t b) { return out;
return strCompare(a, b) == 0; }
}
bool strEquals(str_t a, str_t b) {
int strCompare(str_t a, str_t b) { return strCompare(a, b) == 0;
return a.len == b.len ? }
memcmp(a.buf, b.buf, a.len) :
(int)(a.len - b.len); int strCompare(str_t a, str_t b) {
} return a.len == b.len ?
memcmp(a.buf, b.buf, a.len) :
str_t strDup(arena_t *arena, str_t src) { (int)(a.len - b.len);
return strInitLen(arena, src.buf, src.len); }
}
str_t strDup(arena_t *arena, str_t src) {
bool strIsEmpty(str_t ctx) { return strInitLen(arena, src.buf, src.len);
return ctx.len == 0 || ctx.buf == NULL; }
}
bool strIsEmpty(str_t ctx) {
void strReplace(str_t *ctx, char from, char to) { return ctx.len == 0 || ctx.buf == NULL;
if (!ctx) return; }
char *buf = ctx->buf;
for (usize i = 0; i < ctx->len; ++i) { void strReplace(str_t *ctx, char from, char to) {
buf[i] = buf[i] == from ? to : buf[i]; 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 }; strview_t strSub(str_t ctx, usize from, usize to) {
} if (to > ctx.len) to = ctx.len;
if (from > to) from = to;
void strLower(str_t *ctx) { return (strview_t){ ctx.buf + from, to - from };
char *buf = ctx->buf; }
for (usize i = 0; i < ctx->len; ++i) {
buf[i] = (buf[i] >= 'A' && buf[i] <= 'Z') ? void strLower(str_t *ctx) {
buf[i] += 'a' - 'A' : char *buf = ctx->buf;
buf[i]; 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') ? void strUpper(str_t *ctx) {
buf[i] -= 'a' - 'A' : char *buf = ctx->buf;
buf[i]; 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 strToLower(arena_t *arena, str_t ctx) {
strLower(&ctx);
str_t strToUpper(arena_t *arena, str_t ctx) { return strDup(arena, ctx);
strUpper(&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){ // == STRVIEW_T ====================================================
.buf = cstr,
.len = cstr ? strlen(cstr) : 0, 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 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 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 strvIsEmpty(strview_t ctx) {
bool strvEquals(strview_t a, strview_t b) { return ctx.len == 0 || !ctx.buf;
return strvCompare(a, b) == 0; }
}
bool strvEquals(strview_t a, strview_t b) {
int strvCompare(strview_t a, strview_t b) { return strvCompare(a, b) == 0;
return a.len == b.len ? }
memcmp(a.buf, b.buf, a.len) :
(int)(a.len - b.len); int strvCompare(strview_t a, strview_t b) {
} return a.len == b.len ?
memcmp(a.buf, b.buf, a.len) :
wchar_t *strvToWChar(arena_t *arena, strview_t ctx, usize *outlen) { (int)(a.len - b.len);
wchar_t *out = NULL; }
int len = 0;
char strvFront(strview_t ctx) {
if (strvIsEmpty(ctx)) { return ctx.len > 0 ? ctx.buf[0] : '\0';
goto error; }
}
char strvBack(strview_t ctx) {
#if COLLA_WIN return ctx.len > 0 ? ctx.buf[ctx.len - 1] : '\0';
len = MultiByteToWideChar( }
CP_UTF8, 0,
ctx.buf, (int)ctx.len,
NULL, 0 wchar_t *strvToWChar(arena_t *arena, strview_t ctx, usize *outlen) {
); wchar_t *out = NULL;
int len = 0;
if (len == 0) {
unsigned long error = GetLastError(); if (strvIsEmpty(ctx)) {
if (error == ERROR_NO_UNICODE_TRANSLATION) { goto error;
err("couldn't translate string (%v) to a wide string, no unicode translation", ctx); }
}
else { #if COLLA_WIN
err("couldn't translate string (%v) to a wide string, %u", ctx, error); len = MultiByteToWideChar(
} CP_UTF8, 0,
ctx.buf, (int)ctx.len,
goto error; NULL, 0
} );
out = alloc(arena, wchar_t, len + 1); if (len == 0) {
unsigned long error = GetLastError();
MultiByteToWideChar( if (error == ERROR_NO_UNICODE_TRANSLATION) {
CP_UTF8, 0, err("couldn't translate string (%v) to a wide string, no unicode translation", ctx);
ctx.buf, (int)ctx.len, }
out, len else {
); err("couldn't translate string (%v) to a wide string, %u", ctx, error);
}
#elif COLLA_LIN
fatal("strFromWChar not implemented yet!"); goto error;
#endif }
error: out = alloc(arena, wchar_t, len + 1);
if (outlen) {
*outlen = (usize)len; MultiByteToWideChar(
} CP_UTF8, 0,
return out; ctx.buf, (int)ctx.len,
} out, len
);
TCHAR *strvToTChar(arena_t *arena, strview_t str) {
#if UNICODE #elif COLLA_LIN
return strvToWChar(arena, str, NULL); fatal("strFromWChar not implemented yet!");
#else #endif
char *cstr = alloc(arena, char, str.len + 1);
memcpy(cstr, str.buf, str.len); error:
return cstr; if (outlen) {
#endif *outlen = (usize)len;
} }
return out;
strview_t strvRemovePrefix(strview_t ctx, usize n) { }
if (n > ctx.len) n = ctx.len;
return (strview_t){ TCHAR *strvToTChar(arena_t *arena, strview_t str) {
.buf = ctx.buf + n, #if UNICODE
.len = ctx.len - n, return strvToWChar(arena, str, NULL);
}; #else
} char *cstr = alloc(arena, char, str.len + 1);
memcpy(cstr, str.buf, str.len);
strview_t strvRemoveSuffix(strview_t ctx, usize n) { return cstr;
if (n > ctx.len) n = ctx.len; #endif
return (strview_t){ }
.buf = ctx.buf,
.len = ctx.len - n, strview_t strvRemovePrefix(strview_t ctx, usize n) {
}; if (n > ctx.len) n = ctx.len;
} return (strview_t){
.buf = ctx.buf + n,
strview_t strvTrim(strview_t ctx) { .len = ctx.len - n,
return strvTrimLeft(strvTrimRight(ctx)); };
} }
strview_t strvTrimLeft(strview_t ctx) { strview_t strvRemoveSuffix(strview_t ctx, usize n) {
strview_t out = ctx; if (n > ctx.len) n = ctx.len;
for (usize i = 0; i < ctx.len; ++i) { return (strview_t){
char c = ctx.buf[i]; .buf = ctx.buf,
if (c != ' ' && (c < '\t' || c > '\r')) { .len = ctx.len - n,
break; };
} }
out.buf++;
out.len--; strview_t strvTrim(strview_t ctx) {
} return strvTrimLeft(strvTrimRight(ctx));
return out; }
}
strview_t strvTrimLeft(strview_t ctx) {
strview_t strvTrimRight(strview_t ctx) { strview_t out = ctx;
strview_t out = ctx; for (usize i = 0; i < ctx.len; ++i) {
for (isize i = ctx.len - 1; i >= 0; --i) { char c = ctx.buf[i];
char c = ctx.buf[i]; if (c != ' ' && (c < '\t' || c > '\r')) {
if (c != ' ' && (c < '\t' || c > '\r')) { break;
break; }
} out.buf++;
out.len--; out.len--;
} }
return out; return out;
} }
strview_t strvSub(strview_t ctx, usize from, usize to) { strview_t strvTrimRight(strview_t ctx) {
if (to > ctx.len) to = ctx.len; strview_t out = ctx;
if (from > to) from = to; for (isize i = ctx.len - 1; i >= 0; --i) {
return (strview_t){ ctx.buf + from, to - from }; char c = ctx.buf[i];
} if (c != ' ' && (c < '\t' || c > '\r')) {
break;
bool strvStartsWith(strview_t ctx, char c) { }
return ctx.len > 0 && ctx.buf[0] == c; out.len--;
} }
return out;
bool strvStartsWithView(strview_t ctx, strview_t view) { }
return ctx.len >= view.len && memcmp(ctx.buf, view.buf, view.len) == 0;
} strview_t strvSub(strview_t ctx, usize from, usize to) {
if (to > ctx.len) to = ctx.len;
bool strvEndsWith(strview_t ctx, char c) { if (from > to) from = to;
return ctx.len > 0 && ctx.buf[ctx.len - 1] == c; return (strview_t){ ctx.buf + from, to - from };
} }
bool strvEndsWithView(strview_t ctx, strview_t view) { bool strvStartsWith(strview_t ctx, char c) {
return ctx.len >= view.len && memcmp(ctx.buf + ctx.len, view.buf, view.len) == 0; return ctx.len > 0 && ctx.buf[0] == c;
} }
bool strvContains(strview_t ctx, char c) { bool strvStartsWithView(strview_t ctx, strview_t view) {
for(usize i = 0; i < ctx.len; ++i) { return ctx.len >= view.len && memcmp(ctx.buf, view.buf, view.len) == 0;
if(ctx.buf[i] == c) { }
return true;
} bool strvEndsWith(strview_t ctx, char c) {
} return ctx.len > 0 && ctx.buf[ctx.len - 1] == c;
return false; }
}
bool strvEndsWithView(strview_t ctx, strview_t view) {
bool strvContainsView(strview_t ctx, strview_t view) { return ctx.len >= view.len && memcmp(ctx.buf + ctx.len - view.len, view.buf, view.len) == 0;
if (ctx.len < view.len) return false; }
usize end = ctx.len - view.len;
for (usize i = 0; i < end; ++i) { bool strvContains(strview_t ctx, char c) {
if (memcmp(ctx.buf + i, view.buf, view.len) == 0) { for(usize i = 0; i < ctx.len; ++i) {
return true; if(ctx.buf[i] == c) {
} return true;
} }
return false; }
} return false;
}
usize strvFind(strview_t ctx, char c, usize from) {
for (usize i = from; i < ctx.len; ++i) { bool strvContainsView(strview_t ctx, strview_t view) {
if (ctx.buf[i] == c) { if (ctx.len < view.len) return false;
return i; usize end = ctx.len - view.len;
} for (usize i = 0; i < end; ++i) {
} if (memcmp(ctx.buf + i, view.buf, view.len) == 0) {
return STR_NONE; return true;
} }
}
usize strvFindView(strview_t ctx, strview_t view, usize from) { return false;
if (ctx.len < view.len) return STR_NONE; }
usize end = ctx.len - view.len;
for (usize i = from; i < end; ++i) { usize strvFind(strview_t ctx, char c, usize from) {
if (memcmp(ctx.buf + i, view.buf, view.len) == 0) { for (usize i = from; i < ctx.len; ++i) {
return i; if (ctx.buf[i] == c) {
} return i;
} }
return STR_NONE; }
} return STR_NONE;
}
usize strvRFind(strview_t ctx, char c, usize from_right) {
if (from_right > ctx.len) from_right = ctx.len; usize strvFindView(strview_t ctx, strview_t view, usize from) {
isize end = (isize)(ctx.len - from_right); if (ctx.len < view.len) return STR_NONE;
for (isize i = end; i >= 0; --i) { usize end = ctx.len - view.len;
if (ctx.buf[i] == c) { for (usize i = from; i < end; ++i) {
return (usize)i; if (memcmp(ctx.buf + i, view.buf, view.len) == 0) {
} return i;
} }
return STR_NONE; }
} return STR_NONE;
}
usize strvRFindView(strview_t ctx, strview_t view, usize from_right) {
if (from_right > ctx.len) from_right = ctx.len; usize strvRFind(strview_t ctx, char c, usize from_right) {
isize end = (isize)(ctx.len - from_right); if (from_right > ctx.len) from_right = ctx.len;
if (end < (isize)view.len) return STR_NONE; isize end = (isize)(ctx.len - from_right);
for (isize i = end - view.len; i >= 0; --i) { for (isize i = end; i >= 0; --i) {
if (memcmp(ctx.buf + i, view.buf, view.len) == 0) { if (ctx.buf[i] == c) {
return (usize)i; return (usize)i;
} }
} }
return STR_NONE; return STR_NONE;
} }
#include "warnings/colla_warn_beg.h" usize strvRFindView(strview_t ctx, strview_t view, usize from_right) {
if (from_right > ctx.len) from_right = ctx.len;
#undef CP_UTF8 isize end = (isize)(ctx.len - from_right);
#undef ERROR_NO_UNICODE_TRANSLATION 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"

View file

@ -1,109 +1,119 @@
#pragma once #pragma once
#include <stdarg.h> // va_list #include <stdarg.h> // va_list
#include <string.h> // strlen #include <string.h> // strlen
#include "collatypes.h" #include "collatypes.h"
typedef struct arena_t arena_t; typedef struct arena_t arena_t;
#define STR_NONE SIZE_MAX #define STR_NONE SIZE_MAX
typedef struct { typedef struct {
char *buf; char *buf;
usize len; usize len;
} str_t; } str_t;
typedef struct { typedef struct {
const char *buf; const char *buf;
usize len; usize len;
} strview_t; } strview_t;
// == STR_T ======================================================== // == STR_T ========================================================
#define str__1(arena, x) \ #define str__1(arena, x) \
_Generic((x), \ _Generic((x), \
const char *: strInit, \ const char *: strInit, \
char *: strInit, \ char *: strInit, \
strview_t: strInitView \ const wchar_t *: strFromWChar, \
)(arena, x) wchar_t *: strFromWChar, \
strview_t: strInitView \
#define str__2(arena, cstr, clen) strInitLen(arena, cstr, clen) )(arena, x)
#define str__impl(_1, _2, n, ...) str__##n
#define str__2(arena, cstr, clen) strInitLen(arena, cstr, clen)
// either: #define str__impl(_1, _2, n, ...) str__##n
// arena_t arena, [const] char *cstr, [usize len]
// arena_t arena, strview_t view // either:
#define str(arena, ...) str__impl(__VA_ARGS__, 2, 1, 0)(arena, __VA_ARGS__) // arena_t arena, [const] char *cstr, [usize len]
// arena_t arena, strview_t view
str_t strInit(arena_t *arena, const char *buf); #define str(arena, ...) str__impl(__VA_ARGS__, 2, 1, 0)(arena, __VA_ARGS__)
str_t strInitLen(arena_t *arena, const char *buf, usize len);
str_t strInitView(arena_t *arena, strview_t view); #define STR_EMPTY (str_t){0}
str_t strFmt(arena_t *arena, const char *fmt, ...);
str_t strFmtv(arena_t *arena, const char *fmt, va_list args); str_t strInit(arena_t *arena, const char *buf);
str_t strInitLen(arena_t *arena, const char *buf, usize len);
str_t strFromWChar(arena_t *arena, const wchar_t *src, usize srclen); str_t strInitView(arena_t *arena, strview_t view);
str_t strFmt(arena_t *arena, const char *fmt, ...);
bool strEquals(str_t a, str_t b); str_t strFmtv(arena_t *arena, const char *fmt, va_list args);
int strCompare(str_t a, str_t b);
str_t strFromWChar(arena_t *arena, const wchar_t *src);
str_t strDup(arena_t *arena, str_t src); str_t strFromWCharLen(arena_t *arena, const wchar_t *src, usize srclen);
bool strIsEmpty(str_t ctx);
bool strEquals(str_t a, str_t b);
void strReplace(str_t *ctx, char from, char to); int strCompare(str_t a, str_t b);
// if len == SIZE_MAX, copies until end
strview_t strSub(str_t ctx, usize from, usize to); str_t strDup(arena_t *arena, str_t src);
bool strIsEmpty(str_t ctx);
void strLower(str_t *ctx);
void strUpper(str_t *ctx); void strReplace(str_t *ctx, char from, char to);
// if len == SIZE_MAX, copies until end
str_t strToLower(arena_t *arena, str_t ctx); strview_t strSub(str_t ctx, usize from, usize to);
str_t strToUpper(arena_t *arena, str_t ctx);
void strLower(str_t *ctx);
// == STRVIEW_T ==================================================== void strUpper(str_t *ctx);
#define strv__1(x) \ str_t strToLower(arena_t *arena, str_t ctx);
_Generic((x), \ str_t strToUpper(arena_t *arena, str_t ctx);
char *: strvInit, \
const char *: strvInit, \ // == STRVIEW_T ====================================================
str_t: strvInitStr \
)(x) #define strv__1(x) \
_Generic((x), \
#define strv__2(cstr, clen) strvInitLen(cstr, clen) char *: strvInit, \
#define strv__impl(_1, _2, n, ...) strv__##n const char *: strvInit, \
str_t: strvInitStr \
#define strv(...) strv__impl(__VA_ARGS__, 2, 1, 0)(__VA_ARGS__) )(x)
strview_t strvInit(const char *cstr); #define strv__2(cstr, clen) strvInitLen(cstr, clen)
strview_t strvInitLen(const char *buf, usize size); #define strv__impl(_1, _2, n, ...) strv__##n
strview_t strvInitStr(str_t str);
#define strv(...) strv__impl(__VA_ARGS__, 2, 1, 0)(__VA_ARGS__)
bool strvIsEmpty(strview_t ctx);
bool strvEquals(strview_t a, strview_t b); #define STRV_EMPTY (strview_t){0}
int strvCompare(strview_t a, strview_t b);
strview_t strvInit(const char *cstr);
wchar_t *strvToWChar(arena_t *arena, strview_t ctx, usize *outlen); strview_t strvInitLen(const char *buf, usize size);
TCHAR *strvToTChar(arena_t *arena, strview_t str); strview_t strvInitStr(str_t str);
strview_t strvRemovePrefix(strview_t ctx, usize n); bool strvIsEmpty(strview_t ctx);
strview_t strvRemoveSuffix(strview_t ctx, usize n); bool strvEquals(strview_t a, strview_t b);
strview_t strvTrim(strview_t ctx); int strvCompare(strview_t a, strview_t b);
strview_t strvTrimLeft(strview_t ctx);
strview_t strvTrimRight(strview_t ctx); char strvFront(strview_t ctx);
char strvBack(strview_t ctx);
strview_t strvSub(strview_t ctx, usize from, usize to);
wchar_t *strvToWChar(arena_t *arena, strview_t ctx, usize *outlen);
bool strvStartsWith(strview_t ctx, char c); TCHAR *strvToTChar(arena_t *arena, strview_t str);
bool strvStartsWithView(strview_t ctx, strview_t view);
strview_t strvRemovePrefix(strview_t ctx, usize n);
bool strvEndsWith(strview_t ctx, char c); strview_t strvRemoveSuffix(strview_t ctx, usize n);
bool strvEndsWithView(strview_t ctx, strview_t view); strview_t strvTrim(strview_t ctx);
strview_t strvTrimLeft(strview_t ctx);
bool strvContains(strview_t ctx, char c); strview_t strvTrimRight(strview_t ctx);
bool strvContainsView(strview_t ctx, strview_t view);
strview_t strvSub(strview_t ctx, usize from, usize to);
usize strvFind(strview_t ctx, char c, usize from);
usize strvFindView(strview_t ctx, strview_t view, usize from); bool strvStartsWith(strview_t ctx, char c);
bool strvStartsWithView(strview_t ctx, strview_t view);
usize strvRFind(strview_t ctx, char c, usize from_right);
usize strvRFindView(strview_t ctx, strview_t view, usize from_right); 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);

File diff suppressed because it is too large Load diff

View file

@ -1,155 +1,160 @@
#pragma once #pragma once
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
#include <stdarg.h> #include <stdarg.h>
#include "collatypes.h" #include "collatypes.h"
#include "str.h" #include "str.h"
typedef struct arena_t arena_t; typedef struct arena_t arena_t;
/* == INPUT STREAM ============================================ */ /* == INPUT STREAM ============================================ */
typedef struct { typedef struct instream_t {
const char *start; const char *start;
const char *cur; const char *cur;
usize size; usize size;
} instream_t; } instream_t;
// initialize with null-terminated string // initialize with null-terminated string
instream_t istrInit(const char *str); instream_t istrInit(const char *str);
instream_t istrInitLen(const char *str, usize len); instream_t istrInitLen(const char *str, usize len);
// get the current character and advance // get the current character and advance
char istrGet(instream_t *ctx); char istrGet(instream_t *ctx);
// get the current character but don't advance // get the current character but don't advance
char istrPeek(instream_t *ctx); char istrPeek(instream_t *ctx);
// get the next character but don't advance // get the next character but don't advance
char istrPeekNext(instream_t *ctx); char istrPeekNext(instream_t *ctx);
// ignore characters until the delimiter // returns the previous character
void istrIgnore(instream_t *ctx, char delim); char istrPrev(instream_t *ctx);
// ignore characters until the delimiter and skip it // returns the character before the previous
void istrIgnoreAndSkip(instream_t *ctx, char delim); char istrPrevPrev(instream_t *ctx);
// skip n characters // ignore characters until the delimiter
void istrSkip(instream_t *ctx, usize n); void istrIgnore(instream_t *ctx, char delim);
// skips whitespace (' ', '\\n', '\\t', '\\r') // ignore characters until the delimiter and skip it
void istrSkipWhitespace(instream_t *ctx); void istrIgnoreAndSkip(instream_t *ctx, char delim);
// read len bytes into buffer, the buffer will not be null terminated // skip n characters
void istrRead(instream_t *ctx, char *buf, usize len); void istrSkip(instream_t *ctx, usize n);
// read a maximum of len bytes into buffer, the buffer will not be null terminated // skips whitespace (' ', '\\n', '\\t', '\\r')
// returns the number of bytes read void istrSkipWhitespace(instream_t *ctx);
usize istrReadMax(instream_t *ctx, char *buf, usize len); // read len bytes into buffer, the buffer will not be null terminated
// returns to the beginning of the stream void istrRead(instream_t *ctx, char *buf, usize len);
void istrRewind(instream_t *ctx); // read a maximum of len bytes into buffer, the buffer will not be null terminated
// returns back <amount> characters // returns the number of bytes read
void istrRewindN(instream_t *ctx, usize amount); usize istrReadMax(instream_t *ctx, char *buf, usize len);
// returns the number of bytes read from beginning of stream // returns to the beginning of the stream
usize istrTell(instream_t ctx); void istrRewind(instream_t *ctx);
// returns the number of bytes left to read in the stream // returns back <amount> characters
usize istrRemaining(instream_t ctx); void istrRewindN(instream_t *ctx, usize amount);
// return true if the stream doesn't have any new bytes to read // returns the number of bytes read from beginning of stream
bool istrIsFinished(instream_t ctx); usize istrTell(instream_t ctx);
// returns the number of bytes left to read in the stream
bool istrGetBool(instream_t *ctx, bool *val); usize istrRemaining(instream_t ctx);
bool istrGetU8(instream_t *ctx, uint8 *val); // return true if the stream doesn't have any new bytes to read
bool istrGetU16(instream_t *ctx, uint16 *val); bool istrIsFinished(instream_t ctx);
bool istrGetU32(instream_t *ctx, uint32 *val);
bool istrGetU64(instream_t *ctx, uint64 *val); bool istrGetBool(instream_t *ctx, bool *val);
bool istrGetI8(instream_t *ctx, int8 *val); bool istrGetU8(instream_t *ctx, uint8 *val);
bool istrGetI16(instream_t *ctx, int16 *val); bool istrGetU16(instream_t *ctx, uint16 *val);
bool istrGetI32(instream_t *ctx, int32 *val); bool istrGetU32(instream_t *ctx, uint32 *val);
bool istrGetI64(instream_t *ctx, int64 *val); bool istrGetU64(instream_t *ctx, uint64 *val);
bool istrGetFloat(instream_t *ctx, float *val); bool istrGetI8(instream_t *ctx, int8 *val);
bool istrGetDouble(instream_t *ctx, double *val); bool istrGetI16(instream_t *ctx, int16 *val);
str_t istrGetStr(arena_t *arena, instream_t *ctx, char delim); bool istrGetI32(instream_t *ctx, int32 *val);
// get a string of maximum size len, the string is not allocated by the function and will be null terminated bool istrGetI64(instream_t *ctx, int64 *val);
usize istrGetBuf(instream_t *ctx, char *buf, usize buflen); bool istrGetFloat(instream_t *ctx, float *val);
strview_t istrGetView(instream_t *ctx, char delim); bool istrGetDouble(instream_t *ctx, double *val);
strview_t istrGetViewEither(instream_t *ctx, strview_t chars); str_t istrGetStr(arena_t *arena, instream_t *ctx, char delim);
strview_t istrGetViewLen(instream_t *ctx, usize len); // 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);
/* == OUTPUT STREAM =========================================== */ strview_t istrGetView(instream_t *ctx, char delim);
strview_t istrGetViewEither(instream_t *ctx, strview_t chars);
typedef struct { strview_t istrGetViewLen(instream_t *ctx, usize len);
char *beg; strview_t istrGetLine(instream_t *ctx);
arena_t *arena;
} outstream_t; /* == OUTPUT STREAM =========================================== */
outstream_t ostrInit(arena_t *exclusive_arena); typedef struct outstream_t {
void ostrClear(outstream_t *ctx); char *beg;
arena_t *arena;
usize ostrTell(outstream_t *ctx); } outstream_t;
char ostrBack(outstream_t *ctx); outstream_t ostrInit(arena_t *exclusive_arena);
str_t ostrAsStr(outstream_t *ctx); void ostrClear(outstream_t *ctx);
strview_t ostrAsView(outstream_t *ctx);
usize ostrTell(outstream_t *ctx);
void ostrPrintf(outstream_t *ctx, const char *fmt, ...);
void ostrPrintfV(outstream_t *ctx, const char *fmt, va_list args); char ostrBack(outstream_t *ctx);
void ostrPutc(outstream_t *ctx, char c); str_t ostrAsStr(outstream_t *ctx);
void ostrPuts(outstream_t *ctx, strview_t v); strview_t ostrAsView(outstream_t *ctx);
void ostrAppendBool(outstream_t *ctx, bool val); void ostrPrintf(outstream_t *ctx, const char *fmt, ...);
void ostrAppendUInt(outstream_t *ctx, uint64 val); void ostrPrintfV(outstream_t *ctx, const char *fmt, va_list args);
void ostrAppendInt(outstream_t *ctx, int64 val); void ostrPutc(outstream_t *ctx, char c);
void ostrAppendNum(outstream_t *ctx, double val); void ostrPuts(outstream_t *ctx, strview_t v);
/* == OUT BYTE STREAM ========================================= */ void ostrAppendBool(outstream_t *ctx, bool val);
void ostrAppendUInt(outstream_t *ctx, uint64 val);
typedef struct { void ostrAppendInt(outstream_t *ctx, int64 val);
uint8 *beg; void ostrAppendNum(outstream_t *ctx, double val);
arena_t *arena;
} obytestream_t; /* == OUT BYTE STREAM ========================================= */
obytestream_t obstrInit(arena_t *exclusive_arena); typedef struct {
void obstrClear(obytestream_t *ctx); uint8 *beg;
arena_t *arena;
usize obstrTell(obytestream_t *ctx); } obytestream_t;
buffer_t obstrAsBuf(obytestream_t *ctx);
obytestream_t obstrInit(arena_t *exclusive_arena);
void obstrWrite(obytestream_t *ctx, const void *buf, usize buflen); void obstrClear(obytestream_t *ctx);
void obstrPuts(obytestream_t *ctx, strview_t str);
void obstrAppendU8(obytestream_t *ctx, uint8 value); usize obstrTell(obytestream_t *ctx);
void obstrAppendU16(obytestream_t *ctx, uint16 value); buffer_t obstrAsBuf(obytestream_t *ctx);
void obstrAppendU32(obytestream_t *ctx, uint32 value);
void obstrAppendU64(obytestream_t *ctx, uint64 value); void obstrWrite(obytestream_t *ctx, const void *buf, usize buflen);
void obstrAppendI8(obytestream_t *ctx, int8 value); void obstrPuts(obytestream_t *ctx, strview_t str);
void obstrAppendI16(obytestream_t *ctx, int16 value); void obstrAppendU8(obytestream_t *ctx, uint8 value);
void obstrAppendI32(obytestream_t *ctx, int32 value); void obstrAppendU16(obytestream_t *ctx, uint16 value);
void obstrAppendI64(obytestream_t *ctx, int64 value); void obstrAppendU32(obytestream_t *ctx, uint32 value);
void obstrAppendU64(obytestream_t *ctx, uint64 value);
/* == IN BYTE STREAM ========================================== */ void obstrAppendI8(obytestream_t *ctx, int8 value);
void obstrAppendI16(obytestream_t *ctx, int16 value);
typedef struct { void obstrAppendI32(obytestream_t *ctx, int32 value);
const uint8 *start; void obstrAppendI64(obytestream_t *ctx, int64 value);
const uint8 *cur;
usize size; /* == IN BYTE STREAM ========================================== */
} ibytestream_t;
typedef struct {
ibytestream_t ibstrInit(const void *buf, usize len); const uint8 *start;
const uint8 *cur;
usize ibstrRemaining(ibytestream_t *ctx); usize size;
usize ibstrTell(ibytestream_t *ctx); } ibytestream_t;
usize ibstrRead(ibytestream_t *ctx, void *buf, usize buflen);
ibytestream_t ibstrInit(const void *buf, usize len);
uint8 ibstrGetU8(ibytestream_t *ctx);
uint16 ibstrGetU16(ibytestream_t *ctx); usize ibstrRemaining(ibytestream_t *ctx);
uint32 ibstrGetU32(ibytestream_t *ctx); usize ibstrTell(ibytestream_t *ctx);
uint64 ibstrGetU64(ibytestream_t *ctx); usize ibstrRead(ibytestream_t *ctx, void *buf, usize buflen);
int8 ibstrGetI8(ibytestream_t *ctx);
int16 ibstrGetI16(ibytestream_t *ctx); uint8 ibstrGetU8(ibytestream_t *ctx);
int32 ibstrGetI32(ibytestream_t *ctx); uint16 ibstrGetU16(ibytestream_t *ctx);
int64 ibstrGetI64(ibytestream_t *ctx); uint32 ibstrGetU32(ibytestream_t *ctx);
float ibstrGetFloat(ibytestream_t *ctx); uint64 ibstrGetU64(ibytestream_t *ctx);
double ibstrGetDouble(ibytestream_t *ctx); int8 ibstrGetI8(ibytestream_t *ctx);
int16 ibstrGetI16(ibytestream_t *ctx);
// reads a string, before reads <lensize> bytes for the length (e.g. sizeof(uint32)) int32 ibstrGetI32(ibytestream_t *ctx);
// then reads sizeof(char) * strlen int64 ibstrGetI64(ibytestream_t *ctx);
strview_t ibstrGetView(ibytestream_t *ctx, usize lensize); float ibstrGetFloat(ibytestream_t *ctx);
double ibstrGetDouble(ibytestream_t *ctx);
#ifdef __cplusplus
} // extern "C" // reads a string, before reads <lensize> bytes for the length (e.g. sizeof(uint32))
#endif // then reads sizeof(char) * strlen
strview_t ibstrGetView(ibytestream_t *ctx, usize lensize);
#ifdef __cplusplus
} // extern "C"
#endif

View file

@ -1,6 +1,6 @@
#pragma once #pragma once
#if COLLA_TCC || 1 #if COLLA_TCC
#include <Windows.h> #include <Windows.h>

8
tools/build Normal file
View file

@ -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

429
tools/docs.c Normal file
View file

@ -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("</span>"),
[HL_COLOR_PREPROC] = strv("<span class=\"c-preproc\">"),
[HL_COLOR_TYPES] = strv("<span class=\"c-types\">"),
[HL_COLOR_CUSTOM_TYPES] = strv("<span class=\"c-custom-types\">"),
[HL_COLOR_KEYWORDS] = strv("<span class=\"c-kwrds\">"),
[HL_COLOR_NUMBER] = strv("<span class=\"c-num\">"),
[HL_COLOR_STRING] = strv("<span class=\"c-str\">"),
[HL_COLOR_COMMENT] = strv("<span class=\"c-cmnt\">"),
[HL_COLOR_FUNC] = strv("<span class=\"c-func\">"),
[HL_COLOR_SYMBOL] = strv("<span class=\"c-sym\">"),
[HL_COLOR_MACRO] = strv("<span class=\"c-macro\">"),
},
.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, "<li>%v</li>", 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(<div class="document-padding"></div>);
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"
"";

View file

@ -1,219 +1,212 @@
#include "tracelog.h" #include "tracelog.h"
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include <stdlib.h> #include <stdlib.h>
#include "format.h" #include "format.h"
#if COLLA_WIN #if COLLA_WIN
#if COLLA_MSVC #if COLLA_MSVC
#pragma warning(disable:4996) // _CRT_SECURE_NO_WARNINGS. #pragma warning(disable:4996) // _CRT_SECURE_NO_WARNINGS.
#endif #endif
#include <windows.h> #include <windows.h>
#if COLLA_CMT_LIB #if COLLA_CMT_LIB
#pragma comment(lib, "User32") #pragma comment(lib, "User32")
#endif #endif
// avoid including windows.h #if COLLA_TCC
#include "tcc/colla_tcc.h"
#ifndef STD_OUTPUT_HANDLE #endif
#define STD_OUTPUT_HANDLE ((DWORD)-11)
#endif //#ifndef TLOG_NO_COLOURS
#ifndef CP_UTF8 // #define TLOG_NO_COLOURS
#define CP_UTF8 65001 //#endif
#endif #endif
#ifndef TLOG_NO_COLOURS #if COLLA_EMC
#define TLOG_NO_COLOURS #define TLOG_NO_COLOURS
#endif #endif
#endif
#if COLLA_EMC
#if COLLA_EMC #define COLOUR_BLACK "<span class=\"black\">"
#define TLOG_NO_COLOURS #define COLOUR_RED "<span class=\"red\">"
#endif #define COLOUR_GREEN "<span class=\"green\">"
#define COLOUR_YELLOW "<span class=\"yellow\">"
#if COLLA_EMC #define COLOUR_BLUE "<span class=\"blue\">"
#define COLOUR_BLACK "<span class=\"black\">" #define COLOUR_MAGENTA "<span class=\"magenta\">"
#define COLOUR_RED "<span class=\"red\">" #define COLOUR_CYAN "<span class=\"cyan\">"
#define COLOUR_GREEN "<span class=\"green\">" #define COLOUR_WHITE "<span class=\"white\">"
#define COLOUR_YELLOW "<span class=\"yellow\">" #define COLOUR_RESET "</span></b>"
#define COLOUR_BLUE "<span class=\"blue\">" #define COLOUR_BOLD "<b>"
#define COLOUR_MAGENTA "<span class=\"magenta\">" #elif defined(TLOG_NO_COLOURS)
#define COLOUR_CYAN "<span class=\"cyan\">" #define COLOUR_BLACK ""
#define COLOUR_WHITE "<span class=\"white\">" #define COLOUR_RED ""
#define COLOUR_RESET "</span></b>" #define COLOUR_GREEN ""
#define COLOUR_BOLD "<b>" #define COLOUR_YELLOW ""
#elif defined(TLOG_NO_COLOURS) #define COLOUR_BLUE ""
#define COLOUR_BLACK "" #define COLOUR_MAGENTA ""
#define COLOUR_RED "" #define COLOUR_CYAN ""
#define COLOUR_GREEN "" #define COLOUR_WHITE ""
#define COLOUR_YELLOW "" #define COLOUR_RESET ""
#define COLOUR_BLUE "" #define COLOUR_BOLD ""
#define COLOUR_MAGENTA "" #else
#define COLOUR_CYAN "" #define COLOUR_BLACK "\033[30m"
#define COLOUR_WHITE "" #define COLOUR_RED "\033[31m"
#define COLOUR_RESET "" #define COLOUR_GREEN "\033[32m"
#define COLOUR_BOLD "" #define COLOUR_YELLOW "\033[33m"
#else #define COLOUR_BLUE "\033[22;34m"
#define COLOUR_BLACK "\033[30m" #define COLOUR_MAGENTA "\033[35m"
#define COLOUR_RED "\033[31m" #define COLOUR_CYAN "\033[36m"
#define COLOUR_GREEN "\033[32m" #define COLOUR_WHITE "\033[37m"
#define COLOUR_YELLOW "\033[33m" #define COLOUR_RESET "\033[0m"
#define COLOUR_BLUE "\033[22;34m" #define COLOUR_BOLD "\033[1m"
#define COLOUR_MAGENTA "\033[35m" #endif
#define COLOUR_CYAN "\033[36m"
#define COLOUR_WHITE "\033[37m" static bool tl_use_newline = true;
#define COLOUR_RESET "\033[0m"
#define COLOUR_BOLD "\033[1m" #if COLLA_WIN
#endif static void setLevelColour(int level) {
WORD attribute = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE;
#define MAX_TRACELOG_MSG_LENGTH 1024 switch (level) {
case LogDebug: attribute = FOREGROUND_BLUE; break;
bool use_newline = true; case LogInfo: attribute = FOREGROUND_GREEN; break;
case LogWarning: attribute = FOREGROUND_RED | FOREGROUND_GREEN; break;
#if COLLA_WIN case LogError: attribute = FOREGROUND_RED; break;
static void setLevelColour(int level) { case LogFatal: attribute = FOREGROUND_RED; break;
WORD attribute = 15; }
switch (level) {
case LogDebug: attribute = 1; break; HANDLE hc = GetStdHandle(STD_OUTPUT_HANDLE);
case LogInfo: attribute = 2; break; SetConsoleTextAttribute(hc, attribute);
case LogWarning: attribute = 6; break; }
case LogError: attribute = 4; break; #else
case LogFatal: attribute = 4; break; static void setLevelColour(int level) {
} switch (level) {
case LogDebug: printf(COLOUR_BLUE); break;
HANDLE hc = GetStdHandle(STD_OUTPUT_HANDLE); case LogInfo: printf(COLOUR_GREEN); break;
SetConsoleTextAttribute(hc, attribute); case LogWarning: printf(COLOUR_YELLOW); break;
} case LogError: printf(COLOUR_RED); break;
#else case LogFatal: printf(COLOUR_MAGENTA); break;
static void setLevelColour(int level) { default: printf(COLOUR_RESET); break;
switch (level) { }
case LogDebug: printf(COLOUR_BLUE); break; }
case LogInfo: printf(COLOUR_GREEN); break; #endif
case LogWarning: printf(COLOUR_YELLOW); break;
case LogError: printf(COLOUR_RED); break; void traceLog(int level, const char *fmt, ...) {
case LogFatal: printf(COLOUR_MAGENTA); break; va_list args;
default: printf(COLOUR_RESET); break; va_start(args, fmt);
} traceLogVaList(level, fmt, args);
} va_end(args);
#endif }
void traceLog(int level, const char *fmt, ...) { #ifdef TLOG_MUTEX
va_list args; #include "cthreads.h"
va_start(args, fmt); static cmutex_t g_mtx = 0;
traceLogVaList(level, fmt, args); #endif
va_end(args);
} void traceLogVaList(int level, const char *fmt, va_list args) {
#ifdef TLOG_MUTEX
#ifdef TLOG_MUTEX if (!g_mtx) g_mtx = mtxInit();
#include "cthreads.h" mtxLock(g_mtx);
static cmutex_t g_mtx = 0; #endif
#endif
const char *beg = "";
void traceLogVaList(int level, const char *fmt, va_list args) { switch (level) {
#ifdef TLOG_MUTEX case LogAll: beg = "[ALL" ; break;
if (!g_mtx) g_mtx = mtxInit(); case LogTrace: beg = "[TRACE" ; break;
mtxLock(g_mtx); case LogDebug: beg = "[DEBUG" ; break;
#endif case LogInfo: beg = "[INFO" ; break;
case LogWarning: beg = "[WARNING" ; break;
const char *beg = ""; case LogError: beg = "[ERROR" ; break;
switch (level) { case LogFatal: beg = "[FATAL" ; break;
case LogTrace: beg = "[TRACE" ; break; default: break;
case LogDebug: beg = "[DEBUG" ; break; }
case LogInfo: beg = "[INFO" ; break;
case LogWarning: beg = "[WARNING" ; break; #if COLLA_WIN
case LogError: beg = "[ERROR" ; break; SetConsoleOutputCP(CP_UTF8);
case LogFatal: beg = "[FATAL" ; break; #endif
default: break; setLevelColour(level);
} printf("%s", beg);
#if COLLA_WIN #if GAME_CLIENT
SetConsoleOutputCP(CP_UTF8); putchar(':');
#endif traceSetColour(COL_CYAN);
setLevelColour(level); printf("CLIENT");
printf("%s", beg); #elif GAME_HOST
putchar(':');
#if GAME_CLIENT traceSetColour(COL_MAGENTA);
putchar(':'); printf("HOST");
traceSetColour(COL_CYAN); #endif
printf("CLIENT");
#elif GAME_HOST setLevelColour(level);
putchar(':'); printf("]: ");
traceSetColour(COL_MAGENTA);
printf("HOST"); // set back to white
#endif setLevelColour(LogTrace);
fmtPrintv(fmt, args);
setLevelColour(level);
printf("]: "); if(tl_use_newline) {
#if COLLA_EMC
// set back to white puts("<br>");
setLevelColour(LogTrace); #else
// vprintf(fmt, args); puts("");
fmtPrintv(fmt, args); #endif
}
if(use_newline) {
#if COLLA_EMC #ifndef TLOG_DONT_EXIT_ON_FATAL
puts("<br>"); if (level == LogFatal) {
#else
puts(""); #ifndef TLOG_NO_MSGBOX
#endif
} #if COLLA_WIN
char errbuff[1024];
#ifndef TLOG_DONT_EXIT_ON_FATAL fmtBufferv(errbuff, sizeof(errbuff), fmt, args);
if (level == LogFatal) {
char captionbuf[] =
#ifndef TLOG_NO_MSGBOX #if GAME_HOST
"Fatal Host Error";
#if COLLA_WIN #elif GAME_CLIENT
char errbuff[1024]; "Fatal Client Error";
fmtBufferv(errbuff, sizeof(errbuff), fmt, args); #else
"Fatal Error";
char captionbuf[] = #endif
#if GAME_HOST MessageBoxA(
"Fatal Host Error"; NULL,
#elif GAME_CLIENT errbuff, captionbuf,
"Fatal Client Error"; MB_ICONERROR | MB_OK
#else );
"Fatal Error"; #endif
#endif
MessageBoxA( #endif
NULL, fflush(stdout);
errbuff, captionbuf, abort();
MB_ICONERROR | MB_OK }
); #endif
#endif
#ifdef TLOG_MUTEX
#endif mtxUnlock(g_mtx);
fflush(stdout); #endif
abort(); }
}
#endif void traceUseNewline(bool newline) {
tl_use_newline = newline;
#ifdef TLOG_MUTEX }
mtxUnlock(g_mtx);
#endif void traceSetColour(colour_e colour) {
} #if COLLA_WIN
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), (WORD)colour);
void traceUseNewline(bool newline) { #else
use_newline = newline; switch (colour) {
} case COL_RESET: printf(COLOUR_RESET); break;
case COL_BLACK: printf(COLOUR_BLACK); break;
void traceSetColour(colour_e colour) { case COL_BLUE: printf(COLOUR_BLUE); break;
#if COLLA_WIN case COL_GREEN: printf(COLOUR_GREEN); break;
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), colour); case COL_CYAN: printf(COLOUR_CYAN); break;
#else case COL_RED: printf(COLOUR_RED); break;
switch (colour) { case COL_MAGENTA: printf(COLOUR_MAGENTA); break;
case COL_RESET: printf(COLOUR_RESET); break; case COL_YELLOW: printf(COLOUR_YELLOW); break;
case COL_BLACK: printf(COLOUR_BLACK); break; }
case COL_BLUE: printf(COLOUR_BLUE); break; #endif
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
} }

View file

@ -1,59 +1,59 @@
#pragma once #pragma once
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
/* Define any of this to turn on the option /* Define any of this to turn on the option
* -> TLOG_NO_COLOURS: print without using colours * -> TLOG_NO_COLOURS: print without using colours
* -> TLOG_VS: print to visual studio console, also turns on TLOG_NO_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_DONT_EXIT_ON_FATAL: don't call 'exit(1)' when using LogFatal
* -> TLOG_DISABLE: turn off all logging, useful for release builds * -> TLOG_DISABLE: turn off all logging, useful for release builds
* -> TLOG_MUTEX: use a mutex on every traceLog call * -> TLOG_MUTEX: use a mutex on every traceLog call
*/ */
#include "collatypes.h" #include "collatypes.h"
#include <stdarg.h> #include <stdarg.h>
enum { enum {
LogAll, LogTrace, LogDebug, LogInfo, LogWarning, LogError, LogFatal LogAll, LogTrace, LogDebug, LogInfo, LogWarning, LogError, LogFatal
}; };
typedef enum { typedef enum {
COL_RESET = 15, COL_RESET = 15,
COL_BLACK = 8, COL_BLACK = 8,
COL_BLUE = 9, COL_BLUE = 9,
COL_GREEN = 10, COL_GREEN = 10,
COL_CYAN = 11, COL_CYAN = 11,
COL_RED = 12, COL_RED = 12,
COL_MAGENTA = 13, COL_MAGENTA = 13,
COL_YELLOW = 14, COL_YELLOW = 14,
COL_WHITE = 15 COL_WHITE = 15
} colour_e; } colour_e;
void traceLog(int level, const char *fmt, ...); void traceLog(int level, const char *fmt, ...);
void traceLogVaList(int level, const char *fmt, va_list args); void traceLogVaList(int level, const char *fmt, va_list args);
void traceUseNewline(bool use_newline); void traceUseNewline(bool use_newline);
void traceSetColour(colour_e colour); void traceSetColour(colour_e colour);
#ifdef TLOG_DISABLE #ifdef TLOG_DISABLE
#define tall(...) #define tall(...)
#define trace(...) #define trace(...)
#define debug(...) #define debug(...)
#define info(...) #define info(...)
#define warn(...) #define warn(...)
#define err(...) #define err(...)
#define fatal(...) #define fatal(...)
#else #else
#define tall(...) traceLog(LogAll, __VA_ARGS__) #define tall(...) traceLog(LogAll, __VA_ARGS__)
#define trace(...) traceLog(LogTrace, __VA_ARGS__) #define trace(...) traceLog(LogTrace, __VA_ARGS__)
#define debug(...) traceLog(LogDebug, __VA_ARGS__) #define debug(...) traceLog(LogDebug, __VA_ARGS__)
#define info(...) traceLog(LogInfo, __VA_ARGS__) #define info(...) traceLog(LogInfo, __VA_ARGS__)
#define warn(...) traceLog(LogWarning, __VA_ARGS__) #define warn(...) traceLog(LogWarning, __VA_ARGS__)
#define err(...) traceLog(LogError, __VA_ARGS__) #define err(...) traceLog(LogError, __VA_ARGS__)
#define fatal(...) traceLog(LogFatal, __VA_ARGS__) #define fatal(...) traceLog(LogFatal, __VA_ARGS__)
#endif #endif
#ifdef __cplusplus #ifdef __cplusplus
} // extern "C" } // extern "C"
#endif #endif

View file

@ -1,172 +1,172 @@
#include "utf8.h" #include "utf8.h"
static const uint8 masks[] = { static const uint8 masks[] = {
0x7f, // 0111-1111 0x7f, // 0111-1111
0x1f, // 0001-1111 0x1f, // 0001-1111
0x0f, // 0000-1111 0x0f, // 0000-1111
0x07, // 0000-0111 0x07, // 0000-0111
0x03, // 0000-0011 0x03, // 0000-0011
0x01 // 0000-0001 0x01 // 0000-0001
}; };
static struct { static struct {
uint8 mask; uint8 mask;
uint8 result; uint8 result;
int octets; int octets;
} sizes[] = { } sizes[] = {
{ 0x80, 0x00, 1 }, // 1000-0000, 0000-0000 { 0x80, 0x00, 1 }, // 1000-0000, 0000-0000
{ 0xE0, 0xC0, 2 }, // 1110-0000, 1100-0000 { 0xE0, 0xC0, 2 }, // 1110-0000, 1100-0000
{ 0xF0, 0xE0, 3 }, // 1111-0000, 1110-0000 { 0xF0, 0xE0, 3 }, // 1111-0000, 1110-0000
{ 0xF8, 0xF0, 4 }, // 1111-1000, 1111-0000 { 0xF8, 0xF0, 4 }, // 1111-1000, 1111-0000
{ 0xFC, 0xF8, 5 }, // 1111-1100, 1111-1000 { 0xFC, 0xF8, 5 }, // 1111-1100, 1111-1000
{ 0xFE, 0xF8, 6 }, // 1111-1110, 1111-1000 { 0xFE, 0xF8, 6 }, // 1111-1110, 1111-1000
{ 0x80, 0x80, -1 }, // 1000-0000, 1000-0000 { 0x80, 0x80, -1 }, // 1000-0000, 1000-0000
}; };
/* /*
UTF-8 codepoints are encoded using the first bits of the first character UTF-8 codepoints are encoded using the first bits of the first character
byte 1 | byte 2 | byte 3 | byte 4 byte 1 | byte 2 | byte 3 | byte 4
0xxx xxxx | | | 0xxx xxxx | | |
110x xxxx | 10xx xxxx | | 110x xxxx | 10xx xxxx | |
1110 xxxx | 10xx xxxx | 10xx xxxx | 1110 xxxx | 10xx xxxx | 10xx xxxx |
1111 0xxx | 10xx 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) 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 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 then we keep shifting the rune left 6 and applying the next byte to the mask
until the codepoint is finished (size is 0) until the codepoint is finished (size is 0)
## EXAMPLE ## EXAMPLE
utf8 string () = 1110-0010 1000-0010 1010-1100 utf8 string () = 1110-0010 1000-0010 1010-1100
cp = 0000-0000 0000-0000 0000-0000 0000-0000 cp = 0000-0000 0000-0000 0000-0000 0000-0000
size = 3 size = 3
mask = 0x0f -> 0000-1111 mask = 0x0f -> 0000-1111
cp = *s & mask = 1110-0010 & 0000-1111 = 0000-0000 0000-0000 0000-0000 0000-0010 cp = *s & mask = 1110-0010 & 0000-1111 = 0000-0000 0000-0000 0000-0000 0000-0010
++s = 1000-0010 ++s = 1000-0010
--size = 2 --size = 2
cp <<= 6 = 0000-0000 0000-0000 0000-0000 1000-0000 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 cp |= *s & 0x3f = 1000-0010 & 0011-1111 = 0000-0000 0000-0000 0000-0000 1000-0010
++s = 1010-1100 ++s = 1010-1100
--size = 1 --size = 1
cp <<= 6 = 0000-0000 0000-0000 0010-0000 1000-0000 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 cp |= *s & 0x3f = 1010-1100 & 0011-1111 = 0000-0000 0000-0000 0010-0000 1010-1100
++s = ---------- ++s = ----------
final codepoint = 0010-0000 1010-1100 final codepoint = 0010-0000 1010-1100
codepoint = 0010-0000 1010-1100 codepoint = 0010-0000 1010-1100
*/ */
rune utf8Decode(const char **char_str) { rune utf8Decode(const char **char_str) {
uint8 **s = (uint8 **)char_str; const uint8 **s = (const uint8 **)char_str;
rune ch = 0; rune ch = 0;
// if is ascii // if is ascii
if (**s < 128) { if (**s < 128) {
ch = **s; ch = **s;
++*s; ++*s;
return ch; return ch;
} }
int size = utf8Size((char *)*s); int size = utf8Size((const char *)*s);
if (size == -1) { if (size == -1) {
++*s; ++*s;
return UTF8_INVALID; return UTF8_INVALID;
} }
uint8 mask = masks[size - 1]; uint8 mask = masks[size - 1];
ch = **s & mask; ch = **s & mask;
++*s; ++*s;
while(--size) { while(--size) {
ch <<= 6; ch <<= 6;
ch |= **s & 0x3f; // 0011-1111 ch |= **s & 0x3f; // 0011-1111
++*s; ++*s;
} }
return ch; return ch;
} }
/* /*
to encode a codepoint in a utf8 string we first need to find to encode a codepoint in a utf8 string we first need to find
the length of the codepoint the length of the codepoint
then we start from the rightmost byte and loop for each byte 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) 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 > 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 > or (|) with 0x80 so we make sure that the first two bits are 10
> bitshift the codepoint right 6 > bitshift the codepoint right 6
finally, we apply the correct length-mask to the first byte finally, we apply the correct length-mask to the first byte
## EXAMPLE ## EXAMPLE
ch = 0010-0000 1010-1100 ch = 0010-0000 1010-1100
ch < 0x10000 ch < 0x10000
first = 0xe0 = 1110-0000 first = 0xe0 = 1110-0000
len = 3 len = 3
str[2] = (ch & 0x3f) | 0x80 = 1010-1100 & 0011-1111 | 1000-0000 str[2] = (ch & 0x3f) | 0x80 = 1010-1100 & 0011-1111 | 1000-0000
= 1010-1100 = 1010-1100
ch >>= 6 = 0010-0000 1010-1100 >> 6 = 1000-0010 ch >>= 6 = 0010-0000 1010-1100 >> 6 = 1000-0010
str[1] = (ch & 0x3f) | 0x80 = 1000-0010 & 0011-1111 | 1000-000 str[1] = (ch & 0x3f) | 0x80 = 1000-0010 & 0011-1111 | 1000-000
= 1000-0010 = 1000-0010
ch >>= 6 = 1000-0010 >> 6 = 0000-0010 ch >>= 6 = 1000-0010 >> 6 = 0000-0010
str[0] = ch | first_mask = 0000-0010 | 1111-0000 str[0] = ch | first_mask = 0000-0010 | 1111-0000
= 1111-0010 = 1111-0010
str = 1111-0010 1000-0010 1010-1100 str = 1111-0010 1000-0010 1010-1100
utf8 = 1110-0010 1000-0010 1010-1100 utf8 = 1110-0010 1000-0010 1010-1100
*/ */
usize utf8Encode(char *str, rune codepoint) { usize utf8Encode(char *str, rune codepoint) {
usize len = 0; usize len = 0;
uint8 first; uint8 first;
if (codepoint < 0x80) { // 0000-0000 0000-0000 0000-0000 1000-0000 if (codepoint < 0x80) { // 0000-0000 0000-0000 0000-0000 1000-0000
first = 0; first = 0;
len = 1; len = 1;
} }
else if (codepoint < 0x800) { // 0000-0000 0000-0000 0000-1000 0000-0000 else if (codepoint < 0x800) { // 0000-0000 0000-0000 0000-1000 0000-0000
first = 0xc0; // 1100-0000 first = 0xc0; // 1100-0000
len = 2; len = 2;
} }
else if (codepoint < 0x10000) { // 0000-0000 0000-0001 0000-0000 0000-0000 else if (codepoint < 0x10000) { // 0000-0000 0000-0001 0000-0000 0000-0000
first = 0xe0; // 1110-0000 first = 0xe0; // 1110-0000
len = 3; len = 3;
} }
else { else {
first = 0xf0; // 1111-0000 first = 0xf0; // 1111-0000
len = 4; len = 4;
} }
for (usize i = len - 1; i > 0; --i) { for (usize i = len - 1; i > 0; --i) {
// 0x3f -> 0011-1111 // 0x3f -> 0011-1111
// 0x80 -> 1000-0000 // 0x80 -> 1000-0000
str[i] = (codepoint & 0x3f) | 0x80; str[i] = (codepoint & 0x3f) | 0x80;
codepoint >>= 6; codepoint >>= 6;
} }
str[0] = (char)(codepoint | first); str[0] = (char)(codepoint | first);
return len; return len;
} }
int utf8Size(const char *str) { int utf8Size(const char *str) {
uint8 c = (uint8)*str; uint8 c = (uint8)*str;
for(usize i = 0; i < (sizeof(sizes) / sizeof(*sizes)); ++i) { for(usize i = 0; i < (sizeof(sizes) / sizeof(*sizes)); ++i) {
if ((c & sizes[i].mask) == sizes[i].result) { if ((c & sizes[i].mask) == sizes[i].result) {
return sizes[i].octets; return sizes[i].octets;
} }
} }
return -1; return -1;
} }
usize utf8CpSize(rune ch) { usize utf8CpSize(rune ch) {
if (ch < 0x80) return 1; if (ch < 0x80) return 1;
else if (ch < 0x800) return 2; else if (ch < 0x800) return 2;
else if (ch < 0x10000) return 3; else if (ch < 0x10000) return 3;
return 4; return 4;
} }

View file

@ -1,19 +1,19 @@
#pragma once #pragma once
#include "collatypes.h" #include "collatypes.h"
typedef uint32 rune; typedef uint32 rune;
enum { enum {
UTF8_MAX_SIZE = 4, UTF8_MAX_SIZE = 4,
UTF8_INVALID = 0x80 UTF8_INVALID = 0x80
}; };
// grabs the next UTF-8 codepoint and advances string ptr // grabs the next UTF-8 codepoint and advances string ptr
rune utf8Decode(const char **str); rune utf8Decode(const char **str);
// encodes a codepoint as UTF-8 and returns the length // encodes a codepoint as UTF-8 and returns the length
usize utf8Encode(char *str, rune ch); usize utf8Encode(char *str, rune ch);
// returns the size of the next UTF-8 codepoint // returns the size of the next UTF-8 codepoint
int utf8Size(const char *str); int utf8Size(const char *str);
// returns the size of a UTF-8 codepoint // returns the size of a UTF-8 codepoint
usize utf8CpSize(rune ch); usize utf8CpSize(rune ch);

View file

View file

@ -35,8 +35,7 @@ usize vmemPadToPage(usize byte_count) {
#if COLLA_WIN #if COLLA_WIN
#define WIN32_LEAN_AND_MEAN #include <windows.h>
#include <Windows.h>
void *vmemInit(usize size, usize *out_padded_size) { void *vmemInit(usize size, usize *out_padded_size) {
usize alloc_size = vmemPadToPage(size); usize alloc_size = vmemPadToPage(size);

142
websocket.c Normal file
View file

@ -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;
}

13
websocket.h Normal file
View file

@ -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);

View file

@ -57,7 +57,7 @@ strview_t xmlGetAttribute(xmltag_t *tag, strview_t key) {
} }
a = a->next; a = a->next;
} }
return (strview_t){0}; return STRV_EMPTY;
} }
// == PRIVATE FUNCTIONS ======================================================================== // == PRIVATE FUNCTIONS ========================================================================

View file