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);
arena_t arenaInit(const arena_desc_t *desc) {
arena_t out = {0};
if (desc) {
switch (desc->type) {
case ARENA_VIRTUAL: return arena__make_virtual(desc->allocation);
case ARENA_MALLOC: return arena__make_malloc(desc->allocation);
case ARENA_STATIC: return arena__make_static(desc->static_buffer, desc->allocation);
case ARENA_VIRTUAL: out = arena__make_virtual(desc->allocation); break;
case ARENA_MALLOC: out = arena__make_malloc(desc->allocation); break;
case ARENA_STATIC: out = arena__make_static(desc->static_buffer, desc->allocation); break;
}
}
debug("couldn't init arena: %p %d\n", desc, desc ? desc->type : 0);
return (arena_t){0};
return out;
}
void arenaCleanup(arena_t *arena) {
@ -50,17 +51,9 @@ void arenaCleanup(arena_t *arena) {
arena->type = 0;
}
arena_t arenaScratch(arena_t *arena) {
if (!arena) {
return (arena_t){0};
}
return (arena_t) {
.start = arena->current,
.current = arena->current,
.end = arena->end,
.type = arena->type,
};
arena_t arenaScratch(arena_t *arena, usize size) {
uint8 *buffer = alloc(arena, uint8, size, ALLOC_SOFT_FAIL | ALLOC_NOZERO);
return arena__make_static(buffer, buffer ? size : 0);
}
void *arenaAlloc(const arena_alloc_desc_t *desc) {

View file

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

View file

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

View file

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

51
build.c
View file

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

View file

@ -41,6 +41,14 @@
#endif
#if defined(__COSMOPOLITAN__)
#define COLLA_COSMO 1
#else
#define COLLA_COSMO 0
#endif
#define COLLA_POSIX (COLLA_OSX || COLLA_LIN || COLLA_COSMO)
#if defined(__clang__)
#define COLLA_CLANG 1
@ -88,3 +96,17 @@
#define COLLA_CMT_LIB 0
#endif
#if COLLA_WIN
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#endif
#undef min
#undef max
#define min(a, b) ((a) < (b) ? (a) : (b))
#define max(a, b) ((a) > (b) ? (a) : (b))

View file

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

View file

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

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

View file

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

View file

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

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?
#if COLLA_WIN
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#else
// patch stuff up for cross platform for now, the actual program should not really call anything for now
@ -25,6 +24,9 @@ typedef struct {
hr_f hr_close;
} hr_internal_t;
static bool hr__os_reload(hr_internal_t *hr);
static void hr__os_free(hr_internal_t *hr);
static bool hr__file_copy(arena_t scratch, strview_t src, strview_t dst) {
buffer_t srcbuf = fileReadWhole(&scratch, src);
if (srcbuf.data == NULL || srcbuf.len == 0) {
@ -70,6 +72,93 @@ static bool hr__reload(hr_t *ctx) {
info("loading library: %v", dll);
return hr__os_reload(hr);
}
bool hrOpen(hr_t *ctx, strview_t path) {
#ifdef HR_DISABLE
cr_init(ctx);
return true;
#endif
arena_t arena = arenaMake(ARENA_VIRTUAL, MB(1));
str_t path_copy = str(&arena, path);
if (!fileExists(path_copy.buf)) {
err("dll file: %v does not exist", path);
arenaCleanup(&arena);
return false;
}
hr_internal_t *hr = alloc(&arena, hr_internal_t);
hr->arena = arena;
hr->path = path_copy;
ctx->p = hr;
ctx->last_working_version = 0;
return hr__reload(ctx);
}
void hrClose(hr_t *ctx, bool clean_temp_files) {
#ifdef HR_DISABLE
hr_close(ctx);
return;
#endif
hr_internal_t *hr = ctx->p;
if (hr->hr_close) {
hr->hr_close(ctx);
}
hr__os_free(hr);
hr->handle = NULL;
hr->hr_init = hr->hr_loop = hr->hr_close = NULL;
if (clean_temp_files) {
arena_t scratch = hr->arena;
strview_t dir, name, ext;
fileSplitPath(strv(hr->path), &dir, &name, &ext);
for (int i = 0; i < ctx->last_working_version; ++i) {
str_t fname = strFmt(&scratch, "%v/%v-%d%v", dir, name, i + 1, ext);
if (!fileDelete(scratch, strv(fname))) {
err("couldn't delete %v", fname);
}
}
}
arena_t arena = hr->arena;
arenaCleanup(&arena);
ctx->p = NULL;
}
int hrStep(hr_t *ctx) {
#ifdef CR_DISABLE
hr_loop(ctx);
return 0;
#endif
hr_internal_t *hr = ctx->p;
int result = -1;
if (hr->hr_loop) {
result = hr->hr_loop(ctx);
}
return result;
}
bool hrReload(hr_t *ctx) {
return hr__reload(ctx);
}
//// OS SPECIFIC ////////////////////////////////////////
#if COLLA_WIN
static bool hr__os_reload(hr_internal_t *hr) {
if (hr->handle) {
FreeLibrary(hr->handle);
}
@ -117,84 +206,21 @@ error:
return false;
}
bool hrOpen(hr_t *ctx, strview_t path) {
#ifdef HR_DISABLE
cr_init(ctx);
return true;
#endif
arena_t arena = arenaMake(ARENA_VIRTUAL, MB(1));
str_t path_copy = str(&arena, path);
if (!fileExists(path_copy.buf)) {
err("dll file: %v does not exist", path);
arenaCleanup(&arena);
return false;
}
hr_internal_t *hr = alloc(&arena, hr_internal_t);
hr->arena = arena;
hr->path = path_copy;
ctx->p = hr;
ctx->last_working_version = 0;
return hr__reload(ctx);
}
void hrClose(hr_t *ctx, bool clean_temp_files) {
#ifdef HR_DISABLE
hr_close(ctx);
return;
#endif
hr_internal_t *hr = ctx->p;
if (hr->hr_close) {
hr->hr_close(ctx);
}
static void hr__os_free(hr_internal_t *hr) {
if (hr->handle) {
FreeLibrary(hr->handle);
}
hr->handle = NULL;
hr->hr_init = hr->hr_loop = hr->hr_close = NULL;
if (clean_temp_files) {
arena_t scratch = hr->arena;
strview_t dir, name, ext;
fileSplitPath(strv(hr->path), &dir, &name, &ext);
for (int i = 0; i < ctx->last_working_version; ++i) {
str_t fname = strFmt(&scratch, "%v/%v-%d%v", dir, name, i + 1, ext);
if (!fileDelete(scratch, strv(fname))) {
err("couldn't delete %v: %d", fname, GetLastError());
}
}
}
arena_t arena = hr->arena;
arenaCleanup(&arena);
ctx->p = NULL;
}
int hrStep(hr_t *ctx) {
#ifdef CR_DISABLE
hr_loop(ctx);
return 0;
#endif
hr_internal_t *hr = ctx->p;
#elif COLLA_LIN
int result = -1;
if (hr->hr_loop) {
result = hr->hr_loop(ctx);
}
return result;
static bool hr__os_reload(hr_internal_t *hr) {
fatal("todo: linux hot reload not implemented yet");
return true;
}
bool hrReload(hr_t *ctx) {
return hr__reload(ctx);
static void hr__os_free(hr_internal_t *hr) {
fatal("todo: linux hot reload not implemented yet");
}
#endif

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

View file

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

View file

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

View file

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

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

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);
server_t *serverSetup(arena_t *arena, uint16 port);
server_t *serverSetup(arena_t *arena, uint16 port, bool try_next_port);
void serverRoute(arena_t *arena, server_t *server, strview_t page, server_route_f cb, void *userdata);
void serverRouteDefault(arena_t *arena, server_t *server, server_route_f cb, void *userdata);
void serverStart(arena_t scratch, server_t *server);

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

View file

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

View file

@ -219,6 +219,17 @@ STBSP__PUBLICDEC void STB_SPRINTF_DECORATE(set_separators)(char comma, char peri
#ifdef STB_SPRINTF_IMPLEMENTATION
#if COLLA_CLANG
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wextra-semi-stmt"
#pragma clang diagnostic ignored "-Wconditional-uninitialized"
#pragma clang diagnostic ignored "-Wcast-qual"
#pragma clang diagnostic ignored "-Wimplicit-fallthrough"
#endif
#define stbsp__uint32 unsigned int
#define stbsp__int32 signed int
@ -1885,6 +1896,10 @@ static stbsp__int32 stbsp__real_to_str(char const **start, stbsp__uint32 *len, c
#undef stbsp__int64
#undef STBSP__UNALIGNED
#if COLLA_CLANG
#pragma clang diagnostic pop
#endif
#endif // STB_SPRINTF_IMPLEMENTATION
/*

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

View file

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

View file

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

View file

View file

@ -35,8 +35,7 @@ usize vmemPadToPage(usize byte_count) {
#if COLLA_WIN
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <windows.h>
void *vmemInit(usize size, usize *out_padded_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;
}
return (strview_t){0};
return STRV_EMPTY;
}
// == PRIVATE FUNCTIONS ========================================================================

View file