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

View file

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

View file

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

View file

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

View file

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

43
build.c
View file

@ -3,6 +3,7 @@
#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
#if !COLLA_NOHOTRELOAD
#include "hot_reload.c"
#endif #endif

View file

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

View file

@ -2,18 +2,19 @@
#include <stdlib.h> #include <stdlib.h>
// TODO: swap calloc for arenas
typedef struct { typedef struct {
cthread_func_t func; cthread_func_t func;
void *arg; void *arg;
} _thr_internal_t; } _thr_internal_t;
#if COLLA_WIN #if COLLA_WIN
#define WIN32_LEAN_AND_MEAN
#include <windows.h> #include <windows.h>
// == THREAD =========================================== // == THREAD ===========================================
static DWORD _thrFuncInternal(void *arg) { static DWORD WINAPI _thrFuncInternal(void *arg) {
_thr_internal_t *params = (_thr_internal_t *)arg; _thr_internal_t *params = (_thr_internal_t *)arg;
cthread_func_t func = params->func; cthread_func_t func = params->func;
void *argument = params->arg; void *argument = params->arg;

View file

@ -1,322 +0,0 @@
#include "coropool.h"
#if 0
#include <stdlib.h>
#include <tracelog.h>
#include "jobpool.h"
enum {
NUM_JOBS = 50
};
struct _pool_internal_t;
typedef union job_t {
struct {
mco_coro *co;
struct _pool_internal_t *pool;
};
struct {
union job_t *next_free;
void *__padding;
};
} job_t;
static inline bool _isJobValid(job_t *job) {
return job->pool != NULL;
}
typedef struct jobchunk_t {
job_t jobs[NUM_JOBS];
job_t *first_free;
struct jobchunk_t *next;
} jobchunk_t;
void _chunkInit(jobchunk_t *chunk) {
if (!chunk) return;
chunk->first_free = chunk->jobs;
for (int i = 0; i < (NUM_JOBS - 1); ++i) {
chunk->jobs[i].next_free = chunk->jobs + i + 1;
}
}
jobchunk_t *_chunkNew(jobchunk_t *prev) {
jobchunk_t *chunk = calloc(1, sizeof(jobchunk_t));
_chunkInit(chunk);
prev->next = chunk;
return chunk;
}
job_t *_chunkGetFirstFree(jobchunk_t *chunk) {
job_t *first_free = chunk->first_free;
if (first_free) {
chunk->first_free = first_free->next_free;
}
return first_free;
}
void _chunkSetFree(jobchunk_t *chunk, job_t *job) {
job->pool = NULL;
job->next_free = chunk->first_free;
chunk->first_free = job;
}
typedef struct _pool_internal_t {
jobpool_t pool;
jobchunk_t head_chunk;
cmutex_t chunk_mtx;
} _pool_internal_t;
static int _worker(void *arg);
coropool_t copoInit(uint32 num) {
_pool_internal_t *pool = calloc(1, sizeof(_pool_internal_t));
pool->pool = poolInit(num);
_chunkInit(&pool->head_chunk);
pool->chunk_mtx = mtxInit();
return pool;
}
void copoFree(coropool_t copo) {
_pool_internal_t *pool = copo;
poolWait(pool->pool);
poolFree(pool->pool);
jobchunk_t *chunk = &pool->head_chunk;
while (chunk) {
jobchunk_t *next = chunk->next;
free(chunk);
chunk = next;
}
mtxFree(pool->chunk_mtx);
free(pool);
}
bool copoAdd(coropool_t copo, copo_func_f fn) {
mco_desc desc = mco_desc_init(fn, 0);
return copoAddDesc(copo, &desc);
}
bool copoAddDesc(coropool_t copo, mco_desc *desc) {
mco_coro *co;
mco_create(&co, desc);
return copoAddCo(copo, co);
}
static bool _copoAddJob(job_t *job) {
//return poolAdd(job->pool->pool, _worker, job);
return true;
}
static bool _copoRemoveJob(job_t *job) {
_pool_internal_t *pool = job->pool;
// find chunk
jobchunk_t *chunk = &pool->head_chunk;
while (chunk) {
if (chunk->jobs < job && (chunk->jobs + NUM_JOBS) > job) {
break;
}
chunk = chunk->next;
}
if (!chunk) return false;
mtxLock(pool->chunk_mtx);
_chunkSetFree(chunk, job);
mtxUnlock(pool->chunk_mtx);
}
bool copoAddCo(coropool_t copo, mco_coro *co) {
_pool_internal_t *pool = copo;
job_t *new_job = NULL;
jobchunk_t *chunk = &pool->head_chunk;
mtxLock(pool->chunk_mtx);
while (!new_job) {
new_job = _chunkGetFirstFree(chunk);
if (!new_job) {
if (!chunk->next) {
info("adding new chunk");
chunk->next = _chunkNew(chunk);
}
chunk = chunk->next;
}
}
mtxUnlock(pool->chunk_mtx);
new_job->co = co;
new_job->pool = pool;
//return poolAdd(pool->pool, _worker, new_job);
return _copoAddJob(new_job);
}
void copoWait(coropool_t copo) {
_pool_internal_t *pool = copo;
poolWait(pool->pool);
}
static int _worker(void *arg) {
job_t *job = arg;
mco_result res = mco_resume(job->co);
if (res != MCO_DEAD) {
_copoAddJob(job);
}
else {
_copoRemoveJob(job);
}
return 0;
}
#endif
#include <vec.h>
typedef struct {
vec(mco_coro *) jobs;
uint32 head;
cmutex_t work_mutex;
condvar_t work_cond;
condvar_t working_cond;
int32 working_count;
int32 thread_count;
bool stop;
} _copo_internal_t;
static mco_coro *_getJob(_copo_internal_t *copo);
static int _copoWorker(void *arg);
coropool_t copoInit(uint32 num) {
if (!num) num = 2;
_copo_internal_t *copo = malloc(sizeof(_copo_internal_t));
*copo = (_copo_internal_t){
.work_mutex = mtxInit(),
.work_cond = condInit(),
.working_cond = condInit(),
.thread_count = (int32)num
};
for (usize i = 0; i < num; ++i) {
thrDetach(thrCreate(_copoWorker, copo));
}
return copo;
}
void copoFree(coropool_t copo_in) {
_copo_internal_t *copo = copo_in;
if (!copo) return;
mtxLock(copo->work_mutex);
copo->stop = true;
condWakeAll(copo->work_cond);
mtxUnlock(copo->work_mutex);
copoWait(copo);
vecFree(copo->jobs);
mtxFree(copo->work_mutex);
condFree(copo->work_cond);
condFree(copo->working_cond);
free(copo);
}
bool copoAdd(coropool_t copo, copo_func_f fn) {
mco_desc desc = mco_desc_init(fn, 0);
return copoAddDesc(copo, &desc);
}
bool copoAddDesc(coropool_t copo, mco_desc *desc) {
mco_coro *co;
mco_create(&co, desc);
return copoAddCo(copo, co);
}
bool copoAddCo(coropool_t copo_in, mco_coro *co) {
_copo_internal_t *copo = copo_in;
if (!copo) return false;
mtxLock(copo->work_mutex);
if (copo->head > vecLen(copo->jobs)) {
vecClear(copo->jobs);
copo->head = 0;
}
vecAppend(copo->jobs, co);
condWake(copo->work_cond);
mtxUnlock(copo->work_mutex);
return true;
}
void copoWait(coropool_t copo_in) {
_copo_internal_t *copo = copo_in;
if (!copo) return;
mtxLock(copo->work_mutex);
// while its either
// - working and there's still some threads doing some work
// - not working and there's still some threads exiting
while ((!copo->stop && copo->working_count > 0) ||
(copo->stop && copo->thread_count > 0)
) {
condWait(copo->working_cond, copo->work_mutex);
}
mtxUnlock(copo->work_mutex);
}
// == PRIVATE FUNCTIONS ===================================
static mco_coro *_getJob(_copo_internal_t *copo) {
if (copo->head >= vecLen(copo->jobs)) {
copo->head = 0;
}
return copo->jobs[copo->head++];
}
static int _copoWorker(void *arg) {
_copo_internal_t *copo = arg;
while (true) {
mtxLock(copo->work_mutex);
// wait for a new job
while (copo->head >= vecLen(copo->jobs) && !copo->stop) {
condWait(copo->work_cond, copo->work_mutex);
}
if (copo->stop) {
break;
}
mco_coro *job = _getJob(copo);
copo->working_count++;
mtxUnlock(copo->work_mutex);
if (job && mco_status(job) != MCO_DEAD) {
mco_resume(job);
if (mco_status(job) != MCO_DEAD) {
copoAddCo(copo, job);
}
}
mtxLock(copo->work_mutex);
copo->working_count--;
if (!copo->stop &&
copo->working_count == 0 &&
copo->head == vecLen(copo->jobs)
) {
condWake(copo->working_cond);
}
mtxUnlock(copo->work_mutex);
}
copo->thread_count--;
condWake(copo->working_cond);
mtxUnlock(copo->work_mutex);
return 0;
}

View file

@ -1,17 +0,0 @@
#pragma once
#include <collatypes.h>
#include <cthreads.h>
#include "minicoro.h"
typedef void *coropool_t;
typedef void (*copo_func_f)(mco_coro *co);
coropool_t copoInit(uint32 num);
void copoFree(coropool_t copo);
bool copoAdd(coropool_t copo, copo_func_f fn);
bool copoAddDesc(coropool_t copo, mco_desc *desc);
bool copoAddCo(coropool_t copo, mco_coro *co);
void copoWait(coropool_t copo);

View file

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

View file

@ -1,133 +0,0 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <string.h> // memset
#include "collatypes.h"
#include "tracelog.h" // fatal
// heavily inspired by https://gist.github.com/Enichan/5f01140530ff0133fde19c9549a2a973
#if 0
// Usage example:
typedef struct {
int result;
coroutine_t co;
} co_int_t;
bool coVoid(co_void_t *co) {
costate(co_nostate);
coroutine({
printf("hello"); yield();
printf("world"); yield();
printf("how"); yield();
printf("is"); yield();
printf("it"); yield();
printf("going?\n");
});
}
bool countToTen(co_int_t *co) {
costate(int i; int ii;);
coroutine({
for(self.i = 0; self.i < 10; ++self.i) {
self.ii += self.i;
yieldVal(self.ii);
}
});
}
int main() {
co_void_t covoid = {0};
while(coVoid(&covoid)) {
printf(" ");
}
co_int_t coint;
coint.co = coInit();
while(countToTen(&coint)) {
printf("%d ", coint.result);
}
printf("\n");
// reset coroutine for next call
coint.co = coInit();
while(countToTen(&coint)) {
printf("%d ", coint.result);
}
printf("\n");
}
#endif
typedef struct {
int state;
} coroutine_t;
typedef struct {
coroutine_t co;
} co_void_t;
coroutine_t coInit();
bool coIsDead(coroutine_t co);
#define COVAR co
#define COSELF self
#define costate(...) \
typedef struct { bool init; __VA_ARGS__ } COSTATE; \
static COSTATE self = {0}
#define co_nostate { char __dummy__; }
#define yieldBreak() \
COVAR->co.state = -1; \
self.init = false; \
return false;
#define coroutine(...) \
if(!self.init) { \
if(COVAR->co.state != 0) { \
fatal("coroutine not initialized in %s:%d,\n" \
"did you forget to do '= {0};'?", \
__FILE__, __LINE__); \
} \
memset(&self, 0, sizeof(self)); \
self.init = true; \
} \
switch(COVAR->co.state) { \
case 0:; \
__VA_ARGS__ \
} \
yieldBreak();
#define yield() _yield(__COUNTER__)
#define _yield(count) \
do { \
COVAR->co.state = count; \
return true; \
case count:; \
} while(0);
#define yieldVal(v) _yieldVal(v, __COUNTER__)
#define _yieldVal(v, count) \
do { \
COVAR->co.state = count; \
COVAR->result = v; \
return true; \
case count:; \
} while(0);
// increment __COUNTER__ past 0 so we don't get double case errors
#define CONCAT_IMPL(x, y) x##y
#define MACRO_CONCAT(x, y) CONCAT_IMPL(x, y)
#define __INC_COUNTER int MACRO_CONCAT(__counter_tmp_, __COUNTER__)
__INC_COUNTER;
#undef CONCAT_IMPL
#undef MACRO_CONCAT
#undef __INC_COUNTER
#ifdef __cplusplus
} // extern "C"
#endif

View file

@ -1,204 +0,0 @@
#include "dir.h"
#include "tracelog.h"
#if COLLA_WIN
#include "win32_slim.h"
#include <stdlib.h>
#include <assert.h>
#include "strstream.h"
typedef struct {
dir_entry_t cur;
dir_entry_t next;
HANDLE handle;
} _dir_internal_t;
static dir_entry_t _fillDirEntry(WIN32_FIND_DATAW *data) {
return (dir_entry_t) {
.type =
data->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ?
FS_TYPE_DIR : FS_TYPE_FILE,
.name = strFromWChar(data->cFileName, 0)
};
}
static _dir_internal_t _getFirst(const wchar_t *path) {
_dir_internal_t res = {0};
WIN32_FIND_DATAW data = {0};
res.handle = FindFirstFileW(path, &data);
if(res.handle != INVALID_HANDLE_VALUE) {
res.next = _fillDirEntry(&data);
}
return res;
}
static void _getNext(_dir_internal_t *ctx) {
WIN32_FIND_DATAW data = {0};
BOOL result = FindNextFileW(ctx->handle, &data);
if(!result) {
if(GetLastError() == ERROR_NO_MORE_FILES) {
FindClose(ctx->handle);
ctx->handle = NULL;
return;
}
}
ctx->next = _fillDirEntry(&data);
}
dir_t dirOpen(const char *path) {
DWORD n = GetFullPathName(path, 0, NULL, NULL);
outstream_t out = ostrInitLen(n + 3);
n = GetFullPathName(path, n, out.buf, NULL);
assert(n > 0);
out.len += n;
switch(ostrBack(out)) {
case '\\':
case '/':
case ':':
// directory ends in path separator
// NOP
break;
default:
ostrPutc(&out, '\\');
}
ostrPutc(&out, '*');
_dir_internal_t *dir = malloc(sizeof(_dir_internal_t));
if(dir) {
wchar_t *wpath = strToWCHAR(ostrAsStr(out));
assert(wpath);
*dir = _getFirst(wpath);
free(wpath);
}
ostrFree(out);
return dir;
}
void dirClose(dir_t ctx) {
free(ctx);
}
bool dirValid(dir_t ctx) {
_dir_internal_t *dir = (_dir_internal_t*)ctx;
return dir->handle != INVALID_HANDLE_VALUE;
}
dir_entry_t *dirNext(dir_t ctx) {
_dir_internal_t *dir = (_dir_internal_t*)ctx;
strFree(dir->cur.name);
if(!dir->handle) return NULL;
dir->cur = dir->next;
_getNext(dir);
return &dir->cur;
}
void dirCreate(const char *path) {
CreateDirectoryA(path, NULL);
}
#else
#include <dirent.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
// taken from https://sites.uclouvain.be/SystInfo/usr/include/dirent.h.html
// hopefully shouldn't be needed
#ifndef DT_DIR
#define DT_DIR 4
#endif
#ifndef DT_REG
#define DT_REG 8
#endif
typedef struct {
DIR *dir;
dir_entry_t next;
} _dir_internal_t;
dir_t dirOpen(const char *path) {
_dir_internal_t *ctx = (_dir_internal_t *)calloc(1, sizeof(_dir_internal_t));
if(ctx) ctx->dir = opendir(path);
return ctx;
}
void dirClose(dir_t ctx) {
if(ctx) {
_dir_internal_t *in = (_dir_internal_t *)ctx;
closedir(in->dir);
free(in);
}
}
bool dirValid(dir_t ctx) {
_dir_internal_t *dir = (_dir_internal_t*)ctx;
return dir->dir != NULL;
}
dir_entry_t *dirNext(dir_t ctx) {
if(!ctx) return NULL;
_dir_internal_t *in = (_dir_internal_t *)ctx;
strFree(in->next.name);
struct dirent *dp = readdir(in->dir);
if(!dp) return NULL;
switch(dp->d_type) {
case DT_DIR: in->next.type = FS_TYPE_DIR; break;
case DT_REG: in->next.type = FS_TYPE_FILE; break;
default: in->next.type = FS_TYPE_UNKNOWN; break;
}
in->next.name = strFromStr(dp->d_name);
return &in->next;
}
void dirCreate(const char *path) {
mkdir(path, 0700);
}
#endif
#include <stdio.h>
bool dirRemove(const char *path) {
dir_t dir = dirOpen(path);
if (!dirValid(dir)) return false;
dir_entry_t *it = NULL;
while((it = dirNext(dir))) {
if (it->type == FS_TYPE_FILE) {
str_t file_path = strFromFmt("%s/%s", path, it->name.buf);
if (remove(file_path.buf)) {
err("couldn't remove %s > %s", file_path.buf, strerror(errno));
}
strFree(file_path);
}
else if (it->type == FS_TYPE_DIR) {
if (strcmp(it->name.buf, ".") == 0 || strcmp(it->name.buf, "..") == 0) {
continue;
}
str_t new_path = strFromFmt("%s/%s", path, it->name.buf);
info("new path: %s--%s -> %s", path, it->name.buf, new_path.buf);
if (!dirRemove(new_path.buf)) {
err("couldn't delete folder %s", new_path.buf);
break;
}
strFree(new_path);
}
else {
err("%d -> %s", it->type, it->name.buf);
}
}
dirClose(dir);
#if COLLA_WIN
return RemoveDirectoryA(path);
#else
return rmdir(path) == 0;
#endif
}

View file

@ -1,34 +0,0 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include "str.h"
typedef void *dir_t;
typedef enum {
FS_TYPE_UNKNOWN,
FS_TYPE_FILE,
FS_TYPE_DIR,
} fs_type_t;
typedef struct {
fs_type_t type;
str_t name;
} dir_entry_t;
dir_t dirOpen(const char *path);
void dirClose(dir_t ctx);
bool dirValid(dir_t ctx);
dir_entry_t *dirNext(dir_t ctx);
void dirCreate(const char *path);
bool dirRemove(const char *path);
#ifdef __cplusplus
} // extern "C"
#endif

View file

@ -1,295 +0,0 @@
#include "dirwatch.h"
#include <stdint.h>
#include <stdlib.h>
#include <assert.h>
#include "tracelog.h"
#if COLLA_WIN
#include "win32_slim.h"
#include "str.h"
typedef struct {
const char *path;
dirwatch_cb_t on_event;
BOOL should_continue;
HANDLE stop_event;
} __dirwatch_internal_t;
static int watchDirThread(void *cdata) {
__dirwatch_internal_t *desc = (__dirwatch_internal_t*)cdata;
// stop_event is called from another thread when watchDirThread should exit
desc->stop_event = CreateEvent(NULL, TRUE, FALSE, NULL);
HANDLE file = CreateFile(
desc->path,
FILE_LIST_DIRECTORY,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
NULL
);
assert(file != INVALID_HANDLE_VALUE);
OVERLAPPED overlapped = {0};
overlapped.hEvent = CreateEvent(NULL, FALSE, 0, NULL);
uint8_t change_buf[1024];
BOOL success = ReadDirectoryChangesW(
file, change_buf, sizeof(change_buf), TRUE,
FILE_NOTIFY_CHANGE_FILE_NAME |
FILE_NOTIFY_CHANGE_DIR_NAME |
FILE_NOTIFY_CHANGE_LAST_WRITE,
NULL, &overlapped, NULL
);
assert(success);
HANDLE events[2] = {
overlapped.hEvent,
desc->stop_event
};
WCHAR old_name[MAX_PATH];
dirwatch_file_t data = {0};
// if the variable is 32bit aligned, a read/write is already atomice
// https://docs.microsoft.com/en-us/windows/win32/sync/interlocked-variable-access
while(desc->should_continue) {
DWORD result = WaitForMultipleObjects(2, events, FALSE, INFINITE);
if(result == WAIT_OBJECT_0) {
DWORD bytes_transferred;
GetOverlappedResult(file, &overlapped, &bytes_transferred, FALSE);
FILE_NOTIFY_INFORMATION *event = (FILE_NOTIFY_INFORMATION*)change_buf;
for(;;) {
DWORD name_len = event->FileNameLength / sizeof(wchar_t);
switch(event->Action) {
case FILE_ACTION_ADDED:
data.name = strFromWCHAR(event->FileName, name_len).buf;
data.oldname = NULL;
desc->on_event(desc->path, DIRWATCH_FILE_ADDED, data);
break;
case FILE_ACTION_REMOVED:
data.name = strFromWCHAR(event->FileName, name_len).buf;
data.oldname = NULL;
desc->on_event(desc->path, DIRWATCH_FILE_REMOVED, data);
break;
case FILE_ACTION_RENAMED_OLD_NAME:
memcpy(old_name, event->FileName, event->FileNameLength);
old_name[event->FileNameLength] = '\0';
break;
case FILE_ACTION_RENAMED_NEW_NAME:
data.name = strFromWCHAR(event->FileName, name_len).buf;
data.oldname = strFromWCHAR(old_name, 0).buf;
desc->on_event(desc->path, DIRWATCH_FILE_RENAMED, data);
break;
}
if(data.name) free(data.name);
if(data.oldname) free(data.oldname);
data = (dirwatch_file_t){0};
if(event->NextEntryOffset) {
*((uint8_t**)&event) += event->NextEntryOffset;
}
else {
break;
}
}
success = ReadDirectoryChangesW(
file, change_buf, sizeof(change_buf), TRUE,
FILE_NOTIFY_CHANGE_FILE_NAME |
FILE_NOTIFY_CHANGE_DIR_NAME |
FILE_NOTIFY_CHANGE_LAST_WRITE,
NULL, &overlapped, NULL
);
assert(success);
}
// stop_event
else if(result == WAIT_OBJECT_0 + 1) {
warn("stopping");
break;
}
}
return 0;
}
dirwatch_t watchDir(dirwatch_desc_t desc) {
dirwatch_t dir = {0};
__dirwatch_internal_t *opts = HeapAlloc(
GetProcessHeap(),
0,
sizeof(__dirwatch_internal_t)
);
opts->path = desc.path;
opts->on_event = desc.on_event;
opts->should_continue = true;
dir.desc = (dirwatch_desc_t *)opts;
dir.handle = thrCreate(watchDirThread, (void *)dir.desc);
if(thrValid(dir.handle)) {
info("watching %s", desc.path);
}
return dir;
}
void waitForWatchDir(dirwatch_t *ctx) {
if(!thrValid(ctx->handle)) {
err("not valid");
return;
}
if(!thrJoin(ctx->handle, NULL)) {
err("dirwatch: couldn't wait for thread");
}
info("waited");
HeapFree(GetProcessHeap(), 0, ctx->desc);
}
void stopWatchDir(dirwatch_t *ctx, bool immediately) {
(void)immediately;
__dirwatch_internal_t *opts = (__dirwatch_internal_t *)ctx->desc;
opts->should_continue = false;
if(immediately) {
if(!SetEvent(opts->stop_event)) {
err("couldn't signal event stop_event: %d", GetLastError());
}
}
if(!thrJoin(ctx->handle, NULL)) {
err("dirwatch: couldn't wait for thread");
}
HeapFree(GetProcessHeap(), 0, ctx->desc);
}
#else
#include <sys/inotify.h>
#include <unistd.h> // read
#include <string.h>
#include <errno.h>
#include <linux/limits.h> // MAX_PATH
#include <stdatomic.h>
#define EVENT_SIZE (sizeof(struct inotify_event))
#define BUF_LEN (1024 * (EVENT_SIZE + 16))
#define ERROR(str) { err(str ": %s", strerror(errno)); goto error; }
typedef struct {
const char *path;
dirwatch_cb_t on_event;
atomic_bool should_continue;
int fd;
int wd;
} __dirwatch_internal_t;
static int watchDirThread(void *cdata) {
__dirwatch_internal_t *desc = (__dirwatch_internal_t *)cdata;
info("watching %s", desc->path);
int length/*, fd, wd*/;
char buffer[BUF_LEN];
desc->fd = inotify_init();
if(desc->fd < 0) {
ERROR("inotify_init failed");
}
desc->wd = inotify_add_watch(desc->fd, desc->path, IN_MOVE | IN_CREATE | IN_DELETE);
char old_path[PATH_MAX] = {0};
dirwatch_file_t data = {0};
while(desc->should_continue) {
length = (int)read(desc->fd, buffer, BUF_LEN);
if(length < 0) {
ERROR("couldn't read");
}
for(int i = 0; i < length;) {
struct inotify_event *event = (struct inotify_event *) &buffer[i];
if(event->len) {
uint32_t e = event->mask;
// bool is_dir = e & IN_ISDIR;
if(e & IN_CREATE) {
data.name = event->name;
desc->on_event(desc->path, DIRWATCH_FILE_ADDED, data);
}
else if(e & IN_DELETE) {
data.name = event->name;
desc->on_event(desc->path, DIRWATCH_FILE_REMOVED, data);
}
else if(e & IN_MOVED_FROM) {
memcpy(old_path, event->name, event->len);
old_path[event->len] = '\0';
}
else if(e & IN_MOVED_TO) {
data.oldname = old_path;
data.name = event->name;
desc->on_event(desc->path, DIRWATCH_FILE_RENAMED, data);
}
}
i += EVENT_SIZE + event->len;
}
}
inotify_rm_watch(desc->fd, desc->wd);
close(desc->fd);
return 0;
error:
return 1;
}
dirwatch_t watchDir(dirwatch_desc_t desc) {
dirwatch_t dir = {0};
__dirwatch_internal_t *opts = malloc(sizeof(__dirwatch_internal_t));
opts->path = desc.path;
opts->on_event = desc.on_event;
opts->fd = 0;
opts->wd = 0;
opts->should_continue = true;
dir.desc = (dirwatch_desc_t *)opts;
dir.handle = thrCreate(watchDirThread, opts);
return dir;
}
void waitForWatchDir(dirwatch_t *ctx) {
thrJoin(ctx->handle, NULL);
free(ctx->desc);
}
void stopWatchDir(dirwatch_t *ctx, bool immediately) {
__dirwatch_internal_t *opts = (__dirwatch_internal_t *)ctx->desc;
opts->should_continue = false;
// this one might get called in the thread first, but it doesn't matter
if(immediately) {
inotify_rm_watch(opts->fd, opts->wd);
close(opts->fd);
}
thrJoin(ctx->handle, NULL);
free(opts);
}
#endif

View file

@ -1,67 +0,0 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
/*
* Basic usage:
*
* // this function will be called from another thread every time
* // something happens to 'path'
* void onEvent(const char *path, int action, dirwatch_file_t file) {
* switch(action) {
* case DIRWATCH_FILE_ADDED: [do something] break;
* case DIRWATCH_FILE_REMOVED: [do something] break;
* case DIRWATCH_FILE_RENAMED: [do something] break;
* }
* }
*
* int main() {
* dirwatch_t dir = watchDir((dirwatch_desc_t){
* .dirname = "watch/",
* .on_event = onEvent
* });
*
* waitForWatchDir(&dir);
* }
*/
#include "collatypes.h"
#include "cthreads.h"
enum {
DIRWATCH_FILE_ADDED,
DIRWATCH_FILE_REMOVED,
DIRWATCH_FILE_RENAMED,
};
typedef struct {
char *name;
char *oldname;
} dirwatch_file_t;
typedef void (*dirwatch_cb_t)(const char *path, int action, dirwatch_file_t data);
typedef struct {
const char *path;
dirwatch_cb_t on_event;
} dirwatch_desc_t;
typedef struct {
cthread_t handle;
dirwatch_desc_t *desc;
} dirwatch_t;
// Creates a thread and starts watching the folder specified by desc
// if any action happend on_event will be called from this thread
dirwatch_t watchDir(dirwatch_desc_t desc);
// waits forever
void waitForWatchDir(dirwatch_t *ctx);
// stops dirwatch thread, if immediately is true, it will try to close it right away
// otherwise it might wait for one last event
void stopWatchDir(dirwatch_t *ctx, bool immediately);
#ifdef __cplusplus
} // extern "C"
#endif

View file

@ -1,134 +0,0 @@
#include "fs.h"
#include <time.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include "tracelog.h"
#if COLLA_WIN
#include "win32_slim.h"
#include <sys/stat.h>
static fsmode_t _modeToType(unsigned int mode) {
switch(mode & _S_IFMT) {
case _S_IFDIR: return FS_MODE_DIR;
case _S_IFCHR: return FS_MODE_CHARACTER_DEVICE;
case _S_IFREG: return FS_MODE_FILE;
case _S_IFIFO: return FS_MODE_FIFO;
default: return FS_MODE_UKNOWN;
}
}
fs_stat_t fsStat(file_t fp) {
TCHAR path[MAX_PATH];
GetFinalPathNameByHandle(
(HANDLE)fp.handle,
path,
MAX_PATH,
0
);
struct stat statbuf;
int res = stat(path, &statbuf);
if(res == 0) {
return (fs_stat_t) {
.type = _modeToType(statbuf.st_mode),
.size = statbuf.st_size,
.last_access = statbuf.st_atime,
.last_modif = statbuf.st_mtime
};
}
else {
return (fs_stat_t) { 0 };
}
}
fs_time_t fsAsTime(int64_t timer) {
struct tm t;
errno_t error = localtime_s(&t, &timer);
if(error == 0) {
return (fs_time_t) {
.year = t.tm_year + 1900,
.month = t.tm_mon + 1,
.day = t.tm_mday,
.hour = t.tm_hour,
.minutes = t.tm_min,
.seconds = t.tm_sec,
.daylight_saving = t.tm_isdst > 0
};
}
else {
char buf[128];
strerror_s(buf, sizeof(buf), error);
err("%s", buf);
return (fs_time_t) { 0 };
}
}
bool fsIsDir(const char *path) {
DWORD attr = GetFileAttributes(path);
return attr != INVALID_FILE_ATTRIBUTES &&
attr & FILE_ATTRIBUTE_DIRECTORY;
}
#else
#include <sys/stat.h>
static fsmode_t _modeToType(unsigned int mode) {
switch(mode & __S_IFMT) {
case __S_IFDIR: return FS_MODE_DIR;
case __S_IFCHR: return FS_MODE_CHARACTER_DEVICE;
case __S_IFREG: return FS_MODE_FILE;
case __S_IFIFO: return FS_MODE_FIFO;
default: return FS_MODE_UKNOWN;
}
}
fs_stat_t fsStat(file_t fp) {
int fd = fileno((FILE*)fp);
struct stat statbuf;
int res = fstat(fd, &statbuf);
if(res == 0) {
return (fs_stat_t) {
.type = _modeToType(statbuf.st_mode),
.size = (uint64_t)statbuf.st_size,
.last_access = statbuf.st_atime,
.last_modif = statbuf.st_mtime
};
}
else {
return (fs_stat_t) { 0 };
}
}
fs_time_t fsAsTime(int64_t timer) {
struct tm *t = localtime(&timer);
if(t) {
return (fs_time_t) {
.year = t->tm_year + 1900,
.month = t->tm_mon + 1,
.day = t->tm_mday,
.hour = t->tm_hour,
.minutes = t->tm_min,
.seconds = t->tm_sec,
.daylight_saving = t->tm_isdst > 0
};
}
else {
err("%s", strerror(errno));
return (fs_time_t) { 0 };
}
}
bool fsIsDir(const char *path) {
struct stat statbuf;
return stat(path, &statbuf) == 0 && S_ISDIR(statbuf.st_mode);
}
#endif

View file

@ -1,43 +0,0 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include "collatypes.h"
#include "file.h"
typedef enum {
FS_MODE_FILE,
FS_MODE_DIR,
FS_MODE_CHARACTER_DEVICE,
FS_MODE_FIFO,
FS_MODE_UKNOWN,
} fsmode_t;
typedef struct {
fsmode_t type;
uint64_t size;
int64_t last_access;
int64_t last_modif;
} fs_stat_t;
typedef struct {
int year;
int month;
int day;
int hour;
int minutes;
int seconds;
bool daylight_saving;
} fs_time_t;
fs_stat_t fsStat(file_t fp);
fs_time_t fsAsTime(int64_t time);
bool fsIsDir(const char *path);
#ifdef __cplusplus
} // extern "C"
#endif

View file

@ -1,130 +0,0 @@
#include "hashmap.h"
#include <string.h>
static uint64 hash_seed = 0;
hashmap_t hmInit(usize initial_cap) {
hashmap_t map = {0};
if (!initial_cap) initial_cap = 512;
vecReserve(map.nodes, initial_cap);
memset(map.nodes, 0, sizeof(hashnode_t) * initial_cap);
return map;
}
void hmFree(hashmap_t map) {
vecFree(map.nodes);
}
void hmSet(hashmap_t *map, uint64 hash, uint64 index) {
uint32 hm_index = hash % vecCap(map->nodes);
while (map->nodes[hm_index].hash) {
hashnode_t *node = &map->nodes[hm_index];
if (node->hash == hash) {
node->index = index;
return;
}
hm_index = (hm_index + 1) % vecCap(map->nodes);
}
map->nodes[hm_index].hash = hash;
map->nodes[hm_index].index = index;
_veclen(map->nodes)++;
float load_factor = (float)vecLen(map->nodes) / (float)vecCap(map->nodes);
if (load_factor > 0.75f) {
uint32 old_cap = vecCap(map->nodes);
vecReserve(map->nodes, old_cap);
for (usize i = old_cap; i < vecCap(map->nodes); ++i) {
map->nodes[i].hash = 0;
map->nodes[i].index = 0;
}
}
}
uint64 hmGet(hashmap_t map, uint64 hash) {
uint32 hm_index = hash % vecCap(map.nodes);
do {
hashnode_t *node = &map.nodes[hm_index];
if (node->hash == hash) {
return node->index;
}
hm_index = (hm_index + 1) % vecCap(map.nodes);
} while (map.nodes[hm_index].hash);
return 0;
}
void hmDelete(hashmap_t *map, uint64 hash) {
uint32 hm_index = hash % vecCap(map->nodes);
do {
hashnode_t *node = &map->nodes[hm_index];
if (node->hash == hash) {
node->hash = 0;
node->index = 0;
break;
}
hm_index = (hm_index + 1) % vecCap(map->nodes);
} while (map->nodes[hm_index].hash);
if(vecLen(map->nodes)) _veclen(map->nodes)--;
}
void hashSetSeed(uint64 new_seed) {
hash_seed = new_seed;
}
uint64 hash(const void *ptr, usize len) {
const uint64 m = 0xC6A4A7935BD1E995LLU;
const int r = 47;
uint64 h = hash_seed ^ (len * m);
const uint64 *data = (const uint64 *)ptr;
const uint64 *end = (len >> 3) + data;
while (data != end) {
uint64 k = *data++;
k *= m;
k ^= k >> r;
k *= m;
h ^= k;
h *= m;
}
const unsigned char * data2 = (const unsigned char *)data;
switch(len & 7) {
case 7: h ^= (uint64_t)(data2[6]) << 48;
case 6: h ^= (uint64_t)(data2[5]) << 40;
case 5: h ^= (uint64_t)(data2[4]) << 32;
case 4: h ^= (uint64_t)(data2[3]) << 24;
case 3: h ^= (uint64_t)(data2[2]) << 16;
case 2: h ^= (uint64_t)(data2[1]) << 8;
case 1: h ^= (uint64_t)(data2[0]);
h *= m;
};
h ^= h >> r;
h *= m;
h ^= h >> r;
return h;
}
uint64 hashStr(str_t str) {
return hash(str.buf, str.len);
}
uint64 hashView(strview_t view) {
return hash(view.buf, view.len);
}
uint64 hashCStr(const char *cstr) {
return hash(cstr, strlen(cstr));
}

View file

@ -1,49 +0,0 @@
#pragma once
#include "collatypes.h"
#include "vec.h"
#include "str.h"
/*
Example usage:
hashSetSeed(time(NULL));
vec(const char *) strings = NULL;
hashmap_t map = hmInit(32);
// mapGet returns 0 in case it doesn't find anything, this way we don't need
// to check its return value
vecAppend(strings, "nil");
hmSet(&map, hashCStr("english"), vecAppend(strings, "hello"));
hmSet(&map, hashCStr("french"), vecAppend(strings, "bonjour"));
hmSet(&map, hashCStr("italian"), vecAppend(strings, "ciao"));
printf("english: %s\n", strings[hmGet(map, hashCStr("english"))]);
printf("french: %s\n", strings[hmGet(map, hashCStr("french"))]);
printf("italian: %s\n", strings[hmGet(map, hashCStr("italian"))]);
mapFree(map);
vecFree(strings);
*/
typedef struct {
uint64 hash;
uint64 index;
} hashnode_t;
typedef struct {
vec(hashnode_t) nodes;
} hashmap_t;
hashmap_t hmInit(usize initial_cap);
void hmFree(hashmap_t map);
void hmSet(hashmap_t *map, uint64 hash, uint64 index);
uint64 hmGet(hashmap_t map, uint64 hash);
void hmDelete(hashmap_t *map, uint64 hash);
void hashSetSeed(uint64 new_seed);
uint64 hash(const void *data, usize len);
uint64 hashStr(str_t str);
uint64 hashView(strview_t view);
uint64 hashCStr(const char *cstr);

View file

@ -1,144 +0,0 @@
#include "jobpool.h"
#include "vec.h"
typedef struct {
cthread_func_t func;
void *arg;
} job_t;
typedef struct {
vec(job_t) jobs;
uint32 head;
cmutex_t work_mutex;
condvar_t work_cond;
condvar_t working_cond;
int32 working_count;
int32 thread_count;
bool stop;
} _pool_internal_t;
static job_t _getJob(_pool_internal_t *pool);
static int _poolWorker(void *arg);
jobpool_t poolInit(uint32 num) {
if (!num) num = 2;
_pool_internal_t *pool = malloc(sizeof(_pool_internal_t));
*pool = (_pool_internal_t){
.work_mutex = mtxInit(),
.work_cond = condInit(),
.working_cond = condInit(),
.thread_count = (int32)num
};
for (usize i = 0; i < num; ++i) {
thrDetach(thrCreate(_poolWorker, pool));
}
return pool;
}
void poolFree(jobpool_t pool_in) {
_pool_internal_t *pool = pool_in;
if (!pool) return;
mtxLock(pool->work_mutex);
pool->stop = true;
condWakeAll(pool->work_cond);
mtxUnlock(pool->work_mutex);
poolWait(pool);
vecFree(pool->jobs);
mtxFree(pool->work_mutex);
condFree(pool->work_cond);
condFree(pool->working_cond);
free(pool);
}
bool poolAdd(jobpool_t pool_in, cthread_func_t func, void *arg) {
_pool_internal_t *pool = pool_in;
if (!pool) return false;
mtxLock(pool->work_mutex);
if (pool->head > vecLen(pool->jobs)) {
vecClear(pool->jobs);
pool->head = 0;
}
job_t job = { func, arg };
vecAppend(pool->jobs, job);
condWake(pool->work_cond);
mtxUnlock(pool->work_mutex);
return true;
}
void poolWait(jobpool_t pool_in) {
_pool_internal_t *pool = pool_in;
if (!pool) return;
mtxLock(pool->work_mutex);
// while its either
// - working and there's still some threads doing some work
// - not working and there's still some threads exiting
while ((!pool->stop && pool->working_count > 0) ||
(pool->stop && pool->thread_count > 0)
) {
condWait(pool->working_cond, pool->work_mutex);
}
mtxUnlock(pool->work_mutex);
}
// == PRIVATE FUNCTIONS ===================================
static job_t _getJob(_pool_internal_t *pool) {
if (pool->head >= vecLen(pool->jobs)) {
pool->head = 0;
}
job_t job = pool->jobs[pool->head++];
return job;
}
static int _poolWorker(void *arg) {
_pool_internal_t *pool = arg;
while (true) {
mtxLock(pool->work_mutex);
// wait for a new job
while (pool->head >= vecLen(pool->jobs) && !pool->stop) {
condWait(pool->work_cond, pool->work_mutex);
}
if (pool->stop) {
break;
}
job_t job = _getJob(pool);
pool->working_count++;
mtxUnlock(pool->work_mutex);
if (job.func) {
job.func(job.arg);
}
mtxLock(pool->work_mutex);
pool->working_count--;
if (!pool->stop &&
pool->working_count == 0 &&
pool->head == vecLen(pool->jobs)
) {
condWake(pool->working_cond);
}
mtxUnlock(pool->work_mutex);
}
pool->thread_count--;
condWake(pool->working_cond);
mtxUnlock(pool->work_mutex);
return 0;
}

View file

@ -1,12 +0,0 @@
#pragma once
#include "collatypes.h"
#include "cthreads.h"
typedef void *jobpool_t;
jobpool_t poolInit(uint32 num);
void poolFree(jobpool_t pool);
bool poolAdd(jobpool_t pool, cthread_func_t func, void *arg);
void poolWait(jobpool_t pool);

View file

@ -1,107 +0,0 @@
#include "os.h"
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#if COLLA_WIN
#define _BUFSZ 128
#include <lmcons.h>
// modified from netbsd source http://cvsweb.netbsd.org/bsdweb.cgi/pkgsrc/pkgtools/libnbcompat/files/getdelim.c?only_with_tag=MAIN
isize getdelim(char **buf, size_t *bufsz, int delimiter, FILE *fp) {
char *ptr, *eptr;
if(*buf == NULL || *bufsz == 0) {
*bufsz = _BUFSZ;
if((*buf = malloc(*bufsz)) == NULL) {
return -1;
}
}
isize result = -1;
// usually fgetc locks every read, using windows-specific
// _lock_file and _unlock_file will be faster
_lock_file(fp);
for(ptr = *buf, eptr = *buf + *bufsz;;) {
int c = _getc_nolock(fp);
if(c == -1) {
if(feof(fp)) {
isize diff = (isize)(ptr - *buf);
if(diff != 0) {
*ptr = '\0';
result = diff;
break;
}
}
break;
}
*ptr++ = (char)c;
if(c == delimiter) {
*ptr = '\0';
result = ptr - *buf;
break;
}
if((ptr + 2) >= eptr) {
char *nbuf;
size_t nbufsz = *bufsz * 2;
isize d = ptr - *buf;
if((nbuf = realloc(*buf, nbufsz)) == NULL) {
break;
}
*buf = nbuf;
*bufsz = nbufsz;
eptr = nbuf + nbufsz;
ptr = nbuf + d;
}
}
_unlock_file(fp);
return result;
}
// taken from netbsd source http://cvsweb.netbsd.org/bsdweb.cgi/pkgsrc/pkgtools/libnbcompat/files/getline.c?only_with_tag=MAIN
isize getline(char **line_ptr, size_t *n, FILE *stream) {
return getdelim(line_ptr, n, '\n', stream);
}
str_t getUserName() {
char buf[UNLEN + 1];
DWORD sz = sizeof(buf);
BOOL res = GetUserNameA(buf, &sz);
if(!res) {
return strInit();
}
return strFromBuf(buf, sz);
}
#else
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <ctype.h>
int stricmp(const char *a, const char *b) {
int result;
if (a == b) {
return 0;
}
while ((result = tolower(*a) - tolower(*b++)) == 0) {
if (*a++ == '\0') {
break;
}
}
return result;
}
str_t getUserName() {
return strFromStr(getlogin());
}
#endif

View file

@ -1,30 +0,0 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <stddef.h>
#include <string.h>
#include "str.h"
#include "collatypes.h"
#if COLLA_WIN
#include <stdio.h>
#include "win32_slim.h"
isize getdelim(char **buf, size_t *bufsz, int delimiter, FILE *fp);
isize getline(char **line_ptr, size_t *n, FILE *stream);
#define stricmp _stricmp
#else
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <stdio.h>
int stricmp(const char *a, const char *b);
#endif
str_t getUserName();
#ifdef __cplusplus
} // extern "C"
#endif

163
dir.c Normal file
View file

@ -0,0 +1,163 @@
#include "dir.h"
#if COLLA_WIN
#include <windows.h>
typedef struct dir_t {
WIN32_FIND_DATA find_data;
HANDLE handle;
dir_entry_t cur_entry;
dir_entry_t next_entry;
} dir_t;
static dir_entry_t dir__entry_from_find_data(arena_t *arena, WIN32_FIND_DATA *fd) {
dir_entry_t out = {0};
out.name = str(arena, fd->cFileName);
if (fd->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
out.type = DIRTYPE_DIR;
}
else {
LARGE_INTEGER filesize = {
.LowPart = fd->nFileSizeLow,
.HighPart = fd->nFileSizeHigh,
};
out.filesize = filesize.QuadPart;
}
return out;
}
dir_t *dirOpen(arena_t *arena, strview_t path) {
uint8 tmpbuf[1024] = {0};
arena_t scratch = arenaMake(ARENA_STATIC, sizeof(tmpbuf), tmpbuf);
TCHAR *winpath = strvToTChar(&scratch, path);
// get a little extra leeway
TCHAR fullpath[MAX_PATH + 16] = {0};
DWORD pathlen = GetFullPathName(winpath, MAX_PATH, fullpath, NULL);
// add asterisk at the end of the path
if (fullpath[pathlen ] != '\\' && fullpath[pathlen] != '/') {
fullpath[pathlen++] = '\\';
}
fullpath[pathlen++] = '*';
fullpath[pathlen++] = '\0';
dir_t *ctx = alloc(arena, dir_t);
ctx->handle = FindFirstFile(fullpath, &ctx->find_data);
if (ctx->handle == INVALID_HANDLE_VALUE) {
arenaPop(arena, sizeof(dir_t));
return NULL;
}
ctx->next_entry = dir__entry_from_find_data(arena, &ctx->find_data);
return ctx;
}
void dirClose(dir_t *ctx) {
FindClose(ctx->handle);
ctx->handle = INVALID_HANDLE_VALUE;
}
bool dirIsValid(dir_t *ctx) {
return ctx && ctx->handle != INVALID_HANDLE_VALUE;
}
dir_entry_t *dirNext(arena_t *arena, dir_t *ctx) {
if (!dirIsValid(ctx)) {
return NULL;
}
ctx->cur_entry = ctx->next_entry;
ctx->next_entry = (dir_entry_t){0};
if (FindNextFile(ctx->handle, &ctx->find_data)) {
ctx->next_entry = dir__entry_from_find_data(arena, &ctx->find_data);
}
else {
dirClose(ctx);
}
return &ctx->cur_entry;
}
#elif COLLA_POSIX
#include <dirent.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
// taken from https://sites.uclouvain.be/SystInfo/usr/include/dirent.h.html
// hopefully shouldn't be needed
#ifndef DT_DIR
#define DT_DIR 4
#endif
#ifndef DT_REG
#define DT_REG 8
#endif
typedef struct dir_t {
DIR *dir;
dir_entry_t next;
} dir_t;
dir_t *dirOpen(arena_t *arena, strview_t path) {
if (strvIsEmpty(path)) {
err("path cannot be null");
return NULL;
}
uint8 tmpbuf[1024];
arena_t scratch = arenaMake(ARENA_STATIC, sizeof(tmpbuf), tmpbuf);
str_t dirpath = str(&scratch, path);
DIR *dir = opendir(dirpath.buf);
if (!dir) {
err("could not open dir (%v)", path);
return NULL;
}
dir_t *ctx = alloc(arena, dir_t);
ctx->dir = dir;
return ctx;
}
void dirClose(dir_t *ctx) {
if (ctx) {
closedir(ctx->dir);
}
}
bool dirIsValid(dir_t *ctx) {
return ctx && ctx->dir;
}
dir_entry_t *dirNext(arena_t *arena, dir_t *ctx) {
if (!ctx) return NULL;
struct dirent *dp = readdir(ctx->dir);
if (!dp) {
dirClose(ctx);
return NULL;
}
ctx->next.name = (str_t){ dp->d_name, strlen(dp->d_name) };
ctx->next.type = dp->d_type == DT_DIR ? DIRTYPE_DIR : DIRTYPE_FILE;
ctx->next.filesize = 0;
if (dp->d_type == DT_REG) {
struct stat file_info = {0};
stat(dp->d_name, &file_info);
ctx->next.filesize = file_info.st_size;
}
return &ctx->next;
}
#endif

25
dir.h Normal file
View file

@ -0,0 +1,25 @@
#pragma once
#include "str.h"
#include "arena.h"
typedef struct dir_t dir_t;
typedef enum {
DIRTYPE_FILE,
DIRTYPE_DIR,
} dir_type_e;
typedef struct {
str_t name;
dir_type_e type;
usize filesize;
} dir_entry_t;
dir_t *dirOpen(arena_t *arena, strview_t path);
// optional, only call this if you want to return before dirNext returns NULL
void dirClose(dir_t *ctx);
bool dirIsValid(dir_t *ctx);
dir_entry_t *dirNext(arena_t *arena, dir_t *ctx);

131
docs/arena.md Normal file
View file

@ -0,0 +1,131 @@
---
title = Arena
---
# Arena
-----------
An arena is a bump allocator, under the hood it can use one of 3 strategies to allocate its data:
* `Virtual`: allocates with virtual memory, meaning that it reserves the data upfront, but only allocates one page at a time (usually 64 Kb). This is the preferred way to use the arena as it can freely allocate gigabytes of memory for free
* `Malloc`: allocates the memory upfront using malloc
* `Static`: uses a buffer passed by the user instead of allocating
To help with allocating big chunks of data, `arena.h` defines the macros `KB`, `MB`, and `GB`. If you don't need them you can define `ARENA_NO_SIZE_HELPERS`
To create an arena use the macro `arenaMake`:
```c
arena_t varena = arenaMake(ARENA_VIRTUAL, GB(1));
uint8 buffer[1024];
arena_t sarena = arenaMake(ARENA_STATIC, sizeof(buffer), buffer);
```
To allocate use the `alloc` macro. The parameters to allocate are:
* A pointer to the arena
* The type to allocate
* (optional) the number of items to allocate
* (optional) flags:
* `ALLOC_NOZERO`: by default the arena sets the memory to zero before returning, use this if you want to skip it
* `ALLOC_SOFT_FAIL`: by default the arena panics when an allocation fails, meaning that it never returns `NULL`.
* (automatic) the align of the type
* (automatic) the size of the type
Example usage:
```c
// allocate 15 strings
str_t *string_list = alloc(arena, str_t, 15);
// allocate a 1024 bytes buffer
uint8 *buffer = alloc(arena, uint8, 1024);
// allocate a structure
game_t *game = alloc(arena, game_t);
```
The strength of the arena is that it makes it much easier to reason about lifetimes, for instance:
```c
// pass a pointer to the arena for the data that we need to return
WCHAR *win32_get_full_path(arena_t *arena, const char *rel_path) {
// we need a small scratch arena for allocations that
// will be thrown away at the end of this function
uint8 scratch_buf[1024];
arena_t scratch = arenaMake(ARENA_STATIC, sizeof(scratch_buf, scratch_buf));
WCHAR *win32_rel_path = str_to_wchar(&scratch, rel_path);
DWORD pathlen = GetFullPathName(win32_rel_path, 0, NULL, NULL);
WCHAR *fullpath = alloc(arena, WCHAR, pathlen + 1);
GetFullPath(win32_rel_path, pathlen + 1, fullpath, NULL);
// notice how we don't need to free anything at the end
return fullpath;
}
```
There are a few helper functions:
* `arenaScratch`: sub allocate an arena from another arena
* `arenaTell`: returns the number of bytes allocated
* `arenaRemaining`: returns the number of bytes that can still be allocated
* `arenaRewind`: rewinds the arena to N bytes from the start (effectively "freeing" that memory)
* `arenaPop`: pops N bytes from the arena (effectively "freeing" that memory)
Here is an example usage of a full program:
```c
typedef struct {
char *buf;
usize len;
} str_t;
str_t read_file(arena_t *arena, const char *filename) {
str_t out = {0};
FILE *fp = fopen(filename, "rb");
if (!fp) goto error;
fseek(fp, 0, SEEK_END);
out.len = ftell(fp);
fseek(fp, 0, SEEK_SET);
out.buf = alloc(arena, char, out.len + 1);
fread(out.buf, 1, out.len, fp);
error:
return out;
}
void write_file(const char *filename, str_t data) {
FILE *fp = fopen(filename, "wb");
if (!fp) return;
fwrite(data.buf, 1, data.len, fp);
}
int main() {
arena_t arena = arenaMake(ARENA_VIRTUAL, GB(1));
str_t title = {0};
{
uint8 tmpbuf[KB(5)];
arena_t scratch = arenaMake(ARENA_STATIC, sizeof(tmpbuf), tmpbuf);
str_t file = read_file(&scratch, "file.json");
json_t json = json_parse(&scratch, file);
title = str_dup(arena, json_get(json, "title"));
}
{
// copying an arena by value effectively makes it a scratch arena,
// as long as you don't use the original inside the same scope!
arena_t scratch = arena;
str_t to_write = str_fmt(
&scratch,
"{ \"title\": \"%s\" }", title.buf
);
write_file("out.json", to_write);
}
// cleanup all allocations at once
arenaCleanup(&arena);
}
```

32
docs/base64.md Normal file
View file

@ -0,0 +1,32 @@
---
title = Base 64
---
# Base 64
----------
The `base64.h` header has only two functions, one to encode and one to decode to/from base 64.
Example usage:
```c
char *recv_callback(arena_t *arena, buffer_t msg) {
buffer_t decoded = base64Decode(arena, msg);
alloc(arena, char); // allocate an extra char for the null pointer
return (char *)decoded.data;
}
buffer_t send_callback(arena_t *arena) {
char msg[] = "Hello World!";
buffer_t buf = {
.data = msg,
.len = arrlen(msg) - 1, // don't include the null pointer
};
buffer_t encoded = base64Encode(arena, buf);
return encoded;
}
int main() {
arena_t arena = arenaMake(ARENA_VIRTUAL, GB(1));
run_server(&arena, 8080, recv_callback, send_callback);
arenaCleanup(&arena);
}
```

49
docs/cthreads.md Normal file
View file

@ -0,0 +1,49 @@
---
title = Threads
---
# Threads
----------
Cross platform threads, mutexes and conditional variables.
The api is very similar to pthreads, here is a small example program that uses threads and mutexes.
```c
struct {
bool exit;
cmutex_t mtx;
} state = {0};
int f1(void *) {
mtxLock(state.mtx);
state.exit = true;
mtxUnlock(state.mtx);
return 0;
}
int f2(void *) {
while (true) {
bool exit = false;
if (mtxTryLock(state.mtx)) {
exit = state.exit;
mtxUnlock(state.mtx);
}
if (exit) {
break;
}
}
return 0;
}
int main() {
state.mtx = mtxInit();
cthread_t t1 = thrCreate(f1, NULL);
thrDetach(t1);
cthread_t t2 = thrCreate(f2, NULL);
thrJoin(t2, NULL);
mtxFree(state.mtx);
}
```

52
docs/dir.md Normal file
View file

@ -0,0 +1,52 @@
---
title = Dir
---
# Dir
----------
This header provides a simple directory walker, here is an example usage:
```c
typedef struct source_t {
str_t filename;
struct source_t *next;
} source_t;
sources_t get_sources(arena_t *arena, strview_t path) {
uint8 tmpbuf[KB(5)] = {0};
arena_t scratch = arenaMake(ARENA_STATIC, sizeof(tmpbuf), tmpbuf);
dir_t *dir = dirOpen(&scratch, path);
dir_entry_t *entry = NULL;
source_t *sources = NULL;
while ((entry = dirNext(&scratch, dir))) {
if (entry->type != DIRTYPE_FILE) {
continue;
}
strview_t ext = fileGetExtension(strv(entry->name));
if (!strvEquals(ext, strv(".c"))) {
continue;
}
source_t *new_source = alloc(arena, source_t);
new_source->filename = strFmt(arena, "%v/%v", path, entry->name);
new_source->next = sources;
sources = new_source;
}
return sources;
}
int main() {
arena_t arena = arenaMake(ARENA_VIRTUAL, GB(1));
source_t *sources = get_sources(&arena, strv("src/colla"));
while (sources) {
info("> %v", sources->filename);
sources = sources->next;
}
arenaCleanup(&arena);
}
```

BIN
docs/docs.com Normal file

Binary file not shown.

41
docs/file.md Normal file
View file

@ -0,0 +1,41 @@
---
title = File
---
# File
----------
This header provides cross platform file functionality.
It has all the basics that you can expect which work exactly like the stdio counterparts:
* `fileOpen`
* `fileClose`
* `fileIsValid`
* `fileRead`
* `fileWrite`
* `fileSeekEnd`
* `fileRewind`
* `fileTell`
Then there are a few helpers functions for reading / writing:
* Writing:
* `filePutc`
* `filePuts`
* `filePrintf`
* `fileWriteWhole`
* Reading:
* `fileReadWhole`
* `fileReadWholeStr`
There are also some functions to get info about a file without having to open it:
* `fileExists`
* `fileSize`
* `fileGetTime`
* `fileHasChanged`
And finally, there are some helper functions:
* `fileGetFullPath` (for windows)
* `fileSplitPath` / `fileGetFilename` / `fileGetExtension`
* `fileDelete`

28
docs/format.md Normal file
View file

@ -0,0 +1,28 @@
---
title = Format
---
# Format
----------
Small formatting utility, it has 2 functions (and the `va_list` alternatives):
* `fmtPrint`: equivalent to
* `fmtBuffer`
It uses [stb_sprintf](https://github.com/nothings/stb/blob/master/stb_sprintf.h) under the hood, but it also has support for printing buffers using `%v` (structures that are a pair of pointer/size)
This means it can print strings and [string views](/str).
In
Usage example:
```c
int main() {
strview_t v = strv("world");
char buffer[1024] = {0};
fmtPrint("Hello %v!", v);
fmtBuffer(buffer, sizeof(buffer), "Hello %v!", v);
}
```

37
docs/highlight.md Normal file
View file

@ -0,0 +1,37 @@
---
title = Highlight
---
# Highlight
----------
This header provides an highlighter for c-like languages (mostly c).
The usage is simple, first create a highlight context using hlInit, for which you need an hl_config_t. The only mandatory argument is colors, which are the strings put before the keywords in the highlighted text.
You can also pass some flags:
* `HL_FLAG_HTML`: escapes html characters
If you're using the offline documentation, this is the code used to highlight inside the markdown code blocks (simplified to remove actual markdown parsing):
```c
str_t highlight_code_block(arena_t *arena, strview_t code) {
uint8 tmpbuf[KB(5)];
arena_t scratch = arenaMake(ARENA_STATIC, sizeof(tmpbuf), tmpbuf);
hl_ctx_t *hl = hlInit(&scratch, &(hl_config_t){
.colors = {
[HL_COLOR_NORMAL] = strv("</span>"),
[HL_COLOR_PREPROC] = strv("<span class=\"c-preproc\">"),
[HL_COLOR_TYPES] = strv("<span class=\"c-types\">"),
[HL_COLOR_CUSTOM_TYPES] = strv("<span class=\"c-custom-types\">"),
[HL_COLOR_KEYWORDS] = strv("<span class=\"c-kwrds\">"),
[HL_COLOR_NUMBER] = strv("<span class=\"c-num\">"),
[HL_COLOR_STRING] = strv("<span class=\"c-str\">"),
[HL_COLOR_COMMENT] = strv("<span class=\"c-cmnt\">"),
[HL_COLOR_FUNC] = strv("<span class=\"c-func\">"),
[HL_COLOR_SYMBOL] = strv("<span class=\"c-sym\">"),
[HL_COLOR_MACRO] = strv("<span class=\"c-macro\">"),
},
.flags = HL_FLAG_HTML,
});
}
```

79
docs/hot_reload.md Normal file
View file

@ -0,0 +1,79 @@
---
title = Hot Reload
---
# Hot Reload
----------
This header provides cross-platform library hot-reloading. To use it you need to have to entry points, one for the host and one for the client.
In the client you can then implement these functions:
* `hr_init`: called when the library is loaded (or reloaded)
* `hr_loop`: called every "tick" (or whenever the host decides)
* `hr_close`: called when the host finishes
In the client you need to call these functions:
* `hrOpen`: load the library and call `hr_init`
* `hrStep`: call `hr_loop`
* `hrReload`: check if the library has changed, and if so reload it and call `hr_init` again
* `hrClose`: call `hr_close` and cleanup
Example usage:
### Client
```c
int hr_init(hr_t *ctx) {
uint8 tmpbuf[KB(5)];
arena_t scratch = arenaMake(ARENA_STATIC, sizeof(tmpbuf), tmpbuf);
state_t *state = ctx->userdata;
uint64 timestamp = fileGetTime(scratch, strv("sprite.png"));
if (timestamp > state->last_write) {
state->last_write = timestamp;
destroy_image(state->sprite);
state->sprite = load_image(strv("sprite.png"));
}
}
int hr_loop(hr_t *ctx) {
state_t *state = ctx->userdata;
draw_image(state->sprite, state->posx, state->posy);
}
int hr_close(hr_t *ctx) {
state_t *state = ctx->userdata;
destroy_image(state->sprite);
}
```
### Host
```c
typedef struct {
hr_t hr;
image_t sprite;
int posx;
int posy;
uint64 last_write;
} state_t;
int main() {
arena_t arena = arenaMake(ARENA_VIRTUAL, GB(1));
state_t state = {0};
state.hr.userdata = &state;
if (!hrOpen(&state.hr, strv("bin/client.dll"))) {
return 1;
}
while (game_poll()) {
hrReload(&state.hr);
hrStep(&state.hr);
}
hrClose(&state.hr, true);
}
```

45
docs/html.md Normal file
View file

@ -0,0 +1,45 @@
---
title = HTML
---
# HTML
----------
This header provides an easy (although debatably sane) way to generate html in c.
There are three types of tags:
* One and done tags, like `<img>` or `<hr>` which only have an opening tag
* Basic tags which follow this format: `<tag>content</tag>`
* Tags where the content is probably other tags
You can open and close any tags using `tagBeg` and `tagEnd`, you can also set attributes like this:
```c
tagBeg("div", .class="content", .id="main");
```
Finally, you can use the `htmlRaw` macro to write raw html.
Example code:
```c
str_t generate_page(arena_t *arena, page_t *data) {
str_t out = STR_EMPTY;
htmlBeg(arena, &out);
headBeg();
title(data->title);
htmlRaw(<script src="script.js"></script>)
style(data->css);
headEnd();
bodyBeg();
divBeg(.id="main");
h1("Hello World!");
hr();
p("Some content blah blah");
img(.src="image.png");
divEnd();
bodyEnd();
htmlEnd();
return out;
}
```

5
docs/http.md Normal file
View file

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

5
docs/ini.md Normal file
View file

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

5
docs/json.md Normal file
View file

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

5
docs/markdown.md Normal file
View file

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

5
docs/readme.md Normal file
View file

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

5
docs/server.md Normal file
View file

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

5
docs/sha1.md Normal file
View file

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

5
docs/socket.md Normal file
View file

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

5
docs/str.md Normal file
View file

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

5
docs/strstream.md Normal file
View file

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

5
docs/tracelog.md Normal file
View file

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

5
docs/utf8.md Normal file
View file

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

5
docs/vec.md Normal file
View file

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

5
docs/vmem.md Normal file
View file

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

5
docs/websocket.md Normal file
View file

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

5
docs/xml.md Normal file
View file

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

View file

@ -7,7 +7,6 @@
#if COLLA_WIN #if COLLA_WIN
#define WIN32_LEAN_AND_MEAN
#include <windows.h> #include <windows.h>
#undef FILE_BEGIN #undef FILE_BEGIN
@ -59,40 +58,6 @@ TCHAR *fileGetFullPath(arena_t *arena, strview_t filename) {
return full_path; return full_path;
} }
strview_t fileGetFilename(strview_t path) {
usize last_lin = strvRFind(path, '/', 0);
usize last_win = strvRFind(path, '\\', 0);
last_lin = last_lin != SIZE_MAX ? last_lin : 0;
last_win = last_win != SIZE_MAX ? last_win : 0;
usize last = max(last_lin, last_win);
return strvSub(path, last ? last + 1 : last, SIZE_MAX);
}
strview_t fileGetExtension(strview_t path) {
usize ext_pos = strvRFind(path, '.', 0);
return strvSub(path, ext_pos, SIZE_MAX);
}
void fileSplitPath(strview_t path, strview_t *dir, strview_t *name, strview_t *ext) {
usize dir_lin = strvRFind(path, '/', 0);
usize dir_win = strvRFind(path, '\\', 0);
dir_lin = dir_lin != STR_NONE ? dir_lin : 0;
dir_win = dir_win != STR_NONE ? dir_win : 0;
usize dir_pos = max(dir_lin, dir_win);
usize ext_pos = strvRFind(path, '.', 0);
if (dir) {
*dir = strvSub(path, 0, dir_pos);
}
if (name) {
*name = strvSub(path, dir_pos ? dir_pos + 1 : dir_pos, ext_pos);
}
if (ext) {
*ext = strvSub(path, ext_pos, SIZE_MAX);
}
}
bool fileDelete(arena_t scratch, strview_t filename) { bool fileDelete(arena_t scratch, strview_t filename) {
wchar_t *wfname = strvToWChar(&scratch, filename, NULL); wchar_t *wfname = strvToWChar(&scratch, filename, NULL);
return DeleteFileW(wfname); return DeleteFileW(wfname);
@ -196,6 +161,11 @@ bool fileExists(const char *name) {
return exists; return exists;
} }
bool fileDelete(arena_t scratch, strview_t filename) {
str_t name = str(&scratch, filename);
return remove(name.buf) == 0;
}
file_t fileOpen(arena_t scratch, strview_t name, filemode_e mode) { file_t fileOpen(arena_t scratch, strview_t name, filemode_e mode) {
str_t filename = str(&scratch, name); str_t filename = str(&scratch, name);
return (file_t) { return (file_t) {
@ -261,6 +231,40 @@ uint64 fileGetTimeFP(file_t ctx) {
#endif #endif
strview_t fileGetFilename(strview_t path) {
usize last_lin = strvRFind(path, '/', 0);
usize last_win = strvRFind(path, '\\', 0);
last_lin = last_lin != SIZE_MAX ? last_lin : 0;
last_win = last_win != SIZE_MAX ? last_win : 0;
usize last = max(last_lin, last_win);
return strvSub(path, last ? last + 1 : last, SIZE_MAX);
}
strview_t fileGetExtension(strview_t path) {
usize ext_pos = strvRFind(path, '.', 0);
return strvSub(path, ext_pos, SIZE_MAX);
}
void fileSplitPath(strview_t path, strview_t *dir, strview_t *name, strview_t *ext) {
usize dir_lin = strvRFind(path, '/', 0);
usize dir_win = strvRFind(path, '\\', 0);
dir_lin = dir_lin != STR_NONE ? dir_lin : 0;
dir_win = dir_win != STR_NONE ? dir_win : 0;
usize dir_pos = max(dir_lin, dir_win);
usize ext_pos = strvRFind(path, '.', 0);
if (dir) {
*dir = strvSub(path, 0, dir_pos);
}
if (name) {
*name = strvSub(path, dir_pos ? dir_pos + 1 : dir_pos, ext_pos);
}
if (ext) {
*ext = strvSub(path, ext_pos, SIZE_MAX);
}
}
bool filePutc(file_t ctx, char c) { bool filePutc(file_t ctx, char c) {
return fileWrite(ctx, &c, 1) == 1; return fileWrite(ctx, &c, 1) == 1;
} }
@ -312,13 +316,16 @@ 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) {
file_t fp = fileOpen(*arena, name, FILE_READ); file_t fp = fileOpen(*arena, name, FILE_READ);
if (!fileIsValid(fp)) {
warn("could not open file (%v)", name);
}
str_t out = fileReadWholeStrFP(arena, fp); str_t out = fileReadWholeStrFP(arena, fp);
fileClose(fp); fileClose(fp);
return out; return out;
} }
str_t fileReadWholeStrFP(arena_t *arena, file_t ctx) { str_t fileReadWholeStrFP(arena_t *arena, file_t ctx) {
if (!fileIsValid(ctx)) return (str_t){0}; if (!fileIsValid(ctx)) return STR_EMPTY;
str_t out = {0}; str_t out = {0};
@ -329,7 +336,7 @@ str_t fileReadWholeStrFP(arena_t *arena, file_t ctx) {
if (read != out.len) { if (read != out.len) {
err("fileReadWholeStrFP: fileRead failed, should be %zu but is %zu", out.len, read); err("fileReadWholeStrFP: fileRead failed, should be %zu but is %zu", out.len, read);
arenaPop(arena, out.len + 1); arenaPop(arena, out.len + 1);
return (str_t){0}; return STR_EMPTY;
} }
return out; return out;

View file

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

621
highlight.c Normal file
View file

@ -0,0 +1,621 @@
#include "highlight.h"
// based on https://github.com/Theldus/kat
#include <ctype.h>
#include "arena.h"
#include "tracelog.h"
#include "strstream.h"
typedef enum {
HL_STATE_DEFAULT,
HL_STATE_KEYWORD,
HL_STATE_NUMBER,
HL_STATE_CHAR,
HL_STATE_STRING,
HL_STATE_COMMENT_MULTI,
HL_STATE_PREPROCESSOR,
HL_STATE_PREPROCESSOR_INCLUDE,
HL_STATE_PREPROCESSOR_INCLUDE_STRING,
} hl_state_e;
typedef enum {
HL_HTABLE_FAILED,
HL_HTABLE_REPLACED,
HL_HTABLE_ADDED,
} hl_htable_result_e;
typedef struct hl_node_t {
strview_t key;
hl_color_e value;
struct hl_node_t *next;
} hl_node_t;
typedef struct {
hl_node_t **buckets;
uint count;
uint used;
uint collisions;
} hl_hashtable_t;
static hl_hashtable_t hl_htable_init(arena_t *arena, uint pow2_exp);
static hl_htable_result_e hl_htable_add(arena_t *arena, hl_hashtable_t *table, strview_t key, hl_color_e value);
static hl_node_t *hl_htable_get(hl_hashtable_t *table, strview_t key);
static uint64 hl_htable_hash(const void *bytes, usize count);
typedef struct hl_ctx_t {
hl_state_e state;
hl_flags_e flags;
usize kw_beg;
strview_t colors[HL_COLOR__COUNT]; // todo: maybe should be str_t?
outstream_t ostr;
hl_hashtable_t kw_htable;
bool symbol_table[256];
} hl_ctx_t;
#define KW(str, col) { { str, sizeof(str)-1 }, HL_COLOR_##col }
static hl_keyword_t hl_default_kwrds[] = {
/* C Types. */
KW("double", TYPES),
KW("int", TYPES),
KW("long", TYPES),
KW("char", TYPES),
KW("float", TYPES),
KW("short", TYPES),
KW("unsigned", TYPES),
KW("signed", TYPES),
KW("bool", TYPES),
/* Common typedefs. */
KW("int8", TYPES), KW("uint8", TYPES),
KW("int16", TYPES), KW("uint16", TYPES),
KW("int32", TYPES), KW("uint32", TYPES),
KW("int64", TYPES), KW("uint64", TYPES),
/* Colla keywords */
KW("uchar", TYPES),
KW("ushort", TYPES),
KW("uint", TYPES),
KW("usize", TYPES),
KW("isize", TYPES),
KW("byte", TYPES),
/* Other keywords. */
KW("auto", KEYWORDS), KW("struct", KEYWORDS), KW("break", KEYWORDS),
KW("else", KEYWORDS), KW("switch", KEYWORDS), KW("case", KEYWORDS),
KW("enum", KEYWORDS), KW("register", KEYWORDS), KW("typedef", KEYWORDS),
KW("extern", KEYWORDS), KW("return", KEYWORDS), KW("union", KEYWORDS),
KW("const", KEYWORDS), KW("continue", KEYWORDS), KW("for", KEYWORDS),
KW("void", KEYWORDS), KW("default", KEYWORDS), KW("goto", KEYWORDS),
KW("sizeof", KEYWORDS), KW("volatile", KEYWORDS), KW("do", KEYWORDS),
KW("if", KEYWORDS), KW("static", KEYWORDS), KW("inline", KEYWORDS),
KW("while", KEYWORDS),
};
#undef KW
static bool hl_default_symbols_table[256] = {
['['] = true, [']'] = true, ['('] = true,
[')'] = true, ['{'] = true, ['}'] = true,
['*'] = true, [':'] = true, ['='] = true,
[';'] = true, ['-'] = true, ['>'] = true,
['&'] = true, ['+'] = true, ['~'] = true,
['!'] = true, ['/'] = true, ['%'] = true,
['<'] = true, ['^'] = true, ['|'] = true,
['?'] = true, ['#'] = true,
};
static void hl_write_char(hl_ctx_t *ctx, char c);
static void hl_write(hl_ctx_t *ctx, strview_t v);
static bool hl_is_char_keyword(char c);
static bool hl_highlight_symbol(hl_ctx_t *ctx, char c);
static hl_color_e hl_get_keyword_color(hl_ctx_t *ctx, strview_t keyword);
static bool hl_is_capitalised(strview_t string);
static strview_t hl_finish_keyword(hl_ctx_t *ctx, usize beg, instream_t *in);
static void hl_print_keyword(hl_ctx_t *ctx, strview_t keyword, hl_color_e color);
hl_ctx_t *hlInit(arena_t *arena, hl_config_t *config) {
if (!config) {
err("<config> cannot be null");
return NULL;
}
hl_ctx_t *out = alloc(arena, hl_ctx_t);
out->flags = config->flags;
memcpy(out->symbol_table, hl_default_symbols_table, sizeof(hl_default_symbols_table));
memcpy(out->colors, config->colors, sizeof(config->colors));
int kw_count = arrlen(hl_default_kwrds);
out->kw_htable = hl_htable_init(arena, 8);
for (int i = 0; i < kw_count; ++i) {
hl_keyword_t *kw = &hl_default_kwrds[i];
hl_htable_add(arena, &out->kw_htable, kw->keyword, kw->color);
}
for (int i = 0; i < config->kwrds_count; ++i) {
hl_keyword_t *kw = &config->extra_kwrds[i];
hl_htable_add(arena, &out->kw_htable, kw->keyword, kw->color);
}
return out;
}
void hl_next_char(hl_ctx_t *ctx, instream_t *in) {
char cur = istrGet(in);
bool is_last = istrIsFinished(*in);
switch (ctx->state) {
case HL_STATE_DEFAULT:
{
/*
* If potential keyword.
*
* A valid C keyword may contain numbers, but *not*
* as a suffix.
*/
if (hl_is_char_keyword(cur) && !isdigit(cur)) {
ctx->kw_beg = istrTell(*in);
ctx->state = HL_STATE_KEYWORD;
}
// potential number
else if (isdigit(cur)) {
ctx->kw_beg = istrTell(*in);
ctx->state = HL_STATE_NUMBER;
}
// potential char
else if (cur == '\'') {
ctx->kw_beg = istrTell(*in);
ctx->state = HL_STATE_CHAR;
}
// potential string
else if (cur == '"') {
ctx->kw_beg = istrTell(*in);
ctx->state = HL_STATE_STRING;
}
// line or multiline comment
else if (cur == '/') {
// single line comment
if (istrPeek(in) == '/') {
// rewind before comment begins
istrRewindN(in, 1);
// comment until the end of line
hl_print_keyword(ctx, istrGetLine(in), HL_COLOR_COMMENT);
}
// multiline comment
else if (istrPeek(in) == '*') {
ctx->state = HL_STATE_COMMENT_MULTI;
ctx->kw_beg = istrTell(*in);
istrSkip(in, 1); // skip *
}
else {
// maybe a symbol?
hl_highlight_symbol(ctx, cur);
}
}
// preprocessor
else if (cur == '#') {
// print the # as a symbol
hl_highlight_symbol(ctx, cur);
ctx->kw_beg = istrTell(*in);
ctx->state = HL_STATE_PREPROCESSOR;
}
// other suppored symbols
else if (hl_highlight_symbol(ctx, cur)) {
// noop
}
else {
hl_write_char(ctx, cur);
}
break;
}
case HL_STATE_KEYWORD:
{
// end of keyword, check if it really is a valid keyword
if (!hl_is_char_keyword(cur)) {
strview_t keyword = hl_finish_keyword(ctx, ctx->kw_beg, in);
hl_color_e kw_color = hl_get_keyword_color(ctx, keyword);
if (kw_color != HL_COLOR__COUNT) {
hl_print_keyword(ctx, keyword, kw_color);
// maybe we should highlight this remaining char.
if (!hl_highlight_symbol(ctx, cur)) {
hl_write_char(ctx, cur);
}
}
/*
* If not keyword, maybe its a function call.
*
* Important to note that this is hacky and will only work
* if there is no space between keyword and '('.
*/
else if (cur == '(') {
hl_print_keyword(ctx, keyword, HL_COLOR_FUNC);
// Opening parenthesis will always be highlighted
hl_highlight_symbol(ctx, cur);
}
else {
if (hl_is_capitalised(keyword)) {
hl_print_keyword(ctx, keyword, HL_COLOR_MACRO);
}
else {
hl_write(ctx, keyword);
}
if (!hl_highlight_symbol(ctx, cur)) {
hl_write_char(ctx, cur);
}
}
}
break;
}
case HL_STATE_NUMBER:
{
char c = (char)tolower(cur);
/*
* Should we end the state?.
*
* Very important observation:
* Although the number highlight works fine for most (if not all)
* of the possible cases, it also assumes that the code is written
* correctly and the source is able to compile, meaning that:
*
* Numbers like: 123, 0xABC123, 12.3e4f, 123ULL....
* will be correctly identified and highlighted
*
* But, 'numbers' like: 123ABC, 0xxxxABCxx123, 123UUUUU....
* will also be highlighted.
*
* It also assumes that no keyword will start with a number
* and everything starting with a number (except inside strings or
* comments) will be a number.
*/
if (!isdigit(c) &&
(c < 'a' || c > 'f') &&
c != 'b' && c != 'x' &&
c != 'u' && c != 'l' &&
c != '.'
) {
strview_t keyword = hl_finish_keyword(ctx, ctx->kw_beg, in);
// if not a valid char keyword: valid number
if (!hl_is_char_keyword(cur)) {
hl_print_keyword(ctx, keyword, HL_COLOR_NUMBER);
}
else {
hl_write(ctx, keyword);
}
// maybe we should highlight this remaining char.
if (!hl_highlight_symbol(ctx, cur)) {
hl_write_char(ctx, cur);
}
}
break;
}
case HL_STATE_CHAR:
{
if (is_last || (cur == '\'' && istrPeek(in) != '\'')) {
strview_t keyword = hl_finish_keyword(ctx, ctx->kw_beg, in);
keyword.len++;
hl_print_keyword(ctx, keyword, HL_COLOR_STRING);
}
break;
}
case HL_STATE_STRING:
{
if (is_last || (cur == '"' && istrPrevPrev(in) != '\\')) {
strview_t keyword = hl_finish_keyword(ctx, ctx->kw_beg, in);
keyword.len++;
hl_print_keyword(ctx, keyword, HL_COLOR_STRING);
}
break;
}
case HL_STATE_COMMENT_MULTI:
{
/*
* If we are at the end of line _or_ have identified
* an end of comment...
*/
if (is_last || (cur == '*' && istrPeek(in) == '/')) {
strview_t keyword = hl_finish_keyword(ctx, ctx->kw_beg, in);
hl_print_keyword(ctx, keyword, HL_COLOR_COMMENT);
}
break;
}
case HL_STATE_PREPROCESSOR:
{
if (!hl_is_char_keyword(cur)) {
hl_write_char(ctx, cur);
break;
}
#define hl_check(str, new_state) \
if (cur == str[0]) { \
instream_t temp = *in; \
strview_t a = strvInitLen(&(str[1]), sizeof(str) - 2); \
strview_t b = istrGetViewLen(&temp, a.len); \
if (strvEquals(a, b)) { \
*in = temp; \
hl_print_keyword(ctx, strvInitLen(str, sizeof(str) - 1), HL_COLOR_PREPROC); \
ctx->state = new_state; \
break; \
} \
}
if (is_last) {
strview_t keyword = hl_finish_keyword(ctx, ctx->kw_beg, in);
hl_print_keyword(ctx, keyword, HL_COLOR_PREPROC);
break;
}
hl_check("include", HL_STATE_PREPROCESSOR_INCLUDE)
hl_check("define", HL_STATE_DEFAULT)
hl_check("undef", HL_STATE_DEFAULT)
hl_check("ifdef", HL_STATE_DEFAULT)
hl_check("ifndef", HL_STATE_DEFAULT)
hl_check("if", HL_STATE_DEFAULT)
hl_check("endif", HL_STATE_DEFAULT)
hl_check("pragma", HL_STATE_DEFAULT)
#undef hl_check
break;
}
/*
* Preprocessor/Preprocessor include
*
* This is a 'dumb' preprocessor highlighter:
* it highlights everything with the same color
* and if and only if an '#include' is detected
* the included header will be handled as string
* and thus, will have the same color as the string.
*
* In fact, it is somehow similar to what GtkSourceView
* does (Mousepad, Gedit...) but with one silly difference:
* single-line/multi-line comments will not be handled
* while inside the preprocessor state, meaning that
* comments will also have the same color as the remaining
* of the line, yeah, ugly.
*/
case HL_STATE_PREPROCESSOR_INCLUDE:
{
if (cur == '<' || cur == '"' || is_last) {
ctx->kw_beg = istrTell(*in);
ctx->state = HL_STATE_PREPROCESSOR_INCLUDE_STRING;
}
else {
hl_write_char(ctx, cur);
}
break;
}
case HL_STATE_PREPROCESSOR_INCLUDE_STRING:
{
if (cur == '>' || cur == '"' || is_last) {
strview_t keyword = hl_finish_keyword(ctx, ctx->kw_beg, in);
keyword.len += 1;
hl_print_keyword(ctx, keyword, HL_COLOR_STRING);
}
break;
}
}
}
str_t hlHighlight(arena_t *arena, hl_ctx_t *ctx, strview_t data) {
ctx->ostr = ostrInit(arena);
ctx->state = HL_STATE_DEFAULT;
ctx->kw_beg = 0;
instream_t in = istrInitLen(data.buf, data.len);
while (!istrIsFinished(in)) {
hl_next_char(ctx, &in);
}
hl_next_char(ctx, &in);
return ostrAsStr(&ctx->ostr);
}
void hlSetSymbolInTable(hl_ctx_t *ctx, char symbol, bool value) {
if (!ctx) return;
ctx->symbol_table[(unsigned char)symbol] = value;
}
void hlAddKeyword(arena_t *arena, hl_ctx_t *ctx, hl_keyword_t *keyword) {
hl_htable_add(arena, &ctx->kw_htable, keyword->keyword, keyword->color);
}
//// HASH TABLE ///////////////////////////////////////////////////
static hl_hashtable_t hl_htable_init(arena_t *arena, uint pow2_exp) {
uint count = 1 << pow2_exp;
return (hl_hashtable_t) {
.count = count,
.buckets = alloc(arena, hl_node_t*, count),
};
}
static hl_htable_result_e hl_htable_add(arena_t *arena, hl_hashtable_t *table, strview_t key, hl_color_e value) {
if (!table) {
return HL_HTABLE_FAILED;
}
if ((float)table->used >= table->count * 0.6f) {
warn("more than 60%% of the arena is being used: %d/%d", table->used, table->count);
}
uint64 hash = hl_htable_hash(key.buf, key.len);
usize index = hash & (table->count - 1);
hl_node_t *bucket = table->buckets[index];
if (bucket) table->collisions++;
while (bucket) {
// already exists
if (strvEquals(bucket->key, key)) {
bucket->value = value;
return HL_HTABLE_REPLACED;
}
bucket = bucket->next;
}
bucket = alloc(arena, hl_node_t);
bucket->key = key;
bucket->value = value;
bucket->next = table->buckets[index];
table->buckets[index] = bucket;
table->used++;
return HL_HTABLE_ADDED;
}
static hl_node_t *hl_htable_get(hl_hashtable_t *table, strview_t key) {
if (!table || table->count == 0) {
return NULL;
}
uint64 hash = hl_htable_hash(key.buf, key.len);
usize index = hash & (table->count - 1);
hl_node_t *bucket = table->buckets[index];
while (bucket) {
if (strvEquals(bucket->key, key)) {
return bucket;
}
bucket = bucket->next;
}
return NULL;
}
// uses the sdbm algorithm
static uint64 hl_htable_hash(const void *bytes, usize count) {
const uint8 *data = bytes;
uint64 hash = 0;
for (usize i = 0; i < count; ++i) {
hash = data[i] + (hash << 6) + (hash << 16) - hash;
}
return hash;
}
//// STATIC FUNCTIONS /////////////////////////////////////////////
static inline void hl_escape_html(outstream_t *out, char c) {
switch (c) {
case '&':
ostrPuts(out, strv("&amp"));
break;
case '<':
ostrPuts(out, strv("&lt"));
break;
case '>':
ostrPuts(out, strv("&gt"));
break;
default:
ostrPutc(out, c);
break;
}
}
static void hl_write_char(hl_ctx_t *ctx, char c) {
if (ctx->flags & HL_FLAG_HTML) {
hl_escape_html(&ctx->ostr, c);
}
else {
ostrPutc(&ctx->ostr, c);
}
}
static void hl_write(hl_ctx_t *ctx, strview_t v) {
if (ctx->flags & HL_FLAG_HTML) {
for (usize i = 0; i < v.len; ++i) {
hl_escape_html(&ctx->ostr, v.buf[i]);
}
}
else {
ostrPuts(&ctx->ostr, v);
}
}
static bool hl_is_char_keyword(char c) {
return isalpha(c) || isdigit(c) || c == '_';
}
static bool hl_highlight_symbol(hl_ctx_t *ctx, char c) {
if (!ctx->symbol_table[(unsigned char)c]) {
return false;
}
ostrPuts(&ctx->ostr, ctx->colors[HL_COLOR_SYMBOL]);
hl_write_char(ctx, c);
ostrPuts(&ctx->ostr, ctx->colors[HL_COLOR_NORMAL]);
return true;
}
static hl_color_e hl_get_keyword_color(hl_ctx_t *ctx, strview_t keyword) {
// todo: make this an option?
if (strvEndsWithView(keyword, strv("_t"))) {
return HL_COLOR_CUSTOM_TYPES;
}
hl_node_t *node = hl_htable_get(&ctx->kw_htable, keyword);
return node ? node->value : HL_COLOR__COUNT;
}
static bool hl_is_capitalised(strview_t string) {
for (usize i = 0; i < string.len; ++i) {
char c = string.buf[i];
if (!isdigit(c) && c != '_' && (c < 'A' || c > 'Z')) {
return false;
}
}
return true;
}
static strview_t hl_finish_keyword(hl_ctx_t *ctx, usize beg, instream_t *in) {
ctx->state = HL_STATE_DEFAULT;
beg -= 1;
usize end = istrTell(*in) - 1;
return strv(in->start + beg, end - beg);
}
static void hl_print_keyword(hl_ctx_t *ctx, strview_t keyword, hl_color_e color) {
ostrPuts(&ctx->ostr, ctx->colors[color]);
hl_write(ctx, keyword);
ostrPuts(&ctx->ostr, ctx->colors[HL_COLOR_NORMAL]);
}

49
highlight.h Normal file
View file

@ -0,0 +1,49 @@
#pragma once
#include "str.h"
typedef enum {
HL_COLOR_NORMAL,
HL_COLOR_PREPROC,
HL_COLOR_TYPES,
HL_COLOR_CUSTOM_TYPES,
HL_COLOR_KEYWORDS,
HL_COLOR_NUMBER,
HL_COLOR_STRING,
HL_COLOR_COMMENT,
HL_COLOR_FUNC,
HL_COLOR_SYMBOL,
HL_COLOR_MACRO,
HL_COLOR__COUNT,
} hl_color_e;
typedef enum {
HL_FLAG_NONE = 0,
HL_FLAG_HTML = 1 << 0,
} hl_flags_e;
typedef struct {
strview_t keyword;
hl_color_e color;
} hl_keyword_t;
typedef struct {
usize idx;
usize size;
} hl_line_t;
typedef struct {
strview_t colors[HL_COLOR__COUNT];
hl_keyword_t *extra_kwrds;
int kwrds_count;
hl_flags_e flags;
} hl_config_t;
typedef struct hl_ctx_t hl_ctx_t;
hl_ctx_t *hlInit(arena_t *arena, hl_config_t *config);
str_t hlHighlight(arena_t *arena, hl_ctx_t *ctx, strview_t str);
void hlSetSymbolInTable(hl_ctx_t *ctx, char symbol, bool value);
void hlAddKeyword(arena_t *arena, hl_ctx_t *ctx, hl_keyword_t *keyword);

View file

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

77
html.h Normal file
View file

@ -0,0 +1,77 @@
#pragma once
#include "strstream.h"
typedef struct {
outstream_t stream;
str_t *output;
} html_context_t;
strview_t html__strv_copy(strview_t src) { return src; }
#define html__str_or_strv(str) _Generic(str, \
strview_t: html__strv_copy, \
str_t: strvInitStr, \
const char *: strvInit, \
char *: strvInit \
)(str)
#define htmlPrintf(...) ostrPrintf(&__ctx.stream, __VA_ARGS__)
#define htmlPuts(str) ostrPuts(&__ctx.stream, html__str_or_strv(str))
#define htmlBeg(arena_ptr, str_ptr) { \
html_context_t __ctx = { .stream = ostrInit(arena_ptr), .output = str_ptr }; \
htmlPrintf("<!DOCTYPE html>\n<html>");
#define htmlEnd() htmlPrintf("</html>"); *__ctx.output = ostrAsStr(&__ctx.stream); }
#define html__args() \
X(class) \
X(id) \
X(style) \
X(onclick) \
X(href) \
X(src) \
typedef struct {
#define X(name) const char *name;
html__args()
#undef X
} html_tag_t;
static void html__tag(html_context_t *ctx, const char *tag, html_tag_t *args) {
ostrPrintf(&ctx->stream, "<%s ", tag);
#define X(name, ...) if (args->name) { ostrPrintf(&ctx->stream, #name "=\"%s\" ", args->name); }
html__args()
#undef X
ostrPutc(&ctx->stream, '>');
}
#define tagBeg(tag, ...) do { html_tag_t args = {0, __VA_ARGS__}; html__tag(&__ctx, tag, &args); } while (0)
#define tagEnd(tag) htmlPrintf("</"tag">")
#define html__strv_or_str(s) _Generic(s, str_t: NULL)
#define html__simple_tag(tag, text, ...) do { tagBeg(tag, __VA_ARGS__); htmlPuts(text); tagEnd(tag); } while (0)
#define headBeg(...) tagBeg("head", __VA_ARGS__)
#define headEnd() tagEnd("head")
#define bodyBeg(...) tagBeg("body", __VA_ARGS__)
#define bodyEnd() tagEnd("body")
#define divBeg(...) tagBeg("div", __VA_ARGS__)
#define divEnd() tagEnd("div")
#define htmlRaw(data) ostrPuts(&__ctx.stream, strv(#data))
#define title(text, ...) html__simple_tag("title", text, __VA_ARGS__)
#define h1(text, ...) html__simple_tag("h1", text, __VA_ARGS__)
#define p(text, ...) html__simple_tag("p", text, __VA_ARGS__)
#define span(text, ...) html__simple_tag("span", text, __VA_ARGS__)
#define a(text, ...) html__simple_tag("a", text, __VA_ARGS__)
#define img(...) tagBeg("img", __VA_ARGS__)
#define style(text, ...) html__simple_tag("style", text, __VA_ARGS__)
#define hr() htmlPuts("<hr>")

View file

@ -27,7 +27,7 @@ static const TCHAR *https__get_method_str(http_method_e method);
static http_header_t *http__parse_headers(arena_t *arena, instream_t *in) { static http_header_t *http__parse_headers(arena_t *arena, instream_t *in) {
http_header_t *head = NULL; http_header_t *head = NULL;
strview_t line = (strview_t){0}; strview_t line = STRV_EMPTY;
do { do {
line = istrGetView(in, '\r'); line = istrGetView(in, '\r');
@ -159,7 +159,7 @@ str_t httpReqToStr(arena_t *arena, http_req_t *req) {
case HTTP_HEAD: method = "HEAD"; break; case HTTP_HEAD: method = "HEAD"; break;
case HTTP_PUT: method = "PUT"; break; case HTTP_PUT: method = "PUT"; break;
case HTTP_DELETE: method = "DELETE"; break; case HTTP_DELETE: method = "DELETE"; break;
default: err("unrecognised method: %d", method); return (str_t){0}; default: err("unrecognised method: %d", method); return STR_EMPTY;
} }
ostrPrintf( ostrPrintf(
@ -227,7 +227,7 @@ strview_t httpGetHeader(http_header_t *headers, strview_t key) {
} }
h = h->next; h = h->next;
} }
return (strview_t){0}; return STRV_EMPTY;
} }
str_t httpMakeUrlSafe(arena_t *arena, strview_t string) { str_t httpMakeUrlSafe(arena_t *arena, strview_t string) {
@ -288,7 +288,7 @@ str_t httpDecodeUrlSafe(arena_t *arena, strview_t string) {
int result = sscanf(string.buf + i, "%02X", &ch); int result = sscanf(string.buf + i, "%02X", &ch);
if (result != 1 || ch > UINT8_MAX) { if (result != 1 || ch > UINT8_MAX) {
err("malformed url at %zu (%s)", i, string.buf + i); err("malformed url at %zu (%s)", i, string.buf + i);
return (str_t){0}; return STR_EMPTY;
} }
out.buf[k++] = (char)ch; out.buf[k++] = (char)ch;
@ -423,7 +423,7 @@ error:
#if COLLA_WIN #if COLLA_WIN
buffer_t httpsRequest(http_request_desc_t *req) { buffer_t httpsRequest(http_request_desc_t *req) {
HINTERNET internet = InternetOpenA( HINTERNET internet = InternetOpen(
TEXT("COLLA"), TEXT("COLLA"),
INTERNET_OPEN_TYPE_PRECONFIG, INTERNET_OPEN_TYPE_PRECONFIG,
NULL, NULL,

View file

@ -56,7 +56,7 @@ inivalue_t *iniGet(initable_t *ctx, strview_t key) {
} }
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;
@ -89,7 +89,7 @@ iniarray_t iniAsArr(arena_t *arena, inivalue_t *value, char delim) {
} }
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)) {
@ -99,7 +99,7 @@ uint64 iniAsUInt(inivalue_t *value) {
} }
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)) {
@ -109,7 +109,7 @@ int64 iniAsInt(inivalue_t *value) {
} }
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)) {
@ -119,7 +119,7 @@ double iniAsNum(inivalue_t *value) {
} }
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)) {

View file

@ -19,7 +19,7 @@ typedef struct initable_t {
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;

View file

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

503
markdown.c Normal file
View file

@ -0,0 +1,503 @@
#include "markdown.h"
#include "arena.h"
#include "str.h"
#include "strstream.h"
#include "file.h"
#include "ini.h"
#include "tracelog.h"
#ifndef MD_LIST_MAX_DEPTH
#define MD_LIST_MAX_DEPTH 8
#endif
typedef struct {
struct {
int indent;
int count;
bool list_is_ordered[MD_LIST_MAX_DEPTH];
} list;
struct {
bool is_in_block;
strview_t lang;
} code;
bool is_bold;
bool is_italic;
bool is_in_paragraph;
strview_t raw_line;
md_options_t *options;
md_parser_t *curparser;
} markdown_ctx_t;
static void markdown__parse_config(arena_t *arena, instream_t *in, ini_t *out);
static int markdown__count_chars(strview_t *line, char c);
static void markdown__parse_line(markdown_ctx_t *md, strview_t line, outstream_t *out, bool add_newline, bool is_line_start);
static strview_t markdown__parse_header(markdown_ctx_t *md, strview_t line, outstream_t *out);
static strview_t markdown__parse_ulist_or_line(markdown_ctx_t *md, strview_t line, outstream_t *out);
static strview_t markdown__parse_olist(markdown_ctx_t *md, strview_t line, outstream_t *out);
static strview_t markdown__parse_code_block(markdown_ctx_t *md, strview_t line, outstream_t *out);
static bool markdown__try_parse_url(instream_t *in, strview_t *out_url, strview_t *out_text);
static void markdown__empty_line(markdown_ctx_t *md, outstream_t *out);
static void markdown__close_list(markdown_ctx_t *md, outstream_t *out);
static void markdown__escape(strview_t view, outstream_t *out);
str_t markdown(arena_t *arena, arena_t scratch, strview_t filename, md_options_t *options) {
str_t text = fileReadWholeStr(&scratch, filename);
return markdownStr(arena, strv(text), options);
}
str_t markdownStr(arena_t *arena, strview_t markdown_str, md_options_t *options) {
instream_t in = istrInitLen(markdown_str.buf, markdown_str.len);
markdown__parse_config(arena, &in, options ? options->out_config : NULL);
outstream_t out = ostrInit(arena);
markdown_ctx_t md = {
.list = {
.indent = -1,
},
.options = options,
};
while (!istrIsFinished(in)) {
md.raw_line = istrGetLine(&in);
markdown__parse_line(&md, strvTrimLeft(md.raw_line), &out, true, true);
}
markdown__empty_line(&md, &out);
return ostrAsStr(&out);
}
// == PRIVATE FUNCTIONS ==================================================
static void markdown__parse_config(arena_t *arena, instream_t *in, ini_t *out) {
strview_t first_line = strvTrim(istrGetLine(in));
if (!strvEquals(first_line, strv("---"))) {
return;
}
strview_t ini_data = strvInitLen(in->cur, 0);
usize data_beg = istrTell(*in);
while (!istrIsFinished(*in)) {
strview_t line = istrGetViewEither(in, strv("\r\n"));
if (strvEquals(strvTrim(line), strv("---"))) {
break;
}
istrSkipWhitespace(in);
}
usize data_end = istrTell(*in);
ini_data.len = data_end - data_beg - 3;
if (out) {
// allocate the string as ini_t only as a copy
str_t ini_str = str(arena, ini_data);
*out = iniParseStr(arena, strv(ini_str), NULL);
}
}
static int markdown__count_chars(strview_t *line, char c) {
strview_t temp = *line;
int n = 0;
while (strvFront(temp) == c) {
n++;
temp = strvRemovePrefix(temp, 1);
}
*line = temp;
return n;
}
static strview_t markdown__parse_header(markdown_ctx_t* md, strview_t line, outstream_t *out) {
int n = markdown__count_chars(&line, '#');
line = strvTrimLeft(line);
ostrPrintf(out, "<h%d>", n);
markdown__parse_line(md, line, out, false, false);
ostrPrintf(out, "</h%d>", n);
return STRV_EMPTY;
}
static strview_t markdown__parse_ulist_or_line(markdown_ctx_t *md, strview_t line, outstream_t *out) {
// check if there is anything before this character, if there is
// it means we're in the middle of a line and we should ignore
strview_t prev = strvSub(md->raw_line, 0, line.buf - md->raw_line.buf);
int space_count;
for (space_count = 0; space_count < prev.len; ++space_count) {
if (prev.buf[space_count] != ' ') break;
}
if (space_count < prev.len) {
return line;
}
// if its only * or -, this is a list
if (line.len > 1 && line.buf[1] == ' ') {
strview_t raw_line = md->raw_line;
int cur_indent = markdown__count_chars(&raw_line, ' ');
// start of list
if (md->list.indent < cur_indent) {
if (md->list.count >= MD_LIST_MAX_DEPTH) {
fatal("markdown: too many list levels, max is %d, define MD_LIST_MAX_DEPTH to an higher number", MD_LIST_MAX_DEPTH);
}
md->list.list_is_ordered[md->list.count++] = false;
ostrPuts(out, strv("<ul>\n"));
}
else if (md->list.indent > cur_indent) {
markdown__close_list(md, out);
}
md->list.indent = cur_indent;
ostrPuts(out, strv("<li>"));
markdown__parse_line(md, strvRemovePrefix(line, 2), out, false, false);
ostrPuts(out, strv("</li>"));
goto read_whole_line;
}
// check if it is an <hr>
char hr_char = strvFront(line);
strview_t hr = strvTrim(line);
bool is_hr = true;
for (usize i = 0; i < hr.len; ++i) {
if (hr.buf[i] != hr_char) {
is_hr = false;
break;
}
}
if (is_hr) {
ostrPuts(out, strv("<hr>"));
goto read_whole_line;
}
else {
strview_t to_print = line;
int n = markdown__count_chars(&line, strvFront(line));
to_print = strvSub(to_print, 0, n);
line = strvSub(line, n, SIZE_MAX);
ostrPuts(out, to_print);
}
return line;
read_whole_line:
return STRV_EMPTY;
}
static strview_t markdown__parse_olist(markdown_ctx_t *md, strview_t line, outstream_t *out) {
instream_t in = istrInitLen(line.buf, line.len);
int32 number = 0;
if (!istrGetI32(&in, &number)) {
return line;
}
if (istrPeek(&in) != '.') {
return line;
}
istrSkip(&in, 1);
if (istrPeek(&in) != ' ') {
return line;
}
istrSkip(&in, 1);
strview_t raw_line = md->raw_line;
int cur_indent = markdown__count_chars(&raw_line, ' ');
// start of list
if (md->list.indent < cur_indent) {
if (md->list.count >= MD_LIST_MAX_DEPTH) {
fatal("markdown: too many list levels, max is %d, define MD_LIST_MAX_DEPTH to an higher number", MD_LIST_MAX_DEPTH);
}
md->list.list_is_ordered[md->list.count++] = true;
ostrPuts(out, strv("<ol>\n"));
}
else if (md->list.indent > cur_indent) {
markdown__close_list(md, out);
}
md->list.indent = cur_indent;
ostrPuts(out, strv("<li>"));
markdown__parse_line(md, strvRemovePrefix(line, istrTell(in)), out, false, false);
ostrPuts(out, strv("</li>"));
return STRV_EMPTY;
}
static strview_t markdown__parse_code_block(markdown_ctx_t *md, strview_t line, outstream_t *out) {
strview_t line_copy = line;
int ticks = markdown__count_chars(&line_copy, '`');
if (ticks != 3) {
goto finish;
}
if (md->code.is_in_block) {
md->code.is_in_block = false;
if (md->curparser) {
md_parser_t *p = md->curparser;
if (p->finish) {
p->finish(p->userdata);
}
}
ostrPuts(out, strv("</ol></code></pre>\n"));
line = line_copy;
goto finish;
}
instream_t in = istrInitLen(line_copy.buf, line_copy.len);
strview_t lang = istrGetLine(&in);
if (!strvIsEmpty(lang)) {
md->curparser = NULL;
md_options_t *opt = md->options;
if (opt) {
for (int i = 0; i < opt->parsers_count; ++i) {
if (strvEquals(lang, opt->parsers->lang)) {
md->curparser = &opt->parsers[i];
break;
}
}
}
if (!md->curparser) {
warn("markdown: no parser found for code block in language (%v)", lang);
}
else {
md_parser_t *p = md->curparser;
if (p->init) {
p->init(p->userdata);
}
}
}
ostrPuts(out, strv("<pre><code><ol>"));
md->code.is_in_block = true;
return STRV_EMPTY;
finish:
return line;
}
static bool markdown__try_parse_url(instream_t *in, strview_t *out_url, strview_t *out_text) {
istrSkip(in, 1); // skip [
strview_t text = istrGetView(in, ']');
istrSkip(in, 1); // skip ]
strview_t url = STRV_EMPTY;
if (istrPeek(in) == '(') {
istrSkip(in, 1); // skip (
url = istrGetView(in, ')');
istrSkip(in, 1); // skip )
}
bool success = !strvIsEmpty(url);
if (success) {
*out_url = url;
*out_text = text;
}
return success;
}
static void markdown__parse_line(markdown_ctx_t *md, strview_t line, outstream_t *out, bool add_newline, bool is_line_start) {
if (md->code.is_in_block && strvFront(line) != '`') {
md_parser_t *p = md->curparser;
if (p && p->callback) {
p->callback(md->raw_line, out, p->userdata);
}
else {
ostrPrintf(out, "%v\n", md->raw_line);
}
return;
}
if (strvIsEmpty(line)) {
markdown__empty_line(md, out);
return;
}
switch (strvFront(line)) {
// header
case '#':
line = markdown__parse_header(md, line, out);
break;
// unordered list or <hr>
case '-': case '*': case '_':
line = markdown__parse_ulist_or_line(md, line, out);
break;
// ordered list
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
line = markdown__parse_olist(md, line, out);
break;
// code block
case '`':
line = markdown__parse_code_block(md, line, out);
break;
default:
break;
}
if (!strvIsEmpty(line) && is_line_start && !md->is_in_paragraph) {
md->is_in_paragraph = true;
ostrPuts(out, strv("<p>\n"));
}
for (usize i = 0; i < line.len; ++i) {
switch (line.buf[i]) {
// escape next character
case '\\':
if (++i < line.len) {
ostrPutc(out, line.buf[i]);
}
break;
// bold or italic
case '*':
{
strview_t sub = strvSub(line, i, SIZE_MAX);
int n = markdown__count_chars(&sub, '*');
bool is_both = n >= 3;
bool is_italic = n == 1 || is_both;
bool is_bold = n == 2 || is_both;
if (is_italic) {
ostrPrintf(out, "<%s>", md->is_italic ? "/i" : "i");
md->is_italic = !md->is_italic;
}
if (is_bold) {
ostrPrintf(out, "<%s>", md->is_bold ? "/b" : "b");
md->is_bold = !md->is_bold;
}
if (is_both) {
for (int k = 3; k < n; ++k) {
ostrPutc(out, '*');
}
}
i += n - 1;
break;
}
// url
case '[':
{
instream_t in = istrInitLen(line.buf + i, line.len - i);
strview_t url = STRV_EMPTY;
strview_t text = STRV_EMPTY;
if (markdown__try_parse_url(&in, &url, &text)) {
ostrPrintf(out, "<a href=\"%v\">%v</a>", url, strvIsEmpty(text) ? url : text);
i += istrTell(in) - 1;
}
else{
ostrPutc(out, line.buf[i]);
}
break;
}
// image
case '!':
{
instream_t in = istrInitLen(line.buf + i, line.len - i);
strview_t url = STRV_EMPTY;
strview_t text = STRV_EMPTY;
istrSkip(&in, 1); // skip !
if (markdown__try_parse_url(&in, &url, &text)) {
ostrPrintf(out, "<img src=\"%v\"", url);
if (!strvIsEmpty(text)) {
ostrPrintf(out, " alt=\"%v\"", text);
}
ostrPutc(out, '>');
i += istrTell(in) - 1;
}
else{
ostrPutc(out, line.buf[i]);
}
break;
}
// code block
case '`':
{
bool is_escaped = false;
if ((i + 1) < line.len) {
is_escaped = line.buf[i + 1] == '`';
}
instream_t in = istrInitLen(line.buf + i, line.len - i);
istrSkip(&in, is_escaped ? 2 : 1); // skip `
ostrPuts(out, strv("<code>"));
while (!istrIsFinished(in)) {
strview_t code = istrGetView(&in, '`');
markdown__escape(code, out);
if (!is_escaped || istrPeek(&in) == '`') {
break;
}
ostrPutc(out, '`');
}
ostrPuts(out, strv("</code>"));
i += istrTell(in);
break;
}
default:
ostrPutc(out, line.buf[i]);
break;
}
}
if (add_newline && !md->code.is_in_block) {
ostrPutc(out, '\n');
}
}
static void markdown__empty_line(markdown_ctx_t *md, outstream_t *out) {
// close lists
while (md->list.count > 0) {
if (md->list.list_is_ordered[--md->list.count]) {
ostrPuts(out, strv("</ol>\n"));
}
else {
ostrPuts(out, strv("</ul>\n"));
}
}
md->list.indent = -1;
// close paragraph
if (md->is_in_paragraph) {
ostrPuts(out, strv("</p>\n"));
}
md->is_in_paragraph = false;
}
static void markdown__close_list(markdown_ctx_t *md, outstream_t *out) {
if (md->list.count > 0) {
if (md->list.list_is_ordered[--md->list.count]) {
ostrPuts(out, strv("</ol>\n"));
}
else {
ostrPuts(out, strv("</ul>\n"));
}
}
}
static void markdown__escape(strview_t view, outstream_t *out) {
for (usize i = 0; i < view.len; ++i) {
switch (view.buf[i]){
case '&':
ostrPuts(out, strv("&amp"));
break;
case '<':
ostrPuts(out, strv("&lt"));
break;
case '>':
ostrPuts(out, strv("&gt"));
break;
default:
ostrPutc(out, view.buf[i]);
break;
}
}
}

59
markdown.h Normal file
View file

@ -0,0 +1,59 @@
#pragma once
#include "str.h"
typedef struct outstream_t outstream_t;
typedef struct {
strview_t lang;
void *userdata;
void (*init)(void *userdata);
void (*finish)(void *userdata);
void (*callback)(strview_t line, outstream_t *out, void *userdata);
} md_parser_t;
typedef struct {
md_parser_t *parsers;
int parsers_count;
ini_t *out_config;
} md_options_t;
typedef struct ini_t ini_t;
str_t markdown(arena_t *arena, arena_t scratch, strview_t filename, md_options_t *options);
str_t markdownStr(arena_t *arena, strview_t markdown_str, md_options_t *options);
/*
md-lite
a subset of markdown that can be parsed line by line
rules:
begin of file:
[ ] if there are three dashes (---), everythin until the next three dashes will be read as an ini config
begin of line:
[x] n # -> <h1..n>
[x] *** or --- or ___ on their own line -> <hr>
[x] - or * -> unordered list
[x] n. -> ordered list
[x] ```xyz and newline -> code block of language <xyz> (xyz is optional)
mid of line:
[x] * -> italic
[x] ** -> bold
[x] *** -> bold and italic
[x] [text](link) -> link
[x] ![text](link) -> image
[x] ` -> code block until next backtick
other:
[x] empty line -> </p>
[x] \ -> escape character
todo?:
[ ] two space at end of line or \ -> <br>
[ ] indent inside list -> continue in point
[ ] 4 spaces -> line code block (does NOT work with multiline, use ``` instead)
[ ] <url> -> link
[ ] [text](link "title") -> link
[ ] fix ***both***
*/

View file

@ -72,6 +72,7 @@ typedef struct server_t {
server__route_t *routes_default; server__route_t *routes_default;
socket_t current_client; socket_t current_client;
bool should_stop; bool should_stop;
uint16 port;
} server_t; } server_t;
bool server__parse_chunk(arena_t *arena, server__req_ctx_t *ctx, char buffer[SERVER_BUFSZ], usize buflen) { bool server__parse_chunk(arena_t *arena, server__req_ctx_t *ctx, char buffer[SERVER_BUFSZ], usize buflen) {
@ -275,7 +276,7 @@ void server__parse_req_url(arena_t *arena, server_req_t *req) {
} }
} }
server_t *serverSetup(arena_t *arena, uint16 port) { server_t *serverSetup(arena_t *arena, uint16 port, bool try_next_port) {
if (!skInit()) { if (!skInit()) {
fatal("couldn't initialise sockets: %s", skGetErrorString()); fatal("couldn't initialise sockets: %s", skGetErrorString());
} }
@ -285,14 +286,24 @@ server_t *serverSetup(arena_t *arena, uint16 port) {
fatal("couldn't open socket: %s", skGetErrorString()); fatal("couldn't open socket: %s", skGetErrorString());
} }
bool bound = false;
while (!bound) {
skaddrin_t addr = { skaddrin_t addr = {
.sin_family = AF_INET, .sin_family = AF_INET,
.sin_addr.s_addr = INADDR_ANY, .sin_addr.s_addr = INADDR_ANY,
.sin_port = htons(port), .sin_port = htons(port),
}; };
if (!skBindPro(sk, (skaddr_t *)&addr, sizeof(addr))) { bound = skBindPro(sk, (skaddr_t *)&addr, sizeof(addr));
fatal("could not bind socket: %s", skGetErrorString());
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,7 +359,7 @@ 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);

View file

@ -26,7 +26,7 @@ typedef struct {
typedef str_t (*server_route_f)(arena_t scratch, server_t *server, server_req_t *req, void *userdata); typedef str_t (*server_route_f)(arena_t scratch, server_t *server, server_req_t *req, void *userdata);
server_t *serverSetup(arena_t *arena, uint16 port); server_t *serverSetup(arena_t *arena, uint16 port, bool try_next_port);
void serverRoute(arena_t *arena, server_t *server, strview_t page, server_route_f cb, void *userdata); void serverRoute(arena_t *arena, server_t *server, strview_t page, server_route_f cb, void *userdata);
void serverRouteDefault(arena_t *arena, server_t *server, server_route_f cb, void *userdata); void serverRouteDefault(arena_t *arena, server_t *server, server_route_f cb, void *userdata);
void serverStart(arena_t scratch, server_t *server); void serverStart(arena_t scratch, server_t *server);

120
sha1.c Normal file
View file

@ -0,0 +1,120 @@
#include "sha1.h"
sha1_t sha1_init(void) {
return (sha1_t) {
.digest = {
0x67452301,
0xEFCDAB89,
0x98BADCFE,
0x10325476,
0xC3D2E1F0,
},
};
}
uint32 sha1_left_rotate(uint32 value, uint32 count) {
return (value << count) ^ (value >> (32 - count));
}
void sha1_process_block(sha1_t *ctx) {
uint32 w [80];
for (usize i = 0; i < 16; ++i) {
w[i] = ctx->block[i * 4 + 0] << 24;
w[i] |= ctx->block[i * 4 + 1] << 16;
w[i] |= ctx->block[i * 4 + 2] << 8;
w[i] |= ctx->block[i * 4 + 3] << 0;
}
for (usize i = 16; i < 80; ++i) {
w[i] = sha1_left_rotate(w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16], 1);
}
uint32 a = ctx->digest[0];
uint32 b = ctx->digest[1];
uint32 c = ctx->digest[2];
uint32 d = ctx->digest[3];
uint32 e = ctx->digest[4];
for (usize i = 0; i < 80; ++i) {
uint32 f = 0;
uint32 k = 0;
if (i<20) {
f = (b & c) | (~b & d);
k = 0x5A827999;
} else if (i<40) {
f = b ^ c ^ d;
k = 0x6ED9EBA1;
} else if (i<60) {
f = (b & c) | (b & d) | (c & d);
k = 0x8F1BBCDC;
} else {
f = b ^ c ^ d;
k = 0xCA62C1D6;
}
uint32 temp = sha1_left_rotate(a, 5) + f + e + k + w[i];
e = d;
d = c;
c = sha1_left_rotate(b, 30);
b = a;
a = temp;
}
ctx->digest[0] += a;
ctx->digest[1] += b;
ctx->digest[2] += c;
ctx->digest[3] += d;
ctx->digest[4] += e;
}
void sha1_process_byte(sha1_t *ctx, uint8 b) {
ctx->block[ctx->block_index++] = b;
++ctx->byte_count;
if (ctx->block_index == 64) {
ctx->block_index = 0;
sha1_process_block(ctx);
}
}
sha1_result_t sha1(sha1_t *ctx, const void *buf, usize len) {
const uint8 *block = buf;
for (usize i = 0; i < len; ++i) {
sha1_process_byte(ctx, block[i]);
}
usize bitcount = ctx->byte_count * 8;
sha1_process_byte(ctx, 0x80);
if (ctx->block_index > 56) {
while (ctx->block_index != 0) {
sha1_process_byte(ctx, 0);
}
while (ctx->block_index < 56) {
sha1_process_byte(ctx, 0);
}
} else {
while (ctx->block_index < 56) {
sha1_process_byte(ctx, 0);
}
}
sha1_process_byte(ctx, 0);
sha1_process_byte(ctx, 0);
sha1_process_byte(ctx, 0);
sha1_process_byte(ctx, 0);
sha1_process_byte(ctx, (uchar)((bitcount >> 24) & 0xFF));
sha1_process_byte(ctx, (uchar)((bitcount >> 16) & 0xFF));
sha1_process_byte(ctx, (uchar)((bitcount >> 8 ) & 0xFF));
sha1_process_byte(ctx, (uchar)((bitcount >> 0 ) & 0xFF));
// memcpy(digest, m_digest, 5 * sizeof(uint32_t));#
sha1_result_t result = {0};
memcpy(result.digest, ctx->digest, sizeof(result.digest));
return result;
}
str_t sha1Str(arena_t *arena, sha1_t *ctx, const void *buf, usize len) {
sha1_result_t result = sha1(ctx, buf, len);
return strFmt(arena, "%08x%08x%08x%08x%08x", result.digest[0], result.digest[1], result.digest[2], result.digest[3], result.digest[4]);
}

18
sha1.h Normal file
View file

@ -0,0 +1,18 @@
#pragma once
#include "str.h"
typedef struct {
uint32 digest[5];
uint8 block[64];
usize block_index;
usize byte_count;
} sha1_t;
typedef struct {
uint32 digest[5];
} sha1_result_t;
sha1_t sha1_init(void);
sha1_result_t sha1(sha1_t *ctx, const void *buf, usize len);
str_t sha1Str(arena_t *arena, sha1_t *ctx, const void *buf, usize len);

View file

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

View file

@ -8,7 +8,6 @@
#if COLLA_WIN #if COLLA_WIN
#define WIN32_LEAN_AND_MEAN
#include <windows.h> #include <windows.h>
#else #else
@ -24,11 +23,11 @@
// == 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_t){0}; return buf ? strInitLen(arena, buf, strlen(buf)) : STR_EMPTY;
} }
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_t){0}; if (!buf || !len) return STR_EMPTY;
str_t out = { str_t out = {
.buf = alloc(arena, char, len + 1), .buf = alloc(arena, char, len + 1),
@ -65,8 +64,12 @@ str_t strFmtv(arena_t *arena, const char *fmt, va_list 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, usize srclen) { str_t strFromWChar(arena_t *arena, const wchar_t *src) {
if (!src) return (str_t){0}; return strFromWCharLen(arena, src, 0);
}
str_t strFromWCharLen(arena_t *arena, const wchar_t *src, usize srclen) {
if (!src) return STR_EMPTY;
if (!srclen) srclen = wcslen(src); if (!srclen) srclen = wcslen(src);
str_t out = {0}; str_t out = {0};
@ -88,7 +91,7 @@ str_t strFromWChar(arena_t *arena, const wchar_t *src, usize srclen) {
err("couldn't translate wide string (%S) to utf8, %u", error); err("couldn't translate wide string (%S) to utf8, %u", error);
} }
return (str_t){0}; return STR_EMPTY;
} }
out.buf = alloc(arena, char, outlen + 1); out.buf = alloc(arena, char, outlen + 1);
@ -207,6 +210,15 @@ int strvCompare(strview_t a, strview_t b) {
(int)(a.len - b.len); (int)(a.len - b.len);
} }
char strvFront(strview_t ctx) {
return ctx.len > 0 ? ctx.buf[0] : '\0';
}
char strvBack(strview_t ctx) {
return ctx.len > 0 ? ctx.buf[ctx.len - 1] : '\0';
}
wchar_t *strvToWChar(arena_t *arena, strview_t ctx, usize *outlen) { wchar_t *strvToWChar(arena_t *arena, strview_t ctx, usize *outlen) {
wchar_t *out = NULL; wchar_t *out = NULL;
int len = 0; int len = 0;
@ -327,7 +339,7 @@ bool strvEndsWith(strview_t ctx, char c) {
} }
bool strvEndsWithView(strview_t ctx, strview_t view) { bool strvEndsWithView(strview_t ctx, strview_t view) {
return ctx.len >= view.len && memcmp(ctx.buf + ctx.len, view.buf, view.len) == 0; return ctx.len >= view.len && memcmp(ctx.buf + ctx.len - view.len, view.buf, view.len) == 0;
} }
bool strvContains(strview_t ctx, char c) { bool strvContains(strview_t ctx, char c) {
@ -394,6 +406,3 @@ usize strvRFindView(strview_t ctx, strview_t view, usize from_right) {
} }
#include "warnings/colla_warn_beg.h" #include "warnings/colla_warn_beg.h"
#undef CP_UTF8
#undef ERROR_NO_UNICODE_TRANSLATION

View file

@ -25,6 +25,8 @@ typedef struct {
_Generic((x), \ _Generic((x), \
const char *: strInit, \ const char *: strInit, \
char *: strInit, \ char *: strInit, \
const wchar_t *: strFromWChar, \
wchar_t *: strFromWChar, \
strview_t: strInitView \ strview_t: strInitView \
)(arena, x) )(arena, x)
@ -36,13 +38,16 @@ typedef struct {
// arena_t arena, strview_t view // arena_t arena, strview_t view
#define str(arena, ...) str__impl(__VA_ARGS__, 2, 1, 0)(arena, __VA_ARGS__) #define str(arena, ...) str__impl(__VA_ARGS__, 2, 1, 0)(arena, __VA_ARGS__)
#define STR_EMPTY (str_t){0}
str_t strInit(arena_t *arena, const char *buf); str_t strInit(arena_t *arena, const char *buf);
str_t strInitLen(arena_t *arena, const char *buf, usize len); str_t strInitLen(arena_t *arena, const char *buf, usize len);
str_t strInitView(arena_t *arena, strview_t view); str_t strInitView(arena_t *arena, strview_t view);
str_t strFmt(arena_t *arena, const char *fmt, ...); str_t strFmt(arena_t *arena, const char *fmt, ...);
str_t strFmtv(arena_t *arena, const char *fmt, va_list args); str_t strFmtv(arena_t *arena, const char *fmt, va_list args);
str_t strFromWChar(arena_t *arena, const wchar_t *src, usize srclen); str_t strFromWChar(arena_t *arena, const wchar_t *src);
str_t strFromWCharLen(arena_t *arena, const wchar_t *src, usize srclen);
bool strEquals(str_t a, str_t b); bool strEquals(str_t a, str_t b);
int strCompare(str_t a, str_t b); int strCompare(str_t a, str_t b);
@ -74,6 +79,8 @@ str_t strToUpper(arena_t *arena, str_t ctx);
#define strv(...) strv__impl(__VA_ARGS__, 2, 1, 0)(__VA_ARGS__) #define strv(...) strv__impl(__VA_ARGS__, 2, 1, 0)(__VA_ARGS__)
#define STRV_EMPTY (strview_t){0}
strview_t strvInit(const char *cstr); strview_t strvInit(const char *cstr);
strview_t strvInitLen(const char *buf, usize size); strview_t strvInitLen(const char *buf, usize size);
strview_t strvInitStr(str_t str); strview_t strvInitStr(str_t str);
@ -82,6 +89,9 @@ bool strvIsEmpty(strview_t ctx);
bool strvEquals(strview_t a, strview_t b); bool strvEquals(strview_t a, strview_t b);
int strvCompare(strview_t a, strview_t b); int strvCompare(strview_t a, strview_t b);
char strvFront(strview_t ctx);
char strvBack(strview_t ctx);
wchar_t *strvToWChar(arena_t *arena, strview_t ctx, usize *outlen); wchar_t *strvToWChar(arena_t *arena, strview_t ctx, usize *outlen);
TCHAR *strvToTChar(arena_t *arena, strview_t str); TCHAR *strvToTChar(arena_t *arena, strview_t str);

View file

@ -57,6 +57,16 @@ char istrPeekNext(instream_t *ctx) {
return offset > ctx->size ? '\0' : *(ctx->cur + 1); return offset > ctx->size ? '\0' : *(ctx->cur + 1);
} }
char istrPrev(instream_t *ctx) {
if (!ctx || ctx->cur == ctx->start) return '\0';
return *(ctx->cur - 1);
}
char istrPrevPrev(instream_t *ctx) {
if (!ctx || (ctx->cur - 1) == ctx->start) return '\0';
return *(ctx->cur - 2);
}
void istrSkip(instream_t *ctx, usize n) { void istrSkip(instream_t *ctx, usize n) {
if (!ctx || !ctx->cur) return; if (!ctx || !ctx->cur) return;
usize remaining = ctx->size - (ctx->cur - ctx->start); usize remaining = ctx->size - (ctx->cur - ctx->start);
@ -311,12 +321,12 @@ bool istrGetDouble(instream_t *ctx, double *val) {
} }
str_t istrGetStr(arena_t *arena, instream_t *ctx, char delim) { str_t istrGetStr(arena_t *arena, instream_t *ctx, char delim) {
if (!ctx || !ctx->cur) return (str_t){0}; if (!ctx || !ctx->cur) return STR_EMPTY;
const char *from = ctx->cur; const char *from = ctx->cur;
istrIgnore(ctx, delim); istrIgnore(ctx, delim);
// if it didn't actually find it, it just reached the end of the string // if it didn't actually find it, it just reached the end of the string
if(*ctx->cur != delim) { if(*ctx->cur != delim) {
return (str_t){0}; return STR_EMPTY;
} }
usize len = ctx->cur - from; usize len = ctx->cur - from;
str_t out = { str_t out = {
@ -339,7 +349,7 @@ usize istrGetBuf(instream_t *ctx, char *buf, usize buflen) {
} }
strview_t istrGetView(instream_t *ctx, char delim) { strview_t istrGetView(instream_t *ctx, char delim) {
if (!ctx || !ctx->cur) return (strview_t){0}; if (!ctx || !ctx->cur) return STRV_EMPTY;
const char *from = ctx->cur; const char *from = ctx->cur;
istrIgnore(ctx, delim); istrIgnore(ctx, delim);
usize len = ctx->cur - from; usize len = ctx->cur - from;
@ -347,7 +357,7 @@ strview_t istrGetView(instream_t *ctx, char delim) {
} }
strview_t istrGetViewEither(instream_t *ctx, strview_t chars) { strview_t istrGetViewEither(instream_t *ctx, strview_t chars) {
if (!ctx || !ctx->cur) return (strview_t){0}; if (!ctx || !ctx->cur) return STRV_EMPTY;
const char *from = ctx->cur; const char *from = ctx->cur;
for (; !istrIsFinished(*ctx) && !strvContains(chars, *ctx->cur); ++ctx->cur) { for (; !istrIsFinished(*ctx) && !strvContains(chars, *ctx->cur); ++ctx->cur) {
@ -357,16 +367,25 @@ strview_t istrGetViewEither(instream_t *ctx, strview_t chars) {
} }
strview_t istrGetViewLen(instream_t *ctx, usize len) { strview_t istrGetViewLen(instream_t *ctx, usize len) {
if (!ctx || !ctx->cur) return (strview_t){0}; if (!ctx || !ctx->cur) return STRV_EMPTY;
const char *from = ctx->cur; const char *from = ctx->cur;
istrSkip(ctx, len); istrSkip(ctx, len);
usize buflen = ctx->cur - from; usize buflen = ctx->cur - from;
return (strview_t){ from, buflen }; return (strview_t){ from, buflen };
} }
strview_t istrGetLine(instream_t *ctx) {
strview_t line = istrGetView(ctx, '\n');
istrSkip(ctx, 1);
if (strvEndsWith(line, '\r')) {
line = strvRemoveSuffix(line, 1);
}
return line;
}
/* == OUTPUT STREAM =========================================== */ /* == OUTPUT STREAM =========================================== */
void ostr__remove_null(outstream_t *o) { static void ostr__remove_null(outstream_t *o) {
usize len = ostrTell(o); usize len = ostrTell(o);
if (len && o->beg[len - 1] == '\0') { if (len && o->beg[len - 1] == '\0') {
arenaPop(o->arena, 1); arenaPop(o->arena, 1);
@ -394,11 +413,18 @@ char ostrBack(outstream_t *ctx) {
} }
str_t ostrAsStr(outstream_t *ctx) { str_t ostrAsStr(outstream_t *ctx) {
bool is_null_terminated = ostrBack(ctx) == '\0' && false; if (ostrTell(ctx) == 0 || ostrBack(ctx) != '\0') {
return (str_t){ ostrPutc(ctx, '\0');
}
str_t out = {
.buf = ctx->beg, .buf = ctx->beg,
.len = ostrTell(ctx) - is_null_terminated .len = ostrTell(ctx) - 1
}; };
ctx->beg = NULL;
ctx->arena = NULL;
return out;
} }
strview_t ostrAsView(outstream_t *ctx) { strview_t ostrAsView(outstream_t *ctx) {
@ -609,7 +635,7 @@ strview_t ibstrGetView(ibytestream_t *ctx, usize lensize) {
usize read = ibstrRead(ctx, &len, lensize); usize read = ibstrRead(ctx, &len, lensize);
if (read != lensize) { if (read != lensize) {
warn("couldn't read %zu bytes, instead read %zu for string", lensize, read); warn("couldn't read %zu bytes, instead read %zu for string", lensize, read);
return (strview_t){0}; return STRV_EMPTY;
} }
usize remaining = ibstrRemaining(ctx); usize remaining = ibstrRemaining(ctx);
if (len > remaining) { if (len > remaining) {

View file

@ -13,7 +13,7 @@ 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;
@ -29,6 +29,10 @@ char istrGet(instream_t *ctx);
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);
// returns the previous character
char istrPrev(instream_t *ctx);
// returns the character before the previous
char istrPrevPrev(instream_t *ctx);
// ignore characters until the delimiter // ignore characters until the delimiter
void istrIgnore(instream_t *ctx, char delim); void istrIgnore(instream_t *ctx, char delim);
// ignore characters until the delimiter and skip it // ignore characters until the delimiter and skip it
@ -70,10 +74,11 @@ usize istrGetBuf(instream_t *ctx, char *buf, usize buflen);
strview_t istrGetView(instream_t *ctx, char delim); strview_t istrGetView(instream_t *ctx, char delim);
strview_t istrGetViewEither(instream_t *ctx, strview_t chars); strview_t istrGetViewEither(instream_t *ctx, strview_t chars);
strview_t istrGetViewLen(instream_t *ctx, usize len); strview_t istrGetViewLen(instream_t *ctx, usize len);
strview_t istrGetLine(instream_t *ctx);
/* == OUTPUT STREAM =========================================== */ /* == OUTPUT STREAM =========================================== */
typedef struct { typedef struct outstream_t {
char *beg; char *beg;
arena_t *arena; arena_t *arena;
} outstream_t; } outstream_t;

View file

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

8
tools/build Normal file
View file

@ -0,0 +1,8 @@
#!/bin/bash
# cosmocc -Os -mtiny -o docs.com docs.c
# optimised version for x86/64 only, shaves off ~300KB
x86_64-unknown-cosmo-cc -Os -mtiny -o ../docs/docs.com docs.c
rm ../docs/docs.com.dbg

429
tools/docs.c Normal file
View file

@ -0,0 +1,429 @@
#include "../arena.c"
#include "../file.c"
#include "../format.c"
#include "../ini.c"
#include "../str.c"
#include "../strstream.c"
#include "../tracelog.c"
#include "../vmem.c"
#include "../markdown.c"
#include "../highlight.c"
#include "../dir.c"
#include "../socket.c"
#include "../http.c"
#include "../server.c"
#include "../html.h"
const char *raw_css;
typedef struct page_t {
str_t title;
str_t url;
str_t data;
struct page_t *next;
} page_t;
typedef struct {
uint8 arenabuf[KB(5)];
arena_t arena;
hl_ctx_t *hl;
int line;
} cparser_ctx_t;
void md_cparser_init(void *udata) {
cparser_ctx_t *cparser = udata;
cparser->line = 1;
if (cparser->hl) {
return;
}
cparser->arena = arenaMake(ARENA_STATIC, sizeof(cparser->arenabuf), cparser->arenabuf);
cparser->hl = hlInit(&cparser->arena, &(hl_config_t){
.colors = {
[HL_COLOR_NORMAL] = strv("</span>"),
[HL_COLOR_PREPROC] = strv("<span class=\"c-preproc\">"),
[HL_COLOR_TYPES] = strv("<span class=\"c-types\">"),
[HL_COLOR_CUSTOM_TYPES] = strv("<span class=\"c-custom-types\">"),
[HL_COLOR_KEYWORDS] = strv("<span class=\"c-kwrds\">"),
[HL_COLOR_NUMBER] = strv("<span class=\"c-num\">"),
[HL_COLOR_STRING] = strv("<span class=\"c-str\">"),
[HL_COLOR_COMMENT] = strv("<span class=\"c-cmnt\">"),
[HL_COLOR_FUNC] = strv("<span class=\"c-func\">"),
[HL_COLOR_SYMBOL] = strv("<span class=\"c-sym\">"),
[HL_COLOR_MACRO] = strv("<span class=\"c-macro\">"),
},
.flags = HL_FLAG_HTML,
});
}
void md_cparser_callback(strview_t line, outstream_t *out, void *udata) {
cparser_ctx_t *cparser = udata;
arena_t scratch = cparser->arena;
str_t highlighted = hlHighlight(&scratch, cparser->hl, line);
ostrPrintf(out, "<li>%v</li>", highlighted);
}
page_t *get_pages(arena_t *arena, strview_t path, strview_t default_page) {
arena_t scratch = arenaMake(ARENA_VIRTUAL, MB(1));
dir_t *dir = dirOpen(&scratch, path);
dir_entry_t *entry = NULL;
page_t *first = NULL;
page_t *head = NULL;
page_t *tail = NULL;
cparser_ctx_t cparser = {0};
while ((entry = dirNext(&scratch, dir))) {
if (entry->type != DIRTYPE_FILE) {
continue;
}
strview_t name, ext;
fileSplitPath(strv(entry->name), NULL, &name, &ext);
if (!strvEquals(ext, strv(".md"))) {
continue;
}
str_t fullname = strFmt(&scratch, "%v/%v", path, entry->name);
str_t markdown_str = fileReadWholeStr(&scratch, strv(fullname));
ini_t config = {0};
str_t md = markdownStr(&scratch, strv(markdown_str), &(md_options_t){
.out_config = &config,
.parsers = (md_parser_t[]){
{
.init = md_cparser_init,
.callback = md_cparser_callback,
.userdata = &cparser,
.lang = strv("c"),
},
},
.parsers_count = 1,
});
inivalue_t *title = iniGet(iniGetTable(&config, INI_ROOT), strv("title"));
page_t *page = alloc(arena, page_t);
page->data = md;
page->url = str(arena, name);
if (title) {
page->title = str(arena, title->value);
}
else {
page->title = page->url;
}
if (!first && strvEquals(name, default_page)) {
first = page;
}
else {
if (!head) head = page;
if (tail) tail->next = page;
tail = page;
}
}
if (first) {
first->next = head;
head = first;
}
strview_t css = strv(raw_css);
for_each(page, head) {
str_t html = STR_EMPTY;
htmlBeg(arena, &html);
headBeg();
title(page->title);
style(css);
headEnd();
bodyBeg();
divBeg(.id="main");
divBeg(.class="content");
divBeg(.class="pages-container");
divBeg(.class="pages");
for_each(item, head) {
str_t class = strFmt(&scratch, "page-item%s", item == page ? " page-current" : "");
str_t href = strFmt(&scratch, "/%v", item->url);
str_t onclick = strFmt(&scratch, "window.location = \"/%v\"", item->url);
a(
item->title,
.href = href.buf,
.class = class.buf,
.onclick = onclick.buf
);
}
divEnd();
divEnd();
divBeg(.class="document-container");
divBeg(.class="document");
htmlPuts(page->data);
htmlRaw(<div class="document-padding"></div>);
divEnd();
divEnd();
divEnd();
divEnd();
bodyEnd();
htmlEnd();
page->data = html;
}
arenaCleanup(&scratch);
return head;
}
str_t server_default(arena_t scratch, server_t *server, server_req_t *req, void *userdata) {
strview_t needle = strv(req->page);
if (strvFront(needle) == '/') {
needle = strvRemovePrefix(strv(req->page), 1);
}
page_t *page = userdata;
while (page) {
if (strvEquals(strv(page->url), needle)) {
break;
}
page = page->next;
}
// if the url is invalid, return the default page
if (!page) {
page = userdata;
}
return serverMakeResponse(&scratch, 200, strv("text/html"), strv(page->data));
}
str_t server_quit(arena_t scratch, server_t *server, server_req_t *req, void *userdata) {
serverStop(server);
return STR_EMPTY;
}
int main() {
arena_t arena = arenaMake(ARENA_VIRTUAL, MB(1));
page_t *pages = get_pages(&arena, strv("."), strv("readme"));
if (!pages) {
err("could not get pages");
return 1;
}
server_t *s = serverSetup(&arena, 8080, true);
serverRouteDefault(&arena, s, server_default, pages);
serverRoute(&arena, s, strv("/quit"), server_quit, NULL);
serverStart(arena, s);
arenaCleanup(&arena);
}
//// HTML GENERATION STUFF ///////////////////////////////
const char *raw_css = ""
"html, body, #main {\n"
" margin: 0;\n"
" width: 100%;\n"
" height: 100%;\n"
" font-family: -apple-system,BlinkMacSystemFont,'Segoe UI','Noto Sans',Helvetica,Arial,sans-serif,'Apple Color Emoji','Segoe UI Emoji';\n"
" \n"
" --black: #121212;\n"
" --dark-gray: #212121;\n"
" --gray: #303030;\n"
" --light-gray: #424242;\n"
" --accent: #FFA500;\n"
" /* --accent: #F98334; */\n"
"\n"
" --blue: #45559E;\n"
" --orange: #F98334;\n"
" --yellow: #FABD2F;\n"
" --red: #FB4934;\n"
" --pink: #D3869B;\n"
" --green: #B8BB26;\n"
" --azure: #7FA375;\n"
" --white: #FBF1C7;\n"
" --light-blue: #83A598;\n"
"}\n"
"\n"
"hr {\n"
" color: white;\n"
" opacity: 0.5;\n"
"}\n"
"\n"
"a {\n"
" text-decoration: none;\n"
" font-weight: bold;\n"
" color: var(--red);\n"
"}\n"
"\n"
"a:hover {\n"
" text-decoration: underline;\n"
"}\n"
"\n"
".content {\n"
" width: 100%;\n"
" height: 100%;\n"
" background-color: var(--black);\n"
" display: flex;\n"
" gap: 20px;\n"
" align-items: stretch;\n"
" color: white;\n"
" overflow-x: scroll;\n"
"}\n"
"\n"
".pages-container {\n"
" position: sticky;\n"
" top: 0;\n"
" min-width: 200px;\n"
" background-color: var(--dark-gray);\n"
" border-right: 1px solid var(--light-gray);\n"
" box-shadow: 0 0 20px rgba(0, 0, 0, 1.0);\n"
" overflow-y: scroll;\n"
"}\n"
"\n"
".document-container {\n"
" display: flex;\n"
" justify-content: center;\n"
" width: 100%;\n"
"}\n"
"\n"
".document {\n"
" width: 100%;\n"
" max-width: 700px;\n"
" line-height: 1.5;\n"
"}\n"
"\n"
".document-padding {\n"
" height: 50px;\n"
"}\n"
"\n"
".pages {\n"
" position: relative;\n"
" background-color: var(--dark-gray);\n"
"}\n"
"\n"
".page-item:last-of-type {\n"
" border-bottom: 0;\n"
"}\n"
"\n"
".page-item {\n"
" text-decoration: none;\n"
" display: block;\n"
" color: rgba(255, 255, 255, 0.5);\n"
" padding: 0.2em 0 0.2em 1.5em;\n"
" border-bottom: 1px solid var(--light-gray);\n"
"}\n"
"\n"
".page-item:hover {\n"
" background-color: var(--light-gray);\n"
" cursor: pointer;\n"
" color: white;\n"
" opacity: 1;\n"
"}\n"
"\n"
".page-current {\n"
" color: var(--accent);\n"
" opacity: 1;\n"
"}\n"
"\n"
".page-current:hover {\n"
" color: var(--accent);\n"
"}\n"
"\n"
".page-spacing {\n"
" height: 25px; \n"
"}\n"
"\n"
"code {\n"
" color: var(--accent);\n"
" background-color: var(--dark-gray);\n"
" padding: 0.2em 0.5em 0.2em 0.5em;\n"
" border-radius: 0.5em;\n"
"}\n"
"\n"
"pre {\n"
" margin: 0;\n"
" margin-top: 2em;\n"
" background-color: var(--dark-gray);\n"
" padding: 16px;\n"
" border-radius: 0.3em;\n"
" overflow-x: scroll;\n"
"\n"
" border: 1px solid var(--light-gray);\n"
" box-shadow: 0 0 20px rgba(0, 0, 0, 1.0);\n"
"}\n"
"\n"
"pre > code {\n"
" color: #FBF1C7;\n"
" padding: 0;\n"
" background-color: transparent;\n"
"}\n"
"\n"
"pre ol {\n"
" counter-reset: item;\n"
" padding-left: 0;\n"
"}\n"
"\n"
"pre li {\n"
" display: block;\n"
" margin-left: 0em;\n"
"}\n"
"\n"
"pre li::before {\n"
" display: inline-block;\n"
" content: counter(item);\n"
" counter-increment: item;\n"
" width: 2em;\n"
" padding-right: 1.5em;\n"
" text-align: right;\n"
"}\n"
"\n"
"/* code block colors */\n"
".c-preproc {\n"
" color: #45559E;\n"
"}\n"
"\n"
".c-types {\n"
" color: #F98334;\n"
"}\n"
"\n"
".c-custom-types {\n"
" color: #FABD2F;\n"
"}\n"
"\n"
".c-kwrds {\n"
" color: #FB4934;\n"
"}\n"
"\n"
".c-num {\n"
" color: #D3869B;\n"
"}\n"
"\n"
".c-str {\n"
" color: #B8BB26;\n"
"}\n"
"\n"
".c-cmnt {\n"
" color: #928374;\n"
"}\n"
"\n"
".c-func {\n"
" color: #7FA375;\n"
"}\n"
"\n"
".c-sym {\n"
" color: #FBF1C7;\n"
"}\n"
"\n"
".c-macro {\n"
" color: #83A598;\n"
"}\n"
"";

View file

@ -17,18 +17,13 @@
#pragma comment(lib, "User32") #pragma comment(lib, "User32")
#endif #endif
// avoid including windows.h #if COLLA_TCC
#include "tcc/colla_tcc.h"
#endif
#ifndef STD_OUTPUT_HANDLE //#ifndef TLOG_NO_COLOURS
#define STD_OUTPUT_HANDLE ((DWORD)-11) // #define TLOG_NO_COLOURS
#endif //#endif
#ifndef CP_UTF8
#define CP_UTF8 65001
#endif
#ifndef TLOG_NO_COLOURS
#define TLOG_NO_COLOURS
#endif
#endif #endif
#if COLLA_EMC #if COLLA_EMC
@ -70,19 +65,17 @@
#define COLOUR_BOLD "\033[1m" #define COLOUR_BOLD "\033[1m"
#endif #endif
#define MAX_TRACELOG_MSG_LENGTH 1024 static bool tl_use_newline = true;
bool use_newline = true;
#if COLLA_WIN #if COLLA_WIN
static void setLevelColour(int level) { static void setLevelColour(int level) {
WORD attribute = 15; WORD attribute = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE;
switch (level) { switch (level) {
case LogDebug: attribute = 1; break; case LogDebug: attribute = FOREGROUND_BLUE; break;
case LogInfo: attribute = 2; break; case LogInfo: attribute = FOREGROUND_GREEN; break;
case LogWarning: attribute = 6; break; case LogWarning: attribute = FOREGROUND_RED | FOREGROUND_GREEN; break;
case LogError: attribute = 4; break; case LogError: attribute = FOREGROUND_RED; break;
case LogFatal: attribute = 4; break; case LogFatal: attribute = FOREGROUND_RED; break;
} }
HANDLE hc = GetStdHandle(STD_OUTPUT_HANDLE); HANDLE hc = GetStdHandle(STD_OUTPUT_HANDLE);
@ -121,6 +114,7 @@ void traceLogVaList(int level, const char *fmt, va_list args) {
const char *beg = ""; const char *beg = "";
switch (level) { switch (level) {
case LogAll: beg = "[ALL" ; break;
case LogTrace: beg = "[TRACE" ; break; case LogTrace: beg = "[TRACE" ; break;
case LogDebug: beg = "[DEBUG" ; break; case LogDebug: beg = "[DEBUG" ; break;
case LogInfo: beg = "[INFO" ; break; case LogInfo: beg = "[INFO" ; break;
@ -151,10 +145,9 @@ void traceLogVaList(int level, const char *fmt, va_list args) {
// set back to white // set back to white
setLevelColour(LogTrace); setLevelColour(LogTrace);
// vprintf(fmt, args);
fmtPrintv(fmt, args); fmtPrintv(fmt, args);
if(use_newline) { if(tl_use_newline) {
#if COLLA_EMC #if COLLA_EMC
puts("<br>"); puts("<br>");
#else #else
@ -198,12 +191,12 @@ void traceLogVaList(int level, const char *fmt, va_list args) {
} }
void traceUseNewline(bool newline) { void traceUseNewline(bool newline) {
use_newline = newline; tl_use_newline = newline;
} }
void traceSetColour(colour_e colour) { void traceSetColour(colour_e colour) {
#if COLLA_WIN #if COLLA_WIN
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), colour); SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), (WORD)colour);
#else #else
switch (colour) { switch (colour) {
case COL_RESET: printf(COLOUR_RESET); break; case COL_RESET: printf(COLOUR_RESET); break;

View file

@ -63,7 +63,7 @@ final 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
@ -72,7 +72,7 @@ rune utf8Decode(const char **char_str) {
++*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;

View file

View file

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

142
websocket.c Normal file
View file

@ -0,0 +1,142 @@
#include "websocket.h"
#include "arena.h"
#include "str.h"
#include "server.h"
#include "socket.h"
#include "base64.h"
#include "strstream.h"
#include "sha1.h"
#if !COLLA_MSVC
extern uint16_t ntohs(uint16_t hostshort);
extern uint16_t htons(uint16_t hostshort);
extern uint32_t htonl(uint32_t hostlong);
uint64_t ntohll(uint64_t input) {
uint64_t rval = 0;
uint8_t *data = (uint8_t *)&rval;
data[0] = input >> 56;
data[1] = input >> 48;
data[2] = input >> 40;
data[3] = input >> 32;
data[4] = input >> 24;
data[5] = input >> 16;
data[6] = input >> 8;
data[7] = input >> 0;
return rval;
}
uint64_t htonll(uint64_t input) {
return ntohll(input);
}
#endif
bool wsInitialiseSocket(arena_t scratch, socket_t websocket, strview_t key) {
str_t full_key = strFmt(&scratch, "%v" WEBSOCKET_MAGIC, key, WEBSOCKET_MAGIC);
sha1_t sha1_ctx = sha1_init();
sha1_result_t sha1_data = sha1(&sha1_ctx, full_key.buf, full_key.len);
// convert to big endian for network communication
for (int i = 0; i < 5; ++i) {
sha1_data.digest[i] = htonl(sha1_data.digest[i]);
}
buffer_t encoded_key = base64Encode(&scratch, (buffer_t){ (uint8 *)sha1_data.digest, sizeof(sha1_data.digest) });
str_t response = strFmt(
&scratch,
"HTTP/1.1 101 Switching Protocols\r\n"
"Connection: Upgrade\r\n"
"Upgrade: websocket\r\n"
"Sec-WebSocket-Accept: %v\r\n"
"\r\n",
encoded_key
);
bool success = skSend(websocket, response.buf, response.len);
return success;
}
buffer_t wsEncodeMessage(arena_t *arena, strview_t message) {
int extra = 6;
if (message.len > UINT16_MAX) extra += sizeof(uint64);
else if (message.len > UINT8_MAX) extra += sizeof(uint16);
uint8 *bytes = alloc(arena, uint8, message.len + extra);
bytes[0] = 0b10000001;
bytes[1] = 0b10000000;
int offset = 2;
if (message.len > UINT16_MAX) {
bytes[1] |= 0b01111111;
uint64 len = htonll(message.len);
memcpy(bytes + 2, &len, sizeof(len));
offset += sizeof(uint64);
}
else if (message.len > UINT8_MAX) {
bytes[1] |= 0b01111110;
uint16 len = htons((uint16)message.len);
memcpy(bytes + 2, &len, sizeof(len));
offset += sizeof(uint16);
}
else {
bytes[1] |= (uint8)message.len;
}
uint32 mask = 0;
memcpy(bytes + offset, &mask, sizeof(mask));
offset += sizeof(mask);
memcpy(bytes + offset, message.buf, message.len);
return (buffer_t){ bytes, message.len + extra };
}
str_t wsDecodeMessage(arena_t *arena, buffer_t message) {
str_t out = STR_EMPTY;
uint8 *bytes = message.data;
bool mask = bytes[1] & 0b10000000;
int offset = 2;
uint64 msglen = bytes[1] & 0b01111111;
// 16bit msg len
if (msglen == 126) {
uint64 be_len = 0;
memcpy(&be_len, bytes + 2, sizeof(be_len));
msglen = ntohs(be_len);
offset += sizeof(uint16);
}
// 64bit msg len
else if (msglen == 127) {
uint64 be_len = 0;
memcpy(&be_len, bytes + 2, sizeof(be_len));
msglen = ntohll(be_len);
offset += sizeof(uint64);
}
if (msglen == 0) {
warn("message length = 0");
}
else if (mask) {
uint8 *decoded = alloc(arena, uint8, msglen + 1);
uint8 masks[4] = {0};
memcpy(masks, bytes + offset, sizeof(masks));
offset += 4;
for (uint64 i = 0; i < msglen; ++i) {
decoded[i] = bytes[offset + i] ^ masks[i % 4];
}
out = (str_t){ (char *)decoded, msglen };
}
else {
warn("mask bit not set!");
}
return out;
}

13
websocket.h Normal file
View file

@ -0,0 +1,13 @@
#pragma once
#include "arena.h"
#include "str.h"
#define WEBSOCKET_MAGIC "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
#define WEBSOCKET_HTTP_KEY "Sec-WebSocket-Key"
typedef uintptr_t socket_t;
bool wsInitialiseSocket(arena_t scratch, socket_t websocket, strview_t key);
buffer_t wsEncodeMessage(arena_t *arena, strview_t message);
str_t wsDecodeMessage(arena_t *arena, buffer_t message);

View file

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

View file