From ccb992a44a26c5f3e01305aa83bfc3a8898aff8c Mon Sep 17 00:00:00 2001 From: alessandro bason Date: Fri, 12 Sep 2025 14:12:59 +0200 Subject: [PATCH] moving stuff to custom git instance --- .gitignore | 5 +- README.md | 0 arena.c | 332 ---- arena.h | 108 -- build.c | 17 - colla.c | 3186 +++++++++++++++++++++++++++++++ colla.h | 1274 ++++++++++++ colla_lin.c | 0 tcc/colla_tcc.h => colla_tcc.h | 3 +- win/os_win32.c => colla_win32.c | 487 ++++- core.c | 81 - core.h | 222 --- darr.h | 82 - example.c | 9 + highlight.c | 629 ------ highlight.h | 49 - net.c | 649 ------- net.h | 194 -- os.c | 235 --- os.h | 238 --- parsers.c | 1106 ----------- parsers.h | 199 -- pretty_print.c | 80 - pretty_print.h | 10 - stb/stb_sprintf.h | 1945 ------------------- str.c | 795 -------- str.h | 278 --- tcc/colla.def | 12 - tests/arena_tests.c | 237 --- tests/core_tests.c | 229 --- tests/highlight_tests.c | 179 -- tests/net_tests.c | 238 --- tests/os_tests.c | 374 ---- tests/parsers_tests.c | 370 ---- tests/pretty_print_tests.c | 34 - tests/runner.h | 41 - tests/str_tests.c | 457 ----- tests/string_tests.c | 12 - tools/nob.c | 427 ----- tools/unit_tests.c | 80 - win/net_win32.c | 353 ---- win/str_win32.c | 81 - 42 files changed, 4947 insertions(+), 10390 deletions(-) create mode 100644 README.md delete mode 100644 arena.c delete mode 100644 arena.h delete mode 100644 build.c create mode 100644 colla.c create mode 100644 colla.h create mode 100644 colla_lin.c rename tcc/colla_tcc.h => colla_tcc.h (99%) rename win/os_win32.c => colla_win32.c (61%) delete mode 100644 core.c delete mode 100644 core.h delete mode 100644 darr.h create mode 100644 example.c delete mode 100644 highlight.c delete mode 100644 highlight.h delete mode 100644 net.c delete mode 100644 net.h delete mode 100644 os.c delete mode 100644 os.h delete mode 100644 parsers.c delete mode 100644 parsers.h delete mode 100644 pretty_print.c delete mode 100644 pretty_print.h delete mode 100644 stb/stb_sprintf.h delete mode 100644 str.c delete mode 100644 str.h delete mode 100644 tcc/colla.def delete mode 100644 tests/arena_tests.c delete mode 100644 tests/core_tests.c delete mode 100644 tests/highlight_tests.c delete mode 100644 tests/net_tests.c delete mode 100644 tests/os_tests.c delete mode 100644 tests/parsers_tests.c delete mode 100644 tests/pretty_print_tests.c delete mode 100644 tests/runner.h delete mode 100644 tests/str_tests.c delete mode 100644 tests/string_tests.c delete mode 100644 tools/nob.c delete mode 100644 tools/unit_tests.c delete mode 100644 win/net_win32.c delete mode 100644 win/str_win32.c diff --git a/.gitignore b/.gitignore index dbe9c82..1281274 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,4 @@ -.vscode/ \ No newline at end of file +colla/ +build/ +*.pdb +test.c diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/arena.c b/arena.c deleted file mode 100644 index e74237d..0000000 --- a/arena.c +++ /dev/null @@ -1,332 +0,0 @@ -#include "arena.h" - -#include - -#if COLLA_DEBUG -#include -#endif - -#include "os.h" - -static uptr arena__align(uptr ptr, usize align) { - return (ptr + (align - 1)) & ~(align - 1); -} - -static arena_t arena__make_virtual(usize size); -static arena_t arena__make_malloc(usize size); -static arena_t arena__make_static(u8 *buf, usize len); - -static void *arena__alloc_common(const arena_alloc_desc_t *desc); -static void *arena__alloc_malloc_always(const arena_alloc_desc_t *desc); - -static void arena__free_virtual(arena_t *arena); -static void arena__free_malloc(arena_t *arena); - -arena_t malloc_arena = { - .type = ARENA_MALLOC_ALWAYS, -}; - -arena_t arena_init(const arena_desc_t *desc) { - arena_t out = {0}; - -#if COLLA_DEBUG - out.file = desc->file; - out.line = desc->line; -#endif - - if (desc) { - switch (desc->type) { - case ARENA_VIRTUAL: out = arena__make_virtual(desc->size); break; - case ARENA_MALLOC: out = arena__make_malloc(desc->size); break; - case ARENA_STATIC: out = arena__make_static(desc->static_buffer, desc->size); break; - case ARENA_MALLOC_ALWAYS: out = malloc_arena; break; - default: break; - } - } - - return out; -} - -void arena_cleanup(arena_t *arena) { - if (!arena) { - return; - } - - switch (arena->type) { - case ARENA_VIRTUAL: arena__free_virtual(arena); break; - case ARENA_MALLOC: arena__free_malloc(arena); break; - // ARENA_STATIC does not need to be freed - default: break; - } - - memset(arena, 0, sizeof(arena_t)); -} - -arena_t arena_scratch(arena_t *arena, usize size) { - u8 *buffer = alloc(arena, u8, size, ALLOC_SOFT_FAIL | ALLOC_NOZERO); - return arena__make_static(buffer, buffer ? size : 0); -} - -void *arena_alloc(const arena_alloc_desc_t *desc) { - if (!desc || !desc->arena || desc->arena->type == ARENA_TYPE_NONE) { - return NULL; - } - - arena_t *arena = desc->arena; - - u8 *ptr = NULL; - - switch (arena->type) { - case ARENA_MALLOC_ALWAYS: - ptr = arena__alloc_malloc_always(desc); - break; - default: - ptr = arena__alloc_common(desc); - break; - } - - if (!ptr && desc->flags & ALLOC_SOFT_FAIL) { - return NULL; - } - - usize total = desc->size * desc->count; - - return desc->flags & ALLOC_NOZERO ? ptr : memset(ptr, 0, total); -} - -usize arena_tell(arena_t *arena) { - return arena ? arena->cur - arena->beg : 0; -} - -usize arena_remaining(arena_t *arena) { - return arena && (arena->cur < arena->end) ? arena->end - arena->cur : 0; -} - -usize arena_capacity(arena_t *arena) { - return arena ? arena->end - arena->beg : 0; -} - -void arena_rewind(arena_t *arena, usize from_start) { - if (!arena) { - return; - } - - colla_assert(arena_tell(arena) >= from_start); - - arena->cur = arena->beg + from_start; -} - -void arena_pop(arena_t *arena, usize amount) { - if (!arena) { - return; - } - usize position = arena_tell(arena); - if (!position) { - return; - } - arena_rewind(arena, position - amount); -} - -// == VIRTUAL ARENA ==================================================================================================== - -static arena_t arena__make_virtual(usize size) { - usize alloc_size = 0; - u8 *ptr = os_reserve(size, &alloc_size); - if (!os_commit(ptr, 1)) { - os_release(ptr, alloc_size); - ptr = NULL; - } - - return (arena_t){ - .beg = ptr, - .cur = ptr, - .end = ptr ? ptr + alloc_size : NULL, - .type = ARENA_VIRTUAL, - }; -} - -static void arena__free_virtual(arena_t *arena) { - if (!arena->beg) { - return; - } - - os_release(arena->beg, arena_capacity(arena)); -} -// == MALLOC ARENA ===================================================================================================== - -static arena_t arena__make_malloc(usize size) { - u8 *ptr = os_alloc(size); - colla_assert(ptr); - return (arena_t) { - .beg = ptr, - .cur = ptr, - .end = ptr ? ptr + size : NULL, - .type = ARENA_MALLOC, - }; -} - -static void arena__free_malloc(arena_t *arena) { - os_free(arena->beg); -} - -// == ARENA ALLOC ====================================================================================================== - -static void *arena__alloc_common(const arena_alloc_desc_t *desc) { - usize total = desc->size * desc->count; - arena_t *arena = desc->arena; - - arena->cur = (u8 *)arena__align((uptr)arena->cur, desc->align); - bool soft_fail = desc->flags & ALLOC_SOFT_FAIL; - - if (total > arena_remaining(arena)) { - if (!soft_fail) { -#if COLLA_DEBUG - arena__print_crash(arena); -#endif - fatal("finished space in arena, tried to allocate %_$$$dB out of %_$$$dB (total: %_$$$dB)\n", total, arena_remaining(arena), (usize)(arena->end - arena->beg)); - } - return NULL; - } - - if (arena->type == ARENA_VIRTUAL) { - usize allocated = arena_tell(arena); - usize page_end = os_pad_to_page(allocated); - usize new_cur = allocated + total; - - if (new_cur > page_end) { - usize extra_mem = os_pad_to_page(new_cur - page_end); - usize page_size = os_get_system_info().page_size; - // TODO is this really correct? - usize num_of_pages = (extra_mem / page_size) + 1; - - colla_assert(num_of_pages > 0); - - if (!os_commit(arena->cur, num_of_pages + 1)) { - if (!soft_fail) { -#if COLLA_DEBUG - arena__print_crash(arena); -#endif - fatal("failed to commit memory for virtual arena, tried to commit %zu pages\n", num_of_pages); - } - return NULL; - } - } - } - - u8 *ptr = arena->cur; - arena->cur += total; - -#if COLLA_DEBUG - alloc_header_t *header = calloc(1, sizeof(alloc_header_t)); - header->size = desc->size; - header->count = desc->count; - memcpy(header->type_name, desc->type_name.buf, MIN(desc->type_name.len, sizeof(header->type_name))); - list_push(arena->head, header); -#endif - - return ptr; -} - -static void *arena__alloc_malloc_always(const arena_alloc_desc_t *desc) { - usize total = desc->size * desc->count; - - // TODO: alignment? - u8 *ptr = os_alloc(total); - if (!ptr && !(desc->flags & ALLOC_SOFT_FAIL)) { - fatal("alloc call failed for %_$$$dB", total); - } - - return ptr; -} - - -// == STATIC ARENA ===================================================================================================== - -static arena_t arena__make_static(u8 *buf, usize len) { - return (arena_t) { - .beg = buf, - .cur = buf, - .end = buf ? buf + len : NULL, - .type = ARENA_STATIC, - }; -} - -// == DEBUG ============================================================================================================ -#if COLLA_DEBUG - -#define ARENA_HMAP_SIZE (1024) - -typedef struct arena_hnode_t arena_hnode_t; -struct arena_hnode_t { - u32 size; - u32 count; -}; - -typedef struct arena_hmap_t arena_hmap_t; -struct arena_hmap_t { - u64 hashes[ARENA_HMAP_SIZE]; - char keys[ARENA_HMAP_SIZE][16]; - arena_hnode_t values[ARENA_HMAP_SIZE]; -}; - -u64 arena_hmap_hash(char key[16]) { - const u8 *data = (const u8 *)key; - u64 hash = 0; - - for (usize i = 0; i < 16; ++i) { - hash = data[i] + (hash << 6) + (hash << 16) - hash; - } - - return hash; -} - -arena_hnode_t *arena_hmap_find_or_add(arena_hmap_t *map, char key[16]) { - u64 hash = arena_hmap_hash(key); - usize index = hash & (ARENA_HMAP_SIZE - 1); - - usize beg = index; - usize end = ARENA_HMAP_SIZE; - - for (usize k = 0; k < 2; ++k) { - for (usize i = index; i < ARENA_HMAP_SIZE; ++i) { - if (map->hashes[i] == 0){ - arena_hnode_t *node = &map->values[i]; - map->hashes[i] = hash; - memcpy(map->keys[i], key, 16); - return node; - } - - if (map->hashes[i] == hash && memcmp(map->keys[i], key, 16) == 0) { - return &map->values[i]; - } - } - - beg = 0; - end = index; - } - - return NULL; -} - -void arena__print_crash(arena_t *arena) { - arena_hmap_t hmap = {0}; - - debug("arena %v:%d", arena->file, arena->line); - - for_each (header, arena->head) { - arena_hnode_t *node = arena_hmap_find_or_add(&hmap, header->type_name); - colla_assert(node->size == 0 || node->size == header->size); - node->size = header->size; - node->count += header->count; - } - - print("type name | size\t| count\t| total\n"); - for (usize i = 0; i < ARENA_HMAP_SIZE; ++i) { - if (hmap.hashes[i] == 0) continue; - arena_hnode_t n = hmap.values[i]; - print("%16s| %_$$$dB\t| %d\t| %_$$$dB\n", hmap.keys[i], n.size, n.count, n.size * n.count); - } -} -#endif - - diff --git a/arena.h b/arena.h deleted file mode 100644 index bbfedfa..0000000 --- a/arena.h +++ /dev/null @@ -1,108 +0,0 @@ -#ifndef COLLA_ARENA_H -#define COLLA_ARENA_H - -#include "core.h" - -#if COLLA_DEBUG -#include "str.h" -#endif - -#if COLLA_WIN && !COLLA_TCC -#define alignof __alignof -#endif - -typedef enum arena_type_e { - ARENA_TYPE_NONE, // only here so that a 0 initialised arena is valid - ARENA_VIRTUAL, - ARENA_MALLOC, - ARENA_MALLOC_ALWAYS, - ARENA_STATIC, -} arena_type_e; - -typedef enum alloc_flags_e { - ALLOC_FLAGS_NONE = 0, - ALLOC_NOZERO = 1 << 0, - ALLOC_SOFT_FAIL = 1 << 1, -} alloc_flags_e; - -#if COLLA_DEBUG -typedef struct alloc_header_t alloc_header_t; -struct alloc_header_t { - char type_name[16]; - u32 size; - u32 count; - alloc_header_t *next; -}; -void arena__print_crash(arena_t *arena); -#endif - -typedef struct arena_t arena_t; -struct arena_t { - u8 *beg; - u8 *cur; - u8 *end; - arena_type_e type; -#if COLLA_DEBUG - strview_t file; - int line; - alloc_header_t *head; -#endif -}; - -typedef struct arena_desc_t arena_desc_t; -struct arena_desc_t { -#if COLLA_DEBUG - strview_t file; - int line; -#endif - arena_type_e type; - usize size; - u8 *static_buffer; -}; - -typedef struct arena_alloc_desc_t arena_alloc_desc_t; -struct arena_alloc_desc_t { -#if COLLA_DEBUG - strview_t type_name; -#endif - arena_t *arena; - usize count; - alloc_flags_e flags; - usize align; - usize size; -}; - -// arena_type_e type, usize allocation, [ byte *static_buffer ] -#if !COLLA_DEBUG -#define arena_make(...) arena_init(&(arena_desc_t){ __VA_ARGS__ }) -#else -#define arena_make(...) arena_init(&(arena_desc_t){ strv(__FILE__), __LINE__, __VA_ARGS__ }) -#endif - -// arena_t *arena, T type, [ usize count, alloc_flags_e flags, usize align, usize size ] -#if !COLLA_DEBUG -#define alloc(arenaptr, type, ...) arena_alloc(&(arena_alloc_desc_t){ .size = sizeof(type), .count = 1, .align = alignof(type), .arena = arenaptr, __VA_ARGS__ }) -#else -#define alloc(arenaptr, type, ...) arena_alloc(&(arena_alloc_desc_t){ .type_name = strv(#type), .size = sizeof(type), .count = 1, .align = alignof(type), .arena = arenaptr, __VA_ARGS__ }) -#endif - -// simple arena that always calls malloc internally, this is useful if you need -// malloc for some reason but want to still use the arena interface -// WARN: most arena functions outside of alloc/scratch won't work! -// you also need to each allocation afterwards! this is still -// malloc -extern arena_t malloc_arena; - -arena_t arena_init(const arena_desc_t *desc); -void arena_cleanup(arena_t *arena); - -arena_t arena_scratch(arena_t *arena, usize size); - -void *arena_alloc(const arena_alloc_desc_t *desc); -usize arena_tell(arena_t *arena); -usize arena_remaining(arena_t *arena); -usize arena_capacity(arena_t *arena); -void arena_rewind(arena_t *arena, usize from_start); -void arena_pop(arena_t *arena, usize amount); - -#endif diff --git a/build.c b/build.c deleted file mode 100644 index 08735c4..0000000 --- a/build.c +++ /dev/null @@ -1,17 +0,0 @@ -#include "core.h" - -#if COLLA_TCC -#define COLLA_NO_CONDITION_VARIABLE 1 -#endif - -#include "core.c" -#include "os.c" -#include "arena.c" -#include "str.c" -#include "parsers.c" -#include "pretty_print.c" -#include "darr.h" - -#if !COLLA_NO_NET -#include "net.c" -#endif diff --git a/colla.c b/colla.c new file mode 100644 index 0000000..e7135a6 --- /dev/null +++ b/colla.c @@ -0,0 +1,3186 @@ +#include "colla.h" + +#include +#include +#include +#include +#include + +#if COLLA_TCC +#define COLLA_NO_CONDITION_VARIABLE 1 +#define COLLA_NO_NET 1 +#endif + +#if COLLA_WIN + #include "colla_win32.c" +#else + #include "colla_lin.c" +#endif + + +#if COLLA_CLANG + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Weverything" +#endif + +#define STB_SPRINTF_DECORATE(name) colla_stb_##name +#define STB_SPRINTF_NOUNALIGNED +#define STB_SPRINTF_IMPLEMENTATION +#include "stb/stb_sprintf.h" + +#if COLLA_CLANG + #pragma clang diagnostic pop +#endif + +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); + fwrite(buf, 1, len, stdout); + return (char *)ud; +} + +void colla_init(colla_modules_e modules) { + colla__initialised_modules = modules; + if (modules & COLLA_OS) { + os_init(); + } +#if !COLLA_NO_NET + if (modules & COLLA_NET) { + net_init(); + } +#endif +} + +void colla_cleanup(void) { + colla_modules_e modules = colla__initialised_modules; + if (modules & COLLA_OS) { + os_cleanup(); + } +#if !COLLA_NO_NET + if (modules & COLLA_NET) { + net_cleanup(); + } +#endif +} + +int fmt_print(const char *fmt, ...) { + va_list args; + va_start(args, fmt); + int out = fmt_printv(fmt, args); + va_end(args); + return out; +} + +int fmt_printv(const char *fmt, va_list args) { + char buffer[STB_SPRINTF_MIN] = {0}; + return colla_stb_vsprintfcb(colla_fmt__stb_callback, buffer, buffer, fmt, args); +} + +int fmt_buffer(char *buf, usize len, const char *fmt, ...) { + va_list args; + va_start(args, fmt); + int out = fmt_bufferv(buf, len, fmt, args); + va_end(args); + return out; +} + +int fmt_bufferv(char *buf, usize len, const char *fmt, va_list args) { + return colla_stb_vsnprintf(buf, (int)len, fmt, args); +} + +// == STR_T ======================================================== + +strview_t strv__ignore(str_t s, size_t l) { + COLLA_UNUSED(s); COLLA_UNUSED(l); + return STRV_EMPTY; +} + +str_t str_init(arena_t *arena, const char *buf) { + return str_init_len(arena, buf, buf ? strlen(buf) : 0); +} + +str_t str_init_len(arena_t *arena, const char *buf, usize len) { + if (!buf || !len) return STR_EMPTY; + char *str = alloc(arena, char, len + 1); + memmove(str, buf, len); + return (str_t){ str, len }; +} + +str_t str_init_view(arena_t *arena, strview_t view) { + return str_init_len(arena, view.buf, view.len); +} + +str_t str_fmt(arena_t *arena, const char *fmt, ...) { + va_list args; + va_start(args, fmt); + str_t out = str_fmtv(arena, fmt, args); + va_end(args); + return out; +} + +str_t str_fmtv(arena_t *arena, const char *fmt, va_list args) { + va_list vcopy; + va_copy(vcopy, args); + // stb_vsnprintf returns the length + null_term + int len = fmt_bufferv(NULL, 0, fmt, vcopy); + va_end(vcopy); + + char *buffer = alloc(arena, char, len + 1); + fmt_bufferv(buffer, len + 1, fmt, args); + + return (str_t) { .buf = buffer, .len = (usize)len }; +} + +tstr_t tstr_init(TCHAR *str, usize optional_len) { + if (str && !optional_len) { +#if COLLA_UNICODE + optional_len = wcslen(str); +#else + optional_len = strlen(str); +#endif + } + return (tstr_t){ + .buf = str, + .len = optional_len, + }; +} + +str16_t str16_init(u16 *str, usize optional_len) { + if (str && !optional_len) { + optional_len = wcslen(str); + } + return (str16_t){ + .buf = str, + .len = optional_len, + }; +} + +str_t str_from_str16(arena_t *arena, str16_t src) { + if (!src.buf) return STR_EMPTY; + if (!src.len) return STR_EMPTY; + + str_t out = str_os_from_str16(arena, src); + + return out; +} + +str_t str_from_tstr(arena_t *arena, tstr_t src) { +#if COLLA_UNICODE + return str_from_str16(arena, src); +#else + return str(arena, strv(src)); +#endif +} + +str16_t str16_from_str(arena_t *arena, str_t src) { + return strv_to_str16(arena, strv(src)); +} + +bool str_equals(str_t a, str_t b) { + return str_compare(a, b) == 0; +} + +int str_compare(str_t a, str_t b) { + // TODO unsinged underflow if a.len < b.len + return a.len == b.len ? memcmp(a.buf, b.buf, a.len) : (int)(a.len - b.len); +} + +str_t str_dup(arena_t *arena, str_t src) { + return str_init_len(arena, src.buf, src.len); +} + +str_t str_cat(arena_t *arena, str_t a, str_t b) { + str_t out = STR_EMPTY; + + out.len += a.len + b.len; + out.buf = alloc(arena, char, out.len + 1); + memcpy(out.buf, a.buf, a.len); + memcpy(out.buf + a.len, b.buf, b.len); + + return out; +} + +bool str_is_empty(str_t ctx) { + return !ctx.buf || !ctx.len; +} + +void str_lower(str_t *src) { + for (usize i = 0; i < src->len; ++i) { + if (src->buf[i] >= 'A' && src->buf[i] <= 'Z') { + src->buf[i] += 'a' - 'A'; + } + } +} + +void str_upper(str_t *src) { + for (usize i = 0; i < src->len; ++i) { + if (src->buf[i] >= 'a' && src->buf[i] <= 'z') { + src->buf[i] -= 'a' - 'A'; + } + } +} + +void str_replace(str_t *ctx, char from, char to) { + if (!ctx) return; + char *buf = ctx->buf; + for (usize i = 0; i < ctx->len; ++i) { + buf[i] = buf[i] == from ? to : buf[i]; + } +} + +strview_t str_sub(str_t ctx, usize from, usize to) { + if (to > ctx.len) to = ctx.len; + if (from > to) from = to; + return (strview_t){ ctx.buf + from, to - from }; +} + +// == STRVIEW_T ==================================================== + +strview_t strv_init(const char *cstr) { + return strv_init_len(cstr, cstr ? strlen(cstr) : 0); +} + +strview_t strv_init_len(const char *buf, usize size) { + return (strview_t){ + .buf = buf, + .len = size, + }; +} + +strview_t strv_init_str(str_t str) { + return (strview_t){ + .buf = str.buf, + .len = str.len + }; +} + +bool strv_is_empty(strview_t ctx) { + return ctx.len == 0 || !ctx.buf; +} + +bool strv_equals(strview_t a, strview_t b) { + return strv_compare(a, b) == 0; +} + +int strv_compare(strview_t a, strview_t b) { + // TODO unsinged underflow if a.len < b.len + return a.len == b.len ? + memcmp(a.buf, b.buf, a.len) : + (int)(a.len - b.len); +} + +char strv_front(strview_t ctx) { + return ctx.len > 0 ? ctx.buf[0] : '\0'; +} + +char strv_back(strview_t ctx) { + return ctx.len > 0 ? ctx.buf[ctx.len - 1] : '\0'; +} + +str16_t strv_to_str16(arena_t *arena, strview_t src) { + return strv_os_to_str16(arena, src); +} + +tstr_t strv_to_tstr(arena_t *arena, strview_t src) { +#if UNICODE + return strv_to_str16(arena, src); +#else + return str(arena, src); +#endif +} + +str_t strv_to_upper(arena_t *arena, strview_t src) { + str_t out = str(arena, src); + str_upper(&out); + return out; +} + +str_t strv_to_lower(arena_t *arena, strview_t src) { + str_t out = str(arena, src); + str_lower(&out); + return out; +} + +strview_t strv_remove_prefix(strview_t ctx, usize n) { + if (n > ctx.len) n = ctx.len; + return (strview_t){ + .buf = ctx.buf + n, + .len = ctx.len - n, + }; +} + +strview_t strv_remove_suffix(strview_t ctx, usize n) { + if (n > ctx.len) n = ctx.len; + return (strview_t){ + .buf = ctx.buf, + .len = ctx.len - n, + }; +} + +strview_t strv_trim(strview_t ctx) { + return strv_trim_left(strv_trim_right(ctx)); +} + +strview_t strv_trim_left(strview_t ctx) { + strview_t out = ctx; + for (usize i = 0; i < ctx.len; ++i) { + char c = ctx.buf[i]; + if (c != ' ' && (c < '\t' || c > '\r')) { + break; + } + out.buf++; + out.len--; + } + return out; +} + +strview_t strv_trim_right(strview_t ctx) { + strview_t out = ctx; + for (isize i = ctx.len - 1; i >= 0; --i) { + char c = ctx.buf[i]; + if (c != ' ' && (c < '\t' || c > '\r')) { + break; + } + out.len--; + } + return out; +} + +strview_t strv_sub(strview_t ctx, usize from, usize to) { + if (ctx.len == 0) return STRV_EMPTY; + if (to > ctx.len) to = ctx.len; + if (from > to) from = to; + return (strview_t){ ctx.buf + from, to - from }; +} + +bool strv_starts_with(strview_t ctx, char c) { + return ctx.len > 0 && ctx.buf[0] == c; +} + +bool strv_starts_with_view(strview_t ctx, strview_t view) { + return ctx.len >= view.len && memcmp(ctx.buf, view.buf, view.len) == 0; +} + +bool strv_ends_with(strview_t ctx, char c) { + return ctx.len > 0 && ctx.buf[ctx.len - 1] == c; +} + +bool strv_ends_with_view(strview_t ctx, strview_t view) { + return ctx.len >= view.len && memcmp(ctx.buf + ctx.len - view.len, view.buf, view.len) == 0; +} + +bool strv_contains(strview_t ctx, char c) { + for(usize i = 0; i < ctx.len; ++i) { + if(ctx.buf[i] == c) { + return true; + } + } + return false; +} + +bool strv_contains_view(strview_t ctx, strview_t view) { + if (ctx.len < view.len) return false; + usize end = (ctx.len - view.len) + 1; + + for (usize i = 0; i < end; ++i) { + if (memcmp(ctx.buf + i, view.buf, view.len) == 0) { + return true; + } + } + return false; +} + +bool strv_contains_either(strview_t ctx, strview_t chars) { + for (usize i = 0; i < ctx.len; ++i) { + if (strv_contains(chars, ctx.buf[i])) { + return true; + } + } + + return false; +} + +usize strv_find(strview_t ctx, char c, usize from) { + for (usize i = from; i < ctx.len; ++i) { + if (ctx.buf[i] == c) { + return i; + } + } + return STR_NONE; +} + +usize strv_find_view(strview_t ctx, strview_t view, usize from) { + if (view.len > ctx.len) return STR_NONE; + + usize end = (ctx.len - view.len) + 1; + + for (usize i = from; i < end; ++i) { + if (memcmp(ctx.buf + i, view.buf, view.len) == 0) { + return i; + } + } + return STR_NONE; +} + +usize strv_find_either(strview_t ctx, strview_t chars, usize from) { + if (from > ctx.len) from = ctx.len; + + for (usize i = from; i < ctx.len; ++i) { + if (strv_contains(chars, ctx.buf[i])) { + return i; + } + } + + return STR_NONE; +} + +usize strv_rfind(strview_t ctx, char c, usize from_right) { + if (ctx.len == 0) return STR_NONE; + if (from_right > ctx.len) from_right = ctx.len; + isize end = (isize)(ctx.len - from_right); + for (isize i = end; i >= 0; --i) { + if (ctx.buf[i] == c) { + return (usize)i; + } + } + return STR_NONE; +} + +usize strv_rfind_view(strview_t ctx, strview_t view, usize from_right) { + if (ctx.len == 0) return STR_NONE; + if (from_right > ctx.len) from_right = ctx.len; + isize end = (isize)(ctx.len - from_right); + if (end < (isize)view.len) return STR_NONE; + for (isize i = end - view.len; i >= 0; --i) { + if (memcmp(ctx.buf + i, view.buf, view.len) == 0) { + return (usize)i; + } + } + return STR_NONE; +} + +// == CTYPE ======================================================== + +bool char_is_space(char c) { + return (c >= '\t' && c <= '\r') || c == ' '; +} + +bool char_is_alpha(char c) { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); +} + +bool char_is_num(char c) { + return c >= '0' && c <= '9'; +} + +char char_lower(char c) { + return c >= 'A' && c <= 'Z' ? c + 32 : c; +} + +char char_upper(char c) { + return c <= 'a' && c >= 'z' ? c - 32 : c; +} + +// == INPUT STREAM ================================================= + +instream_t istr_init(strview_t str) { + return (instream_t) { + .beg = str.buf, + .cur = str.buf, + .len = str.len, + }; +} + +char istr_get(instream_t *ctx) { + return istr_remaining(ctx) ? *ctx->cur++ : '\0'; +} + +char istr_peek(instream_t *ctx) { + return istr_remaining(ctx) ? *ctx->cur : '\0'; +} + +char istr_peek_next(instream_t *ctx) { + return istr_remaining(ctx) > 1 ? *(ctx->cur + 1) : '\0'; +} + +char istr_prev(instream_t *ctx) { + return istr_tell(ctx) ? *(ctx->cur - 1) : '\0'; +} + +char istr_prev_prev(instream_t *ctx) { + return istr_tell(ctx) > 1 ? *(ctx->cur - 2) : '\0'; +} + +void istr_ignore(instream_t *ctx, char delim) { + while (!istr_is_finished(ctx) && *ctx->cur != delim) { + ctx->cur++; + } +} + +void istr_ignore_and_skip(instream_t *ctx, char delim) { + istr_ignore(ctx, delim); + istr_skip(ctx, 1); +} + +void istr_skip(instream_t *ctx, usize n) { + if (!ctx) return; + usize rem = istr_remaining(ctx); + if (n > rem) n = rem; + ctx->cur += n; +} + +void istr_skip_whitespace(instream_t *ctx) { + while (!istr_is_finished(ctx) && char_is_space(*ctx->cur)) { + ctx->cur++; + } +} + +void istr_rewind(instream_t *ctx) { + if (ctx) ctx->cur = ctx->beg; +} + +void istr_rewind_n(instream_t *ctx, usize amount) { + if (!ctx) return; + usize rem = istr_remaining(ctx); + ctx->cur -= MIN(amount, rem); +} + +usize istr_tell(instream_t *ctx) { + return ctx ? ctx->cur - ctx->beg : 0; +} + +usize istr_remaining(instream_t *ctx) { + return ctx ? ctx->len - (ctx->cur - ctx->beg) : 0; +} + +bool istr_is_finished(instream_t *ctx) { + return !(ctx && istr_remaining(ctx) > 0); +} + +bool istr_get_bool(instream_t *ctx, bool *val) { + if (!ctx || !ctx->cur || !val) return false; + usize rem = istr_remaining(ctx); + if (rem >= 4 && memcmp(ctx->cur, "true", 4) == 0) { + *val = true; + ctx->cur += 4; + return true; + } + if (rem >= 5 && memcmp(ctx->cur, "false", 5) == 0) { + *val = false; + ctx->cur += 5; + return true; + } + return false; +} + +bool istr_get_u8(instream_t *ctx, u8 *val) { + u64 out = 0; + bool result = istr_get_u64(ctx, &out); + if (result && out < UINT8_MAX) { + *val = (u8)out; + } + return result; +} + +bool istr_get_u16(instream_t *ctx, u16 *val) { + u64 out = 0; + bool result = istr_get_u64(ctx, &out); + if (result && out < UINT16_MAX) { + *val = (u16)out; + } + return result; +} + +bool istr_get_u32(instream_t *ctx, u32 *val) { + u64 out = 0; + bool result = istr_get_u64(ctx, &out); + if (result && out < UINT32_MAX) { + *val = (u32)out; + } + return result; +} + + +bool istr_get_u64(instream_t *ctx, u64 *val) { + if (!ctx || !ctx->cur || !val) return false; + char *end = NULL; + *val = strtoull(ctx->cur, &end, 0); + + if (ctx->cur == end) { + return false; + } + else if (*val == ULLONG_MAX) { + return false; + } + + ctx->cur = end; + return true; +} + +bool istr_get_i8(instream_t *ctx, i8 *val) { + i64 out = 0; + bool result = istr_get_i64(ctx, &out); + if (result && out > INT8_MIN && out < INT8_MAX) { + *val = (i8)out; + } + return result; +} + +bool istr_get_i16(instream_t *ctx, i16 *val) { + i64 out = 0; + bool result = istr_get_i64(ctx, &out); + if (result && out > INT16_MIN && out < INT16_MAX) { + *val = (i16)out; + } + return result; +} + +bool istr_get_i32(instream_t *ctx, i32 *val) { + i64 out = 0; + bool result = istr_get_i64(ctx, &out); + if (result && out > INT32_MIN && out < INT32_MAX) { + *val = (i32)out; + } + return result; +} + +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); + + if (ctx->cur == end) { + return false; + } + else if(*val == INT64_MAX || *val == INT64_MIN) { + return false; + } + + ctx->cur = end; + return true; +} + +bool istr_get_num(instream_t *ctx, double *val) { + if (!ctx || !ctx->cur || !val) return false; + char *end = NULL; + *val = strtod(ctx->cur, &end); + + if(ctx->cur == end) { + warn("istrGetDouble: no valid conversion could be performed (%.5s)", ctx->cur); + return false; + } + else if(*val == HUGE_VAL || *val == -HUGE_VAL) { + warn("istrGetDouble: value read is out of the range of representable values"); + return false; + } + + ctx->cur = end; + return true; +} + +strview_t istr_get_view(instream_t *ctx, char delim) { + if (!ctx || !ctx->cur) return STRV_EMPTY; + const char *from = ctx->cur; + istr_ignore(ctx, delim); + usize len = ctx->cur - from; + return strv(from, len); +} + +strview_t istr_get_view_either(instream_t *ctx, strview_t chars) { + if (!ctx || !ctx->cur) return STRV_EMPTY; + const char *from = ctx->cur; + while (!istr_is_finished(ctx) && !strv_contains(chars, *ctx->cur)) { + ctx->cur++; + } + + usize len = ctx->cur - from; + return strv(from, len); +} + +strview_t istr_get_view_len(instream_t *ctx, usize len) { + if (!ctx || !ctx->cur) return STRV_EMPTY; + const char *from = ctx->cur; + istr_skip(ctx, len); + usize buflen = ctx->cur - from; + return (strview_t){ from, buflen }; +} + +strview_t istr_get_line(instream_t *ctx) { + strview_t line = istr_get_view(ctx, '\n'); + istr_skip(ctx, 1); + if (strv_ends_with(line, '\r')) { + line = strv_remove_suffix(line, 1); + } + return line; +} + +// == OUTPUT STREAM ================================================ + +outstream_t ostr_init(arena_t *exclusive_arena) { + return (outstream_t) { + .beg = (char *)(exclusive_arena ? exclusive_arena->cur : NULL), + .arena = exclusive_arena, + }; +} + +void ostr_clear(outstream_t *ctx) { + arena_pop(ctx->arena, ostr_tell(ctx)); +} + +usize ostr_tell(outstream_t *ctx) { + return ctx->arena ? (char *)ctx->arena->cur - ctx->beg : 0; +} + +char ostr_back(outstream_t *ctx) { + usize len = ostr_tell(ctx); + return len ? ctx->beg[len - 1] : '\0'; +} + +str_t ostr_to_str(outstream_t *ctx) { + ostr_putc(ctx, '\0'); + + usize len = ostr_tell(ctx); + + str_t out = { + .buf = ctx->beg, + .len = len ? len - 1 : 0, + }; + + memset(ctx, 0, sizeof(outstream_t)); + return out; +} + +strview_t ostr_as_view(outstream_t *ctx) { + return strv(ctx->beg, ostr_tell(ctx)); +} + +void ostr_pop(outstream_t *ctx, usize count) { + if (!ctx->arena) return; + arena_pop(ctx->arena, count); +} + +void ostr_print(outstream_t *ctx, const char *fmt, ...) { + va_list args; + va_start(args, fmt); + ostr_printv(ctx, fmt, args); + va_end(args); +} + +void ostr_printv(outstream_t *ctx, const char *fmt, va_list args) { + if (!ctx->arena) return; + str_fmtv(ctx->arena, fmt, args); + // remove null terminator + arena_pop(ctx->arena, 1); +} + +void ostr_putc(outstream_t *ctx, char c) { + if (!ctx->arena) return; + char *newc = alloc(ctx->arena, char); + *newc = c; +} + +void ostr_puts(outstream_t *ctx, strview_t v) { + if (strv_is_empty(v)) return; + str(ctx->arena, v); + // remove null terminator + arena_pop(ctx->arena, 1); +} + +void ostr_append_bool(outstream_t *ctx, bool val) { + ostr_puts(ctx, val ? strv("true") : strv("false")); +} + +void ostr_append_uint(outstream_t *ctx, u64 val) { + ostr_print(ctx, "%I64u", val); +} + +void ostr_append_int(outstream_t *ctx, i64 val) { + ostr_print(ctx, "%I64d", val); +} + +void ostr_append_num(outstream_t *ctx, double val) { + ostr_print(ctx, "%g", val); +} + +// == INPUT BINARY STREAM ========================================== + +ibstream_t ibstr_init(buffer_t buffer) { + return (ibstream_t){ + .beg = buffer.data, + .cur = buffer.data, + .len = buffer.len, + }; +} + +bool ibstr_is_finished(ibstream_t *ib) { + return !(ib && ibstr_remaining(ib) > 0); +} + +usize ibstr_tell(ibstream_t *ib) { + return ib && ib->cur ? ib->cur - ib->beg : 0; +} + +usize ibstr_remaining(ibstream_t *ib) { + return ib ? ib->len - ibstr_tell(ib) : 0; +} + +usize ibstr_read(ibstream_t *ib, void *buffer, usize len) { + usize rem = ibstr_remaining(ib); + if (len > rem) len = rem; + memmove(buffer, ib->cur, len); + ib->cur += len; + return len; +} + +void ibstr_skip(ibstream_t *ib, usize count) { + usize rem = ibstr_remaining(ib); + if (count > rem) count = rem; + ib->cur += count; +} + +bool ibstr_get_u8(ibstream_t *ib, u8 *out) { + return ibstr_read(ib, out, sizeof(*out)) == sizeof(*out); +} + +bool ibstr_get_u16(ibstream_t *ib, u16 *out) { + return ibstr_read(ib, out, sizeof(*out)) == sizeof(*out); +} + +bool ibstr_get_u32(ibstream_t *ib, u32 *out) { + return ibstr_read(ib, out, sizeof(*out)) == sizeof(*out); +} + +bool ibstr_get_u64(ibstream_t *ib, u64 *out) { + return ibstr_read(ib, out, sizeof(*out)) == sizeof(*out); +} + +bool ibstr_get_i8(ibstream_t *ib, i8 *out) { + return ibstr_read(ib, out, sizeof(*out)) == sizeof(*out); +} + +bool ibstr_get_i16(ibstream_t *ib, i16 *out) { + return ibstr_read(ib, out, sizeof(*out)) == sizeof(*out); +} + +bool ibstr_get_i32(ibstream_t *ib, i32 *out) { + return ibstr_read(ib, out, sizeof(*out)) == sizeof(*out); +} + +bool ibstr_get_i64(ibstream_t *ib, i64 *out) { + return ibstr_read(ib, out, sizeof(*out)) == sizeof(*out); +} + +static uptr arena__align(uptr ptr, usize align) { + return (ptr + (align - 1)) & ~(align - 1); +} + +static arena_t arena__make_virtual(usize size); +static arena_t arena__make_malloc(usize size); +static arena_t arena__make_static(u8 *buf, usize len); + +static void *arena__alloc_common(const arena_alloc_desc_t *desc); +static void *arena__alloc_malloc_always(const arena_alloc_desc_t *desc); + +static void arena__free_virtual(arena_t *arena); +static void arena__free_malloc(arena_t *arena); + +arena_t malloc_arena = { + .type = ARENA_MALLOC_ALWAYS, +}; + +arena_t arena_init(const arena_desc_t *desc) { + arena_t out = {0}; + + if (desc) { + switch (desc->type) { + case ARENA_VIRTUAL: out = arena__make_virtual(desc->size); break; + case ARENA_MALLOC: out = arena__make_malloc(desc->size); break; + case ARENA_STATIC: out = arena__make_static(desc->static_buffer, desc->size); break; + case ARENA_MALLOC_ALWAYS: out = malloc_arena; break; + default: break; + } + } + + return out; +} + +void arena_cleanup(arena_t *arena) { + if (!arena) { + return; + } + + switch (arena->type) { + case ARENA_VIRTUAL: arena__free_virtual(arena); break; + case ARENA_MALLOC: arena__free_malloc(arena); break; + // ARENA_STATIC does not need to be freed + default: break; + } + + memset(arena, 0, sizeof(arena_t)); +} + +arena_t arena_scratch(arena_t *arena, usize size) { + u8 *buffer = alloc(arena, u8, size, ALLOC_SOFT_FAIL | ALLOC_NOZERO); + return arena__make_static(buffer, buffer ? size : 0); +} + +void *arena_alloc(const arena_alloc_desc_t *desc) { + if (!desc || !desc->arena || desc->arena->type == ARENA_TYPE_NONE) { + return NULL; + } + + arena_t *arena = desc->arena; + + u8 *ptr = NULL; + + switch (arena->type) { + case ARENA_MALLOC_ALWAYS: + ptr = arena__alloc_malloc_always(desc); + break; + default: + ptr = arena__alloc_common(desc); + break; + } + + if (!ptr && desc->flags & ALLOC_SOFT_FAIL) { + return NULL; + } + + usize total = desc->size * desc->count; + + return desc->flags & ALLOC_NOZERO ? ptr : memset(ptr, 0, total); +} + +usize arena_tell(arena_t *arena) { + return arena ? arena->cur - arena->beg : 0; +} + +usize arena_remaining(arena_t *arena) { + return arena && (arena->cur < arena->end) ? arena->end - arena->cur : 0; +} + +usize arena_capacity(arena_t *arena) { + return arena ? arena->end - arena->beg : 0; +} + +void arena_rewind(arena_t *arena, usize from_start) { + if (!arena) { + return; + } + + colla_assert(arena_tell(arena) >= from_start); + + arena->cur = arena->beg + from_start; +} + +void arena_pop(arena_t *arena, usize amount) { + if (!arena) { + return; + } + usize position = arena_tell(arena); + if (!position) { + return; + } + arena_rewind(arena, position - amount); +} + +// == VIRTUAL ARENA ==================================================================================================== + +static arena_t arena__make_virtual(usize size) { + usize alloc_size = 0; + u8 *ptr = os_reserve(size, &alloc_size); + if (!os_commit(ptr, 1)) { + os_release(ptr, alloc_size); + ptr = NULL; + } + + return (arena_t){ + .beg = ptr, + .cur = ptr, + .end = ptr ? ptr + alloc_size : NULL, + .type = ARENA_VIRTUAL, + }; +} + +static void arena__free_virtual(arena_t *arena) { + if (!arena->beg) { + return; + } + + os_release(arena->beg, arena_capacity(arena)); +} +// == MALLOC ARENA ===================================================================================================== + +static arena_t arena__make_malloc(usize size) { + u8 *ptr = os_alloc(size); + colla_assert(ptr); + return (arena_t) { + .beg = ptr, + .cur = ptr, + .end = ptr ? ptr + size : NULL, + .type = ARENA_MALLOC, + }; +} + +static void arena__free_malloc(arena_t *arena) { + os_free(arena->beg); +} + +// == ARENA ALLOC ====================================================================================================== + +static void *arena__alloc_common(const arena_alloc_desc_t *desc) { + usize total = desc->size * desc->count; + arena_t *arena = desc->arena; + + arena->cur = (u8 *)arena__align((uptr)arena->cur, desc->align); + bool soft_fail = desc->flags & ALLOC_SOFT_FAIL; + + if (total > arena_remaining(arena)) { + if (!soft_fail) { + fatal("finished space in arena, tried to allocate %_$$$dB out of %_$$$dB (total: %_$$$dB)\n", total, arena_remaining(arena), (usize)(arena->end - arena->beg)); + } + return NULL; + } + + if (arena->type == ARENA_VIRTUAL) { + usize allocated = arena_tell(arena); + usize page_end = os_pad_to_page(allocated); + usize new_cur = allocated + total; + + if (new_cur > page_end) { + usize extra_mem = os_pad_to_page(new_cur - page_end); + usize page_size = os_get_system_info().page_size; + // TODO is this really correct? + usize num_of_pages = (extra_mem / page_size) + 1; + + colla_assert(num_of_pages > 0); + + if (!os_commit(arena->cur, num_of_pages + 1)) { + if (!soft_fail) { + fatal("failed to commit memory for virtual arena, tried to commit %zu pages\n", num_of_pages); + } + return NULL; + } + } + } + + u8 *ptr = arena->cur; + arena->cur += total; + + return ptr; +} + +static void *arena__alloc_malloc_always(const arena_alloc_desc_t *desc) { + usize total = desc->size * desc->count; + + u8 *ptr = os_alloc(total); + if (!ptr && !(desc->flags & ALLOC_SOFT_FAIL)) { + fatal("alloc call failed for %_$$$dB", total); + } + + return ptr; +} + +// == STATIC ARENA ===================================================================================================== + +static arena_t arena__make_static(u8 *buf, usize len) { + return (arena_t) { + .beg = buf, + .cur = buf, + .end = buf ? buf + len : NULL, + .type = ARENA_STATIC, + }; +} + +// == HANDLE ==================================== + +oshandle_t os_handle_zero(void) { + return (oshandle_t){0}; +} + +bool os_handle_match(oshandle_t a, oshandle_t b) { + return a.data == b.data; +} + +bool os_handle_valid(oshandle_t handle) { + return !os_handle_match(handle, os_handle_zero()); +} + +// == 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; + } + return colour; +} + +void os_log_print(os_log_level_e level, const char *fmt, ...) { + va_list args; + va_start(args, fmt); + os_log_printv(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; + } + + 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); + + fmt_printv(fmt, args); + fmt_print("\n"); + + if (level == LOG_FATAL) { + os_abort(1); + } +} + +// == FILE ====================================== + +void os_file_split_path(strview_t path, strview_t *dir, strview_t *name, strview_t *ext) { + usize dir_lin = strv_rfind(path, '/', 0); + usize dir_win = strv_rfind(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 = strv_rfind(path, '.', 0); + + if (dir) { + *dir = strv_sub(path, 0, dir_pos); + } + if (name) { + *name = strv_sub(path, dir_pos ? dir_pos + 1 : 0, ext_pos); + } + if (ext) { + *ext = strv_sub(path, ext_pos, SIZE_MAX); + } +} + +bool os_file_putc(oshandle_t handle, char c) { + return os_file_write(handle, &c, sizeof(c)) == sizeof(c); +} + +bool os_file_puts(oshandle_t handle, strview_t str) { + return os_file_write(handle, str.buf, str.len) == str.len; +} + +bool os_file_print(arena_t scratch, oshandle_t handle, const char *fmt, ...) { + va_list args; + va_start(args, fmt); + bool result = os_file_printv(scratch, handle, fmt, args); + va_end(args); + return result; +} + +bool os_file_printv(arena_t scratch, oshandle_t handle, const char *fmt, va_list args) { + str_t s = str_fmtv(&scratch, fmt, args); + return os_file_puts(handle, strv(s)); +} + +usize os_file_read_buf(oshandle_t handle, buffer_t *buf) { + return os_file_read(handle, buf->data, buf->len); +} + +usize os_file_write_buf(oshandle_t handle, buffer_t buf) { + return os_file_write(handle, buf.data, buf.len); +} + +buffer_t os_file_read_all(arena_t *arena, strview_t path) { + oshandle_t fp = os_file_open(path, FILEMODE_READ); + if (!os_handle_valid(fp)) { + err("could not open file: %v", path); + return (buffer_t){0}; + } + buffer_t out = os_file_read_all_fp(arena, fp); + os_file_close(fp); + return out; +} + +buffer_t os_file_read_all_fp(arena_t *arena, oshandle_t handle) { + if (!os_handle_valid(handle)) return (buffer_t){0}; + buffer_t out = {0}; + + out.len = os_file_size(handle); + out.data = alloc(arena, u8, out.len); + usize read = os_file_read_buf(handle, &out); + + if (read != out.len) { + err("os_file_read_all_fp: read failed, should be %zu but is %zu", out.len, read); + arena_pop(arena, out.len); + return (buffer_t){0}; + } + + return out; +} + +str_t os_file_read_all_str(arena_t *arena, strview_t path) { + oshandle_t fp = os_file_open(path, FILEMODE_READ); + if (!os_handle_valid(fp)) { + err("could not open file %v: %v", path, os_get_error_string(os_get_last_error())); + return STR_EMPTY; + } + str_t out = os_file_read_all_str_fp(arena, fp); + os_file_close(fp); + return out; +} + +str_t os_file_read_all_str_fp(arena_t *arena, oshandle_t handle) { + if (!os_handle_valid(handle)) { + return STR_EMPTY; + } + + str_t out = STR_EMPTY; + + out.len = os_file_size(handle); + out.buf = alloc(arena, u8, out.len + 1); + + usize read = os_file_read(handle, out.buf, out.len); + if (read != out.len) { + err("os_file_read_all_str_fp: read failed, should be %zu but is %zu", out.len, read); + arena_pop(arena, out.len + 1); + return STR_EMPTY; + } + + return out; +} + +bool os_file_write_all(strview_t name, buffer_t buffer) { + oshandle_t fp = os_file_open(name, FILEMODE_WRITE); + bool result = os_file_write_all_fp(fp, buffer); + os_file_close(fp); + return result; +} + +bool os_file_write_all_fp(oshandle_t handle, buffer_t buffer) { + return os_file_write(handle, buffer.data, buffer.len) == buffer.len; +} + +bool os_file_write_all_str(strview_t name, strview_t data) { + oshandle_t fp = os_file_open(name, FILEMODE_WRITE); + bool result = os_file_write_all_str_fp(fp, data); + os_file_close(fp); + return result; +} + +bool os_file_write_all_str_fp(oshandle_t handle, strview_t data) { + return os_file_write(handle, data.buf, data.len) == data.len; +} + +u64 os_file_time(strview_t path) { + oshandle_t fp = os_file_open(path, FILEMODE_READ); + u64 result = os_file_time_fp(fp); + os_file_close(fp); + return result; +} + +bool os_file_has_changed(strview_t path, u64 last_change) { + u64 timestamp = os_file_time(path); + return timestamp > last_change; +} + +// == PROCESS =================================== + +bool os_run_cmd(arena_t scratch, os_cmd_t *cmd, os_cmd_options_t *options) { + oshandle_t proc = os_run_cmd_async(scratch, cmd, options); + return os_handle_valid(proc) ? os_process_wait(proc, OS_WAIT_INFINITE, NULL) : false; +} + +// == VMEM ====================================== + +usize os_pad_to_page(usize byte_count) { + usize page_size = os_get_system_info().page_size; + + if (byte_count == 0) { + return page_size; + } + + usize padding = page_size - (byte_count & (page_size - 1)); + if (padding == page_size) { + padding = 0; + } + return byte_count + padding; +} + +// == INI ============================================ + +void ini__parse(arena_t *arena, ini_t *ini, const iniopt_t *options); + +ini_t ini_parse(arena_t *arena, strview_t filename, iniopt_t *opt) { + oshandle_t fp = os_file_open(filename, FILEMODE_READ); + ini_t out = ini_parse_fp(arena, fp, opt); + os_file_close(fp); + return out; +} + +ini_t ini_parse_fp(arena_t *arena, oshandle_t file, iniopt_t *opt) { + str_t data = os_file_read_all_str_fp(arena, file); + return ini_parse_str(arena, strv(data), opt); +} + +ini_t ini_parse_str(arena_t *arena, strview_t str, iniopt_t *opt) { + ini_t out = { + .text = str, + .tables = NULL, + }; + ini__parse(arena, &out, opt); + return out; +} + +bool ini_is_valid(ini_t *ini) { + return ini && !strv_is_empty(ini->text); +} + +initable_t *ini_get_table(ini_t *ini, strview_t name) { + initable_t *t = ini ? ini->tables : NULL; + while (t) { + if (strv_equals(t->name, name)) { + return t; + } + t = t->next; + } + return NULL; +} + +inivalue_t *ini_get(initable_t *table, strview_t key) { + inivalue_t *v = table ? table->values : NULL; + while (v) { + if (strv_equals(v->key, key)) { + return v; + } + v = v->next; + } + return NULL; +} + +iniarray_t ini_as_arr(arena_t *arena, inivalue_t *value, char delim) { + strview_t v = value ? value->value : STRV_EMPTY; + if (!delim) delim = ' '; + + strview_t *beg = (strview_t *)arena->cur; + usize count = 0; + + usize start = 0; + for (usize i = 0; i < v.len; ++i) { + if (v.buf[i] == delim) { + strview_t arrval = strv_trim(strv_sub(v, start, i)); + if (!strv_is_empty(arrval)) { + strview_t *newval = alloc(arena, strview_t); + *newval = arrval; + ++count; + } + start = i + 1; + } + } + + strview_t last = strv_trim(strv_sub(v, start, SIZE_MAX)); + if (!strv_is_empty(last)) { + strview_t *newval = alloc(arena, strview_t); + *newval = last; + ++count; + } + + return (iniarray_t){ + .values = beg, + .count = count, + }; +} + +u64 ini_as_uint(inivalue_t *value) { + strview_t v = value ? value->value : STRV_EMPTY; + instream_t in = istr_init(v); + u64 out = 0; + if (!istr_get_u64(&in, &out)) { + out = 0; + } + return out; +} + +i64 ini_as_int(inivalue_t *value) { + strview_t v = value ? value->value : STRV_EMPTY; + instream_t in = istr_init(v); + i64 out = 0; + if (!istr_get_i64(&in, &out)) { + out = 0; + } + return out; +} + +double ini_as_num(inivalue_t *value) { + strview_t v = value ? value->value : STRV_EMPTY; + instream_t in = istr_init(v); + double out = 0; + if (!istr_get_num(&in, &out)) { + out = 0; + } + return out; +} + +bool ini_as_bool(inivalue_t *value) { + strview_t v = value ? value->value : STRV_EMPTY; + instream_t in = istr_init(v); + bool out = 0; + if (!istr_get_bool(&in, &out)) { + out = 0; + } + return out; +} + +void ini_pretty_print(ini_t *ini, const ini_pretty_opts_t *options) { + ini_pretty_opts_t opt = {0}; + if (options) { + memmove(&opt, options, sizeof(ini_pretty_opts_t)); + } + + if (!os_handle_valid(opt.custom_target)) { + opt.custom_target = os_stdout(); + } + + if (!opt.use_custom_colours) { + os_log_colour_e default_col[INI_PRETTY_COLOUR__COUNT] = { + LOG_COL_YELLOW, // INI_PRETTY_COLOUR_KEY, + LOG_COL_GREEN, // INI_PRETTY_COLOUR_VALUE, + LOG_COL_WHITE, // INI_PRETTY_COLOUR_DIVIDER, + LOG_COL_RED, // INI_PRETTY_COLOUR_TABLE, + }; + memmove(opt.colours, default_col, sizeof(default_col)); + } + + for_each (t, ini->tables) { + if (!strv_equals(t->name, INI_ROOT)) { + os_log_set_colour(opt.colours[INI_PRETTY_COLOUR_TABLE]); + os_file_puts(opt.custom_target, strv("[")); + os_file_puts(opt.custom_target, t->name); + os_file_puts(opt.custom_target, strv("]\n")); + } + + for_each (pair, t->values) { + if (strv_is_empty(pair->key) || strv_is_empty(pair->value)) continue; + os_log_set_colour(opt.colours[INI_PRETTY_COLOUR_KEY]); + os_file_puts(opt.custom_target, pair->key); + + os_log_set_colour(opt.colours[INI_PRETTY_COLOUR_DIVIDER]); + os_file_puts(opt.custom_target, strv(" = ")); + + os_log_set_colour(opt.colours[INI_PRETTY_COLOUR_VALUE]); + os_file_puts(opt.custom_target, pair->value); + + os_file_puts(opt.custom_target, strv("\n")); + } + } + + os_log_set_colour(LOG_COL_RESET); +} + +///// ini-private //////////////////////////////////// + +iniopt_t ini__get_options(const iniopt_t *options) { + iniopt_t out = { + .key_value_divider = '=', + .comment_vals = strv(";#"), + }; + +#define SETOPT(v) out.v = options->v ? options->v : out.v + + if (options) { + SETOPT(key_value_divider); + SETOPT(merge_duplicate_keys); + SETOPT(merge_duplicate_tables); + out.comment_vals = strv_is_empty(options->comment_vals) ? out.comment_vals : options->comment_vals; + } + +#undef SETOPT + + return out; +} + + +void ini__add_value(arena_t *arena, initable_t *table, instream_t *in, iniopt_t *opts) { + colla_assert(table); + + strview_t key = strv_trim(istr_get_view(in, opts->key_value_divider)); + istr_skip(in, 1); + + strview_t value = strv_trim(istr_get_view(in, '\n')); + usize comment_pos = strv_find_either(value, opts->comment_vals, 0); + if (comment_pos != STR_NONE) { + value = strv_sub(value, 0, comment_pos); + } + istr_skip(in, 1); + inivalue_t *newval = NULL; + + if (opts->merge_duplicate_keys) { + newval = table->values; + while (newval) { + if (strv_equals(newval->key, key)) { + break; + } + newval = newval->next; + } + } + + if (newval) { + newval->value = value; + } + else { + newval = alloc(arena, inivalue_t); + newval->key = key; + newval->value = value; + + if (!table->values) { + table->values = newval; + } + else { + table->tail->next = newval; + } + + table->tail = newval; + } +} + +void ini__add_table(arena_t *arena, ini_t *ctx, instream_t *in, iniopt_t *options) { + istr_skip(in, 1); // skip [ + strview_t name = istr_get_view(in, ']'); + istr_skip(in, 1); // skip ] + initable_t *table = NULL; + + if (options->merge_duplicate_tables) { + table = ctx->tables; + while (table) { + if (strv_equals(table->name, name)) { + break; + } + table = table->next; + } + } + + if (!table) { + table = alloc(arena, initable_t); + + table->name = name; + + if (!ctx->tables) { + ctx->tables = table; + } + else { + ctx->tail->next = table; + } + + ctx->tail = table; + } + + istr_ignore_and_skip(in, '\n'); + while (!istr_is_finished(in)) { + switch (istr_peek(in)) { + case '\n': // fallthrough + case '\r': + return; + case '#': // fallthrough + case ';': + istr_ignore_and_skip(in, '\n'); + break; + default: + ini__add_value(arena, table, in, options); + break; + } + } +} + +void ini__parse(arena_t *arena, ini_t *ini, const iniopt_t *options) { + iniopt_t opts = ini__get_options(options); + + initable_t *root = alloc(arena, initable_t); + root->name = INI_ROOT; + ini->tables = root; + ini->tail = root; + + instream_t in = istr_init(ini->text); + + while (!istr_is_finished(&in)) { + istr_skip_whitespace(&in); + switch (istr_peek(&in)) { + case '[': + ini__add_table(arena, ini, &in, &opts); + break; + case '#': // fallthrough + case ';': + istr_ignore_and_skip(&in, '\n'); + break; + default: + ini__add_value(arena, ini->tables, &in, &opts); + break; + } + } +} + +// == JSON =========================================== + +bool json__parse_obj(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); + return json_parse_str(arena, strv(data), flags); +} + +json_t *json_parse_str(arena_t *arena, strview_t str, jsonflags_e flags) { + arena_t before = *arena; + + json_t *root = alloc(arena, json_t); + root->type = JSON_OBJECT; + + instream_t in = istr_init(str); + + if (!json__parse_obj(arena, &in, flags, &root->object)) { + // reset arena + *arena = before; + return NULL; + } + + return root; +} + +json_t *json_get(json_t *node, strview_t key) { + if (!node) return NULL; + + if (node->type != JSON_OBJECT) { + return NULL; + } + + node = node->object; + + while (node) { + if (strv_equals(node->key, key)) { + return node; + } + node = node->next; + } + + return NULL; +} + +void json__pretty_print_value(json_t *value, int indent, const json_pretty_opts_t *options); + +void json_pretty_print(json_t *root, const json_pretty_opts_t *options) { + json_pretty_opts_t default_options = { 0 }; + if (options) { + memmove(&default_options, options, sizeof(json_pretty_opts_t)); + } + + if (!os_handle_valid(default_options.custom_target)) { + default_options.custom_target = os_stdout(); + } + if (!default_options.use_custom_colours) { + os_log_colour_e default_col[JSON_PRETTY_COLOUR__COUNT] = { + LOG_COL_YELLOW, // JSON_PRETTY_COLOUR_KEY, + LOG_COL_CYAN, // JSON_PRETTY_COLOUR_STRING, + LOG_COL_BLUE, // JSON_PRETTY_COLOUR_NUM, + LOG_COL_DARK_GREY, // JSON_PRETTY_COLOUR_NULL, + LOG_COL_GREEN, // JSON_PRETTY_COLOUR_TRUE, + LOG_COL_RED, // JSON_PRETTY_COLOUR_FALSE, + }; + memmove(default_options.colours, default_col, sizeof(default_col)); + } + + json__pretty_print_value(root, 0, &default_options); + os_file_putc(default_options.custom_target, '\n'); +} + +///// json-private /////////////////////////////////// + +#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; + } + istr_rewind_n(in, 1); + return false; +} + +bool json__is_value_finished(instream_t *in) { + usize old_pos = istr_tell(in); + + istr_skip_whitespace(in); + switch(istr_peek(in)) { + case '}': // fallthrough + case ']': // fallthrough + case ',': + return true; + } + + in->cur = in->beg + old_pos; + return false; +} + +bool json__parse_null(instream_t *in) { + strview_t null_view = istr_get_view_len(in, 4); + bool is_valid = true; + + if (!strv_equals(null_view, strv("null"))) { + is_valid = false; + } + + if (!json__is_value_finished(in)) { + is_valid = false; + } + + return is_valid; +} + +bool json__parse_array(arena_t *arena, instream_t *in, jsonflags_e flags, json_t **out) { + json_t *head = NULL; + + if (!json__ensure('[')) { + goto fail; + } + + istr_skip_whitespace(in); + + // if it is an empty array + if (istr_peek(in) == ']') { + istr_skip(in, 1); + goto success; + } + + if (!json__parse_value(arena, in, flags, &head)) { + goto fail; + } + + json_t *cur = head; + + while (true) { + istr_skip_whitespace(in); + switch (istr_get(in)) { + case ']': + goto success; + case ',': + { + istr_skip_whitespace(in); + // trailing comma + if (istr_peek(in) == ']') { + if (flags & JSON_NO_TRAILING_COMMAS) { + goto fail; + } + else { + continue; + } + } + + json_t *next = NULL; + if (!json__parse_value(arena, in, flags, &next)) { + goto fail; + } + cur->next = next; + next->prev = cur; + cur = next; + break; + } + default: + istr_rewind_n(in, 1); + goto fail; + } + } + +success: + *out = head; + return true; +fail: + *out = NULL; + return false; +} + +bool json__parse_string(arena_t *arena, instream_t *in, strview_t *out) { + COLLA_UNUSED(arena); + *out = STRV_EMPTY; + + istr_skip_whitespace(in); + + if (!json__ensure('"')) { + goto fail; + } + + const char *from = in->cur; + + for (; !istr_is_finished(in) && *in->cur != '"'; ++in->cur) { + if (istr_peek(in) == '\\') { + ++in->cur; + } + } + + usize len = in->cur - from; + + *out = strv(from, len); + + if (!json__ensure('"')) { + goto fail; + } + + return true; +fail: + return false; +} + +bool json__parse_pair(arena_t *arena, instream_t *in, jsonflags_e flags, json_t **out) { + strview_t key = {0}; + if (!json__parse_string(arena, in, &key)) { + goto fail; + } + + // skip preamble + istr_skip_whitespace(in); + if (!json__ensure(':')) { + goto fail; + } + + if (!json__parse_value(arena, in, flags, out)) { + goto fail; + } + + (*out)->key = key; + return true; + +fail: + *out = NULL; + return false; +} + +bool json__parse_obj(arena_t *arena, instream_t *in, jsonflags_e flags, json_t **out) { + if (!json__ensure('{')) { + goto fail; + } + + istr_skip_whitespace(in); + + // if it is an empty object + if (istr_peek(in) == '}') { + istr_skip(in, 1); + *out = NULL; + return true; + } + + json_t *head = NULL; + if (!json__parse_pair(arena, in, flags, &head)) { + goto fail; + } + json_t *cur = head; + + while (true) { + istr_skip_whitespace(in); + switch (istr_get(in)) { + case '}': + goto success; + case ',': + { + istr_skip_whitespace(in); + // trailing commas + if (!(flags & JSON_NO_TRAILING_COMMAS) && istr_peek(in) == '}') { + goto success; + } + + json_t *next = NULL; + if (!json__parse_pair(arena, in, flags, &next)) { + goto fail; + } + cur->next = next; + next->prev = cur; + cur = next; + break; + } + default: + istr_rewind_n(in, 1); + goto fail; + } + } + +success: + *out = head; + return true; +fail: + *out = NULL; + return false; +} + +bool json__parse_value(arena_t *arena, instream_t *in, jsonflags_e flags, json_t **out) { + json_t *val = alloc(arena, json_t); + + istr_skip_whitespace(in); + + switch (istr_peek(in)) { + // object + case '{': + if (!json__parse_obj(arena, in, flags, &val->object)) { + goto fail; + } + val->type = JSON_OBJECT; + break; + // array + case '[': + if (!json__parse_array(arena, in, flags, &val->array)) { + goto fail; + } + val->type = JSON_ARRAY; + break; + // string + case '"': + if (!json__parse_string(arena, in, &val->string)) { + goto fail; + } + val->type = JSON_STRING; + break; + // boolean + case 't': // fallthrough + case 'f': + if (!istr_get_bool(in, &val->boolean)) { + goto fail; + } + val->type = JSON_BOOL; + break; + // null + case 'n': + if (!json__parse_null(in)) { + goto fail; + } + val->type = JSON_NULL; + break; + // comment + case '/': + err("TODO comments"); + break; + // number + default: + if (!istr_get_num(in, &val->number)) { + goto fail; + } + val->type = JSON_NUMBER; + break; + } + + *out = val; + return true; +fail: + *out = NULL; + return false; +} + +#undef json__ensure + +#define JSON_PRETTY_INDENT(ind) for (int i = 0; i < ind; ++i) os_file_puts(options->custom_target, strv(" ")) + +void json__pretty_print_value(json_t *value, int indent, const json_pretty_opts_t *options) { + switch (value->type) { + case JSON_NULL: + os_log_set_colour(options->colours[JSON_PRETTY_COLOUR_NULL]); + os_file_puts(options->custom_target, strv("null")); + os_log_set_colour(LOG_COL_RESET); + break; + case JSON_ARRAY: + os_file_puts(options->custom_target, strv("[\n")); + for_each (node, value->array) { + JSON_PRETTY_INDENT(indent + 1); + json__pretty_print_value(node, indent + 1, options); + if (node->next) { + os_file_putc(options->custom_target, ','); + } + os_file_putc(options->custom_target, '\n'); + } + JSON_PRETTY_INDENT(indent); + os_file_putc(options->custom_target, ']'); + break; + case JSON_STRING: + os_log_set_colour(options->colours[JSON_PRETTY_COLOUR_STRING]); + os_file_putc(options->custom_target, '\"'); + os_file_puts(options->custom_target, value->string); + os_file_putc(options->custom_target, '\"'); + os_log_set_colour(LOG_COL_RESET); + break; + case JSON_NUMBER: + { + os_log_set_colour(options->colours[JSON_PRETTY_COLOUR_NUM]); + u8 scratchbuf[256]; + arena_t scratch = arena_make(ARENA_STATIC, sizeof(scratchbuf), scratchbuf); + os_file_print( + scratch, + options->custom_target, + "%g", + value->number + ); + os_log_set_colour(LOG_COL_RESET); + break; + } + case JSON_BOOL: + os_log_set_colour(options->colours[value->boolean ? JSON_PRETTY_COLOUR_TRUE : JSON_PRETTY_COLOUR_FALSE]); + os_file_puts(options->custom_target, value->boolean ? strv("true") : strv("false")); + os_log_set_colour(LOG_COL_RESET); + break; + case JSON_OBJECT: + os_file_puts(options->custom_target, strv("{\n")); + for_each(node, value->object) { + JSON_PRETTY_INDENT(indent + 1); + os_log_set_colour(options->colours[JSON_PRETTY_COLOUR_KEY]); + os_file_putc(options->custom_target, '\"'); + os_file_puts(options->custom_target, node->key); + os_file_putc(options->custom_target, '\"'); + os_log_set_colour(LOG_COL_RESET); + + os_file_puts(options->custom_target, strv(": ")); + + json__pretty_print_value(node, indent + 1, options); + if (node->next) { + os_file_putc(options->custom_target, ','); + } + os_file_putc(options->custom_target, '\n'); + } + JSON_PRETTY_INDENT(indent); + os_file_putc(options->custom_target, '}'); + break; + } +} + +#undef JSON_PRETTY_INDENT + + +// == XML ============================================ + +xmltag_t *xml__parse_tag(arena_t *arena, instream_t *in); + +xml_t xml_parse(arena_t *arena, strview_t filename) { + str_t str = os_file_read_all_str(arena, filename); + return xml_parse_str(arena, strv(str)); +} + +xml_t xml_parse_str(arena_t *arena, strview_t xmlstr) { + xml_t out = { + .text = xmlstr, + .root = alloc(arena, xmltag_t), + }; + + instream_t in = istr_init(xmlstr); + + while (!istr_is_finished(&in)) { + xmltag_t *tag = xml__parse_tag(arena, &in); + + if (out.tail) out.tail->next = tag; + else out.root->child = tag; + + out.tail = tag; + } + + return out; +} + +xmltag_t *xml_get_tag(xmltag_t *parent, strview_t key, bool recursive) { + xmltag_t *t = parent ? parent->child : NULL; + while (t) { + if (strv_equals(key, t->key)) { + return t; + } + if (recursive && t->child) { + xmltag_t *out = xml_get_tag(t, key, recursive); + if (out) { + return out; + } + } + t = t->next; + } + return NULL; +} + +strview_t xml_get_attribute(xmltag_t *tag, strview_t key) { + xmlattr_t *a = tag ? tag->attributes : NULL; + while (a) { + if (strv_equals(key, a->key)) { + return a->value; + } + a = a->next; + } + return STRV_EMPTY; +} + +///// xml-private //////////////////////////////////// + +xmlattr_t *xml__parse_attr(arena_t *arena, instream_t *in) { + if (istr_peek(in) != ' ') { + return NULL; + } + + strview_t key = strv_trim(istr_get_view(in, '=')); + istr_skip(in, 1); // skip = + strview_t val = strv_trim(istr_get_view_either(in, strv("\">"))); + if (istr_peek(in) != '>') { + istr_skip(in, 1); // skip " + } + + if (strv_is_empty(key) || strv_is_empty(val)) { + warn("key or value empty"); + return NULL; + } + + xmlattr_t *attr = alloc(arena, xmlattr_t); + attr->key = key; + attr->value = val; + return attr; +} + +xmltag_t *xml__parse_tag(arena_t *arena, instream_t *in) { + istr_skip_whitespace(in); + + // we're either parsing the body, or we have finished the object + if (istr_peek(in) != '<' || istr_peek_next(in) == '/') { + return NULL; + } + + istr_skip(in, 1); // skip < + + // meta tag, we don't care about these + if (istr_peek(in) == '?') { + istr_ignore_and_skip(in, '\n'); + return NULL; + } + + xmltag_t *tag = alloc(arena, xmltag_t); + + tag->key = strv_trim(istr_get_view_either(in, strv(" >"))); + + xmlattr_t *attr = xml__parse_attr(arena, in); + while (attr) { + attr->next = tag->attributes; + tag->attributes = attr; + attr = xml__parse_attr(arena, in); + } + + // this tag does not have children, return + if (istr_peek(in) == '/') { + istr_skip(in, 2); // skip / and > + return tag; + } + + istr_skip(in, 1); // skip > + + xmltag_t *child = xml__parse_tag(arena, in); + while (child) { + if (tag->tail) { + tag->tail->next = child; + tag->tail = child; + } + else { + tag->child = tag->tail = child; + } + child = xml__parse_tag(arena, in); + } + + // parse content + istr_skip_whitespace(in); + tag->content = istr_get_view(in, '<'); + + // closing tag + istr_skip(in, 2); // skip < and / + strview_t closing = strv_trim(istr_get_view(in, '>')); + if (!strv_equals(tag->key, closing)) { + warn("opening and closing tags are different!: (%v) != (%v)", tag->key, closing); + } + istr_skip(in, 1); // skip > + return tag; +} + +// == HTML =========================================== + +htmltag_t *html__parse_tag(arena_t *arena, instream_t *in); + +html_t html_parse(arena_t *arena, strview_t filename) { + str_t str = os_file_read_all_str(arena, filename); + return html_parse_str(arena, strv(str)); +} + +html_t html_parse_str(arena_t *arena, strview_t str) { + html_t out = { + .text = str, + .root = alloc(arena, xmltag_t), + }; + + instream_t in = istr_init(str); + + while (!istr_is_finished(&in)) { + htmltag_t *tag = html__parse_tag(arena, &in); + + if (out.tail) out.tail->next = tag; + else out.root->children = tag; + + out.tail = tag; + } + + return out; +} + +htmltag_t *html__get_tag_internal(htmltag_t *parent, str_t key, bool recursive) { + htmltag_t *t = parent ? parent->children : NULL; + while (t) { + if (str_equals(key, t->key)) { + return t; + } + if (recursive && t->children) { + htmltag_t *out = html__get_tag_internal(t, key, recursive); + if (out) { + return out; + } + } + t = t->next; + } + return NULL; +} + +htmltag_t *html_get_tag(htmltag_t *parent, strview_t key, bool recursive) { + u8 tmpbuf[KB(1)]; + arena_t scratch = arena_make(ARENA_STATIC, sizeof(tmpbuf), tmpbuf); + str_t upper = strv_to_upper(&scratch, key); + return html__get_tag_internal(parent, upper, recursive); +} + +strview_t html_get_attribute(htmltag_t *tag, strview_t key) { + xmlattr_t *a = tag ? tag->attributes : NULL; + while (a) { + if (strv_equals(key, a->key)) { + return a->value; + } + a = a->next; + } + return STRV_EMPTY; +} + +///// html-private /////////////////////////////////// + +/* + +special rules: +

tag does not need to be closed when followed by + address, article, aside, blockquote, details, dialog, div, + dl, fieldset, figcaption, figure, footer, form, h1, h2, h3, + h4, h5, h6, header, hgroup, hr, main, menu, nav, ol, p, pre, + search, section, table, or ul +*/ + +strview_t html_closing_p_tags[] = { + cstrv("ADDRESS"), + cstrv("ARTICLE"), + cstrv("ASIDE"), + cstrv("BLOCKQUOTE"), + cstrv("DETAILS"), + cstrv("DIALOG"), + cstrv("DIV"), + cstrv("DL"), + cstrv("FIELDSET"), + cstrv("FIGCAPTION"), + cstrv("FIGURE"), + cstrv("FOOTER"), + cstrv("FORM"), + cstrv("H1"), + cstrv("H2"), + cstrv("H3"), + cstrv("H4"), + cstrv("H5"), + cstrv("H6"), + cstrv("HEADER"), + cstrv("HGROUP"), + cstrv("HR"), + cstrv("MAIN"), + cstrv("MENU"), + cstrv("NAV"), + cstrv("OL"), + cstrv("P"), + cstrv("PRE"), + cstrv("SEARCH"), + cstrv("SECTION"), + cstrv("TABLE"), + cstrv("UL"), +}; + +bool html__closes_p_tag(strview_t tag) { + for (int i = 0; i < arrlen(html_closing_p_tags); ++i) { + if (strv_equals(html_closing_p_tags[i], tag)) { + return true; + } + } + + return false; +} + +htmltag_t *html__parse_tag(arena_t *arena, instream_t *in) { + istr_skip_whitespace(in); + + // we're either parsing the body, or we have finished the object + if (istr_peek(in) != '<' || istr_peek_next(in) == '/') { + return NULL; + } + + istr_skip(in, 1); // skip < + + // meta tag, we don't care about these + if (istr_peek(in) == '?') { + istr_ignore_and_skip(in, '\n'); + return NULL; + } + + htmltag_t *tag = alloc(arena, htmltag_t); + + tag->key = strv_to_upper( + arena, + strv_trim(istr_get_view_either(in, strv(" >"))) + ); + + xmlattr_t *attr = xml__parse_attr(arena, in); + while (attr) { + attr->next = tag->attributes; + tag->attributes = attr; + attr = xml__parse_attr(arena, in); + } + + // this tag does not have children, return + if (istr_peek(in) == '/') { + istr_skip(in, 2); // skip / and > + return tag; + } + + istr_skip(in, 1); // skip > + + bool is_p_tag = strv_equals(strv(tag->key), strv("P")); + while (!istr_is_finished(in)) { + istr_skip_whitespace(in); + strview_t content = strv_trim(istr_get_view(in, '<')); + + // skip < + istr_skip(in, 1); + + bool is_closing = istr_peek(in) == '/'; + if (is_closing) { + istr_skip(in, 1); + } + + + arena_t scratch = *arena; + instream_t scratch_in = *in; + str_t next_tag = strv_to_upper(&scratch, strv_trim(istr_get_view_either(&scratch_in, strv(" >")))); + + // rewind < + istr_rewind_n(in, 1); + + // if we don't have children, it means this is the only content + // otherwise, it means this is content in-between other tags, + // if so: create an empty tag with the content and add it as a child + if (!strv_is_empty(content)) { + if (tag->children == NULL) { + tag->content = content; + } + else { + htmltag_t *empty = alloc(arena, htmltag_t); + empty->content = content; + olist_push(tag->children, tag->tail, empty); + } + } + + bool close_tag = + (is_closing && str_equals(tag->key, next_tag)) || + (is_p_tag && html__closes_p_tag(strv(next_tag))); + + if (close_tag) { + if (is_closing) { + istr_skip(in, 2 + next_tag.len); + } + break; + } + + htmltag_t *child = html__parse_tag(arena, in); + if (tag->tail) { + (tag->tail)->next = (child); + (tag->tail) = (child); + } + else { + (tag->children) = (tag->tail) = (child); + } + } + + return tag; +} + +const char *http_get_method_string(http_method_e method) { + switch (method) { + case HTTP_GET: return "GET"; + case HTTP_POST: return "POST"; + case HTTP_HEAD: return "HEAD"; + case HTTP_PUT: return "PUT"; + case HTTP_DELETE: return "DELETE"; + } + return "GET"; +} + +const char *http_get_status_string(int status) { + switch (status) { + case 200: return "OK"; + case 201: return "CREATED"; + case 202: return "ACCEPTED"; + case 204: return "NO CONTENT"; + case 205: return "RESET CONTENT"; + case 206: return "PARTIAL CONTENT"; + + case 300: return "MULTIPLE CHOICES"; + case 301: return "MOVED PERMANENTLY"; + case 302: return "MOVED TEMPORARILY"; + case 304: return "NOT MODIFIED"; + + case 400: return "BAD REQUEST"; + case 401: return "UNAUTHORIZED"; + case 403: return "FORBIDDEN"; + case 404: return "NOT FOUND"; + case 407: return "RANGE NOT SATISFIABLE"; + + case 500: return "INTERNAL SERVER ERROR"; + case 501: return "NOT IMPLEMENTED"; + case 502: return "BAD GATEWAY"; + case 503: return "SERVICE NOT AVAILABLE"; + case 504: return "GATEWAY TIMEOUT"; + case 505: return "VERSION NOT SUPPORTED"; + } + + return "UNKNOWN"; +} + +http_header_t *http__parse_headers_instream(arena_t *arena, instream_t *in) { + http_header_t *head = NULL; + + while (!istr_is_finished(in)) { + strview_t line = istr_get_line(in); + + // end of headers + if (strv_is_empty(line)) { + break; + } + + usize pos = strv_find(line, ':', 0); + if (pos != STR_NONE) { + http_header_t *new_head = alloc(arena, http_header_t); + + new_head->key = strv_sub(line, 0, pos); + new_head->value = strv_sub(line, pos + 2, SIZE_MAX); + + list_push(head, new_head); + } + } + + return head; +} + +http_header_t *http_parse_headers(arena_t *arena, strview_t header_string) { + instream_t in = istr_init(header_string); + return http__parse_headers_instream(arena, &in); +} + +http_req_t http_parse_req(arena_t *arena, strview_t request) { + http_req_t req = {0}; + instream_t in = istr_init(request); + + strview_t method = strv_trim(istr_get_view(&in, '/')); + istr_skip(&in, 1); // skip / + req.url = strv_trim(istr_get_view(&in, ' ')); + strview_t http = strv_trim(istr_get_view(&in, '\n')); + + istr_skip(&in, 1); // skip \n + + req.headers = http__parse_headers_instream(arena, &in); + + req.body = strv_trim(istr_get_view_len(&in, SIZE_MAX)); + + strview_t methods[5] = { strv("GET"), strv("POST"), strv("HEAD"), strv("PUT"), strv("DELETE") }; + usize methods_count = arrlen(methods); + + for (usize i = 0; i < methods_count; ++i) { + if (strv_equals(method, methods[i])) { + req.method = (http_method_e)i; + break; + } + } + + in = istr_init(http); + istr_ignore_and_skip(&in, '/'); // skip HTTP/ + istr_get_u8(&in, &req.version.major); + istr_skip(&in, 1); // skip . + istr_get_u8(&in, &req.version.minor); + + return req; +} + +http_res_t http_parse_res(arena_t *arena, strview_t response) { + http_res_t res = {0}; + instream_t in = istr_init(response); + + strview_t http = istr_get_view_len(&in, 4); + if (!strv_equals(http, strv("HTTP"))) { + err("response doesn't start with 'HTTP', instead with %v", http); + return (http_res_t){0}; + } + istr_skip(&in, 1); // skip / + istr_get_u8(&in, &res.version.major); + istr_skip(&in, 1); // skip . + istr_get_u8(&in, &res.version.minor); + istr_get_i32(&in, (i32*)&res.status_code); + + istr_ignore(&in, '\n'); + istr_skip(&in, 1); // skip \n + + res.headers = http__parse_headers_instream(arena, &in); + + strview_t encoding = http_get_header(res.headers, strv("transfer-encoding")); + if (!strv_equals(encoding, strv("chunked"))) { + res.body = istr_get_view_len(&in, SIZE_MAX); + } + else { + err("chunked encoding not implemented yet! body ignored"); + } + + return res; +} + +str_t http_req_to_str(arena_t *arena, http_req_t *req) { + outstream_t out = ostr_init(arena); + + const char *method = NULL; + switch (req->method) { + case HTTP_GET: method = "GET"; break; + case HTTP_POST: method = "POST"; break; + case HTTP_HEAD: method = "HEAD"; break; + case HTTP_PUT: method = "PUT"; break; + case HTTP_DELETE: method = "DELETE"; break; + default: err("unrecognised method: %d", method); return STR_EMPTY; + } + + ostr_print( + &out, + "%s /%v HTTP/%hhu.%hhu\r\n", + method, req->url, req->version.major, req->version.minor + ); + + http_header_t *h = req->headers; + while (h) { + ostr_print(&out, "%v: %v\r\n", h->key, h->value); + h = h->next; + } + + ostr_puts(&out, strv("\r\n")); + ostr_puts(&out, req->body); + + return ostr_to_str(&out); +} + +str_t http_res_to_str(arena_t *arena, http_res_t *res) { + outstream_t out = ostr_init(arena); + + ostr_print( + &out, + "HTTP/%hhu.%hhu %d %s\r\n", + res->version.major, + res->version.minor, + res->status_code, + http_get_status_string(res->status_code) + ); + ostr_puts(&out, strv("\r\n")); + ostr_puts(&out, res->body); + + return ostr_to_str(&out); +} + +bool http_has_header(http_header_t *headers, strview_t key) { + for_each(h, headers) { + if (strv_equals(h->key, key)) { + return true; + } + } + return false; +} + +void http_set_header(http_header_t *headers, strview_t key, strview_t value) { + http_header_t *h = headers; + while (h) { + if (strv_equals(h->key, key)) { + h->value = value; + break; + } + h = h->next; + } +} + +strview_t http_get_header(http_header_t *headers, strview_t key) { + http_header_t *h = headers; + while (h) { + if (strv_equals(h->key, key)) { + return h->value; + } + h = h->next; + } + return STRV_EMPTY; +} + +str_t http_make_url_safe(arena_t *arena, strview_t string) { + strview_t chars = strv(" !\"#$%%&'()*+,/:;=?@[]"); + usize final_len = string.len; + + // find final string length first + for (usize i = 0; i < string.len; ++i) { + if (strv_contains(chars, string.buf[i])) { + final_len += 2; + } + } + + str_t out = { + .buf = alloc(arena, char, final_len + 1), + .len = final_len + }; + usize cur = 0; + // substitute characters + for (usize i = 0; i < string.len; ++i) { + if (strv_contains(chars, string.buf[i])) { + fmt_buffer(out.buf + cur, 4, "%%%X", string.buf[i]); + cur += 3; + } + else { + out.buf[cur++] = string.buf[i]; + } + } + + return out; +} + +str_t http_decode_url_safe(arena_t *arena, strview_t string) { + usize final_len = string.len; + + for (usize i = 0; i < string.len; ++i) { + if (string.buf[i] == '%') { + final_len -= 2; + i += 2; + } + } + + colla_assert(final_len <= string.len); + + str_t out = { + .buf = alloc(arena, char, final_len + 1), + .len = final_len + }; + + usize k = 0; + + for (usize i = 0; i < string.len; ++i) { + if (string.buf[i] == '%') { + // skip % + ++i; + + unsigned int ch = 0; + int result = sscanf(string.buf + i, "%02X", &ch); + if (result != 1 || ch > UINT8_MAX) { + err("malformed url at %zu (%s)", i, string.buf + i); + return STR_EMPTY; + } + out.buf[k++] = (char)ch; + + // skip first char of hex + ++i; + } + else { + out.buf[k++] = string.buf[i]; + } + } + + return out; +} + +http_url_t http_split_url(strview_t url) { + http_url_t out = {0}; + + if (strv_starts_with_view(url, strv("https://"))) { + url = strv_remove_prefix(url, 8); + } + else if (strv_starts_with_view(url, strv("http://"))) { + url = strv_remove_prefix(url, 7); + } + + out.host = strv_sub(url, 0, strv_find(url, '/', 0)); + out.uri = strv_sub(url, out.host.len, SIZE_MAX); + + return out; +} + +#if !COLLA_NO_NET +// WEBSOCKETS /////////////////////// + +#define WEBSOCKET_MAGIC "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" +#define WEBSOCKET_HTTP_KEY "Sec-WebSocket-Key" + +bool websocket_init(arena_t scratch, socket_t socket, strview_t key) { + str_t full_key = str_fmt(&scratch, "%v" WEBSOCKET_MAGIC, key); + + 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 = base64_encode(&scratch, (buffer_t){ (u8 *)sha1_data.digest, sizeof(sha1_data.digest) }); + + str_t response = str_fmt( + &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 + ); + + int result = sk_send(socket, response.buf, (int)response.len); + return result != SOCKET_ERROR; +} + +buffer_t websocket_encode(arena_t *arena, strview_t message) { + int extra = 6; + if (message.len > UINT16_MAX) extra += sizeof(u64); + else if (message.len > UINT8_MAX) extra += sizeof(u16); + u8 *bytes = alloc(arena, u8, message.len + extra); + bytes[0] = 0b10000001; + bytes[1] = 0b10000000; + int offset = 2; + if (message.len > UINT16_MAX) { + bytes[1] |= 0b01111111; + u64 len = htonll(message.len); + memmove(bytes + 2, &len, sizeof(len)); + offset += sizeof(u64); + } + else if (message.len > UINT8_MAX) { + bytes[1] |= 0b01111110; + u16 len = htons((u16)message.len); + memmove(bytes + 2, &len, sizeof(len)); + offset += sizeof(u16); + } + else { + bytes[1] |= (u8)message.len; + } + + u32 mask = 0; + memmove(bytes + offset, &mask, sizeof(mask)); + offset += sizeof(mask); + memmove(bytes + offset, message.buf, message.len); + + return (buffer_t){ bytes, message.len + extra }; +} + +str_t websocket_decode(arena_t *arena, buffer_t message) { + str_t out = STR_EMPTY; + u8 *bytes = message.data; + + bool mask = bytes[1] & 0b10000000; + int offset = 2; + u64 msglen = bytes[1] & 0b01111111; + + // 16bit msg len + if (msglen == 126) { + u16 be_len = 0; + memmove(&be_len, bytes + 2, sizeof(be_len)); + msglen = ntohs(be_len); + offset += sizeof(u16); + } + // 64bit msg len + else if (msglen == 127) { + u64 be_len = 0; + memmove(&be_len, bytes + 2, sizeof(be_len)); + msglen = ntohll(be_len); + offset += sizeof(u64); + } + + if (msglen == 0) { + warn("message length = 0"); + } + else if (mask) { + u8 *decoded = alloc(arena, u8, msglen + 1); + u8 masks[4] = {0}; + memmove(masks, bytes + offset, sizeof(masks)); + offset += 4; + + for (u64 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; +} + +#endif + +// SHA 1 //////////////////////////// + +sha1_t sha1_init(void) { + return (sha1_t) { + .digest = { + 0x67452301, + 0xEFCDAB89, + 0x98BADCFE, + 0x10325476, + 0xC3D2E1F0, + }, + }; +} + +u32 sha1__left_rotate(u32 value, u32 count) { + return (value << count) ^ (value >> (32 - count)); +} + +void sha1__process_block(sha1_t *ctx) { + u32 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); + } + + u32 a = ctx->digest[0]; + u32 b = ctx->digest[1]; + u32 c = ctx->digest[2]; + u32 d = ctx->digest[3]; + u32 e = ctx->digest[4]; + + for (usize i = 0; i < 80; ++i) { + u32 f = 0; + u32 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; + } + u32 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, u8 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 u8 *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)); + + sha1_result_t result = {0}; + memcpy(result.digest, ctx->digest, sizeof(result.digest)); + return result; +} + +str_t sha1_str(arena_t *arena, sha1_t *ctx, const void *buf, usize len) { + sha1_result_t result = sha1(ctx, buf, len); + return str_fmt(arena, "%08x%08x%08x%08x%08x", result.digest[0], result.digest[1], result.digest[2], result.digest[3], result.digest[4]); +} + +// BASE 64 ////////////////////////// + +unsigned char b64__encoding_table[] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', + 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', + '4', '5', '6', '7', '8', '9', '+', '/' +}; + +u8 b64__decoding_table[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, + 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, + 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, + 44, 45, 46, 47, 48, 49, 50, 51, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +}; + +buffer_t base64_encode(arena_t *arena, buffer_t buffer) { + usize outlen = ((buffer.len + 2) / 3) * 4; + u8 *out = alloc(arena, u8, outlen); + + for (usize i = 0, j = 0; i < buffer.len;) { + u32 a = i < buffer.len ? buffer.data[i++] : 0; + u32 b = i < buffer.len ? buffer.data[i++] : 0; + u32 c = i < buffer.len ? buffer.data[i++] : 0; + + u32 triple = (a << 16) | (b << 8) | c; + + out[j++] = b64__encoding_table[(triple >> 18) & 0x3F]; + out[j++] = b64__encoding_table[(triple >> 12) & 0x3F]; + out[j++] = b64__encoding_table[(triple >> 6) & 0x3F]; + out[j++] = b64__encoding_table[(triple >> 0) & 0x3F]; + } + + usize mod = buffer.len % 3; + if (mod) { + mod = 3 - mod; + for (usize i = 0; i < mod; ++i) { + out[outlen - 1 - i] = '='; + } + } + + return (buffer_t){ + .data = out, + .len = outlen + }; +} + +buffer_t base64_decode(arena_t *arena, buffer_t buffer) { + u8 *out = arena->cur; + usize start = arena_tell(arena); + + for (usize i = 0; i < buffer.len; i += 4) { + u8 a = b64__decoding_table[buffer.data[i + 0]]; + u8 b = b64__decoding_table[buffer.data[i + 1]]; + u8 c = b64__decoding_table[buffer.data[i + 2]]; + u8 d = b64__decoding_table[buffer.data[i + 3]]; + + u32 triple = + ((u32)a << 18) | + ((u32)b << 12) | + ((u32)c << 6) | + ((u32)d); + + u8 *bytes = alloc(arena, u8, 3); + + bytes[0] = (triple >> 16) & 0xFF; + bytes[1] = (triple >> 8) & 0xFF; + bytes[2] = (triple >> 0) & 0xFF; + } + + usize spaces_count = 0; + for (isize i = buffer.len - 1; i >= 0; --i) { + if (buffer.data[i] == '=') { + spaces_count++; + } + else { + break; + } + } + + usize outlen = arena_tell(arena) - start; + + return (buffer_t){ + .data = out, + .len = outlen - spaces_count, + }; +} + +strview_t pretty__colour[LOG_COL__COUNT] = { + [LOG_COL_BLACK] = cstrv("black"), + [LOG_COL_BLUE] = cstrv("blue"), + [LOG_COL_GREEN] = cstrv("green"), + [LOG_COL_CYAN] = cstrv("cyan"), + [LOG_COL_RED] = cstrv("red"), + [LOG_COL_MAGENTA] = cstrv("magenta"), + [LOG_COL_YELLOW] = cstrv("yellow"), + [LOG_COL_GREY] = cstrv("grey"), + + [LOG_COL_DARK_GREY] = cstrv("dark_grey"), + [LOG_COL_WHITE] = cstrv("white"), + [LOG_COL_LIGHT_BLUE] = cstrv("light_blue"), + [LOG_COL_LIGHT_GREEN] = cstrv("light_green"), + [LOG_COL_LIGHT_CYAN] = cstrv("light_cyan"), + [LOG_COL_LIGHT_RED] = cstrv("light_red"), + [LOG_COL_LIGHT_MAGENTA] = cstrv("light_magenta"), + [LOG_COL_LIGHT_YELLOW] = cstrv("light_yellow"), + + [LOG_COL_RESET] = cstrv("/"), +}; + +strview_t pretty_log_to_colour(os_log_colour_e colour) { + return pretty__colour[colour]; +} + +void pretty_print(arena_t scratch, const char *fmt, ...) { + va_list args; + va_start(args, fmt); + pretty_printv(scratch, fmt, args); + va_end(args); +} + +void pretty_printv(arena_t scratch, const char *fmt, va_list args) { + va_list tmp_args; + va_copy(tmp_args, args); + int len = fmt_bufferv(NULL, 0, fmt, tmp_args); + va_end(tmp_args); + + char *buf = alloc(&scratch, char, len + 1); + + fmt_bufferv(buf, len + 1, fmt, args); + + oshandle_t out = os_stdout(); + instream_t in = istr_init(strv(buf, len)); + while (!istr_is_finished(&in)) { + strview_t part = istr_get_view(&in, '<'); + bool has_escape = strv_ends_with(part, '\\'); + + os_file_write(out, part.buf, part.len - has_escape); + istr_skip(&in, 1); + + if (has_escape) { + os_file_putc(out, istr_prev(&in)); + continue; + } + + strview_t tag = istr_get_view(&in, '>'); + + for (usize i = 0; i < arrlen(pretty__colour); ++i) { + if (strv_equals(tag, pretty__colour[i])) { + os_log_set_colour(i); + break; + } + } + + istr_skip(&in, 1); + } +} + +str_t pretty_print_get_string(arena_t *arena, const char *fmt, ...) { + va_list args; + va_start(args, fmt); + str_t out = pretty_print_get_stringv(arena, fmt, args); + va_end(args); + return out; +} + +str_t pretty_print_get_stringv(arena_t *arena, const char *fmt, va_list args) { + va_list tmp_args; + va_copy(tmp_args, args); + int len = fmt_bufferv(NULL, 0, fmt, tmp_args); + va_end(tmp_args); + + char *buf = alloc(arena, char, len + 1); + + fmt_bufferv(buf, len + 1, fmt, args); + + outstream_t out = ostr_init(arena); + + instream_t in = istr_init(strv(buf, len)); + 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; + } + + ostr_puts(&out, part); + istr_skip(&in, 1); + + if (has_escape) { + ostr_putc(&out, '<'); + continue; + } + + istr_get_view(&in, '>'); + + istr_skip(&in, 1); + } + + return ostr_to_str(&out); +} diff --git a/colla.h b/colla.h new file mode 100644 index 0000000..1b22aff --- /dev/null +++ b/colla.h @@ -0,0 +1,1274 @@ +#ifndef COLLA_HEADER +#define COLLA_HEADER + +#include +#include +#include + +// CORE MODULES ///////////////////////////////// + +typedef enum { + COLLA_CORE = 0, + COLLA_OS = 1 << 0, + COLLA_NET = 1 << 1, + COLLA_ALL = 0xff, +} colla_modules_e; + +void colla_init(colla_modules_e modules); +void colla_cleanup(void); + +///////////////////////////////////////////////// + +// OS AND COMPILER MACROS /////////////////////// + +#if defined(_DEBUG) + #define COLLA_DEBUG 1 + #define COLLA_RELEASE 0 +#else + #define COLLA_DEBUG 0 + #define COLLA_RELEASE 1 +#endif + +#if defined(_WIN32) + #define COLLA_WIN 1 + #define COLLA_OSX 0 + #define COLLA_LIN 0 + #define COLLA_EMC 0 +#elif defined(__EMSCRIPTEN__) + #define COLLA_WIN 0 + #define COLLA_OSX 0 + #define COLLA_LIN 0 + #define COLLA_EMC 1 +#elif defined(__linux__) + #define COLLA_WIN 0 + #define COLLA_OSX 0 + #define COLLA_LIN 1 + #define COLLA_EMC 0 +#elif defined(__APPLE__) + #define COLLA_WIN 0 + #define COLLA_OSX 1 + #define COLLA_LIN 0 + #define COLLA_EMC 0 +#endif + +#if defined(__COSMOPOLITAN__) + #define COLLA_COSMO 1 +#else + #define COLLA_COSMO 0 +#endif + +#define COLLA_POSIX (COLLA_OSX || COLLA_LIN || COLLA_COSMO) + +#if defined(__clang__) + #define COLLA_CLANG 1 + #define COLLA_MSVC 0 + #define COLLA_TCC 0 + #define COLLA_GCC 0 +#elif defined(_MSC_VER) + #define COLLA_CLANG 0 + #define COLLA_MSVC 1 + #define COLLA_TCC 0 + #define COLLA_GCC 0 +#elif defined(__TINYC__) + #define COLLA_CLANG 0 + #define COLLA_MSVC 0 + #define COLLA_TCC 1 + #define COLLA_GCC 0 +#elif defined(__GNUC__) + #define COLLA_CLANG 0 + #define COLLA_MSVC 0 + #define COLLA_TCC 0 + #define COLLA_GCC 1 +#endif + +#if COLLA_CLANG + #define COLLA_CMT_LIB 0 +#elif COLLA_MSVC + #define COLLA_CMT_LIB 1 +#elif COLLA_TCC + #define COLLA_CMT_LIB 1 +#elif COLLA_GCC + #define COLLA_CMT_LIB 0 +#endif + +#if COLLA_TCC + #define alignof __alignof__ +#endif + +#if COLLA_WIN + #undef NOMINMAX + #undef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN + #define NOMINMAX + + #ifdef UNICODE + #define COLLA_UNICODE 1 + #else + #define COLLA_UNICODE 0 + #endif + +#endif + +///////////////////////////////////////////////// + +// USEFUL MACROS //////////////////////////////// + +#define arrlen(a) (sizeof(a) / sizeof((a)[0])) +#define COLLA_UNUSED(v) (void)(v) + +#define COLLA__STRINGIFY(x) #x +#define COLLA_STRINGIFY(x) COLLA__STRINGIFY(x) + +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#define MAX(a, b) ((a) > (b) ? (a) : (b)) + +#define KB(n) (((u64)n) << 10) +#define MB(n) (((u64)n) << 20) +#define GB(n) (((u64)n) << 30) +#define TB(n) (((u64)n) << 40) + +#if COLLA_DEBUG + #define colla__assert(file, line, cond, ...) do { if (!(cond)) fatal(file ":" line " ASSERT FAILED: (" COLLA__STRINGIFY(cond) ") " __VA_ARGS__); } while (0) + #define colla_assert(...) colla__assert(__FILE__, COLLA__STRINGIFY(__LINE__), __VA_ARGS__) +#else + #define colla_assert(...) (void)0 +#endif + +///////////////////////////////////////////////// + +// BASIC TYPES ////////////////////////////////// + +#if COLLA_WIN && COLLA_UNICODE + typedef wchar_t TCHAR; +#else + typedef char TCHAR; +#endif + +typedef unsigned char uchar; +typedef unsigned short ushort; +typedef unsigned int uint; + +typedef uint8_t u8; +typedef uint16_t u16; +typedef uint32_t u32; +typedef uint64_t u64; + +typedef int8_t i8; +typedef int16_t i16; +typedef int32_t i32; +typedef int64_t i64; + +typedef size_t usize; +typedef ptrdiff_t isize; + +typedef uintptr_t uptr; +typedef intptr_t iptr; + +typedef struct { + u8 *data; + usize len; +} buffer_t; + +typedef struct arena_t arena_t; + +///////////////////////////////////////////////// + +// LINKED LISTS ///////////////////////////////// + +// singly linked list +#define list_push(list, item) ((item)->next=(list), (list)=(item)) +#define list_pop(list) ((list) = (list) ? (list)->next : NULL) + +// double linked list +#define dlist_push(list, item) do { \ + if (item) (item)->next = (list); \ + if (list) (list)->prev = (item); \ + (list) = (item); \ + } while (0) + +#define dlist_pop(list, item) do { \ + if (!(item)) break; \ + if ((item)->prev) (item)->prev->next = (item)->next; \ + if ((item)->next) (item)->next->prev = (item)->prev; \ + if ((item) == (list)) (list) = (item)->next; \ + } while (0) + +// ordered linked list + +#define olist_push(head, tail, item) do { \ + if (tail) { \ + (tail)->next = (item); \ + (tail) = (item); \ + } \ + else { \ + (head) = (tail) = (item); \ + } \ + } while (0) + +#define for_each(it, list) for (typeof(list) it = list; it; it = it->next) + +///////////////////////////////////////////////// + +// FORMATTING /////////////////////////////////// + +int fmt_print(const char *fmt, ...); +int fmt_printv(const char *fmt, va_list args); +int fmt_buffer(char *buf, usize len, const char *fmt, ...); +int fmt_bufferv(char *buf, usize len, const char *fmt, va_list args); + +///////////////////////////////////////////////// + + +/* +dynamic chunked array which uses an arena to allocate, +the structure needs to follow this exact format, if you want +you can use the macro darr_define(struct_name, item_type) instead: + +//////////////////////////////////// + +typedef struct arr_t arr_t; +struct arr_t { + int *items; + usize block_size; + usize count; + arr_t *next; + arr_t *head; +}; +// equivalent to + +darr_define(arr_t, int); + +//////////////////////////////////// + +by default a chunk is 64 items long, you can change this default +by modifying the arr.block_size value before adding to the array, +or by defining DARRAY_DEFAULT_BLOCK_SIZE + +usage example: + +//////////////////////////////////// + +darr_define(arr_t, int); + +arr_t *arr = NULL; + +for (int i = 0; i < 100; ++i) { + darr_push(&arena, arr, i); +} + +for_each (chunk, arr) { + for (int i = 0; i < chunk->count; ++i) { + info("%d -> %d", i, chunk->items[i]); + } +} +*/ + +#define DARRAY_DEFAULT_BLOCK_SIZE (64) + +#define darr_define(struct_name, item_type) typedef struct struct_name struct_name; \ + struct struct_name { \ + item_type *items; \ + usize block_size; \ + usize count; \ + struct_name *next; \ + struct_name *head; \ + } + +#define darr__alloc_first(arena, arr) do { \ + (arr) = (arr) ? (arr) : alloc(arena, typeof(*arr)); \ + (arr)->head = (arr)->head ? (arr)->head : (arr); \ + (arr)->block_size = (arr)->block_size ? (arr)->block_size : DARRAY_DEFAULT_BLOCK_SIZE; \ + (arr)->items = alloc(arena, typeof(*arr->items), arr->block_size); \ + colla_assert((arr)->count == 0); \ + } while (0) +#define darr__alloc_block(arena, arr) do { \ + typeof(arr) newarr = alloc(arena, typeof(*arr)); \ + newarr->block_size = arr->block_size; \ + newarr->items = alloc(arena, typeof(*arr->items), arr->block_size); \ + newarr->head = arr->head; \ + arr->next = newarr; \ + arr = newarr; \ + } while (0) + +#define darr_push(arena, arr, item) do { \ + if (!(arr) || (arr)->items == NULL) darr__alloc_first(arena, arr); \ + if ((arr)->count >= (arr)->block_size) darr__alloc_block(arena, arr); \ + (arr)->items[(arr)->count++] = (item); \ + } while (0) + +// STRING TYPES ///////////////////////////////// + +#define STR_NONE SIZE_MAX +#define STR_END SIZE_MAX + +typedef struct str_t str_t; +struct str_t { + char *buf; + usize len; +}; + +typedef struct str16_t str16_t; +struct str16_t { + u16 *buf; + usize len; +}; + +#if COLLA_UNICODE +typedef str16_t tstr_t; +#else +typedef str_t tstr_t; +#endif + +typedef struct strview_t strview_t; +struct strview_t { + const char *buf; + usize len; +}; + +darr_define(str_list_t, str_t); +darr_define(strv_list_t, strview_t); + +// ALLOCATED STRING ///////////////////////////// + +#define str__1(arena, x) \ + _Generic((x), \ + const char *: str_init, \ + char *: str_init, \ + strview_t: str_init_view \ + )(arena, x) + +#define str__2(arena, cstr, clen) str_init_len(arena, cstr, clen) +#define str__impl(_1, _2, n, ...) str__##n + +// either: +// arena_t arena, [const] char *cstr, [usize len] +// arena_t arena, strview_t view +#define str(arena, ...) str__impl(__VA_ARGS__, 2, 1, 0)(arena, __VA_ARGS__) + +#define STR_EMPTY (str_t){0} + +str_t str_init(arena_t *arena, const char *buf); +str_t str_init_len(arena_t *arena, const char *buf, usize len); +str_t str_init_view(arena_t *arena, strview_t view); +str_t str_fmt(arena_t *arena, const char *fmt, ...); +str_t str_fmtv(arena_t *arena, const char *fmt, va_list args); + +tstr_t tstr_init(TCHAR *str, usize optional_len); +str16_t str16_init(u16 *str, usize optional_len); + +str_t str_from_str16(arena_t *arena, str16_t src); +str_t str_from_tstr(arena_t *arena, tstr_t src); +str16_t str16_from_str(arena_t *arena, str_t src); + +bool str_equals(str_t a, str_t b); +int str_compare(str_t a, str_t b); + +str_t str_dup(arena_t *arena, str_t src); +str_t str_cat(arena_t *arena, str_t a, str_t b); +bool str_is_empty(str_t ctx); + +void str_lower(str_t *src); +void str_upper(str_t *src); + +void str_replace(str_t *ctx, char from, char to); +// if len == SIZE_MAX, copies until end +strview_t str_sub(str_t ctx, usize from, usize to); + +// STRING VIEW ////////////////////////////////// + +// these macros might be THE worst code ever written, but they work ig +// detects if you're trying to create a string view from either: +// - a str_t -> calls strv_init_str +// - a string literal -> calls strv_init_len with comptime size +// - a c string -> calls strv_init with runtime size + +#define STRV_EMPTY (strview_t){0} + +// needed for strv__init_literal _Generic implementation, it's never actually called +strview_t strv__ignore(str_t s, size_t l); + +#define strv__check(x, ...) ((#x)[0] == '"') +#define strv__init_literal(x, ...) \ + _Generic((x), \ + char *: strv_init_len, \ + const char *: strv_init_len, \ + str_t: strv__ignore \ + )(x, sizeof(x) - 1) + +#define strv__1(x) \ + _Generic((x), \ + char *: strv_init, \ + const char *: strv_init, \ + str_t: strv_init_str \ + )(x) + +#define strv__2(cstr, clen) strv_init_len(cstr, clen) + +#define strv__impl(_1, _2, n, ...) strv__##n + +#define strv(...) strv__check(__VA_ARGS__) ? strv__init_literal(__VA_ARGS__) : strv__impl(__VA_ARGS__, 2, 1, 0)(__VA_ARGS__) + +#define cstrv(cstr) { cstr, sizeof(cstr) - 1, } + +strview_t strv_init(const char *cstr); +strview_t strv_init_len(const char *buf, usize size); +strview_t strv_init_str(str_t str); + +bool strv_is_empty(strview_t ctx); +bool strv_equals(strview_t a, strview_t b); +int strv_compare(strview_t a, strview_t b); + +char strv_front(strview_t ctx); +char strv_back(strview_t ctx); + +str16_t strv_to_str16(arena_t *arena, strview_t src); +tstr_t strv_to_tstr(arena_t *arena, strview_t src); + +str_t strv_to_upper(arena_t *arena, strview_t src); +str_t strv_to_lower(arena_t *arena, strview_t src); + +strview_t strv_remove_prefix(strview_t ctx, usize n); +strview_t strv_remove_suffix(strview_t ctx, usize n); +strview_t strv_trim(strview_t ctx); +strview_t strv_trim_left(strview_t ctx); +strview_t strv_trim_right(strview_t ctx); + +strview_t strv_sub(strview_t ctx, usize from, usize to); + +bool strv_starts_with(strview_t ctx, char c); +bool strv_starts_with_view(strview_t ctx, strview_t view); + +bool strv_ends_with(strview_t ctx, char c); +bool strv_ends_with_view(strview_t ctx, strview_t view); + +bool strv_contains(strview_t ctx, char c); +bool strv_contains_view(strview_t ctx, strview_t view); +bool strv_contains_either(strview_t ctx, strview_t chars); + +usize strv_find(strview_t ctx, char c, usize from); +usize strv_find_view(strview_t ctx, strview_t view, usize from); +usize strv_find_either(strview_t ctx, strview_t chars, usize from); + +usize strv_rfind(strview_t ctx, char c, usize from_right); +usize strv_rfind_view(strview_t ctx, strview_t view, usize from_right); + +// CTYPE //////////////////////////////////////// + +bool char_is_space(char c); +bool char_is_alpha(char c); +bool char_is_num(char c); +char char_lower(char c); +char char_upper(char c); + +// INPUT STREAM ///////////////////////////////// + +typedef struct instream_t instream_t; +struct instream_t { + const char *beg; + const char *cur; + usize len; +}; + +instream_t istr_init(strview_t str); + +// get the current character and advance +char istr_get(instream_t *ctx); +// get the current character but don't advance +char istr_peek(instream_t *ctx); +// get the next character but don't advance +char istr_peek_next(instream_t *ctx); +// returns the previous character +char istr_prev(instream_t *ctx); +// returns the character before the previous +char istr_prev_prev(instream_t *ctx); +// ignore characters until the delimiter +void istr_ignore(instream_t *ctx, char delim); +// ignore characters until the delimiter and skip it +void istr_ignore_and_skip(instream_t *ctx, char delim); +// skip n characters +void istr_skip(instream_t *ctx, usize n); +// skips whitespace (' ', '\\n', '\\t', '\\r') +void istr_skip_whitespace(instream_t *ctx); +// returns to the beginning of the stream +void istr_rewind(instream_t *ctx); +// returns back characters +void istr_rewind_n(instream_t *ctx, usize amount); +// returns the number of bytes read from beginning of stream +usize istr_tell(instream_t *ctx); +// returns the number of bytes left to read in the stream +usize istr_remaining(instream_t *ctx); +// return true if the stream doesn't have any new bytes to read +bool istr_is_finished(instream_t *ctx); + +bool istr_get_bool(instream_t *ctx, bool *val); +bool istr_get_u8(instream_t *ctx, u8 *val); +bool istr_get_u16(instream_t *ctx, u16 *val); +bool istr_get_u32(instream_t *ctx, u32 *val); +bool istr_get_u64(instream_t *ctx, u64 *val); +bool istr_get_i8(instream_t *ctx, i8 *val); +bool istr_get_i16(instream_t *ctx, i16 *val); +bool istr_get_i32(instream_t *ctx, i32 *val); +bool istr_get_i64(instream_t *ctx, i64 *val); +bool istr_get_num(instream_t *ctx, double *val); +strview_t istr_get_view(instream_t *ctx, char delim); +strview_t istr_get_view_either(instream_t *ctx, strview_t chars); +strview_t istr_get_view_len(instream_t *ctx, usize len); +strview_t istr_get_line(instream_t *ctx); + +// OUTPUT STREAM //////////////////////////////// + +typedef struct outstream_t outstream_t; +struct outstream_t { + char *beg; + arena_t *arena; +}; + +outstream_t ostr_init(arena_t *exclusive_arena); +void ostr_clear(outstream_t *ctx); + +usize ostr_tell(outstream_t *ctx); + +char ostr_back(outstream_t *ctx); +str_t ostr_to_str(outstream_t *ctx); +strview_t ostr_as_view(outstream_t *ctx); + +void ostr_pop(outstream_t *ctx, usize count); + +void ostr_print(outstream_t *ctx, const char *fmt, ...); +void ostr_printv(outstream_t *ctx, const char *fmt, va_list args); +void ostr_putc(outstream_t *ctx, char c); +void ostr_puts(outstream_t *ctx, strview_t v); + +void ostr_append_bool(outstream_t *ctx, bool val); +void ostr_append_uint(outstream_t *ctx, u64 val); +void ostr_append_int(outstream_t *ctx, i64 val); +void ostr_append_num(outstream_t *ctx, double val); + +// INPUT BINARY STREAM ////////////////////////// + +typedef struct { + const u8 *beg; + const u8 *cur; + usize len; +} ibstream_t; + +ibstream_t ibstr_init(buffer_t buffer); + +bool ibstr_is_finished(ibstream_t *ib); +usize ibstr_tell(ibstream_t *ib); +usize ibstr_remaining(ibstream_t *ib); +usize ibstr_read(ibstream_t *ib, void *buffer, usize len); +void ibstr_skip(ibstream_t *ib, usize count); + +bool ibstr_get_u8(ibstream_t *ib, u8 *out); +bool ibstr_get_u16(ibstream_t *ib, u16 *out); +bool ibstr_get_u32(ibstream_t *ib, u32 *out); +bool ibstr_get_u64(ibstream_t *ib, u64 *out); + +bool ibstr_get_i8(ibstream_t *ib, i8 *out); +bool ibstr_get_i16(ibstream_t *ib, i16 *out); +bool ibstr_get_i32(ibstream_t *ib, i32 *out); +bool ibstr_get_i64(ibstream_t *ib, i64 *out); + +// ARENA //////////////////////////////////////// + +#if COLLA_WIN && !COLLA_TCC +#define alignof __alignof +#endif + +typedef enum arena_type_e { + ARENA_TYPE_NONE, // only here so that a 0 initialised arena is valid + ARENA_VIRTUAL, + ARENA_MALLOC, + ARENA_MALLOC_ALWAYS, + ARENA_STATIC, +} arena_type_e; + +typedef enum alloc_flags_e { + ALLOC_FLAGS_NONE = 0, + ALLOC_NOZERO = 1 << 0, + ALLOC_SOFT_FAIL = 1 << 1, +} alloc_flags_e; + +typedef struct arena_t arena_t; +struct arena_t { + u8 *beg; + u8 *cur; + u8 *end; + arena_type_e type; +}; + +typedef struct arena_desc_t arena_desc_t; +struct arena_desc_t { + arena_type_e type; + usize size; + u8 *static_buffer; +}; + +typedef struct arena_alloc_desc_t arena_alloc_desc_t; +struct arena_alloc_desc_t { + arena_t *arena; + usize count; + alloc_flags_e flags; + usize align; + usize size; +}; + +// arena_type_e type, usize allocation, [ byte *static_buffer ] +#define arena_make(...) arena_init(&(arena_desc_t){ __VA_ARGS__ }) + +// arena_t *arena, T type, [ usize count, alloc_flags_e flags, usize align, usize size ] +#define alloc(arenaptr, type, ...) arena_alloc(&(arena_alloc_desc_t){ .size = sizeof(type), .count = 1, .align = alignof(type), .arena = arenaptr, __VA_ARGS__ }) + +// simple arena that always calls malloc internally, this is useful if you need +// malloc for some reason but want to still use the arena interface +// WARN: most arena functions outside of alloc/scratch won't work! +// you also need to each allocation afterwards! this is still +// malloc +extern arena_t malloc_arena; + +arena_t arena_init(const arena_desc_t *desc); +void arena_cleanup(arena_t *arena); + +arena_t arena_scratch(arena_t *arena, usize size); + +void *arena_alloc(const arena_alloc_desc_t *desc); +usize arena_tell(arena_t *arena); +usize arena_remaining(arena_t *arena); +usize arena_capacity(arena_t *arena); +void arena_rewind(arena_t *arena, usize from_start); +void arena_pop(arena_t *arena, usize amount); + +// OS LAYER ///////////////////////////////////// + +#define OS_ARENA_SIZE (MB(1)) +#define OS_WAIT_INFINITE (0xFFFFFFFF) + +typedef struct oshandle_t oshandle_t; +struct oshandle_t { + uptr data; +}; + +typedef struct os_system_info_t os_system_info_t; +struct os_system_info_t { + u32 processor_count; + u64 page_size; + str_t machine_name; +}; + +void os_init(void); +void os_cleanup(void); +os_system_info_t os_get_system_info(void); +void os_abort(int code); + +iptr os_get_last_error(void); +// NOT thread safe +str_t os_get_error_string(iptr error); + +// == HANDLE ==================================== + +oshandle_t os_handle_zero(void); +bool os_handle_match(oshandle_t a, oshandle_t b); +bool os_handle_valid(oshandle_t handle); + +#define OS_MAX_WAITABLE_HANDLES 256 + +typedef enum { + OS_WAIT_FINISHED, + OS_WAIT_ABANDONED, + OS_WAIT_TIMEOUT, + OS_WAIT_FAILED +} os_wait_result_e; + +typedef struct { + os_wait_result_e result; + u32 index; +} os_wait_t; + +os_wait_t os_wait_on_handles(oshandle_t *handles, int count, bool wait_all, u32 milliseconds); + +// == LOGGING =================================== + +typedef enum os_log_level_e { + LOG_BASIC, + LOG_DEBUG, + LOG_INFO, + LOG_WARN, + LOG_ERR, + LOG_FATAL, +} os_log_level_e; + +typedef enum os_log_colour_e { + LOG_COL_BLACK = 0, + LOG_COL_BLUE = 1, + LOG_COL_GREEN = 2, + LOG_COL_CYAN = LOG_COL_BLUE | LOG_COL_GREEN, + LOG_COL_RED = 4, + LOG_COL_MAGENTA = LOG_COL_RED | LOG_COL_BLUE, + LOG_COL_YELLOW = LOG_COL_RED | LOG_COL_GREEN, + LOG_COL_GREY = LOG_COL_RED | LOG_COL_BLUE | LOG_COL_GREEN, + + LOG_COL_LIGHT = 8, + + LOG_COL_DARK_GREY = LOG_COL_BLACK | LOG_COL_LIGHT, + LOG_COL_LIGHT_BLUE = LOG_COL_BLUE | LOG_COL_LIGHT, + LOG_COL_LIGHT_GREEN = LOG_COL_GREEN | LOG_COL_LIGHT, + LOG_COL_LIGHT_CYAN = LOG_COL_CYAN | LOG_COL_LIGHT, + LOG_COL_LIGHT_RED = LOG_COL_RED | LOG_COL_LIGHT, + LOG_COL_LIGHT_MAGENTA = LOG_COL_MAGENTA | LOG_COL_LIGHT, + LOG_COL_LIGHT_YELLOW = LOG_COL_YELLOW | LOG_COL_LIGHT, + LOG_COL_WHITE = LOG_COL_GREY | LOG_COL_LIGHT, + + LOG_COL_RESET, + + LOG_COL__COUNT, +} os_log_colour_e; + +void os_log_print(os_log_level_e level, const char *fmt, ...); +void os_log_printv(os_log_level_e level, const char *fmt, va_list args); +void os_log_set_colour(os_log_colour_e colour); +void os_log_set_colour_bg(os_log_colour_e foreground, os_log_colour_e background); + +oshandle_t os_stdout(void); +oshandle_t os_stdin(void); + +#define print(...) fmt_print(__VA_ARGS__) +#define println(...) os_log_print(LOG_BASIC, __VA_ARGS__) +#define debug(...) os_log_print(LOG_DEBUG, __VA_ARGS__) +#define info(...) os_log_print(LOG_INFO, __VA_ARGS__) +#define warn(...) os_log_print(LOG_WARN, __VA_ARGS__) +#define err(...) os_log_print(LOG_ERR, __VA_ARGS__) +#define fatal(...) os_log_print(LOG_FATAL, __VA_ARGS__) + +// == FILE ====================================== + +typedef enum filemode_e { + FILEMODE_READ = 1 << 0, + FILEMODE_WRITE = 1 << 1, +} filemode_e; + +bool os_file_exists(strview_t filename); +bool os_dir_exists(strview_t folder); +bool os_file_or_dir_exists(strview_t path); +bool os_dir_create(strview_t folder); +tstr_t os_file_fullpath(arena_t *arena, strview_t filename); +void os_file_split_path(strview_t path, strview_t *dir, strview_t *name, strview_t *ext); +bool os_file_delete(strview_t path); +bool os_dir_delete(strview_t path); + +oshandle_t os_file_open(strview_t path, filemode_e mode); +void os_file_close(oshandle_t handle); + +bool os_file_putc(oshandle_t handle, char c); +bool os_file_puts(oshandle_t handle, strview_t str); +bool os_file_print(arena_t scratch, oshandle_t handle, const char *fmt, ...); +bool os_file_printv(arena_t scratch, oshandle_t handle, const char *fmt, va_list args); + +usize os_file_read(oshandle_t handle, void *buf, usize len); +usize os_file_write(oshandle_t handle, const void *buf, usize len); + +usize os_file_read_buf(oshandle_t handle, buffer_t *buf); +usize os_file_write_buf(oshandle_t handle, buffer_t buf); + +bool os_file_seek(oshandle_t handle, usize offset); +bool os_file_seek_end(oshandle_t handle); +void os_file_rewind(oshandle_t handle); +usize os_file_tell(oshandle_t handle); +usize os_file_size(oshandle_t handle); +bool os_file_is_finished(oshandle_t handle); + +buffer_t os_file_read_all(arena_t *arena, strview_t path); +buffer_t os_file_read_all_fp(arena_t *arena, oshandle_t handle); + +str_t os_file_read_all_str(arena_t *arena, strview_t path); +str_t os_file_read_all_str_fp(arena_t *arena, oshandle_t handle); + +bool os_file_write_all(strview_t name, buffer_t buffer); +bool os_file_write_all_fp(oshandle_t handle, buffer_t buffer); + +bool os_file_write_all_str(strview_t name, strview_t data); +bool os_file_write_all_str_fp(oshandle_t handle, strview_t data); + +u64 os_file_time(strview_t path); +u64 os_file_time_fp(oshandle_t handle); +bool os_file_has_changed(strview_t path, u64 last_change); + +// == DIR WALKER ================================ + +typedef enum dir_type_e { + DIRTYPE_FILE, + DIRTYPE_DIR, +} dir_type_e; + +typedef struct dir_entry_t dir_entry_t; +struct dir_entry_t { + str_t name; + dir_type_e type; + usize file_size; +}; + +#define dir_foreach(arena, it, dir) for (dir_entry_t *it = os_dir_next(arena, dir); it; it = os_dir_next(arena, dir)) + +typedef struct dir_t dir_t; +dir_t *os_dir_open(arena_t *arena, strview_t path); +bool os_dir_is_valid(dir_t *dir); +// optional, only call this if you want to return before os_dir_next returns NULL +void os_dir_close(dir_t *dir); + +dir_entry_t *os_dir_next(arena_t *arena, dir_t *dir); + +// == PROCESS =================================== + +typedef struct os_env_t os_env_t; +typedef strv_list_t os_cmd_t; +#define os_make_cmd(...) &(os_cmd_t){ .items = (strview_t[]){ __VA_ARGS__ }, .count = arrlen(((strview_t[]){ __VA_ARGS__ })) } + +typedef struct os_cmd_options_t os_cmd_options_t; +struct os_cmd_options_t { + os_env_t *env; + // redirected if !NULL + oshandle_t *error; + oshandle_t *out; + oshandle_t *in; +}; + +void os_set_env_var(arena_t scratch, strview_t key, strview_t value); +str_t os_get_env_var(arena_t *arena, strview_t key); +os_env_t *os_get_env(arena_t *arena); +bool os_run_cmd(arena_t scratch, os_cmd_t *cmd, os_cmd_options_t *options); +oshandle_t os_run_cmd_async(arena_t scratch, os_cmd_t *cmd, os_cmd_options_t *options); +bool os_process_wait(oshandle_t proc, uint time, int *out_exit); + +// == MEMORY ==================================== + +void *os_alloc(usize size); +void os_free(void *ptr); + +void *os_reserve(usize size, usize *out_padded_size); +bool os_commit(void *ptr, usize num_of_pages); +bool os_release(void *ptr, usize size); +usize os_pad_to_page(usize byte_count); + +// == THREAD ==================================== + +typedef int (thread_func_t)(u64 thread_id, void *userdata); + +oshandle_t os_thread_launch(thread_func_t func, void *userdata); +bool os_thread_detach(oshandle_t thread); +bool os_thread_join(oshandle_t thread, int *code); + +u64 os_thread_get_id(oshandle_t thread); + +// == MUTEX ===================================== + +oshandle_t os_mutex_create(void); +void os_mutex_free(oshandle_t mutex); +void os_mutex_lock(oshandle_t mutex); +void os_mutex_unlock(oshandle_t mutex); +bool os_mutex_try_lock(oshandle_t mutex); + +#if !COLLA_NO_CONDITION_VARIABLE +// == CONDITION VARIABLE ======================== + +oshandle_t os_cond_create(void); +void os_cond_free(oshandle_t cond); + +void os_cond_signal(oshandle_t cond); +void os_cond_broadcast(oshandle_t cond); + +void os_cond_wait(oshandle_t cond, oshandle_t mutex, int milliseconds); + +#endif + +// PARSERS ////////////////////////////////////// + +// == INI ============================================ + +typedef struct inivalue_t inivalue_t; +struct inivalue_t { + strview_t key; + strview_t value; + inivalue_t *next; +}; + +typedef struct initable_t initable_t; +struct initable_t { + strview_t name; + inivalue_t *values; + inivalue_t *tail; + initable_t *next; +}; + +typedef struct ini_t ini_t; +struct ini_t { + strview_t text; + initable_t *tables; + initable_t *tail; +}; + +typedef struct iniopt_t iniopt_t; +struct iniopt_t { + bool merge_duplicate_tables; // default false + bool merge_duplicate_keys; // default false + char key_value_divider; // default = + strview_t comment_vals; // default ;# +}; + +typedef struct iniarray_t iniarray_t; +struct iniarray_t { + strview_t *values; + usize count; +}; + +#define INI_ROOT strv("__ROOT__") + +ini_t ini_parse(arena_t *arena, strview_t filename, iniopt_t *opt); +ini_t ini_parse_fp(arena_t *arena, oshandle_t file, iniopt_t *opt); +ini_t ini_parse_str(arena_t *arena, strview_t str, iniopt_t *opt); + +bool ini_is_valid(ini_t *ini); + +initable_t *ini_get_table(ini_t *ini, strview_t name); +inivalue_t *ini_get(initable_t *table, strview_t key); + +iniarray_t ini_as_arr(arena_t *arena, inivalue_t *value, char delim); +u64 ini_as_uint(inivalue_t *value); +i64 ini_as_int(inivalue_t *value); +double ini_as_num(inivalue_t *value); +bool ini_as_bool(inivalue_t *value); + +typedef enum { + INI_PRETTY_COLOUR_KEY, + INI_PRETTY_COLOUR_VALUE, + INI_PRETTY_COLOUR_DIVIDER, + INI_PRETTY_COLOUR_TABLE, + INI_PRETTY_COLOUR__COUNT, +} ini_pretty_colours_e; + +typedef struct ini_pretty_opts_t ini_pretty_opts_t; +struct ini_pretty_opts_t { + oshandle_t custom_target; + bool use_custom_colours; + os_log_colour_e colours[INI_PRETTY_COLOUR__COUNT]; +}; + +void ini_pretty_print(ini_t *ini, const ini_pretty_opts_t *options); + + +// == JSON =========================================== + +typedef enum jsontype_e { + JSON_NULL, + JSON_ARRAY, + JSON_STRING, + JSON_NUMBER, + JSON_BOOL, + JSON_OBJECT, +} jsontype_e; + +typedef enum jsonflags_e { + JSON_DEFAULT = 0, + JSON_NO_TRAILING_COMMAS = 1 << 0, + JSON_NO_COMMENTS = 1 << 1, +} jsonflags_e; + +typedef struct json_t json_t; +struct json_t { + json_t *next; + json_t *prev; + + strview_t key; + + union { + json_t *array; + strview_t string; + double number; + bool boolean; + json_t *object; + }; + jsontype_e type; +}; + +json_t *json_parse(arena_t *arena, strview_t filename, jsonflags_e flags); +json_t *json_parse_str(arena_t *arena, strview_t str, jsonflags_e flags); + +json_t *json_get(json_t *node, strview_t key); + +#define json_check(val, js_type) ((val) && (val)->type == js_type) +#define json_for(it, arr) for (json_t *it = json_check(arr, JSON_ARRAY) ? arr->array : NULL; it; it = it->next) + +typedef enum json_pretty_colours_e { + JSON_PRETTY_COLOUR_KEY, + JSON_PRETTY_COLOUR_STRING, + JSON_PRETTY_COLOUR_NUM, + JSON_PRETTY_COLOUR_NULL, + JSON_PRETTY_COLOUR_TRUE, + JSON_PRETTY_COLOUR_FALSE, + JSON_PRETTY_COLOUR__COUNT, +} json_pretty_colours_e; + +typedef struct json_pretty_opts_t json_pretty_opts_t; +struct json_pretty_opts_t { + oshandle_t custom_target; + bool use_custom_colours; + os_log_colour_e colours[JSON_PRETTY_COLOUR__COUNT]; +}; + +void json_pretty_print(json_t *root, const json_pretty_opts_t *options); + +// == XML ============================================ + +typedef struct xmlattr_t xmlattr_t; +struct xmlattr_t { + strview_t key; + strview_t value; + xmlattr_t *next; +}; + +typedef struct xmltag_t xmltag_t; +struct xmltag_t { + strview_t key; + xmlattr_t *attributes; + strview_t content; + xmltag_t *child; + xmltag_t *tail; + xmltag_t *next; +}; + +typedef struct xml_t xml_t; +struct xml_t { + strview_t text; + xmltag_t *root; + xmltag_t *tail; +}; + +xml_t xml_parse(arena_t *arena, strview_t filename); +xml_t xml_parse_str(arena_t *arena, strview_t xmlstr); + +xmltag_t *xml_get_tag(xmltag_t *parent, strview_t key, bool recursive); +strview_t xml_get_attribute(xmltag_t *tag, strview_t key); + +// == HTML =========================================== + +typedef struct htmltag_t htmltag_t; +struct htmltag_t { + str_t key; + xmlattr_t *attributes; + strview_t content; + htmltag_t *children; + htmltag_t *tail; + htmltag_t *next; +}; + +typedef struct html_t html_t; +struct html_t { + strview_t text; + htmltag_t *root; + htmltag_t *tail; +}; + +html_t html_parse(arena_t *arena, strview_t filename); +html_t html_parse_str(arena_t *arena, strview_t str); + +htmltag_t *html_get_tag(htmltag_t *parent, strview_t key, bool recursive); +strview_t html_get_attribute(htmltag_t *tag, strview_t key); + +// NETWORKING /////////////////////////////////// + +void net_init(void); +void net_cleanup(void); +iptr net_get_last_error(void); + +typedef enum http_method_e { + HTTP_GET, + HTTP_POST, + HTTP_HEAD, + HTTP_PUT, + HTTP_DELETE, + HTTP_METHOD__COUNT, +} http_method_e; + +const char *http_get_method_string(http_method_e method); +const char *http_get_status_string(int status); + +typedef struct http_version_t http_version_t; +struct http_version_t { + u8 major; + u8 minor; +}; + +typedef struct http_header_t http_header_t; +struct http_header_t { + strview_t key; + strview_t value; + http_header_t *next; +}; + +typedef struct http_req_t http_req_t; +struct http_req_t { + http_method_e method; + http_version_t version; + http_header_t *headers; + strview_t url; + strview_t body; +}; + +typedef struct http_res_t http_res_t; +struct http_res_t { + int status_code; + http_version_t version; + http_header_t *headers; + strview_t body; +}; + +http_header_t *http_parse_headers(arena_t *arena, strview_t header_string); + +http_req_t http_parse_req(arena_t *arena, strview_t request); +http_res_t http_parse_res(arena_t *arena, strview_t response); + +str_t http_req_to_str(arena_t *arena, http_req_t *req); +str_t http_res_to_str(arena_t *arena, http_res_t *res); + +bool http_has_header(http_header_t *headers, strview_t key); +void http_set_header(http_header_t *headers, strview_t key, strview_t value); +strview_t http_get_header(http_header_t *headers, strview_t key); + +str_t http_make_url_safe(arena_t *arena, strview_t string); +str_t http_decode_url_safe(arena_t *arena, strview_t string); + +typedef struct { + strview_t host; + strview_t uri; +} http_url_t; + +http_url_t http_split_url(strview_t url); + +typedef struct { + arena_t *arena; + strview_t url; + http_version_t version; // 1.1 by default + http_method_e request_type; + http_header_t *headers; + int header_count; // optional, if set to 0 it traverses headers using h->next + strview_t body; +} http_request_desc_t; + +typedef void (*http_request_callback_fn)(strview_t chunk, void *udata); + +// arena_t *arena, strview_t url, [ http_header_t *headers, int header_count, strview_t body ] +#define http_get(arena, url, ...) http_request(&(http_request_desc_t){ arena, url, .request_type = HTTP_GET, .version = { 1, 1 }, __VA_ARGS__ }) + +http_res_t http_request(http_request_desc_t *request); +http_res_t http_request_cb(http_request_desc_t *request, http_request_callback_fn callback, void *userdata); + +// SOCKETS ////////////////////////// + +typedef uintptr_t socket_t; +typedef struct skpoll_t skpoll_t; + +#define SK_ADDR_LOOPBACK "127.0.0.1" +#define SK_ADDR_ANY "0.0.0.0" +#define SK_ADDR_BROADCAST "255.255.255.255" + +struct skpoll_t { + uintptr_t socket; + short events; + short revents; +}; + +#define SOCKET_ERROR (-1) + +typedef enum { + SOCK_TCP, + SOCK_UDP, +} sktype_e; + +typedef enum { + SOCK_EVENT_NONE, + SOCK_EVENT_READ = 1 << 0, + SOCK_EVENT_WRITE = 1 << 1, + SOCK_EVENT_ACCEPT = 1 << 2, + SOCK_EVENT_CONNECT = 1 << 3, + SOCK_EVENT_CLOSE = 1 << 4, +} skevent_e; + +// Opens a socket +socket_t sk_open(sktype_e type); +// Opens a socket using 'protocol', options are +// ip, icmp, ggp, tcp, egp, pup, udp, hmp, xns-idp, rdp +socket_t sk_open_protocol(const char *protocol); + +// Checks that a opened socket is valid, returns true on success +bool sk_is_valid(socket_t sock); + +// Closes a socket, returns true on success +bool sk_close(socket_t sock); + +// Fill out a sk_addrin_t structure with "ip" and "port" +// skaddrin_t sk_addrin_init(const char *ip, uint16_t port); + +// Associate a local address with a socket +bool sk_bind(socket_t sock, const char *ip, u16 port); + +// Place a socket in a state in which it is listening for an incoming connection +bool sk_listen(socket_t sock, int backlog); + +// Permits an incoming connection attempt on a socket +socket_t sk_accept(socket_t sock); + +// Connects to a server (e.g. "127.0.0.1" or "google.com") with a port(e.g. 1234), returns true on success +bool sk_connect(socket_t sock, const char *server, u16 server_port); + +// Sends data on a socket, returns true on success +int sk_send(socket_t sock, const void *buf, int len); +// Receives data from a socket, returns byte count on success, 0 on connection close or -1 on error +int sk_recv(socket_t sock, void *buf, int len); + +// Wait for an event on some sockets +int sk_poll(skpoll_t *to_poll, int num_to_poll, int timeout); + +oshandle_t sk_bind_event(socket_t sock, skevent_e event); +void sk_destroy_event(oshandle_t handle); +void sk_reset_event(oshandle_t handle); + +// WEBSOCKETS /////////////////////// + +bool websocket_init(arena_t scratch, socket_t socket, strview_t key); +buffer_t websocket_encode(arena_t *arena, strview_t message); +str_t websocket_decode(arena_t *arena, buffer_t message); + +// SHA 1 //////////////////////////// + +typedef struct sha1_t sha1_t; +struct sha1_t { + u32 digest[5]; + u8 block[64]; + usize block_index; + usize byte_count; +}; + +typedef struct sha1_result_t sha1_result_t; +struct sha1_result_t { + u32 digest[5]; +}; + +sha1_t sha1_init(void); +sha1_result_t sha1(sha1_t *ctx, const void *buf, usize len); +str_t sha1_str(arena_t *arena, sha1_t *ctx, const void *buf, usize len); + +// BASE 64 ////////////////////////// + +buffer_t base64_encode(arena_t *arena, buffer_t buffer); +buffer_t base64_decode(arena_t *arena, buffer_t buffer); + +// PRETTY PRINTING ////////////////////////////// + +strview_t pretty_log_to_colour(os_log_colour_e colour); + +void pretty_print(arena_t scratch, const char *fmt, ...); +void pretty_printv(arena_t scratch, const char *fmt, va_list args); + +str_t pretty_print_get_string(arena_t *arena, const char *fmt, ...); +str_t pretty_print_get_stringv(arena_t *arena, const char *fmt, va_list args); + +#endif diff --git a/colla_lin.c b/colla_lin.c new file mode 100644 index 0000000..e69de29 diff --git a/tcc/colla_tcc.h b/colla_tcc.h similarity index 99% rename from tcc/colla_tcc.h rename to colla_tcc.h index daea037..6524807 100644 --- a/tcc/colla_tcc.h +++ b/colla_tcc.h @@ -318,4 +318,5 @@ extern BOOL __stdcall InternetCloseHandle(HINTERNET hInternet); #endif -#endif \ No newline at end of file +#endif + diff --git a/win/os_win32.c b/colla_win32.c similarity index 61% rename from win/os_win32.c rename to colla_win32.c index 3317edf..0701b3a 100644 --- a/win/os_win32.c +++ b/colla_win32.c @@ -1,17 +1,93 @@ +#include "colla.h" + #include -#include - -#if COLLA_DEBUG -#include // abort -#endif - -#include "../os.h" -#include "../net.h" #if COLLA_TCC -#include "../tcc/colla_tcc.h" + #include "colla_tcc.h" +#elif !COLLA_NO_NET + #include + #include + #include + + #if COLLA_CMT_LIB + #pragma comment(lib, "Wininet") + #pragma comment(lib, "Ws2_32") + #endif #endif +str_t str_os_from_str16(arena_t *arena, str16_t src) { + str_t out = {0}; + + int outlen = WideCharToMultiByte( + CP_UTF8, 0, + src.buf, (int)src.len, + NULL, 0, + NULL, NULL + ); + + if (outlen == 0) { + unsigned long error = GetLastError(); + if (error == ERROR_NO_UNICODE_TRANSLATION) { + err("couldn't translate wide string (%S) to utf8, no unicode translation", src.buf); + } + else { + err("couldn't translate wide string (%S) to utf8, %v", src.buf, os_get_error_string(os_get_last_error())); + } + + return STR_EMPTY; + } + + out.buf = alloc(arena, char, outlen + 1); + WideCharToMultiByte( + CP_UTF8, 0, + src.buf, (int)src.len, + out.buf, outlen, + NULL, NULL + ); + + out.len = outlen; + + return out; +} + +str16_t strv_os_to_str16(arena_t *arena, strview_t src) { + str16_t out = {0}; + + if (strv_is_empty(src)) { + return out; + } + + int len = MultiByteToWideChar( + CP_UTF8, 0, + src.buf, (int)src.len, + NULL, 0 + ); + + if (len == 0) { + unsigned long error = GetLastError(); + if (error == ERROR_NO_UNICODE_TRANSLATION) { + err("couldn't translate string (%v) to a wide string, no unicode translation", src); + } + else { + err("couldn't translate string (%v) to a wide string, %v", src, os_get_error_string(os_get_last_error())); + } + + return out; + } + + out.buf = alloc(arena, wchar_t, len + 1); + + MultiByteToWideChar( + CP_UTF8, 0, + src.buf, (int)src.len, + out.buf, len + ); + + out.len = len; + + return out; +} + typedef enum os_entity_kind_e { OS_KIND_NULL, OS_KIND_THREAD, @@ -502,12 +578,42 @@ os_env_t *os_get_env(arena_t *arena) { return out; } -oshandle_t os_run_cmd_async(arena_t scratch, os_cmd_t *cmd, os_env_t *optional_env) { +oshandle_t os_run_cmd_async(arena_t scratch, os_cmd_t *cmd, os_cmd_options_t *options) { + HANDLE hstdout_read = NULL; + HANDLE hstderr_read = NULL; + HANDLE hstdin_write = NULL; + HANDLE hstdout_write = NULL; + HANDLE hstderr_write = NULL; + HANDLE hstdin_read = NULL; + + SECURITY_ATTRIBUTES sa_attr = { + .nLength = sizeof(SECURITY_ATTRIBUTES), + .bInheritHandle = TRUE, + }; + + if (options && options->out) { + CreatePipe(&hstdout_read, &hstdout_write, &sa_attr, 0); + options->out->data = (uptr)hstdout_read; + SetHandleInformation(hstdout_read, HANDLE_FLAG_INHERIT, 0); + } + + if (options && options->error) { + CreatePipe(&hstderr_read, &hstderr_write, &sa_attr, 0); + options->error->data = (uptr)hstderr_read; + SetHandleInformation(hstderr_read, HANDLE_FLAG_INHERIT, 0); + } + + if (options && options->in) { + CreatePipe(&hstdin_read, &hstdin_write, &sa_attr, 0); + options->in->data = (uptr)hstdin_read; + SetHandleInformation(hstdin_write, HANDLE_FLAG_INHERIT, 0); + } + STARTUPINFOW start_info = { .cb = sizeof(STARTUPINFO), - .hStdError = GetStdHandle(STD_ERROR_HANDLE), - .hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE), - .hStdInput = GetStdHandle(STD_INPUT_HANDLE), + .hStdError = hstderr_write ? hstderr_write : GetStdHandle(STD_ERROR_HANDLE), + .hStdOutput = hstdout_write ? hstdout_write : GetStdHandle(STD_OUTPUT_HANDLE), + .hStdInput = hstdin_read ? hstdin_read : GetStdHandle(STD_INPUT_HANDLE), .dwFlags = STARTF_USESTDHANDLES, }; @@ -534,7 +640,7 @@ oshandle_t os_run_cmd_async(arena_t scratch, os_cmd_t *cmd, os_env_t *optional_e strview_t cmd_view = ostr_as_view(&cmdline); str16_t command = strv_to_str16(&scratch, cmd_view); - WCHAR *env = optional_env ? optional_env->data : NULL; + WCHAR* env = (options && options->env) ? options->env->data : NULL; BOOL success = CreateProcessW( NULL, @@ -549,9 +655,21 @@ oshandle_t os_run_cmd_async(arena_t scratch, os_cmd_t *cmd, os_env_t *optional_e &proc_info ); + if (hstdout_write) { + CloseHandle(hstdout_write); + } + + if (hstderr_write) { + CloseHandle(hstderr_write); + } + + if (hstdin_read) { + CloseHandle(hstdin_read); + } + if (env) { FreeEnvironmentStringsW(env); - optional_env->data = NULL; + options->env->data = NULL; } if (!success) { @@ -749,3 +867,342 @@ void os_cond_wait(oshandle_t cond, oshandle_t mutex, int milliseconds) { } #endif + +#if !COLLA_NO_NET + +struct { + HINTERNET internet; +} http_win = {0}; + +void net_init(void) { + if (http_win.internet) return; + http_win.internet = InternetOpen( + TEXT("COLLA_WININET"), + INTERNET_OPEN_TYPE_PRECONFIG, + NULL, + NULL, + 0 + ); + + WSADATA wsdata = {0}; + if (WSAStartup(0x0202, &wsdata)) { + fatal("couldn't startup sockets: %v", os_get_error_string(WSAGetLastError())); + } +} + +void net_cleanup(void) { + if (!http_win.internet) return; + InternetCloseHandle(http_win.internet); + http_win.internet = NULL; + WSACleanup(); +} + +iptr net_get_last_error(void) { + return WSAGetLastError(); +} + +http_res_t http_request(http_request_desc_t *req) { + return http_request_cb(req, NULL, NULL); +} + +http_res_t http_request_cb(http_request_desc_t *req, http_request_callback_fn callback, void *userdata) { + HINTERNET connection = NULL; + HINTERNET request = NULL; + BOOL result = FALSE; + bool success = false; + http_res_t res = {0}; + arena_t arena_before = *req->arena; + + if (!http_win.internet) { + err("net_init has not been called"); + goto failed; + } + + http_url_t split = http_split_url(req->url); + strview_t server = split.host; + strview_t page = split.uri; + + if (strv_starts_with_view(server, strv("http://"))) { + server = strv_remove_prefix(server, 7); + } + + if (strv_starts_with_view(server, strv("https://"))) { + server = strv_remove_prefix(server, 8); + } + + { + arena_t scratch = *req->arena; + + if (req->version.major == 0) req->version.major = 1; + if (req->version.minor == 0) req->version.minor = 1; + + const TCHAR *accepted_types[] = { TEXT("*/*"), NULL }; + const char *method = http_get_method_string(req->request_type); + str_t http_ver = str_fmt(&scratch, "HTTP/%u.%u", req->version.major, req->version.minor); + + tstr_t tserver = strv_to_tstr(&scratch, server); + tstr_t tpage = strv_to_tstr(&scratch, page); + tstr_t tmethod = strv_to_tstr(&scratch, strv(method)); + tstr_t thttp_ver = strv_to_tstr(&scratch, strv(http_ver)); + + connection = InternetConnect( + http_win.internet, + tserver.buf, + INTERNET_DEFAULT_HTTPS_PORT, + NULL, + NULL, + INTERNET_SERVICE_HTTP, + 0, + (DWORD_PTR)NULL // userdata + ); + if (!connection) { + err("call to InternetConnect failed: %u", os_get_error_string(os_get_last_error())); + goto failed; + } + + request = HttpOpenRequest( + connection, + tmethod.buf, + tpage.buf, + thttp_ver.buf, + NULL, + accepted_types, + INTERNET_FLAG_SECURE, + (DWORD_PTR)NULL // userdata + ); + if (!request) { + err("call to HttpOpenRequest failed: %v", os_get_error_string(os_get_last_error())); + goto failed; + } + } + + for (int i = 0; i < req->header_count; ++i) { + http_header_t *h = &req->headers[i]; + arena_t scratch = *req->arena; + + str_t header = str_fmt(&scratch, "%v: %v\r\n", h->key, h->value); + tstr_t theader = strv_to_tstr(&scratch, strv(header)); + HttpAddRequestHeaders(request, theader.buf, (DWORD)theader.len, 0); + } + + result = HttpSendRequest( + request, + NULL, + 0, + (void *)req->body.buf, + (DWORD)req->body.len + ); + if (!result) { + iptr error = os_get_last_error(); + if (error == ERROR_INTERNET_NAME_NOT_RESOLVED) { + err("invalid url: %v", req->url); + } + else { + err("call to HttpSendRequest failed: %lld", error); + // os_get_error_string(error)); + } + goto failed; + } + + u8 smallbuf[KB(5)]; + DWORD bufsize = sizeof(smallbuf); + + u8 *buffer = smallbuf; + + // try and read it into a static buffer + result = HttpQueryInfo(request, HTTP_QUERY_RAW_HEADERS_CRLF, smallbuf, &bufsize, NULL); + + // buffer is not big enough, allocate one with the arena instead + if (!result && GetLastError() == ERROR_INSUFFICIENT_BUFFER) { + info("buffer is too small"); + buffer = alloc(req->arena, u8, bufsize + 1); + result = HttpQueryInfo(request, HTTP_QUERY_RAW_HEADERS_CRLF, buffer, &bufsize, NULL); + } + + if (!result) { + err("couldn't get headers"); + goto failed; + } + + tstr_t theaders = { (TCHAR *)buffer, bufsize }; + str_t headers = str_from_tstr(req->arena, theaders); + + res.headers = http_parse_headers(req->arena, strv(headers)); + res.version = req->version; + + DWORD status_code = 0; + DWORD status_code_len = sizeof(status_code); + result = HttpQueryInfo(request, HTTP_QUERY_FLAG_NUMBER | HTTP_QUERY_STATUS_CODE, &status_code, &status_code_len, 0); + if (!result) { + err("couldn't get status code"); + goto failed; + } + + res.status_code = status_code; + + outstream_t body = ostr_init(req->arena); + + while (true) { + DWORD read = 0; + char read_buffer[4096]; + BOOL read_success = InternetReadFile( + request, read_buffer, sizeof(read_buffer), &read + ); + if (!read_success || read == 0) { + break; + } + + strview_t chunk = strv(read_buffer, read); + if (callback) { + callback(chunk, userdata); + } + ostr_puts(&body, chunk); + } + + res.body = strv(ostr_to_str(&body)); + + success = true; + +failed: + if (request) InternetCloseHandle(request); + if (connection) InternetCloseHandle(connection); + if (!success) *req->arena = arena_before; + return res; +} + +// SOCKETS ////////////////////////// + +SOCKADDR_IN sk__addrin_in(const char *ip, u16 port) { + SOCKADDR_IN sk_addr = { + .sin_family = AF_INET, + .sin_port = htons(port), + }; + + if (!inet_pton(AF_INET, ip, &sk_addr.sin_addr)) { + err("inet_pton failed: %v", os_get_error_string(net_get_last_error())); + return (SOCKADDR_IN){0}; + } + + return sk_addr; +} + +socket_t sk_open(sktype_e type) { + int sock_type = 0; + + switch(type) { + case SOCK_TCP: sock_type = SOCK_STREAM; break; + case SOCK_UDP: sock_type = SOCK_DGRAM; break; + default: fatal("skType not recognized: %d", type); break; + } + + return socket(AF_INET, sock_type, 0); +} + +socket_t sk_open_protocol(const char *protocol) { + struct protoent *proto = getprotobyname(protocol); + if(!proto) { + return INVALID_SOCKET; + } + return socket(AF_INET, SOCK_STREAM, proto->p_proto); +} + +bool sk_is_valid(socket_t sock) { + return sock != INVALID_SOCKET; +} + +bool sk_close(socket_t sock) { + return closesocket(sock) != SOCKET_ERROR; +} + +bool sk_bind(socket_t sock, const char *ip, u16 port) { + SOCKADDR_IN sk_addr = sk__addrin_in(ip, port); + if (sk_addr.sin_family == 0) { + return false; + } + return bind(sock, (SOCKADDR*)&sk_addr, sizeof(sk_addr)) != SOCKET_ERROR; +} + +bool sk_listen(socket_t sock, int backlog) { + return listen(sock, backlog) != SOCKET_ERROR; +} + +socket_t sk_accept(socket_t sock) { + SOCKADDR_IN addr = {0}; + int addr_size = sizeof(addr); + return accept(sock, (SOCKADDR*)&addr, &addr_size); +} + +bool sk_connect(socket_t sock, const char *server, u16 server_port) { + u8 tmpbuf[1024] = {0}; + arena_t scratch = arena_make(ARENA_STATIC, sizeof(tmpbuf), tmpbuf); + + str16_t wserver = strv_to_str16(&scratch, strv(server)); + + ADDRINFOW *addrinfo = NULL; + int result = GetAddrInfoW(wserver.buf, NULL, NULL, &addrinfo); + if (result) { + return false; + } + + char ip_str[1024] = {0}; + inet_ntop(addrinfo->ai_family, addrinfo->ai_addr, ip_str, sizeof(ip_str)); + + SOCKADDR_IN sk_addr = sk__addrin_in(ip_str, server_port); + if (sk_addr.sin_family == 0) { + return false; + } + + return connect(sock, (SOCKADDR*)&sk_addr, sizeof(sk_addr)) != SOCKET_ERROR; +} + +int sk_send(socket_t sock, const void *buf, int len) { + return send(sock, (const char *)buf, len, 0); +} + +int sk_recv(socket_t sock, void *buf, int len) { + return recv(sock, (char *)buf, len, 0); +} + +int sk_poll(skpoll_t *to_poll, int num_to_poll, int timeout) { + return WSAPoll((WSAPOLLFD*)to_poll, (ULONG)num_to_poll, timeout); +} + +oshandle_t sk_bind_event(socket_t sock, skevent_e event) { + if (event == SOCK_EVENT_NONE) { + return os_handle_zero(); + } + + HANDLE handle = WSACreateEvent(); + if (handle == WSA_INVALID_EVENT) { + return os_handle_zero(); + } + + int wsa_event = 0; + if (event & SOCK_EVENT_READ) wsa_event |= FD_READ; + if (event & SOCK_EVENT_WRITE) wsa_event |= FD_WRITE; + if (event & SOCK_EVENT_ACCEPT) wsa_event |= FD_ACCEPT; + if (event & SOCK_EVENT_CONNECT) wsa_event |= FD_CONNECT; + if (event & SOCK_EVENT_CLOSE) wsa_event |= FD_CLOSE; + + if (WSAEventSelect(sock, handle, wsa_event) != 0) { + WSACloseEvent(handle); + return os_handle_zero(); + } + + return (oshandle_t){ .data = (uptr)handle }; +} + +void sk_reset_event(oshandle_t handle) { + if (!os_handle_valid(handle)) { + warn("invalid handle"); + return; + } + WSAResetEvent((HANDLE)handle.data); +} + +void sk_destroy_event(oshandle_t handle) { + if (!os_handle_valid(handle)) return; + WSACloseEvent((HANDLE)handle.data); +} + +#endif diff --git a/core.c b/core.c deleted file mode 100644 index aa165ac..0000000 --- a/core.c +++ /dev/null @@ -1,81 +0,0 @@ -#include "core.h" - -#include - -#if COLLA_CLANG -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Weverything" -#endif - -#define STB_SPRINTF_DECORATE(name) colla_stb_##name -#define STB_SPRINTF_NOUNALIGNED -#define STB_SPRINTF_IMPLEMENTATION -#include "stb/stb_sprintf.h" - -#if COLLA_CLANG -#pragma clang diagnostic pop -#endif - -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) { - fflush(stdout); - fwrite(buf, 1, len, stdout); - return (char *)ud; -} - -void colla_init(colla_modules_e modules) { - colla__initialised_modules = modules; - if (modules & COLLA_OS) { - os_init(); - } -#if !COLLA_NO_NET - if (modules & COLLA_NET) { - net_init(); - } -#endif -} - -void colla_cleanup(void) { - colla_modules_e modules = colla__initialised_modules; - if (modules & COLLA_OS) { - os_cleanup(); - } -#if !COLLA_NO_NET - if (modules & COLLA_NET) { - net_cleanup(); - } -#endif -} - -int fmt_print(const char *fmt, ...) { - va_list args; - va_start(args, fmt); - int out = fmt_printv(fmt, args); - va_end(args); - return out; -} - -int fmt_printv(const char *fmt, va_list args) { - char buffer[STB_SPRINTF_MIN] = {0}; - return colla_stb_vsprintfcb(colla_fmt__stb_callback, buffer, buffer, fmt, args); -} - -int fmt_buffer(char *buf, usize len, const char *fmt, ...) { - va_list args; - va_start(args, fmt); - int out = fmt_bufferv(buf, len, fmt, args); - va_end(args); - return out; -} - -int fmt_bufferv(char *buf, usize len, const char *fmt, va_list args) { - return colla_stb_vsnprintf(buf, (int)len, fmt, args); -} diff --git a/core.h b/core.h deleted file mode 100644 index 1f9e940..0000000 --- a/core.h +++ /dev/null @@ -1,222 +0,0 @@ -#ifndef COLLA_CORE_H -#define COLLA_CORE_H - -#include -#include -#include -#include - -// CORE MODULES ///////////////////////////////// - -typedef enum { - COLLA_CORE = 0, - COLLA_OS = 1 << 0, - COLLA_NET = 1 << 1, - COLLA_ALL = 0xff, -} colla_modules_e; - -void colla_init(colla_modules_e modules); -void colla_cleanup(void); - -///////////////////////////////////////////////// - -// LINKED LISTS ///////////////////////////////// - -// singly linked list -#define list_push(list, item) ((item)->next=(list), (list)=(item)) -#define list_pop(list) ((list) = (list) ? (list)->next : NULL) - -// double linked list -#define dlist_push(list, item) do { \ - if (item) (item)->next = (list); \ - if (list) (list)->prev = (item); \ - (list) = (item); \ - } while (0) - -#define dlist_pop(list, item) do { \ - if (!(item)) break; \ - if ((item)->prev) (item)->prev->next = (item)->next; \ - if ((item)->next) (item)->next->prev = (item)->prev; \ - if ((item) == (list)) (list) = (item)->next; \ - } while (0) - -// ordered linked list - -#define olist_push(head, tail, item) do { \ - if (tail) { \ - (tail)->next = (item); \ - (tail) = (item); \ - } \ - else { \ - (head) = (tail) = (item); \ - } \ - } while (0) - -#define for_each(it, list) for (typeof(list) it = list; it; it = it->next) - -///////////////////////////////////////////////// - -// OS AND COMPILER MACROS /////////////////////// - -#if defined(_DEBUG) - #define COLLA_DEBUG 1 - #define COLLA_RELEASE 0 -#else - #define COLLA_DEBUG 0 - #define COLLA_RELEASE 1 -#endif - -#if defined(_WIN32) - #define COLLA_WIN 1 - #define COLLA_OSX 0 - #define COLLA_LIN 0 - #define COLLA_EMC 0 -#elif defined(__EMSCRIPTEN__) - #define COLLA_WIN 0 - #define COLLA_OSX 0 - #define COLLA_LIN 0 - #define COLLA_EMC 1 -#elif defined(__linux__) - #define COLLA_WIN 0 - #define COLLA_OSX 0 - #define COLLA_LIN 1 - #define COLLA_EMC 0 -#elif defined(__APPLE__) - #define COLLA_WIN 0 - #define COLLA_OSX 1 - #define COLLA_LIN 0 - #define COLLA_EMC 0 -#endif - -#if defined(__COSMOPOLITAN__) - #define COLLA_COSMO 1 -#else - #define COLLA_COSMO 0 -#endif - -#define COLLA_POSIX (COLLA_OSX || COLLA_LIN || COLLA_COSMO) - -#if defined(__clang__) - #define COLLA_CLANG 1 - #define COLLA_MSVC 0 - #define COLLA_TCC 0 - #define COLLA_GCC 0 -#elif defined(_MSC_VER) - #define COLLA_CLANG 0 - #define COLLA_MSVC 1 - #define COLLA_TCC 0 - #define COLLA_GCC 0 -#elif defined(__TINYC__) - #define COLLA_CLANG 0 - #define COLLA_MSVC 0 - #define COLLA_TCC 1 - #define COLLA_GCC 0 -#elif defined(__GNUC__) - #define COLLA_CLANG 0 - #define COLLA_MSVC 0 - #define COLLA_TCC 0 - #define COLLA_GCC 1 -#endif - -#if COLLA_CLANG - #define COLLA_CMT_LIB 0 -#elif COLLA_MSVC - #define COLLA_CMT_LIB 1 -#elif COLLA_TCC - #define COLLA_CMT_LIB 1 -#elif COLLA_GCC - #define COLLA_CMT_LIB 0 -#endif - -#if COLLA_TCC - #define alignof __alignof__ -#endif - -#if COLLA_WIN - #undef NOMINMAX - #undef WIN32_LEAN_AND_MEAN - #define WIN32_LEAN_AND_MEAN - #define NOMINMAX - - #ifdef UNICODE - #define COLLA_UNICODE 1 - #else - #define COLLA_UNICODE 0 - #endif - -#endif - -///////////////////////////////////////////////// - -// USEFUL MACROS //////////////////////////////// - -#define arrlen(a) (sizeof(a) / sizeof((a)[0])) -#define COLLA_UNUSED(v) (void)(v) - -#define COLLA__STRINGIFY(x) #x -#define COLLA_STRINGIFY(x) COLLA__STRINGIFY(x) - -#define MIN(a, b) ((a) < (b) ? (a) : (b)) -#define MAX(a, b) ((a) > (b) ? (a) : (b)) - -#define KB(n) (((u64)n) << 10) -#define MB(n) (((u64)n) << 20) -#define GB(n) (((u64)n) << 30) -#define TB(n) (((u64)n) << 40) - -#if COLLA_DEBUG - #define colla__assert(file, line, cond, ...) do { if (!(cond)) fatal(file ":" line " ASSERT FAILED: (" COLLA__STRINGIFY(cond) ") " __VA_ARGS__); } while (0) - #define colla_assert(...) colla__assert(__FILE__, COLLA__STRINGIFY(__LINE__), __VA_ARGS__) -#else - #define colla_assert(...) (void)0 -#endif - -///////////////////////////////////////////////// - -// BASIC TYPES ////////////////////////////////// - -#if COLLA_WIN && COLLA_UNICODE - typedef wchar_t TCHAR; -#else - typedef char TCHAR; -#endif - -typedef unsigned char uchar; -typedef unsigned short ushort; -typedef unsigned int uint; - -typedef uint8_t u8; -typedef uint16_t u16; -typedef uint32_t u32; -typedef uint64_t u64; - -typedef int8_t i8; -typedef int16_t i16; -typedef int32_t i32; -typedef int64_t i64; - -typedef size_t usize; -typedef ptrdiff_t isize; - -typedef uintptr_t uptr; -typedef intptr_t iptr; - -typedef struct { - u8 *data; - usize len; -} buffer_t; - -typedef struct arena_t arena_t; - -///////////////////////////////////////////////// - -// FORMATTING /////////////////////////////////// - -int fmt_print(const char *fmt, ...); -int fmt_printv(const char *fmt, va_list args); -int fmt_buffer(char *buf, usize len, const char *fmt, ...); -int fmt_bufferv(char *buf, usize len, const char *fmt, va_list args); - -///////////////////////////////////////////////// - -#endif diff --git a/darr.h b/darr.h deleted file mode 100644 index c48c898..0000000 --- a/darr.h +++ /dev/null @@ -1,82 +0,0 @@ -#ifndef COLLA_DARR_HEADER -#define COLLA_DARR_HEADER - -/* -dynamic chunked array which uses an arena to allocate, -the structure needs to follow this exact format, if you want -you can use the macro darr_define(struct_name, item_type) instead: - -//////////////////////////////////// - -typedef struct arr_t arr_t; -struct arr_t { - int *items; - usize block_size; - usize count; - arr_t *next; - arr_t *head; -}; -// equivalent to - -darr_define(arr_t, int); - -//////////////////////////////////// - -by default a chunk is 64 items long, you can change this default -by modifying the arr.block_size value before adding to the array, -or by defining DARRAY_DEFAULT_BLOCK_SIZE - -usage example: - -//////////////////////////////////// - -darr_define(arr_t, int); - -arr_t *arr = NULL; - -for (int i = 0; i < 100; ++i) { - darr_push(&arena, arr, i); -} - -for_each (chunk, arr) { - for (int i = 0; i < chunk->count; ++i) { - info("%d -> %d", i, chunk->items[i]); - } -} -*/ - -#define DARRAY_DEFAULT_BLOCK_SIZE (64) - -#define darr_define(struct_name, item_type) typedef struct struct_name struct_name; \ - struct struct_name { \ - item_type *items; \ - usize block_size; \ - usize count; \ - struct_name *next; \ - struct_name *head; \ - } - -#define darr__alloc_first(arena, arr) do { \ - (arr) = (arr) ? (arr) : alloc(arena, typeof(*arr)); \ - (arr)->head = (arr)->head ? (arr)->head : (arr); \ - (arr)->block_size = (arr)->block_size ? (arr)->block_size : DARRAY_DEFAULT_BLOCK_SIZE; \ - (arr)->items = alloc(arena, typeof(*arr->items), arr->block_size); \ - assert((arr)->count == 0); \ - } while (0) - -#define darr__alloc_block(arena, arr) do { \ - typeof(arr) newarr = alloc(arena, typeof(*arr)); \ - newarr->block_size = arr->block_size; \ - newarr->items = alloc(arena, typeof(*arr->items), arr->block_size); \ - newarr->head = arr->head; \ - arr->next = newarr; \ - arr = newarr; \ - } while (0) - -#define darr_push(arena, arr, item) do { \ - if (!(arr) || (arr)->items == NULL) darr__alloc_first(arena, arr); \ - if ((arr)->count >= (arr)->block_size) darr__alloc_block(arena, arr); \ - (arr)->items[(arr)->count++] = (item); \ - } while (0) - -#endif \ No newline at end of file diff --git a/example.c b/example.c new file mode 100644 index 0000000..63c509e --- /dev/null +++ b/example.c @@ -0,0 +1,9 @@ +#include "colla.c" + +int main() { + colla_init(COLLA_ALL); + + info("hello world!"); + + colla_cleanup(); +} diff --git a/highlight.c b/highlight.c deleted file mode 100644 index 6619fd6..0000000 --- a/highlight.c +++ /dev/null @@ -1,629 +0,0 @@ -#include "highlight.h" - -// based on https://github.com/Theldus/kat - -#include "arena.h" -#include "str.h" -#include "os.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 u64 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_c_cpp_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_t", TYPES), KW("uint8_t", TYPES), - KW("int16_t", TYPES), KW("uint16_t", TYPES), - KW("int32_t", TYPES), KW("uint32_t", TYPES), - KW("int64_t", TYPES), KW("uint64_t", TYPES), - KW("int8", TYPES), KW("uint8", TYPES), - KW("int16", TYPES), KW("uint16", TYPES), - KW("int32", TYPES), KW("uint32", TYPES), - KW("int64", TYPES), KW("uint64", TYPES), - KW("i8", TYPES), KW("u8", TYPES), - KW("i16", TYPES), KW("u16", TYPES), - KW("i32", TYPES), KW("u32", TYPES), - KW("i64", TYPES), KW("u64", 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 *hl_init(arena_t *arena, hl_config_t *config) { - if (!config) { - err(" 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_c_cpp_kwrds); - - out->kw_htable = hl_htable_init(arena, 8); - - for (int i = 0; i < kw_count; ++i) { - hl_keyword_t *kw = &hl_c_cpp_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 = istr_get(in); - bool is_last = istr_is_finished(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) && !char_is_num(cur)) { - ctx->kw_beg = istr_tell(in); - ctx->state = HL_STATE_KEYWORD; - } - - // potential number - else if (char_is_num(cur)) { - ctx->kw_beg = istr_tell(in); - ctx->state = HL_STATE_NUMBER; - } - - // potential char - else if (cur == '\'') { - ctx->kw_beg = istr_tell(in); - ctx->state = HL_STATE_CHAR; - } - - // potential string - else if (cur == '"') { - ctx->kw_beg = istr_tell(in); - ctx->state = HL_STATE_STRING; - } - - // line or multiline comment - else if (cur == '/') { - // single line comment - if (istr_peek(in) == '/') { - // rewind before comment begins - istr_rewind_n(in, 1); - - // comment until the end of line - hl_print_keyword(ctx, istr_get_line(in), HL_COLOR_COMMENT); - } - - // multiline comment - else if (istr_peek(in) == '*') { - ctx->state = HL_STATE_COMMENT_MULTI; - ctx->kw_beg = istr_tell(in); - istr_skip(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 = istr_tell(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_lower(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 (!char_is_num(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 == '\'' && istr_peek(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 == '"' && istr_prev_prev(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 == '*' && istr_peek(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 = { &(str[1]), sizeof(str) - 2 }; \ - strview_t b = istr_get_view_len(&temp, a.len); \ - if (strv_equals(a, b)) { \ - *in = temp; \ - hl_print_keyword(ctx, (strview_t){ 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 = istr_tell(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 hl_highlight(arena_t *arena, hl_ctx_t *ctx, strview_t data) { - ctx->ostr = ostr_init(arena); - - ctx->state = HL_STATE_DEFAULT; - ctx->kw_beg = 0; - - instream_t in = istr_init(data); - - while (!istr_is_finished(&in)) { - hl_next_char(ctx, &in); - } - - hl_next_char(ctx, &in); - - return ostr_to_str(&ctx->ostr); -} - -void hl_set_symbol_in_table(hl_ctx_t *ctx, char symbol, bool value) { - if (!ctx) return; - ctx->symbol_table[(unsigned char)symbol] = value; -} - -void hl_add_keyword(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); - } - - u64 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 (strv_equals(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; - } - - u64 hash = hl_htable_hash(key.buf, key.len); - usize index = hash & (table->count - 1); - hl_node_t *bucket = table->buckets[index]; - while (bucket) { - if (strv_equals(bucket->key, key)) { - return bucket; - } - bucket = bucket->next; - } - - return NULL; -} - -// uses the sdbm algorithm -static u64 hl_htable_hash(const void *bytes, usize count) { - const u8 *data = bytes; - u64 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 '&': - ostr_puts(out, strv("&")); - break; - case '<': - ostr_puts(out, strv("<")); - break; - case '>': - ostr_puts(out, strv(">")); - break; - default: - ostr_putc(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 { - ostr_putc(&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 { - ostr_puts(&ctx->ostr, v); - } -} - -static bool hl_is_char_keyword(char c) { - return char_is_alpha(c) || char_is_num(c) || c == '_'; -} - -static bool hl_highlight_symbol(hl_ctx_t *ctx, char c) { - if (!ctx->symbol_table[(unsigned char)c]) { - return false; - } - - ostr_puts(&ctx->ostr, ctx->colors[HL_COLOR_SYMBOL]); - hl_write_char(ctx, c); - ostr_puts(&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 (strv_ends_with_view(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 (!char_is_num(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 = istr_tell(in) - 1; - - return strv(in->beg + beg, end - beg); -} - -static void hl_print_keyword(hl_ctx_t *ctx, strview_t keyword, hl_color_e color) { - ostr_puts(&ctx->ostr, ctx->colors[color]); - hl_write(ctx, keyword); - ostr_puts(&ctx->ostr, ctx->colors[HL_COLOR_NORMAL]); -} - diff --git a/highlight.h b/highlight.h deleted file mode 100644 index f095d9e..0000000 --- a/highlight.h +++ /dev/null @@ -1,49 +0,0 @@ -#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 *hl_init(arena_t *arena, hl_config_t *config); -str_t hl_highlight(arena_t *arena, hl_ctx_t *ctx, strview_t str); - -void hl_set_symbol_in_table(hl_ctx_t *ctx, char symbol, bool value); -void hl_add_keyword(arena_t *arena, hl_ctx_t *ctx, hl_keyword_t *keyword); diff --git a/net.c b/net.c deleted file mode 100644 index 4377fd0..0000000 --- a/net.c +++ /dev/null @@ -1,649 +0,0 @@ -#include "net.h" -#include "arena.h" - -#include // sscanf - -#if COLLA_WIN -#include "win/net_win32.c" -#else -#error "platform not supported" -#endif - -const char *http_get_method_string(http_method_e method) { - switch (method) { - case HTTP_GET: return "GET"; - case HTTP_POST: return "POST"; - case HTTP_HEAD: return "HEAD"; - case HTTP_PUT: return "PUT"; - case HTTP_DELETE: return "DELETE"; - } - return "GET"; -} - -const char *http_get_status_string(int status) { - switch (status) { - case 200: return "OK"; - case 201: return "CREATED"; - case 202: return "ACCEPTED"; - case 204: return "NO CONTENT"; - case 205: return "RESET CONTENT"; - case 206: return "PARTIAL CONTENT"; - - case 300: return "MULTIPLE CHOICES"; - case 301: return "MOVED PERMANENTLY"; - case 302: return "MOVED TEMPORARILY"; - case 304: return "NOT MODIFIED"; - - case 400: return "BAD REQUEST"; - case 401: return "UNAUTHORIZED"; - case 403: return "FORBIDDEN"; - case 404: return "NOT FOUND"; - case 407: return "RANGE NOT SATISFIABLE"; - - case 500: return "INTERNAL SERVER ERROR"; - case 501: return "NOT IMPLEMENTED"; - case 502: return "BAD GATEWAY"; - case 503: return "SERVICE NOT AVAILABLE"; - case 504: return "GATEWAY TIMEOUT"; - case 505: return "VERSION NOT SUPPORTED"; - } - - return "UNKNOWN"; -} - -http_header_t *http__parse_headers_instream(arena_t *arena, instream_t *in) { - http_header_t *head = NULL; - - while (!istr_is_finished(in)) { - strview_t line = istr_get_line(in); - - // end of headers - if (strv_is_empty(line)) { - break; - } - - usize pos = strv_find(line, ':', 0); - if (pos != STR_NONE) { - http_header_t *new_head = alloc(arena, http_header_t); - - new_head->key = strv_sub(line, 0, pos); - new_head->value = strv_sub(line, pos + 2, SIZE_MAX); - - list_push(head, new_head); - } - } - - return head; -} - -http_header_t *http_parse_headers(arena_t *arena, strview_t header_string) { - instream_t in = istr_init(header_string); - return http__parse_headers_instream(arena, &in); -} - -http_req_t http_parse_req(arena_t *arena, strview_t request) { - http_req_t req = {0}; - instream_t in = istr_init(request); - - strview_t method = strv_trim(istr_get_view(&in, '/')); - istr_skip(&in, 1); // skip / - req.url = strv_trim(istr_get_view(&in, ' ')); - strview_t http = strv_trim(istr_get_view(&in, '\n')); - - istr_skip(&in, 1); // skip \n - - req.headers = http__parse_headers_instream(arena, &in); - - req.body = strv_trim(istr_get_view_len(&in, SIZE_MAX)); - - strview_t methods[5] = { strv("GET"), strv("POST"), strv("HEAD"), strv("PUT"), strv("DELETE") }; - usize methods_count = arrlen(methods); - - for (usize i = 0; i < methods_count; ++i) { - if (strv_equals(method, methods[i])) { - req.method = (http_method_e)i; - break; - } - } - - in = istr_init(http); - istr_ignore_and_skip(&in, '/'); // skip HTTP/ - istr_get_u8(&in, &req.version.major); - istr_skip(&in, 1); // skip . - istr_get_u8(&in, &req.version.minor); - - return req; -} - -http_res_t http_parse_res(arena_t *arena, strview_t response) { - http_res_t res = {0}; - instream_t in = istr_init(response); - - strview_t http = istr_get_view_len(&in, 4); - if (!strv_equals(http, strv("HTTP"))) { - err("response doesn't start with 'HTTP', instead with %v", http); - return (http_res_t){0}; - } - istr_skip(&in, 1); // skip / - istr_get_u8(&in, &res.version.major); - istr_skip(&in, 1); // skip . - istr_get_u8(&in, &res.version.minor); - istr_get_i32(&in, (i32*)&res.status_code); - - istr_ignore(&in, '\n'); - istr_skip(&in, 1); // skip \n - - res.headers = http__parse_headers_instream(arena, &in); - - strview_t encoding = http_get_header(res.headers, strv("transfer-encoding")); - if (!strv_equals(encoding, strv("chunked"))) { - res.body = istr_get_view_len(&in, SIZE_MAX); - } - else { - err("chunked encoding not implemented yet! body ignored"); - } - - return res; -} - -str_t http_req_to_str(arena_t *arena, http_req_t *req) { - outstream_t out = ostr_init(arena); - - const char *method = NULL; - switch (req->method) { - case HTTP_GET: method = "GET"; break; - case HTTP_POST: method = "POST"; break; - case HTTP_HEAD: method = "HEAD"; break; - case HTTP_PUT: method = "PUT"; break; - case HTTP_DELETE: method = "DELETE"; break; - default: err("unrecognised method: %d", method); return STR_EMPTY; - } - - ostr_print( - &out, - "%s /%v HTTP/%hhu.%hhu\r\n", - method, req->url, req->version.major, req->version.minor - ); - - http_header_t *h = req->headers; - while (h) { - ostr_print(&out, "%v: %v\r\n", h->key, h->value); - h = h->next; - } - - ostr_puts(&out, strv("\r\n")); - ostr_puts(&out, req->body); - - return ostr_to_str(&out); -} - -str_t http_res_to_str(arena_t *arena, http_res_t *res) { - outstream_t out = ostr_init(arena); - - ostr_print( - &out, - "HTTP/%hhu.%hhu %d %s\r\n", - res->version.major, - res->version.minor, - res->status_code, - http_get_status_string(res->status_code) - ); - ostr_puts(&out, strv("\r\n")); - ostr_puts(&out, res->body); - - return ostr_to_str(&out); -} - -bool http_has_header(http_header_t *headers, strview_t key) { - for_each(h, headers) { - if (strv_equals(h->key, key)) { - return true; - } - } - return false; -} - -void http_set_header(http_header_t *headers, strview_t key, strview_t value) { - http_header_t *h = headers; - while (h) { - if (strv_equals(h->key, key)) { - h->value = value; - break; - } - h = h->next; - } -} - -strview_t http_get_header(http_header_t *headers, strview_t key) { - http_header_t *h = headers; - while (h) { - if (strv_equals(h->key, key)) { - return h->value; - } - h = h->next; - } - return STRV_EMPTY; -} - -str_t http_make_url_safe(arena_t *arena, strview_t string) { - strview_t chars = strv(" !\"#$%%&'()*+,/:;=?@[]"); - usize final_len = string.len; - - // find final string length first - for (usize i = 0; i < string.len; ++i) { - if (strv_contains(chars, string.buf[i])) { - final_len += 2; - } - } - - str_t out = { - .buf = alloc(arena, char, final_len + 1), - .len = final_len - }; - usize cur = 0; - // substitute characters - for (usize i = 0; i < string.len; ++i) { - if (strv_contains(chars, string.buf[i])) { - fmt_buffer(out.buf + cur, 4, "%%%X", string.buf[i]); - cur += 3; - } - else { - out.buf[cur++] = string.buf[i]; - } - } - - return out; -} - -str_t http_decode_url_safe(arena_t *arena, strview_t string) { - usize final_len = string.len; - - for (usize i = 0; i < string.len; ++i) { - if (string.buf[i] == '%') { - final_len -= 2; - i += 2; - } - } - - colla_assert(final_len <= string.len); - - str_t out = { - .buf = alloc(arena, char, final_len + 1), - .len = final_len - }; - - usize k = 0; - - for (usize i = 0; i < string.len; ++i) { - if (string.buf[i] == '%') { - // skip % - ++i; - - unsigned int ch = 0; - int result = sscanf(string.buf + i, "%02X", &ch); - if (result != 1 || ch > UINT8_MAX) { - err("malformed url at %zu (%s)", i, string.buf + i); - return STR_EMPTY; - } - out.buf[k++] = (char)ch; - - // skip first char of hex - ++i; - } - else { - out.buf[k++] = string.buf[i]; - } - } - - return out; -} - -http_url_t http_split_url(strview_t url) { - http_url_t out = {0}; - - if (strv_starts_with_view(url, strv("https://"))) { - url = strv_remove_prefix(url, 8); - } - else if (strv_starts_with_view(url, strv("http://"))) { - url = strv_remove_prefix(url, 7); - } - - out.host = strv_sub(url, 0, strv_find(url, '/', 0)); - out.uri = strv_sub(url, out.host.len, SIZE_MAX); - - return out; -} - -// WEBSOCKETS /////////////////////// - -#define WEBSOCKET_MAGIC "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" -#define WEBSOCKET_HTTP_KEY "Sec-WebSocket-Key" - -bool websocket_init(arena_t scratch, socket_t socket, strview_t key) { - str_t full_key = str_fmt(&scratch, "%v" WEBSOCKET_MAGIC, key); - - 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 = base64_encode(&scratch, (buffer_t){ (u8 *)sha1_data.digest, sizeof(sha1_data.digest) }); - - str_t response = str_fmt( - &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 - ); - - int result = sk_send(socket, response.buf, (int)response.len); - return result != SOCKET_ERROR; -} - -buffer_t websocket_encode(arena_t *arena, strview_t message) { - int extra = 6; - if (message.len > UINT16_MAX) extra += sizeof(u64); - else if (message.len > UINT8_MAX) extra += sizeof(u16); - u8 *bytes = alloc(arena, u8, message.len + extra); - bytes[0] = 0b10000001; - bytes[1] = 0b10000000; - int offset = 2; - if (message.len > UINT16_MAX) { - bytes[1] |= 0b01111111; - u64 len = htonll(message.len); - memmove(bytes + 2, &len, sizeof(len)); - offset += sizeof(u64); - } - else if (message.len > UINT8_MAX) { - bytes[1] |= 0b01111110; - u16 len = htons((u16)message.len); - memmove(bytes + 2, &len, sizeof(len)); - offset += sizeof(u16); - } - else { - bytes[1] |= (u8)message.len; - } - - u32 mask = 0; - memmove(bytes + offset, &mask, sizeof(mask)); - offset += sizeof(mask); - memmove(bytes + offset, message.buf, message.len); - - return (buffer_t){ bytes, message.len + extra }; -} - -str_t websocket_decode(arena_t *arena, buffer_t message) { - str_t out = STR_EMPTY; - u8 *bytes = message.data; - - bool mask = bytes[1] & 0b10000000; - int offset = 2; - u64 msglen = bytes[1] & 0b01111111; - - // 16bit msg len - if (msglen == 126) { - u16 be_len = 0; - memmove(&be_len, bytes + 2, sizeof(be_len)); - msglen = ntohs(be_len); - offset += sizeof(u16); - } - // 64bit msg len - else if (msglen == 127) { - u64 be_len = 0; - memmove(&be_len, bytes + 2, sizeof(be_len)); - msglen = ntohll(be_len); - offset += sizeof(u64); - } - - if (msglen == 0) { - warn("message length = 0"); - } - else if (mask) { - u8 *decoded = alloc(arena, u8, msglen + 1); - u8 masks[4] = {0}; - memmove(masks, bytes + offset, sizeof(masks)); - offset += 4; - - for (u64 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; -} - - -// SHA 1 //////////////////////////// - -sha1_t sha1_init(void) { - return (sha1_t) { - .digest = { - 0x67452301, - 0xEFCDAB89, - 0x98BADCFE, - 0x10325476, - 0xC3D2E1F0, - }, - }; -} - -u32 sha1__left_rotate(u32 value, u32 count) { - return (value << count) ^ (value >> (32 - count)); -} - -void sha1__process_block(sha1_t *ctx) { - u32 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); - } - - u32 a = ctx->digest[0]; - u32 b = ctx->digest[1]; - u32 c = ctx->digest[2]; - u32 d = ctx->digest[3]; - u32 e = ctx->digest[4]; - - for (usize i = 0; i < 80; ++i) { - u32 f = 0; - u32 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; - } - u32 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, u8 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 u8 *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)); - - sha1_result_t result = {0}; - memcpy(result.digest, ctx->digest, sizeof(result.digest)); - return result; -} - -str_t sha1_str(arena_t *arena, sha1_t *ctx, const void *buf, usize len) { - sha1_result_t result = sha1(ctx, buf, len); - return str_fmt(arena, "%08x%08x%08x%08x%08x", result.digest[0], result.digest[1], result.digest[2], result.digest[3], result.digest[4]); -} - -// BASE 64 ////////////////////////// - -unsigned char b64__encoding_table[] = { - 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', - 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', - 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', - 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', - 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', - 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', - 'w', 'x', 'y', 'z', '0', '1', '2', '3', - '4', '5', '6', '7', '8', '9', '+', '/' -}; - -u8 b64__decoding_table[] = { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0, 63, - 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, - 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, - 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, - 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, - 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, - 44, 45, 46, 47, 48, 49, 50, 51, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -}; - -buffer_t base64_encode(arena_t *arena, buffer_t buffer) { - usize outlen = ((buffer.len + 2) / 3) * 4; - u8 *out = alloc(arena, u8, outlen); - - for (usize i = 0, j = 0; i < buffer.len;) { - u32 a = i < buffer.len ? buffer.data[i++] : 0; - u32 b = i < buffer.len ? buffer.data[i++] : 0; - u32 c = i < buffer.len ? buffer.data[i++] : 0; - - u32 triple = (a << 16) | (b << 8) | c; - - out[j++] = b64__encoding_table[(triple >> 18) & 0x3F]; - out[j++] = b64__encoding_table[(triple >> 12) & 0x3F]; - out[j++] = b64__encoding_table[(triple >> 6) & 0x3F]; - out[j++] = b64__encoding_table[(triple >> 0) & 0x3F]; - } - - usize mod = buffer.len % 3; - if (mod) { - mod = 3 - mod; - for (usize i = 0; i < mod; ++i) { - out[outlen - 1 - i] = '='; - } - } - - return (buffer_t){ - .data = out, - .len = outlen - }; -} - -buffer_t base64_decode(arena_t *arena, buffer_t buffer) { - u8 *out = arena->cur; - usize start = arena_tell(arena); - - for (usize i = 0; i < buffer.len; i += 4) { - u8 a = b64__decoding_table[buffer.data[i + 0]]; - u8 b = b64__decoding_table[buffer.data[i + 1]]; - u8 c = b64__decoding_table[buffer.data[i + 2]]; - u8 d = b64__decoding_table[buffer.data[i + 3]]; - - u32 triple = - ((u32)a << 18) | - ((u32)b << 12) | - ((u32)c << 6) | - ((u32)d); - - u8 *bytes = alloc(arena, u8, 3); - - bytes[0] = (triple >> 16) & 0xFF; - bytes[1] = (triple >> 8) & 0xFF; - bytes[2] = (triple >> 0) & 0xFF; - } - - usize spaces_count = 0; - for (isize i = buffer.len - 1; i >= 0; --i) { - if (buffer.data[i] == '=') { - spaces_count++; - } - else { - break; - } - } - - usize outlen = arena_tell(arena) - start; - - return (buffer_t){ - .data = out, - .len = outlen - spaces_count, - }; -} diff --git a/net.h b/net.h deleted file mode 100644 index 7734463..0000000 --- a/net.h +++ /dev/null @@ -1,194 +0,0 @@ -#ifndef COLLA_NET_HEADER -#define COLLA_NET_HEADER - -#include "core.h" -#include "str.h" -#include "os.h" - -void net_init(void); -void net_cleanup(void); -iptr net_get_last_error(void); - -typedef enum http_method_e { - HTTP_GET, - HTTP_POST, - HTTP_HEAD, - HTTP_PUT, - HTTP_DELETE, - HTTP_METHOD__COUNT, -} http_method_e; - -const char *http_get_method_string(http_method_e method); -const char *http_get_status_string(int status); - -typedef struct http_version_t http_version_t; -struct http_version_t { - u8 major; - u8 minor; -}; - -typedef struct http_header_t http_header_t; -struct http_header_t { - strview_t key; - strview_t value; - http_header_t *next; -}; - -typedef struct http_req_t http_req_t; -struct http_req_t { - http_method_e method; - http_version_t version; - http_header_t *headers; - strview_t url; - strview_t body; -}; - -typedef struct http_res_t http_res_t; -struct http_res_t { - int status_code; - http_version_t version; - http_header_t *headers; - strview_t body; -}; - -http_header_t *http_parse_headers(arena_t *arena, strview_t header_string); - -http_req_t http_parse_req(arena_t *arena, strview_t request); -http_res_t http_parse_res(arena_t *arena, strview_t response); - -str_t http_req_to_str(arena_t *arena, http_req_t *req); -str_t http_res_to_str(arena_t *arena, http_res_t *res); - -bool http_has_header(http_header_t *headers, strview_t key); -void http_set_header(http_header_t *headers, strview_t key, strview_t value); -strview_t http_get_header(http_header_t *headers, strview_t key); - -str_t http_make_url_safe(arena_t *arena, strview_t string); -str_t http_decode_url_safe(arena_t *arena, strview_t string); - -typedef struct { - strview_t host; - strview_t uri; -} http_url_t; - -http_url_t http_split_url(strview_t url); - -typedef struct { - arena_t *arena; - strview_t url; - http_version_t version; // 1.1 by default - http_method_e request_type; - http_header_t *headers; - int header_count; // optional, if set to 0 it traverses headers using h->next - strview_t body; -} http_request_desc_t; - -typedef void (*http_request_callback_fn)(strview_t chunk, void *udata); - -// arena_t *arena, strview_t url, [ http_header_t *headers, int header_count, strview_t body ] -#define http_get(arena, url, ...) http_request(&(http_request_desc_t){ arena, url, .request_type = HTTP_GET, .version = { 1, 1 }, __VA_ARGS__ }) - -http_res_t http_request(http_request_desc_t *request); -http_res_t http_request_cb(http_request_desc_t *request, http_request_callback_fn callback, void *userdata); - -// SOCKETS ////////////////////////// - -typedef uintptr_t socket_t; -typedef struct skpoll_t skpoll_t; - -#define SK_ADDR_LOOPBACK "127.0.0.1" -#define SK_ADDR_ANY "0.0.0.0" -#define SK_ADDR_BROADCAST "255.255.255.255" - -struct skpoll_t { - uintptr_t socket; - short events; - short revents; -}; - -#define SOCKET_ERROR (-1) - -typedef enum { - SOCK_TCP, - SOCK_UDP, -} sktype_e; - -typedef enum { - SOCK_EVENT_NONE, - SOCK_EVENT_READ = 1 << 0, - SOCK_EVENT_WRITE = 1 << 1, - SOCK_EVENT_ACCEPT = 1 << 2, - SOCK_EVENT_CONNECT = 1 << 3, - SOCK_EVENT_CLOSE = 1 << 4, -} skevent_e; - -// Opens a socket -socket_t sk_open(sktype_e type); -// Opens a socket using 'protocol', options are -// ip, icmp, ggp, tcp, egp, pup, udp, hmp, xns-idp, rdp -socket_t sk_open_protocol(const char *protocol); - -// Checks that a opened socket is valid, returns true on success -bool sk_is_valid(socket_t sock); - -// Closes a socket, returns true on success -bool sk_close(socket_t sock); - -// Fill out a sk_addrin_t structure with "ip" and "port" -// skaddrin_t sk_addrin_init(const char *ip, uint16_t port); - -// Associate a local address with a socket -bool sk_bind(socket_t sock, const char *ip, u16 port); - -// Place a socket in a state in which it is listening for an incoming connection -bool sk_listen(socket_t sock, int backlog); - -// Permits an incoming connection attempt on a socket -socket_t sk_accept(socket_t sock); - -// Connects to a server (e.g. "127.0.0.1" or "google.com") with a port(e.g. 1234), returns true on success -bool sk_connect(socket_t sock, const char *server, u16 server_port); - -// Sends data on a socket, returns true on success -int sk_send(socket_t sock, const void *buf, int len); -// Receives data from a socket, returns byte count on success, 0 on connection close or -1 on error -int sk_recv(socket_t sock, void *buf, int len); - -// Wait for an event on some sockets -int sk_poll(skpoll_t *to_poll, int num_to_poll, int timeout); - -oshandle_t sk_bind_event(socket_t sock, skevent_e event); -void sk_destroy_event(oshandle_t handle); -void sk_reset_event(oshandle_t handle); - -// WEBSOCKETS /////////////////////// - -bool websocket_init(arena_t scratch, socket_t socket, strview_t key); -buffer_t websocket_encode(arena_t *arena, strview_t message); -str_t websocket_decode(arena_t *arena, buffer_t message); - -// SHA 1 //////////////////////////// - -typedef struct sha1_t sha1_t; -struct sha1_t { - u32 digest[5]; - u8 block[64]; - usize block_index; - usize byte_count; -}; - -typedef struct sha1_result_t sha1_result_t; -struct sha1_result_t { - u32 digest[5]; -}; - -sha1_t sha1_init(void); -sha1_result_t sha1(sha1_t *ctx, const void *buf, usize len); -str_t sha1_str(arena_t *arena, sha1_t *ctx, const void *buf, usize len); - -// BASE 64 ////////////////////////// - -buffer_t base64_encode(arena_t *arena, buffer_t buffer); -buffer_t base64_decode(arena_t *arena, buffer_t buffer); - -#endif diff --git a/os.c b/os.c deleted file mode 100644 index cae0952..0000000 --- a/os.c +++ /dev/null @@ -1,235 +0,0 @@ -#include "os.h" - -#if COLLA_WIN - #include "win/os_win32.c" -#else - #error "platform not supported yet" -#endif - -// == HANDLE ==================================== - -oshandle_t os_handle_zero(void) { - return (oshandle_t){0}; -} - -bool os_handle_match(oshandle_t a, oshandle_t b) { - return a.data == b.data; -} - -bool os_handle_valid(oshandle_t handle) { - return !os_handle_match(handle, os_handle_zero()); -} - -// == 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; - } - return colour; -} - -void os_log_print(os_log_level_e level, const char *fmt, ...) { - va_list args; - va_start(args, fmt); - os_log_printv(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; - } - - 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); - - fmt_printv(fmt, args); - fmt_print("\n"); - - if (level == LOG_FATAL) { - os_abort(1); - } -} - -// == FILE ====================================== - -void os_file_split_path(strview_t path, strview_t *dir, strview_t *name, strview_t *ext) { - usize dir_lin = strv_rfind(path, '/', 0); - usize dir_win = strv_rfind(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 = strv_rfind(path, '.', 0); - - if (dir) { - *dir = strv_sub(path, 0, dir_pos); - } - if (name) { - *name = strv_sub(path, dir_pos ? dir_pos + 1 : 0, ext_pos); - } - if (ext) { - *ext = strv_sub(path, ext_pos, SIZE_MAX); - } -} - -bool os_file_putc(oshandle_t handle, char c) { - return os_file_write(handle, &c, sizeof(c)) == sizeof(c); -} - -bool os_file_puts(oshandle_t handle, strview_t str) { - return os_file_write(handle, str.buf, str.len) == str.len; -} - -bool os_file_print(arena_t scratch, oshandle_t handle, const char *fmt, ...) { - va_list args; - va_start(args, fmt); - bool result = os_file_printv(scratch, handle, fmt, args); - va_end(args); - return result; -} - -bool os_file_printv(arena_t scratch, oshandle_t handle, const char *fmt, va_list args) { - str_t s = str_fmtv(&scratch, fmt, args); - return os_file_puts(handle, strv(s)); -} - -usize os_file_read_buf(oshandle_t handle, buffer_t *buf) { - return os_file_read(handle, buf->data, buf->len); -} - -usize os_file_write_buf(oshandle_t handle, buffer_t buf) { - return os_file_write(handle, buf.data, buf.len); -} - -buffer_t os_file_read_all(arena_t *arena, strview_t path) { - oshandle_t fp = os_file_open(path, FILEMODE_READ); - if (!os_handle_valid(fp)) { - err("could not open file: %v", path); - return (buffer_t){0}; - } - buffer_t out = os_file_read_all_fp(arena, fp); - os_file_close(fp); - return out; -} - -buffer_t os_file_read_all_fp(arena_t *arena, oshandle_t handle) { - if (!os_handle_valid(handle)) return (buffer_t){0}; - buffer_t out = {0}; - - out.len = os_file_size(handle); - out.data = alloc(arena, u8, out.len); - usize read = os_file_read_buf(handle, &out); - - if (read != out.len) { - err("os_file_read_all_fp: read failed, should be %zu but is %zu", out.len, read); - arena_pop(arena, out.len); - return (buffer_t){0}; - } - - return out; -} - -str_t os_file_read_all_str(arena_t *arena, strview_t path) { - oshandle_t fp = os_file_open(path, FILEMODE_READ); - if (!os_handle_valid(fp)) { - err("could not open file %v: %v", path, os_get_error_string(os_get_last_error())); - return STR_EMPTY; - } - str_t out = os_file_read_all_str_fp(arena, fp); - os_file_close(fp); - return out; -} - -str_t os_file_read_all_str_fp(arena_t *arena, oshandle_t handle) { - if (!os_handle_valid(handle)) { - return STR_EMPTY; - } - - str_t out = STR_EMPTY; - - out.len = os_file_size(handle); - out.buf = alloc(arena, u8, out.len + 1); - - usize read = os_file_read(handle, out.buf, out.len); - if (read != out.len) { - err("os_file_read_all_str_fp: read failed, should be %zu but is %zu", out.len, read); - arena_pop(arena, out.len + 1); - return STR_EMPTY; - } - - return out; -} - -bool os_file_write_all(strview_t name, buffer_t buffer) { - oshandle_t fp = os_file_open(name, FILEMODE_WRITE); - bool result = os_file_write_all_fp(fp, buffer); - os_file_close(fp); - return result; -} - -bool os_file_write_all_fp(oshandle_t handle, buffer_t buffer) { - return os_file_write(handle, buffer.data, buffer.len) == buffer.len; -} - -bool os_file_write_all_str(strview_t name, strview_t data) { - oshandle_t fp = os_file_open(name, FILEMODE_WRITE); - bool result = os_file_write_all_str_fp(fp, data); - os_file_close(fp); - return result; -} - -bool os_file_write_all_str_fp(oshandle_t handle, strview_t data) { - return os_file_write(handle, data.buf, data.len) == data.len; -} - -u64 os_file_time(strview_t path) { - oshandle_t fp = os_file_open(path, FILEMODE_READ); - u64 result = os_file_time_fp(fp); - os_file_close(fp); - return result; -} - -bool os_file_has_changed(strview_t path, u64 last_change) { - u64 timestamp = os_file_time(path); - return timestamp > last_change; -} - -// == PROCESS =================================== - -bool os_run_cmd(arena_t scratch, os_cmd_t *cmd, os_env_t *optional_env) { - oshandle_t proc = os_run_cmd_async(scratch, cmd, optional_env); - return os_handle_valid(proc) ? os_process_wait(proc, OS_WAIT_INFINITE, NULL) : false; -} - -// == VMEM ====================================== - -usize os_pad_to_page(usize byte_count) { - usize page_size = os_get_system_info().page_size; - - if (byte_count == 0) { - return page_size; - } - - usize padding = page_size - (byte_count & (page_size - 1)); - if (padding == page_size) { - padding = 0; - } - return byte_count + padding; -} diff --git a/os.h b/os.h deleted file mode 100644 index c1208b5..0000000 --- a/os.h +++ /dev/null @@ -1,238 +0,0 @@ -#ifndef COLLA_OS_H -#define COLLA_OS_H - -#include "core.h" -#include "str.h" -#include "arena.h" - -#define OS_ARENA_SIZE (MB(1)) -#define OS_WAIT_INFINITE (0xFFFFFFFF) - -typedef struct oshandle_t oshandle_t; -struct oshandle_t { - uptr data; -}; - -typedef struct os_system_info_t os_system_info_t; -struct os_system_info_t { - u32 processor_count; - u64 page_size; - str_t machine_name; -}; - -void os_init(void); -void os_cleanup(void); -os_system_info_t os_get_system_info(void); -void os_abort(int code); - -iptr os_get_last_error(void); -// NOT thread safe -str_t os_get_error_string(iptr error); - -// == HANDLE ==================================== - -oshandle_t os_handle_zero(void); -bool os_handle_match(oshandle_t a, oshandle_t b); -bool os_handle_valid(oshandle_t handle); - -#define OS_MAX_WAITABLE_HANDLES 256 - -typedef enum { - OS_WAIT_FINISHED, - OS_WAIT_ABANDONED, - OS_WAIT_TIMEOUT, - OS_WAIT_FAILED -} os_wait_result_e; - -typedef struct { - os_wait_result_e result; - u32 index; -} os_wait_t; - -os_wait_t os_wait_on_handles(oshandle_t *handles, int count, bool wait_all, u32 milliseconds); - -// == LOGGING =================================== - -typedef enum os_log_level_e { - LOG_BASIC, - LOG_DEBUG, - LOG_INFO, - LOG_WARN, - LOG_ERR, - LOG_FATAL, -} os_log_level_e; - -typedef enum os_log_colour_e { - LOG_COL_BLACK = 0, - LOG_COL_BLUE = 1, - LOG_COL_GREEN = 2, - LOG_COL_CYAN = LOG_COL_BLUE | LOG_COL_GREEN, - LOG_COL_RED = 4, - LOG_COL_MAGENTA = LOG_COL_RED | LOG_COL_BLUE, - LOG_COL_YELLOW = LOG_COL_RED | LOG_COL_GREEN, - LOG_COL_GREY = LOG_COL_RED | LOG_COL_BLUE | LOG_COL_GREEN, - - LOG_COL_LIGHT = 8, - - LOG_COL_DARK_GREY = LOG_COL_BLACK | LOG_COL_LIGHT, - LOG_COL_LIGHT_BLUE = LOG_COL_BLUE | LOG_COL_LIGHT, - LOG_COL_LIGHT_GREEN = LOG_COL_GREEN | LOG_COL_LIGHT, - LOG_COL_LIGHT_CYAN = LOG_COL_CYAN | LOG_COL_LIGHT, - LOG_COL_LIGHT_RED = LOG_COL_RED | LOG_COL_LIGHT, - LOG_COL_LIGHT_MAGENTA = LOG_COL_MAGENTA | LOG_COL_LIGHT, - LOG_COL_LIGHT_YELLOW = LOG_COL_YELLOW | LOG_COL_LIGHT, - LOG_COL_WHITE = LOG_COL_GREY | LOG_COL_LIGHT, - - LOG_COL_RESET, - - LOG_COL__COUNT, -} os_log_colour_e; - -void os_log_print(os_log_level_e level, const char *fmt, ...); -void os_log_printv(os_log_level_e level, const char *fmt, va_list args); -void os_log_set_colour(os_log_colour_e colour); -void os_log_set_colour_bg(os_log_colour_e foreground, os_log_colour_e background); - -oshandle_t os_stdout(void); -oshandle_t os_stdin(void); - -#define print(...) fmt_print(__VA_ARGS__) -#define println(...) os_log_print(LOG_BASIC, __VA_ARGS__) -#define debug(...) os_log_print(LOG_DEBUG, __VA_ARGS__) -#define info(...) os_log_print(LOG_INFO, __VA_ARGS__) -#define warn(...) os_log_print(LOG_WARN, __VA_ARGS__) -#define err(...) os_log_print(LOG_ERR, __VA_ARGS__) -#define fatal(...) os_log_print(LOG_FATAL, __VA_ARGS__) - -// == FILE ====================================== - -typedef enum filemode_e { - FILEMODE_READ = 1 << 0, - FILEMODE_WRITE = 1 << 1, -} filemode_e; - -bool os_file_exists(strview_t filename); -bool os_dir_exists(strview_t folder); -bool os_file_or_dir_exists(strview_t path); -bool os_dir_create(strview_t folder); -tstr_t os_file_fullpath(arena_t *arena, strview_t filename); -void os_file_split_path(strview_t path, strview_t *dir, strview_t *name, strview_t *ext); -bool os_file_delete(strview_t path); -bool os_dir_delete(strview_t path); - -oshandle_t os_file_open(strview_t path, filemode_e mode); -void os_file_close(oshandle_t handle); - -bool os_file_putc(oshandle_t handle, char c); -bool os_file_puts(oshandle_t handle, strview_t str); -bool os_file_print(arena_t scratch, oshandle_t handle, const char *fmt, ...); -bool os_file_printv(arena_t scratch, oshandle_t handle, const char *fmt, va_list args); - -usize os_file_read(oshandle_t handle, void *buf, usize len); -usize os_file_write(oshandle_t handle, const void *buf, usize len); - -usize os_file_read_buf(oshandle_t handle, buffer_t *buf); -usize os_file_write_buf(oshandle_t handle, buffer_t buf); - -bool os_file_seek(oshandle_t handle, usize offset); -bool os_file_seek_end(oshandle_t handle); -void os_file_rewind(oshandle_t handle); -usize os_file_tell(oshandle_t handle); -usize os_file_size(oshandle_t handle); -bool os_file_is_finished(oshandle_t handle); - -buffer_t os_file_read_all(arena_t *arena, strview_t path); -buffer_t os_file_read_all_fp(arena_t *arena, oshandle_t handle); - -str_t os_file_read_all_str(arena_t *arena, strview_t path); -str_t os_file_read_all_str_fp(arena_t *arena, oshandle_t handle); - -bool os_file_write_all(strview_t name, buffer_t buffer); -bool os_file_write_all_fp(oshandle_t handle, buffer_t buffer); - -bool os_file_write_all_str(strview_t name, strview_t data); -bool os_file_write_all_str_fp(oshandle_t handle, strview_t data); - -u64 os_file_time(strview_t path); -u64 os_file_time_fp(oshandle_t handle); -bool os_file_has_changed(strview_t path, u64 last_change); - -// == DIR WALKER ================================ - -typedef enum dir_type_e { - DIRTYPE_FILE, - DIRTYPE_DIR, -} dir_type_e; - -typedef struct dir_entry_t dir_entry_t; -struct dir_entry_t { - str_t name; - dir_type_e type; - usize file_size; -}; - -#define dir_foreach(arena, it, dir) for (dir_entry_t *it = os_dir_next(arena, dir); it; it = os_dir_next(arena, dir)) - -typedef struct dir_t dir_t; -dir_t *os_dir_open(arena_t *arena, strview_t path); -bool os_dir_is_valid(dir_t *dir); -// optional, only call this if you want to return before os_dir_next returns NULL -void os_dir_close(dir_t *dir); - -dir_entry_t *os_dir_next(arena_t *arena, dir_t *dir); - -// == PROCESS =================================== - -typedef struct os_env_t os_env_t; -typedef strv_list_t os_cmd_t; -#define os_make_cmd(...) &(os_cmd_t){ .items = (strview_t[]){ __VA_ARGS__ }, .count = arrlen(((strview_t[]){ __VA_ARGS__ })) } - -void os_set_env_var(arena_t scratch, strview_t key, strview_t value); -str_t os_get_env_var(arena_t *arena, strview_t key); -os_env_t *os_get_env(arena_t *arena); -bool os_run_cmd(arena_t scratch, os_cmd_t *cmd, os_env_t *optional_env); -oshandle_t os_run_cmd_async(arena_t scratch, os_cmd_t *cmd, os_env_t *optional_env); -bool os_process_wait(oshandle_t proc, uint time, int *out_exit); - -// == MEMORY ==================================== - -void *os_alloc(usize size); -void os_free(void *ptr); - -void *os_reserve(usize size, usize *out_padded_size); -bool os_commit(void *ptr, usize num_of_pages); -bool os_release(void *ptr, usize size); -usize os_pad_to_page(usize byte_count); - -// == THREAD ==================================== - -typedef int (thread_func_t)(u64 thread_id, void *userdata); - -oshandle_t os_thread_launch(thread_func_t func, void *userdata); -bool os_thread_detach(oshandle_t thread); -bool os_thread_join(oshandle_t thread, int *code); - -u64 os_thread_get_id(oshandle_t thread); - -// == MUTEX ===================================== - -oshandle_t os_mutex_create(void); -void os_mutex_free(oshandle_t mutex); -void os_mutex_lock(oshandle_t mutex); -void os_mutex_unlock(oshandle_t mutex); -bool os_mutex_try_lock(oshandle_t mutex); - -#if !COLLA_NO_CONDITION_VARIABLE -// == CONDITION VARIABLE ======================== - -oshandle_t os_cond_create(void); -void os_cond_free(oshandle_t cond); - -void os_cond_signal(oshandle_t cond); -void os_cond_broadcast(oshandle_t cond); - -void os_cond_wait(oshandle_t cond, oshandle_t mutex, int milliseconds); - -#endif - -#endif diff --git a/parsers.c b/parsers.c deleted file mode 100644 index 57b3fc5..0000000 --- a/parsers.c +++ /dev/null @@ -1,1106 +0,0 @@ -#include "parsers.h" - -#include - -#include "os.h" -#include "darr.h" - -// == INI ============================================ - -void ini__parse(arena_t *arena, ini_t *ini, const iniopt_t *options); - -ini_t ini_parse(arena_t *arena, strview_t filename, iniopt_t *opt) { - oshandle_t fp = os_file_open(filename, FILEMODE_READ); - ini_t out = ini_parse_fp(arena, fp, opt); - os_file_close(fp); - return out; -} - -ini_t ini_parse_fp(arena_t *arena, oshandle_t file, iniopt_t *opt) { - str_t data = os_file_read_all_str_fp(arena, file); - return ini_parse_str(arena, strv(data), opt); -} - -ini_t ini_parse_str(arena_t *arena, strview_t str, iniopt_t *opt) { - ini_t out = { - .text = str, - .tables = NULL, - }; - ini__parse(arena, &out, opt); - return out; -} - -bool ini_is_valid(ini_t *ini) { - return ini && !strv_is_empty(ini->text); -} - -initable_t *ini_get_table(ini_t *ini, strview_t name) { - initable_t *t = ini ? ini->tables : NULL; - while (t) { - if (strv_equals(t->name, name)) { - return t; - } - t = t->next; - } - return NULL; -} - -inivalue_t *ini_get(initable_t *table, strview_t key) { - inivalue_t *v = table ? table->values : NULL; - while (v) { - if (strv_equals(v->key, key)) { - return v; - } - v = v->next; - } - return NULL; -} - -iniarray_t ini_as_arr(arena_t *arena, inivalue_t *value, char delim) { - strview_t v = value ? value->value : STRV_EMPTY; - if (!delim) delim = ' '; - - strview_t *beg = (strview_t *)arena->cur; - usize count = 0; - - usize start = 0; - for (usize i = 0; i < v.len; ++i) { - if (v.buf[i] == delim) { - strview_t arrval = strv_trim(strv_sub(v, start, i)); - if (!strv_is_empty(arrval)) { - strview_t *newval = alloc(arena, strview_t); - *newval = arrval; - ++count; - } - start = i + 1; - } - } - - strview_t last = strv_trim(strv_sub(v, start, SIZE_MAX)); - if (!strv_is_empty(last)) { - strview_t *newval = alloc(arena, strview_t); - *newval = last; - ++count; - } - - return (iniarray_t){ - .values = beg, - .count = count, - }; -} - -u64 ini_as_uint(inivalue_t *value) { - strview_t v = value ? value->value : STRV_EMPTY; - instream_t in = istr_init(v); - u64 out = 0; - if (!istr_get_u64(&in, &out)) { - out = 0; - } - return out; -} - -i64 ini_as_int(inivalue_t *value) { - strview_t v = value ? value->value : STRV_EMPTY; - instream_t in = istr_init(v); - i64 out = 0; - if (!istr_get_i64(&in, &out)) { - out = 0; - } - return out; -} - -double ini_as_num(inivalue_t *value) { - strview_t v = value ? value->value : STRV_EMPTY; - instream_t in = istr_init(v); - double out = 0; - if (!istr_get_num(&in, &out)) { - out = 0; - } - return out; -} - -bool ini_as_bool(inivalue_t *value) { - strview_t v = value ? value->value : STRV_EMPTY; - instream_t in = istr_init(v); - bool out = 0; - if (!istr_get_bool(&in, &out)) { - out = 0; - } - return out; -} - -void ini_pretty_print(ini_t *ini, const ini_pretty_opts_t *options) { - ini_pretty_opts_t opt = {0}; - if (options) { - memmove(&opt, options, sizeof(ini_pretty_opts_t)); - } - - if (!os_handle_valid(opt.custom_target)) { - opt.custom_target = os_stdout(); - } - - if (!opt.use_custom_colours) { - os_log_colour_e default_col[INI_PRETTY_COLOUR__COUNT] = { - LOG_COL_YELLOW, // INI_PRETTY_COLOUR_KEY, - LOG_COL_GREEN, // INI_PRETTY_COLOUR_VALUE, - LOG_COL_WHITE, // INI_PRETTY_COLOUR_DIVIDER, - LOG_COL_RED, // INI_PRETTY_COLOUR_TABLE, - }; - memmove(opt.colours, default_col, sizeof(default_col)); - } - - for_each (t, ini->tables) { - if (!strv_equals(t->name, INI_ROOT)) { - os_log_set_colour(opt.colours[INI_PRETTY_COLOUR_TABLE]); - os_file_puts(opt.custom_target, strv("[")); - os_file_puts(opt.custom_target, t->name); - os_file_puts(opt.custom_target, strv("]\n")); - } - - for_each (pair, t->values) { - if (strv_is_empty(pair->key) || strv_is_empty(pair->value)) continue; - os_log_set_colour(opt.colours[INI_PRETTY_COLOUR_KEY]); - os_file_puts(opt.custom_target, pair->key); - - os_log_set_colour(opt.colours[INI_PRETTY_COLOUR_DIVIDER]); - os_file_puts(opt.custom_target, strv(" = ")); - - os_log_set_colour(opt.colours[INI_PRETTY_COLOUR_VALUE]); - os_file_puts(opt.custom_target, pair->value); - - os_file_puts(opt.custom_target, strv("\n")); - } - } - - os_log_set_colour(LOG_COL_RESET); -} - -///// ini-private //////////////////////////////////// - -iniopt_t ini__get_options(const iniopt_t *options) { - iniopt_t out = { - .key_value_divider = '=', - .comment_vals = strv(";#"), - }; - -#define SETOPT(v) out.v = options->v ? options->v : out.v - - if (options) { - SETOPT(key_value_divider); - SETOPT(merge_duplicate_keys); - SETOPT(merge_duplicate_tables); - out.comment_vals = strv_is_empty(options->comment_vals) ? out.comment_vals : options->comment_vals; - } - -#undef SETOPT - - return out; -} - - -void ini__add_value(arena_t *arena, initable_t *table, instream_t *in, iniopt_t *opts) { - assert(table); - - strview_t key = strv_trim(istr_get_view(in, opts->key_value_divider)); - istr_skip(in, 1); - - strview_t value = strv_trim(istr_get_view(in, '\n')); - usize comment_pos = strv_find_either(value, opts->comment_vals, 0); - if (comment_pos != STR_NONE) { - value = strv_sub(value, 0, comment_pos); - } - istr_skip(in, 1); - inivalue_t *newval = NULL; - - if (opts->merge_duplicate_keys) { - newval = table->values; - while (newval) { - if (strv_equals(newval->key, key)) { - break; - } - newval = newval->next; - } - } - - if (newval) { - newval->value = value; - } - else { - newval = alloc(arena, inivalue_t); - newval->key = key; - newval->value = value; - - if (!table->values) { - table->values = newval; - } - else { - table->tail->next = newval; - } - - table->tail = newval; - } -} - -void ini__add_table(arena_t *arena, ini_t *ctx, instream_t *in, iniopt_t *options) { - istr_skip(in, 1); // skip [ - strview_t name = istr_get_view(in, ']'); - istr_skip(in, 1); // skip ] - initable_t *table = NULL; - - if (options->merge_duplicate_tables) { - table = ctx->tables; - while (table) { - if (strv_equals(table->name, name)) { - break; - } - table = table->next; - } - } - - if (!table) { - table = alloc(arena, initable_t); - - table->name = name; - - if (!ctx->tables) { - ctx->tables = table; - } - else { - ctx->tail->next = table; - } - - ctx->tail = table; - } - - istr_ignore_and_skip(in, '\n'); - while (!istr_is_finished(in)) { - switch (istr_peek(in)) { - case '\n': // fallthrough - case '\r': - return; - case '#': // fallthrough - case ';': - istr_ignore_and_skip(in, '\n'); - break; - default: - ini__add_value(arena, table, in, options); - break; - } - } -} - -void ini__parse(arena_t *arena, ini_t *ini, const iniopt_t *options) { - iniopt_t opts = ini__get_options(options); - - initable_t *root = alloc(arena, initable_t); - root->name = INI_ROOT; - ini->tables = root; - ini->tail = root; - - instream_t in = istr_init(ini->text); - - while (!istr_is_finished(&in)) { - istr_skip_whitespace(&in); - switch (istr_peek(&in)) { - case '[': - ini__add_table(arena, ini, &in, &opts); - break; - case '#': // fallthrough - case ';': - istr_ignore_and_skip(&in, '\n'); - break; - default: - ini__add_value(arena, ini->tables, &in, &opts); - break; - } - } -} - -// == JSON =========================================== - -bool json__parse_obj(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); - return json_parse_str(arena, strv(data), flags); -} - -json_t *json_parse_str(arena_t *arena, strview_t str, jsonflags_e flags) { - arena_t before = *arena; - - json_t *root = alloc(arena, json_t); - root->type = JSON_OBJECT; - - instream_t in = istr_init(str); - - if (!json__parse_obj(arena, &in, flags, &root->object)) { - // reset arena - *arena = before; - return NULL; - } - - return root; -} - -json_t *json_get(json_t *node, strview_t key) { - if (!node) return NULL; - - if (node->type != JSON_OBJECT) { - return NULL; - } - - node = node->object; - - while (node) { - if (strv_equals(node->key, key)) { - return node; - } - node = node->next; - } - - return NULL; -} - -void json__pretty_print_value(json_t *value, int indent, const json_pretty_opts_t *options); - -void json_pretty_print(json_t *root, const json_pretty_opts_t *options) { - json_pretty_opts_t default_options = { 0 }; - if (options) { - memmove(&default_options, options, sizeof(json_pretty_opts_t)); - } - - if (!os_handle_valid(default_options.custom_target)) { - default_options.custom_target = os_stdout(); - } - if (!default_options.use_custom_colours) { - os_log_colour_e default_col[JSON_PRETTY_COLOUR__COUNT] = { - LOG_COL_YELLOW, // JSON_PRETTY_COLOUR_KEY, - LOG_COL_CYAN, // JSON_PRETTY_COLOUR_STRING, - LOG_COL_BLUE, // JSON_PRETTY_COLOUR_NUM, - LOG_COL_DARK_GREY, // JSON_PRETTY_COLOUR_NULL, - LOG_COL_GREEN, // JSON_PRETTY_COLOUR_TRUE, - LOG_COL_RED, // JSON_PRETTY_COLOUR_FALSE, - }; - memmove(default_options.colours, default_col, sizeof(default_col)); - } - - json__pretty_print_value(root, 0, &default_options); - os_file_putc(default_options.custom_target, '\n'); -} - -///// json-private /////////////////////////////////// - -#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; - } - istr_rewind_n(in, 1); - return false; -} - -bool json__is_value_finished(instream_t *in) { - usize old_pos = istr_tell(in); - - istr_skip_whitespace(in); - switch(istr_peek(in)) { - case '}': // fallthrough - case ']': // fallthrough - case ',': - return true; - } - - in->cur = in->beg + old_pos; - return false; -} - -bool json__parse_null(instream_t *in) { - strview_t null_view = istr_get_view_len(in, 4); - bool is_valid = true; - - if (!strv_equals(null_view, strv("null"))) { - is_valid = false; - } - - if (!json__is_value_finished(in)) { - is_valid = false; - } - - return is_valid; -} - -bool json__parse_array(arena_t *arena, instream_t *in, jsonflags_e flags, json_t **out) { - json_t *head = NULL; - - if (!json__ensure('[')) { - goto fail; - } - - istr_skip_whitespace(in); - - // if it is an empty array - if (istr_peek(in) == ']') { - istr_skip(in, 1); - goto success; - } - - if (!json__parse_value(arena, in, flags, &head)) { - goto fail; - } - - json_t *cur = head; - - while (true) { - istr_skip_whitespace(in); - switch (istr_get(in)) { - case ']': - goto success; - case ',': - { - istr_skip_whitespace(in); - // trailing comma - if (istr_peek(in) == ']') { - if (flags & JSON_NO_TRAILING_COMMAS) { - goto fail; - } - else { - continue; - } - } - - json_t *next = NULL; - if (!json__parse_value(arena, in, flags, &next)) { - goto fail; - } - cur->next = next; - next->prev = cur; - cur = next; - break; - } - default: - istr_rewind_n(in, 1); - goto fail; - } - } - -success: - *out = head; - return true; -fail: - *out = NULL; - return false; -} - -bool json__parse_string(arena_t *arena, instream_t *in, strview_t *out) { - COLLA_UNUSED(arena); - *out = STRV_EMPTY; - - istr_skip_whitespace(in); - - if (!json__ensure('"')) { - goto fail; - } - - const char *from = in->cur; - - for (; !istr_is_finished(in) && *in->cur != '"'; ++in->cur) { - if (istr_peek(in) == '\\') { - ++in->cur; - } - } - - usize len = in->cur - from; - - *out = strv(from, len); - - if (!json__ensure('"')) { - goto fail; - } - - return true; -fail: - return false; -} - -bool json__parse_pair(arena_t *arena, instream_t *in, jsonflags_e flags, json_t **out) { - strview_t key = {0}; - if (!json__parse_string(arena, in, &key)) { - goto fail; - } - - // skip preamble - istr_skip_whitespace(in); - if (!json__ensure(':')) { - goto fail; - } - - if (!json__parse_value(arena, in, flags, out)) { - goto fail; - } - - (*out)->key = key; - return true; - -fail: - *out = NULL; - return false; -} - -bool json__parse_obj(arena_t *arena, instream_t *in, jsonflags_e flags, json_t **out) { - if (!json__ensure('{')) { - goto fail; - } - - istr_skip_whitespace(in); - - // if it is an empty object - if (istr_peek(in) == '}') { - istr_skip(in, 1); - *out = NULL; - return true; - } - - json_t *head = NULL; - if (!json__parse_pair(arena, in, flags, &head)) { - goto fail; - } - json_t *cur = head; - - while (true) { - istr_skip_whitespace(in); - switch (istr_get(in)) { - case '}': - goto success; - case ',': - { - istr_skip_whitespace(in); - // trailing commas - if (!(flags & JSON_NO_TRAILING_COMMAS) && istr_peek(in) == '}') { - goto success; - } - - json_t *next = NULL; - if (!json__parse_pair(arena, in, flags, &next)) { - goto fail; - } - cur->next = next; - next->prev = cur; - cur = next; - break; - } - default: - istr_rewind_n(in, 1); - goto fail; - } - } - -success: - *out = head; - return true; -fail: - *out = NULL; - return false; -} - -bool json__parse_value(arena_t *arena, instream_t *in, jsonflags_e flags, json_t **out) { - json_t *val = alloc(arena, json_t); - - istr_skip_whitespace(in); - - switch (istr_peek(in)) { - // object - case '{': - if (!json__parse_obj(arena, in, flags, &val->object)) { - goto fail; - } - val->type = JSON_OBJECT; - break; - // array - case '[': - if (!json__parse_array(arena, in, flags, &val->array)) { - goto fail; - } - val->type = JSON_ARRAY; - break; - // string - case '"': - if (!json__parse_string(arena, in, &val->string)) { - goto fail; - } - val->type = JSON_STRING; - break; - // boolean - case 't': // fallthrough - case 'f': - if (!istr_get_bool(in, &val->boolean)) { - goto fail; - } - val->type = JSON_BOOL; - break; - // null - case 'n': - if (!json__parse_null(in)) { - goto fail; - } - val->type = JSON_NULL; - break; - // comment - case '/': - err("TODO comments"); - break; - // number - default: - if (!istr_get_num(in, &val->number)) { - goto fail; - } - val->type = JSON_NUMBER; - break; - } - - *out = val; - return true; -fail: - *out = NULL; - return false; -} - -#undef json__ensure - -#define JSON_PRETTY_INDENT(ind) for (int i = 0; i < ind; ++i) os_file_puts(options->custom_target, strv(" ")) - -void json__pretty_print_value(json_t *value, int indent, const json_pretty_opts_t *options) { - switch (value->type) { - case JSON_NULL: - os_log_set_colour(options->colours[JSON_PRETTY_COLOUR_NULL]); - os_file_puts(options->custom_target, strv("null")); - os_log_set_colour(LOG_COL_RESET); - break; - case JSON_ARRAY: - os_file_puts(options->custom_target, strv("[\n")); - for_each (node, value->array) { - JSON_PRETTY_INDENT(indent + 1); - json__pretty_print_value(node, indent + 1, options); - if (node->next) { - os_file_putc(options->custom_target, ','); - } - os_file_putc(options->custom_target, '\n'); - } - JSON_PRETTY_INDENT(indent); - os_file_putc(options->custom_target, ']'); - break; - case JSON_STRING: - os_log_set_colour(options->colours[JSON_PRETTY_COLOUR_STRING]); - os_file_putc(options->custom_target, '\"'); - os_file_puts(options->custom_target, value->string); - os_file_putc(options->custom_target, '\"'); - os_log_set_colour(LOG_COL_RESET); - break; - case JSON_NUMBER: - { - os_log_set_colour(options->colours[JSON_PRETTY_COLOUR_NUM]); - u8 scratchbuf[256]; - arena_t scratch = arena_make(ARENA_STATIC, sizeof(scratchbuf), scratchbuf); - os_file_print( - scratch, - options->custom_target, - "%g", - value->number - ); - os_log_set_colour(LOG_COL_RESET); - break; - } - case JSON_BOOL: - os_log_set_colour(options->colours[value->boolean ? JSON_PRETTY_COLOUR_TRUE : JSON_PRETTY_COLOUR_FALSE]); - os_file_puts(options->custom_target, value->boolean ? strv("true") : strv("false")); - os_log_set_colour(LOG_COL_RESET); - break; - case JSON_OBJECT: - os_file_puts(options->custom_target, strv("{\n")); - for_each(node, value->object) { - JSON_PRETTY_INDENT(indent + 1); - os_log_set_colour(options->colours[JSON_PRETTY_COLOUR_KEY]); - os_file_putc(options->custom_target, '\"'); - os_file_puts(options->custom_target, node->key); - os_file_putc(options->custom_target, '\"'); - os_log_set_colour(LOG_COL_RESET); - - os_file_puts(options->custom_target, strv(": ")); - - json__pretty_print_value(node, indent + 1, options); - if (node->next) { - os_file_putc(options->custom_target, ','); - } - os_file_putc(options->custom_target, '\n'); - } - JSON_PRETTY_INDENT(indent); - os_file_putc(options->custom_target, '}'); - break; - } -} - -#undef JSON_PRETTY_INDENT - - -// == XML ============================================ - -xmltag_t *xml__parse_tag(arena_t *arena, instream_t *in); - -xml_t xml_parse(arena_t *arena, strview_t filename) { - str_t str = os_file_read_all_str(arena, filename); - return xml_parse_str(arena, strv(str)); -} - -xml_t xml_parse_str(arena_t *arena, strview_t xmlstr) { - xml_t out = { - .text = xmlstr, - .root = alloc(arena, xmltag_t), - }; - - instream_t in = istr_init(xmlstr); - - while (!istr_is_finished(&in)) { - xmltag_t *tag = xml__parse_tag(arena, &in); - - if (out.tail) out.tail->next = tag; - else out.root->child = tag; - - out.tail = tag; - } - - return out; -} - -xmltag_t *xml_get_tag(xmltag_t *parent, strview_t key, bool recursive) { - xmltag_t *t = parent ? parent->child : NULL; - while (t) { - if (strv_equals(key, t->key)) { - return t; - } - if (recursive && t->child) { - xmltag_t *out = xml_get_tag(t, key, recursive); - if (out) { - return out; - } - } - t = t->next; - } - return NULL; -} - -strview_t xml_get_attribute(xmltag_t *tag, strview_t key) { - xmlattr_t *a = tag ? tag->attributes : NULL; - while (a) { - if (strv_equals(key, a->key)) { - return a->value; - } - a = a->next; - } - return STRV_EMPTY; -} - -///// xml-private //////////////////////////////////// - -xmlattr_t *xml__parse_attr(arena_t *arena, instream_t *in) { - if (istr_peek(in) != ' ') { - return NULL; - } - - strview_t key = strv_trim(istr_get_view(in, '=')); - istr_skip(in, 1); // skip = - strview_t val = strv_trim(istr_get_view_either(in, strv("\">"))); - if (istr_peek(in) != '>') { - istr_skip(in, 1); // skip " - } - - if (strv_is_empty(key) || strv_is_empty(val)) { - warn("key or value empty"); - return NULL; - } - - xmlattr_t *attr = alloc(arena, xmlattr_t); - attr->key = key; - attr->value = val; - return attr; -} - -xmltag_t *xml__parse_tag(arena_t *arena, instream_t *in) { - istr_skip_whitespace(in); - - // we're either parsing the body, or we have finished the object - if (istr_peek(in) != '<' || istr_peek_next(in) == '/') { - return NULL; - } - - istr_skip(in, 1); // skip < - - // meta tag, we don't care about these - if (istr_peek(in) == '?') { - istr_ignore_and_skip(in, '\n'); - return NULL; - } - - xmltag_t *tag = alloc(arena, xmltag_t); - - tag->key = strv_trim(istr_get_view_either(in, strv(" >"))); - - xmlattr_t *attr = xml__parse_attr(arena, in); - while (attr) { - attr->next = tag->attributes; - tag->attributes = attr; - attr = xml__parse_attr(arena, in); - } - - // this tag does not have children, return - if (istr_peek(in) == '/') { - istr_skip(in, 2); // skip / and > - return tag; - } - - istr_skip(in, 1); // skip > - - xmltag_t *child = xml__parse_tag(arena, in); - while (child) { - if (tag->tail) { - tag->tail->next = child; - tag->tail = child; - } - else { - tag->child = tag->tail = child; - } - child = xml__parse_tag(arena, in); - } - - // parse content - istr_skip_whitespace(in); - tag->content = istr_get_view(in, '<'); - - // closing tag - istr_skip(in, 2); // skip < and / - strview_t closing = strv_trim(istr_get_view(in, '>')); - if (!strv_equals(tag->key, closing)) { - warn("opening and closing tags are different!: (%v) != (%v)", tag->key, closing); - } - istr_skip(in, 1); // skip > - return tag; -} - -// == HTML =========================================== - -htmltag_t *html__parse_tag(arena_t *arena, instream_t *in); - -html_t html_parse(arena_t *arena, strview_t filename) { - str_t str = os_file_read_all_str(arena, filename); - return html_parse_str(arena, strv(str)); -} - -html_t html_parse_str(arena_t *arena, strview_t str) { - html_t out = { - .text = str, - .root = alloc(arena, xmltag_t), - }; - - instream_t in = istr_init(str); - - while (!istr_is_finished(&in)) { - htmltag_t *tag = html__parse_tag(arena, &in); - - if (out.tail) out.tail->next = tag; - else out.root->children = tag; - - out.tail = tag; - } - - return out; -} - -htmltag_t *html__get_tag_internal(htmltag_t *parent, str_t key, bool recursive) { - htmltag_t *t = parent ? parent->children : NULL; - while (t) { - if (str_equals(key, t->key)) { - return t; - } - if (recursive && t->children) { - htmltag_t *out = html__get_tag_internal(t, key, recursive); - if (out) { - return out; - } - } - t = t->next; - } - return NULL; -} - -htmltag_t *html_get_tag(htmltag_t *parent, strview_t key, bool recursive) { - u8 tmpbuf[KB(1)]; - arena_t scratch = arena_make(ARENA_STATIC, sizeof(tmpbuf), tmpbuf); - str_t upper = strv_to_upper(&scratch, key); - return html__get_tag_internal(parent, upper, recursive); -} - -strview_t html_get_attribute(htmltag_t *tag, strview_t key) { - xmlattr_t *a = tag ? tag->attributes : NULL; - while (a) { - if (strv_equals(key, a->key)) { - return a->value; - } - a = a->next; - } - return STRV_EMPTY; -} - -///// html-private /////////////////////////////////// - -/* - -special rules: -

tag does not need to be closed when followed by - address, article, aside, blockquote, details, dialog, div, - dl, fieldset, figcaption, figure, footer, form, h1, h2, h3, - h4, h5, h6, header, hgroup, hr, main, menu, nav, ol, p, pre, - search, section, table, or ul -*/ - -strview_t html_closing_p_tags[] = { - cstrv("ADDRESS"), - cstrv("ARTICLE"), - cstrv("ASIDE"), - cstrv("BLOCKQUOTE"), - cstrv("DETAILS"), - cstrv("DIALOG"), - cstrv("DIV"), - cstrv("DL"), - cstrv("FIELDSET"), - cstrv("FIGCAPTION"), - cstrv("FIGURE"), - cstrv("FOOTER"), - cstrv("FORM"), - cstrv("H1"), - cstrv("H2"), - cstrv("H3"), - cstrv("H4"), - cstrv("H5"), - cstrv("H6"), - cstrv("HEADER"), - cstrv("HGROUP"), - cstrv("HR"), - cstrv("MAIN"), - cstrv("MENU"), - cstrv("NAV"), - cstrv("OL"), - cstrv("P"), - cstrv("PRE"), - cstrv("SEARCH"), - cstrv("SECTION"), - cstrv("TABLE"), - cstrv("UL"), -}; - -bool html__closes_p_tag(strview_t tag) { - for (int i = 0; i < arrlen(html_closing_p_tags); ++i) { - if (strv_equals(html_closing_p_tags[i], tag)) { - return true; - } - } - - return false; -} - -htmltag_t *html__parse_tag(arena_t *arena, instream_t *in) { - istr_skip_whitespace(in); - - // we're either parsing the body, or we have finished the object - if (istr_peek(in) != '<' || istr_peek_next(in) == '/') { - return NULL; - } - - istr_skip(in, 1); // skip < - - // meta tag, we don't care about these - if (istr_peek(in) == '?') { - istr_ignore_and_skip(in, '\n'); - return NULL; - } - - htmltag_t *tag = alloc(arena, htmltag_t); - - tag->key = strv_to_upper( - arena, - strv_trim(istr_get_view_either(in, strv(" >"))) - ); - - xmlattr_t *attr = xml__parse_attr(arena, in); - while (attr) { - attr->next = tag->attributes; - tag->attributes = attr; - attr = xml__parse_attr(arena, in); - } - - // this tag does not have children, return - if (istr_peek(in) == '/') { - istr_skip(in, 2); // skip / and > - return tag; - } - - istr_skip(in, 1); // skip > - - bool is_p_tag = strv_equals(strv(tag->key), strv("P")); - while (!istr_is_finished(in)) { - istr_skip_whitespace(in); - strview_t content = strv_trim(istr_get_view(in, '<')); - - // skip < - istr_skip(in, 1); - - bool is_closing = istr_peek(in) == '/'; - if (is_closing) { - istr_skip(in, 1); - } - - - arena_t scratch = *arena; - instream_t scratch_in = *in; - str_t next_tag = strv_to_upper(&scratch, strv_trim(istr_get_view_either(&scratch_in, strv(" >")))); - - // rewind < - istr_rewind_n(in, 1); - - // if we don't have children, it means this is the only content - // otherwise, it means this is content in-between other tags, - // if so: create an empty tag with the content and add it as a child - if (!strv_is_empty(content)) { - if (tag->children == NULL) { - tag->content = content; - } - else { - htmltag_t *empty = alloc(arena, htmltag_t); - empty->content = content; - olist_push(tag->children, tag->tail, empty); - } - } - - bool close_tag = - (is_closing && str_equals(tag->key, next_tag)) || - (is_p_tag && html__closes_p_tag(strv(next_tag))); - - if (close_tag) { - if (is_closing) { - istr_skip(in, 2 + next_tag.len); - } - break; - } - - htmltag_t *child = html__parse_tag(arena, in); - if (tag->tail) { - (tag->tail)->next = (child); - (tag->tail) = (child); - } - else { - (tag->children) = (tag->tail) = (child); - } - } - - return tag; -} diff --git a/parsers.h b/parsers.h deleted file mode 100644 index f793151..0000000 --- a/parsers.h +++ /dev/null @@ -1,199 +0,0 @@ -#ifndef COLLA_PARSERS_H -#define COLLA_PARSERS_H - -#include "core.h" -#include "os.h" -#include "str.h" - -// == INI ============================================ - -typedef struct inivalue_t inivalue_t; -struct inivalue_t { - strview_t key; - strview_t value; - inivalue_t *next; -}; - -typedef struct initable_t initable_t; -struct initable_t { - strview_t name; - inivalue_t *values; - inivalue_t *tail; - initable_t *next; -}; - -typedef struct ini_t ini_t; -struct ini_t { - strview_t text; - initable_t *tables; - initable_t *tail; -}; - -typedef struct iniopt_t iniopt_t; -struct iniopt_t { - bool merge_duplicate_tables; // default false - bool merge_duplicate_keys; // default false - char key_value_divider; // default = - strview_t comment_vals; // default ;# -}; - -typedef struct iniarray_t iniarray_t; -struct iniarray_t { - strview_t *values; - usize count; -}; - -#define INI_ROOT strv("__ROOT__") - -ini_t ini_parse(arena_t *arena, strview_t filename, iniopt_t *opt); -ini_t ini_parse_fp(arena_t *arena, oshandle_t file, iniopt_t *opt); -ini_t ini_parse_str(arena_t *arena, strview_t str, iniopt_t *opt); - -bool ini_is_valid(ini_t *ini); - -initable_t *ini_get_table(ini_t *ini, strview_t name); -inivalue_t *ini_get(initable_t *table, strview_t key); - -iniarray_t ini_as_arr(arena_t *arena, inivalue_t *value, char delim); -u64 ini_as_uint(inivalue_t *value); -i64 ini_as_int(inivalue_t *value); -double ini_as_num(inivalue_t *value); -bool ini_as_bool(inivalue_t *value); - -typedef enum { - INI_PRETTY_COLOUR_KEY, - INI_PRETTY_COLOUR_VALUE, - INI_PRETTY_COLOUR_DIVIDER, - INI_PRETTY_COLOUR_TABLE, - INI_PRETTY_COLOUR__COUNT, -} ini_pretty_colours_e; - -typedef struct ini_pretty_opts_t ini_pretty_opts_t; -struct ini_pretty_opts_t { - oshandle_t custom_target; - bool use_custom_colours; - os_log_colour_e colours[INI_PRETTY_COLOUR__COUNT]; -}; - -void ini_pretty_print(ini_t *ini, const ini_pretty_opts_t *options); - - -// == JSON =========================================== - -typedef enum jsontype_e { - JSON_NULL, - JSON_ARRAY, - JSON_STRING, - JSON_NUMBER, - JSON_BOOL, - JSON_OBJECT, -} jsontype_e; - -typedef enum jsonflags_e { - JSON_DEFAULT = 0, - JSON_NO_TRAILING_COMMAS = 1 << 0, - JSON_NO_COMMENTS = 1 << 1, -} jsonflags_e; - -typedef struct json_t json_t; -struct json_t { - json_t *next; - json_t *prev; - - strview_t key; - - union { - json_t *array; - strview_t string; - double number; - bool boolean; - json_t *object; - }; - jsontype_e type; -}; - -json_t *json_parse(arena_t *arena, strview_t filename, jsonflags_e flags); -json_t *json_parse_str(arena_t *arena, strview_t str, jsonflags_e flags); - -json_t *json_get(json_t *node, strview_t key); - -#define json_check(val, js_type) ((val) && (val)->type == js_type) -#define json_for(it, arr) for (json_t *it = json_check(arr, JSON_ARRAY) ? arr->array : NULL; it; it = it->next) - -typedef enum json_pretty_colours_e { - JSON_PRETTY_COLOUR_KEY, - JSON_PRETTY_COLOUR_STRING, - JSON_PRETTY_COLOUR_NUM, - JSON_PRETTY_COLOUR_NULL, - JSON_PRETTY_COLOUR_TRUE, - JSON_PRETTY_COLOUR_FALSE, - JSON_PRETTY_COLOUR__COUNT, -} json_pretty_colours_e; - -typedef struct json_pretty_opts_t json_pretty_opts_t; -struct json_pretty_opts_t { - oshandle_t custom_target; - bool use_custom_colours; - os_log_colour_e colours[JSON_PRETTY_COLOUR__COUNT]; -}; - -void json_pretty_print(json_t *root, const json_pretty_opts_t *options); - -// == XML ============================================ - -typedef struct xmlattr_t xmlattr_t; -struct xmlattr_t { - strview_t key; - strview_t value; - xmlattr_t *next; -}; - -typedef struct xmltag_t xmltag_t; -struct xmltag_t { - strview_t key; - xmlattr_t *attributes; - strview_t content; - xmltag_t *child; - xmltag_t *tail; - xmltag_t *next; -}; - -typedef struct xml_t xml_t; -struct xml_t { - strview_t text; - xmltag_t *root; - xmltag_t *tail; -}; - -xml_t xml_parse(arena_t *arena, strview_t filename); -xml_t xml_parse_str(arena_t *arena, strview_t xmlstr); - -xmltag_t *xml_get_tag(xmltag_t *parent, strview_t key, bool recursive); -strview_t xml_get_attribute(xmltag_t *tag, strview_t key); - -// == HTML =========================================== - -typedef struct htmltag_t htmltag_t; -struct htmltag_t { - str_t key; - xmlattr_t *attributes; - strview_t content; - htmltag_t *children; - htmltag_t *tail; - htmltag_t *next; -}; - -typedef struct html_t html_t; -struct html_t { - strview_t text; - htmltag_t *root; - htmltag_t *tail; -}; - -html_t html_parse(arena_t *arena, strview_t filename); -html_t html_parse_str(arena_t *arena, strview_t str); - -htmltag_t *html_get_tag(htmltag_t *parent, strview_t key, bool recursive); -strview_t html_get_attribute(htmltag_t *tag, strview_t key); - -#endif diff --git a/pretty_print.c b/pretty_print.c deleted file mode 100644 index bbe23a9..0000000 --- a/pretty_print.c +++ /dev/null @@ -1,80 +0,0 @@ -#include "pretty_print.h" - -#include - -#include "core.h" - -strview_t pretty__colour[LOG_COL__COUNT] = { - [LOG_COL_BLACK] = cstrv("black"), - [LOG_COL_BLUE] = cstrv("blue"), - [LOG_COL_GREEN] = cstrv("green"), - [LOG_COL_CYAN] = cstrv("cyan"), - [LOG_COL_RED] = cstrv("red"), - [LOG_COL_MAGENTA] = cstrv("magenta"), - [LOG_COL_YELLOW] = cstrv("yellow"), - [LOG_COL_GREY] = cstrv("grey"), - - [LOG_COL_DARK_GREY] = cstrv("dark_grey"), - [LOG_COL_WHITE] = cstrv("white"), - [LOG_COL_LIGHT_BLUE] = cstrv("light_blue"), - [LOG_COL_LIGHT_GREEN] = cstrv("light_green"), - [LOG_COL_LIGHT_CYAN] = cstrv("light_cyan"), - [LOG_COL_LIGHT_RED] = cstrv("light_red"), - [LOG_COL_LIGHT_MAGENTA] = cstrv("light_magenta"), - [LOG_COL_LIGHT_YELLOW] = cstrv("light_yellow"), - - [LOG_COL_RESET] = cstrv("/"), -}; - -strview_t pretty_log_to_colour(os_log_colour_e colour) { - return pretty__colour[colour]; -} - -void pretty_print(arena_t scratch, const char *fmt, ...) { - va_list args; - va_start(args, fmt); - pretty_printv(scratch, fmt, args); - va_end(args); -} - -void pretty_printv(arena_t scratch, const char *fmt, va_list args) { - va_list tmp_args; - va_copy(tmp_args, args); - int len = fmt_bufferv(NULL, 0, fmt, tmp_args); - va_end(tmp_args); - - char *buf = alloc(&scratch, char, len + 1); - - fmt_bufferv(buf, len + 1, fmt, args); - - oshandle_t out = os_stdout(); - instream_t in = istr_init(strv(buf, len)); - 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; - } - - os_file_write(out, part.buf, part.len); - istr_skip(&in, 1); - - if (has_escape) { - os_file_putc(out, '<'); - continue; - } - - strview_t tag = istr_get_view(&in, '>'); - - for (usize i = 0; i < arrlen(pretty__colour); ++i) { - if (strv_equals(tag, pretty__colour[i])) { - os_log_set_colour(i); - break; - } - } - - istr_skip(&in, 1); - } -} - diff --git a/pretty_print.h b/pretty_print.h deleted file mode 100644 index bd5c35a..0000000 --- a/pretty_print.h +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once - -#include "os.h" -#include "arena.h" -#include "str.h" - -strview_t pretty_log_to_colour(os_log_colour_e colour); -void pretty_print(arena_t scratch, const char *fmt, ...); -void pretty_printv(arena_t scratch, const char *fmt, va_list args); - diff --git a/stb/stb_sprintf.h b/stb/stb_sprintf.h deleted file mode 100644 index 60a81b2..0000000 --- a/stb/stb_sprintf.h +++ /dev/null @@ -1,1945 +0,0 @@ -// stb_sprintf - v1.10 - public domain snprintf() implementation -// originally by Jeff Roberts / RAD Game Tools, 2015/10/20 -// http://github.com/nothings/stb -// -// allowed types: sc uidBboXx p AaGgEef n -// lengths : hh h ll j z t I64 I32 I -// -// Contributors: -// Fabian "ryg" Giesen (reformatting) -// github:aganm (attribute format) -// -// Contributors (bugfixes): -// github:d26435 -// github:trex78 -// github:account-login -// Jari Komppa (SI suffixes) -// Rohit Nirmal -// Marcin Wojdyr -// Leonard Ritter -// Stefano Zanotti -// Adam Allison -// Arvid Gerstmann -// Markus Kolb -// -// LICENSE: -// -// See end of file for license information. - -#define STB_SPRINTF_IMPLEMENTATION - -#ifndef STB_SPRINTF_H_INCLUDE -#define STB_SPRINTF_H_INCLUDE - -/* -Single file sprintf replacement. - -Originally written by Jeff Roberts at RAD Game Tools - 2015/10/20. -Hereby placed in public domain. - -This is a full sprintf replacement that supports everything that -the C runtime sprintfs support, including float/double, 64-bit integers, -hex floats, field parameters (%*.*d stuff), length reads backs, etc. - -Why would you need this if sprintf already exists? Well, first off, -it's *much* faster (see below). It's also much smaller than the CRT -versions code-space-wise. We've also added some simple improvements -that are super handy (commas in thousands, callbacks at buffer full, -for example). Finally, the format strings for MSVC and GCC differ -for 64-bit integers (among other small things), so this lets you use -the same format strings in cross platform code. - -It uses the standard single file trick of being both the header file -and the source itself. If you just include it normally, you just get -the header file function definitions. To get the code, you include -it from a C or C++ file and define STB_SPRINTF_IMPLEMENTATION first. - -It only uses va_args macros from the C runtime to do it's work. It -does cast doubles to S64s and shifts and divides U64s, which does -drag in CRT code on most platforms. - -It compiles to roughly 8K with float support, and 4K without. -As a comparison, when using MSVC static libs, calling sprintf drags -in 16K. - -API: -==== -int stbsp_sprintf( char * buf, char const * fmt, ... ) -int stbsp_snprintf( char * buf, int count, char const * fmt, ... ) - Convert an arg list into a buffer. stbsp_snprintf always returns - a zero-terminated string (unlike regular snprintf). - -int stbsp_vsprintf( char * buf, char const * fmt, va_list va ) -int stbsp_vsnprintf( char * buf, int count, char const * fmt, va_list va ) - Convert a va_list arg list into a buffer. stbsp_vsnprintf always returns - a zero-terminated string (unlike regular snprintf). - -int stbsp_vsprintfcb( STBSP_SPRINTFCB * callback, void * user, char * buf, char const * fmt, va_list va ) - typedef char * STBSP_SPRINTFCB( char const * buf, void * user, int len ); - Convert into a buffer, calling back every STB_SPRINTF_MIN chars. - Your callback can then copy the chars out, print them or whatever. - This function is actually the workhorse for everything else. - The buffer you pass in must hold at least STB_SPRINTF_MIN characters. - // you return the next buffer to use or 0 to stop converting - -void stbsp_set_separators( char comma, char period ) - Set the comma and period characters to use. - -FLOATS/DOUBLES: -=============== -This code uses a internal float->ascii conversion method that uses -doubles with error correction (double-doubles, for ~105 bits of -precision). This conversion is round-trip perfect - that is, an atof -of the values output here will give you the bit-exact double back. - -One difference is that our insignificant digits will be different than -with MSVC or GCC (but they don't match each other either). We also -don't attempt to find the minimum length matching float (pre-MSVC15 -doesn't either). - -If you don't need float or doubles at all, define STB_SPRINTF_NOFLOAT -and you'll save 4K of code space. - -64-BIT INTS: -============ -This library also supports 64-bit integers and you can use MSVC style or -GCC style indicators (%I64d or %lld). It supports the C99 specifiers -for size_t and ptr_diff_t (%jd %zd) as well. - -EXTRAS: -======= -Like some GCCs, for integers and floats, you can use a ' (single quote) -specifier and commas will be inserted on the thousands: "%'d" on 12345 -would print 12,345. - -For integers and floats, you can use a "$" specifier and the number -will be converted to float and then divided to get kilo, mega, giga or -tera and then printed, so "%$d" 1000 is "1.0 k", "%$.2d" 2536000 is -"2.53 M", etc. For byte values, use two $:s, like "%$$d" to turn -2536000 to "2.42 Mi". If you prefer JEDEC suffixes to SI ones, use three -$:s: "%$$$d" -> "2.42 M". To remove the space between the number and the -suffix, add "_" specifier: "%_$d" -> "2.53M". - -In addition to octal and hexadecimal conversions, you can print -integers in binary: "%b" for 256 would print 100. - -PERFORMANCE vs MSVC 2008 32-/64-bit (GCC is even slower than MSVC): -=================================================================== -"%d" across all 32-bit ints (4.8x/4.0x faster than 32-/64-bit MSVC) -"%24d" across all 32-bit ints (4.5x/4.2x faster) -"%x" across all 32-bit ints (4.5x/3.8x faster) -"%08x" across all 32-bit ints (4.3x/3.8x faster) -"%f" across e-10 to e+10 floats (7.3x/6.0x faster) -"%e" across e-10 to e+10 floats (8.1x/6.0x faster) -"%g" across e-10 to e+10 floats (10.0x/7.1x faster) -"%f" for values near e-300 (7.9x/6.5x faster) -"%f" for values near e+300 (10.0x/9.1x faster) -"%e" for values near e-300 (10.1x/7.0x faster) -"%e" for values near e+300 (9.2x/6.0x faster) -"%.320f" for values near e-300 (12.6x/11.2x faster) -"%a" for random values (8.6x/4.3x faster) -"%I64d" for 64-bits with 32-bit values (4.8x/3.4x faster) -"%I64d" for 64-bits > 32-bit values (4.9x/5.5x faster) -"%s%s%s" for 64 char strings (7.1x/7.3x faster) -"...512 char string..." ( 35.0x/32.5x faster!) -*/ - -#if defined(__clang__) - #if defined(__has_feature) && defined(__has_attribute) - #if __has_feature(address_sanitizer) - #if __has_attribute(__no_sanitize__) - #define STBSP__ASAN __attribute__((__no_sanitize__("address"))) - #elif __has_attribute(__no_sanitize_address__) - #define STBSP__ASAN __attribute__((__no_sanitize_address__)) - #elif __has_attribute(__no_address_safety_analysis__) - #define STBSP__ASAN __attribute__((__no_address_safety_analysis__)) - #endif - #endif - #endif -#elif defined(__GNUC__) && (__GNUC__ >= 5 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)) - #if defined(__SANITIZE_ADDRESS__) && __SANITIZE_ADDRESS__ - #define STBSP__ASAN __attribute__((__no_sanitize_address__)) - #endif -#endif - -#ifndef STBSP__ASAN -#define STBSP__ASAN -#endif - -#ifdef STB_SPRINTF_STATIC -#define STBSP__PUBLICDEC static -#define STBSP__PUBLICDEF static STBSP__ASAN -#else -#ifdef __cplusplus -#define STBSP__PUBLICDEC extern "C" -#define STBSP__PUBLICDEF extern "C" STBSP__ASAN -#else -#define STBSP__PUBLICDEC extern -#define STBSP__PUBLICDEF STBSP__ASAN -#endif -#endif - -#if defined(__has_attribute) - #if __has_attribute(format) - #define STBSP__ATTRIBUTE_FORMAT(fmt,va) __attribute__((format(printf,fmt,va))) - #endif -#endif - -#ifndef STBSP__ATTRIBUTE_FORMAT -#define STBSP__ATTRIBUTE_FORMAT(fmt,va) -#endif - -#ifdef _MSC_VER -#define STBSP__NOTUSED(v) (void)(v) -#else -#define STBSP__NOTUSED(v) (void)sizeof(v) -#endif - -#include // for va_arg(), va_list() -#include // size_t, ptrdiff_t - -#ifndef STB_SPRINTF_MIN -#define STB_SPRINTF_MIN 512 // how many characters per callback -#endif -typedef char *STBSP_SPRINTFCB(const char *buf, void *user, int len); - -#ifndef STB_SPRINTF_DECORATE -#define STB_SPRINTF_DECORATE(name) stbsp_##name // define this before including if you want to change the names -#endif - -STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(vsprintf)(char *buf, char const *fmt, va_list va); -STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(vsnprintf)(char *buf, int count, char const *fmt, va_list va); -STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(sprintf)(char *buf, char const *fmt, ...) STBSP__ATTRIBUTE_FORMAT(2,3); -STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(snprintf)(char *buf, int count, char const *fmt, ...) STBSP__ATTRIBUTE_FORMAT(3,4); - -STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(vsprintfcb)(STBSP_SPRINTFCB *callback, void *user, char *buf, char const *fmt, va_list va); -STBSP__PUBLICDEC void STB_SPRINTF_DECORATE(set_separators)(char comma, char period); - -#endif // STB_SPRINTF_H_INCLUDE - -#ifdef STB_SPRINTF_IMPLEMENTATION - -#if COLLA_CLANG - -#pragma clang diagnostic push - -#pragma clang diagnostic ignored "-Wextra-semi-stmt" -#pragma clang diagnostic ignored "-Wconditional-uninitialized" -#pragma clang diagnostic ignored "-Wcast-qual" -#pragma clang diagnostic ignored "-Wimplicit-fallthrough" - -#endif - -#define stbsp__uint32 unsigned int -#define stbsp__int32 signed int - -#ifdef _MSC_VER -#define stbsp__uint64 unsigned __int64 -#define stbsp__int64 signed __int64 -#else -#define stbsp__uint64 unsigned long long -#define stbsp__int64 signed long long -#endif -#define stbsp__uint16 unsigned short - -#ifndef stbsp__uintptr -#if defined(__ppc64__) || defined(__powerpc64__) || defined(__aarch64__) || defined(_M_X64) || defined(__x86_64__) || defined(__x86_64) || defined(__s390x__) -#define stbsp__uintptr stbsp__uint64 -#else -#define stbsp__uintptr stbsp__uint32 -#endif -#endif - -#ifndef STB_SPRINTF_MSVC_MODE // used for MSVC2013 and earlier (MSVC2015 matches GCC) -#if defined(_MSC_VER) && (_MSC_VER < 1900) -#define STB_SPRINTF_MSVC_MODE -#endif -#endif - -#ifdef STB_SPRINTF_NOUNALIGNED // define this before inclusion to force stbsp_sprintf to always use aligned accesses -#define STBSP__UNALIGNED(code) -#else -#define STBSP__UNALIGNED(code) code -#endif - -#ifndef STB_SPRINTF_NOFLOAT -// internal float utility functions -static stbsp__int32 stbsp__real_to_str(char const **start, stbsp__uint32 *len, char *out, stbsp__int32 *decimal_pos, double value, stbsp__uint32 frac_digits); -static stbsp__int32 stbsp__real_to_parts(stbsp__int64 *bits, stbsp__int32 *expo, double value); -#define STBSP__SPECIAL 0x7000 -#endif - -static char stbsp__period = '.'; -static char stbsp__comma = ','; -static struct -{ - short temp; // force next field to be 2-byte aligned - char pair[201]; -} stbsp__digitpair = -{ - 0, - "00010203040506070809101112131415161718192021222324" - "25262728293031323334353637383940414243444546474849" - "50515253545556575859606162636465666768697071727374" - "75767778798081828384858687888990919293949596979899" -}; - -STBSP__PUBLICDEF void STB_SPRINTF_DECORATE(set_separators)(char pcomma, char pperiod) -{ - stbsp__period = pperiod; - stbsp__comma = pcomma; -} - -#define STBSP__LEFTJUST 1 -#define STBSP__LEADINGPLUS 2 -#define STBSP__LEADINGSPACE 4 -#define STBSP__LEADING_0X 8 -#define STBSP__LEADINGZERO 16 -#define STBSP__INTMAX 32 -#define STBSP__TRIPLET_COMMA 64 -#define STBSP__NEGATIVE 128 -#define STBSP__METRIC_SUFFIX 256 -#define STBSP__HALFWIDTH 512 -#define STBSP__METRIC_NOSPACE 1024 -#define STBSP__METRIC_1024 2048 -#define STBSP__METRIC_JEDEC 4096 - -static void stbsp__lead_sign(stbsp__uint32 fl, char *sign) -{ - sign[0] = 0; - if (fl & STBSP__NEGATIVE) { - sign[0] = 1; - sign[1] = '-'; - } else if (fl & STBSP__LEADINGSPACE) { - sign[0] = 1; - sign[1] = ' '; - } else if (fl & STBSP__LEADINGPLUS) { - sign[0] = 1; - sign[1] = '+'; - } -} - -static STBSP__ASAN stbsp__uint32 stbsp__strlen_limited(char const *s, stbsp__uint32 limit) -{ - char const * sn = s; - - // get up to 4-byte alignment - for (;;) { - if (((stbsp__uintptr)sn & 3) == 0) - break; - - if (!limit || *sn == 0) - return (stbsp__uint32)(sn - s); - - ++sn; - --limit; - } - - // scan over 4 bytes at a time to find terminating 0 - // this will intentionally scan up to 3 bytes past the end of buffers, - // but becase it works 4B aligned, it will never cross page boundaries - // (hence the STBSP__ASAN markup; the over-read here is intentional - // and harmless) - while (limit >= 4) { - stbsp__uint32 v = *(stbsp__uint32 *)sn; - // bit hack to find if there's a 0 byte in there - if ((v - 0x01010101) & (~v) & 0x80808080UL) - break; - - sn += 4; - limit -= 4; - } - - // handle the last few characters to find actual size - while (limit && *sn) { - ++sn; - --limit; - } - - return (stbsp__uint32)(sn - s); -} - -typedef struct { - char *buf; - size_t len; -} stb__strv_t; - -STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(vsprintfcb)(STBSP_SPRINTFCB *callback, void *user, char *buf, char const *fmt, va_list va) -{ - static char hex[] = "0123456789abcdefxp"; - static char hexu[] = "0123456789ABCDEFXP"; - char *bf; - char const *f; - int tlen = 0; - - bf = buf; - f = fmt; - for (;;) { - stbsp__int32 fw, pr, tz; - stbsp__uint32 fl; - - // macros for the callback buffer stuff - #define stbsp__chk_cb_bufL(bytes) \ - { \ - int len = (int)(bf - buf); \ - if ((len + (bytes)) >= STB_SPRINTF_MIN) { \ - tlen += len; \ - if (0 == (bf = buf = callback(buf, user, len))) \ - goto done; \ - } \ - } - #define stbsp__chk_cb_buf(bytes) \ - { \ - if (callback) { \ - stbsp__chk_cb_bufL(bytes); \ - } \ - } - #define stbsp__flush_cb() \ - { \ - stbsp__chk_cb_bufL(STB_SPRINTF_MIN - 1); \ - } // flush if there is even one byte in the buffer - #define stbsp__cb_buf_clamp(cl, v) \ - cl = v; \ - if (callback) { \ - int lg = STB_SPRINTF_MIN - (int)(bf - buf); \ - if (cl > lg) \ - cl = lg; \ - } - - // fast copy everything up to the next % (or end of string) - for (;;) { - while (((stbsp__uintptr)f) & 3) { - schk1: - if (f[0] == '%') - goto scandd; - schk2: - if (f[0] == 0) - goto endfmt; - stbsp__chk_cb_buf(1); - *bf++ = f[0]; - ++f; - } - for (;;) { - // Check if the next 4 bytes contain %(0x25) or end of string. - // Using the 'hasless' trick: - // https://graphics.stanford.edu/~seander/bithacks.html#HasLessInWord - stbsp__uint32 v, c; - v = *(stbsp__uint32 *)f; - c = (~v) & 0x80808080; - if (((v ^ 0x25252525) - 0x01010101) & c) - goto schk1; - if ((v - 0x01010101) & c) - goto schk2; - if (callback) - if ((STB_SPRINTF_MIN - (int)(bf - buf)) < 4) - goto schk1; - #ifdef STB_SPRINTF_NOUNALIGNED - if(((stbsp__uintptr)bf) & 3) { - bf[0] = f[0]; - bf[1] = f[1]; - bf[2] = f[2]; - bf[3] = f[3]; - } else - #endif - { - *(stbsp__uint32 *)bf = v; - } - bf += 4; - f += 4; - } - } - scandd: - - ++f; - - // ok, we have a percent, read the modifiers first - fw = 0; - pr = -1; - fl = 0; - tz = 0; - - // flags - for (;;) { - switch (f[0]) { - // if we have left justify - case '-': - fl |= STBSP__LEFTJUST; - ++f; - continue; - // if we have leading plus - case '+': - fl |= STBSP__LEADINGPLUS; - ++f; - continue; - // if we have leading space - case ' ': - fl |= STBSP__LEADINGSPACE; - ++f; - continue; - // if we have leading 0x - case '#': - fl |= STBSP__LEADING_0X; - ++f; - continue; - // if we have thousand commas - case '\'': - fl |= STBSP__TRIPLET_COMMA; - ++f; - continue; - // if we have kilo marker (none->kilo->kibi->jedec) - case '$': - if (fl & STBSP__METRIC_SUFFIX) { - if (fl & STBSP__METRIC_1024) { - fl |= STBSP__METRIC_JEDEC; - } else { - fl |= STBSP__METRIC_1024; - } - } else { - fl |= STBSP__METRIC_SUFFIX; - } - ++f; - continue; - // if we don't want space between metric suffix and number - case '_': - fl |= STBSP__METRIC_NOSPACE; - ++f; - continue; - // if we have leading zero - case '0': - fl |= STBSP__LEADINGZERO; - ++f; - goto flags_done; - default: goto flags_done; - } - } - flags_done: - - // get the field width - if (f[0] == '*') { - fw = va_arg(va, stbsp__uint32); - ++f; - } else { - while ((f[0] >= '0') && (f[0] <= '9')) { - fw = fw * 10 + f[0] - '0'; - f++; - } - } - // get the precision - if (f[0] == '.') { - ++f; - if (f[0] == '*') { - pr = va_arg(va, stbsp__uint32); - ++f; - } else { - pr = 0; - while ((f[0] >= '0') && (f[0] <= '9')) { - pr = pr * 10 + f[0] - '0'; - f++; - } - } - } - - // handle integer size overrides - switch (f[0]) { - // are we halfwidth? - case 'h': - fl |= STBSP__HALFWIDTH; - ++f; - if (f[0] == 'h') - ++f; // QUARTERWIDTH - break; - // are we 64-bit (unix style) - case 'l': - fl |= ((sizeof(long) == 8) ? STBSP__INTMAX : 0); - ++f; - if (f[0] == 'l') { - fl |= STBSP__INTMAX; - ++f; - } - break; - // are we 64-bit on intmax? (c99) - case 'j': - fl |= (sizeof(size_t) == 8) ? STBSP__INTMAX : 0; - ++f; - break; - // are we 64-bit on size_t or ptrdiff_t? (c99) - case 'z': - fl |= (sizeof(ptrdiff_t) == 8) ? STBSP__INTMAX : 0; - ++f; - break; - case 't': - fl |= (sizeof(ptrdiff_t) == 8) ? STBSP__INTMAX : 0; - ++f; - break; - // are we 64-bit (msft style) - case 'I': - if ((f[1] == '6') && (f[2] == '4')) { - fl |= STBSP__INTMAX; - f += 3; - } else if ((f[1] == '3') && (f[2] == '2')) { - f += 3; - } else { - fl |= ((sizeof(void *) == 8) ? STBSP__INTMAX : 0); - ++f; - } - break; - default: break; - } - - // handle each replacement - switch (f[0]) { - #define STBSP__NUMSZ 512 // big enough for e308 (with commas) or e-307 - char num[STBSP__NUMSZ]; - char lead[8]; - char tail[8]; - char *s; - char const *h; - stbsp__uint32 l, n, cs; - stbsp__uint64 n64; -#ifndef STB_SPRINTF_NOFLOAT - double fv; -#endif - stbsp__int32 dp; - char const *sn; - - stb__strv_t strv; - - case 's': - // get the string - s = va_arg(va, char *); - if (s == 0) - s = (char *)"null"; - // get the length, limited to desired precision - // always limit to ~0u chars since our counts are 32b - l = stbsp__strlen_limited(s, (pr >= 0) ? pr : ~0u); - lead[0] = 0; - tail[0] = 0; - pr = 0; - dp = 0; - cs = 0; - // copy the string in - goto scopy; - - case 'v': - // get the view - strv = va_arg(va, stb__strv_t); - s = strv.buf; - if (s == 0) - s = (char *)"null"; - l = (unsigned int)strv.len; - lead[0] = 0; - tail[0] = 0; - pr = 0; - dp = 0; - cs = 0; - // copy the string in - goto scopy; - - case 'c': // char - // get the character - s = num + STBSP__NUMSZ - 1; - *s = (char)va_arg(va, int); - l = 1; - lead[0] = 0; - tail[0] = 0; - pr = 0; - dp = 0; - cs = 0; - goto scopy; - - case 'n': // weird write-bytes specifier - { - int *d = va_arg(va, int *); - *d = tlen + (int)(bf - buf); - } break; - -#ifdef STB_SPRINTF_NOFLOAT - case 'A': // float - case 'a': // hex float - case 'G': // float - case 'g': // float - case 'E': // float - case 'e': // float - case 'f': // float - va_arg(va, double); // eat it - s = (char *)"No float"; - l = 8; - lead[0] = 0; - tail[0] = 0; - pr = 0; - cs = 0; - STBSP__NOTUSED(dp); - goto scopy; -#else - case 'A': // hex float - case 'a': // hex float - h = (f[0] == 'A') ? hexu : hex; - fv = va_arg(va, double); - if (pr == -1) - pr = 6; // default is 6 - // read the double into a string - if (stbsp__real_to_parts((stbsp__int64 *)&n64, &dp, fv)) - fl |= STBSP__NEGATIVE; - - s = num + 64; - - stbsp__lead_sign(fl, lead); - - if (dp == -1023) - dp = (n64) ? -1022 : 0; - else - n64 |= (((stbsp__uint64)1) << 52); - n64 <<= (64 - 56); - if (pr < 15) - n64 += ((((stbsp__uint64)8) << 56) >> (pr * 4)); -// add leading chars - -#ifdef STB_SPRINTF_MSVC_MODE - *s++ = '0'; - *s++ = 'x'; -#else - lead[1 + lead[0]] = '0'; - lead[2 + lead[0]] = 'x'; - lead[0] += 2; -#endif - *s++ = h[(n64 >> 60) & 15]; - n64 <<= 4; - if (pr) - *s++ = stbsp__period; - sn = s; - - // print the bits - n = pr; - if (n > 13) - n = 13; - if (pr > (stbsp__int32)n) - tz = pr - n; - pr = 0; - while (n--) { - *s++ = h[(n64 >> 60) & 15]; - n64 <<= 4; - } - - // print the expo - tail[1] = h[17]; - if (dp < 0) { - tail[2] = '-'; - dp = -dp; - } else - tail[2] = '+'; - n = (dp >= 1000) ? 6 : ((dp >= 100) ? 5 : ((dp >= 10) ? 4 : 3)); - tail[0] = (char)n; - for (;;) { - tail[n] = '0' + dp % 10; - if (n <= 3) - break; - --n; - dp /= 10; - } - - dp = (int)(s - sn); - l = (int)(s - (num + 64)); - s = num + 64; - cs = 1 + (3 << 24); - goto scopy; - - case 'G': // float - case 'g': // float - h = (f[0] == 'G') ? hexu : hex; - fv = va_arg(va, double); - if (pr == -1) - pr = 6; - else if (pr == 0) - pr = 1; // default is 6 - // read the double into a string - if (stbsp__real_to_str(&sn, &l, num, &dp, fv, (pr - 1) | 0x80000000)) - fl |= STBSP__NEGATIVE; - - // clamp the precision and delete extra zeros after clamp - n = pr; - if (l > (stbsp__uint32)pr) - l = pr; - while ((l > 1) && (pr) && (sn[l - 1] == '0')) { - --pr; - --l; - } - - // should we use %e - if ((dp <= -4) || (dp > (stbsp__int32)n)) { - if (pr > (stbsp__int32)l) - pr = l - 1; - else if (pr) - --pr; // when using %e, there is one digit before the decimal - goto doexpfromg; - } - // this is the insane action to get the pr to match %g semantics for %f - if (dp > 0) { - pr = (dp < (stbsp__int32)l) ? l - dp : 0; - } else { - pr = -dp + ((pr > (stbsp__int32)l) ? (stbsp__int32) l : pr); - } - goto dofloatfromg; - - case 'E': // float - case 'e': // float - h = (f[0] == 'E') ? hexu : hex; - fv = va_arg(va, double); - if (pr == -1) - pr = 6; // default is 6 - // read the double into a string - if (stbsp__real_to_str(&sn, &l, num, &dp, fv, pr | 0x80000000)) - fl |= STBSP__NEGATIVE; - doexpfromg: - tail[0] = 0; - stbsp__lead_sign(fl, lead); - if (dp == STBSP__SPECIAL) { - s = (char *)sn; - cs = 0; - pr = 0; - goto scopy; - } - s = num + 64; - // handle leading chars - *s++ = sn[0]; - - if (pr) - *s++ = stbsp__period; - - // handle after decimal - if ((l - 1) > (stbsp__uint32)pr) - l = pr + 1; - for (n = 1; n < l; n++) - *s++ = sn[n]; - // trailing zeros - tz = pr - (l - 1); - pr = 0; - // dump expo - tail[1] = h[0xe]; - dp -= 1; - if (dp < 0) { - tail[2] = '-'; - dp = -dp; - } else - tail[2] = '+'; -#ifdef STB_SPRINTF_MSVC_MODE - n = 5; -#else - n = (dp >= 100) ? 5 : 4; -#endif - tail[0] = (char)n; - for (;;) { - tail[n] = '0' + dp % 10; - if (n <= 3) - break; - --n; - dp /= 10; - } - cs = 1 + (3 << 24); // how many tens - goto flt_lead; - - case 'f': // float - fv = va_arg(va, double); - doafloat: - // do kilos - if (fl & STBSP__METRIC_SUFFIX) { - double divisor; - divisor = 1000.0f; - if (fl & STBSP__METRIC_1024) - divisor = 1024.0; - while (fl < 0x4000000) { - if ((fv < divisor) && (fv > -divisor)) - break; - fv /= divisor; - fl += 0x1000000; - } - } - if (pr == -1) - pr = 6; // default is 6 - // read the double into a string - if (stbsp__real_to_str(&sn, &l, num, &dp, fv, pr)) - fl |= STBSP__NEGATIVE; - dofloatfromg: - tail[0] = 0; - stbsp__lead_sign(fl, lead); - if (dp == STBSP__SPECIAL) { - s = (char *)sn; - cs = 0; - pr = 0; - goto scopy; - } - s = num + 64; - - // handle the three decimal varieties - if (dp <= 0) { - stbsp__int32 i; - // handle 0.000*000xxxx - *s++ = '0'; - if (pr) - *s++ = stbsp__period; - n = -dp; - if ((stbsp__int32)n > pr) - n = pr; - i = n; - while (i) { - if ((((stbsp__uintptr)s) & 3) == 0) - break; - *s++ = '0'; - --i; - } - while (i >= 4) { - *(stbsp__uint32 *)s = 0x30303030; - s += 4; - i -= 4; - } - while (i) { - *s++ = '0'; - --i; - } - if ((stbsp__int32)(l + n) > pr) - l = pr - n; - i = l; - while (i) { - *s++ = *sn++; - --i; - } - tz = pr - (n + l); - cs = 1 + (3 << 24); // how many tens did we write (for commas below) - } else { - cs = (fl & STBSP__TRIPLET_COMMA) ? ((600 - (stbsp__uint32)dp) % 3) : 0; - if ((stbsp__uint32)dp >= l) { - // handle xxxx000*000.0 - n = 0; - for (;;) { - if ((fl & STBSP__TRIPLET_COMMA) && (++cs == 4)) { - cs = 0; - *s++ = stbsp__comma; - } else { - *s++ = sn[n]; - ++n; - if (n >= l) - break; - } - } - if (n < (stbsp__uint32)dp) { - n = dp - n; - if ((fl & STBSP__TRIPLET_COMMA) == 0) { - while (n) { - if ((((stbsp__uintptr)s) & 3) == 0) - break; - *s++ = '0'; - --n; - } - while (n >= 4) { - *(stbsp__uint32 *)s = 0x30303030; - s += 4; - n -= 4; - } - } - while (n) { - if ((fl & STBSP__TRIPLET_COMMA) && (++cs == 4)) { - cs = 0; - *s++ = stbsp__comma; - } else { - *s++ = '0'; - --n; - } - } - } - cs = (int)(s - (num + 64)) + (3 << 24); // cs is how many tens - if (pr) { - *s++ = stbsp__period; - tz = pr; - } - } else { - // handle xxxxx.xxxx000*000 - n = 0; - for (;;) { - if ((fl & STBSP__TRIPLET_COMMA) && (++cs == 4)) { - cs = 0; - *s++ = stbsp__comma; - } else { - *s++ = sn[n]; - ++n; - if (n >= (stbsp__uint32)dp) - break; - } - } - cs = (int)(s - (num + 64)) + (3 << 24); // cs is how many tens - if (pr) - *s++ = stbsp__period; - if ((l - dp) > (stbsp__uint32)pr) - l = pr + dp; - while (n < l) { - *s++ = sn[n]; - ++n; - } - tz = pr - (l - dp); - } - } - pr = 0; - - // handle k,m,g,t - if (fl & STBSP__METRIC_SUFFIX) { - char idx; - idx = 1; - if (fl & STBSP__METRIC_NOSPACE) - idx = 0; - tail[0] = idx; - tail[1] = ' '; - { - if (fl >> 24) { // SI kilo is 'k', JEDEC and SI kibits are 'K'. - if (fl & STBSP__METRIC_1024) - tail[idx + 1] = "_KMGT"[fl >> 24]; - else - tail[idx + 1] = "_kMGT"[fl >> 24]; - idx++; - // If printing kibits and not in jedec, add the 'i'. - if (fl & STBSP__METRIC_1024 && !(fl & STBSP__METRIC_JEDEC)) { - tail[idx + 1] = 'i'; - idx++; - } - tail[0] = idx; - } - } - }; - - flt_lead: - // get the length that we copied - l = (stbsp__uint32)(s - (num + 64)); - s = num + 64; - goto scopy; -#endif - - case 'B': // upper binary - case 'b': // lower binary - h = (f[0] == 'B') ? hexu : hex; - lead[0] = 0; - if (fl & STBSP__LEADING_0X) { - lead[0] = 2; - lead[1] = '0'; - lead[2] = h[0xb]; - } - l = (8 << 4) | (1 << 8); - goto radixnum; - - case 'o': // octal - h = hexu; - lead[0] = 0; - if (fl & STBSP__LEADING_0X) { - lead[0] = 1; - lead[1] = '0'; - } - l = (3 << 4) | (3 << 8); - goto radixnum; - - case 'p': // pointer - fl |= (sizeof(void *) == 8) ? STBSP__INTMAX : 0; - pr = sizeof(void *) * 2; - fl &= ~STBSP__LEADINGZERO; // 'p' only prints the pointer with zeros - // fall through - to X - - case 'X': // upper hex - case 'x': // lower hex - h = (f[0] == 'X') ? hexu : hex; - l = (4 << 4) | (4 << 8); - lead[0] = 0; - if (fl & STBSP__LEADING_0X) { - lead[0] = 2; - lead[1] = '0'; - lead[2] = h[16]; - } - radixnum: - // get the number - if (fl & STBSP__INTMAX) - n64 = va_arg(va, stbsp__uint64); - else - n64 = va_arg(va, stbsp__uint32); - - s = num + STBSP__NUMSZ; - dp = 0; - // clear tail, and clear leading if value is zero - tail[0] = 0; - if (n64 == 0) { - lead[0] = 0; - if (pr == 0) { - l = 0; - cs = 0; - goto scopy; - } - } - // convert to string - for (;;) { - *--s = h[n64 & ((1 << (l >> 8)) - 1)]; - n64 >>= (l >> 8); - if (!((n64) || ((stbsp__int32)((num + STBSP__NUMSZ) - s) < pr))) - break; - if (fl & STBSP__TRIPLET_COMMA) { - ++l; - if ((l & 15) == ((l >> 4) & 15)) { - l &= ~15; - *--s = stbsp__comma; - } - } - }; - // get the tens and the comma pos - cs = (stbsp__uint32)((num + STBSP__NUMSZ) - s) + ((((l >> 4) & 15)) << 24); - // get the length that we copied - l = (stbsp__uint32)((num + STBSP__NUMSZ) - s); - // copy it - goto scopy; - - case 'u': // unsigned - case 'i': - case 'd': // integer - // get the integer and abs it - if (fl & STBSP__INTMAX) { - stbsp__int64 i64 = va_arg(va, stbsp__int64); - n64 = (stbsp__uint64)i64; - if ((f[0] != 'u') && (i64 < 0)) { - n64 = (stbsp__uint64)-i64; - fl |= STBSP__NEGATIVE; - } - } else { - stbsp__int32 i = va_arg(va, stbsp__int32); - n64 = (stbsp__uint32)i; - if ((f[0] != 'u') && (i < 0)) { - n64 = (stbsp__uint32)-i; - fl |= STBSP__NEGATIVE; - } - } - -#ifndef STB_SPRINTF_NOFLOAT - if (fl & STBSP__METRIC_SUFFIX) { - if (n64 < 1024) - pr = 0; - else if (pr == -1) - pr = 1; - fv = (double)(stbsp__int64)n64; - goto doafloat; - } -#endif - - // convert to string - s = num + STBSP__NUMSZ; - l = 0; - - for (;;) { - // do in 32-bit chunks (avoid lots of 64-bit divides even with constant denominators) - char *o = s - 8; - if (n64 >= 100000000) { - n = (stbsp__uint32)(n64 % 100000000); - n64 /= 100000000; - } else { - n = (stbsp__uint32)n64; - n64 = 0; - } - if ((fl & STBSP__TRIPLET_COMMA) == 0) { - do { - s -= 2; - *(stbsp__uint16 *)s = *(stbsp__uint16 *)&stbsp__digitpair.pair[(n % 100) * 2]; - n /= 100; - } while (n); - } - while (n) { - if ((fl & STBSP__TRIPLET_COMMA) && (l++ == 3)) { - l = 0; - *--s = stbsp__comma; - --o; - } else { - *--s = (char)(n % 10) + '0'; - n /= 10; - } - } - if (n64 == 0) { - if ((s[0] == '0') && (s != (num + STBSP__NUMSZ))) - ++s; - break; - } - while (s != o) - if ((fl & STBSP__TRIPLET_COMMA) && (l++ == 3)) { - l = 0; - *--s = stbsp__comma; - --o; - } else { - *--s = '0'; - } - } - - tail[0] = 0; - stbsp__lead_sign(fl, lead); - - // get the length that we copied - l = (stbsp__uint32)((num + STBSP__NUMSZ) - s); - if (l == 0) { - *--s = '0'; - l = 1; - } - cs = l + (3 << 24); - if (pr < 0) - pr = 0; - - scopy: - // get fw=leading/trailing space, pr=leading zeros - if (pr < (stbsp__int32)l) - pr = l; - n = pr + lead[0] + tail[0] + tz; - if (fw < (stbsp__int32)n) - fw = n; - fw -= n; - pr -= l; - - // handle right justify and leading zeros - if ((fl & STBSP__LEFTJUST) == 0) { - if (fl & STBSP__LEADINGZERO) // if leading zeros, everything is in pr - { - pr = (fw > pr) ? fw : pr; - fw = 0; - } else { - fl &= ~STBSP__TRIPLET_COMMA; // if no leading zeros, then no commas - } - } - - // copy the spaces and/or zeros - if (fw + pr) { - stbsp__int32 i; - stbsp__uint32 c; - - // copy leading spaces (or when doing %8.4d stuff) - if ((fl & STBSP__LEFTJUST) == 0) - while (fw > 0) { - stbsp__cb_buf_clamp(i, fw); - fw -= i; - while (i) { - if ((((stbsp__uintptr)bf) & 3) == 0) - break; - *bf++ = ' '; - --i; - } - while (i >= 4) { - *(stbsp__uint32 *)bf = 0x20202020; - bf += 4; - i -= 4; - } - while (i) { - *bf++ = ' '; - --i; - } - stbsp__chk_cb_buf(1); - } - - // copy leader - sn = lead + 1; - while (lead[0]) { - stbsp__cb_buf_clamp(i, lead[0]); - lead[0] -= (char)i; - while (i) { - *bf++ = *sn++; - --i; - } - stbsp__chk_cb_buf(1); - } - - // copy leading zeros - c = cs >> 24; - cs &= 0xffffff; - cs = (fl & STBSP__TRIPLET_COMMA) ? ((stbsp__uint32)(c - ((pr + cs) % (c + 1)))) : 0; - while (pr > 0) { - stbsp__cb_buf_clamp(i, pr); - pr -= i; - if ((fl & STBSP__TRIPLET_COMMA) == 0) { - while (i) { - if ((((stbsp__uintptr)bf) & 3) == 0) - break; - *bf++ = '0'; - --i; - } - while (i >= 4) { - *(stbsp__uint32 *)bf = 0x30303030; - bf += 4; - i -= 4; - } - } - while (i) { - if ((fl & STBSP__TRIPLET_COMMA) && (cs++ == c)) { - cs = 0; - *bf++ = stbsp__comma; - } else - *bf++ = '0'; - --i; - } - stbsp__chk_cb_buf(1); - } - } - - // copy leader if there is still one - sn = lead + 1; - while (lead[0]) { - stbsp__int32 i; - stbsp__cb_buf_clamp(i, lead[0]); - lead[0] -= (char)i; - while (i) { - *bf++ = *sn++; - --i; - } - stbsp__chk_cb_buf(1); - } - - // copy the string - n = l; - while (n) { - stbsp__int32 i; - stbsp__cb_buf_clamp(i, n); - n -= i; - STBSP__UNALIGNED(while (i >= 4) { - *(stbsp__uint32 volatile *)bf = *(stbsp__uint32 volatile *)s; - bf += 4; - s += 4; - i -= 4; - }) - while (i) { - *bf++ = *s++; - --i; - } - stbsp__chk_cb_buf(1); - } - - // copy trailing zeros - while (tz) { - stbsp__int32 i; - stbsp__cb_buf_clamp(i, tz); - tz -= i; - while (i) { - if ((((stbsp__uintptr)bf) & 3) == 0) - break; - *bf++ = '0'; - --i; - } - while (i >= 4) { - *(stbsp__uint32 *)bf = 0x30303030; - bf += 4; - i -= 4; - } - while (i) { - *bf++ = '0'; - --i; - } - stbsp__chk_cb_buf(1); - } - - // copy tail if there is one - sn = tail + 1; - while (tail[0]) { - stbsp__int32 i; - stbsp__cb_buf_clamp(i, tail[0]); - tail[0] -= (char)i; - while (i) { - *bf++ = *sn++; - --i; - } - stbsp__chk_cb_buf(1); - } - - // handle the left justify - if (fl & STBSP__LEFTJUST) - if (fw > 0) { - while (fw) { - stbsp__int32 i; - stbsp__cb_buf_clamp(i, fw); - fw -= i; - while (i) { - if ((((stbsp__uintptr)bf) & 3) == 0) - break; - *bf++ = ' '; - --i; - } - while (i >= 4) { - *(stbsp__uint32 *)bf = 0x20202020; - bf += 4; - i -= 4; - } - while (i--) - *bf++ = ' '; - stbsp__chk_cb_buf(1); - } - } - break; - - default: // unknown, just copy code - s = num + STBSP__NUMSZ - 1; - *s = f[0]; - l = 1; - fw = fl = 0; - lead[0] = 0; - tail[0] = 0; - pr = 0; - dp = 0; - cs = 0; - goto scopy; - } - ++f; - } -endfmt: - - if (!callback) - *bf = 0; - else - stbsp__flush_cb(); - -done: - return tlen + (int)(bf - buf); -} - -// cleanup -#undef STBSP__LEFTJUST -#undef STBSP__LEADINGPLUS -#undef STBSP__LEADINGSPACE -#undef STBSP__LEADING_0X -#undef STBSP__LEADINGZERO -#undef STBSP__INTMAX -#undef STBSP__TRIPLET_COMMA -#undef STBSP__NEGATIVE -#undef STBSP__METRIC_SUFFIX -#undef STBSP__NUMSZ -#undef stbsp__chk_cb_bufL -#undef stbsp__chk_cb_buf -#undef stbsp__flush_cb -#undef stbsp__cb_buf_clamp - -// ============================================================================ -// wrapper functions - -STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(sprintf)(char *buf, char const *fmt, ...) -{ - int result; - va_list va; - va_start(va, fmt); - result = STB_SPRINTF_DECORATE(vsprintfcb)(0, 0, buf, fmt, va); - va_end(va); - return result; -} - -typedef struct stbsp__context { - char *buf; - int count; - int length; - char tmp[STB_SPRINTF_MIN]; -} stbsp__context; - -static char *stbsp__clamp_callback(const char *buf, void *user, int len) -{ - stbsp__context *c = (stbsp__context *)user; - c->length += len; - - if (len > c->count) - len = c->count; - - if (len) { - if (buf != c->buf) { - const char *s, *se; - char *d; - d = c->buf; - s = buf; - se = buf + len; - do { - *d++ = *s++; - } while (s < se); - } - c->buf += len; - c->count -= len; - } - - if (c->count <= 0) - return c->tmp; - return (c->count >= STB_SPRINTF_MIN) ? c->buf : c->tmp; // go direct into buffer if you can -} - -static char * stbsp__count_clamp_callback( const char * buf, void * user, int len ) -{ - stbsp__context * c = (stbsp__context*)user; - (void) sizeof(buf); - - c->length += len; - return c->tmp; // go direct into buffer if you can -} - -STBSP__PUBLICDEF int STB_SPRINTF_DECORATE( vsnprintf )( char * buf, int count, char const * fmt, va_list va ) -{ - stbsp__context c; - - if ( (count == 0) && !buf ) - { - c.length = 0; - - STB_SPRINTF_DECORATE( vsprintfcb )( stbsp__count_clamp_callback, &c, c.tmp, fmt, va ); - } - else - { - int l; - - c.buf = buf; - c.count = count; - c.length = 0; - - STB_SPRINTF_DECORATE( vsprintfcb )( stbsp__clamp_callback, &c, stbsp__clamp_callback(0,&c,0), fmt, va ); - - // zero-terminate - l = (int)( c.buf - buf ); - if ( l >= count ) // should never be greater, only equal (or less) than count - l = count - 1; - buf[l] = 0; - } - - return c.length; -} - -STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(snprintf)(char *buf, int count, char const *fmt, ...) -{ - int result; - va_list va; - va_start(va, fmt); - - result = STB_SPRINTF_DECORATE(vsnprintf)(buf, count, fmt, va); - va_end(va); - - return result; -} - -STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(vsprintf)(char *buf, char const *fmt, va_list va) -{ - return STB_SPRINTF_DECORATE(vsprintfcb)(0, 0, buf, fmt, va); -} - -// ======================================================================= -// low level float utility functions - -#ifndef STB_SPRINTF_NOFLOAT - -// copies d to bits w/ strict aliasing (this compiles to nothing on /Ox) -#define STBSP__COPYFP(dest, src) \ - { \ - int cn; \ - for (cn = 0; cn < 8; cn++) \ - ((char *)&dest)[cn] = ((char *)&src)[cn]; \ - } - -// get float info -static stbsp__int32 stbsp__real_to_parts(stbsp__int64 *bits, stbsp__int32 *expo, double value) -{ - double d; - stbsp__int64 b = 0; - - // load value and round at the frac_digits - d = value; - - STBSP__COPYFP(b, d); - - *bits = b & ((((stbsp__uint64)1) << 52) - 1); - *expo = (stbsp__int32)(((b >> 52) & 2047) - 1023); - - return (stbsp__int32)((stbsp__uint64) b >> 63); -} - -static double const stbsp__bot[23] = { - 1e+000, 1e+001, 1e+002, 1e+003, 1e+004, 1e+005, 1e+006, 1e+007, 1e+008, 1e+009, 1e+010, 1e+011, - 1e+012, 1e+013, 1e+014, 1e+015, 1e+016, 1e+017, 1e+018, 1e+019, 1e+020, 1e+021, 1e+022 -}; -static double const stbsp__negbot[22] = { - 1e-001, 1e-002, 1e-003, 1e-004, 1e-005, 1e-006, 1e-007, 1e-008, 1e-009, 1e-010, 1e-011, - 1e-012, 1e-013, 1e-014, 1e-015, 1e-016, 1e-017, 1e-018, 1e-019, 1e-020, 1e-021, 1e-022 -}; -static double const stbsp__negboterr[22] = { - -5.551115123125783e-018, -2.0816681711721684e-019, -2.0816681711721686e-020, -4.7921736023859299e-021, -8.1803053914031305e-022, 4.5251888174113741e-023, - 4.5251888174113739e-024, -2.0922560830128471e-025, -6.2281591457779853e-026, -3.6432197315497743e-027, 6.0503030718060191e-028, 2.0113352370744385e-029, - -3.0373745563400371e-030, 1.1806906454401013e-032, -7.7705399876661076e-032, 2.0902213275965398e-033, -7.1542424054621921e-034, -7.1542424054621926e-035, - 2.4754073164739869e-036, 5.4846728545790429e-037, 9.2462547772103625e-038, -4.8596774326570872e-039 -}; -static double const stbsp__top[13] = { - 1e+023, 1e+046, 1e+069, 1e+092, 1e+115, 1e+138, 1e+161, 1e+184, 1e+207, 1e+230, 1e+253, 1e+276, 1e+299 -}; -static double const stbsp__negtop[13] = { - 1e-023, 1e-046, 1e-069, 1e-092, 1e-115, 1e-138, 1e-161, 1e-184, 1e-207, 1e-230, 1e-253, 1e-276, 1e-299 -}; -static double const stbsp__toperr[13] = { - 8388608, - 6.8601809640529717e+028, - -7.253143638152921e+052, - -4.3377296974619174e+075, - -1.5559416129466825e+098, - -3.2841562489204913e+121, - -3.7745893248228135e+144, - -1.7356668416969134e+167, - -3.8893577551088374e+190, - -9.9566444326005119e+213, - 6.3641293062232429e+236, - -5.2069140800249813e+259, - -5.2504760255204387e+282 -}; -static double const stbsp__negtoperr[13] = { - 3.9565301985100693e-040, -2.299904345391321e-063, 3.6506201437945798e-086, 1.1875228833981544e-109, - -5.0644902316928607e-132, -6.7156837247865426e-155, -2.812077463003139e-178, -5.7778912386589953e-201, - 7.4997100559334532e-224, -4.6439668915134491e-247, -6.3691100762962136e-270, -9.436808465446358e-293, - 8.0970921678014997e-317 -}; - -#if defined(_MSC_VER) && (_MSC_VER <= 1200) -static stbsp__uint64 const stbsp__powten[20] = { - 1, - 10, - 100, - 1000, - 10000, - 100000, - 1000000, - 10000000, - 100000000, - 1000000000, - 10000000000, - 100000000000, - 1000000000000, - 10000000000000, - 100000000000000, - 1000000000000000, - 10000000000000000, - 100000000000000000, - 1000000000000000000, - 10000000000000000000U -}; -#define stbsp__tento19th ((stbsp__uint64)1000000000000000000) -#else -static stbsp__uint64 const stbsp__powten[20] = { - 1, - 10, - 100, - 1000, - 10000, - 100000, - 1000000, - 10000000, - 100000000, - 1000000000, - 10000000000ULL, - 100000000000ULL, - 1000000000000ULL, - 10000000000000ULL, - 100000000000000ULL, - 1000000000000000ULL, - 10000000000000000ULL, - 100000000000000000ULL, - 1000000000000000000ULL, - 10000000000000000000ULL -}; -#define stbsp__tento19th (1000000000000000000ULL) -#endif - -#define stbsp__ddmulthi(oh, ol, xh, yh) \ - { \ - double ahi = 0, alo, bhi = 0, blo; \ - stbsp__int64 bt; \ - oh = xh * yh; \ - STBSP__COPYFP(bt, xh); \ - bt &= ((~(stbsp__uint64)0) << 27); \ - STBSP__COPYFP(ahi, bt); \ - alo = xh - ahi; \ - STBSP__COPYFP(bt, yh); \ - bt &= ((~(stbsp__uint64)0) << 27); \ - STBSP__COPYFP(bhi, bt); \ - blo = yh - bhi; \ - ol = ((ahi * bhi - oh) + ahi * blo + alo * bhi) + alo * blo; \ - } - -#define stbsp__ddtoS64(ob, xh, xl) \ - { \ - double ahi = 0, alo, vh, t; \ - ob = (stbsp__int64)xh; \ - vh = (double)ob; \ - ahi = (xh - vh); \ - t = (ahi - xh); \ - alo = (xh - (ahi - t)) - (vh + t); \ - ob += (stbsp__int64)(ahi + alo + xl); \ - } - -#define stbsp__ddrenorm(oh, ol) \ - { \ - double s; \ - s = oh + ol; \ - ol = ol - (s - oh); \ - oh = s; \ - } - -#define stbsp__ddmultlo(oh, ol, xh, xl, yh, yl) ol = ol + (xh * yl + xl * yh); - -#define stbsp__ddmultlos(oh, ol, xh, yl) ol = ol + (xh * yl); - -static void stbsp__raise_to_power10(double *ohi, double *olo, double d, stbsp__int32 power) // power can be -323 to +350 -{ - double ph, pl; - if ((power >= 0) && (power <= 22)) { - stbsp__ddmulthi(ph, pl, d, stbsp__bot[power]); - } else { - stbsp__int32 e, et, eb; - double p2h, p2l; - - e = power; - if (power < 0) - e = -e; - et = (e * 0x2c9) >> 14; /* %23 */ - if (et > 13) - et = 13; - eb = e - (et * 23); - - ph = d; - pl = 0.0; - if (power < 0) { - if (eb) { - --eb; - stbsp__ddmulthi(ph, pl, d, stbsp__negbot[eb]); - stbsp__ddmultlos(ph, pl, d, stbsp__negboterr[eb]); - } - if (et) { - stbsp__ddrenorm(ph, pl); - --et; - stbsp__ddmulthi(p2h, p2l, ph, stbsp__negtop[et]); - stbsp__ddmultlo(p2h, p2l, ph, pl, stbsp__negtop[et], stbsp__negtoperr[et]); - ph = p2h; - pl = p2l; - } - } else { - if (eb) { - e = eb; - if (eb > 22) - eb = 22; - e -= eb; - stbsp__ddmulthi(ph, pl, d, stbsp__bot[eb]); - if (e) { - stbsp__ddrenorm(ph, pl); - stbsp__ddmulthi(p2h, p2l, ph, stbsp__bot[e]); - stbsp__ddmultlos(p2h, p2l, stbsp__bot[e], pl); - ph = p2h; - pl = p2l; - } - } - if (et) { - stbsp__ddrenorm(ph, pl); - --et; - stbsp__ddmulthi(p2h, p2l, ph, stbsp__top[et]); - stbsp__ddmultlo(p2h, p2l, ph, pl, stbsp__top[et], stbsp__toperr[et]); - ph = p2h; - pl = p2l; - } - } - } - stbsp__ddrenorm(ph, pl); - *ohi = ph; - *olo = pl; -} - -// given a float value, returns the significant bits in bits, and the position of the -// decimal point in decimal_pos. +/-INF and NAN are specified by special values -// returned in the decimal_pos parameter. -// frac_digits is absolute normally, but if you want from first significant digits (got %g and %e), or in 0x80000000 -static stbsp__int32 stbsp__real_to_str(char const **start, stbsp__uint32 *len, char *out, stbsp__int32 *decimal_pos, double value, stbsp__uint32 frac_digits) -{ - double d; - stbsp__int64 bits = 0; - stbsp__int32 expo, e, ng, tens; - - d = value; - STBSP__COPYFP(bits, d); - expo = (stbsp__int32)((bits >> 52) & 2047); - ng = (stbsp__int32)((stbsp__uint64) bits >> 63); - if (ng) - d = -d; - - if (expo == 2047) // is nan or inf? - { - *start = (bits & ((((stbsp__uint64)1) << 52) - 1)) ? "NaN" : "Inf"; - *decimal_pos = STBSP__SPECIAL; - *len = 3; - return ng; - } - - if (expo == 0) // is zero or denormal - { - if (((stbsp__uint64) bits << 1) == 0) // do zero - { - *decimal_pos = 1; - *start = out; - out[0] = '0'; - *len = 1; - return ng; - } - // find the right expo for denormals - { - stbsp__int64 v = ((stbsp__uint64)1) << 51; - while ((bits & v) == 0) { - --expo; - v >>= 1; - } - } - } - - // find the decimal exponent as well as the decimal bits of the value - { - double ph, pl; - - // log10 estimate - very specifically tweaked to hit or undershoot by no more than 1 of log10 of all expos 1..2046 - tens = expo - 1023; - tens = (tens < 0) ? ((tens * 617) / 2048) : (((tens * 1233) / 4096) + 1); - - // move the significant bits into position and stick them into an int - stbsp__raise_to_power10(&ph, &pl, d, 18 - tens); - - // get full as much precision from double-double as possible - stbsp__ddtoS64(bits, ph, pl); - - // check if we undershot - if (((stbsp__uint64)bits) >= stbsp__tento19th) - ++tens; - } - - // now do the rounding in integer land - frac_digits = (frac_digits & 0x80000000) ? ((frac_digits & 0x7ffffff) + 1) : (tens + frac_digits); - if ((frac_digits < 24)) { - stbsp__uint32 dg = 1; - if ((stbsp__uint64)bits >= stbsp__powten[9]) - dg = 10; - while ((stbsp__uint64)bits >= stbsp__powten[dg]) { - ++dg; - if (dg == 20) - goto noround; - } - if (frac_digits < dg) { - stbsp__uint64 r; - // add 0.5 at the right position and round - e = dg - frac_digits; - if ((stbsp__uint32)e >= 24) - goto noround; - r = stbsp__powten[e]; - bits = bits + (r / 2); - if ((stbsp__uint64)bits >= stbsp__powten[dg]) - ++tens; - bits /= r; - } - noround:; - } - - // kill long trailing runs of zeros - if (bits) { - stbsp__uint32 n; - for (;;) { - if (bits <= 0xffffffff) - break; - if (bits % 1000) - goto donez; - bits /= 1000; - } - n = (stbsp__uint32)bits; - while ((n % 1000) == 0) - n /= 1000; - bits = n; - donez:; - } - - // convert to string - out += 64; - e = 0; - for (;;) { - stbsp__uint32 n; - char *o = out - 8; - // do the conversion in chunks of U32s (avoid most 64-bit divides, worth it, constant denomiators be damned) - if (bits >= 100000000) { - n = (stbsp__uint32)(bits % 100000000); - bits /= 100000000; - } else { - n = (stbsp__uint32)bits; - bits = 0; - } - while (n) { - out -= 2; - *(stbsp__uint16 *)out = *(stbsp__uint16 *)&stbsp__digitpair.pair[(n % 100) * 2]; - n /= 100; - e += 2; - } - if (bits == 0) { - if ((e) && (out[0] == '0')) { - ++out; - --e; - } - break; - } - while (out != o) { - *--out = '0'; - ++e; - } - } - - *decimal_pos = tens; - *start = out; - *len = e; - return ng; -} - -#undef stbsp__ddmulthi -#undef stbsp__ddrenorm -#undef stbsp__ddmultlo -#undef stbsp__ddmultlos -#undef STBSP__SPECIAL -#undef STBSP__COPYFP - -#endif // STB_SPRINTF_NOFLOAT - -// clean up -#undef stbsp__uint16 -#undef stbsp__uint32 -#undef stbsp__int32 -#undef stbsp__uint64 -#undef stbsp__int64 -#undef STBSP__UNALIGNED - -#if COLLA_CLANG -#pragma clang diagnostic pop -#endif - -#endif // STB_SPRINTF_IMPLEMENTATION - -/* ------------------------------------------------------------------------------- -This software is available under 2 licenses -- choose whichever you prefer. ------------------------------------------------------------------------------- -ALTERNATIVE A - MIT License -Copyright (c) 2017 Sean Barrett -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. ------------------------------------------------------------------------------- -ALTERNATIVE B - Public Domain (www.unlicense.org) -This is free and unencumbered software released into the public domain. -Anyone is free to copy, modify, publish, use, compile, sell, or distribute this -software, either in source code form or as a compiled binary, for any purpose, -commercial or non-commercial, and by any means. -In jurisdictions that recognize copyright laws, the author or authors of this -software dedicate any and all copyright interest in the software to the public -domain. We make this dedication for the benefit of the public at large and to -the detriment of our heirs and successors. We intend this dedication to be an -overt act of relinquishment in perpetuity of all present and future rights to -this software under copyright law. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------------------------------- -*/ diff --git a/str.c b/str.c deleted file mode 100644 index f6dae95..0000000 --- a/str.c +++ /dev/null @@ -1,795 +0,0 @@ -#include "str.h" - -#include "os.h" - -#include -#include -#include - -#if COLLA_WIN -#include "win/str_win32.c" -#else -#error "platform not supported" -#endif - -// == STR_T ======================================================== - -strview_t strv__ignore(str_t s, size_t l) { - COLLA_UNUSED(s); COLLA_UNUSED(l); - return STRV_EMPTY; -} - -str_t str_init(arena_t *arena, const char *buf) { - return str_init_len(arena, buf, buf ? strlen(buf) : 0); -} - -str_t str_init_len(arena_t *arena, const char *buf, usize len) { - if (!buf || !len) return STR_EMPTY; - char *str = alloc(arena, char, len + 1); - memmove(str, buf, len); - return (str_t){ str, len }; -} - -str_t str_init_view(arena_t *arena, strview_t view) { - return str_init_len(arena, view.buf, view.len); -} - -str_t str_fmt(arena_t *arena, const char *fmt, ...) { - va_list args; - va_start(args, fmt); - str_t out = str_fmtv(arena, fmt, args); - va_end(args); - return out; -} - -str_t str_fmtv(arena_t *arena, const char *fmt, va_list args) { - va_list vcopy; - va_copy(vcopy, args); - // stb_vsnprintf returns the length + null_term - int len = fmt_bufferv(NULL, 0, fmt, vcopy); - va_end(vcopy); - - char *buffer = alloc(arena, char, len + 1); - fmt_bufferv(buffer, len + 1, fmt, args); - - return (str_t) { .buf = buffer, .len = (usize)len }; -} - -tstr_t tstr_init(TCHAR *str, usize optional_len) { - if (str && !optional_len) { -#if COLLA_UNICODE - optional_len = wcslen(str); -#else - optional_len = strlen(str); -#endif - } - return (tstr_t){ - .buf = str, - .len = optional_len, - }; -} - -str16_t str16_init(u16 *str, usize optional_len) { - if (str && !optional_len) { - optional_len = wcslen(str); - } - return (str16_t){ - .buf = str, - .len = optional_len, - }; -} - -str_t str_from_str16(arena_t *arena, str16_t src) { - if (!src.buf) return STR_EMPTY; - if (!src.len) return STR_EMPTY; - - str_t out = str_os_from_str16(arena, src); - - return out; -} - -str_t str_from_tstr(arena_t *arena, tstr_t src) { -#if COLLA_UNICODE - return str_from_str16(arena, src); -#else - return str(arena, strv(src)); -#endif -} - -str16_t str16_from_str(arena_t *arena, str_t src) { - return strv_to_str16(arena, strv(src)); -} - -bool str_equals(str_t a, str_t b) { - return str_compare(a, b) == 0; -} - -int str_compare(str_t a, str_t b) { - // TODO unsinged underflow if a.len < b.len - return a.len == b.len ? memcmp(a.buf, b.buf, a.len) : (int)(a.len - b.len); -} - -str_t str_dup(arena_t *arena, str_t src) { - return str_init_len(arena, src.buf, src.len); -} - -str_t str_cat(arena_t *arena, str_t a, str_t b) { - str_t out = STR_EMPTY; - - out.len += a.len + b.len; - out.buf = alloc(arena, char, out.len + 1); - memcpy(out.buf, a.buf, a.len); - memcpy(out.buf + a.len, b.buf, b.len); - - return out; -} - -bool str_is_empty(str_t ctx) { - return !ctx.buf || !ctx.len; -} - -void str_lower(str_t *src) { - for (usize i = 0; i < src->len; ++i) { - if (src->buf[i] >= 'A' && src->buf[i] <= 'Z') { - src->buf[i] += 'a' - 'A'; - } - } -} - -void str_upper(str_t *src) { - for (usize i = 0; i < src->len; ++i) { - if (src->buf[i] >= 'a' && src->buf[i] <= 'z') { - src->buf[i] -= 'a' - 'A'; - } - } -} - -void str_replace(str_t *ctx, char from, char to) { - if (!ctx) return; - char *buf = ctx->buf; - for (usize i = 0; i < ctx->len; ++i) { - buf[i] = buf[i] == from ? to : buf[i]; - } -} - -strview_t str_sub(str_t ctx, usize from, usize to) { - if (to > ctx.len) to = ctx.len; - if (from > to) from = to; - return (strview_t){ ctx.buf + from, to - from }; -} - -// == STRVIEW_T ==================================================== - -strview_t strv_init(const char *cstr) { - return strv_init_len(cstr, cstr ? strlen(cstr) : 0); -} - -strview_t strv_init_len(const char *buf, usize size) { - return (strview_t){ - .buf = buf, - .len = size, - }; -} - -strview_t strv_init_str(str_t str) { - return (strview_t){ - .buf = str.buf, - .len = str.len - }; -} - -bool strv_is_empty(strview_t ctx) { - return ctx.len == 0 || !ctx.buf; -} - -bool strv_equals(strview_t a, strview_t b) { - return strv_compare(a, b) == 0; -} - -int strv_compare(strview_t a, strview_t b) { - // TODO unsinged underflow if a.len < b.len - return a.len == b.len ? - memcmp(a.buf, b.buf, a.len) : - (int)(a.len - b.len); -} - -char strv_front(strview_t ctx) { - return ctx.len > 0 ? ctx.buf[0] : '\0'; -} - -char strv_back(strview_t ctx) { - return ctx.len > 0 ? ctx.buf[ctx.len - 1] : '\0'; -} - -str16_t strv_to_str16(arena_t *arena, strview_t src) { - return strv_os_to_str16(arena, src); -} - -tstr_t strv_to_tstr(arena_t *arena, strview_t src) { -#if UNICODE - return strv_to_str16(arena, src); -#else - return str(arena, src); -#endif -} - -str_t strv_to_upper(arena_t *arena, strview_t src) { - str_t out = str(arena, src); - str_upper(&out); - return out; -} - -str_t strv_to_lower(arena_t *arena, strview_t src) { - str_t out = str(arena, src); - str_lower(&out); - return out; -} - -strview_t strv_remove_prefix(strview_t ctx, usize n) { - if (n > ctx.len) n = ctx.len; - return (strview_t){ - .buf = ctx.buf + n, - .len = ctx.len - n, - }; -} - -strview_t strv_remove_suffix(strview_t ctx, usize n) { - if (n > ctx.len) n = ctx.len; - return (strview_t){ - .buf = ctx.buf, - .len = ctx.len - n, - }; -} - -strview_t strv_trim(strview_t ctx) { - return strv_trim_left(strv_trim_right(ctx)); -} - -strview_t strv_trim_left(strview_t ctx) { - strview_t out = ctx; - for (usize i = 0; i < ctx.len; ++i) { - char c = ctx.buf[i]; - if (c != ' ' && (c < '\t' || c > '\r')) { - break; - } - out.buf++; - out.len--; - } - return out; -} - -strview_t strv_trim_right(strview_t ctx) { - strview_t out = ctx; - for (isize i = ctx.len - 1; i >= 0; --i) { - char c = ctx.buf[i]; - if (c != ' ' && (c < '\t' || c > '\r')) { - break; - } - out.len--; - } - return out; -} - -strview_t strv_sub(strview_t ctx, usize from, usize to) { - if (ctx.len == 0) return STRV_EMPTY; - if (to > ctx.len) to = ctx.len; - if (from > to) from = to; - return (strview_t){ ctx.buf + from, to - from }; -} - -bool strv_starts_with(strview_t ctx, char c) { - return ctx.len > 0 && ctx.buf[0] == c; -} - -bool strv_starts_with_view(strview_t ctx, strview_t view) { - return ctx.len >= view.len && memcmp(ctx.buf, view.buf, view.len) == 0; -} - -bool strv_ends_with(strview_t ctx, char c) { - return ctx.len > 0 && ctx.buf[ctx.len - 1] == c; -} - -bool strv_ends_with_view(strview_t ctx, strview_t view) { - return ctx.len >= view.len && memcmp(ctx.buf + ctx.len - view.len, view.buf, view.len) == 0; -} - -bool strv_contains(strview_t ctx, char c) { - for(usize i = 0; i < ctx.len; ++i) { - if(ctx.buf[i] == c) { - return true; - } - } - return false; -} - -bool strv_contains_view(strview_t ctx, strview_t view) { - if (ctx.len < view.len) return false; - usize end = (ctx.len - view.len) + 1; - - for (usize i = 0; i < end; ++i) { - if (memcmp(ctx.buf + i, view.buf, view.len) == 0) { - return true; - } - } - return false; -} - -bool strv_contains_either(strview_t ctx, strview_t chars) { - for (usize i = 0; i < ctx.len; ++i) { - if (strv_contains(chars, ctx.buf[i])) { - return true; - } - } - - return false; -} - -usize strv_find(strview_t ctx, char c, usize from) { - for (usize i = from; i < ctx.len; ++i) { - if (ctx.buf[i] == c) { - return i; - } - } - return STR_NONE; -} - -usize strv_find_view(strview_t ctx, strview_t view, usize from) { - if (view.len > ctx.len) return STR_NONE; - - usize end = (ctx.len - view.len) + 1; - - for (usize i = from; i < end; ++i) { - if (memcmp(ctx.buf + i, view.buf, view.len) == 0) { - return i; - } - } - return STR_NONE; -} - -usize strv_find_either(strview_t ctx, strview_t chars, usize from) { - if (from > ctx.len) from = ctx.len; - - for (usize i = from; i < ctx.len; ++i) { - if (strv_contains(chars, ctx.buf[i])) { - return i; - } - } - - return STR_NONE; -} - -usize strv_rfind(strview_t ctx, char c, usize from_right) { - if (ctx.len == 0) return STR_NONE; - if (from_right > ctx.len) from_right = ctx.len; - isize end = (isize)(ctx.len - from_right); - for (isize i = end; i >= 0; --i) { - if (ctx.buf[i] == c) { - return (usize)i; - } - } - return STR_NONE; -} - -usize strv_rfind_view(strview_t ctx, strview_t view, usize from_right) { - if (ctx.len == 0) return STR_NONE; - if (from_right > ctx.len) from_right = ctx.len; - isize end = (isize)(ctx.len - from_right); - if (end < (isize)view.len) return STR_NONE; - for (isize i = end - view.len; i >= 0; --i) { - if (memcmp(ctx.buf + i, view.buf, view.len) == 0) { - return (usize)i; - } - } - return STR_NONE; -} - -// == CTYPE ======================================================== - -bool char_is_space(char c) { - return (c >= '\t' && c <= '\r') || c == ' '; -} - -bool char_is_alpha(char c) { - return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); -} - -bool char_is_num(char c) { - return c >= '0' && c <= '9'; -} - -char char_lower(char c) { - return c >= 'A' && c <= 'Z' ? c + 32 : c; -} - -char char_upper(char c) { - return c <= 'a' && c >= 'z' ? c - 32 : c; -} - -// == INPUT STREAM ================================================= - -instream_t istr_init(strview_t str) { - return (instream_t) { - .beg = str.buf, - .cur = str.buf, - .len = str.len, - }; -} - -char istr_get(instream_t *ctx) { - return istr_remaining(ctx) ? *ctx->cur++ : '\0'; -} - -char istr_peek(instream_t *ctx) { - return istr_remaining(ctx) ? *ctx->cur : '\0'; -} - -char istr_peek_next(instream_t *ctx) { - return istr_remaining(ctx) > 1 ? *(ctx->cur + 1) : '\0'; -} - -char istr_prev(instream_t *ctx) { - return istr_tell(ctx) ? *(ctx->cur - 1) : '\0'; -} - -char istr_prev_prev(instream_t *ctx) { - return istr_tell(ctx) > 1 ? *(ctx->cur - 2) : '\0'; -} - -void istr_ignore(instream_t *ctx, char delim) { - while (!istr_is_finished(ctx) && *ctx->cur != delim) { - ctx->cur++; - } -} - -void istr_ignore_and_skip(instream_t *ctx, char delim) { - istr_ignore(ctx, delim); - istr_skip(ctx, 1); -} - -void istr_skip(instream_t *ctx, usize n) { - if (!ctx) return; - usize rem = istr_remaining(ctx); - if (n > rem) n = rem; - ctx->cur += n; -} - -void istr_skip_whitespace(instream_t *ctx) { - while (!istr_is_finished(ctx) && char_is_space(*ctx->cur)) { - ctx->cur++; - } -} - -void istr_rewind(instream_t *ctx) { - if (ctx) ctx->cur = ctx->beg; -} - -void istr_rewind_n(instream_t *ctx, usize amount) { - if (!ctx) return; - usize rem = istr_remaining(ctx); - ctx->cur -= MIN(amount, rem); -} - -usize istr_tell(instream_t *ctx) { - return ctx ? ctx->cur - ctx->beg : 0; -} - -usize istr_remaining(instream_t *ctx) { - return ctx ? ctx->len - (ctx->cur - ctx->beg) : 0; -} - -bool istr_is_finished(instream_t *ctx) { - return !(ctx && istr_remaining(ctx) > 0); -} - -bool istr_get_bool(instream_t *ctx, bool *val) { - if (!ctx || !ctx->cur || !val) return false; - usize rem = istr_remaining(ctx); - if (rem >= 4 && memcmp(ctx->cur, "true", 4) == 0) { - *val = true; - ctx->cur += 4; - return true; - } - if (rem >= 5 && memcmp(ctx->cur, "false", 5) == 0) { - *val = false; - ctx->cur += 5; - return true; - } - return false; -} - -bool istr_get_u8(instream_t *ctx, u8 *val) { - u64 out = 0; - bool result = istr_get_u64(ctx, &out); - if (result && out < UINT8_MAX) { - *val = (u8)out; - } - return result; -} - -bool istr_get_u16(instream_t *ctx, u16 *val) { - u64 out = 0; - bool result = istr_get_u64(ctx, &out); - if (result && out < UINT16_MAX) { - *val = (u16)out; - } - return result; -} - -bool istr_get_u32(instream_t *ctx, u32 *val) { - u64 out = 0; - bool result = istr_get_u64(ctx, &out); - if (result && out < UINT32_MAX) { - *val = (u32)out; - } - return result; -} - - -bool istr_get_u64(instream_t *ctx, u64 *val) { - if (!ctx || !ctx->cur || !val) return false; - char *end = NULL; - *val = strtoull(ctx->cur, &end, 0); - - if (ctx->cur == end) { - return false; - } - else if (*val == ULLONG_MAX) { - return false; - } - - ctx->cur = end; - return true; -} - -bool istr_get_i8(instream_t *ctx, i8 *val) { - i64 out = 0; - bool result = istr_get_i64(ctx, &out); - if (result && out > INT8_MIN && out < INT8_MAX) { - *val = (i8)out; - } - return result; -} - -bool istr_get_i16(instream_t *ctx, i16 *val) { - i64 out = 0; - bool result = istr_get_i64(ctx, &out); - if (result && out > INT16_MIN && out < INT16_MAX) { - *val = (i16)out; - } - return result; -} - -bool istr_get_i32(instream_t *ctx, i32 *val) { - i64 out = 0; - bool result = istr_get_i64(ctx, &out); - if (result && out > INT32_MIN && out < INT32_MAX) { - *val = (i32)out; - } - return result; -} - -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); - - if (ctx->cur == end) { - return false; - } - else if(*val == INT64_MAX || *val == INT64_MIN) { - return false; - } - - ctx->cur = end; - return true; -} - -bool istr_get_num(instream_t *ctx, double *val) { - if (!ctx || !ctx->cur || !val) return false; - char *end = NULL; - *val = strtod(ctx->cur, &end); - - if(ctx->cur == end) { - warn("istrGetDouble: no valid conversion could be performed (%.5s)", ctx->cur); - return false; - } - else if(*val == HUGE_VAL || *val == -HUGE_VAL) { - warn("istrGetDouble: value read is out of the range of representable values"); - return false; - } - - ctx->cur = end; - return true; -} - -strview_t istr_get_view(instream_t *ctx, char delim) { - if (!ctx || !ctx->cur) return STRV_EMPTY; - const char *from = ctx->cur; - istr_ignore(ctx, delim); - usize len = ctx->cur - from; - return strv(from, len); -} - -strview_t istr_get_view_either(instream_t *ctx, strview_t chars) { - if (!ctx || !ctx->cur) return STRV_EMPTY; - const char *from = ctx->cur; - while (!istr_is_finished(ctx) && !strv_contains(chars, *ctx->cur)) { - ctx->cur++; - } - - usize len = ctx->cur - from; - return strv(from, len); -} - -strview_t istr_get_view_len(instream_t *ctx, usize len) { - if (!ctx || !ctx->cur) return STRV_EMPTY; - const char *from = ctx->cur; - istr_skip(ctx, len); - usize buflen = ctx->cur - from; - return (strview_t){ from, buflen }; -} - -strview_t istr_get_line(instream_t *ctx) { - strview_t line = istr_get_view(ctx, '\n'); - istr_skip(ctx, 1); - if (strv_ends_with(line, '\r')) { - line = strv_remove_suffix(line, 1); - } - return line; -} - -// == OUTPUT STREAM ================================================ - -outstream_t ostr_init(arena_t *exclusive_arena) { - return (outstream_t) { - .beg = (char *)(exclusive_arena ? exclusive_arena->cur : NULL), - .arena = exclusive_arena, - }; -} - -void ostr_clear(outstream_t *ctx) { - arena_pop(ctx->arena, ostr_tell(ctx)); -} - -usize ostr_tell(outstream_t *ctx) { - return ctx->arena ? (char *)ctx->arena->cur - ctx->beg : 0; -} - -char ostr_back(outstream_t *ctx) { - usize len = ostr_tell(ctx); - return len ? ctx->beg[len - 1] : '\0'; -} - -str_t ostr_to_str(outstream_t *ctx) { - ostr_putc(ctx, '\0'); - - usize len = ostr_tell(ctx); - - str_t out = { - .buf = ctx->beg, - .len = len ? len - 1 : 0, - }; - - memset(ctx, 0, sizeof(outstream_t)); - return out; -} - -strview_t ostr_as_view(outstream_t *ctx) { - return strv(ctx->beg, ostr_tell(ctx)); -} - -void ostr_pop(outstream_t *ctx, usize count) { - if (!ctx->arena) return; - arena_pop(ctx->arena, count); -} - -void ostr_print(outstream_t *ctx, const char *fmt, ...) { - va_list args; - va_start(args, fmt); - ostr_printv(ctx, fmt, args); - va_end(args); -} - -void ostr_printv(outstream_t *ctx, const char *fmt, va_list args) { - if (!ctx->arena) return; - str_fmtv(ctx->arena, fmt, args); - // remove null terminator - arena_pop(ctx->arena, 1); -} - -void ostr_putc(outstream_t *ctx, char c) { - if (!ctx->arena) return; - char *newc = alloc(ctx->arena, char); - *newc = c; -} - -void ostr_puts(outstream_t *ctx, strview_t v) { - if (strv_is_empty(v)) return; - str(ctx->arena, v); - // remove null terminator - arena_pop(ctx->arena, 1); -} - -void ostr_append_bool(outstream_t *ctx, bool val) { - ostr_puts(ctx, val ? strv("true") : strv("false")); -} - -void ostr_append_uint(outstream_t *ctx, u64 val) { - ostr_print(ctx, "%I64u", val); -} - -void ostr_append_int(outstream_t *ctx, i64 val) { - ostr_print(ctx, "%I64d", val); -} - -void ostr_append_num(outstream_t *ctx, double val) { - ostr_print(ctx, "%g", val); -} - -// == INPUT BINARY STREAM ========================================== - -ibstream_t ibstr_init(buffer_t buffer) { - return (ibstream_t){ - .beg = buffer.data, - .cur = buffer.data, - .len = buffer.len, - }; -} - -bool ibstr_is_finished(ibstream_t *ib) { - return !(ib && ibstr_remaining(ib) > 0); -} - -usize ibstr_tell(ibstream_t *ib) { - return ib && ib->cur ? ib->cur - ib->beg : 0; -} - -usize ibstr_remaining(ibstream_t *ib) { - return ib ? ib->len - ibstr_tell(ib) : 0; -} - -usize ibstr_read(ibstream_t *ib, void *buffer, usize len) { - usize rem = ibstr_remaining(ib); - if (len > rem) len = rem; - memmove(buffer, ib->cur, len); - ib->cur += len; - return len; -} - -void ibstr_skip(ibstream_t *ib, usize count) { - usize rem = ibstr_remaining(ib); - if (count > rem) count = rem; - ib->cur += count; -} - -bool ibstr_get_u8(ibstream_t *ib, u8 *out) { - return ibstr_read(ib, out, sizeof(*out)) == sizeof(*out); -} - -bool ibstr_get_u16(ibstream_t *ib, u16 *out) { - return ibstr_read(ib, out, sizeof(*out)) == sizeof(*out); -} - -bool ibstr_get_u32(ibstream_t *ib, u32 *out) { - return ibstr_read(ib, out, sizeof(*out)) == sizeof(*out); -} - -bool ibstr_get_u64(ibstream_t *ib, u64 *out) { - return ibstr_read(ib, out, sizeof(*out)) == sizeof(*out); -} - -bool ibstr_get_i8(ibstream_t *ib, i8 *out) { - return ibstr_read(ib, out, sizeof(*out)) == sizeof(*out); -} - -bool ibstr_get_i16(ibstream_t *ib, i16 *out) { - return ibstr_read(ib, out, sizeof(*out)) == sizeof(*out); -} - -bool ibstr_get_i32(ibstream_t *ib, i32 *out) { - return ibstr_read(ib, out, sizeof(*out)) == sizeof(*out); -} - -bool ibstr_get_i64(ibstream_t *ib, i64 *out) { - return ibstr_read(ib, out, sizeof(*out)) == sizeof(*out); -} diff --git a/str.h b/str.h deleted file mode 100644 index 8e5b398..0000000 --- a/str.h +++ /dev/null @@ -1,278 +0,0 @@ -#ifndef COLLA_STR_H -#define COLLA_STR_H - -#include "core.h" -#include "darr.h" - -#define STR_NONE SIZE_MAX - -typedef struct str_t str_t; -struct str_t { - char *buf; - usize len; -}; - -typedef struct str16_t str16_t; -struct str16_t { - u16 *buf; - usize len; -}; - -#if COLLA_UNICODE -typedef str16_t tstr_t; -#else -typedef str_t tstr_t; -#endif - -typedef struct strview_t strview_t; -struct strview_t { - const char *buf; - usize len; -}; - -darr_define(str_list_t, str_t); -darr_define(strv_list_t, strview_t); - -// == STR_T ======================================================== - -#define str__1(arena, x) \ - _Generic((x), \ - const char *: str_init, \ - char *: str_init, \ - strview_t: str_init_view \ - )(arena, x) - -#define str__2(arena, cstr, clen) str_init_len(arena, cstr, clen) -#define str__impl(_1, _2, n, ...) str__##n - -// either: -// arena_t arena, [const] char *cstr, [usize len] -// arena_t arena, strview_t view -#define str(arena, ...) str__impl(__VA_ARGS__, 2, 1, 0)(arena, __VA_ARGS__) - -#define STR_EMPTY (str_t){0} - -str_t str_init(arena_t *arena, const char *buf); -str_t str_init_len(arena_t *arena, const char *buf, usize len); -str_t str_init_view(arena_t *arena, strview_t view); -str_t str_fmt(arena_t *arena, const char *fmt, ...); -str_t str_fmtv(arena_t *arena, const char *fmt, va_list args); - -tstr_t tstr_init(TCHAR *str, usize optional_len); -str16_t str16_init(u16 *str, usize optional_len); - -str_t str_from_str16(arena_t *arena, str16_t src); -str_t str_from_tstr(arena_t *arena, tstr_t src); -str16_t str16_from_str(arena_t *arena, str_t src); - -bool str_equals(str_t a, str_t b); -int str_compare(str_t a, str_t b); - -str_t str_dup(arena_t *arena, str_t src); -str_t str_cat(arena_t *arena, str_t a, str_t b); -bool str_is_empty(str_t ctx); - -void str_lower(str_t *src); -void str_upper(str_t *src); - -void str_replace(str_t *ctx, char from, char to); -// if len == SIZE_MAX, copies until end -strview_t str_sub(str_t ctx, usize from, usize to); - -// == STRVIEW_T ==================================================== - -// these macros might be THE worst code ever written, but they work ig -// detects if you're trying to create a string view from either: -// - a str_t -> calls strv_init_str -// - a string literal -> calls strv_init_len with comptime size -// - a c string -> calls strv_init with runtime size - -#define STRV_EMPTY (strview_t){0} - -// needed for strv__init_literal _Generic implementation, it's never actually called -strview_t strv__ignore(str_t s, size_t l); - -#define strv__check(x, ...) ((#x)[0] == '"') -#define strv__init_literal(x, ...) \ - _Generic((x), \ - char *: strv_init_len, \ - const char *: strv_init_len, \ - str_t: strv__ignore \ - )(x, sizeof(x) - 1) - -#define strv__1(x) \ - _Generic((x), \ - char *: strv_init, \ - const char *: strv_init, \ - str_t: strv_init_str \ - )(x) - -#define strv__2(cstr, clen) strv_init_len(cstr, clen) - -#define strv__impl(_1, _2, n, ...) strv__##n - -#define strv(...) strv__check(__VA_ARGS__) ? strv__init_literal(__VA_ARGS__) : strv__impl(__VA_ARGS__, 2, 1, 0)(__VA_ARGS__) - -#define cstrv(cstr) { cstr, sizeof(cstr) - 1, } - -strview_t strv_init(const char *cstr); -strview_t strv_init_len(const char *buf, usize size); -strview_t strv_init_str(str_t str); - -bool strv_is_empty(strview_t ctx); -bool strv_equals(strview_t a, strview_t b); -int strv_compare(strview_t a, strview_t b); - -char strv_front(strview_t ctx); -char strv_back(strview_t ctx); - -str16_t strv_to_str16(arena_t *arena, strview_t src); -tstr_t strv_to_tstr(arena_t *arena, strview_t src); - -str_t strv_to_upper(arena_t *arena, strview_t src); -str_t strv_to_lower(arena_t *arena, strview_t src); - -strview_t strv_remove_prefix(strview_t ctx, usize n); -strview_t strv_remove_suffix(strview_t ctx, usize n); -strview_t strv_trim(strview_t ctx); -strview_t strv_trim_left(strview_t ctx); -strview_t strv_trim_right(strview_t ctx); - -strview_t strv_sub(strview_t ctx, usize from, usize to); - -bool strv_starts_with(strview_t ctx, char c); -bool strv_starts_with_view(strview_t ctx, strview_t view); - -bool strv_ends_with(strview_t ctx, char c); -bool strv_ends_with_view(strview_t ctx, strview_t view); - -bool strv_contains(strview_t ctx, char c); -bool strv_contains_view(strview_t ctx, strview_t view); -bool strv_contains_either(strview_t ctx, strview_t chars); - -usize strv_find(strview_t ctx, char c, usize from); -usize strv_find_view(strview_t ctx, strview_t view, usize from); -usize strv_find_either(strview_t ctx, strview_t chars, usize from); - -usize strv_rfind(strview_t ctx, char c, usize from_right); -usize strv_rfind_view(strview_t ctx, strview_t view, usize from_right); - -// == CTYPE ======================================================== - -bool char_is_space(char c); -bool char_is_alpha(char c); -bool char_is_num(char c); -char char_lower(char c); -char char_upper(char c); - -// == INPUT STREAM ================================================= - -typedef struct instream_t instream_t; -struct instream_t { - const char *beg; - const char *cur; - usize len; -}; - -instream_t istr_init(strview_t str); - -// get the current character and advance -char istr_get(instream_t *ctx); -// get the current character but don't advance -char istr_peek(instream_t *ctx); -// get the next character but don't advance -char istr_peek_next(instream_t *ctx); -// returns the previous character -char istr_prev(instream_t *ctx); -// returns the character before the previous -char istr_prev_prev(instream_t *ctx); -// ignore characters until the delimiter -void istr_ignore(instream_t *ctx, char delim); -// ignore characters until the delimiter and skip it -void istr_ignore_and_skip(instream_t *ctx, char delim); -// skip n characters -void istr_skip(instream_t *ctx, usize n); -// skips whitespace (' ', '\\n', '\\t', '\\r') -void istr_skip_whitespace(instream_t *ctx); -// returns to the beginning of the stream -void istr_rewind(instream_t *ctx); -// returns back characters -void istr_rewind_n(instream_t *ctx, usize amount); -// returns the number of bytes read from beginning of stream -usize istr_tell(instream_t *ctx); -// returns the number of bytes left to read in the stream -usize istr_remaining(instream_t *ctx); -// return true if the stream doesn't have any new bytes to read -bool istr_is_finished(instream_t *ctx); - -bool istr_get_bool(instream_t *ctx, bool *val); -bool istr_get_u8(instream_t *ctx, u8 *val); -bool istr_get_u16(instream_t *ctx, u16 *val); -bool istr_get_u32(instream_t *ctx, u32 *val); -bool istr_get_u64(instream_t *ctx, u64 *val); -bool istr_get_i8(instream_t *ctx, i8 *val); -bool istr_get_i16(instream_t *ctx, i16 *val); -bool istr_get_i32(instream_t *ctx, i32 *val); -bool istr_get_i64(instream_t *ctx, i64 *val); -bool istr_get_num(instream_t *ctx, double *val); -strview_t istr_get_view(instream_t *ctx, char delim); -strview_t istr_get_view_either(instream_t *ctx, strview_t chars); -strview_t istr_get_view_len(instream_t *ctx, usize len); -strview_t istr_get_line(instream_t *ctx); - -// == OUTPUT STREAM ================================================ - -typedef struct outstream_t outstream_t; -struct outstream_t { - char *beg; - arena_t *arena; -}; - -outstream_t ostr_init(arena_t *exclusive_arena); -void ostr_clear(outstream_t *ctx); - -usize ostr_tell(outstream_t *ctx); - -char ostr_back(outstream_t *ctx); -str_t ostr_to_str(outstream_t *ctx); -strview_t ostr_as_view(outstream_t *ctx); - -void ostr_pop(outstream_t *ctx, usize count); - -void ostr_print(outstream_t *ctx, const char *fmt, ...); -void ostr_printv(outstream_t *ctx, const char *fmt, va_list args); -void ostr_putc(outstream_t *ctx, char c); -void ostr_puts(outstream_t *ctx, strview_t v); - -void ostr_append_bool(outstream_t *ctx, bool val); -void ostr_append_uint(outstream_t *ctx, u64 val); -void ostr_append_int(outstream_t *ctx, i64 val); -void ostr_append_num(outstream_t *ctx, double val); - -// == INPUT BINARY STREAM ========================================== - -typedef struct { - const u8 *beg; - const u8 *cur; - usize len; -} ibstream_t; - -ibstream_t ibstr_init(buffer_t buffer); - -bool ibstr_is_finished(ibstream_t *ib); -usize ibstr_tell(ibstream_t *ib); -usize ibstr_remaining(ibstream_t *ib); -usize ibstr_read(ibstream_t *ib, void *buffer, usize len); -void ibstr_skip(ibstream_t *ib, usize count); - -bool ibstr_get_u8(ibstream_t *ib, u8 *out); -bool ibstr_get_u16(ibstream_t *ib, u16 *out); -bool ibstr_get_u32(ibstream_t *ib, u32 *out); -bool ibstr_get_u64(ibstream_t *ib, u64 *out); - -bool ibstr_get_i8(ibstream_t *ib, i8 *out); -bool ibstr_get_i16(ibstream_t *ib, i16 *out); -bool ibstr_get_i32(ibstream_t *ib, i32 *out); -bool ibstr_get_i64(ibstream_t *ib, i64 *out); - -#endif diff --git a/tcc/colla.def b/tcc/colla.def deleted file mode 100644 index 4f47962..0000000 --- a/tcc/colla.def +++ /dev/null @@ -1,12 +0,0 @@ -LIBRARY - -EXPORTS -GetThreadId -InitializeConditionVariable -WakeConditionVariable -WakeAllConditionVariable -SleepConditionVariableCS -InternetOpen -InternetConnect -HttpOpenRequest -HttpSendRequest \ No newline at end of file diff --git a/tests/arena_tests.c b/tests/arena_tests.c deleted file mode 100644 index 3523e51..0000000 --- a/tests/arena_tests.c +++ /dev/null @@ -1,237 +0,0 @@ -#include "../arena.h" -#include "../os.h" -#include "../core.h" - -#include "runner.h" - -UNIT_TEST(arena_init_virtual) { - arena_t arena = arena_make(ARENA_VIRTUAL, MB(1)); - ASSERT(arena.type == ARENA_VIRTUAL); - ASSERT(arena.beg != NULL); - ASSERT(arena.cur == arena.beg); - ASSERT(arena.end == arena.beg + MB(1)); - arena_cleanup(&arena); -} - -UNIT_TEST(arena_init_malloc) { - arena_t arena = arena_make(ARENA_MALLOC, KB(4)); - ASSERT(arena.type == ARENA_MALLOC); - ASSERT(arena.beg != NULL); - ASSERT(arena.cur == arena.beg); - ASSERT(arena.end == arena.beg + KB(4)); - arena_cleanup(&arena); -} - -UNIT_TEST(arena_init_malloc_always) { - arena_t arena = arena_make(ARENA_MALLOC_ALWAYS, KB(4)); - ASSERT(arena.type == ARENA_MALLOC_ALWAYS); - arena_cleanup(&arena); -} - -UNIT_TEST(arena_init_static) { - u8 buffer[KB(4)]; - arena_t arena = arena_make(ARENA_STATIC, KB(4), buffer); - ASSERT(arena.type == ARENA_STATIC); - ASSERT(arena.beg == buffer); - ASSERT(arena.cur == arena.beg); - ASSERT(arena.end == arena.beg + KB(4)); - arena_cleanup(&arena); -} - -UNIT_TEST(arena_alloc_basic) { - arena_t arena = arena_make(ARENA_VIRTUAL, MB(1)); - int *ptr = alloc(&arena, int); - ASSERT(ptr != NULL); - *ptr = 42; - ASSERT(*ptr == 42); - ASSERT(arena.cur > arena.beg); - arena_cleanup(&arena); -} - -UNIT_TEST(arena_alloc_array) { - arena_t arena = arena_make(ARENA_VIRTUAL, MB(1)); - int *arr = alloc(&arena, int, .count = 10); - ASSERT(arr != NULL); - for (int i = 0; i < 10; i++) { - arr[i] = i; - } - for (int i = 0; i < 10; i++) { - ASSERT(arr[i] == i); - } - arena_cleanup(&arena); -} - -UNIT_TEST(arena_alloc_custom_align) { - arena_t arena = arena_make(ARENA_VIRTUAL, MB(1)); - void *ptr = alloc(&arena, char, .align = 64); - ASSERT(ptr != NULL); - ASSERT(((uintptr_t)ptr & 63) == 0); // Should be 64-byte aligned - arena_cleanup(&arena); -} - -UNIT_TEST(arena_alloc_nozero) { - arena_t arena = arena_make(ARENA_VIRTUAL, MB(1)); - int *ptr1 = alloc(&arena, int); - ASSERT(*ptr1 == 0); // Default zeroed - - int *ptr2 = alloc(&arena, int, .flags = ALLOC_NOZERO); - // We can't assert on the value as it's uninitialized - ASSERT(ptr2 != NULL); - arena_cleanup(&arena); -} - -UNIT_TEST(arena_alloc_soft_fail) { - u8 buffer[10]; - arena_t arena = arena_make(ARENA_STATIC, 10, buffer); - - void *ptr1 = alloc(&arena, char, .count = 5); - ASSERT(ptr1 != NULL); - - // This would normally fail, but with SOFT_FAIL it returns NULL - void *ptr2 = alloc(&arena, char, .count = 10, .flags = ALLOC_SOFT_FAIL); - ASSERT(ptr2 == NULL); - - arena_cleanup(&arena); -} - -UNIT_TEST(arena_scratch) { - arena_t arena = arena_make(ARENA_VIRTUAL, MB(1)); - arena_t scratch = arena_scratch(&arena, KB(1)); - - ASSERT(scratch.beg != NULL); - ASSERT(scratch.type == ARENA_STATIC); - - void *ptr = alloc(&scratch, int); - ASSERT(ptr != NULL); - - // Scratch cleanup happens implicitly when parent arena is cleaned up - arena_cleanup(&arena); -} - -UNIT_TEST(arena_tell) { - arena_t arena = arena_make(ARENA_VIRTUAL, MB(1)); - usize pos1 = arena_tell(&arena); - ASSERT(pos1 == 0); - - alloc(&arena, int); - usize pos2 = arena_tell(&arena); - ASSERT(pos2 > pos1); - - arena_cleanup(&arena); -} - -UNIT_TEST(arena_remaining) { - arena_t arena = arena_make(ARENA_VIRTUAL, KB(64)); - usize initial_remaining = arena_remaining(&arena); - ASSERT(initial_remaining == KB(64)); - - alloc(&arena, char, .count = KB(4)); - usize after_alloc = arena_remaining(&arena); - ASSERT(after_alloc < initial_remaining); - ASSERT(after_alloc >= KB(60)); // Account for possible alignment padding - - arena_cleanup(&arena); -} - -UNIT_TEST(arena_capacity) { - arena_t arena = arena_make(ARENA_VIRTUAL, KB(64)); - usize cap = arena_capacity(&arena); - ASSERT(cap == KB(64)); - arena_cleanup(&arena); -} - -UNIT_TEST(arena_rewind) { - arena_t arena = arena_make(ARENA_VIRTUAL, MB(1)); - - usize mark = arena_tell(&arena); - - int *ptr1 = alloc(&arena, int); - *ptr1 = 42; - - alloc(&arena, char, .count = 100); - - arena_rewind(&arena, mark); - - int *ptr2 = alloc(&arena, int); - ASSERT(ptr2 == ptr1); // Should reuse the same memory - - // Original value is lost after rewind - *ptr2 = 24; - ASSERT(*ptr2 == 24); - - arena_cleanup(&arena); -} - -UNIT_TEST(arena_pop) { - arena_t arena = arena_make(ARENA_VIRTUAL, MB(1)); - - alloc(&arena, char, .count = 100); - usize pos = arena_tell(&arena); - - alloc(&arena, char, .count = 50); - - arena_pop(&arena, 50); - ASSERT(arena_tell(&arena) == pos); - - arena_cleanup(&arena); -} - -UNIT_TEST(arena_malloc_arena) { - void *ptr = alloc(&malloc_arena, int); - ASSERT(ptr != NULL); - - // We need to free each allocation from malloc_arena manually - os_free(ptr); -} - -UNIT_TEST(arena_alloc_mixed_types) { - arena_t arena = arena_make(ARENA_VIRTUAL, MB(1)); - - int *i = alloc(&arena, int); - float *f = alloc(&arena, float); - char *c = alloc(&arena, char); - - *i = 42; - *f = 3.14f; - *c = 'A'; - - ASSERT(*i == 42); - ASSERT(*f == 3.14f); - ASSERT(*c == 'A'); - - arena_cleanup(&arena); -} - -UNIT_TEST(arena_multiple_arenas) { - arena_t arena1 = arena_make(ARENA_VIRTUAL, KB(4)); - arena_t arena2 = arena_make(ARENA_VIRTUAL, KB(4)); - - int *ptr1 = alloc(&arena1, int); - int *ptr2 = alloc(&arena2, int); - - *ptr1 = 42; - *ptr2 = 24; - - ASSERT(*ptr1 == 42); - ASSERT(*ptr2 == 24); - - arena_cleanup(&arena1); - arena_cleanup(&arena2); -} - -UNIT_TEST(arena_stress_test) { - arena_t arena = arena_make(ARENA_VIRTUAL, MB(10)); - - // Allocate many objects - for (int i = 0; i < 1000; i++) { - int *ptr = alloc(&arena, int); - ASSERT(ptr != NULL); - *ptr = i; - } - - // Allocate a large block - void *large = alloc(&arena, char, .count = MB(5)); - ASSERT(large != NULL); - - arena_cleanup(&arena); -} diff --git a/tests/core_tests.c b/tests/core_tests.c deleted file mode 100644 index de6b3fb..0000000 --- a/tests/core_tests.c +++ /dev/null @@ -1,229 +0,0 @@ -#include "runner.h" -#include "../core.h" -#include -#include - -UNIT_TEST(arrlen_macro) { - int array[5] = {1, 2, 3, 4, 5}; - ASSERT(arrlen(array) == 5); - - char str[] = "hello"; - ASSERT(arrlen(str) == 6); // Including null terminator -} - -UNIT_TEST(min_max_macros) { - ASSERT(MIN(5, 10) == 5); - ASSERT(MIN(-5, 10) == -5); - ASSERT(MIN(5.5, 10.1) == 5.5); - - ASSERT(MAX(5, 10) == 10); - ASSERT(MAX(-5, 10) == 10); - ASSERT(MAX(5.5, 10.1) == 10.1); -} - -UNIT_TEST(size_constants) { - ASSERT(KB(1) == 1024); - ASSERT(MB(1) == 1024 * 1024); - ASSERT(GB(1) == 1024 * 1024 * 1024); - ASSERT(TB(1) == 1024ULL * 1024ULL * 1024ULL * 1024ULL); - - ASSERT(KB(2) == 2048); - ASSERT(MB(2) == 2 * 1024 * 1024); -} - -UNIT_TEST(linked_list) { - // Define a simple node structure - typedef struct Node { - int value; - struct Node *next; - } Node; - - // Create some nodes - Node n1 = {1, NULL}; - Node n2 = {2, NULL}; - Node n3 = {3, NULL}; - - // Initialize list - Node *list = NULL; - - // Push nodes onto list - list_push(list, &n3); - list_push(list, &n2); - list_push(list, &n1); - - // Check list order - ASSERT(list == &n1); - ASSERT(list->next == &n2); - ASSERT(list->next->next == &n3); - ASSERT(list->next->next->next == NULL); - - // Pop from list - list_pop(list); - ASSERT(list == &n2); - ASSERT(list->next == &n3); - - list_pop(list); - ASSERT(list == &n3); - - list_pop(list); - ASSERT(list == NULL); -} - -UNIT_TEST(double_linked_list) { - // Define a double linked node - typedef struct DNode { - int value; - struct DNode *next; - struct DNode *prev; - } DNode; - - // Create some nodes - DNode n1 = {1, NULL, NULL}; - DNode n2 = {2, NULL, NULL}; - DNode n3 = {3, NULL, NULL}; - - // Initialize list - DNode *list = NULL; - - // Push nodes - dlist_push(list, &n3); - dlist_push(list, &n2); - dlist_push(list, &n1); - - // Check list structure - ASSERT(list == &n1); - ASSERT(list->next == &n2); - ASSERT(list->next->next == &n3); - ASSERT(list->prev == NULL); - ASSERT(list->next->prev == &n1); - ASSERT(list->next->next->prev == &n2); - - // Pop middle node - dlist_pop(list, &n2); - - // Check updated structure - ASSERT(list == &n1); - ASSERT(list->next == &n3); - ASSERT(list->next->prev == &n1); - - // Pop first node - dlist_pop(list, &n1); - ASSERT(list == &n3); - ASSERT(list->prev == NULL); -} - -UNIT_TEST(ordered_linked_list) { - // Define a simple node - typedef struct ONode { - int value; - struct ONode *next; - } ONode; - - // Create nodes - ONode n1 = {1, NULL}; - ONode n2 = {2, NULL}; - ONode n3 = {3, NULL}; - - // Initialize head and tail - ONode *head = NULL; - ONode *tail = NULL; - - // Push nodes in order - olist_push(head, tail, &n1); - ASSERT(head == &n1); - ASSERT(tail == &n1); - - olist_push(head, tail, &n2); - ASSERT(head == &n1); - ASSERT(tail == &n2); - ASSERT(head->next == &n2); - - olist_push(head, tail, &n3); - ASSERT(head == &n1); - ASSERT(tail == &n3); - ASSERT(head->next == &n2); - ASSERT(head->next->next == &n3); -} - -UNIT_TEST(for_each_macro) { - // Define a simple node - typedef struct Node { - int value; - struct Node *next; - } Node; - - // Create linked list - Node n1 = {1, NULL}; - Node n2 = {2, NULL}; - Node n3 = {3, NULL}; - - n1.next = &n2; - n2.next = &n3; - - Node *list = &n1; - - // Use for_each to sum values - int sum = 0; - for_each(it, list) { - sum += it->value; - } - - ASSERT(sum == 6); // 1 + 2 + 3 -} - -UNIT_TEST(fmt_print) { - // This function outputs to stdout, so we can't easily test its output - // Just verify it doesn't crash and returns a positive value - int result = fmt_print("Test %d %s\n", 42, "hello"); - ASSERT(result > 0); -} - -UNIT_TEST(fmt_buffer) { - char buffer[128]; - - // Basic formatting - int result = fmt_buffer(buffer, sizeof(buffer), "Int: %d", 42); - ASSERT(result > 0); - ASSERT(strcmp(buffer, "Int: 42") == 0); - - // Multiple arguments - result = fmt_buffer(buffer, sizeof(buffer), "%s %d %.2f", "Test", 123, 3.14159); - ASSERT(result > 0); - ASSERT(strcmp(buffer, "Test 123 3.14") == 0); - - // Buffer size limiting - result = fmt_buffer(buffer, 5, "Long text that won't fit"); - ASSERT(result == 24); // fmt_buffer returns the lenght if it did fit - ASSERT(strlen(buffer) == 4); -} - -// Helper function to test variadic function -int test_fmt_printv(const char *fmt, ...) { - va_list args; - va_start(args, fmt); - int result = fmt_printv(fmt, args); - va_end(args); - return result; -} - -UNIT_TEST(fmt_printv) { - // Just verify it doesn't crash and returns positive value - int result = test_fmt_printv("Test %d %s\n", 42, "hello"); - ASSERT(result > 0); -} - -// Helper function to test variadic function -int test_fmt_bufferv(char *buf, usize len, const char *fmt, ...) { - va_list args; - va_start(args, fmt); - int result = fmt_bufferv(buf, len, fmt, args); - va_end(args); - return result; -} - -UNIT_TEST(fmt_bufferv) { - char buffer[128]; - int result = test_fmt_bufferv(buffer, sizeof(buffer), "%d %s", 42, "test"); - ASSERT(result > 0); - ASSERT(strcmp(buffer, "42 test") == 0); -} diff --git a/tests/highlight_tests.c b/tests/highlight_tests.c deleted file mode 100644 index 216ba8c..0000000 --- a/tests/highlight_tests.c +++ /dev/null @@ -1,179 +0,0 @@ -#include "runner.h" -#include "../highlight.h" -#include "../arena.h" -#include - -UNIT_TEST(highlight_init) { - arena_t arena = arena_make(ARENA_MALLOC, KB(4)); - - // Create a basic configuration - hl_config_t config = {0}; - - // Define custom colors - config.colors[HL_COLOR_NORMAL] = strv_init("normal"); - config.colors[HL_COLOR_KEYWORDS] = strv_init("keyword"); - config.colors[HL_COLOR_STRING] = strv_init("string"); - config.colors[HL_COLOR_COMMENT] = strv_init("comment"); - - // Initialize highlighter - hl_ctx_t *ctx = hl_init(&arena, &config); - ASSERT(ctx != NULL); - - arena_cleanup(&arena); -} - -UNIT_TEST(highlight_basic) { - arena_t arena = arena_make(ARENA_MALLOC, KB(4)); - - // Create a configuration - hl_config_t config = {0}; - - // Define custom colors for HTML output - config.colors[HL_COLOR_NORMAL] = strv_init("color:black"); - config.colors[HL_COLOR_KEYWORDS] = strv_init("color:blue"); - config.colors[HL_COLOR_STRING] = strv_init("color:green"); - config.colors[HL_COLOR_COMMENT] = strv_init("color:gray"); - config.colors[HL_COLOR_NUMBER] = strv_init("color:purple"); - - // Set HTML output flag - config.flags = HL_FLAG_HTML; - - // Initialize highlighter - hl_ctx_t *ctx = hl_init(&arena, &config); - ASSERT(ctx != NULL); - - // Sample C code to highlight - strview_t code = strv_init( - "// This is a comment\n" - "int main() {\n" - " printf(\"Hello, World!\\n\");\n" - " return 0;\n" - "}\n" - ); - - // Highlight the code - str_t highlighted = hl_highlight(&arena, ctx, code); - - // Verify the output - ASSERT(!str_is_empty(highlighted)); - - // We can't easily test the exact output without parsing HTML, - // but we can check that key strings are present - const char *html_start = " - -// Initialization Tests -UNIT_TEST(net_init_cleanup) { - net_init(); - // Simple test to make sure initialization succeeds - ASSERT(net_get_last_error() == 0); - net_cleanup(); -} - -// HTTP Method/Status Tests -UNIT_TEST(http_method_strings) { - ASSERT(strcmp(http_get_method_string(HTTP_GET), "GET") == 0); - ASSERT(strcmp(http_get_method_string(HTTP_POST), "POST") == 0); - ASSERT(strcmp(http_get_method_string(HTTP_HEAD), "HEAD") == 0); - ASSERT(strcmp(http_get_method_string(HTTP_PUT), "PUT") == 0); - ASSERT(strcmp(http_get_method_string(HTTP_DELETE), "DELETE") == 0); -} - -UNIT_TEST(http_status_strings) { - ASSERT(strcmp(http_get_status_string(200), "OK") == 0); - ASSERT(strcmp(http_get_status_string(404), "NOT FOUND") == 0); - ASSERT(strcmp(http_get_status_string(500), "INTERNAL SERVER ERROR") == 0); -} - -// HTTP Headers Tests -UNIT_TEST(http_headers) { - arena_t arena = arena_make(ARENA_MALLOC, KB(4)); - - // Parse headers - strview_t header_str = strv_init("Content-Type: application/json\r\nUser-Agent: Colla\r\n"); - http_header_t *headers = http_parse_headers(&arena, header_str); - - // Check headers were parsed correctly - ASSERT(headers != NULL); - - // Headers are parsed in reverse order - ASSERT(strv_equals(headers->key, strv_init("User-Agent"))); - ASSERT(strv_equals(headers->value, strv_init("Colla"))); - ASSERT(strv_equals(headers->next->key, strv_init("Content-Type"))); - ASSERT(strv_equals(headers->next->value, strv_init("application/json"))); - - // Test header operations - ASSERT(http_has_header(headers, strv_init("Content-Type"))); - ASSERT(!http_has_header(headers, strv_init("Accept"))); - - strview_t content_type = http_get_header(headers, strv_init("Content-Type")); - ASSERT(strv_equals(content_type, strv_init("application/json"))); - - // Don't try to free headers as they're allocated in the arena - arena_cleanup(&arena); -} - -// HTTP Request/Response Parsing Tests -UNIT_TEST(http_request_parsing) { - arena_t arena = arena_make(ARENA_MALLOC, KB(4)); - - strview_t req_str = strv_init( - "GET /index.html HTTP/1.1\r\n" - "Host: example.com\r\n" - "User-Agent: Colla\r\n" - "\r\n" - ); - - http_req_t req = http_parse_req(&arena, req_str); - - ASSERT(req.method == HTTP_GET); - ASSERT(req.version.major == 1); - ASSERT(req.version.minor == 1); - ASSERT(strv_equals(req.url, strv("index.html"))); - - ASSERT(http_has_header(req.headers, strv_init("Host"))); - ASSERT(strv_equals(http_get_header(req.headers, strv_init("Host")), strv_init("example.com"))); - - // Convert back to string - str_t req_out = http_req_to_str(&arena, &req); - ASSERT(!str_is_empty(req_out)); - - arena_cleanup(&arena); -} - -UNIT_TEST(http_response_parsing) { - arena_t arena = arena_make(ARENA_MALLOC, KB(4)); - - strview_t res_str = strv_init( - "HTTP/1.1 200 OK\r\n" - "Content-Type: text/html\r\n" - "Content-Length: 13\r\n" - "\r\n" - "Hello, World!" - ); - - http_res_t res = http_parse_res(&arena, res_str); - - ASSERT(res.status_code == 200); - ASSERT(res.version.major == 1); - ASSERT(res.version.minor == 1); - ASSERT(strv_equals(res.body, strv_init("Hello, World!"))); - - ASSERT(http_has_header(res.headers, strv_init("Content-Type"))); - ASSERT(strv_equals(http_get_header(res.headers, strv_init("Content-Type")), strv_init("text/html"))); - - // Convert back to string - str_t res_out = http_res_to_str(&arena, &res); - ASSERT(!str_is_empty(res_out)); - - arena_cleanup(&arena); -} - -// URL Encoding/Decoding Tests -UNIT_TEST(http_url_encoding) { - arena_t arena = arena_make(ARENA_MALLOC, KB(4)); - - strview_t original = strv_init("hello world & special chars: ?=&/"); - str_t encoded = http_make_url_safe(&arena, original); - str_t decoded = http_decode_url_safe(&arena, strv_init_str(encoded)); - - ASSERT(!str_is_empty(encoded)); - ASSERT(str_equals(decoded, str_init(&arena, "hello world & special chars: ?=&/"))); - - arena_cleanup(&arena); -} - -UNIT_TEST(http_url_splitting) { - strview_t url = strv_init("http://example.com/path?query=value"); - http_url_t split = http_split_url(url); - - ASSERT(strv_equals(split.host, strv_init("example.com"))); - ASSERT(strv_equals(split.uri, strv_init("/path?query=value"))); -} - -// HTTP Request Tests -// Note: These tests would actually make network requests, so we should mock them -// for real unit tests. Here we'll just test the setup part. -UNIT_TEST(http_request_setup) { - arena_t arena = arena_make(ARENA_MALLOC, KB(4)); - - // Prepare headers - http_header_t headers[2] = { - { .key = strv_init("Content-Type"), .value = strv_init("application/json"), .next = &headers[1] }, - { .key = strv_init("User-Agent"), .value = strv_init("Colla Test"), .next = NULL } - }; - - // Setup request descriptor - http_request_desc_t desc = { - .arena = &arena, - .url = strv_init("http://example.com"), - .version = { .major = 1, .minor = 1 }, - .request_type = HTTP_GET, - .headers = headers, - .header_count = 2, - .body = strv_init("") - }; - - // We don't actually make the request, just verify the setup is correct - ASSERT(desc.arena == &arena); - ASSERT(strv_equals(desc.url, strv_init("http://example.com"))); - ASSERT(desc.request_type == HTTP_GET); - ASSERT(desc.headers == headers); - ASSERT(desc.header_count == 2); - - arena_cleanup(&arena); -} - -// Socket Tests -UNIT_TEST(socket_basic) { - net_init(); - - // Open a socket - socket_t sock = sk_open(SOCK_TCP); - ASSERT(sk_is_valid(sock)); - - // Close the socket - ASSERT(sk_close(sock)); - - net_cleanup(); -} - -// SHA1 Tests -UNIT_TEST(sha1_hash) { - arena_t arena = arena_make(ARENA_MALLOC, KB(4)); - - sha1_t ctx = sha1_init(); - - const char *data = "Hello, World!"; - str_t hash = sha1_str(&arena, &ctx, data, strlen(data)); - - // The SHA1 hash for "Hello, World!" is known - // But we'll just verify it's not empty and has the expected format (40 hex chars) - ASSERT(!str_is_empty(hash)); - ASSERT(hash.len == 40); - - arena_cleanup(&arena); -} - -// Base64 Tests -UNIT_TEST(base64_encoding) { - arena_t arena = arena_make(ARENA_MALLOC, KB(4)); - - const char *original = "Hello, World!"; - buffer_t input = { (u8*)original, strlen(original) }; - - // Encode - buffer_t encoded = base64_encode(&arena, input); - ASSERT(encoded.data != NULL); - ASSERT(encoded.len > 0); - - // Decode - buffer_t decoded = base64_decode(&arena, encoded); - - ASSERT(decoded.data != NULL); - ASSERT(decoded.len == input.len); - ASSERT(memcmp(decoded.data, input.data, input.len) == 0); - - arena_cleanup(&arena); -} - -// WebSocket Tests -UNIT_TEST(websocket_encoding) { - arena_t arena = arena_make(ARENA_MALLOC, KB(4)); - - strview_t message = strv_init("Hello, WebSocket!"); - - // Encode message to WebSocket format - buffer_t encoded = websocket_encode(&arena, message); - ASSERT(encoded.data != NULL); - ASSERT(encoded.len > 0); - - // Decode WebSocket message - str_t decoded = websocket_decode(&arena, encoded); - ASSERT(!str_is_empty(decoded)); - ASSERT(str_equals(decoded, str_init(&arena, "Hello, WebSocket!"))); - - arena_cleanup(&arena); -} diff --git a/tests/os_tests.c b/tests/os_tests.c deleted file mode 100644 index 33a72c3..0000000 --- a/tests/os_tests.c +++ /dev/null @@ -1,374 +0,0 @@ -#include "runner.h" -#include "../os.h" -#include "../arena.h" -#include - -// Handle Tests -UNIT_TEST(os_handle) { - oshandle_t zero = os_handle_zero(); - ASSERT(!os_handle_valid(zero)); - - // Create a handle (using file open) - arena_t arena = arena_make(ARENA_MALLOC, KB(4)); - strview_t test_file = strv_init("test_file.txt"); - - // Create test file - oshandle_t h_write = os_file_open(test_file, FILEMODE_WRITE); - ASSERT(os_handle_valid(h_write)); - os_file_puts(h_write, strv_init("test content")); - os_file_close(h_write); - - // Open the file and test handle functions - oshandle_t h_read = os_file_open(test_file, FILEMODE_READ); - ASSERT(os_handle_valid(h_read)); - ASSERT(!os_handle_match(h_read, zero)); - - oshandle_t h_read2 = os_file_open(test_file, FILEMODE_READ); - ASSERT(os_handle_valid(h_read2)); - ASSERT(!os_handle_match(h_read, h_read2)); - - os_file_close(h_read); - os_file_close(h_read2); - os_file_delete(test_file); - - arena_cleanup(&arena); -} - -// File Operations Tests -UNIT_TEST(os_file_basic) { - arena_t arena = arena_make(ARENA_MALLOC, KB(4)); - strview_t test_file = strv_init("test_file.txt"); - - // Delete if exists - if (os_file_exists(test_file)) { - os_file_delete(test_file); - } - - // Check existence - ASSERT(!os_file_exists(test_file)); - - // Create and write - oshandle_t h_write = os_file_open(test_file, FILEMODE_WRITE); - ASSERT(os_handle_valid(h_write)); - - os_file_putc(h_write, 'H'); - os_file_puts(h_write, strv_init("ello World")); - - os_file_close(h_write); - - // Check existence after creation - ASSERT(os_file_exists(test_file)); - - // Read back - oshandle_t h_read = os_file_open(test_file, FILEMODE_READ); - ASSERT(os_handle_valid(h_read)); - - char buffer[12] = {0}; - usize read = os_file_read(h_read, buffer, 11); - ASSERT(read == 11); - ASSERT(strcmp(buffer, "Hello World") == 0); - - os_file_close(h_read); - - // Clean up - os_file_delete(test_file); - ASSERT(!os_file_exists(test_file)); - - arena_cleanup(&arena); -} - -UNIT_TEST(os_file_seek) { - arena_t arena = arena_make(ARENA_MALLOC, KB(4)); - strview_t test_file = strv_init("test_file.txt"); - - // Create and write - oshandle_t h_write = os_file_open(test_file, FILEMODE_WRITE); - ASSERT(os_handle_valid(h_write)); - - os_file_puts(h_write, strv_init("ABCDEFGHIJ")); - os_file_close(h_write); - - // Open for reading - oshandle_t h_read = os_file_open(test_file, FILEMODE_READ); - ASSERT(os_handle_valid(h_read)); - - // Seek to position 5 - ASSERT(os_file_seek(h_read, 5)); - - // Read from position 5 - char buffer[6] = {0}; - usize read = os_file_read(h_read, buffer, 5); - ASSERT(read == 5); - ASSERT(strcmp(buffer, "FGHIJ") == 0); - - // Rewind and read from beginning - os_file_rewind(h_read); - - char buffer2[6] = {0}; - read = os_file_read(h_read, buffer2, 5); - ASSERT(read == 5); - ASSERT(strcmp(buffer2, "ABCDE") == 0); - - // Test file position - ASSERT(os_file_tell(h_read) == 5); - - // Test file size - ASSERT(os_file_size(h_read) == 10); - - // Seek to end - ASSERT(os_file_seek_end(h_read)); - ASSERT(os_file_is_finished(h_read)); - - os_file_close(h_read); - os_file_delete(test_file); - - arena_cleanup(&arena); -} - -UNIT_TEST(os_file_read_write_all) { - arena_t arena = arena_make(ARENA_MALLOC, KB(4)); - strview_t test_file = strv_init("test_file.txt"); - - // Write string - strview_t test_data = strv_init("This is test data for read/write all functions"); - ASSERT(os_file_write_all_str(test_file, test_data)); - - // Read back as string - str_t read_data = os_file_read_all_str(&arena, test_file); - ASSERT(str_equals(read_data, str_init(&arena, "This is test data for read/write all functions"))); - - // Read as buffer - buffer_t buffer = os_file_read_all(&arena, test_file); - ASSERT(buffer.len == test_data.len); - ASSERT(memcmp(buffer.data, test_data.buf, test_data.len) == 0); - - // Write buffer - const char *new_data = "New buffer data"; - buffer_t write_buffer = {(u8*)new_data, strlen(new_data)}; - ASSERT(os_file_write_all(test_file, write_buffer)); - - // Read back after buffer write - str_t read_new = os_file_read_all_str(&arena, test_file); - ASSERT(str_equals(read_new, str_init(&arena, "New buffer data"))); - - // Clean up - os_file_delete(test_file); - arena_cleanup(&arena); -} - -UNIT_TEST(os_file_path) { - arena_t arena = arena_make(ARENA_MALLOC, KB(4)); - - // Test path splitting - strview_t path = strv_init("/path/to/file.txt"); - strview_t dir, name, ext; - - os_file_split_path(path, &dir, &name, &ext); - - ASSERT(strv_equals(dir, strv_init("/path/to"))); - ASSERT(strv_equals(name, strv_init("file"))); - ASSERT(strv_equals(ext, strv_init(".txt"))); - - // Test full path resolution - strview_t relative_path = strv_init("test_file.txt"); - tstr_t full_path = os_file_fullpath(&arena, relative_path); - - // Can't easily test the exact value, but can verify it's not empty - ASSERT(full_path.len > 0); - - arena_cleanup(&arena); -} - -// Directory Tests -UNIT_TEST(os_dir_operations) { - arena_t arena = arena_make(ARENA_MALLOC, KB(4)); - strview_t test_dir = strv_init("test_dir"); - - // Delete if exists - if (os_dir_exists(test_dir)) { - os_dir_delete(test_dir); - } - - // Create directory - ASSERT(os_dir_create(test_dir)); - ASSERT(os_dir_exists(test_dir)); - - // Create test file in directory - strview_t test_file_path = strv_init("test_dir/test_file.txt"); - oshandle_t h_write = os_file_open(test_file_path, FILEMODE_WRITE); - ASSERT(os_handle_valid(h_write)); - os_file_puts(h_write, strv_init("test content")); - os_file_close(h_write); - - // Test directory listing - dir_t *dir = os_dir_open(&arena, test_dir); - ASSERT(os_dir_is_valid(dir)); - - bool found_file = false; - dir_foreach(&arena, entry, dir) { - if (str_equals(entry->name, str_init(&arena, "test_file.txt"))) { - found_file = true; - ASSERT(entry->type == DIRTYPE_FILE); - ASSERT(entry->file_size == 12); // "test content" - } - } - - ASSERT(found_file); - - // Clean up - os_file_delete(test_file_path); - os_dir_close(dir); - - // Directory should now be empty, so we can delete it - os_dir_delete(test_dir); - ASSERT(!os_dir_exists(test_dir)); - - arena_cleanup(&arena); -} - -// Environment Variable Tests -UNIT_TEST(os_env_vars) { - arena_t arena = arena_make(ARENA_MALLOC, KB(4)); - arena_t scratch = arena_make(ARENA_MALLOC, KB(1)); - - // Set environment variable - strview_t key = strv_init("COLLA_TEST_VAR"); - strview_t value = strv_init("test_value"); - - os_set_env_var(scratch, key, value); - - // Get environment variable - str_t read_value = os_get_env_var(&arena, key); - ASSERT(str_equals(read_value, str_init(&arena, "test_value"))); - - // Get all environment variables - os_env_t *env = os_get_env(&arena); - ASSERT(env != NULL); - - arena_cleanup(&scratch); - arena_cleanup(&arena); -} - -// Virtual Memory Tests -UNIT_TEST(os_virtual_memory) { - usize page_size; - void *memory = os_reserve(MB(1), &page_size); - ASSERT(memory != NULL); - ASSERT(page_size > 0); - - // Commit a page - ASSERT(os_commit(memory, 1)); - - // Write to the committed memory - memset(memory, 0x42, os_get_system_info().page_size); - - // Release the memory - ASSERT(os_release(memory, MB(1))); -} - -// Thread Tests -static int thread_test_func(u64 thread_id, void *userdata) { - int *value = (int*)userdata; - (*value)++; - return 42; -} - -UNIT_TEST(os_thread) { - // Create thread data - int value = 0; - - // Launch thread - oshandle_t thread = os_thread_launch(thread_test_func, &value); - ASSERT(os_handle_valid(thread)); - - // Get thread ID - u64 thread_id = os_thread_get_id(thread); - ASSERT(thread_id != 0); - - // Join thread - int exit_code; - ASSERT(os_thread_join(thread, &exit_code)); - ASSERT(exit_code == 42); - ASSERT(value == 1); -} - -int test_mutex_trylock(u64 id, void *userdata) { - oshandle_t mutex = *((oshandle_t*)userdata); - return os_mutex_try_lock(mutex); -} - - -// Mutex Tests -UNIT_TEST(os_mutex) { - oshandle_t mutex = os_mutex_create(); - ASSERT(os_handle_valid(mutex)); - - oshandle_t thread = os_thread_launch(test_mutex_trylock, &mutex); - - // Lock mutex - os_mutex_lock(mutex); - - int locked = 0; - os_thread_join(thread, &locked); - ASSERT(locked == false); - - // Unlock - os_mutex_unlock(mutex); - - // Try lock should succeed now - ASSERT(os_mutex_try_lock(mutex)); - - // Unlock again - os_mutex_unlock(mutex); - - // Free mutex - os_mutex_free(mutex); -} - -#if !COLLA_NO_CONDITION_VARIABLE -// Condition Variable Tests -struct cond_test_data { - oshandle_t mutex; - oshandle_t cond; - int counter; -}; - -static int cond_test_thread(u64 thread_id, void *userdata) { - struct cond_test_data *data = (struct cond_test_data*)userdata; - - os_mutex_lock(data->mutex); - data->counter++; - os_mutex_unlock(data->mutex); - - // Signal the condition - os_cond_signal(data->cond); - - return 0; -} - -UNIT_TEST(os_condition_variable) { - struct cond_test_data data; - data.mutex = os_mutex_create(); - data.cond = os_cond_create(); - data.counter = 0; - - // Lock mutex before launching thread - os_mutex_lock(data.mutex); - - // Launch thread - oshandle_t thread = os_thread_launch(cond_test_thread, &data); - - // Wait for condition with timeout - os_cond_wait(data.cond, data.mutex, 1000); - - // We should have the lock again, and counter should be 1 - ASSERT(data.counter == 1); - - // Unlock and cleanup - os_mutex_unlock(data.mutex); - os_thread_join(thread, NULL); - - os_mutex_free(data.mutex); - os_cond_free(data.cond); -} -#endif diff --git a/tests/parsers_tests.c b/tests/parsers_tests.c deleted file mode 100644 index 8004fe6..0000000 --- a/tests/parsers_tests.c +++ /dev/null @@ -1,370 +0,0 @@ -#include "runner.h" -#include "../parsers.h" -#include "../arena.h" -#include - -// INI Parser Tests -UNIT_TEST(ini_parse_basic) { - arena_t arena = arena_make(ARENA_MALLOC, KB(4)); - - strview_t ini_content = strv_init( - "[section1]\n" - "key1=value1\n" - "key2=value2\n" - "\n" - "[section2]\n" - "key3=value3\n" - "key4=value4\n" - ); - - ini_t ini = ini_parse_str(&arena, ini_content, NULL); - ASSERT(ini_is_valid(&ini)); - - // Test section1 - initable_t *section1 = ini_get_table(&ini, strv_init("section1")); - ASSERT(section1 != NULL); - ASSERT(strv_equals(section1->name, strv_init("section1"))); - - // Test section1 values - inivalue_t *key1 = ini_get(section1, strv_init("key1")); - ASSERT(key1 != NULL); - ASSERT(strv_equals(key1->key, strv_init("key1"))); - ASSERT(strv_equals(key1->value, strv_init("value1"))); - - inivalue_t *key2 = ini_get(section1, strv_init("key2")); - ASSERT(key2 != NULL); - ASSERT(strv_equals(key2->key, strv_init("key2"))); - ASSERT(strv_equals(key2->value, strv_init("value2"))); - - // Test section2 - initable_t *section2 = ini_get_table(&ini, strv_init("section2")); - ASSERT(section2 != NULL); - ASSERT(strv_equals(section2->name, strv_init("section2"))); - - // Test section2 values - inivalue_t *key3 = ini_get(section2, strv_init("key3")); - ASSERT(key3 != NULL); - ASSERT(strv_equals(key3->key, strv_init("key3"))); - ASSERT(strv_equals(key3->value, strv_init("value3"))); - - inivalue_t *key4 = ini_get(section2, strv_init("key4")); - ASSERT(key4 != NULL); - ASSERT(strv_equals(key4->key, strv_init("key4"))); - ASSERT(strv_equals(key4->value, strv_init("value4"))); - - arena_cleanup(&arena); -} - -UNIT_TEST(ini_parse_with_options) { - arena_t arena = arena_make(ARENA_MALLOC, KB(4)); - - strview_t ini_content = strv_init( - "[section1]\n" - "key1:value1\n" - "# This is a comment\n" - "key2:value2\n" - "\n" - "[section1]\n" // Duplicate section - "key3:value3\n" - ); - - iniopt_t options = { - .merge_duplicate_tables = true, - .merge_duplicate_keys = false, - .key_value_divider = ':', - .comment_vals = strv_init("#") - }; - - ini_t ini = ini_parse_str(&arena, ini_content, &options); - ASSERT(ini_is_valid(&ini)); - - // Test section1 (should be merged) - initable_t *section1 = ini_get_table(&ini, strv_init("section1")); - ASSERT(section1 != NULL); - - // Check all keys exist in merged section - inivalue_t *key1 = ini_get(section1, strv_init("key1")); - ASSERT(key1 != NULL); - ASSERT(strv_equals(key1->value, strv_init("value1"))); - - inivalue_t *key2 = ini_get(section1, strv_init("key2")); - ASSERT(key2 != NULL); - ASSERT(strv_equals(key2->value, strv_init("value2"))); - - inivalue_t *key3 = ini_get(section1, strv_init("key3")); - ASSERT(key3 != NULL); - ASSERT(strv_equals(key3->value, strv_init("value3"))); - - arena_cleanup(&arena); -} - -UNIT_TEST(ini_value_conversion) { - arena_t arena = arena_make(ARENA_MALLOC, KB(4)); - - strview_t ini_content = strv_init( - "[values]\n" - "uint=42\n" - "int=-42\n" - "float=3.14\n" - "bool_true=true\n" - "bool_false=false\n" - "array=item1,item2,item3\n" - ); - - ini_t ini = ini_parse_str(&arena, ini_content, NULL); - initable_t *values = ini_get_table(&ini, strv_init("values")); - ASSERT(values != NULL); - - // Test uint conversion - inivalue_t *uint_val = ini_get(values, strv_init("uint")); - ASSERT(uint_val != NULL); - ASSERT(ini_as_uint(uint_val) == 42); - - // Test int conversion - inivalue_t *int_val = ini_get(values, strv_init("int")); - ASSERT(int_val != NULL); - ASSERT(ini_as_int(int_val) == -42); - - // Test float conversion - inivalue_t *float_val = ini_get(values, strv_init("float")); - ASSERT(float_val != NULL); - ASSERT(ini_as_num(float_val) > 3.13 && ini_as_num(float_val) < 3.15); - - // Test bool conversion - inivalue_t *bool_true = ini_get(values, strv_init("bool_true")); - ASSERT(bool_true != NULL); - ASSERT(ini_as_bool(bool_true) == true); - - inivalue_t *bool_false = ini_get(values, strv_init("bool_false")); - ASSERT(bool_false != NULL); - ASSERT(ini_as_bool(bool_false) == false); - - // Test array conversion - inivalue_t *array_val = ini_get(values, strv_init("array")); - ASSERT(array_val != NULL); - - iniarray_t array = ini_as_arr(&arena, array_val, ','); - ASSERT(array.count == 3); - ASSERT(strv_equals(array.values[0], strv_init("item1"))); - ASSERT(strv_equals(array.values[1], strv_init("item2"))); - ASSERT(strv_equals(array.values[2], strv_init("item3"))); - - arena_cleanup(&arena); -} - -// JSON Parser Tests -UNIT_TEST(json_parse_basic) { - arena_t arena = arena_make(ARENA_MALLOC, KB(4)); - - strview_t json_content = strv_init( - "{\n" - " \"string\": \"value\",\n" - " \"number\": 42,\n" - " \"bool\": true,\n" - " \"null\": null,\n" - " \"array\": [1, 2, 3],\n" - " \"object\": {\n" - " \"nested\": \"nested_value\"\n" - " }\n" - "}" - ); - - json_t *root = json_parse_str(&arena, json_content, JSON_DEFAULT); - ASSERT(root != NULL); - ASSERT(root->type == JSON_OBJECT); - - // Test string - json_t *string_node = json_get(root, strv_init("string")); - ASSERT(json_check(string_node, JSON_STRING)); - ASSERT(strv_equals(string_node->string, strv_init("value"))); - - // Test number - json_t *number_node = json_get(root, strv_init("number")); - ASSERT(json_check(number_node, JSON_NUMBER)); - ASSERT(number_node->number == 42); - - // Test bool - json_t *bool_node = json_get(root, strv_init("bool")); - ASSERT(json_check(bool_node, JSON_BOOL)); - ASSERT(bool_node->boolean == true); - - // Test null - json_t *null_node = json_get(root, strv_init("null")); - ASSERT(json_check(null_node, JSON_NULL)); - - // Test array - json_t *array_node = json_get(root, strv_init("array")); - ASSERT(json_check(array_node, JSON_ARRAY)); - - // Test array contents - int count = 0; - int sum = 0; - json_for(item, array_node) { - ASSERT(json_check(item, JSON_NUMBER)); - sum += (int)item->number; - count++; - } - ASSERT(count == 3); - ASSERT(sum == 6); // 1 + 2 + 3 - - // Test nested object - json_t *object_node = json_get(root, strv_init("object")); - ASSERT(json_check(object_node, JSON_OBJECT)); - - json_t *nested_node = json_get(object_node, strv_init("nested")); - ASSERT(json_check(nested_node, JSON_STRING)); - ASSERT(strv_equals(nested_node->string, strv_init("nested_value"))); - - arena_cleanup(&arena); -} - -UNIT_TEST(json_parse_with_options) { - arena_t arena = arena_make(ARENA_MALLOC, KB(4)); - - // JSON with comments and trailing commas - strview_t json_content = strv_init( - "{\n" - " \"key1\": \"value1\",\n" - " // This is a comment\n" - " \"key2\": \"value2\",\n" - " \"array\": [\n" - " 1,\n" - " 2,\n" - " 3,\n" // Trailing comma - " ],\n" // Trailing comma - "}" - ); - - // Test with default flags (should allow comments and trailing commas) - json_t *root1 = json_parse_str(&arena, json_content, JSON_DEFAULT); - ASSERT(root1 != NULL); - ASSERT(json_get(root1, strv_init("key1")) != NULL); - ASSERT(json_get(root1, strv_init("key2")) != NULL); - - // Test with NO_COMMENTS and NO_TRAILING_COMMAS flags - json_t *root2 = json_parse_str(&arena, json_content, JSON_NO_COMMENTS | JSON_NO_TRAILING_COMMAS); - - // This should fail parsing due to the strict flags - but the behavior depends on implementation - // Some parsers might ignore the errors, others might return NULL - // We'll check both possibilities - if (root2 != NULL) { - // If parsing succeeded despite strict flags, ensure the content is correct - ASSERT(json_get(root2, strv_init("key1")) != NULL); - // key2 might be missing if comment handling failed - } - - arena_cleanup(&arena); -} - -// XML Parser Tests -UNIT_TEST(xml_parse_basic) { - arena_t arena = arena_make(ARENA_MALLOC, KB(4)); - - strview_t xml_content = strv_init( - "\n" - "\n" - " \n" - " Test Title\n" - " Test Author\n" - " \n" - " \n" - " Another Title\n" - " \n" - "" - ); - - xml_t xml = xml_parse_str(&arena, xml_content); - ASSERT(xml.root != NULL); - ASSERT(strv_equals(xml.root->key, strv_init("root"))); - - // Find item tags - xmltag_t *item = xml_get_tag(xml.root, strv_init("item"), false); - ASSERT(item != NULL); - - // Check attributes - strview_t id = xml_get_attribute(item, strv_init("id")); - ASSERT(strv_equals(id, strv_init("1"))); - - strview_t type = xml_get_attribute(item, strv_init("type")); - ASSERT(strv_equals(type, strv_init("book"))); - - // Check nested tags - xmltag_t *title = xml_get_tag(item, strv_init("title"), false); - ASSERT(title != NULL); - ASSERT(strv_equals(title->content, strv_init("Test Title"))); - - xmltag_t *author = xml_get_tag(item, strv_init("author"), false); - ASSERT(author != NULL); - ASSERT(strv_equals(author->content, strv_init("Test Author"))); - - // Check recursive tag finding - xmltag_t *title_recursive = xml_get_tag(xml.root, strv_init("title"), true); - ASSERT(title_recursive != NULL); - ASSERT(strv_equals(title_recursive->content, strv_init("Test Title"))); - - arena_cleanup(&arena); -} - -// HTML Parser Tests -UNIT_TEST(html_parse_basic) { - arena_t arena = arena_make(ARENA_MALLOC, KB(4)); - - strview_t html_content = strv_init( - "\n" - "\n" - "\n" - " Test Page\n" - "\n" - "\n" - "

Hello World

\n" - "

This is a test.

\n" - "
\n" - "

More content here.

\n" - "
\n" - "\n" - "" - ); - - html_t html = html_parse_str(&arena, html_content); - ASSERT(html.root != NULL); - ASSERT(str_equals(html.root->key, str_init(&arena, "html"))); - - // Find head and body - htmltag_t *head = html_get_tag(html.root, strv_init("head"), false); - ASSERT(head != NULL); - - htmltag_t *body = html_get_tag(html.root, strv_init("body"), false); - ASSERT(body != NULL); - - // Find title in head - htmltag_t *title = html_get_tag(head, strv_init("title"), false); - ASSERT(title != NULL); - ASSERT(strv_equals(title->content, strv_init("Test Page"))); - - // Find elements in body - htmltag_t *h1 = html_get_tag(body, strv_init("h1"), false); - ASSERT(h1 != NULL); - ASSERT(strv_equals(h1->content, strv_init("Hello World"))); - - // Find paragraph with class - htmltag_t *p = html_get_tag(body, strv_init("p"), false); - ASSERT(p != NULL); - - strview_t p_class = html_get_attribute(p, strv_init("class")); - ASSERT(strv_equals(p_class, strv_init("intro"))); - ASSERT(strv_equals(p->content, strv_init("This is a test."))); - - // Find div by id - htmltag_t *div = html_get_tag(body, strv_init("div"), false); - ASSERT(div != NULL); - - strview_t div_id = html_get_attribute(div, strv_init("id")); - ASSERT(strv_equals(div_id, strv_init("content"))); - - // Find nested paragraph using recursive search - htmltag_t *nested_p = html_get_tag(div, strv_init("p"), false); - ASSERT(nested_p != NULL); - ASSERT(strv_equals(nested_p->content, strv_init("More content here."))); - - arena_cleanup(&arena); -} diff --git a/tests/pretty_print_tests.c b/tests/pretty_print_tests.c deleted file mode 100644 index 754bb1f..0000000 --- a/tests/pretty_print_tests.c +++ /dev/null @@ -1,34 +0,0 @@ -#include "runner.h" -#include "../pretty_print.h" -#include "../arena.h" -#include - -UNIT_TEST(pretty_print_basic) { - arena_t arena = arena_make(ARENA_MALLOC, KB(4)); - - // The pretty_print function outputs to console, so we can't easily verify its exact output - // Instead, we'll just verify it doesn't crash - pretty_print(arena, "Hello, World!"); - - // Test with formatting - pretty_print(arena, "Value: %d, String: %s", 42, "test"); - - arena_cleanup(&arena); -} - -// Helper function to test variadic function -void test_pretty_printv(arena_t arena, const char *fmt, ...) { - va_list args; - va_start(args, fmt); - pretty_printv(arena, fmt, args); - va_end(args); -} - -UNIT_TEST(pretty_printv) { - arena_t arena = arena_make(ARENA_MALLOC, KB(4)); - - // Test the helper function - test_pretty_printv(arena, "Test %d %s", 42, "variadic"); - - arena_cleanup(&arena); -} diff --git a/tests/runner.h b/tests/runner.h deleted file mode 100644 index 55085c5..0000000 --- a/tests/runner.h +++ /dev/null @@ -1,41 +0,0 @@ -#pragma once - -#include "../core.h" -#include "../str.h" - -typedef struct unit_test_t unit_test_t; -struct unit_test_t { - strview_t fname; - strview_t name; - void (*fn)(void); - unit_test_t *next; -}; - -extern unit_test_t *test_head; -extern unit_test_t *test_tail; -extern const char *last_fail_reason; -extern bool last_failed; - -void ut_register(const char *file, const char *name, void (*fn)(void)); - -// #pragma data_seg(".CRT$XCU") - -#define INITIALIZER(f) \ - static void f(void); \ - __declspec(allocate(".CRT$XCU")) void (*f##_)(void) = f; \ - __pragma(comment(linker,"/include:" #f "_")) \ - static void f(void) - -#define UNIT_TEST(name) \ - void ut__test_##name(void); \ - INITIALIZER(ut__register_##name) { ut_register(__FILE__, #name, ut__test_##name); } \ - void ut__test_##name(void) - - -#define ASSERT(cond) \ - if (!(cond)) { \ - last_fail_reason = "assert(" COLLA_STRINGIFY(cond) ") at " COLLA_STRINGIFY(__LINE__); \ - last_failed = true; \ - return; \ - } - diff --git a/tests/str_tests.c b/tests/str_tests.c deleted file mode 100644 index 0e90b7c..0000000 --- a/tests/str_tests.c +++ /dev/null @@ -1,457 +0,0 @@ -#include "runner.h" -#include "../str.h" -#include "../arena.h" -#include - -// String (str_t) Tests -UNIT_TEST(str_init_basic) { - arena_t arena = arena_make(ARENA_MALLOC, KB(4)); - str_t s = str_init(&arena, "hello"); - - ASSERT(s.len == 5); - ASSERT(s.buf != NULL); - ASSERT(memcmp(s.buf, "hello", 5) == 0); - - arena_cleanup(&arena); -} - -UNIT_TEST(str_init_len) { - arena_t arena = arena_make(ARENA_MALLOC, KB(4)); - str_t s = str_init_len(&arena, "hello world", 5); - - ASSERT(s.len == 5); - ASSERT(s.buf != NULL); - ASSERT(memcmp(s.buf, "hello", 5) == 0); - - arena_cleanup(&arena); -} - -UNIT_TEST(str_fmt) { - arena_t arena = arena_make(ARENA_MALLOC, KB(4)); - str_t s = str_fmt(&arena, "Number: %d, String: %s", 42, "test"); - - ASSERT(s.buf != NULL); - ASSERT(memcmp(s.buf, "Number: 42, String: test", s.len) == 0); - - arena_cleanup(&arena); -} - -UNIT_TEST(str_equals) { - arena_t arena = arena_make(ARENA_MALLOC, KB(4)); - - str_t s1 = str_init(&arena, "hello"); - str_t s2 = str_init(&arena, "hello"); - str_t s3 = str_init(&arena, "world"); - - ASSERT(str_equals(s1, s2) == true); - ASSERT(str_equals(s1, s3) == false); - - arena_cleanup(&arena); -} - -UNIT_TEST(str_compare) { - arena_t arena = arena_make(ARENA_MALLOC, KB(4)); - - str_t s1 = str_init(&arena, "abc"); - str_t s2 = str_init(&arena, "abc"); - str_t s3 = str_init(&arena, "abd"); - str_t s4 = str_init(&arena, "abb"); - - ASSERT(str_compare(s1, s2) == 0); - ASSERT(str_compare(s1, s3) < 0); - ASSERT(str_compare(s1, s4) > 0); - - arena_cleanup(&arena); -} - -UNIT_TEST(str_dup) { - arena_t arena = arena_make(ARENA_MALLOC, KB(4)); - - str_t s1 = str_init(&arena, "test"); - str_t s2 = str_dup(&arena, s1); - - ASSERT(s1.len == s2.len); - ASSERT(s1.buf != s2.buf); // Different memory locations - ASSERT(memcmp(s1.buf, s2.buf, s1.len) == 0); - - arena_cleanup(&arena); -} - -UNIT_TEST(str_cat) { - arena_t arena = arena_make(ARENA_MALLOC, KB(4)); - - str_t s1 = str_init(&arena, "hello "); - str_t s2 = str_init(&arena, "world"); - str_t s3 = str_cat(&arena, s1, s2); - - ASSERT(s3.len == s1.len + s2.len); - ASSERT(memcmp(s3.buf, "hello world", s3.len) == 0); - - arena_cleanup(&arena); -} - -UNIT_TEST(str_is_empty) { - arena_t arena = arena_make(ARENA_MALLOC, KB(4)); - - str_t s1 = str_init(&arena, "test"); - str_t s2 = STR_EMPTY; - - ASSERT(str_is_empty(s1) == false); - ASSERT(str_is_empty(s2) == true); - - arena_cleanup(&arena); -} - -UNIT_TEST(str_lower_upper) { - arena_t arena = arena_make(ARENA_MALLOC, KB(4)); - - str_t s1 = str_init(&arena, "TeSt"); - str_lower(&s1); - ASSERT(memcmp(s1.buf, "test", 4) == 0); - - str_t s2 = str_init(&arena, "TeSt"); - str_upper(&s2); - ASSERT(memcmp(s2.buf, "TEST", 4) == 0); - - arena_cleanup(&arena); -} - -UNIT_TEST(str_replace) { - arena_t arena = arena_make(ARENA_MALLOC, KB(4)); - - str_t s = str_init(&arena, "hello"); - str_replace(&s, 'l', 'x'); - ASSERT(memcmp(s.buf, "hexxo", 5) == 0); - - arena_cleanup(&arena); -} - -UNIT_TEST(str_sub) { - arena_t arena = arena_make(ARENA_MALLOC, KB(4)); - - str_t s = str_init(&arena, "hello world"); - strview_t sv = str_sub(s, 6, 11); - - ASSERT(sv.len == 5); - ASSERT(memcmp(sv.buf, "world", 5) == 0); - - arena_cleanup(&arena); -} - -// String View (strview_t) Tests -UNIT_TEST(strv_init) { - strview_t sv = strv_init("hello"); - - ASSERT(sv.len == 5); - ASSERT(sv.buf != NULL); - ASSERT(memcmp(sv.buf, "hello", 5) == 0); -} - -UNIT_TEST(strv_init_len) { - strview_t sv = strv_init_len("hello world", 5); - - ASSERT(sv.len == 5); - ASSERT(sv.buf != NULL); - ASSERT(memcmp(sv.buf, "hello", 5) == 0); -} - -UNIT_TEST(strv_init_str) { - arena_t arena = arena_make(ARENA_MALLOC, KB(4)); - - str_t s = str_init(&arena, "hello"); - strview_t sv = strv_init_str(s); - - ASSERT(sv.len == s.len); - ASSERT(sv.buf == s.buf); - - arena_cleanup(&arena); -} - -UNIT_TEST(strv_is_empty) { - strview_t sv1 = strv_init("test"); - strview_t sv2 = STRV_EMPTY; - - ASSERT(strv_is_empty(sv1) == false); - ASSERT(strv_is_empty(sv2) == true); -} - -UNIT_TEST(strv_equals) { - strview_t sv1 = strv_init("hello"); - strview_t sv2 = strv_init("hello"); - strview_t sv3 = strv_init("world"); - - ASSERT(strv_equals(sv1, sv2) == true); - ASSERT(strv_equals(sv1, sv3) == false); -} - -UNIT_TEST(strv_compare) { - strview_t sv1 = strv_init("abc"); - strview_t sv2 = strv_init("abc"); - strview_t sv3 = strv_init("abd"); - strview_t sv4 = strv_init("abb"); - - ASSERT(strv_compare(sv1, sv2) == 0); - ASSERT(strv_compare(sv1, sv3) < 0); - ASSERT(strv_compare(sv1, sv4) > 0); -} - -UNIT_TEST(strv_front_back) { - strview_t sv = strv_init("hello"); - - ASSERT(strv_front(sv) == 'h'); - ASSERT(strv_back(sv) == 'o'); -} - -UNIT_TEST(strv_remove_prefix_suffix) { - strview_t sv = strv_init("hello"); - - strview_t prefix_removed = strv_remove_prefix(sv, 2); - ASSERT(prefix_removed.len == 3); - ASSERT(memcmp(prefix_removed.buf, "llo", 3) == 0); - - strview_t suffix_removed = strv_remove_suffix(sv, 2); - ASSERT(suffix_removed.len == 3); - ASSERT(memcmp(suffix_removed.buf, "hel", 3) == 0); -} - -UNIT_TEST(strv_trim) { - strview_t sv1 = strv_init(" hello "); - strview_t sv2 = strv_init(" hello"); - strview_t sv3 = strv_init("hello "); - - strview_t trimmed1 = strv_trim(sv1); - strview_t trimmed2 = strv_trim_left(sv2); - strview_t trimmed3 = strv_trim_right(sv3); - - ASSERT(trimmed1.len == 5); - ASSERT(memcmp(trimmed1.buf, "hello", 5) == 0); - - ASSERT(trimmed2.len == 5); - ASSERT(memcmp(trimmed2.buf, "hello", 5) == 0); - - ASSERT(trimmed3.len == 5); - ASSERT(memcmp(trimmed3.buf, "hello", 5) == 0); -} - -UNIT_TEST(strv_sub) { - strview_t sv = strv_init("hello world"); - strview_t sub = strv_sub(sv, 6, 11); - - ASSERT(sub.len == 5); - ASSERT(memcmp(sub.buf, "world", 5) == 0); -} - -UNIT_TEST(strv_starts_ends_with) { - strview_t sv = strv_init("hello"); - - ASSERT(strv_starts_with(sv, 'h') == true); - ASSERT(strv_starts_with(sv, 'e') == false); - - ASSERT(strv_ends_with(sv, 'o') == true); - ASSERT(strv_ends_with(sv, 'l') == false); - - strview_t prefix = strv_init("hel"); - strview_t suffix = strv_init("llo"); - - ASSERT(strv_starts_with_view(sv, prefix) == true); - ASSERT(strv_ends_with_view(sv, suffix) == true); -} - -UNIT_TEST(strv_contains) { - strview_t sv = strv_init("hello world"); - - ASSERT(strv_contains(sv, 'e') == true); - ASSERT(strv_contains(sv, 'z') == false); - - strview_t sub = strv_init("world"); - ASSERT(strv_contains_view(sv, sub) == true); - - strview_t chars = strv_init("xyz"); - ASSERT(strv_contains_either(sv, chars) == false); - - strview_t chars2 = strv_init("xyo"); - ASSERT(strv_contains_either(sv, chars2) == true); -} - -UNIT_TEST(strv_find) { - strview_t sv = strv_init("hello world"); - - ASSERT(strv_find(sv, 'o', 0) == 4); - ASSERT(strv_find(sv, 'o', 5) == 7); - ASSERT(strv_find(sv, 'z', 0) == STR_NONE); - - strview_t sub = strv_init("world"); - ASSERT(strv_find_view(sv, sub, 0) == 6); - - strview_t chars = strv_init("xwo"); - ASSERT(strv_find_either(sv, chars, 0) == 4); // 'w' at position 6 -} - -UNIT_TEST(strv_rfind) { - strview_t sv = strv_init("hello world"); - - ASSERT(strv_rfind(sv, 'o', 0) == 7); - ASSERT(strv_rfind(sv, 'o', 5) == 4); - ASSERT(strv_rfind(sv, 'z', 0) == STR_NONE); - - strview_t sub = strv_init("world"); - ASSERT(strv_rfind_view(sv, sub, 0) == 6); -} - -// Character Functions Tests -UNIT_TEST(char_functions) { - ASSERT(char_is_space(' ') == true); - ASSERT(char_is_space('\t') == true); - ASSERT(char_is_space('a') == false); - - ASSERT(char_is_alpha('a') == true); - ASSERT(char_is_alpha('Z') == true); - ASSERT(char_is_alpha('1') == false); - - ASSERT(char_is_num('0') == true); - ASSERT(char_is_num('9') == true); - ASSERT(char_is_num('a') == false); - - ASSERT(char_lower('A') == 'a'); - ASSERT(char_lower('a') == 'a'); - ASSERT(char_lower('1') == '1'); -} - -// Input Stream Tests -UNIT_TEST(instream_basic) { - strview_t sv = strv_init("hello world"); - instream_t is = istr_init(sv); - - ASSERT(istr_get(&is) == 'h'); - ASSERT(istr_get(&is) == 'e'); - ASSERT(istr_peek(&is) == 'l'); - ASSERT(istr_peek_next(&is) == 'l'); - ASSERT(istr_get(&is) == 'l'); - ASSERT(istr_prev(&is) == 'l'); - ASSERT(istr_prev_prev(&is) == 'e'); - - istr_skip(&is, 2); - ASSERT(istr_peek(&is) == ' '); - - istr_skip_whitespace(&is); - ASSERT(istr_peek(&is) == 'w'); - - istr_rewind(&is); - ASSERT(istr_peek(&is) == 'h'); - - ASSERT(istr_tell(&is) == 0); - ASSERT(istr_remaining(&is) == 11); - ASSERT(istr_is_finished(&is) == false); -} - -UNIT_TEST(instream_ignore) { - strview_t sv = strv_init("hello,world"); - instream_t is = istr_init(sv); - - istr_ignore(&is, ','); - ASSERT(istr_peek(&is) == ','); - - istr_ignore_and_skip(&is, ','); - ASSERT(istr_peek(&is) == 'w'); -} - -UNIT_TEST(instream_get_values) { - strview_t sv = strv_init("true 42 3.14 hello"); - instream_t is = istr_init(sv); - - bool b; - ASSERT(istr_get_bool(&is, &b) == true); - ASSERT(b == true); - - istr_skip_whitespace(&is); - - u32 u; - ASSERT(istr_get_u32(&is, &u) == true); - ASSERT(u == 42); - - istr_skip_whitespace(&is); - - double d; - ASSERT(istr_get_num(&is, &d) == true); - ASSERT(d > 3.13 && d < 3.15); - - istr_skip_whitespace(&is); - - strview_t word = istr_get_view(&is, ' '); - ASSERT(word.len == 5); - ASSERT(memcmp(word.buf, "hello", 5) == 0); -} - -// Output Stream Tests -UNIT_TEST(outstream_basic) { - arena_t arena = arena_make(ARENA_MALLOC, KB(4)); - outstream_t os = ostr_init(&arena); - - ostr_putc(&os, 'h'); - ostr_putc(&os, 'i'); - - ASSERT(ostr_tell(&os) == 2); - ASSERT(ostr_back(&os) == 'i'); - - ostr_print(&os, " %d", 42); - - strview_t result = ostr_as_view(&os); - ASSERT(result.len == 5); - ASSERT(memcmp(result.buf, "hi 42", 5) == 0); - - ostr_pop(&os, 3); - result = ostr_as_view(&os); - ASSERT(result.len == 2); - ASSERT(memcmp(result.buf, "hi", 2) == 0); - - ostr_clear(&os); - ASSERT(ostr_tell(&os) == 0); - - arena_cleanup(&arena); -} - -UNIT_TEST(outstream_append) { - arena_t arena = arena_make(ARENA_MALLOC, KB(4)); - outstream_t os = ostr_init(&arena); - - ostr_append_bool(&os, true); - ostr_putc(&os, ' '); - ostr_append_uint(&os, 42); - ostr_putc(&os, ' '); - ostr_append_int(&os, -10); - ostr_putc(&os, ' '); - ostr_append_num(&os, 3.14); - - str_t result = ostr_to_str(&os); - ASSERT(result.len > 0); - - arena_cleanup(&arena); -} - -// Binary Input Stream Tests -UNIT_TEST(binary_stream) { - u8 data[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}; - buffer_t buffer = {data, sizeof(data)}; - - ibstream_t bs = ibstr_init(buffer); - - ASSERT(ibstr_remaining(&bs) == 8); - ASSERT(ibstr_tell(&bs) == 0); - ASSERT(ibstr_is_finished(&bs) == false); - - u8 val8; - ASSERT(ibstr_get_u8(&bs, &val8) == true); - ASSERT(val8 == 0x01); - - u16 val16; - ASSERT(ibstr_get_u16(&bs, &val16) == true); - ASSERT(val16 == 0x0302); // Assuming little-endian - - ibstr_skip(&bs, 1); - - u32 val32; - ASSERT(ibstr_get_u32(&bs, &val32) == true); - ASSERT(val32 == 0x08070605); // Assuming little-endian - - ASSERT(ibstr_is_finished(&bs) == true); -} diff --git a/tests/string_tests.c b/tests/string_tests.c deleted file mode 100644 index ffc204b..0000000 --- a/tests/string_tests.c +++ /dev/null @@ -1,12 +0,0 @@ -#include "runner.h" -#include "../str.h" -#include "../arena.h" - -UNIT_TEST(str_format) { - arena_t arena = arena_make(ARENA_MALLOC, KB(1)); - str_t s = str_fmt(&arena, "%d %s", 42, "test"); - str_t lit = str(&arena, "42 test"); - ASSERT(str_equals(s, lit)); - arena_cleanup(&arena); -} - diff --git a/tools/nob.c b/tools/nob.c deleted file mode 100644 index 834bf61..0000000 --- a/tools/nob.c +++ /dev/null @@ -1,427 +0,0 @@ -#define COLLA_NO_CONDITION_VARIABLE 1 -#define COLLA_NO_NET 1 - -#include "../build.c" -#include -#include - -#if COLLA_TCC - -WINBASEAPI LPCH WINAPI GetEnvironmentStringsj(VOID); -WINBASEAPI LPWCH WINAPI GetEnvironmentStringsW(VOID); -WINBASEAPI BOOL WINAPI FreeEnvironmentStringsA(LPCH penv); -WINBASEAPI BOOL WINAPI FreeEnvironmentStringsW(LPWCH penv); -#ifdef UNICODE - #define GetEnvironmentStrings GetEnvironmentStringsW - #define FreeEnvironmentStrings FreeEnvironmentStringsW -#else - #define GetEnvironmentStrings GetEnvironmentStringsA - #define FreeEnvironmentStrings FreeEnvironmentStringsA -#endif - -#endif - -int strv_to_int(strview_t strv) { - instream_t in = istr_init(strv); - i32 value = 0; - if (!istr_get_i32(&in, &value)) { - return 0; - } - return value; -} - -str_t find_vcvars_path(arena_t *arena) { - strview_t base_path = strv("C:/Program Files/Microsoft Visual Studio"); - // find year - int year = 0; - { - arena_t tmp = *arena; - - dir_t *dir = os_dir_open(&tmp, base_path); - if (!os_dir_is_valid(dir)) { - err("couldn't open directory (%v)", base_path); - return STR_EMPTY; - } - - dir_foreach(&tmp, entry, dir) { - if (entry->type != DIRTYPE_DIR) continue; - - int number = strv_to_int(strv(entry->name)); - if (number > year) year = number; - } - } - - if (year == 0) { - err("couldn't find visual studio year version"); - return STR_EMPTY; - } - - str_t path_with_year = str_fmt(arena, "%v/%d", base_path, year); - - // find edition - const char *editions[] = { - "Enterprise", - "Professional", - "Community", - }; - - int edition = 0; - - for (; edition < arrlen(editions); ++edition) { - arena_t tmp = *arena; - str_t path = str_fmt(&tmp, "%v/%s", path_with_year, editions[edition]); - if (os_file_exists(strv(path))) { - break; - } - } - - if (edition >= arrlen(editions)) { - err("couldn't find visual studio edition"); - return STR_EMPTY; - } - - str_t vcvars = str_fmt(arena, "%v/%s/VC/Auxiliary/Build/vcvars64.bat", path_with_year, editions[edition]); - - return vcvars; -} - -bool load_cache(arena_t *arena) { - if (!os_file_exists(strv("build/cache.ini"))) { - err("build/cache.ini doesn't exist"); - return false; - } - - { - arena_t scratch = *arena; - - ini_t ini = ini_parse(&scratch, strv("build/cache.ini"), &(iniopt_t){ .comment_vals = strv("#") }); - initable_t *root = ini_get_table(&ini, INI_ROOT); - if (!root) fatal("fail"); - - for_each (val, root->values) { - os_set_env_var(scratch, val->key, val->value); - } - } - - return true; -} - -typedef enum optimise_level_e { - OPTIMISE_NONE, - OPTIMISE_FAST, - OPTIMISE_SMALL, - OPTIMISE__COUNT, -} optimise_level_e; - -typedef enum warning_level_e { - WARNING_NONE, - WARNING_DEFAULT, - WARNING_ALL, - WARNING__COUNT, -} warning_level_e; - -typedef enum sanitiser_e { - SANITISER_NONE, - SANITISER_ADDRESS, - SANITISER__COUNT, -} sanitiser_e; - -typedef enum cversion_e { - CVERSION_LATEST, - CVERSION_17, - CVERSION_11, - CVERSION__COUNT -} cversion_e; - -typedef struct options_t options_t; -struct options_t { - strview_t input_fname; - strview_t out_fname; - optimise_level_e optimisation; - warning_level_e warnings; - bool warnings_as_error; - sanitiser_e sanitiser; - bool fast_math; - bool debug; - strv_list_t *defines; - cversion_e cstd; - bool run; - strv_list_t *run_args; - bool is_cpp; -}; - -void print_help_message(void) { - puts("usage:"); - puts(" -r / -run [input.c] [args...] compiles and runs , forwards "); - puts(" -h / -help print this message"); - puts(" -o / -out [filename] output filename (default: build/.exe)"); - puts(" -O / -optimise [fast,small] optimisation level"); - puts(" -w / -warning [default,all] warning level"); - puts(" -werror treat warnings as errors"); - puts(" -fsanitize [address] turn on sanitiser"); - puts(" -fastmath turn on fast math"); - puts(" -g / -debug generate debug information"); - puts(" -D / -define [key=value,key] add a preprocessor define "); - puts(" -std [c11,c17,clatest] select c standard (default: clatest)"); - puts(" -cpp compile c++ instead of c"); - exit(0); -} - -optimise_level_e get_optimisation_level(strview_t arg) { - if (strv_equals(arg, strv("fast"))) { - return OPTIMISE_FAST; - } - else if (strv_equals(arg, strv("small"))) { - return OPTIMISE_SMALL; - } - warn("unrecognised optimisation level: (%v)", arg); - return OPTIMISE_NONE; -} - -warning_level_e get_warning_level(strview_t arg) { - if (strv_equals(arg, strv("default"))) { - return WARNING_DEFAULT; - } - else if (strv_equals(arg, strv("all"))) { - return WARNING_ALL; - } - warn("unrecognised warning level: (%v)", arg); - return WARNING_NONE; -} - -sanitiser_e get_sanitiser(strview_t arg) { - if (strv_equals(arg, strv("address"))) { - return SANITISER_ADDRESS; - } - warn("unrecognised sanitiser: (%v)", arg); - return SANITISER_NONE; -} - -cversion_e get_cversion(strview_t arg) { - if (strv_equals(arg, strv("clatest"))) { - return CVERSION_LATEST; - } - else if (strv_equals(arg, strv("c17"))) { - return CVERSION_17; - } - else if (strv_equals(arg, strv("c11"))) { - return CVERSION_11; - } - warn("unrecognised c std version: (%v)", arg); - return CVERSION_LATEST; -} - -options_t parse_options(arena_t *arena, int argc, char **argv) { - options_t out = {0}; - - for (int i = 1; i < argc; ++i) { - strview_t arg = strv(argv[i]); - -#define CHECK_OPT_BEG() if (false) {} -#define CHECK_OPT1(opt) else if (strv_equals(arg, strv("-" opt))) -#define CHECK_OPT2(small, big) else if (strv_equals(arg, strv("-" small)) || strv_equals(arg, strv("-" big))) - -#define GET_NEXT_ARG() (i + 1) < argc ? strv(argv[++i]) : STRV_EMPTY - - CHECK_OPT_BEG() - CHECK_OPT2("h", "help") { - print_help_message(); - } - CHECK_OPT2("o", "out") { - strview_t out_fname = GET_NEXT_ARG(); - str_t out_fname_str = str_fmt(arena, "build/%v", out_fname); - out.out_fname = strv(out_fname_str); - } - CHECK_OPT2("O", "optimise") { - out.optimisation = get_optimisation_level(GET_NEXT_ARG()); - } - CHECK_OPT2("w", "warning") { - out.warnings = get_warning_level(GET_NEXT_ARG()); - } - CHECK_OPT1("werror") { - out.warnings_as_error = true; - } - CHECK_OPT1("fsanitize") { - out.sanitiser = get_sanitiser(GET_NEXT_ARG()); - } - CHECK_OPT1("fastmath") { - out.fast_math = true; - } - CHECK_OPT2("g", "debug") { - out.debug = true; - } - CHECK_OPT2("D", "define") { - darr_push(arena, out.defines, GET_NEXT_ARG()); - } - CHECK_OPT1("std") { - out.cstd = get_cversion(GET_NEXT_ARG()); - } - CHECK_OPT1("cpp") { - out.is_cpp = true; - } - CHECK_OPT2("r", "run") { - out.run = true; - out.input_fname = GET_NEXT_ARG(); - for (i += 1; i < argc; ++i) { - darr_push(arena, out.run_args, strv(argv[i])); - } - } - else { - out.input_fname = arg; - } - } - -#undef CHECK_OPT_BEG -#undef CHECK_OPT1 -#undef CHECK_OPT2 -#undef GET_NEXT_ARG - - if (strv_is_empty(out.out_fname)) { - strview_t name; - os_file_split_path(out.input_fname, NULL, &name, NULL); - str_t out_fname = str_fmt(arena, "build\\%v", name); - out.out_fname = strv(out_fname); - } - - return out; -} - -int main(int argc, char **argv) { - os_init(); - - arena_t arena = arena_make(ARENA_VIRTUAL, GB(1)); - - if (argc < 2) { - print_help_message(); - } - - options_t opt = parse_options(&arena, argc, argv); - - if (!os_dir_exists(strv("build/"))) { - info("creating build folder"); - _mkdir("build"); - } - - if (!os_file_exists(strv("build/cache.ini"))) { - info("couldn't find cache.ini, creating it now"); - - arena_t scratch = arena; - str_t vcvars_path = find_vcvars_path(&scratch); - - os_cmd_t *cmd = NULL; - darr_push(&scratch, cmd, strv(vcvars_path)); - darr_push(&scratch, cmd, strv("&&")); - darr_push(&scratch, cmd, strv("set")); - darr_push(&scratch, cmd, strv(">")); - darr_push(&scratch, cmd, strv("build\\cache.ini")); - - if (!os_run_cmd(scratch, cmd, NULL)) { - fatal("failed to run vcvars64.bat"); - os_abort(1); - } - } - - { - arena_t scratch = arena; - - if (!load_cache(&scratch)) { - os_abort(1); - } - - os_cmd_t *cmd = NULL; - - darr_push(&scratch, cmd, strv("cl")); - darr_push(&scratch, cmd, strv("/nologo")); - darr_push(&scratch, cmd, strv("/utf-8")); - if (!opt.is_cpp) { - darr_push(&scratch, cmd, strv("/TC")); - } - - str_t output = str_fmt(&scratch, "/Fe:%v.exe", opt.out_fname); - str_t object = str_fmt(&scratch, "/Fo:%v.obj", opt.out_fname); - darr_push(&scratch, cmd, strv(output)); - darr_push(&scratch, cmd, strv(object)); - - strview_t optimisations[OPTIMISE__COUNT] = { - strv("/Od"), // disabled - strv("/O2"), // fast code - strv("/O1"), // small code - }; - darr_push(&scratch, cmd, optimisations[opt.optimisation]); - - strview_t warnings[WARNING__COUNT] = { - strv("/W0"), - strv("/W3"), - strv("/W4"), - }; - darr_push(&scratch, cmd, warnings[opt.warnings]); - - if (opt.warnings_as_error) { - darr_push(&scratch, cmd, strv("/WX")); - } - - if (opt.sanitiser) { - strview_t sanitisers[SANITISER__COUNT] = { - strv(""), - strv("/fsanitize=address"), - }; - darr_push(&scratch, cmd, sanitisers[opt.sanitiser]); - } - - if (opt.fast_math) { - darr_push(&scratch, cmd, strv("/fp:fast")); - } - - if (opt.debug) { - darr_push(&scratch, cmd, strv("/Zi")); - darr_push(&scratch, cmd, strv("/D_DEBUG")); - } - - for_each (def, opt.defines) { - for (int i = 0; i < def->count; ++i) { - str_t define = str_fmt(&scratch, "/D%v", def->items[i]); - darr_push(&scratch, cmd, strv(define)); - } - } - - strview_t cversion[CVERSION__COUNT] = { - strv("clatest"), - strv("c17"), - strv("c11"), - }; - - str_t cstd = str_fmt(&scratch, "/std:%v", cversion[opt.cstd]); - darr_push(&scratch, cmd, strv(cstd)); - - darr_push(&scratch, cmd, opt.input_fname); - - // /LD -> create dynamic lib - // /LDd -> create debug dynamic lib - // /link - - if (!os_run_cmd(scratch, cmd, NULL)) { - return 1; - } - } - - if (opt.run) { - arena_t scratch = arena; - os_cmd_t *cmd = NULL; - - darr_push(&scratch, cmd, opt.out_fname); - - for_each (arg, opt.run_args) { - for (int i = 0; i < arg->count; ++i) { - darr_push(&scratch, cmd, arg->items[i]); - } - } - - if (!os_run_cmd(scratch, cmd, NULL)) { - return 1; - } - } - - arena_cleanup(&arena); - - os_cleanup(); -} diff --git a/tools/unit_tests.c b/tools/unit_tests.c deleted file mode 100644 index 0f5f0ab..0000000 --- a/tools/unit_tests.c +++ /dev/null @@ -1,80 +0,0 @@ -#pragma section(".CRT$XCU", read) - -#include "../build.c" -#include "../tests/runner.h" - -#include "../tests/arena_tests.c" -#include "../tests/core_tests.c" -#include "../tests/net_tests.c" -#include "../tests/os_tests.c" -// #include "../tests/parsers_tests.c" -// #include "../tests/pretty_print_tests.c" -#include "../tests/str_tests.c" - -unit_test_t *test_head = NULL; -unit_test_t *test_tail = NULL; -const char *last_fail_reason = NULL; -bool last_failed = false; - -void ut_register(const char *file, const char *name, void (*fn)(void)) { - strview_t fname; - os_file_split_path(strv(file), NULL, &fname, NULL); - - fname = strv_remove_suffix(fname, arrlen("_tests") - 1); - - unit_test_t *test = calloc(1, sizeof(unit_test_t)); - test->name = strv(name); - test->fn = fn; - test->fname = fname; - - olist_push(test_head, test_tail, test); -} - -int main() { - colla_init(COLLA_ALL); - arena_t arena = arena_make(ARENA_VIRTUAL, GB(1)); - - strview_t last_file = STRV_EMPTY; - int success = 0; - int failed = 0; - int total = 0; - - unit_test_t *test = test_head; - while (test) { - if (!strv_equals(test->fname, last_file)) { - last_file = test->fname; - pretty_print(arena, "> %v\n", test->fname); - } - - test->fn(); - - total++; - - if (last_failed) { - pretty_print(arena, "%4s[X] %v: %s\n", "", test->name, last_fail_reason); - } - else { - pretty_print(arena, "%4s[V] %v\n", "", test->name); - success++; - } - - last_failed = false; - - test = test->next; - } - - print("\n"); - - strview_t colors[] = { - cstrv("red"), - cstrv("light_red"), - cstrv("yellow"), - cstrv("light_yellow"), - cstrv("light_green"), - cstrv("green"), - }; - - usize col = success * (arrlen(colors) - 1) / total; - - pretty_print(arena, "<%v>%d/%d tests passed\n", colors[col], success, total); -} diff --git a/win/net_win32.c b/win/net_win32.c deleted file mode 100644 index b844eb7..0000000 --- a/win/net_win32.c +++ /dev/null @@ -1,353 +0,0 @@ -#include "../net.h" -#include "../os.h" - -#include - -#if !COLLA_TCC - #include - #include - #include -#else - #include "../tcc/colla_tcc.h" -#endif - -#if COLLA_CMT_LIB - #pragma comment(lib, "Wininet") - #pragma comment(lib, "Ws2_32") -#endif - -struct { - HINTERNET internet; -} http_win = {0}; - -void net_init(void) { - if (http_win.internet) return; - http_win.internet = InternetOpen( - TEXT("COLLA_WININET"), - INTERNET_OPEN_TYPE_PRECONFIG, - NULL, - NULL, - 0 - ); - - WSADATA wsdata = {0}; - if (WSAStartup(0x0202, &wsdata)) { - fatal("couldn't startup sockets: %v", os_get_error_string(WSAGetLastError())); - } -} - -void net_cleanup(void) { - if (!http_win.internet) return; - InternetCloseHandle(http_win.internet); - http_win.internet = NULL; - WSACleanup(); -} - -iptr net_get_last_error(void) { - return WSAGetLastError(); -} - -http_res_t http_request(http_request_desc_t *req) { - return http_request_cb(req, NULL, NULL); -} - -http_res_t http_request_cb(http_request_desc_t *req, http_request_callback_fn callback, void *userdata) { - HINTERNET connection = NULL; - HINTERNET request = NULL; - BOOL result = FALSE; - bool success = false; - http_res_t res = {0}; - arena_t arena_before = *req->arena; - - if (!http_win.internet) { - err("net_init has not been called"); - goto failed; - } - - http_url_t split = http_split_url(req->url); - strview_t server = split.host; - strview_t page = split.uri; - - if (strv_starts_with_view(server, strv("http://"))) { - server = strv_remove_prefix(server, 7); - } - - if (strv_starts_with_view(server, strv("https://"))) { - server = strv_remove_prefix(server, 8); - } - - { - arena_t scratch = *req->arena; - - if (req->version.major == 0) req->version.major = 1; - if (req->version.minor == 0) req->version.minor = 1; - - const TCHAR *accepted_types[] = { TEXT("*/*"), NULL }; - const char *method = http_get_method_string(req->request_type); - str_t http_ver = str_fmt(&scratch, "HTTP/%u.%u", req->version.major, req->version.minor); - - tstr_t tserver = strv_to_tstr(&scratch, server); - tstr_t tpage = strv_to_tstr(&scratch, page); - tstr_t tmethod = strv_to_tstr(&scratch, strv(method)); - tstr_t thttp_ver = strv_to_tstr(&scratch, strv(http_ver)); - - connection = InternetConnect( - http_win.internet, - tserver.buf, - INTERNET_DEFAULT_HTTPS_PORT, - NULL, - NULL, - INTERNET_SERVICE_HTTP, - 0, - (DWORD_PTR)NULL // userdata - ); - if (!connection) { - err("call to InternetConnect failed: %u", os_get_error_string(os_get_last_error())); - goto failed; - } - - request = HttpOpenRequest( - connection, - tmethod.buf, - tpage.buf, - thttp_ver.buf, - NULL, - accepted_types, - INTERNET_FLAG_SECURE, - (DWORD_PTR)NULL // userdata - ); - if (!request) { - err("call to HttpOpenRequest failed: %v", os_get_error_string(os_get_last_error())); - goto failed; - } - } - - for (int i = 0; i < req->header_count; ++i) { - http_header_t *h = &req->headers[i]; - arena_t scratch = *req->arena; - - str_t header = str_fmt(&scratch, "%v: %v\r\n", h->key, h->value); - tstr_t theader = strv_to_tstr(&scratch, strv(header)); - HttpAddRequestHeaders(request, theader.buf, (DWORD)theader.len, 0); - } - - result = HttpSendRequest( - request, - NULL, - 0, - (void *)req->body.buf, - (DWORD)req->body.len - ); - if (!result) { - iptr error = os_get_last_error(); - if (error == ERROR_INTERNET_NAME_NOT_RESOLVED) { - err("invalid url: %v", req->url); - } - else { - err("call to HttpSendRequest failed: %lld", error); - // os_get_error_string(error)); - } - goto failed; - } - - u8 smallbuf[KB(5)]; - DWORD bufsize = sizeof(smallbuf); - - u8 *buffer = smallbuf; - - // try and read it into a static buffer - result = HttpQueryInfo(request, HTTP_QUERY_RAW_HEADERS_CRLF, smallbuf, &bufsize, NULL); - - // buffer is not big enough, allocate one with the arena instead - if (!result && GetLastError() == ERROR_INSUFFICIENT_BUFFER) { - info("buffer is too small"); - buffer = alloc(req->arena, u8, bufsize + 1); - result = HttpQueryInfo(request, HTTP_QUERY_RAW_HEADERS_CRLF, buffer, &bufsize, NULL); - } - - if (!result) { - err("couldn't get headers"); - goto failed; - } - - tstr_t theaders = { (TCHAR *)buffer, bufsize }; - str_t headers = str_from_tstr(req->arena, theaders); - - res.headers = http_parse_headers(req->arena, strv(headers)); - res.version = req->version; - - DWORD status_code = 0; - DWORD status_code_len = sizeof(status_code); - result = HttpQueryInfo(request, HTTP_QUERY_FLAG_NUMBER | HTTP_QUERY_STATUS_CODE, &status_code, &status_code_len, 0); - if (!result) { - err("couldn't get status code"); - goto failed; - } - - res.status_code = status_code; - - outstream_t body = ostr_init(req->arena); - - while (true) { - DWORD read = 0; - char read_buffer[4096]; - BOOL read_success = InternetReadFile( - request, read_buffer, sizeof(read_buffer), &read - ); - if (!read_success || read == 0) { - break; - } - - strview_t chunk = strv(read_buffer, read); - if (callback) { - callback(chunk, userdata); - } - ostr_puts(&body, chunk); - } - - res.body = strv(ostr_to_str(&body)); - - success = true; - -failed: - if (request) InternetCloseHandle(request); - if (connection) InternetCloseHandle(connection); - if (!success) *req->arena = arena_before; - return res; -} - -// SOCKETS ////////////////////////// - -SOCKADDR_IN sk__addrin_in(const char *ip, u16 port) { - SOCKADDR_IN sk_addr = { - .sin_family = AF_INET, - .sin_port = htons(port), - }; - - if (!inet_pton(AF_INET, ip, &sk_addr.sin_addr)) { - err("inet_pton failed: %v", os_get_error_string(net_get_last_error())); - return (SOCKADDR_IN){0}; - } - - return sk_addr; -} - -socket_t sk_open(sktype_e type) { - int sock_type = 0; - - switch(type) { - case SOCK_TCP: sock_type = SOCK_STREAM; break; - case SOCK_UDP: sock_type = SOCK_DGRAM; break; - default: fatal("skType not recognized: %d", type); break; - } - - return socket(AF_INET, sock_type, 0); -} - -socket_t sk_open_protocol(const char *protocol) { - struct protoent *proto = getprotobyname(protocol); - if(!proto) { - return INVALID_SOCKET; - } - return socket(AF_INET, SOCK_STREAM, proto->p_proto); -} - -bool sk_is_valid(socket_t sock) { - return sock != INVALID_SOCKET; -} - -bool sk_close(socket_t sock) { - return closesocket(sock) != SOCKET_ERROR; -} - -bool sk_bind(socket_t sock, const char *ip, u16 port) { - SOCKADDR_IN sk_addr = sk__addrin_in(ip, port); - if (sk_addr.sin_family == 0) { - return false; - } - return bind(sock, (SOCKADDR*)&sk_addr, sizeof(sk_addr)) != SOCKET_ERROR; -} - -bool sk_listen(socket_t sock, int backlog) { - return listen(sock, backlog) != SOCKET_ERROR; -} - -socket_t sk_accept(socket_t sock) { - SOCKADDR_IN addr = {0}; - int addr_size = sizeof(addr); - return accept(sock, (SOCKADDR*)&addr, &addr_size); -} - -bool sk_connect(socket_t sock, const char *server, u16 server_port) { - u8 tmpbuf[1024] = {0}; - arena_t scratch = arena_make(ARENA_STATIC, sizeof(tmpbuf), tmpbuf); - - str16_t wserver = strv_to_str16(&scratch, strv(server)); - - ADDRINFOW *addrinfo = NULL; - int result = GetAddrInfoW(wserver.buf, NULL, NULL, &addrinfo); - if (result) { - return false; - } - - char ip_str[1024] = {0}; - inet_ntop(addrinfo->ai_family, addrinfo->ai_addr, ip_str, sizeof(ip_str)); - - SOCKADDR_IN sk_addr = sk__addrin_in(ip_str, server_port); - if (sk_addr.sin_family == 0) { - return false; - } - - return connect(sock, (SOCKADDR*)&sk_addr, sizeof(sk_addr)) != SOCKET_ERROR; -} - -int sk_send(socket_t sock, const void *buf, int len) { - return send(sock, (const char *)buf, len, 0); -} - -int sk_recv(socket_t sock, void *buf, int len) { - return recv(sock, (char *)buf, len, 0); -} - -int sk_poll(skpoll_t *to_poll, int num_to_poll, int timeout) { - return WSAPoll((WSAPOLLFD*)to_poll, (ULONG)num_to_poll, timeout); -} - -oshandle_t sk_bind_event(socket_t sock, skevent_e event) { - if (event == SOCK_EVENT_NONE) { - return os_handle_zero(); - } - - HANDLE handle = WSACreateEvent(); - if (handle == WSA_INVALID_EVENT) { - return os_handle_zero(); - } - - int wsa_event = 0; - if (event & SOCK_EVENT_READ) wsa_event |= FD_READ; - if (event & SOCK_EVENT_WRITE) wsa_event |= FD_WRITE; - if (event & SOCK_EVENT_ACCEPT) wsa_event |= FD_ACCEPT; - if (event & SOCK_EVENT_CONNECT) wsa_event |= FD_CONNECT; - if (event & SOCK_EVENT_CLOSE) wsa_event |= FD_CLOSE; - - if (WSAEventSelect(sock, handle, wsa_event) != 0) { - WSACloseEvent(handle); - return os_handle_zero(); - } - - return (oshandle_t){ .data = (uptr)handle }; -} - -void sk_reset_event(oshandle_t handle) { - if (!os_handle_valid(handle)) { - warn("invalid handle"); - return; - } - WSAResetEvent((HANDLE)handle.data); -} - -void sk_destroy_event(oshandle_t handle) { - if (!os_handle_valid(handle)) return; - WSACloseEvent((HANDLE)handle.data); -} - diff --git a/win/str_win32.c b/win/str_win32.c deleted file mode 100644 index 4785ecb..0000000 --- a/win/str_win32.c +++ /dev/null @@ -1,81 +0,0 @@ -#include "../str.h" -#include "../arena.h" - -#include - -#if COLLA_TCC -#include "../tcc/colla_tcc.h" -#endif - -str_t str_os_from_str16(arena_t *arena, str16_t src) { - str_t out = {0}; - - int outlen = WideCharToMultiByte( - CP_UTF8, 0, - src.buf, (int)src.len, - NULL, 0, - NULL, NULL - ); - - if (outlen == 0) { - unsigned long error = GetLastError(); - if (error == ERROR_NO_UNICODE_TRANSLATION) { - err("couldn't translate wide string (%S) to utf8, no unicode translation", src.buf); - } - else { - err("couldn't translate wide string (%S) to utf8, %v", src.buf, os_get_error_string(os_get_last_error())); - } - - return STR_EMPTY; - } - - out.buf = alloc(arena, char, outlen + 1); - WideCharToMultiByte( - CP_UTF8, 0, - src.buf, (int)src.len, - out.buf, outlen, - NULL, NULL - ); - - out.len = outlen; - - return out; -} - -str16_t strv_os_to_str16(arena_t *arena, strview_t src) { - str16_t out = {0}; - - if (strv_is_empty(src)) { - return out; - } - - int len = MultiByteToWideChar( - CP_UTF8, 0, - src.buf, (int)src.len, - NULL, 0 - ); - - if (len == 0) { - unsigned long error = GetLastError(); - if (error == ERROR_NO_UNICODE_TRANSLATION) { - err("couldn't translate string (%v) to a wide string, no unicode translation", src); - } - else { - err("couldn't translate string (%v) to a wide string, %v", src, os_get_error_string(os_get_last_error())); - } - - return out; - } - - out.buf = alloc(arena, wchar_t, len + 1); - - MultiByteToWideChar( - CP_UTF8, 0, - src.buf, (int)src.len, - out.buf, len - ); - - out.len = len; - - return out; -} \ No newline at end of file