diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..dbe9c82 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.vscode/ \ No newline at end of file diff --git a/arena.c b/arena.c index 1758638..c52f4e2 100644 --- a/arena.c +++ b/arena.c @@ -1,188 +1,230 @@ #include "arena.h" #include -#include #include -#include -#include "vmem.h" -#include "tracelog.h" +#include "os.h" -static uintptr_t arena__align(uintptr_t ptr, usize align) { +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(byte *buf, usize len); +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 arenaInit(const arena_desc_t *desc) { +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->allocation); break; - case ARENA_MALLOC: out = arena__make_malloc(desc->allocation); break; - case ARENA_STATIC: out = arena__make_static(desc->static_buffer, desc->allocation); break; + 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; + default: break; } } return out; } -void arenaCleanup(arena_t *arena) { +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 - case ARENA_STATIC: break; + default: break; } - - arena->start = NULL; - arena->current = NULL; - arena->end = NULL; - arena->type = 0; + + memset(arena, 0, sizeof(arena_t)); } -arena_t arenaScratch(arena_t *arena, usize size) { - uint8 *buffer = alloc(arena, uint8, size, ALLOC_SOFT_FAIL | ALLOC_NOZERO); +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 *arenaAlloc(const arena_alloc_desc_t *desc) { +void *arena_alloc(const arena_alloc_desc_t *desc) { if (!desc || !desc->arena || desc->arena->type == ARENA_TYPE_NONE) { return NULL; } - usize total = desc->size * desc->count; arena_t *arena = desc->arena; - arena->current = (byte *)arena__align((uintptr_t)arena->current, desc->align); + u8 *ptr = NULL; - if (total > arenaRemaining(arena)) { - if (desc->flags & ALLOC_SOFT_FAIL) { - return NULL; - } - fatal("finished space in arena, tried to allocate %_$$$dB out of %_$$$dB\n", total, arenaRemaining(arena)); - abort(); + switch (arena->type) { + case ARENA_MALLOC_ALWAYS: + ptr = arena__alloc_malloc_always(desc); + break; + default: + ptr = arena__alloc_common(desc); + break; } - if (arena->type == ARENA_VIRTUAL) { - usize allocated = arenaTell(arena); - usize page_end = vmemPadToPage(allocated); - usize new_cur = allocated + total; - - if (new_cur > page_end) { - usize extra_mem = vmemPadToPage(new_cur - page_end); - usize page_size = vmemGetPageSize(); - // TODO is this really correct? - usize num_of_pages = (extra_mem / page_size) + 1; - - assert(num_of_pages > 0); - - if (!vmemCommit(arena->current, num_of_pages + 1)) { - if (desc->flags & ALLOC_SOFT_FAIL) { - return NULL; - } - printf("failed to commit memory for virtual arena, tried to commit %zu pages\n", num_of_pages); - exit(1); - } - } - } - - byte *ptr = arena->current; - arena->current += total; + usize total = desc->size * desc->count; return desc->flags & ALLOC_NOZERO ? ptr : memset(ptr, 0, total); } -usize arenaTell(arena_t *arena) { - return arena ? arena->current - arena->start : 0; +usize arena_tell(arena_t *arena) { + return arena ? arena->cur - arena->beg : 0; } -usize arenaRemaining(arena_t *arena) { - return arena && (arena->current < arena->end) ? arena->end - arena->current : 0; +usize arena_remaining(arena_t *arena) { + return arena && (arena->cur < arena->end) ? arena->end - arena->cur : 0; } -void arenaRewind(arena_t *arena, usize from_start) { +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; } - assert(arenaTell(arena) >= from_start); + assert(arena_tell(arena) >= from_start); - arena->current = arena->start + from_start; + arena->cur = arena->beg + from_start; } -void arenaPop(arena_t *arena, usize amount) { +void arena_pop(arena_t *arena, usize amount) { if (!arena) { return; } - usize position = arenaTell(arena); + usize position = arena_tell(arena); if (!position) { return; } - arenaRewind(arena, position - amount); + arena_rewind(arena, position - amount); } // == VIRTUAL ARENA ==================================================================================================== static arena_t arena__make_virtual(usize size) { usize alloc_size = 0; - byte *ptr = vmemInit(size, &alloc_size); - if (!vmemCommit(ptr, 1)) { - vmemRelease(ptr); + u8 *ptr = os_reserve(size, &alloc_size); + if (!os_commit(ptr, 1)) { + os_release(ptr, alloc_size); ptr = NULL; } return (arena_t){ - .start = ptr, - .current = ptr, + .beg = ptr, + .cur = ptr, .end = ptr ? ptr + alloc_size : NULL, .type = ARENA_VIRTUAL, }; } static void arena__free_virtual(arena_t *arena) { - if (!arena->start) { + if (!arena->beg) { return; } - bool success = vmemRelease(arena->start); + bool success = os_release(arena->beg, arena_capacity(arena)); assert(success && "Failed arena free"); } - // == MALLOC ARENA ===================================================================================================== +extern void *malloc(usize size); +extern void free(void *ptr); + static arena_t arena__make_malloc(usize size) { - byte *ptr = malloc(size); + u8 *ptr = malloc(size); assert(ptr); return (arena_t) { - .start = ptr, - .current = ptr, + .beg = ptr, + .cur = ptr, .end = ptr ? ptr + size : NULL, .type = ARENA_MALLOC, }; } static void arena__free_malloc(arena_t *arena) { - free(arena->start); + 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\n", total, arena_remaining(arena)); + } + 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; + + 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; + + // TODO: alignment? + u8 *ptr = malloc(total); + if (!ptr && !(desc->flags & ALLOC_SOFT_FAIL)) { + fatal("malloc call failed for %_$$$dB", total); + } + + return ptr; +} + + // == STATIC ARENA ===================================================================================================== -static arena_t arena__make_static(byte *buf, usize len) { +static arena_t arena__make_static(u8 *buf, usize len) { return (arena_t) { - .start = buf, - .current = buf, + .beg = buf, + .cur = buf, .end = buf ? buf + len : NULL, .type = ARENA_STATIC, }; } + diff --git a/arena.h b/arena.h index e3c0656..40b9130 100644 --- a/arena.h +++ b/arena.h @@ -1,66 +1,73 @@ -#pragma once +#ifndef COLLA_ARENA_H +#define COLLA_ARENA_H -#include "collatypes.h" +#include "core.h" -#if COLLA_TCC -#define alignof __alignof__ -#else -#define alignof _Alignof +#if COLLA_WIN && !COLLA_TCC +#define alignof __alignof #endif -typedef enum { +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 { +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 { - uint8 *start; - uint8 *current; - uint8 *end; +typedef struct arena_t arena_t; +struct arena_t { + u8 *beg; + u8 *cur; + u8 *end; arena_type_e type; -} arena_t; +}; -typedef struct { +typedef struct arena_desc_t arena_desc_t; +struct arena_desc_t { arena_type_e type; - usize allocation; - byte *static_buffer; -} arena_desc_t; + usize size; + u8 *static_buffer; +}; -typedef struct { +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_alloc_desc_t; - -#ifndef ARENA_NO_SIZE_HELPERS -#define KB(count) ( (count) * 1024) -#define MB(count) (KB(count) * 1024) -#define GB(count) (MB(count) * 1024) -#endif +}; // arena_type_e type, usize allocation, [ byte *static_buffer ] -#define arenaMake(...) arenaInit(&(arena_desc_t){ __VA_ARGS__ }) +#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, ...) arenaAlloc(&(arena_alloc_desc_t){ .size = sizeof(type), .count = 1, .align = alignof(type), .arena = arenaptr, __VA_ARGS__ }) +#define alloc(arenaptr, type, ...) arena_alloc(&(arena_alloc_desc_t){ .size = sizeof(type), .count = 1, .align = alignof(type), .arena = arenaptr, __VA_ARGS__ }) -arena_t arenaInit(const arena_desc_t *desc); -void arenaCleanup(arena_t *arena); +// 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 arenaScratch(arena_t *arena, usize size); +arena_t arena_init(const arena_desc_t *desc); +void arena_cleanup(arena_t *arena); -void *arenaAlloc(const arena_alloc_desc_t *desc); -usize arenaTell(arena_t *arena); -usize arenaRemaining(arena_t *arena); -void arenaRewind(arena_t *arena, usize from_start); -void arenaPop(arena_t *arena, usize amount); +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 \ No newline at end of file diff --git a/base64.c b/base64.c deleted file mode 100644 index 4c78ba2..0000000 --- a/base64.c +++ /dev/null @@ -1,100 +0,0 @@ -#include "base64.h" - -#include "warnings/colla_warn_beg.h" - -#include "arena.h" - -static unsigned char 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', '+', '/' -}; - -static uint8 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 base64Encode(arena_t *arena, buffer_t buffer) { - usize outlen = ((buffer.len + 2) / 3) * 4; - uint8 *out = alloc(arena, uint8, outlen); - - for (usize i = 0, j = 0; i < buffer.len;) { - uint32 a = i < buffer.len ? buffer.data[i++] : 0; - uint32 b = i < buffer.len ? buffer.data[i++] : 0; - uint32 c = i < buffer.len ? buffer.data[i++] : 0; - - uint32 triple = (a << 16) | (b << 8) | c; - - out[j++] = encoding_table[(triple >> 18) & 0x3F]; - out[j++] = encoding_table[(triple >> 12) & 0x3F]; - out[j++] = encoding_table[(triple >> 6) & 0x3F]; - out[j++] = 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 base64Decode(arena_t *arena, buffer_t buffer) { - uint8 *out = arena->current; - usize start = arenaTell(arena); - - for (usize i = 0; i < buffer.len; i += 4) { - uint8 a = decoding_table[buffer.data[i + 0]]; - uint8 b = decoding_table[buffer.data[i + 1]]; - uint8 c = decoding_table[buffer.data[i + 2]]; - uint8 d = decoding_table[buffer.data[i + 3]]; - - uint32 triple = - ((uint32)a << 18) | - ((uint32)b << 12) | - ((uint32)c << 6) | - ((uint32)d); - - uint8 *bytes = alloc(arena, uint8, 3); - - bytes[0] = (triple >> 16) & 0xFF; - bytes[1] = (triple >> 8) & 0xFF; - bytes[2] = (triple >> 0) & 0xFF; - } - - usize outlen = arenaTell(arena) - start; - - return (buffer_t){ - .data = out, - .len = outlen, - }; -} - -#include "warnings/colla_warn_end.h" diff --git a/base64.h b/base64.h deleted file mode 100644 index 71c4b03..0000000 --- a/base64.h +++ /dev/null @@ -1,8 +0,0 @@ -#pragma once - -#include "collatypes.h" - -typedef struct arena_t arena_t; - -buffer_t base64Encode(arena_t *arena, buffer_t buffer); -buffer_t base64Decode(arena_t *arena, buffer_t buffer); \ No newline at end of file diff --git a/bits.h b/bits.h deleted file mode 100644 index 8cd15bd..0000000 --- a/bits.h +++ /dev/null @@ -1,54 +0,0 @@ -#pragma once - -#include "collatypes.h" - -uint32 bitsCtz(uint32 v); -uint32 bitsNextPow2(uint32 v); - -// == INLINE IMPLEMENTATION ============================================================== - -#if COLLA_MSVC -#define BITS_WIN 1 -#define BITS_LIN 0 -#include -#elif COLLA_GCC || COLLA_CLANG || COLLA_EMC -#define BITS_WIN 0 -#define BITS_LIN 1 -#elif COLLA_TCC -#define BITS_WIN 0 -#define BITS_LIN 0 -#else -#error "bits header not supported on this compiler" -#endif - -#include "tracelog.h" - -inline uint32 bitsCtz(uint32 v) { -#if BITS_LIN - return v ? __builtin_ctz(v) : 0; -#elif BITS_WIN - uint32 trailing = 0; - return _BitScanForward((unsigned long *)&trailing, v) ? trailing : 32; -#else - if (v == 0) return 0; - for (uint32 i = 0; i < 32; ++i) { - if (v & (1 << i)) return i; - } - return 32; -#endif -} - -inline uint32 bitsNextPow2(uint32 v) { - v--; - v |= v >> 1; - v |= v >> 2; - v |= v >> 4; - v |= v >> 8; - v |= v >> 16; - v++; - - return v; -} - -#undef BITS_WIN -#undef BITS_LIN diff --git a/build.c b/build.c index a98ac51..843c4e7 100644 --- a/build.c +++ b/build.c @@ -1,52 +1,13 @@ -#if COLLA_ONLYCORE - #define COLLA_NOTHREADS 1 - #define COLLA_NOSOCKETS 1 - #define COLLA_NOHTTP 1 - #define COLLA_NOSERVER 1 - #define COLLA_NOHOTRELOAD 1 -#endif - -#if COLLA_NOSOCKETS - #undef COLLA_NOHTTP - #undef COLLA_NOSERVER - #define COLLA_NOHTTP 1 - #define COLLA_NOSERVER 1 +#include "core.h" + +#if COLLA_TCC +#define COLLA_NO_CONDITION_VARIABLE 1 #endif +#include "core.c" +#include "os.c" #include "arena.c" -#include "base64.c" -#include "file.c" -#include "format.c" -#include "ini.c" -#include "json.c" #include "str.c" -#include "strstream.c" -#include "tracelog.c" -#include "utf8.c" -#include "vmem.c" -#include "xml.c" -#include "sha1.c" -#include "markdown.c" -#include "highlight.c" -#include "dir.c" - -#if !COLLA_NOTHREADS -#include "cthreads.c" -#endif - -#if !COLLA_NOSOCKETS -#include "socket.c" -#include "websocket.c" -#endif - -#if !COLLA_NOHTTP -#include "http.c" -#endif - -#if !COLLA_NOSERVER -#include "server.c" -#endif - -#if !COLLA_NOHOTRELOAD -#include "hot_reload.c" -#endif \ No newline at end of file +#include "parsers.c" +#include "net.c" +#include "darr.h" \ No newline at end of file diff --git a/colladefines.h b/colladefines.h deleted file mode 100644 index c499018..0000000 --- a/colladefines.h +++ /dev/null @@ -1,115 +0,0 @@ -#pragma once - -#define arrlen(a) (sizeof(a) / sizeof((a)[0])) -#define for_each(it, list) for (typeof(list) it = list; it; it = it->next) - -#if defined(_DEBUG) || !defined(NDEBUG) -#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_WIN - -#undef NOMINMAX -#undef WIN32_LEAN_AND_MEAN - -#define WIN32_LEAN_AND_MEAN -#define NOMINMAX - -#endif - -#undef min -#undef max - -#define min(a, b) ((a) < (b) ? (a) : (b)) -#define max(a, b) ((a) > (b) ? (a) : (b)) \ No newline at end of file diff --git a/collatypes.h b/collatypes.h deleted file mode 100644 index b62af8a..0000000 --- a/collatypes.h +++ /dev/null @@ -1,37 +0,0 @@ -#pragma once - -#include -#include -#include - -#include "colladefines.h" - -typedef unsigned char uchar; -typedef unsigned short ushort; -typedef unsigned int uint; - -typedef uint8_t uint8; -typedef uint16_t uint16; -typedef uint32_t uint32; -typedef uint64_t uint64; - -typedef int8_t int8; -typedef int16_t int16; -typedef int32_t int32; -typedef int64_t int64; - -typedef size_t usize; -typedef ptrdiff_t isize; - -typedef uint8 byte; - -typedef struct { - uint8 *data; - usize len; -} buffer_t; - -#if COLLA_WIN && defined(UNICODE) -typedef wchar_t TCHAR; -#else -typedef char TCHAR; -#endif diff --git a/core.c b/core.c new file mode 100644 index 0000000..fd25d3a --- /dev/null +++ b/core.c @@ -0,0 +1,75 @@ +#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 net_init(void); +extern void os_cleanup(void); +extern void net_cleanup(void); + +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 (modules & COLLA_NET) { + net_init(); + } +} + +void colla_cleanup(void) { + colla_modules_e modules = colla__initialised_modules; + if (modules & COLLA_OS) { + os_cleanup(); + } + if (modules & COLLA_NET) { + net_cleanup(); + } +} + +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 new file mode 100644 index 0000000..30db78f --- /dev/null +++ b/core.h @@ -0,0 +1,199 @@ +#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); + +///////////////////////////////////////////////// + +// USEFUL MACROS //////////////////////////////// + +#define arrlen(a) (sizeof(a) / sizeof((a)[0])) +#define COLLA_UNUSED(v) (void)(v) + +#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) + +///////////////////////////////////////////////// + +// LINKED LISTS ///////////////////////////////// + +#define list_push_n(list, item, next) ((item)->next=(list), (list)=(item)) +#define list_pop_n(list, next) ((list) = (list) ? (list)->next : NULL) + +#define list_push(list, item) list_push_n(list, item, next) +#define list_pop(list) list_pop_n(list, next) + +#define dlist_push_pn(list, item, next, prev) if (item) (item)->next = (list); if (list) (list)->prev = (item); (list) = (item) +#define dlist_pop_pn(list, item, next, prev) 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) + +#define dlist_push(list, item) dlist_push_pn(list, item, next, prev) +#define dlist_pop(list, item) dlist_pop_pn(list, item, next, prev) + +#define for_each(it, list) for (typeof(list) it = list; it; it = it->next) + +///////////////////////////////////////////////// + +// OS AND COMPILER MACROS /////////////////////// + +#if defined(_DEBUG) || !defined(NDEBUG) + #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 + +///////////////////////////////////////////////// + +// 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 \ No newline at end of file diff --git a/cthreads.c b/cthreads.c deleted file mode 100644 index b910438..0000000 --- a/cthreads.c +++ /dev/null @@ -1,282 +0,0 @@ -#include "cthreads.h" - -#include - -// TODO: swap calloc for arenas - -typedef struct { - cthread_func_t func; - void *arg; -} _thr_internal_t; - -#if COLLA_WIN -#include - -// == THREAD =========================================== - -static DWORD WINAPI _thrFuncInternal(void *arg) { - _thr_internal_t *params = (_thr_internal_t *)arg; - cthread_func_t func = params->func; - void *argument = params->arg; - free(params); - return (DWORD)func(argument); -} - -cthread_t thrCreate(cthread_func_t func, void *arg) { - HANDLE thread = INVALID_HANDLE_VALUE; - _thr_internal_t *params = calloc(1, sizeof(_thr_internal_t)); - - if(params) { - params->func = func; - params->arg = arg; - - thread = CreateThread(NULL, 0, _thrFuncInternal, params, 0, NULL); - } - - return (cthread_t)thread; -} - -bool thrValid(cthread_t ctx) { - return (HANDLE)ctx != INVALID_HANDLE_VALUE; -} - -bool thrDetach(cthread_t ctx) { - return CloseHandle((HANDLE)ctx); -} - -cthread_t thrCurrent(void) { - return (cthread_t)GetCurrentThread(); -} - -int thrCurrentId(void) { - return GetCurrentThreadId(); -} - -int thrGetId(cthread_t ctx) { -#if COLLA_TCC - return 0; -#endif - return GetThreadId((HANDLE)ctx); -} - -void thrExit(int code) { - ExitThread(code); -} - -bool thrJoin(cthread_t ctx, int *code) { - if(!ctx) return false; - int return_code = WaitForSingleObject((HANDLE)ctx, INFINITE); - if(code) *code = return_code; - BOOL success = CloseHandle((HANDLE)ctx); - return return_code != WAIT_FAILED && success; -} - -// == MUTEX ============================================ - -cmutex_t mtxInit(void) { - CRITICAL_SECTION *crit_sec = calloc(1, sizeof(CRITICAL_SECTION)); - if(crit_sec) { - InitializeCriticalSection(crit_sec); - } - return (cmutex_t)crit_sec; -} - -void mtxFree(cmutex_t ctx) { - DeleteCriticalSection((CRITICAL_SECTION *)ctx); - free((CRITICAL_SECTION *)ctx); -} - -bool mtxValid(cmutex_t ctx) { - return (void *)ctx != NULL; -} - -bool mtxLock(cmutex_t ctx) { - EnterCriticalSection((CRITICAL_SECTION *)ctx); - return true; -} - -bool mtxTryLock(cmutex_t ctx) { - return TryEnterCriticalSection((CRITICAL_SECTION *)ctx); -} - -bool mtxUnlock(cmutex_t ctx) { - LeaveCriticalSection((CRITICAL_SECTION *)ctx); - return true; -} - -#if !COLLA_NO_CONDITION_VAR -// == CONDITION VARIABLE =============================== - -#include "tracelog.h" - -condvar_t condInit(void) { - CONDITION_VARIABLE *cond = calloc(1, sizeof(CONDITION_VARIABLE)); - InitializeConditionVariable(cond); - return (condvar_t)cond; -} - -void condFree(condvar_t cond) { - free((CONDITION_VARIABLE *)cond); -} - -void condWake(condvar_t cond) { - WakeConditionVariable((CONDITION_VARIABLE *)cond); -} - -void condWakeAll(condvar_t cond) { - WakeAllConditionVariable((CONDITION_VARIABLE *)cond); -} - -void condWait(condvar_t cond, cmutex_t mtx) { - SleepConditionVariableCS((CONDITION_VARIABLE *)cond, (CRITICAL_SECTION *)mtx, INFINITE); -} - -void condWaitTimed(condvar_t cond, cmutex_t mtx, int milliseconds) { - SleepConditionVariableCS((CONDITION_VARIABLE *)cond, (CRITICAL_SECTION *)mtx, milliseconds); -} - -#endif - -#else -#include -#include -#include -#include - -// == THREAD =========================================== - -#define INT_TO_VOIDP(a) ((void *)((uintptr_t)(a))) - -static void *_thrFuncInternal(void *arg) { - _thr_internal_t *params = (_thr_internal_t *)arg; - cthread_func_t func = params->func; - void *argument = params->arg; - free(params); - return INT_TO_VOIDP(func(argument)); -} - -cthread_t thrCreate(cthread_func_t func, void *arg) { - pthread_t handle = (pthread_t)NULL; - - _thr_internal_t *params = calloc(1, sizeof(_thr_internal_t)); - - if(params) { - params->func = func; - params->arg = arg; - - int result = pthread_create(&handle, NULL, _thrFuncInternal, params); - if(result) handle = (pthread_t)NULL; - } - - return (cthread_t)handle; -} - -bool thrValid(cthread_t ctx) { - return (void *)ctx != NULL; -} - -bool thrDetach(cthread_t ctx) { - return pthread_detach((pthread_t)ctx) == 0; -} - -cthread_t thrCurrent(void) { - return (cthread_t)pthread_self(); -} - -int thrCurrentId(void) { - return (int)pthread_self(); -} - -int thrGetId(cthread_t ctx) { - return (int)ctx; -} - -void thrExit(int code) { - pthread_exit(INT_TO_VOIDP(code)); -} - -bool thrJoin(cthread_t ctx, int *code) { - void *result = code; - return pthread_join((pthread_t)ctx, &result) != 0; -} - -// == MUTEX ============================================ - -cmutex_t mtxInit(void) { - pthread_mutex_t *mutex = calloc(1, sizeof(pthread_mutex_t)); - - if(mutex) { - if(pthread_mutex_init(mutex, NULL)) { - free(mutex); - mutex = NULL; - } - } - - return (cmutex_t)mutex; -} - -void mtxFree(cmutex_t ctx) { - pthread_mutex_destroy((pthread_mutex_t *)ctx); -} - -bool mtxValid(cmutex_t ctx) { - return (void *)ctx != NULL; -} - -bool mtxLock(cmutex_t ctx) { - return pthread_mutex_lock((pthread_mutex_t *)ctx) == 0; -} - -bool mtxTryLock(cmutex_t ctx) { - return pthread_mutex_trylock((pthread_mutex_t *)ctx) == 0; -} - -bool mtxUnlock(cmutex_t ctx) { - return pthread_mutex_unlock((pthread_mutex_t *)ctx) == 0; -} - -#if !COLLA_NO_CONDITION_VAR - -// == CONDITION VARIABLE =============================== - -condvar_t condInit(void) { - pthread_cond_t *cond = calloc(1, sizeof(pthread_cond_t)); - - if(cond) { - if(pthread_cond_init(cond, NULL)) { - free(cond); - cond = NULL; - } - } - - return (condvar_t)cond; -} - -void condFree(condvar_t cond) { - if (!cond) return; - pthread_cond_destroy((pthread_cond_t *)cond); - free((pthread_cond_t *)cond); -} - -void condWake(condvar_t cond) { - pthread_cond_signal((pthread_cond_t *)cond); -} - -void condWakeAll(condvar_t cond) { - pthread_cond_broadcast((pthread_cond_t *)cond); -} - -void condWait(condvar_t cond, cmutex_t mtx) { - pthread_cond_wait((pthread_cond_t *)cond, (pthread_mutex_t *)mtx); -} - -void condWaitTimed(condvar_t cond, cmutex_t mtx, int milliseconds) { - struct timespec timeout; - time(&timeout.tv_sec); - timeout.tv_nsec += milliseconds * 1000000; - pthread_cond_timedwait((pthread_cond_t *)cond, (pthread_mutex_t *)mtx, &timeout); -} - -#endif - -#endif diff --git a/cthreads.h b/cthreads.h deleted file mode 100644 index 03202f2..0000000 --- a/cthreads.h +++ /dev/null @@ -1,57 +0,0 @@ -#pragma once - -#include "collatypes.h" - -#if COLLA_TCC -#define COLLA_NO_CONDITION_VAR 1 -#endif - -typedef struct arena_t arena_t; - -// == THREAD =========================================== - -typedef uintptr_t cthread_t; - -typedef int (*cthread_func_t)(void *); - -cthread_t thrCreate(cthread_func_t func, void *arg); -bool thrValid(cthread_t ctx); -bool thrDetach(cthread_t ctx); - -cthread_t thrCurrent(void); -int thrCurrentId(void); -int thrGetId(cthread_t ctx); - -void thrExit(int code); -bool thrJoin(cthread_t ctx, int *code); - -// == MUTEX ============================================ - -typedef uintptr_t cmutex_t; - -cmutex_t mtxInit(void); -void mtxFree(cmutex_t ctx); - -bool mtxValid(cmutex_t ctx); - -bool mtxLock(cmutex_t ctx); -bool mtxTryLock(cmutex_t ctx); -bool mtxUnlock(cmutex_t ctx); - -#if !COLLA_NO_CONDITION_VAR -// == CONDITION VARIABLE =============================== - -typedef uintptr_t condvar_t; - -#define COND_WAIT_INFINITE 0xFFFFFFFF - -condvar_t condInit(void); -void condFree(condvar_t cond); - -void condWake(condvar_t cond); -void condWakeAll(condvar_t cond); - -void condWait(condvar_t cond, cmutex_t mtx); -void condWaitTimed(condvar_t cond, cmutex_t mtx, int milliseconds); - -#endif \ No newline at end of file diff --git a/darr.h b/darr.h new file mode 100644 index 0000000..c48c898 --- /dev/null +++ b/darr.h @@ -0,0 +1,82 @@ +#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/dir.c b/dir.c deleted file mode 100644 index 3dd1b35..0000000 --- a/dir.c +++ /dev/null @@ -1,163 +0,0 @@ -#include "dir.h" - -#if COLLA_WIN - -#include - -typedef struct dir_t { - WIN32_FIND_DATA find_data; - HANDLE handle; - dir_entry_t cur_entry; - dir_entry_t next_entry; -} dir_t; - -static dir_entry_t dir__entry_from_find_data(arena_t *arena, WIN32_FIND_DATA *fd) { - dir_entry_t out = {0}; - - out.name = str(arena, fd->cFileName); - - if (fd->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { - out.type = DIRTYPE_DIR; - } - else { - LARGE_INTEGER filesize = { - .LowPart = fd->nFileSizeLow, - .HighPart = fd->nFileSizeHigh, - }; - out.filesize = filesize.QuadPart; - } - - return out; -} - -dir_t *dirOpen(arena_t *arena, strview_t path) { - uint8 tmpbuf[1024] = {0}; - arena_t scratch = arenaMake(ARENA_STATIC, sizeof(tmpbuf), tmpbuf); - - TCHAR *winpath = strvToTChar(&scratch, path); - // get a little extra leeway - TCHAR fullpath[MAX_PATH + 16] = {0}; - DWORD pathlen = GetFullPathName(winpath, MAX_PATH, fullpath, NULL); - // add asterisk at the end of the path - if (fullpath[pathlen ] != '\\' && fullpath[pathlen] != '/') { - fullpath[pathlen++] = '\\'; - } - fullpath[pathlen++] = '*'; - fullpath[pathlen++] = '\0'; - - dir_t *ctx = alloc(arena, dir_t); - ctx->handle = FindFirstFile(fullpath, &ctx->find_data); - - if (ctx->handle == INVALID_HANDLE_VALUE) { - arenaPop(arena, sizeof(dir_t)); - return NULL; - } - - ctx->next_entry = dir__entry_from_find_data(arena, &ctx->find_data); - - return ctx; -} - -void dirClose(dir_t *ctx) { - FindClose(ctx->handle); - ctx->handle = INVALID_HANDLE_VALUE; -} - -bool dirIsValid(dir_t *ctx) { - return ctx && ctx->handle != INVALID_HANDLE_VALUE; -} - -dir_entry_t *dirNext(arena_t *arena, dir_t *ctx) { - if (!dirIsValid(ctx)) { - return NULL; - } - - ctx->cur_entry = ctx->next_entry; - - ctx->next_entry = (dir_entry_t){0}; - - if (FindNextFile(ctx->handle, &ctx->find_data)) { - ctx->next_entry = dir__entry_from_find_data(arena, &ctx->find_data); - } - else { - dirClose(ctx); - } - - return &ctx->cur_entry; -} - -#elif COLLA_POSIX - -#include -#include -#include -#include - -// taken from https://sites.uclouvain.be/SystInfo/usr/include/dirent.h.html -// hopefully shouldn't be needed -#ifndef DT_DIR - #define DT_DIR 4 -#endif -#ifndef DT_REG - #define DT_REG 8 -#endif - -typedef struct dir_t { - DIR *dir; - dir_entry_t next; -} dir_t; - -dir_t *dirOpen(arena_t *arena, strview_t path) { - if (strvIsEmpty(path)) { - err("path cannot be null"); - return NULL; - } - - uint8 tmpbuf[1024]; - arena_t scratch = arenaMake(ARENA_STATIC, sizeof(tmpbuf), tmpbuf); - str_t dirpath = str(&scratch, path); - - DIR *dir = opendir(dirpath.buf); - if (!dir) { - err("could not open dir (%v)", path); - return NULL; - } - - dir_t *ctx = alloc(arena, dir_t); - ctx->dir = dir; - return ctx; -} - -void dirClose(dir_t *ctx) { - if (ctx) { - closedir(ctx->dir); - } -} - -bool dirIsValid(dir_t *ctx) { - return ctx && ctx->dir; -} - -dir_entry_t *dirNext(arena_t *arena, dir_t *ctx) { - if (!ctx) return NULL; - - struct dirent *dp = readdir(ctx->dir); - if (!dp) { - dirClose(ctx); - return NULL; - } - - ctx->next.name = (str_t){ dp->d_name, strlen(dp->d_name) }; - ctx->next.type = dp->d_type == DT_DIR ? DIRTYPE_DIR : DIRTYPE_FILE; - ctx->next.filesize = 0; - - if (dp->d_type == DT_REG) { - struct stat file_info = {0}; - stat(dp->d_name, &file_info); - ctx->next.filesize = file_info.st_size; - } - - return &ctx->next; -} - -#endif diff --git a/dir.h b/dir.h deleted file mode 100644 index e4ed540..0000000 --- a/dir.h +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once - -#include "str.h" -#include "arena.h" - -typedef struct dir_t dir_t; - -typedef enum { - DIRTYPE_FILE, - DIRTYPE_DIR, -} dir_type_e; - -typedef struct { - str_t name; - dir_type_e type; - usize filesize; -} dir_entry_t; - -dir_t *dirOpen(arena_t *arena, strview_t path); -// optional, only call this if you want to return before dirNext returns NULL -void dirClose(dir_t *ctx); - -bool dirIsValid(dir_t *ctx); - -dir_entry_t *dirNext(arena_t *arena, dir_t *ctx); diff --git a/docs/arena.md b/docs/arena.md deleted file mode 100644 index 438b390..0000000 --- a/docs/arena.md +++ /dev/null @@ -1,128 +0,0 @@ -# Arena ------------ - -An arena is a bump allocator, under the hood it can use one of 3 strategies to allocate its data: - -* `Virtual`: allocates with virtual memory, meaning that it reserves the data upfront, but only allocates one page at a time (usually 64 Kb). This is the preferred way to use the arena as it can freely allocate gigabytes of memory for free -* `Malloc`: allocates the memory upfront using malloc -* `Static`: uses a buffer passed by the user instead of allocating - -To help with allocating big chunks of data, `arena.h` defines the macros `KB`, `MB`, and `GB`. If you don't need them you can define `ARENA_NO_SIZE_HELPERS` - -To create an arena use the macro `arenaMake`: -```c -arena_t varena = arenaMake(ARENA_VIRTUAL, GB(1)); -uint8 buffer[1024]; -arena_t sarena = arenaMake(ARENA_STATIC, sizeof(buffer), buffer); -``` - -To allocate use the `alloc` macro. The parameters to allocate are: - -* A pointer to the arena -* The type to allocate -* (optional) the number of items to allocate -* (optional) flags: - * `ALLOC_NOZERO`: by default the arena sets the memory to zero before returning, use this if you want to skip it - * `ALLOC_SOFT_FAIL`: by default the arena panics when an allocation fails, meaning that it never returns `NULL`. -* (automatic) the align of the type -* (automatic) the size of the type - -Example usage: - -```c -// allocate 15 strings -str_t *string_list = alloc(arena, str_t, 15); -// allocate a 1024 bytes buffer -uint8 *buffer = alloc(arena, uint8, 1024); -// allocate a structure -game_t *game = alloc(arena, game_t); -``` - -The strength of the arena is that it makes it much easier to reason about lifetimes, for instance: - -```c -// pass a pointer to the arena for the data that we need to return -WCHAR *win32_get_full_path(arena_t *arena, const char *rel_path) { - // we need a small scratch arena for allocations that - // will be thrown away at the end of this function - uint8 scratch_buf[1024]; - arena_t scratch = arenaMake(ARENA_STATIC, sizeof(scratch_buf, scratch_buf)); - - WCHAR *win32_rel_path = str_to_wchar(&scratch, rel_path); - - DWORD pathlen = GetFullPathName(win32_rel_path, 0, NULL, NULL); - WCHAR *fullpath = alloc(arena, WCHAR, pathlen + 1); - GetFullPath(win32_rel_path, pathlen + 1, fullpath, NULL); - - // notice how we don't need to free anything at the end - return fullpath; -} -``` - -There are a few helper functions: - -* `arenaScratch`: sub allocate an arena from another arena -* `arenaTell`: returns the number of bytes allocated -* `arenaRemaining`: returns the number of bytes that can still be allocated -* `arenaRewind`: rewinds the arena to N bytes from the start (effectively "freeing" that memory) -* `arenaPop`: pops N bytes from the arena (effectively "freeing" that memory) - -Here is an example usage of a full program: - -```c -typedef struct { - char *buf; - usize len; -} str_t; - -str_t read_file(arena_t *arena, const char *filename) { - str_t out = {0}; - - FILE *fp = fopen(filename, "rb"); - if (!fp) goto error; - - fseek(fp, 0, SEEK_END); - out.len = ftell(fp); - fseek(fp, 0, SEEK_SET); - - out.buf = alloc(arena, char, out.len + 1); - - fread(out.buf, 1, out.len, fp); - -error: - return out; -} - -void write_file(const char *filename, str_t data) { - FILE *fp = fopen(filename, "wb"); - if (!fp) return; - fwrite(data.buf, 1, data.len, fp); -} - -int main() { - arena_t arena = arenaMake(ARENA_VIRTUAL, GB(1)); - str_t title = {0}; - - { - uint8 tmpbuf[KB(5)]; - arena_t scratch = arenaMake(ARENA_STATIC, sizeof(tmpbuf), tmpbuf); - str_t file = read_file(&scratch, "file.json"); - json_t json = json_parse(&scratch, file); - title = str_dup(arena, json_get(json, "title")); - } - - { - // copying an arena by value effectively makes it a scratch arena, - // as long as you don't use the original inside the same scope! - arena_t scratch = arena; - str_t to_write = str_fmt( - &scratch, - "{ \"title\": \"%s\" }", title.buf - ); - write_file("out.json", to_write); - } - - // cleanup all allocations at once - arenaCleanup(&arena); -} -``` \ No newline at end of file diff --git a/docs/base64.md b/docs/base64.md deleted file mode 100644 index e238648..0000000 --- a/docs/base64.md +++ /dev/null @@ -1,29 +0,0 @@ -# Base 64 ----------- - -The `base64.h` header has only two functions, one to encode and one to decode to/from base 64. - -Example usage: -```c -char *recv_callback(arena_t *arena, buffer_t msg) { - buffer_t decoded = base64Decode(arena, msg); - alloc(arena, char); // allocate an extra char for the null pointer - return (char *)decoded.data; -} - -buffer_t send_callback(arena_t *arena) { - char msg[] = "Hello World!"; - buffer_t buf = { - .data = msg, - .len = arrlen(msg) - 1, // don't include the null pointer - }; - buffer_t encoded = base64Encode(arena, buf); - return encoded; -} - -int main() { - arena_t arena = arenaMake(ARENA_VIRTUAL, GB(1)); - run_server(&arena, 8080, recv_callback, send_callback); - arenaCleanup(&arena); -} -``` diff --git a/docs/cthreads.md b/docs/cthreads.md deleted file mode 100644 index fee03d9..0000000 --- a/docs/cthreads.md +++ /dev/null @@ -1,46 +0,0 @@ -# Threads ----------- - -Cross platform threads, mutexes and conditional variables. -The api is very similar to pthreads, here is a small example program that uses threads and mutexes. - -```c -struct { - bool exit; - cmutex_t mtx; -} state = {0}; - -int f1(void *) { - mtxLock(state.mtx); - state.exit = true; - mtxUnlock(state.mtx); - return 0; -} - -int f2(void *) { - while (true) { - bool exit = false; - if (mtxTryLock(state.mtx)) { - exit = state.exit; - mtxUnlock(state.mtx); - } - - if (exit) { - break; - } - } - return 0; -} - -int main() { - state.mtx = mtxInit(); - - cthread_t t1 = thrCreate(f1, NULL); - thrDetach(t1); - - cthread_t t2 = thrCreate(f2, NULL); - thrJoin(t2, NULL); - - mtxFree(state.mtx); -} -``` \ No newline at end of file diff --git a/docs/dir.md b/docs/dir.md deleted file mode 100644 index ea481da..0000000 --- a/docs/dir.md +++ /dev/null @@ -1,49 +0,0 @@ -# Dir ----------- - -This header provides a simple directory walker, here is an example usage: - -```c -typedef struct source_t { - str_t filename; - struct source_t *next; -} source_t; - -sources_t get_sources(arena_t *arena, strview_t path) { - uint8 tmpbuf[KB(5)] = {0}; - arena_t scratch = arenaMake(ARENA_STATIC, sizeof(tmpbuf), tmpbuf); - - dir_t *dir = dirOpen(&scratch, path); - dir_entry_t *entry = NULL; - - source_t *sources = NULL; - - while ((entry = dirNext(&scratch, dir))) { - if (entry->type != DIRTYPE_FILE) { - continue; - } - - strview_t ext = fileGetExtension(strv(entry->name)); - if (!strvEquals(ext, strv(".c"))) { - continue; - } - - source_t *new_source = alloc(arena, source_t); - new_source->filename = strFmt(arena, "%v/%v", path, entry->name); - new_source->next = sources; - sources = new_source; - } - - return sources; -} - -int main() { - arena_t arena = arenaMake(ARENA_VIRTUAL, GB(1)); - source_t *sources = get_sources(&arena, strv("src/colla")); - while (sources) { - info("> %v", sources->filename); - sources = sources->next; - } - arenaCleanup(&arena); -} -``` \ No newline at end of file diff --git a/docs/docs.com b/docs/docs.com deleted file mode 100644 index 3bdaaf4..0000000 Binary files a/docs/docs.com and /dev/null differ diff --git a/docs/file.md b/docs/file.md deleted file mode 100644 index 79abefa..0000000 --- a/docs/file.md +++ /dev/null @@ -1,38 +0,0 @@ -# File ----------- - -This header provides cross platform file functionality. -It has all the basics that you can expect which work exactly like the stdio counterparts: - -* `fileOpen` -* `fileClose` -* `fileIsValid` -* `fileRead` -* `fileWrite` -* `fileSeekEnd` -* `fileRewind` -* `fileTell` - -Then there are a few helpers functions for reading / writing: - -* Writing: - * `filePutc` - * `filePuts` - * `filePrintf` - * `fileWriteWhole` -* Reading: - * `fileReadWhole` - * `fileReadWholeStr` - -There are also some functions to get info about a file without having to open it: - -* `fileExists` -* `fileSize` -* `fileGetTime` -* `fileHasChanged` - -And finally, there are some helper functions: - -* `fileGetFullPath` (for windows) -* `fileSplitPath` / `fileGetFilename` / `fileGetExtension` -* `fileDelete` diff --git a/docs/format.md b/docs/format.md deleted file mode 100644 index 2afd810..0000000 --- a/docs/format.md +++ /dev/null @@ -1,25 +0,0 @@ -# Format ----------- - -Small formatting utility, it has 2 functions (and the `va_list` alternatives): - -* `fmtPrint`: equivalent to -* `fmtBuffer` - -It uses [stb_sprintf](https://github.com/nothings/stb/blob/master/stb_sprintf.h) under the hood, but it also has support for printing buffers using `%v` (structures that are a pair of pointer/size) - -This means it can print strings and [string views](/str). - -In - -Usage example: - -```c -int main() { - strview_t v = strv("world"); - char buffer[1024] = {0}; - - fmtPrint("Hello %v!", v); - fmtBuffer(buffer, sizeof(buffer), "Hello %v!", v); -} -``` \ No newline at end of file diff --git a/docs/highlight.md b/docs/highlight.md deleted file mode 100644 index 42edc70..0000000 --- a/docs/highlight.md +++ /dev/null @@ -1,34 +0,0 @@ -# Highlight ----------- - -This header provides an highlighter for c-like languages (mostly c). -The usage is simple, first create a highlight context using hlInit, for which you need an hl_config_t. The only mandatory argument is colors, which are the strings put before the keywords in the highlighted text. - -You can also pass some flags: -* `HL_FLAG_HTML`: escapes html characters - -If you're using the offline documentation, this is the code used to highlight inside the markdown code blocks (simplified to remove actual markdown parsing): - -```c -str_t highlight_code_block(arena_t *arena, strview_t code) { - uint8 tmpbuf[KB(5)]; - arena_t scratch = arenaMake(ARENA_STATIC, sizeof(tmpbuf), tmpbuf); - - hl_ctx_t *hl = hlInit(&scratch, &(hl_config_t){ - .colors = { - [HL_COLOR_NORMAL] = strv(""), - [HL_COLOR_PREPROC] = strv(""), - [HL_COLOR_TYPES] = strv(""), - [HL_COLOR_CUSTOM_TYPES] = strv(""), - [HL_COLOR_KEYWORDS] = strv(""), - [HL_COLOR_NUMBER] = strv(""), - [HL_COLOR_STRING] = strv(""), - [HL_COLOR_COMMENT] = strv(""), - [HL_COLOR_FUNC] = strv(""), - [HL_COLOR_SYMBOL] = strv(""), - [HL_COLOR_MACRO] = strv(""), - }, - .flags = HL_FLAG_HTML, - }); -} -``` \ No newline at end of file diff --git a/docs/hot_reload.md b/docs/hot_reload.md deleted file mode 100644 index 65a59b5..0000000 --- a/docs/hot_reload.md +++ /dev/null @@ -1,76 +0,0 @@ -# Hot Reload ----------- - -This header provides cross-platform library hot-reloading. To use it you need to have to entry points, one for the host and one for the client. - -In the client you can then implement these functions: - -* `hr_init`: called when the library is loaded (or reloaded) -* `hr_loop`: called every "tick" (or whenever the host decides) -* `hr_close`: called when the host finishes - -In the client you need to call these functions: - -* `hrOpen`: load the library and call `hr_init` -* `hrStep`: call `hr_loop` -* `hrReload`: check if the library has changed, and if so reload it and call `hr_init` again -* `hrClose`: call `hr_close` and cleanup - -Example usage: - -### Client - -```c -int hr_init(hr_t *ctx) { - uint8 tmpbuf[KB(5)]; - arena_t scratch = arenaMake(ARENA_STATIC, sizeof(tmpbuf), tmpbuf); - - state_t *state = ctx->userdata; - uint64 timestamp = fileGetTime(scratch, strv("sprite.png")); - if (timestamp > state->last_write) { - state->last_write = timestamp; - destroy_image(state->sprite); - state->sprite = load_image(strv("sprite.png")); - } -} - -int hr_loop(hr_t *ctx) { - state_t *state = ctx->userdata; - draw_image(state->sprite, state->posx, state->posy); -} - -int hr_close(hr_t *ctx) { - state_t *state = ctx->userdata; - destroy_image(state->sprite); -} -``` - -### Host - -```c -typedef struct { - hr_t hr; - image_t sprite; - int posx; - int posy; - uint64 last_write; -} state_t; - -int main() { - arena_t arena = arenaMake(ARENA_VIRTUAL, GB(1)); - - state_t state = {0}; - state.hr.userdata = &state; - - if (!hrOpen(&state.hr, strv("bin/client.dll"))) { - return 1; - } - - while (game_poll()) { - hrReload(&state.hr); - hrStep(&state.hr); - } - - hrClose(&state.hr, true); -} -``` \ No newline at end of file diff --git a/docs/html.md b/docs/html.md deleted file mode 100644 index 873736d..0000000 --- a/docs/html.md +++ /dev/null @@ -1,42 +0,0 @@ -# HTML ----------- - -This header provides an easy (although debatably sane) way to generate html in c. - -There are three types of tags: -* One and done tags, like `` or `
` which only have an opening tag -* Basic tags which follow this format: `content` -* Tags where the content is probably other tags - -You can open and close any tags using `tagBeg` and `tagEnd`, you can also set attributes like this: - -```c -tagBeg("div", .class="content", .id="main"); -``` - -Finally, you can use the `htmlRaw` macro to write raw html. - -Example code: -```c -str_t generate_page(arena_t *arena, page_t *data) { - str_t out = STR_EMPTY; - - htmlBeg(arena, &out); - headBeg(); - title(data->title); - htmlRaw() - style(data->css); - headEnd(); - bodyBeg(); - divBeg(.id="main"); - h1("Hello World!"); - hr(); - p("Some content blah blah"); - img(.src="image.png"); - divEnd(); - bodyEnd(); - htmlEnd(); - - return out; -} -``` \ No newline at end of file diff --git a/docs/http.md b/docs/http.md deleted file mode 100644 index 95f751c..0000000 --- a/docs/http.md +++ /dev/null @@ -1,2 +0,0 @@ -# HTTP ----------- \ No newline at end of file diff --git a/docs/ini.md b/docs/ini.md deleted file mode 100644 index 62590b6..0000000 --- a/docs/ini.md +++ /dev/null @@ -1,2 +0,0 @@ -# Ini ----------- \ No newline at end of file diff --git a/docs/json.md b/docs/json.md deleted file mode 100644 index e529cd9..0000000 --- a/docs/json.md +++ /dev/null @@ -1,2 +0,0 @@ -# Json ----------- \ No newline at end of file diff --git a/docs/markdown.md b/docs/markdown.md deleted file mode 100644 index f65eab2..0000000 --- a/docs/markdown.md +++ /dev/null @@ -1,2 +0,0 @@ -# Markdown ----------- \ No newline at end of file diff --git a/docs/readme.md b/docs/readme.md deleted file mode 100644 index ce9fbcc..0000000 --- a/docs/readme.md +++ /dev/null @@ -1,4 +0,0 @@ -# Colla ----------- - -Colla is a library that I use personally for all my C projects. It doesn't have one specific purpose, but more a collection of useful things that I've written. \ No newline at end of file diff --git a/docs/server.md b/docs/server.md deleted file mode 100644 index 61d57d2..0000000 --- a/docs/server.md +++ /dev/null @@ -1,2 +0,0 @@ -# Server ----------- \ No newline at end of file diff --git a/docs/sha1.md b/docs/sha1.md deleted file mode 100644 index f9f2ba1..0000000 --- a/docs/sha1.md +++ /dev/null @@ -1,2 +0,0 @@ -# SHA-1 ----------- \ No newline at end of file diff --git a/docs/socket.md b/docs/socket.md deleted file mode 100644 index 6188d75..0000000 --- a/docs/socket.md +++ /dev/null @@ -1,2 +0,0 @@ -# Socket ----------- \ No newline at end of file diff --git a/docs/str.md b/docs/str.md deleted file mode 100644 index 375e85d..0000000 --- a/docs/str.md +++ /dev/null @@ -1,2 +0,0 @@ -# Str ----------- \ No newline at end of file diff --git a/docs/strstream.md b/docs/strstream.md deleted file mode 100644 index e619e1d..0000000 --- a/docs/strstream.md +++ /dev/null @@ -1,2 +0,0 @@ -# StrStream ----------- \ No newline at end of file diff --git a/docs/tracelog.md b/docs/tracelog.md deleted file mode 100644 index 0d12d1f..0000000 --- a/docs/tracelog.md +++ /dev/null @@ -1,2 +0,0 @@ -# Tracelog ----------- \ No newline at end of file diff --git a/docs/utf8.md b/docs/utf8.md deleted file mode 100644 index 5a15b43..0000000 --- a/docs/utf8.md +++ /dev/null @@ -1,2 +0,0 @@ -# UTF-8 ----------- \ No newline at end of file diff --git a/docs/vec.md b/docs/vec.md deleted file mode 100644 index 7500a52..0000000 --- a/docs/vec.md +++ /dev/null @@ -1,2 +0,0 @@ -# Vec ----------- \ No newline at end of file diff --git a/docs/vmem.md b/docs/vmem.md deleted file mode 100644 index 694c7e2..0000000 --- a/docs/vmem.md +++ /dev/null @@ -1,2 +0,0 @@ -# VMem ----------- \ No newline at end of file diff --git a/docs/websocket.md b/docs/websocket.md deleted file mode 100644 index f590eb5..0000000 --- a/docs/websocket.md +++ /dev/null @@ -1,2 +0,0 @@ -# WebSocket ----------- \ No newline at end of file diff --git a/docs/xml.md b/docs/xml.md deleted file mode 100644 index 493d0f9..0000000 --- a/docs/xml.md +++ /dev/null @@ -1,2 +0,0 @@ -# Xml ----------- \ No newline at end of file diff --git a/file.c b/file.c deleted file mode 100644 index c24fb14..0000000 --- a/file.c +++ /dev/null @@ -1,398 +0,0 @@ -#include "file.h" - -#include "warnings/colla_warn_beg.h" - -#include "tracelog.h" -#include "format.h" - -#define FILE_MAKE_SCRATCH() \ - uint8 tmpbuf[KB(1)]; \ - arena_t scratch = arenaMake(ARENA_STATIC, sizeof(tmpbuf), tmpbuf) - -#if COLLA_WIN - -#include - -#undef FILE_BEGIN -#undef FILE_CURRENT -#undef FILE_END - -#define FILE_BEGIN 0 -#define FILE_CURRENT 1 -#define FILE_END 2 - -#if COLLA_TCC -#include "tcc/colla_tcc.h" -#endif - -static DWORD file__mode_to_access(filemode_e mode) { - if (mode & FILE_APPEND) return FILE_APPEND_DATA; - - DWORD out = 0; - if (mode & FILE_READ) out |= GENERIC_READ; - if (mode & FILE_WRITE) out |= GENERIC_WRITE; - return out; -} - -static DWORD file__mode_to_creation(filemode_e mode) { - if (mode == FILE_READ) return OPEN_EXISTING; - if (mode == FILE_WRITE) return CREATE_ALWAYS; - return OPEN_ALWAYS; -} - -bool fileExists(strview_t path) { - FILE_MAKE_SCRATCH(); - str_t name = str(&scratch, path); - return GetFileAttributesA(name.buf) != INVALID_FILE_ATTRIBUTES; -} - -TCHAR *fileGetFullPath(arena_t *arena, strview_t filename) { - FILE_MAKE_SCRATCH(); - - TCHAR long_path_prefix[] = TEXT("\\\\?\\"); - const usize prefix_len = arrlen(long_path_prefix) - 1; - - TCHAR *rel_path = strvToTChar(&scratch, filename); - DWORD pathlen = GetFullPathName(rel_path, 0, NULL, NULL); - - TCHAR *full_path = alloc(arena, TCHAR, pathlen + prefix_len + 1); - memcpy(full_path, long_path_prefix, prefix_len * sizeof(TCHAR)); - - GetFullPathName(rel_path, pathlen + 1, full_path + prefix_len, NULL); - - return full_path; -} - -bool fileDelete(strview_t filename) { - FILE_MAKE_SCRATCH(); - wchar_t *wfname = strvToWChar(&scratch, filename, NULL); - return DeleteFileW(wfname); -} - -file_t fileOpen(strview_t name, filemode_e mode) { - FILE_MAKE_SCRATCH(); - - TCHAR *full_path = fileGetFullPath(&scratch, name); - - HANDLE handle = CreateFile( - full_path, - file__mode_to_access(mode), - FILE_SHARE_READ, - NULL, - file__mode_to_creation(mode), - FILE_ATTRIBUTE_NORMAL, - NULL - ); - - return (file_t){ - .handle = (uintptr_t)handle, - }; -} - -void fileClose(file_t ctx) { - if (!fileIsValid(ctx)) return; - CloseHandle((HANDLE)ctx.handle); -} - -bool fileIsValid(file_t ctx) { - return (HANDLE)ctx.handle != 0 && - (HANDLE)ctx.handle != INVALID_HANDLE_VALUE; -} - -usize fileRead(file_t ctx, void *buf, usize len) { - if (!fileIsValid(ctx)) return 0; - DWORD read = 0; - ReadFile((HANDLE)ctx.handle, buf, (DWORD)len, &read, NULL); - return (usize)read; -} - -usize fileWrite(file_t ctx, const void *buf, usize len) { - if (!fileIsValid(ctx)) return 0; - DWORD written = 0; - WriteFile((HANDLE)ctx.handle, buf, (DWORD)len, &written, NULL); - return (usize)written; -} - -bool fileSeek(file_t ctx, usize pos) { - if (!fileIsValid(ctx)) return false; - LARGE_INTEGER offset = { - .QuadPart = pos, - }; - DWORD result = SetFilePointer((HANDLE)ctx.handle, offset.LowPart, &offset.HighPart, FILE_BEGIN); - return result != INVALID_SET_FILE_POINTER; -} - -bool fileSeekEnd(file_t ctx) { - if (!fileIsValid(ctx)) return false; - DWORD result = SetFilePointer((HANDLE)ctx.handle, 0, NULL, FILE_END); - return result != INVALID_SET_FILE_POINTER; -} - -void fileRewind(file_t ctx) { - if (!fileIsValid(ctx)) return; - SetFilePointer((HANDLE)ctx.handle, 0, NULL, FILE_BEGIN); -} - -usize fileTell(file_t ctx) { - if (!fileIsValid(ctx)) return 0; - LARGE_INTEGER tell = {0}; - BOOL result = SetFilePointerEx((HANDLE)ctx.handle, (LARGE_INTEGER){0}, &tell, FILE_CURRENT); - return result == TRUE ? (usize)tell.QuadPart : 0; -} - -usize fileSize(file_t ctx) { - if (!fileIsValid(ctx)) return 0; - LARGE_INTEGER size = {0}; - BOOL result = GetFileSizeEx((HANDLE)ctx.handle, &size); - return result == TRUE ? (usize)size.QuadPart : 0; -} - -uint64 fileGetTimeFP(file_t ctx) { - if (!fileIsValid(ctx)) return 0; - FILETIME time = {0}; - GetFileTime((HANDLE)ctx.handle, NULL, NULL, &time); - ULARGE_INTEGER utime = { - .HighPart = time.dwHighDateTime, - .LowPart = time.dwLowDateTime, - }; - return (uint64)utime.QuadPart; -} - -#else - -#include - -static const char *file__mode_to_stdio(filemode_e mode) { - if (mode == FILE_READ) return "rb"; - if (mode == FILE_WRITE) return "wb"; - if (mode == FILE_APPEND) return "ab"; - if (mode == (FILE_READ | FILE_WRITE)) return "rb+"; - - return "ab+"; -} - -bool fileExists(strview_t path) { - FILE_MAKE_SCRATCH(); - str_t name = str(&scratch, path); - - FILE *fp = fopen(name.buf, "rb"); - bool exists = fp != NULL; - fclose(fp); - - return exists; -} - -bool fileDelete(strview_t filename) { - FILE_MAKE_SCRATCH(); - str_t name = str(&scratch, filename); - return remove(name.buf) == 0; -} - -file_t fileOpen(strview_t name, filemode_e mode) { - FILE_MAKE_SCRATCH(); - str_t filename = str(&scratch, name); - return (file_t) { - .handle = (uintptr_t)fopen(filename.buf, file__mode_to_stdio(mode)) - }; -} - -void fileClose(file_t ctx) { - FILE *fp = (FILE *)ctx.handle; - if (fp) { - fclose(fp); - } -} - -bool fileIsValid(file_t ctx) { - bool is_valid = (FILE *)ctx.handle != NULL; - if (!is_valid) warn("file not valid"); - return is_valid; -} - -usize fileRead(file_t ctx, void *buf, usize len) { - if (!fileIsValid(ctx)) return 0; - return fread(buf, 1, len, (FILE *)ctx.handle); -} - -usize fileWrite(file_t ctx, const void *buf, usize len) { - if (!fileIsValid(ctx)) return 0; - return fwrite(buf, 1, len, (FILE *)ctx.handle); -} - -bool fileSeek(file_t ctx, usize pos) { - assert(pos < INT32_MAX); - if (!fileIsValid(ctx)) return false; - return fseek((FILE *)ctx.handle, (long)pos, SEEK_SET) == 0; -} - -bool fileSeekEnd(file_t ctx) { - if (!fileIsValid(ctx)) return false; - return fseek((FILE *)ctx.handle, 0, SEEK_END) == 0; -} - -void fileRewind(file_t ctx) { - if (!fileIsValid(ctx)) return; - fseek((FILE *)ctx.handle, 0, SEEK_SET); -} - -usize fileTell(file_t ctx) { - if (!fileIsValid(ctx)) return 0; - return ftell((FILE *)ctx.handle); -} - -usize fileSize(file_t ctx) { - if (!fileIsValid(ctx)) return 0; - FILE *fp = (FILE *)ctx.handle; - fseek(fp, 0, SEEK_END); - long len = ftell(fp); - fseek(fp, 0, SEEK_SET); - return (usize)len; -} - -uint64 fileGetTimeFP(file_t ctx) { -#if COLLA_LIN - return 0; -#else - fatal("fileGetTime not implemented yet outside of linux and windows"); - return 0; -#endif -} - -#endif - -strview_t fileGetFilename(strview_t path) { - usize last_lin = strvRFind(path, '/', 0); - usize last_win = strvRFind(path, '\\', 0); - last_lin = last_lin != SIZE_MAX ? last_lin : 0; - last_win = last_win != SIZE_MAX ? last_win : 0; - usize last = max(last_lin, last_win); - return strvSub(path, last ? last + 1 : last, SIZE_MAX); -} - -strview_t fileGetExtension(strview_t path) { - usize ext_pos = strvRFind(path, '.', 0); - return strvSub(path, ext_pos, SIZE_MAX); -} - -void fileSplitPath(strview_t path, strview_t *dir, strview_t *name, strview_t *ext) { - usize dir_lin = strvRFind(path, '/', 0); - usize dir_win = strvRFind(path, '\\', 0); - dir_lin = dir_lin != STR_NONE ? dir_lin : 0; - dir_win = dir_win != STR_NONE ? dir_win : 0; - usize dir_pos = max(dir_lin, dir_win); - - usize ext_pos = strvRFind(path, '.', 0); - - if (dir) { - *dir = strvSub(path, 0, dir_pos); - } - if (name) { - *name = strvSub(path, dir_pos ? dir_pos + 1 : dir_pos, ext_pos); - } - if (ext) { - *ext = strvSub(path, ext_pos, SIZE_MAX); - } -} - -bool filePutc(file_t ctx, char c) { - return fileWrite(ctx, &c, 1) == 1; -} - -bool filePuts(file_t ctx, strview_t v) { - return fileWrite(ctx, v.buf, v.len) == v.len; -} - -bool filePrintf(arena_t scratch, file_t ctx, const char *fmt, ...) { - va_list args; - va_start(args, fmt); - bool result = filePrintfv(scratch, ctx, fmt, args); - va_end(args); - return result; -} - -bool filePrintfv(arena_t scratch, file_t ctx, const char *fmt, va_list args) { - str_t string = strFmtv(&scratch, fmt, args); - return fileWrite(ctx, string.buf, string.len) == string.len; -} - -buffer_t fileReadWhole(arena_t *arena, strview_t name) { - file_t fp = fileOpen(name, FILE_READ); - if (!fileIsValid(fp)) { - err("could not open file: %v", name); - return (buffer_t){0}; - } - buffer_t out = fileReadWholeFP(arena, fp); - fileClose(fp); - return out; -} - -buffer_t fileReadWholeFP(arena_t *arena, file_t ctx) { - if (!fileIsValid(ctx)) return (buffer_t){0}; - buffer_t out = {0}; - - out.len = fileSize(ctx); - out.data = alloc(arena, uint8, out.len); - usize read = fileRead(ctx, out.data, out.len); - - if (read != out.len) { - err("fileReadWholeFP: fileRead failed, should be %zu but is %zu", out.len, read); - arenaPop(arena, out.len); - return (buffer_t){0}; - } - - return out; -} - -str_t fileReadWholeStr(arena_t *arena, strview_t name) { - file_t fp = fileOpen(name, FILE_READ); - if (!fileIsValid(fp)) { - warn("could not open file (%v)", name); - } - str_t out = fileReadWholeStrFP(arena, fp); - fileClose(fp); - return out; -} - -str_t fileReadWholeStrFP(arena_t *arena, file_t ctx) { - if (!fileIsValid(ctx)) return STR_EMPTY; - - str_t out = {0}; - - out.len = fileSize(ctx); - out.buf = alloc(arena, uint8, out.len + 1); - usize read = fileRead(ctx, out.buf, out.len); - - if (read != out.len) { - err("fileReadWholeStrFP: fileRead failed, should be %zu but is %zu", out.len, read); - arenaPop(arena, out.len + 1); - return STR_EMPTY; - } - - return out; -} - -bool fileWriteWhole(strview_t name, const void *buf, usize len) { - file_t fp = fileOpen(name, FILE_WRITE); - if (!fileIsValid(fp)) { - return false; - } - usize written = fileWrite(fp, buf, len); - fileClose(fp); - return written == len; -} - -uint64 fileGetTime(strview_t name) { - file_t fp = fileOpen(name, FILE_READ); - uint64 result = fileGetTimeFP(fp); - fileClose(fp); - return result; -} - -bool fileHasChanged(strview_t name, uint64 last_timestamp) { - uint64 timestamp = fileGetTime(name); - return timestamp > last_timestamp; -} - -#include "warnings/colla_warn_end.h" - -#undef FILE_MAKE_SCRATCH \ No newline at end of file diff --git a/file.h b/file.h deleted file mode 100644 index 088f656..0000000 --- a/file.h +++ /dev/null @@ -1,56 +0,0 @@ -#pragma once - -#include - -#include "collatypes.h" -#include "str.h" -#include "arena.h" - -typedef enum { - FILE_READ = 1 << 0, - FILE_WRITE = 1 << 1, - FILE_APPEND = 1 << 2, -} filemode_e; - -typedef struct { - uintptr_t handle; -} file_t; - -bool fileExists(strview_t path); -TCHAR *fileGetFullPath(arena_t *arena, strview_t filename); -strview_t fileGetFilename(strview_t path); -strview_t fileGetExtension(strview_t path); -void fileSplitPath(strview_t path, strview_t *dir, strview_t *name, strview_t *ext); -bool fileDelete(strview_t filename); - -file_t fileOpen(strview_t name, filemode_e mode); -void fileClose(file_t ctx); - -bool fileIsValid(file_t ctx); - -bool filePutc(file_t ctx, char c); -bool filePuts(file_t ctx, strview_t v); -bool filePrintf(arena_t scratch, file_t ctx, const char *fmt, ...); -bool filePrintfv(arena_t scratch, file_t ctx, const char *fmt, va_list args); - -usize fileRead(file_t ctx, void *buf, usize len); -usize fileWrite(file_t ctx, const void *buf, usize len); - -bool fileSeek(file_t ctx, usize pos); -bool fileSeekEnd(file_t ctx); -void fileRewind(file_t ctx); - -usize fileTell(file_t ctx); -usize fileSize(file_t ctx); - -buffer_t fileReadWhole(arena_t *arena, strview_t name); -buffer_t fileReadWholeFP(arena_t *arena, file_t ctx); - -str_t fileReadWholeStr(arena_t *arena, strview_t name); -str_t fileReadWholeStrFP(arena_t *arena, file_t ctx); - -bool fileWriteWhole(strview_t name, const void *buf, usize len); - -uint64 fileGetTime(strview_t name); -uint64 fileGetTimeFP(file_t ctx); -bool fileHasChanged(strview_t name, uint64 last_timestamp); \ No newline at end of file diff --git a/format.c b/format.c deleted file mode 100644 index 7ed9d21..0000000 --- a/format.c +++ /dev/null @@ -1,61 +0,0 @@ -#include "format.h" - -#define STB_SPRINTF_DECORATE(name) stb_##name -#define STB_SPRINTF_NOUNALIGNED -#define STB_SPRINTF_IMPLEMENTATION -#include "stb/stb_sprintf.h" - -#include "arena.h" - -static char *fmt__stb_callback(const char *buf, void *ud, int len) { - (void)len; - printf("%.*s", len, buf); - return (char *)ud; -} - -int fmtPrint(const char *fmt, ...) { - va_list args; - va_start(args, fmt); - int out = fmtPrintv(fmt, args); - va_end(args); - return out; -} - -int fmtPrintv(const char *fmt, va_list args) { - char buffer[STB_SPRINTF_MIN] = {0}; - return stb_vsprintfcb(fmt__stb_callback, buffer, buffer, fmt, args); -} - -int fmtBuffer(char *buffer, usize buflen, const char *fmt, ...) { - va_list args; - va_start(args, fmt); - int out = fmtBufferv(buffer, buflen, fmt, args); - va_end(args); - return out; -} - -int fmtBufferv(char *buffer, usize buflen, const char *fmt, va_list args) { - return stb_vsnprintf(buffer, (int)buflen, fmt, args); -} - -#if 0 -str_t fmtStr(Arena *arena, const char *fmt, ...) { - va_list args; - va_start(args, fmt); - str_t out = fmtStrv(arena, fmt, args); - va_end(args); - return out; -} - -str_t fmtStrv(Arena *arena, const char *fmt, va_list args) { - va_list vcopy; - va_copy(vcopy, args); - int len = stb_vsnprintf(NULL, 0, fmt, vcopy); - va_end(vcopy); - - char *buffer = alloc(arena, char, len + 1); - stb_vsnprintf(buffer, len + 1, fmt, args); - - return (str_t){ .buf = buffer, .len = (usize)len }; -} -#endif \ No newline at end of file diff --git a/format.h b/format.h deleted file mode 100644 index 4d566ad..0000000 --- a/format.h +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -#include - -#include "collatypes.h" - -typedef struct arena_t arena_t; - -int fmtPrint(const char *fmt, ...); -int fmtPrintv(const char *fmt, va_list args); - -int fmtBuffer(char *buffer, usize buflen, const char *fmt, ...); -int fmtBufferv(char *buffer, usize buflen, const char *fmt, va_list args); diff --git a/highlight.c b/highlight.c deleted file mode 100644 index 466f242..0000000 --- a/highlight.c +++ /dev/null @@ -1,621 +0,0 @@ -#include "highlight.h" - -// based on https://github.com/Theldus/kat - -#include - -#include "arena.h" -#include "tracelog.h" -#include "strstream.h" - -typedef enum { - HL_STATE_DEFAULT, - HL_STATE_KEYWORD, - HL_STATE_NUMBER, - HL_STATE_CHAR, - HL_STATE_STRING, - HL_STATE_COMMENT_MULTI, - HL_STATE_PREPROCESSOR, - HL_STATE_PREPROCESSOR_INCLUDE, - HL_STATE_PREPROCESSOR_INCLUDE_STRING, -} hl_state_e; - -typedef enum { - HL_HTABLE_FAILED, - HL_HTABLE_REPLACED, - HL_HTABLE_ADDED, -} hl_htable_result_e; - -typedef struct hl_node_t { - strview_t key; - hl_color_e value; - struct hl_node_t *next; -} hl_node_t; - -typedef struct { - hl_node_t **buckets; - uint count; - uint used; - uint collisions; -} hl_hashtable_t; - -static hl_hashtable_t hl_htable_init(arena_t *arena, uint pow2_exp); -static hl_htable_result_e hl_htable_add(arena_t *arena, hl_hashtable_t *table, strview_t key, hl_color_e value); -static hl_node_t *hl_htable_get(hl_hashtable_t *table, strview_t key); -static uint64 hl_htable_hash(const void *bytes, usize count); - -typedef struct hl_ctx_t { - hl_state_e state; - hl_flags_e flags; - usize kw_beg; - strview_t colors[HL_COLOR__COUNT]; // todo: maybe should be str_t? - outstream_t ostr; - hl_hashtable_t kw_htable; - bool symbol_table[256]; -} hl_ctx_t; - -#define KW(str, col) { { str, sizeof(str)-1 }, HL_COLOR_##col } - -static hl_keyword_t hl_default_kwrds[] = { - /* C Types. */ - KW("double", TYPES), - KW("int", TYPES), - KW("long", TYPES), - KW("char", TYPES), - KW("float", TYPES), - KW("short", TYPES), - KW("unsigned", TYPES), - KW("signed", TYPES), - KW("bool", TYPES), - - /* Common typedefs. */ - KW("int8", TYPES), KW("uint8", TYPES), - KW("int16", TYPES), KW("uint16", TYPES), - KW("int32", TYPES), KW("uint32", TYPES), - KW("int64", TYPES), KW("uint64", TYPES), - - /* Colla keywords */ - KW("uchar", TYPES), - KW("ushort", TYPES), - KW("uint", TYPES), - KW("usize", TYPES), - KW("isize", TYPES), - KW("byte", TYPES), - - /* Other keywords. */ - KW("auto", KEYWORDS), KW("struct", KEYWORDS), KW("break", KEYWORDS), - KW("else", KEYWORDS), KW("switch", KEYWORDS), KW("case", KEYWORDS), - KW("enum", KEYWORDS), KW("register", KEYWORDS), KW("typedef", KEYWORDS), - KW("extern", KEYWORDS), KW("return", KEYWORDS), KW("union", KEYWORDS), - KW("const", KEYWORDS), KW("continue", KEYWORDS), KW("for", KEYWORDS), - KW("void", KEYWORDS), KW("default", KEYWORDS), KW("goto", KEYWORDS), - KW("sizeof", KEYWORDS), KW("volatile", KEYWORDS), KW("do", KEYWORDS), - KW("if", KEYWORDS), KW("static", KEYWORDS), KW("inline", KEYWORDS), - KW("while", KEYWORDS), -}; - -#undef KW - -static bool hl_default_symbols_table[256] = { - ['['] = true, [']'] = true, ['('] = true, - [')'] = true, ['{'] = true, ['}'] = true, - ['*'] = true, [':'] = true, ['='] = true, - [';'] = true, ['-'] = true, ['>'] = true, - ['&'] = true, ['+'] = true, ['~'] = true, - ['!'] = true, ['/'] = true, ['%'] = true, - ['<'] = true, ['^'] = true, ['|'] = true, - ['?'] = true, ['#'] = true, -}; - -static void hl_write_char(hl_ctx_t *ctx, char c); -static void hl_write(hl_ctx_t *ctx, strview_t v); -static bool hl_is_char_keyword(char c); -static bool hl_highlight_symbol(hl_ctx_t *ctx, char c); -static hl_color_e hl_get_keyword_color(hl_ctx_t *ctx, strview_t keyword); -static bool hl_is_capitalised(strview_t string); -static strview_t hl_finish_keyword(hl_ctx_t *ctx, usize beg, instream_t *in); -static void hl_print_keyword(hl_ctx_t *ctx, strview_t keyword, hl_color_e color); - -hl_ctx_t *hlInit(arena_t *arena, hl_config_t *config) { - if (!config) { - err(" cannot be null"); - return NULL; - } - - hl_ctx_t *out = alloc(arena, hl_ctx_t); - - out->flags = config->flags; - - memcpy(out->symbol_table, hl_default_symbols_table, sizeof(hl_default_symbols_table)); - memcpy(out->colors, config->colors, sizeof(config->colors)); - - int kw_count = arrlen(hl_default_kwrds); - - out->kw_htable = hl_htable_init(arena, 8); - - for (int i = 0; i < kw_count; ++i) { - hl_keyword_t *kw = &hl_default_kwrds[i]; - hl_htable_add(arena, &out->kw_htable, kw->keyword, kw->color); - } - - for (int i = 0; i < config->kwrds_count; ++i) { - hl_keyword_t *kw = &config->extra_kwrds[i]; - hl_htable_add(arena, &out->kw_htable, kw->keyword, kw->color); - } - - return out; -} - -void hl_next_char(hl_ctx_t *ctx, instream_t *in) { - char cur = istrGet(in); - bool is_last = istrIsFinished(*in); - - switch (ctx->state) { - case HL_STATE_DEFAULT: - { - /* - * If potential keyword. - * - * A valid C keyword may contain numbers, but *not* - * as a suffix. - */ - if (hl_is_char_keyword(cur) && !isdigit(cur)) { - ctx->kw_beg = istrTell(*in); - ctx->state = HL_STATE_KEYWORD; - } - - // potential number - else if (isdigit(cur)) { - ctx->kw_beg = istrTell(*in); - ctx->state = HL_STATE_NUMBER; - } - - // potential char - else if (cur == '\'') { - ctx->kw_beg = istrTell(*in); - ctx->state = HL_STATE_CHAR; - } - - // potential string - else if (cur == '"') { - ctx->kw_beg = istrTell(*in); - ctx->state = HL_STATE_STRING; - } - - // line or multiline comment - else if (cur == '/') { - // single line comment - if (istrPeek(in) == '/') { - // rewind before comment begins - istrRewindN(in, 1); - - // comment until the end of line - hl_print_keyword(ctx, istrGetLine(in), HL_COLOR_COMMENT); - } - - // multiline comment - else if (istrPeek(in) == '*') { - ctx->state = HL_STATE_COMMENT_MULTI; - ctx->kw_beg = istrTell(*in); - istrSkip(in, 1); // skip * - } - - else { - // maybe a symbol? - hl_highlight_symbol(ctx, cur); - } - } - - // preprocessor - else if (cur == '#') { - // print the # as a symbol - hl_highlight_symbol(ctx, cur); - ctx->kw_beg = istrTell(*in); - ctx->state = HL_STATE_PREPROCESSOR; - } - - // other suppored symbols - else if (hl_highlight_symbol(ctx, cur)) { - // noop - } - - else { - hl_write_char(ctx, cur); - } - - break; - } - - case HL_STATE_KEYWORD: - { - // end of keyword, check if it really is a valid keyword - if (!hl_is_char_keyword(cur)) { - strview_t keyword = hl_finish_keyword(ctx, ctx->kw_beg, in); - hl_color_e kw_color = hl_get_keyword_color(ctx, keyword); - - if (kw_color != HL_COLOR__COUNT) { - hl_print_keyword(ctx, keyword, kw_color); - - // maybe we should highlight this remaining char. - if (!hl_highlight_symbol(ctx, cur)) { - hl_write_char(ctx, cur); - } - } - - /* - * If not keyword, maybe its a function call. - * - * Important to note that this is hacky and will only work - * if there is no space between keyword and '('. - */ - else if (cur == '(') { - hl_print_keyword(ctx, keyword, HL_COLOR_FUNC); - - // Opening parenthesis will always be highlighted - hl_highlight_symbol(ctx, cur); - } - else { - if (hl_is_capitalised(keyword)) { - hl_print_keyword(ctx, keyword, HL_COLOR_MACRO); - } - else { - hl_write(ctx, keyword); - } - if (!hl_highlight_symbol(ctx, cur)) { - hl_write_char(ctx, cur); - } - } - } - break; - } - - case HL_STATE_NUMBER: - { - char c = (char)tolower(cur); - - /* - * Should we end the state?. - * - * Very important observation: - * Although the number highlight works fine for most (if not all) - * of the possible cases, it also assumes that the code is written - * correctly and the source is able to compile, meaning that: - * - * Numbers like: 123, 0xABC123, 12.3e4f, 123ULL.... - * will be correctly identified and highlighted - * - * But, 'numbers' like: 123ABC, 0xxxxABCxx123, 123UUUUU.... - * will also be highlighted. - * - * It also assumes that no keyword will start with a number - * and everything starting with a number (except inside strings or - * comments) will be a number. - */ - if (!isdigit(c) && - (c < 'a' || c > 'f') && - c != 'b' && c != 'x' && - c != 'u' && c != 'l' && - c != '.' - ) { - strview_t keyword = hl_finish_keyword(ctx, ctx->kw_beg, in); - - // if not a valid char keyword: valid number - if (!hl_is_char_keyword(cur)) { - hl_print_keyword(ctx, keyword, HL_COLOR_NUMBER); - } - else { - hl_write(ctx, keyword); - } - - // maybe we should highlight this remaining char. - if (!hl_highlight_symbol(ctx, cur)) { - hl_write_char(ctx, cur); - } - } - - break; - } - - case HL_STATE_CHAR: - { - if (is_last || (cur == '\'' && istrPeek(in) != '\'')) { - strview_t keyword = hl_finish_keyword(ctx, ctx->kw_beg, in); - keyword.len++; - - hl_print_keyword(ctx, keyword, HL_COLOR_STRING); - } - break; - } - - case HL_STATE_STRING: - { - if (is_last || (cur == '"' && istrPrevPrev(in) != '\\')) { - strview_t keyword = hl_finish_keyword(ctx, ctx->kw_beg, in); - keyword.len++; - - hl_print_keyword(ctx, keyword, HL_COLOR_STRING); - } - break; - } - - case HL_STATE_COMMENT_MULTI: - { - /* - * If we are at the end of line _or_ have identified - * an end of comment... - */ - if (is_last || (cur == '*' && istrPeek(in) == '/')) { - strview_t keyword = hl_finish_keyword(ctx, ctx->kw_beg, in); - - hl_print_keyword(ctx, keyword, HL_COLOR_COMMENT); - } - break; - } - - case HL_STATE_PREPROCESSOR: - { - - if (!hl_is_char_keyword(cur)) { - hl_write_char(ctx, cur); - break; - } - -#define hl_check(str, new_state) \ - if (cur == str[0]) { \ - instream_t temp = *in; \ - strview_t a = strvInitLen(&(str[1]), sizeof(str) - 2); \ - strview_t b = istrGetViewLen(&temp, a.len); \ - if (strvEquals(a, b)) { \ - *in = temp; \ - hl_print_keyword(ctx, strvInitLen(str, sizeof(str) - 1), HL_COLOR_PREPROC); \ - ctx->state = new_state; \ - break; \ - } \ - } - if (is_last) { - strview_t keyword = hl_finish_keyword(ctx, ctx->kw_beg, in); - hl_print_keyword(ctx, keyword, HL_COLOR_PREPROC); - break; - } - - hl_check("include", HL_STATE_PREPROCESSOR_INCLUDE) - hl_check("define", HL_STATE_DEFAULT) - hl_check("undef", HL_STATE_DEFAULT) - hl_check("ifdef", HL_STATE_DEFAULT) - hl_check("ifndef", HL_STATE_DEFAULT) - hl_check("if", HL_STATE_DEFAULT) - hl_check("endif", HL_STATE_DEFAULT) - hl_check("pragma", HL_STATE_DEFAULT) - -#undef hl_check - break; - } - - - /* - * Preprocessor/Preprocessor include - * - * This is a 'dumb' preprocessor highlighter: - * it highlights everything with the same color - * and if and only if an '#include' is detected - * the included header will be handled as string - * and thus, will have the same color as the string. - * - * In fact, it is somehow similar to what GtkSourceView - * does (Mousepad, Gedit...) but with one silly difference: - * single-line/multi-line comments will not be handled - * while inside the preprocessor state, meaning that - * comments will also have the same color as the remaining - * of the line, yeah, ugly. - */ - case HL_STATE_PREPROCESSOR_INCLUDE: - { - if (cur == '<' || cur == '"' || is_last) { - ctx->kw_beg = istrTell(*in); - ctx->state = HL_STATE_PREPROCESSOR_INCLUDE_STRING; - } - else { - hl_write_char(ctx, cur); - } - break; - } - case HL_STATE_PREPROCESSOR_INCLUDE_STRING: - { - if (cur == '>' || cur == '"' || is_last) { - strview_t keyword = hl_finish_keyword(ctx, ctx->kw_beg, in); - keyword.len += 1; - hl_print_keyword(ctx, keyword, HL_COLOR_STRING); - } - break; - } - } -} - -str_t hlHighlight(arena_t *arena, hl_ctx_t *ctx, strview_t data) { - ctx->ostr = ostrInit(arena); - - ctx->state = HL_STATE_DEFAULT; - ctx->kw_beg = 0; - - instream_t in = istrInitLen(data.buf, data.len); - - while (!istrIsFinished(in)) { - hl_next_char(ctx, &in); - } - - hl_next_char(ctx, &in); - - return ostrAsStr(&ctx->ostr); -} - -void hlSetSymbolInTable(hl_ctx_t *ctx, char symbol, bool value) { - if (!ctx) return; - ctx->symbol_table[(unsigned char)symbol] = value; -} - -void hlAddKeyword(arena_t *arena, hl_ctx_t *ctx, hl_keyword_t *keyword) { - hl_htable_add(arena, &ctx->kw_htable, keyword->keyword, keyword->color); -} - -//// HASH TABLE /////////////////////////////////////////////////// - -static hl_hashtable_t hl_htable_init(arena_t *arena, uint pow2_exp) { - uint count = 1 << pow2_exp; - return (hl_hashtable_t) { - .count = count, - .buckets = alloc(arena, hl_node_t*, count), - }; -} - -static hl_htable_result_e hl_htable_add(arena_t *arena, hl_hashtable_t *table, strview_t key, hl_color_e value) { - if (!table) { - return HL_HTABLE_FAILED; - } - - if ((float)table->used >= table->count * 0.6f) { - warn("more than 60%% of the hashmap is being used: %d/%d", table->used, table->count); - } - - uint64 hash = hl_htable_hash(key.buf, key.len); - usize index = hash & (table->count - 1); - hl_node_t *bucket = table->buckets[index]; - if (bucket) table->collisions++; - while (bucket) { - // already exists - if (strvEquals(bucket->key, key)) { - bucket->value = value; - return HL_HTABLE_REPLACED; - } - bucket = bucket->next; - } - - bucket = alloc(arena, hl_node_t); - - bucket->key = key; - bucket->value = value; - bucket->next = table->buckets[index]; - - table->buckets[index] = bucket; - table->used++; - - return HL_HTABLE_ADDED; -} - -static hl_node_t *hl_htable_get(hl_hashtable_t *table, strview_t key) { - if (!table || table->used == 0) { - return NULL; - } - - uint64 hash = hl_htable_hash(key.buf, key.len); - usize index = hash & (table->count - 1); - hl_node_t *bucket = table->buckets[index]; - while (bucket) { - if (strvEquals(bucket->key, key)) { - return bucket; - } - bucket = bucket->next; - } - - return NULL; -} - -// uses the sdbm algorithm -static uint64 hl_htable_hash(const void *bytes, usize count) { - const uint8 *data = bytes; - uint64 hash = 0; - - for (usize i = 0; i < count; ++i) { - hash = data[i] + (hash << 6) + (hash << 16) - hash; - } - - return hash; -} - -//// STATIC FUNCTIONS ///////////////////////////////////////////// - -static inline void hl_escape_html(outstream_t *out, char c) { - switch (c) { - case '&': - ostrPuts(out, strv("&")); - break; - case '<': - ostrPuts(out, strv("<")); - break; - case '>': - ostrPuts(out, strv(">")); - break; - default: - ostrPutc(out, c); - break; - } -} - -static void hl_write_char(hl_ctx_t *ctx, char c) { - if (ctx->flags & HL_FLAG_HTML) { - hl_escape_html(&ctx->ostr, c); - } - else { - ostrPutc(&ctx->ostr, c); - } -} - -static void hl_write(hl_ctx_t *ctx, strview_t v) { - if (ctx->flags & HL_FLAG_HTML) { - for (usize i = 0; i < v.len; ++i) { - hl_escape_html(&ctx->ostr, v.buf[i]); - } - } - else { - ostrPuts(&ctx->ostr, v); - } -} - -static bool hl_is_char_keyword(char c) { - return isalpha(c) || isdigit(c) || c == '_'; -} - -static bool hl_highlight_symbol(hl_ctx_t *ctx, char c) { - if (!ctx->symbol_table[(unsigned char)c]) { - return false; - } - - ostrPuts(&ctx->ostr, ctx->colors[HL_COLOR_SYMBOL]); - hl_write_char(ctx, c); - ostrPuts(&ctx->ostr, ctx->colors[HL_COLOR_NORMAL]); - - return true; -} - -static hl_color_e hl_get_keyword_color(hl_ctx_t *ctx, strview_t keyword) { - // todo: make this an option? - if (strvEndsWithView(keyword, strv("_t"))) { - return HL_COLOR_CUSTOM_TYPES; - } - - hl_node_t *node = hl_htable_get(&ctx->kw_htable, keyword); - return node ? node->value : HL_COLOR__COUNT; -} - -static bool hl_is_capitalised(strview_t string) { - for (usize i = 0; i < string.len; ++i) { - char c = string.buf[i]; - if (!isdigit(c) && c != '_' && (c < 'A' || c > 'Z')) { - return false; - } - } - return true; -} - -static strview_t hl_finish_keyword(hl_ctx_t *ctx, usize beg, instream_t *in) { - ctx->state = HL_STATE_DEFAULT; - beg -= 1; - usize end = istrTell(*in) - 1; - - return strv(in->start + beg, end - beg); -} - -static void hl_print_keyword(hl_ctx_t *ctx, strview_t keyword, hl_color_e color) { - ostrPuts(&ctx->ostr, ctx->colors[color]); - hl_write(ctx, keyword); - ostrPuts(&ctx->ostr, ctx->colors[HL_COLOR_NORMAL]); -} \ No newline at end of file diff --git a/highlight.h b/highlight.h deleted file mode 100644 index 4ef28a8..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 *hlInit(arena_t *arena, hl_config_t *config); -str_t hlHighlight(arena_t *arena, hl_ctx_t *ctx, strview_t str); - -void hlSetSymbolInTable(hl_ctx_t *ctx, char symbol, bool value); -void hlAddKeyword(arena_t *arena, hl_ctx_t *ctx, hl_keyword_t *keyword); diff --git a/hot_reload.c b/hot_reload.c deleted file mode 100644 index 217bd1f..0000000 --- a/hot_reload.c +++ /dev/null @@ -1,227 +0,0 @@ -#include "hot_reload.h" - -#include "arena.h" -#include "file.h" -#include "tracelog.h" - -// todo linux support? -#if COLLA_WIN -#include -#else -// patch stuff up for cross platform for now, the actual program should not really call anything for now -#define HMODULE void* -#endif - -typedef int (*hr_f)(hr_t *ctx); - -typedef struct { - arena_t arena; - str_t path; - uint64 last_timestamp; - HMODULE handle; - hr_f hr_init; - hr_f hr_loop; - hr_f hr_close; -} hr_internal_t; - -static bool hr__os_reload(hr_internal_t *hr, str_t libpath); -static void hr__os_free(hr_internal_t *hr); - -static bool hr__file_copy(arena_t scratch, strview_t src, strview_t dst) { - buffer_t srcbuf = fileReadWhole(&scratch, src); - if (srcbuf.data == NULL || srcbuf.len == 0) { - err("fileReadWhole(%v) returned an empty buffer", src); - return false; - } - if (!fileWriteWhole(dst, srcbuf.data, srcbuf.len)) { - err("fileWriteWhole failed"); - return false; - } - return true; -} - -static bool hr__reload(hr_t *ctx) { -#ifdef HR_DISABLE - return true; -#endif - - hr_internal_t *hr = ctx->p; - arena_t scratch = hr->arena; - - if (!fileExists(strv(hr->path))) { - err("dll file %v does not exist anymore!", hr->path); - return false; - } - - uint64 now = fileGetTime(strv(hr->path)); - if (now <= hr->last_timestamp) { - return false; - } - - ctx->version = ctx->last_working_version + 1; - - // can't copy the dll directly, make a temporary one based on the version - strview_t dir, name, ext; - fileSplitPath(strv(hr->path), &dir, &name, &ext); - str_t libpath = strFmt(&scratch, "%v/%v-%d%v", dir, name, ctx->version, ext); - - if (!hr__file_copy(scratch, strv(hr->path), strv(libpath))) { - err("failed to copy %v to %v", hr->path, libpath); - return false; - } - - info("loading library: %v", libpath); - - bool success = hr__os_reload(hr, libpath); - if (success) { - info("Reloaded, version: %d", ctx->version); - ctx->last_working_version = ctx->version; - hr->last_timestamp = now; - hr->hr_init(ctx); - } - - return success; -} - -bool hrOpen(hr_t *ctx, strview_t path) { -#ifdef HR_DISABLE - cr_init(ctx); - return true; -#endif - - if (!fileExists(path)) { - err("dll file: %v does not exist", path); - return false; - } - - arena_t arena = arenaMake(ARENA_VIRTUAL, MB(1)); - - hr_internal_t *hr = alloc(&arena, hr_internal_t); - hr->arena = arena; - hr->path = str(&arena, path); - - ctx->p = hr; - ctx->last_working_version = 0; - - return hr__reload(ctx); -} - -void hrClose(hr_t *ctx, bool clean_temp_files) { -#ifdef HR_DISABLE - hr_close(ctx); - return; -#endif - - hr_internal_t *hr = ctx->p; - if (hr->hr_close) { - hr->hr_close(ctx); - } - - hr__os_free(hr); - - hr->handle = NULL; - hr->hr_init = hr->hr_loop = hr->hr_close = NULL; - - if (clean_temp_files) { - arena_t scratch = hr->arena; - - strview_t dir, name, ext; - fileSplitPath(strv(hr->path), &dir, &name, &ext); - - for (int i = 0; i < ctx->last_working_version; ++i) { - str_t fname = strFmt(&scratch, "%v/%v-%d%v", dir, name, i + 1, ext); - if (!fileDelete(strv(fname))) { - err("couldn't delete %v", fname); - } - } - } - - arena_t arena = hr->arena; - arenaCleanup(&arena); - - ctx->p = NULL; -} - -int hrStep(hr_t *ctx) { -#ifdef CR_DISABLE - hr_loop(ctx); - return 0; -#endif - hr_internal_t *hr = ctx->p; - - int result = -1; - if (hr->hr_loop) { - result = hr->hr_loop(ctx); - } - return result; -} - -bool hrReload(hr_t *ctx) { - return hr__reload(ctx); -} - -//// OS SPECIFIC //////////////////////////////////////// - -#if COLLA_WIN - -static bool hr__os_reload(hr_internal_t *hr, str_t libpath) { - if (hr->handle) { - FreeLibrary(hr->handle); - } - - hr->handle = LoadLibraryA(libpath.buf); - if (!hr->handle) { - err("couldn't load %v: %u", libpath, GetLastError()); - return true; - } - - hr->hr_init = (hr_f)GetProcAddress(hr->handle, "hr_init"); - DWORD init_err = GetLastError(); - hr->hr_loop = (hr_f)GetProcAddress(hr->handle, "hr_loop"); - DWORD loop_err = GetLastError(); - hr->hr_close = (hr_f)GetProcAddress(hr->handle, "hr_close"); - DWORD close_err = GetLastError(); - - if (!hr->hr_init) { - err("couldn't load address for hr_init: %u", init_err); - goto error; - } - - if (!hr->hr_loop) { - err("couldn't load address for hr_loop: %u", loop_err); - goto error; - } - - if (!hr->hr_close) { - err("couldn't load address for hr_close: %u", close_err); - goto error; - } - - - return true; - -error: - if (hr->handle) FreeLibrary(hr->handle); - hr->handle = NULL; - hr->hr_init = hr->hr_loop = hr->hr_close = NULL; - return false; -} - -static void hr__os_free(hr_internal_t *hr) { - if (hr->handle) { - FreeLibrary(hr->handle); - } -} - -#elif COLLA_LIN - -static bool hr__os_reload(hr_internal_t *hr, str_t libpath) { - fatal("todo: linux hot reload not implemented yet"); - return true; -} - -static void hr__os_free(hr_internal_t *hr) { - fatal("todo: linux hot reload not implemented yet"); -} - -#endif \ No newline at end of file diff --git a/hot_reload.h b/hot_reload.h deleted file mode 100644 index 3c03f88..0000000 --- a/hot_reload.h +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once - -// api functions: -// int hr_init(hr_t *ctx) -// int hr_loop(hr_t *ctx) -// int hr_close(hr_t *ctx) - -// you can turn off hot reloading and run the program -// "as normal" by defining -// HR_DISABLE - -#include "str.h" - -#if COLLA_WIN -#define HR_EXPORT __declspec(dllexport) -#else -// todo linux support? -#define HR_EXPORT -#endif - -typedef struct hr_t { - void *p; - void *userdata; - int version; - int last_working_version; -} hr_t; - -bool hrOpen(hr_t *ctx, strview_t path); -void hrClose(hr_t *ctx, bool clean_temp_files); -int hrStep(hr_t *ctx); -bool hrReload(hr_t *ctx); diff --git a/html.h b/html.h deleted file mode 100644 index bc3c0c6..0000000 --- a/html.h +++ /dev/null @@ -1,77 +0,0 @@ -#pragma once - -#include "strstream.h" - -typedef struct { - outstream_t stream; - str_t *output; -} html_context_t; - -strview_t html__strv_copy(strview_t src) { return src; } - -#define html__str_or_strv(str) _Generic(str, \ - strview_t: html__strv_copy, \ - str_t: strvInitStr, \ - const char *: strvInit, \ - char *: strvInit \ - )(str) - -#define htmlPrintf(...) ostrPrintf(&__ctx.stream, __VA_ARGS__) -#define htmlPuts(str) ostrPuts(&__ctx.stream, html__str_or_strv(str)) - -#define htmlBeg(arena_ptr, str_ptr) { \ - html_context_t __ctx = { .stream = ostrInit(arena_ptr), .output = str_ptr }; \ - htmlPrintf("\n"); -#define htmlEnd() htmlPrintf(""); *__ctx.output = ostrAsStr(&__ctx.stream); } - -#define html__args() \ - X(class) \ - X(id) \ - X(style) \ - X(onclick) \ - X(href) \ - X(src) \ - -typedef struct { -#define X(name) const char *name; - html__args() -#undef X -} html_tag_t; - -static void html__tag(html_context_t *ctx, const char *tag, html_tag_t *args) { - ostrPrintf(&ctx->stream, "<%s", tag); - -#define X(name, ...) if (args->name) { ostrPrintf(&ctx->stream, " " #name "=\"%s\"", args->name); } - html__args() -#undef X - - ostrPutc(&ctx->stream, '>'); -} - -#define tagBeg(tag, ...) do { html_tag_t args = {0, __VA_ARGS__}; html__tag(&__ctx, tag, &args); } while (0) -#define tagEnd(tag) htmlPrintf("") - -#define html__strv_or_str(s) _Generic(s, str_t: NULL) - -#define html__simple_tag(tag, text, ...) do { tagBeg(tag, __VA_ARGS__); htmlPuts(text); tagEnd(tag); } while (0) - -#define headBeg(...) tagBeg("head", __VA_ARGS__) -#define headEnd() tagEnd("head") - -#define bodyBeg(...) tagBeg("body", __VA_ARGS__) -#define bodyEnd() tagEnd("body") - -#define divBeg(...) tagBeg("div", __VA_ARGS__) -#define divEnd() tagEnd("div") - -#define htmlRaw(data) ostrPuts(&__ctx.stream, strv(#data)) - -#define title(text, ...) html__simple_tag("title", text, __VA_ARGS__) -#define h1(text, ...) html__simple_tag("h1", text, __VA_ARGS__) -#define p(text, ...) html__simple_tag("p", text, __VA_ARGS__) -#define span(text, ...) html__simple_tag("span", text, __VA_ARGS__) -#define a(text, ...) html__simple_tag("a", text, __VA_ARGS__) -#define img(...) tagBeg("img", __VA_ARGS__) -#define style(text, ...) html__simple_tag("style", text, __VA_ARGS__) - -#define hr() htmlPuts("
") diff --git a/http.c b/http.c deleted file mode 100644 index 4415e00..0000000 --- a/http.c +++ /dev/null @@ -1,556 +0,0 @@ -#include "http.h" - -#include "warnings/colla_warn_beg.h" - -#include -#include - -#include "arena.h" -#include "str.h" -#include "strstream.h" -#include "format.h" -#include "socket.h" -#include "tracelog.h" - -#if COLLA_WIN - #if COLLA_CMT_LIB - #pragma comment(lib, "Wininet") - #endif - - #include - #if !COLLA_TCC - #include - #endif -#endif - -static const TCHAR *https__get_method_str(http_method_e method); - -static http_header_t *http__parse_headers(arena_t *arena, instream_t *in) { - http_header_t *head = NULL; - strview_t line = STRV_EMPTY; - - do { - line = istrGetView(in, '\r'); - - usize pos = strvFind(line, ':', 0); - if (pos != STR_NONE) { - http_header_t *h = alloc(arena, http_header_t); - - h->key = strvSub(line, 0, pos); - h->value = strvSub(line, pos + 2, SIZE_MAX); - - h->next = head; - head = h; - } - - istrSkip(in, 2); // skip \r\n - } while (line.len > 2); // while line != "\r\n" - - return head; -} - -const char *httpGetStatusString(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"; -} - -int httpVerNumber(http_version_t ver) { - return (ver.major * 10) + ver.minor; -} - -http_req_t httpParseReq(arena_t *arena, strview_t request) { - http_req_t req = {0}; - instream_t in = istrInitLen(request.buf, request.len); - - strview_t method = strvTrim(istrGetView(&in, '/')); - istrSkip(&in, 1); // skip / - req.url = strvTrim(istrGetView(&in, ' ')); - strview_t http = strvTrim(istrGetView(&in, '\n')); - - istrSkip(&in, 1); // skip \n - - req.headers = http__parse_headers(arena, &in); - - req.body = strvTrim(istrGetViewLen(&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 (strvEquals(method, methods[i])) { - req.method = (http_method_e)i; - break; - } - } - - in = istrInitLen(http.buf, http.len); - istrIgnoreAndSkip(&in, '/'); // skip HTTP/ - istrGetU8(&in, &req.version.major); - istrSkip(&in, 1); // skip . - istrGetU8(&in, &req.version.minor); - - return req; -} - -http_res_t httpParseRes(arena_t *arena, strview_t response) { - http_res_t res = {0}; - instream_t in = istrInitLen(response.buf, response.len); - - strview_t http = istrGetViewLen(&in, 5); - if (!strvEquals(http, strv("HTTP"))) { - err("response doesn't start with 'HTTP', instead with %v", http); - return (http_res_t){0}; - } - istrSkip(&in, 1); // skip / - istrGetU8(&in, &res.version.major); - istrSkip(&in, 1); // skip . - istrGetU8(&in, &res.version.minor); - istrGetI32(&in, (int32*)&res.status_code); - - istrIgnore(&in, '\n'); - istrSkip(&in, 1); // skip \n - - res.headers = http__parse_headers(arena, &in); - - strview_t encoding = httpGetHeader(res.headers, strv("transfer-encoding")); - if (!strvEquals(encoding, strv("chunked"))) { - res.body = istrGetViewLen(&in, SIZE_MAX); - } - else { - err("chunked encoding not implemented yet! body ignored"); - } - - return res; -} - -str_t httpReqToStr(arena_t *arena, http_req_t *req) { - outstream_t out = ostrInit(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; - } - - ostrPrintf( - &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) { - ostrPrintf(&out, "%v: %v\r\n", h->key, h->value); - h = h->next; - } - - ostrPuts(&out, strv("\r\n")); - ostrPuts(&out, req->body); - - return ostrAsStr(&out); -} - -str_t httpResToStr(arena_t *arena, http_res_t *res) { - outstream_t out = ostrInit(arena); - - ostrPrintf( - &out, - "HTTP/%hhu.%hhu %d %s\r\n", - res->version.major, - res->version.minor, - res->status_code, - httpGetStatusString(res->status_code) - ); - ostrPuts(&out, strv("\r\n")); - ostrPuts(&out, res->body); - - return ostrAsStr(&out); -} - -bool httpHasHeader(http_header_t *headers, strview_t key) { - http_header_t *h = headers; - while (h) { - if (strvEquals(h->key, key)) { - return true; - } - h = h->next; - } - return false; -} - -void httpSetHeader(http_header_t *headers, strview_t key, strview_t value) { - http_header_t *h = headers; - while (h) { - if (strvEquals(h->key, key)) { - h->value = value; - break; - } - h = h->next; - } -} - -strview_t httpGetHeader(http_header_t *headers, strview_t key) { - http_header_t *h = headers; - while (h) { - if (strvEquals(h->key, key)) { - return h->value; - } - h = h->next; - } - return STRV_EMPTY; -} - -str_t httpMakeUrlSafe(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 (strvContains(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 (strvContains(chars, string.buf[i])) { - fmtBuffer(out.buf + cur, 4, "%%%X", string.buf[i]); - cur += 3; - } - else { - out.buf[cur++] = string.buf[i]; - } - } - - return out; -} - -str_t httpDecodeUrlSafe(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; - } - } - - 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 httpSplitUrl(strview_t url) { - http_url_t out = {0}; - - if (strvStartsWithView(url, strv("https://"))) { - url = strvRemovePrefix(url, 8); - } - else if (strvStartsWithView(url, strv("http://"))) { - url = strvRemovePrefix(url, 7); - } - - out.host = strvSub(url, 0, strvFind(url, '/', 0)); - out.uri = strvSub(url, out.host.len, SIZE_MAX); - - return out; -} - -http_res_t httpRequest(http_request_desc_t *request) { - usize arena_begin = arenaTell(request->arena); - - http_req_t req = { - .version = (http_version_t){ 1, 1 }, - .url = request->url, - .body = request->body, - .method = request->request_type, - }; - - http_header_t *h = NULL; - - for (int i = 0; i < request->header_count; ++i) { - http_header_t *header = request->headers + i; - header->next = h; - h = header; - } - - req.headers = h; - - http_url_t url = httpSplitUrl(req.url); - - if (strvEndsWith(url.host, '/')) { - url.host = strvRemoveSuffix(url.host, 1); - } - - if (!httpHasHeader(req.headers, strv("Host"))) { - httpSetHeader(req.headers, strv("Host"), url.host); - } - if (!httpHasHeader(req.headers, strv("Content-Length"))) { - char tmp[16] = {0}; - fmtBuffer(tmp, arrlen(tmp), "%zu", req.body.len); - httpSetHeader(req.headers, strv("Content-Length"), strv(tmp)); - } - if (req.method == HTTP_POST && !httpHasHeader(req.headers, strv("Content-Type"))) { - httpSetHeader(req.headers, strv("Content-Type"), strv("application/x-www-form-urlencoded")); - } - if (!httpHasHeader(req.headers, strv("Connection"))) { - httpSetHeader(req.headers, strv("Connection"), strv("close")); - } - - if (!skInit()) { - err("couldn't initialise sockets: %s", skGetErrorString()); - goto error; - } - - socket_t sock = skOpen(SOCK_TCP); - if (!skIsValid(sock)) { - err("couldn't open socket: %s", skGetErrorString()); - goto error; - } - - char hostname[64] = {0}; - assert(url.host.len < arrlen(hostname)); - memcpy(hostname, url.host.buf, url.host.len); - - const uint16 DEFAULT_HTTP_PORT = 80; - if (!skConnect(sock, hostname, DEFAULT_HTTP_PORT)) { - err("Couldn't connect to host %s: %s", hostname, skGetErrorString()); - goto error; - } - - str_t reqstr = httpReqToStr(request->arena, &req); - if (strIsEmpty(reqstr)) { - err("couldn't get string from request"); - goto error; - } - - if (skSend(sock, reqstr.buf, (int)reqstr.len) == SOCKET_ERROR) { - err("couldn't send request to socket: %s", skGetErrorString()); - goto error; - } - - outstream_t response = ostrInit(request->arena); - char buffer[4096]; - int read = 0; - do { - read = skReceive(sock, buffer, arrlen(buffer)); - if (read == SOCKET_ERROR) { - err("couldn't get the data from the server: %s", skGetErrorString()); - goto error; - } - ostrPuts(&response, strv(buffer, read)); - } while (read != 0); - - if (!skClose(sock)) { - err("couldn't close socket: %s", skGetErrorString()); - } - - if (!skCleanup()) { - err("couldn't clean up sockets: %s", skGetErrorString()); - } - - return httpParseRes(request->arena, ostrAsView(&response)); - -error: - arenaRewind(request->arena, arena_begin); - skCleanup(); - return (http_res_t){0}; -} - -#if COLLA_WIN - -buffer_t httpsRequest(http_request_desc_t *req) { - HINTERNET internet = InternetOpen( - TEXT("COLLA"), - INTERNET_OPEN_TYPE_PRECONFIG, - NULL, - NULL, - 0 - ); - if (!internet) { - fatal("call to InternetOpen failed: %u", GetLastError()); - } - - http_url_t split = httpSplitUrl(req->url); - strview_t server = split.host; - strview_t page = split.uri; - - if (strvStartsWithView(server, strv("http://"))) { - server = strvRemovePrefix(server, 7); - } - - if (strvStartsWithView(server, strv("https://"))) { - server = strvRemovePrefix(server, 8); - } - - arena_t scratch = *req->arena; - const TCHAR *tserver = strvToTChar(&scratch, server); - const TCHAR *tpage = strvToTChar(&scratch, page); - - HINTERNET connection = InternetConnect( - internet, - tserver, - INTERNET_DEFAULT_HTTPS_PORT, - NULL, - NULL, - INTERNET_SERVICE_HTTP, - 0, - (DWORD_PTR)NULL // userdata - ); - if (!connection) { - fatal("call to InternetConnect failed: %u", GetLastError()); - } - - const TCHAR *accepted_types[] = { TEXT("*/*"), NULL }; - - HINTERNET request = HttpOpenRequest( - connection, - https__get_method_str(req->request_type), - tpage, - TEXT("HTTP/1.1"), - NULL, - accepted_types, - INTERNET_FLAG_SECURE, - (DWORD_PTR)NULL // userdata - ); - if (!request) { - fatal("call to HttpOpenRequest failed: %u", GetLastError()); - } - - outstream_t header = ostrInit(&scratch); - - for (int i = 0; i < req->header_count; ++i) { - http_header_t *h = &req->headers[i]; - ostrClear(&header); - ostrPrintf( - &header, - "%.*s: %.*s\r\n", - h->key.len, h->key.buf, - h->value.len, h->value.buf - ); - str_t header_str = ostrAsStr(&header); - HttpAddRequestHeadersA( - request, - header_str.buf, - (DWORD)header_str.len, - 0 - ); - } - - BOOL request_sent = HttpSendRequest( - request, - NULL, - 0, - (void *)req->body.buf, - (DWORD)req->body.len - ); - if (!request_sent) { - fatal("call to HttpSendRequest failed: %u", GetLastError()); - } - - outstream_t out = ostrInit(req->arena); - - while (true) { - DWORD bytes_read = 0; - char buffer[4096]; - BOOL read = InternetReadFile( - request, - buffer, - sizeof(buffer), - &bytes_read - ); - if (!read || bytes_read == 0) { - break; - } - ostrPuts(&out, strv(buffer, bytes_read)); - } - - InternetCloseHandle(request); - InternetCloseHandle(connection); - InternetCloseHandle(internet); - - str_t outstr = ostrAsStr(&out); - - return (buffer_t) { - .data = (uint8 *)outstr.buf, - .len = outstr.len - }; -} - -static const TCHAR *https__get_method_str(http_method_e method) { - switch (method) { - case HTTP_GET: return TEXT("GET"); - case HTTP_POST: return TEXT("POST"); - case HTTP_HEAD: return TEXT("HEAD"); - case HTTP_PUT: return TEXT("PUT"); - case HTTP_DELETE: return TEXT("DELETE"); - } - // default GET - return NULL; -} -#endif - -#include "warnings/colla_warn_end.h" diff --git a/http.h b/http.h deleted file mode 100644 index cfb65c2..0000000 --- a/http.h +++ /dev/null @@ -1,83 +0,0 @@ -#pragma once - -#include "collatypes.h" -#include "str.h" - -typedef struct arena_t arena_t; -typedef uintptr_t socket_t; - -typedef enum { - HTTP_GET, - HTTP_POST, - HTTP_HEAD, - HTTP_PUT, - HTTP_DELETE -} http_method_e; - -const char *httpGetStatusString(int status); - -typedef struct { - uint8 major; - uint8 minor; -} http_version_t; - -// translates a http_version_t to a single readable number (e.g. 1.1 -> 11, 1.0 -> 10, etc) -int httpVerNumber(http_version_t ver); - -typedef struct http_header_t { - strview_t key; - strview_t value; - struct http_header_t *next; -} http_header_t; - -typedef struct { - http_method_e method; - http_version_t version; - http_header_t *headers; - strview_t url; - strview_t body; -} http_req_t; - -typedef struct { - int status_code; - http_version_t version; - http_header_t *headers; - strview_t body; -} http_res_t; - -// strview_t request needs to be valid for http_req_t to be valid! -http_req_t httpParseReq(arena_t *arena, strview_t request); -http_res_t httpParseRes(arena_t *arena, strview_t response); - -str_t httpReqToStr(arena_t *arena, http_req_t *req); -str_t httpResToStr(arena_t *arena, http_res_t *res); - -bool httpHasHeader(http_header_t *headers, strview_t key); -void httpSetHeader(http_header_t *headers, strview_t key, strview_t value); -strview_t httpGetHeader(http_header_t *headers, strview_t key); - -str_t httpMakeUrlSafe(arena_t *arena, strview_t string); -str_t httpDecodeUrlSafe(arena_t *arena, strview_t string); - -typedef struct { - strview_t host; - strview_t uri; -} http_url_t; - -http_url_t httpSplitUrl(strview_t url); - -typedef struct { - arena_t *arena; - strview_t url; - http_method_e request_type; - http_header_t *headers; - int header_count; - strview_t body; -} http_request_desc_t; - -// arena_t *arena, strview_t url, [ http_header_t *headers, int header_count, strview_t body ] -#define httpGet(arena, url, ...) httpRequest(&(http_request_desc_t){ arena, url, .request_type = HTTP_GET, __VA_ARGS__ }) -#define httpsGet(arena, url, ...) httpsRequest(&(http_request_desc_t){ arena, url, .request_type = HTTP_GET, __VA_ARGS__ }) - -http_res_t httpRequest(http_request_desc_t *request); -buffer_t httpsRequest(http_request_desc_t *request); diff --git a/ini.c b/ini.c deleted file mode 100644 index da515cc..0000000 --- a/ini.c +++ /dev/null @@ -1,279 +0,0 @@ -#include "ini.h" - -#include "warnings/colla_warn_beg.h" - -#include - -#include "strstream.h" - -static void ini__parse(arena_t *arena, ini_t *ini, const iniopts_t *options); - -ini_t iniParse(arena_t *arena, strview_t filename, const iniopts_t *options) { - file_t fp = fileOpen(filename, FILE_READ); - ini_t out = iniParseFile(arena, fp, options); - fileClose(fp); - return out; -} - -ini_t iniParseFile(arena_t *arena, file_t file, const iniopts_t *options) { - str_t data = fileReadWholeStrFP(arena, file); - return iniParseStr(arena, strv(data), options); -} - -ini_t iniParseStr(arena_t *arena, strview_t str, const iniopts_t *options) { - ini_t out = { - .text = str, - .tables = NULL, - }; - ini__parse(arena, &out, options); - return out; -} - -bool iniIsValid(ini_t *ctx) { - return ctx && !strvIsEmpty(ctx->text); -} - -initable_t *iniGetTable(ini_t *ctx, strview_t name) { - initable_t *t = ctx ? ctx->tables : NULL; - while (t) { - if (strvEquals(t->name, name)) { - return t; - } - t = t->next; - } - return NULL; -} - -inivalue_t *iniGet(initable_t *ctx, strview_t key) { - inivalue_t *v = ctx ? ctx->values : NULL; - while (v) { - if (strvEquals(v->key, key)) { - return v; - } - v = v->next; - } - return NULL; -} - -iniarray_t iniAsArr(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->current; - usize count = 0; - - usize start = 0; - for (usize i = 0; i < v.len; ++i) { - if (v.buf[i] == delim) { - strview_t arrval = strvTrim(strvSub(v, start, i)); - if (!strvIsEmpty(arrval)) { - strview_t *newval = alloc(arena, strview_t); - *newval = arrval; - ++count; - } - start = i + 1; - } - } - - strview_t last = strvTrim(strvSub(v, start, SIZE_MAX)); - if (!strvIsEmpty(last)) { - strview_t *newval = alloc(arena, strview_t); - *newval = last; - ++count; - } - - return (iniarray_t){ - .values = beg, - .count = count, - }; -} - -uint64 iniAsUInt(inivalue_t *value) { - strview_t v = value ? value->value : STRV_EMPTY; - instream_t in = istrInitLen(v.buf, v.len); - uint64 out = 0; - if (!istrGetU64(&in, &out)) { - out = 0; - } - return out; -} - -int64 iniAsInt(inivalue_t *value) { - strview_t v = value ? value->value : STRV_EMPTY; - instream_t in = istrInitLen(v.buf, v.len); - int64 out = 0; - if (!istrGetI64(&in, &out)) { - out = 0; - } - return out; -} - -double iniAsNum(inivalue_t *value) { - strview_t v = value ? value->value : STRV_EMPTY; - instream_t in = istrInitLen(v.buf, v.len); - double out = 0; - if (!istrGetDouble(&in, &out)) { - out = 0.0; - } - return out; -} - -bool iniAsBool(inivalue_t *value) { - strview_t v = value ? value->value : STRV_EMPTY; - instream_t in = istrInitLen(v.buf, v.len); - bool out = 0; - if (!istrGetBool(&in, &out)) { - out = false; - } - return out; -} - -// == PRIVATE FUNCTIONS ============================================================================== - -#define INIPUSH(head, tail, val) \ - do { \ - if (!head) { \ - head = val; \ - tail = val; \ - } \ - else { \ - tail->next = val; \ - val = tail; \ - } \ - } while (0) - -static iniopts_t ini__get_options(const iniopts_t *options) { - iniopts_t out = { - .key_value_divider = '=', - }; - -#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); - } - -#undef SETOPT - - return out; -} - -static void ini__add_value(arena_t *arena, initable_t *table, instream_t *in, iniopts_t *opts) { - assert(table); - - strview_t key = strvTrim(istrGetView(in, opts->key_value_divider)); - istrSkip(in, 1); - strview_t value = strvTrim(istrGetViewEither(in, strv("\n#;"))); - istrSkip(in, 1); - inivalue_t *newval = NULL; - - - if (opts->merge_duplicate_keys) { - newval = table->values; - while (newval) { - if (strvEquals(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; - } -} - -static void ini__add_table(arena_t *arena, ini_t *ctx, instream_t *in, iniopts_t *options) { - istrSkip(in, 1); // skip [ - strview_t name = istrGetView(in, ']'); - istrSkip(in, 1); // skip ] - initable_t *table = NULL; - - if (options->merge_duplicate_tables) { - table = ctx->tables; - while (table) { - if (strvEquals(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; - } - - istrIgnoreAndSkip(in, '\n'); - while (!istrIsFinished(*in)) { - switch (istrPeek(in)) { - case '\n': // fallthrough - case '\r': - return; - case '#': // fallthrough - case ';': - istrIgnoreAndSkip(in, '\n'); - break; - default: - ini__add_value(arena, table, in, options); - break; - } - } -} - -static void ini__parse(arena_t *arena, ini_t *ini, const iniopts_t *options) { - iniopts_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 = istrInitLen(ini->text.buf, ini->text.len); - - while (!istrIsFinished(in)) { - istrSkipWhitespace(&in); - switch (istrPeek(&in)) { - case '[': - ini__add_table(arena, ini, &in, &opts); - break; - case '#': // fallthrough - case ';': - istrIgnoreAndSkip(&in, '\n'); - break; - default: - ini__add_value(arena, ini->tables, &in, &opts); - break; - } - } -} - -#undef INIPUSH - -#include "warnings/colla_warn_end.h" diff --git a/ini.h b/ini.h deleted file mode 100644 index 359a7b6..0000000 --- a/ini.h +++ /dev/null @@ -1,54 +0,0 @@ -#pragma once - -#include "collatypes.h" -#include "str.h" -#include "file.h" - -typedef struct arena_t arena_t; - -typedef struct inivalue_t { - strview_t key; - strview_t value; - struct inivalue_t *next; -} inivalue_t; - -typedef struct initable_t { - strview_t name; - inivalue_t *values; - inivalue_t *tail; - struct initable_t *next; -} initable_t; - -typedef struct ini_t { - strview_t text; - initable_t *tables; - initable_t *tail; -} ini_t; - -typedef struct { - bool merge_duplicate_tables; // default false - bool merge_duplicate_keys; // default false - char key_value_divider; // default = -} iniopts_t; - -ini_t iniParse(arena_t *arena, strview_t filename, const iniopts_t *options); -ini_t iniParseFile(arena_t *arena, file_t file, const iniopts_t *options); -ini_t iniParseStr(arena_t *arena, strview_t str, const iniopts_t *options); - -bool iniIsValid(ini_t *ctx); - -#define INI_ROOT strv("__ROOT__") - -initable_t *iniGetTable(ini_t *ctx, strview_t name); -inivalue_t *iniGet(initable_t *ctx, strview_t key); - -typedef struct { - strview_t *values; - usize count; -} iniarray_t; - -iniarray_t iniAsArr(arena_t *arena, inivalue_t *value, char delim); -uint64 iniAsUInt(inivalue_t *value); -int64 iniAsInt(inivalue_t *value); -double iniAsNum(inivalue_t *value); -bool iniAsBool(inivalue_t *value); \ No newline at end of file diff --git a/json.c b/json.c deleted file mode 100644 index 84ec155..0000000 --- a/json.c +++ /dev/null @@ -1,386 +0,0 @@ -#include "json.h" - -#include "warnings/colla_warn_beg.h" - -#include - -#include "strstream.h" -#include "file.h" -#include "tracelog.h" - -// #define json__logv() warn("%s:%d", __FILE__, __LINE__) -#define json__logv() -#define json__ensure(c) json__check_char(in, c) - -static bool json__check_char(instream_t *in, char c) { - if (istrGet(in) == c) { - return true; - } - istrRewindN(in, 1); - err("wrong character at %zu, should be '%c' but is 0x%02x '%c'", istrTell(*in), c, istrPeek(in), istrPeek(in)); - json__logv(); - return false; -} - -static bool json__parse_pair(arena_t *arena, instream_t *in, jsonflags_e flags, jsonval_t **out); -static bool json__parse_value(arena_t *arena, instream_t *in, jsonflags_e flags, jsonval_t **out); - -static bool json__is_value_finished(instream_t *in) { - usize old_pos = istrTell(*in); - - istrSkipWhitespace(in); - switch(istrPeek(in)) { - case '}': // fallthrough - case ']': // fallthrough - case ',': - return true; - } - - in->cur = in->start + old_pos; - return false; -} - -static bool json__parse_null(instream_t *in) { - strview_t null_view = istrGetViewLen(in, 4); - bool is_valid = true; - - if (!strvEquals(null_view, strv("null"))) { - err("should be null but is: (%.*s) at %zu", null_view.len, null_view.buf, istrTell(*in)); - is_valid = false; - } - - if (!json__is_value_finished(in)) { - err("null, should be finished, but isn't at %zu", istrTell(*in)); - is_valid = false; - } - - if (!is_valid) json__logv(); - - return is_valid; -} - -static bool json__parse_array(arena_t *arena, instream_t *in, jsonflags_e flags, jsonval_t **out) { - jsonval_t *head = NULL; - - if (!json__ensure('[')) { - json__logv(); - goto fail; - } - - istrSkipWhitespace(in); - - // if it is an empty array - if (istrPeek(in) == ']') { - istrSkip(in, 1); - goto success; - } - - if (!json__parse_value(arena, in, flags, &head)) { - json__logv(); - goto fail; - } - - jsonval_t *cur = head; - - while (true) { - istrSkipWhitespace(in); - switch (istrGet(in)) { - case ']': - return head; - case ',': - { - istrSkipWhitespace(in); - // trailing comma - if (istrPeek(in) == ']') { - if (flags & JSON_NO_TRAILING_COMMAS) { - err("trailing comma in array at at %zu: (%c)(%d)", istrTell(*in), *in->cur, *in->cur); - json__logv(); - goto fail; - } - else { - continue; - } - } - - jsonval_t *next = NULL; - if (!json__parse_value(arena, in, flags, &next)) { - json__logv(); - goto fail; - } - cur->next = next; - next->prev = cur; - cur = next; - break; - } - default: - istrRewindN(in, 1); - err("unknown char after array at %zu: (%c)(%d)", istrTell(*in), *in->cur, *in->cur); - json__logv(); - goto fail; - } - } - -success: - *out = head; - return true; -fail: - *out = NULL; - return false; -} - -static bool json__parse_string(arena_t *arena, instream_t *in, str_t *out) { - istrSkipWhitespace(in); - - if (!json__ensure('"')) { - json__logv(); - goto fail; - } - - const char *from = in->cur; - - for (; !istrIsFinished(*in) && *in->cur != '"'; ++in->cur) { - if (istrPeek(in) == '\\') { - ++in->cur; - } - } - - usize len = in->cur - from; - - *out = str(arena, from, len); - - if (!json__ensure('"')) { - json__logv(); - goto fail; - } - - return true; -fail: - *out = STR_EMPTY; - return false; -} - -static bool json__parse_number(instream_t *in, double *out) { - return istrGetDouble(in, out); -} - -static bool json__parse_bool(instream_t *in, bool *out) { - size_t remaining = istrRemaining(*in); - if (remaining >= 4 && memcmp(in->cur, "true", 4) == 0) { - istrSkip(in, 4); - *out = true; - } - else if (remaining >= 5 && memcmp(in->cur, "false", 5) == 0) { - istrSkip(in, 5); - *out = false; - } - else { - err("unknown boolean at %zu: %.10s", istrTell(*in), in->cur); - json__logv(); - return false; - } - - return true; -} - -static bool json__parse_obj(arena_t *arena, instream_t *in, jsonflags_e flags, jsonval_t **out) { - if (!json__ensure('{')) { - json__logv(); - goto fail; - } - - istrSkipWhitespace(in); - - // if it is an empty object - if (istrPeek(in) == '}') { - istrSkip(in, 1); - *out = NULL; - return true; - } - - jsonval_t *head = NULL; - if (!json__parse_pair(arena, in, flags, &head)) { - json__logv(); - goto fail; - } - jsonval_t *cur = head; - - while (true) { - istrSkipWhitespace(in); - switch (istrGet(in)) { - case '}': - goto success; - case ',': - { - istrSkipWhitespace(in); - // trailing commas - if (!(flags & JSON_NO_TRAILING_COMMAS) && istrPeek(in) == '}') { - goto success; - } - - jsonval_t *next = NULL; - if (!json__parse_pair(arena, in, flags, &next)) { - json__logv(); - goto fail; - } - cur->next = next; - next->prev = cur; - cur = next; - break; - } - default: - istrRewindN(in, 1); - err("unknown char after object at %zu: (%c)(%d)", istrTell(*in), *in->cur, *in->cur); - json__logv(); - goto fail; - } - } - -success: - *out = head; - return true; -fail: - *out = NULL; - return false; -} - -static bool json__parse_pair(arena_t *arena, instream_t *in, jsonflags_e flags, jsonval_t **out) { - str_t key = {0}; - if (!json__parse_string(arena, in, &key)) { - json__logv(); - goto fail; - } - - // skip preamble - istrSkipWhitespace(in); - if (!json__ensure(':')) { - json__logv(); - goto fail; - } - - if (!json__parse_value(arena, in, flags, out)) { - json__logv(); - goto fail; - } - - (*out)->key = key; - return true; - -fail: - *out = NULL; - return false; -} - -static bool json__parse_value(arena_t *arena, instream_t *in, jsonflags_e flags, jsonval_t **out) { - jsonval_t *val = alloc(arena, jsonval_t); - - istrSkipWhitespace(in); - - switch (istrPeek(in)) { - // object - case '{': - if (!json__parse_obj(arena, in, flags, &val->object)) { - json__logv(); - goto fail; - } - val->type = JSON_OBJECT; - break; - // array - case '[': - if (!json__parse_array(arena, in, flags, &val->array)) { - json__logv(); - goto fail; - } - val->type = JSON_ARRAY; - break; - // string - case '"': - if (!json__parse_string(arena, in, &val->string)) { - json__logv(); - goto fail; - } - val->type = JSON_STRING; - break; - // boolean - case 't': // fallthrough - case 'f': - if (!json__parse_bool(in, &val->boolean)) { - json__logv(); - goto fail; - } - val->type = JSON_BOOL; - break; - // null - case 'n': - if (!json__parse_null(in)) { - json__logv(); - goto fail; - } - val->type = JSON_NULL; - break; - // comment - case '/': - err("TODO comments"); - break; - // number - default: - if (!json__parse_number(in, &val->number)) { - json__logv(); - goto fail; - } - val->type = JSON_NUMBER; - break; - } - - *out = val; - return true; -fail: - *out = NULL; - return false; -} - -json_t jsonParse(arena_t *arena, arena_t scratch, strview_t filename, jsonflags_e flags) { - str_t data = fileReadWholeStr(&scratch, filename); - return NULL; - json_t json = jsonParseStr(arena, strv(data), flags); - return json; -} - -json_t jsonParseStr(arena_t *arena, strview_t jsonstr, jsonflags_e flags) { - arena_t before = *arena; - - jsonval_t *root = alloc(arena, jsonval_t); - root->type = JSON_OBJECT; - - instream_t in = istrInitLen(jsonstr.buf, jsonstr.len); - - if (!json__parse_obj(arena, &in, flags, &root->object)) { - // reset arena - *arena = before; - json__logv(); - return NULL; - } - - return root; -} - -jsonval_t *jsonGet(jsonval_t *node, strview_t key) { - if (!node) return NULL; - - if (node->type != JSON_OBJECT) { - err("passed type is not an object"); - return NULL; - } - - node = node->object; - - while (node) { - if (strvEquals(strv(node->key), key)) { - return node; - } - node = node->next; - } - - return NULL; -} - -#include "warnings/colla_warn_end.h" \ No newline at end of file diff --git a/json.h b/json.h deleted file mode 100644 index 762e53a..0000000 --- a/json.h +++ /dev/null @@ -1,47 +0,0 @@ -#pragma once - -#include "str.h" -#include "arena.h" - -typedef enum { - JSON_NULL, - JSON_ARRAY, - JSON_STRING, - JSON_NUMBER, - JSON_BOOL, - JSON_OBJECT, -} jsontype_e; - -typedef enum { - JSON_DEFAULT = 0, - JSON_NO_TRAILING_COMMAS = 1 << 0, - JSON_NO_COMMENTS = 1 << 1, -} jsonflags_e; - -typedef struct jsonval_t jsonval_t; - -typedef struct jsonval_t { - jsonval_t *next; - jsonval_t *prev; - - str_t key; - - union { - jsonval_t *array; - str_t string; - double number; - bool boolean; - jsonval_t *object; - }; - jsontype_e type; -} jsonval_t; - -typedef jsonval_t *json_t; - -json_t jsonParse(arena_t *arena, arena_t scratch, strview_t filename, jsonflags_e flags); -json_t jsonParseStr(arena_t *arena, strview_t jsonstr, jsonflags_e flags); - -jsonval_t *jsonGet(jsonval_t *node, strview_t key); - -#define json_check(val, js_type) ((val) && (val)->type == js_type) -#define json_for(name, arr) for (jsonval_t *name = json_check(arr, JSON_ARRAY) ? arr->array : NULL; name; name = name->next) diff --git a/lz4/lz4.c b/lz4/lz4.c deleted file mode 100644 index 2aa4661..0000000 --- a/lz4/lz4.c +++ /dev/null @@ -1,2829 +0,0 @@ -/* - LZ4 - Fast LZ compression algorithm - Copyright (C) 2011-2023, Yann Collet. - - BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are - met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following disclaimer - in the documentation and/or other materials provided with the - distribution. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - You can contact the author at : - - LZ4 homepage : http://www.lz4.org - - LZ4 source repository : https://github.com/lz4/lz4 -*/ - -/*-************************************ -* Tuning parameters -**************************************/ -/* - * LZ4_HEAPMODE : - * Select how stateless compression functions like `LZ4_compress_default()` - * allocate memory for their hash table, - * in memory stack (0:default, fastest), or in memory heap (1:requires malloc()). - */ -#ifndef LZ4_HEAPMODE -# define LZ4_HEAPMODE 0 -#endif - -/* - * LZ4_ACCELERATION_DEFAULT : - * Select "acceleration" for LZ4_compress_fast() when parameter value <= 0 - */ -#define LZ4_ACCELERATION_DEFAULT 1 -/* - * LZ4_ACCELERATION_MAX : - * Any "acceleration" value higher than this threshold - * get treated as LZ4_ACCELERATION_MAX instead (fix #876) - */ -#define LZ4_ACCELERATION_MAX 65537 - - -/*-************************************ -* CPU Feature Detection -**************************************/ -/* LZ4_FORCE_MEMORY_ACCESS - * By default, access to unaligned memory is controlled by `memcpy()`, which is safe and portable. - * Unfortunately, on some target/compiler combinations, the generated assembly is sub-optimal. - * The below switch allow to select different access method for improved performance. - * Method 0 (default) : use `memcpy()`. Safe and portable. - * Method 1 : `__packed` statement. It depends on compiler extension (ie, not portable). - * This method is safe if your compiler supports it, and *generally* as fast or faster than `memcpy`. - * Method 2 : direct access. This method is portable but violate C standard. - * It can generate buggy code on targets which assembly generation depends on alignment. - * But in some circumstances, it's the only known way to get the most performance (ie GCC + ARMv6) - * See https://fastcompression.blogspot.fr/2015/08/accessing-unaligned-memory.html for details. - * Prefer these methods in priority order (0 > 1 > 2) - */ -#ifndef LZ4_FORCE_MEMORY_ACCESS /* can be defined externally */ -# if defined(__GNUC__) && \ - ( defined(__ARM_ARCH_6__) || defined(__ARM_ARCH_6J__) || defined(__ARM_ARCH_6K__) \ - || defined(__ARM_ARCH_6Z__) || defined(__ARM_ARCH_6ZK__) || defined(__ARM_ARCH_6T2__) ) -# define LZ4_FORCE_MEMORY_ACCESS 2 -# elif (defined(__INTEL_COMPILER) && !defined(_WIN32)) || defined(__GNUC__) || defined(_MSC_VER) -# define LZ4_FORCE_MEMORY_ACCESS 1 -# endif -#endif - -/* - * LZ4_FORCE_SW_BITCOUNT - * Define this parameter if your target system or compiler does not support hardware bit count - */ -#if defined(_MSC_VER) && defined(_WIN32_WCE) /* Visual Studio for WinCE doesn't support Hardware bit count */ -# undef LZ4_FORCE_SW_BITCOUNT /* avoid double def */ -# define LZ4_FORCE_SW_BITCOUNT -#endif - - - -/*-************************************ -* Dependency -**************************************/ -/* - * LZ4_SRC_INCLUDED: - * Amalgamation flag, whether lz4.c is included - */ -#ifndef LZ4_SRC_INCLUDED -# define LZ4_SRC_INCLUDED 1 -#endif - -#ifndef LZ4_DISABLE_DEPRECATE_WARNINGS -# define LZ4_DISABLE_DEPRECATE_WARNINGS /* due to LZ4_decompress_safe_withPrefix64k */ -#endif - -#ifndef LZ4_STATIC_LINKING_ONLY -# define LZ4_STATIC_LINKING_ONLY -#endif -#include "lz4.h" -/* see also "memory routines" below */ - - -/*-************************************ -* Compiler Options -**************************************/ -#if defined(_MSC_VER) && (_MSC_VER >= 1400) /* Visual Studio 2005+ */ -# include /* only present in VS2005+ */ -# pragma warning(disable : 4127) /* disable: C4127: conditional expression is constant */ -# pragma warning(disable : 6237) /* disable: C6237: conditional expression is always 0 */ -# pragma warning(disable : 6239) /* disable: C6239: ( && ) always evaluates to the result of */ -# pragma warning(disable : 6240) /* disable: C6240: ( && ) always evaluates to the result of */ -# pragma warning(disable : 6326) /* disable: C6326: Potential comparison of a constant with another constant */ -#endif /* _MSC_VER */ - -#ifndef LZ4_FORCE_INLINE -# if defined (_MSC_VER) && !defined (__clang__) /* MSVC */ -# define LZ4_FORCE_INLINE static __forceinline -# else -# if defined (__cplusplus) || defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L /* C99 */ -# if defined (__GNUC__) || defined (__clang__) -# define LZ4_FORCE_INLINE static inline __attribute__((always_inline)) -# else -# define LZ4_FORCE_INLINE static inline -# endif -# else -# define LZ4_FORCE_INLINE static -# endif /* __STDC_VERSION__ */ -# endif /* _MSC_VER */ -#endif /* LZ4_FORCE_INLINE */ - -/* LZ4_FORCE_O2 and LZ4_FORCE_INLINE - * gcc on ppc64le generates an unrolled SIMDized loop for LZ4_wildCopy8, - * together with a simple 8-byte copy loop as a fall-back path. - * However, this optimization hurts the decompression speed by >30%, - * because the execution does not go to the optimized loop - * for typical compressible data, and all of the preamble checks - * before going to the fall-back path become useless overhead. - * This optimization happens only with the -O3 flag, and -O2 generates - * a simple 8-byte copy loop. - * With gcc on ppc64le, all of the LZ4_decompress_* and LZ4_wildCopy8 - * functions are annotated with __attribute__((optimize("O2"))), - * and also LZ4_wildCopy8 is forcibly inlined, so that the O2 attribute - * of LZ4_wildCopy8 does not affect the compression speed. - */ -#if defined(__PPC64__) && defined(__LITTLE_ENDIAN__) && defined(__GNUC__) && !defined(__clang__) -# define LZ4_FORCE_O2 __attribute__((optimize("O2"))) -# undef LZ4_FORCE_INLINE -# define LZ4_FORCE_INLINE static __inline __attribute__((optimize("O2"),always_inline)) -#else -# define LZ4_FORCE_O2 -#endif - -#if (defined(__GNUC__) && (__GNUC__ >= 3)) || (defined(__INTEL_COMPILER) && (__INTEL_COMPILER >= 800)) || defined(__clang__) -# define expect(expr,value) (__builtin_expect ((expr),(value)) ) -#else -# define expect(expr,value) (expr) -#endif - -#ifndef likely -#define likely(expr) expect((expr) != 0, 1) -#endif -#ifndef unlikely -#define unlikely(expr) expect((expr) != 0, 0) -#endif - -/* Should the alignment test prove unreliable, for some reason, - * it can be disabled by setting LZ4_ALIGN_TEST to 0 */ -#ifndef LZ4_ALIGN_TEST /* can be externally provided */ -# define LZ4_ALIGN_TEST 1 -#endif - - -/*-************************************ -* Memory routines -**************************************/ - -/*! LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION : - * Disable relatively high-level LZ4/HC functions that use dynamic memory - * allocation functions (malloc(), calloc(), free()). - * - * Note that this is a compile-time switch. And since it disables - * public/stable LZ4 v1 API functions, we don't recommend using this - * symbol to generate a library for distribution. - * - * The following public functions are removed when this symbol is defined. - * - lz4 : LZ4_createStream, LZ4_freeStream, - * LZ4_createStreamDecode, LZ4_freeStreamDecode, LZ4_create (deprecated) - * - lz4hc : LZ4_createStreamHC, LZ4_freeStreamHC, - * LZ4_createHC (deprecated), LZ4_freeHC (deprecated) - * - lz4frame, lz4file : All LZ4F_* functions - */ -#if defined(LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION) -# define ALLOC(s) lz4_error_memory_allocation_is_disabled -# define ALLOC_AND_ZERO(s) lz4_error_memory_allocation_is_disabled -# define FREEMEM(p) lz4_error_memory_allocation_is_disabled -#elif defined(LZ4_USER_MEMORY_FUNCTIONS) -/* memory management functions can be customized by user project. - * Below functions must exist somewhere in the Project - * and be available at link time */ -void* LZ4_malloc(size_t s); -void* LZ4_calloc(size_t n, size_t s); -void LZ4_free(void* p); -# define ALLOC(s) LZ4_malloc(s) -# define ALLOC_AND_ZERO(s) LZ4_calloc(1,s) -# define FREEMEM(p) LZ4_free(p) -#else -# include /* malloc, calloc, free */ -# define ALLOC(s) malloc(s) -# define ALLOC_AND_ZERO(s) calloc(1,s) -# define FREEMEM(p) free(p) -#endif - -#if ! LZ4_FREESTANDING -# include /* memset, memcpy */ -#endif -#if !defined(LZ4_memset) -# define LZ4_memset(p,v,s) memset((p),(v),(s)) -#endif -#define MEM_INIT(p,v,s) LZ4_memset((p),(v),(s)) - - -/*-************************************ -* Common Constants -**************************************/ -#define MINMATCH 4 - -#define WILDCOPYLENGTH 8 -#define LASTLITERALS 5 /* see ../doc/lz4_Block_format.md#parsing-restrictions */ -#define MFLIMIT 12 /* see ../doc/lz4_Block_format.md#parsing-restrictions */ -#define MATCH_SAFEGUARD_DISTANCE ((2*WILDCOPYLENGTH) - MINMATCH) /* ensure it's possible to write 2 x wildcopyLength without overflowing output buffer */ -#define FASTLOOP_SAFE_DISTANCE 64 -static const int LZ4_minLength = (MFLIMIT+1); - -#define LZ_KB *(1 <<10) -#define LZ_MB *(1 <<20) -#define LZ_GB *(1U<<30) - -#define LZ4_DISTANCE_ABSOLUTE_MAX 65535 -#if (LZ4_DISTANCE_MAX > LZ4_DISTANCE_ABSOLUTE_MAX) /* max supported by LZ4 format */ -# error "LZ4_DISTANCE_MAX is too big : must be <= 65535" -#endif - -#define ML_BITS 4 -#define ML_MASK ((1U<=1) -# include -#else -# ifndef assert -# define assert(condition) ((void)0) -# endif -#endif - -#define LZ4_STATIC_ASSERT(c) { enum { LZ4_static_assert = 1/(int)(!!(c)) }; } /* use after variable declarations */ - -#if defined(LZ4_DEBUG) && (LZ4_DEBUG>=2) -# include - static int g_debuglog_enable = 1; -# define DEBUGLOG(l, ...) { \ - if ((g_debuglog_enable) && (l<=LZ4_DEBUG)) { \ - fprintf(stderr, __FILE__ " %i: ", __LINE__); \ - fprintf(stderr, __VA_ARGS__); \ - fprintf(stderr, " \n"); \ - } } -#else -# define DEBUGLOG(l, ...) {} /* disabled */ -#endif - -static int LZ4_isAligned(const void* ptr, size_t alignment) -{ - return ((size_t)ptr & (alignment -1)) == 0; -} - - -/*-************************************ -* Types -**************************************/ -#include -#if defined(__cplusplus) || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) -# include - typedef uint8_t BYTE; - typedef uint16_t U16; - typedef uint32_t U32; - typedef int32_t S32; - typedef uint64_t U64; - typedef uintptr_t uptrval; -#else -# if UINT_MAX != 4294967295UL -# error "LZ4 code (when not C++ or C99) assumes that sizeof(int) == 4" -# endif - typedef unsigned char BYTE; - typedef unsigned short U16; - typedef unsigned int U32; - typedef signed int S32; - typedef unsigned long long U64; - typedef size_t uptrval; /* generally true, except OpenVMS-64 */ -#endif - -#if defined(__x86_64__) - typedef U64 reg_t; /* 64-bits in x32 mode */ -#else - typedef size_t reg_t; /* 32-bits in x32 mode */ -#endif - -typedef enum { - notLimited = 0, - limitedOutput = 1, - fillOutput = 2 -} limitedOutput_directive; - - -/*-************************************ -* Reading and writing into memory -**************************************/ - -/** - * LZ4 relies on memcpy with a constant size being inlined. In freestanding - * environments, the compiler can't assume the implementation of memcpy() is - * standard compliant, so it can't apply its specialized memcpy() inlining - * logic. When possible, use __builtin_memcpy() to tell the compiler to analyze - * memcpy() as if it were standard compliant, so it can inline it in freestanding - * environments. This is needed when decompressing the Linux Kernel, for example. - */ -#if !defined(LZ4_memcpy) -# if defined(__GNUC__) && (__GNUC__ >= 4) -# define LZ4_memcpy(dst, src, size) __builtin_memcpy(dst, src, size) -# else -# define LZ4_memcpy(dst, src, size) memcpy(dst, src, size) -# endif -#endif - -#if !defined(LZ4_memmove) -# if defined(__GNUC__) && (__GNUC__ >= 4) -# define LZ4_memmove __builtin_memmove -# else -# define LZ4_memmove memmove -# endif -#endif - -static unsigned LZ4_isLittleEndian(void) -{ - const union { U32 u; BYTE c[4]; } one = { 1 }; /* don't use static : performance detrimental */ - return one.c[0]; -} - -#if defined(__GNUC__) || defined(__INTEL_COMPILER) -#define LZ4_PACK( __Declaration__ ) __Declaration__ __attribute__((__packed__)) -#elif defined(_MSC_VER) -#define LZ4_PACK( __Declaration__ ) __pragma( pack(push, 1) ) __Declaration__ __pragma( pack(pop)) -#endif - -#if defined(LZ4_FORCE_MEMORY_ACCESS) && (LZ4_FORCE_MEMORY_ACCESS==2) -/* lie to the compiler about data alignment; use with caution */ - -static U16 LZ4_read16(const void* memPtr) { return *(const U16*) memPtr; } -static U32 LZ4_read32(const void* memPtr) { return *(const U32*) memPtr; } -static reg_t LZ4_read_ARCH(const void* memPtr) { return *(const reg_t*) memPtr; } - -static void LZ4_write16(void* memPtr, U16 value) { *(U16*)memPtr = value; } -static void LZ4_write32(void* memPtr, U32 value) { *(U32*)memPtr = value; } - -#elif defined(LZ4_FORCE_MEMORY_ACCESS) && (LZ4_FORCE_MEMORY_ACCESS==1) - -/* __pack instructions are safer, but compiler specific, hence potentially problematic for some compilers */ -/* currently only defined for gcc and icc */ -LZ4_PACK(typedef struct { U16 u16; }) LZ4_unalign16; -LZ4_PACK(typedef struct { U32 u32; }) LZ4_unalign32; -LZ4_PACK(typedef struct { reg_t uArch; }) LZ4_unalignST; - -static U16 LZ4_read16(const void* ptr) { return ((const LZ4_unalign16*)ptr)->u16; } -static U32 LZ4_read32(const void* ptr) { return ((const LZ4_unalign32*)ptr)->u32; } -static reg_t LZ4_read_ARCH(const void* ptr) { return ((const LZ4_unalignST*)ptr)->uArch; } - -static void LZ4_write16(void* memPtr, U16 value) { ((LZ4_unalign16*)memPtr)->u16 = value; } -static void LZ4_write32(void* memPtr, U32 value) { ((LZ4_unalign32*)memPtr)->u32 = value; } - -#else /* safe and portable access using memcpy() */ - -static U16 LZ4_read16(const void* memPtr) -{ - U16 val; LZ4_memcpy(&val, memPtr, sizeof(val)); return val; -} - -static U32 LZ4_read32(const void* memPtr) -{ - U32 val; LZ4_memcpy(&val, memPtr, sizeof(val)); return val; -} - -static reg_t LZ4_read_ARCH(const void* memPtr) -{ - reg_t val; LZ4_memcpy(&val, memPtr, sizeof(val)); return val; -} - -static void LZ4_write16(void* memPtr, U16 value) -{ - LZ4_memcpy(memPtr, &value, sizeof(value)); -} - -static void LZ4_write32(void* memPtr, U32 value) -{ - LZ4_memcpy(memPtr, &value, sizeof(value)); -} - -#endif /* LZ4_FORCE_MEMORY_ACCESS */ - - -static U16 LZ4_readLE16(const void* memPtr) -{ - if (LZ4_isLittleEndian()) { - return LZ4_read16(memPtr); - } else { - const BYTE* p = (const BYTE*)memPtr; - return (U16)((U16)p[0] | (p[1]<<8)); - } -} - -#ifdef LZ4_STATIC_LINKING_ONLY_ENDIANNESS_INDEPENDENT_OUTPUT -static U32 LZ4_readLE32(const void* memPtr) -{ - if (LZ4_isLittleEndian()) { - return LZ4_read32(memPtr); - } else { - const BYTE* p = (const BYTE*)memPtr; - return (U32)p[0] | (p[1]<<8) | (p[2]<<16) | (p[3]<<24); - } -} -#endif - -static void LZ4_writeLE16(void* memPtr, U16 value) -{ - if (LZ4_isLittleEndian()) { - LZ4_write16(memPtr, value); - } else { - BYTE* p = (BYTE*)memPtr; - p[0] = (BYTE) value; - p[1] = (BYTE)(value>>8); - } -} - -/* customized variant of memcpy, which can overwrite up to 8 bytes beyond dstEnd */ -LZ4_FORCE_INLINE -void LZ4_wildCopy8(void* dstPtr, const void* srcPtr, void* dstEnd) -{ - BYTE* d = (BYTE*)dstPtr; - const BYTE* s = (const BYTE*)srcPtr; - BYTE* const e = (BYTE*)dstEnd; - - do { LZ4_memcpy(d,s,8); d+=8; s+=8; } while (d= 16. */ -LZ4_FORCE_INLINE void -LZ4_wildCopy32(void* dstPtr, const void* srcPtr, void* dstEnd) -{ - BYTE* d = (BYTE*)dstPtr; - const BYTE* s = (const BYTE*)srcPtr; - BYTE* const e = (BYTE*)dstEnd; - - do { LZ4_memcpy(d,s,16); LZ4_memcpy(d+16,s+16,16); d+=32; s+=32; } while (d= dstPtr + MINMATCH - * - there is at least 12 bytes available to write after dstEnd */ -LZ4_FORCE_INLINE void -LZ4_memcpy_using_offset(BYTE* dstPtr, const BYTE* srcPtr, BYTE* dstEnd, const size_t offset) -{ - BYTE v[8]; - - assert(dstEnd >= dstPtr + MINMATCH); - - switch(offset) { - case 1: - MEM_INIT(v, *srcPtr, 8); - break; - case 2: - LZ4_memcpy(v, srcPtr, 2); - LZ4_memcpy(&v[2], srcPtr, 2); -#if defined(_MSC_VER) && (_MSC_VER <= 1937) /* MSVC 2022 ver 17.7 or earlier */ -# pragma warning(push) -# pragma warning(disable : 6385) /* warning C6385: Reading invalid data from 'v'. */ -#endif - LZ4_memcpy(&v[4], v, 4); -#if defined(_MSC_VER) && (_MSC_VER <= 1937) /* MSVC 2022 ver 17.7 or earlier */ -# pragma warning(pop) -#endif - break; - case 4: - LZ4_memcpy(v, srcPtr, 4); - LZ4_memcpy(&v[4], srcPtr, 4); - break; - default: - LZ4_memcpy_using_offset_base(dstPtr, srcPtr, dstEnd, offset); - return; - } - - LZ4_memcpy(dstPtr, v, 8); - dstPtr += 8; - while (dstPtr < dstEnd) { - LZ4_memcpy(dstPtr, v, 8); - dstPtr += 8; - } -} -#endif - - -/*-************************************ -* Common functions -**************************************/ -static unsigned LZ4_NbCommonBytes (reg_t val) -{ - assert(val != 0); - if (LZ4_isLittleEndian()) { - if (sizeof(val) == 8) { -# if defined(_MSC_VER) && (_MSC_VER >= 1800) && (defined(_M_AMD64) && !defined(_M_ARM64EC)) && !defined(LZ4_FORCE_SW_BITCOUNT) -/*-************************************************************************************************* -* ARM64EC is a Microsoft-designed ARM64 ABI compatible with AMD64 applications on ARM64 Windows 11. -* The ARM64EC ABI does not support AVX/AVX2/AVX512 instructions, nor their relevant intrinsics -* including _tzcnt_u64. Therefore, we need to neuter the _tzcnt_u64 code path for ARM64EC. -****************************************************************************************************/ -# if defined(__clang__) && (__clang_major__ < 10) - /* Avoid undefined clang-cl intrinsics issue. - * See https://github.com/lz4/lz4/pull/1017 for details. */ - return (unsigned)__builtin_ia32_tzcnt_u64(val) >> 3; -# else - /* x64 CPUS without BMI support interpret `TZCNT` as `REP BSF` */ - return (unsigned)_tzcnt_u64(val) >> 3; -# endif -# elif defined(_MSC_VER) && defined(_WIN64) && !defined(LZ4_FORCE_SW_BITCOUNT) - unsigned long r = 0; - _BitScanForward64(&r, (U64)val); - return (unsigned)r >> 3; -# elif (defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 3) || \ - ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 4))))) && \ - !defined(LZ4_FORCE_SW_BITCOUNT) - return (unsigned)__builtin_ctzll((U64)val) >> 3; -# else - const U64 m = 0x0101010101010101ULL; - val ^= val - 1; - return (unsigned)(((U64)((val & (m - 1)) * m)) >> 56); -# endif - } else /* 32 bits */ { -# if defined(_MSC_VER) && (_MSC_VER >= 1400) && !defined(LZ4_FORCE_SW_BITCOUNT) - unsigned long r; - _BitScanForward(&r, (U32)val); - return (unsigned)r >> 3; -# elif (defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 3) || \ - ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 4))))) && \ - !defined(__TINYC__) && !defined(LZ4_FORCE_SW_BITCOUNT) - return (unsigned)__builtin_ctz((U32)val) >> 3; -# else - const U32 m = 0x01010101; - return (unsigned)((((val - 1) ^ val) & (m - 1)) * m) >> 24; -# endif - } - } else /* Big Endian CPU */ { - if (sizeof(val)==8) { -# if (defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 3) || \ - ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 4))))) && \ - !defined(__TINYC__) && !defined(LZ4_FORCE_SW_BITCOUNT) - return (unsigned)__builtin_clzll((U64)val) >> 3; -# else -#if 1 - /* this method is probably faster, - * but adds a 128 bytes lookup table */ - static const unsigned char ctz7_tab[128] = { - 7, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, - 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, - 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, - 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, - 6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, - 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, - 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, - 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, - }; - U64 const mask = 0x0101010101010101ULL; - U64 const t = (((val >> 8) - mask) | val) & mask; - return ctz7_tab[(t * 0x0080402010080402ULL) >> 57]; -#else - /* this method doesn't consume memory space like the previous one, - * but it contains several branches, - * that may end up slowing execution */ - static const U32 by32 = sizeof(val)*4; /* 32 on 64 bits (goal), 16 on 32 bits. - Just to avoid some static analyzer complaining about shift by 32 on 32-bits target. - Note that this code path is never triggered in 32-bits mode. */ - unsigned r; - if (!(val>>by32)) { r=4; } else { r=0; val>>=by32; } - if (!(val>>16)) { r+=2; val>>=8; } else { val>>=24; } - r += (!val); - return r; -#endif -# endif - } else /* 32 bits */ { -# if (defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 3) || \ - ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 4))))) && \ - !defined(LZ4_FORCE_SW_BITCOUNT) - return (unsigned)__builtin_clz((U32)val) >> 3; -# else - val >>= 8; - val = ((((val + 0x00FFFF00) | 0x00FFFFFF) + val) | - (val + 0x00FF0000)) >> 24; - return (unsigned)val ^ 3; -# endif - } - } -} - - -#define STEPSIZE sizeof(reg_t) -LZ4_FORCE_INLINE -unsigned LZ4_count(const BYTE* pIn, const BYTE* pMatch, const BYTE* pInLimit) -{ - const BYTE* const pStart = pIn; - - if (likely(pIn < pInLimit-(STEPSIZE-1))) { - reg_t const diff = LZ4_read_ARCH(pMatch) ^ LZ4_read_ARCH(pIn); - if (!diff) { - pIn+=STEPSIZE; pMatch+=STEPSIZE; - } else { - return LZ4_NbCommonBytes(diff); - } } - - while (likely(pIn < pInLimit-(STEPSIZE-1))) { - reg_t const diff = LZ4_read_ARCH(pMatch) ^ LZ4_read_ARCH(pIn); - if (!diff) { pIn+=STEPSIZE; pMatch+=STEPSIZE; continue; } - pIn += LZ4_NbCommonBytes(diff); - return (unsigned)(pIn - pStart); - } - - if ((STEPSIZE==8) && (pIn<(pInLimit-3)) && (LZ4_read32(pMatch) == LZ4_read32(pIn))) { pIn+=4; pMatch+=4; } - if ((pIn<(pInLimit-1)) && (LZ4_read16(pMatch) == LZ4_read16(pIn))) { pIn+=2; pMatch+=2; } - if ((pIn compression run slower on incompressible data */ - - -/*-************************************ -* Local Structures and types -**************************************/ -typedef enum { clearedTable = 0, byPtr, byU32, byU16 } tableType_t; - -/** - * This enum distinguishes several different modes of accessing previous - * content in the stream. - * - * - noDict : There is no preceding content. - * - withPrefix64k : Table entries up to ctx->dictSize before the current blob - * blob being compressed are valid and refer to the preceding - * content (of length ctx->dictSize), which is available - * contiguously preceding in memory the content currently - * being compressed. - * - usingExtDict : Like withPrefix64k, but the preceding content is somewhere - * else in memory, starting at ctx->dictionary with length - * ctx->dictSize. - * - usingDictCtx : Everything concerning the preceding content is - * in a separate context, pointed to by ctx->dictCtx. - * ctx->dictionary, ctx->dictSize, and table entries - * in the current context that refer to positions - * preceding the beginning of the current compression are - * ignored. Instead, ctx->dictCtx->dictionary and ctx->dictCtx - * ->dictSize describe the location and size of the preceding - * content, and matches are found by looking in the ctx - * ->dictCtx->hashTable. - */ -typedef enum { noDict = 0, withPrefix64k, usingExtDict, usingDictCtx } dict_directive; -typedef enum { noDictIssue = 0, dictSmall } dictIssue_directive; - - -/*-************************************ -* Local Utils -**************************************/ -int LZ4_versionNumber (void) { return LZ4_VERSION_NUMBER; } -const char* LZ4_versionString(void) { return LZ4_VERSION_STRING; } -int LZ4_compressBound(int isize) { return LZ4_COMPRESSBOUND(isize); } -int LZ4_sizeofState(void) { return sizeof(LZ4_stream_t); } - - -/*-**************************************** -* Internal Definitions, used only in Tests -*******************************************/ -#if defined (__cplusplus) -extern "C" { -#endif - -int LZ4_compress_forceExtDict (LZ4_stream_t* LZ4_dict, const char* source, char* dest, int srcSize); - -int LZ4_decompress_safe_forceExtDict(const char* source, char* dest, - int compressedSize, int maxOutputSize, - const void* dictStart, size_t dictSize); -int LZ4_decompress_safe_partial_forceExtDict(const char* source, char* dest, - int compressedSize, int targetOutputSize, int dstCapacity, - const void* dictStart, size_t dictSize); -#if defined (__cplusplus) -} -#endif - -/*-****************************** -* Compression functions -********************************/ -LZ4_FORCE_INLINE U32 LZ4_hash4(U32 sequence, tableType_t const tableType) -{ - if (tableType == byU16) - return ((sequence * 2654435761U) >> ((MINMATCH*8)-(LZ4_HASHLOG+1))); - else - return ((sequence * 2654435761U) >> ((MINMATCH*8)-LZ4_HASHLOG)); -} - -LZ4_FORCE_INLINE U32 LZ4_hash5(U64 sequence, tableType_t const tableType) -{ - const U32 hashLog = (tableType == byU16) ? LZ4_HASHLOG+1 : LZ4_HASHLOG; - if (LZ4_isLittleEndian()) { - const U64 prime5bytes = 889523592379ULL; - return (U32)(((sequence << 24) * prime5bytes) >> (64 - hashLog)); - } else { - const U64 prime8bytes = 11400714785074694791ULL; - return (U32)(((sequence >> 24) * prime8bytes) >> (64 - hashLog)); - } -} - -LZ4_FORCE_INLINE U32 LZ4_hashPosition(const void* const p, tableType_t const tableType) -{ - if ((sizeof(reg_t)==8) && (tableType != byU16)) return LZ4_hash5(LZ4_read_ARCH(p), tableType); - -#ifdef LZ4_STATIC_LINKING_ONLY_ENDIANNESS_INDEPENDENT_OUTPUT - return LZ4_hash4(LZ4_readLE32(p), tableType); -#else - return LZ4_hash4(LZ4_read32(p), tableType); -#endif -} - -LZ4_FORCE_INLINE void LZ4_clearHash(U32 h, void* tableBase, tableType_t const tableType) -{ - switch (tableType) - { - default: /* fallthrough */ - case clearedTable: { /* illegal! */ assert(0); return; } - case byPtr: { const BYTE** hashTable = (const BYTE**)tableBase; hashTable[h] = NULL; return; } - case byU32: { U32* hashTable = (U32*) tableBase; hashTable[h] = 0; return; } - case byU16: { U16* hashTable = (U16*) tableBase; hashTable[h] = 0; return; } - } -} - -LZ4_FORCE_INLINE void LZ4_putIndexOnHash(U32 idx, U32 h, void* tableBase, tableType_t const tableType) -{ - switch (tableType) - { - default: /* fallthrough */ - case clearedTable: /* fallthrough */ - case byPtr: { /* illegal! */ assert(0); return; } - case byU32: { U32* hashTable = (U32*) tableBase; hashTable[h] = idx; return; } - case byU16: { U16* hashTable = (U16*) tableBase; assert(idx < 65536); hashTable[h] = (U16)idx; return; } - } -} - -/* LZ4_putPosition*() : only used in byPtr mode */ -LZ4_FORCE_INLINE void LZ4_putPositionOnHash(const BYTE* p, U32 h, - void* tableBase, tableType_t const tableType) -{ - const BYTE** const hashTable = (const BYTE**)tableBase; - assert(tableType == byPtr); (void)tableType; - hashTable[h] = p; -} - -LZ4_FORCE_INLINE void LZ4_putPosition(const BYTE* p, void* tableBase, tableType_t tableType) -{ - U32 const h = LZ4_hashPosition(p, tableType); - LZ4_putPositionOnHash(p, h, tableBase, tableType); -} - -/* LZ4_getIndexOnHash() : - * Index of match position registered in hash table. - * hash position must be calculated by using base+index, or dictBase+index. - * Assumption 1 : only valid if tableType == byU32 or byU16. - * Assumption 2 : h is presumed valid (within limits of hash table) - */ -LZ4_FORCE_INLINE U32 LZ4_getIndexOnHash(U32 h, const void* tableBase, tableType_t tableType) -{ - LZ4_STATIC_ASSERT(LZ4_MEMORY_USAGE > 2); - if (tableType == byU32) { - const U32* const hashTable = (const U32*) tableBase; - assert(h < (1U << (LZ4_MEMORY_USAGE-2))); - return hashTable[h]; - } - if (tableType == byU16) { - const U16* const hashTable = (const U16*) tableBase; - assert(h < (1U << (LZ4_MEMORY_USAGE-1))); - return hashTable[h]; - } - assert(0); return 0; /* forbidden case */ -} - -static const BYTE* LZ4_getPositionOnHash(U32 h, const void* tableBase, tableType_t tableType) -{ - assert(tableType == byPtr); (void)tableType; - { const BYTE* const* hashTable = (const BYTE* const*) tableBase; return hashTable[h]; } -} - -LZ4_FORCE_INLINE const BYTE* -LZ4_getPosition(const BYTE* p, - const void* tableBase, tableType_t tableType) -{ - U32 const h = LZ4_hashPosition(p, tableType); - return LZ4_getPositionOnHash(h, tableBase, tableType); -} - -LZ4_FORCE_INLINE void -LZ4_prepareTable(LZ4_stream_t_internal* const cctx, - const int inputSize, - const tableType_t tableType) { - /* If the table hasn't been used, it's guaranteed to be zeroed out, and is - * therefore safe to use no matter what mode we're in. Otherwise, we figure - * out if it's safe to leave as is or whether it needs to be reset. - */ - if ((tableType_t)cctx->tableType != clearedTable) { - assert(inputSize >= 0); - if ((tableType_t)cctx->tableType != tableType - || ((tableType == byU16) && cctx->currentOffset + (unsigned)inputSize >= 0xFFFFU) - || ((tableType == byU32) && cctx->currentOffset > 1 LZ_GB) - || tableType == byPtr - || inputSize >= 4 LZ_KB) - { - DEBUGLOG(4, "LZ4_prepareTable: Resetting table in %p", cctx); - MEM_INIT(cctx->hashTable, 0, LZ4_HASHTABLESIZE); - cctx->currentOffset = 0; - cctx->tableType = (U32)clearedTable; - } else { - DEBUGLOG(4, "LZ4_prepareTable: Re-use hash table (no reset)"); - } - } - - /* Adding a gap, so all previous entries are > LZ4_DISTANCE_MAX back, - * is faster than compressing without a gap. - * However, compressing with currentOffset == 0 is faster still, - * so we preserve that case. - */ - if (cctx->currentOffset != 0 && tableType == byU32) { - DEBUGLOG(5, "LZ4_prepareTable: adding 64KB to currentOffset"); - cctx->currentOffset += 64 LZ_KB; - } - - /* Finally, clear history */ - cctx->dictCtx = NULL; - cctx->dictionary = NULL; - cctx->dictSize = 0; -} - -/** LZ4_compress_generic_validated() : - * inlined, to ensure branches are decided at compilation time. - * The following conditions are presumed already validated: - * - source != NULL - * - inputSize > 0 - */ -LZ4_FORCE_INLINE int LZ4_compress_generic_validated( - LZ4_stream_t_internal* const cctx, - const char* const source, - char* const dest, - const int inputSize, - int* inputConsumed, /* only written when outputDirective == fillOutput */ - const int maxOutputSize, - const limitedOutput_directive outputDirective, - const tableType_t tableType, - const dict_directive dictDirective, - const dictIssue_directive dictIssue, - const int acceleration) -{ - int result; - const BYTE* ip = (const BYTE*)source; - - U32 const startIndex = cctx->currentOffset; - const BYTE* base = (const BYTE*)source - startIndex; - const BYTE* lowLimit; - - const LZ4_stream_t_internal* dictCtx = (const LZ4_stream_t_internal*) cctx->dictCtx; - const BYTE* const dictionary = - dictDirective == usingDictCtx ? dictCtx->dictionary : cctx->dictionary; - const U32 dictSize = - dictDirective == usingDictCtx ? dictCtx->dictSize : cctx->dictSize; - const U32 dictDelta = - (dictDirective == usingDictCtx) ? startIndex - dictCtx->currentOffset : 0; /* make indexes in dictCtx comparable with indexes in current context */ - - int const maybe_extMem = (dictDirective == usingExtDict) || (dictDirective == usingDictCtx); - U32 const prefixIdxLimit = startIndex - dictSize; /* used when dictDirective == dictSmall */ - const BYTE* const dictEnd = dictionary ? dictionary + dictSize : dictionary; - const BYTE* anchor = (const BYTE*) source; - const BYTE* const iend = ip + inputSize; - const BYTE* const mflimitPlusOne = iend - MFLIMIT + 1; - const BYTE* const matchlimit = iend - LASTLITERALS; - - /* the dictCtx currentOffset is indexed on the start of the dictionary, - * while a dictionary in the current context precedes the currentOffset */ - const BYTE* dictBase = (dictionary == NULL) ? NULL : - (dictDirective == usingDictCtx) ? - dictionary + dictSize - dictCtx->currentOffset : - dictionary + dictSize - startIndex; - - BYTE* op = (BYTE*) dest; - BYTE* const olimit = op + maxOutputSize; - - U32 offset = 0; - U32 forwardH; - - DEBUGLOG(5, "LZ4_compress_generic_validated: srcSize=%i, tableType=%u", inputSize, tableType); - assert(ip != NULL); - if (tableType == byU16) assert(inputSize= 1); - - lowLimit = (const BYTE*)source - (dictDirective == withPrefix64k ? dictSize : 0); - - /* Update context state */ - if (dictDirective == usingDictCtx) { - /* Subsequent linked blocks can't use the dictionary. */ - /* Instead, they use the block we just compressed. */ - cctx->dictCtx = NULL; - cctx->dictSize = (U32)inputSize; - } else { - cctx->dictSize += (U32)inputSize; - } - cctx->currentOffset += (U32)inputSize; - cctx->tableType = (U32)tableType; - - if (inputSizehashTable, byPtr); - } else { - LZ4_putIndexOnHash(startIndex, h, cctx->hashTable, tableType); - } } - ip++; forwardH = LZ4_hashPosition(ip, tableType); - - /* Main Loop */ - for ( ; ; ) { - const BYTE* match; - BYTE* token; - const BYTE* filledIp; - - /* Find a match */ - if (tableType == byPtr) { - const BYTE* forwardIp = ip; - int step = 1; - int searchMatchNb = acceleration << LZ4_skipTrigger; - do { - U32 const h = forwardH; - ip = forwardIp; - forwardIp += step; - step = (searchMatchNb++ >> LZ4_skipTrigger); - - if (unlikely(forwardIp > mflimitPlusOne)) goto _last_literals; - assert(ip < mflimitPlusOne); - - match = LZ4_getPositionOnHash(h, cctx->hashTable, tableType); - forwardH = LZ4_hashPosition(forwardIp, tableType); - LZ4_putPositionOnHash(ip, h, cctx->hashTable, tableType); - - } while ( (match+LZ4_DISTANCE_MAX < ip) - || (LZ4_read32(match) != LZ4_read32(ip)) ); - - } else { /* byU32, byU16 */ - - const BYTE* forwardIp = ip; - int step = 1; - int searchMatchNb = acceleration << LZ4_skipTrigger; - do { - U32 const h = forwardH; - U32 const current = (U32)(forwardIp - base); - U32 matchIndex = LZ4_getIndexOnHash(h, cctx->hashTable, tableType); - assert(matchIndex <= current); - assert(forwardIp - base < (ptrdiff_t)(2 LZ_GB - 1)); - ip = forwardIp; - forwardIp += step; - step = (searchMatchNb++ >> LZ4_skipTrigger); - - if (unlikely(forwardIp > mflimitPlusOne)) goto _last_literals; - assert(ip < mflimitPlusOne); - - if (dictDirective == usingDictCtx) { - if (matchIndex < startIndex) { - /* there was no match, try the dictionary */ - assert(tableType == byU32); - matchIndex = LZ4_getIndexOnHash(h, dictCtx->hashTable, byU32); - match = dictBase + matchIndex; - matchIndex += dictDelta; /* make dictCtx index comparable with current context */ - lowLimit = dictionary; - } else { - match = base + matchIndex; - lowLimit = (const BYTE*)source; - } - } else if (dictDirective == usingExtDict) { - if (matchIndex < startIndex) { - DEBUGLOG(7, "extDict candidate: matchIndex=%5u < startIndex=%5u", matchIndex, startIndex); - assert(startIndex - matchIndex >= MINMATCH); - assert(dictBase); - match = dictBase + matchIndex; - lowLimit = dictionary; - } else { - match = base + matchIndex; - lowLimit = (const BYTE*)source; - } - } else { /* single continuous memory segment */ - match = base + matchIndex; - } - forwardH = LZ4_hashPosition(forwardIp, tableType); - LZ4_putIndexOnHash(current, h, cctx->hashTable, tableType); - - DEBUGLOG(7, "candidate at pos=%u (offset=%u \n", matchIndex, current - matchIndex); - if ((dictIssue == dictSmall) && (matchIndex < prefixIdxLimit)) { continue; } /* match outside of valid area */ - assert(matchIndex < current); - if ( ((tableType != byU16) || (LZ4_DISTANCE_MAX < LZ4_DISTANCE_ABSOLUTE_MAX)) - && (matchIndex+LZ4_DISTANCE_MAX < current)) { - continue; - } /* too far */ - assert((current - matchIndex) <= LZ4_DISTANCE_MAX); /* match now expected within distance */ - - if (LZ4_read32(match) == LZ4_read32(ip)) { - if (maybe_extMem) offset = current - matchIndex; - break; /* match found */ - } - - } while(1); - } - - /* Catch up */ - filledIp = ip; - assert(ip > anchor); /* this is always true as ip has been advanced before entering the main loop */ - if ((match > lowLimit) && unlikely(ip[-1] == match[-1])) { - do { ip--; match--; } while (((ip > anchor) & (match > lowLimit)) && (unlikely(ip[-1] == match[-1]))); - } - - /* Encode Literals */ - { unsigned const litLength = (unsigned)(ip - anchor); - token = op++; - if ((outputDirective == limitedOutput) && /* Check output buffer overflow */ - (unlikely(op + litLength + (2 + 1 + LASTLITERALS) + (litLength/255) > olimit)) ) { - return 0; /* cannot compress within `dst` budget. Stored indexes in hash table are nonetheless fine */ - } - if ((outputDirective == fillOutput) && - (unlikely(op + (litLength+240)/255 /* litlen */ + litLength /* literals */ + 2 /* offset */ + 1 /* token */ + MFLIMIT - MINMATCH /* min last literals so last match is <= end - MFLIMIT */ > olimit))) { - op--; - goto _last_literals; - } - if (litLength >= RUN_MASK) { - unsigned len = litLength - RUN_MASK; - *token = (RUN_MASK<= 255 ; len-=255) *op++ = 255; - *op++ = (BYTE)len; - } - else *token = (BYTE)(litLength< olimit)) { - /* the match was too close to the end, rewind and go to last literals */ - op = token; - goto _last_literals; - } - - /* Encode Offset */ - if (maybe_extMem) { /* static test */ - DEBUGLOG(6, " with offset=%u (ext if > %i)", offset, (int)(ip - (const BYTE*)source)); - assert(offset <= LZ4_DISTANCE_MAX && offset > 0); - LZ4_writeLE16(op, (U16)offset); op+=2; - } else { - DEBUGLOG(6, " with offset=%u (same segment)", (U32)(ip - match)); - assert(ip-match <= LZ4_DISTANCE_MAX); - LZ4_writeLE16(op, (U16)(ip - match)); op+=2; - } - - /* Encode MatchLength */ - { unsigned matchCode; - - if ( (dictDirective==usingExtDict || dictDirective==usingDictCtx) - && (lowLimit==dictionary) /* match within extDict */ ) { - const BYTE* limit = ip + (dictEnd-match); - assert(dictEnd > match); - if (limit > matchlimit) limit = matchlimit; - matchCode = LZ4_count(ip+MINMATCH, match+MINMATCH, limit); - ip += (size_t)matchCode + MINMATCH; - if (ip==limit) { - unsigned const more = LZ4_count(limit, (const BYTE*)source, matchlimit); - matchCode += more; - ip += more; - } - DEBUGLOG(6, " with matchLength=%u starting in extDict", matchCode+MINMATCH); - } else { - matchCode = LZ4_count(ip+MINMATCH, match+MINMATCH, matchlimit); - ip += (size_t)matchCode + MINMATCH; - DEBUGLOG(6, " with matchLength=%u", matchCode+MINMATCH); - } - - if ((outputDirective) && /* Check output buffer overflow */ - (unlikely(op + (1 + LASTLITERALS) + (matchCode+240)/255 > olimit)) ) { - if (outputDirective == fillOutput) { - /* Match description too long : reduce it */ - U32 newMatchCode = 15 /* in token */ - 1 /* to avoid needing a zero byte */ + ((U32)(olimit - op) - 1 - LASTLITERALS) * 255; - ip -= matchCode - newMatchCode; - assert(newMatchCode < matchCode); - matchCode = newMatchCode; - if (unlikely(ip <= filledIp)) { - /* We have already filled up to filledIp so if ip ends up less than filledIp - * we have positions in the hash table beyond the current position. This is - * a problem if we reuse the hash table. So we have to remove these positions - * from the hash table. - */ - const BYTE* ptr; - DEBUGLOG(5, "Clearing %u positions", (U32)(filledIp - ip)); - for (ptr = ip; ptr <= filledIp; ++ptr) { - U32 const h = LZ4_hashPosition(ptr, tableType); - LZ4_clearHash(h, cctx->hashTable, tableType); - } - } - } else { - assert(outputDirective == limitedOutput); - return 0; /* cannot compress within `dst` budget. Stored indexes in hash table are nonetheless fine */ - } - } - if (matchCode >= ML_MASK) { - *token += ML_MASK; - matchCode -= ML_MASK; - LZ4_write32(op, 0xFFFFFFFF); - while (matchCode >= 4*255) { - op+=4; - LZ4_write32(op, 0xFFFFFFFF); - matchCode -= 4*255; - } - op += matchCode / 255; - *op++ = (BYTE)(matchCode % 255); - } else - *token += (BYTE)(matchCode); - } - /* Ensure we have enough space for the last literals. */ - assert(!(outputDirective == fillOutput && op + 1 + LASTLITERALS > olimit)); - - anchor = ip; - - /* Test end of chunk */ - if (ip >= mflimitPlusOne) break; - - /* Fill table */ - { U32 const h = LZ4_hashPosition(ip-2, tableType); - if (tableType == byPtr) { - LZ4_putPositionOnHash(ip-2, h, cctx->hashTable, byPtr); - } else { - U32 const idx = (U32)((ip-2) - base); - LZ4_putIndexOnHash(idx, h, cctx->hashTable, tableType); - } } - - /* Test next position */ - if (tableType == byPtr) { - - match = LZ4_getPosition(ip, cctx->hashTable, tableType); - LZ4_putPosition(ip, cctx->hashTable, tableType); - if ( (match+LZ4_DISTANCE_MAX >= ip) - && (LZ4_read32(match) == LZ4_read32(ip)) ) - { token=op++; *token=0; goto _next_match; } - - } else { /* byU32, byU16 */ - - U32 const h = LZ4_hashPosition(ip, tableType); - U32 const current = (U32)(ip-base); - U32 matchIndex = LZ4_getIndexOnHash(h, cctx->hashTable, tableType); - assert(matchIndex < current); - if (dictDirective == usingDictCtx) { - if (matchIndex < startIndex) { - /* there was no match, try the dictionary */ - assert(tableType == byU32); - matchIndex = LZ4_getIndexOnHash(h, dictCtx->hashTable, byU32); - match = dictBase + matchIndex; - lowLimit = dictionary; /* required for match length counter */ - matchIndex += dictDelta; - } else { - match = base + matchIndex; - lowLimit = (const BYTE*)source; /* required for match length counter */ - } - } else if (dictDirective==usingExtDict) { - if (matchIndex < startIndex) { - assert(dictBase); - match = dictBase + matchIndex; - lowLimit = dictionary; /* required for match length counter */ - } else { - match = base + matchIndex; - lowLimit = (const BYTE*)source; /* required for match length counter */ - } - } else { /* single memory segment */ - match = base + matchIndex; - } - LZ4_putIndexOnHash(current, h, cctx->hashTable, tableType); - assert(matchIndex < current); - if ( ((dictIssue==dictSmall) ? (matchIndex >= prefixIdxLimit) : 1) - && (((tableType==byU16) && (LZ4_DISTANCE_MAX == LZ4_DISTANCE_ABSOLUTE_MAX)) ? 1 : (matchIndex+LZ4_DISTANCE_MAX >= current)) - && (LZ4_read32(match) == LZ4_read32(ip)) ) { - token=op++; - *token=0; - if (maybe_extMem) offset = current - matchIndex; - DEBUGLOG(6, "seq.start:%i, literals=%u, match.start:%i", - (int)(anchor-(const BYTE*)source), 0, (int)(ip-(const BYTE*)source)); - goto _next_match; - } - } - - /* Prepare next loop */ - forwardH = LZ4_hashPosition(++ip, tableType); - - } - -_last_literals: - /* Encode Last Literals */ - { size_t lastRun = (size_t)(iend - anchor); - if ( (outputDirective) && /* Check output buffer overflow */ - (op + lastRun + 1 + ((lastRun+255-RUN_MASK)/255) > olimit)) { - if (outputDirective == fillOutput) { - /* adapt lastRun to fill 'dst' */ - assert(olimit >= op); - lastRun = (size_t)(olimit-op) - 1/*token*/; - lastRun -= (lastRun + 256 - RUN_MASK) / 256; /*additional length tokens*/ - } else { - assert(outputDirective == limitedOutput); - return 0; /* cannot compress within `dst` budget. Stored indexes in hash table are nonetheless fine */ - } - } - DEBUGLOG(6, "Final literal run : %i literals", (int)lastRun); - if (lastRun >= RUN_MASK) { - size_t accumulator = lastRun - RUN_MASK; - *op++ = RUN_MASK << ML_BITS; - for(; accumulator >= 255 ; accumulator-=255) *op++ = 255; - *op++ = (BYTE) accumulator; - } else { - *op++ = (BYTE)(lastRun< 0); - DEBUGLOG(5, "LZ4_compress_generic: compressed %i bytes into %i bytes", inputSize, result); - return result; -} - -/** LZ4_compress_generic() : - * inlined, to ensure branches are decided at compilation time; - * takes care of src == (NULL, 0) - * and forward the rest to LZ4_compress_generic_validated */ -LZ4_FORCE_INLINE int LZ4_compress_generic( - LZ4_stream_t_internal* const cctx, - const char* const src, - char* const dst, - const int srcSize, - int *inputConsumed, /* only written when outputDirective == fillOutput */ - const int dstCapacity, - const limitedOutput_directive outputDirective, - const tableType_t tableType, - const dict_directive dictDirective, - const dictIssue_directive dictIssue, - const int acceleration) -{ - DEBUGLOG(5, "LZ4_compress_generic: srcSize=%i, dstCapacity=%i", - srcSize, dstCapacity); - - if ((U32)srcSize > (U32)LZ4_MAX_INPUT_SIZE) { return 0; } /* Unsupported srcSize, too large (or negative) */ - if (srcSize == 0) { /* src == NULL supported if srcSize == 0 */ - if (outputDirective != notLimited && dstCapacity <= 0) return 0; /* no output, can't write anything */ - DEBUGLOG(5, "Generating an empty block"); - assert(outputDirective == notLimited || dstCapacity >= 1); - assert(dst != NULL); - dst[0] = 0; - if (outputDirective == fillOutput) { - assert (inputConsumed != NULL); - *inputConsumed = 0; - } - return 1; - } - assert(src != NULL); - - return LZ4_compress_generic_validated(cctx, src, dst, srcSize, - inputConsumed, /* only written into if outputDirective == fillOutput */ - dstCapacity, outputDirective, - tableType, dictDirective, dictIssue, acceleration); -} - - -int LZ4_compress_fast_extState(void* state, const char* source, char* dest, int inputSize, int maxOutputSize, int acceleration) -{ - LZ4_stream_t_internal* const ctx = & LZ4_initStream(state, sizeof(LZ4_stream_t)) -> internal_donotuse; - assert(ctx != NULL); - if (acceleration < 1) acceleration = LZ4_ACCELERATION_DEFAULT; - if (acceleration > LZ4_ACCELERATION_MAX) acceleration = LZ4_ACCELERATION_MAX; - if (maxOutputSize >= LZ4_compressBound(inputSize)) { - if (inputSize < LZ4_64Klimit) { - return LZ4_compress_generic(ctx, source, dest, inputSize, NULL, 0, notLimited, byU16, noDict, noDictIssue, acceleration); - } else { - const tableType_t tableType = ((sizeof(void*)==4) && ((uptrval)source > LZ4_DISTANCE_MAX)) ? byPtr : byU32; - return LZ4_compress_generic(ctx, source, dest, inputSize, NULL, 0, notLimited, tableType, noDict, noDictIssue, acceleration); - } - } else { - if (inputSize < LZ4_64Klimit) { - return LZ4_compress_generic(ctx, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, byU16, noDict, noDictIssue, acceleration); - } else { - const tableType_t tableType = ((sizeof(void*)==4) && ((uptrval)source > LZ4_DISTANCE_MAX)) ? byPtr : byU32; - return LZ4_compress_generic(ctx, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, tableType, noDict, noDictIssue, acceleration); - } - } -} - -/** - * LZ4_compress_fast_extState_fastReset() : - * A variant of LZ4_compress_fast_extState(). - * - * Using this variant avoids an expensive initialization step. It is only safe - * to call if the state buffer is known to be correctly initialized already - * (see comment in lz4.h on LZ4_resetStream_fast() for a definition of - * "correctly initialized"). - */ -int LZ4_compress_fast_extState_fastReset(void* state, const char* src, char* dst, int srcSize, int dstCapacity, int acceleration) -{ - LZ4_stream_t_internal* const ctx = &((LZ4_stream_t*)state)->internal_donotuse; - if (acceleration < 1) acceleration = LZ4_ACCELERATION_DEFAULT; - if (acceleration > LZ4_ACCELERATION_MAX) acceleration = LZ4_ACCELERATION_MAX; - assert(ctx != NULL); - - if (dstCapacity >= LZ4_compressBound(srcSize)) { - if (srcSize < LZ4_64Klimit) { - const tableType_t tableType = byU16; - LZ4_prepareTable(ctx, srcSize, tableType); - if (ctx->currentOffset) { - return LZ4_compress_generic(ctx, src, dst, srcSize, NULL, 0, notLimited, tableType, noDict, dictSmall, acceleration); - } else { - return LZ4_compress_generic(ctx, src, dst, srcSize, NULL, 0, notLimited, tableType, noDict, noDictIssue, acceleration); - } - } else { - const tableType_t tableType = ((sizeof(void*)==4) && ((uptrval)src > LZ4_DISTANCE_MAX)) ? byPtr : byU32; - LZ4_prepareTable(ctx, srcSize, tableType); - return LZ4_compress_generic(ctx, src, dst, srcSize, NULL, 0, notLimited, tableType, noDict, noDictIssue, acceleration); - } - } else { - if (srcSize < LZ4_64Klimit) { - const tableType_t tableType = byU16; - LZ4_prepareTable(ctx, srcSize, tableType); - if (ctx->currentOffset) { - return LZ4_compress_generic(ctx, src, dst, srcSize, NULL, dstCapacity, limitedOutput, tableType, noDict, dictSmall, acceleration); - } else { - return LZ4_compress_generic(ctx, src, dst, srcSize, NULL, dstCapacity, limitedOutput, tableType, noDict, noDictIssue, acceleration); - } - } else { - const tableType_t tableType = ((sizeof(void*)==4) && ((uptrval)src > LZ4_DISTANCE_MAX)) ? byPtr : byU32; - LZ4_prepareTable(ctx, srcSize, tableType); - return LZ4_compress_generic(ctx, src, dst, srcSize, NULL, dstCapacity, limitedOutput, tableType, noDict, noDictIssue, acceleration); - } - } -} - - -int LZ4_compress_fast(const char* src, char* dest, int srcSize, int dstCapacity, int acceleration) -{ - int result; -#if (LZ4_HEAPMODE) - LZ4_stream_t* const ctxPtr = (LZ4_stream_t*)ALLOC(sizeof(LZ4_stream_t)); /* malloc-calloc always properly aligned */ - if (ctxPtr == NULL) return 0; -#else - LZ4_stream_t ctx; - LZ4_stream_t* const ctxPtr = &ctx; -#endif - result = LZ4_compress_fast_extState(ctxPtr, src, dest, srcSize, dstCapacity, acceleration); - -#if (LZ4_HEAPMODE) - FREEMEM(ctxPtr); -#endif - return result; -} - - -int LZ4_compress_default(const char* src, char* dst, int srcSize, int dstCapacity) -{ - return LZ4_compress_fast(src, dst, srcSize, dstCapacity, 1); -} - - -/* Note!: This function leaves the stream in an unclean/broken state! - * It is not safe to subsequently use the same state with a _fastReset() or - * _continue() call without resetting it. */ -static int LZ4_compress_destSize_extState_internal(LZ4_stream_t* state, const char* src, char* dst, int* srcSizePtr, int targetDstSize, int acceleration) -{ - void* const s = LZ4_initStream(state, sizeof (*state)); - assert(s != NULL); (void)s; - - if (targetDstSize >= LZ4_compressBound(*srcSizePtr)) { /* compression success is guaranteed */ - return LZ4_compress_fast_extState(state, src, dst, *srcSizePtr, targetDstSize, acceleration); - } else { - if (*srcSizePtr < LZ4_64Klimit) { - return LZ4_compress_generic(&state->internal_donotuse, src, dst, *srcSizePtr, srcSizePtr, targetDstSize, fillOutput, byU16, noDict, noDictIssue, acceleration); - } else { - tableType_t const addrMode = ((sizeof(void*)==4) && ((uptrval)src > LZ4_DISTANCE_MAX)) ? byPtr : byU32; - return LZ4_compress_generic(&state->internal_donotuse, src, dst, *srcSizePtr, srcSizePtr, targetDstSize, fillOutput, addrMode, noDict, noDictIssue, acceleration); - } } -} - -int LZ4_compress_destSize_extState(void* state, const char* src, char* dst, int* srcSizePtr, int targetDstSize, int acceleration) -{ - int const r = LZ4_compress_destSize_extState_internal((LZ4_stream_t*)state, src, dst, srcSizePtr, targetDstSize, acceleration); - /* clean the state on exit */ - LZ4_initStream(state, sizeof (LZ4_stream_t)); - return r; -} - - -int LZ4_compress_destSize(const char* src, char* dst, int* srcSizePtr, int targetDstSize) -{ -#if (LZ4_HEAPMODE) - LZ4_stream_t* const ctx = (LZ4_stream_t*)ALLOC(sizeof(LZ4_stream_t)); /* malloc-calloc always properly aligned */ - if (ctx == NULL) return 0; -#else - LZ4_stream_t ctxBody; - LZ4_stream_t* const ctx = &ctxBody; -#endif - - int result = LZ4_compress_destSize_extState_internal(ctx, src, dst, srcSizePtr, targetDstSize, 1); - -#if (LZ4_HEAPMODE) - FREEMEM(ctx); -#endif - return result; -} - - - -/*-****************************** -* Streaming functions -********************************/ - -#if !defined(LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION) -LZ4_stream_t* LZ4_createStream(void) -{ - LZ4_stream_t* const lz4s = (LZ4_stream_t*)ALLOC(sizeof(LZ4_stream_t)); - LZ4_STATIC_ASSERT(sizeof(LZ4_stream_t) >= sizeof(LZ4_stream_t_internal)); - DEBUGLOG(4, "LZ4_createStream %p", lz4s); - if (lz4s == NULL) return NULL; - LZ4_initStream(lz4s, sizeof(*lz4s)); - return lz4s; -} -#endif - -static size_t LZ4_stream_t_alignment(void) -{ -#if LZ4_ALIGN_TEST - typedef struct { char c; LZ4_stream_t t; } t_a; - return sizeof(t_a) - sizeof(LZ4_stream_t); -#else - return 1; /* effectively disabled */ -#endif -} - -LZ4_stream_t* LZ4_initStream (void* buffer, size_t size) -{ - DEBUGLOG(5, "LZ4_initStream"); - if (buffer == NULL) { return NULL; } - if (size < sizeof(LZ4_stream_t)) { return NULL; } - if (!LZ4_isAligned(buffer, LZ4_stream_t_alignment())) return NULL; - MEM_INIT(buffer, 0, sizeof(LZ4_stream_t_internal)); - return (LZ4_stream_t*)buffer; -} - -/* resetStream is now deprecated, - * prefer initStream() which is more general */ -void LZ4_resetStream (LZ4_stream_t* LZ4_stream) -{ - DEBUGLOG(5, "LZ4_resetStream (ctx:%p)", LZ4_stream); - MEM_INIT(LZ4_stream, 0, sizeof(LZ4_stream_t_internal)); -} - -void LZ4_resetStream_fast(LZ4_stream_t* ctx) { - LZ4_prepareTable(&(ctx->internal_donotuse), 0, byU32); -} - -#if !defined(LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION) -int LZ4_freeStream (LZ4_stream_t* LZ4_stream) -{ - if (!LZ4_stream) return 0; /* support free on NULL */ - DEBUGLOG(5, "LZ4_freeStream %p", LZ4_stream); - FREEMEM(LZ4_stream); - return (0); -} -#endif - - -typedef enum { _ld_fast, _ld_slow } LoadDict_mode_e; -#define HASH_UNIT sizeof(reg_t) -int LZ4_loadDict_internal(LZ4_stream_t* LZ4_dict, - const char* dictionary, int dictSize, - LoadDict_mode_e _ld) -{ - LZ4_stream_t_internal* const dict = &LZ4_dict->internal_donotuse; - const tableType_t tableType = byU32; - const BYTE* p = (const BYTE*)dictionary; - const BYTE* const dictEnd = p + dictSize; - U32 idx32; - - DEBUGLOG(4, "LZ4_loadDict (%i bytes from %p into %p)", dictSize, dictionary, LZ4_dict); - - /* It's necessary to reset the context, - * and not just continue it with prepareTable() - * to avoid any risk of generating overflowing matchIndex - * when compressing using this dictionary */ - LZ4_resetStream(LZ4_dict); - - /* We always increment the offset by 64 LZ_KB, since, if the dict is longer, - * we truncate it to the last 64k, and if it's shorter, we still want to - * advance by a whole window length so we can provide the guarantee that - * there are only valid offsets in the window, which allows an optimization - * in LZ4_compress_fast_continue() where it uses noDictIssue even when the - * dictionary isn't a full 64k. */ - dict->currentOffset += 64 LZ_KB; - - if (dictSize < (int)HASH_UNIT) { - return 0; - } - - if ((dictEnd - p) > 64 LZ_KB) p = dictEnd - 64 LZ_KB; - dict->dictionary = p; - dict->dictSize = (U32)(dictEnd - p); - dict->tableType = (U32)tableType; - idx32 = dict->currentOffset - dict->dictSize; - - while (p <= dictEnd-HASH_UNIT) { - U32 const h = LZ4_hashPosition(p, tableType); - /* Note: overwriting => favors positions end of dictionary */ - LZ4_putIndexOnHash(idx32, h, dict->hashTable, tableType); - p+=3; idx32+=3; - } - - if (_ld == _ld_slow) { - /* Fill hash table with additional references, to improve compression capability */ - p = dict->dictionary; - idx32 = dict->currentOffset - dict->dictSize; - while (p <= dictEnd-HASH_UNIT) { - U32 const h = LZ4_hashPosition(p, tableType); - U32 const limit = dict->currentOffset - 64 LZ_KB; - if (LZ4_getIndexOnHash(h, dict->hashTable, tableType) <= limit) { - /* Note: not overwriting => favors positions beginning of dictionary */ - LZ4_putIndexOnHash(idx32, h, dict->hashTable, tableType); - } - p++; idx32++; - } - } - - return (int)dict->dictSize; -} - -int LZ4_loadDict(LZ4_stream_t* LZ4_dict, const char* dictionary, int dictSize) -{ - return LZ4_loadDict_internal(LZ4_dict, dictionary, dictSize, _ld_fast); -} - -int LZ4_loadDictSlow(LZ4_stream_t* LZ4_dict, const char* dictionary, int dictSize) -{ - return LZ4_loadDict_internal(LZ4_dict, dictionary, dictSize, _ld_slow); -} - -void LZ4_attach_dictionary(LZ4_stream_t* workingStream, const LZ4_stream_t* dictionaryStream) -{ - const LZ4_stream_t_internal* dictCtx = (dictionaryStream == NULL) ? NULL : - &(dictionaryStream->internal_donotuse); - - DEBUGLOG(4, "LZ4_attach_dictionary (%p, %p, size %u)", - workingStream, dictionaryStream, - dictCtx != NULL ? dictCtx->dictSize : 0); - - if (dictCtx != NULL) { - /* If the current offset is zero, we will never look in the - * external dictionary context, since there is no value a table - * entry can take that indicate a miss. In that case, we need - * to bump the offset to something non-zero. - */ - if (workingStream->internal_donotuse.currentOffset == 0) { - workingStream->internal_donotuse.currentOffset = 64 LZ_KB; - } - - /* Don't actually attach an empty dictionary. - */ - if (dictCtx->dictSize == 0) { - dictCtx = NULL; - } - } - workingStream->internal_donotuse.dictCtx = dictCtx; -} - - -static void LZ4_renormDictT(LZ4_stream_t_internal* LZ4_dict, int nextSize) -{ - assert(nextSize >= 0); - if (LZ4_dict->currentOffset + (unsigned)nextSize > 0x80000000) { /* potential ptrdiff_t overflow (32-bits mode) */ - /* rescale hash table */ - U32 const delta = LZ4_dict->currentOffset - 64 LZ_KB; - const BYTE* dictEnd = LZ4_dict->dictionary + LZ4_dict->dictSize; - int i; - DEBUGLOG(4, "LZ4_renormDictT"); - for (i=0; ihashTable[i] < delta) LZ4_dict->hashTable[i]=0; - else LZ4_dict->hashTable[i] -= delta; - } - LZ4_dict->currentOffset = 64 LZ_KB; - if (LZ4_dict->dictSize > 64 LZ_KB) LZ4_dict->dictSize = 64 LZ_KB; - LZ4_dict->dictionary = dictEnd - LZ4_dict->dictSize; - } -} - - -int LZ4_compress_fast_continue (LZ4_stream_t* LZ4_stream, - const char* source, char* dest, - int inputSize, int maxOutputSize, - int acceleration) -{ - const tableType_t tableType = byU32; - LZ4_stream_t_internal* const streamPtr = &LZ4_stream->internal_donotuse; - const char* dictEnd = streamPtr->dictSize ? (const char*)streamPtr->dictionary + streamPtr->dictSize : NULL; - - DEBUGLOG(5, "LZ4_compress_fast_continue (inputSize=%i, dictSize=%u)", inputSize, streamPtr->dictSize); - - LZ4_renormDictT(streamPtr, inputSize); /* fix index overflow */ - if (acceleration < 1) acceleration = LZ4_ACCELERATION_DEFAULT; - if (acceleration > LZ4_ACCELERATION_MAX) acceleration = LZ4_ACCELERATION_MAX; - - /* invalidate tiny dictionaries */ - if ( (streamPtr->dictSize < 4) /* tiny dictionary : not enough for a hash */ - && (dictEnd != source) /* prefix mode */ - && (inputSize > 0) /* tolerance : don't lose history, in case next invocation would use prefix mode */ - && (streamPtr->dictCtx == NULL) /* usingDictCtx */ - ) { - DEBUGLOG(5, "LZ4_compress_fast_continue: dictSize(%u) at addr:%p is too small", streamPtr->dictSize, streamPtr->dictionary); - /* remove dictionary existence from history, to employ faster prefix mode */ - streamPtr->dictSize = 0; - streamPtr->dictionary = (const BYTE*)source; - dictEnd = source; - } - - /* Check overlapping input/dictionary space */ - { const char* const sourceEnd = source + inputSize; - if ((sourceEnd > (const char*)streamPtr->dictionary) && (sourceEnd < dictEnd)) { - streamPtr->dictSize = (U32)(dictEnd - sourceEnd); - if (streamPtr->dictSize > 64 LZ_KB) streamPtr->dictSize = 64 LZ_KB; - if (streamPtr->dictSize < 4) streamPtr->dictSize = 0; - streamPtr->dictionary = (const BYTE*)dictEnd - streamPtr->dictSize; - } - } - - /* prefix mode : source data follows dictionary */ - if (dictEnd == source) { - if ((streamPtr->dictSize < 64 LZ_KB) && (streamPtr->dictSize < streamPtr->currentOffset)) - return LZ4_compress_generic(streamPtr, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, tableType, withPrefix64k, dictSmall, acceleration); - else - return LZ4_compress_generic(streamPtr, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, tableType, withPrefix64k, noDictIssue, acceleration); - } - - /* external dictionary mode */ - { int result; - if (streamPtr->dictCtx) { - /* We depend here on the fact that dictCtx'es (produced by - * LZ4_loadDict) guarantee that their tables contain no references - * to offsets between dictCtx->currentOffset - 64 LZ_KB and - * dictCtx->currentOffset - dictCtx->dictSize. This makes it safe - * to use noDictIssue even when the dict isn't a full 64 LZ_KB. - */ - if (inputSize > 4 LZ_KB) { - /* For compressing large blobs, it is faster to pay the setup - * cost to copy the dictionary's tables into the active context, - * so that the compression loop is only looking into one table. - */ - LZ4_memcpy(streamPtr, streamPtr->dictCtx, sizeof(*streamPtr)); - result = LZ4_compress_generic(streamPtr, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, tableType, usingExtDict, noDictIssue, acceleration); - } else { - result = LZ4_compress_generic(streamPtr, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, tableType, usingDictCtx, noDictIssue, acceleration); - } - } else { /* small data <= 4 LZ_KB */ - if ((streamPtr->dictSize < 64 LZ_KB) && (streamPtr->dictSize < streamPtr->currentOffset)) { - result = LZ4_compress_generic(streamPtr, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, tableType, usingExtDict, dictSmall, acceleration); - } else { - result = LZ4_compress_generic(streamPtr, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, tableType, usingExtDict, noDictIssue, acceleration); - } - } - streamPtr->dictionary = (const BYTE*)source; - streamPtr->dictSize = (U32)inputSize; - return result; - } -} - - -/* Hidden debug function, to force-test external dictionary mode */ -int LZ4_compress_forceExtDict (LZ4_stream_t* LZ4_dict, const char* source, char* dest, int srcSize) -{ - LZ4_stream_t_internal* const streamPtr = &LZ4_dict->internal_donotuse; - int result; - - LZ4_renormDictT(streamPtr, srcSize); - - if ((streamPtr->dictSize < 64 LZ_KB) && (streamPtr->dictSize < streamPtr->currentOffset)) { - result = LZ4_compress_generic(streamPtr, source, dest, srcSize, NULL, 0, notLimited, byU32, usingExtDict, dictSmall, 1); - } else { - result = LZ4_compress_generic(streamPtr, source, dest, srcSize, NULL, 0, notLimited, byU32, usingExtDict, noDictIssue, 1); - } - - streamPtr->dictionary = (const BYTE*)source; - streamPtr->dictSize = (U32)srcSize; - - return result; -} - - -/*! LZ4_saveDict() : - * If previously compressed data block is not guaranteed to remain available at its memory location, - * save it into a safer place (char* safeBuffer). - * Note : no need to call LZ4_loadDict() afterwards, dictionary is immediately usable, - * one can therefore call LZ4_compress_fast_continue() right after. - * @return : saved dictionary size in bytes (necessarily <= dictSize), or 0 if error. - */ -int LZ4_saveDict (LZ4_stream_t* LZ4_dict, char* safeBuffer, int dictSize) -{ - LZ4_stream_t_internal* const dict = &LZ4_dict->internal_donotuse; - - DEBUGLOG(5, "LZ4_saveDict : dictSize=%i, safeBuffer=%p", dictSize, safeBuffer); - - if ((U32)dictSize > 64 LZ_KB) { dictSize = 64 LZ_KB; } /* useless to define a dictionary > 64 LZ_KB */ - if ((U32)dictSize > dict->dictSize) { dictSize = (int)dict->dictSize; } - - if (safeBuffer == NULL) assert(dictSize == 0); - if (dictSize > 0) { - const BYTE* const previousDictEnd = dict->dictionary + dict->dictSize; - assert(dict->dictionary); - LZ4_memmove(safeBuffer, previousDictEnd - dictSize, (size_t)dictSize); - } - - dict->dictionary = (const BYTE*)safeBuffer; - dict->dictSize = (U32)dictSize; - - return dictSize; -} - - - -/*-******************************* - * Decompression functions - ********************************/ - -typedef enum { decode_full_block = 0, partial_decode = 1 } earlyEnd_directive; - -#undef MIN -#define MIN(a,b) ( (a) < (b) ? (a) : (b) ) - - -/* variant for decompress_unsafe() - * does not know end of input - * presumes input is well formed - * note : will consume at least one byte */ -static size_t read_long_length_no_check(const BYTE** pp) -{ - size_t b, l = 0; - do { b = **pp; (*pp)++; l += b; } while (b==255); - DEBUGLOG(6, "read_long_length_no_check: +length=%zu using %zu input bytes", l, l/255 + 1) - return l; -} - -/* core decoder variant for LZ4_decompress_fast*() - * for legacy support only : these entry points are deprecated. - * - Presumes input is correctly formed (no defense vs malformed inputs) - * - Does not know input size (presume input buffer is "large enough") - * - Decompress a full block (only) - * @return : nb of bytes read from input. - * Note : this variant is not optimized for speed, just for maintenance. - * the goal is to remove support of decompress_fast*() variants by v2.0 -**/ -LZ4_FORCE_INLINE int -LZ4_decompress_unsafe_generic( - const BYTE* const istart, - BYTE* const ostart, - int decompressedSize, - - size_t prefixSize, - const BYTE* const dictStart, /* only if dict==usingExtDict */ - const size_t dictSize /* note: =0 if dictStart==NULL */ - ) -{ - const BYTE* ip = istart; - BYTE* op = (BYTE*)ostart; - BYTE* const oend = ostart + decompressedSize; - const BYTE* const prefixStart = ostart - prefixSize; - - DEBUGLOG(5, "LZ4_decompress_unsafe_generic"); - if (dictStart == NULL) assert(dictSize == 0); - - while (1) { - /* start new sequence */ - unsigned token = *ip++; - - /* literals */ - { size_t ll = token >> ML_BITS; - if (ll==15) { - /* long literal length */ - ll += read_long_length_no_check(&ip); - } - if ((size_t)(oend-op) < ll) return -1; /* output buffer overflow */ - LZ4_memmove(op, ip, ll); /* support in-place decompression */ - op += ll; - ip += ll; - if ((size_t)(oend-op) < MFLIMIT) { - if (op==oend) break; /* end of block */ - DEBUGLOG(5, "invalid: literals end at distance %zi from end of block", oend-op); - /* incorrect end of block : - * last match must start at least MFLIMIT==12 bytes before end of output block */ - return -1; - } } - - /* match */ - { size_t ml = token & 15; - size_t const offset = LZ4_readLE16(ip); - ip+=2; - - if (ml==15) { - /* long literal length */ - ml += read_long_length_no_check(&ip); - } - ml += MINMATCH; - - if ((size_t)(oend-op) < ml) return -1; /* output buffer overflow */ - - { const BYTE* match = op - offset; - - /* out of range */ - if (offset > (size_t)(op - prefixStart) + dictSize) { - DEBUGLOG(6, "offset out of range"); - return -1; - } - - /* check special case : extDict */ - if (offset > (size_t)(op - prefixStart)) { - /* extDict scenario */ - const BYTE* const dictEnd = dictStart + dictSize; - const BYTE* extMatch = dictEnd - (offset - (size_t)(op-prefixStart)); - size_t const extml = (size_t)(dictEnd - extMatch); - if (extml > ml) { - /* match entirely within extDict */ - LZ4_memmove(op, extMatch, ml); - op += ml; - ml = 0; - } else { - /* match split between extDict & prefix */ - LZ4_memmove(op, extMatch, extml); - op += extml; - ml -= extml; - } - match = prefixStart; - } - - /* match copy - slow variant, supporting overlap copy */ - { size_t u; - for (u=0; u= ipmax before start of loop. Returns initial_error if so. - * @error (output) - error code. Must be set to 0 before call. -**/ -typedef size_t Rvl_t; -static const Rvl_t rvl_error = (Rvl_t)(-1); -LZ4_FORCE_INLINE Rvl_t -read_variable_length(const BYTE** ip, const BYTE* ilimit, - int initial_check) -{ - Rvl_t s, length = 0; - assert(ip != NULL); - assert(*ip != NULL); - assert(ilimit != NULL); - if (initial_check && unlikely((*ip) >= ilimit)) { /* read limit reached */ - return rvl_error; - } - s = **ip; - (*ip)++; - length += s; - if (unlikely((*ip) > ilimit)) { /* read limit reached */ - return rvl_error; - } - /* accumulator overflow detection (32-bit mode only) */ - if ((sizeof(length) < 8) && unlikely(length > ((Rvl_t)(-1)/2)) ) { - return rvl_error; - } - if (likely(s != 255)) return length; - do { - s = **ip; - (*ip)++; - length += s; - if (unlikely((*ip) > ilimit)) { /* read limit reached */ - return rvl_error; - } - /* accumulator overflow detection (32-bit mode only) */ - if ((sizeof(length) < 8) && unlikely(length > ((Rvl_t)(-1)/2)) ) { - return rvl_error; - } - } while (s == 255); - - return length; -} - -/*! LZ4_decompress_generic() : - * This generic decompression function covers all use cases. - * It shall be instantiated several times, using different sets of directives. - * Note that it is important for performance that this function really get inlined, - * in order to remove useless branches during compilation optimization. - */ -LZ4_FORCE_INLINE int -LZ4_decompress_generic( - const char* const src, - char* const dst, - int srcSize, - int outputSize, /* If endOnInput==endOnInputSize, this value is `dstCapacity` */ - - earlyEnd_directive partialDecoding, /* full, partial */ - dict_directive dict, /* noDict, withPrefix64k, usingExtDict */ - const BYTE* const lowPrefix, /* always <= dst, == dst when no prefix */ - const BYTE* const dictStart, /* only if dict==usingExtDict */ - const size_t dictSize /* note : = 0 if noDict */ - ) -{ - if ((src == NULL) || (outputSize < 0)) { return -1; } - - { const BYTE* ip = (const BYTE*) src; - const BYTE* const iend = ip + srcSize; - - BYTE* op = (BYTE*) dst; - BYTE* const oend = op + outputSize; - BYTE* cpy; - - const BYTE* const dictEnd = (dictStart == NULL) ? NULL : dictStart + dictSize; - - const int checkOffset = (dictSize < (int)(64 LZ_KB)); - - - /* Set up the "end" pointers for the shortcut. */ - const BYTE* const shortiend = iend - 14 /*maxLL*/ - 2 /*offset*/; - const BYTE* const shortoend = oend - 14 /*maxLL*/ - 18 /*maxML*/; - - const BYTE* match; - size_t offset; - unsigned token; - size_t length; - - - DEBUGLOG(5, "LZ4_decompress_generic (srcSize:%i, dstSize:%i)", srcSize, outputSize); - - /* Special cases */ - assert(lowPrefix <= op); - if (unlikely(outputSize==0)) { - /* Empty output buffer */ - if (partialDecoding) return 0; - return ((srcSize==1) && (*ip==0)) ? 0 : -1; - } - if (unlikely(srcSize==0)) { return -1; } - - /* LZ4_FAST_DEC_LOOP: - * designed for modern OoO performance cpus, - * where copying reliably 32-bytes is preferable to an unpredictable branch. - * note : fast loop may show a regression for some client arm chips. */ -#if LZ4_FAST_DEC_LOOP - if ((oend - op) < FASTLOOP_SAFE_DISTANCE) { - DEBUGLOG(6, "move to safe decode loop"); - goto safe_decode; - } - - /* Fast loop : decode sequences as long as output < oend-FASTLOOP_SAFE_DISTANCE */ - DEBUGLOG(6, "using fast decode loop"); - while (1) { - /* Main fastloop assertion: We can always wildcopy FASTLOOP_SAFE_DISTANCE */ - assert(oend - op >= FASTLOOP_SAFE_DISTANCE); - assert(ip < iend); - token = *ip++; - length = token >> ML_BITS; /* literal length */ - DEBUGLOG(7, "blockPos%6u: litLength token = %u", (unsigned)(op-(BYTE*)dst), (unsigned)length); - - /* decode literal length */ - if (length == RUN_MASK) { - size_t const addl = read_variable_length(&ip, iend-RUN_MASK, 1); - if (addl == rvl_error) { - DEBUGLOG(6, "error reading long literal length"); - goto _output_error; - } - length += addl; - if (unlikely((uptrval)(op)+length<(uptrval)(op))) { goto _output_error; } /* overflow detection */ - if (unlikely((uptrval)(ip)+length<(uptrval)(ip))) { goto _output_error; } /* overflow detection */ - - /* copy literals */ - LZ4_STATIC_ASSERT(MFLIMIT >= WILDCOPYLENGTH); - if ((op+length>oend-32) || (ip+length>iend-32)) { goto safe_literal_copy; } - LZ4_wildCopy32(op, ip, op+length); - ip += length; op += length; - } else if (ip <= iend-(16 + 1/*max lit + offset + nextToken*/)) { - /* We don't need to check oend, since we check it once for each loop below */ - DEBUGLOG(7, "copy %u bytes in a 16-bytes stripe", (unsigned)length); - /* Literals can only be <= 14, but hope compilers optimize better when copy by a register size */ - LZ4_memcpy(op, ip, 16); - ip += length; op += length; - } else { - goto safe_literal_copy; - } - - /* get offset */ - offset = LZ4_readLE16(ip); ip+=2; - DEBUGLOG(6, "blockPos%6u: offset = %u", (unsigned)(op-(BYTE*)dst), (unsigned)offset); - match = op - offset; - assert(match <= op); /* overflow check */ - - /* get matchlength */ - length = token & ML_MASK; - DEBUGLOG(7, " match length token = %u (len==%u)", (unsigned)length, (unsigned)length+MINMATCH); - - if (length == ML_MASK) { - size_t const addl = read_variable_length(&ip, iend - LASTLITERALS + 1, 0); - if (addl == rvl_error) { - DEBUGLOG(5, "error reading long match length"); - goto _output_error; - } - length += addl; - length += MINMATCH; - DEBUGLOG(7, " long match length == %u", (unsigned)length); - if (unlikely((uptrval)(op)+length<(uptrval)op)) { goto _output_error; } /* overflow detection */ - if (op + length >= oend - FASTLOOP_SAFE_DISTANCE) { - goto safe_match_copy; - } - } else { - length += MINMATCH; - if (op + length >= oend - FASTLOOP_SAFE_DISTANCE) { - DEBUGLOG(7, "moving to safe_match_copy (ml==%u)", (unsigned)length); - goto safe_match_copy; - } - - /* Fastpath check: skip LZ4_wildCopy32 when true */ - if ((dict == withPrefix64k) || (match >= lowPrefix)) { - if (offset >= 8) { - assert(match >= lowPrefix); - assert(match <= op); - assert(op + 18 <= oend); - - LZ4_memcpy(op, match, 8); - LZ4_memcpy(op+8, match+8, 8); - LZ4_memcpy(op+16, match+16, 2); - op += length; - continue; - } } } - - if ( checkOffset && (unlikely(match + dictSize < lowPrefix)) ) { - DEBUGLOG(5, "Error : pos=%zi, offset=%zi => outside buffers", op-lowPrefix, op-match); - goto _output_error; - } - /* match starting within external dictionary */ - if ((dict==usingExtDict) && (match < lowPrefix)) { - assert(dictEnd != NULL); - if (unlikely(op+length > oend-LASTLITERALS)) { - if (partialDecoding) { - DEBUGLOG(7, "partialDecoding: dictionary match, close to dstEnd"); - length = MIN(length, (size_t)(oend-op)); - } else { - DEBUGLOG(6, "end-of-block condition violated") - goto _output_error; - } } - - if (length <= (size_t)(lowPrefix-match)) { - /* match fits entirely within external dictionary : just copy */ - LZ4_memmove(op, dictEnd - (lowPrefix-match), length); - op += length; - } else { - /* match stretches into both external dictionary and current block */ - size_t const copySize = (size_t)(lowPrefix - match); - size_t const restSize = length - copySize; - LZ4_memcpy(op, dictEnd - copySize, copySize); - op += copySize; - if (restSize > (size_t)(op - lowPrefix)) { /* overlap copy */ - BYTE* const endOfMatch = op + restSize; - const BYTE* copyFrom = lowPrefix; - while (op < endOfMatch) { *op++ = *copyFrom++; } - } else { - LZ4_memcpy(op, lowPrefix, restSize); - op += restSize; - } } - continue; - } - - /* copy match within block */ - cpy = op + length; - - assert((op <= oend) && (oend-op >= 32)); - if (unlikely(offset<16)) { - LZ4_memcpy_using_offset(op, match, cpy, offset); - } else { - LZ4_wildCopy32(op, match, cpy); - } - - op = cpy; /* wildcopy correction */ - } - safe_decode: -#endif - - /* Main Loop : decode remaining sequences where output < FASTLOOP_SAFE_DISTANCE */ - DEBUGLOG(6, "using safe decode loop"); - while (1) { - assert(ip < iend); - token = *ip++; - length = token >> ML_BITS; /* literal length */ - DEBUGLOG(7, "blockPos%6u: litLength token = %u", (unsigned)(op-(BYTE*)dst), (unsigned)length); - - /* A two-stage shortcut for the most common case: - * 1) If the literal length is 0..14, and there is enough space, - * enter the shortcut and copy 16 bytes on behalf of the literals - * (in the fast mode, only 8 bytes can be safely copied this way). - * 2) Further if the match length is 4..18, copy 18 bytes in a similar - * manner; but we ensure that there's enough space in the output for - * those 18 bytes earlier, upon entering the shortcut (in other words, - * there is a combined check for both stages). - */ - if ( (length != RUN_MASK) - /* strictly "less than" on input, to re-enter the loop with at least one byte */ - && likely((ip < shortiend) & (op <= shortoend)) ) { - /* Copy the literals */ - LZ4_memcpy(op, ip, 16); - op += length; ip += length; - - /* The second stage: prepare for match copying, decode full info. - * If it doesn't work out, the info won't be wasted. */ - length = token & ML_MASK; /* match length */ - DEBUGLOG(7, "blockPos%6u: matchLength token = %u (len=%u)", (unsigned)(op-(BYTE*)dst), (unsigned)length, (unsigned)length + 4); - offset = LZ4_readLE16(ip); ip += 2; - match = op - offset; - assert(match <= op); /* check overflow */ - - /* Do not deal with overlapping matches. */ - if ( (length != ML_MASK) - && (offset >= 8) - && (dict==withPrefix64k || match >= lowPrefix) ) { - /* Copy the match. */ - LZ4_memcpy(op + 0, match + 0, 8); - LZ4_memcpy(op + 8, match + 8, 8); - LZ4_memcpy(op +16, match +16, 2); - op += length + MINMATCH; - /* Both stages worked, load the next token. */ - continue; - } - - /* The second stage didn't work out, but the info is ready. - * Propel it right to the point of match copying. */ - goto _copy_match; - } - - /* decode literal length */ - if (length == RUN_MASK) { - size_t const addl = read_variable_length(&ip, iend-RUN_MASK, 1); - if (addl == rvl_error) { goto _output_error; } - length += addl; - if (unlikely((uptrval)(op)+length<(uptrval)(op))) { goto _output_error; } /* overflow detection */ - if (unlikely((uptrval)(ip)+length<(uptrval)(ip))) { goto _output_error; } /* overflow detection */ - } - -#if LZ4_FAST_DEC_LOOP - safe_literal_copy: -#endif - /* copy literals */ - cpy = op+length; - - LZ4_STATIC_ASSERT(MFLIMIT >= WILDCOPYLENGTH); - if ((cpy>oend-MFLIMIT) || (ip+length>iend-(2+1+LASTLITERALS))) { - /* We've either hit the input parsing restriction or the output parsing restriction. - * In the normal scenario, decoding a full block, it must be the last sequence, - * otherwise it's an error (invalid input or dimensions). - * In partialDecoding scenario, it's necessary to ensure there is no buffer overflow. - */ - if (partialDecoding) { - /* Since we are partial decoding we may be in this block because of the output parsing - * restriction, which is not valid since the output buffer is allowed to be undersized. - */ - DEBUGLOG(7, "partialDecoding: copying literals, close to input or output end") - DEBUGLOG(7, "partialDecoding: literal length = %u", (unsigned)length); - DEBUGLOG(7, "partialDecoding: remaining space in dstBuffer : %i", (int)(oend - op)); - DEBUGLOG(7, "partialDecoding: remaining space in srcBuffer : %i", (int)(iend - ip)); - /* Finishing in the middle of a literals segment, - * due to lack of input. - */ - if (ip+length > iend) { - length = (size_t)(iend-ip); - cpy = op + length; - } - /* Finishing in the middle of a literals segment, - * due to lack of output space. - */ - if (cpy > oend) { - cpy = oend; - assert(op<=oend); - length = (size_t)(oend-op); - } - } else { - /* We must be on the last sequence (or invalid) because of the parsing limitations - * so check that we exactly consume the input and don't overrun the output buffer. - */ - if ((ip+length != iend) || (cpy > oend)) { - DEBUGLOG(5, "should have been last run of literals") - DEBUGLOG(5, "ip(%p) + length(%i) = %p != iend (%p)", ip, (int)length, ip+length, iend); - DEBUGLOG(5, "or cpy(%p) > (oend-MFLIMIT)(%p)", cpy, oend-MFLIMIT); - DEBUGLOG(5, "after writing %u bytes / %i bytes available", (unsigned)(op-(BYTE*)dst), outputSize); - goto _output_error; - } - } - LZ4_memmove(op, ip, length); /* supports overlapping memory regions, for in-place decompression scenarios */ - ip += length; - op += length; - /* Necessarily EOF when !partialDecoding. - * When partialDecoding, it is EOF if we've either - * filled the output buffer or - * can't proceed with reading an offset for following match. - */ - if (!partialDecoding || (cpy == oend) || (ip >= (iend-2))) { - break; - } - } else { - LZ4_wildCopy8(op, ip, cpy); /* can overwrite up to 8 bytes beyond cpy */ - ip += length; op = cpy; - } - - /* get offset */ - offset = LZ4_readLE16(ip); ip+=2; - match = op - offset; - - /* get matchlength */ - length = token & ML_MASK; - DEBUGLOG(7, "blockPos%6u: matchLength token = %u", (unsigned)(op-(BYTE*)dst), (unsigned)length); - - _copy_match: - if (length == ML_MASK) { - size_t const addl = read_variable_length(&ip, iend - LASTLITERALS + 1, 0); - if (addl == rvl_error) { goto _output_error; } - length += addl; - if (unlikely((uptrval)(op)+length<(uptrval)op)) goto _output_error; /* overflow detection */ - } - length += MINMATCH; - -#if LZ4_FAST_DEC_LOOP - safe_match_copy: -#endif - if ((checkOffset) && (unlikely(match + dictSize < lowPrefix))) goto _output_error; /* Error : offset outside buffers */ - /* match starting within external dictionary */ - if ((dict==usingExtDict) && (match < lowPrefix)) { - assert(dictEnd != NULL); - if (unlikely(op+length > oend-LASTLITERALS)) { - if (partialDecoding) length = MIN(length, (size_t)(oend-op)); - else goto _output_error; /* doesn't respect parsing restriction */ - } - - if (length <= (size_t)(lowPrefix-match)) { - /* match fits entirely within external dictionary : just copy */ - LZ4_memmove(op, dictEnd - (lowPrefix-match), length); - op += length; - } else { - /* match stretches into both external dictionary and current block */ - size_t const copySize = (size_t)(lowPrefix - match); - size_t const restSize = length - copySize; - LZ4_memcpy(op, dictEnd - copySize, copySize); - op += copySize; - if (restSize > (size_t)(op - lowPrefix)) { /* overlap copy */ - BYTE* const endOfMatch = op + restSize; - const BYTE* copyFrom = lowPrefix; - while (op < endOfMatch) *op++ = *copyFrom++; - } else { - LZ4_memcpy(op, lowPrefix, restSize); - op += restSize; - } } - continue; - } - assert(match >= lowPrefix); - - /* copy match within block */ - cpy = op + length; - - /* partialDecoding : may end anywhere within the block */ - assert(op<=oend); - if (partialDecoding && (cpy > oend-MATCH_SAFEGUARD_DISTANCE)) { - size_t const mlen = MIN(length, (size_t)(oend-op)); - const BYTE* const matchEnd = match + mlen; - BYTE* const copyEnd = op + mlen; - if (matchEnd > op) { /* overlap copy */ - while (op < copyEnd) { *op++ = *match++; } - } else { - LZ4_memcpy(op, match, mlen); - } - op = copyEnd; - if (op == oend) { break; } - continue; - } - - if (unlikely(offset<8)) { - LZ4_write32(op, 0); /* silence msan warning when offset==0 */ - op[0] = match[0]; - op[1] = match[1]; - op[2] = match[2]; - op[3] = match[3]; - match += inc32table[offset]; - LZ4_memcpy(op+4, match, 4); - match -= dec64table[offset]; - } else { - LZ4_memcpy(op, match, 8); - match += 8; - } - op += 8; - - if (unlikely(cpy > oend-MATCH_SAFEGUARD_DISTANCE)) { - BYTE* const oCopyLimit = oend - (WILDCOPYLENGTH-1); - if (cpy > oend-LASTLITERALS) { goto _output_error; } /* Error : last LASTLITERALS bytes must be literals (uncompressed) */ - if (op < oCopyLimit) { - LZ4_wildCopy8(op, match, oCopyLimit); - match += oCopyLimit - op; - op = oCopyLimit; - } - while (op < cpy) { *op++ = *match++; } - } else { - LZ4_memcpy(op, match, 8); - if (length > 16) { LZ4_wildCopy8(op+8, match+8, cpy); } - } - op = cpy; /* wildcopy correction */ - } - - /* end of decoding */ - DEBUGLOG(5, "decoded %i bytes", (int) (((char*)op)-dst)); - return (int) (((char*)op)-dst); /* Nb of output bytes decoded */ - - /* Overflow error detected */ - _output_error: - return (int) (-(((const char*)ip)-src))-1; - } -} - - -/*===== Instantiate the API decoding functions. =====*/ - -LZ4_FORCE_O2 -int LZ4_decompress_safe(const char* source, char* dest, int compressedSize, int maxDecompressedSize) -{ - return LZ4_decompress_generic(source, dest, compressedSize, maxDecompressedSize, - decode_full_block, noDict, - (BYTE*)dest, NULL, 0); -} - -LZ4_FORCE_O2 -int LZ4_decompress_safe_partial(const char* src, char* dst, int compressedSize, int targetOutputSize, int dstCapacity) -{ - dstCapacity = MIN(targetOutputSize, dstCapacity); - return LZ4_decompress_generic(src, dst, compressedSize, dstCapacity, - partial_decode, - noDict, (BYTE*)dst, NULL, 0); -} - -LZ4_FORCE_O2 -int LZ4_decompress_fast(const char* source, char* dest, int originalSize) -{ - DEBUGLOG(5, "LZ4_decompress_fast"); - return LZ4_decompress_unsafe_generic( - (const BYTE*)source, (BYTE*)dest, originalSize, - 0, NULL, 0); -} - -/*===== Instantiate a few more decoding cases, used more than once. =====*/ - -LZ4_FORCE_O2 /* Exported, an obsolete API function. */ -int LZ4_decompress_safe_withPrefix64k(const char* source, char* dest, int compressedSize, int maxOutputSize) -{ - return LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize, - decode_full_block, withPrefix64k, - (BYTE*)dest - 64 LZ_KB, NULL, 0); -} - -LZ4_FORCE_O2 -static int LZ4_decompress_safe_partial_withPrefix64k(const char* source, char* dest, int compressedSize, int targetOutputSize, int dstCapacity) -{ - dstCapacity = MIN(targetOutputSize, dstCapacity); - return LZ4_decompress_generic(source, dest, compressedSize, dstCapacity, - partial_decode, withPrefix64k, - (BYTE*)dest - 64 LZ_KB, NULL, 0); -} - -/* Another obsolete API function, paired with the previous one. */ -int LZ4_decompress_fast_withPrefix64k(const char* source, char* dest, int originalSize) -{ - return LZ4_decompress_unsafe_generic( - (const BYTE*)source, (BYTE*)dest, originalSize, - 64 LZ_KB, NULL, 0); -} - -LZ4_FORCE_O2 -static int LZ4_decompress_safe_withSmallPrefix(const char* source, char* dest, int compressedSize, int maxOutputSize, - size_t prefixSize) -{ - return LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize, - decode_full_block, noDict, - (BYTE*)dest-prefixSize, NULL, 0); -} - -LZ4_FORCE_O2 -static int LZ4_decompress_safe_partial_withSmallPrefix(const char* source, char* dest, int compressedSize, int targetOutputSize, int dstCapacity, - size_t prefixSize) -{ - dstCapacity = MIN(targetOutputSize, dstCapacity); - return LZ4_decompress_generic(source, dest, compressedSize, dstCapacity, - partial_decode, noDict, - (BYTE*)dest-prefixSize, NULL, 0); -} - -LZ4_FORCE_O2 -int LZ4_decompress_safe_forceExtDict(const char* source, char* dest, - int compressedSize, int maxOutputSize, - const void* dictStart, size_t dictSize) -{ - DEBUGLOG(5, "LZ4_decompress_safe_forceExtDict"); - return LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize, - decode_full_block, usingExtDict, - (BYTE*)dest, (const BYTE*)dictStart, dictSize); -} - -LZ4_FORCE_O2 -int LZ4_decompress_safe_partial_forceExtDict(const char* source, char* dest, - int compressedSize, int targetOutputSize, int dstCapacity, - const void* dictStart, size_t dictSize) -{ - dstCapacity = MIN(targetOutputSize, dstCapacity); - return LZ4_decompress_generic(source, dest, compressedSize, dstCapacity, - partial_decode, usingExtDict, - (BYTE*)dest, (const BYTE*)dictStart, dictSize); -} - -LZ4_FORCE_O2 -static int LZ4_decompress_fast_extDict(const char* source, char* dest, int originalSize, - const void* dictStart, size_t dictSize) -{ - return LZ4_decompress_unsafe_generic( - (const BYTE*)source, (BYTE*)dest, originalSize, - 0, (const BYTE*)dictStart, dictSize); -} - -/* The "double dictionary" mode, for use with e.g. ring buffers: the first part - * of the dictionary is passed as prefix, and the second via dictStart + dictSize. - * These routines are used only once, in LZ4_decompress_*_continue(). - */ -LZ4_FORCE_INLINE -int LZ4_decompress_safe_doubleDict(const char* source, char* dest, int compressedSize, int maxOutputSize, - size_t prefixSize, const void* dictStart, size_t dictSize) -{ - return LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize, - decode_full_block, usingExtDict, - (BYTE*)dest-prefixSize, (const BYTE*)dictStart, dictSize); -} - -/*===== streaming decompression functions =====*/ - -#if !defined(LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION) -LZ4_streamDecode_t* LZ4_createStreamDecode(void) -{ - LZ4_STATIC_ASSERT(sizeof(LZ4_streamDecode_t) >= sizeof(LZ4_streamDecode_t_internal)); - return (LZ4_streamDecode_t*) ALLOC_AND_ZERO(sizeof(LZ4_streamDecode_t)); -} - -int LZ4_freeStreamDecode (LZ4_streamDecode_t* LZ4_stream) -{ - if (LZ4_stream == NULL) { return 0; } /* support free on NULL */ - FREEMEM(LZ4_stream); - return 0; -} -#endif - -/*! LZ4_setStreamDecode() : - * Use this function to instruct where to find the dictionary. - * This function is not necessary if previous data is still available where it was decoded. - * Loading a size of 0 is allowed (same effect as no dictionary). - * @return : 1 if OK, 0 if error - */ -int LZ4_setStreamDecode (LZ4_streamDecode_t* LZ4_streamDecode, const char* dictionary, int dictSize) -{ - LZ4_streamDecode_t_internal* lz4sd = &LZ4_streamDecode->internal_donotuse; - lz4sd->prefixSize = (size_t)dictSize; - if (dictSize) { - assert(dictionary != NULL); - lz4sd->prefixEnd = (const BYTE*) dictionary + dictSize; - } else { - lz4sd->prefixEnd = (const BYTE*) dictionary; - } - lz4sd->externalDict = NULL; - lz4sd->extDictSize = 0; - return 1; -} - -/*! LZ4_decoderRingBufferSize() : - * when setting a ring buffer for streaming decompression (optional scenario), - * provides the minimum size of this ring buffer - * to be compatible with any source respecting maxBlockSize condition. - * Note : in a ring buffer scenario, - * blocks are presumed decompressed next to each other. - * When not enough space remains for next block (remainingSize < maxBlockSize), - * decoding resumes from beginning of ring buffer. - * @return : minimum ring buffer size, - * or 0 if there is an error (invalid maxBlockSize). - */ -int LZ4_decoderRingBufferSize(int maxBlockSize) -{ - if (maxBlockSize < 0) return 0; - if (maxBlockSize > LZ4_MAX_INPUT_SIZE) return 0; - if (maxBlockSize < 16) maxBlockSize = 16; - return LZ4_DECODER_RING_BUFFER_SIZE(maxBlockSize); -} - -/* -*_continue() : - These decoding functions allow decompression of multiple blocks in "streaming" mode. - Previously decoded blocks must still be available at the memory position where they were decoded. - If it's not possible, save the relevant part of decoded data into a safe buffer, - and indicate where it stands using LZ4_setStreamDecode() -*/ -LZ4_FORCE_O2 -int LZ4_decompress_safe_continue (LZ4_streamDecode_t* LZ4_streamDecode, const char* source, char* dest, int compressedSize, int maxOutputSize) -{ - LZ4_streamDecode_t_internal* lz4sd = &LZ4_streamDecode->internal_donotuse; - int result; - - if (lz4sd->prefixSize == 0) { - /* The first call, no dictionary yet. */ - assert(lz4sd->extDictSize == 0); - result = LZ4_decompress_safe(source, dest, compressedSize, maxOutputSize); - if (result <= 0) return result; - lz4sd->prefixSize = (size_t)result; - lz4sd->prefixEnd = (BYTE*)dest + result; - } else if (lz4sd->prefixEnd == (BYTE*)dest) { - /* They're rolling the current segment. */ - if (lz4sd->prefixSize >= 64 LZ_KB - 1) - result = LZ4_decompress_safe_withPrefix64k(source, dest, compressedSize, maxOutputSize); - else if (lz4sd->extDictSize == 0) - result = LZ4_decompress_safe_withSmallPrefix(source, dest, compressedSize, maxOutputSize, - lz4sd->prefixSize); - else - result = LZ4_decompress_safe_doubleDict(source, dest, compressedSize, maxOutputSize, - lz4sd->prefixSize, lz4sd->externalDict, lz4sd->extDictSize); - if (result <= 0) return result; - lz4sd->prefixSize += (size_t)result; - lz4sd->prefixEnd += result; - } else { - /* The buffer wraps around, or they're switching to another buffer. */ - lz4sd->extDictSize = lz4sd->prefixSize; - lz4sd->externalDict = lz4sd->prefixEnd - lz4sd->extDictSize; - result = LZ4_decompress_safe_forceExtDict(source, dest, compressedSize, maxOutputSize, - lz4sd->externalDict, lz4sd->extDictSize); - if (result <= 0) return result; - lz4sd->prefixSize = (size_t)result; - lz4sd->prefixEnd = (BYTE*)dest + result; - } - - return result; -} - -LZ4_FORCE_O2 int -LZ4_decompress_fast_continue (LZ4_streamDecode_t* LZ4_streamDecode, - const char* source, char* dest, int originalSize) -{ - LZ4_streamDecode_t_internal* const lz4sd = - (assert(LZ4_streamDecode!=NULL), &LZ4_streamDecode->internal_donotuse); - int result; - - DEBUGLOG(5, "LZ4_decompress_fast_continue (toDecodeSize=%i)", originalSize); - assert(originalSize >= 0); - - if (lz4sd->prefixSize == 0) { - DEBUGLOG(5, "first invocation : no prefix nor extDict"); - assert(lz4sd->extDictSize == 0); - result = LZ4_decompress_fast(source, dest, originalSize); - if (result <= 0) return result; - lz4sd->prefixSize = (size_t)originalSize; - lz4sd->prefixEnd = (BYTE*)dest + originalSize; - } else if (lz4sd->prefixEnd == (BYTE*)dest) { - DEBUGLOG(5, "continue using existing prefix"); - result = LZ4_decompress_unsafe_generic( - (const BYTE*)source, (BYTE*)dest, originalSize, - lz4sd->prefixSize, - lz4sd->externalDict, lz4sd->extDictSize); - if (result <= 0) return result; - lz4sd->prefixSize += (size_t)originalSize; - lz4sd->prefixEnd += originalSize; - } else { - DEBUGLOG(5, "prefix becomes extDict"); - lz4sd->extDictSize = lz4sd->prefixSize; - lz4sd->externalDict = lz4sd->prefixEnd - lz4sd->extDictSize; - result = LZ4_decompress_fast_extDict(source, dest, originalSize, - lz4sd->externalDict, lz4sd->extDictSize); - if (result <= 0) return result; - lz4sd->prefixSize = (size_t)originalSize; - lz4sd->prefixEnd = (BYTE*)dest + originalSize; - } - - return result; -} - - -/* -Advanced decoding functions : -*_usingDict() : - These decoding functions work the same as "_continue" ones, - the dictionary must be explicitly provided within parameters -*/ - -int LZ4_decompress_safe_usingDict(const char* source, char* dest, int compressedSize, int maxOutputSize, const char* dictStart, int dictSize) -{ - if (dictSize==0) - return LZ4_decompress_safe(source, dest, compressedSize, maxOutputSize); - if (dictStart+dictSize == dest) { - if (dictSize >= 64 LZ_KB - 1) { - return LZ4_decompress_safe_withPrefix64k(source, dest, compressedSize, maxOutputSize); - } - assert(dictSize >= 0); - return LZ4_decompress_safe_withSmallPrefix(source, dest, compressedSize, maxOutputSize, (size_t)dictSize); - } - assert(dictSize >= 0); - return LZ4_decompress_safe_forceExtDict(source, dest, compressedSize, maxOutputSize, dictStart, (size_t)dictSize); -} - -int LZ4_decompress_safe_partial_usingDict(const char* source, char* dest, int compressedSize, int targetOutputSize, int dstCapacity, const char* dictStart, int dictSize) -{ - if (dictSize==0) - return LZ4_decompress_safe_partial(source, dest, compressedSize, targetOutputSize, dstCapacity); - if (dictStart+dictSize == dest) { - if (dictSize >= 64 LZ_KB - 1) { - return LZ4_decompress_safe_partial_withPrefix64k(source, dest, compressedSize, targetOutputSize, dstCapacity); - } - assert(dictSize >= 0); - return LZ4_decompress_safe_partial_withSmallPrefix(source, dest, compressedSize, targetOutputSize, dstCapacity, (size_t)dictSize); - } - assert(dictSize >= 0); - return LZ4_decompress_safe_partial_forceExtDict(source, dest, compressedSize, targetOutputSize, dstCapacity, dictStart, (size_t)dictSize); -} - -int LZ4_decompress_fast_usingDict(const char* source, char* dest, int originalSize, const char* dictStart, int dictSize) -{ - if (dictSize==0 || dictStart+dictSize == dest) - return LZ4_decompress_unsafe_generic( - (const BYTE*)source, (BYTE*)dest, originalSize, - (size_t)dictSize, NULL, 0); - assert(dictSize >= 0); - return LZ4_decompress_fast_extDict(source, dest, originalSize, dictStart, (size_t)dictSize); -} - - -/*=************************************************* -* Obsolete Functions -***************************************************/ -/* obsolete compression functions */ -int LZ4_compress_limitedOutput(const char* source, char* dest, int inputSize, int maxOutputSize) -{ - return LZ4_compress_default(source, dest, inputSize, maxOutputSize); -} -int LZ4_compress(const char* src, char* dest, int srcSize) -{ - return LZ4_compress_default(src, dest, srcSize, LZ4_compressBound(srcSize)); -} -int LZ4_compress_limitedOutput_withState (void* state, const char* src, char* dst, int srcSize, int dstSize) -{ - return LZ4_compress_fast_extState(state, src, dst, srcSize, dstSize, 1); -} -int LZ4_compress_withState (void* state, const char* src, char* dst, int srcSize) -{ - return LZ4_compress_fast_extState(state, src, dst, srcSize, LZ4_compressBound(srcSize), 1); -} -int LZ4_compress_limitedOutput_continue (LZ4_stream_t* LZ4_stream, const char* src, char* dst, int srcSize, int dstCapacity) -{ - return LZ4_compress_fast_continue(LZ4_stream, src, dst, srcSize, dstCapacity, 1); -} -int LZ4_compress_continue (LZ4_stream_t* LZ4_stream, const char* source, char* dest, int inputSize) -{ - return LZ4_compress_fast_continue(LZ4_stream, source, dest, inputSize, LZ4_compressBound(inputSize), 1); -} - -/* -These decompression functions are deprecated and should no longer be used. -They are only provided here for compatibility with older user programs. -- LZ4_uncompress is totally equivalent to LZ4_decompress_fast -- LZ4_uncompress_unknownOutputSize is totally equivalent to LZ4_decompress_safe -*/ -int LZ4_uncompress (const char* source, char* dest, int outputSize) -{ - return LZ4_decompress_fast(source, dest, outputSize); -} -int LZ4_uncompress_unknownOutputSize (const char* source, char* dest, int isize, int maxOutputSize) -{ - return LZ4_decompress_safe(source, dest, isize, maxOutputSize); -} - -/* Obsolete Streaming functions */ - -int LZ4_sizeofStreamState(void) { return sizeof(LZ4_stream_t); } - -int LZ4_resetStreamState(void* state, char* inputBuffer) -{ - (void)inputBuffer; - LZ4_resetStream((LZ4_stream_t*)state); - return 0; -} - -#if !defined(LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION) -void* LZ4_create (char* inputBuffer) -{ - (void)inputBuffer; - return LZ4_createStream(); -} -#endif - -char* LZ4_slideInputBuffer (void* state) -{ - /* avoid const char * -> char * conversion warning */ - return (char *)(uptrval)((LZ4_stream_t*)state)->internal_donotuse.dictionary; -} - -#endif /* LZ4_COMMONDEFS_ONLY */ \ No newline at end of file diff --git a/lz4/lz4.h b/lz4/lz4.h deleted file mode 100644 index 8b7482d..0000000 --- a/lz4/lz4.h +++ /dev/null @@ -1,884 +0,0 @@ -/* - * LZ4 - Fast LZ compression algorithm - * Header File - * Copyright (C) 2011-2023, Yann Collet. - - BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are - met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following disclaimer - in the documentation and/or other materials provided with the - distribution. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - You can contact the author at : - - LZ4 homepage : http://www.lz4.org - - LZ4 source repository : https://github.com/lz4/lz4 -*/ -#if defined (__cplusplus) -extern "C" { -#endif - -#ifndef LZ4_H_2983827168210 -#define LZ4_H_2983827168210 - -/* --- Dependency --- */ -#include /* size_t */ - - -/** - Introduction - - LZ4 is lossless compression algorithm, providing compression speed >500 MB/s per core, - scalable with multi-cores CPU. It features an extremely fast decoder, with speed in - multiple GB/s per core, typically reaching RAM speed limits on multi-core systems. - - The LZ4 compression library provides in-memory compression and decompression functions. - It gives full buffer control to user. - Compression can be done in: - - a single step (described as Simple Functions) - - a single step, reusing a context (described in Advanced Functions) - - unbounded multiple steps (described as Streaming compression) - - lz4.h generates and decodes LZ4-compressed blocks (doc/lz4_Block_format.md). - Decompressing such a compressed block requires additional metadata. - Exact metadata depends on exact decompression function. - For the typical case of LZ4_decompress_safe(), - metadata includes block's compressed size, and maximum bound of decompressed size. - Each application is free to encode and pass such metadata in whichever way it wants. - - lz4.h only handle blocks, it can not generate Frames. - - Blocks are different from Frames (doc/lz4_Frame_format.md). - Frames bundle both blocks and metadata in a specified manner. - Embedding metadata is required for compressed data to be self-contained and portable. - Frame format is delivered through a companion API, declared in lz4frame.h. - The `lz4` CLI can only manage frames. -*/ - -/*^*************************************************************** -* Export parameters -*****************************************************************/ -/* -* LZ4_DLL_EXPORT : -* Enable exporting of functions when building a Windows DLL -* LZ4LIB_VISIBILITY : -* Control library symbols visibility. -*/ -#ifndef LZ4LIB_VISIBILITY -# if defined(__GNUC__) && (__GNUC__ >= 4) -# define LZ4LIB_VISIBILITY __attribute__ ((visibility ("default"))) -# else -# define LZ4LIB_VISIBILITY -# endif -#endif -#if defined(LZ4_DLL_EXPORT) && (LZ4_DLL_EXPORT==1) -# define LZ4LIB_API __declspec(dllexport) LZ4LIB_VISIBILITY -#elif defined(LZ4_DLL_IMPORT) && (LZ4_DLL_IMPORT==1) -# define LZ4LIB_API __declspec(dllimport) LZ4LIB_VISIBILITY /* It isn't required but allows to generate better code, saving a function pointer load from the IAT and an indirect jump.*/ -#else -# define LZ4LIB_API LZ4LIB_VISIBILITY -#endif - -/*! LZ4_FREESTANDING : - * When this macro is set to 1, it enables "freestanding mode" that is - * suitable for typical freestanding environment which doesn't support - * standard C library. - * - * - LZ4_FREESTANDING is a compile-time switch. - * - It requires the following macros to be defined: - * LZ4_memcpy, LZ4_memmove, LZ4_memset. - * - It only enables LZ4/HC functions which don't use heap. - * All LZ4F_* functions are not supported. - * - See tests/freestanding.c to check its basic setup. - */ -#if defined(LZ4_FREESTANDING) && (LZ4_FREESTANDING == 1) -# define LZ4_HEAPMODE 0 -# define LZ4HC_HEAPMODE 0 -# define LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION 1 -# if !defined(LZ4_memcpy) -# error "LZ4_FREESTANDING requires macro 'LZ4_memcpy'." -# endif -# if !defined(LZ4_memset) -# error "LZ4_FREESTANDING requires macro 'LZ4_memset'." -# endif -# if !defined(LZ4_memmove) -# error "LZ4_FREESTANDING requires macro 'LZ4_memmove'." -# endif -#elif ! defined(LZ4_FREESTANDING) -# define LZ4_FREESTANDING 0 -#endif - - -/*------ Version ------*/ -#define LZ4_VERSION_MAJOR 1 /* for breaking interface changes */ -#define LZ4_VERSION_MINOR 10 /* for new (non-breaking) interface capabilities */ -#define LZ4_VERSION_RELEASE 0 /* for tweaks, bug-fixes, or development */ - -#define LZ4_VERSION_NUMBER (LZ4_VERSION_MAJOR *100*100 + LZ4_VERSION_MINOR *100 + LZ4_VERSION_RELEASE) - -#define LZ4_LIB_VERSION LZ4_VERSION_MAJOR.LZ4_VERSION_MINOR.LZ4_VERSION_RELEASE -#define LZ4_QUOTE(str) #str -#define LZ4_EXPAND_AND_QUOTE(str) LZ4_QUOTE(str) -#define LZ4_VERSION_STRING LZ4_EXPAND_AND_QUOTE(LZ4_LIB_VERSION) /* requires v1.7.3+ */ - -LZ4LIB_API int LZ4_versionNumber (void); /**< library version number; useful to check dll version; requires v1.3.0+ */ -LZ4LIB_API const char* LZ4_versionString (void); /**< library version string; useful to check dll version; requires v1.7.5+ */ - - -/*-************************************ -* Tuning memory usage -**************************************/ -/*! - * LZ4_MEMORY_USAGE : - * Can be selected at compile time, by setting LZ4_MEMORY_USAGE. - * Memory usage formula : N->2^N Bytes (examples : 10 -> 1KB; 12 -> 4KB ; 16 -> 64KB; 20 -> 1MB) - * Increasing memory usage improves compression ratio, generally at the cost of speed. - * Reduced memory usage may improve speed at the cost of ratio, thanks to better cache locality. - * Default value is 14, for 16KB, which nicely fits into most L1 caches. - */ -#ifndef LZ4_MEMORY_USAGE -# define LZ4_MEMORY_USAGE LZ4_MEMORY_USAGE_DEFAULT -#endif - -/* These are absolute limits, they should not be changed by users */ -#define LZ4_MEMORY_USAGE_MIN 10 -#define LZ4_MEMORY_USAGE_DEFAULT 14 -#define LZ4_MEMORY_USAGE_MAX 20 - -#if (LZ4_MEMORY_USAGE < LZ4_MEMORY_USAGE_MIN) -# error "LZ4_MEMORY_USAGE is too small !" -#endif - -#if (LZ4_MEMORY_USAGE > LZ4_MEMORY_USAGE_MAX) -# error "LZ4_MEMORY_USAGE is too large !" -#endif - -/*-************************************ -* Simple Functions -**************************************/ -/*! LZ4_compress_default() : - * Compresses 'srcSize' bytes from buffer 'src' - * into already allocated 'dst' buffer of size 'dstCapacity'. - * Compression is guaranteed to succeed if 'dstCapacity' >= LZ4_compressBound(srcSize). - * It also runs faster, so it's a recommended setting. - * If the function cannot compress 'src' into a more limited 'dst' budget, - * compression stops *immediately*, and the function result is zero. - * In which case, 'dst' content is undefined (invalid). - * srcSize : max supported value is LZ4_MAX_INPUT_SIZE. - * dstCapacity : size of buffer 'dst' (which must be already allocated) - * @return : the number of bytes written into buffer 'dst' (necessarily <= dstCapacity) - * or 0 if compression fails - * Note : This function is protected against buffer overflow scenarios (never writes outside 'dst' buffer, nor read outside 'source' buffer). - */ -LZ4LIB_API int LZ4_compress_default(const char* src, char* dst, int srcSize, int dstCapacity); - -/*! LZ4_decompress_safe() : - * @compressedSize : is the exact complete size of the compressed block. - * @dstCapacity : is the size of destination buffer (which must be already allocated), - * presumed an upper bound of decompressed size. - * @return : the number of bytes decompressed into destination buffer (necessarily <= dstCapacity) - * If destination buffer is not large enough, decoding will stop and output an error code (negative value). - * If the source stream is detected malformed, the function will stop decoding and return a negative result. - * Note 1 : This function is protected against malicious data packets : - * it will never writes outside 'dst' buffer, nor read outside 'source' buffer, - * even if the compressed block is maliciously modified to order the decoder to do these actions. - * In such case, the decoder stops immediately, and considers the compressed block malformed. - * Note 2 : compressedSize and dstCapacity must be provided to the function, the compressed block does not contain them. - * The implementation is free to send / store / derive this information in whichever way is most beneficial. - * If there is a need for a different format which bundles together both compressed data and its metadata, consider looking at lz4frame.h instead. - */ -LZ4LIB_API int LZ4_decompress_safe (const char* src, char* dst, int compressedSize, int dstCapacity); - - -/*-************************************ -* Advanced Functions -**************************************/ -#define LZ4_MAX_INPUT_SIZE 0x7E000000 /* 2 113 929 216 bytes */ -#define LZ4_COMPRESSBOUND(isize) ((unsigned)(isize) > (unsigned)LZ4_MAX_INPUT_SIZE ? 0 : (isize) + ((isize)/255) + 16) - -/*! LZ4_compressBound() : - Provides the maximum size that LZ4 compression may output in a "worst case" scenario (input data not compressible) - This function is primarily useful for memory allocation purposes (destination buffer size). - Macro LZ4_COMPRESSBOUND() is also provided for compilation-time evaluation (stack memory allocation for example). - Note that LZ4_compress_default() compresses faster when dstCapacity is >= LZ4_compressBound(srcSize) - inputSize : max supported value is LZ4_MAX_INPUT_SIZE - return : maximum output size in a "worst case" scenario - or 0, if input size is incorrect (too large or negative) -*/ -LZ4LIB_API int LZ4_compressBound(int inputSize); - -/*! LZ4_compress_fast() : - Same as LZ4_compress_default(), but allows selection of "acceleration" factor. - The larger the acceleration value, the faster the algorithm, but also the lesser the compression. - It's a trade-off. It can be fine tuned, with each successive value providing roughly +~3% to speed. - An acceleration value of "1" is the same as regular LZ4_compress_default() - Values <= 0 will be replaced by LZ4_ACCELERATION_DEFAULT (currently == 1, see lz4.c). - Values > LZ4_ACCELERATION_MAX will be replaced by LZ4_ACCELERATION_MAX (currently == 65537, see lz4.c). -*/ -LZ4LIB_API int LZ4_compress_fast (const char* src, char* dst, int srcSize, int dstCapacity, int acceleration); - - -/*! LZ4_compress_fast_extState() : - * Same as LZ4_compress_fast(), using an externally allocated memory space for its state. - * Use LZ4_sizeofState() to know how much memory must be allocated, - * and allocate it on 8-bytes boundaries (using `malloc()` typically). - * Then, provide this buffer as `void* state` to compression function. - */ -LZ4LIB_API int LZ4_sizeofState(void); -LZ4LIB_API int LZ4_compress_fast_extState (void* state, const char* src, char* dst, int srcSize, int dstCapacity, int acceleration); - -/*! LZ4_compress_destSize() : - * Reverse the logic : compresses as much data as possible from 'src' buffer - * into already allocated buffer 'dst', of size >= 'dstCapacity'. - * This function either compresses the entire 'src' content into 'dst' if it's large enough, - * or fill 'dst' buffer completely with as much data as possible from 'src'. - * note: acceleration parameter is fixed to "default". - * - * *srcSizePtr : in+out parameter. Initially contains size of input. - * Will be modified to indicate how many bytes where read from 'src' to fill 'dst'. - * New value is necessarily <= input value. - * @return : Nb bytes written into 'dst' (necessarily <= dstCapacity) - * or 0 if compression fails. - * - * Note : from v1.8.2 to v1.9.1, this function had a bug (fixed in v1.9.2+): - * the produced compressed content could, in specific circumstances, - * require to be decompressed into a destination buffer larger - * by at least 1 byte than the content to decompress. - * If an application uses `LZ4_compress_destSize()`, - * it's highly recommended to update liblz4 to v1.9.2 or better. - * If this can't be done or ensured, - * the receiving decompression function should provide - * a dstCapacity which is > decompressedSize, by at least 1 byte. - * See https://github.com/lz4/lz4/issues/859 for details - */ -LZ4LIB_API int LZ4_compress_destSize(const char* src, char* dst, int* srcSizePtr, int targetDstSize); - -/*! LZ4_decompress_safe_partial() : - * Decompress an LZ4 compressed block, of size 'srcSize' at position 'src', - * into destination buffer 'dst' of size 'dstCapacity'. - * Up to 'targetOutputSize' bytes will be decoded. - * The function stops decoding on reaching this objective. - * This can be useful to boost performance - * whenever only the beginning of a block is required. - * - * @return : the number of bytes decoded in `dst` (necessarily <= targetOutputSize) - * If source stream is detected malformed, function returns a negative result. - * - * Note 1 : @return can be < targetOutputSize, if compressed block contains less data. - * - * Note 2 : targetOutputSize must be <= dstCapacity - * - * Note 3 : this function effectively stops decoding on reaching targetOutputSize, - * so dstCapacity is kind of redundant. - * This is because in older versions of this function, - * decoding operation would still write complete sequences. - * Therefore, there was no guarantee that it would stop writing at exactly targetOutputSize, - * it could write more bytes, though only up to dstCapacity. - * Some "margin" used to be required for this operation to work properly. - * Thankfully, this is no longer necessary. - * The function nonetheless keeps the same signature, in an effort to preserve API compatibility. - * - * Note 4 : If srcSize is the exact size of the block, - * then targetOutputSize can be any value, - * including larger than the block's decompressed size. - * The function will, at most, generate block's decompressed size. - * - * Note 5 : If srcSize is _larger_ than block's compressed size, - * then targetOutputSize **MUST** be <= block's decompressed size. - * Otherwise, *silent corruption will occur*. - */ -LZ4LIB_API int LZ4_decompress_safe_partial (const char* src, char* dst, int srcSize, int targetOutputSize, int dstCapacity); - - -/*-********************************************* -* Streaming Compression Functions -***********************************************/ -typedef union LZ4_stream_u LZ4_stream_t; /* incomplete type (defined later) */ - -/*! - Note about RC_INVOKED - - - RC_INVOKED is predefined symbol of rc.exe (the resource compiler which is part of MSVC/Visual Studio). - https://docs.microsoft.com/en-us/windows/win32/menurc/predefined-macros - - - Since rc.exe is a legacy compiler, it truncates long symbol (> 30 chars) - and reports warning "RC4011: identifier truncated". - - - To eliminate the warning, we surround long preprocessor symbol with - "#if !defined(RC_INVOKED) ... #endif" block that means - "skip this block when rc.exe is trying to read it". -*/ -#if !defined(RC_INVOKED) /* https://docs.microsoft.com/en-us/windows/win32/menurc/predefined-macros */ -#if !defined(LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION) -LZ4LIB_API LZ4_stream_t* LZ4_createStream(void); -LZ4LIB_API int LZ4_freeStream (LZ4_stream_t* streamPtr); -#endif /* !defined(LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION) */ -#endif - -/*! LZ4_resetStream_fast() : v1.9.0+ - * Use this to prepare an LZ4_stream_t for a new chain of dependent blocks - * (e.g., LZ4_compress_fast_continue()). - * - * An LZ4_stream_t must be initialized once before usage. - * This is automatically done when created by LZ4_createStream(). - * However, should the LZ4_stream_t be simply declared on stack (for example), - * it's necessary to initialize it first, using LZ4_initStream(). - * - * After init, start any new stream with LZ4_resetStream_fast(). - * A same LZ4_stream_t can be re-used multiple times consecutively - * and compress multiple streams, - * provided that it starts each new stream with LZ4_resetStream_fast(). - * - * LZ4_resetStream_fast() is much faster than LZ4_initStream(), - * but is not compatible with memory regions containing garbage data. - * - * Note: it's only useful to call LZ4_resetStream_fast() - * in the context of streaming compression. - * The *extState* functions perform their own resets. - * Invoking LZ4_resetStream_fast() before is redundant, and even counterproductive. - */ -LZ4LIB_API void LZ4_resetStream_fast (LZ4_stream_t* streamPtr); - -/*! LZ4_loadDict() : - * Use this function to reference a static dictionary into LZ4_stream_t. - * The dictionary must remain available during compression. - * LZ4_loadDict() triggers a reset, so any previous data will be forgotten. - * The same dictionary will have to be loaded on decompression side for successful decoding. - * Dictionary are useful for better compression of small data (KB range). - * While LZ4 itself accepts any input as dictionary, dictionary efficiency is also a topic. - * When in doubt, employ the Zstandard's Dictionary Builder. - * Loading a size of 0 is allowed, and is the same as reset. - * @return : loaded dictionary size, in bytes (note: only the last 64 KB are loaded) - */ -LZ4LIB_API int LZ4_loadDict (LZ4_stream_t* streamPtr, const char* dictionary, int dictSize); - -/*! LZ4_loadDictSlow() : v1.10.0+ - * Same as LZ4_loadDict(), - * but uses a bit more cpu to reference the dictionary content more thoroughly. - * This is expected to slightly improve compression ratio. - * The extra-cpu cost is likely worth it if the dictionary is re-used across multiple sessions. - * @return : loaded dictionary size, in bytes (note: only the last 64 KB are loaded) - */ -LZ4LIB_API int LZ4_loadDictSlow(LZ4_stream_t* streamPtr, const char* dictionary, int dictSize); - -/*! LZ4_attach_dictionary() : stable since v1.10.0 - * - * This allows efficient re-use of a static dictionary multiple times. - * - * Rather than re-loading the dictionary buffer into a working context before - * each compression, or copying a pre-loaded dictionary's LZ4_stream_t into a - * working LZ4_stream_t, this function introduces a no-copy setup mechanism, - * in which the working stream references @dictionaryStream in-place. - * - * Several assumptions are made about the state of @dictionaryStream. - * Currently, only states which have been prepared by LZ4_loadDict() or - * LZ4_loadDictSlow() should be expected to work. - * - * Alternatively, the provided @dictionaryStream may be NULL, - * in which case any existing dictionary stream is unset. - * - * If a dictionary is provided, it replaces any pre-existing stream history. - * The dictionary contents are the only history that can be referenced and - * logically immediately precede the data compressed in the first subsequent - * compression call. - * - * The dictionary will only remain attached to the working stream through the - * first compression call, at the end of which it is cleared. - * @dictionaryStream stream (and source buffer) must remain in-place / accessible / unchanged - * through the completion of the compression session. - * - * Note: there is no equivalent LZ4_attach_*() method on the decompression side - * because there is no initialization cost, hence no need to share the cost across multiple sessions. - * To decompress LZ4 blocks using dictionary, attached or not, - * just employ the regular LZ4_setStreamDecode() for streaming, - * or the stateless LZ4_decompress_safe_usingDict() for one-shot decompression. - */ -LZ4LIB_API void -LZ4_attach_dictionary(LZ4_stream_t* workingStream, - const LZ4_stream_t* dictionaryStream); - -/*! LZ4_compress_fast_continue() : - * Compress 'src' content using data from previously compressed blocks, for better compression ratio. - * 'dst' buffer must be already allocated. - * If dstCapacity >= LZ4_compressBound(srcSize), compression is guaranteed to succeed, and runs faster. - * - * @return : size of compressed block - * or 0 if there is an error (typically, cannot fit into 'dst'). - * - * Note 1 : Each invocation to LZ4_compress_fast_continue() generates a new block. - * Each block has precise boundaries. - * Each block must be decompressed separately, calling LZ4_decompress_*() with relevant metadata. - * It's not possible to append blocks together and expect a single invocation of LZ4_decompress_*() to decompress them together. - * - * Note 2 : The previous 64KB of source data is __assumed__ to remain present, unmodified, at same address in memory ! - * - * Note 3 : When input is structured as a double-buffer, each buffer can have any size, including < 64 KB. - * Make sure that buffers are separated, by at least one byte. - * This construction ensures that each block only depends on previous block. - * - * Note 4 : If input buffer is a ring-buffer, it can have any size, including < 64 KB. - * - * Note 5 : After an error, the stream status is undefined (invalid), it can only be reset or freed. - */ -LZ4LIB_API int LZ4_compress_fast_continue (LZ4_stream_t* streamPtr, const char* src, char* dst, int srcSize, int dstCapacity, int acceleration); - -/*! LZ4_saveDict() : - * If last 64KB data cannot be guaranteed to remain available at its current memory location, - * save it into a safer place (char* safeBuffer). - * This is schematically equivalent to a memcpy() followed by LZ4_loadDict(), - * but is much faster, because LZ4_saveDict() doesn't need to rebuild tables. - * @return : saved dictionary size in bytes (necessarily <= maxDictSize), or 0 if error. - */ -LZ4LIB_API int LZ4_saveDict (LZ4_stream_t* streamPtr, char* safeBuffer, int maxDictSize); - - -/*-********************************************** -* Streaming Decompression Functions -* Bufferless synchronous API -************************************************/ -typedef union LZ4_streamDecode_u LZ4_streamDecode_t; /* tracking context */ - -/*! LZ4_createStreamDecode() and LZ4_freeStreamDecode() : - * creation / destruction of streaming decompression tracking context. - * A tracking context can be re-used multiple times. - */ -#if !defined(RC_INVOKED) /* https://docs.microsoft.com/en-us/windows/win32/menurc/predefined-macros */ -#if !defined(LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION) -LZ4LIB_API LZ4_streamDecode_t* LZ4_createStreamDecode(void); -LZ4LIB_API int LZ4_freeStreamDecode (LZ4_streamDecode_t* LZ4_stream); -#endif /* !defined(LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION) */ -#endif - -/*! LZ4_setStreamDecode() : - * An LZ4_streamDecode_t context can be allocated once and re-used multiple times. - * Use this function to start decompression of a new stream of blocks. - * A dictionary can optionally be set. Use NULL or size 0 for a reset order. - * Dictionary is presumed stable : it must remain accessible and unmodified during next decompression. - * @return : 1 if OK, 0 if error - */ -LZ4LIB_API int LZ4_setStreamDecode (LZ4_streamDecode_t* LZ4_streamDecode, const char* dictionary, int dictSize); - -/*! LZ4_decoderRingBufferSize() : v1.8.2+ - * Note : in a ring buffer scenario (optional), - * blocks are presumed decompressed next to each other - * up to the moment there is not enough remaining space for next block (remainingSize < maxBlockSize), - * at which stage it resumes from beginning of ring buffer. - * When setting such a ring buffer for streaming decompression, - * provides the minimum size of this ring buffer - * to be compatible with any source respecting maxBlockSize condition. - * @return : minimum ring buffer size, - * or 0 if there is an error (invalid maxBlockSize). - */ -LZ4LIB_API int LZ4_decoderRingBufferSize(int maxBlockSize); -#define LZ4_DECODER_RING_BUFFER_SIZE(maxBlockSize) (65536 + 14 + (maxBlockSize)) /* for static allocation; maxBlockSize presumed valid */ - -/*! LZ4_decompress_safe_continue() : - * This decoding function allows decompression of consecutive blocks in "streaming" mode. - * The difference with the usual independent blocks is that - * new blocks are allowed to find references into former blocks. - * A block is an unsplittable entity, and must be presented entirely to the decompression function. - * LZ4_decompress_safe_continue() only accepts one block at a time. - * It's modeled after `LZ4_decompress_safe()` and behaves similarly. - * - * @LZ4_streamDecode : decompression state, tracking the position in memory of past data - * @compressedSize : exact complete size of one compressed block. - * @dstCapacity : size of destination buffer (which must be already allocated), - * must be an upper bound of decompressed size. - * @return : number of bytes decompressed into destination buffer (necessarily <= dstCapacity) - * If destination buffer is not large enough, decoding will stop and output an error code (negative value). - * If the source stream is detected malformed, the function will stop decoding and return a negative result. - * - * The last 64KB of previously decoded data *must* remain available and unmodified - * at the memory position where they were previously decoded. - * If less than 64KB of data has been decoded, all the data must be present. - * - * Special : if decompression side sets a ring buffer, it must respect one of the following conditions : - * - Decompression buffer size is _at least_ LZ4_decoderRingBufferSize(maxBlockSize). - * maxBlockSize is the maximum size of any single block. It can have any value > 16 bytes. - * In which case, encoding and decoding buffers do not need to be synchronized. - * Actually, data can be produced by any source compliant with LZ4 format specification, and respecting maxBlockSize. - * - Synchronized mode : - * Decompression buffer size is _exactly_ the same as compression buffer size, - * and follows exactly same update rule (block boundaries at same positions), - * and decoding function is provided with exact decompressed size of each block (exception for last block of the stream), - * _then_ decoding & encoding ring buffer can have any size, including small ones ( < 64 KB). - * - Decompression buffer is larger than encoding buffer, by a minimum of maxBlockSize more bytes. - * In which case, encoding and decoding buffers do not need to be synchronized, - * and encoding ring buffer can have any size, including small ones ( < 64 KB). - * - * Whenever these conditions are not possible, - * save the last 64KB of decoded data into a safe buffer where it can't be modified during decompression, - * then indicate where this data is saved using LZ4_setStreamDecode(), before decompressing next block. -*/ -LZ4LIB_API int -LZ4_decompress_safe_continue (LZ4_streamDecode_t* LZ4_streamDecode, - const char* src, char* dst, - int srcSize, int dstCapacity); - - -/*! LZ4_decompress_safe_usingDict() : - * Works the same as - * a combination of LZ4_setStreamDecode() followed by LZ4_decompress_safe_continue() - * However, it's stateless: it doesn't need any LZ4_streamDecode_t state. - * Dictionary is presumed stable : it must remain accessible and unmodified during decompression. - * Performance tip : Decompression speed can be substantially increased - * when dst == dictStart + dictSize. - */ -LZ4LIB_API int -LZ4_decompress_safe_usingDict(const char* src, char* dst, - int srcSize, int dstCapacity, - const char* dictStart, int dictSize); - -/*! LZ4_decompress_safe_partial_usingDict() : - * Behaves the same as LZ4_decompress_safe_partial() - * with the added ability to specify a memory segment for past data. - * Performance tip : Decompression speed can be substantially increased - * when dst == dictStart + dictSize. - */ -LZ4LIB_API int -LZ4_decompress_safe_partial_usingDict(const char* src, char* dst, - int compressedSize, - int targetOutputSize, int maxOutputSize, - const char* dictStart, int dictSize); - -#endif /* LZ4_H_2983827168210 */ - - -/*^************************************* - * !!!!!! STATIC LINKING ONLY !!!!!! - ***************************************/ - -/*-**************************************************************************** - * Experimental section - * - * Symbols declared in this section must be considered unstable. Their - * signatures or semantics may change, or they may be removed altogether in the - * future. They are therefore only safe to depend on when the caller is - * statically linked against the library. - * - * To protect against unsafe usage, not only are the declarations guarded, - * the definitions are hidden by default - * when building LZ4 as a shared/dynamic library. - * - * In order to access these declarations, - * define LZ4_STATIC_LINKING_ONLY in your application - * before including LZ4's headers. - * - * In order to make their implementations accessible dynamically, you must - * define LZ4_PUBLISH_STATIC_FUNCTIONS when building the LZ4 library. - ******************************************************************************/ - -#ifdef LZ4_STATIC_LINKING_ONLY - -#ifndef LZ4_STATIC_3504398509 -#define LZ4_STATIC_3504398509 - -#ifdef LZ4_PUBLISH_STATIC_FUNCTIONS -# define LZ4LIB_STATIC_API LZ4LIB_API -#else -# define LZ4LIB_STATIC_API -#endif - - -/*! LZ4_compress_fast_extState_fastReset() : - * A variant of LZ4_compress_fast_extState(). - * - * Using this variant avoids an expensive initialization step. - * It is only safe to call if the state buffer is known to be correctly initialized already - * (see above comment on LZ4_resetStream_fast() for a definition of "correctly initialized"). - * From a high level, the difference is that - * this function initializes the provided state with a call to something like LZ4_resetStream_fast() - * while LZ4_compress_fast_extState() starts with a call to LZ4_resetStream(). - */ -LZ4LIB_STATIC_API int LZ4_compress_fast_extState_fastReset (void* state, const char* src, char* dst, int srcSize, int dstCapacity, int acceleration); - -/*! LZ4_compress_destSize_extState() : introduced in v1.10.0 - * Same as LZ4_compress_destSize(), but using an externally allocated state. - * Also: exposes @acceleration - */ -int LZ4_compress_destSize_extState(void* state, const char* src, char* dst, int* srcSizePtr, int targetDstSize, int acceleration); - -/*! In-place compression and decompression - * - * It's possible to have input and output sharing the same buffer, - * for highly constrained memory environments. - * In both cases, it requires input to lay at the end of the buffer, - * and decompression to start at beginning of the buffer. - * Buffer size must feature some margin, hence be larger than final size. - * - * |<------------------------buffer--------------------------------->| - * |<-----------compressed data--------->| - * |<-----------decompressed size------------------>| - * |<----margin---->| - * - * This technique is more useful for decompression, - * since decompressed size is typically larger, - * and margin is short. - * - * In-place decompression will work inside any buffer - * which size is >= LZ4_DECOMPRESS_INPLACE_BUFFER_SIZE(decompressedSize). - * This presumes that decompressedSize > compressedSize. - * Otherwise, it means compression actually expanded data, - * and it would be more efficient to store such data with a flag indicating it's not compressed. - * This can happen when data is not compressible (already compressed, or encrypted). - * - * For in-place compression, margin is larger, as it must be able to cope with both - * history preservation, requiring input data to remain unmodified up to LZ4_DISTANCE_MAX, - * and data expansion, which can happen when input is not compressible. - * As a consequence, buffer size requirements are much higher, - * and memory savings offered by in-place compression are more limited. - * - * There are ways to limit this cost for compression : - * - Reduce history size, by modifying LZ4_DISTANCE_MAX. - * Note that it is a compile-time constant, so all compressions will apply this limit. - * Lower values will reduce compression ratio, except when input_size < LZ4_DISTANCE_MAX, - * so it's a reasonable trick when inputs are known to be small. - * - Require the compressor to deliver a "maximum compressed size". - * This is the `dstCapacity` parameter in `LZ4_compress*()`. - * When this size is < LZ4_COMPRESSBOUND(inputSize), then compression can fail, - * in which case, the return code will be 0 (zero). - * The caller must be ready for these cases to happen, - * and typically design a backup scheme to send data uncompressed. - * The combination of both techniques can significantly reduce - * the amount of margin required for in-place compression. - * - * In-place compression can work in any buffer - * which size is >= (maxCompressedSize) - * with maxCompressedSize == LZ4_COMPRESSBOUND(srcSize) for guaranteed compression success. - * LZ4_COMPRESS_INPLACE_BUFFER_SIZE() depends on both maxCompressedSize and LZ4_DISTANCE_MAX, - * so it's possible to reduce memory requirements by playing with them. - */ - -#define LZ4_DECOMPRESS_INPLACE_MARGIN(compressedSize) (((compressedSize) >> 8) + 32) -#define LZ4_DECOMPRESS_INPLACE_BUFFER_SIZE(decompressedSize) ((decompressedSize) + LZ4_DECOMPRESS_INPLACE_MARGIN(decompressedSize)) /**< note: presumes that compressedSize < decompressedSize. note2: margin is overestimated a bit, since it could use compressedSize instead */ - -#ifndef LZ4_DISTANCE_MAX /* history window size; can be user-defined at compile time */ -# define LZ4_DISTANCE_MAX 65535 /* set to maximum value by default */ -#endif - -#define LZ4_COMPRESS_INPLACE_MARGIN (LZ4_DISTANCE_MAX + 32) /* LZ4_DISTANCE_MAX can be safely replaced by srcSize when it's smaller */ -#define LZ4_COMPRESS_INPLACE_BUFFER_SIZE(maxCompressedSize) ((maxCompressedSize) + LZ4_COMPRESS_INPLACE_MARGIN) /**< maxCompressedSize is generally LZ4_COMPRESSBOUND(inputSize), but can be set to any lower value, with the risk that compression can fail (return code 0(zero)) */ - -#endif /* LZ4_STATIC_3504398509 */ -#endif /* LZ4_STATIC_LINKING_ONLY */ - - - -#ifndef LZ4_H_98237428734687 -#define LZ4_H_98237428734687 - -/*-************************************************************ - * Private Definitions - ************************************************************** - * Do not use these definitions directly. - * They are only exposed to allow static allocation of `LZ4_stream_t` and `LZ4_streamDecode_t`. - * Accessing members will expose user code to API and/or ABI break in future versions of the library. - **************************************************************/ -#define LZ4_HASHLOG (LZ4_MEMORY_USAGE-2) -#define LZ4_HASHTABLESIZE (1 << LZ4_MEMORY_USAGE) -#define LZ4_HASH_SIZE_U32 (1 << LZ4_HASHLOG) /* required as macro for static allocation */ - -#if defined(__cplusplus) || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) -# include - typedef int8_t LZ4_i8; - typedef uint8_t LZ4_byte; - typedef uint16_t LZ4_u16; - typedef uint32_t LZ4_u32; -#else - typedef signed char LZ4_i8; - typedef unsigned char LZ4_byte; - typedef unsigned short LZ4_u16; - typedef unsigned int LZ4_u32; -#endif - -/*! LZ4_stream_t : - * Never ever use below internal definitions directly ! - * These definitions are not API/ABI safe, and may change in future versions. - * If you need static allocation, declare or allocate an LZ4_stream_t object. -**/ - -typedef struct LZ4_stream_t_internal LZ4_stream_t_internal; -struct LZ4_stream_t_internal { - LZ4_u32 hashTable[LZ4_HASH_SIZE_U32]; - const LZ4_byte* dictionary; - const LZ4_stream_t_internal* dictCtx; - LZ4_u32 currentOffset; - LZ4_u32 tableType; - LZ4_u32 dictSize; - /* Implicit padding to ensure structure is aligned */ -}; - -#define LZ4_STREAM_MINSIZE ((1UL << (LZ4_MEMORY_USAGE)) + 32) /* static size, for inter-version compatibility */ -union LZ4_stream_u { - char minStateSize[LZ4_STREAM_MINSIZE]; - LZ4_stream_t_internal internal_donotuse; -}; /* previously typedef'd to LZ4_stream_t */ - - -/*! LZ4_initStream() : v1.9.0+ - * An LZ4_stream_t structure must be initialized at least once. - * This is automatically done when invoking LZ4_createStream(), - * but it's not when the structure is simply declared on stack (for example). - * - * Use LZ4_initStream() to properly initialize a newly declared LZ4_stream_t. - * It can also initialize any arbitrary buffer of sufficient size, - * and will @return a pointer of proper type upon initialization. - * - * Note : initialization fails if size and alignment conditions are not respected. - * In which case, the function will @return NULL. - * Note2: An LZ4_stream_t structure guarantees correct alignment and size. - * Note3: Before v1.9.0, use LZ4_resetStream() instead -**/ -LZ4LIB_API LZ4_stream_t* LZ4_initStream (void* stateBuffer, size_t size); - - -/*! LZ4_streamDecode_t : - * Never ever use below internal definitions directly ! - * These definitions are not API/ABI safe, and may change in future versions. - * If you need static allocation, declare or allocate an LZ4_streamDecode_t object. -**/ -typedef struct { - const LZ4_byte* externalDict; - const LZ4_byte* prefixEnd; - size_t extDictSize; - size_t prefixSize; -} LZ4_streamDecode_t_internal; - -#define LZ4_STREAMDECODE_MINSIZE 32 -union LZ4_streamDecode_u { - char minStateSize[LZ4_STREAMDECODE_MINSIZE]; - LZ4_streamDecode_t_internal internal_donotuse; -} ; /* previously typedef'd to LZ4_streamDecode_t */ - - - -/*-************************************ -* Obsolete Functions -**************************************/ - -/*! Deprecation warnings - * - * Deprecated functions make the compiler generate a warning when invoked. - * This is meant to invite users to update their source code. - * Should deprecation warnings be a problem, it is generally possible to disable them, - * typically with -Wno-deprecated-declarations for gcc - * or _CRT_SECURE_NO_WARNINGS in Visual. - * - * Another method is to define LZ4_DISABLE_DEPRECATE_WARNINGS - * before including the header file. - */ -#ifdef LZ4_DISABLE_DEPRECATE_WARNINGS -# define LZ4_DEPRECATED(message) /* disable deprecation warnings */ -#else -# if defined (__cplusplus) && (__cplusplus >= 201402) /* C++14 or greater */ -# define LZ4_DEPRECATED(message) [[deprecated(message)]] -# elif defined(_MSC_VER) -# define LZ4_DEPRECATED(message) __declspec(deprecated(message)) -# elif defined(__clang__) || (defined(__GNUC__) && (__GNUC__ * 10 + __GNUC_MINOR__ >= 45)) -# define LZ4_DEPRECATED(message) __attribute__((deprecated(message))) -# elif defined(__GNUC__) && (__GNUC__ * 10 + __GNUC_MINOR__ >= 31) -# define LZ4_DEPRECATED(message) __attribute__((deprecated)) -# else -# pragma message("WARNING: LZ4_DEPRECATED needs custom implementation for this compiler") -# define LZ4_DEPRECATED(message) /* disabled */ -# endif -#endif /* LZ4_DISABLE_DEPRECATE_WARNINGS */ - -/*! Obsolete compression functions (since v1.7.3) */ -LZ4_DEPRECATED("use LZ4_compress_default() instead") LZ4LIB_API int LZ4_compress (const char* src, char* dest, int srcSize); -LZ4_DEPRECATED("use LZ4_compress_default() instead") LZ4LIB_API int LZ4_compress_limitedOutput (const char* src, char* dest, int srcSize, int maxOutputSize); -LZ4_DEPRECATED("use LZ4_compress_fast_extState() instead") LZ4LIB_API int LZ4_compress_withState (void* state, const char* source, char* dest, int inputSize); -LZ4_DEPRECATED("use LZ4_compress_fast_extState() instead") LZ4LIB_API int LZ4_compress_limitedOutput_withState (void* state, const char* source, char* dest, int inputSize, int maxOutputSize); -LZ4_DEPRECATED("use LZ4_compress_fast_continue() instead") LZ4LIB_API int LZ4_compress_continue (LZ4_stream_t* LZ4_streamPtr, const char* source, char* dest, int inputSize); -LZ4_DEPRECATED("use LZ4_compress_fast_continue() instead") LZ4LIB_API int LZ4_compress_limitedOutput_continue (LZ4_stream_t* LZ4_streamPtr, const char* source, char* dest, int inputSize, int maxOutputSize); - -/*! Obsolete decompression functions (since v1.8.0) */ -LZ4_DEPRECATED("use LZ4_decompress_fast() instead") LZ4LIB_API int LZ4_uncompress (const char* source, char* dest, int outputSize); -LZ4_DEPRECATED("use LZ4_decompress_safe() instead") LZ4LIB_API int LZ4_uncompress_unknownOutputSize (const char* source, char* dest, int isize, int maxOutputSize); - -/* Obsolete streaming functions (since v1.7.0) - * degraded functionality; do not use! - * - * In order to perform streaming compression, these functions depended on data - * that is no longer tracked in the state. They have been preserved as well as - * possible: using them will still produce a correct output. However, they don't - * actually retain any history between compression calls. The compression ratio - * achieved will therefore be no better than compressing each chunk - * independently. - */ -LZ4_DEPRECATED("Use LZ4_createStream() instead") LZ4LIB_API void* LZ4_create (char* inputBuffer); -LZ4_DEPRECATED("Use LZ4_createStream() instead") LZ4LIB_API int LZ4_sizeofStreamState(void); -LZ4_DEPRECATED("Use LZ4_resetStream() instead") LZ4LIB_API int LZ4_resetStreamState(void* state, char* inputBuffer); -LZ4_DEPRECATED("Use LZ4_saveDict() instead") LZ4LIB_API char* LZ4_slideInputBuffer (void* state); - -/*! Obsolete streaming decoding functions (since v1.7.0) */ -LZ4_DEPRECATED("use LZ4_decompress_safe_usingDict() instead") LZ4LIB_API int LZ4_decompress_safe_withPrefix64k (const char* src, char* dst, int compressedSize, int maxDstSize); -LZ4_DEPRECATED("use LZ4_decompress_fast_usingDict() instead") LZ4LIB_API int LZ4_decompress_fast_withPrefix64k (const char* src, char* dst, int originalSize); - -/*! Obsolete LZ4_decompress_fast variants (since v1.9.0) : - * These functions used to be faster than LZ4_decompress_safe(), - * but this is no longer the case. They are now slower. - * This is because LZ4_decompress_fast() doesn't know the input size, - * and therefore must progress more cautiously into the input buffer to not read beyond the end of block. - * On top of that `LZ4_decompress_fast()` is not protected vs malformed or malicious inputs, making it a security liability. - * As a consequence, LZ4_decompress_fast() is strongly discouraged, and deprecated. - * - * The last remaining LZ4_decompress_fast() specificity is that - * it can decompress a block without knowing its compressed size. - * Such functionality can be achieved in a more secure manner - * by employing LZ4_decompress_safe_partial(). - * - * Parameters: - * originalSize : is the uncompressed size to regenerate. - * `dst` must be already allocated, its size must be >= 'originalSize' bytes. - * @return : number of bytes read from source buffer (== compressed size). - * The function expects to finish at block's end exactly. - * If the source stream is detected malformed, the function stops decoding and returns a negative result. - * note : LZ4_decompress_fast*() requires originalSize. Thanks to this information, it never writes past the output buffer. - * However, since it doesn't know its 'src' size, it may read an unknown amount of input, past input buffer bounds. - * Also, since match offsets are not validated, match reads from 'src' may underflow too. - * These issues never happen if input (compressed) data is correct. - * But they may happen if input data is invalid (error or intentional tampering). - * As a consequence, use these functions in trusted environments with trusted data **only**. - */ -LZ4_DEPRECATED("This function is deprecated and unsafe. Consider using LZ4_decompress_safe_partial() instead") -LZ4LIB_API int LZ4_decompress_fast (const char* src, char* dst, int originalSize); -LZ4_DEPRECATED("This function is deprecated and unsafe. Consider migrating towards LZ4_decompress_safe_continue() instead. " - "Note that the contract will change (requires block's compressed size, instead of decompressed size)") -LZ4LIB_API int LZ4_decompress_fast_continue (LZ4_streamDecode_t* LZ4_streamDecode, const char* src, char* dst, int originalSize); -LZ4_DEPRECATED("This function is deprecated and unsafe. Consider using LZ4_decompress_safe_partial_usingDict() instead") -LZ4LIB_API int LZ4_decompress_fast_usingDict (const char* src, char* dst, int originalSize, const char* dictStart, int dictSize); - -/*! LZ4_resetStream() : - * An LZ4_stream_t structure must be initialized at least once. - * This is done with LZ4_initStream(), or LZ4_resetStream(). - * Consider switching to LZ4_initStream(), - * invoking LZ4_resetStream() will trigger deprecation warnings in the future. - */ -LZ4LIB_API void LZ4_resetStream (LZ4_stream_t* streamPtr); - - -#endif /* LZ4_H_98237428734687 */ - - -#if defined (__cplusplus) -} -#endif \ No newline at end of file diff --git a/markdown.c b/markdown.c deleted file mode 100644 index ed8d0ab..0000000 --- a/markdown.c +++ /dev/null @@ -1,504 +0,0 @@ -#include "markdown.h" - -#include "arena.h" -#include "str.h" -#include "strstream.h" -#include "file.h" -#include "ini.h" -#include "tracelog.h" - -#ifndef MD_LIST_MAX_DEPTH - #define MD_LIST_MAX_DEPTH 8 -#endif - -typedef struct { - struct { - int indent; - int count; - bool list_is_ordered[MD_LIST_MAX_DEPTH]; - } list; - struct { - bool is_in_block; - strview_t lang; - } code; - bool is_bold; - bool is_italic; - bool is_in_paragraph; - strview_t raw_line; - md_options_t *options; - md_parser_t *curparser; -} markdown_ctx_t; - -static void markdown__parse_config(arena_t *arena, instream_t *in, ini_t *out); -static int markdown__count_chars(strview_t *line, char c); -static void markdown__parse_line(markdown_ctx_t *md, strview_t line, outstream_t *out, bool add_newline, bool is_line_start); -static strview_t markdown__parse_header(markdown_ctx_t *md, strview_t line, outstream_t *out); -static strview_t markdown__parse_ulist_or_line(markdown_ctx_t *md, strview_t line, outstream_t *out); -static strview_t markdown__parse_olist(markdown_ctx_t *md, strview_t line, outstream_t *out); -static strview_t markdown__parse_code_block(markdown_ctx_t *md, strview_t line, outstream_t *out); -static bool markdown__try_parse_url(instream_t *in, strview_t *out_url, strview_t *out_text); -static void markdown__empty_line(markdown_ctx_t *md, outstream_t *out); -static void markdown__close_list(markdown_ctx_t *md, outstream_t *out); -static void markdown__escape(strview_t view, outstream_t *out); - -str_t markdown(arena_t *arena, arena_t scratch, strview_t filename, md_options_t *options) { - str_t text = fileReadWholeStr(&scratch, filename); - return markdownStr(arena, strv(text), options); -} - -str_t markdownStr(arena_t *arena, strview_t markdown_str, md_options_t *options) { - instream_t in = istrInitLen(markdown_str.buf, markdown_str.len); - - markdown__parse_config(arena, &in, options ? options->out_config : NULL); - - outstream_t out = ostrInit(arena); - - markdown_ctx_t md = { - .list = { - .indent = -1, - }, - .options = options, - }; - - while (!istrIsFinished(in)) { - md.raw_line = istrGetLine(&in); - markdown__parse_line(&md, strvTrimLeft(md.raw_line), &out, true, true); - } - - markdown__empty_line(&md, &out); - - return ostrAsStr(&out); -} - -// == PRIVATE FUNCTIONS ================================================== - -static void markdown__parse_config(arena_t *arena, instream_t *in, ini_t *out) { - strview_t first_line = strvTrim(istrGetLine(in)); - if (!strvEquals(first_line, strv("---"))) { - istrRewind(in); - return; - } - - strview_t ini_data = strvInitLen(in->cur, 0); - usize data_beg = istrTell(*in); - while (!istrIsFinished(*in)) { - strview_t line = istrGetViewEither(in, strv("\r\n")); - if (strvEquals(strvTrim(line), strv("---"))) { - break; - } - istrSkipWhitespace(in); - } - usize data_end = istrTell(*in); - ini_data.len = data_end - data_beg - 3; - - if (out) { - // allocate the string as ini_t only as a copy - str_t ini_str = str(arena, ini_data); - *out = iniParseStr(arena, strv(ini_str), NULL); - } -} - -static int markdown__count_chars(strview_t *line, char c) { - strview_t temp = *line; - int n = 0; - while (strvFront(temp) == c) { - n++; - temp = strvRemovePrefix(temp, 1); - } - - *line = temp; - return n; -} - -static strview_t markdown__parse_header(markdown_ctx_t* md, strview_t line, outstream_t *out) { - int n = markdown__count_chars(&line, '#'); - line = strvTrimLeft(line); - - ostrPrintf(out, "", n); - markdown__parse_line(md, line, out, false, false); - ostrPrintf(out, "", n); - - return STRV_EMPTY; -} - -static strview_t markdown__parse_ulist_or_line(markdown_ctx_t *md, strview_t line, outstream_t *out) { - // check if there is anything before this character, if there is - // it means we're in the middle of a line and we should ignore - strview_t prev = strvSub(md->raw_line, 0, line.buf - md->raw_line.buf); - int space_count; - for (space_count = 0; space_count < prev.len; ++space_count) { - if (prev.buf[space_count] != ' ') break; - } - - if (space_count < prev.len) { - return line; - } - - // if its only * or -, this is a list - if (line.len > 1 && line.buf[1] == ' ') { - strview_t raw_line = md->raw_line; - int cur_indent = markdown__count_chars(&raw_line, ' '); - // start of list - if (md->list.indent < cur_indent) { - if (md->list.count >= MD_LIST_MAX_DEPTH) { - fatal("markdown: too many list levels, max is %d, define MD_LIST_MAX_DEPTH to an higher number", MD_LIST_MAX_DEPTH); - } - md->list.list_is_ordered[md->list.count++] = false; - ostrPuts(out, strv("
    \n")); - } - else if (md->list.indent > cur_indent) { - markdown__close_list(md, out); - } - - md->list.indent = cur_indent; - ostrPuts(out, strv("
  • ")); - markdown__parse_line(md, strvRemovePrefix(line, 2), out, false, false); - ostrPuts(out, strv("
  • ")); - goto read_whole_line; - } - - // check if it is an
    - char hr_char = strvFront(line); - strview_t hr = strvTrim(line); - bool is_hr = true; - for (usize i = 0; i < hr.len; ++i) { - if (hr.buf[i] != hr_char) { - is_hr = false; - break; - } - } - - if (is_hr) { - ostrPuts(out, strv("
    ")); - goto read_whole_line; - } - else { - strview_t to_print = line; - int n = markdown__count_chars(&line, strvFront(line)); - to_print = strvSub(to_print, 0, n); - line = strvSub(line, n, SIZE_MAX); - ostrPuts(out, to_print); - } - - return line; -read_whole_line: - return STRV_EMPTY; -} - -static strview_t markdown__parse_olist(markdown_ctx_t *md, strview_t line, outstream_t *out) { - instream_t in = istrInitLen(line.buf, line.len); - - int32 number = 0; - if (!istrGetI32(&in, &number)) { - return line; - } - - if (istrPeek(&in) != '.') { - return line; - } - - istrSkip(&in, 1); - - if (istrPeek(&in) != ' ') { - return line; - } - - istrSkip(&in, 1); - - strview_t raw_line = md->raw_line; - int cur_indent = markdown__count_chars(&raw_line, ' '); - // start of list - if (md->list.indent < cur_indent) { - if (md->list.count >= MD_LIST_MAX_DEPTH) { - fatal("markdown: too many list levels, max is %d, define MD_LIST_MAX_DEPTH to an higher number", MD_LIST_MAX_DEPTH); - } - md->list.list_is_ordered[md->list.count++] = true; - ostrPuts(out, strv("
      \n")); - } - else if (md->list.indent > cur_indent) { - markdown__close_list(md, out); - } - - md->list.indent = cur_indent; - ostrPuts(out, strv("
    1. ")); - markdown__parse_line(md, strvRemovePrefix(line, istrTell(in)), out, false, false); - ostrPuts(out, strv("
    2. ")); - - return STRV_EMPTY; -} - -static strview_t markdown__parse_code_block(markdown_ctx_t *md, strview_t line, outstream_t *out) { - strview_t line_copy = line; - int ticks = markdown__count_chars(&line_copy, '`'); - - if (ticks != 3) { - goto finish; - } - - if (md->code.is_in_block) { - md->code.is_in_block = false; - if (md->curparser) { - md_parser_t *p = md->curparser; - if (p->finish) { - p->finish(p->userdata); - } - } - ostrPuts(out, strv("
    \n")); - line = line_copy; - goto finish; - } - - instream_t in = istrInitLen(line_copy.buf, line_copy.len); - strview_t lang = istrGetLine(&in); - - if (!strvIsEmpty(lang)) { - md->curparser = NULL; - md_options_t *opt = md->options; - if (opt) { - for (int i = 0; i < opt->parsers_count; ++i) { - if (strvEquals(lang, opt->parsers->lang)) { - md->curparser = &opt->parsers[i]; - break; - } - } - } - - if (!md->curparser) { - warn("markdown: no parser found for code block in language (%v)", lang); - } - else { - md_parser_t *p = md->curparser; - if (p->init) { - p->init(p->userdata); - } - } - } - - ostrPuts(out, strv("
      ")); - md->code.is_in_block = true; - - return STRV_EMPTY; -finish: - return line; -} - -static bool markdown__try_parse_url(instream_t *in, strview_t *out_url, strview_t *out_text) { - istrSkip(in, 1); // skip [ - - strview_t text = istrGetView(in, ']'); - istrSkip(in, 1); // skip ] - - strview_t url = STRV_EMPTY; - if (istrPeek(in) == '(') { - istrSkip(in, 1); // skip ( - url = istrGetView(in, ')'); - istrSkip(in, 1); // skip ) - } - - bool success = !strvIsEmpty(url); - - if (success) { - *out_url = url; - *out_text = text; - } - - return success; -} - -static void markdown__parse_line(markdown_ctx_t *md, strview_t line, outstream_t *out, bool add_newline, bool is_line_start) { - if (md->code.is_in_block && strvFront(line) != '`') { - md_parser_t *p = md->curparser; - if (p && p->callback) { - p->callback(md->raw_line, out, p->userdata); - } - else { - ostrPrintf(out, "%v\n", md->raw_line); - } - return; - } - - if (strvIsEmpty(line)) { - markdown__empty_line(md, out); - return; - } - - switch (strvFront(line)) { - // header - case '#': - line = markdown__parse_header(md, line, out); - break; - // unordered list or
      - case '-': case '*': case '_': - line = markdown__parse_ulist_or_line(md, line, out); - break; - // ordered list - case '0': case '1': case '2': case '3': case '4': - case '5': case '6': case '7': case '8': case '9': - line = markdown__parse_olist(md, line, out); - break; - // code block - case '`': - line = markdown__parse_code_block(md, line, out); - break; - default: - break; - } - - if (!strvIsEmpty(line) && is_line_start && !md->is_in_paragraph) { - md->is_in_paragraph = true; - ostrPuts(out, strv("

      \n")); - } - - for (usize i = 0; i < line.len; ++i) { - switch (line.buf[i]) { - // escape next character - case '\\': - if (++i < line.len) { - ostrPutc(out, line.buf[i]); - } - break; - // bold or italic - case '*': - { - strview_t sub = strvSub(line, i, SIZE_MAX); - int n = markdown__count_chars(&sub, '*'); - - bool is_both = n >= 3; - bool is_italic = n == 1 || is_both; - bool is_bold = n == 2 || is_both; - - if (is_italic) { - ostrPrintf(out, "<%s>", md->is_italic ? "/i" : "i"); - md->is_italic = !md->is_italic; - } - if (is_bold) { - ostrPrintf(out, "<%s>", md->is_bold ? "/b" : "b"); - md->is_bold = !md->is_bold; - } - if (is_both) { - for (int k = 3; k < n; ++k) { - ostrPutc(out, '*'); - } - } - i += n - 1; - break; - } - // url - case '[': - { - instream_t in = istrInitLen(line.buf + i, line.len - i); - strview_t url = STRV_EMPTY; - strview_t text = STRV_EMPTY; - if (markdown__try_parse_url(&in, &url, &text)) { - ostrPrintf(out, "%v", url, strvIsEmpty(text) ? url : text); - i += istrTell(in) - 1; - } - else{ - ostrPutc(out, line.buf[i]); - } - break; - } - // image - case '!': - { - instream_t in = istrInitLen(line.buf + i, line.len - i); - strview_t url = STRV_EMPTY; - strview_t text = STRV_EMPTY; - - istrSkip(&in, 1); // skip ! - - if (markdown__try_parse_url(&in, &url, &text)) { - ostrPrintf(out, "\"%v\"",'); - i += istrTell(in) - 1; - } - else{ - ostrPutc(out, line.buf[i]); - } - break; - } - // code block - case '`': - { - bool is_escaped = false; - if ((i + 1) < line.len) { - is_escaped = line.buf[i + 1] == '`'; - } - instream_t in = istrInitLen(line.buf + i, line.len - i); - - istrSkip(&in, is_escaped ? 2 : 1); // skip ` - ostrPuts(out, strv("")); - while (!istrIsFinished(in)) { - strview_t code = istrGetView(&in, '`'); - markdown__escape(code, out); - if (!is_escaped || istrPeek(&in) == '`') { - break; - } - ostrPutc(out, '`'); - } - ostrPuts(out, strv("")); - i += istrTell(in); - break; - } - default: - ostrPutc(out, line.buf[i]); - break; - } - } - - if (add_newline && !md->code.is_in_block) { - ostrPutc(out, '\n'); - } -} - -static void markdown__empty_line(markdown_ctx_t *md, outstream_t *out) { - // close lists - while (md->list.count > 0) { - if (md->list.list_is_ordered[--md->list.count]) { - ostrPuts(out, strv("

    \n")); - } - else { - ostrPuts(out, strv("
\n")); - } - } - md->list.indent = -1; - - // close paragraph - if (md->is_in_paragraph) { - ostrPuts(out, strv("

\n")); - } - md->is_in_paragraph = false; -} - -static void markdown__close_list(markdown_ctx_t *md, outstream_t *out) { - if (md->list.count > 0) { - if (md->list.list_is_ordered[--md->list.count]) { - ostrPuts(out, strv("\n")); - } - else { - ostrPuts(out, strv("\n")); - } - } -} - -static void markdown__escape(strview_t view, outstream_t *out) { - for (usize i = 0; i < view.len; ++i) { - switch (view.buf[i]){ - case '&': - ostrPuts(out, strv("&")); - break; - case '<': - ostrPuts(out, strv("<")); - break; - case '>': - ostrPuts(out, strv(">")); - break; - default: - ostrPutc(out, view.buf[i]); - break; - } - } -} \ No newline at end of file diff --git a/markdown.h b/markdown.h deleted file mode 100644 index a4184ca..0000000 --- a/markdown.h +++ /dev/null @@ -1,59 +0,0 @@ -#pragma once - -#include "str.h" - -typedef struct outstream_t outstream_t; - -typedef struct { - strview_t lang; - void *userdata; - void (*init)(void *userdata); - void (*finish)(void *userdata); - void (*callback)(strview_t line, outstream_t *out, void *userdata); -} md_parser_t; - -typedef struct { - md_parser_t *parsers; - int parsers_count; - ini_t *out_config; -} md_options_t; - -typedef struct ini_t ini_t; - -str_t markdown(arena_t *arena, arena_t scratch, strview_t filename, md_options_t *options); -str_t markdownStr(arena_t *arena, strview_t markdown_str, md_options_t *options); - -/* -md-lite -a subset of markdown that can be parsed line by line -rules: - begin of file: - [ ] if there are three dashes (---), everythin until the next three dashes will be read as an ini config - - begin of line: - [x] n # -> - [x] *** or --- or ___ on their own line ->
- [x] - or * -> unordered list - [x] n. -> ordered list - [x] ```xyz and newline -> code block of language (xyz is optional) - - mid of line: - [x] * -> italic - [x] ** -> bold - [x] *** -> bold and italic - [x] [text](link) -> link - [x] ![text](link) -> image - [x] ` -> code block until next backtick - - other: - [x] empty line ->

- [x] \ -> escape character - - todo?: - [ ] two space at end of line or \ ->
- [ ] indent inside list -> continue in point - [ ] 4 spaces -> line code block (does NOT work with multiline, use ``` instead) - [ ] -> link - [ ] [text](link "title") -> link - [ ] fix ***both*** -*/ \ No newline at end of file diff --git a/net.c b/net.c new file mode 100644 index 0000000..6a511ee --- /dev/null +++ b/net.c @@ -0,0 +1,632 @@ +#include "net.h" +#include "arena.h" + +#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); + + 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, 5); + 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; + } + } + + 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 sha1Str(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 outlen = arena_tell(arena) - start; + + return (buffer_t){ + .data = out, + .len = outlen, + }; +} \ No newline at end of file diff --git a/net.h b/net.h new file mode 100644 index 0000000..0cfd9f0 --- /dev/null +++ b/net.h @@ -0,0 +1,190 @@ +#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_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; + strview_t body; +} http_request_desc_t; + +// 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); + +// 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 \ No newline at end of file diff --git a/os.c b/os.c new file mode 100644 index 0000000..aef91b8 --- /dev/null +++ b/os.c @@ -0,0 +1,235 @@ +#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", path); + 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 new file mode 100644 index 0000000..95dafa8 --- /dev/null +++ b/os.h @@ -0,0 +1,216 @@ +#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_RESET, + LOG_COL_BLACK, + LOG_COL_BLUE, + LOG_COL_GREEN, + LOG_COL_CYAN, + LOG_COL_RED, + LOG_COL_MAGENTA, + LOG_COL_YELLOW, + LOG_COL_WHITE, +} 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); + +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 path); +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); + +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); + +// == VMEM ====================================== + +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 \ No newline at end of file diff --git a/parsers.c b/parsers.c new file mode 100644 index 0000000..807fb5f --- /dev/null +++ b/parsers.c @@ -0,0 +1,844 @@ +#include "parsers.h" + +#include "os.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; +} + +///// 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) { + err("passed type is not an 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_BLACK, // 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); + err("wrong character at %zu, should be '%c' but is 0x%02x '%c'", istr_tell(in), c, istr_peek(in), istr_peek(in)); + 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"))) { + err("should be null but is: (%.*s) at %zu", null_view.len, null_view.buf, istr_tell(in)); + is_valid = false; + } + + if (!json__is_value_finished(in)) { + err("null, should be finished, but isn't at %zu", istr_tell(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) { + err("trailing comma in array at at %zu: (%c)(%d)", istr_tell(in), *in->cur, *in->cur); + 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); + err("unknown char after array at %zu: (%c)(%d)", istr_tell(in), *in->cur, *in->cur); + 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); + err("unknown char after object at %zu: (%c)(%d)", istr_tell(in), *in->cur, *in->cur); + 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, 2); // skip = and " + strview_t val = strv_trim(istr_get_view(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; +} diff --git a/parsers.h b/parsers.h new file mode 100644 index 0000000..2c17ee1 --- /dev/null +++ b/parsers.h @@ -0,0 +1,156 @@ +#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); + +// == 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); + +#endif \ No newline at end of file diff --git a/server.c b/server.c deleted file mode 100644 index 5a57cc3..0000000 --- a/server.c +++ /dev/null @@ -1,454 +0,0 @@ -#include "server.h" - -#include - -#include "socket.h" -#include "tracelog.h" -#include "strstream.h" -#include "arena.h" - -#if COLLA_NOHTTP -const char *httpGetStatusString(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"; -} -#endif - -#define SERVER_BUFSZ 4096 - -typedef enum { - PARSE_REQ_BEGIN, - PARSE_REQ_PAGE, - PARSE_REQ_VERSION, - PARSE_REQ_FIELDS, - PARSE_REQ_FINISHED, - PARSE_REQ_FAILED, -} server__req_status_e; - -typedef struct { - server_req_t req; - server__req_status_e status; - char fullbuf[SERVER_BUFSZ * 2]; - usize prevbuf_len; -} server__req_ctx_t; - - -typedef struct server__route_t { - str_t page; - server_route_f fn; - void *userdata; - struct server__route_t *next; -} server__route_t; - -typedef struct server_t { - socket_t socket; - server__route_t *routes; - server__route_t *routes_default; - socket_t current_client; - bool should_stop; - uint16 port; -} server_t; - -bool server__parse_chunk(arena_t *arena, server__req_ctx_t *ctx, char buffer[SERVER_BUFSZ], usize buflen) { - memcpy(ctx->fullbuf + ctx->prevbuf_len, buffer, buflen); - usize fulllen = ctx->prevbuf_len + buflen; - - instream_t in = istrInitLen(ctx->fullbuf, fulllen); - -#define RESET_STREAM() in.cur = in.start + begin -#define BEGIN_STREAM() begin = istrTell(in) - - usize begin = istrTell(in); - - switch (ctx->status) { - case PARSE_REQ_BEGIN: - { - BEGIN_STREAM(); - - strview_t method = istrGetView(&in, ' '); - if (istrGet(&in) != ' ') { - RESET_STREAM(); - break; - } - - if (strvEquals(method, strv("GET"))) { - ctx->req.method = HTTP_GET; - } - else if(strvEquals(method, strv("POST"))) { - ctx->req.method = HTTP_POST; - } - else { - err("unknown method: (%.*s)", method.len, method.buf); - ctx->status = PARSE_REQ_FAILED; - break; - } - - ctx->status = PARSE_REQ_PAGE; - } - // fallthrough - case PARSE_REQ_PAGE: - { - BEGIN_STREAM(); - - strview_t page = istrGetView(&in, ' '); - if (istrGet(&in) != ' ') { - RESET_STREAM(); - break; - } - - ctx->req.page = str(arena, page); - - ctx->status = PARSE_REQ_VERSION; - } - // fallthrough - case PARSE_REQ_VERSION: - { - BEGIN_STREAM(); - - strview_t version = istrGetView(&in, '\n'); - if (istrGet(&in) != '\n') { - RESET_STREAM(); - break; - } - - if (version.len < 8) { - err("version too short: (%.*s)", version.len, version.buf); - ctx->status = PARSE_REQ_FAILED; - break; - } - - if (!strvEquals(strvSub(version, 0, 4), strv("HTTP"))) { - err("version does not start with HTTP: (%.4s)", version.buf); - ctx->status = PARSE_REQ_FAILED; - break; - } - - // skip HTTP - version = strvRemovePrefix(version, 4); - - uint8 major, minor; - int scanned = sscanf(version.buf, "/%hhu.%hhu", &major, &minor); - - if (scanned != 2) { - err("could not scan both major and minor from version: (%.*s)", version.len, version.buf); - ctx->status = PARSE_REQ_FAILED; - break; - } - - ctx->req.version_major = major; - ctx->req.version_minor = minor; - - ctx->status = PARSE_REQ_FIELDS; - } - // fallthrough - case PARSE_REQ_FIELDS: - { - bool finished_parsing = false; - - while (true) { - BEGIN_STREAM(); - - strview_t field = istrGetView(&in, '\n'); - if (istrGet(&in) != '\n') { - RESET_STREAM(); - break; - } - - instream_t field_in = istrInitLen(field.buf, field.len); - - strview_t key = istrGetView(&field_in, ':'); - if (istrGet(&field_in) != ':') { - err("field does not have ':' (%.*s)", field.len, field.buf); - ctx->status = PARSE_REQ_FAILED; - break; - } - - istrSkipWhitespace(&field_in); - - strview_t value = istrGetView(&field_in, '\r'); - if (istrGet(&field_in) != '\r') { - warn("field does not finish with \\r: (%.*s)", field.len, field.buf); - RESET_STREAM(); - break; - } - - server_field_t *new_field = alloc(arena, server_field_t); - new_field->key = str(arena, key); - new_field->value = str(arena, value); - - if (!ctx->req.fields) { - ctx->req.fields = new_field; - } - - if (ctx->req.fields_tail) { - ctx->req.fields_tail->next = new_field; - } - - ctx->req.fields_tail = new_field; - - // check if we finished parsing the fields - if (istrGet(&in) == '\r') { - if (istrGet(&in) == '\n') { - finished_parsing = true; - break; - } - else { - istrRewindN(&in, 2); - warn("should have finished parsing field, but apparently not?: (%.*s)", in.cur, istrRemaining(in)); - } - } - else { - istrRewindN(&in, 1); - } - } - - if (!finished_parsing) { - break; - } - - ctx->status = PARSE_REQ_FINISHED; - break; - } - default: break; - } - -#undef RESET_STREAM - - ctx->prevbuf_len = istrRemaining(in); - - memmove(ctx->fullbuf, ctx->fullbuf + istrTell(in), ctx->prevbuf_len); - - return ctx->status >= PARSE_REQ_FINISHED; -} - -void server__parse_req_url(arena_t *arena, server_req_t *req) { - instream_t in = istrInitLen(req->page.buf, req->page.len); - istrIgnore(&in, '?'); - // no fields in url - if (istrGet(&in) != '?') { - return; - } - - req->page.len = istrTell(in) - 1; - req->page.buf[req->page.len] = '\0'; - - while (!istrIsFinished(in)) { - strview_t field = istrGetView(&in, '&'); - istrSkip(&in, 1); // skip & - usize pos = strvFind(field, '=', 0); - if (pos == SIZE_MAX) { - fatal("url parameter does not include =: %.*s", field.buf, field.len); - } - strview_t key = strvSub(field, 0, pos); - strview_t val = strvSub(field, pos + 1, SIZE_MAX); - - server_field_t *f = alloc(arena, server_field_t); - f->key = str(arena, key); - f->value = str(arena, val); - f->next = req->page_fields; - req->page_fields = f; - } -} - -server_t *serverSetup(arena_t *arena, uint16 port, bool try_next_port) { - if (!skInit()) { - fatal("couldn't initialise sockets: %s", skGetErrorString()); - } - - socket_t sk = skOpen(SOCK_TCP); - if (!skIsValid(sk)) { - fatal("couldn't open socket: %s", skGetErrorString()); - } - - bool bound = false; - - while (!bound) { - skaddrin_t addr = { - .sin_family = AF_INET, - .sin_addr.s_addr = INADDR_ANY, - .sin_port = htons(port), - }; - - bound = skBindPro(sk, (skaddr_t *)&addr, sizeof(addr)); - - if (!bound && try_next_port) { - port++; - } - } - - if (!bound) { - fatal("couldn't open socket: %s", skGetErrorString()); - } - - if (!skListenPro(sk, 10)) { - fatal("could not listen on socket: %s", skGetErrorString()); - } - - server_t *server = alloc(arena, server_t); - - server->socket = sk; - server->port = port; - - return server; -} - -void serverRoute(arena_t *arena, server_t *server, strview_t page, server_route_f cb, void *userdata) { - // check if a route for that page already exists - server__route_t *r = server->routes; - - while (r) { - if (strvEquals(strv(r->page), page)) { - r->fn = cb; - r->userdata = userdata; - break; - } - r = r->next; - } - - // no route found, make a new one - if (!r) { - r = alloc(arena, server__route_t); - r->next = server->routes; - server->routes = r; - - r->page = str(arena, page); - } - - r->fn = cb; - r->userdata = userdata; -} - -void serverRouteDefault(arena_t *arena, server_t *server, server_route_f cb, void *userdata) { - server__route_t *r = server->routes_default; - - if (!r) { - r = alloc(arena, server__route_t); - server->routes_default = r; - } - - r->fn = cb; - r->userdata = userdata; -} - -void serverStart(arena_t scratch, server_t *server) { - usize start = arenaTell(&scratch); - - info("Server started at (http://localhost:%d)!", server->port); - - while (!server->should_stop) { - socket_t client = skAccept(server->socket); - if (!skIsValid(client)) { - err("accept failed"); - continue; - } - - arenaRewind(&scratch, start); - - server__req_ctx_t req_ctx = {0}; - - char buffer[SERVER_BUFSZ]; - int read = 0; - do { - read = skReceive(client, buffer, sizeof(buffer)); - if(read == SOCKET_ERROR) { - fatal("couldn't get the data from the server: %s", skGetErrorString()); - } - if (server__parse_chunk(&scratch, &req_ctx, buffer, read)) { - break; - } - } while(read != 0); - - if (req_ctx.status == PARSE_REQ_FAILED || req_ctx.status == PARSE_REQ_BEGIN) { - err("failed to parse request!"); - goto end_connection; - } - - server_req_t req = req_ctx.req; - - server__parse_req_url(&scratch, &req); - - server__route_t *route = server->routes; - while (route) { - if (strEquals(route->page, req.page)) { - break; - } - route = route->next; - } - - if (!route) { - route = server->routes_default; - } - - server->current_client = client; - str_t response = route->fn(scratch, server, &req, route->userdata); - - if (!skIsValid(server->current_client)) { - continue; - } - - skSend(client, response.buf, (int)response.len); - -end_connection: - skClose(client); - } - - if (!skClose(server->socket)) { - fatal("couldn't close socket: %s", skGetErrorString()); - } -} - -void serverStop(server_t *server) { - server->should_stop = true; -} - -str_t serverMakeResponse(arena_t *arena, int status_code, strview_t content_type, strview_t body) { - return strFmt( - arena, - - "HTTP/1.1 %d %s\r\n" - "Content-Type: %v\r\n" - "\r\n" - "%v", - - status_code, httpGetStatusString(status_code), - content_type, - body - ); -} - -socket_t serverGetClient(server_t *server) { - return server->current_client; -} - -void serverSetClient(server_t *server, socket_t client) { - server->current_client = client; -} - - -#undef SERVER_BUFSZ \ No newline at end of file diff --git a/server.h b/server.h deleted file mode 100644 index 0cd59b1..0000000 --- a/server.h +++ /dev/null @@ -1,37 +0,0 @@ -#pragma once - -#include "collatypes.h" -#include "str.h" -#include "http.h" - -typedef struct arena_t arena_t; -typedef struct server_t server_t; - -typedef struct server_field_t { - str_t key; - str_t value; - struct server_field_t *next; -} server_field_t; - -typedef struct { - http_method_e method; - str_t page; - server_field_t *page_fields; - uint8 version_minor; - uint8 version_major; - server_field_t *fields; - server_field_t *fields_tail; - // buffer_t body; -} server_req_t; - -typedef str_t (*server_route_f)(arena_t scratch, server_t *server, server_req_t *req, void *userdata); - -server_t *serverSetup(arena_t *arena, uint16 port, bool try_next_port); -void serverRoute(arena_t *arena, server_t *server, strview_t page, server_route_f cb, void *userdata); -void serverRouteDefault(arena_t *arena, server_t *server, server_route_f cb, void *userdata); -void serverStart(arena_t scratch, server_t *server); -void serverStop(server_t *server); - -str_t serverMakeResponse(arena_t *arena, int status_code, strview_t content_type, strview_t body); -socket_t serverGetClient(server_t *server); -void serverSetClient(server_t *server, socket_t client); \ No newline at end of file diff --git a/sha1.c b/sha1.c deleted file mode 100644 index 498759b..0000000 --- a/sha1.c +++ /dev/null @@ -1,120 +0,0 @@ -#include "sha1.h" - -sha1_t sha1_init(void) { - return (sha1_t) { - .digest = { - 0x67452301, - 0xEFCDAB89, - 0x98BADCFE, - 0x10325476, - 0xC3D2E1F0, - }, - }; -} - -uint32 sha1_left_rotate(uint32 value, uint32 count) { - return (value << count) ^ (value >> (32 - count)); -} - -void sha1_process_block(sha1_t *ctx) { - uint32 w [80]; - for (usize i = 0; i < 16; ++i) { - w[i] = ctx->block[i * 4 + 0] << 24; - w[i] |= ctx->block[i * 4 + 1] << 16; - w[i] |= ctx->block[i * 4 + 2] << 8; - w[i] |= ctx->block[i * 4 + 3] << 0; - } - - for (usize i = 16; i < 80; ++i) { - w[i] = sha1_left_rotate(w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16], 1); - } - - uint32 a = ctx->digest[0]; - uint32 b = ctx->digest[1]; - uint32 c = ctx->digest[2]; - uint32 d = ctx->digest[3]; - uint32 e = ctx->digest[4]; - - for (usize i = 0; i < 80; ++i) { - uint32 f = 0; - uint32 k = 0; - - if (i<20) { - f = (b & c) | (~b & d); - k = 0x5A827999; - } else if (i<40) { - f = b ^ c ^ d; - k = 0x6ED9EBA1; - } else if (i<60) { - f = (b & c) | (b & d) | (c & d); - k = 0x8F1BBCDC; - } else { - f = b ^ c ^ d; - k = 0xCA62C1D6; - } - uint32 temp = sha1_left_rotate(a, 5) + f + e + k + w[i]; - e = d; - d = c; - c = sha1_left_rotate(b, 30); - b = a; - a = temp; - } - - ctx->digest[0] += a; - ctx->digest[1] += b; - ctx->digest[2] += c; - ctx->digest[3] += d; - ctx->digest[4] += e; -} - -void sha1_process_byte(sha1_t *ctx, uint8 b) { - ctx->block[ctx->block_index++] = b; - ++ctx->byte_count; - if (ctx->block_index == 64) { - ctx->block_index = 0; - sha1_process_block(ctx); - } -} - -sha1_result_t sha1(sha1_t *ctx, const void *buf, usize len) { - const uint8 *block = buf; - - for (usize i = 0; i < len; ++i) { - sha1_process_byte(ctx, block[i]); - } - - usize bitcount = ctx->byte_count * 8; - sha1_process_byte(ctx, 0x80); - - if (ctx->block_index > 56) { - while (ctx->block_index != 0) { - sha1_process_byte(ctx, 0); - } - while (ctx->block_index < 56) { - sha1_process_byte(ctx, 0); - } - } else { - while (ctx->block_index < 56) { - sha1_process_byte(ctx, 0); - } - } - sha1_process_byte(ctx, 0); - sha1_process_byte(ctx, 0); - sha1_process_byte(ctx, 0); - sha1_process_byte(ctx, 0); - sha1_process_byte(ctx, (uchar)((bitcount >> 24) & 0xFF)); - sha1_process_byte(ctx, (uchar)((bitcount >> 16) & 0xFF)); - sha1_process_byte(ctx, (uchar)((bitcount >> 8 ) & 0xFF)); - sha1_process_byte(ctx, (uchar)((bitcount >> 0 ) & 0xFF)); - - // memcpy(digest, m_digest, 5 * sizeof(uint32_t));# - - sha1_result_t result = {0}; - memcpy(result.digest, ctx->digest, sizeof(result.digest)); - return result; -} - -str_t sha1Str(arena_t *arena, sha1_t *ctx, const void *buf, usize len) { - sha1_result_t result = sha1(ctx, buf, len); - return strFmt(arena, "%08x%08x%08x%08x%08x", result.digest[0], result.digest[1], result.digest[2], result.digest[3], result.digest[4]); -} \ No newline at end of file diff --git a/sha1.h b/sha1.h deleted file mode 100644 index b240f84..0000000 --- a/sha1.h +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once - -#include "str.h" - -typedef struct { - uint32 digest[5]; - uint8 block[64]; - usize block_index; - usize byte_count; -} sha1_t; - -typedef struct { - uint32 digest[5]; -} sha1_result_t; - -sha1_t sha1_init(void); -sha1_result_t sha1(sha1_t *ctx, const void *buf, usize len); -str_t sha1Str(arena_t *arena, sha1_t *ctx, const void *buf, usize len); diff --git a/socket.c b/socket.c deleted file mode 100644 index 2ac3e24..0000000 --- a/socket.c +++ /dev/null @@ -1,286 +0,0 @@ -#include "socket.h" - -#if COLLA_WIN && COLLA_CMT_LIB -#pragma comment(lib, "Ws2_32") -#endif - -#if COLLA_WIN - -typedef int socklen_t; - -bool skInit(void) { - WSADATA w; - int error = WSAStartup(0x0202, &w); - return error == 0; -} - -bool skCleanup(void) { - return WSACleanup() == 0; -} - -bool skClose(socket_t sock) { - return closesocket(sock) != SOCKET_ERROR; -} - -int skPoll(skpoll_t *to_poll, int num_to_poll, int timeout) { - return WSAPoll(to_poll, num_to_poll, timeout); -} - -int skGetError(void) { - return WSAGetLastError(); -} - -#else - -#include -#include -#include -#include -#include // strerror -#include - -bool skInit(void) { - return true; -} - -bool skCleanup(void) { - return true; -} - -bool skClose(socket_t sock) { - return close(sock) != SOCKET_ERROR; -} - -int skPoll(skpoll_t *to_poll, int num_to_poll, int timeout) { - return poll(to_poll, num_to_poll, timeout); -} - -int skGetError(void) { - return errno; -} - -const char *skGetErrorString(void) { - return strerror(errno); -} - -#endif - -socket_t skOpen(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 skOpenPro(AF_INET, sock_type, 0); -} - -socket_t skOpenEx(const char *protocol) { - struct protoent *proto = getprotobyname(protocol); - if(!proto) { - return (socket_t){0}; - } - return skOpenPro(AF_INET, SOCK_STREAM, proto->p_proto); -} - -socket_t skOpenPro(int af, int type, int protocol) { - return socket(af, type, protocol); -} - -bool skIsValid(socket_t sock) { - return sock != SOCKET_ERROR; -} - -skaddrin_t skAddrinInit(const char *ip, uint16_t port) { - return (skaddrin_t){ - .sin_family = AF_INET, - .sin_port = htons(port), - // TODO use inet_pton instead - .sin_addr.s_addr = inet_addr(ip), - }; -} - -bool skBind(socket_t sock, const char *ip, uint16_t port) { - skaddrin_t addr = skAddrinInit(ip, port); - return skBindPro(sock, (skaddr_t *)&addr, sizeof(addr)); -} - -bool skBindPro(socket_t sock, const skaddr_t *name, int namelen) { - return bind(sock, name, namelen) != SOCKET_ERROR; -} - -bool skListen(socket_t sock) { - return skListenPro(sock, 1); -} - -bool skListenPro(socket_t sock, int backlog) { - return listen(sock, backlog) != SOCKET_ERROR; -} - -socket_t skAccept(socket_t sock) { - skaddrin_t addr = {0}; - int addr_size = sizeof(addr); - return skAcceptPro(sock, (skaddr_t *)&addr, &addr_size); -} - -socket_t skAcceptPro(socket_t sock, skaddr_t *addr, int *addrlen) { - return accept(sock, addr, (socklen_t *)addrlen); -} - -bool skConnect(socket_t sock, const char *server, unsigned short server_port) { - // TODO use getaddrinfo insetad - struct hostent *host = gethostbyname(server); - // if gethostbyname fails, inet_addr will also fail and return an easier to debug error - const char *address = server; - if(host) { - address = inet_ntoa(*(struct in_addr*)host->h_addr_list[0]); - } - - skaddrin_t addr = skAddrinInit(address, server_port); - - return skConnectPro(sock, (skaddr_t *)&addr, sizeof(addr)); -} - -bool skConnectPro(socket_t sock, const skaddr_t *name, int namelen) { - return connect(sock, name, namelen) != SOCKET_ERROR; -} - -int skSend(socket_t sock, const void *buf, int len) { - return skSendPro(sock, buf, len, 0); -} - -int skSendPro(socket_t sock, const void *buf, int len, int flags) { - return send(sock, buf, len, flags); -} - -int skSendTo(socket_t sock, const void *buf, int len, const skaddrin_t *to) { - return skSendToPro(sock, buf, len, 0, (skaddr_t*)to, sizeof(skaddrin_t)); -} - -int skSendToPro(socket_t sock, const void *buf, int len, int flags, const skaddr_t *to, int tolen) { - return sendto(sock, buf, len, flags, to, tolen); -} - -int skReceive(socket_t sock, void *buf, int len) { - return skReceivePro(sock, buf, len, 0); -} - -int skReceivePro(socket_t sock, void *buf, int len, int flags) { - return recv(sock, buf, len, flags); -} - -int skReceiveFrom(socket_t sock, void *buf, int len, skaddrin_t *from) { - int fromlen = sizeof(skaddr_t); - return skReceiveFromPro(sock, buf, len, 0, (skaddr_t*)from, &fromlen); -} - -int skReceiveFromPro(socket_t sock, void *buf, int len, int flags, skaddr_t *from, int *fromlen) { - return recvfrom(sock, buf, len, flags, from, (socklen_t *)fromlen); -} - -// put this at the end of file to not make everything else unreadable -#if COLLA_WIN -const char *skGetErrorString(void) { - switch(skGetError()) { - case WSA_INVALID_HANDLE: return "Specified event object handle is invalid."; - case WSA_NOT_ENOUGH_MEMORY: return "Insufficient memory available."; - case WSA_INVALID_PARAMETER: return "One or more parameters are invalid."; - case WSA_OPERATION_ABORTED: return "Overlapped operation aborted."; - case WSA_IO_INCOMPLETE: return "Overlapped I/O event object not in signaled state."; - case WSA_IO_PENDING: return "Overlapped operations will complete later."; - case WSAEINTR: return "Interrupted function call."; - case WSAEBADF: return "File handle is not valid."; - case WSAEACCES: return "Permission denied."; - case WSAEFAULT: return "Bad address."; - case WSAEINVAL: return "Invalid argument."; - case WSAEMFILE: return "Too many open files."; - case WSAEWOULDBLOCK: return "Resource temporarily unavailable."; - case WSAEINPROGRESS: return "Operation now in progress."; - case WSAEALREADY: return "Operation already in progress."; - case WSAENOTSOCK: return "Socket operation on nonsocket."; - case WSAEDESTADDRREQ: return "Destination address required."; - case WSAEMSGSIZE: return "Message too long."; - case WSAEPROTOTYPE: return "Protocol wrong type for socket."; - case WSAENOPROTOOPT: return "Bad protocol option."; - case WSAEPROTONOSUPPORT: return "Protocol not supported."; - case WSAESOCKTNOSUPPORT: return "Socket type not supported."; - case WSAEOPNOTSUPP: return "Operation not supported."; - case WSAEPFNOSUPPORT: return "Protocol family not supported."; - case WSAEAFNOSUPPORT: return "Address family not supported by protocol family."; - case WSAEADDRINUSE: return "Address already in use."; - case WSAEADDRNOTAVAIL: return "Cannot assign requested address."; - case WSAENETDOWN: return "Network is down."; - case WSAENETUNREACH: return "Network is unreachable."; - case WSAENETRESET: return "Network dropped connection on reset."; - case WSAECONNABORTED: return "Software caused connection abort."; - case WSAECONNRESET: return "Connection reset by peer."; - case WSAENOBUFS: return "No buffer space available."; - case WSAEISCONN: return "Socket is already connected."; - case WSAENOTCONN: return "Socket is not connected."; - case WSAESHUTDOWN: return "Cannot send after socket shutdown."; - case WSAETOOMANYREFS: return "Too many references."; - case WSAETIMEDOUT: return "Connection timed out."; - case WSAECONNREFUSED: return "Connection refused."; - case WSAELOOP: return "Cannot translate name."; - case WSAENAMETOOLONG: return "Name too long."; - case WSAEHOSTDOWN: return "Host is down."; - case WSAEHOSTUNREACH: return "No route to host."; - case WSAENOTEMPTY: return "Directory not empty."; - case WSAEPROCLIM: return "Too many processes."; - case WSAEUSERS: return "User quota exceeded."; - case WSAEDQUOT: return "Disk quota exceeded."; - case WSAESTALE: return "Stale file handle reference."; - case WSAEREMOTE: return "Item is remote."; - case WSASYSNOTREADY: return "Network subsystem is unavailable."; - case WSAVERNOTSUPPORTED: return "Winsock.dll version out of range."; - case WSANOTINITIALISED: return "Successful WSAStartup not yet performed."; - case WSAEDISCON: return "Graceful shutdown in progress."; - case WSAENOMORE: return "No more results."; - case WSAECANCELLED: return "Call has been canceled."; - case WSAEINVALIDPROCTABLE: return "Procedure call table is invalid."; - case WSAEINVALIDPROVIDER: return "Service provider is invalid."; - case WSAEPROVIDERFAILEDINIT: return "Service provider failed to initialize."; - case WSASYSCALLFAILURE: return "System call failure."; - case WSASERVICE_NOT_FOUND: return "Service not found."; - case WSATYPE_NOT_FOUND: return "Class type not found."; - case WSA_E_NO_MORE: return "No more results."; - case WSA_E_CANCELLED: return "Call was canceled."; - case WSAEREFUSED: return "Database query was refused."; - case WSAHOST_NOT_FOUND: return "Host not found."; - case WSATRY_AGAIN: return "Nonauthoritative host not found."; - case WSANO_RECOVERY: return "This is a nonrecoverable error."; - case WSANO_DATA: return "Valid name, no data record of requested type."; - case WSA_QOS_RECEIVERS: return "QoS receivers."; - case WSA_QOS_SENDERS: return "QoS senders."; - case WSA_QOS_NO_SENDERS: return "No QoS senders."; - case WSA_QOS_NO_RECEIVERS: return "QoS no receivers."; - case WSA_QOS_REQUEST_CONFIRMED: return "QoS request confirmed."; - case WSA_QOS_ADMISSION_FAILURE: return "QoS admission error."; - case WSA_QOS_POLICY_FAILURE: return "QoS policy failure."; - case WSA_QOS_BAD_STYLE: return "QoS bad style."; - case WSA_QOS_BAD_OBJECT: return "QoS bad object."; - case WSA_QOS_TRAFFIC_CTRL_ERROR: return "QoS traffic control error."; - case WSA_QOS_GENERIC_ERROR: return "QoS generic error."; - case WSA_QOS_ESERVICETYPE: return "QoS service type error."; - case WSA_QOS_EFLOWSPEC: return "QoS flowspec error."; - case WSA_QOS_EPROVSPECBUF: return "Invalid QoS provider buffer."; - case WSA_QOS_EFILTERSTYLE: return "Invalid QoS filter style."; - case WSA_QOS_EFILTERTYPE: return "Invalid QoS filter type."; - case WSA_QOS_EFILTERCOUNT: return "Incorrect QoS filter count."; - case WSA_QOS_EOBJLENGTH: return "Invalid QoS object length."; - case WSA_QOS_EFLOWCOUNT: return "Incorrect QoS flow count."; - case WSA_QOS_EUNKOWNPSOBJ: return "Unrecognized QoS object."; - case WSA_QOS_EPOLICYOBJ: return "Invalid QoS policy object."; - case WSA_QOS_EFLOWDESC: return "Invalid QoS flow descriptor."; - case WSA_QOS_EPSFLOWSPEC: return "Invalid QoS provider-specific flowspec."; - case WSA_QOS_EPSFILTERSPEC: return "Invalid QoS provider-specific filterspec."; - case WSA_QOS_ESDMODEOBJ: return "Invalid QoS shape discard mode object."; - case WSA_QOS_ESHAPERATEOBJ: return "Invalid QoS shaping rate object."; - case WSA_QOS_RESERVED_PETYPE: return "Reserved policy QoS element type."; - } - - return "(nothing)"; -} -#endif \ No newline at end of file diff --git a/socket.h b/socket.h deleted file mode 100644 index 024685c..0000000 --- a/socket.h +++ /dev/null @@ -1,93 +0,0 @@ -#pragma once - -#include "collatypes.h" - -#if COLLA_TCC -#include "tcc/colla_tcc.h" -#elif COLLA_WIN -#define _WINSOCK_DEPRECATED_NO_WARNINGS -#include -#elif COLLA_LIN || COLLA_OSX -#include -#include -#endif - -typedef uintptr_t socket_t; -typedef struct sockaddr skaddr_t; -typedef struct sockaddr_in skaddrin_t; -typedef struct pollfd skpoll_t; - -#define SOCKET_ERROR (-1) - -typedef enum { - SOCK_TCP, - SOCK_UDP, -} sktype_e; - -// Initialize sockets, returns true on success -bool skInit(void); -// Terminates sockets, returns true on success -bool skCleanup(void); - -// Opens a socket, check socket_t with skValid -socket_t skOpen(sktype_e type); -// Opens a socket using 'protocol', options are -// ip, icmp, ggp, tcp, egp, pup, udp, hmp, xns-idp, rdp -// check socket_t with skValid -socket_t skOpenEx(const char *protocol); -// Opens a socket, check socket_t with skValid -socket_t skOpenPro(int af, int type, int protocol); - -// Checks that a opened socket is valid, returns true on success -bool skIsValid(socket_t sock); - -// Closes a socket, returns true on success -bool skClose(socket_t sock); - -// Fill out a sk_addrin_t structure with "ip" and "port" -skaddrin_t skAddrinInit(const char *ip, uint16_t port); - -// Associate a local address with a socket -bool skBind(socket_t sock, const char *ip, uint16_t port); -// Associate a local address with a socket -bool skBindPro(socket_t sock, const skaddr_t *name, int namelen); - -// Place a socket in a state in which it is listening for an incoming connection -bool skListen(socket_t sock); -// Place a socket in a state in which it is listening for an incoming connection -bool skListenPro(socket_t sock, int backlog); - -// Permits an incoming connection attempt on a socket -socket_t skAccept(socket_t sock); -// Permits an incoming connection attempt on a socket -socket_t skAcceptPro(socket_t sock, skaddr_t *addr, int *addrlen); - -// Connects to a server (e.g. "127.0.0.1" or "google.com") with a port(e.g. 1234), returns true on success -bool skConnect(socket_t sock, const char *server, unsigned short server_port); -// Connects to a server, returns true on success -bool skConnectPro(socket_t sock, const skaddr_t *name, int namelen); - -// Sends data on a socket, returns true on success -int skSend(socket_t sock, const void *buf, int len); -// Sends data on a socket, returns true on success -int skSendPro(socket_t sock, const void *buf, int len, int flags); -// Sends data to a specific destination -int skSendTo(socket_t sock, const void *buf, int len, const skaddrin_t *to); -// Sends data to a specific destination -int skSendToPro(socket_t sock, const void *buf, int len, int flags, const skaddr_t *to, int tolen); -// Receives data from a socket, returns byte count on success, 0 on connection close or -1 on error -int skReceive(socket_t sock, void *buf, int len); -// Receives data from a socket, returns byte count on success, 0 on connection close or -1 on error -int skReceivePro(socket_t sock, void *buf, int len, int flags); -// Receives a datagram and stores the source address. -int skReceiveFrom(socket_t sock, void *buf, int len, skaddrin_t *from); -// Receives a datagram and stores the source address. -int skReceiveFromPro(socket_t sock, void *buf, int len, int flags, skaddr_t *from, int *fromlen); - -// Wait for an event on some sockets -int skPoll(skpoll_t *to_poll, int num_to_poll, int timeout); - -// Returns latest socket error, returns 0 if there is no error -int skGetError(void); -// Returns a human-readable string from a skGetError -const char *skGetErrorString(void); diff --git a/str.c b/str.c index 3ee1018..acca8f9 100644 --- a/str.c +++ b/str.c @@ -1,135 +1,143 @@ #include "str.h" -#include "warnings/colla_warn_beg.h" - -#include "arena.h" -#include "format.h" -#include "tracelog.h" +#include +#include +#include #if COLLA_WIN - -#include - +#include "win/str_win32.c" #else - -#include - -#endif - -#if COLLA_TCC -#include "tcc/colla_tcc.h" +#error "platform not supported" #endif // == STR_T ======================================================== -str_t strInit(arena_t *arena, const char *buf) { - return buf ? strInitLen(arena, buf, strlen(buf)) : STR_EMPTY; +str_t str_init(arena_t *arena, const char *buf) { + return str_init_len(arena, buf, buf ? strlen(buf) : 0); } -str_t strInitLen(arena_t *arena, const char *buf, usize len) { - if (!buf || !len) return STR_EMPTY; - - str_t out = { - .buf = alloc(arena, char, len + 1), - .len = len - }; - - memcpy(out.buf, buf, len); - - return out; +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 strInitView(arena_t *arena, strview_t view) { - return strInitLen(arena, view.buf, view.len); +str_t str_init_view(arena_t *arena, strview_t view) { + return str_init_len(arena, view.buf, view.len); } -str_t strFmt(arena_t *arena, const char *fmt, ...) { - va_list args; - va_start(args, fmt); - str_t out = strFmtv(arena, fmt, args); - va_end(args); - return out; +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 strFmtv(arena_t *arena, const char *fmt, va_list args) { +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 = fmtBufferv(NULL, 0, fmt, vcopy); + int len = fmt_bufferv(NULL, 0, fmt, vcopy); va_end(vcopy); char *buffer = alloc(arena, char, len + 1); - fmtBufferv(buffer, len + 1, fmt, args); + fmt_bufferv(buffer, len + 1, fmt, args); return (str_t) { .buf = buffer, .len = (usize)len }; } -str_t strFromWChar(arena_t *arena, const wchar_t *src) { - return strFromWCharLen(arena, src, 0); +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, + }; } -str_t strFromWCharLen(arena_t *arena, const wchar_t *src, usize srclen) { - if (!src) return STR_EMPTY; - if (!srclen) srclen = wcslen(src); - - str_t out = {0}; - -#if COLLA_WIN - int outlen = WideCharToMultiByte( - CP_UTF8, 0, - src, (int)srclen, - 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); - } - else { - err("couldn't translate wide string (%S) to utf8, %u", error); - } - - return STR_EMPTY; +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, + }; +} - out.buf = alloc(arena, char, outlen + 1); - WideCharToMultiByte( - CP_UTF8, 0, - src, (int)srclen, - out.buf, outlen, - NULL, NULL - ); +str_t str_from_str16(arena_t *arena, str16_t src) { + if (!src.buf) return STR_EMPTY; + if (!src.len) return STR_EMPTY; - out.len = outlen; - -#elif COLLA_LIN - fatal("strFromWChar not implemented yet!"); -#endif + str_t out = str_os_from_str16(arena, src); return out; } -bool strEquals(str_t a, str_t b) { - return strCompare(a, b) == 0; +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 } -int strCompare(str_t a, str_t b) { - return a.len == b.len ? - memcmp(a.buf, b.buf, a.len) : - (int)(a.len - b.len); +str16_t str16_from_str(arena_t *arena, str_t src) { + return strv_to_str16(arena, strv(src)); } -str_t strDup(arena_t *arena, str_t src) { - return strInitLen(arena, src.buf, src.len); +bool str_equals(str_t a, str_t b) { + return str_compare(a, b) == 0; } -bool strIsEmpty(str_t ctx) { - return ctx.len == 0 || ctx.buf == NULL; +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); } -void strReplace(str_t *ctx, char from, char to) { +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) { @@ -137,145 +145,68 @@ void strReplace(str_t *ctx, char from, char to) { } } -strview_t strSub(str_t ctx, usize from, usize to) { +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 }; } -void strLower(str_t *ctx) { - char *buf = ctx->buf; - for (usize i = 0; i < ctx->len; ++i) { - buf[i] = (buf[i] >= 'A' && buf[i] <= 'Z') ? - buf[i] += 'a' - 'A' : - buf[i]; - } -} - -void strUpper(str_t *ctx) { - char *buf = ctx->buf; - for (usize i = 0; i < ctx->len; ++i) { - buf[i] = (buf[i] >= 'a' && buf[i] <= 'z') ? - buf[i] -= 'a' - 'A' : - buf[i]; - } -} - -str_t strToLower(arena_t *arena, str_t ctx) { - strLower(&ctx); - return strDup(arena, ctx); -} - -str_t strToUpper(arena_t *arena, str_t ctx) { - strUpper(&ctx); - return strDup(arena, ctx); -} - - // == STRVIEW_T ==================================================== -strview_t strvInit(const char *cstr) { - return (strview_t){ - .buf = cstr, - .len = cstr ? strlen(cstr) : 0, - }; +strview_t strv_init(const char *cstr) { + return strv_init_len(cstr, cstr ? strlen(cstr) : 0); } -strview_t strvInitLen(const char *buf, usize size) { +strview_t strv_init_len(const char *buf, usize size) { return (strview_t){ .buf = buf, .len = size, }; } -strview_t strvInitStr(str_t str) { +strview_t strv_init_str(str_t str) { return (strview_t){ .buf = str.buf, .len = str.len }; } - -bool strvIsEmpty(strview_t ctx) { +bool strv_is_empty(strview_t ctx) { return ctx.len == 0 || !ctx.buf; } -bool strvEquals(strview_t a, strview_t b) { - return strvCompare(a, b) == 0; +bool strv_equals(strview_t a, strview_t b) { + return strv_compare(a, b) == 0; } -int strvCompare(strview_t a, strview_t b) { +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 strvFront(strview_t ctx) { +char strv_front(strview_t ctx) { return ctx.len > 0 ? ctx.buf[0] : '\0'; } -char strvBack(strview_t ctx) { +char strv_back(strview_t ctx) { return ctx.len > 0 ? ctx.buf[ctx.len - 1] : '\0'; } - -wchar_t *strvToWChar(arena_t *arena, strview_t ctx, usize *outlen) { - wchar_t *out = NULL; - int len = 0; - - if (strvIsEmpty(ctx)) { - goto error; - } - -#if COLLA_WIN - len = MultiByteToWideChar( - CP_UTF8, 0, - ctx.buf, (int)ctx.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", ctx); - } - else { - err("couldn't translate string (%v) to a wide string, %u", ctx, error); - } - - goto error; - } - - out = alloc(arena, wchar_t, len + 1); - - MultiByteToWideChar( - CP_UTF8, 0, - ctx.buf, (int)ctx.len, - out, len - ); - -#elif COLLA_LIN - fatal("strFromWChar not implemented yet!"); -#endif - -error: - if (outlen) { - *outlen = (usize)len; - } - return out; +str16_t strv_to_str16(arena_t *arena, strview_t src) { + return strv_os_to_str16(arena, src); } -TCHAR *strvToTChar(arena_t *arena, strview_t str) { +tstr_t strv_to_tstr(arena_t *arena, strview_t src) { #if UNICODE - return strvToWChar(arena, str, NULL); + return strv_to_str16(arena, src); #else - char *cstr = alloc(arena, char, str.len + 1); - memcpy(cstr, str.buf, str.len); - return cstr; + return str(arena, src); #endif } -strview_t strvRemovePrefix(strview_t ctx, usize n) { +strview_t strv_remove_prefix(strview_t ctx, usize n) { if (n > ctx.len) n = ctx.len; return (strview_t){ .buf = ctx.buf + n, @@ -283,7 +214,7 @@ strview_t strvRemovePrefix(strview_t ctx, usize n) { }; } -strview_t strvRemoveSuffix(strview_t ctx, usize n) { +strview_t strv_remove_suffix(strview_t ctx, usize n) { if (n > ctx.len) n = ctx.len; return (strview_t){ .buf = ctx.buf, @@ -291,11 +222,11 @@ strview_t strvRemoveSuffix(strview_t ctx, usize n) { }; } -strview_t strvTrim(strview_t ctx) { - return strvTrimLeft(strvTrimRight(ctx)); +strview_t strv_trim(strview_t ctx) { + return strv_trim_left(strv_trim_right(ctx)); } -strview_t strvTrimLeft(strview_t 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]; @@ -308,7 +239,7 @@ strview_t strvTrimLeft(strview_t ctx) { return out; } -strview_t strvTrimRight(strview_t ctx) { +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]; @@ -320,29 +251,30 @@ strview_t strvTrimRight(strview_t ctx) { return out; } -strview_t strvSub(strview_t ctx, usize from, usize to) { +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 strvStartsWith(strview_t ctx, char c) { +bool strv_starts_with(strview_t ctx, char c) { return ctx.len > 0 && ctx.buf[0] == c; } -bool strvStartsWithView(strview_t ctx, strview_t view) { +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 strvEndsWith(strview_t ctx, char c) { +bool strv_ends_with(strview_t ctx, char c) { return ctx.len > 0 && ctx.buf[ctx.len - 1] == c; } -bool strvEndsWithView(strview_t ctx, strview_t view) { +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 strvContains(strview_t ctx, char c) { +bool strv_contains(strview_t ctx, char c) { for(usize i = 0; i < ctx.len; ++i) { if(ctx.buf[i] == c) { return true; @@ -351,7 +283,7 @@ bool strvContains(strview_t ctx, char c) { return false; } -bool strvContainsView(strview_t ctx, strview_t view) { +bool strv_contains_view(strview_t ctx, strview_t view) { if (ctx.len < view.len) return false; usize end = ctx.len - view.len; for (usize i = 0; i < end; ++i) { @@ -362,7 +294,17 @@ bool strvContainsView(strview_t ctx, strview_t view) { return false; } -usize strvFind(strview_t ctx, char c, usize from) { +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; @@ -371,8 +313,7 @@ usize strvFind(strview_t ctx, char c, usize from) { return STR_NONE; } -usize strvFindView(strview_t ctx, strview_t view, usize from) { - if (ctx.len < view.len) return STR_NONE; +usize strv_find_view(strview_t ctx, strview_t view, usize from) { usize end = ctx.len - view.len; for (usize i = from; i < end; ++i) { if (memcmp(ctx.buf + i, view.buf, view.len) == 0) { @@ -382,7 +323,20 @@ usize strvFindView(strview_t ctx, strview_t view, usize from) { return STR_NONE; } -usize strvRFind(strview_t ctx, char c, usize from_right) { +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) { @@ -393,7 +347,8 @@ usize strvRFind(strview_t ctx, char c, usize from_right) { return STR_NONE; } -usize strvRFindView(strview_t ctx, strview_t view, usize from_right) { +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; @@ -405,4 +360,403 @@ usize strvRFindView(strview_t ctx, strview_t view, usize from_right) { return STR_NONE; } -#include "warnings/colla_warn_beg.h" \ No newline at end of file +// == 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'; +} + +// == 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'); + + str_t out = { + .buf = ctx->beg, + .len = ostr_tell(ctx) - 1, + }; + + 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 index db216ae..bd72244 100644 --- a/str.h +++ b/str.h @@ -1,36 +1,50 @@ -#pragma once +#ifndef COLLA_STR_H +#define COLLA_STR_H -#include // va_list #include // strlen -#include "collatypes.h" - -typedef struct arena_t arena_t; +#include "core.h" +#include "darr.h" #define STR_NONE SIZE_MAX -typedef struct { +typedef struct str_t str_t; +struct str_t { char *buf; usize len; -} str_t; +}; -typedef struct { +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; -} strview_t; +}; + +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 *: strInit, \ - char *: strInit, \ - const wchar_t *: strFromWChar, \ - wchar_t *: strFromWChar, \ - strview_t: strInitView \ +#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) strInitLen(arena, cstr, clen) +#define str__2(arena, cstr, clen) str_init_len(arena, cstr, clen) #define str__impl(_1, _2, n, ...) str__##n // either: @@ -40,80 +54,223 @@ typedef struct { #define STR_EMPTY (str_t){0} -str_t strInit(arena_t *arena, const char *buf); -str_t strInitLen(arena_t *arena, const char *buf, usize len); -str_t strInitView(arena_t *arena, strview_t view); -str_t strFmt(arena_t *arena, const char *fmt, ...); -str_t strFmtv(arena_t *arena, const char *fmt, va_list args); +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); -str_t strFromWChar(arena_t *arena, const wchar_t *src); -str_t strFromWCharLen(arena_t *arena, const wchar_t *src, usize srclen); +tstr_t tstr_init(TCHAR *str, usize optional_len); +str16_t str16_init(u16 *str, usize optional_len); -bool strEquals(str_t a, str_t b); -int strCompare(str_t a, str_t b); +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); -str_t strDup(arena_t *arena, str_t src); -bool strIsEmpty(str_t ctx); +bool str_equals(str_t a, str_t b); +int str_compare(str_t a, str_t b); -void strReplace(str_t *ctx, char from, char to); +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 strSub(str_t ctx, usize from, usize to); - -void strLower(str_t *ctx); -void strUpper(str_t *ctx); - -str_t strToLower(arena_t *arena, str_t ctx); -str_t strToUpper(arena_t *arena, str_t ctx); +strview_t str_sub(str_t ctx, usize from, usize to); // == STRVIEW_T ==================================================== -#define strv__1(x) \ - _Generic((x), \ - char *: strvInit, \ - const char *: strvInit, \ - str_t: strvInitStr \ - )(x) - -#define strv__2(cstr, clen) strvInitLen(cstr, clen) -#define strv__impl(_1, _2, n, ...) strv__##n - -#define strv(...) strv__impl(__VA_ARGS__, 2, 1, 0)(__VA_ARGS__) +// 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} -strview_t strvInit(const char *cstr); -strview_t strvInitLen(const char *buf, usize size); -strview_t strvInitStr(str_t str); +// needed for strv__init_literal _Generic implementation, it's never actually called +inline strview_t strv__ignore(str_t s, size_t l) { + COLLA_UNUSED(s); COLLA_UNUSED(l); + return STRV_EMPTY; +} -bool strvIsEmpty(strview_t ctx); -bool strvEquals(strview_t a, strview_t b); -int strvCompare(strview_t a, strview_t b); +#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) -char strvFront(strview_t ctx); -char strvBack(strview_t ctx); +#define strv__1(x) \ + _Generic((x), \ + char *: strv_init, \ + const char *: strv_init, \ + str_t: strv_init_str \ + )(x) -wchar_t *strvToWChar(arena_t *arena, strview_t ctx, usize *outlen); -TCHAR *strvToTChar(arena_t *arena, strview_t str); +#define strv__2(cstr, clen) strv_init_len(cstr, clen) -strview_t strvRemovePrefix(strview_t ctx, usize n); -strview_t strvRemoveSuffix(strview_t ctx, usize n); -strview_t strvTrim(strview_t ctx); -strview_t strvTrimLeft(strview_t ctx); -strview_t strvTrimRight(strview_t ctx); +#define strv__impl(_1, _2, n, ...) strv__##n -strview_t strvSub(strview_t ctx, usize from, usize to); +#define strv(...) strv__check(__VA_ARGS__) ? strv__init_literal(__VA_ARGS__) : strv__impl(__VA_ARGS__, 2, 1, 0)(__VA_ARGS__) -bool strvStartsWith(strview_t ctx, char c); -bool strvStartsWithView(strview_t ctx, strview_t view); +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 strvEndsWith(strview_t ctx, char c); -bool strvEndsWithView(strview_t ctx, strview_t view); +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); -bool strvContains(strview_t ctx, char c); -bool strvContainsView(strview_t ctx, strview_t view); +char strv_front(strview_t ctx); +char strv_back(strview_t ctx); -usize strvFind(strview_t ctx, char c, usize from); -usize strvFindView(strview_t ctx, strview_t view, usize from); +str16_t strv_to_str16(arena_t *arena, strview_t src); +tstr_t strv_to_tstr(arena_t *arena, strview_t src); -usize strvRFind(strview_t ctx, char c, usize from_right); -usize strvRFindView(strview_t ctx, strview_t view, usize from_right); +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); + +// == 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 \ No newline at end of file diff --git a/strstream.c b/strstream.c deleted file mode 100644 index 1e90bc8..0000000 --- a/strstream.c +++ /dev/null @@ -1,655 +0,0 @@ -#include "strstream.h" - -#include "warnings/colla_warn_beg.h" - -#include -#include -#include -#include -#include -#include -#include // HUGE_VALF - -#include "tracelog.h" -#include "arena.h" - -#if COLLA_WIN && COLLA_TCC -#define strtoull _strtoui64 -#define strtoll _strtoi64 -#define strtof strtod -#endif - -/* == INPUT STREAM ============================================ */ - -instream_t istrInit(const char *str) { - return istrInitLen(str, strlen(str)); -} - -instream_t istrInitLen(const char *str, usize len) { - instream_t res; - res.start = res.cur = str; - res.size = len; - return res; -} - -char istrGet(instream_t *ctx) { - return ctx && ctx->cur ? *ctx->cur++ : '\0'; -} - -void istrIgnore(instream_t *ctx, char delim) { - for (; !istrIsFinished(*ctx) && *ctx->cur != delim; ++ctx->cur) { - - } -} - -void istrIgnoreAndSkip(instream_t *ctx, char delim) { - istrIgnore(ctx, delim); - istrSkip(ctx, 1); -} - -char istrPeek(instream_t *ctx) { - return ctx && ctx->cur ? *ctx->cur : '\0'; -} - -char istrPeekNext(instream_t *ctx) { - if (!ctx || !ctx->cur) return '\0'; - usize offset = (ctx->cur - ctx->start) + 1; - return offset > ctx->size ? '\0' : *(ctx->cur + 1); -} - -char istrPrev(instream_t *ctx) { - if (!ctx || ctx->cur == ctx->start) return '\0'; - return *(ctx->cur - 1); -} - -char istrPrevPrev(instream_t *ctx) { - if (!ctx || (ctx->cur - 1) == ctx->start) return '\0'; - return *(ctx->cur - 2); -} - -void istrSkip(instream_t *ctx, usize n) { - if (!ctx || !ctx->cur) return; - usize remaining = ctx->size - (ctx->cur - ctx->start); - if(n > remaining) { - return; - } - ctx->cur += n; -} - -void istrSkipWhitespace(instream_t *ctx) { - if (!ctx || !ctx->cur) return; - while (*ctx->cur && isspace(*ctx->cur)) { - ++ctx->cur; - } -} - -void istrRead(instream_t *ctx, char *buf, usize len) { - if (!ctx || !ctx->cur) return; - usize remaining = ctx->size - (ctx->cur - ctx->start); - if(len > remaining) { - warn("istrRead: trying to read len %zu from remaining %zu", len, remaining); - return; - } - memcpy(buf, ctx->cur, len); - ctx->cur += len; -} - -usize istrReadMax(instream_t *ctx, char *buf, usize len) { - if (!ctx || !ctx->cur) return 0; - usize remaining = ctx->size - (ctx->cur - ctx->start); - len = remaining < len ? remaining : len; - memcpy(buf, ctx->cur, len); - ctx->cur += len; - return len; -} - -void istrRewind(instream_t *ctx) { - ctx->cur = ctx->start; -} - -void istrRewindN(instream_t *ctx, usize amount) { - if (!ctx || !ctx->cur) return; - usize remaining = ctx->size - (ctx->cur - ctx->start); - if (amount > remaining) amount = remaining; - ctx->cur -= amount; -} - -usize istrTell(instream_t ctx) { - return ctx.cur ? ctx.cur - ctx.start : 0; -} - -usize istrRemaining(instream_t ctx) { - return ctx.cur ? ctx.size - (ctx.cur - ctx.start) : 0; -} - -bool istrIsFinished(instream_t ctx) { - return ctx.cur ? (usize)(ctx.cur - ctx.start) >= ctx.size : true; -} - -bool istrGetBool(instream_t *ctx, bool *val) { - if (!ctx || !ctx->cur) return false; - usize remaining = ctx->size - (ctx->cur - ctx->start); - if(strncmp(ctx->cur, "true", remaining) == 0) { - *val = true; - return true; - } - if(strncmp(ctx->cur, "false", remaining) == 0) { - *val = false; - return true; - } - return false; -} - -bool istrGetU8(instream_t *ctx, uint8 *val) { - if (!ctx || !ctx->cur) return false; - char *end = NULL; - *val = (uint8) strtoul(ctx->cur, &end, 0); - - if(ctx->cur == end) { - warn("istrGetU8: no valid conversion could be performed"); - return false; - } - else if(*val == UINT8_MAX) { - warn("istrGetU8: value read is out of the range of representable values"); - return false; - } - - ctx->cur = end; - return true; -} - -bool istrGetU16(instream_t *ctx, uint16 *val) { - if (!ctx || !ctx->cur) return false; - char *end = NULL; - *val = (uint16) strtoul(ctx->cur, &end, 0); - - if(ctx->cur == end) { - warn("istrGetU16: no valid conversion could be performed"); - return false; - } - else if(*val == UINT16_MAX) { - warn("istrGetU16: value read is out of the range of representable values"); - return false; - } - - ctx->cur = end; - return true; -} - -bool istrGetU32(instream_t *ctx, uint32 *val) { - if (!ctx || !ctx->cur) return false; - char *end = NULL; - *val = (uint32) strtoul(ctx->cur, &end, 0); - - if(ctx->cur == end) { - warn("istrGetU32: no valid conversion could be performed"); - return false; - } - else if(*val == UINT32_MAX) { - warn("istrGetU32: value read is out of the range of representable values"); - return false; - } - - ctx->cur = end; - return true; -} - -bool istrGetU64(instream_t *ctx, uint64 *val) { - if (!ctx || !ctx->cur) return false; - char *end = NULL; - *val = strtoull(ctx->cur, &end, 0); - - if(ctx->cur == end) { - warn("istrGetU64: no valid conversion could be performed"); - return false; - } - else if(*val == ULLONG_MAX) { - warn("istrGetU64: value read is out of the range of representable values"); - return false; - } - - ctx->cur = end; - return true; -} - -bool istrGetI8(instream_t *ctx, int8 *val) { - if (!ctx || !ctx->cur) return false; - char *end = NULL; - *val = (int8) strtol(ctx->cur, &end, 0); - - if(ctx->cur == end) { - warn("istrGetI8: no valid conversion could be performed"); - return false; - } - else if(*val == INT8_MAX || *val == INT8_MIN) { - warn("istrGetI8: value read is out of the range of representable values"); - return false; - } - - ctx->cur = end; - return true; -} - -bool istrGetI16(instream_t *ctx, int16 *val) { - if (!ctx || !ctx->cur) return false; - char *end = NULL; - *val = (int16) strtol(ctx->cur, &end, 0); - - if(ctx->cur == end) { - warn("istrGetI16: no valid conversion could be performed"); - return false; - } - else if(*val == INT16_MAX || *val == INT16_MIN) { - warn("istrGetI16: value read is out of the range of representable values"); - return false; - } - - ctx->cur = end; - return true; -} - -bool istrGetI32(instream_t *ctx, int32 *val) { - if (!ctx || !ctx->cur) return false; - char *end = NULL; - *val = (int32) strtol(ctx->cur, &end, 0); - - if(ctx->cur == end) { - warn("istrGetI32: no valid conversion could be performed"); - return false; - } - else if(*val == INT32_MAX || *val == INT32_MIN) { - warn("istrGetI32: value read is out of the range of representable values"); - return false; - } - - ctx->cur = end; - return true; -} - -bool istrGetI64(instream_t *ctx, int64 *val) { - if (!ctx || !ctx->cur) return false; - char *end = NULL; - *val = strtoll(ctx->cur, &end, 0); - - if(ctx->cur == end) { - warn("istrGetI64: no valid conversion could be performed"); - return false; - } - else if(*val == INT64_MAX || *val == INT64_MIN) { - warn("istrGetI64: value read is out of the range of representable values"); - return false; - } - - ctx->cur = end; - return true; -} - -bool istrGetFloat(instream_t *ctx, float *val) { - if (!ctx || !ctx->cur) return false; - char *end = NULL; - *val = strtof(ctx->cur, &end); - - if(ctx->cur == end) { - warn("istrGetFloat: no valid conversion could be performed"); - return false; - } - else if(*val == HUGE_VALF || *val == -HUGE_VALF) { - warn("istrGetFloat: value read is out of the range of representable values"); - return false; - } - - ctx->cur = end; - return true; -} - -bool istrGetDouble(instream_t *ctx, double *val) { - if (!ctx || !ctx->cur) 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; -} - -str_t istrGetStr(arena_t *arena, instream_t *ctx, char delim) { - if (!ctx || !ctx->cur) return STR_EMPTY; - const char *from = ctx->cur; - istrIgnore(ctx, delim); - // if it didn't actually find it, it just reached the end of the string - if(*ctx->cur != delim) { - return STR_EMPTY; - } - usize len = ctx->cur - from; - str_t out = { - .buf = alloc(arena, char, len + 1), - .len = len - }; - memcpy(out.buf, from, len); - return out; -} - -usize istrGetBuf(instream_t *ctx, char *buf, usize buflen) { - if (!ctx || !ctx->cur) return 0; - usize remaining = ctx->size - (ctx->cur - ctx->start); - buflen -= 1; - buflen = remaining < buflen ? remaining : buflen; - memcpy(buf, ctx->cur, buflen); - buf[buflen] = '\0'; - ctx->cur += buflen; - return buflen; -} - -strview_t istrGetView(instream_t *ctx, char delim) { - if (!ctx || !ctx->cur) return STRV_EMPTY; - const char *from = ctx->cur; - istrIgnore(ctx, delim); - usize len = ctx->cur - from; - return strvInitLen(from, len); -} - -strview_t istrGetViewEither(instream_t *ctx, strview_t chars) { - if (!ctx || !ctx->cur) return STRV_EMPTY; - const char *from = ctx->cur; - for (; !istrIsFinished(*ctx) && !strvContains(chars, *ctx->cur); ++ctx->cur) { - - } - usize len = ctx->cur - from; - return strvInitLen(from, len); -} - -strview_t istrGetViewLen(instream_t *ctx, usize len) { - if (!ctx || !ctx->cur) return STRV_EMPTY; - const char *from = ctx->cur; - istrSkip(ctx, len); - usize buflen = ctx->cur - from; - return (strview_t){ from, buflen }; -} - -strview_t istrGetLine(instream_t *ctx) { - strview_t line = istrGetView(ctx, '\n'); - istrSkip(ctx, 1); - if (strvEndsWith(line, '\r')) { - line = strvRemoveSuffix(line, 1); - } - return line; -} - -/* == OUTPUT STREAM =========================================== */ - -static void ostr__remove_null(outstream_t *o) { - usize len = ostrTell(o); - if (len && o->beg[len - 1] == '\0') { - arenaPop(o->arena, 1); - } -} - -outstream_t ostrInit(arena_t *arena) { - return (outstream_t){ - .beg = (char *)(arena ? arena->current : NULL), - .arena = arena, - }; -} - -void ostrClear(outstream_t *ctx) { - arenaPop(ctx->arena, ostrTell(ctx)); -} - -usize ostrTell(outstream_t *ctx) { - return ctx->arena ? (char *)ctx->arena->current - ctx->beg : 0; -} - -char ostrBack(outstream_t *ctx) { - usize len = ostrTell(ctx); - return len ? ctx->beg[len - 1] : '\0'; -} - -str_t ostrAsStr(outstream_t *ctx) { - if (ostrTell(ctx) == 0 || ostrBack(ctx) != '\0') { - ostrPutc(ctx, '\0'); - } - - str_t out = { - .buf = ctx->beg, - .len = ostrTell(ctx) - 1 - }; - - ctx->beg = NULL; - ctx->arena = NULL; - return out; -} - -strview_t ostrAsView(outstream_t *ctx) { - bool is_null_terminated = ostrBack(ctx) == '\0'; - return (strview_t){ - .buf = ctx->beg, - .len = ostrTell(ctx) - is_null_terminated - }; -} - -void ostrPrintf(outstream_t *ctx, const char *fmt, ...) { - va_list args; - va_start(args, fmt); - ostrPrintfV(ctx, fmt, args); - va_end(args); -} - -void ostrPrintfV(outstream_t *ctx, const char *fmt, va_list args) { - if (!ctx->arena) return; - ostr__remove_null(ctx); - strFmtv(ctx->arena, fmt, args); - // remove null termination - arenaPop(ctx->arena, 1); -} - -void ostrPutc(outstream_t *ctx, char c) { - if (!ctx->arena) return; - ostr__remove_null(ctx); - char *newc = alloc(ctx->arena, char); - *newc = c; -} - -void ostrPuts(outstream_t *ctx, strview_t v) { - if (strvIsEmpty(v)) return; - ostr__remove_null(ctx); - str(ctx->arena, v); -} - -void ostrAppendBool(outstream_t *ctx, bool val) { - ostrPuts(ctx, val ? strv("true") : strv("false")); -} - -void ostrAppendUInt(outstream_t *ctx, uint64 val) { - ostrPrintf(ctx, "%I64u", val); -} - -void ostrAppendInt(outstream_t *ctx, int64 val) { - ostrPrintf(ctx, "%I64d", val); -} - -void ostrAppendNum(outstream_t *ctx, double val) { - ostrPrintf(ctx, "%g", val); -} - -/* == OUT BYTE STREAM ========================================= */ - -obytestream_t obstrInit(arena_t *exclusive_arena) { - return (obytestream_t){ - .beg = exclusive_arena ? exclusive_arena->current : NULL, - .arena = exclusive_arena, - }; -} - -void obstrClear(obytestream_t *ctx) { - if (ctx->arena) { - ctx->arena->current = ctx->beg; - } -} - -usize obstrTell(obytestream_t *ctx) { - return ctx->arena ? ctx->arena->current - ctx->beg : 0; -} - -buffer_t obstrAsBuf(obytestream_t *ctx) { - return (buffer_t){ .data = ctx->beg, .len = obstrTell(ctx) }; -} - -void obstrWrite(obytestream_t *ctx, const void *buf, usize buflen) { - uint8 *dst = alloc(ctx->arena, uint8, buflen, ALLOC_NOZERO); - memcpy(dst, buf, buflen); -} - -void obstrPuts(obytestream_t *ctx, strview_t str) { - obstrWrite(ctx, str.buf, str.len); -} - -void obstrAppendU8(obytestream_t *ctx, uint8 value) { - obstrWrite(ctx, &value, sizeof(value)); -} - -void obstrAppendU16(obytestream_t *ctx, uint16 value) { - obstrWrite(ctx, &value, sizeof(value)); -} - -void obstrAppendU32(obytestream_t *ctx, uint32 value) { - obstrWrite(ctx, &value, sizeof(value)); -} - -void obstrAppendU64(obytestream_t *ctx, uint64 value) { - obstrWrite(ctx, &value, sizeof(value)); -} - -void obstrAppendI8(obytestream_t *ctx, int8 value) { - obstrWrite(ctx, &value, sizeof(value)); -} - -void obstrAppendI16(obytestream_t *ctx, int16 value) { - obstrWrite(ctx, &value, sizeof(value)); -} - -void obstrAppendI32(obytestream_t *ctx, int32 value) { - obstrWrite(ctx, &value, sizeof(value)); -} - -void obstrAppendI64(obytestream_t *ctx, int64 value) { - obstrWrite(ctx, &value, sizeof(value)); -} - -/* == IN BYTE STREAM ========================================== */ - -ibytestream_t ibstrInit(const void *buf, usize len) { - return (ibytestream_t) { - .cur = buf, - .start = buf, - .size = len, - }; -} - -usize ibstrRemaining(ibytestream_t *ctx) { - return ctx->size - (ctx->cur - ctx->start); -} - -usize ibstrTell(ibytestream_t *ctx) { - return ctx->cur ? ctx->cur - ctx->start : 0; -} - -usize ibstrRead(ibytestream_t *ctx, void *buf, usize buflen) { - if (!ctx->cur) return 0; - usize remaining = ibstrRemaining(ctx); - if (buflen > remaining) buflen = remaining; - memcpy(buf, ctx->cur, buflen); - ctx->cur += buflen; - return buflen; -} - -uint8 ibstrGetU8(ibytestream_t *ctx) { - uint8 value = 0; - usize read = ibstrRead(ctx, &value, sizeof(value)); - return read == sizeof(value) ? value : 0; -} - -uint16 ibstrGetU16(ibytestream_t *ctx) { - uint16 value = 0; - usize read = ibstrRead(ctx, &value, sizeof(value)); - return read == sizeof(value) ? value : 0; -} - -uint32 ibstrGetU32(ibytestream_t *ctx) { - uint32 value = 0; - usize read = ibstrRead(ctx, &value, sizeof(value)); - return read == sizeof(value) ? value : 0; -} - -uint64 ibstrGetU64(ibytestream_t *ctx) { - uint64 value = 0; - usize read = ibstrRead(ctx, &value, sizeof(value)); - return read == sizeof(value) ? value : 0; -} - -int8 ibstrGetI8(ibytestream_t *ctx) { - int8 value = 0; - usize read = ibstrRead(ctx, &value, sizeof(value)); - return read == sizeof(value) ? value : 0; -} - -int16 ibstrGetI16(ibytestream_t *ctx) { - int16 value = 0; - usize read = ibstrRead(ctx, &value, sizeof(value)); - return read == sizeof(value) ? value : 0; -} - -int32 ibstrGetI32(ibytestream_t *ctx) { - int32 value = 0; - usize read = ibstrRead(ctx, &value, sizeof(value)); - return read == sizeof(value) ? value : 0; -} - -int64 ibstrGetI64(ibytestream_t *ctx) { - int64 value = 0; - usize read = ibstrRead(ctx, &value, sizeof(value)); - return read == sizeof(value) ? value : 0; -} - -float ibstrGetFloat(ibytestream_t *ctx) { - float value = 0; - usize read = ibstrRead(ctx, &value, sizeof(value)); - return read == sizeof(value) ? value : 0; -} - -double ibstrGetDouble(ibytestream_t *ctx) { - double value = 0; - usize read = ibstrRead(ctx, &value, sizeof(value)); - return read == sizeof(value) ? value : 0; -} - -strview_t ibstrGetView(ibytestream_t *ctx, usize lensize) { - uint64 len = 0; - usize read = ibstrRead(ctx, &len, lensize); - if (read != lensize) { - warn("couldn't read %zu bytes, instead read %zu for string", lensize, read); - return STRV_EMPTY; - } - usize remaining = ibstrRemaining(ctx); - if (len > remaining) { - warn("trying to read a string of length %zu, but only %zu bytes remaining", len, remaining); - len = remaining; - } - - const char *str = (const char *)ctx->cur; - ctx->cur += len; - - return (strview_t){ - .buf = str, - .len = len, - }; -} - -#include "warnings/colla_warn_end.h" \ No newline at end of file diff --git a/strstream.h b/strstream.h deleted file mode 100644 index e0d4e26..0000000 --- a/strstream.h +++ /dev/null @@ -1,160 +0,0 @@ -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -#include - -#include "collatypes.h" -#include "str.h" - -typedef struct arena_t arena_t; - -/* == INPUT STREAM ============================================ */ - -typedef struct instream_t { - const char *start; - const char *cur; - usize size; -} instream_t; - -// initialize with null-terminated string -instream_t istrInit(const char *str); -instream_t istrInitLen(const char *str, usize len); - -// get the current character and advance -char istrGet(instream_t *ctx); -// get the current character but don't advance -char istrPeek(instream_t *ctx); -// get the next character but don't advance -char istrPeekNext(instream_t *ctx); -// returns the previous character -char istrPrev(instream_t *ctx); -// returns the character before the previous -char istrPrevPrev(instream_t *ctx); -// ignore characters until the delimiter -void istrIgnore(instream_t *ctx, char delim); -// ignore characters until the delimiter and skip it -void istrIgnoreAndSkip(instream_t *ctx, char delim); -// skip n characters -void istrSkip(instream_t *ctx, usize n); -// skips whitespace (' ', '\\n', '\\t', '\\r') -void istrSkipWhitespace(instream_t *ctx); -// read len bytes into buffer, the buffer will not be null terminated -void istrRead(instream_t *ctx, char *buf, usize len); -// read a maximum of len bytes into buffer, the buffer will not be null terminated -// returns the number of bytes read -usize istrReadMax(instream_t *ctx, char *buf, usize len); -// returns to the beginning of the stream -void istrRewind(instream_t *ctx); -// returns back characters -void istrRewindN(instream_t *ctx, usize amount); -// returns the number of bytes read from beginning of stream -usize istrTell(instream_t ctx); -// returns the number of bytes left to read in the stream -usize istrRemaining(instream_t ctx); -// return true if the stream doesn't have any new bytes to read -bool istrIsFinished(instream_t ctx); - -bool istrGetBool(instream_t *ctx, bool *val); -bool istrGetU8(instream_t *ctx, uint8 *val); -bool istrGetU16(instream_t *ctx, uint16 *val); -bool istrGetU32(instream_t *ctx, uint32 *val); -bool istrGetU64(instream_t *ctx, uint64 *val); -bool istrGetI8(instream_t *ctx, int8 *val); -bool istrGetI16(instream_t *ctx, int16 *val); -bool istrGetI32(instream_t *ctx, int32 *val); -bool istrGetI64(instream_t *ctx, int64 *val); -bool istrGetFloat(instream_t *ctx, float *val); -bool istrGetDouble(instream_t *ctx, double *val); -str_t istrGetStr(arena_t *arena, instream_t *ctx, char delim); -// get a string of maximum size len, the string is not allocated by the function and will be null terminated -usize istrGetBuf(instream_t *ctx, char *buf, usize buflen); -strview_t istrGetView(instream_t *ctx, char delim); -strview_t istrGetViewEither(instream_t *ctx, strview_t chars); -strview_t istrGetViewLen(instream_t *ctx, usize len); -strview_t istrGetLine(instream_t *ctx); - -/* == OUTPUT STREAM =========================================== */ - -typedef struct outstream_t { - char *beg; - arena_t *arena; -} outstream_t; - -outstream_t ostrInit(arena_t *exclusive_arena); -void ostrClear(outstream_t *ctx); - -usize ostrTell(outstream_t *ctx); - -char ostrBack(outstream_t *ctx); -str_t ostrAsStr(outstream_t *ctx); -strview_t ostrAsView(outstream_t *ctx); - -void ostrPrintf(outstream_t *ctx, const char *fmt, ...); -void ostrPrintfV(outstream_t *ctx, const char *fmt, va_list args); -void ostrPutc(outstream_t *ctx, char c); -void ostrPuts(outstream_t *ctx, strview_t v); - -void ostrAppendBool(outstream_t *ctx, bool val); -void ostrAppendUInt(outstream_t *ctx, uint64 val); -void ostrAppendInt(outstream_t *ctx, int64 val); -void ostrAppendNum(outstream_t *ctx, double val); - -/* == OUT BYTE STREAM ========================================= */ - -typedef struct { - uint8 *beg; - arena_t *arena; -} obytestream_t; - -obytestream_t obstrInit(arena_t *exclusive_arena); -void obstrClear(obytestream_t *ctx); - -usize obstrTell(obytestream_t *ctx); -buffer_t obstrAsBuf(obytestream_t *ctx); - -void obstrWrite(obytestream_t *ctx, const void *buf, usize buflen); -void obstrPuts(obytestream_t *ctx, strview_t str); -void obstrAppendU8(obytestream_t *ctx, uint8 value); -void obstrAppendU16(obytestream_t *ctx, uint16 value); -void obstrAppendU32(obytestream_t *ctx, uint32 value); -void obstrAppendU64(obytestream_t *ctx, uint64 value); -void obstrAppendI8(obytestream_t *ctx, int8 value); -void obstrAppendI16(obytestream_t *ctx, int16 value); -void obstrAppendI32(obytestream_t *ctx, int32 value); -void obstrAppendI64(obytestream_t *ctx, int64 value); - -/* == IN BYTE STREAM ========================================== */ - -typedef struct { - const uint8 *start; - const uint8 *cur; - usize size; -} ibytestream_t; - -ibytestream_t ibstrInit(const void *buf, usize len); - -usize ibstrRemaining(ibytestream_t *ctx); -usize ibstrTell(ibytestream_t *ctx); -usize ibstrRead(ibytestream_t *ctx, void *buf, usize buflen); - -uint8 ibstrGetU8(ibytestream_t *ctx); -uint16 ibstrGetU16(ibytestream_t *ctx); -uint32 ibstrGetU32(ibytestream_t *ctx); -uint64 ibstrGetU64(ibytestream_t *ctx); -int8 ibstrGetI8(ibytestream_t *ctx); -int16 ibstrGetI16(ibytestream_t *ctx); -int32 ibstrGetI32(ibytestream_t *ctx); -int64 ibstrGetI64(ibytestream_t *ctx); -float ibstrGetFloat(ibytestream_t *ctx); -double ibstrGetDouble(ibytestream_t *ctx); - -// reads a string, before reads bytes for the length (e.g. sizeof(uint32)) -// then reads sizeof(char) * strlen -strview_t ibstrGetView(ibytestream_t *ctx, usize lensize); - -#ifdef __cplusplus -} // extern "C" -#endif diff --git a/tcc/colla_tcc.h b/tcc/colla_tcc.h index 2be284c..daea037 100644 --- a/tcc/colla_tcc.h +++ b/tcc/colla_tcc.h @@ -3,6 +3,7 @@ #if COLLA_TCC && COLLA_WIN #include +#include //// FILE.H //////////////////////////////////////////////////////////////////////////////////// @@ -20,6 +21,15 @@ static BOOL tcc_GetFileSizeEx(HANDLE hFile, PLARGE_INTEGER lpFileSize) { //// STR.H ///////////////////////////////////////////////////////////////////////////////////// #define CP_UTF8 65001 + +#define strtoull _strtoui64 +#define strtoll _strtoi64 +#define strtof strtod + +extern unsigned __int64 __stdcall _strtoui64(const char *strSource, char **endptr, int base); +extern __int64 __stdcall _strtoi64(const char *strSource, char **endptr, int base); +// extern double __cdecl strtod(const char *strSource, char **endptr); + extern int __stdcall WideCharToMultiByte(UINT CodePage, DWORD dwFlags, LPCWCH lpWideCharStr, int cchWideChar, LPSTR lpMultiByteStr, int cbMultiByte, LPCCH lpDefaultChar, LPBOOL lpUsedDefaultChar); extern int __stdcall MultiByteToWideChar(UINT CodePage, DWORD dwFlags, LPCCH lpMultiByteStr, int cbMultiByte, LPWSTR lpWideCharStr, int cchWideChar); @@ -264,6 +274,9 @@ extern BOOL __stdcall SleepConditionVariableCS(PCONDITION_VARIABLE ConditionVari #define INTERNET_DEFAULT_HTTPS_PORT 443 #define INTERNET_SERVICE_HTTP 3 #define INTERNET_FLAG_SECURE 0x00800000 +#define HTTP_QUERY_STATUS_CODE 19 +#define HTTP_QUERY_RAW_HEADERS_CRLF 22 +#define HTTP_QUERY_FLAG_NUMBER 0x20000000 typedef LPVOID HINTERNET; @@ -274,27 +287,35 @@ typedef WORD INTERNET_PORT; #define InternetConnect InternetConnectW #define HttpOpenRequest HttpOpenRequestW #define HttpSendRequest HttpSendRequestW + #define HttpAddRequestHeaders HttpAddRequestHeadersW + #define HttpQueryInfo HttpQueryInfoW #else #define InternetOpen InternetOpenA #define InternetConnect InternetConnectA #define HttpOpenRequest HttpOpenRequestA #define HttpSendRequest HttpSendRequestA + #define HttpAddRequestHeaders HttpAddRequestHeadersA + #define HttpQueryInfo HttpQueryInfoA #endif extern HINTERNET __stdcall InternetOpenW(LPCWSTR lpszAgent, DWORD dwAccessType, LPCWSTR lpszProxy, LPCWSTR lpszProxyBypass, DWORD dwFlags); extern HINTERNET __stdcall InternetConnectW(HINTERNET hInternet, LPCWSTR lpszServerName, INTERNET_PORT nServerPort, LPCWSTR lpszUserName, LPCWSTR lpszPassword, DWORD dwService, DWORD dwFlags, DWORD_PTR dwContext); extern HINTERNET __stdcall HttpOpenRequestW(HINTERNET hConnect, LPCWSTR lpszVerb, LPCWSTR lpszObjectName, LPCWSTR lpszVersion, LPCWSTR lpszReferrer, LPCWSTR *lplpszAcceptTypes, DWORD dwFlags, DWORD_PTR dwContext); extern BOOL __stdcall HttpSendRequestW(HINTERNET hRequest, LPCWSTR lpszHeaders, DWORD dwHeadersLength, LPVOID lpOptional, DWORD dwOptionalLength); +extern BOOL __stdcall HttpAddRequestHeadersW(HINTERNET hRequest, LPCWSTR lpszHeaders, DWORD dwHeadersLength, DWORD dwModifiers); +extern BOOL __stdcall HttpQueryInfoW(HINTERNET hRequest, DWORD dwInfoLevel, LPVOID lpBuffer, LPDWORD lpdwBufferLength, LPDWORD lpdwIndex); extern HINTERNET __stdcall InternetOpenA(LPCSTR lpszAgent, DWORD dwAccessType, LPCSTR lpszProxy, LPCSTR lpszProxyBypass, DWORD dwFlags); extern HINTERNET __stdcall InternetConnectA(HINTERNET hInternet, LPCSTR lpszServerName, INTERNET_PORT nServerPort, LPCSTR lpszUserName, LPCSTR lpszPassword, DWORD dwService, DWORD dwFlags, DWORD_PTR dwContext); extern HINTERNET __stdcall HttpOpenRequestA(HINTERNET hConnect, LPCSTR lpszVerb, LPCSTR lpszObjectName, LPCSTR lpszVersion, LPCSTR lpszReferrer, LPCSTR *lplpszAcceptTypes, DWORD dwFlags, DWORD_PTR dwContext); extern BOOL __stdcall HttpSendRequestA(HINTERNET hRequest, LPCSTR lpszHeaders, DWORD dwHeadersLength, LPVOID lpOptional, DWORD dwOptionalLength); - extern BOOL __stdcall HttpAddRequestHeadersA(HINTERNET hRequest, LPCSTR lpszHeaders, DWORD dwHeadersLength, DWORD dwModifiers); +extern BOOL __stdcall HttpQueryInfoA(HINTERNET hRequest, DWORD dwInfoLevel, LPVOID lpBuffer, LPDWORD lpdwBufferLength, LPDWORD lpdwIndex); + extern BOOL __stdcall InternetReadFile(HINTERNET hFile, LPVOID lpBuffer, DWORD dwNumberOfBytesToRead, LPDWORD lpdwNumberOfBytesRead); extern BOOL __stdcall InternetCloseHandle(HINTERNET hInternet); + #endif #endif \ No newline at end of file diff --git a/tools/build b/tools/build deleted file mode 100644 index bed761b..0000000 --- a/tools/build +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash - -# cosmocc -Os -mtiny -o docs.com docs.c - -# optimised version for x86/64 only, shaves off ~300KB -x86_64-unknown-cosmo-cc -Os -mtiny -o ../docs/docs.com docs.c - -rm ../docs/docs.com.dbg \ No newline at end of file diff --git a/tools/docs.c b/tools/docs.c deleted file mode 100644 index b20ef50..0000000 --- a/tools/docs.c +++ /dev/null @@ -1,465 +0,0 @@ -#include "../arena.c" -#include "../file.c" -#include "../format.c" -#include "../ini.c" -#include "../str.c" -#include "../strstream.c" -#include "../tracelog.c" -#include "../vmem.c" -#include "../markdown.c" -#include "../highlight.c" -#include "../dir.c" -#include "../socket.c" -#include "../http.c" -#include "../server.c" - -#include "../html.h" - -const char *raw_css; - -struct { - bool gen; - strview_t gendir; -} options = {0}; - -typedef struct page_t { - str_t title; - str_t url; - str_t data; - struct page_t *next; -} page_t; - -typedef struct { - uint8 arenabuf[KB(5)]; - arena_t arena; - hl_ctx_t *hl; - int line; -} cparser_ctx_t; - -void md_cparser_init(void *udata) { - cparser_ctx_t *cparser = udata; - cparser->line = 1; - if (cparser->hl) { - return; - } - cparser->arena = arenaMake(ARENA_STATIC, sizeof(cparser->arenabuf), cparser->arenabuf); - cparser->hl = hlInit(&cparser->arena, &(hl_config_t){ - .colors = { - [HL_COLOR_NORMAL] = strv("
"), - [HL_COLOR_PREPROC] = strv(""), - [HL_COLOR_TYPES] = strv(""), - [HL_COLOR_CUSTOM_TYPES] = strv(""), - [HL_COLOR_KEYWORDS] = strv(""), - [HL_COLOR_NUMBER] = strv(""), - [HL_COLOR_STRING] = strv(""), - [HL_COLOR_COMMENT] = strv(""), - [HL_COLOR_FUNC] = strv(""), - [HL_COLOR_SYMBOL] = strv(""), - [HL_COLOR_MACRO] = strv(""), - }, - .flags = HL_FLAG_HTML, - }); -} - -void md_cparser_callback(strview_t line, outstream_t *out, void *udata) { - cparser_ctx_t *cparser = udata; - - arena_t scratch = cparser->arena; - str_t highlighted = hlHighlight(&scratch, cparser->hl, line); - ostrPrintf(out, "
  • %v
  • ", highlighted); -} - -page_t *get_pages(arena_t *arena, strview_t path, strview_t default_page) { - arena_t scratch = arenaMake(ARENA_VIRTUAL, MB(1)); - - dir_t *dir = dirOpen(&scratch, path); - dir_entry_t *entry = NULL; - - page_t *first = NULL; - page_t *head = NULL; - page_t *tail = NULL; - - cparser_ctx_t cparser = {0}; - - while ((entry = dirNext(&scratch, dir))) { - if (entry->type != DIRTYPE_FILE) { - continue; - } - - strview_t name, ext; - fileSplitPath(strv(entry->name), NULL, &name, &ext); - - if (!strvEquals(ext, strv(".md"))) { - continue; - } - - str_t fullname = strFmt(&scratch, "%v/%v", path, entry->name); - str_t markdown_str = fileReadWholeStr(&scratch, strv(fullname)); - - str_t md = markdownStr(&scratch, strv(markdown_str), &(md_options_t){ - .parsers = (md_parser_t[]){ - { - .init = md_cparser_init, - .callback = md_cparser_callback, - .userdata = &cparser, - .lang = strv("c"), - }, - }, - .parsers_count = 1, - }); - - page_t *page = alloc(arena, page_t); - page->data = md; - page->url = str(arena, name); - - usize line_end = strvFind(strv(markdown_str), '\n', 0); - strview_t line = strvSub(strv(markdown_str), 0, line_end); - strview_t page_title = strvTrim(strvRemovePrefix(line, 1)); - page->title = strFmt(arena, "%v", page_title); - - if (!first && strvEquals(name, default_page)) { - first = page; - } - else { - if (!head) head = page; - if (tail) tail->next = page; - tail = page; - } - } - - if (first) { - first->next = head; - head = first; - } - - strview_t css = strv(raw_css); - - for_each(page, head) { - str_t html = STR_EMPTY; - - htmlBeg(arena, &html); - headBeg(); - title(page->title); - style(css); - headEnd(); - bodyBeg(); - divBeg(.id="main"); - divBeg(.class="content"); - divBeg(.class="pages-container"); - divBeg(.class="pages"); - for_each(item, head) { - str_t class = strFmt(&scratch, "page-item%s", item == page ? " page-current" : ""); - str_t href = STR_EMPTY; - if (options.gen) { - href = strFmt(&scratch, "%v.html", item->url); - } - else { - href = strFmt(&scratch, "%v", item->url); - } - - a( - item->title, - .href = href.buf, - .class = class.buf, - ); - } - divEnd(); - divEnd(); - - divBeg(.class="document-container"); - divBeg(.class="document"); - htmlPuts(page->data); - htmlRaw(
    ); - divEnd(); - divEnd(); - divEnd(); - divEnd(); - bodyEnd(); - htmlEnd(); - - page->data = html; - } - - arenaCleanup(&scratch); - - return head; -} - -str_t server_default(arena_t scratch, server_t *server, server_req_t *req, void *userdata) { - strview_t needle = strv(req->page); - if (strvFront(needle) == '/') { - needle = strvRemovePrefix(strv(req->page), 1); - } - - page_t *page = userdata; - while (page) { - if (strvEquals(strv(page->url), needle)) { - break; - } - page = page->next; - } - - // if the url is invalid, return the default page - if (!page) { - page = userdata; - } - - return serverMakeResponse(&scratch, 200, strv("text/html"), strv(page->data)); -} - -str_t server_quit(arena_t scratch, server_t *server, server_req_t *req, void *userdata) { - serverStop(server); - return STR_EMPTY; -} - -int main(int argc, char **argv) { - arena_t arena = arenaMake(ARENA_VIRTUAL, MB(1)); - - for (int i = 1; i < argc; ++i) { - strview_t arg = strv(argv[i]); - if (strvEquals(arg, strv("-h"))) { - info("usage: %s [-h, -gen ]", argv[0]); - return 0; - } - else if (strvEquals(arg, strv("-gen"))) { - options.gen = true; - if ((i + 1) < argc) { - options.gendir = strv(argv[++i]); - } - } - } - - page_t *pages = get_pages(&arena, strv("."), strv("readme")); - if (!pages) { - err("could not get pages"); - return 1; - } - - if (options.gen) { - if (strvBack(options.gendir) == '/' || strvBack(options.gendir) == '\\') { - options.gendir.len -= 1; - } - - for_each(page, pages) { - arena_t scratch = arena; - str_t fname = strFmt(&scratch, "%v/%v.html", options.gendir, page->url); - if (!fileWriteWhole(strv(fname), page->data.buf, page->data.len)) { - err("couldn't save page %v", fname); - } - else { - info("saved %v", fname); - } - } - - return 0; - } - - server_t *s = serverSetup(&arena, 8080, true); - serverRouteDefault(&arena, s, server_default, pages); - serverRoute(&arena, s, strv("/quit"), server_quit, NULL); - serverStart(arena, s); - - arenaCleanup(&arena); -} - -//// HTML GENERATION STUFF /////////////////////////////// - -const char *raw_css = "" -"html, body, #main {\n" -" margin: 0;\n" -" width: 100%;\n" -" height: 100%;\n" -" font-family: -apple-system,BlinkMacSystemFont,'Segoe UI','Noto Sans',Helvetica,Arial,sans-serif,'Apple Color Emoji','Segoe UI Emoji';\n" -" \n" -" --black: #121212;\n" -" --dark-gray: #212121;\n" -" --gray: #303030;\n" -" --light-gray: #424242;\n" -" --accent: #FFA500;\n" -" /* --accent: #F98334; */\n" -"\n" -" --blue: #45559E;\n" -" --orange: #F98334;\n" -" --yellow: #FABD2F;\n" -" --red: #FB4934;\n" -" --pink: #D3869B;\n" -" --green: #B8BB26;\n" -" --azure: #7FA375;\n" -" --white: #FBF1C7;\n" -" --light-blue: #83A598;\n" -"}\n" -"\n" -"hr {\n" -" color: white;\n" -" opacity: 0.5;\n" -"}\n" -"\n" -"a {\n" -" text-decoration: none;\n" -" font-weight: bold;\n" -" color: var(--red);\n" -"}\n" -"\n" -"a:hover {\n" -" text-decoration: underline;\n" -"}\n" -"\n" -".content {\n" -" width: 100%;\n" -" height: 100%;\n" -" background-color: var(--black);\n" -" display: flex;\n" -" gap: 20px;\n" -" align-items: stretch;\n" -" color: white;\n" -" overflow-x: scroll;\n" -"}\n" -"\n" -".pages-container {\n" -" position: sticky;\n" -" top: 0;\n" -" min-width: 200px;\n" -" background-color: var(--dark-gray);\n" -" border-right: 1px solid var(--light-gray);\n" -" box-shadow: 0 0 20px rgba(0, 0, 0, 1.0);\n" -" overflow-y: scroll;\n" -"}\n" -"\n" -".document-container {\n" -" display: flex;\n" -" justify-content: center;\n" -" width: 100%;\n" -"}\n" -"\n" -".document {\n" -" width: 100%;\n" -" max-width: 700px;\n" -" line-height: 1.5;\n" -"}\n" -"\n" -".document-padding {\n" -" height: 50px;\n" -"}\n" -"\n" -".pages {\n" -" position: relative;\n" -" background-color: var(--dark-gray);\n" -"}\n" -"\n" -".page-item:last-of-type {\n" -" border-bottom: 0;\n" -"}\n" -"\n" -".page-item {\n" -" text-decoration: none;\n" -" display: block;\n" -" color: rgba(255, 255, 255, 0.5);\n" -" padding: 0.2em 0 0.2em 1.5em;\n" -" border-bottom: 1px solid var(--light-gray);\n" -"}\n" -"\n" -".page-item:hover {\n" -" background-color: var(--light-gray);\n" -" cursor: pointer;\n" -" color: white;\n" -" opacity: 1;\n" -"}\n" -"\n" -".page-current {\n" -" color: var(--accent);\n" -" opacity: 1;\n" -"}\n" -"\n" -".page-current:hover {\n" -" color: var(--accent);\n" -"}\n" -"\n" -".page-spacing {\n" -" height: 25px; \n" -"}\n" -"\n" -"code {\n" -" color: var(--accent);\n" -" background-color: var(--dark-gray);\n" -" padding: 0.2em 0.5em 0.2em 0.5em;\n" -" border-radius: 0.5em;\n" -"}\n" -"\n" -"pre {\n" -" margin: 0;\n" -" margin-top: 2em;\n" -" background-color: var(--dark-gray);\n" -" padding: 16px;\n" -" border-radius: 0.3em;\n" -" overflow-x: scroll;\n" -"\n" -" border: 1px solid var(--light-gray);\n" -" box-shadow: 0 0 20px rgba(0, 0, 0, 1.0);\n" -"}\n" -"\n" -"pre > code {\n" -" color: #FBF1C7;\n" -" padding: 0;\n" -" background-color: transparent;\n" -"}\n" -"\n" -"pre ol {\n" -" counter-reset: item;\n" -" padding-left: 0;\n" -"}\n" -"\n" -"pre li {\n" -" display: block;\n" -" margin-left: 0em;\n" -"}\n" -"\n" -"pre li::before {\n" -" display: inline-block;\n" -" content: counter(item);\n" -" counter-increment: item;\n" -" width: 2em;\n" -" padding-right: 1.5em;\n" -" text-align: right;\n" -"}\n" -"\n" -"/* code block colors */\n" -".c-preproc {\n" -" color: #45559E;\n" -"}\n" -"\n" -".c-types {\n" -" color: #F98334;\n" -"}\n" -"\n" -".c-custom-types {\n" -" color: #FABD2F;\n" -"}\n" -"\n" -".c-kwrds {\n" -" color: #FB4934;\n" -"}\n" -"\n" -".c-num {\n" -" color: #D3869B;\n" -"}\n" -"\n" -".c-str {\n" -" color: #B8BB26;\n" -"}\n" -"\n" -".c-cmnt {\n" -" color: #928374;\n" -"}\n" -"\n" -".c-func {\n" -" color: #7FA375;\n" -"}\n" -"\n" -".c-sym {\n" -" color: #FBF1C7;\n" -"}\n" -"\n" -".c-macro {\n" -" color: #83A598;\n" -"}\n" -""; \ No newline at end of file diff --git a/tools/nob.c b/tools/nob.c new file mode 100644 index 0000000..090beb7 --- /dev/null +++ b/tools/nob.c @@ -0,0 +1,418 @@ +#define COLLA_NO_CONDITION_VARIABLE 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_file_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); + + if (!os_run_cmd(scratch, os_make_cmd(strv(vcvars_path), strv("&&"), strv("set"), strv(">"), strv("build\\cache.ini")), 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")); + } + + 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(); +} \ No newline at end of file diff --git a/tracelog.c b/tracelog.c deleted file mode 100644 index 9867cc7..0000000 --- a/tracelog.c +++ /dev/null @@ -1,212 +0,0 @@ -#include "tracelog.h" - -#include -#include -#include - -#include "format.h" - -#if COLLA_WIN - #if COLLA_MSVC - #pragma warning(disable:4996) // _CRT_SECURE_NO_WARNINGS. - #endif - - #include - -#if COLLA_CMT_LIB - #pragma comment(lib, "User32") -#endif - -#if COLLA_TCC - #include "tcc/colla_tcc.h" -#endif - - //#ifndef TLOG_NO_COLOURS - // #define TLOG_NO_COLOURS - //#endif -#endif - -#if COLLA_EMC - #define TLOG_NO_COLOURS -#endif - -#if COLLA_EMC - #define COLOUR_BLACK "" - #define COLOUR_RED "" - #define COLOUR_GREEN "" - #define COLOUR_YELLOW "" - #define COLOUR_BLUE "" - #define COLOUR_MAGENTA "" - #define COLOUR_CYAN "" - #define COLOUR_WHITE "" - #define COLOUR_RESET "" - #define COLOUR_BOLD "" -#elif defined(TLOG_NO_COLOURS) - #define COLOUR_BLACK "" - #define COLOUR_RED "" - #define COLOUR_GREEN "" - #define COLOUR_YELLOW "" - #define COLOUR_BLUE "" - #define COLOUR_MAGENTA "" - #define COLOUR_CYAN "" - #define COLOUR_WHITE "" - #define COLOUR_RESET "" - #define COLOUR_BOLD "" -#else - #define COLOUR_BLACK "\033[30m" - #define COLOUR_RED "\033[31m" - #define COLOUR_GREEN "\033[32m" - #define COLOUR_YELLOW "\033[33m" - #define COLOUR_BLUE "\033[22;34m" - #define COLOUR_MAGENTA "\033[35m" - #define COLOUR_CYAN "\033[36m" - #define COLOUR_WHITE "\033[37m" - #define COLOUR_RESET "\033[0m" - #define COLOUR_BOLD "\033[1m" -#endif - -static bool tl_use_newline = true; - -#if COLLA_WIN -static void setLevelColour(int level) { - WORD attribute = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; - switch (level) { - case LogDebug: attribute = FOREGROUND_BLUE; break; - case LogInfo: attribute = FOREGROUND_GREEN; break; - case LogWarning: attribute = FOREGROUND_RED | FOREGROUND_GREEN; break; - case LogError: attribute = FOREGROUND_RED; break; - case LogFatal: attribute = FOREGROUND_RED; break; - } - - HANDLE hc = GetStdHandle(STD_OUTPUT_HANDLE); - SetConsoleTextAttribute(hc, attribute); -} -#else -static void setLevelColour(int level) { - switch (level) { - case LogDebug: printf(COLOUR_BLUE); break; - case LogInfo: printf(COLOUR_GREEN); break; - case LogWarning: printf(COLOUR_YELLOW); break; - case LogError: printf(COLOUR_RED); break; - case LogFatal: printf(COLOUR_MAGENTA); break; - default: printf(COLOUR_RESET); break; - } -} -#endif - -void traceLog(int level, const char *fmt, ...) { - va_list args; - va_start(args, fmt); - traceLogVaList(level, fmt, args); - va_end(args); -} - -#ifdef TLOG_MUTEX -#include "cthreads.h" -static cmutex_t g_mtx = 0; -#endif - -void traceLogVaList(int level, const char *fmt, va_list args) { - #ifdef TLOG_MUTEX - if (!g_mtx) g_mtx = mtxInit(); - mtxLock(g_mtx); - #endif - - const char *beg = ""; - switch (level) { - case LogAll: beg = "[ALL" ; break; - case LogTrace: beg = "[TRACE" ; break; - case LogDebug: beg = "[DEBUG" ; break; - case LogInfo: beg = "[INFO" ; break; - case LogWarning: beg = "[WARNING" ; break; - case LogError: beg = "[ERROR" ; break; - case LogFatal: beg = "[FATAL" ; break; - default: break; - } - -#if COLLA_WIN - SetConsoleOutputCP(CP_UTF8); -#endif - setLevelColour(level); - printf("%s", beg); - -#if GAME_CLIENT - putchar(':'); - traceSetColour(COL_CYAN); - printf("CLIENT"); -#elif GAME_HOST - putchar(':'); - traceSetColour(COL_MAGENTA); - printf("HOST"); -#endif - - setLevelColour(level); - printf("]: "); - - // set back to white - setLevelColour(LogTrace); - fmtPrintv(fmt, args); - - if(tl_use_newline) { -#if COLLA_EMC - puts("
    "); -#else - puts(""); -#endif - } - -#ifndef TLOG_DONT_EXIT_ON_FATAL - if (level == LogFatal) { - -#ifndef TLOG_NO_MSGBOX - -#if COLLA_WIN - char errbuff[1024]; - fmtBufferv(errbuff, sizeof(errbuff), fmt, args); - - char captionbuf[] = -#if GAME_HOST - "Fatal Host Error"; -#elif GAME_CLIENT - "Fatal Client Error"; -#else - "Fatal Error"; -#endif - MessageBoxA( - NULL, - errbuff, captionbuf, - MB_ICONERROR | MB_OK - ); -#endif - -#endif - fflush(stdout); - abort(); - } -#endif - -#ifdef TLOG_MUTEX - mtxUnlock(g_mtx); -#endif -} - -void traceUseNewline(bool newline) { - tl_use_newline = newline; -} - -void traceSetColour(colour_e colour) { -#if COLLA_WIN - SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), (WORD)colour); -#else - switch (colour) { - case COL_RESET: printf(COLOUR_RESET); break; - case COL_BLACK: printf(COLOUR_BLACK); break; - case COL_BLUE: printf(COLOUR_BLUE); break; - case COL_GREEN: printf(COLOUR_GREEN); break; - case COL_CYAN: printf(COLOUR_CYAN); break; - case COL_RED: printf(COLOUR_RED); break; - case COL_MAGENTA: printf(COLOUR_MAGENTA); break; - case COL_YELLOW: printf(COLOUR_YELLOW); break; - } -#endif -} \ No newline at end of file diff --git a/tracelog.h b/tracelog.h deleted file mode 100644 index 800fe49..0000000 --- a/tracelog.h +++ /dev/null @@ -1,59 +0,0 @@ -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -/* Define any of this to turn on the option - * -> TLOG_NO_COLOURS: print without using colours - * -> TLOG_VS: print to visual studio console, also turns on TLOG_NO_COLOURS - * -> TLOG_DONT_EXIT_ON_FATAL: don't call 'exit(1)' when using LogFatal - * -> TLOG_DISABLE: turn off all logging, useful for release builds - * -> TLOG_MUTEX: use a mutex on every traceLog call -*/ - -#include "collatypes.h" -#include - -enum { - LogAll, LogTrace, LogDebug, LogInfo, LogWarning, LogError, LogFatal -}; - -typedef enum { - COL_RESET = 15, - COL_BLACK = 8, - COL_BLUE = 9, - COL_GREEN = 10, - COL_CYAN = 11, - COL_RED = 12, - COL_MAGENTA = 13, - COL_YELLOW = 14, - COL_WHITE = 15 -} colour_e; - -void traceLog(int level, const char *fmt, ...); -void traceLogVaList(int level, const char *fmt, va_list args); -void traceUseNewline(bool use_newline); -void traceSetColour(colour_e colour); - -#ifdef TLOG_DISABLE -#define tall(...) -#define trace(...) -#define debug(...) -#define info(...) -#define warn(...) -#define err(...) -#define fatal(...) -#else -#define tall(...) traceLog(LogAll, __VA_ARGS__) -#define trace(...) traceLog(LogTrace, __VA_ARGS__) -#define debug(...) traceLog(LogDebug, __VA_ARGS__) -#define info(...) traceLog(LogInfo, __VA_ARGS__) -#define warn(...) traceLog(LogWarning, __VA_ARGS__) -#define err(...) traceLog(LogError, __VA_ARGS__) -#define fatal(...) traceLog(LogFatal, __VA_ARGS__) -#endif - -#ifdef __cplusplus -} // extern "C" -#endif diff --git a/utf8.c b/utf8.c deleted file mode 100644 index 5d47c0a..0000000 --- a/utf8.c +++ /dev/null @@ -1,172 +0,0 @@ -#include "utf8.h" - -static const uint8 masks[] = { - 0x7f, // 0111-1111 - 0x1f, // 0001-1111 - 0x0f, // 0000-1111 - 0x07, // 0000-0111 - 0x03, // 0000-0011 - 0x01 // 0000-0001 -}; - -static struct { - uint8 mask; - uint8 result; - int octets; -} sizes[] = { - { 0x80, 0x00, 1 }, // 1000-0000, 0000-0000 - { 0xE0, 0xC0, 2 }, // 1110-0000, 1100-0000 - { 0xF0, 0xE0, 3 }, // 1111-0000, 1110-0000 - { 0xF8, 0xF0, 4 }, // 1111-1000, 1111-0000 - { 0xFC, 0xF8, 5 }, // 1111-1100, 1111-1000 - { 0xFE, 0xF8, 6 }, // 1111-1110, 1111-1000 - { 0x80, 0x80, -1 }, // 1000-0000, 1000-0000 -}; - - -/* -UTF-8 codepoints are encoded using the first bits of the first character - -byte 1 | byte 2 | byte 3 | byte 4 -0xxx xxxx | | | -110x xxxx | 10xx xxxx | | -1110 xxxx | 10xx xxxx | 10xx xxxx | -1111 0xxx | 10xx xxxx | 10xx xxxx | 10xx xxxx - -so when we decode it we first find the size of the codepoint (from 1 to 4) -then we apply the mask to the first byte to get the first character -then we keep shifting the rune left 6 and applying the next byte to the mask -until the codepoint is finished (size is 0) - -## EXAMPLE - -utf8 string (€) = 1110-0010 1000-0010 1010-1100 - -cp = 0000-0000 0000-0000 0000-0000 0000-0000 -size = 3 -mask = 0x0f -> 0000-1111 -cp = *s & mask = 1110-0010 & 0000-1111 = 0000-0000 0000-0000 0000-0000 0000-0010 -++s = 1000-0010 - ---size = 2 -cp <<= 6 = 0000-0000 0000-0000 0000-0000 1000-0000 -cp |= *s & 0x3f = 1000-0010 & 0011-1111 = 0000-0000 0000-0000 0000-0000 1000-0010 -++s = 1010-1100 - ---size = 1 -cp <<= 6 = 0000-0000 0000-0000 0010-0000 1000-0000 -cp |= *s & 0x3f = 1010-1100 & 0011-1111 = 0000-0000 0000-0000 0010-0000 1010-1100 -++s = ---------- - -final codepoint = 0010-0000 1010-1100 -€ codepoint = 0010-0000 1010-1100 -*/ - -rune utf8Decode(const char **char_str) { - const uint8 **s = (const uint8 **)char_str; - - rune ch = 0; - // if is ascii - if (**s < 128) { - ch = **s; - ++*s; - return ch; - } - int size = utf8Size((const char *)*s); - if (size == -1) { - ++*s; - return UTF8_INVALID; - } - uint8 mask = masks[size - 1]; - ch = **s & mask; - ++*s; - while(--size) { - ch <<= 6; - ch |= **s & 0x3f; // 0011-1111 - ++*s; - } - return ch; -} - - -/* -to encode a codepoint in a utf8 string we first need to find -the length of the codepoint -then we start from the rightmost byte and loop for each byte of the codepoint -using the length we got before until the first byte (which we skip) -> and (&) with 0x3f so we ignore the first to bits of the codepoint -> or (|) with 0x80 so we make sure that the first two bits are 10 -> bitshift the codepoint right 6 - -finally, we apply the correct length-mask to the first byte - -## EXAMPLE - -ch € = 0010-0000 1010-1100 -ch < 0x10000 - first = 0xe0 = 1110-0000 - len = 3 - -str[2] = (ch & 0x3f) | 0x80 = 1010-1100 & 0011-1111 | 1000-0000 - = 1010-1100 -ch >>= 6 = 0010-0000 1010-1100 >> 6 = 1000-0010 - -str[1] = (ch & 0x3f) | 0x80 = 1000-0010 & 0011-1111 | 1000-000 - = 1000-0010 -ch >>= 6 = 1000-0010 >> 6 = 0000-0010 - -str[0] = ch | first_mask = 0000-0010 | 1111-0000 - = 1111-0010 - -str = 1111-0010 1000-0010 1010-1100 -utf8 € = 1110-0010 1000-0010 1010-1100 -*/ - -usize utf8Encode(char *str, rune codepoint) { - usize len = 0; - uint8 first; - - if (codepoint < 0x80) { // 0000-0000 0000-0000 0000-0000 1000-0000 - first = 0; - len = 1; - } - else if (codepoint < 0x800) { // 0000-0000 0000-0000 0000-1000 0000-0000 - first = 0xc0; // 1100-0000 - len = 2; - } - else if (codepoint < 0x10000) { // 0000-0000 0000-0001 0000-0000 0000-0000 - first = 0xe0; // 1110-0000 - len = 3; - } - else { - first = 0xf0; // 1111-0000 - len = 4; - } - - for (usize i = len - 1; i > 0; --i) { - // 0x3f -> 0011-1111 - // 0x80 -> 1000-0000 - str[i] = (codepoint & 0x3f) | 0x80; - codepoint >>= 6; - } - - str[0] = (char)(codepoint | first); - return len; -} - -int utf8Size(const char *str) { - uint8 c = (uint8)*str; - for(usize i = 0; i < (sizeof(sizes) / sizeof(*sizes)); ++i) { - if ((c & sizes[i].mask) == sizes[i].result) { - return sizes[i].octets; - } - } - return -1; -} - -usize utf8CpSize(rune ch) { - if (ch < 0x80) return 1; - else if (ch < 0x800) return 2; - else if (ch < 0x10000) return 3; - return 4; -} diff --git a/utf8.h b/utf8.h deleted file mode 100644 index 907e1b4..0000000 --- a/utf8.h +++ /dev/null @@ -1,19 +0,0 @@ -#pragma once - -#include "collatypes.h" - -typedef uint32 rune; - -enum { - UTF8_MAX_SIZE = 4, - UTF8_INVALID = 0x80 -}; - -// grabs the next UTF-8 codepoint and advances string ptr -rune utf8Decode(const char **str); -// encodes a codepoint as UTF-8 and returns the length -usize utf8Encode(char *str, rune ch); -// returns the size of the next UTF-8 codepoint -int utf8Size(const char *str); -// returns the size of a UTF-8 codepoint -usize utf8CpSize(rune ch); diff --git a/vec.h b/vec.h deleted file mode 100644 index 8982c9b..0000000 --- a/vec.h +++ /dev/null @@ -1,71 +0,0 @@ -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -#define vec(T) T * - -#define vecFree(vec) ((vec) ? free(_vecheader(vec)), NULL : NULL) -#define vecCopy(src, dest) (vecFree(dest), vecAdd(dest, vecCount(src)), memcpy(dest, src, vecCount(src))) - -#define vecAppend(vec, ...) (_vecmaygrow(vec, 1), (vec)[_veclen(vec)] = (__VA_ARGS__), _veclen(vec)++) -#define vecRemove(vec, ind) ((vec) ? (vec)[(ind)] = (vec)[--_veclen(vec)], NULL : 0) -#define vecRemoveIt(vec, it) (vecRemove((vec), (it) - (vec))) -#define vecLen(vec) ((vec) ? _veclen(vec) : 0) -#define vecCap(vec) ((vec) ? _veccap(vec) : 0) - -#define vecBeg(vec) (vec) -#define vecEnd(vec) ((vec) ? (vec) + _veclen(vec) : NULL) -#define vecBack(vec) ((vec)[_veclen(vec) - 1]) - -#define vecAdd(vec, n) (_vecmaygrow(vec, (n)), _veclen(vec) += (size_type)(n), &(vec)[_veclen(vec)-(n)]) -#define vecReserve(vec, n) (_vecmaygrow(vec, (n))) -#define vecShrink(vec) (_vecshrink((void **)&(vec), _veclen(vec), sizeof(*(vec)))) - -#define vecClear(vec) ((vec) ? _veclen(vec) = 0 : 0) -#define vecPop(vec) ((vec)[--_veclen(vec)]) - -// == IMPLEMENTATION ========================================================================================== - -#include -#include -#include - -#ifndef size_type - #define size_type uint32_t -#endif - -#define _vecheader(vec) ((size_type *)(vec) - 2) -#define _veccap(vec) _vecheader(vec)[0] -#define _veclen(vec) _vecheader(vec)[1] - -#define _vecneedgrow(vec, n) ((vec) == NULL || _veclen(vec) + n >= _veccap(vec)) -#define _vecmaygrow(vec, n) (_vecneedgrow(vec, (n)) ? _vecgrow(vec, (size_type)(n)) : (void)0) -#define _vecgrow(vec, n) _vecgrowimpl((void **)&(vec), (n), sizeof(*(vec))) - -inline static void _vecgrowimpl(void **arr, size_type increment, size_type itemsize) { - int newcap = *arr ? 2 * _veccap(*arr) + increment : increment + 1; - void *ptr = realloc(*arr ? _vecheader(*arr) : 0, itemsize * newcap + sizeof(size_type) * 2); - assert(ptr); - if (ptr) { - if (!*arr) ((size_type *)ptr)[1] = 0; - *arr = (void *) ((size_type *)ptr + 2); - _veccap(*arr) = newcap; - } -} - -inline static void _vecshrink(void **arr, size_type newcap, size_t itemsize) { - if (newcap == _veccap(*arr) || !*arr) return; - void *ptr = realloc(_vecheader(*arr), itemsize * newcap + sizeof(size_type) * 2); - assert(ptr); - if (ptr) { - *arr = (void *) ((size_type *)ptr + 2); - if (_veclen(*arr) < newcap) _veclen(*arr) = newcap; - _veccap(*arr) = newcap; - } -} - -#ifdef __cplusplus -} // extern "C" -#endif diff --git a/vfs.c b/vfs.c deleted file mode 100644 index 473564d..0000000 --- a/vfs.c +++ /dev/null @@ -1,635 +0,0 @@ -#include "vfs.h" - -#include "lz4/lz4.c" - -#include "file.h" -#include "strstream.h" -#include "arena.h" -#include "dir.h" -#include "bits.h" - -/* -vfs format: -===================== -header: ------------ -magic: VFS -u32 header_size -u32 file_count -u8 flags -for each file: - u64 hash // todo remove - u8 namelen - char *name - u64 offset - u64 size - u64 compressed_size ------------ -* binary data * -*/ - -typedef struct vfsfile_t vfsfile_t; - -struct vfsfile_t { - vfsfile_t *next; - str_t path; - uint64 size; - uint64 comp_size; -}; - -typedef struct vfshmap_t vfshmap_t; -typedef struct vfshnode_t vfshnode_t; - -typedef struct { - uint64 offset; - uint64 size; - uint64 compsize; -} vfsdata_t; - -struct vfshnode_t { - vfshnode_t *next; - uint64 hash; - str_t key; - uint32 index; -}; - -struct vfshmap_t { - vfshnode_t **buckets; - vfsdata_t *values; - - uint32 size; - uint32 count; - uint32 collisions; - uint32 max_values; -}; - -struct virtualfs_t { - file_t fp; - buffer_t buffer; - vfshmap_t hmap; - uint64 base_offset; - vfsflags_e flags; -}; - -vfsflags_e vfs_validate_flags(vfsflags_e flags); -bool vfs_read(arena_t *arena, virtualfs_t *vfs, vfsdata_t *data, buffer_t *out, bool null_terminate); -vfsfile_t *vfs_add_dir(arena_t *arena, strview_t path, vfsfile_t *tail, uint32 *count, uint64 *bytesize); -vfshmap_t vfs_hmap_init(arena_t *arena, int pow2, uint32 max_values); -void vfs_hmap_add(arena_t *arena, vfshmap_t *hmap, strview_t key, vfsdata_t value); -vfsdata_t *vfs_hmap_get(vfshmap_t *hmap, strview_t key); -uint64 sdbm_hash(const void *bytes, usize count); -uint64 djb2_hash(const void *bytes, usize count); - -bool vfsVirtualiseDir(arena_t scratch, strview_t dirpath, strview_t outfile, vfsflags_e flags) { - bool success = false; - - flags = vfs_validate_flags(flags); - - if (strvBack(dirpath) != '/') { - str_t newpath = strFmt(&scratch, "%v/", dirpath); - dirpath = strv(newpath); - } - - uint32 count = 0; - uint64 bytesize = 0; - vfsfile_t file_head = {0}; - vfs_add_dir(&scratch, dirpath, &file_head, &count, &bytesize); - vfsfile_t *files = file_head.next; - - arena_t comp_arena = {0}; - - if (flags & VFS_FLAGS_COMPRESSED) { - arena_t comp_arena = arenaMake(ARENA_VIRTUAL, GB(1)); - - for_each (file, files) { - arena_t tmp = scratch; - buffer_t buf = fileReadWhole(&tmp, strv(file->path)); - usize maxlen = LZ4_compressBound(buf.len); - - uint8 *compressed = alloc(&comp_arena, uint8, maxlen); - - int actual_len = LZ4_compress_default(buf.data, compressed, buf.len, maxlen); - assert(actual_len > 0 && actual_len <= maxlen); - usize pop = maxlen - (usize)actual_len; - - // pop extra bytes that were allocated but not used - arenaPop(&comp_arena, pop); - - file->comp_size = actual_len; - } - } - - obytestream_t header = obstrInit(&scratch); - - obstrAppendU32(&header, count); - obstrAppendU8(&header, flags); - - uint64 offset = 0; - for_each (file, files) { - assert(file->path.len < 256); - uint64 hash = djb2_hash(file->path.buf, file->path.len); - - obstrAppendU64(&header, hash); - obstrAppendU8(&header, file->path.len); - obstrPuts(&header, strv(file->path)); - obstrAppendU64(&header, offset); - obstrAppendU64(&header, file->size); - obstrAppendU64(&header, file->comp_size); - - offset += file->comp_size; - } - - buffer_t headerbuf = obstrAsBuf(&header); - - buffer_t binbuf = {0}; - - file_t fp = fileOpen(outfile, FILE_WRITE); - - if (!fileIsValid(fp)) { - err("could not open file %v", outfile); - goto failed; - } - - uint32 header_size = headerbuf.len + 3 + sizeof(uint32); // + strlen("VFS") + sizeof(header_size) - - filePuts(fp, strv("VFS")); - fileWrite(fp, &header_size, sizeof(header_size)); - fileWrite(fp, headerbuf.data, headerbuf.len); - - if (flags & VFS_FLAGS_COMPRESSED) { - buffer_t compressed = { - .data = comp_arena.start, - .len = arenaTell(&comp_arena) - }; - fileWrite(fp, compressed.data, compressed.len); - } - else { - for_each (file, files) { - arena_t tmp = scratch; - buffer_t bin = fileReadWhole(&tmp, strv(file->path)); - if (flags & VFS_FLAGS_NULL_TERMINATE_FILES) { - alloc(&tmp, uint8); - bin.len += 1; - } - fileWrite(fp, bin.data, bin.len); - } - } - - fileClose(fp); - - success = true; -failed: - arenaCleanup(&comp_arena); - arenaCleanup(&scratch); - return success; -} - -virtualfs_t *vfsReadFromFile(arena_t *arena, strview_t vfs_file, vfsflags_e flags) { - usize pos_before = arenaTell(arena); - - virtualfs_t *vfs = alloc(arena, virtualfs_t); - - file_t fp = fileOpen(vfs_file, FILE_READ); - if (!fileIsValid(fp)) { - goto failed; - } - - // read header - struct { - char magic[3]; - uint32 size; - uint32 file_count; - uint8 flags; - } header = {0}; - - fileRead(fp, &header.magic, sizeof(header.magic)); - fileRead(fp, &header.size, sizeof(header.size)); - fileRead(fp, &header.file_count, sizeof(header.file_count)); - fileRead(fp, &header.flags, sizeof(header.flags)); - - if (memcmp(header.magic, "VFS", 3) != 0) { - err("VirtualFS: magic characters are wrong: %.3s (0x%x%x%x)", header.magic, header.magic[0], header.magic[1], header.magic[2]); - goto failed; - } - - uint32 default_pow2 = 1 << 10; - uint32 pow2 = bitsNextPow2(header.file_count); - pow2 = bitsCtz(max(pow2, default_pow2)); - - vfs->hmap = vfs_hmap_init(arena, pow2, header.file_count); - - for (uint32 i = 0; i < header.file_count; ++i) { - struct { - uint64 hash; - char name[256]; - uint64 offset; - uint64 size; - uint64 comp; - } file = {0}; - - uint8 namelen = 0; - - fileRead(fp, &file.hash, sizeof(file.hash)); - fileRead(fp, &namelen, sizeof(namelen)); - fileRead(fp, &file.name, namelen); - fileRead(fp, &file.offset, sizeof(file.offset)); - fileRead(fp, &file.size, sizeof(file.size)); - fileRead(fp, &file.comp, sizeof(file.comp)); - - vfsdata_t data = { - .offset = file.offset, - .size = file.size, - .compsize = file.comp, - }; - - strview_t path = strvInitLen(file.name, namelen); - - vfs_hmap_add(arena, &vfs->hmap, path, data); - } - - vfs->flags = vfs_validate_flags(header.flags | flags); - vfs->base_offset = header.size; - - if (vfs->flags & VFS_FLAGS_DONT_STREAM) { - // get remaining size of the file - usize pos = fileTell(fp); - fileSeekEnd(fp); - usize endpos = fileTell(fp); - fileSeek(fp, pos); - usize binsize = endpos - pos; - // read binary data and save it to buffer for later - buffer_t buf = { - .data = alloc(arena, uint8, binsize), - .len = binsize, - }; - usize read = fileRead(fp, buf.data, buf.len); - if (read != buf.len) { - err("couldn't read all of the binary data, expected %zu bytes but got %zu", buf.len, read); - goto failed; - } - fileClose(fp); - - vfs->buffer = buf; - } - else { - vfs->fp = fp; - } - - return vfs; -failed: - fileClose(fp); - arenaRewind(arena, pos_before); - return NULL; -} - -buffer_t vfsRead(arena_t *arena, virtualfs_t *vfs, strview_t path) { - buffer_t out = {0}; - usize pos_before = arenaTell(arena); - - if (!vfs) { - goto failed; - } - - vfsdata_t *data = vfs_hmap_get(&vfs->hmap, path); - if (!data) { - goto failed; - } - - if (!vfs_read(arena, vfs, data, &out, false)) { - goto failed; - } - - return out; -failed: - arenaRewind(arena, pos_before); - return (buffer_t){0}; -} - -str_t vfsReadStr(arena_t *arena, virtualfs_t *vfs, strview_t path) { - buffer_t buf = {0}; - usize pos_before = arenaTell(arena); - - if (!vfs) { - goto failed; - } - - vfsdata_t *data = vfs_hmap_get(&vfs->hmap, path); - if (!data) { - goto failed; - } - - if (!vfs_read(arena, vfs, data, &buf, true)) { - goto failed; - } - - return (str_t){ - .buf = buf.data, - .len = buf.len, - }; -failed: - arenaRewind(arena, pos_before); - return STR_EMPTY; -} - -// == VFS FILE API =================================== - -virtualfs_t *g_vfs = NULL; - -void vfsSetGlobalVirtualFS(virtualfs_t *vfs) { - g_vfs = vfs; -} - -bool vfsFileExists(strview_t path) { - if (!g_vfs) return false; - return vfs_hmap_get(&g_vfs->hmap, path) != NULL; -} - -vfs_file_t vfsFileOpen(strview_t name, int mode) { - if (!g_vfs) goto failed; - if (mode != FILE_READ) { - err("VirtualFS: trying to open file (%v) for write, VirtualFS is read only!", name); - goto failed; - } - - vfsdata_t *data = vfs_hmap_get(&g_vfs->hmap, name); - - return (vfs_file_t){ - .handle = (uintptr_t)data, - }; - -failed: - return (vfs_file_t){0}; -} - -void vfsFileClose(vfs_file_t ctx) { - (void)ctx; -} - -bool vfsFileIsValid(vfs_file_t ctx) { - return g_vfs && ctx.handle != 0; -} - -usize vfsFileSize(vfs_file_t ctx) { - if (!vfsFileIsValid(ctx)) return 0; - vfsdata_t *data = (vfsdata_t *)ctx.handle; - return data->size; -} - -buffer_t vfsFileReadWhole(arena_t *arena, strview_t name) { - return vfsRead(arena, g_vfs, name); -} - -buffer_t vfsFileReadWholeFP(arena_t *arena, vfs_file_t ctx) { - if (!vfsFileIsValid(ctx)) return (buffer_t){0}; - vfsdata_t *data = (vfsdata_t *)ctx.handle; - buffer_t out = {0}; - usize pos_before = arenaTell(arena); - if (!vfs_read(arena, g_vfs, data, &out, false)) { - arenaRewind(arena, pos_before); - return (buffer_t){0}; - } - return out; -} - -str_t vfsFileReadWholeStr(arena_t *arena, strview_t name) { - return vfsReadStr(arena, g_vfs, name); -} - -str_t vfsFileReadWholeStrFP(arena_t *arena, vfs_file_t ctx) { - if (!vfsFileIsValid(ctx)) return STR_EMPTY; - vfsdata_t *data = (vfsdata_t *)ctx.handle; - buffer_t buf = {0}; - usize pos_before = arenaTell(arena); - if (!vfs_read(arena, g_vfs, data, &buf, true)) { - arenaRewind(arena, pos_before); - return STR_EMPTY; - } - return (str_t){ - .buf = buf.data, - .len = buf.len, - }; -} - -// == PRIVATE FUNCTIONS ============================== - -vfsflags_e vfs_validate_flags(vfsflags_e flags) { - if (flags & VFS_FLAGS_COMPRESSED && flags & VFS_FLAGS_NULL_TERMINATE_FILES) { - warn("VirtualFS: both COMPRESSEd and NULL_TERMINATE_FILES flags are set to ON, but they are mutually exclusive. turning NULL_TERMINATE_FILES off"); - flags &= ~VFS_FLAGS_NULL_TERMINATE_FILES; - } - - return flags; -} - -bool vfs_read(arena_t *arena, virtualfs_t *vfs, vfsdata_t *data, buffer_t *out, bool null_terminate) { - if (!vfs || !data || !out) { - return false; - } - - bool is_allocated = true; - out->len = data->size; - - if (vfs->flags & VFS_FLAGS_COMPRESSED) { - out->data = alloc(arena, uint8, out->len); - - uint8 *compressed = NULL; - - if (vfs->flags & VFS_FLAGS_DONT_STREAM) { - assert((data->offset + data->compsize) < vfs->buffer.len); - compressed = vfs->buffer.data + data->offset; - } - else { - uint64 offset = vfs->base_offset + data->offset; - fileSeek(vfs->fp, offset); - - arena_t scratch = *arena; - uint8 *compressed = alloc(&scratch, uint8, data->compsize); - usize read = fileRead(vfs->fp, compressed, data->compsize); - if (read != data->compsize) { - err("VirtualFS: read %zu bytes, but should have read %zu", read, data->compsize); - return false; - } - } - - int decompsize = LZ4_decompress_safe(compressed, out->data, data->compsize, out->len); - if (decompsize < 0) { - err("VirtualFS: couldn't decompress buffer: %d", decompsize); - return false; - } - } - else { - if (vfs->flags & VFS_FLAGS_DONT_STREAM) { - assert((data->offset + data->size) < vfs->buffer.len); - out->data = vfs->buffer.data + data->offset; - is_allocated = false; - } - else { - out->data = alloc(arena, uint8, data->size); - - uint64 offset = vfs->base_offset + data->offset; - fileSeek(vfs->fp, offset); - usize read = fileRead(vfs->fp, out->data, out->len); - if (read != out->len) { - err("VirtualFS: read %zu bytes, but should have read %zu", read, out->len); - return false; - } - } - } - - if (null_terminate && !(vfs->flags & VFS_FLAGS_NULL_TERMINATE_FILES)) { - if (is_allocated) { - alloc(arena, char); - } - else { - uint8 *buf = alloc(arena, uint8, out->len + 1); - memcpy(buf, out->data, out->len); - out->data = buf; - } - out->len += 1; - } - - return true; -} - -vfsfile_t *vfs_add_dir(arena_t *arena, strview_t path, vfsfile_t *tail, uint32 *count, uint64 *bytesize) { - uint8 tmpbuf[KB(1)]; - - dir_t *dir = dirOpen(arena, path); - dir_entry_t *entry = NULL; - - if (strvEquals(path, strv("./"))) { - path = STRV_EMPTY; - } - - vfsfile_t *head = tail; - vfsfile_t *cur = tail; - - while ((entry = dirNext(arena, dir))) { - arena_t scratch = arenaMake(ARENA_STATIC, sizeof(tmpbuf), tmpbuf); - - vfsfile_t *newfile = NULL; - - if (entry->type == DIRTYPE_DIR) { - if (strvEquals(strv(entry->name), strv(".")) || - strvEquals(strv(entry->name), strv("..")) - ) { - continue; - } - str_t fullpath = strFmt(&scratch, "%v%v/", path, entry->name); - newfile = vfs_add_dir(arena, strv(fullpath), cur, count, bytesize); - } - else { - newfile = alloc(arena, vfsfile_t); - newfile->path = strFmt(arena, "%v%v", path, entry->name); - newfile->size = entry->filesize; - newfile->comp_size = newfile->size; - - if (cur) cur->next = newfile; - (*count)++; - (*bytesize) += newfile->size; - } - - if (!head) head = newfile; - cur = newfile; - } - - return cur; -} - - -// == HASH MAP ======================================= - -vfshmap_t vfs_hmap_init(arena_t *arena, int pow2, uint32 max_values) { - uint size = 1 << pow2; - return (vfshmap_t) { - .size = size, - .max_values = max_values, - .buckets = alloc(arena, vfshnode_t*, size), - .values = alloc(arena, vfsdata_t, max_values), - }; -} - -void vfs_hmap_add(arena_t *arena, vfshmap_t *hmap, strview_t key, vfsdata_t value) { - if (!hmap) return; - - if ((float)hmap->count >= (float)hmap->size * 0.6f) { - warn("more than 60%% of the hashmap is being used: %d/%d", hmap->count, hmap->size); - } - - uint64 hash = djb2_hash(key.buf, key.len); - usize index = hash & (hmap->size - 1); - vfshnode_t *bucket = hmap->buckets[index]; - if (bucket) hmap->collisions++; - while (bucket) { - // already exists - if (bucket->hash == hash && strvEquals(strv(bucket->key), key)) { - hmap->values[bucket->index] = value; - return; - } - bucket = bucket->next; - } - - assert(hmap->count < hmap->max_values); - - bucket = alloc(arena, vfshnode_t); - - bucket->hash = hash; - bucket->key = str(arena, key); - bucket->index = hmap->count; - bucket->next = hmap->buckets[index]; - - hmap->values[hmap->count++] = value; - hmap->buckets[index] = bucket; -} - -vfsdata_t *vfs_hmap_get(vfshmap_t *hmap, strview_t key) { - if (!hmap || hmap->count == 0) { - return NULL; - } - - uint64 hash = djb2_hash(key.buf, key.len); - usize index = hash & (hmap->size - 1); - vfshnode_t *bucket = hmap->buckets[index]; - while (bucket) { - if (bucket->hash == hash && strvEquals(strv(bucket->key), key)) { - return &hmap->values[bucket->index]; - } - bucket = bucket->next; - } - - return NULL; -} - -uint64 sdbm_hash(const void *bytes, usize count) { - const uint8 *data = bytes; - uint64 hash = 0; - - for (usize i = 0; i < count; ++i) { - hash = data[i] + (hash << 6) + (hash << 16) - hash; - } - - return hash; -} - -uint64 djb2_hash(const void *bytes, usize count) { - const uint8 *data = bytes; - uint64 hash = 5381; - int c; - - for (usize i = 0; i < count; ++i) { - hash = ((hash << 5) + hash) + data[i]; - } - - return hash; -} - -uint64 java_hash(const void *bytes, usize count) { - const uint8 *data = bytes; - uint64 hash = 1125899906842597L; - - for (usize i = 0; i < count; ++i) { - hash = ((hash << 5) - hash) + data[i]; - } - - return hash; -} diff --git a/vfs.h b/vfs.h deleted file mode 100644 index b09aa60..0000000 --- a/vfs.h +++ /dev/null @@ -1,36 +0,0 @@ -#pragma once - -#include "str.h" -#include "arena.h" - -typedef enum { - VFS_FLAGS_NONE = 0, - VFS_FLAGS_COMPRESSED = 1 << 0, - VFS_FLAGS_NULL_TERMINATE_FILES = 1 << 1, // only valid when compress is false - VFS_FLAGS_DONT_STREAM = 1 << 2, -} vfsflags_e; - -typedef struct virtualfs_t virtualfs_t; - -bool vfsVirtualiseDir(arena_t scratch, strview_t dirpath, strview_t outfile, vfsflags_e flags); -virtualfs_t *vfsReadFromFile(arena_t *arena, strview_t vfs_file, vfsflags_e flags); -buffer_t vfsRead(arena_t *arena, virtualfs_t *vfs, strview_t path); -str_t vfsReadStr(arena_t *arena, virtualfs_t *vfs, strview_t path); - -// vfs replacement for the file api - -typedef struct { - uintptr_t handle; -} vfs_file_t; - -void vfsSetGlobalVirtualFS(virtualfs_t *vfs); - -bool vfsFileExists(strview_t path); -vfs_file_t vfsFileOpen(strview_t name, int mode); -void vfsFileClose(vfs_file_t ctx); -bool vfsFileIsValid(vfs_file_t ctx); -usize vfsFileSize(vfs_file_t ctx); -buffer_t vfsFileReadWhole(arena_t *arena, strview_t name); -buffer_t vfsFileReadWholeFP(arena_t *arena, vfs_file_t ctx); -str_t vfsFileReadWholeStr(arena_t *arena, strview_t name); -str_t vfsFileReadWholeStrFP(arena_t *arena, vfs_file_t ctx); \ No newline at end of file diff --git a/vfs_file.h b/vfs_file.h deleted file mode 100644 index 8f63c84..0000000 --- a/vfs_file.h +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once - -#include "vfs.h" -#include "tracelog.h" - -#define fileExists vfsFileExists -#define fileOpen vfsFileOpen -#define fileClose vfsFileClose -#define fileIsValid vfsFileIsValid -#define fileSize vfsFileSize -#define fileReadWhole vfsFileReadWhole -#define fileReadWholeFP vfsFileReadWholeFP -#define fileReadWholeStr vfsFileReadWholeStr -#define fileReadWholeStrFP vfsFileReadWholeStrFP - -#define VFS_FAIL_READONLY(fn) err("VirtualFS: trying to call function " #fn "which requires writing to files. VirtualFS is read only!") -#define VFS_FAIL_STATELESS(fn) err("VirtualFS: trying to call function " #fn "which requires state. VirtualFS only works on stateless file's commands!") - -#define fileDelete(...) VFS_FAIL_READONLY(fileDelete) -#define filePutc(...) VFS_FAIL_READONLY(filePutc) -#define filePuts(...) VFS_FAIL_READONLY(filePuts) -#define filePrintf(...) VFS_FAIL_READONLY(filePrintf) -#define filePrintfv(...) VFS_FAIL_READONLY(filePrintfv) -#define fileWrite(...) VFS_FAIL_READONLY(fileWrite) -#define fileWriteWhole(...) VFS_FAIL_READONLY(fileWriteWhole) -#define fileGetTime(...) VFS_FAIL_READONLY(fileGetTime) -#define fileGetTimeFP(...) VFS_FAIL_READONLY(fileGetTimeFP) -#define fileHasChanged(...) VFS_FAIL_READONLY(fileHasChanged) - -#define fileTell(...) VFS_FAIL_STATELESS(fileTell) -#define fileRead(...) VFS_FAIL_STATELESS(fileRead) -#define fileSeek(...) VFS_FAIL_STATELESS(fileSeek) -#define fileSeekEnd(...) VFS_FAIL_STATELESS(fileSeekEnd) -#define fileRewind(...) VFS_FAIL_STATELESS(fileRewind) \ No newline at end of file diff --git a/vmem.c b/vmem.c deleted file mode 100644 index b7cfd9c..0000000 --- a/vmem.c +++ /dev/null @@ -1,133 +0,0 @@ -#include "vmem.h" - -#include - -#include "tracelog.h" - -static usize vmem__page_size = 0; -static void vmem__update_page_size(void); - -// platform generic functions - -usize vmemGetPageSize(void) { - if (!vmem__page_size) { - vmem__update_page_size(); - } - return vmem__page_size; -} - -usize vmemPadToPage(usize byte_count) { - if (!vmem__page_size) { - vmem__update_page_size(); - } - - if (byte_count == 0) { - return vmem__page_size; - } - - // bit twiddiling, vmem__page_size MUST be a power of 2 - usize padding = vmem__page_size - (byte_count & (vmem__page_size - 1)); - if (padding == vmem__page_size) { - padding = 0; - } - return byte_count + padding; -} - -#if COLLA_WIN - -#include - -void *vmemInit(usize size, usize *out_padded_size) { - usize alloc_size = vmemPadToPage(size); - - void *ptr = VirtualAlloc(NULL, alloc_size, MEM_RESERVE, PAGE_NOACCESS); - - if (out_padded_size) { - *out_padded_size = alloc_size; - } - - return ptr; -} - -bool vmemRelease(void *base_ptr) { - return VirtualFree(base_ptr, 0, MEM_RELEASE); -} - -bool vmemCommit(void *ptr, usize num_of_pages) { - usize page_size = vmemGetPageSize(); - - void *new_ptr = VirtualAlloc(ptr, num_of_pages * page_size, MEM_COMMIT, PAGE_READWRITE); - - if (!new_ptr) { - debug("ERROR: failed to commit memory: %lu\n", GetLastError()); - } - - return new_ptr != NULL; -} - -static void vmem__update_page_size(void) { - SYSTEM_INFO info = {0}; - GetSystemInfo(&info); - vmem__page_size = info.dwPageSize; -} - -// #elif COLLA_LIN -#else - -#include -#include -#include -#include - -typedef struct { - usize len; -} vmem__header; - -void *vmemInit(usize size, usize *out_padded_size) { - size += sizeof(vmem__header); - usize alloc_size = vmemPadToPage(size); - - vmem__header *header = mmap(NULL, alloc_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); - - if (header == MAP_FAILED) { - fatal("could not map %zu memory: %s", size, strerror(errno)); - } - - if (out_padded_size) { - *out_padded_size = alloc_size; - } - - header->len = alloc_size; - - return header + 1; -} - -bool vmemRelease(void *base_ptr) { - if (!base_ptr) return false; - vmem__header *header = (vmem__header *)base_ptr - 1; - - int res = munmap(header, header->len); - if (res == -1) { - err("munmap failed: %s", strerror(errno)); - } - return res != -1; -} - -bool vmemCommit(void *ptr, usize num_of_pages) { - // mmap doesn't need a commit step - (void)ptr; - (void)num_of_pages; - return true; -} - -static void vmem__update_page_size(void) { - long lin_page_size = sysconf(_SC_PAGE_SIZE); - - if (lin_page_size < 0) { - fatal("could not get page size: %s", strerror(errno)); - } - - vmem__page_size = (usize)lin_page_size; -} - -#endif \ No newline at end of file diff --git a/vmem.h b/vmem.h deleted file mode 100644 index 39c81ed..0000000 --- a/vmem.h +++ /dev/null @@ -1,12 +0,0 @@ -#ifndef VIRTUAL_MEMORY_HEADER -#define VIRTUAL_MEMORY_HEADER - -#include "collatypes.h" - -void *vmemInit(usize size, usize *out_padded_size); -bool vmemRelease(void *base_ptr); -bool vmemCommit(void *ptr, usize num_of_pages); -usize vmemGetPageSize(void); -usize vmemPadToPage(usize byte_count); - -#endif // VIRTUAL_MEMORY_HEADER \ No newline at end of file diff --git a/warnings/colla_warn_beg.h b/warnings/colla_warn_beg.h deleted file mode 100644 index 4747736..0000000 --- a/warnings/colla_warn_beg.h +++ /dev/null @@ -1,7 +0,0 @@ -#if COLLA_CLANG - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Winitializer-overrides" -#pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments" - -#endif \ No newline at end of file diff --git a/warnings/colla_warn_end.h b/warnings/colla_warn_end.h deleted file mode 100644 index e45a222..0000000 --- a/warnings/colla_warn_end.h +++ /dev/null @@ -1,5 +0,0 @@ -#if COLLA_CLANG - -#pragma clang diagnostic pop - -#endif \ No newline at end of file diff --git a/websocket.c b/websocket.c deleted file mode 100644 index f370a57..0000000 --- a/websocket.c +++ /dev/null @@ -1,142 +0,0 @@ -#include "websocket.h" - -#include "arena.h" -#include "str.h" -#include "server.h" -#include "socket.h" -#include "base64.h" -#include "strstream.h" - -#include "sha1.h" - -#if !COLLA_MSVC -extern uint16_t ntohs(uint16_t hostshort); -extern uint16_t htons(uint16_t hostshort); -extern uint32_t htonl(uint32_t hostlong); - -uint64_t ntohll(uint64_t input) { - uint64_t rval = 0; - uint8_t *data = (uint8_t *)&rval; - - data[0] = input >> 56; - data[1] = input >> 48; - data[2] = input >> 40; - data[3] = input >> 32; - data[4] = input >> 24; - data[5] = input >> 16; - data[6] = input >> 8; - data[7] = input >> 0; - - return rval; -} - -uint64_t htonll(uint64_t input) { - return ntohll(input); -} - -#endif - -bool wsInitialiseSocket(arena_t scratch, socket_t websocket, strview_t key) { - str_t full_key = strFmt(&scratch, "%v" WEBSOCKET_MAGIC, key, WEBSOCKET_MAGIC); - - sha1_t sha1_ctx = sha1_init(); - sha1_result_t sha1_data = sha1(&sha1_ctx, full_key.buf, full_key.len); - - // convert to big endian for network communication - for (int i = 0; i < 5; ++i) { - sha1_data.digest[i] = htonl(sha1_data.digest[i]); - } - - buffer_t encoded_key = base64Encode(&scratch, (buffer_t){ (uint8 *)sha1_data.digest, sizeof(sha1_data.digest) }); - - str_t response = strFmt( - &scratch, - - "HTTP/1.1 101 Switching Protocols\r\n" - "Connection: Upgrade\r\n" - "Upgrade: websocket\r\n" - "Sec-WebSocket-Accept: %v\r\n" - "\r\n", - encoded_key - ); - - bool success = skSend(websocket, response.buf, response.len); - return success; -} - -buffer_t wsEncodeMessage(arena_t *arena, strview_t message) { - int extra = 6; - if (message.len > UINT16_MAX) extra += sizeof(uint64); - else if (message.len > UINT8_MAX) extra += sizeof(uint16); - uint8 *bytes = alloc(arena, uint8, message.len + extra); - bytes[0] = 0b10000001; - bytes[1] = 0b10000000; - int offset = 2; - if (message.len > UINT16_MAX) { - bytes[1] |= 0b01111111; - uint64 len = htonll(message.len); - memcpy(bytes + 2, &len, sizeof(len)); - offset += sizeof(uint64); - } - else if (message.len > UINT8_MAX) { - bytes[1] |= 0b01111110; - uint16 len = htons((uint16)message.len); - memcpy(bytes + 2, &len, sizeof(len)); - offset += sizeof(uint16); - } - else { - bytes[1] |= (uint8)message.len; - } - - uint32 mask = 0; - memcpy(bytes + offset, &mask, sizeof(mask)); - offset += sizeof(mask); - memcpy(bytes + offset, message.buf, message.len); - - return (buffer_t){ bytes, message.len + extra }; -} - -str_t wsDecodeMessage(arena_t *arena, buffer_t message) { - str_t out = STR_EMPTY; - uint8 *bytes = message.data; - - bool mask = bytes[1] & 0b10000000; - int offset = 2; - uint64 msglen = bytes[1] & 0b01111111; - - // 16bit msg len - if (msglen == 126) { - uint64 be_len = 0; - memcpy(&be_len, bytes + 2, sizeof(be_len)); - msglen = ntohs(be_len); - offset += sizeof(uint16); - } - // 64bit msg len - else if (msglen == 127) { - uint64 be_len = 0; - memcpy(&be_len, bytes + 2, sizeof(be_len)); - msglen = ntohll(be_len); - offset += sizeof(uint64); - } - - if (msglen == 0) { - warn("message length = 0"); - } - else if (mask) { - uint8 *decoded = alloc(arena, uint8, msglen + 1); - uint8 masks[4] = {0}; - memcpy(masks, bytes + offset, sizeof(masks)); - offset += 4; - - for (uint64 i = 0; i < msglen; ++i) { - decoded[i] = bytes[offset + i] ^ masks[i % 4]; - } - - out = (str_t){ (char *)decoded, msglen }; - } - else { - warn("mask bit not set!"); - } - - return out; -} diff --git a/websocket.h b/websocket.h deleted file mode 100644 index 3758e5f..0000000 --- a/websocket.h +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -#include "arena.h" -#include "str.h" - -#define WEBSOCKET_MAGIC "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" -#define WEBSOCKET_HTTP_KEY "Sec-WebSocket-Key" - -typedef uintptr_t socket_t; - -bool wsInitialiseSocket(arena_t scratch, socket_t websocket, strview_t key); -buffer_t wsEncodeMessage(arena_t *arena, strview_t message); -str_t wsDecodeMessage(arena_t *arena, buffer_t message); \ No newline at end of file diff --git a/win/net_win32.c b/win/net_win32.c new file mode 100644 index 0000000..f209ba9 --- /dev/null +++ b/win/net_win32.c @@ -0,0 +1,337 @@ +#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) { + 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) { + err("call to HttpSendRequest failed: %v", os_get_error_string(os_get_last_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; + } + ostr_puts(&body, strv(read_buffer, read)); + } + + 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/os_win32.c b/win/os_win32.c new file mode 100644 index 0000000..9f17762 --- /dev/null +++ b/win/os_win32.c @@ -0,0 +1,693 @@ +#include +#include + +#include "../os.h" +#include "../net.h" + +#if COLLA_TCC +#include "../tcc/colla_tcc.h" +#endif + +typedef enum os_entity_kind_e { + OS_KIND_NULL, + OS_KIND_THREAD, + OS_KIND_MUTEX, + OS_KIND_CONDITION_VARIABLE, +} os_entity_kind_e; + +typedef struct os_entity_t os_entity_t; +struct os_entity_t { + os_entity_t *next; + os_entity_kind_e kind; + union { + struct { + HANDLE handle; + thread_func_t *func; + void *userdata; + DWORD id; + } thread; + CRITICAL_SECTION mutex; + CONDITION_VARIABLE cv; + }; +}; + +struct { + arena_t arena; + os_system_info_t info; + os_entity_t *entity_free; + oshandle_t hstdout; + oshandle_t hstdin; +} w32_data = {0}; + +os_entity_t *os__win_alloc_entity(os_entity_kind_e kind) { + os_entity_t *entity = w32_data.entity_free; + if (entity) { + list_pop(w32_data.entity_free); + } + else { + entity = alloc(&w32_data.arena, os_entity_t); + } + entity->kind = kind; + return entity; +} + +void os__win_free_entity(os_entity_t *entity) { + entity->kind = OS_KIND_NULL; + list_push(w32_data.entity_free, entity); +} + +void os_init(void) { + SetConsoleOutputCP(CP_UTF8); + + SYSTEM_INFO sysinfo = {0}; + GetSystemInfo(&sysinfo); + + os_system_info_t *info = &w32_data.info; + info->processor_count = (u64)sysinfo.dwNumberOfProcessors; + info->page_size = sysinfo.dwPageSize; + + w32_data.arena = arena_make(ARENA_VIRTUAL, OS_ARENA_SIZE); + + TCHAR namebuf[MAX_COMPUTERNAME_LENGTH + 1]; + DWORD namebuflen = sizeof(namebuf); + BOOL result = GetComputerName(namebuf, &namebuflen); + if (!result) { + err("failed to get computer name: %v", os_get_error_string(os_get_last_error())); + } + + info->machine_name = str_from_tstr(&w32_data.arena, (tstr_t){ namebuf, namebuflen}); + + HANDLE hstdout = CreateFile(TEXT("CONOUT$"), GENERIC_WRITE, FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); + HANDLE hstdin = CreateFile(TEXT("CONIN$"), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); + + if (hstdout == INVALID_HANDLE_VALUE) err("couldn't open CONOUT$"); + else w32_data.hstdout.data = (uptr)hstdout; + + if (hstdin == INVALID_HANDLE_VALUE) err("couldn't open CONIN$"); + else w32_data.hstdin.data = (uptr)hstdin; +} + +void os_cleanup(void) { + os_file_close(w32_data.hstdout); + os_file_close(w32_data.hstdin); + + arena_cleanup(&w32_data.arena); +} + +void os_abort(int code) { + ExitProcess(code); +} + +iptr os_get_last_error(void) { + return (iptr)GetLastError(); +} + +str_t os_get_error_string(iptr error) { + static u8 tmpbuf[1024] = {0}; + arena_t arena = arena_make(ARENA_STATIC, sizeof(tmpbuf), tmpbuf); + DWORD code = LOWORD(error); + + WCHAR msgbuf[512]; + DWORD chars; + chars = FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, code, 0, msgbuf, arrlen(msgbuf), NULL); + if (chars == 0) { + err("FormatMessageW: %ld", os_get_last_error()); + return STR_EMPTY; + } + + debug("error: %zi 0x%zX", error, error); + + // remove \r\n at the end + return str_from_str16(&arena, str16_init(msgbuf, chars - 2)); +} + +os_wait_t os_wait_on_handles(oshandle_t *handles, int count, bool wait_all, u32 milliseconds) { + HANDLE win_handles[OS_MAX_WAITABLE_HANDLES] = {0}; + assert(count < MAXIMUM_WAIT_OBJECTS); + + for (int i = 0; i < count; ++i) { + win_handles[i] = (HANDLE)(handles[i].data); + } + + DWORD result = WaitForMultipleObjects(count, win_handles, wait_all, milliseconds); + + os_wait_t out = {0}; + + if (result == WAIT_FAILED) { + out.result = OS_WAIT_FAILED; + } + else if (result == WAIT_TIMEOUT) { + out.result = OS_WAIT_TIMEOUT; + } + else if (result >= WAIT_ABANDONED_0) { + out.result = OS_WAIT_ABANDONED; + out.index = result - WAIT_ABANDONED_0; + } + else { + out.result = OS_WAIT_FINISHED; + out.index = result - WAIT_OBJECT_0; + } + + return out; +} + +os_system_info_t os_get_system_info(void) { + return w32_data.info; +} + +void os_log_set_colour(os_log_colour_e colour) { + WORD attribute = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; + switch (colour) { + case LOG_COL_BLACK: attribute = 0; break; + case LOG_COL_RED: attribute = FOREGROUND_RED; break; + case LOG_COL_GREEN: attribute = FOREGROUND_GREEN; break; + case LOG_COL_BLUE: attribute = FOREGROUND_BLUE; break; + case LOG_COL_MAGENTA: attribute = FOREGROUND_RED | FOREGROUND_BLUE; break; + case LOG_COL_YELLOW: attribute = FOREGROUND_RED | FOREGROUND_GREEN; break; + case LOG_COL_CYAN: attribute = FOREGROUND_GREEN | FOREGROUND_BLUE; break; + default: break; + } + + HANDLE hc = GetStdHandle(STD_OUTPUT_HANDLE); + SetConsoleTextAttribute(hc, attribute | FOREGROUND_INTENSITY); +} + +oshandle_t os_stdout(void) { + return w32_data.hstdout; +} + +oshandle_t os_stdin(void) { + return w32_data.hstdin; +} + +// == FILE ====================================== + +#define OS_SMALL_SCRATCH() \ +u8 tmpbuf[KB(1)]; \ +arena_t scratch = arena_make(ARENA_STATIC, sizeof(tmpbuf), tmpbuf) + +DWORD os__win_mode_to_access(filemode_e mode) { + DWORD out = 0; + if (mode & FILEMODE_READ) out |= GENERIC_READ; + if (mode & FILEMODE_WRITE) out |= GENERIC_WRITE; + return out; +} + +DWORD os__win_mode_to_creation(filemode_e mode) { + if (mode == FILEMODE_READ) return OPEN_EXISTING; + if (mode == FILEMODE_WRITE) return CREATE_ALWAYS; + return OPEN_ALWAYS; +} + +bool os_file_exists(strview_t path) { + OS_SMALL_SCRATCH(); + tstr_t name = strv_to_tstr(&scratch, path); + DWORD attributes = GetFileAttributes(name.buf); + return attributes != INVALID_FILE_ATTRIBUTES && !(attributes & FILE_ATTRIBUTE_DIRECTORY); +} + +tstr_t os_file_fullpath(arena_t *arena, strview_t filename) { + OS_SMALL_SCRATCH(); + + TCHAR long_path_prefix[] = TEXT("\\\\?\\"); + const usize prefix_len = arrlen(long_path_prefix) - 1; + + tstr_t rel_path = strv_to_tstr(&scratch, filename); + DWORD pathlen = GetFullPathName(rel_path.buf, 0, NULL, NULL); + + tstr_t full_path = { + .buf = alloc(arena, TCHAR, pathlen + prefix_len + 1), + .len = pathlen + prefix_len, + }; + memcpy(full_path.buf, long_path_prefix, prefix_len * sizeof(TCHAR)); + + GetFullPathName(rel_path.buf, pathlen + 1, full_path.buf + prefix_len, NULL); + + return full_path; +} + +bool os_file_delete(strview_t path) { + OS_SMALL_SCRATCH(); + tstr_t fname = strv_to_tstr(&scratch, path); + return DeleteFile(fname.buf); +} + +oshandle_t os_file_open(strview_t path, filemode_e mode) { + OS_SMALL_SCRATCH(); + + tstr_t full_path = os_file_fullpath(&scratch, path); + + HANDLE handle = CreateFile( + full_path.buf, + os__win_mode_to_access(mode), + FILE_SHARE_READ, + NULL, + os__win_mode_to_creation(mode), + FILE_ATTRIBUTE_NORMAL, + NULL + ); + + if (handle == INVALID_HANDLE_VALUE) { + handle = NULL; + } + + return (oshandle_t){ + .data = (uptr)handle + }; +} + +void os_file_close(oshandle_t handle) { + if (!os_handle_valid(handle)) return; + CloseHandle((HANDLE)handle.data); +} + +usize os_file_read(oshandle_t handle, void *buf, usize len) { + if (!os_handle_valid(handle)) return 0; + DWORD read = 0; + ReadFile((HANDLE)handle.data, buf, (DWORD)len, &read, NULL); + return (usize)read; +} + +usize os_file_write(oshandle_t handle, const void *buf, usize len) { + if (!os_handle_valid(handle)) return 0; + DWORD written = 0; + WriteFile((HANDLE)handle.data, buf, (DWORD)len, &written, NULL); + return (usize)written; +} + +bool os_file_seek(oshandle_t handle, usize offset) { + if (!os_handle_valid(handle)) return false; + LARGE_INTEGER offset_large = { + .QuadPart = offset, + }; + DWORD result = SetFilePointer((HANDLE)handle.data, offset_large.LowPart, &offset_large.HighPart, FILE_BEGIN); + return result != INVALID_SET_FILE_POINTER; +} + +bool os_file_seek_end(oshandle_t handle) { + if (!os_handle_valid(handle)) return false; + DWORD result = SetFilePointer((HANDLE)handle.data, 0, NULL, FILE_END); + return result != INVALID_SET_FILE_POINTER; +} + +void os_file_rewind(oshandle_t handle) { + if (!os_handle_valid(handle)) return; + SetFilePointer((HANDLE)handle.data, 0, NULL, FILE_BEGIN); +} + +usize os_file_tell(oshandle_t handle) { + if (!os_handle_valid(handle)) return 0; + LARGE_INTEGER tell = {0}; + BOOL result = SetFilePointerEx((HANDLE)handle.data, (LARGE_INTEGER){0}, &tell, FILE_CURRENT); + return result == TRUE ? (usize)tell.QuadPart : 0; +} + +usize os_file_size(oshandle_t handle) { + if (!os_handle_valid(handle)) return 0; + LARGE_INTEGER size = {0}; + BOOL result = GetFileSizeEx((HANDLE)handle.data, &size); + return result == TRUE ? (usize)size.QuadPart : 0; +} + +bool os_file_is_finished(oshandle_t handle) { + if (!os_handle_valid(handle)) return 0; + + char tmp = 0; + DWORD read = 0; + BOOL success = ReadFile((HANDLE)handle.data, &tmp, sizeof(tmp), &read, NULL); + bool is_finished = success && read == 0; + + if (!is_finished) { + SetFilePointer((HANDLE)handle.data, -1, NULL, FILE_CURRENT); + } + + return is_finished; +} + +u64 os_file_time_fp(oshandle_t handle) { + if (!os_handle_valid(handle)) return 0; + FILETIME time = {0}; + GetFileTime((HANDLE)handle.data, NULL, NULL, &time); + ULARGE_INTEGER utime = { + .HighPart = time.dwHighDateTime, + .LowPart = time.dwLowDateTime, + }; + return (u64)utime.QuadPart; +} + +// == DIR WALKER ================================ + +typedef struct dir_t { + WIN32_FIND_DATA find_data; + HANDLE handle; + dir_entry_t cur_entry; + dir_entry_t next_entry; +} dir_t; + +dir_entry_t os__dir_entry_from_find_data(arena_t *arena, WIN32_FIND_DATA *fd) { + dir_entry_t out = {0}; + + out.name = str_from_tstr(arena, tstr_init(fd->cFileName, 0)); + + if (fd->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + out.type = DIRTYPE_DIR; + } + else { + LARGE_INTEGER filesize = { + .LowPart = fd->nFileSizeLow, + .HighPart = fd->nFileSizeHigh, + }; + out.file_size = filesize.QuadPart; + } + + return out; +} + +dir_t *os_dir_open(arena_t *arena, strview_t path) { + u8 tmpbuf[KB(1)] = {0}; + arena_t scratch = arena_make(ARENA_STATIC, sizeof(tmpbuf), tmpbuf); + + tstr_t winpath = strv_to_tstr(&scratch, path); + // get a little extra leeway + TCHAR fullpath[MAX_PATH + 16] = {0}; + DWORD pathlen = GetFullPathName(winpath.buf, MAX_PATH, fullpath, NULL); + // add asterisk at the end of the path + if (fullpath[pathlen] != '\\' && fullpath[pathlen] != '/') { + fullpath[pathlen++] = '\\'; + } + fullpath[pathlen++] = '*'; + fullpath[pathlen++] = '\0'; + + dir_t *ctx = alloc(arena, dir_t); + ctx->handle = FindFirstFile(fullpath, &ctx->find_data); + + if (ctx->handle == INVALID_HANDLE_VALUE) { + arena_pop(arena, sizeof(dir_t)); + return NULL; + } + + ctx->next_entry = os__dir_entry_from_find_data(arena, &ctx->find_data); + + return ctx; +} + +void os_dir_close(dir_t *dir) { + FindClose(dir->handle); + dir->handle = INVALID_HANDLE_VALUE; +} + +bool os_dir_is_valid(dir_t *dir) { + return dir && dir->handle != INVALID_HANDLE_VALUE; +} + +dir_entry_t *os_dir_next(arena_t *arena, dir_t *dir) { + if (!os_dir_is_valid(dir)) { + return NULL; + } + + dir->cur_entry = dir->next_entry; + + dir->next_entry = (dir_entry_t){0}; + + if (FindNextFile(dir->handle, &dir->find_data)) { + dir->next_entry = os__dir_entry_from_find_data(arena, &dir->find_data); + } + else { + os_dir_close(dir); + } + + return &dir->cur_entry; +} + +// == PROCESS =================================== + +struct os_env_t { + void *data; +}; + +void os_set_env_var(arena_t scratch, strview_t key, strview_t value) { + str16_t k = strv_to_str16(&scratch, key); + str16_t v = strv_to_str16(&scratch, value); + SetEnvironmentVariableW(k.buf, v.buf); +} + +str_t os_get_env_var(arena_t *arena, strview_t key) { + u8 tmpbuf[KB(1)]; + arena_t scratch = arena_make(ARENA_STATIC, sizeof(tmpbuf), tmpbuf); + + wchar_t static_buf[1024] = {0}; + wchar_t *buf = static_buf; + + str16_t k = strv_to_str16(&scratch, key); + DWORD len = GetEnvironmentVariableW(k.buf, static_buf, arrlen(static_buf)); + + if (len > arrlen(static_buf)) { + buf = alloc(&scratch, wchar_t, len); + len = GetEnvironmentVariableW(k.buf, buf, len); + } + + return str_from_str16(arena, str16_init(buf, len)); +} + +os_env_t *os_get_env(arena_t *arena) { + os_env_t *out = alloc(arena, os_env_t); + out->data = GetEnvironmentStringsW(); + return out; +} + +oshandle_t os_run_cmd_async(arena_t scratch, os_cmd_t *cmd, os_env_t *optional_env) { + STARTUPINFOW start_info = { + .cb = sizeof(STARTUPINFO), + .hStdError = GetStdHandle(STD_ERROR_HANDLE), + .hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE), + .hStdInput = GetStdHandle(STD_INPUT_HANDLE), + .dwFlags = STARTF_USESTDHANDLES, + }; + + PROCESS_INFORMATION proc_info = {0}; + + outstream_t cmdline = ostr_init(&scratch); + + for_each (cur, cmd->head ? cmd->head : cmd) { + for (int i = 0; i < cur->count; ++i) { + strview_t arg = cur->items[i]; + if (strv_contains(arg, ' ')) { + ostr_print(&cmdline, "\"%v\"", arg); + } + else { + ostr_puts(&cmdline, arg); + } + ostr_putc(&cmdline, ' '); + } + } + + ostr_pop(&cmdline, 1); + ostr_putc(&cmdline, '\0'); + + 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; + + BOOL success = CreateProcessW( + NULL, + command.buf, + NULL, + NULL, + TRUE, + 0, + env, + NULL, + &start_info, + &proc_info + ); + + if (env) { + FreeEnvironmentStringsW(env); + optional_env->data = NULL; + } + + if (!success) { + err("couldn't create process (%v): %v", cmd_view, os_get_error_string(os_get_last_error())); + return os_handle_zero(); + } + + CloseHandle(proc_info.hThread); + + return (oshandle_t) { + .data = (uptr)proc_info.hProcess + }; +} + +bool os_process_wait(oshandle_t proc, uint time, int *out_exit) { + if (!os_handle_valid(proc)) { + err("waiting on invalid handle"); + return false; + } + + DWORD result = WaitForSingleObject((HANDLE)proc.data, (DWORD)time); + + if (result == WAIT_TIMEOUT) { + return false; + } + + if (result != WAIT_OBJECT_0) { + err("could not wait for proces: %v", os_get_error_string(os_get_last_error())); + return false; + } + + DWORD exit_status; + if (!GetExitCodeProcess((HANDLE)proc.data, &exit_status)) { + err("could not get exit status from process: %v", os_get_error_string(os_get_last_error())); + return false; + } + + CloseHandle((HANDLE)proc.data); + + if (out_exit) { + *out_exit = exit_status; + } + + return exit_status == 0; +} + + +// == VMEM ====================================== + +void *os_reserve(usize size, usize *out_padded_size) { + usize alloc_size = os_pad_to_page(size); + void *ptr = VirtualAlloc(NULL, alloc_size, MEM_RESERVE, PAGE_NOACCESS); + if (out_padded_size) { + *out_padded_size = alloc_size; + } + return ptr; +} + +bool os_commit(void *ptr, usize num_of_pages) { + usize page_size = os_get_system_info().page_size; + void *new_ptr = VirtualAlloc(ptr, num_of_pages * page_size, MEM_COMMIT, PAGE_READWRITE); + return new_ptr != NULL; +} + +bool os_release(void *ptr, usize size) { + COLLA_UNUSED(size); + return VirtualFree(ptr, 0, MEM_RELEASE); +} + +// == THREAD ==================================== + +DWORD os__win_thread_entry_point(void *ptr) { + os_entity_t *entity = (os_entity_t *)ptr; + thread_func_t *func = entity->thread.func; + void *userdata = entity->thread.userdata; + u64 id = entity->thread.id; + return func(id, userdata); +} + +oshandle_t os_thread_launch(thread_func_t func, void *userdata) { + os_entity_t *entity = os__win_alloc_entity(OS_KIND_THREAD); + + entity->thread.func = func; + entity->thread.userdata = userdata; + entity->thread.handle = CreateThread(NULL, 0, os__win_thread_entry_point, entity, 0, &entity->thread.id); + + return (oshandle_t){ (uptr)entity }; +} + +bool os_thread_detach(oshandle_t thread) { + if (!os_handle_valid(thread)) return false; + os_entity_t *entity = (os_entity_t *)thread.data; + BOOL result = CloseHandle(entity->thread.handle); + os__win_free_entity(entity); + return result; +} + +bool os_thread_join(oshandle_t thread, int *code) { + if (!os_handle_valid(thread)) return false; + os_entity_t *entity = (os_entity_t *)thread.data; + int return_code = WaitForSingleObject(entity->thread.handle, INFINITE); + if (code) *code = return_code; + BOOL result = CloseHandle(entity->thread.handle); + os__win_free_entity(entity); + return return_code != WAIT_FAILED && result; +} + +u64 os_thread_get_id(oshandle_t thread) { + if (!os_handle_valid(thread)) return 0; + os_entity_t *entity = (os_entity_t *)thread.data; + return entity->thread.id; +} + +// == MUTEX ===================================== + +oshandle_t os_mutex_create(void) { + os_entity_t *entity = os__win_alloc_entity(OS_KIND_MUTEX); + + InitializeCriticalSection(&entity->mutex); + + return (oshandle_t){ (uptr)entity }; +} + +void os_mutex_free(oshandle_t mutex) { + if (!os_handle_valid(mutex)) return; + os_entity_t *entity = (os_entity_t *)mutex.data; + DeleteCriticalSection(&entity->mutex); + os__win_free_entity(entity); +} + +void os_mutex_lock(oshandle_t mutex) { + if (!os_handle_valid(mutex)) return; + os_entity_t *entity = (os_entity_t *)mutex.data; + EnterCriticalSection(&entity->mutex); +} + +void os_mutex_unlock(oshandle_t mutex) { + if (!os_handle_valid(mutex)) return; + os_entity_t *entity = (os_entity_t *)mutex.data; + LeaveCriticalSection(&entity->mutex); +} + +bool os_mutex_try_lock(oshandle_t mutex) { + if (!os_handle_valid(mutex)) return false; + os_entity_t *entity = (os_entity_t *)mutex.data; + return TryEnterCriticalSection(&entity->mutex); +} + +#if !COLLA_NO_CONDITION_VARIABLE + +// == CONDITION VARIABLE ======================== + +oshandle_t os_cond_create(void) { + os_entity_t *entity = os__win_alloc_entity(OS_KIND_CONDITION_VARIABLE); + + InitializeConditionVariable(&entity->cv); + + return (oshandle_t){ (uptr)entity }; +} + +void os_cond_free(oshandle_t cond) { + if (!os_handle_valid(cond)) return; + os_entity_t *entity = (os_entity_t *)cond.data; + os__win_free_entity(entity); +} + +void os_cond_signal(oshandle_t cond) { + if (!os_handle_valid(cond)) return; + os_entity_t *entity = (os_entity_t *)cond.data; + WakeConditionVariable(&entity->cv); +} + +void os_cond_broadcast(oshandle_t cond) { + if (!os_handle_valid(cond)) return; + os_entity_t *entity = (os_entity_t *)cond.data; + WakeAllConditionVariable(&entity->cv); +} + +void os_cond_wait(oshandle_t cond, oshandle_t mutex, int milliseconds) { + if (!os_handle_valid(cond)) return; + os_entity_t *entity_cv = (os_entity_t *)cond.data; + os_entity_t *entity_mtx = (os_entity_t *)mutex.data; + SleepConditionVariableCS(&entity_cv->cv, &entity_mtx->mutex, milliseconds); +} + +#endif diff --git a/win/str_win32.c b/win/str_win32.c new file mode 100644 index 0000000..4785ecb --- /dev/null +++ b/win/str_win32.c @@ -0,0 +1,81 @@ +#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 diff --git a/xml.c b/xml.c deleted file mode 100644 index 0b9ab95..0000000 --- a/xml.c +++ /dev/null @@ -1,145 +0,0 @@ -#include "xml.h" - -#include "file.h" -#include "strstream.h" -#include "tracelog.h" - -static xmltag_t *xml__parse_tag(arena_t *arena, instream_t *in); - -xml_t xmlParse(arena_t *arena, strview_t filename) { - return xmlParseStr(arena, fileReadWholeStr(arena, filename)); -} - -xml_t xmlParseStr(arena_t *arena, str_t xmlstr) { - xml_t out = { - .text = xmlstr, - .root = alloc(arena, xmltag_t), - }; - return out; - - - instream_t in = istrInitLen(xmlstr.buf, xmlstr.len); - - while (!istrIsFinished(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 *xmlGetTag(xmltag_t *parent, strview_t key, bool recursive) { - xmltag_t *t = parent ? parent->child : NULL; - while (t) { - if (strvEquals(key, t->key)) { - return t; - } - if (recursive && t->child) { - xmltag_t *out = xmlGetTag(t, key, recursive); - if (out) { - return out; - } - } - t = t->next; - } - return NULL; -} - -strview_t xmlGetAttribute(xmltag_t *tag, strview_t key) { - xmlattr_t *a = tag ? tag->attributes : NULL; - while (a) { - if (strvEquals(key, a->key)) { - return a->value; - } - a = a->next; - } - return STRV_EMPTY; -} - -// == PRIVATE FUNCTIONS ======================================================================== - -static xmlattr_t *xml__parse_attr(arena_t *arena, instream_t *in) { - if (istrPeek(in) != ' ') { - return NULL; - } - - strview_t key = strvTrim(istrGetView(in, '=')); - istrSkip(in, 2); // skip = and " - strview_t val = strvTrim(istrGetView(in, '"')); - istrSkip(in, 1); // skip " - - if (strvIsEmpty(key) || strvIsEmpty(val)) { - warn("key or value empty"); - return NULL; - } - - xmlattr_t *attr = alloc(arena, xmlattr_t); - attr->key = key; - attr->value = val; - return attr; -} - -static xmltag_t *xml__parse_tag(arena_t *arena, instream_t *in) { - istrSkipWhitespace(in); - - // we're either parsing the body, or we have finished the object - if (istrPeek(in) != '<' || istrPeekNext(in) == '/') { - return NULL; - } - - istrSkip(in, 1); // skip < - - // meta tag, we don't care about these - if (istrPeek(in) == '?') { - istrIgnoreAndSkip(in, '\n'); - return NULL; - } - - xmltag_t *tag = alloc(arena, xmltag_t); - - tag->key = strvTrim(istrGetViewEither(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 (istrPeek(in) == '/') { - istrSkip(in, 2); // skip / and > - return tag; - } - - istrSkip(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 - istrSkipWhitespace(in); - tag->content = istrGetView(in, '<'); - - // closing tag - istrSkip(in, 2); // skip < and / - strview_t closing = strvTrim(istrGetView(in, '>')); - if (!strvEquals(tag->key, closing)) { - warn("opening and closing tags are different!: (%v) != (%v)", tag->key, closing); - } - istrSkip(in, 1); // skip > - return tag; -} diff --git a/xml.h b/xml.h deleted file mode 100644 index 573ec33..0000000 --- a/xml.h +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once - -#include "str.h" -#include "arena.h" - -typedef struct xmlattr_t { - strview_t key; - strview_t value; - struct xmlattr_t *next; -} xmlattr_t; - -typedef struct xmltag_t { - strview_t key; - xmlattr_t *attributes; - strview_t content; - struct xmltag_t *child; - struct xmltag_t *tail; - struct xmltag_t *next; -} xmltag_t; - -typedef struct { - str_t text; - xmltag_t *root; - xmltag_t *tail; -} xml_t; - -xml_t xmlParse(arena_t *arena, strview_t filename); -xml_t xmlParseStr(arena_t *arena, str_t xmlstr); - -xmltag_t *xmlGetTag(xmltag_t *parent, strview_t key, bool recursive); -strview_t xmlGetAttribute(xmltag_t *tag, strview_t key); \ No newline at end of file