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