mmmmh
This commit is contained in:
parent
82aee127b0
commit
a92b119549
99 changed files with 6922 additions and 5723 deletions
|
|
@ -20,16 +20,17 @@ static void arena__free_virtual(arena_t *arena);
|
||||||
static void arena__free_malloc(arena_t *arena);
|
static void arena__free_malloc(arena_t *arena);
|
||||||
|
|
||||||
arena_t arenaInit(const arena_desc_t *desc) {
|
arena_t arenaInit(const arena_desc_t *desc) {
|
||||||
|
arena_t out = {0};
|
||||||
|
|
||||||
if (desc) {
|
if (desc) {
|
||||||
switch (desc->type) {
|
switch (desc->type) {
|
||||||
case ARENA_VIRTUAL: return arena__make_virtual(desc->allocation);
|
case ARENA_VIRTUAL: out = arena__make_virtual(desc->allocation); break;
|
||||||
case ARENA_MALLOC: return arena__make_malloc(desc->allocation);
|
case ARENA_MALLOC: out = arena__make_malloc(desc->allocation); break;
|
||||||
case ARENA_STATIC: return arena__make_static(desc->static_buffer, desc->allocation);
|
case ARENA_STATIC: out = arena__make_static(desc->static_buffer, desc->allocation); break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
debug("couldn't init arena: %p %d\n", desc, desc ? desc->type : 0);
|
return out;
|
||||||
return (arena_t){0};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void arenaCleanup(arena_t *arena) {
|
void arenaCleanup(arena_t *arena) {
|
||||||
|
|
@ -50,17 +51,9 @@ void arenaCleanup(arena_t *arena) {
|
||||||
arena->type = 0;
|
arena->type = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
arena_t arenaScratch(arena_t *arena) {
|
arena_t arenaScratch(arena_t *arena, usize size) {
|
||||||
if (!arena) {
|
uint8 *buffer = alloc(arena, uint8, size, ALLOC_SOFT_FAIL | ALLOC_NOZERO);
|
||||||
return (arena_t){0};
|
return arena__make_static(buffer, buffer ? size : 0);
|
||||||
}
|
|
||||||
|
|
||||||
return (arena_t) {
|
|
||||||
.start = arena->current,
|
|
||||||
.current = arena->current,
|
|
||||||
.end = arena->end,
|
|
||||||
.type = arena->type,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void *arenaAlloc(const arena_alloc_desc_t *desc) {
|
void *arenaAlloc(const arena_alloc_desc_t *desc) {
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
#include "collatypes.h"
|
#include "collatypes.h"
|
||||||
|
|
||||||
#ifdef __TINYC__
|
#if COLLA_TCC
|
||||||
#define alignof __alignof__
|
#define alignof __alignof__
|
||||||
#else
|
#else
|
||||||
#define alignof _Alignof
|
#define alignof _Alignof
|
||||||
|
|
@ -37,23 +37,27 @@ typedef struct {
|
||||||
arena_t *arena;
|
arena_t *arena;
|
||||||
usize count;
|
usize count;
|
||||||
alloc_flags_e flags;
|
alloc_flags_e flags;
|
||||||
usize size;
|
|
||||||
usize align;
|
usize align;
|
||||||
|
usize size;
|
||||||
} arena_alloc_desc_t;
|
} arena_alloc_desc_t;
|
||||||
|
|
||||||
|
#ifndef ARENA_NO_SIZE_HELPERS
|
||||||
#define KB(count) ( (count) * 1024)
|
#define KB(count) ( (count) * 1024)
|
||||||
#define MB(count) (KB(count) * 1024)
|
#define MB(count) (KB(count) * 1024)
|
||||||
#define GB(count) (MB(count) * 1024)
|
#define GB(count) (MB(count) * 1024)
|
||||||
|
#endif
|
||||||
|
|
||||||
// arena_type_e type, usize allocation, [ byte *static_buffer ]
|
// arena_type_e type, usize allocation, [ byte *static_buffer ]
|
||||||
#define arenaMake(...) arenaInit(&(arena_desc_t){ __VA_ARGS__ })
|
#define arenaMake(...) arenaInit(&(arena_desc_t){ __VA_ARGS__ })
|
||||||
|
|
||||||
// arena_t *arena, T type, [ usize count, alloc_flags_e flags, usize size, usize align ]
|
// arena_t *arena, T type, [ usize count, alloc_flags_e flags, usize align, usize size ]
|
||||||
#define alloc(arenaptr, type, ...) arenaAlloc(&(arena_alloc_desc_t){ .size = sizeof(type), .count = 1, .align = alignof(type), .arena = arenaptr, __VA_ARGS__ })
|
#define alloc(arenaptr, type, ...) arenaAlloc(&(arena_alloc_desc_t){ .size = sizeof(type), .count = 1, .align = alignof(type), .arena = arenaptr, __VA_ARGS__ })
|
||||||
|
|
||||||
arena_t arenaInit(const arena_desc_t *desc);
|
arena_t arenaInit(const arena_desc_t *desc);
|
||||||
void arenaCleanup(arena_t *arena);
|
void arenaCleanup(arena_t *arena);
|
||||||
|
|
||||||
|
arena_t arenaScratch(arena_t *arena, usize size);
|
||||||
|
|
||||||
void *arenaAlloc(const arena_alloc_desc_t *desc);
|
void *arenaAlloc(const arena_alloc_desc_t *desc);
|
||||||
usize arenaTell(arena_t *arena);
|
usize arenaTell(arena_t *arena);
|
||||||
usize arenaRemaining(arena_t *arena);
|
usize arenaRemaining(arena_t *arena);
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
#include "arena.h"
|
#include "arena.h"
|
||||||
|
|
||||||
static char encoding_table[] = {
|
static unsigned char encoding_table[] = {
|
||||||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
|
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
|
||||||
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
|
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
|
||||||
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
|
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
|
||||||
|
|
@ -24,7 +24,7 @@ inline uint32 bitsCtz(uint32 v) {
|
||||||
return v ? __builtin_ctz(v) : 0;
|
return v ? __builtin_ctz(v) : 0;
|
||||||
#elif BITS_WIN
|
#elif BITS_WIN
|
||||||
uint32 trailing = 0;
|
uint32 trailing = 0;
|
||||||
return _BitScanForward(&trailing, v) ? trailing : 0;
|
return _BitScanForward((unsigned long *)&trailing, v) ? trailing : 0;
|
||||||
#else
|
#else
|
||||||
return 0;
|
return 0;
|
||||||
#endif
|
#endif
|
||||||
51
build.c
51
build.c
|
|
@ -1,8 +1,9 @@
|
||||||
#if COLLA_ONLYCORE
|
#if COLLA_ONLYCORE
|
||||||
#define COLLA_NOTHREADS 1
|
#define COLLA_NOTHREADS 1
|
||||||
#define COLLA_NOSOCKETS 1
|
#define COLLA_NOSOCKETS 1
|
||||||
#define COLLA_NOHTTP 1
|
#define COLLA_NOHTTP 1
|
||||||
#define COLLA_NOSERVER 1
|
#define COLLA_NOSERVER 1
|
||||||
|
#define COLLA_NOHOTRELOAD 1
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if COLLA_NOSOCKETS
|
#if COLLA_NOSOCKETS
|
||||||
|
|
@ -12,32 +13,40 @@
|
||||||
#define COLLA_NOSERVER 1
|
#define COLLA_NOSERVER 1
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "src/arena.c"
|
#include "arena.c"
|
||||||
#include "src/base64.c"
|
#include "base64.c"
|
||||||
#include "src/file.c"
|
#include "file.c"
|
||||||
#include "src/format.c"
|
#include "format.c"
|
||||||
#include "src/ini.c"
|
#include "ini.c"
|
||||||
#include "src/json.c"
|
#include "json.c"
|
||||||
#include "src/str.c"
|
#include "str.c"
|
||||||
#include "src/strstream.c"
|
#include "strstream.c"
|
||||||
#include "src/tracelog.c"
|
#include "tracelog.c"
|
||||||
#include "src/utf8.c"
|
#include "utf8.c"
|
||||||
#include "src/vmem.c"
|
#include "vmem.c"
|
||||||
#include "src/xml.c"
|
#include "xml.c"
|
||||||
#include "src/hot_reload.c"
|
#include "sha1.c"
|
||||||
|
#include "markdown.c"
|
||||||
|
#include "highlight.c"
|
||||||
|
#include "dir.c"
|
||||||
|
|
||||||
#if !COLLA_NOTHREADS
|
#if !COLLA_NOTHREADS
|
||||||
#include "src/cthreads.c"
|
#include "cthreads.c"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if !COLLA_NOSOCKETS
|
#if !COLLA_NOSOCKETS
|
||||||
#include "src/socket.c"
|
#include "socket.c"
|
||||||
|
#include "websocket.c"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if !COLLA_NOHTTP
|
#if !COLLA_NOHTTP
|
||||||
#include "src/http.c"
|
#include "http.c"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if !COLLA_NOSERVER
|
#if !COLLA_NOSERVER
|
||||||
#include "src/server.c"
|
#include "server.c"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if !COLLA_NOHOTRELOAD
|
||||||
|
#include "hot_reload.c"
|
||||||
|
#endif
|
||||||
|
|
@ -41,6 +41,14 @@
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if defined(__COSMOPOLITAN__)
|
||||||
|
#define COLLA_COSMO 1
|
||||||
|
#else
|
||||||
|
#define COLLA_COSMO 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define COLLA_POSIX (COLLA_OSX || COLLA_LIN || COLLA_COSMO)
|
||||||
|
|
||||||
#if defined(__clang__)
|
#if defined(__clang__)
|
||||||
|
|
||||||
#define COLLA_CLANG 1
|
#define COLLA_CLANG 1
|
||||||
|
|
@ -88,3 +96,17 @@
|
||||||
#define COLLA_CMT_LIB 0
|
#define COLLA_CMT_LIB 0
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
#if COLLA_WIN
|
||||||
|
|
||||||
|
#define WIN32_LEAN_AND_MEAN
|
||||||
|
#define NOMINMAX
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#undef min
|
||||||
|
#undef max
|
||||||
|
|
||||||
|
#define min(a, b) ((a) < (b) ? (a) : (b))
|
||||||
|
#define max(a, b) ((a) > (b) ? (a) : (b))
|
||||||
|
|
@ -1,281 +1,282 @@
|
||||||
#include "cthreads.h"
|
#include "cthreads.h"
|
||||||
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
typedef struct {
|
// TODO: swap calloc for arenas
|
||||||
cthread_func_t func;
|
|
||||||
void *arg;
|
typedef struct {
|
||||||
} _thr_internal_t;
|
cthread_func_t func;
|
||||||
|
void *arg;
|
||||||
#if COLLA_WIN
|
} _thr_internal_t;
|
||||||
#define WIN32_LEAN_AND_MEAN
|
|
||||||
#include <windows.h>
|
#if COLLA_WIN
|
||||||
|
#include <windows.h>
|
||||||
// == THREAD ===========================================
|
|
||||||
|
// == THREAD ===========================================
|
||||||
static DWORD _thrFuncInternal(void *arg) {
|
|
||||||
_thr_internal_t *params = (_thr_internal_t *)arg;
|
static DWORD WINAPI _thrFuncInternal(void *arg) {
|
||||||
cthread_func_t func = params->func;
|
_thr_internal_t *params = (_thr_internal_t *)arg;
|
||||||
void *argument = params->arg;
|
cthread_func_t func = params->func;
|
||||||
free(params);
|
void *argument = params->arg;
|
||||||
return (DWORD)func(argument);
|
free(params);
|
||||||
}
|
return (DWORD)func(argument);
|
||||||
|
}
|
||||||
cthread_t thrCreate(cthread_func_t func, void *arg) {
|
|
||||||
HANDLE thread = INVALID_HANDLE_VALUE;
|
cthread_t thrCreate(cthread_func_t func, void *arg) {
|
||||||
_thr_internal_t *params = calloc(1, sizeof(_thr_internal_t));
|
HANDLE thread = INVALID_HANDLE_VALUE;
|
||||||
|
_thr_internal_t *params = calloc(1, sizeof(_thr_internal_t));
|
||||||
if(params) {
|
|
||||||
params->func = func;
|
if(params) {
|
||||||
params->arg = arg;
|
params->func = func;
|
||||||
|
params->arg = arg;
|
||||||
thread = CreateThread(NULL, 0, _thrFuncInternal, params, 0, NULL);
|
|
||||||
}
|
thread = CreateThread(NULL, 0, _thrFuncInternal, params, 0, NULL);
|
||||||
|
}
|
||||||
return (cthread_t)thread;
|
|
||||||
}
|
return (cthread_t)thread;
|
||||||
|
}
|
||||||
bool thrValid(cthread_t ctx) {
|
|
||||||
return (HANDLE)ctx != INVALID_HANDLE_VALUE;
|
bool thrValid(cthread_t ctx) {
|
||||||
}
|
return (HANDLE)ctx != INVALID_HANDLE_VALUE;
|
||||||
|
}
|
||||||
bool thrDetach(cthread_t ctx) {
|
|
||||||
return CloseHandle((HANDLE)ctx);
|
bool thrDetach(cthread_t ctx) {
|
||||||
}
|
return CloseHandle((HANDLE)ctx);
|
||||||
|
}
|
||||||
cthread_t thrCurrent(void) {
|
|
||||||
return (cthread_t)GetCurrentThread();
|
cthread_t thrCurrent(void) {
|
||||||
}
|
return (cthread_t)GetCurrentThread();
|
||||||
|
}
|
||||||
int thrCurrentId(void) {
|
|
||||||
return GetCurrentThreadId();
|
int thrCurrentId(void) {
|
||||||
}
|
return GetCurrentThreadId();
|
||||||
|
}
|
||||||
int thrGetId(cthread_t ctx) {
|
|
||||||
#if COLLA_TCC
|
int thrGetId(cthread_t ctx) {
|
||||||
return 0;
|
#if COLLA_TCC
|
||||||
#endif
|
return 0;
|
||||||
return GetThreadId((HANDLE)ctx);
|
#endif
|
||||||
}
|
return GetThreadId((HANDLE)ctx);
|
||||||
|
}
|
||||||
void thrExit(int code) {
|
|
||||||
ExitThread(code);
|
void thrExit(int code) {
|
||||||
}
|
ExitThread(code);
|
||||||
|
}
|
||||||
bool thrJoin(cthread_t ctx, int *code) {
|
|
||||||
if(!ctx) return false;
|
bool thrJoin(cthread_t ctx, int *code) {
|
||||||
int return_code = WaitForSingleObject((HANDLE)ctx, INFINITE);
|
if(!ctx) return false;
|
||||||
if(code) *code = return_code;
|
int return_code = WaitForSingleObject((HANDLE)ctx, INFINITE);
|
||||||
BOOL success = CloseHandle((HANDLE)ctx);
|
if(code) *code = return_code;
|
||||||
return return_code != WAIT_FAILED && success;
|
BOOL success = CloseHandle((HANDLE)ctx);
|
||||||
}
|
return return_code != WAIT_FAILED && success;
|
||||||
|
}
|
||||||
// == MUTEX ============================================
|
|
||||||
|
// == MUTEX ============================================
|
||||||
cmutex_t mtxInit(void) {
|
|
||||||
CRITICAL_SECTION *crit_sec = calloc(1, sizeof(CRITICAL_SECTION));
|
cmutex_t mtxInit(void) {
|
||||||
if(crit_sec) {
|
CRITICAL_SECTION *crit_sec = calloc(1, sizeof(CRITICAL_SECTION));
|
||||||
InitializeCriticalSection(crit_sec);
|
if(crit_sec) {
|
||||||
}
|
InitializeCriticalSection(crit_sec);
|
||||||
return (cmutex_t)crit_sec;
|
}
|
||||||
}
|
return (cmutex_t)crit_sec;
|
||||||
|
}
|
||||||
void mtxFree(cmutex_t ctx) {
|
|
||||||
DeleteCriticalSection((CRITICAL_SECTION *)ctx);
|
void mtxFree(cmutex_t ctx) {
|
||||||
free((CRITICAL_SECTION *)ctx);
|
DeleteCriticalSection((CRITICAL_SECTION *)ctx);
|
||||||
}
|
free((CRITICAL_SECTION *)ctx);
|
||||||
|
}
|
||||||
bool mtxValid(cmutex_t ctx) {
|
|
||||||
return (void *)ctx != NULL;
|
bool mtxValid(cmutex_t ctx) {
|
||||||
}
|
return (void *)ctx != NULL;
|
||||||
|
}
|
||||||
bool mtxLock(cmutex_t ctx) {
|
|
||||||
EnterCriticalSection((CRITICAL_SECTION *)ctx);
|
bool mtxLock(cmutex_t ctx) {
|
||||||
return true;
|
EnterCriticalSection((CRITICAL_SECTION *)ctx);
|
||||||
}
|
return true;
|
||||||
|
}
|
||||||
bool mtxTryLock(cmutex_t ctx) {
|
|
||||||
return TryEnterCriticalSection((CRITICAL_SECTION *)ctx);
|
bool mtxTryLock(cmutex_t ctx) {
|
||||||
}
|
return TryEnterCriticalSection((CRITICAL_SECTION *)ctx);
|
||||||
|
}
|
||||||
bool mtxUnlock(cmutex_t ctx) {
|
|
||||||
LeaveCriticalSection((CRITICAL_SECTION *)ctx);
|
bool mtxUnlock(cmutex_t ctx) {
|
||||||
return true;
|
LeaveCriticalSection((CRITICAL_SECTION *)ctx);
|
||||||
}
|
return true;
|
||||||
|
}
|
||||||
#if !COLLA_NO_CONDITION_VAR
|
|
||||||
// == CONDITION VARIABLE ===============================
|
#if !COLLA_NO_CONDITION_VAR
|
||||||
|
// == CONDITION VARIABLE ===============================
|
||||||
#include "tracelog.h"
|
|
||||||
|
#include "tracelog.h"
|
||||||
condvar_t condInit(void) {
|
|
||||||
CONDITION_VARIABLE *cond = calloc(1, sizeof(CONDITION_VARIABLE));
|
condvar_t condInit(void) {
|
||||||
InitializeConditionVariable(cond);
|
CONDITION_VARIABLE *cond = calloc(1, sizeof(CONDITION_VARIABLE));
|
||||||
return (condvar_t)cond;
|
InitializeConditionVariable(cond);
|
||||||
}
|
return (condvar_t)cond;
|
||||||
|
}
|
||||||
void condFree(condvar_t cond) {
|
|
||||||
free((CONDITION_VARIABLE *)cond);
|
void condFree(condvar_t cond) {
|
||||||
}
|
free((CONDITION_VARIABLE *)cond);
|
||||||
|
}
|
||||||
void condWake(condvar_t cond) {
|
|
||||||
WakeConditionVariable((CONDITION_VARIABLE *)cond);
|
void condWake(condvar_t cond) {
|
||||||
}
|
WakeConditionVariable((CONDITION_VARIABLE *)cond);
|
||||||
|
}
|
||||||
void condWakeAll(condvar_t cond) {
|
|
||||||
WakeAllConditionVariable((CONDITION_VARIABLE *)cond);
|
void condWakeAll(condvar_t cond) {
|
||||||
}
|
WakeAllConditionVariable((CONDITION_VARIABLE *)cond);
|
||||||
|
}
|
||||||
void condWait(condvar_t cond, cmutex_t mtx) {
|
|
||||||
SleepConditionVariableCS((CONDITION_VARIABLE *)cond, (CRITICAL_SECTION *)mtx, INFINITE);
|
void condWait(condvar_t cond, cmutex_t mtx) {
|
||||||
}
|
SleepConditionVariableCS((CONDITION_VARIABLE *)cond, (CRITICAL_SECTION *)mtx, INFINITE);
|
||||||
|
}
|
||||||
void condWaitTimed(condvar_t cond, cmutex_t mtx, int milliseconds) {
|
|
||||||
SleepConditionVariableCS((CONDITION_VARIABLE *)cond, (CRITICAL_SECTION *)mtx, milliseconds);
|
void condWaitTimed(condvar_t cond, cmutex_t mtx, int milliseconds) {
|
||||||
}
|
SleepConditionVariableCS((CONDITION_VARIABLE *)cond, (CRITICAL_SECTION *)mtx, milliseconds);
|
||||||
|
}
|
||||||
#endif
|
|
||||||
|
#endif
|
||||||
#else
|
|
||||||
#include <pthread.h>
|
#else
|
||||||
#include <unistd.h>
|
#include <pthread.h>
|
||||||
#include <sys/syscall.h>
|
#include <unistd.h>
|
||||||
#include <sys/types.h>
|
#include <sys/syscall.h>
|
||||||
|
#include <sys/types.h>
|
||||||
// == THREAD ===========================================
|
|
||||||
|
// == THREAD ===========================================
|
||||||
#define INT_TO_VOIDP(a) ((void *)((uintptr_t)(a)))
|
|
||||||
|
#define INT_TO_VOIDP(a) ((void *)((uintptr_t)(a)))
|
||||||
static void *_thrFuncInternal(void *arg) {
|
|
||||||
_thr_internal_t *params = (_thr_internal_t *)arg;
|
static void *_thrFuncInternal(void *arg) {
|
||||||
cthread_func_t func = params->func;
|
_thr_internal_t *params = (_thr_internal_t *)arg;
|
||||||
void *argument = params->arg;
|
cthread_func_t func = params->func;
|
||||||
free(params);
|
void *argument = params->arg;
|
||||||
return INT_TO_VOIDP(func(argument));
|
free(params);
|
||||||
}
|
return INT_TO_VOIDP(func(argument));
|
||||||
|
}
|
||||||
cthread_t thrCreate(cthread_func_t func, void *arg) {
|
|
||||||
pthread_t handle = (pthread_t)NULL;
|
cthread_t thrCreate(cthread_func_t func, void *arg) {
|
||||||
|
pthread_t handle = (pthread_t)NULL;
|
||||||
_thr_internal_t *params = calloc(1, sizeof(_thr_internal_t));
|
|
||||||
|
_thr_internal_t *params = calloc(1, sizeof(_thr_internal_t));
|
||||||
if(params) {
|
|
||||||
params->func = func;
|
if(params) {
|
||||||
params->arg = arg;
|
params->func = func;
|
||||||
|
params->arg = arg;
|
||||||
int result = pthread_create(&handle, NULL, _thrFuncInternal, params);
|
|
||||||
if(result) handle = (pthread_t)NULL;
|
int result = pthread_create(&handle, NULL, _thrFuncInternal, params);
|
||||||
}
|
if(result) handle = (pthread_t)NULL;
|
||||||
|
}
|
||||||
return (cthread_t)handle;
|
|
||||||
}
|
return (cthread_t)handle;
|
||||||
|
}
|
||||||
bool thrValid(cthread_t ctx) {
|
|
||||||
return (void *)ctx != NULL;
|
bool thrValid(cthread_t ctx) {
|
||||||
}
|
return (void *)ctx != NULL;
|
||||||
|
}
|
||||||
bool thrDetach(cthread_t ctx) {
|
|
||||||
return pthread_detach((pthread_t)ctx) == 0;
|
bool thrDetach(cthread_t ctx) {
|
||||||
}
|
return pthread_detach((pthread_t)ctx) == 0;
|
||||||
|
}
|
||||||
cthread_t thrCurrent(void) {
|
|
||||||
return (cthread_t)pthread_self();
|
cthread_t thrCurrent(void) {
|
||||||
}
|
return (cthread_t)pthread_self();
|
||||||
|
}
|
||||||
int thrCurrentId(void) {
|
|
||||||
return (int)pthread_self();
|
int thrCurrentId(void) {
|
||||||
}
|
return (int)pthread_self();
|
||||||
|
}
|
||||||
int thrGetId(cthread_t ctx) {
|
|
||||||
return (int)ctx;
|
int thrGetId(cthread_t ctx) {
|
||||||
}
|
return (int)ctx;
|
||||||
|
}
|
||||||
void thrExit(int code) {
|
|
||||||
pthread_exit(INT_TO_VOIDP(code));
|
void thrExit(int code) {
|
||||||
}
|
pthread_exit(INT_TO_VOIDP(code));
|
||||||
|
}
|
||||||
bool thrJoin(cthread_t ctx, int *code) {
|
|
||||||
void *result = code;
|
bool thrJoin(cthread_t ctx, int *code) {
|
||||||
return pthread_join((pthread_t)ctx, &result) != 0;
|
void *result = code;
|
||||||
}
|
return pthread_join((pthread_t)ctx, &result) != 0;
|
||||||
|
}
|
||||||
// == MUTEX ============================================
|
|
||||||
|
// == MUTEX ============================================
|
||||||
cmutex_t mtxInit(void) {
|
|
||||||
pthread_mutex_t *mutex = calloc(1, sizeof(pthread_mutex_t));
|
cmutex_t mtxInit(void) {
|
||||||
|
pthread_mutex_t *mutex = calloc(1, sizeof(pthread_mutex_t));
|
||||||
if(mutex) {
|
|
||||||
if(pthread_mutex_init(mutex, NULL)) {
|
if(mutex) {
|
||||||
free(mutex);
|
if(pthread_mutex_init(mutex, NULL)) {
|
||||||
mutex = NULL;
|
free(mutex);
|
||||||
}
|
mutex = NULL;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return (cmutex_t)mutex;
|
|
||||||
}
|
return (cmutex_t)mutex;
|
||||||
|
}
|
||||||
void mtxFree(cmutex_t ctx) {
|
|
||||||
pthread_mutex_destroy((pthread_mutex_t *)ctx);
|
void mtxFree(cmutex_t ctx) {
|
||||||
}
|
pthread_mutex_destroy((pthread_mutex_t *)ctx);
|
||||||
|
}
|
||||||
bool mtxValid(cmutex_t ctx) {
|
|
||||||
return (void *)ctx != NULL;
|
bool mtxValid(cmutex_t ctx) {
|
||||||
}
|
return (void *)ctx != NULL;
|
||||||
|
}
|
||||||
bool mtxLock(cmutex_t ctx) {
|
|
||||||
return pthread_mutex_lock((pthread_mutex_t *)ctx) == 0;
|
bool mtxLock(cmutex_t ctx) {
|
||||||
}
|
return pthread_mutex_lock((pthread_mutex_t *)ctx) == 0;
|
||||||
|
}
|
||||||
bool mtxTryLock(cmutex_t ctx) {
|
|
||||||
return pthread_mutex_trylock((pthread_mutex_t *)ctx) == 0;
|
bool mtxTryLock(cmutex_t ctx) {
|
||||||
}
|
return pthread_mutex_trylock((pthread_mutex_t *)ctx) == 0;
|
||||||
|
}
|
||||||
bool mtxUnlock(cmutex_t ctx) {
|
|
||||||
return pthread_mutex_unlock((pthread_mutex_t *)ctx) == 0;
|
bool mtxUnlock(cmutex_t ctx) {
|
||||||
}
|
return pthread_mutex_unlock((pthread_mutex_t *)ctx) == 0;
|
||||||
|
}
|
||||||
#if !COLLA_NO_CONDITION_VAR
|
|
||||||
|
#if !COLLA_NO_CONDITION_VAR
|
||||||
// == CONDITION VARIABLE ===============================
|
|
||||||
|
// == CONDITION VARIABLE ===============================
|
||||||
condvar_t condInit(void) {
|
|
||||||
pthread_cond_t *cond = calloc(1, sizeof(pthread_cond_t));
|
condvar_t condInit(void) {
|
||||||
|
pthread_cond_t *cond = calloc(1, sizeof(pthread_cond_t));
|
||||||
if(cond) {
|
|
||||||
if(pthread_cond_init(cond, NULL)) {
|
if(cond) {
|
||||||
free(cond);
|
if(pthread_cond_init(cond, NULL)) {
|
||||||
cond = NULL;
|
free(cond);
|
||||||
}
|
cond = NULL;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return (condvar_t)cond;
|
|
||||||
}
|
return (condvar_t)cond;
|
||||||
|
}
|
||||||
void condFree(condvar_t cond) {
|
|
||||||
if (!cond) return;
|
void condFree(condvar_t cond) {
|
||||||
pthread_cond_destroy((pthread_cond_t *)cond);
|
if (!cond) return;
|
||||||
free((pthread_cond_t *)cond);
|
pthread_cond_destroy((pthread_cond_t *)cond);
|
||||||
}
|
free((pthread_cond_t *)cond);
|
||||||
|
}
|
||||||
void condWake(condvar_t cond) {
|
|
||||||
pthread_cond_signal((pthread_cond_t *)cond);
|
void condWake(condvar_t cond) {
|
||||||
}
|
pthread_cond_signal((pthread_cond_t *)cond);
|
||||||
|
}
|
||||||
void condWakeAll(condvar_t cond) {
|
|
||||||
pthread_cond_broadcast((pthread_cond_t *)cond);
|
void condWakeAll(condvar_t cond) {
|
||||||
}
|
pthread_cond_broadcast((pthread_cond_t *)cond);
|
||||||
|
}
|
||||||
void condWait(condvar_t cond, cmutex_t mtx) {
|
|
||||||
pthread_cond_wait((pthread_cond_t *)cond, (pthread_mutex_t *)mtx);
|
void condWait(condvar_t cond, cmutex_t mtx) {
|
||||||
}
|
pthread_cond_wait((pthread_cond_t *)cond, (pthread_mutex_t *)mtx);
|
||||||
|
}
|
||||||
void condWaitTimed(condvar_t cond, cmutex_t mtx, int milliseconds) {
|
|
||||||
struct timespec timeout;
|
void condWaitTimed(condvar_t cond, cmutex_t mtx, int milliseconds) {
|
||||||
time(&timeout.tv_sec);
|
struct timespec timeout;
|
||||||
timeout.tv_nsec += milliseconds * 1000000;
|
time(&timeout.tv_sec);
|
||||||
pthread_cond_timedwait((pthread_cond_t *)cond, (pthread_mutex_t *)mtx, &timeout);
|
timeout.tv_nsec += milliseconds * 1000000;
|
||||||
}
|
pthread_cond_timedwait((pthread_cond_t *)cond, (pthread_mutex_t *)mtx, &timeout);
|
||||||
|
}
|
||||||
#endif
|
|
||||||
|
#endif
|
||||||
#endif
|
|
||||||
|
#endif
|
||||||
|
|
@ -1,57 +1,57 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "collatypes.h"
|
#include "collatypes.h"
|
||||||
|
|
||||||
#if COLLA_TCC
|
#if COLLA_TCC
|
||||||
#define COLLA_NO_CONDITION_VAR 1
|
#define COLLA_NO_CONDITION_VAR 1
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
typedef struct arena_t arena_t;
|
typedef struct arena_t arena_t;
|
||||||
|
|
||||||
// == THREAD ===========================================
|
// == THREAD ===========================================
|
||||||
|
|
||||||
typedef uintptr_t cthread_t;
|
typedef uintptr_t cthread_t;
|
||||||
|
|
||||||
typedef int (*cthread_func_t)(void *);
|
typedef int (*cthread_func_t)(void *);
|
||||||
|
|
||||||
cthread_t thrCreate(cthread_func_t func, void *arg);
|
cthread_t thrCreate(cthread_func_t func, void *arg);
|
||||||
bool thrValid(cthread_t ctx);
|
bool thrValid(cthread_t ctx);
|
||||||
bool thrDetach(cthread_t ctx);
|
bool thrDetach(cthread_t ctx);
|
||||||
|
|
||||||
cthread_t thrCurrent(void);
|
cthread_t thrCurrent(void);
|
||||||
int thrCurrentId(void);
|
int thrCurrentId(void);
|
||||||
int thrGetId(cthread_t ctx);
|
int thrGetId(cthread_t ctx);
|
||||||
|
|
||||||
void thrExit(int code);
|
void thrExit(int code);
|
||||||
bool thrJoin(cthread_t ctx, int *code);
|
bool thrJoin(cthread_t ctx, int *code);
|
||||||
|
|
||||||
// == MUTEX ============================================
|
// == MUTEX ============================================
|
||||||
|
|
||||||
typedef uintptr_t cmutex_t;
|
typedef uintptr_t cmutex_t;
|
||||||
|
|
||||||
cmutex_t mtxInit(void);
|
cmutex_t mtxInit(void);
|
||||||
void mtxFree(cmutex_t ctx);
|
void mtxFree(cmutex_t ctx);
|
||||||
|
|
||||||
bool mtxValid(cmutex_t ctx);
|
bool mtxValid(cmutex_t ctx);
|
||||||
|
|
||||||
bool mtxLock(cmutex_t ctx);
|
bool mtxLock(cmutex_t ctx);
|
||||||
bool mtxTryLock(cmutex_t ctx);
|
bool mtxTryLock(cmutex_t ctx);
|
||||||
bool mtxUnlock(cmutex_t ctx);
|
bool mtxUnlock(cmutex_t ctx);
|
||||||
|
|
||||||
#if !COLLA_NO_CONDITION_VAR
|
#if !COLLA_NO_CONDITION_VAR
|
||||||
// == CONDITION VARIABLE ===============================
|
// == CONDITION VARIABLE ===============================
|
||||||
|
|
||||||
typedef uintptr_t condvar_t;
|
typedef uintptr_t condvar_t;
|
||||||
|
|
||||||
#define COND_WAIT_INFINITE 0xFFFFFFFF
|
#define COND_WAIT_INFINITE 0xFFFFFFFF
|
||||||
|
|
||||||
condvar_t condInit(void);
|
condvar_t condInit(void);
|
||||||
void condFree(condvar_t cond);
|
void condFree(condvar_t cond);
|
||||||
|
|
||||||
void condWake(condvar_t cond);
|
void condWake(condvar_t cond);
|
||||||
void condWakeAll(condvar_t cond);
|
void condWakeAll(condvar_t cond);
|
||||||
|
|
||||||
void condWait(condvar_t cond, cmutex_t mtx);
|
void condWait(condvar_t cond, cmutex_t mtx);
|
||||||
void condWaitTimed(condvar_t cond, cmutex_t mtx, int milliseconds);
|
void condWaitTimed(condvar_t cond, cmutex_t mtx, int milliseconds);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
@ -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);
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
#include "coroutine.h"
|
|
||||||
|
|
||||||
coroutine_t coInit() {
|
|
||||||
return (coroutine_t) {
|
|
||||||
.state = 0
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
bool coIsDead(coroutine_t co) {
|
|
||||||
return co.state == -1;
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
|
||||||
204
deprecated/dir.c
204
deprecated/dir.c
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
134
deprecated/fs.c
134
deprecated/fs.c
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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));
|
|
||||||
}
|
|
||||||
|
|
@ -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);
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
@ -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);
|
|
||||||
107
deprecated/os.c
107
deprecated/os.c
|
|
@ -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
|
|
||||||
|
|
@ -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
163
dir.c
Normal 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
25
dir.h
Normal 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
131
docs/arena.md
Normal 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
32
docs/base64.md
Normal 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
49
docs/cthreads.md
Normal 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
52
docs/dir.md
Normal 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
BIN
docs/docs.com
Normal file
Binary file not shown.
41
docs/file.md
Normal file
41
docs/file.md
Normal 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
28
docs/format.md
Normal 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
37
docs/highlight.md
Normal 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
79
docs/hot_reload.md
Normal 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
45
docs/html.md
Normal 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
5
docs/http.md
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title = HTTP
|
||||||
|
---
|
||||||
|
# HTTP
|
||||||
|
----------
|
||||||
5
docs/ini.md
Normal file
5
docs/ini.md
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title = Ini
|
||||||
|
---
|
||||||
|
# Ini
|
||||||
|
----------
|
||||||
5
docs/json.md
Normal file
5
docs/json.md
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title = Json
|
||||||
|
---
|
||||||
|
# Json
|
||||||
|
----------
|
||||||
5
docs/markdown.md
Normal file
5
docs/markdown.md
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title = Markdown
|
||||||
|
---
|
||||||
|
# Markdown
|
||||||
|
----------
|
||||||
5
docs/readme.md
Normal file
5
docs/readme.md
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title = Readme
|
||||||
|
---
|
||||||
|
# Readme
|
||||||
|
----------
|
||||||
5
docs/server.md
Normal file
5
docs/server.md
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title = Server
|
||||||
|
---
|
||||||
|
# Server
|
||||||
|
----------
|
||||||
5
docs/sha1.md
Normal file
5
docs/sha1.md
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title = SHA-1
|
||||||
|
---
|
||||||
|
# SHA-1
|
||||||
|
----------
|
||||||
5
docs/socket.md
Normal file
5
docs/socket.md
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title = Socket
|
||||||
|
---
|
||||||
|
# Socket
|
||||||
|
----------
|
||||||
5
docs/str.md
Normal file
5
docs/str.md
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title = Str
|
||||||
|
---
|
||||||
|
# Str
|
||||||
|
----------
|
||||||
5
docs/strstream.md
Normal file
5
docs/strstream.md
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title = StrStream
|
||||||
|
---
|
||||||
|
# StrStream
|
||||||
|
----------
|
||||||
5
docs/tracelog.md
Normal file
5
docs/tracelog.md
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title = Tracelog
|
||||||
|
---
|
||||||
|
# Tracelog
|
||||||
|
----------
|
||||||
5
docs/utf8.md
Normal file
5
docs/utf8.md
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title = UTF-8
|
||||||
|
---
|
||||||
|
# UTF-8
|
||||||
|
----------
|
||||||
5
docs/vec.md
Normal file
5
docs/vec.md
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title = Vec
|
||||||
|
---
|
||||||
|
# Vec
|
||||||
|
----------
|
||||||
5
docs/vmem.md
Normal file
5
docs/vmem.md
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title = VMem
|
||||||
|
---
|
||||||
|
# VMem
|
||||||
|
----------
|
||||||
5
docs/websocket.md
Normal file
5
docs/websocket.md
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title = WebSocket
|
||||||
|
---
|
||||||
|
# WebSocket
|
||||||
|
----------
|
||||||
5
docs/xml.md
Normal file
5
docs/xml.md
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title = Xml
|
||||||
|
---
|
||||||
|
# Xml
|
||||||
|
----------
|
||||||
|
|
@ -1,360 +1,367 @@
|
||||||
#include "file.h"
|
#include "file.h"
|
||||||
|
|
||||||
#include "warnings/colla_warn_beg.h"
|
#include "warnings/colla_warn_beg.h"
|
||||||
|
|
||||||
#include "tracelog.h"
|
#include "tracelog.h"
|
||||||
#include "format.h"
|
#include "format.h"
|
||||||
|
|
||||||
#if COLLA_WIN
|
#if COLLA_WIN
|
||||||
|
|
||||||
#define WIN32_LEAN_AND_MEAN
|
#include <windows.h>
|
||||||
#include <windows.h>
|
|
||||||
|
#undef FILE_BEGIN
|
||||||
#undef FILE_BEGIN
|
#undef FILE_CURRENT
|
||||||
#undef FILE_CURRENT
|
#undef FILE_END
|
||||||
#undef FILE_END
|
|
||||||
|
#define FILE_BEGIN 0
|
||||||
#define FILE_BEGIN 0
|
#define FILE_CURRENT 1
|
||||||
#define FILE_CURRENT 1
|
#define FILE_END 2
|
||||||
#define FILE_END 2
|
|
||||||
|
#if COLLA_TCC
|
||||||
#if COLLA_TCC
|
#include "tcc/colla_tcc.h"
|
||||||
#include "tcc/colla_tcc.h"
|
#endif
|
||||||
#endif
|
|
||||||
|
static DWORD file__mode_to_access(filemode_e mode) {
|
||||||
static DWORD file__mode_to_access(filemode_e mode) {
|
if (mode & FILE_APPEND) return FILE_APPEND_DATA;
|
||||||
if (mode & FILE_APPEND) return FILE_APPEND_DATA;
|
|
||||||
|
DWORD out = 0;
|
||||||
DWORD out = 0;
|
if (mode & FILE_READ) out |= GENERIC_READ;
|
||||||
if (mode & FILE_READ) out |= GENERIC_READ;
|
if (mode & FILE_WRITE) out |= GENERIC_WRITE;
|
||||||
if (mode & FILE_WRITE) out |= GENERIC_WRITE;
|
return out;
|
||||||
return out;
|
}
|
||||||
}
|
|
||||||
|
static DWORD file__mode_to_creation(filemode_e mode) {
|
||||||
static DWORD file__mode_to_creation(filemode_e mode) {
|
if (mode == FILE_READ) return OPEN_EXISTING;
|
||||||
if (mode == FILE_READ) return OPEN_EXISTING;
|
if (mode == FILE_WRITE) return CREATE_ALWAYS;
|
||||||
if (mode == FILE_WRITE) return CREATE_ALWAYS;
|
return OPEN_ALWAYS;
|
||||||
return OPEN_ALWAYS;
|
}
|
||||||
}
|
|
||||||
|
bool fileExists(const char *name) {
|
||||||
bool fileExists(const char *name) {
|
return GetFileAttributesA(name) != INVALID_FILE_ATTRIBUTES;
|
||||||
return GetFileAttributesA(name) != INVALID_FILE_ATTRIBUTES;
|
}
|
||||||
}
|
|
||||||
|
TCHAR *fileGetFullPath(arena_t *arena, strview_t filename) {
|
||||||
TCHAR *fileGetFullPath(arena_t *arena, strview_t filename) {
|
TCHAR long_path_prefix[] = TEXT("\\\\?\\");
|
||||||
TCHAR long_path_prefix[] = TEXT("\\\\?\\");
|
const usize prefix_len = arrlen(long_path_prefix) - 1;
|
||||||
const usize prefix_len = arrlen(long_path_prefix) - 1;
|
|
||||||
|
uint8 tempbuf[4096];
|
||||||
uint8 tempbuf[4096];
|
arena_t scratch = arenaMake(ARENA_STATIC, sizeof(tempbuf), tempbuf);
|
||||||
arena_t scratch = arenaMake(ARENA_STATIC, sizeof(tempbuf), tempbuf);
|
|
||||||
|
TCHAR *rel_path = strvToTChar(&scratch, filename);
|
||||||
TCHAR *rel_path = strvToTChar(&scratch, filename);
|
DWORD pathlen = GetFullPathName(rel_path, 0, NULL, NULL);
|
||||||
DWORD pathlen = GetFullPathName(rel_path, 0, NULL, NULL);
|
|
||||||
|
TCHAR *full_path = alloc(arena, TCHAR, pathlen + prefix_len + 1);
|
||||||
TCHAR *full_path = alloc(arena, TCHAR, pathlen + prefix_len + 1);
|
memcpy(full_path, long_path_prefix, prefix_len * sizeof(TCHAR));
|
||||||
memcpy(full_path, long_path_prefix, prefix_len * sizeof(TCHAR));
|
|
||||||
|
GetFullPathName(rel_path, pathlen + 1, full_path + prefix_len, NULL);
|
||||||
GetFullPathName(rel_path, pathlen + 1, full_path + prefix_len, NULL);
|
|
||||||
|
return full_path;
|
||||||
return full_path;
|
}
|
||||||
}
|
|
||||||
|
bool fileDelete(arena_t scratch, strview_t filename) {
|
||||||
strview_t fileGetFilename(strview_t path) {
|
wchar_t *wfname = strvToWChar(&scratch, filename, NULL);
|
||||||
usize last_lin = strvRFind(path, '/', 0);
|
return DeleteFileW(wfname);
|
||||||
usize last_win = strvRFind(path, '\\', 0);
|
}
|
||||||
last_lin = last_lin != SIZE_MAX ? last_lin : 0;
|
|
||||||
last_win = last_win != SIZE_MAX ? last_win : 0;
|
file_t fileOpen(arena_t scratch, strview_t name, filemode_e mode) {
|
||||||
usize last = max(last_lin, last_win);
|
TCHAR *full_path = fileGetFullPath(&scratch, name);
|
||||||
return strvSub(path, last ? last + 1 : last, SIZE_MAX);
|
|
||||||
}
|
HANDLE handle = CreateFile(
|
||||||
|
full_path,
|
||||||
strview_t fileGetExtension(strview_t path) {
|
file__mode_to_access(mode),
|
||||||
usize ext_pos = strvRFind(path, '.', 0);
|
FILE_SHARE_READ,
|
||||||
return strvSub(path, ext_pos, SIZE_MAX);
|
NULL,
|
||||||
}
|
file__mode_to_creation(mode),
|
||||||
|
FILE_ATTRIBUTE_NORMAL,
|
||||||
void fileSplitPath(strview_t path, strview_t *dir, strview_t *name, strview_t *ext) {
|
NULL
|
||||||
usize dir_lin = strvRFind(path, '/', 0);
|
);
|
||||||
usize dir_win = strvRFind(path, '\\', 0);
|
|
||||||
dir_lin = dir_lin != STR_NONE ? dir_lin : 0;
|
return (file_t){
|
||||||
dir_win = dir_win != STR_NONE ? dir_win : 0;
|
.handle = (uintptr_t)handle,
|
||||||
usize dir_pos = max(dir_lin, dir_win);
|
};
|
||||||
|
}
|
||||||
usize ext_pos = strvRFind(path, '.', 0);
|
|
||||||
|
void fileClose(file_t ctx) {
|
||||||
if (dir) {
|
if (!fileIsValid(ctx)) return;
|
||||||
*dir = strvSub(path, 0, dir_pos);
|
CloseHandle((HANDLE)ctx.handle);
|
||||||
}
|
}
|
||||||
if (name) {
|
|
||||||
*name = strvSub(path, dir_pos ? dir_pos + 1 : dir_pos, ext_pos);
|
bool fileIsValid(file_t ctx) {
|
||||||
}
|
return (HANDLE)ctx.handle != 0 &&
|
||||||
if (ext) {
|
(HANDLE)ctx.handle != INVALID_HANDLE_VALUE;
|
||||||
*ext = strvSub(path, ext_pos, SIZE_MAX);
|
}
|
||||||
}
|
|
||||||
}
|
usize fileRead(file_t ctx, void *buf, usize len) {
|
||||||
|
if (!fileIsValid(ctx)) return 0;
|
||||||
bool fileDelete(arena_t scratch, strview_t filename) {
|
DWORD read = 0;
|
||||||
wchar_t *wfname = strvToWChar(&scratch, filename, NULL);
|
ReadFile((HANDLE)ctx.handle, buf, (DWORD)len, &read, NULL);
|
||||||
return DeleteFileW(wfname);
|
return (usize)read;
|
||||||
}
|
}
|
||||||
|
|
||||||
file_t fileOpen(arena_t scratch, strview_t name, filemode_e mode) {
|
usize fileWrite(file_t ctx, const void *buf, usize len) {
|
||||||
TCHAR *full_path = fileGetFullPath(&scratch, name);
|
if (!fileIsValid(ctx)) return 0;
|
||||||
|
DWORD written = 0;
|
||||||
HANDLE handle = CreateFile(
|
WriteFile((HANDLE)ctx.handle, buf, (DWORD)len, &written, NULL);
|
||||||
full_path,
|
return (usize)written;
|
||||||
file__mode_to_access(mode),
|
}
|
||||||
FILE_SHARE_READ,
|
|
||||||
NULL,
|
bool fileSeekEnd(file_t ctx) {
|
||||||
file__mode_to_creation(mode),
|
if (!fileIsValid(ctx)) return false;
|
||||||
FILE_ATTRIBUTE_NORMAL,
|
DWORD result = SetFilePointer((HANDLE)ctx.handle, 0, NULL, FILE_END);
|
||||||
NULL
|
return result != INVALID_SET_FILE_POINTER;
|
||||||
);
|
}
|
||||||
|
|
||||||
return (file_t){
|
void fileRewind(file_t ctx) {
|
||||||
.handle = (uintptr_t)handle,
|
if (!fileIsValid(ctx)) return;
|
||||||
};
|
SetFilePointer((HANDLE)ctx.handle, 0, NULL, FILE_BEGIN);
|
||||||
}
|
}
|
||||||
|
|
||||||
void fileClose(file_t ctx) {
|
usize fileTell(file_t ctx) {
|
||||||
if (!fileIsValid(ctx)) return;
|
if (!fileIsValid(ctx)) return 0;
|
||||||
CloseHandle((HANDLE)ctx.handle);
|
LARGE_INTEGER tell = {0};
|
||||||
}
|
BOOL result = SetFilePointerEx((HANDLE)ctx.handle, (LARGE_INTEGER){0}, &tell, FILE_CURRENT);
|
||||||
|
return result == TRUE ? (usize)tell.QuadPart : 0;
|
||||||
bool fileIsValid(file_t ctx) {
|
}
|
||||||
return (HANDLE)ctx.handle != 0 &&
|
|
||||||
(HANDLE)ctx.handle != INVALID_HANDLE_VALUE;
|
usize fileSize(file_t ctx) {
|
||||||
}
|
if (!fileIsValid(ctx)) return 0;
|
||||||
|
LARGE_INTEGER size = {0};
|
||||||
usize fileRead(file_t ctx, void *buf, usize len) {
|
BOOL result = GetFileSizeEx((HANDLE)ctx.handle, &size);
|
||||||
if (!fileIsValid(ctx)) return 0;
|
return result == TRUE ? (usize)size.QuadPart : 0;
|
||||||
DWORD read = 0;
|
}
|
||||||
ReadFile((HANDLE)ctx.handle, buf, (DWORD)len, &read, NULL);
|
|
||||||
return (usize)read;
|
uint64 fileGetTimeFP(file_t ctx) {
|
||||||
}
|
if (!fileIsValid(ctx)) return 0;
|
||||||
|
FILETIME time = {0};
|
||||||
usize fileWrite(file_t ctx, const void *buf, usize len) {
|
GetFileTime((HANDLE)ctx.handle, NULL, NULL, &time);
|
||||||
if (!fileIsValid(ctx)) return 0;
|
ULARGE_INTEGER utime = {
|
||||||
DWORD written = 0;
|
.HighPart = time.dwHighDateTime,
|
||||||
WriteFile((HANDLE)ctx.handle, buf, (DWORD)len, &written, NULL);
|
.LowPart = time.dwLowDateTime,
|
||||||
return (usize)written;
|
};
|
||||||
}
|
return (uint64)utime.QuadPart;
|
||||||
|
}
|
||||||
bool fileSeekEnd(file_t ctx) {
|
|
||||||
if (!fileIsValid(ctx)) return false;
|
#else
|
||||||
DWORD result = SetFilePointer((HANDLE)ctx.handle, 0, NULL, FILE_END);
|
|
||||||
return result != INVALID_SET_FILE_POINTER;
|
#include <stdio.h>
|
||||||
}
|
|
||||||
|
static const char *file__mode_to_stdio(filemode_e mode) {
|
||||||
void fileRewind(file_t ctx) {
|
if (mode == FILE_READ) return "rb";
|
||||||
if (!fileIsValid(ctx)) return;
|
if (mode == FILE_WRITE) return "wb";
|
||||||
SetFilePointer((HANDLE)ctx.handle, 0, NULL, FILE_BEGIN);
|
if (mode == FILE_APPEND) return "ab";
|
||||||
}
|
if (mode == (FILE_READ | FILE_WRITE)) return "rb+";
|
||||||
|
|
||||||
usize fileTell(file_t ctx) {
|
return "ab+";
|
||||||
if (!fileIsValid(ctx)) return 0;
|
}
|
||||||
LARGE_INTEGER tell = {0};
|
|
||||||
BOOL result = SetFilePointerEx((HANDLE)ctx.handle, (LARGE_INTEGER){0}, &tell, FILE_CURRENT);
|
bool fileExists(const char *name) {
|
||||||
return result == TRUE ? (usize)tell.QuadPart : 0;
|
FILE *fp = fopen(name, "rb");
|
||||||
}
|
bool exists = fp != NULL;
|
||||||
|
fclose(fp);
|
||||||
usize fileSize(file_t ctx) {
|
return exists;
|
||||||
if (!fileIsValid(ctx)) return 0;
|
}
|
||||||
LARGE_INTEGER size = {0};
|
|
||||||
BOOL result = GetFileSizeEx((HANDLE)ctx.handle, &size);
|
bool fileDelete(arena_t scratch, strview_t filename) {
|
||||||
return result == TRUE ? (usize)size.QuadPart : 0;
|
str_t name = str(&scratch, filename);
|
||||||
}
|
return remove(name.buf) == 0;
|
||||||
|
}
|
||||||
uint64 fileGetTimeFP(file_t ctx) {
|
|
||||||
if (!fileIsValid(ctx)) return 0;
|
file_t fileOpen(arena_t scratch, strview_t name, filemode_e mode) {
|
||||||
FILETIME time = {0};
|
str_t filename = str(&scratch, name);
|
||||||
GetFileTime((HANDLE)ctx.handle, NULL, NULL, &time);
|
return (file_t) {
|
||||||
ULARGE_INTEGER utime = {
|
.handle = (uintptr_t)fopen(filename.buf, file__mode_to_stdio(mode))
|
||||||
.HighPart = time.dwHighDateTime,
|
};
|
||||||
.LowPart = time.dwLowDateTime,
|
}
|
||||||
};
|
|
||||||
return (uint64)utime.QuadPart;
|
void fileClose(file_t ctx) {
|
||||||
}
|
FILE *fp = (FILE *)ctx.handle;
|
||||||
|
if (fp) {
|
||||||
#else
|
fclose(fp);
|
||||||
|
}
|
||||||
#include <stdio.h>
|
}
|
||||||
|
|
||||||
static const char *file__mode_to_stdio(filemode_e mode) {
|
bool fileIsValid(file_t ctx) {
|
||||||
if (mode == FILE_READ) return "rb";
|
bool is_valid = (FILE *)ctx.handle != NULL;
|
||||||
if (mode == FILE_WRITE) return "wb";
|
if (!is_valid) warn("file not valid");
|
||||||
if (mode == FILE_APPEND) return "ab";
|
return is_valid;
|
||||||
if (mode == (FILE_READ | FILE_WRITE)) return "rb+";
|
}
|
||||||
|
|
||||||
return "ab+";
|
usize fileRead(file_t ctx, void *buf, usize len) {
|
||||||
}
|
if (!fileIsValid(ctx)) return 0;
|
||||||
|
return fread(buf, 1, len, (FILE *)ctx.handle);
|
||||||
bool fileExists(const char *name) {
|
}
|
||||||
FILE *fp = fopen(name, "rb");
|
|
||||||
bool exists = fp != NULL;
|
usize fileWrite(file_t ctx, const void *buf, usize len) {
|
||||||
fclose(fp);
|
if (!fileIsValid(ctx)) return 0;
|
||||||
return exists;
|
return fwrite(buf, 1, len, (FILE *)ctx.handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
file_t fileOpen(arena_t scratch, strview_t name, filemode_e mode) {
|
bool fileSeekEnd(file_t ctx) {
|
||||||
str_t filename = str(&scratch, name);
|
if (!fileIsValid(ctx)) return false;
|
||||||
return (file_t) {
|
return fseek((FILE *)ctx.handle, 0, SEEK_END) == 0;
|
||||||
.handle = (uintptr_t)fopen(filename.buf, file__mode_to_stdio(mode))
|
}
|
||||||
};
|
|
||||||
}
|
void fileRewind(file_t ctx) {
|
||||||
|
if (!fileIsValid(ctx)) return;
|
||||||
void fileClose(file_t ctx) {
|
fseek((FILE *)ctx.handle, 0, SEEK_SET);
|
||||||
FILE *fp = (FILE *)ctx.handle;
|
}
|
||||||
if (fp) {
|
|
||||||
fclose(fp);
|
usize fileTell(file_t ctx) {
|
||||||
}
|
if (!fileIsValid(ctx)) return 0;
|
||||||
}
|
return ftell((FILE *)ctx.handle);
|
||||||
|
}
|
||||||
bool fileIsValid(file_t ctx) {
|
|
||||||
bool is_valid = (FILE *)ctx.handle != NULL;
|
usize fileSize(file_t ctx) {
|
||||||
if (!is_valid) warn("file not valid");
|
if (!fileIsValid(ctx)) return 0;
|
||||||
return is_valid;
|
FILE *fp = (FILE *)ctx.handle;
|
||||||
}
|
fseek(fp, 0, SEEK_END);
|
||||||
|
long len = ftell(fp);
|
||||||
usize fileRead(file_t ctx, void *buf, usize len) {
|
fseek(fp, 0, SEEK_SET);
|
||||||
if (!fileIsValid(ctx)) return 0;
|
return (usize)len;
|
||||||
return fread(buf, 1, len, (FILE *)ctx.handle);
|
}
|
||||||
}
|
|
||||||
|
uint64 fileGetTimeFP(file_t ctx) {
|
||||||
usize fileWrite(file_t ctx, const void *buf, usize len) {
|
#if COLLA_LIN
|
||||||
if (!fileIsValid(ctx)) return 0;
|
return 0;
|
||||||
return fwrite(buf, 1, len, (FILE *)ctx.handle);
|
#else
|
||||||
}
|
fatal("fileGetTime not implemented yet outside of linux and windows");
|
||||||
|
return 0;
|
||||||
bool fileSeekEnd(file_t ctx) {
|
#endif
|
||||||
if (!fileIsValid(ctx)) return false;
|
}
|
||||||
return fseek((FILE *)ctx.handle, 0, SEEK_END) == 0;
|
|
||||||
}
|
#endif
|
||||||
|
|
||||||
void fileRewind(file_t ctx) {
|
strview_t fileGetFilename(strview_t path) {
|
||||||
if (!fileIsValid(ctx)) return;
|
usize last_lin = strvRFind(path, '/', 0);
|
||||||
fseek((FILE *)ctx.handle, 0, SEEK_SET);
|
usize last_win = strvRFind(path, '\\', 0);
|
||||||
}
|
last_lin = last_lin != SIZE_MAX ? last_lin : 0;
|
||||||
|
last_win = last_win != SIZE_MAX ? last_win : 0;
|
||||||
usize fileTell(file_t ctx) {
|
usize last = max(last_lin, last_win);
|
||||||
if (!fileIsValid(ctx)) return 0;
|
return strvSub(path, last ? last + 1 : last, SIZE_MAX);
|
||||||
return ftell((FILE *)ctx.handle);
|
}
|
||||||
}
|
|
||||||
|
strview_t fileGetExtension(strview_t path) {
|
||||||
usize fileSize(file_t ctx) {
|
usize ext_pos = strvRFind(path, '.', 0);
|
||||||
if (!fileIsValid(ctx)) return 0;
|
return strvSub(path, ext_pos, SIZE_MAX);
|
||||||
FILE *fp = (FILE *)ctx.handle;
|
}
|
||||||
fseek(fp, 0, SEEK_END);
|
|
||||||
long len = ftell(fp);
|
void fileSplitPath(strview_t path, strview_t *dir, strview_t *name, strview_t *ext) {
|
||||||
fseek(fp, 0, SEEK_SET);
|
usize dir_lin = strvRFind(path, '/', 0);
|
||||||
return (usize)len;
|
usize dir_win = strvRFind(path, '\\', 0);
|
||||||
}
|
dir_lin = dir_lin != STR_NONE ? dir_lin : 0;
|
||||||
|
dir_win = dir_win != STR_NONE ? dir_win : 0;
|
||||||
uint64 fileGetTimeFP(file_t ctx) {
|
usize dir_pos = max(dir_lin, dir_win);
|
||||||
#if COLLA_LIN
|
|
||||||
return 0;
|
usize ext_pos = strvRFind(path, '.', 0);
|
||||||
#else
|
|
||||||
fatal("fileGetTime not implemented yet outside of linux and windows");
|
if (dir) {
|
||||||
return 0;
|
*dir = strvSub(path, 0, dir_pos);
|
||||||
#endif
|
}
|
||||||
}
|
if (name) {
|
||||||
|
*name = strvSub(path, dir_pos ? dir_pos + 1 : dir_pos, ext_pos);
|
||||||
#endif
|
}
|
||||||
|
if (ext) {
|
||||||
bool filePutc(file_t ctx, char c) {
|
*ext = strvSub(path, ext_pos, SIZE_MAX);
|
||||||
return fileWrite(ctx, &c, 1) == 1;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool filePuts(file_t ctx, strview_t v) {
|
bool filePutc(file_t ctx, char c) {
|
||||||
return fileWrite(ctx, v.buf, v.len) == v.len;
|
return fileWrite(ctx, &c, 1) == 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool filePrintf(arena_t scratch, file_t ctx, const char *fmt, ...) {
|
bool filePuts(file_t ctx, strview_t v) {
|
||||||
va_list args;
|
return fileWrite(ctx, v.buf, v.len) == v.len;
|
||||||
va_start(args, fmt);
|
}
|
||||||
bool result = filePrintfv(scratch, ctx, fmt, args);
|
|
||||||
va_end(args);
|
bool filePrintf(arena_t scratch, file_t ctx, const char *fmt, ...) {
|
||||||
return result;
|
va_list args;
|
||||||
}
|
va_start(args, fmt);
|
||||||
|
bool result = filePrintfv(scratch, ctx, fmt, args);
|
||||||
bool filePrintfv(arena_t scratch, file_t ctx, const char *fmt, va_list args) {
|
va_end(args);
|
||||||
str_t string = strFmtv(&scratch, fmt, args);
|
return result;
|
||||||
return fileWrite(ctx, string.buf, string.len) == string.len;
|
}
|
||||||
}
|
|
||||||
|
bool filePrintfv(arena_t scratch, file_t ctx, const char *fmt, va_list args) {
|
||||||
buffer_t fileReadWhole(arena_t *arena, strview_t name) {
|
str_t string = strFmtv(&scratch, fmt, args);
|
||||||
file_t fp = fileOpen(*arena, name, FILE_READ);
|
return fileWrite(ctx, string.buf, string.len) == string.len;
|
||||||
if (!fileIsValid(fp)) {
|
}
|
||||||
err("could not open file: %v", name);
|
|
||||||
return (buffer_t){0};
|
buffer_t fileReadWhole(arena_t *arena, strview_t name) {
|
||||||
}
|
file_t fp = fileOpen(*arena, name, FILE_READ);
|
||||||
buffer_t out = fileReadWholeFP(arena, fp);
|
if (!fileIsValid(fp)) {
|
||||||
fileClose(fp);
|
err("could not open file: %v", name);
|
||||||
return out;
|
return (buffer_t){0};
|
||||||
}
|
}
|
||||||
|
buffer_t out = fileReadWholeFP(arena, fp);
|
||||||
buffer_t fileReadWholeFP(arena_t *arena, file_t ctx) {
|
fileClose(fp);
|
||||||
if (!fileIsValid(ctx)) return (buffer_t){0};
|
return out;
|
||||||
buffer_t out = {0};
|
}
|
||||||
|
|
||||||
out.len = fileSize(ctx);
|
buffer_t fileReadWholeFP(arena_t *arena, file_t ctx) {
|
||||||
out.data = alloc(arena, uint8, out.len);
|
if (!fileIsValid(ctx)) return (buffer_t){0};
|
||||||
usize read = fileRead(ctx, out.data, out.len);
|
buffer_t out = {0};
|
||||||
|
|
||||||
if (read != out.len) {
|
out.len = fileSize(ctx);
|
||||||
err("fileReadWholeFP: fileRead failed, should be %zu but is %zu", out.len, read);
|
out.data = alloc(arena, uint8, out.len);
|
||||||
arenaPop(arena, out.len);
|
usize read = fileRead(ctx, out.data, out.len);
|
||||||
return (buffer_t){0};
|
|
||||||
}
|
if (read != out.len) {
|
||||||
|
err("fileReadWholeFP: fileRead failed, should be %zu but is %zu", out.len, read);
|
||||||
return out;
|
arenaPop(arena, out.len);
|
||||||
}
|
return (buffer_t){0};
|
||||||
|
}
|
||||||
str_t fileReadWholeStr(arena_t *arena, strview_t name) {
|
|
||||||
file_t fp = fileOpen(*arena, name, FILE_READ);
|
return out;
|
||||||
str_t out = fileReadWholeStrFP(arena, fp);
|
}
|
||||||
fileClose(fp);
|
|
||||||
return out;
|
str_t fileReadWholeStr(arena_t *arena, strview_t name) {
|
||||||
}
|
file_t fp = fileOpen(*arena, name, FILE_READ);
|
||||||
|
if (!fileIsValid(fp)) {
|
||||||
str_t fileReadWholeStrFP(arena_t *arena, file_t ctx) {
|
warn("could not open file (%v)", name);
|
||||||
if (!fileIsValid(ctx)) return (str_t){0};
|
}
|
||||||
|
str_t out = fileReadWholeStrFP(arena, fp);
|
||||||
str_t out = {0};
|
fileClose(fp);
|
||||||
|
return out;
|
||||||
out.len = fileSize(ctx);
|
}
|
||||||
out.buf = alloc(arena, uint8, out.len + 1);
|
|
||||||
usize read = fileRead(ctx, out.buf, out.len);
|
str_t fileReadWholeStrFP(arena_t *arena, file_t ctx) {
|
||||||
|
if (!fileIsValid(ctx)) return STR_EMPTY;
|
||||||
if (read != out.len) {
|
|
||||||
err("fileReadWholeStrFP: fileRead failed, should be %zu but is %zu", out.len, read);
|
str_t out = {0};
|
||||||
arenaPop(arena, out.len + 1);
|
|
||||||
return (str_t){0};
|
out.len = fileSize(ctx);
|
||||||
}
|
out.buf = alloc(arena, uint8, out.len + 1);
|
||||||
|
usize read = fileRead(ctx, out.buf, out.len);
|
||||||
return out;
|
|
||||||
}
|
if (read != out.len) {
|
||||||
|
err("fileReadWholeStrFP: fileRead failed, should be %zu but is %zu", out.len, read);
|
||||||
bool fileWriteWhole(arena_t scratch, strview_t name, const void *buf, usize len) {
|
arenaPop(arena, out.len + 1);
|
||||||
file_t fp = fileOpen(scratch, name, FILE_WRITE);
|
return STR_EMPTY;
|
||||||
if (!fileIsValid(fp)) {
|
}
|
||||||
return false;
|
|
||||||
}
|
return out;
|
||||||
usize written = fileWrite(fp, buf, len);
|
}
|
||||||
fileClose(fp);
|
|
||||||
return written == len;
|
bool fileWriteWhole(arena_t scratch, strview_t name, const void *buf, usize len) {
|
||||||
}
|
file_t fp = fileOpen(scratch, name, FILE_WRITE);
|
||||||
|
if (!fileIsValid(fp)) {
|
||||||
uint64 fileGetTime(arena_t scratch, strview_t name) {
|
return false;
|
||||||
file_t fp = fileOpen(scratch, name, FILE_READ);
|
}
|
||||||
uint64 result = fileGetTimeFP(fp);
|
usize written = fileWrite(fp, buf, len);
|
||||||
fileClose(fp);
|
fileClose(fp);
|
||||||
return result;
|
return written == len;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool fileHasChanged(arena_t scratch, strview_t name, uint64 last_timestamp) {
|
uint64 fileGetTime(arena_t scratch, strview_t name) {
|
||||||
uint64 timestamp = fileGetTime(scratch, name);
|
file_t fp = fileOpen(scratch, name, FILE_READ);
|
||||||
return timestamp > last_timestamp;
|
uint64 result = fileGetTimeFP(fp);
|
||||||
}
|
fileClose(fp);
|
||||||
|
return result;
|
||||||
#include "warnings/colla_warn_end.h"
|
}
|
||||||
|
|
||||||
|
bool fileHasChanged(arena_t scratch, strview_t name, uint64 last_timestamp) {
|
||||||
|
uint64 timestamp = fileGetTime(scratch, name);
|
||||||
|
return timestamp > last_timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
#include "warnings/colla_warn_end.h"
|
||||||
|
|
@ -1,55 +1,55 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
|
|
||||||
#include "collatypes.h"
|
#include "collatypes.h"
|
||||||
#include "str.h"
|
#include "str.h"
|
||||||
#include "arena.h"
|
#include "arena.h"
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
FILE_READ = 1 << 0,
|
FILE_READ = 1 << 0,
|
||||||
FILE_WRITE = 1 << 1,
|
FILE_WRITE = 1 << 1,
|
||||||
FILE_APPEND = 1 << 2,
|
FILE_APPEND = 1 << 2,
|
||||||
} filemode_e;
|
} filemode_e;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uintptr_t handle;
|
uintptr_t handle;
|
||||||
} file_t;
|
} file_t;
|
||||||
|
|
||||||
bool fileExists(const char *name);
|
bool fileExists(const char *name);
|
||||||
TCHAR *fileGetFullPath(arena_t *arena, strview_t filename);
|
TCHAR *fileGetFullPath(arena_t *arena, strview_t filename);
|
||||||
strview_t fileGetFilename(strview_t path);
|
strview_t fileGetFilename(strview_t path);
|
||||||
strview_t fileGetExtension(strview_t path);
|
strview_t fileGetExtension(strview_t path);
|
||||||
void fileSplitPath(strview_t path, strview_t *dir, strview_t *name, strview_t *ext);
|
void fileSplitPath(strview_t path, strview_t *dir, strview_t *name, strview_t *ext);
|
||||||
bool fileDelete(arena_t scratch, strview_t filename);
|
bool fileDelete(arena_t scratch, strview_t filename);
|
||||||
|
|
||||||
file_t fileOpen(arena_t scratch, strview_t name, filemode_e mode);
|
file_t fileOpen(arena_t scratch, strview_t name, filemode_e mode);
|
||||||
void fileClose(file_t ctx);
|
void fileClose(file_t ctx);
|
||||||
|
|
||||||
bool fileIsValid(file_t ctx);
|
bool fileIsValid(file_t ctx);
|
||||||
|
|
||||||
bool filePutc(file_t ctx, char c);
|
bool filePutc(file_t ctx, char c);
|
||||||
bool filePuts(file_t ctx, strview_t v);
|
bool filePuts(file_t ctx, strview_t v);
|
||||||
bool filePrintf(arena_t scratch, file_t ctx, const char *fmt, ...);
|
bool filePrintf(arena_t scratch, file_t ctx, const char *fmt, ...);
|
||||||
bool filePrintfv(arena_t scratch, file_t ctx, const char *fmt, va_list args);
|
bool filePrintfv(arena_t scratch, file_t ctx, const char *fmt, va_list args);
|
||||||
|
|
||||||
usize fileRead(file_t ctx, void *buf, usize len);
|
usize fileRead(file_t ctx, void *buf, usize len);
|
||||||
usize fileWrite(file_t ctx, const void *buf, usize len);
|
usize fileWrite(file_t ctx, const void *buf, usize len);
|
||||||
|
|
||||||
bool fileSeekEnd(file_t ctx);
|
bool fileSeekEnd(file_t ctx);
|
||||||
void fileRewind(file_t ctx);
|
void fileRewind(file_t ctx);
|
||||||
|
|
||||||
usize fileTell(file_t ctx);
|
usize fileTell(file_t ctx);
|
||||||
usize fileSize(file_t ctx);
|
usize fileSize(file_t ctx);
|
||||||
|
|
||||||
buffer_t fileReadWhole(arena_t *arena, strview_t name);
|
buffer_t fileReadWhole(arena_t *arena, strview_t name);
|
||||||
buffer_t fileReadWholeFP(arena_t *arena, file_t ctx);
|
buffer_t fileReadWholeFP(arena_t *arena, file_t ctx);
|
||||||
|
|
||||||
str_t fileReadWholeStr(arena_t *arena, strview_t name);
|
str_t fileReadWholeStr(arena_t *arena, strview_t name);
|
||||||
str_t fileReadWholeStrFP(arena_t *arena, file_t ctx);
|
str_t fileReadWholeStrFP(arena_t *arena, file_t ctx);
|
||||||
|
|
||||||
bool fileWriteWhole(arena_t scratch, strview_t name, const void *buf, usize len);
|
bool fileWriteWhole(arena_t scratch, strview_t name, const void *buf, usize len);
|
||||||
|
|
||||||
uint64 fileGetTime(arena_t scratch, strview_t name);
|
uint64 fileGetTime(arena_t scratch, strview_t name);
|
||||||
uint64 fileGetTimeFP(file_t ctx);
|
uint64 fileGetTimeFP(file_t ctx);
|
||||||
bool fileHasChanged(arena_t scratch, strview_t name, uint64 last_timestamp);
|
bool fileHasChanged(arena_t scratch, strview_t name, uint64 last_timestamp);
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
|
|
||||||
static char *fmt__stb_callback(const char *buf, void *ud, int len) {
|
static char *fmt__stb_callback(const char *buf, void *ud, int len) {
|
||||||
(void)len;
|
(void)len;
|
||||||
printf("%s", buf);
|
printf("%.*s", len, buf);
|
||||||
return (char *)ud;
|
return (char *)ud;
|
||||||
}
|
}
|
||||||
|
|
||||||
621
highlight.c
Normal file
621
highlight.c
Normal 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("&"));
|
||||||
|
break;
|
||||||
|
case '<':
|
||||||
|
ostrPuts(out, strv("<"));
|
||||||
|
break;
|
||||||
|
case '>':
|
||||||
|
ostrPuts(out, strv(">"));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ostrPutc(out, c);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void hl_write_char(hl_ctx_t *ctx, char c) {
|
||||||
|
if (ctx->flags & HL_FLAG_HTML) {
|
||||||
|
hl_escape_html(&ctx->ostr, c);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ostrPutc(&ctx->ostr, c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void hl_write(hl_ctx_t *ctx, strview_t v) {
|
||||||
|
if (ctx->flags & HL_FLAG_HTML) {
|
||||||
|
for (usize i = 0; i < v.len; ++i) {
|
||||||
|
hl_escape_html(&ctx->ostr, v.buf[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ostrPuts(&ctx->ostr, v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool hl_is_char_keyword(char c) {
|
||||||
|
return isalpha(c) || isdigit(c) || c == '_';
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool hl_highlight_symbol(hl_ctx_t *ctx, char c) {
|
||||||
|
if (!ctx->symbol_table[(unsigned char)c]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ostrPuts(&ctx->ostr, ctx->colors[HL_COLOR_SYMBOL]);
|
||||||
|
hl_write_char(ctx, c);
|
||||||
|
ostrPuts(&ctx->ostr, ctx->colors[HL_COLOR_NORMAL]);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static hl_color_e hl_get_keyword_color(hl_ctx_t *ctx, strview_t keyword) {
|
||||||
|
// todo: make this an option?
|
||||||
|
if (strvEndsWithView(keyword, strv("_t"))) {
|
||||||
|
return HL_COLOR_CUSTOM_TYPES;
|
||||||
|
}
|
||||||
|
|
||||||
|
hl_node_t *node = hl_htable_get(&ctx->kw_htable, keyword);
|
||||||
|
return node ? node->value : HL_COLOR__COUNT;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool hl_is_capitalised(strview_t string) {
|
||||||
|
for (usize i = 0; i < string.len; ++i) {
|
||||||
|
char c = string.buf[i];
|
||||||
|
if (!isdigit(c) && c != '_' && (c < 'A' || c > 'Z')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static strview_t hl_finish_keyword(hl_ctx_t *ctx, usize beg, instream_t *in) {
|
||||||
|
ctx->state = HL_STATE_DEFAULT;
|
||||||
|
beg -= 1;
|
||||||
|
usize end = istrTell(*in) - 1;
|
||||||
|
|
||||||
|
return strv(in->start + beg, end - beg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void hl_print_keyword(hl_ctx_t *ctx, strview_t keyword, hl_color_e color) {
|
||||||
|
ostrPuts(&ctx->ostr, ctx->colors[color]);
|
||||||
|
hl_write(ctx, keyword);
|
||||||
|
ostrPuts(&ctx->ostr, ctx->colors[HL_COLOR_NORMAL]);
|
||||||
|
}
|
||||||
49
highlight.h
Normal file
49
highlight.h
Normal 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);
|
||||||
|
|
@ -6,7 +6,6 @@
|
||||||
|
|
||||||
// todo linux support?
|
// todo linux support?
|
||||||
#if COLLA_WIN
|
#if COLLA_WIN
|
||||||
#define WIN32_LEAN_AND_MEAN
|
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#else
|
#else
|
||||||
// patch stuff up for cross platform for now, the actual program should not really call anything for now
|
// patch stuff up for cross platform for now, the actual program should not really call anything for now
|
||||||
|
|
@ -25,6 +24,9 @@ typedef struct {
|
||||||
hr_f hr_close;
|
hr_f hr_close;
|
||||||
} hr_internal_t;
|
} hr_internal_t;
|
||||||
|
|
||||||
|
static bool hr__os_reload(hr_internal_t *hr);
|
||||||
|
static void hr__os_free(hr_internal_t *hr);
|
||||||
|
|
||||||
static bool hr__file_copy(arena_t scratch, strview_t src, strview_t dst) {
|
static bool hr__file_copy(arena_t scratch, strview_t src, strview_t dst) {
|
||||||
buffer_t srcbuf = fileReadWhole(&scratch, src);
|
buffer_t srcbuf = fileReadWhole(&scratch, src);
|
||||||
if (srcbuf.data == NULL || srcbuf.len == 0) {
|
if (srcbuf.data == NULL || srcbuf.len == 0) {
|
||||||
|
|
@ -70,6 +72,93 @@ static bool hr__reload(hr_t *ctx) {
|
||||||
|
|
||||||
info("loading library: %v", dll);
|
info("loading library: %v", dll);
|
||||||
|
|
||||||
|
return hr__os_reload(hr);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hrOpen(hr_t *ctx, strview_t path) {
|
||||||
|
#ifdef HR_DISABLE
|
||||||
|
cr_init(ctx);
|
||||||
|
return true;
|
||||||
|
#endif
|
||||||
|
arena_t arena = arenaMake(ARENA_VIRTUAL, MB(1));
|
||||||
|
|
||||||
|
str_t path_copy = str(&arena, path);
|
||||||
|
|
||||||
|
if (!fileExists(path_copy.buf)) {
|
||||||
|
err("dll file: %v does not exist", path);
|
||||||
|
arenaCleanup(&arena);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
hr_internal_t *hr = alloc(&arena, hr_internal_t);
|
||||||
|
hr->arena = arena;
|
||||||
|
hr->path = path_copy;
|
||||||
|
|
||||||
|
ctx->p = hr;
|
||||||
|
ctx->last_working_version = 0;
|
||||||
|
|
||||||
|
return hr__reload(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
void hrClose(hr_t *ctx, bool clean_temp_files) {
|
||||||
|
#ifdef HR_DISABLE
|
||||||
|
hr_close(ctx);
|
||||||
|
return;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
hr_internal_t *hr = ctx->p;
|
||||||
|
if (hr->hr_close) {
|
||||||
|
hr->hr_close(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
hr__os_free(hr);
|
||||||
|
|
||||||
|
hr->handle = NULL;
|
||||||
|
hr->hr_init = hr->hr_loop = hr->hr_close = NULL;
|
||||||
|
|
||||||
|
if (clean_temp_files) {
|
||||||
|
arena_t scratch = hr->arena;
|
||||||
|
|
||||||
|
strview_t dir, name, ext;
|
||||||
|
fileSplitPath(strv(hr->path), &dir, &name, &ext);
|
||||||
|
|
||||||
|
for (int i = 0; i < ctx->last_working_version; ++i) {
|
||||||
|
str_t fname = strFmt(&scratch, "%v/%v-%d%v", dir, name, i + 1, ext);
|
||||||
|
if (!fileDelete(scratch, strv(fname))) {
|
||||||
|
err("couldn't delete %v", fname);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
arena_t arena = hr->arena;
|
||||||
|
arenaCleanup(&arena);
|
||||||
|
|
||||||
|
ctx->p = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int hrStep(hr_t *ctx) {
|
||||||
|
#ifdef CR_DISABLE
|
||||||
|
hr_loop(ctx);
|
||||||
|
return 0;
|
||||||
|
#endif
|
||||||
|
hr_internal_t *hr = ctx->p;
|
||||||
|
|
||||||
|
int result = -1;
|
||||||
|
if (hr->hr_loop) {
|
||||||
|
result = hr->hr_loop(ctx);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hrReload(hr_t *ctx) {
|
||||||
|
return hr__reload(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
//// OS SPECIFIC ////////////////////////////////////////
|
||||||
|
|
||||||
|
#if COLLA_WIN
|
||||||
|
|
||||||
|
static bool hr__os_reload(hr_internal_t *hr) {
|
||||||
if (hr->handle) {
|
if (hr->handle) {
|
||||||
FreeLibrary(hr->handle);
|
FreeLibrary(hr->handle);
|
||||||
}
|
}
|
||||||
|
|
@ -117,84 +206,21 @@ error:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool hrOpen(hr_t *ctx, strview_t path) {
|
static void hr__os_free(hr_internal_t *hr) {
|
||||||
#ifdef HR_DISABLE
|
|
||||||
cr_init(ctx);
|
|
||||||
return true;
|
|
||||||
#endif
|
|
||||||
arena_t arena = arenaMake(ARENA_VIRTUAL, MB(1));
|
|
||||||
|
|
||||||
str_t path_copy = str(&arena, path);
|
|
||||||
|
|
||||||
if (!fileExists(path_copy.buf)) {
|
|
||||||
err("dll file: %v does not exist", path);
|
|
||||||
arenaCleanup(&arena);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
hr_internal_t *hr = alloc(&arena, hr_internal_t);
|
|
||||||
hr->arena = arena;
|
|
||||||
hr->path = path_copy;
|
|
||||||
|
|
||||||
ctx->p = hr;
|
|
||||||
ctx->last_working_version = 0;
|
|
||||||
|
|
||||||
return hr__reload(ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
void hrClose(hr_t *ctx, bool clean_temp_files) {
|
|
||||||
#ifdef HR_DISABLE
|
|
||||||
hr_close(ctx);
|
|
||||||
return;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
hr_internal_t *hr = ctx->p;
|
|
||||||
if (hr->hr_close) {
|
|
||||||
hr->hr_close(ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hr->handle) {
|
if (hr->handle) {
|
||||||
FreeLibrary(hr->handle);
|
FreeLibrary(hr->handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
hr->handle = NULL;
|
|
||||||
hr->hr_init = hr->hr_loop = hr->hr_close = NULL;
|
|
||||||
|
|
||||||
if (clean_temp_files) {
|
|
||||||
arena_t scratch = hr->arena;
|
|
||||||
|
|
||||||
strview_t dir, name, ext;
|
|
||||||
fileSplitPath(strv(hr->path), &dir, &name, &ext);
|
|
||||||
|
|
||||||
for (int i = 0; i < ctx->last_working_version; ++i) {
|
|
||||||
str_t fname = strFmt(&scratch, "%v/%v-%d%v", dir, name, i + 1, ext);
|
|
||||||
if (!fileDelete(scratch, strv(fname))) {
|
|
||||||
err("couldn't delete %v: %d", fname, GetLastError());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
arena_t arena = hr->arena;
|
|
||||||
arenaCleanup(&arena);
|
|
||||||
|
|
||||||
ctx->p = NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int hrStep(hr_t *ctx) {
|
#elif COLLA_LIN
|
||||||
#ifdef CR_DISABLE
|
|
||||||
hr_loop(ctx);
|
|
||||||
return 0;
|
|
||||||
#endif
|
|
||||||
hr_internal_t *hr = ctx->p;
|
|
||||||
|
|
||||||
int result = -1;
|
static bool hr__os_reload(hr_internal_t *hr) {
|
||||||
if (hr->hr_loop) {
|
fatal("todo: linux hot reload not implemented yet");
|
||||||
result = hr->hr_loop(ctx);
|
return true;
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool hrReload(hr_t *ctx) {
|
static void hr__os_free(hr_internal_t *hr) {
|
||||||
return hr__reload(ctx);
|
fatal("todo: linux hot reload not implemented yet");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
77
html.h
Normal file
77
html.h
Normal 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
|
|
@ -1,83 +1,83 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "collatypes.h"
|
#include "collatypes.h"
|
||||||
#include "str.h"
|
#include "str.h"
|
||||||
|
|
||||||
typedef struct arena_t arena_t;
|
typedef struct arena_t arena_t;
|
||||||
typedef uintptr_t socket_t;
|
typedef uintptr_t socket_t;
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
HTTP_GET,
|
HTTP_GET,
|
||||||
HTTP_POST,
|
HTTP_POST,
|
||||||
HTTP_HEAD,
|
HTTP_HEAD,
|
||||||
HTTP_PUT,
|
HTTP_PUT,
|
||||||
HTTP_DELETE
|
HTTP_DELETE
|
||||||
} http_method_e;
|
} http_method_e;
|
||||||
|
|
||||||
const char *httpGetStatusString(int status);
|
const char *httpGetStatusString(int status);
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint8 major;
|
uint8 major;
|
||||||
uint8 minor;
|
uint8 minor;
|
||||||
} http_version_t;
|
} http_version_t;
|
||||||
|
|
||||||
// translates a http_version_t to a single readable number (e.g. 1.1 -> 11, 1.0 -> 10, etc)
|
// translates a http_version_t to a single readable number (e.g. 1.1 -> 11, 1.0 -> 10, etc)
|
||||||
int httpVerNumber(http_version_t ver);
|
int httpVerNumber(http_version_t ver);
|
||||||
|
|
||||||
typedef struct http_header_t {
|
typedef struct http_header_t {
|
||||||
strview_t key;
|
strview_t key;
|
||||||
strview_t value;
|
strview_t value;
|
||||||
struct http_header_t *next;
|
struct http_header_t *next;
|
||||||
} http_header_t;
|
} http_header_t;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
http_method_e method;
|
http_method_e method;
|
||||||
http_version_t version;
|
http_version_t version;
|
||||||
http_header_t *headers;
|
http_header_t *headers;
|
||||||
strview_t url;
|
strview_t url;
|
||||||
strview_t body;
|
strview_t body;
|
||||||
} http_req_t;
|
} http_req_t;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
int status_code;
|
int status_code;
|
||||||
http_version_t version;
|
http_version_t version;
|
||||||
http_header_t *headers;
|
http_header_t *headers;
|
||||||
strview_t body;
|
strview_t body;
|
||||||
} http_res_t;
|
} http_res_t;
|
||||||
|
|
||||||
// strview_t request needs to be valid for http_req_t to be valid!
|
// strview_t request needs to be valid for http_req_t to be valid!
|
||||||
http_req_t httpParseReq(arena_t *arena, strview_t request);
|
http_req_t httpParseReq(arena_t *arena, strview_t request);
|
||||||
http_res_t httpParseRes(arena_t *arena, strview_t response);
|
http_res_t httpParseRes(arena_t *arena, strview_t response);
|
||||||
|
|
||||||
str_t httpReqToStr(arena_t *arena, http_req_t *req);
|
str_t httpReqToStr(arena_t *arena, http_req_t *req);
|
||||||
str_t httpResToStr(arena_t *arena, http_res_t *res);
|
str_t httpResToStr(arena_t *arena, http_res_t *res);
|
||||||
|
|
||||||
bool httpHasHeader(http_header_t *headers, strview_t key);
|
bool httpHasHeader(http_header_t *headers, strview_t key);
|
||||||
void httpSetHeader(http_header_t *headers, strview_t key, strview_t value);
|
void httpSetHeader(http_header_t *headers, strview_t key, strview_t value);
|
||||||
strview_t httpGetHeader(http_header_t *headers, strview_t key);
|
strview_t httpGetHeader(http_header_t *headers, strview_t key);
|
||||||
|
|
||||||
str_t httpMakeUrlSafe(arena_t *arena, strview_t string);
|
str_t httpMakeUrlSafe(arena_t *arena, strview_t string);
|
||||||
str_t httpDecodeUrlSafe(arena_t *arena, strview_t string);
|
str_t httpDecodeUrlSafe(arena_t *arena, strview_t string);
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
strview_t host;
|
strview_t host;
|
||||||
strview_t uri;
|
strview_t uri;
|
||||||
} http_url_t;
|
} http_url_t;
|
||||||
|
|
||||||
http_url_t httpSplitUrl(strview_t url);
|
http_url_t httpSplitUrl(strview_t url);
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
arena_t *arena;
|
arena_t *arena;
|
||||||
strview_t url;
|
strview_t url;
|
||||||
http_method_e request_type;
|
http_method_e request_type;
|
||||||
http_header_t *headers;
|
http_header_t *headers;
|
||||||
int header_count;
|
int header_count;
|
||||||
strview_t body;
|
strview_t body;
|
||||||
} http_request_desc_t;
|
} http_request_desc_t;
|
||||||
|
|
||||||
// arena_t *arena, strview_t url, [ http_header_t *headers, int header_count, strview_t body ]
|
// arena_t *arena, strview_t url, [ http_header_t *headers, int header_count, strview_t body ]
|
||||||
#define httpGet(arena, url, ...) httpRequest(&(http_request_desc_t){ arena, url, .request_type = HTTP_GET, __VA_ARGS__ })
|
#define httpGet(arena, url, ...) httpRequest(&(http_request_desc_t){ arena, url, .request_type = HTTP_GET, __VA_ARGS__ })
|
||||||
#define httpsGet(arena, url, ...) httpsRequest(&(http_request_desc_t){ arena, url, .request_type = HTTP_GET, __VA_ARGS__ })
|
#define httpsGet(arena, url, ...) httpsRequest(&(http_request_desc_t){ arena, url, .request_type = HTTP_GET, __VA_ARGS__ })
|
||||||
|
|
||||||
http_res_t httpRequest(http_request_desc_t *request);
|
http_res_t httpRequest(http_request_desc_t *request);
|
||||||
buffer_t httpsRequest(http_request_desc_t *request);
|
buffer_t httpsRequest(http_request_desc_t *request);
|
||||||
|
|
@ -1,279 +1,279 @@
|
||||||
#include "ini.h"
|
#include "ini.h"
|
||||||
|
|
||||||
#include "warnings/colla_warn_beg.h"
|
#include "warnings/colla_warn_beg.h"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
||||||
#include "strstream.h"
|
#include "strstream.h"
|
||||||
|
|
||||||
static void ini__parse(arena_t *arena, ini_t *ini, const iniopts_t *options);
|
static void ini__parse(arena_t *arena, ini_t *ini, const iniopts_t *options);
|
||||||
|
|
||||||
ini_t iniParse(arena_t *arena, strview_t filename, const iniopts_t *options) {
|
ini_t iniParse(arena_t *arena, strview_t filename, const iniopts_t *options) {
|
||||||
file_t fp = fileOpen(*arena, filename, FILE_READ);
|
file_t fp = fileOpen(*arena, filename, FILE_READ);
|
||||||
ini_t out = iniParseFile(arena, fp, options);
|
ini_t out = iniParseFile(arena, fp, options);
|
||||||
fileClose(fp);
|
fileClose(fp);
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
ini_t iniParseFile(arena_t *arena, file_t file, const iniopts_t *options) {
|
ini_t iniParseFile(arena_t *arena, file_t file, const iniopts_t *options) {
|
||||||
str_t data = fileReadWholeStrFP(arena, file);
|
str_t data = fileReadWholeStrFP(arena, file);
|
||||||
return iniParseStr(arena, strv(data), options);
|
return iniParseStr(arena, strv(data), options);
|
||||||
}
|
}
|
||||||
|
|
||||||
ini_t iniParseStr(arena_t *arena, strview_t str, const iniopts_t *options) {
|
ini_t iniParseStr(arena_t *arena, strview_t str, const iniopts_t *options) {
|
||||||
ini_t out = {
|
ini_t out = {
|
||||||
.text = str,
|
.text = str,
|
||||||
.tables = NULL,
|
.tables = NULL,
|
||||||
};
|
};
|
||||||
ini__parse(arena, &out, options);
|
ini__parse(arena, &out, options);
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool iniIsValid(ini_t *ctx) {
|
bool iniIsValid(ini_t *ctx) {
|
||||||
return ctx && !strvIsEmpty(ctx->text);
|
return ctx && !strvIsEmpty(ctx->text);
|
||||||
}
|
}
|
||||||
|
|
||||||
initable_t *iniGetTable(ini_t *ctx, strview_t name) {
|
initable_t *iniGetTable(ini_t *ctx, strview_t name) {
|
||||||
initable_t *t = ctx ? ctx->tables : NULL;
|
initable_t *t = ctx ? ctx->tables : NULL;
|
||||||
while (t) {
|
while (t) {
|
||||||
if (strvEquals(t->name, name)) {
|
if (strvEquals(t->name, name)) {
|
||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
t = t->next;
|
t = t->next;
|
||||||
}
|
}
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
inivalue_t *iniGet(initable_t *ctx, strview_t key) {
|
inivalue_t *iniGet(initable_t *ctx, strview_t key) {
|
||||||
inivalue_t *v = ctx ? ctx->values : NULL;
|
inivalue_t *v = ctx ? ctx->values : NULL;
|
||||||
while (v) {
|
while (v) {
|
||||||
if (strvEquals(v->key, key)) {
|
if (strvEquals(v->key, key)) {
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
v = v->next;
|
v = v->next;
|
||||||
}
|
}
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
iniarray_t iniAsArr(arena_t *arena, inivalue_t *value, char delim) {
|
iniarray_t iniAsArr(arena_t *arena, inivalue_t *value, char delim) {
|
||||||
strview_t v = value ? value->value : (strview_t){0};
|
strview_t v = value ? value->value : STRV_EMPTY;
|
||||||
if (!delim) delim = ' ';
|
if (!delim) delim = ' ';
|
||||||
|
|
||||||
strview_t *beg = (strview_t *)arena->current;
|
strview_t *beg = (strview_t *)arena->current;
|
||||||
usize count = 0;
|
usize count = 0;
|
||||||
|
|
||||||
usize start = 0;
|
usize start = 0;
|
||||||
for (usize i = 0; i < v.len; ++i) {
|
for (usize i = 0; i < v.len; ++i) {
|
||||||
if (v.buf[i] == delim) {
|
if (v.buf[i] == delim) {
|
||||||
strview_t arrval = strvTrim(strvSub(v, start, i));
|
strview_t arrval = strvTrim(strvSub(v, start, i));
|
||||||
if (!strvIsEmpty(arrval)) {
|
if (!strvIsEmpty(arrval)) {
|
||||||
strview_t *newval = alloc(arena, strview_t);
|
strview_t *newval = alloc(arena, strview_t);
|
||||||
*newval = arrval;
|
*newval = arrval;
|
||||||
++count;
|
++count;
|
||||||
}
|
}
|
||||||
start = i + 1;
|
start = i + 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
strview_t last = strvTrim(strvSub(v, start, SIZE_MAX));
|
strview_t last = strvTrim(strvSub(v, start, SIZE_MAX));
|
||||||
if (!strvIsEmpty(last)) {
|
if (!strvIsEmpty(last)) {
|
||||||
strview_t *newval = alloc(arena, strview_t);
|
strview_t *newval = alloc(arena, strview_t);
|
||||||
*newval = last;
|
*newval = last;
|
||||||
++count;
|
++count;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (iniarray_t){
|
return (iniarray_t){
|
||||||
.values = beg,
|
.values = beg,
|
||||||
.count = count,
|
.count = count,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64 iniAsUInt(inivalue_t *value) {
|
uint64 iniAsUInt(inivalue_t *value) {
|
||||||
strview_t v = value ? value->value : (strview_t){0};
|
strview_t v = value ? value->value : STRV_EMPTY;
|
||||||
instream_t in = istrInitLen(v.buf, v.len);
|
instream_t in = istrInitLen(v.buf, v.len);
|
||||||
uint64 out = 0;
|
uint64 out = 0;
|
||||||
if (!istrGetU64(&in, &out)) {
|
if (!istrGetU64(&in, &out)) {
|
||||||
out = 0;
|
out = 0;
|
||||||
}
|
}
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
int64 iniAsInt(inivalue_t *value) {
|
int64 iniAsInt(inivalue_t *value) {
|
||||||
strview_t v = value ? value->value : (strview_t){0};
|
strview_t v = value ? value->value : STRV_EMPTY;
|
||||||
instream_t in = istrInitLen(v.buf, v.len);
|
instream_t in = istrInitLen(v.buf, v.len);
|
||||||
int64 out = 0;
|
int64 out = 0;
|
||||||
if (!istrGetI64(&in, &out)) {
|
if (!istrGetI64(&in, &out)) {
|
||||||
out = 0;
|
out = 0;
|
||||||
}
|
}
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
double iniAsNum(inivalue_t *value) {
|
double iniAsNum(inivalue_t *value) {
|
||||||
strview_t v = value ? value->value : (strview_t){0};
|
strview_t v = value ? value->value : STRV_EMPTY;
|
||||||
instream_t in = istrInitLen(v.buf, v.len);
|
instream_t in = istrInitLen(v.buf, v.len);
|
||||||
double out = 0;
|
double out = 0;
|
||||||
if (!istrGetDouble(&in, &out)) {
|
if (!istrGetDouble(&in, &out)) {
|
||||||
out = 0.0;
|
out = 0.0;
|
||||||
}
|
}
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool iniAsBool(inivalue_t *value) {
|
bool iniAsBool(inivalue_t *value) {
|
||||||
strview_t v = value ? value->value : (strview_t){0};
|
strview_t v = value ? value->value : STRV_EMPTY;
|
||||||
instream_t in = istrInitLen(v.buf, v.len);
|
instream_t in = istrInitLen(v.buf, v.len);
|
||||||
bool out = 0;
|
bool out = 0;
|
||||||
if (!istrGetBool(&in, &out)) {
|
if (!istrGetBool(&in, &out)) {
|
||||||
out = false;
|
out = false;
|
||||||
}
|
}
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
// == PRIVATE FUNCTIONS ==============================================================================
|
// == PRIVATE FUNCTIONS ==============================================================================
|
||||||
|
|
||||||
#define INIPUSH(head, tail, val) \
|
#define INIPUSH(head, tail, val) \
|
||||||
do { \
|
do { \
|
||||||
if (!head) { \
|
if (!head) { \
|
||||||
head = val; \
|
head = val; \
|
||||||
tail = val; \
|
tail = val; \
|
||||||
} \
|
} \
|
||||||
else { \
|
else { \
|
||||||
tail->next = val; \
|
tail->next = val; \
|
||||||
val = tail; \
|
val = tail; \
|
||||||
} \
|
} \
|
||||||
} while (0)
|
} while (0)
|
||||||
|
|
||||||
static iniopts_t ini__get_options(const iniopts_t *options) {
|
static iniopts_t ini__get_options(const iniopts_t *options) {
|
||||||
iniopts_t out = {
|
iniopts_t out = {
|
||||||
.key_value_divider = '=',
|
.key_value_divider = '=',
|
||||||
};
|
};
|
||||||
|
|
||||||
#define SETOPT(v) out.v = options->v ? options->v : out.v
|
#define SETOPT(v) out.v = options->v ? options->v : out.v
|
||||||
|
|
||||||
if (options) {
|
if (options) {
|
||||||
SETOPT(key_value_divider);
|
SETOPT(key_value_divider);
|
||||||
SETOPT(merge_duplicate_keys);
|
SETOPT(merge_duplicate_keys);
|
||||||
SETOPT(merge_duplicate_tables);
|
SETOPT(merge_duplicate_tables);
|
||||||
}
|
}
|
||||||
|
|
||||||
#undef SETOPT
|
#undef SETOPT
|
||||||
|
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void ini__add_value(arena_t *arena, initable_t *table, instream_t *in, iniopts_t *opts) {
|
static void ini__add_value(arena_t *arena, initable_t *table, instream_t *in, iniopts_t *opts) {
|
||||||
assert(table);
|
assert(table);
|
||||||
|
|
||||||
strview_t key = strvTrim(istrGetView(in, opts->key_value_divider));
|
strview_t key = strvTrim(istrGetView(in, opts->key_value_divider));
|
||||||
istrSkip(in, 1);
|
istrSkip(in, 1);
|
||||||
strview_t value = strvTrim(istrGetViewEither(in, strv("\n#;")));
|
strview_t value = strvTrim(istrGetViewEither(in, strv("\n#;")));
|
||||||
istrSkip(in, 1);
|
istrSkip(in, 1);
|
||||||
inivalue_t *newval = NULL;
|
inivalue_t *newval = NULL;
|
||||||
|
|
||||||
|
|
||||||
if (opts->merge_duplicate_keys) {
|
if (opts->merge_duplicate_keys) {
|
||||||
newval = table->values;
|
newval = table->values;
|
||||||
while (newval) {
|
while (newval) {
|
||||||
if (strvEquals(newval->key, key)) {
|
if (strvEquals(newval->key, key)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
newval = newval->next;
|
newval = newval->next;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newval) {
|
if (newval) {
|
||||||
newval->value = value;
|
newval->value = value;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
newval = alloc(arena, inivalue_t);
|
newval = alloc(arena, inivalue_t);
|
||||||
newval->key = key;
|
newval->key = key;
|
||||||
newval->value = value;
|
newval->value = value;
|
||||||
|
|
||||||
if (!table->values) {
|
if (!table->values) {
|
||||||
table->values = newval;
|
table->values = newval;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
table->tail->next = newval;
|
table->tail->next = newval;
|
||||||
}
|
}
|
||||||
|
|
||||||
table->tail = newval;
|
table->tail = newval;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void ini__add_table(arena_t *arena, ini_t *ctx, instream_t *in, iniopts_t *options) {
|
static void ini__add_table(arena_t *arena, ini_t *ctx, instream_t *in, iniopts_t *options) {
|
||||||
istrSkip(in, 1); // skip [
|
istrSkip(in, 1); // skip [
|
||||||
strview_t name = istrGetView(in, ']');
|
strview_t name = istrGetView(in, ']');
|
||||||
istrSkip(in, 1); // skip ]
|
istrSkip(in, 1); // skip ]
|
||||||
initable_t *table = NULL;
|
initable_t *table = NULL;
|
||||||
|
|
||||||
if (options->merge_duplicate_tables) {
|
if (options->merge_duplicate_tables) {
|
||||||
table = ctx->tables;
|
table = ctx->tables;
|
||||||
while (table) {
|
while (table) {
|
||||||
if (strvEquals(table->name, name)) {
|
if (strvEquals(table->name, name)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
table = table->next;
|
table = table->next;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!table) {
|
if (!table) {
|
||||||
table = alloc(arena, initable_t);
|
table = alloc(arena, initable_t);
|
||||||
|
|
||||||
table->name = name;
|
table->name = name;
|
||||||
|
|
||||||
if (!ctx->tables) {
|
if (!ctx->tables) {
|
||||||
ctx->tables = table;
|
ctx->tables = table;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
ctx->tail->next = table;
|
ctx->tail->next = table;
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx->tail = table;
|
ctx->tail = table;
|
||||||
}
|
}
|
||||||
|
|
||||||
istrIgnoreAndSkip(in, '\n');
|
istrIgnoreAndSkip(in, '\n');
|
||||||
while (!istrIsFinished(*in)) {
|
while (!istrIsFinished(*in)) {
|
||||||
switch (istrPeek(in)) {
|
switch (istrPeek(in)) {
|
||||||
case '\n': // fallthrough
|
case '\n': // fallthrough
|
||||||
case '\r':
|
case '\r':
|
||||||
return;
|
return;
|
||||||
case '#': // fallthrough
|
case '#': // fallthrough
|
||||||
case ';':
|
case ';':
|
||||||
istrIgnoreAndSkip(in, '\n');
|
istrIgnoreAndSkip(in, '\n');
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
ini__add_value(arena, table, in, options);
|
ini__add_value(arena, table, in, options);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void ini__parse(arena_t *arena, ini_t *ini, const iniopts_t *options) {
|
static void ini__parse(arena_t *arena, ini_t *ini, const iniopts_t *options) {
|
||||||
iniopts_t opts = ini__get_options(options);
|
iniopts_t opts = ini__get_options(options);
|
||||||
|
|
||||||
initable_t *root = alloc(arena, initable_t);
|
initable_t *root = alloc(arena, initable_t);
|
||||||
root->name = INI_ROOT;
|
root->name = INI_ROOT;
|
||||||
ini->tables = root;
|
ini->tables = root;
|
||||||
ini->tail = root;
|
ini->tail = root;
|
||||||
|
|
||||||
instream_t in = istrInitLen(ini->text.buf, ini->text.len);
|
instream_t in = istrInitLen(ini->text.buf, ini->text.len);
|
||||||
|
|
||||||
while (!istrIsFinished(in)) {
|
while (!istrIsFinished(in)) {
|
||||||
istrSkipWhitespace(&in);
|
istrSkipWhitespace(&in);
|
||||||
switch (istrPeek(&in)) {
|
switch (istrPeek(&in)) {
|
||||||
case '[':
|
case '[':
|
||||||
ini__add_table(arena, ini, &in, &opts);
|
ini__add_table(arena, ini, &in, &opts);
|
||||||
break;
|
break;
|
||||||
case '#': // fallthrough
|
case '#': // fallthrough
|
||||||
case ';':
|
case ';':
|
||||||
istrIgnoreAndSkip(&in, '\n');
|
istrIgnoreAndSkip(&in, '\n');
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
ini__add_value(arena, ini->tables, &in, &opts);
|
ini__add_value(arena, ini->tables, &in, &opts);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#undef INIPUSH
|
#undef INIPUSH
|
||||||
|
|
||||||
#include "warnings/colla_warn_end.h"
|
#include "warnings/colla_warn_end.h"
|
||||||
|
|
@ -1,54 +1,54 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "collatypes.h"
|
#include "collatypes.h"
|
||||||
#include "str.h"
|
#include "str.h"
|
||||||
#include "file.h"
|
#include "file.h"
|
||||||
|
|
||||||
typedef struct arena_t arena_t;
|
typedef struct arena_t arena_t;
|
||||||
|
|
||||||
typedef struct inivalue_t {
|
typedef struct inivalue_t {
|
||||||
strview_t key;
|
strview_t key;
|
||||||
strview_t value;
|
strview_t value;
|
||||||
struct inivalue_t *next;
|
struct inivalue_t *next;
|
||||||
} inivalue_t;
|
} inivalue_t;
|
||||||
|
|
||||||
typedef struct initable_t {
|
typedef struct initable_t {
|
||||||
strview_t name;
|
strview_t name;
|
||||||
inivalue_t *values;
|
inivalue_t *values;
|
||||||
inivalue_t *tail;
|
inivalue_t *tail;
|
||||||
struct initable_t *next;
|
struct initable_t *next;
|
||||||
} initable_t;
|
} initable_t;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct ini_t {
|
||||||
strview_t text;
|
strview_t text;
|
||||||
initable_t *tables;
|
initable_t *tables;
|
||||||
initable_t *tail;
|
initable_t *tail;
|
||||||
} ini_t;
|
} ini_t;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
bool merge_duplicate_tables; // default false
|
bool merge_duplicate_tables; // default false
|
||||||
bool merge_duplicate_keys; // default false
|
bool merge_duplicate_keys; // default false
|
||||||
char key_value_divider; // default =
|
char key_value_divider; // default =
|
||||||
} iniopts_t;
|
} iniopts_t;
|
||||||
|
|
||||||
ini_t iniParse(arena_t *arena, strview_t filename, const iniopts_t *options);
|
ini_t iniParse(arena_t *arena, strview_t filename, const iniopts_t *options);
|
||||||
ini_t iniParseFile(arena_t *arena, file_t file, const iniopts_t *options);
|
ini_t iniParseFile(arena_t *arena, file_t file, const iniopts_t *options);
|
||||||
ini_t iniParseStr(arena_t *arena, strview_t str, const iniopts_t *options);
|
ini_t iniParseStr(arena_t *arena, strview_t str, const iniopts_t *options);
|
||||||
|
|
||||||
bool iniIsValid(ini_t *ctx);
|
bool iniIsValid(ini_t *ctx);
|
||||||
|
|
||||||
#define INI_ROOT strv("__ROOT__")
|
#define INI_ROOT strv("__ROOT__")
|
||||||
|
|
||||||
initable_t *iniGetTable(ini_t *ctx, strview_t name);
|
initable_t *iniGetTable(ini_t *ctx, strview_t name);
|
||||||
inivalue_t *iniGet(initable_t *ctx, strview_t key);
|
inivalue_t *iniGet(initable_t *ctx, strview_t key);
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
strview_t *values;
|
strview_t *values;
|
||||||
usize count;
|
usize count;
|
||||||
} iniarray_t;
|
} iniarray_t;
|
||||||
|
|
||||||
iniarray_t iniAsArr(arena_t *arena, inivalue_t *value, char delim);
|
iniarray_t iniAsArr(arena_t *arena, inivalue_t *value, char delim);
|
||||||
uint64 iniAsUInt(inivalue_t *value);
|
uint64 iniAsUInt(inivalue_t *value);
|
||||||
int64 iniAsInt(inivalue_t *value);
|
int64 iniAsInt(inivalue_t *value);
|
||||||
double iniAsNum(inivalue_t *value);
|
double iniAsNum(inivalue_t *value);
|
||||||
bool iniAsBool(inivalue_t *value);
|
bool iniAsBool(inivalue_t *value);
|
||||||
|
|
@ -153,10 +153,9 @@ static bool json__parse_string(arena_t *arena, instream_t *in, str_t *out) {
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
success:
|
|
||||||
return true;
|
return true;
|
||||||
fail:
|
fail:
|
||||||
*out = (str_t){0};
|
*out = STR_EMPTY;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
503
markdown.c
Normal file
503
markdown.c
Normal 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("&"));
|
||||||
|
break;
|
||||||
|
case '<':
|
||||||
|
ostrPuts(out, strv("<"));
|
||||||
|
break;
|
||||||
|
case '>':
|
||||||
|
ostrPuts(out, strv(">"));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ostrPutc(out, view.buf[i]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
59
markdown.h
Normal file
59
markdown.h
Normal 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]  -> 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***
|
||||||
|
*/
|
||||||
|
|
@ -72,6 +72,7 @@ typedef struct server_t {
|
||||||
server__route_t *routes_default;
|
server__route_t *routes_default;
|
||||||
socket_t current_client;
|
socket_t current_client;
|
||||||
bool should_stop;
|
bool should_stop;
|
||||||
|
uint16 port;
|
||||||
} server_t;
|
} server_t;
|
||||||
|
|
||||||
bool server__parse_chunk(arena_t *arena, server__req_ctx_t *ctx, char buffer[SERVER_BUFSZ], usize buflen) {
|
bool server__parse_chunk(arena_t *arena, server__req_ctx_t *ctx, char buffer[SERVER_BUFSZ], usize buflen) {
|
||||||
|
|
@ -275,7 +276,7 @@ void server__parse_req_url(arena_t *arena, server_req_t *req) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
server_t *serverSetup(arena_t *arena, uint16 port) {
|
server_t *serverSetup(arena_t *arena, uint16 port, bool try_next_port) {
|
||||||
if (!skInit()) {
|
if (!skInit()) {
|
||||||
fatal("couldn't initialise sockets: %s", skGetErrorString());
|
fatal("couldn't initialise sockets: %s", skGetErrorString());
|
||||||
}
|
}
|
||||||
|
|
@ -285,14 +286,24 @@ server_t *serverSetup(arena_t *arena, uint16 port) {
|
||||||
fatal("couldn't open socket: %s", skGetErrorString());
|
fatal("couldn't open socket: %s", skGetErrorString());
|
||||||
}
|
}
|
||||||
|
|
||||||
skaddrin_t addr = {
|
bool bound = false;
|
||||||
.sin_family = AF_INET,
|
|
||||||
.sin_addr.s_addr = INADDR_ANY,
|
|
||||||
.sin_port = htons(port),
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!skBindPro(sk, (skaddr_t *)&addr, sizeof(addr))) {
|
while (!bound) {
|
||||||
fatal("could not bind socket: %s", skGetErrorString());
|
skaddrin_t addr = {
|
||||||
|
.sin_family = AF_INET,
|
||||||
|
.sin_addr.s_addr = INADDR_ANY,
|
||||||
|
.sin_port = htons(port),
|
||||||
|
};
|
||||||
|
|
||||||
|
bound = skBindPro(sk, (skaddr_t *)&addr, sizeof(addr));
|
||||||
|
|
||||||
|
if (!bound && try_next_port) {
|
||||||
|
port++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!bound) {
|
||||||
|
fatal("couldn't open socket: %s", skGetErrorString());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!skListenPro(sk, 10)) {
|
if (!skListenPro(sk, 10)) {
|
||||||
|
|
@ -302,6 +313,7 @@ server_t *serverSetup(arena_t *arena, uint16 port) {
|
||||||
server_t *server = alloc(arena, server_t);
|
server_t *server = alloc(arena, server_t);
|
||||||
|
|
||||||
server->socket = sk;
|
server->socket = sk;
|
||||||
|
server->port = port;
|
||||||
|
|
||||||
return server;
|
return server;
|
||||||
}
|
}
|
||||||
|
|
@ -347,8 +359,8 @@ void serverRouteDefault(arena_t *arena, server_t *server, server_route_f cb, voi
|
||||||
void serverStart(arena_t scratch, server_t *server) {
|
void serverStart(arena_t scratch, server_t *server) {
|
||||||
usize start = arenaTell(&scratch);
|
usize start = arenaTell(&scratch);
|
||||||
|
|
||||||
info("Server started!");
|
info("Server started at (http://localhost:%d)!", server->port);
|
||||||
|
|
||||||
while (!server->should_stop) {
|
while (!server->should_stop) {
|
||||||
socket_t client = skAccept(server->socket);
|
socket_t client = skAccept(server->socket);
|
||||||
if (!skIsValid(client)) {
|
if (!skIsValid(client)) {
|
||||||
|
|
@ -26,7 +26,7 @@ typedef struct {
|
||||||
|
|
||||||
typedef str_t (*server_route_f)(arena_t scratch, server_t *server, server_req_t *req, void *userdata);
|
typedef str_t (*server_route_f)(arena_t scratch, server_t *server, server_req_t *req, void *userdata);
|
||||||
|
|
||||||
server_t *serverSetup(arena_t *arena, uint16 port);
|
server_t *serverSetup(arena_t *arena, uint16 port, bool try_next_port);
|
||||||
void serverRoute(arena_t *arena, server_t *server, strview_t page, server_route_f cb, void *userdata);
|
void serverRoute(arena_t *arena, server_t *server, strview_t page, server_route_f cb, void *userdata);
|
||||||
void serverRouteDefault(arena_t *arena, server_t *server, server_route_f cb, void *userdata);
|
void serverRouteDefault(arena_t *arena, server_t *server, server_route_f cb, void *userdata);
|
||||||
void serverStart(arena_t scratch, server_t *server);
|
void serverStart(arena_t scratch, server_t *server);
|
||||||
120
sha1.c
Normal file
120
sha1.c
Normal 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
18
sha1.h
Normal 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);
|
||||||
|
|
@ -1,286 +1,286 @@
|
||||||
#include "socket.h"
|
#include "socket.h"
|
||||||
|
|
||||||
#if COLLA_WIN && COLLA_CMT_LIB
|
#if COLLA_WIN && COLLA_CMT_LIB
|
||||||
#pragma comment(lib, "Ws2_32")
|
#pragma comment(lib, "Ws2_32")
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if COLLA_WIN
|
#if COLLA_WIN
|
||||||
|
|
||||||
typedef int socklen_t;
|
typedef int socklen_t;
|
||||||
|
|
||||||
bool skInit(void) {
|
bool skInit(void) {
|
||||||
WSADATA w;
|
WSADATA w;
|
||||||
int error = WSAStartup(0x0202, &w);
|
int error = WSAStartup(0x0202, &w);
|
||||||
return error == 0;
|
return error == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool skCleanup(void) {
|
bool skCleanup(void) {
|
||||||
return WSACleanup() == 0;
|
return WSACleanup() == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool skClose(socket_t sock) {
|
bool skClose(socket_t sock) {
|
||||||
return closesocket(sock) != SOCKET_ERROR;
|
return closesocket(sock) != SOCKET_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
int skPoll(skpoll_t *to_poll, int num_to_poll, int timeout) {
|
int skPoll(skpoll_t *to_poll, int num_to_poll, int timeout) {
|
||||||
return WSAPoll(to_poll, num_to_poll, timeout);
|
return WSAPoll(to_poll, num_to_poll, timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
int skGetError(void) {
|
int skGetError(void) {
|
||||||
return WSAGetLastError();
|
return WSAGetLastError();
|
||||||
}
|
}
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
||||||
#include <arpa/inet.h>
|
#include <arpa/inet.h>
|
||||||
#include <netdb.h>
|
#include <netdb.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <string.h> // strerror
|
#include <string.h> // strerror
|
||||||
#include <poll.h>
|
#include <poll.h>
|
||||||
|
|
||||||
bool skInit(void) {
|
bool skInit(void) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool skCleanup(void) {
|
bool skCleanup(void) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool skClose(socket_t sock) {
|
bool skClose(socket_t sock) {
|
||||||
return close(sock) != SOCKET_ERROR;
|
return close(sock) != SOCKET_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
int skPoll(skpoll_t *to_poll, int num_to_poll, int timeout) {
|
int skPoll(skpoll_t *to_poll, int num_to_poll, int timeout) {
|
||||||
return poll(to_poll, num_to_poll, timeout);
|
return poll(to_poll, num_to_poll, timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
int skGetError(void) {
|
int skGetError(void) {
|
||||||
return errno;
|
return errno;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *skGetErrorString(void) {
|
const char *skGetErrorString(void) {
|
||||||
return strerror(errno);
|
return strerror(errno);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
socket_t skOpen(sktype_e type) {
|
socket_t skOpen(sktype_e type) {
|
||||||
int sock_type = 0;
|
int sock_type = 0;
|
||||||
|
|
||||||
switch(type) {
|
switch(type) {
|
||||||
case SOCK_TCP: sock_type = SOCK_STREAM; break;
|
case SOCK_TCP: sock_type = SOCK_STREAM; break;
|
||||||
case SOCK_UDP: sock_type = SOCK_DGRAM; break;
|
case SOCK_UDP: sock_type = SOCK_DGRAM; break;
|
||||||
default: fatal("skType not recognized: %d", type); break;
|
default: fatal("skType not recognized: %d", type); break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return skOpenPro(AF_INET, sock_type, 0);
|
return skOpenPro(AF_INET, sock_type, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
socket_t skOpenEx(const char *protocol) {
|
socket_t skOpenEx(const char *protocol) {
|
||||||
struct protoent *proto = getprotobyname(protocol);
|
struct protoent *proto = getprotobyname(protocol);
|
||||||
if(!proto) {
|
if(!proto) {
|
||||||
return (socket_t){0};
|
return (socket_t){0};
|
||||||
}
|
}
|
||||||
return skOpenPro(AF_INET, SOCK_STREAM, proto->p_proto);
|
return skOpenPro(AF_INET, SOCK_STREAM, proto->p_proto);
|
||||||
}
|
}
|
||||||
|
|
||||||
socket_t skOpenPro(int af, int type, int protocol) {
|
socket_t skOpenPro(int af, int type, int protocol) {
|
||||||
return socket(af, type, protocol);
|
return socket(af, type, protocol);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool skIsValid(socket_t sock) {
|
bool skIsValid(socket_t sock) {
|
||||||
return sock != SOCKET_ERROR;
|
return sock != SOCKET_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
skaddrin_t skAddrinInit(const char *ip, uint16_t port) {
|
skaddrin_t skAddrinInit(const char *ip, uint16_t port) {
|
||||||
return (skaddrin_t){
|
return (skaddrin_t){
|
||||||
.sin_family = AF_INET,
|
.sin_family = AF_INET,
|
||||||
.sin_port = htons(port),
|
.sin_port = htons(port),
|
||||||
// TODO use inet_pton instead
|
// TODO use inet_pton instead
|
||||||
.sin_addr.s_addr = inet_addr(ip),
|
.sin_addr.s_addr = inet_addr(ip),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
bool skBind(socket_t sock, const char *ip, uint16_t port) {
|
bool skBind(socket_t sock, const char *ip, uint16_t port) {
|
||||||
skaddrin_t addr = skAddrinInit(ip, port);
|
skaddrin_t addr = skAddrinInit(ip, port);
|
||||||
return skBindPro(sock, (skaddr_t *)&addr, sizeof(addr));
|
return skBindPro(sock, (skaddr_t *)&addr, sizeof(addr));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool skBindPro(socket_t sock, const skaddr_t *name, int namelen) {
|
bool skBindPro(socket_t sock, const skaddr_t *name, int namelen) {
|
||||||
return bind(sock, name, namelen) != SOCKET_ERROR;
|
return bind(sock, name, namelen) != SOCKET_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool skListen(socket_t sock) {
|
bool skListen(socket_t sock) {
|
||||||
return skListenPro(sock, 1);
|
return skListenPro(sock, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool skListenPro(socket_t sock, int backlog) {
|
bool skListenPro(socket_t sock, int backlog) {
|
||||||
return listen(sock, backlog) != SOCKET_ERROR;
|
return listen(sock, backlog) != SOCKET_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
socket_t skAccept(socket_t sock) {
|
socket_t skAccept(socket_t sock) {
|
||||||
skaddrin_t addr = {0};
|
skaddrin_t addr = {0};
|
||||||
int addr_size = sizeof(addr);
|
int addr_size = sizeof(addr);
|
||||||
return skAcceptPro(sock, (skaddr_t *)&addr, &addr_size);
|
return skAcceptPro(sock, (skaddr_t *)&addr, &addr_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
socket_t skAcceptPro(socket_t sock, skaddr_t *addr, int *addrlen) {
|
socket_t skAcceptPro(socket_t sock, skaddr_t *addr, int *addrlen) {
|
||||||
return accept(sock, addr, (socklen_t *)addrlen);
|
return accept(sock, addr, (socklen_t *)addrlen);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool skConnect(socket_t sock, const char *server, unsigned short server_port) {
|
bool skConnect(socket_t sock, const char *server, unsigned short server_port) {
|
||||||
// TODO use getaddrinfo insetad
|
// TODO use getaddrinfo insetad
|
||||||
struct hostent *host = gethostbyname(server);
|
struct hostent *host = gethostbyname(server);
|
||||||
// if gethostbyname fails, inet_addr will also fail and return an easier to debug error
|
// if gethostbyname fails, inet_addr will also fail and return an easier to debug error
|
||||||
const char *address = server;
|
const char *address = server;
|
||||||
if(host) {
|
if(host) {
|
||||||
address = inet_ntoa(*(struct in_addr*)host->h_addr_list[0]);
|
address = inet_ntoa(*(struct in_addr*)host->h_addr_list[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
skaddrin_t addr = skAddrinInit(address, server_port);
|
skaddrin_t addr = skAddrinInit(address, server_port);
|
||||||
|
|
||||||
return skConnectPro(sock, (skaddr_t *)&addr, sizeof(addr));
|
return skConnectPro(sock, (skaddr_t *)&addr, sizeof(addr));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool skConnectPro(socket_t sock, const skaddr_t *name, int namelen) {
|
bool skConnectPro(socket_t sock, const skaddr_t *name, int namelen) {
|
||||||
return connect(sock, name, namelen) != SOCKET_ERROR;
|
return connect(sock, name, namelen) != SOCKET_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
int skSend(socket_t sock, const void *buf, int len) {
|
int skSend(socket_t sock, const void *buf, int len) {
|
||||||
return skSendPro(sock, buf, len, 0);
|
return skSendPro(sock, buf, len, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
int skSendPro(socket_t sock, const void *buf, int len, int flags) {
|
int skSendPro(socket_t sock, const void *buf, int len, int flags) {
|
||||||
return send(sock, buf, len, flags);
|
return send(sock, buf, len, flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
int skSendTo(socket_t sock, const void *buf, int len, const skaddrin_t *to) {
|
int skSendTo(socket_t sock, const void *buf, int len, const skaddrin_t *to) {
|
||||||
return skSendToPro(sock, buf, len, 0, (skaddr_t*)to, sizeof(skaddrin_t));
|
return skSendToPro(sock, buf, len, 0, (skaddr_t*)to, sizeof(skaddrin_t));
|
||||||
}
|
}
|
||||||
|
|
||||||
int skSendToPro(socket_t sock, const void *buf, int len, int flags, const skaddr_t *to, int tolen) {
|
int skSendToPro(socket_t sock, const void *buf, int len, int flags, const skaddr_t *to, int tolen) {
|
||||||
return sendto(sock, buf, len, flags, to, tolen);
|
return sendto(sock, buf, len, flags, to, tolen);
|
||||||
}
|
}
|
||||||
|
|
||||||
int skReceive(socket_t sock, void *buf, int len) {
|
int skReceive(socket_t sock, void *buf, int len) {
|
||||||
return skReceivePro(sock, buf, len, 0);
|
return skReceivePro(sock, buf, len, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
int skReceivePro(socket_t sock, void *buf, int len, int flags) {
|
int skReceivePro(socket_t sock, void *buf, int len, int flags) {
|
||||||
return recv(sock, buf, len, flags);
|
return recv(sock, buf, len, flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
int skReceiveFrom(socket_t sock, void *buf, int len, skaddrin_t *from) {
|
int skReceiveFrom(socket_t sock, void *buf, int len, skaddrin_t *from) {
|
||||||
int fromlen = sizeof(skaddr_t);
|
int fromlen = sizeof(skaddr_t);
|
||||||
return skReceiveFromPro(sock, buf, len, 0, (skaddr_t*)from, &fromlen);
|
return skReceiveFromPro(sock, buf, len, 0, (skaddr_t*)from, &fromlen);
|
||||||
}
|
}
|
||||||
|
|
||||||
int skReceiveFromPro(socket_t sock, void *buf, int len, int flags, skaddr_t *from, int *fromlen) {
|
int skReceiveFromPro(socket_t sock, void *buf, int len, int flags, skaddr_t *from, int *fromlen) {
|
||||||
return recvfrom(sock, buf, len, flags, from, (socklen_t *)fromlen);
|
return recvfrom(sock, buf, len, flags, from, (socklen_t *)fromlen);
|
||||||
}
|
}
|
||||||
|
|
||||||
// put this at the end of file to not make everything else unreadable
|
// put this at the end of file to not make everything else unreadable
|
||||||
#if COLLA_WIN
|
#if COLLA_WIN
|
||||||
const char *skGetErrorString(void) {
|
const char *skGetErrorString(void) {
|
||||||
switch(skGetError()) {
|
switch(skGetError()) {
|
||||||
case WSA_INVALID_HANDLE: return "Specified event object handle is invalid.";
|
case WSA_INVALID_HANDLE: return "Specified event object handle is invalid.";
|
||||||
case WSA_NOT_ENOUGH_MEMORY: return "Insufficient memory available.";
|
case WSA_NOT_ENOUGH_MEMORY: return "Insufficient memory available.";
|
||||||
case WSA_INVALID_PARAMETER: return "One or more parameters are invalid.";
|
case WSA_INVALID_PARAMETER: return "One or more parameters are invalid.";
|
||||||
case WSA_OPERATION_ABORTED: return "Overlapped operation aborted.";
|
case WSA_OPERATION_ABORTED: return "Overlapped operation aborted.";
|
||||||
case WSA_IO_INCOMPLETE: return "Overlapped I/O event object not in signaled state.";
|
case WSA_IO_INCOMPLETE: return "Overlapped I/O event object not in signaled state.";
|
||||||
case WSA_IO_PENDING: return "Overlapped operations will complete later.";
|
case WSA_IO_PENDING: return "Overlapped operations will complete later.";
|
||||||
case WSAEINTR: return "Interrupted function call.";
|
case WSAEINTR: return "Interrupted function call.";
|
||||||
case WSAEBADF: return "File handle is not valid.";
|
case WSAEBADF: return "File handle is not valid.";
|
||||||
case WSAEACCES: return "Permission denied.";
|
case WSAEACCES: return "Permission denied.";
|
||||||
case WSAEFAULT: return "Bad address.";
|
case WSAEFAULT: return "Bad address.";
|
||||||
case WSAEINVAL: return "Invalid argument.";
|
case WSAEINVAL: return "Invalid argument.";
|
||||||
case WSAEMFILE: return "Too many open files.";
|
case WSAEMFILE: return "Too many open files.";
|
||||||
case WSAEWOULDBLOCK: return "Resource temporarily unavailable.";
|
case WSAEWOULDBLOCK: return "Resource temporarily unavailable.";
|
||||||
case WSAEINPROGRESS: return "Operation now in progress.";
|
case WSAEINPROGRESS: return "Operation now in progress.";
|
||||||
case WSAEALREADY: return "Operation already in progress.";
|
case WSAEALREADY: return "Operation already in progress.";
|
||||||
case WSAENOTSOCK: return "Socket operation on nonsocket.";
|
case WSAENOTSOCK: return "Socket operation on nonsocket.";
|
||||||
case WSAEDESTADDRREQ: return "Destination address required.";
|
case WSAEDESTADDRREQ: return "Destination address required.";
|
||||||
case WSAEMSGSIZE: return "Message too long.";
|
case WSAEMSGSIZE: return "Message too long.";
|
||||||
case WSAEPROTOTYPE: return "Protocol wrong type for socket.";
|
case WSAEPROTOTYPE: return "Protocol wrong type for socket.";
|
||||||
case WSAENOPROTOOPT: return "Bad protocol option.";
|
case WSAENOPROTOOPT: return "Bad protocol option.";
|
||||||
case WSAEPROTONOSUPPORT: return "Protocol not supported.";
|
case WSAEPROTONOSUPPORT: return "Protocol not supported.";
|
||||||
case WSAESOCKTNOSUPPORT: return "Socket type not supported.";
|
case WSAESOCKTNOSUPPORT: return "Socket type not supported.";
|
||||||
case WSAEOPNOTSUPP: return "Operation not supported.";
|
case WSAEOPNOTSUPP: return "Operation not supported.";
|
||||||
case WSAEPFNOSUPPORT: return "Protocol family not supported.";
|
case WSAEPFNOSUPPORT: return "Protocol family not supported.";
|
||||||
case WSAEAFNOSUPPORT: return "Address family not supported by protocol family.";
|
case WSAEAFNOSUPPORT: return "Address family not supported by protocol family.";
|
||||||
case WSAEADDRINUSE: return "Address already in use.";
|
case WSAEADDRINUSE: return "Address already in use.";
|
||||||
case WSAEADDRNOTAVAIL: return "Cannot assign requested address.";
|
case WSAEADDRNOTAVAIL: return "Cannot assign requested address.";
|
||||||
case WSAENETDOWN: return "Network is down.";
|
case WSAENETDOWN: return "Network is down.";
|
||||||
case WSAENETUNREACH: return "Network is unreachable.";
|
case WSAENETUNREACH: return "Network is unreachable.";
|
||||||
case WSAENETRESET: return "Network dropped connection on reset.";
|
case WSAENETRESET: return "Network dropped connection on reset.";
|
||||||
case WSAECONNABORTED: return "Software caused connection abort.";
|
case WSAECONNABORTED: return "Software caused connection abort.";
|
||||||
case WSAECONNRESET: return "Connection reset by peer.";
|
case WSAECONNRESET: return "Connection reset by peer.";
|
||||||
case WSAENOBUFS: return "No buffer space available.";
|
case WSAENOBUFS: return "No buffer space available.";
|
||||||
case WSAEISCONN: return "Socket is already connected.";
|
case WSAEISCONN: return "Socket is already connected.";
|
||||||
case WSAENOTCONN: return "Socket is not connected.";
|
case WSAENOTCONN: return "Socket is not connected.";
|
||||||
case WSAESHUTDOWN: return "Cannot send after socket shutdown.";
|
case WSAESHUTDOWN: return "Cannot send after socket shutdown.";
|
||||||
case WSAETOOMANYREFS: return "Too many references.";
|
case WSAETOOMANYREFS: return "Too many references.";
|
||||||
case WSAETIMEDOUT: return "Connection timed out.";
|
case WSAETIMEDOUT: return "Connection timed out.";
|
||||||
case WSAECONNREFUSED: return "Connection refused.";
|
case WSAECONNREFUSED: return "Connection refused.";
|
||||||
case WSAELOOP: return "Cannot translate name.";
|
case WSAELOOP: return "Cannot translate name.";
|
||||||
case WSAENAMETOOLONG: return "Name too long.";
|
case WSAENAMETOOLONG: return "Name too long.";
|
||||||
case WSAEHOSTDOWN: return "Host is down.";
|
case WSAEHOSTDOWN: return "Host is down.";
|
||||||
case WSAEHOSTUNREACH: return "No route to host.";
|
case WSAEHOSTUNREACH: return "No route to host.";
|
||||||
case WSAENOTEMPTY: return "Directory not empty.";
|
case WSAENOTEMPTY: return "Directory not empty.";
|
||||||
case WSAEPROCLIM: return "Too many processes.";
|
case WSAEPROCLIM: return "Too many processes.";
|
||||||
case WSAEUSERS: return "User quota exceeded.";
|
case WSAEUSERS: return "User quota exceeded.";
|
||||||
case WSAEDQUOT: return "Disk quota exceeded.";
|
case WSAEDQUOT: return "Disk quota exceeded.";
|
||||||
case WSAESTALE: return "Stale file handle reference.";
|
case WSAESTALE: return "Stale file handle reference.";
|
||||||
case WSAEREMOTE: return "Item is remote.";
|
case WSAEREMOTE: return "Item is remote.";
|
||||||
case WSASYSNOTREADY: return "Network subsystem is unavailable.";
|
case WSASYSNOTREADY: return "Network subsystem is unavailable.";
|
||||||
case WSAVERNOTSUPPORTED: return "Winsock.dll version out of range.";
|
case WSAVERNOTSUPPORTED: return "Winsock.dll version out of range.";
|
||||||
case WSANOTINITIALISED: return "Successful WSAStartup not yet performed.";
|
case WSANOTINITIALISED: return "Successful WSAStartup not yet performed.";
|
||||||
case WSAEDISCON: return "Graceful shutdown in progress.";
|
case WSAEDISCON: return "Graceful shutdown in progress.";
|
||||||
case WSAENOMORE: return "No more results.";
|
case WSAENOMORE: return "No more results.";
|
||||||
case WSAECANCELLED: return "Call has been canceled.";
|
case WSAECANCELLED: return "Call has been canceled.";
|
||||||
case WSAEINVALIDPROCTABLE: return "Procedure call table is invalid.";
|
case WSAEINVALIDPROCTABLE: return "Procedure call table is invalid.";
|
||||||
case WSAEINVALIDPROVIDER: return "Service provider is invalid.";
|
case WSAEINVALIDPROVIDER: return "Service provider is invalid.";
|
||||||
case WSAEPROVIDERFAILEDINIT: return "Service provider failed to initialize.";
|
case WSAEPROVIDERFAILEDINIT: return "Service provider failed to initialize.";
|
||||||
case WSASYSCALLFAILURE: return "System call failure.";
|
case WSASYSCALLFAILURE: return "System call failure.";
|
||||||
case WSASERVICE_NOT_FOUND: return "Service not found.";
|
case WSASERVICE_NOT_FOUND: return "Service not found.";
|
||||||
case WSATYPE_NOT_FOUND: return "Class type not found.";
|
case WSATYPE_NOT_FOUND: return "Class type not found.";
|
||||||
case WSA_E_NO_MORE: return "No more results.";
|
case WSA_E_NO_MORE: return "No more results.";
|
||||||
case WSA_E_CANCELLED: return "Call was canceled.";
|
case WSA_E_CANCELLED: return "Call was canceled.";
|
||||||
case WSAEREFUSED: return "Database query was refused.";
|
case WSAEREFUSED: return "Database query was refused.";
|
||||||
case WSAHOST_NOT_FOUND: return "Host not found.";
|
case WSAHOST_NOT_FOUND: return "Host not found.";
|
||||||
case WSATRY_AGAIN: return "Nonauthoritative host not found.";
|
case WSATRY_AGAIN: return "Nonauthoritative host not found.";
|
||||||
case WSANO_RECOVERY: return "This is a nonrecoverable error.";
|
case WSANO_RECOVERY: return "This is a nonrecoverable error.";
|
||||||
case WSANO_DATA: return "Valid name, no data record of requested type.";
|
case WSANO_DATA: return "Valid name, no data record of requested type.";
|
||||||
case WSA_QOS_RECEIVERS: return "QoS receivers.";
|
case WSA_QOS_RECEIVERS: return "QoS receivers.";
|
||||||
case WSA_QOS_SENDERS: return "QoS senders.";
|
case WSA_QOS_SENDERS: return "QoS senders.";
|
||||||
case WSA_QOS_NO_SENDERS: return "No QoS senders.";
|
case WSA_QOS_NO_SENDERS: return "No QoS senders.";
|
||||||
case WSA_QOS_NO_RECEIVERS: return "QoS no receivers.";
|
case WSA_QOS_NO_RECEIVERS: return "QoS no receivers.";
|
||||||
case WSA_QOS_REQUEST_CONFIRMED: return "QoS request confirmed.";
|
case WSA_QOS_REQUEST_CONFIRMED: return "QoS request confirmed.";
|
||||||
case WSA_QOS_ADMISSION_FAILURE: return "QoS admission error.";
|
case WSA_QOS_ADMISSION_FAILURE: return "QoS admission error.";
|
||||||
case WSA_QOS_POLICY_FAILURE: return "QoS policy failure.";
|
case WSA_QOS_POLICY_FAILURE: return "QoS policy failure.";
|
||||||
case WSA_QOS_BAD_STYLE: return "QoS bad style.";
|
case WSA_QOS_BAD_STYLE: return "QoS bad style.";
|
||||||
case WSA_QOS_BAD_OBJECT: return "QoS bad object.";
|
case WSA_QOS_BAD_OBJECT: return "QoS bad object.";
|
||||||
case WSA_QOS_TRAFFIC_CTRL_ERROR: return "QoS traffic control error.";
|
case WSA_QOS_TRAFFIC_CTRL_ERROR: return "QoS traffic control error.";
|
||||||
case WSA_QOS_GENERIC_ERROR: return "QoS generic error.";
|
case WSA_QOS_GENERIC_ERROR: return "QoS generic error.";
|
||||||
case WSA_QOS_ESERVICETYPE: return "QoS service type error.";
|
case WSA_QOS_ESERVICETYPE: return "QoS service type error.";
|
||||||
case WSA_QOS_EFLOWSPEC: return "QoS flowspec error.";
|
case WSA_QOS_EFLOWSPEC: return "QoS flowspec error.";
|
||||||
case WSA_QOS_EPROVSPECBUF: return "Invalid QoS provider buffer.";
|
case WSA_QOS_EPROVSPECBUF: return "Invalid QoS provider buffer.";
|
||||||
case WSA_QOS_EFILTERSTYLE: return "Invalid QoS filter style.";
|
case WSA_QOS_EFILTERSTYLE: return "Invalid QoS filter style.";
|
||||||
case WSA_QOS_EFILTERTYPE: return "Invalid QoS filter type.";
|
case WSA_QOS_EFILTERTYPE: return "Invalid QoS filter type.";
|
||||||
case WSA_QOS_EFILTERCOUNT: return "Incorrect QoS filter count.";
|
case WSA_QOS_EFILTERCOUNT: return "Incorrect QoS filter count.";
|
||||||
case WSA_QOS_EOBJLENGTH: return "Invalid QoS object length.";
|
case WSA_QOS_EOBJLENGTH: return "Invalid QoS object length.";
|
||||||
case WSA_QOS_EFLOWCOUNT: return "Incorrect QoS flow count.";
|
case WSA_QOS_EFLOWCOUNT: return "Incorrect QoS flow count.";
|
||||||
case WSA_QOS_EUNKOWNPSOBJ: return "Unrecognized QoS object.";
|
case WSA_QOS_EUNKOWNPSOBJ: return "Unrecognized QoS object.";
|
||||||
case WSA_QOS_EPOLICYOBJ: return "Invalid QoS policy object.";
|
case WSA_QOS_EPOLICYOBJ: return "Invalid QoS policy object.";
|
||||||
case WSA_QOS_EFLOWDESC: return "Invalid QoS flow descriptor.";
|
case WSA_QOS_EFLOWDESC: return "Invalid QoS flow descriptor.";
|
||||||
case WSA_QOS_EPSFLOWSPEC: return "Invalid QoS provider-specific flowspec.";
|
case WSA_QOS_EPSFLOWSPEC: return "Invalid QoS provider-specific flowspec.";
|
||||||
case WSA_QOS_EPSFILTERSPEC: return "Invalid QoS provider-specific filterspec.";
|
case WSA_QOS_EPSFILTERSPEC: return "Invalid QoS provider-specific filterspec.";
|
||||||
case WSA_QOS_ESDMODEOBJ: return "Invalid QoS shape discard mode object.";
|
case WSA_QOS_ESDMODEOBJ: return "Invalid QoS shape discard mode object.";
|
||||||
case WSA_QOS_ESHAPERATEOBJ: return "Invalid QoS shaping rate object.";
|
case WSA_QOS_ESHAPERATEOBJ: return "Invalid QoS shaping rate object.";
|
||||||
case WSA_QOS_RESERVED_PETYPE: return "Reserved policy QoS element type.";
|
case WSA_QOS_RESERVED_PETYPE: return "Reserved policy QoS element type.";
|
||||||
}
|
}
|
||||||
|
|
||||||
return "(nothing)";
|
return "(nothing)";
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -1,93 +1,93 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "collatypes.h"
|
#include "collatypes.h"
|
||||||
|
|
||||||
#if COLLA_TCC
|
#if COLLA_TCC
|
||||||
#include "tcc/colla_tcc.h"
|
#include "tcc/colla_tcc.h"
|
||||||
#elif COLLA_WIN
|
#elif COLLA_WIN
|
||||||
#define _WINSOCK_DEPRECATED_NO_WARNINGS
|
#define _WINSOCK_DEPRECATED_NO_WARNINGS
|
||||||
#include <winsock2.h>
|
#include <winsock2.h>
|
||||||
#elif COLLA_LIN || COLLA_OSX
|
#elif COLLA_LIN || COLLA_OSX
|
||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
#include <netinet/in.h>
|
#include <netinet/in.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
typedef uintptr_t socket_t;
|
typedef uintptr_t socket_t;
|
||||||
typedef struct sockaddr skaddr_t;
|
typedef struct sockaddr skaddr_t;
|
||||||
typedef struct sockaddr_in skaddrin_t;
|
typedef struct sockaddr_in skaddrin_t;
|
||||||
typedef struct pollfd skpoll_t;
|
typedef struct pollfd skpoll_t;
|
||||||
|
|
||||||
#define SOCKET_ERROR (-1)
|
#define SOCKET_ERROR (-1)
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
SOCK_TCP,
|
SOCK_TCP,
|
||||||
SOCK_UDP,
|
SOCK_UDP,
|
||||||
} sktype_e;
|
} sktype_e;
|
||||||
|
|
||||||
// Initialize sockets, returns true on success
|
// Initialize sockets, returns true on success
|
||||||
bool skInit(void);
|
bool skInit(void);
|
||||||
// Terminates sockets, returns true on success
|
// Terminates sockets, returns true on success
|
||||||
bool skCleanup(void);
|
bool skCleanup(void);
|
||||||
|
|
||||||
// Opens a socket, check socket_t with skValid
|
// Opens a socket, check socket_t with skValid
|
||||||
socket_t skOpen(sktype_e type);
|
socket_t skOpen(sktype_e type);
|
||||||
// Opens a socket using 'protocol', options are
|
// Opens a socket using 'protocol', options are
|
||||||
// ip, icmp, ggp, tcp, egp, pup, udp, hmp, xns-idp, rdp
|
// ip, icmp, ggp, tcp, egp, pup, udp, hmp, xns-idp, rdp
|
||||||
// check socket_t with skValid
|
// check socket_t with skValid
|
||||||
socket_t skOpenEx(const char *protocol);
|
socket_t skOpenEx(const char *protocol);
|
||||||
// Opens a socket, check socket_t with skValid
|
// Opens a socket, check socket_t with skValid
|
||||||
socket_t skOpenPro(int af, int type, int protocol);
|
socket_t skOpenPro(int af, int type, int protocol);
|
||||||
|
|
||||||
// Checks that a opened socket is valid, returns true on success
|
// Checks that a opened socket is valid, returns true on success
|
||||||
bool skIsValid(socket_t sock);
|
bool skIsValid(socket_t sock);
|
||||||
|
|
||||||
// Closes a socket, returns true on success
|
// Closes a socket, returns true on success
|
||||||
bool skClose(socket_t sock);
|
bool skClose(socket_t sock);
|
||||||
|
|
||||||
// Fill out a sk_addrin_t structure with "ip" and "port"
|
// Fill out a sk_addrin_t structure with "ip" and "port"
|
||||||
skaddrin_t skAddrinInit(const char *ip, uint16_t port);
|
skaddrin_t skAddrinInit(const char *ip, uint16_t port);
|
||||||
|
|
||||||
// Associate a local address with a socket
|
// Associate a local address with a socket
|
||||||
bool skBind(socket_t sock, const char *ip, uint16_t port);
|
bool skBind(socket_t sock, const char *ip, uint16_t port);
|
||||||
// Associate a local address with a socket
|
// Associate a local address with a socket
|
||||||
bool skBindPro(socket_t sock, const skaddr_t *name, int namelen);
|
bool skBindPro(socket_t sock, const skaddr_t *name, int namelen);
|
||||||
|
|
||||||
// Place a socket in a state in which it is listening for an incoming connection
|
// Place a socket in a state in which it is listening for an incoming connection
|
||||||
bool skListen(socket_t sock);
|
bool skListen(socket_t sock);
|
||||||
// Place a socket in a state in which it is listening for an incoming connection
|
// Place a socket in a state in which it is listening for an incoming connection
|
||||||
bool skListenPro(socket_t sock, int backlog);
|
bool skListenPro(socket_t sock, int backlog);
|
||||||
|
|
||||||
// Permits an incoming connection attempt on a socket
|
// Permits an incoming connection attempt on a socket
|
||||||
socket_t skAccept(socket_t sock);
|
socket_t skAccept(socket_t sock);
|
||||||
// Permits an incoming connection attempt on a socket
|
// Permits an incoming connection attempt on a socket
|
||||||
socket_t skAcceptPro(socket_t sock, skaddr_t *addr, int *addrlen);
|
socket_t skAcceptPro(socket_t sock, skaddr_t *addr, int *addrlen);
|
||||||
|
|
||||||
// Connects to a server (e.g. "127.0.0.1" or "google.com") with a port(e.g. 1234), returns true on success
|
// Connects to a server (e.g. "127.0.0.1" or "google.com") with a port(e.g. 1234), returns true on success
|
||||||
bool skConnect(socket_t sock, const char *server, unsigned short server_port);
|
bool skConnect(socket_t sock, const char *server, unsigned short server_port);
|
||||||
// Connects to a server, returns true on success
|
// Connects to a server, returns true on success
|
||||||
bool skConnectPro(socket_t sock, const skaddr_t *name, int namelen);
|
bool skConnectPro(socket_t sock, const skaddr_t *name, int namelen);
|
||||||
|
|
||||||
// Sends data on a socket, returns true on success
|
// Sends data on a socket, returns true on success
|
||||||
int skSend(socket_t sock, const void *buf, int len);
|
int skSend(socket_t sock, const void *buf, int len);
|
||||||
// Sends data on a socket, returns true on success
|
// Sends data on a socket, returns true on success
|
||||||
int skSendPro(socket_t sock, const void *buf, int len, int flags);
|
int skSendPro(socket_t sock, const void *buf, int len, int flags);
|
||||||
// Sends data to a specific destination
|
// Sends data to a specific destination
|
||||||
int skSendTo(socket_t sock, const void *buf, int len, const skaddrin_t *to);
|
int skSendTo(socket_t sock, const void *buf, int len, const skaddrin_t *to);
|
||||||
// Sends data to a specific destination
|
// Sends data to a specific destination
|
||||||
int skSendToPro(socket_t sock, const void *buf, int len, int flags, const skaddr_t *to, int tolen);
|
int skSendToPro(socket_t sock, const void *buf, int len, int flags, const skaddr_t *to, int tolen);
|
||||||
// Receives data from a socket, returns byte count on success, 0 on connection close or -1 on error
|
// Receives data from a socket, returns byte count on success, 0 on connection close or -1 on error
|
||||||
int skReceive(socket_t sock, void *buf, int len);
|
int skReceive(socket_t sock, void *buf, int len);
|
||||||
// Receives data from a socket, returns byte count on success, 0 on connection close or -1 on error
|
// Receives data from a socket, returns byte count on success, 0 on connection close or -1 on error
|
||||||
int skReceivePro(socket_t sock, void *buf, int len, int flags);
|
int skReceivePro(socket_t sock, void *buf, int len, int flags);
|
||||||
// Receives a datagram and stores the source address.
|
// Receives a datagram and stores the source address.
|
||||||
int skReceiveFrom(socket_t sock, void *buf, int len, skaddrin_t *from);
|
int skReceiveFrom(socket_t sock, void *buf, int len, skaddrin_t *from);
|
||||||
// Receives a datagram and stores the source address.
|
// Receives a datagram and stores the source address.
|
||||||
int skReceiveFromPro(socket_t sock, void *buf, int len, int flags, skaddr_t *from, int *fromlen);
|
int skReceiveFromPro(socket_t sock, void *buf, int len, int flags, skaddr_t *from, int *fromlen);
|
||||||
|
|
||||||
// Wait for an event on some sockets
|
// Wait for an event on some sockets
|
||||||
int skPoll(skpoll_t *to_poll, int num_to_poll, int timeout);
|
int skPoll(skpoll_t *to_poll, int num_to_poll, int timeout);
|
||||||
|
|
||||||
// Returns latest socket error, returns 0 if there is no error
|
// Returns latest socket error, returns 0 if there is no error
|
||||||
int skGetError(void);
|
int skGetError(void);
|
||||||
// Returns a human-readable string from a skGetError
|
// Returns a human-readable string from a skGetError
|
||||||
const char *skGetErrorString(void);
|
const char *skGetErrorString(void);
|
||||||
|
|
@ -219,6 +219,17 @@ STBSP__PUBLICDEC void STB_SPRINTF_DECORATE(set_separators)(char comma, char peri
|
||||||
|
|
||||||
#ifdef STB_SPRINTF_IMPLEMENTATION
|
#ifdef STB_SPRINTF_IMPLEMENTATION
|
||||||
|
|
||||||
|
#if COLLA_CLANG
|
||||||
|
|
||||||
|
#pragma clang diagnostic push
|
||||||
|
|
||||||
|
#pragma clang diagnostic ignored "-Wextra-semi-stmt"
|
||||||
|
#pragma clang diagnostic ignored "-Wconditional-uninitialized"
|
||||||
|
#pragma clang diagnostic ignored "-Wcast-qual"
|
||||||
|
#pragma clang diagnostic ignored "-Wimplicit-fallthrough"
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
#define stbsp__uint32 unsigned int
|
#define stbsp__uint32 unsigned int
|
||||||
#define stbsp__int32 signed int
|
#define stbsp__int32 signed int
|
||||||
|
|
||||||
|
|
@ -1885,6 +1896,10 @@ static stbsp__int32 stbsp__real_to_str(char const **start, stbsp__uint32 *len, c
|
||||||
#undef stbsp__int64
|
#undef stbsp__int64
|
||||||
#undef STBSP__UNALIGNED
|
#undef STBSP__UNALIGNED
|
||||||
|
|
||||||
|
#if COLLA_CLANG
|
||||||
|
#pragma clang diagnostic pop
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif // STB_SPRINTF_IMPLEMENTATION
|
#endif // STB_SPRINTF_IMPLEMENTATION
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -1,399 +1,408 @@
|
||||||
#include "str.h"
|
#include "str.h"
|
||||||
|
|
||||||
#include "warnings/colla_warn_beg.h"
|
#include "warnings/colla_warn_beg.h"
|
||||||
|
|
||||||
#include "arena.h"
|
#include "arena.h"
|
||||||
#include "format.h"
|
#include "format.h"
|
||||||
#include "tracelog.h"
|
#include "tracelog.h"
|
||||||
|
|
||||||
#if COLLA_WIN
|
#if COLLA_WIN
|
||||||
|
|
||||||
#define WIN32_LEAN_AND_MEAN
|
#include <windows.h>
|
||||||
#include <windows.h>
|
|
||||||
|
#else
|
||||||
#else
|
|
||||||
|
#include <wchar.h>
|
||||||
#include <wchar.h>
|
|
||||||
|
#endif
|
||||||
#endif
|
|
||||||
|
#if COLLA_TCC
|
||||||
#if COLLA_TCC
|
#include "tcc/colla_tcc.h"
|
||||||
#include "tcc/colla_tcc.h"
|
#endif
|
||||||
#endif
|
|
||||||
|
// == STR_T ========================================================
|
||||||
// == STR_T ========================================================
|
|
||||||
|
str_t strInit(arena_t *arena, const char *buf) {
|
||||||
str_t strInit(arena_t *arena, const char *buf) {
|
return buf ? strInitLen(arena, buf, strlen(buf)) : STR_EMPTY;
|
||||||
return buf ? strInitLen(arena, buf, strlen(buf)) : (str_t){0};
|
}
|
||||||
}
|
|
||||||
|
str_t strInitLen(arena_t *arena, const char *buf, usize len) {
|
||||||
str_t strInitLen(arena_t *arena, const char *buf, usize len) {
|
if (!buf || !len) return STR_EMPTY;
|
||||||
if (!buf || !len) return (str_t){0};
|
|
||||||
|
str_t out = {
|
||||||
str_t out = {
|
.buf = alloc(arena, char, len + 1),
|
||||||
.buf = alloc(arena, char, len + 1),
|
.len = len
|
||||||
.len = len
|
};
|
||||||
};
|
|
||||||
|
memcpy(out.buf, buf, len);
|
||||||
memcpy(out.buf, buf, len);
|
|
||||||
|
return out;
|
||||||
return out;
|
}
|
||||||
}
|
|
||||||
|
str_t strInitView(arena_t *arena, strview_t view) {
|
||||||
str_t strInitView(arena_t *arena, strview_t view) {
|
return strInitLen(arena, view.buf, view.len);
|
||||||
return strInitLen(arena, view.buf, view.len);
|
}
|
||||||
}
|
|
||||||
|
str_t strFmt(arena_t *arena, const char *fmt, ...) {
|
||||||
str_t strFmt(arena_t *arena, const char *fmt, ...) {
|
va_list args;
|
||||||
va_list args;
|
va_start(args, fmt);
|
||||||
va_start(args, fmt);
|
str_t out = strFmtv(arena, fmt, args);
|
||||||
str_t out = strFmtv(arena, fmt, args);
|
va_end(args);
|
||||||
va_end(args);
|
return out;
|
||||||
return out;
|
}
|
||||||
}
|
|
||||||
|
str_t strFmtv(arena_t *arena, const char *fmt, va_list args) {
|
||||||
str_t strFmtv(arena_t *arena, const char *fmt, va_list args) {
|
va_list vcopy;
|
||||||
va_list vcopy;
|
va_copy(vcopy, args);
|
||||||
va_copy(vcopy, args);
|
// stb_vsnprintf returns the length + null_term
|
||||||
// stb_vsnprintf returns the length + null_term
|
int len = fmtBufferv(NULL, 0, fmt, vcopy);
|
||||||
int len = fmtBufferv(NULL, 0, fmt, vcopy);
|
va_end(vcopy);
|
||||||
va_end(vcopy);
|
|
||||||
|
char *buffer = alloc(arena, char, len + 1);
|
||||||
char *buffer = alloc(arena, char, len + 1);
|
fmtBufferv(buffer, len + 1, fmt, args);
|
||||||
fmtBufferv(buffer, len + 1, fmt, args);
|
|
||||||
|
return (str_t) { .buf = buffer, .len = (usize)len };
|
||||||
return (str_t) { .buf = buffer, .len = (usize)len };
|
}
|
||||||
}
|
|
||||||
|
str_t strFromWChar(arena_t *arena, const wchar_t *src) {
|
||||||
str_t strFromWChar(arena_t *arena, const wchar_t *src, usize srclen) {
|
return strFromWCharLen(arena, src, 0);
|
||||||
if (!src) return (str_t){0};
|
}
|
||||||
if (!srclen) srclen = wcslen(src);
|
|
||||||
|
str_t strFromWCharLen(arena_t *arena, const wchar_t *src, usize srclen) {
|
||||||
str_t out = {0};
|
if (!src) return STR_EMPTY;
|
||||||
|
if (!srclen) srclen = wcslen(src);
|
||||||
#if COLLA_WIN
|
|
||||||
int outlen = WideCharToMultiByte(
|
str_t out = {0};
|
||||||
CP_UTF8, 0,
|
|
||||||
src, (int)srclen,
|
#if COLLA_WIN
|
||||||
NULL, 0,
|
int outlen = WideCharToMultiByte(
|
||||||
NULL, NULL
|
CP_UTF8, 0,
|
||||||
);
|
src, (int)srclen,
|
||||||
|
NULL, 0,
|
||||||
if (outlen == 0) {
|
NULL, NULL
|
||||||
unsigned long error = GetLastError();
|
);
|
||||||
if (error == ERROR_NO_UNICODE_TRANSLATION) {
|
|
||||||
err("couldn't translate wide string (%S) to utf8, no unicode translation", src);
|
if (outlen == 0) {
|
||||||
}
|
unsigned long error = GetLastError();
|
||||||
else {
|
if (error == ERROR_NO_UNICODE_TRANSLATION) {
|
||||||
err("couldn't translate wide string (%S) to utf8, %u", error);
|
err("couldn't translate wide string (%S) to utf8, no unicode translation", src);
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
return (str_t){0};
|
err("couldn't translate wide string (%S) to utf8, %u", error);
|
||||||
}
|
}
|
||||||
|
|
||||||
out.buf = alloc(arena, char, outlen + 1);
|
return STR_EMPTY;
|
||||||
WideCharToMultiByte(
|
}
|
||||||
CP_UTF8, 0,
|
|
||||||
src, (int)srclen,
|
out.buf = alloc(arena, char, outlen + 1);
|
||||||
out.buf, outlen,
|
WideCharToMultiByte(
|
||||||
NULL, NULL
|
CP_UTF8, 0,
|
||||||
);
|
src, (int)srclen,
|
||||||
|
out.buf, outlen,
|
||||||
out.len = outlen;
|
NULL, NULL
|
||||||
|
);
|
||||||
#elif COLLA_LIN
|
|
||||||
fatal("strFromWChar not implemented yet!");
|
out.len = outlen;
|
||||||
#endif
|
|
||||||
|
#elif COLLA_LIN
|
||||||
return out;
|
fatal("strFromWChar not implemented yet!");
|
||||||
}
|
#endif
|
||||||
|
|
||||||
bool strEquals(str_t a, str_t b) {
|
return out;
|
||||||
return strCompare(a, b) == 0;
|
}
|
||||||
}
|
|
||||||
|
bool strEquals(str_t a, str_t b) {
|
||||||
int strCompare(str_t a, str_t b) {
|
return strCompare(a, b) == 0;
|
||||||
return a.len == b.len ?
|
}
|
||||||
memcmp(a.buf, b.buf, a.len) :
|
|
||||||
(int)(a.len - b.len);
|
int strCompare(str_t a, str_t b) {
|
||||||
}
|
return a.len == b.len ?
|
||||||
|
memcmp(a.buf, b.buf, a.len) :
|
||||||
str_t strDup(arena_t *arena, str_t src) {
|
(int)(a.len - b.len);
|
||||||
return strInitLen(arena, src.buf, src.len);
|
}
|
||||||
}
|
|
||||||
|
str_t strDup(arena_t *arena, str_t src) {
|
||||||
bool strIsEmpty(str_t ctx) {
|
return strInitLen(arena, src.buf, src.len);
|
||||||
return ctx.len == 0 || ctx.buf == NULL;
|
}
|
||||||
}
|
|
||||||
|
bool strIsEmpty(str_t ctx) {
|
||||||
void strReplace(str_t *ctx, char from, char to) {
|
return ctx.len == 0 || ctx.buf == NULL;
|
||||||
if (!ctx) return;
|
}
|
||||||
char *buf = ctx->buf;
|
|
||||||
for (usize i = 0; i < ctx->len; ++i) {
|
void strReplace(str_t *ctx, char from, char to) {
|
||||||
buf[i] = buf[i] == from ? to : buf[i];
|
if (!ctx) return;
|
||||||
}
|
char *buf = ctx->buf;
|
||||||
}
|
for (usize i = 0; i < ctx->len; ++i) {
|
||||||
|
buf[i] = buf[i] == from ? to : buf[i];
|
||||||
strview_t strSub(str_t ctx, usize from, usize to) {
|
}
|
||||||
if (to > ctx.len) to = ctx.len;
|
}
|
||||||
if (from > to) from = to;
|
|
||||||
return (strview_t){ ctx.buf + from, to - from };
|
strview_t strSub(str_t ctx, usize from, usize to) {
|
||||||
}
|
if (to > ctx.len) to = ctx.len;
|
||||||
|
if (from > to) from = to;
|
||||||
void strLower(str_t *ctx) {
|
return (strview_t){ ctx.buf + from, to - from };
|
||||||
char *buf = ctx->buf;
|
}
|
||||||
for (usize i = 0; i < ctx->len; ++i) {
|
|
||||||
buf[i] = (buf[i] >= 'A' && buf[i] <= 'Z') ?
|
void strLower(str_t *ctx) {
|
||||||
buf[i] += 'a' - 'A' :
|
char *buf = ctx->buf;
|
||||||
buf[i];
|
for (usize i = 0; i < ctx->len; ++i) {
|
||||||
}
|
buf[i] = (buf[i] >= 'A' && buf[i] <= 'Z') ?
|
||||||
}
|
buf[i] += 'a' - 'A' :
|
||||||
|
buf[i];
|
||||||
void strUpper(str_t *ctx) {
|
}
|
||||||
char *buf = ctx->buf;
|
}
|
||||||
for (usize i = 0; i < ctx->len; ++i) {
|
|
||||||
buf[i] = (buf[i] >= 'a' && buf[i] <= 'z') ?
|
void strUpper(str_t *ctx) {
|
||||||
buf[i] -= 'a' - 'A' :
|
char *buf = ctx->buf;
|
||||||
buf[i];
|
for (usize i = 0; i < ctx->len; ++i) {
|
||||||
}
|
buf[i] = (buf[i] >= 'a' && buf[i] <= 'z') ?
|
||||||
}
|
buf[i] -= 'a' - 'A' :
|
||||||
|
buf[i];
|
||||||
str_t strToLower(arena_t *arena, str_t ctx) {
|
}
|
||||||
strLower(&ctx);
|
}
|
||||||
return strDup(arena, ctx);
|
|
||||||
}
|
str_t strToLower(arena_t *arena, str_t ctx) {
|
||||||
|
strLower(&ctx);
|
||||||
str_t strToUpper(arena_t *arena, str_t ctx) {
|
return strDup(arena, ctx);
|
||||||
strUpper(&ctx);
|
}
|
||||||
return strDup(arena, ctx);
|
|
||||||
}
|
str_t strToUpper(arena_t *arena, str_t ctx) {
|
||||||
|
strUpper(&ctx);
|
||||||
|
return strDup(arena, ctx);
|
||||||
// == STRVIEW_T ====================================================
|
}
|
||||||
|
|
||||||
strview_t strvInit(const char *cstr) {
|
|
||||||
return (strview_t){
|
// == STRVIEW_T ====================================================
|
||||||
.buf = cstr,
|
|
||||||
.len = cstr ? strlen(cstr) : 0,
|
strview_t strvInit(const char *cstr) {
|
||||||
};
|
return (strview_t){
|
||||||
}
|
.buf = cstr,
|
||||||
|
.len = cstr ? strlen(cstr) : 0,
|
||||||
strview_t strvInitLen(const char *buf, usize size) {
|
};
|
||||||
return (strview_t){
|
}
|
||||||
.buf = buf,
|
|
||||||
.len = size,
|
strview_t strvInitLen(const char *buf, usize size) {
|
||||||
};
|
return (strview_t){
|
||||||
}
|
.buf = buf,
|
||||||
|
.len = size,
|
||||||
strview_t strvInitStr(str_t str) {
|
};
|
||||||
return (strview_t){
|
}
|
||||||
.buf = str.buf,
|
|
||||||
.len = str.len
|
strview_t strvInitStr(str_t str) {
|
||||||
};
|
return (strview_t){
|
||||||
}
|
.buf = str.buf,
|
||||||
|
.len = str.len
|
||||||
|
};
|
||||||
bool strvIsEmpty(strview_t ctx) {
|
}
|
||||||
return ctx.len == 0 || !ctx.buf;
|
|
||||||
}
|
|
||||||
|
bool strvIsEmpty(strview_t ctx) {
|
||||||
bool strvEquals(strview_t a, strview_t b) {
|
return ctx.len == 0 || !ctx.buf;
|
||||||
return strvCompare(a, b) == 0;
|
}
|
||||||
}
|
|
||||||
|
bool strvEquals(strview_t a, strview_t b) {
|
||||||
int strvCompare(strview_t a, strview_t b) {
|
return strvCompare(a, b) == 0;
|
||||||
return a.len == b.len ?
|
}
|
||||||
memcmp(a.buf, b.buf, a.len) :
|
|
||||||
(int)(a.len - b.len);
|
int strvCompare(strview_t a, strview_t b) {
|
||||||
}
|
return a.len == b.len ?
|
||||||
|
memcmp(a.buf, b.buf, a.len) :
|
||||||
wchar_t *strvToWChar(arena_t *arena, strview_t ctx, usize *outlen) {
|
(int)(a.len - b.len);
|
||||||
wchar_t *out = NULL;
|
}
|
||||||
int len = 0;
|
|
||||||
|
char strvFront(strview_t ctx) {
|
||||||
if (strvIsEmpty(ctx)) {
|
return ctx.len > 0 ? ctx.buf[0] : '\0';
|
||||||
goto error;
|
}
|
||||||
}
|
|
||||||
|
char strvBack(strview_t ctx) {
|
||||||
#if COLLA_WIN
|
return ctx.len > 0 ? ctx.buf[ctx.len - 1] : '\0';
|
||||||
len = MultiByteToWideChar(
|
}
|
||||||
CP_UTF8, 0,
|
|
||||||
ctx.buf, (int)ctx.len,
|
|
||||||
NULL, 0
|
wchar_t *strvToWChar(arena_t *arena, strview_t ctx, usize *outlen) {
|
||||||
);
|
wchar_t *out = NULL;
|
||||||
|
int len = 0;
|
||||||
if (len == 0) {
|
|
||||||
unsigned long error = GetLastError();
|
if (strvIsEmpty(ctx)) {
|
||||||
if (error == ERROR_NO_UNICODE_TRANSLATION) {
|
goto error;
|
||||||
err("couldn't translate string (%v) to a wide string, no unicode translation", ctx);
|
}
|
||||||
}
|
|
||||||
else {
|
#if COLLA_WIN
|
||||||
err("couldn't translate string (%v) to a wide string, %u", ctx, error);
|
len = MultiByteToWideChar(
|
||||||
}
|
CP_UTF8, 0,
|
||||||
|
ctx.buf, (int)ctx.len,
|
||||||
goto error;
|
NULL, 0
|
||||||
}
|
);
|
||||||
|
|
||||||
out = alloc(arena, wchar_t, len + 1);
|
if (len == 0) {
|
||||||
|
unsigned long error = GetLastError();
|
||||||
MultiByteToWideChar(
|
if (error == ERROR_NO_UNICODE_TRANSLATION) {
|
||||||
CP_UTF8, 0,
|
err("couldn't translate string (%v) to a wide string, no unicode translation", ctx);
|
||||||
ctx.buf, (int)ctx.len,
|
}
|
||||||
out, len
|
else {
|
||||||
);
|
err("couldn't translate string (%v) to a wide string, %u", ctx, error);
|
||||||
|
}
|
||||||
#elif COLLA_LIN
|
|
||||||
fatal("strFromWChar not implemented yet!");
|
goto error;
|
||||||
#endif
|
}
|
||||||
|
|
||||||
error:
|
out = alloc(arena, wchar_t, len + 1);
|
||||||
if (outlen) {
|
|
||||||
*outlen = (usize)len;
|
MultiByteToWideChar(
|
||||||
}
|
CP_UTF8, 0,
|
||||||
return out;
|
ctx.buf, (int)ctx.len,
|
||||||
}
|
out, len
|
||||||
|
);
|
||||||
TCHAR *strvToTChar(arena_t *arena, strview_t str) {
|
|
||||||
#if UNICODE
|
#elif COLLA_LIN
|
||||||
return strvToWChar(arena, str, NULL);
|
fatal("strFromWChar not implemented yet!");
|
||||||
#else
|
#endif
|
||||||
char *cstr = alloc(arena, char, str.len + 1);
|
|
||||||
memcpy(cstr, str.buf, str.len);
|
error:
|
||||||
return cstr;
|
if (outlen) {
|
||||||
#endif
|
*outlen = (usize)len;
|
||||||
}
|
}
|
||||||
|
return out;
|
||||||
strview_t strvRemovePrefix(strview_t ctx, usize n) {
|
}
|
||||||
if (n > ctx.len) n = ctx.len;
|
|
||||||
return (strview_t){
|
TCHAR *strvToTChar(arena_t *arena, strview_t str) {
|
||||||
.buf = ctx.buf + n,
|
#if UNICODE
|
||||||
.len = ctx.len - n,
|
return strvToWChar(arena, str, NULL);
|
||||||
};
|
#else
|
||||||
}
|
char *cstr = alloc(arena, char, str.len + 1);
|
||||||
|
memcpy(cstr, str.buf, str.len);
|
||||||
strview_t strvRemoveSuffix(strview_t ctx, usize n) {
|
return cstr;
|
||||||
if (n > ctx.len) n = ctx.len;
|
#endif
|
||||||
return (strview_t){
|
}
|
||||||
.buf = ctx.buf,
|
|
||||||
.len = ctx.len - n,
|
strview_t strvRemovePrefix(strview_t ctx, usize n) {
|
||||||
};
|
if (n > ctx.len) n = ctx.len;
|
||||||
}
|
return (strview_t){
|
||||||
|
.buf = ctx.buf + n,
|
||||||
strview_t strvTrim(strview_t ctx) {
|
.len = ctx.len - n,
|
||||||
return strvTrimLeft(strvTrimRight(ctx));
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
strview_t strvTrimLeft(strview_t ctx) {
|
strview_t strvRemoveSuffix(strview_t ctx, usize n) {
|
||||||
strview_t out = ctx;
|
if (n > ctx.len) n = ctx.len;
|
||||||
for (usize i = 0; i < ctx.len; ++i) {
|
return (strview_t){
|
||||||
char c = ctx.buf[i];
|
.buf = ctx.buf,
|
||||||
if (c != ' ' && (c < '\t' || c > '\r')) {
|
.len = ctx.len - n,
|
||||||
break;
|
};
|
||||||
}
|
}
|
||||||
out.buf++;
|
|
||||||
out.len--;
|
strview_t strvTrim(strview_t ctx) {
|
||||||
}
|
return strvTrimLeft(strvTrimRight(ctx));
|
||||||
return out;
|
}
|
||||||
}
|
|
||||||
|
strview_t strvTrimLeft(strview_t ctx) {
|
||||||
strview_t strvTrimRight(strview_t ctx) {
|
strview_t out = ctx;
|
||||||
strview_t out = ctx;
|
for (usize i = 0; i < ctx.len; ++i) {
|
||||||
for (isize i = ctx.len - 1; i >= 0; --i) {
|
char c = ctx.buf[i];
|
||||||
char c = ctx.buf[i];
|
if (c != ' ' && (c < '\t' || c > '\r')) {
|
||||||
if (c != ' ' && (c < '\t' || c > '\r')) {
|
break;
|
||||||
break;
|
}
|
||||||
}
|
out.buf++;
|
||||||
out.len--;
|
out.len--;
|
||||||
}
|
}
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
strview_t strvSub(strview_t ctx, usize from, usize to) {
|
strview_t strvTrimRight(strview_t ctx) {
|
||||||
if (to > ctx.len) to = ctx.len;
|
strview_t out = ctx;
|
||||||
if (from > to) from = to;
|
for (isize i = ctx.len - 1; i >= 0; --i) {
|
||||||
return (strview_t){ ctx.buf + from, to - from };
|
char c = ctx.buf[i];
|
||||||
}
|
if (c != ' ' && (c < '\t' || c > '\r')) {
|
||||||
|
break;
|
||||||
bool strvStartsWith(strview_t ctx, char c) {
|
}
|
||||||
return ctx.len > 0 && ctx.buf[0] == c;
|
out.len--;
|
||||||
}
|
}
|
||||||
|
return out;
|
||||||
bool strvStartsWithView(strview_t ctx, strview_t view) {
|
}
|
||||||
return ctx.len >= view.len && memcmp(ctx.buf, view.buf, view.len) == 0;
|
|
||||||
}
|
strview_t strvSub(strview_t ctx, usize from, usize to) {
|
||||||
|
if (to > ctx.len) to = ctx.len;
|
||||||
bool strvEndsWith(strview_t ctx, char c) {
|
if (from > to) from = to;
|
||||||
return ctx.len > 0 && ctx.buf[ctx.len - 1] == c;
|
return (strview_t){ ctx.buf + from, to - from };
|
||||||
}
|
}
|
||||||
|
|
||||||
bool strvEndsWithView(strview_t ctx, strview_t view) {
|
bool strvStartsWith(strview_t ctx, char c) {
|
||||||
return ctx.len >= view.len && memcmp(ctx.buf + ctx.len, view.buf, view.len) == 0;
|
return ctx.len > 0 && ctx.buf[0] == c;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool strvContains(strview_t ctx, char c) {
|
bool strvStartsWithView(strview_t ctx, strview_t view) {
|
||||||
for(usize i = 0; i < ctx.len; ++i) {
|
return ctx.len >= view.len && memcmp(ctx.buf, view.buf, view.len) == 0;
|
||||||
if(ctx.buf[i] == c) {
|
}
|
||||||
return true;
|
|
||||||
}
|
bool strvEndsWith(strview_t ctx, char c) {
|
||||||
}
|
return ctx.len > 0 && ctx.buf[ctx.len - 1] == c;
|
||||||
return false;
|
}
|
||||||
}
|
|
||||||
|
bool strvEndsWithView(strview_t ctx, strview_t view) {
|
||||||
bool strvContainsView(strview_t ctx, strview_t view) {
|
return ctx.len >= view.len && memcmp(ctx.buf + ctx.len - view.len, view.buf, view.len) == 0;
|
||||||
if (ctx.len < view.len) return false;
|
}
|
||||||
usize end = ctx.len - view.len;
|
|
||||||
for (usize i = 0; i < end; ++i) {
|
bool strvContains(strview_t ctx, char c) {
|
||||||
if (memcmp(ctx.buf + i, view.buf, view.len) == 0) {
|
for(usize i = 0; i < ctx.len; ++i) {
|
||||||
return true;
|
if(ctx.buf[i] == c) {
|
||||||
}
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
}
|
||||||
}
|
return false;
|
||||||
|
}
|
||||||
usize strvFind(strview_t ctx, char c, usize from) {
|
|
||||||
for (usize i = from; i < ctx.len; ++i) {
|
bool strvContainsView(strview_t ctx, strview_t view) {
|
||||||
if (ctx.buf[i] == c) {
|
if (ctx.len < view.len) return false;
|
||||||
return i;
|
usize end = ctx.len - view.len;
|
||||||
}
|
for (usize i = 0; i < end; ++i) {
|
||||||
}
|
if (memcmp(ctx.buf + i, view.buf, view.len) == 0) {
|
||||||
return STR_NONE;
|
return true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
usize strvFindView(strview_t ctx, strview_t view, usize from) {
|
return false;
|
||||||
if (ctx.len < view.len) return STR_NONE;
|
}
|
||||||
usize end = ctx.len - view.len;
|
|
||||||
for (usize i = from; i < end; ++i) {
|
usize strvFind(strview_t ctx, char c, usize from) {
|
||||||
if (memcmp(ctx.buf + i, view.buf, view.len) == 0) {
|
for (usize i = from; i < ctx.len; ++i) {
|
||||||
return i;
|
if (ctx.buf[i] == c) {
|
||||||
}
|
return i;
|
||||||
}
|
}
|
||||||
return STR_NONE;
|
}
|
||||||
}
|
return STR_NONE;
|
||||||
|
}
|
||||||
usize strvRFind(strview_t ctx, char c, usize from_right) {
|
|
||||||
if (from_right > ctx.len) from_right = ctx.len;
|
usize strvFindView(strview_t ctx, strview_t view, usize from) {
|
||||||
isize end = (isize)(ctx.len - from_right);
|
if (ctx.len < view.len) return STR_NONE;
|
||||||
for (isize i = end; i >= 0; --i) {
|
usize end = ctx.len - view.len;
|
||||||
if (ctx.buf[i] == c) {
|
for (usize i = from; i < end; ++i) {
|
||||||
return (usize)i;
|
if (memcmp(ctx.buf + i, view.buf, view.len) == 0) {
|
||||||
}
|
return i;
|
||||||
}
|
}
|
||||||
return STR_NONE;
|
}
|
||||||
}
|
return STR_NONE;
|
||||||
|
}
|
||||||
usize strvRFindView(strview_t ctx, strview_t view, usize from_right) {
|
|
||||||
if (from_right > ctx.len) from_right = ctx.len;
|
usize strvRFind(strview_t ctx, char c, usize from_right) {
|
||||||
isize end = (isize)(ctx.len - from_right);
|
if (from_right > ctx.len) from_right = ctx.len;
|
||||||
if (end < (isize)view.len) return STR_NONE;
|
isize end = (isize)(ctx.len - from_right);
|
||||||
for (isize i = end - view.len; i >= 0; --i) {
|
for (isize i = end; i >= 0; --i) {
|
||||||
if (memcmp(ctx.buf + i, view.buf, view.len) == 0) {
|
if (ctx.buf[i] == c) {
|
||||||
return (usize)i;
|
return (usize)i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return STR_NONE;
|
return STR_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
#include "warnings/colla_warn_beg.h"
|
usize strvRFindView(strview_t ctx, strview_t view, usize from_right) {
|
||||||
|
if (from_right > ctx.len) from_right = ctx.len;
|
||||||
#undef CP_UTF8
|
isize end = (isize)(ctx.len - from_right);
|
||||||
#undef ERROR_NO_UNICODE_TRANSLATION
|
if (end < (isize)view.len) return STR_NONE;
|
||||||
|
for (isize i = end - view.len; i >= 0; --i) {
|
||||||
|
if (memcmp(ctx.buf + i, view.buf, view.len) == 0) {
|
||||||
|
return (usize)i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return STR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
#include "warnings/colla_warn_beg.h"
|
||||||
|
|
@ -1,109 +1,119 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <stdarg.h> // va_list
|
#include <stdarg.h> // va_list
|
||||||
#include <string.h> // strlen
|
#include <string.h> // strlen
|
||||||
|
|
||||||
#include "collatypes.h"
|
#include "collatypes.h"
|
||||||
|
|
||||||
typedef struct arena_t arena_t;
|
typedef struct arena_t arena_t;
|
||||||
|
|
||||||
#define STR_NONE SIZE_MAX
|
#define STR_NONE SIZE_MAX
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
char *buf;
|
char *buf;
|
||||||
usize len;
|
usize len;
|
||||||
} str_t;
|
} str_t;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
const char *buf;
|
const char *buf;
|
||||||
usize len;
|
usize len;
|
||||||
} strview_t;
|
} strview_t;
|
||||||
|
|
||||||
// == STR_T ========================================================
|
// == STR_T ========================================================
|
||||||
|
|
||||||
#define str__1(arena, x) \
|
#define str__1(arena, x) \
|
||||||
_Generic((x), \
|
_Generic((x), \
|
||||||
const char *: strInit, \
|
const char *: strInit, \
|
||||||
char *: strInit, \
|
char *: strInit, \
|
||||||
strview_t: strInitView \
|
const wchar_t *: strFromWChar, \
|
||||||
)(arena, x)
|
wchar_t *: strFromWChar, \
|
||||||
|
strview_t: strInitView \
|
||||||
#define str__2(arena, cstr, clen) strInitLen(arena, cstr, clen)
|
)(arena, x)
|
||||||
#define str__impl(_1, _2, n, ...) str__##n
|
|
||||||
|
#define str__2(arena, cstr, clen) strInitLen(arena, cstr, clen)
|
||||||
// either:
|
#define str__impl(_1, _2, n, ...) str__##n
|
||||||
// arena_t arena, [const] char *cstr, [usize len]
|
|
||||||
// arena_t arena, strview_t view
|
// either:
|
||||||
#define str(arena, ...) str__impl(__VA_ARGS__, 2, 1, 0)(arena, __VA_ARGS__)
|
// arena_t arena, [const] char *cstr, [usize len]
|
||||||
|
// arena_t arena, strview_t view
|
||||||
str_t strInit(arena_t *arena, const char *buf);
|
#define str(arena, ...) str__impl(__VA_ARGS__, 2, 1, 0)(arena, __VA_ARGS__)
|
||||||
str_t strInitLen(arena_t *arena, const char *buf, usize len);
|
|
||||||
str_t strInitView(arena_t *arena, strview_t view);
|
#define STR_EMPTY (str_t){0}
|
||||||
str_t strFmt(arena_t *arena, const char *fmt, ...);
|
|
||||||
str_t strFmtv(arena_t *arena, const char *fmt, va_list args);
|
str_t strInit(arena_t *arena, const char *buf);
|
||||||
|
str_t strInitLen(arena_t *arena, const char *buf, usize len);
|
||||||
str_t strFromWChar(arena_t *arena, const wchar_t *src, usize srclen);
|
str_t strInitView(arena_t *arena, strview_t view);
|
||||||
|
str_t strFmt(arena_t *arena, const char *fmt, ...);
|
||||||
bool strEquals(str_t a, str_t b);
|
str_t strFmtv(arena_t *arena, const char *fmt, va_list args);
|
||||||
int strCompare(str_t a, str_t b);
|
|
||||||
|
str_t strFromWChar(arena_t *arena, const wchar_t *src);
|
||||||
str_t strDup(arena_t *arena, str_t src);
|
str_t strFromWCharLen(arena_t *arena, const wchar_t *src, usize srclen);
|
||||||
bool strIsEmpty(str_t ctx);
|
|
||||||
|
bool strEquals(str_t a, str_t b);
|
||||||
void strReplace(str_t *ctx, char from, char to);
|
int strCompare(str_t a, str_t b);
|
||||||
// if len == SIZE_MAX, copies until end
|
|
||||||
strview_t strSub(str_t ctx, usize from, usize to);
|
str_t strDup(arena_t *arena, str_t src);
|
||||||
|
bool strIsEmpty(str_t ctx);
|
||||||
void strLower(str_t *ctx);
|
|
||||||
void strUpper(str_t *ctx);
|
void strReplace(str_t *ctx, char from, char to);
|
||||||
|
// if len == SIZE_MAX, copies until end
|
||||||
str_t strToLower(arena_t *arena, str_t ctx);
|
strview_t strSub(str_t ctx, usize from, usize to);
|
||||||
str_t strToUpper(arena_t *arena, str_t ctx);
|
|
||||||
|
void strLower(str_t *ctx);
|
||||||
// == STRVIEW_T ====================================================
|
void strUpper(str_t *ctx);
|
||||||
|
|
||||||
#define strv__1(x) \
|
str_t strToLower(arena_t *arena, str_t ctx);
|
||||||
_Generic((x), \
|
str_t strToUpper(arena_t *arena, str_t ctx);
|
||||||
char *: strvInit, \
|
|
||||||
const char *: strvInit, \
|
// == STRVIEW_T ====================================================
|
||||||
str_t: strvInitStr \
|
|
||||||
)(x)
|
#define strv__1(x) \
|
||||||
|
_Generic((x), \
|
||||||
#define strv__2(cstr, clen) strvInitLen(cstr, clen)
|
char *: strvInit, \
|
||||||
#define strv__impl(_1, _2, n, ...) strv__##n
|
const char *: strvInit, \
|
||||||
|
str_t: strvInitStr \
|
||||||
#define strv(...) strv__impl(__VA_ARGS__, 2, 1, 0)(__VA_ARGS__)
|
)(x)
|
||||||
|
|
||||||
strview_t strvInit(const char *cstr);
|
#define strv__2(cstr, clen) strvInitLen(cstr, clen)
|
||||||
strview_t strvInitLen(const char *buf, usize size);
|
#define strv__impl(_1, _2, n, ...) strv__##n
|
||||||
strview_t strvInitStr(str_t str);
|
|
||||||
|
#define strv(...) strv__impl(__VA_ARGS__, 2, 1, 0)(__VA_ARGS__)
|
||||||
bool strvIsEmpty(strview_t ctx);
|
|
||||||
bool strvEquals(strview_t a, strview_t b);
|
#define STRV_EMPTY (strview_t){0}
|
||||||
int strvCompare(strview_t a, strview_t b);
|
|
||||||
|
strview_t strvInit(const char *cstr);
|
||||||
wchar_t *strvToWChar(arena_t *arena, strview_t ctx, usize *outlen);
|
strview_t strvInitLen(const char *buf, usize size);
|
||||||
TCHAR *strvToTChar(arena_t *arena, strview_t str);
|
strview_t strvInitStr(str_t str);
|
||||||
|
|
||||||
strview_t strvRemovePrefix(strview_t ctx, usize n);
|
bool strvIsEmpty(strview_t ctx);
|
||||||
strview_t strvRemoveSuffix(strview_t ctx, usize n);
|
bool strvEquals(strview_t a, strview_t b);
|
||||||
strview_t strvTrim(strview_t ctx);
|
int strvCompare(strview_t a, strview_t b);
|
||||||
strview_t strvTrimLeft(strview_t ctx);
|
|
||||||
strview_t strvTrimRight(strview_t ctx);
|
char strvFront(strview_t ctx);
|
||||||
|
char strvBack(strview_t ctx);
|
||||||
strview_t strvSub(strview_t ctx, usize from, usize to);
|
|
||||||
|
wchar_t *strvToWChar(arena_t *arena, strview_t ctx, usize *outlen);
|
||||||
bool strvStartsWith(strview_t ctx, char c);
|
TCHAR *strvToTChar(arena_t *arena, strview_t str);
|
||||||
bool strvStartsWithView(strview_t ctx, strview_t view);
|
|
||||||
|
strview_t strvRemovePrefix(strview_t ctx, usize n);
|
||||||
bool strvEndsWith(strview_t ctx, char c);
|
strview_t strvRemoveSuffix(strview_t ctx, usize n);
|
||||||
bool strvEndsWithView(strview_t ctx, strview_t view);
|
strview_t strvTrim(strview_t ctx);
|
||||||
|
strview_t strvTrimLeft(strview_t ctx);
|
||||||
bool strvContains(strview_t ctx, char c);
|
strview_t strvTrimRight(strview_t ctx);
|
||||||
bool strvContainsView(strview_t ctx, strview_t view);
|
|
||||||
|
strview_t strvSub(strview_t ctx, usize from, usize to);
|
||||||
usize strvFind(strview_t ctx, char c, usize from);
|
|
||||||
usize strvFindView(strview_t ctx, strview_t view, usize from);
|
bool strvStartsWith(strview_t ctx, char c);
|
||||||
|
bool strvStartsWithView(strview_t ctx, strview_t view);
|
||||||
usize strvRFind(strview_t ctx, char c, usize from_right);
|
|
||||||
usize strvRFindView(strview_t ctx, strview_t view, usize from_right);
|
bool strvEndsWith(strview_t ctx, char c);
|
||||||
|
bool strvEndsWithView(strview_t ctx, strview_t view);
|
||||||
|
|
||||||
|
bool strvContains(strview_t ctx, char c);
|
||||||
|
bool strvContainsView(strview_t ctx, strview_t view);
|
||||||
|
|
||||||
|
usize strvFind(strview_t ctx, char c, usize from);
|
||||||
|
usize strvFindView(strview_t ctx, strview_t view, usize from);
|
||||||
|
|
||||||
|
usize strvRFind(strview_t ctx, char c, usize from_right);
|
||||||
|
usize strvRFindView(strview_t ctx, strview_t view, usize from_right);
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,155 +1,160 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
|
|
||||||
#include "collatypes.h"
|
#include "collatypes.h"
|
||||||
#include "str.h"
|
#include "str.h"
|
||||||
|
|
||||||
typedef struct arena_t arena_t;
|
typedef struct arena_t arena_t;
|
||||||
|
|
||||||
/* == INPUT STREAM ============================================ */
|
/* == INPUT STREAM ============================================ */
|
||||||
|
|
||||||
typedef struct {
|
typedef struct instream_t {
|
||||||
const char *start;
|
const char *start;
|
||||||
const char *cur;
|
const char *cur;
|
||||||
usize size;
|
usize size;
|
||||||
} instream_t;
|
} instream_t;
|
||||||
|
|
||||||
// initialize with null-terminated string
|
// initialize with null-terminated string
|
||||||
instream_t istrInit(const char *str);
|
instream_t istrInit(const char *str);
|
||||||
instream_t istrInitLen(const char *str, usize len);
|
instream_t istrInitLen(const char *str, usize len);
|
||||||
|
|
||||||
// get the current character and advance
|
// get the current character and advance
|
||||||
char istrGet(instream_t *ctx);
|
char istrGet(instream_t *ctx);
|
||||||
// get the current character but don't advance
|
// get the current character but don't advance
|
||||||
char istrPeek(instream_t *ctx);
|
char istrPeek(instream_t *ctx);
|
||||||
// get the next character but don't advance
|
// get the next character but don't advance
|
||||||
char istrPeekNext(instream_t *ctx);
|
char istrPeekNext(instream_t *ctx);
|
||||||
// ignore characters until the delimiter
|
// returns the previous character
|
||||||
void istrIgnore(instream_t *ctx, char delim);
|
char istrPrev(instream_t *ctx);
|
||||||
// ignore characters until the delimiter and skip it
|
// returns the character before the previous
|
||||||
void istrIgnoreAndSkip(instream_t *ctx, char delim);
|
char istrPrevPrev(instream_t *ctx);
|
||||||
// skip n characters
|
// ignore characters until the delimiter
|
||||||
void istrSkip(instream_t *ctx, usize n);
|
void istrIgnore(instream_t *ctx, char delim);
|
||||||
// skips whitespace (' ', '\\n', '\\t', '\\r')
|
// ignore characters until the delimiter and skip it
|
||||||
void istrSkipWhitespace(instream_t *ctx);
|
void istrIgnoreAndSkip(instream_t *ctx, char delim);
|
||||||
// read len bytes into buffer, the buffer will not be null terminated
|
// skip n characters
|
||||||
void istrRead(instream_t *ctx, char *buf, usize len);
|
void istrSkip(instream_t *ctx, usize n);
|
||||||
// read a maximum of len bytes into buffer, the buffer will not be null terminated
|
// skips whitespace (' ', '\\n', '\\t', '\\r')
|
||||||
// returns the number of bytes read
|
void istrSkipWhitespace(instream_t *ctx);
|
||||||
usize istrReadMax(instream_t *ctx, char *buf, usize len);
|
// read len bytes into buffer, the buffer will not be null terminated
|
||||||
// returns to the beginning of the stream
|
void istrRead(instream_t *ctx, char *buf, usize len);
|
||||||
void istrRewind(instream_t *ctx);
|
// read a maximum of len bytes into buffer, the buffer will not be null terminated
|
||||||
// returns back <amount> characters
|
// returns the number of bytes read
|
||||||
void istrRewindN(instream_t *ctx, usize amount);
|
usize istrReadMax(instream_t *ctx, char *buf, usize len);
|
||||||
// returns the number of bytes read from beginning of stream
|
// returns to the beginning of the stream
|
||||||
usize istrTell(instream_t ctx);
|
void istrRewind(instream_t *ctx);
|
||||||
// returns the number of bytes left to read in the stream
|
// returns back <amount> characters
|
||||||
usize istrRemaining(instream_t ctx);
|
void istrRewindN(instream_t *ctx, usize amount);
|
||||||
// return true if the stream doesn't have any new bytes to read
|
// returns the number of bytes read from beginning of stream
|
||||||
bool istrIsFinished(instream_t ctx);
|
usize istrTell(instream_t ctx);
|
||||||
|
// returns the number of bytes left to read in the stream
|
||||||
bool istrGetBool(instream_t *ctx, bool *val);
|
usize istrRemaining(instream_t ctx);
|
||||||
bool istrGetU8(instream_t *ctx, uint8 *val);
|
// return true if the stream doesn't have any new bytes to read
|
||||||
bool istrGetU16(instream_t *ctx, uint16 *val);
|
bool istrIsFinished(instream_t ctx);
|
||||||
bool istrGetU32(instream_t *ctx, uint32 *val);
|
|
||||||
bool istrGetU64(instream_t *ctx, uint64 *val);
|
bool istrGetBool(instream_t *ctx, bool *val);
|
||||||
bool istrGetI8(instream_t *ctx, int8 *val);
|
bool istrGetU8(instream_t *ctx, uint8 *val);
|
||||||
bool istrGetI16(instream_t *ctx, int16 *val);
|
bool istrGetU16(instream_t *ctx, uint16 *val);
|
||||||
bool istrGetI32(instream_t *ctx, int32 *val);
|
bool istrGetU32(instream_t *ctx, uint32 *val);
|
||||||
bool istrGetI64(instream_t *ctx, int64 *val);
|
bool istrGetU64(instream_t *ctx, uint64 *val);
|
||||||
bool istrGetFloat(instream_t *ctx, float *val);
|
bool istrGetI8(instream_t *ctx, int8 *val);
|
||||||
bool istrGetDouble(instream_t *ctx, double *val);
|
bool istrGetI16(instream_t *ctx, int16 *val);
|
||||||
str_t istrGetStr(arena_t *arena, instream_t *ctx, char delim);
|
bool istrGetI32(instream_t *ctx, int32 *val);
|
||||||
// get a string of maximum size len, the string is not allocated by the function and will be null terminated
|
bool istrGetI64(instream_t *ctx, int64 *val);
|
||||||
usize istrGetBuf(instream_t *ctx, char *buf, usize buflen);
|
bool istrGetFloat(instream_t *ctx, float *val);
|
||||||
strview_t istrGetView(instream_t *ctx, char delim);
|
bool istrGetDouble(instream_t *ctx, double *val);
|
||||||
strview_t istrGetViewEither(instream_t *ctx, strview_t chars);
|
str_t istrGetStr(arena_t *arena, instream_t *ctx, char delim);
|
||||||
strview_t istrGetViewLen(instream_t *ctx, usize len);
|
// get a string of maximum size len, the string is not allocated by the function and will be null terminated
|
||||||
|
usize istrGetBuf(instream_t *ctx, char *buf, usize buflen);
|
||||||
/* == OUTPUT STREAM =========================================== */
|
strview_t istrGetView(instream_t *ctx, char delim);
|
||||||
|
strview_t istrGetViewEither(instream_t *ctx, strview_t chars);
|
||||||
typedef struct {
|
strview_t istrGetViewLen(instream_t *ctx, usize len);
|
||||||
char *beg;
|
strview_t istrGetLine(instream_t *ctx);
|
||||||
arena_t *arena;
|
|
||||||
} outstream_t;
|
/* == OUTPUT STREAM =========================================== */
|
||||||
|
|
||||||
outstream_t ostrInit(arena_t *exclusive_arena);
|
typedef struct outstream_t {
|
||||||
void ostrClear(outstream_t *ctx);
|
char *beg;
|
||||||
|
arena_t *arena;
|
||||||
usize ostrTell(outstream_t *ctx);
|
} outstream_t;
|
||||||
|
|
||||||
char ostrBack(outstream_t *ctx);
|
outstream_t ostrInit(arena_t *exclusive_arena);
|
||||||
str_t ostrAsStr(outstream_t *ctx);
|
void ostrClear(outstream_t *ctx);
|
||||||
strview_t ostrAsView(outstream_t *ctx);
|
|
||||||
|
usize ostrTell(outstream_t *ctx);
|
||||||
void ostrPrintf(outstream_t *ctx, const char *fmt, ...);
|
|
||||||
void ostrPrintfV(outstream_t *ctx, const char *fmt, va_list args);
|
char ostrBack(outstream_t *ctx);
|
||||||
void ostrPutc(outstream_t *ctx, char c);
|
str_t ostrAsStr(outstream_t *ctx);
|
||||||
void ostrPuts(outstream_t *ctx, strview_t v);
|
strview_t ostrAsView(outstream_t *ctx);
|
||||||
|
|
||||||
void ostrAppendBool(outstream_t *ctx, bool val);
|
void ostrPrintf(outstream_t *ctx, const char *fmt, ...);
|
||||||
void ostrAppendUInt(outstream_t *ctx, uint64 val);
|
void ostrPrintfV(outstream_t *ctx, const char *fmt, va_list args);
|
||||||
void ostrAppendInt(outstream_t *ctx, int64 val);
|
void ostrPutc(outstream_t *ctx, char c);
|
||||||
void ostrAppendNum(outstream_t *ctx, double val);
|
void ostrPuts(outstream_t *ctx, strview_t v);
|
||||||
|
|
||||||
/* == OUT BYTE STREAM ========================================= */
|
void ostrAppendBool(outstream_t *ctx, bool val);
|
||||||
|
void ostrAppendUInt(outstream_t *ctx, uint64 val);
|
||||||
typedef struct {
|
void ostrAppendInt(outstream_t *ctx, int64 val);
|
||||||
uint8 *beg;
|
void ostrAppendNum(outstream_t *ctx, double val);
|
||||||
arena_t *arena;
|
|
||||||
} obytestream_t;
|
/* == OUT BYTE STREAM ========================================= */
|
||||||
|
|
||||||
obytestream_t obstrInit(arena_t *exclusive_arena);
|
typedef struct {
|
||||||
void obstrClear(obytestream_t *ctx);
|
uint8 *beg;
|
||||||
|
arena_t *arena;
|
||||||
usize obstrTell(obytestream_t *ctx);
|
} obytestream_t;
|
||||||
buffer_t obstrAsBuf(obytestream_t *ctx);
|
|
||||||
|
obytestream_t obstrInit(arena_t *exclusive_arena);
|
||||||
void obstrWrite(obytestream_t *ctx, const void *buf, usize buflen);
|
void obstrClear(obytestream_t *ctx);
|
||||||
void obstrPuts(obytestream_t *ctx, strview_t str);
|
|
||||||
void obstrAppendU8(obytestream_t *ctx, uint8 value);
|
usize obstrTell(obytestream_t *ctx);
|
||||||
void obstrAppendU16(obytestream_t *ctx, uint16 value);
|
buffer_t obstrAsBuf(obytestream_t *ctx);
|
||||||
void obstrAppendU32(obytestream_t *ctx, uint32 value);
|
|
||||||
void obstrAppendU64(obytestream_t *ctx, uint64 value);
|
void obstrWrite(obytestream_t *ctx, const void *buf, usize buflen);
|
||||||
void obstrAppendI8(obytestream_t *ctx, int8 value);
|
void obstrPuts(obytestream_t *ctx, strview_t str);
|
||||||
void obstrAppendI16(obytestream_t *ctx, int16 value);
|
void obstrAppendU8(obytestream_t *ctx, uint8 value);
|
||||||
void obstrAppendI32(obytestream_t *ctx, int32 value);
|
void obstrAppendU16(obytestream_t *ctx, uint16 value);
|
||||||
void obstrAppendI64(obytestream_t *ctx, int64 value);
|
void obstrAppendU32(obytestream_t *ctx, uint32 value);
|
||||||
|
void obstrAppendU64(obytestream_t *ctx, uint64 value);
|
||||||
/* == IN BYTE STREAM ========================================== */
|
void obstrAppendI8(obytestream_t *ctx, int8 value);
|
||||||
|
void obstrAppendI16(obytestream_t *ctx, int16 value);
|
||||||
typedef struct {
|
void obstrAppendI32(obytestream_t *ctx, int32 value);
|
||||||
const uint8 *start;
|
void obstrAppendI64(obytestream_t *ctx, int64 value);
|
||||||
const uint8 *cur;
|
|
||||||
usize size;
|
/* == IN BYTE STREAM ========================================== */
|
||||||
} ibytestream_t;
|
|
||||||
|
typedef struct {
|
||||||
ibytestream_t ibstrInit(const void *buf, usize len);
|
const uint8 *start;
|
||||||
|
const uint8 *cur;
|
||||||
usize ibstrRemaining(ibytestream_t *ctx);
|
usize size;
|
||||||
usize ibstrTell(ibytestream_t *ctx);
|
} ibytestream_t;
|
||||||
usize ibstrRead(ibytestream_t *ctx, void *buf, usize buflen);
|
|
||||||
|
ibytestream_t ibstrInit(const void *buf, usize len);
|
||||||
uint8 ibstrGetU8(ibytestream_t *ctx);
|
|
||||||
uint16 ibstrGetU16(ibytestream_t *ctx);
|
usize ibstrRemaining(ibytestream_t *ctx);
|
||||||
uint32 ibstrGetU32(ibytestream_t *ctx);
|
usize ibstrTell(ibytestream_t *ctx);
|
||||||
uint64 ibstrGetU64(ibytestream_t *ctx);
|
usize ibstrRead(ibytestream_t *ctx, void *buf, usize buflen);
|
||||||
int8 ibstrGetI8(ibytestream_t *ctx);
|
|
||||||
int16 ibstrGetI16(ibytestream_t *ctx);
|
uint8 ibstrGetU8(ibytestream_t *ctx);
|
||||||
int32 ibstrGetI32(ibytestream_t *ctx);
|
uint16 ibstrGetU16(ibytestream_t *ctx);
|
||||||
int64 ibstrGetI64(ibytestream_t *ctx);
|
uint32 ibstrGetU32(ibytestream_t *ctx);
|
||||||
float ibstrGetFloat(ibytestream_t *ctx);
|
uint64 ibstrGetU64(ibytestream_t *ctx);
|
||||||
double ibstrGetDouble(ibytestream_t *ctx);
|
int8 ibstrGetI8(ibytestream_t *ctx);
|
||||||
|
int16 ibstrGetI16(ibytestream_t *ctx);
|
||||||
// reads a string, before reads <lensize> bytes for the length (e.g. sizeof(uint32))
|
int32 ibstrGetI32(ibytestream_t *ctx);
|
||||||
// then reads sizeof(char) * strlen
|
int64 ibstrGetI64(ibytestream_t *ctx);
|
||||||
strview_t ibstrGetView(ibytestream_t *ctx, usize lensize);
|
float ibstrGetFloat(ibytestream_t *ctx);
|
||||||
|
double ibstrGetDouble(ibytestream_t *ctx);
|
||||||
#ifdef __cplusplus
|
|
||||||
} // extern "C"
|
// reads a string, before reads <lensize> bytes for the length (e.g. sizeof(uint32))
|
||||||
#endif
|
// then reads sizeof(char) * strlen
|
||||||
|
strview_t ibstrGetView(ibytestream_t *ctx, usize lensize);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
} // extern "C"
|
||||||
|
#endif
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#if COLLA_TCC || 1
|
#if COLLA_TCC
|
||||||
|
|
||||||
#include <Windows.h>
|
#include <Windows.h>
|
||||||
|
|
||||||
8
tools/build
Normal file
8
tools/build
Normal 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
429
tools/docs.c
Normal 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"
|
||||||
|
"";
|
||||||
|
|
@ -1,219 +1,212 @@
|
||||||
#include "tracelog.h"
|
#include "tracelog.h"
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
#include "format.h"
|
#include "format.h"
|
||||||
|
|
||||||
#if COLLA_WIN
|
#if COLLA_WIN
|
||||||
#if COLLA_MSVC
|
#if COLLA_MSVC
|
||||||
#pragma warning(disable:4996) // _CRT_SECURE_NO_WARNINGS.
|
#pragma warning(disable:4996) // _CRT_SECURE_NO_WARNINGS.
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
|
|
||||||
#if COLLA_CMT_LIB
|
#if COLLA_CMT_LIB
|
||||||
#pragma comment(lib, "User32")
|
#pragma comment(lib, "User32")
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// avoid including windows.h
|
#if COLLA_TCC
|
||||||
|
#include "tcc/colla_tcc.h"
|
||||||
#ifndef STD_OUTPUT_HANDLE
|
#endif
|
||||||
#define STD_OUTPUT_HANDLE ((DWORD)-11)
|
|
||||||
#endif
|
//#ifndef TLOG_NO_COLOURS
|
||||||
#ifndef CP_UTF8
|
// #define TLOG_NO_COLOURS
|
||||||
#define CP_UTF8 65001
|
//#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef TLOG_NO_COLOURS
|
#if COLLA_EMC
|
||||||
#define TLOG_NO_COLOURS
|
#define TLOG_NO_COLOURS
|
||||||
#endif
|
#endif
|
||||||
#endif
|
|
||||||
|
#if COLLA_EMC
|
||||||
#if COLLA_EMC
|
#define COLOUR_BLACK "<span class=\"black\">"
|
||||||
#define TLOG_NO_COLOURS
|
#define COLOUR_RED "<span class=\"red\">"
|
||||||
#endif
|
#define COLOUR_GREEN "<span class=\"green\">"
|
||||||
|
#define COLOUR_YELLOW "<span class=\"yellow\">"
|
||||||
#if COLLA_EMC
|
#define COLOUR_BLUE "<span class=\"blue\">"
|
||||||
#define COLOUR_BLACK "<span class=\"black\">"
|
#define COLOUR_MAGENTA "<span class=\"magenta\">"
|
||||||
#define COLOUR_RED "<span class=\"red\">"
|
#define COLOUR_CYAN "<span class=\"cyan\">"
|
||||||
#define COLOUR_GREEN "<span class=\"green\">"
|
#define COLOUR_WHITE "<span class=\"white\">"
|
||||||
#define COLOUR_YELLOW "<span class=\"yellow\">"
|
#define COLOUR_RESET "</span></b>"
|
||||||
#define COLOUR_BLUE "<span class=\"blue\">"
|
#define COLOUR_BOLD "<b>"
|
||||||
#define COLOUR_MAGENTA "<span class=\"magenta\">"
|
#elif defined(TLOG_NO_COLOURS)
|
||||||
#define COLOUR_CYAN "<span class=\"cyan\">"
|
#define COLOUR_BLACK ""
|
||||||
#define COLOUR_WHITE "<span class=\"white\">"
|
#define COLOUR_RED ""
|
||||||
#define COLOUR_RESET "</span></b>"
|
#define COLOUR_GREEN ""
|
||||||
#define COLOUR_BOLD "<b>"
|
#define COLOUR_YELLOW ""
|
||||||
#elif defined(TLOG_NO_COLOURS)
|
#define COLOUR_BLUE ""
|
||||||
#define COLOUR_BLACK ""
|
#define COLOUR_MAGENTA ""
|
||||||
#define COLOUR_RED ""
|
#define COLOUR_CYAN ""
|
||||||
#define COLOUR_GREEN ""
|
#define COLOUR_WHITE ""
|
||||||
#define COLOUR_YELLOW ""
|
#define COLOUR_RESET ""
|
||||||
#define COLOUR_BLUE ""
|
#define COLOUR_BOLD ""
|
||||||
#define COLOUR_MAGENTA ""
|
#else
|
||||||
#define COLOUR_CYAN ""
|
#define COLOUR_BLACK "\033[30m"
|
||||||
#define COLOUR_WHITE ""
|
#define COLOUR_RED "\033[31m"
|
||||||
#define COLOUR_RESET ""
|
#define COLOUR_GREEN "\033[32m"
|
||||||
#define COLOUR_BOLD ""
|
#define COLOUR_YELLOW "\033[33m"
|
||||||
#else
|
#define COLOUR_BLUE "\033[22;34m"
|
||||||
#define COLOUR_BLACK "\033[30m"
|
#define COLOUR_MAGENTA "\033[35m"
|
||||||
#define COLOUR_RED "\033[31m"
|
#define COLOUR_CYAN "\033[36m"
|
||||||
#define COLOUR_GREEN "\033[32m"
|
#define COLOUR_WHITE "\033[37m"
|
||||||
#define COLOUR_YELLOW "\033[33m"
|
#define COLOUR_RESET "\033[0m"
|
||||||
#define COLOUR_BLUE "\033[22;34m"
|
#define COLOUR_BOLD "\033[1m"
|
||||||
#define COLOUR_MAGENTA "\033[35m"
|
#endif
|
||||||
#define COLOUR_CYAN "\033[36m"
|
|
||||||
#define COLOUR_WHITE "\033[37m"
|
static bool tl_use_newline = true;
|
||||||
#define COLOUR_RESET "\033[0m"
|
|
||||||
#define COLOUR_BOLD "\033[1m"
|
#if COLLA_WIN
|
||||||
#endif
|
static void setLevelColour(int level) {
|
||||||
|
WORD attribute = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE;
|
||||||
#define MAX_TRACELOG_MSG_LENGTH 1024
|
switch (level) {
|
||||||
|
case LogDebug: attribute = FOREGROUND_BLUE; break;
|
||||||
bool use_newline = true;
|
case LogInfo: attribute = FOREGROUND_GREEN; break;
|
||||||
|
case LogWarning: attribute = FOREGROUND_RED | FOREGROUND_GREEN; break;
|
||||||
#if COLLA_WIN
|
case LogError: attribute = FOREGROUND_RED; break;
|
||||||
static void setLevelColour(int level) {
|
case LogFatal: attribute = FOREGROUND_RED; break;
|
||||||
WORD attribute = 15;
|
}
|
||||||
switch (level) {
|
|
||||||
case LogDebug: attribute = 1; break;
|
HANDLE hc = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||||
case LogInfo: attribute = 2; break;
|
SetConsoleTextAttribute(hc, attribute);
|
||||||
case LogWarning: attribute = 6; break;
|
}
|
||||||
case LogError: attribute = 4; break;
|
#else
|
||||||
case LogFatal: attribute = 4; break;
|
static void setLevelColour(int level) {
|
||||||
}
|
switch (level) {
|
||||||
|
case LogDebug: printf(COLOUR_BLUE); break;
|
||||||
HANDLE hc = GetStdHandle(STD_OUTPUT_HANDLE);
|
case LogInfo: printf(COLOUR_GREEN); break;
|
||||||
SetConsoleTextAttribute(hc, attribute);
|
case LogWarning: printf(COLOUR_YELLOW); break;
|
||||||
}
|
case LogError: printf(COLOUR_RED); break;
|
||||||
#else
|
case LogFatal: printf(COLOUR_MAGENTA); break;
|
||||||
static void setLevelColour(int level) {
|
default: printf(COLOUR_RESET); break;
|
||||||
switch (level) {
|
}
|
||||||
case LogDebug: printf(COLOUR_BLUE); break;
|
}
|
||||||
case LogInfo: printf(COLOUR_GREEN); break;
|
#endif
|
||||||
case LogWarning: printf(COLOUR_YELLOW); break;
|
|
||||||
case LogError: printf(COLOUR_RED); break;
|
void traceLog(int level, const char *fmt, ...) {
|
||||||
case LogFatal: printf(COLOUR_MAGENTA); break;
|
va_list args;
|
||||||
default: printf(COLOUR_RESET); break;
|
va_start(args, fmt);
|
||||||
}
|
traceLogVaList(level, fmt, args);
|
||||||
}
|
va_end(args);
|
||||||
#endif
|
}
|
||||||
|
|
||||||
void traceLog(int level, const char *fmt, ...) {
|
#ifdef TLOG_MUTEX
|
||||||
va_list args;
|
#include "cthreads.h"
|
||||||
va_start(args, fmt);
|
static cmutex_t g_mtx = 0;
|
||||||
traceLogVaList(level, fmt, args);
|
#endif
|
||||||
va_end(args);
|
|
||||||
}
|
void traceLogVaList(int level, const char *fmt, va_list args) {
|
||||||
|
#ifdef TLOG_MUTEX
|
||||||
#ifdef TLOG_MUTEX
|
if (!g_mtx) g_mtx = mtxInit();
|
||||||
#include "cthreads.h"
|
mtxLock(g_mtx);
|
||||||
static cmutex_t g_mtx = 0;
|
#endif
|
||||||
#endif
|
|
||||||
|
const char *beg = "";
|
||||||
void traceLogVaList(int level, const char *fmt, va_list args) {
|
switch (level) {
|
||||||
#ifdef TLOG_MUTEX
|
case LogAll: beg = "[ALL" ; break;
|
||||||
if (!g_mtx) g_mtx = mtxInit();
|
case LogTrace: beg = "[TRACE" ; break;
|
||||||
mtxLock(g_mtx);
|
case LogDebug: beg = "[DEBUG" ; break;
|
||||||
#endif
|
case LogInfo: beg = "[INFO" ; break;
|
||||||
|
case LogWarning: beg = "[WARNING" ; break;
|
||||||
const char *beg = "";
|
case LogError: beg = "[ERROR" ; break;
|
||||||
switch (level) {
|
case LogFatal: beg = "[FATAL" ; break;
|
||||||
case LogTrace: beg = "[TRACE" ; break;
|
default: break;
|
||||||
case LogDebug: beg = "[DEBUG" ; break;
|
}
|
||||||
case LogInfo: beg = "[INFO" ; break;
|
|
||||||
case LogWarning: beg = "[WARNING" ; break;
|
#if COLLA_WIN
|
||||||
case LogError: beg = "[ERROR" ; break;
|
SetConsoleOutputCP(CP_UTF8);
|
||||||
case LogFatal: beg = "[FATAL" ; break;
|
#endif
|
||||||
default: break;
|
setLevelColour(level);
|
||||||
}
|
printf("%s", beg);
|
||||||
|
|
||||||
#if COLLA_WIN
|
#if GAME_CLIENT
|
||||||
SetConsoleOutputCP(CP_UTF8);
|
putchar(':');
|
||||||
#endif
|
traceSetColour(COL_CYAN);
|
||||||
setLevelColour(level);
|
printf("CLIENT");
|
||||||
printf("%s", beg);
|
#elif GAME_HOST
|
||||||
|
putchar(':');
|
||||||
#if GAME_CLIENT
|
traceSetColour(COL_MAGENTA);
|
||||||
putchar(':');
|
printf("HOST");
|
||||||
traceSetColour(COL_CYAN);
|
#endif
|
||||||
printf("CLIENT");
|
|
||||||
#elif GAME_HOST
|
setLevelColour(level);
|
||||||
putchar(':');
|
printf("]: ");
|
||||||
traceSetColour(COL_MAGENTA);
|
|
||||||
printf("HOST");
|
// set back to white
|
||||||
#endif
|
setLevelColour(LogTrace);
|
||||||
|
fmtPrintv(fmt, args);
|
||||||
setLevelColour(level);
|
|
||||||
printf("]: ");
|
if(tl_use_newline) {
|
||||||
|
#if COLLA_EMC
|
||||||
// set back to white
|
puts("<br>");
|
||||||
setLevelColour(LogTrace);
|
#else
|
||||||
// vprintf(fmt, args);
|
puts("");
|
||||||
fmtPrintv(fmt, args);
|
#endif
|
||||||
|
}
|
||||||
if(use_newline) {
|
|
||||||
#if COLLA_EMC
|
#ifndef TLOG_DONT_EXIT_ON_FATAL
|
||||||
puts("<br>");
|
if (level == LogFatal) {
|
||||||
#else
|
|
||||||
puts("");
|
#ifndef TLOG_NO_MSGBOX
|
||||||
#endif
|
|
||||||
}
|
#if COLLA_WIN
|
||||||
|
char errbuff[1024];
|
||||||
#ifndef TLOG_DONT_EXIT_ON_FATAL
|
fmtBufferv(errbuff, sizeof(errbuff), fmt, args);
|
||||||
if (level == LogFatal) {
|
|
||||||
|
char captionbuf[] =
|
||||||
#ifndef TLOG_NO_MSGBOX
|
#if GAME_HOST
|
||||||
|
"Fatal Host Error";
|
||||||
#if COLLA_WIN
|
#elif GAME_CLIENT
|
||||||
char errbuff[1024];
|
"Fatal Client Error";
|
||||||
fmtBufferv(errbuff, sizeof(errbuff), fmt, args);
|
#else
|
||||||
|
"Fatal Error";
|
||||||
char captionbuf[] =
|
#endif
|
||||||
#if GAME_HOST
|
MessageBoxA(
|
||||||
"Fatal Host Error";
|
NULL,
|
||||||
#elif GAME_CLIENT
|
errbuff, captionbuf,
|
||||||
"Fatal Client Error";
|
MB_ICONERROR | MB_OK
|
||||||
#else
|
);
|
||||||
"Fatal Error";
|
#endif
|
||||||
#endif
|
|
||||||
MessageBoxA(
|
#endif
|
||||||
NULL,
|
fflush(stdout);
|
||||||
errbuff, captionbuf,
|
abort();
|
||||||
MB_ICONERROR | MB_OK
|
}
|
||||||
);
|
#endif
|
||||||
#endif
|
|
||||||
|
#ifdef TLOG_MUTEX
|
||||||
#endif
|
mtxUnlock(g_mtx);
|
||||||
fflush(stdout);
|
#endif
|
||||||
abort();
|
}
|
||||||
}
|
|
||||||
#endif
|
void traceUseNewline(bool newline) {
|
||||||
|
tl_use_newline = newline;
|
||||||
#ifdef TLOG_MUTEX
|
}
|
||||||
mtxUnlock(g_mtx);
|
|
||||||
#endif
|
void traceSetColour(colour_e colour) {
|
||||||
}
|
#if COLLA_WIN
|
||||||
|
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), (WORD)colour);
|
||||||
void traceUseNewline(bool newline) {
|
#else
|
||||||
use_newline = newline;
|
switch (colour) {
|
||||||
}
|
case COL_RESET: printf(COLOUR_RESET); break;
|
||||||
|
case COL_BLACK: printf(COLOUR_BLACK); break;
|
||||||
void traceSetColour(colour_e colour) {
|
case COL_BLUE: printf(COLOUR_BLUE); break;
|
||||||
#if COLLA_WIN
|
case COL_GREEN: printf(COLOUR_GREEN); break;
|
||||||
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), colour);
|
case COL_CYAN: printf(COLOUR_CYAN); break;
|
||||||
#else
|
case COL_RED: printf(COLOUR_RED); break;
|
||||||
switch (colour) {
|
case COL_MAGENTA: printf(COLOUR_MAGENTA); break;
|
||||||
case COL_RESET: printf(COLOUR_RESET); break;
|
case COL_YELLOW: printf(COLOUR_YELLOW); break;
|
||||||
case COL_BLACK: printf(COLOUR_BLACK); break;
|
}
|
||||||
case COL_BLUE: printf(COLOUR_BLUE); break;
|
#endif
|
||||||
case COL_GREEN: printf(COLOUR_GREEN); break;
|
|
||||||
case COL_CYAN: printf(COLOUR_CYAN); break;
|
|
||||||
case COL_RED: printf(COLOUR_RED); break;
|
|
||||||
case COL_MAGENTA: printf(COLOUR_MAGENTA); break;
|
|
||||||
case COL_YELLOW: printf(COLOUR_YELLOW); break;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,59 +1,59 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* Define any of this to turn on the option
|
/* Define any of this to turn on the option
|
||||||
* -> TLOG_NO_COLOURS: print without using colours
|
* -> TLOG_NO_COLOURS: print without using colours
|
||||||
* -> TLOG_VS: print to visual studio console, also turns on TLOG_NO_COLOURS
|
* -> TLOG_VS: print to visual studio console, also turns on TLOG_NO_COLOURS
|
||||||
* -> TLOG_DONT_EXIT_ON_FATAL: don't call 'exit(1)' when using LogFatal
|
* -> TLOG_DONT_EXIT_ON_FATAL: don't call 'exit(1)' when using LogFatal
|
||||||
* -> TLOG_DISABLE: turn off all logging, useful for release builds
|
* -> TLOG_DISABLE: turn off all logging, useful for release builds
|
||||||
* -> TLOG_MUTEX: use a mutex on every traceLog call
|
* -> TLOG_MUTEX: use a mutex on every traceLog call
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "collatypes.h"
|
#include "collatypes.h"
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
LogAll, LogTrace, LogDebug, LogInfo, LogWarning, LogError, LogFatal
|
LogAll, LogTrace, LogDebug, LogInfo, LogWarning, LogError, LogFatal
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
COL_RESET = 15,
|
COL_RESET = 15,
|
||||||
COL_BLACK = 8,
|
COL_BLACK = 8,
|
||||||
COL_BLUE = 9,
|
COL_BLUE = 9,
|
||||||
COL_GREEN = 10,
|
COL_GREEN = 10,
|
||||||
COL_CYAN = 11,
|
COL_CYAN = 11,
|
||||||
COL_RED = 12,
|
COL_RED = 12,
|
||||||
COL_MAGENTA = 13,
|
COL_MAGENTA = 13,
|
||||||
COL_YELLOW = 14,
|
COL_YELLOW = 14,
|
||||||
COL_WHITE = 15
|
COL_WHITE = 15
|
||||||
} colour_e;
|
} colour_e;
|
||||||
|
|
||||||
void traceLog(int level, const char *fmt, ...);
|
void traceLog(int level, const char *fmt, ...);
|
||||||
void traceLogVaList(int level, const char *fmt, va_list args);
|
void traceLogVaList(int level, const char *fmt, va_list args);
|
||||||
void traceUseNewline(bool use_newline);
|
void traceUseNewline(bool use_newline);
|
||||||
void traceSetColour(colour_e colour);
|
void traceSetColour(colour_e colour);
|
||||||
|
|
||||||
#ifdef TLOG_DISABLE
|
#ifdef TLOG_DISABLE
|
||||||
#define tall(...)
|
#define tall(...)
|
||||||
#define trace(...)
|
#define trace(...)
|
||||||
#define debug(...)
|
#define debug(...)
|
||||||
#define info(...)
|
#define info(...)
|
||||||
#define warn(...)
|
#define warn(...)
|
||||||
#define err(...)
|
#define err(...)
|
||||||
#define fatal(...)
|
#define fatal(...)
|
||||||
#else
|
#else
|
||||||
#define tall(...) traceLog(LogAll, __VA_ARGS__)
|
#define tall(...) traceLog(LogAll, __VA_ARGS__)
|
||||||
#define trace(...) traceLog(LogTrace, __VA_ARGS__)
|
#define trace(...) traceLog(LogTrace, __VA_ARGS__)
|
||||||
#define debug(...) traceLog(LogDebug, __VA_ARGS__)
|
#define debug(...) traceLog(LogDebug, __VA_ARGS__)
|
||||||
#define info(...) traceLog(LogInfo, __VA_ARGS__)
|
#define info(...) traceLog(LogInfo, __VA_ARGS__)
|
||||||
#define warn(...) traceLog(LogWarning, __VA_ARGS__)
|
#define warn(...) traceLog(LogWarning, __VA_ARGS__)
|
||||||
#define err(...) traceLog(LogError, __VA_ARGS__)
|
#define err(...) traceLog(LogError, __VA_ARGS__)
|
||||||
#define fatal(...) traceLog(LogFatal, __VA_ARGS__)
|
#define fatal(...) traceLog(LogFatal, __VA_ARGS__)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
} // extern "C"
|
} // extern "C"
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -1,172 +1,172 @@
|
||||||
#include "utf8.h"
|
#include "utf8.h"
|
||||||
|
|
||||||
static const uint8 masks[] = {
|
static const uint8 masks[] = {
|
||||||
0x7f, // 0111-1111
|
0x7f, // 0111-1111
|
||||||
0x1f, // 0001-1111
|
0x1f, // 0001-1111
|
||||||
0x0f, // 0000-1111
|
0x0f, // 0000-1111
|
||||||
0x07, // 0000-0111
|
0x07, // 0000-0111
|
||||||
0x03, // 0000-0011
|
0x03, // 0000-0011
|
||||||
0x01 // 0000-0001
|
0x01 // 0000-0001
|
||||||
};
|
};
|
||||||
|
|
||||||
static struct {
|
static struct {
|
||||||
uint8 mask;
|
uint8 mask;
|
||||||
uint8 result;
|
uint8 result;
|
||||||
int octets;
|
int octets;
|
||||||
} sizes[] = {
|
} sizes[] = {
|
||||||
{ 0x80, 0x00, 1 }, // 1000-0000, 0000-0000
|
{ 0x80, 0x00, 1 }, // 1000-0000, 0000-0000
|
||||||
{ 0xE0, 0xC0, 2 }, // 1110-0000, 1100-0000
|
{ 0xE0, 0xC0, 2 }, // 1110-0000, 1100-0000
|
||||||
{ 0xF0, 0xE0, 3 }, // 1111-0000, 1110-0000
|
{ 0xF0, 0xE0, 3 }, // 1111-0000, 1110-0000
|
||||||
{ 0xF8, 0xF0, 4 }, // 1111-1000, 1111-0000
|
{ 0xF8, 0xF0, 4 }, // 1111-1000, 1111-0000
|
||||||
{ 0xFC, 0xF8, 5 }, // 1111-1100, 1111-1000
|
{ 0xFC, 0xF8, 5 }, // 1111-1100, 1111-1000
|
||||||
{ 0xFE, 0xF8, 6 }, // 1111-1110, 1111-1000
|
{ 0xFE, 0xF8, 6 }, // 1111-1110, 1111-1000
|
||||||
{ 0x80, 0x80, -1 }, // 1000-0000, 1000-0000
|
{ 0x80, 0x80, -1 }, // 1000-0000, 1000-0000
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
UTF-8 codepoints are encoded using the first bits of the first character
|
UTF-8 codepoints are encoded using the first bits of the first character
|
||||||
|
|
||||||
byte 1 | byte 2 | byte 3 | byte 4
|
byte 1 | byte 2 | byte 3 | byte 4
|
||||||
0xxx xxxx | | |
|
0xxx xxxx | | |
|
||||||
110x xxxx | 10xx xxxx | |
|
110x xxxx | 10xx xxxx | |
|
||||||
1110 xxxx | 10xx xxxx | 10xx xxxx |
|
1110 xxxx | 10xx xxxx | 10xx xxxx |
|
||||||
1111 0xxx | 10xx xxxx | 10xx xxxx | 10xx xxxx
|
1111 0xxx | 10xx xxxx | 10xx xxxx | 10xx xxxx
|
||||||
|
|
||||||
so when we decode it we first find the size of the codepoint (from 1 to 4)
|
so when we decode it we first find the size of the codepoint (from 1 to 4)
|
||||||
then we apply the mask to the first byte to get the first character
|
then we apply the mask to the first byte to get the first character
|
||||||
then we keep shifting the rune left 6 and applying the next byte to the mask
|
then we keep shifting the rune left 6 and applying the next byte to the mask
|
||||||
until the codepoint is finished (size is 0)
|
until the codepoint is finished (size is 0)
|
||||||
|
|
||||||
## EXAMPLE
|
## EXAMPLE
|
||||||
|
|
||||||
utf8 string (€) = 1110-0010 1000-0010 1010-1100
|
utf8 string (€) = 1110-0010 1000-0010 1010-1100
|
||||||
|
|
||||||
cp = 0000-0000 0000-0000 0000-0000 0000-0000
|
cp = 0000-0000 0000-0000 0000-0000 0000-0000
|
||||||
size = 3
|
size = 3
|
||||||
mask = 0x0f -> 0000-1111
|
mask = 0x0f -> 0000-1111
|
||||||
cp = *s & mask = 1110-0010 & 0000-1111 = 0000-0000 0000-0000 0000-0000 0000-0010
|
cp = *s & mask = 1110-0010 & 0000-1111 = 0000-0000 0000-0000 0000-0000 0000-0010
|
||||||
++s = 1000-0010
|
++s = 1000-0010
|
||||||
|
|
||||||
--size = 2
|
--size = 2
|
||||||
cp <<= 6 = 0000-0000 0000-0000 0000-0000 1000-0000
|
cp <<= 6 = 0000-0000 0000-0000 0000-0000 1000-0000
|
||||||
cp |= *s & 0x3f = 1000-0010 & 0011-1111 = 0000-0000 0000-0000 0000-0000 1000-0010
|
cp |= *s & 0x3f = 1000-0010 & 0011-1111 = 0000-0000 0000-0000 0000-0000 1000-0010
|
||||||
++s = 1010-1100
|
++s = 1010-1100
|
||||||
|
|
||||||
--size = 1
|
--size = 1
|
||||||
cp <<= 6 = 0000-0000 0000-0000 0010-0000 1000-0000
|
cp <<= 6 = 0000-0000 0000-0000 0010-0000 1000-0000
|
||||||
cp |= *s & 0x3f = 1010-1100 & 0011-1111 = 0000-0000 0000-0000 0010-0000 1010-1100
|
cp |= *s & 0x3f = 1010-1100 & 0011-1111 = 0000-0000 0000-0000 0010-0000 1010-1100
|
||||||
++s = ----------
|
++s = ----------
|
||||||
|
|
||||||
final codepoint = 0010-0000 1010-1100
|
final codepoint = 0010-0000 1010-1100
|
||||||
€ codepoint = 0010-0000 1010-1100
|
€ codepoint = 0010-0000 1010-1100
|
||||||
*/
|
*/
|
||||||
|
|
||||||
rune utf8Decode(const char **char_str) {
|
rune utf8Decode(const char **char_str) {
|
||||||
uint8 **s = (uint8 **)char_str;
|
const uint8 **s = (const uint8 **)char_str;
|
||||||
|
|
||||||
rune ch = 0;
|
rune ch = 0;
|
||||||
// if is ascii
|
// if is ascii
|
||||||
if (**s < 128) {
|
if (**s < 128) {
|
||||||
ch = **s;
|
ch = **s;
|
||||||
++*s;
|
++*s;
|
||||||
return ch;
|
return ch;
|
||||||
}
|
}
|
||||||
int size = utf8Size((char *)*s);
|
int size = utf8Size((const char *)*s);
|
||||||
if (size == -1) {
|
if (size == -1) {
|
||||||
++*s;
|
++*s;
|
||||||
return UTF8_INVALID;
|
return UTF8_INVALID;
|
||||||
}
|
}
|
||||||
uint8 mask = masks[size - 1];
|
uint8 mask = masks[size - 1];
|
||||||
ch = **s & mask;
|
ch = **s & mask;
|
||||||
++*s;
|
++*s;
|
||||||
while(--size) {
|
while(--size) {
|
||||||
ch <<= 6;
|
ch <<= 6;
|
||||||
ch |= **s & 0x3f; // 0011-1111
|
ch |= **s & 0x3f; // 0011-1111
|
||||||
++*s;
|
++*s;
|
||||||
}
|
}
|
||||||
return ch;
|
return ch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
to encode a codepoint in a utf8 string we first need to find
|
to encode a codepoint in a utf8 string we first need to find
|
||||||
the length of the codepoint
|
the length of the codepoint
|
||||||
then we start from the rightmost byte and loop for each byte of the codepoint
|
then we start from the rightmost byte and loop for each byte of the codepoint
|
||||||
using the length we got before until the first byte (which we skip)
|
using the length we got before until the first byte (which we skip)
|
||||||
> and (&) with 0x3f so we ignore the first to bits of the codepoint
|
> and (&) with 0x3f so we ignore the first to bits of the codepoint
|
||||||
> or (|) with 0x80 so we make sure that the first two bits are 10
|
> or (|) with 0x80 so we make sure that the first two bits are 10
|
||||||
> bitshift the codepoint right 6
|
> bitshift the codepoint right 6
|
||||||
|
|
||||||
finally, we apply the correct length-mask to the first byte
|
finally, we apply the correct length-mask to the first byte
|
||||||
|
|
||||||
## EXAMPLE
|
## EXAMPLE
|
||||||
|
|
||||||
ch € = 0010-0000 1010-1100
|
ch € = 0010-0000 1010-1100
|
||||||
ch < 0x10000
|
ch < 0x10000
|
||||||
first = 0xe0 = 1110-0000
|
first = 0xe0 = 1110-0000
|
||||||
len = 3
|
len = 3
|
||||||
|
|
||||||
str[2] = (ch & 0x3f) | 0x80 = 1010-1100 & 0011-1111 | 1000-0000
|
str[2] = (ch & 0x3f) | 0x80 = 1010-1100 & 0011-1111 | 1000-0000
|
||||||
= 1010-1100
|
= 1010-1100
|
||||||
ch >>= 6 = 0010-0000 1010-1100 >> 6 = 1000-0010
|
ch >>= 6 = 0010-0000 1010-1100 >> 6 = 1000-0010
|
||||||
|
|
||||||
str[1] = (ch & 0x3f) | 0x80 = 1000-0010 & 0011-1111 | 1000-000
|
str[1] = (ch & 0x3f) | 0x80 = 1000-0010 & 0011-1111 | 1000-000
|
||||||
= 1000-0010
|
= 1000-0010
|
||||||
ch >>= 6 = 1000-0010 >> 6 = 0000-0010
|
ch >>= 6 = 1000-0010 >> 6 = 0000-0010
|
||||||
|
|
||||||
str[0] = ch | first_mask = 0000-0010 | 1111-0000
|
str[0] = ch | first_mask = 0000-0010 | 1111-0000
|
||||||
= 1111-0010
|
= 1111-0010
|
||||||
|
|
||||||
str = 1111-0010 1000-0010 1010-1100
|
str = 1111-0010 1000-0010 1010-1100
|
||||||
utf8 € = 1110-0010 1000-0010 1010-1100
|
utf8 € = 1110-0010 1000-0010 1010-1100
|
||||||
*/
|
*/
|
||||||
|
|
||||||
usize utf8Encode(char *str, rune codepoint) {
|
usize utf8Encode(char *str, rune codepoint) {
|
||||||
usize len = 0;
|
usize len = 0;
|
||||||
uint8 first;
|
uint8 first;
|
||||||
|
|
||||||
if (codepoint < 0x80) { // 0000-0000 0000-0000 0000-0000 1000-0000
|
if (codepoint < 0x80) { // 0000-0000 0000-0000 0000-0000 1000-0000
|
||||||
first = 0;
|
first = 0;
|
||||||
len = 1;
|
len = 1;
|
||||||
}
|
}
|
||||||
else if (codepoint < 0x800) { // 0000-0000 0000-0000 0000-1000 0000-0000
|
else if (codepoint < 0x800) { // 0000-0000 0000-0000 0000-1000 0000-0000
|
||||||
first = 0xc0; // 1100-0000
|
first = 0xc0; // 1100-0000
|
||||||
len = 2;
|
len = 2;
|
||||||
}
|
}
|
||||||
else if (codepoint < 0x10000) { // 0000-0000 0000-0001 0000-0000 0000-0000
|
else if (codepoint < 0x10000) { // 0000-0000 0000-0001 0000-0000 0000-0000
|
||||||
first = 0xe0; // 1110-0000
|
first = 0xe0; // 1110-0000
|
||||||
len = 3;
|
len = 3;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
first = 0xf0; // 1111-0000
|
first = 0xf0; // 1111-0000
|
||||||
len = 4;
|
len = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (usize i = len - 1; i > 0; --i) {
|
for (usize i = len - 1; i > 0; --i) {
|
||||||
// 0x3f -> 0011-1111
|
// 0x3f -> 0011-1111
|
||||||
// 0x80 -> 1000-0000
|
// 0x80 -> 1000-0000
|
||||||
str[i] = (codepoint & 0x3f) | 0x80;
|
str[i] = (codepoint & 0x3f) | 0x80;
|
||||||
codepoint >>= 6;
|
codepoint >>= 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
str[0] = (char)(codepoint | first);
|
str[0] = (char)(codepoint | first);
|
||||||
return len;
|
return len;
|
||||||
}
|
}
|
||||||
|
|
||||||
int utf8Size(const char *str) {
|
int utf8Size(const char *str) {
|
||||||
uint8 c = (uint8)*str;
|
uint8 c = (uint8)*str;
|
||||||
for(usize i = 0; i < (sizeof(sizes) / sizeof(*sizes)); ++i) {
|
for(usize i = 0; i < (sizeof(sizes) / sizeof(*sizes)); ++i) {
|
||||||
if ((c & sizes[i].mask) == sizes[i].result) {
|
if ((c & sizes[i].mask) == sizes[i].result) {
|
||||||
return sizes[i].octets;
|
return sizes[i].octets;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
usize utf8CpSize(rune ch) {
|
usize utf8CpSize(rune ch) {
|
||||||
if (ch < 0x80) return 1;
|
if (ch < 0x80) return 1;
|
||||||
else if (ch < 0x800) return 2;
|
else if (ch < 0x800) return 2;
|
||||||
else if (ch < 0x10000) return 3;
|
else if (ch < 0x10000) return 3;
|
||||||
return 4;
|
return 4;
|
||||||
}
|
}
|
||||||
|
|
@ -1,19 +1,19 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "collatypes.h"
|
#include "collatypes.h"
|
||||||
|
|
||||||
typedef uint32 rune;
|
typedef uint32 rune;
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
UTF8_MAX_SIZE = 4,
|
UTF8_MAX_SIZE = 4,
|
||||||
UTF8_INVALID = 0x80
|
UTF8_INVALID = 0x80
|
||||||
};
|
};
|
||||||
|
|
||||||
// grabs the next UTF-8 codepoint and advances string ptr
|
// grabs the next UTF-8 codepoint and advances string ptr
|
||||||
rune utf8Decode(const char **str);
|
rune utf8Decode(const char **str);
|
||||||
// encodes a codepoint as UTF-8 and returns the length
|
// encodes a codepoint as UTF-8 and returns the length
|
||||||
usize utf8Encode(char *str, rune ch);
|
usize utf8Encode(char *str, rune ch);
|
||||||
// returns the size of the next UTF-8 codepoint
|
// returns the size of the next UTF-8 codepoint
|
||||||
int utf8Size(const char *str);
|
int utf8Size(const char *str);
|
||||||
// returns the size of a UTF-8 codepoint
|
// returns the size of a UTF-8 codepoint
|
||||||
usize utf8CpSize(rune ch);
|
usize utf8CpSize(rune ch);
|
||||||
|
|
@ -35,8 +35,7 @@ usize vmemPadToPage(usize byte_count) {
|
||||||
|
|
||||||
#if COLLA_WIN
|
#if COLLA_WIN
|
||||||
|
|
||||||
#define WIN32_LEAN_AND_MEAN
|
#include <windows.h>
|
||||||
#include <Windows.h>
|
|
||||||
|
|
||||||
void *vmemInit(usize size, usize *out_padded_size) {
|
void *vmemInit(usize size, usize *out_padded_size) {
|
||||||
usize alloc_size = vmemPadToPage(size);
|
usize alloc_size = vmemPadToPage(size);
|
||||||
142
websocket.c
Normal file
142
websocket.c
Normal 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
13
websocket.h
Normal 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);
|
||||||
|
|
@ -57,7 +57,7 @@ strview_t xmlGetAttribute(xmltag_t *tag, strview_t key) {
|
||||||
}
|
}
|
||||||
a = a->next;
|
a = a->next;
|
||||||
}
|
}
|
||||||
return (strview_t){0};
|
return STRV_EMPTY;
|
||||||
}
|
}
|
||||||
|
|
||||||
// == PRIVATE FUNCTIONS ========================================================================
|
// == PRIVATE FUNCTIONS ========================================================================
|
||||||
Loading…
Add table
Add a link
Reference in a new issue