windows stuff

This commit is contained in:
alessandro bason 2025-10-11 20:35:40 +02:00
parent 7e7c371b9e
commit ae3536529b
3 changed files with 587 additions and 106 deletions

426
colla.c
View file

@ -5,6 +5,7 @@
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#if COLLA_TCC
#define COLLA_NO_CONDITION_VARIABLE 1
@ -34,6 +35,13 @@
colla_modules_e colla__initialised_modules = 0;
extern void os_init(void);
extern void os_cleanup(void);
#if !COLLA_NO_NET
extern void net_init(void);
extern void net_cleanup(void);
#endif
static char *colla_fmt__stb_callback(const char *buf, void *ud, int len) {
// TODO maybe use os_write?
fflush(stdout);
@ -284,6 +292,18 @@ int strv_compare(strview_t a, strview_t b) {
(int)(a.len - b.len);
}
usize strv_get_utf8_len(strview_t v) {
usize len = 0;
for (usize i = 0; i < v.len; ++i) {
if ((v.buf[i] & 0xC0) != 0x80) {
len++;
}
}
return len;
}
char strv_front(strview_t ctx) {
return ctx.len > 0 ? ctx.buf[0] : '\0';
}
@ -662,7 +682,7 @@ bool istr_get_i32(instream_t *ctx, i32 *val) {
bool istr_get_i64(instream_t *ctx, i64 *val) {
if (!ctx || !ctx->cur || !val) return false;
char *end = NULL;
*val = strtoll(ctx->cur, &end, 0);
i64 out = strtoll(ctx->cur, &end, 0);
if (ctx->cur == end) {
return false;
@ -672,6 +692,7 @@ bool istr_get_i64(instream_t *ctx, i64 *val) {
}
ctx->cur = end;
*val = out;
return true;
}
@ -897,6 +918,91 @@ bool ibstr_get_i64(ibstream_t *ib, i64 *out) {
return ibstr_read(ib, out, sizeof(*out)) == sizeof(*out);
}
// == REGEX ========================================================
bool rg__match_impl(rg_match_t *ctx, instream_t *r, instream_t *t) {
bool match_any = false;
usize beg = STR_END;
usize end = STR_END;
while (!istr_is_finished(r) && !istr_is_finished(t)) {
char rc = istr_peek(r);
char tc = istr_peek(t);
if (rc == '\\') {
if (istr_peek_next(r) != '*' && istr_peek_next(r) != '\\') {
warn("expected * or \\ after escape character");
return false;
}
istr_skip(r, 1);
continue;
}
if (istr_peek_next(r) == '*' && rc == tc) {
match_any = true;
istr_skip(r, 2);
istr_skip(t, 1);
beg = istr_tell(t);
continue;
}
if (rc == '*') {
match_any = true;
istr_skip(r, 1);
istr_skip(t, 1);
beg = istr_tell(t);
continue;
}
if (rc == tc) {
if (match_any && istr_peek_next(r) == istr_peek_next(t)) {
end = istr_tell(t);
ctx->text[ctx->count++] = strv(t->beg + beg, end - beg);
beg = STR_END;
match_any = false;
}
istr_skip(r, 1);
istr_skip(t, 1);
continue;
}
if (match_any) {
istr_skip(t, 1);
continue;
}
return false;
}
if (match_any && istr_is_finished(r)) {
end = t->len;
ctx->text[ctx->count++] = strv(t->beg + beg, end - beg);
return true;
}
return ctx->count > 0 && istr_is_finished(r) && istr_is_finished(t);
}
rg_match_t rg_match(strview_t rg, strview_t text) {
rg_match_t out = {0};
if (strv_contains(rg, '*')) {
instream_t r = istr_init(rg);
instream_t t = istr_init(text);
out.matches = rg__match_impl(&out, &r, &t);
}
else {
out.matches = strv_equals(rg, text);
}
return out;
}
bool rg_match_easy(strview_t rg, strview_t text) {
return rg_match(rg, text).matches;
}
// == ARENA ========================================================
static uptr arena__align(uptr ptr, usize align) {
return (ptr + (align - 1)) & ~(align - 1);
}
@ -1036,6 +1142,7 @@ static void arena__free_virtual(arena_t *arena) {
os_release(arena->beg, arena_capacity(arena));
}
// == MALLOC ARENA =====================================================================================================
static arena_t arena__make_malloc(usize size) {
@ -1134,47 +1241,138 @@ bool os_handle_valid(oshandle_t handle) {
// == LOGGING ===================================
os_log_colour_e log__level_to_colour(os_log_level_e level) {
os_log_colour_e colour = LOG_COL_RESET;
switch (level) {
case LOG_DEBUG: colour = LOG_COL_BLUE; break;
case LOG_INFO: colour = LOG_COL_GREEN; break;
case LOG_WARN: colour = LOG_COL_YELLOW; break;
case LOG_ERR: colour = LOG_COL_MAGENTA; break;
case LOG_FATAL: colour = LOG_COL_RED; break;
default: break;
const char* os_log_level_strings[LOG_COL__COUNT] = {
[LOG_DEBUG] = "[DEBUG]:",
[LOG_INFO] = "[INFO]:",
[LOG_WARN] = "[WARN]:",
[LOG_ERR] = "[ERR]:",
[LOG_FATAL] = "[FATAL]:",
};
os_log_colour_e os_log_level_colours[LOG_COL__COUNT] = {
[LOG_DEBUG] = LOG_COL_BLUE,
[LOG_INFO] = LOG_COL_GREEN,
[LOG_WARN] = LOG_COL_YELLOW,
[LOG_ERR] = LOG_COL_MAGENTA,
[LOG_FATAL] = LOG_COL_RED,
};
os_log_options_e log_opts = OS_LOG_SIMPLE;
log_callback_t log_cbs[COLLA_LOG_MAX_CALLBACKS] = {0};
int log_cbs_count = 0;
void os_log__stdout(log_event_t *ev) {
bool notime = log_opts & OS_LOG_NOTIME;
bool nofile = log_opts & OS_LOG_NOFILE;
if (!notime) {
os_log_set_colour(LOG_COL_DARK_GREY);
fmt_print(
"%02d:%02d:%02d ",
ev->time->tm_hour,
ev->time->tm_min,
ev->time->tm_sec
);
}
return colour;
os_log_set_colour(os_log_level_colours[ev->level]);
if (ev->level != LOG_BASIC) {
fmt_print("%-8s ", os_log_level_strings[ev->level]);
}
if (!nofile) {
os_log_set_colour(LOG_COL_DARK_GREY);
fmt_print("%s:%d ", ev->file, ev->line);
}
os_log_set_colour(LOG_COL_RESET);
fmt_printv(ev->fmt, ev->args);
fmt_print("\n");
}
void os_log_print(os_log_level_e level, const char *fmt, ...) {
void os_log__fp(log_event_t *ev) {
u8 tmpbuf[KB(1)] = {0};
arena_t scratch = arena_make(ARENA_STATIC, sizeof(tmpbuf), tmpbuf);
oshandle_t fp = {0};
fp.data = (uptr)ev->udata;
bool notime = log_opts & OS_LOG_NOTIME;
bool nofile = log_opts & OS_LOG_NOFILE;
if (!notime) {
os_file_print(
scratch,
fp,
"%02d:%02d:%02d ",
ev->time->tm_hour,
ev->time->tm_min,
ev->time->tm_sec
);
}
if (ev->level != LOG_BASIC) {
os_file_print(scratch, fp, "%-8s ", os_log_level_strings[ev->level]);
}
if (!nofile) {
os_file_print(scratch, fp, "%s:%d ", ev->file, ev->line);
}
os_file_printv(scratch, fp, ev->fmt, ev->args);
os_file_putc(fp, '\n');
}
void os_log_add_callback(log_callback_t cb) {
colla_assert(log_cbs_count < arrlen(log_cbs));
log_cbs[log_cbs_count++] = cb;
}
void os_log_add_fp(oshandle_t fp, os_log_level_e level) {
os_log_add_callback((log_callback_t){
.fn = os_log__fp,
.udata = (void*)fp.data,
.level = level
});
}
void os__log_init(void) {
os_log_add_callback((log_callback_t){ .fn = os_log__stdout });
}
void os_log_print(const char *file, int line, os_log_level_e level, const char *fmt, ...) {
va_list args;
va_start(args, fmt);
os_log_printv(level, fmt, args);
os_log_printv(file, line, level, fmt, args);
va_end(args);
}
void os_log_printv(os_log_level_e level, const char *fmt, va_list args) {
const char *level_str = "";
switch (level) {
case LOG_DEBUG: level_str = "DEBUG"; break;
case LOG_INFO: level_str = "INFO"; break;
case LOG_WARN: level_str = "WARN"; break;
case LOG_ERR: level_str = "ERR"; break;
case LOG_FATAL: level_str = "FATAL"; break;
default: break;
void os_log_printv(const char *file, int line, os_log_level_e level, const char *fmt, va_list args) {
time_t t = time(NULL);
struct tm log_time = { 0 };
#define gettime(src, dst) localtime_s(&dst, &src)
log_event_t ev = {
.level = level,
.line = line,
.args = args,
.fmt = fmt,
.file = file,
.time = &log_time,
};
localtime_s(&log_time, &t);
for (int i = 0; i < log_cbs_count; ++i) {
if (log_cbs[i].level > level) {
continue;
}
ev.udata = log_cbs[i].udata;
log_cbs[i].fn(&ev);
}
os_log_set_colour(log__level_to_colour(level));
if (level != LOG_BASIC) {
fmt_print("[%s]: ", level_str);
}
os_log_set_colour(LOG_COL_RESET);
bool nocrash = log_opts & OS_LOG_NOCRASH;
fmt_printv(fmt, args);
fmt_print("\n");
if (level == LOG_FATAL) {
if (!nocrash && level == LOG_FATAL) {
os_abort(1);
}
}
@ -1346,6 +1544,103 @@ usize os_pad_to_page(usize byte_count) {
return byte_count + padding;
}
#if !COLLA_NO_CONDITION_VARIABLE
// == JOB QUEUE =================================
int jq__worker_function(u64 id, void *udata) {
job_queue_t *q = udata;
// TODO: is this safe to not have an atomic variable?
while (!q->should_stop) {
job_t *job = jq_pop_job(q);
if (!job) {
if (q->stop_when_finished) {
break;
}
continue;
}
job->func(job->userdata);
os_mutex_lock(q->mutex);
list_push(q->freelist, job);
os_mutex_unlock(q->mutex);
}
return 0;
}
job_queue_t *jq_init(arena_t *arena, int worker_count) {
job_queue_t *q = alloc(arena, job_queue_t);
q->mutex = os_mutex_create();
q->condvar = os_cond_create();
q->thread_count = worker_count ? worker_count : os_get_system_info().processor_count;
q->threads = alloc(arena, oshandle_t, q->thread_count);
for (int i = 0; i < q->thread_count; ++i) {
q->threads[i] = os_thread_launch(jq__worker_function, q);
}
return q;
}
void jq_stop(job_queue_t *queue) {
os_mutex_lock(queue->mutex);
job_t *remaining = queue->jobs;
list_push(queue->freelist, remaining);
queue->jobs = NULL;
queue->should_stop = true;
os_cond_broadcast(queue->condvar);
os_mutex_unlock(queue->mutex);
for (int i = 0; i < queue->thread_count; ++i) {
os_thread_join(queue->threads[i], NULL);
}
os_mutex_free(queue->mutex);
os_cond_free(queue->condvar);
}
void jq_cleanup(job_queue_t *queue) {
os_mutex_lock(queue->mutex);
queue->stop_when_finished = true;
os_cond_broadcast(queue->condvar);
os_mutex_unlock(queue->mutex);
for (int i = 0; i < queue->thread_count; ++i) {
os_thread_join(queue->threads[i], NULL);
}
os_mutex_free(queue->mutex);
os_cond_free(queue->condvar);
}
void jq_push(arena_t *arena, job_queue_t *queue, job_func_f *func, void *userdata) {
os_mutex_lock(queue->mutex);
job_t *job = queue->freelist;
if (!job) {
job = alloc(arena, job_t);
}
job->func = func;
job->userdata = userdata;
list_push(queue->jobs, job);
os_cond_broadcast(queue->condvar);
os_mutex_unlock(queue->mutex);
}
job_t *jq_pop_job(job_queue_t *queue) {
job_t *job = NULL;
os_mutex_lock(queue->mutex);
while (!queue->jobs && !queue->should_stop && !queue->stop_when_finished) {
os_cond_wait(queue->condvar, queue->mutex, OS_WAIT_INFINITE);
}
job = queue->jobs;
list_pop(queue->jobs);
os_mutex_unlock(queue->mutex);
return job;
}
#endif
// == INI ============================================
void ini__parse(arena_t *arena, ini_t *ini, const iniopt_t *options);
@ -1538,7 +1833,6 @@ iniopt_t ini__get_options(const iniopt_t *options) {
return out;
}
void ini__add_value(arena_t *arena, initable_t *table, instream_t *in, iniopt_t *opts) {
colla_assert(table);
@ -1660,6 +1954,7 @@ void ini__parse(arena_t *arena, ini_t *ini, const iniopt_t *options) {
// == JSON ===========================================
bool json__parse_obj(arena_t *arena, instream_t *in, jsonflags_e flags, json_t **out);
bool json__parse_value(arena_t *arena, instream_t *in, jsonflags_e flags, json_t **out);
json_t *json_parse(arena_t *arena, strview_t filename, jsonflags_e flags) {
str_t data = os_file_read_all_str(arena, filename);
@ -1674,12 +1969,20 @@ json_t *json_parse_str(arena_t *arena, strview_t str, jsonflags_e flags) {
instream_t in = istr_init(str);
if (!json__parse_obj(arena, &in, flags, &root->object)) {
// reset arena
*arena = before;
return NULL;
if (flags & JSON_ONLY_OBJECT_START) {
if (!json__parse_obj(arena, &in, flags, &root->object)) {
// reset arena
*arena = before;
return NULL;
}
}
else {
if (!json__parse_value(arena, &in, flags, &root)) {
*arena = before;
return NULL;
}
}
return root;
}
@ -1733,8 +2036,6 @@ void json_pretty_print(json_t *root, const json_pretty_opts_t *options) {
#define json__ensure(c) json__check_char(in, c)
bool json__parse_value(arena_t *arena, instream_t *in, jsonflags_e flags, json_t **out);
bool json__check_char(instream_t *in, char c) {
if (istr_get(in) == c) {
return true;
@ -2044,10 +2345,14 @@ void json__pretty_print_value(json_t *value, int indent, const json_pretty_opts_
os_log_set_colour(options->colours[JSON_PRETTY_COLOUR_NUM]);
u8 scratchbuf[256];
arena_t scratch = arena_make(ARENA_STATIC, sizeof(scratchbuf), scratchbuf);
const char *fmt = "%g";
if (round(value->number) == value->number) {
fmt = "%.0f";
}
os_file_print(
scratch,
options->custom_target,
"%g",
fmt,
value->number
);
os_log_set_colour(LOG_COL_RESET);
@ -2631,6 +2936,14 @@ str_t http_res_to_str(arena_t *arena, http_res_t *res) {
return ostr_to_str(&out);
}
http_header_t *http_add_header(arena_t *arena, http_header_t *headers, strview_t key, strview_t value) {
http_header_t *h = alloc(arena, http_header_t);
h->key = key;
h->value = value;
list_push(headers, h);
return headers;
}
bool http_has_header(http_header_t *headers, strview_t key) {
for_each(h, headers) {
if (strv_equals(h->key, key)) {
@ -2752,13 +3065,6 @@ http_url_t http_split_url(strview_t url) {
}
#if !COLLA_NO_NET
// HTTP /////////////////////////////
http_res_t http_request(http_request_desc_t *req) {
return http_request_cb(req, NULL, NULL);
}
// WEBSOCKETS ///////////////////////
#define WEBSOCKET_MAGIC "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
@ -3208,3 +3514,29 @@ str_t pretty_print_get_stringv(arena_t *arena, const char *fmt, va_list args) {
return ostr_to_str(&out);
}
usize pretty_print_get_length(strview_t view) {
usize size = 0;
instream_t in = istr_init(view);
while (!istr_is_finished(&in)) {
strview_t part = istr_get_view(&in, '<');
bool has_escape = strv_ends_with(part, '\\');
if (has_escape) {
part.len -= 1;
}
size += strv_get_utf8_len(part);
istr_skip(&in, 1);
if (has_escape) {
size++;
continue;
}
istr_ignore_and_skip(&in, '>');
}
return size;
}