From ccec386ad26755d15fb42232d8592cfdc0b886bf Mon Sep 17 00:00:00 2001 From: Brendan Hansen Date: Thu, 14 Apr 2022 19:33:17 -0500 Subject: [PATCH] adding bh.h and stb_ds.h --- build.sh | 9 +- include/bh.h | 2743 ++++++++++++++++++++++++++++++++++++++++++++++ include/stb_ds.h | 1895 ++++++++++++++++++++++++++++++++ src/cli.c | 4 +- src/embedder.c | 9 +- 5 files changed, 4651 insertions(+), 9 deletions(-) create mode 100644 include/bh.h create mode 100644 include/stb_ds.h diff --git a/build.sh b/build.sh index 20502cc..c868ee5 100755 --- a/build.sh +++ b/build.sh @@ -1,16 +1,17 @@ #!/bin/sh CC="gcc" +WARNINGS='-Wimplicit -Wmisleading-indentation -Wparentheses -Wsequence-point -Wreturn-type -Wshift-negative-value -Wunused-but-set-parameter -Wunused-but-set-variable -Wunused-function -Wunused-label -Wmaybe-uninitialized -Wsign-compare -Wstrict-overflow -Wduplicated-branches -Wduplicated-cond -Wtrigraphs -Waddress -Wlogical-op' FLAGS="-g3" LIBS= -INCLUDES= +INCLUDES="-I include" TARGET="libonyx_embedder.so" C_FILES="src/embedder.c" -$CC $FLAGS $INCLUDES -o $TARGET -c $C_FILES $LIBS +$CC $FLAGS $INCLUDES -o $TARGET -c $C_FILES $LIBS $WARNINGS -C_FILES=src/cli.c +C_FILES="src/cli.c" TARGET=onyx-debug LIBS="-L$(pwd) -lonyx_embedder" -$CC $FLAGS $INCLUDES -o $TARGET $C_FILES $LIBS \ No newline at end of file +$CC $FLAGS $INCLUDES -o $TARGET $C_FILES $LIBS $WARNINGS \ No newline at end of file diff --git a/include/bh.h b/include/bh.h new file mode 100644 index 0000000..b187b96 --- /dev/null +++ b/include/bh.h @@ -0,0 +1,2743 @@ +#ifndef BH_H +#define BH_H + +// NOTE: For lseek64 +#define _LARGEFILE64_SOURCE + +#if defined(_WIN32) || defined(_WIN64) + #define _BH_WINDOWS 1 +#endif + +#if defined(__unix__) + #define _BH_LINUX 1 +#endif + +#include +#include +#include + +#ifdef _BH_LINUX + #include + #include + #include + #include +#endif + +#include +#include +#include // TODO: Replace with needed functions +#include +#include +#include + +#if defined(_MSC_VER) && !defined(_WINDOWS_) + #include "small_windows.h" +#endif + +//------------------------------------------------------------------------------------- +// Better types +//------------------------------------------------------------------------------------- +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 int64_t isize; +typedef i32 b32; +typedef void* ptr; +typedef float f32; +typedef double f64; + + + + + + + + + + + + + + + + + + + + +//------------------------------------------------------------------------------------- +// Better character functions +//------------------------------------------------------------------------------------- +b32 char_is_alpha(const char a); +b32 char_is_num(const char a); +b32 char_is_alphanum(const char a); +char charset_contains(const char* charset, char ch); +b32 char_is_whitespace(const char a); +b32 char_in_range(const char lo, const char hi, const char a); +i64 chars_match(char* ptr1, char* ptr2); + + + + + +//------------------------------------------------------------------------------------- +// Better math functions +//------------------------------------------------------------------------------------- +#define bh_max(a, b) ((a) > (b) ? (a) : (b)) +#define bh_min(a, b) ((a) < (b) ? (a) : (b)) +#define bh_clamp(v, a, b) (bh_min((b), bh_max((a), (v)))) +#define bh_abs(x) ((x) < 0 ? -(x) : (x)) +#define size_of(x) (isize) sizeof(x) + +static inline u64 log2_dumb(u64 n) { + switch (n) { + case 1 << 0: return 0; + case 1 << 1: return 1; + case 1 << 2: return 2; + case 1 << 3: return 3; + case 1 << 4: return 4; + case 1 << 5: return 5; + case 1 << 6: return 6; + case 1 << 7: return 7; + case 1 << 8: return 8; + case 1 << 9: return 9; + case 1 << 10: return 10; + case 1 << 11: return 11; + case 1 << 12: return 12; + case 1 << 13: return 13; + case 1 << 14: return 14; + case 1 << 15: return 15; + case 1 << 16: return 16; + case 1 << 17: return 17; + case 1 << 18: return 18; + case 1 << 19: return 19; + case 1 << 20: return 20; + case 1 << 21: return 21; + case 1 << 22: return 22; + case 1 << 23: return 23; + case 1 << 24: return 24; + case 1 << 25: return 25; + case 1 << 26: return 26; + case 1 << 27: return 27; + case 1 << 28: return 28; + case 1 << 29: return 29; + case 1 << 30: return 30; + case 1 << 31: return 31; + + default: return 0; + } +} + + + +static inline const char* bh_num_suffix(u64 i) { + if (i == 11 || i == 12 || i == 13) return "th"; + + switch (i % 10) { + case 0: return "th"; + case 1: return "st"; + case 2: return "nd"; + case 3: return "rd"; + case 4: return "th"; + case 5: return "th"; + case 6: return "th"; + case 7: return "th"; + case 8: return "th"; + case 9: return "th"; + + default: return ""; + } +} + + + + +//------------------------------------------------------------------------------------- +// Conversion functions +//------------------------------------------------------------------------------------- + +// Converts an unsigned integer to the unsigned LEB128 format +u8* uint_to_uleb128(u64 n, i32* output_length); +u8* int_to_leb128(i64 n, i32* output_length); +u8* float_to_ieee754(f32 f, b32 reverse); +u8* double_to_ieee754(f64 f, b32 reverse); + +u64 uleb128_to_uint(u8* bytes, i32 *byte_walker); + + + +//------------------------------------------------------------------------------------- +// Helpful macros +//------------------------------------------------------------------------------------- +#define bh_offset_of(Type, elem) ((isize)&(((Type)*) 0)->elem) +#define bh_align_of(Type) bh_offset_of(struct { char c; Type member; }, member) +#define bh_swap(Type, a, b) do { Type tmp = (a); (a) = (b); (b) = tmp; } while(0) + +#define bh_align(x, a) if ((x) % (a) != 0) (x) += (a) - ((x) % (a)); + +#define bh_pointer_add(ptr, amm) ((void *)((u8 *) ptr + amm)) +#define BH_BIT(x) (1 << (x)) +#define BH_MASK_SET(var, set, mask) ((set) ? ((var) |= (mask)) : ((var) &= ~(mask))) + +#define fori(var, lo, hi) for (i64 var = (lo); var < (hi); var++) +#define forir(var, hi, lo) for (i64 var = (hi); var >= (lo); var--) +#define forll(T, var, start, step) for (T* var = (start); var != NULL; var = (T *) var->step) + +#if defined(BH_DEBUG) && !defined(_BH_WINDOWS) + #define DEBUG_HERE __asm("int $3") +#else + #define DEBUG_HERE +#endif + + + + + +//------------------------------------------------------------------------------------- +// Custom allocators +//------------------------------------------------------------------------------------- + +typedef enum bh_allocator_actions { + bh_allocator_action_alloc, + bh_allocator_action_free, + bh_allocator_action_resize, +} bh_allocator_actions; + +#define BH_ALLOCATOR_PROC(name) \ +ptr name(ptr data, bh_allocator_actions action, \ + isize size, isize alignment, \ + void* prev_memory, \ + u64 flags) + +typedef BH_ALLOCATOR_PROC(bh__allocator_proc); // NOTE: so bh__allocator_proc can be used instead of that type + +typedef struct bh_allocator { + bh__allocator_proc* proc; // Procedure that can handle bh_allocator_actions + ptr data; // Pointer to the other data for the allocator +} bh_allocator; + +typedef enum bh_allocator_flags { + bh_allocator_flag_clear = 1 // Sets all memory to be 0 +} bh_allocator_flags; + +ptr bh_alloc(bh_allocator a, isize size); +ptr bh_alloc_aligned(bh_allocator a, isize size, isize alignment); +ptr bh_resize(bh_allocator a, ptr data, isize new_size); +ptr bh_resize_aligned(bh_allocator a, ptr data, isize new_size, isize alignment); +void bh_free(bh_allocator a, ptr data); + +#define bh_alloc_item(allocator_, T) (T *) bh_alloc(allocator_, sizeof(T)) +#define bh_alloc_array(allocator_, T, n) (T *) bh_alloc(allocator_, sizeof(T) * (n)) + +// NOTE: This should get optimized out since alignment should be a power of two +#define bh__align(x, alignment) ((((x) / alignment) + 1) * alignment) + + + + +// HEAP ALLOCATOR +// Essentially a wrapper for malloc, free and realloc +bh_allocator bh_heap_allocator(void); +BH_ALLOCATOR_PROC(bh_heap_allocator_proc); + + + + + +// ARENA ALLOCATOR +typedef struct bh_arena { + bh_allocator backing; + ptr first_arena, current_arena; + isize size, arena_size; // in bytes +} bh_arena; + +typedef struct bh__arena_internal { + ptr next_arena; + void* data; // Not actually a pointer, just used for the offset +} bh__arena_internal; + +void bh_arena_init(bh_arena* alloc, bh_allocator backing, isize arena_size); +void bh_arena_free(bh_arena* alloc); +bh_allocator bh_arena_allocator(bh_arena* alloc); +BH_ALLOCATOR_PROC(bh_arena_allocator_proc); + + + + + + +// SCRATCH ALLOCATOR +typedef struct bh_scratch { + bh_allocator backing; + ptr memory, end, curr; +} bh_scratch; + +void bh_scratch_init(bh_scratch* scratch, bh_allocator backing, isize scratch_size); +void bh_scratch_free(bh_scratch* scratch); +bh_allocator bh_scratch_allocator(bh_scratch* scratch); +BH_ALLOCATOR_PROC(bh_scratch_allocator_proc); + + + + + + + +//------------------------------------------------------------------------------------- +// Allocator based string functions +//------------------------------------------------------------------------------------- + +b32 bh_str_starts_with(char* str, char* start); +b32 bh_str_ends_with(char* str, char* end); +char* bh_strdup(bh_allocator a, char* str); + + + + + + + + + + + + + +//------------------------------------------------------------------------------------- +// Better files +//------------------------------------------------------------------------------------- +#ifndef BH_NO_FILE + +typedef enum bh_file_error { + BH_FILE_ERROR_NONE, + BH_FILE_ERROR_INVALID, + BH_FILE_ERROR_BAD_FD, +} bh_file_error; + +typedef enum bh_file_mode { + BH_FILE_MODE_READ = 1 << 0, + BH_FILE_MODE_WRITE = 1 << 1, + BH_FILE_MODE_APPEND = 1 << 2, + BH_FILE_MODE_RW = 1 << 3, + + BH_FILE_MODE_MODES = BH_FILE_MODE_READ | BH_FILE_MODE_WRITE | BH_FILE_MODE_APPEND | BH_FILE_MODE_RW +} bh_file_mode; + +typedef enum bh_file_whence { + BH_FILE_WHENCE_BEGIN = SEEK_SET, + BH_FILE_WHENCE_CURRENT = SEEK_CUR, + BH_FILE_WHENCE_END = SEEK_END, +} bh_file_whence; + +#ifdef _BH_WINDOWS + typedef HANDLE bh_file_descriptor; +#else + typedef int bh_file_descriptor; +#endif + +typedef struct bh_file { + bh_file_descriptor fd; + char const* filename; +} bh_file; + +typedef enum bh_file_standard { + BH_FILE_STANDARD_INPUT, + BH_FILE_STANDARD_OUTPUT, + BH_FILE_STANDARD_ERROR +} bh_file_standard; + +typedef struct bh_file_contents { + bh_allocator allocator; + const char *filename; + isize length; + void* data; +} bh_file_contents; + +bh_file_error bh_file_get_standard(bh_file* file, bh_file_standard stand); + +bh_file_error bh_file_create(bh_file* file, char const* filename); +bh_file_error bh_file_open(bh_file* file, char const* filename); +bh_file_error bh_file_open_mode(bh_file* file, bh_file_mode mode, const char* filename); +bh_file_error bh_file_new(bh_file* file, bh_file_descriptor fd, const char* filename); +b32 bh_file_read_at(bh_file* file, i64 offset, void* buffer, isize buff_size, isize* bytes_read); +b32 bh_file_write_at(bh_file* file, i64 offset, void const* buffer, isize buff_size, isize* bytes_wrote); +i64 bh_file_seek(bh_file* file, i64 offset, bh_file_whence whence); +i64 bh_file_seek_to(bh_file* file, i64 offset); +i64 bh_file_seek_to_end(bh_file* file); +i64 bh_file_skip(bh_file* file, i64 bytes); +i64 bh_file_tell(bh_file* file); +bh_file_error bh_file_close(bh_file* file); +i32 bh_file_read(bh_file* file, void* buffer, isize buff_size); +i32 bh_file_write(bh_file* file, void* buffer, isize buff_size); +void bh_file_flush(bh_file* file); +i64 bh_file_size(bh_file* file); +b32 bh_file_exists(char const* filename); +char* bh_path_get_full_name(char const* filename, bh_allocator a); +char* bh_path_get_parent(char const* filename, bh_allocator a); +char* bh_path_convert_separators(char* path); + +// This function returns a volatile pointer. Do not store it without copying! +// `included_folders` is bh_arr(const char *). +char* bh_lookup_file(char* filename, char* relative_to, char *suffix, b32 add_suffix, const char ** included_folders, b32 search_included_folders); + +#define bh_file_read_contents(allocator_, x) _Generic((x), \ + bh_file*: bh_file_read_contents_bh_file, \ + const char*: bh_file_read_contents_direct, \ + char*: bh_file_read_contents_direct)((allocator_), x) + +bh_file_contents bh_file_read_contents_bh_file(bh_allocator alloc, bh_file* file); +bh_file_contents bh_file_read_contents_direct(bh_allocator alloc, const char* filename); +i32 bh_file_contents_free(bh_file_contents* contents); + + +#ifdef _BH_WINDOWS + typedef struct Windows_Directory_Opened { + HANDLE hndl; + WIN32_FIND_DATAA found_file; + } Windows_Directory_Opened; + + typedef Windows_Directory_Opened *bh_dir; +#else + typedef DIR *bh_dir; +#endif + +typedef enum bh_dirent_type { + BH_DIRENT_UNKNOWN, + BH_DIRENT_BLOCK, + BH_DIRENT_CHAR, + BH_DIRENT_DIRECTORY, + BH_DIRENT_FILE, + BH_DIRENT_SYMLINK, + BH_DIRENT_OTHER, +} bh_dirent_type; + +typedef struct bh_dirent { + bh_dirent_type type; + u32 id; + u32 name_length; + char name[256]; +} bh_dirent; + +bh_dir bh_dir_open(char* path); +b32 bh_dir_read(bh_dir dir, bh_dirent* out); +void bh_dir_close(bh_dir dir); + +#endif + + + + + + + + + + +//------------------------------------------------------------------------------------- +// Alternate printing +//------------------------------------------------------------------------------------- +// Barebones implementation of printf. Does not support all format options +// Currently supports: +// %c - chars +// %_(u)d - ints where _ is: +// nothing - decimal +// o - octal +// x - hexadecimal +// %_(u)l - longs where _ is: +// nothing - decimal +// o - octal +// x - hexadecimal +// %f - floating points +// %s - null terminated strings +// %p - pointers +// %% - literal % + +typedef struct bh__print_format { + u32 base; +} bh__print_format; + +isize bh_printf(char const *fmt, ...); +isize bh_printf_va(char const *fmt, va_list va); +isize bh_printf_err(char const *fmt, ...); +isize bh_printf_err_va(char const *fmt, va_list va); +isize bh_fprintf(bh_file* f, char const *fmt, ...); +isize bh_fprintf_va(bh_file* f, char const *fmt, va_list va); +char* bh_bprintf(char const *fmt, ...); +char* bh_bprintf_va(char const *fmt, va_list va); +char* bh_aprintf(bh_allocator alloc, const char* fmt, ...); +char* bh_aprintf_va(bh_allocator alloc, const char* fmt, va_list va); +isize bh_snprintf(char *str, isize n, char const *fmt, ...); +isize bh_snprintf_va(char *str, isize n, char const *fmt, va_list va); + + + + + + + + + + + + + + + + + + + +//------------------------------------------------------------------------------------- +// Flexible buffer +//------------------------------------------------------------------------------------- + +typedef struct bh_buffer { + bh_allocator allocator; + i32 length, capacity; + u8* data; +} bh_buffer; + +#ifndef BH_BUFFER_GROW_FORMULA +#define BH_BUFFER_GROW_FORMULA(x) ((x) > 0 ? ((x) << 1) : 16) +#endif + +void bh_buffer_init(bh_buffer* buffer, bh_allocator alloc, i32 length); +void bh_buffer_free(bh_buffer* buffer); +void bh_buffer_grow(bh_buffer* buffer, i32 length); +void bh_buffer_append(bh_buffer* buffer, const void * data, i32 length); +void bh_buffer_concat(bh_buffer* buffer, bh_buffer other); +void bh_buffer_write_byte(bh_buffer* buffer, u8 byte); +void bh_buffer_write_u32(bh_buffer* buffer, u32 i); +void bh_buffer_write_u64(bh_buffer* buffer, u64 i); +void bh_buffer_align(bh_buffer* buffer, u32 alignment); + + + + + + + + + + + + + + +//------------------------------------------------------------------------------------- +// Better dynamically-sized arrays +//------------------------------------------------------------------------------------- +#ifndef BH_NO_ARRAY + +typedef struct bh__arr { + bh_allocator allocator; + i32 length, capacity; +} bh__arr; + +#ifndef BH_ARR_GROW_FORMULA +#define BH_ARR_GROW_FORMULA(x) ((x) > 0 ? ((x) << 1) : 4) +#endif + +#define bh_arr(T) T* +#define bh__arrhead(arr) (((bh__arr *)(arr)) - 1) + +#define bh_arr_allocator(arr) (arr ? bh__arrhead(arr)->allocator : bh_heap_allocator()) +#define bh_arr_length(arr) (arr ? bh__arrhead(arr)->length : 0) +#define bh_arr_capacity(arr) (arr ? bh__arrhead(arr)->capacity : 0) +#define bh_arr_size(arr) (arr ? bh__arrhead(arr)->capacity * sizeof(*(arr)) : 0) +#define bh_arr_valid(arr, i) (arr ? (i32)(i) < bh__arrhead(arr)->length : 0) + +#define bh_arr_pop(arr) ((arr)[--bh__arrhead(arr)->length]) +#define bh_arr_last(arr) ((arr)[bh__arrhead(arr)->length - 1]) +#define bh_arr_end(arr, i) ((i) >= &(arr)[bh_arr_length(arr)]) +#define bh_arr_start(arr, i) ((i) < (arr)) + +#define bh_arr_new(allocator_, arr, cap) (bh__arr_grow((allocator_), (void**) &(arr), sizeof(*(arr)), cap)) +#define bh_arr_free(arr) (bh__arr_free((void**) &(arr))) +#define bh_arr_copy(allocator_, arr) (bh__arr_copy((allocator_), (arr), sizeof(*(arr)))) + +#define bh_arr_grow(arr, cap) (bh__arr_grow(bh_arr_allocator(arr), (void **) &(arr), sizeof(*(arr)), cap)) +#define bh_arr_shrink(arr, cap) (bh__arr_shrink((void **) &(arr), sizeof(*(arr)), cap)) +#define bh_arr_set_length(arr, n) (bh__arrhead(arr)->length = n) + +#define bh_arr_insertn(arr, i, n) (bh__arr_insertn((void **) &(arr), sizeof(*(arr)), i, n)) + +#define bh_arr_insert_end(arr, n) ( \ + bh__arr_grow(bh_arr_allocator(arr), (void **) &(arr), sizeof(*(arr)), bh_arr_length(arr) + n), \ + bh__arrhead(arr)->length += n) + +#define bh_arr_push(arr, value) ( \ + bh_arr_length(arr) + 1 > bh_arr_capacity(arr) ? bh__arr_grow(bh_arr_allocator(arr), (void **) &(arr), sizeof(*(arr)), bh_arr_length(arr) + 1) : 0, \ + arr[bh__arrhead(arr)->length++] = value) + +#define bh_arr_set_at(arr, n, value) ( \ + bh__arr_grow(bh_arr_allocator(arr), (void **) &(arr), sizeof(*(arr)), (n) + 1), \ + bh_arr_set_length((arr), bh_max(bh_arr_length(arr), (i32) (n) + 1)), \ + arr[n] = value) + +#define bh_arr_is_empty(arr) (arr ? bh__arrhead(arr)->length == 0 : 1) +#define bh_arr_clear(arr) (arr ? (bh__arrhead(arr)->length = 0) : 0) + +#define bh_arr_deleten(arr, i, n) (bh__arr_deleten((void **) &(arr), sizeof(*(arr)), i, n)) +#define bh_arr_fastdelete(arr, i) (arr[i] = arr[--bh__arrhead(arr)->length]) + +#define bh_arr_each(T, var, arr) for (T* var = (arr); !bh_arr_end((arr), var); var++) +#define bh_arr_rev_each(T, var, arr) for (T* var = &bh_arr_last((arr)); !bh_arr_start((arr), var); var--) + +#define bh_arr_zero(arr) memset(arr, 0, bh_arr_length(arr) * sizeof(*(arr))); + +b32 bh__arr_grow(bh_allocator alloc, void** arr, i32 elemsize, i32 cap); +b32 bh__arr_shrink(void** arr, i32 elemsize, i32 cap); +b32 bh__arr_free(void **arr); +void* bh__arr_copy(bh_allocator alloc, void *arr, i32 elemsize); +void bh__arr_insertn(void **arr, i32 elemsize, i32 index, i32 numelems); +void bh__arr_deleten(void **arr, i32 elemsize, i32 index, i32 numelems); + +#endif + + + + + + + + + + + + +//------------------------------------------------------------------------------------- +// STRING HASH TABLE FUNCTIONS +//------------------------------------------------------------------------------------- +#ifndef BH_NO_TABLE + +#ifdef BH_DEFINE +u64 bh__table_hash_function(const char* str, i32 len, i32 mod) { + u64 hash = 5381; + i32 c, l = 0; + if (len == 0) len = ((u32) 1 << 31) - 1; + + while ((c = *str++) && l++ < len) { + hash = (hash << 5) + hash + c; + } + + return hash % mod; +} +#endif + +typedef struct bh_table_iterator { + ptr *tab, *endtab; + i32 elemsize, arrlen; + ptr entry; +} bh_table_iterator; + +typedef struct bh__table { + bh_allocator allocator; + u64 table_size; // NOTE: u64 since padding will make it 8-bytes no matter what + ptr arrs[]; +} bh__table; + +#define bh_table(T) T* + +#ifdef BH_TABLE_SIZE_SAFE + #define bh_table_init(allocator_, tab, hs) bh__table_init(allocator_, (bh__table **)&(tab), hs) + #define bh_table_free(tab) bh__table_free((bh__table **)&(tab)) + #define bh_table_put(T, tab, key, value) (assert(sizeof(T) == sizeof(*(tab))), (*((T *) bh__table_put((bh__table *) tab, sizeof(T), key)) = (T) value)) + #define bh_table_has(T, tab, key) (assert(sizeof(T) == sizeof(*(tab))), (bh__table_has((bh__table *) tab, sizeof(T), key))) + #define bh_table_get(T, tab, key) (assert(sizeof(T) == sizeof(*(tab))), (*((T *) bh__table_get((bh__table *) tab, sizeof(T), key)))) + #define bh_table_delete(T, tab, key) (assert(sizeof(T) == sizeof(*(tab))), bh__table_delete((bh__table *) tab, sizeof(T), key)) + #define bh_table_clear(tab) (bh__table_clear((bh__table *) tab)) + + #define bh_table_iter_setup(T, tab) (assert(sizeof(T) == sizeof(*(tab))), bh__table_iter_setup((bh__table *) tab, sizeof(T))) + #define bh_table_iter_key(it) ((char *)(bh_pointer_add(it.entry, it.elemsize + sizeof(u16)))) + #define bh_table_iter_value(T, it) (*(T *)it.entry) +#else + #define bh_table_init(allocator_, tab, hs) bh__table_init(allocator_, (bh__table **)&(tab), hs) + #define bh_table_free(tab) bh__table_free((bh__table **)&(tab)) + #define bh_table_put(T, tab, key, value) (*((T *) bh__table_put((bh__table *) tab, sizeof(T), key)) = value) + #define bh_table_has(T, tab, key) (bh__table_has((bh__table *) tab, sizeof(T), key)) + #define bh_table_get(T, tab, key) (*((T *) bh__table_get((bh__table *) tab, sizeof(T), key))) + #define bh_table_delete(T, tab, key) (bh__table_delete((bh__table *) tab, sizeof(T), key)) + #define bh_table_clear(tab) (bh__table_clear((bh__table *) tab)) + + #define bh_table_iter_setup(T, tab) (bh__table_iter_setup((bh__table *) tab, sizeof(T))) + #define bh_table_iter_key(it) ((char *)(bh_pointer_add(it.entry, it.elemsize + sizeof(u16)))) + #define bh_table_iter_value(T, it) (*(T *)it.entry) +#endif + +#define bh_table_each_start(T, table) { \ + bh_table_iterator it = bh_table_iter_setup(T, (table)); \ + while (bh_table_iter_next(&it)) { \ + const char* key = bh_table_iter_key(it); \ + T value = bh_table_iter_value(T, it); +#define bh_table_each_end } } + +b32 bh__table_init(bh_allocator allocator, bh__table **table, i32 table_size); +b32 bh__table_free(bh__table **table); +ptr bh__table_put(bh__table *table, i32 elemsize, char *key); +b32 bh__table_has(bh__table *table, i32 elemsize, char *key); +ptr bh__table_get(bh__table *table, i32 elemsize, char *key); +void bh__table_delete(bh__table *table, i32 elemsize, char *key); +void bh__table_clear(bh__table *table); +bh_table_iterator bh__table_iter_setup(bh__table *table, i32 elemsize); +b32 bh_table_iter_next(bh_table_iterator* it); + +#endif +// Using stb_ds for tables now because they are better in every single way. +#define Table(T) struct { char *key; T value; } * + + + + + + + + +//------------------------------------------------------------------------------- +// IMAP (integer to integer map) +//------------------------------------------------------------------------------- +#ifndef BH_NO_IMAP + +typedef u64 bh_imap_entry_t; + +typedef struct bh__imap_lookup_result { + i64 hash_index; + i64 entry_prev; + i64 entry_index; +} bh__imap_lookup_result; + +typedef struct bh__imap_entry { + bh_imap_entry_t key, value; + i64 next; +} bh__imap_entry; + +typedef struct bh_imap { + bh_allocator allocator; + + bh_arr(i64) hashes; + bh_arr(bh__imap_entry) entries; +} bh_imap; + + +void bh_imap_init(bh_imap* imap, bh_allocator alloc, i32 hash_count); +void bh_imap_free(bh_imap* imap); +void bh_imap_put(bh_imap* imap, bh_imap_entry_t key, bh_imap_entry_t value); +b32 bh_imap_has(bh_imap* imap, bh_imap_entry_t key); +bh_imap_entry_t bh_imap_get(bh_imap* imap, bh_imap_entry_t key); +void bh_imap_delete(bh_imap* imap, bh_imap_entry_t key); +void bh_imap_clear(bh_imap* imap); + +#ifdef BH_DEFINE +#endif // BH_DEFINE + + +#endif + + + + + + + + + + + +// MANAGED HEAP ALLOCATOR +typedef struct bh_managed_heap { + bh_imap ptrs; +} bh_managed_heap; + +void bh_managed_heap_init(bh_managed_heap* mh); +void bh_managed_heap_free(bh_managed_heap* mh); +bh_allocator bh_managed_heap_allocator(bh_managed_heap* mh); +BH_ALLOCATOR_PROC(bh_managed_heap_allocator_proc); + + + + + + + + + + + +//------------------------------------------------------------------------------- +// OTHER COMMON DATA STRUCTURES +//------------------------------------------------------------------------------- +#ifndef BH_NO_DATASTRUCTURES + + + + + + + + + + + + + + + + +#endif // BH_NO_DATASTRUCTURES + + + + + +//------------------------------------------------------------------------------ +// TIME / DURATION +//------------------------------------------------------------------------------ +u64 bh_time_curr(); +u64 bh_time_duration(u64 old); + + + + + + + + + + + +#ifdef BH_DEFINE + +#undef BH_DEFINE +//------------------------------------------------------------------------------------- +// IMPLEMENTATIONS +//------------------------------------------------------------------------------------- + +//------------------------------------------------------------------------------------- +// CHAR FUNCTIONS +//------------------------------------------------------------------------------------- + +b32 char_is_alpha(const char a) { + return ('a' <= a && a <= 'z') || ('A' <= a && a <= 'Z'); +} + +char charset_contains(const char* charset, char ch) { + while (*charset) { + if (*charset == ch) return ch; + charset++; + } + + return 0; +} + +b32 char_is_num(const char a) { + return ('0' <= a && a <= '9'); +} + +b32 char_is_alphanum(const char a) { + return char_is_alpha(a) || char_is_num(a); +} + +b32 char_is_whitespace(const char a) { + return charset_contains(" \t\r\n", a); +} + +b32 char_in_range(const char lo, const char hi, const char a) { + return lo <= a && a <= hi; +} + +i64 chars_match(char* ptr1, char* ptr2) { + i64 len = 0; + while (*ptr2 != '\0' && *ptr1 == *ptr2) ptr1++, ptr2++, len++; + return *ptr2 == '\0' ? len : 0; +} + + + + + + + + +//------------------------------------------------------------------------------------- +// CUSTOM ALLOCATORS IMPLEMENTATION +//------------------------------------------------------------------------------------- +ptr bh_alloc(bh_allocator a, isize size) { + return bh_alloc_aligned(a, size, 16); +} + +ptr bh_alloc_aligned(bh_allocator a, isize size, isize alignment) { + return a.proc(a.data, bh_allocator_action_alloc, size, alignment, NULL, 0); +} + +ptr bh_resize(bh_allocator a, ptr data, isize new_size) { + return bh_resize_aligned(a, data, new_size, 16); +} + +ptr bh_resize_aligned(bh_allocator a, ptr data, isize new_size, isize alignment) { + return a.proc(a.data, bh_allocator_action_resize, new_size, alignment, data, 0); +} + +void bh_free(bh_allocator a, ptr data) { + if (data != NULL) a.proc(a.data, bh_allocator_action_free, 0, 0, data, 0); +} + + + +// HEAP ALLOCATOR IMPLEMENTATION +bh_allocator bh_heap_allocator(void) { + return (bh_allocator) { + .proc = bh_heap_allocator_proc, + .data = NULL + }; +} + +BH_ALLOCATOR_PROC(bh_heap_allocator_proc) { + ptr retval = NULL; + + switch (action) { + case bh_allocator_action_alloc: { +#if defined(_BH_WINDOWS) + retval = _aligned_malloc(size, alignment); +#elif defined(_BH_LINUX) + i32 success = posix_memalign(&retval, alignment, size); +#endif + if (flags & bh_allocator_flag_clear && retval != NULL) { + memset(retval, 0, size); + } + } break; + + case bh_allocator_action_resize: { + // TODO: Maybe replace with better custom function +#if defined(_BH_WINDOWS) + retval = _aligned_realloc(prev_memory, size, alignment); +#elif defined(_BH_LINUX) + retval = realloc(prev_memory, size); +#endif + } break; + + case bh_allocator_action_free: { +#if defined(_BH_WINDOWS) + _aligned_free(prev_memory); +#elif defined(_BH_LINUX) + free(prev_memory); +#endif + } break; + } + + return retval; +} + + + + + + +// MANAGED HEAP ALLOCATOR IMPLEMENTATION +void bh_managed_heap_init(bh_managed_heap* mh) { + bh_imap_init(&mh->ptrs, bh_heap_allocator(), 512); +} + +void bh_managed_heap_free(bh_managed_heap* mh) { + bh_arr_each(bh__imap_entry, p, mh->ptrs.entries) { +#if defined(_BH_WINDOWS) + _aligned_free((void *) p->key); +#elif defined(_BH_LINUX) + free((void *) p->key); +#endif + } + + bh_imap_free(&mh->ptrs); +} + +bh_allocator bh_managed_heap_allocator(bh_managed_heap* mh) { + return (bh_allocator) { + .proc = bh_managed_heap_allocator_proc, + .data = mh + }; +} + +BH_ALLOCATOR_PROC(bh_managed_heap_allocator_proc) { + bh_managed_heap* mh = (bh_managed_heap *) data; + ptr retval = NULL; + + switch (action) { + case bh_allocator_action_alloc: { +#if defined(_BH_WINDOWS) + retval = _aligned_malloc(size, alignment); +#elif defined(_BH_LINUX) + i32 success = posix_memalign(&retval, alignment, size); +#endif + + if (flags & bh_allocator_flag_clear && retval != NULL) { + memset(retval, 0, size); + } + + if (retval != NULL) + bh_imap_put(&mh->ptrs, (u64) retval, 1); + } break; + + case bh_allocator_action_resize: { + bh_imap_delete(&mh->ptrs, (u64) prev_memory); +#if defined(_BH_WINDOWS) + retval = _aligned_realloc(prev_memory, size, alignment); +#elif defined(_BH_LINUX) + retval = realloc(prev_memory, size); +#endif + bh_imap_put(&mh->ptrs, (u64) retval, 1); + } break; + + case bh_allocator_action_free: { + bh_imap_delete(&mh->ptrs, (u64) prev_memory); +#if defined(_BH_WINDOWS) + _aligned_free(prev_memory); +#elif defined(_BH_LINUX) + free(prev_memory); +#endif + } break; + } + + return retval; +} + + + + + + + + +// ARENA ALLOCATOR IMPLEMENTATION +void bh_arena_init(bh_arena* alloc, bh_allocator backing, isize arena_size) { + arena_size = bh_max(arena_size, size_of(ptr)); + ptr data = bh_alloc(backing, arena_size); + + alloc->backing = backing; + alloc->arena_size = arena_size; + alloc->size = sizeof(ptr); + alloc->first_arena = data; + alloc->current_arena = data; + + ((bh__arena_internal *)(alloc->first_arena))->next_arena = NULL; +} + +void bh_arena_free(bh_arena* alloc) { + bh__arena_internal *walker = (bh__arena_internal *) alloc->first_arena; + bh__arena_internal *trailer = walker; + while (walker != NULL) { + walker = walker->next_arena; + bh_free(alloc->backing, trailer); + trailer = walker; + } + + alloc->first_arena = NULL; + alloc->current_arena = NULL; + alloc->arena_size = 0; + alloc->size = 0; +} + +bh_allocator bh_arena_allocator(bh_arena* alloc) { + return (bh_allocator) { + .proc = bh_arena_allocator_proc, + .data = alloc, + }; +} + +BH_ALLOCATOR_PROC(bh_arena_allocator_proc) { + bh_arena* alloc_arena = (bh_arena*) data; + + ptr retval = NULL; + + switch (action) { + case bh_allocator_action_alloc: { + bh_align(size, alignment); + bh_align(alloc_arena->size, alignment); + + if (size > alloc_arena->arena_size - size_of(ptr)) { + // Size too large for the arena + return NULL; + } + + if (alloc_arena->size + size >= alloc_arena->arena_size) { + bh__arena_internal* new_arena = (bh__arena_internal *) bh_alloc(alloc_arena->backing, alloc_arena->arena_size); + + if (new_arena == NULL) { + bh_printf_err("Arena Allocator: couldn't allocate new arena"); + return NULL; + } + + new_arena->next_arena = NULL; + ((bh__arena_internal *)(alloc_arena->current_arena))->next_arena = new_arena; + alloc_arena->current_arena = new_arena; + alloc_arena->size = sizeof(ptr); + } + + retval = bh_pointer_add(alloc_arena->current_arena, alloc_arena->size); + alloc_arena->size += size; + } break; + + case bh_allocator_action_resize: { + // Do nothing since this is a fixed allocator + } break; + + case bh_allocator_action_free: { + // Do nothing since this allocator isn't made for freeing memory + } break; + } + + return retval; +} + + + + +// SCRATCH ALLOCATOR IMPLEMENTATION +void bh_scratch_init(bh_scratch* scratch, bh_allocator backing, isize scratch_size) { + ptr memory = bh_alloc(backing, scratch_size); + + scratch->backing = backing; + scratch->memory = memory; + scratch->curr = memory; + scratch->end = bh_pointer_add(memory, scratch_size); +} + +void bh_scratch_free(bh_scratch* scratch) { + bh_free(scratch->backing, scratch->memory); + + scratch->memory = NULL; + scratch->curr = NULL; + scratch->end = NULL; +} + +bh_allocator bh_scratch_allocator(bh_scratch* scratch) { + return (bh_allocator) { + .proc = bh_scratch_allocator_proc, + .data = scratch, + }; +} + +BH_ALLOCATOR_PROC(bh_scratch_allocator_proc) { + bh_scratch* scratch = (bh_scratch*) data; + ptr retval = NULL; + + switch (action) { + case bh_allocator_action_alloc: { + if (size > ((u8 *) scratch->end) - ((u8 *) scratch->memory)) { + return NULL; + } + + retval = scratch->curr; + scratch->curr = bh_pointer_add(scratch->curr, size); + + if (scratch->curr >= scratch->end) { + scratch->curr = scratch->memory; + retval = scratch->curr; + } + } break; + + case bh_allocator_action_free: break; + + case bh_allocator_action_resize: { + if (size > ((u8 *) scratch->end) - ((u8 *) scratch->memory)) { + return NULL; + } + + retval = scratch->curr; + scratch->curr = bh_pointer_add(scratch->curr, size); + + if (scratch->curr >= scratch->end) { + scratch->curr = scratch->memory; + retval = scratch->curr; + } + + // HACK!!!!!: Using size instead of some kind of "old size" + memcpy(retval, prev_memory, size); + } break; + } + + return retval; +} + + + + +//------------------------------------------------------------------------------------- +// CONVERSION FUNCTIONS IMPLEMENTATION +//------------------------------------------------------------------------------------- +u8* uint_to_uleb128(u64 n, i32* output_length) { + static u8 buffer[16]; + + *output_length = 0; + u8* output = buffer; + u8 byte; + do { + byte = n & 0x7f; + n >>= 7; + if (n != 0) byte |= (1 << 7); + *output++ = byte; + (*output_length)++; + } while (n != 0); + + return buffer; +} + + +// Converts a signed integer to the signed LEB128 format +u8* int_to_leb128(i64 n, i32* output_length) { + static u8 buffer[16]; + + *output_length = 0; + u8* output = buffer; + b32 more = 1; + + i32 size = 64; + + u8 byte; + do { + byte = n & 0x7f; + n >>= 7; + + more = !(((n == 0) && (byte & 0x40) == 0) || ((n == -1) && (byte & 0x40) != 0)); + if (more) + byte |= 0x80; + *output++ = byte; + (*output_length)++; + } while (more); + + return buffer; +} + +// NOTE: This assumes the underlying implementation of float on the host +// system is already IEEE-754. This is safe to assume in most cases. +u8* float_to_ieee754(f32 f, b32 reverse) { + static u8 buffer[4]; + + u8* fmem = (u8*) &f; + if (reverse) { + buffer[0] = fmem[3]; + buffer[1] = fmem[2]; + buffer[2] = fmem[1]; + buffer[3] = fmem[0]; + } else { + buffer[0] = fmem[0]; + buffer[1] = fmem[1]; + buffer[2] = fmem[2]; + buffer[3] = fmem[3]; + } + + return buffer; +} + +u8* double_to_ieee754(f64 f, b32 reverse) { + static u8 buffer[8]; + + u8* fmem = (u8*) &f; + if (reverse) { + buffer[0] = fmem[7]; + buffer[1] = fmem[6]; + buffer[2] = fmem[5]; + buffer[3] = fmem[4]; + buffer[4] = fmem[3]; + buffer[5] = fmem[2]; + buffer[6] = fmem[1]; + buffer[7] = fmem[0]; + } else { + buffer[0] = fmem[0]; + buffer[1] = fmem[1]; + buffer[2] = fmem[2]; + buffer[3] = fmem[3]; + buffer[4] = fmem[4]; + buffer[5] = fmem[5]; + buffer[6] = fmem[6]; + buffer[7] = fmem[7]; + } + + return buffer; +} + +u64 uleb128_to_uint(u8* bytes, i32 *byte_count) { + u64 res = 0; + u64 shift = 0; + + while (1) { + u8 byte = bytes[(*byte_count)++]; + res |= (byte & 0x7f) << shift; + if ((byte & 0x80) == 0) break; + shift += 7; + } + return res; +} + + + +//------------------------------------------------------------------------------------- +// STRING IMPLEMENTATION +//------------------------------------------------------------------------------------- +b32 bh_str_starts_with(char* str, char* start) { + char* s = str; + char* p = start; + + while (*s != '\0' && *p != '\0' && *s == *p) s++, p++; + + return *p == '\0'; +} + +b32 bh_str_ends_with(char* str, char* end) { + i32 slen = strlen(str); + i32 elen = strlen(end); + + char* s = str + slen - 1; + char* e = end + elen - 1; + + while (*e == *s && e != end && s != str) e--, s--; + + return *e == *s; +} + +char* bh_strdup(bh_allocator a, char* str) { + u32 len = strlen(str); + char* buf = bh_alloc(a, len + 1); + + char* t = buf; + while ((*t++ = *str++)); + return buf; +} + + + + + +//------------------------------------------------------------------------------------- +// FILE IMPLEMENTATION +//------------------------------------------------------------------------------------- +#ifndef BH_NO_FILE + +static b32 bh__file_seek_wrapper(bh_file_descriptor fd, i64 offset, bh_file_whence whence, i64* new_offset); + +bh_file_error bh_file_get_standard(bh_file* file, bh_file_standard stand) { + const char* filename = NULL; + +#if defined(_BH_WINDOWS) + bh_file_descriptor sd_fd; + + switch (stand) { + case BH_FILE_STANDARD_INPUT: + sd_fd = GetStdHandle(STD_INPUT_HANDLE); + filename = "stdin"; + break; + case BH_FILE_STANDARD_OUTPUT: + sd_fd = GetStdHandle(STD_OUTPUT_HANDLE); + filename = "stdout"; + break; + case BH_FILE_STANDARD_ERROR: + sd_fd = GetStdHandle(STD_ERROR_HANDLE); + filename = "stderr"; + break; + default: + return BH_FILE_ERROR_BAD_FD; + } + file->fd = sd_fd; + +#elif defined(_BH_LINUX) + i32 sd_fd = -1; + + switch (stand) { + case BH_FILE_STANDARD_INPUT: + sd_fd = STDIN_FILENO; + filename = "stdin"; // These are constants in the data section so everything should be okay + break; + case BH_FILE_STANDARD_OUTPUT: + sd_fd = STDOUT_FILENO; + filename = "stdout"; + break; + case BH_FILE_STANDARD_ERROR: + sd_fd = STDERR_FILENO; + filename = "stderr"; + break; + default: + return BH_FILE_ERROR_BAD_FD; + } + + file->fd = sd_fd; +#endif + + + file->filename = filename; + return BH_FILE_ERROR_NONE; +} + +bh_file_error bh_file_create(bh_file* file, const char* filename) { + // Need to do this to avoid compiler complaining about types + bh_file_mode write_rw = (bh_file_mode) (BH_FILE_MODE_WRITE | BH_FILE_MODE_RW); + return bh_file_open_mode(file, write_rw, filename); +} + +bh_file_error bh_file_open(bh_file* file, const char* filename) { + return bh_file_open_mode(file, BH_FILE_MODE_READ, filename); +} + +bh_file_error bh_file_open_mode(bh_file* file, bh_file_mode mode, const char* filename) { +#if defined(_BH_WINDOWS) + DWORD desired_access; + DWORD creation_disposition; + + switch (mode & BH_FILE_MODE_MODES) { + case BH_FILE_MODE_READ: + desired_access = GENERIC_READ; + creation_disposition = OPEN_EXISTING; + break; + case BH_FILE_MODE_WRITE: + desired_access = GENERIC_WRITE; + creation_disposition = CREATE_ALWAYS; + break; + case BH_FILE_MODE_APPEND: + desired_access = GENERIC_WRITE; + creation_disposition = OPEN_ALWAYS; + break; + case BH_FILE_MODE_READ | BH_FILE_MODE_RW: + desired_access = GENERIC_READ | GENERIC_WRITE; + creation_disposition = OPEN_EXISTING; + break; + case BH_FILE_MODE_WRITE | BH_FILE_MODE_RW: + desired_access = GENERIC_READ | GENERIC_WRITE; + creation_disposition = CREATE_ALWAYS; + break; + case BH_FILE_MODE_APPEND | BH_FILE_MODE_RW: + desired_access = GENERIC_READ | GENERIC_WRITE; + creation_disposition = OPEN_ALWAYS; + break; + default: + return BH_FILE_ERROR_INVALID; + } + + + file->fd = CreateFileA(filename, + desired_access, + FILE_SHARE_READ, + NULL, + creation_disposition, + FILE_ATTRIBUTE_NORMAL, + NULL); + + if (file->fd == INVALID_HANDLE_VALUE) { + return BH_FILE_ERROR_INVALID; + } + + file->filename = filename; + return BH_FILE_ERROR_NONE; + +#elif defined(_BH_LINUX) + i32 os_mode = 0; + + switch (mode & BH_FILE_MODE_MODES) { + case BH_FILE_MODE_READ: os_mode = O_RDONLY; break; + case BH_FILE_MODE_WRITE: os_mode = O_WRONLY | O_CREAT | O_TRUNC; break; + case BH_FILE_MODE_APPEND: os_mode = O_RDONLY | O_APPEND | O_CREAT; break; + case BH_FILE_MODE_READ | BH_FILE_MODE_RW: os_mode = O_RDWR; break; + case BH_FILE_MODE_WRITE | BH_FILE_MODE_RW: os_mode = O_RDWR | O_CREAT | O_TRUNC; break; + case BH_FILE_MODE_APPEND | BH_FILE_MODE_RW: os_mode = O_RDWR | O_APPEND | O_CREAT; break; + //default: // TODO Handle errors + } + + file->fd = open(filename, os_mode, + S_IRUSR | S_IWUSR | S_IROTH | S_IWOTH | S_IRGRP | S_IWGRP //+rw-rw-rw- + ); + if (file->fd < 0) { + return BH_FILE_ERROR_INVALID; + } + + // TODO: Set this using some allocator + file->filename = filename; + + return BH_FILE_ERROR_NONE; +#endif +} + +bh_file_error bh_file_new(bh_file* file, bh_file_descriptor fd, const char* filename) { + file->filename = filename; // This may be unsafe + file->fd = fd; + return BH_FILE_ERROR_NONE; +} + +b32 bh_file_read_at(bh_file* file, i64 offset, void* buffer, isize buff_size, isize* bytes_read) { +#if defined(_BH_WINDOWS) + bh_file_seek_to(file, offset); + BOOL res = ReadFile(file->fd, buffer, buff_size, (i32 *) bytes_read, NULL); + if (res) return 1; + else return 0; + +#elif defined(_BH_LINUX) + if (file->fd == 0) { + isize res = read(file->fd, buffer, buff_size); + if (res < 0) return 0; + if (bytes_read) *bytes_read = res; + return 1; + } + + isize res = pread(file->fd, buffer, buff_size, offset); + if (res < 0) return 0; + if (bytes_read) *bytes_read = res; + return 1; +#endif +} + +b32 bh_file_write_at(bh_file* file, i64 offset, void const* buffer, isize buff_size, isize* bytes_wrote) { + isize res; + i64 current_offset = 0; + bh__file_seek_wrapper(file->fd, 0, BH_FILE_WHENCE_CURRENT, ¤t_offset); + +#if defined(_BH_WINDOWS) + if (current_offset != offset) bh_file_seek_to(file, offset); + res = (isize) WriteFile(file->fd, buffer, buff_size, (i32 *) bytes_wrote, NULL); + return res; + +#elif defined(_BH_LINUX) + if (current_offset == offset || file->fd == 1 || file->fd == 2) { + // Standard in and out do like pwrite() + res = write(file->fd, buffer, buff_size); + } else { + res = pwrite(file->fd, buffer, buff_size, offset); + } + if (res < 0) return 0; + if (bytes_wrote) *bytes_wrote = res; + + return 1; +#endif +} + +static b32 bh__file_seek_wrapper(bh_file_descriptor fd, i64 offset, bh_file_whence whence, i64* new_offset) { +#if defined(_BH_WINDOWS) + LARGE_INTEGER new_file_pointer; + LARGE_INTEGER dest; + dest.QuadPart = offset; + + BOOL res = SetFilePointerEx(fd, dest, &new_file_pointer, whence); + *new_offset = new_file_pointer.QuadPart; + + return res; + +#elif defined(_BH_LINUX) + i64 res = lseek64(fd, offset, whence); + if (res < 0) return 0; + if (new_offset) *new_offset = res; + return 1; +#endif +} + +// Returns new offset +i64 bh_file_seek(bh_file* file, i64 offset, bh_file_whence whence) { + i64 new_offset = -1; + bh__file_seek_wrapper(file->fd, offset, whence, &new_offset); + return new_offset; +} + +i64 bh_file_seek_to(bh_file* file, i64 offset) { + i64 new_offset = -1; + bh__file_seek_wrapper(file->fd, offset, BH_FILE_WHENCE_BEGIN, &new_offset); + return new_offset; +} + +i64 bh_file_seek_to_end(bh_file* file) { + i64 new_offset = -1; + bh__file_seek_wrapper(file->fd, 0, BH_FILE_WHENCE_END, &new_offset); + return new_offset; +} + +i64 bh_file_skip(bh_file* file, i64 bytes) { + i64 new_offset = 0; + bh__file_seek_wrapper(file->fd, bytes, BH_FILE_WHENCE_CURRENT, &new_offset); + return new_offset; +} + +i64 bh_file_tell(bh_file* file) { + i64 new_offset = 0; + bh__file_seek_wrapper(file->fd, 0, BH_FILE_WHENCE_CURRENT, &new_offset); + return new_offset; +} + +bh_file_error bh_file_close(bh_file* file) { + bh_file_error err = BH_FILE_ERROR_NONE; + +#if defined(_BH_WINDOWS) + BOOL success = CloseHandle(file->fd); + if (!success) err = BH_FILE_ERROR_INVALID; + + return err; + +#elif defined(_BH_LINUX) + i32 res = close(file->fd); + if (res < 0) + err = BH_FILE_ERROR_INVALID; + + return err; +#endif +} + +b32 bh_file_read(bh_file* file, void* buffer, isize buff_size) { + return bh_file_read_at(file, bh_file_tell(file), buffer, buff_size, NULL); +} + +b32 bh_file_write(bh_file* file, void* buffer, isize buff_size) { + return bh_file_write_at(file, bh_file_tell(file), buffer, buff_size, NULL); +} + +void bh_file_flush(bh_file* file) { + #ifdef _BH_LINUX + fdatasync(file->fd); + #endif +} + +i64 bh_file_size(bh_file* file) { + i64 size = 0; + i64 prev = bh_file_tell(file); + bh_file_seek_to_end(file); + size = bh_file_tell(file); + bh_file_seek_to(file, prev); + return size; +} + +bh_file_contents bh_file_read_contents_bh_file(bh_allocator alloc, bh_file* file) { + bh_file_contents fc = { + .allocator = alloc, + .filename = bh_strdup(alloc, (char *) file->filename), + .length = 0, .data = NULL + }; + + isize size = bh_file_size(file); + if (size <= 0) return fc; + + fc.data = bh_alloc(alloc, size + 1); + fc.length = size; + bh_file_read_at(file, 0, fc.data, fc.length, NULL); + ((u8*) fc.data)[fc.length] = '\0'; + + return fc; +} + +bh_file_contents bh_file_read_contents_direct(bh_allocator alloc, const char* filename) { + bh_file file; + bh_file_open(&file, filename); + bh_file_contents fc = bh_file_read_contents(alloc, &file); + bh_file_close(&file); + return fc; +} + +b32 bh_file_contents_free(bh_file_contents* contents) { + bh_free(contents->allocator, contents->data); + contents->length = 0; + return 1; +} + +b32 bh_file_exists(char const* filename) { + struct stat s; + return stat(filename, &s) != -1; +} + +char* bh_path_get_full_name(char const* filename, bh_allocator a) { +#if defined(_BH_WINDOWS) + char buffer[4096]; + GetFullPathNameA(filename, 4096, buffer, NULL); + + i32 len = strlen(buffer); + char* result = bh_alloc_array(a, char, len + 1); + memmove(result, buffer, len); + result[len] = 0; + + return result; + +#elif defined(_BH_LINUX) + char* p = realpath(filename, NULL); + + // Check if the file did not exists. + // :Cleanup should this return NULL? + if (p == NULL) return (char *) filename; + + i32 len = strlen(p); + char* result = bh_alloc_array(a, char, len + 1); + memmove(result, p, len); + result[len] = 0; + + free(p); + + return result; +#endif +} + +// NOTE: This assumes the filename is the full path, not relative to anything else. +#if defined(_BH_LINUX) + #define DIR_SEPARATOR '/' +#elif defined(_BH_WINDOWS) + #define DIR_SEPARATOR '\\' +#endif +char* bh_path_get_parent(char const* filename, bh_allocator a) { + + char* result = bh_strdup(a, (char *) filename); + char* end = result + strlen(result); + while (*end != DIR_SEPARATOR && end != result) *end-- = '\0'; + + return result; +} + +// This function returns a volatile pointer. Do not store it without copying! +char* bh_lookup_file(char* filename, char* relative_to, char *suffix, b32 add_suffix, bh_arr(const char *) included_folders, b32 search_included_folders) { + assert(relative_to != NULL); + + static char path[512]; + fori (i, 0, 512) path[i] = 0; + + static char fn[256]; + fori (i, 0, 256) fn[i] = 0; + + if (!bh_str_ends_with(filename, suffix) && add_suffix) { + bh_snprintf(fn, 256, "%s%s", filename, suffix); + } else { + bh_snprintf(fn, 256, "%s", filename); + } + + fori (i, 0, 256) if (fn[i] == '/') fn[i] = DIR_SEPARATOR; + + if (bh_str_starts_with(filename, "./")) { + if (relative_to[strlen(relative_to) - 1] != DIR_SEPARATOR) + bh_snprintf(path, 512, "%s%c%s", relative_to, DIR_SEPARATOR, fn + 2); + else + bh_snprintf(path, 512, "%s%s", relative_to, fn + 2); + + if (bh_file_exists(path)) return bh_path_get_full_name(path, bh_heap_allocator()); + + return fn; + } + + if (search_included_folders) { + bh_arr_each(const char *, folder, included_folders) { + if ((*folder)[strlen(*folder) - 1] != DIR_SEPARATOR) + bh_snprintf(path, 512, "%s%c%s", *folder, DIR_SEPARATOR, fn); + else + bh_snprintf(path, 512, "%s%s", *folder, fn); + + if (bh_file_exists(path)) return bh_path_get_full_name(path, bh_heap_allocator()); + } + } + + return fn; +} + +// +// Modifies the path in-place. +char* bh_path_convert_separators(char* path) { +#if defined(_BH_LINUX) + #define DIR_SEPARATOR '/' + #define OTHER_SEPARATOR '\\' +#elif defined(_BH_WINDOWS) + #define DIR_SEPARATOR '\\' + #define OTHER_SEPARATOR '/' +#endif + + fori (i, 0, (i64) strlen(path)) { + if (path[i] == OTHER_SEPARATOR) { + path[i] = DIR_SEPARATOR; + } + } + + return path; +} + + +bh_dir bh_dir_open(char* path) { +#ifdef _BH_WINDOWS + char new_path[512] = { 0 }; + strncpy(new_path, path, 511); + bh_path_convert_separators(new_path); + strncat(new_path, "\\*.*", 511); + + Windows_Directory_Opened* dir = malloc(sizeof(Windows_Directory_Opened)); + dir->hndl = FindFirstFileA(new_path, &dir->found_file); + if (dir->hndl == INVALID_HANDLE_VALUE) { + return NULL; + } + + return dir; +#endif + +#ifdef _BH_LINUX + DIR* dir = opendir(path); + return dir; +#endif +} + +b32 bh_dir_read(bh_dir dir, bh_dirent* out) { + +#ifdef _BH_WINDOWS + do { + BOOL success = FindNextFileA(dir->hndl, &dir->found_file); + if (!success) return 0; + } while (!strcmp(dir->found_file.cFileName, ".") || !strcmp(dir->found_file.cFileName, "..")); + + if (out == NULL) return 1; + + out->type = (dir->found_file.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + ? BH_DIRENT_DIRECTORY : BH_DIRENT_FILE; + out->id = 0; + out->name_length = strlen(dir->found_file.cFileName); + strncpy(out->name, dir->found_file.cFileName, 256); + + return 1; +#endif + +#ifdef _BH_LINUX + struct dirent *ent; + while (1) { + ent = readdir(dir); + if (ent == NULL) return 0; + + // Skip the current directory and parent directory + if (strcmp(ent->d_name, ".") && strcmp(ent->d_name, "..")) break; + } + + bh_dirent_type type = 0; + switch (ent->d_type) { + case DT_UNKNOWN: break; + case DT_BLK: type = BH_DIRENT_BLOCK; break; + case DT_CHR: type = BH_DIRENT_CHAR; break; + case DT_DIR: type = BH_DIRENT_DIRECTORY; break; + case DT_LNK: type = BH_DIRENT_SYMLINK; break; + case DT_REG: type = BH_DIRENT_FILE; break; + default: type = BH_DIRENT_OTHER; break; + } + + if (out == NULL) return 1; + + out->type = type; + out->id = (u32) ent->d_ino; + out->name_length = strlen(ent->d_name); + strncpy(out->name, ent->d_name, 256); + + return 1; +#endif +} + +void bh_dir_close(bh_dir dir) { +#ifdef _BH_WINDOWS + if (dir == NULL) return; + + FindClose(dir->hndl); + free(dir); +#endif + +#ifdef _BH_LINUX + if (dir == NULL) return; + closedir(dir); +#endif +} + +#undef DIR_SEPARATOR + +#endif // ifndef BH_NO_FILE + + + + + + + + + + + + + + + + + + +//------------------------------------------------------------------------------------- +// ALTERNATE PRINTF IMPLEMENTATION +//------------------------------------------------------------------------------------- +isize bh_printf(char const *fmt, ...) { + isize res; + va_list va; + va_start(va, fmt); + res = bh_printf_va(fmt, va); + va_end(va); + return res; +} + +isize bh_printf_va(char const *fmt, va_list va) { + bh_file file; + bh_file_get_standard(&file, BH_FILE_STANDARD_OUTPUT); + return bh_fprintf_va(&file, fmt, va); +} + +isize bh_printf_err(char const *fmt, ...) { + isize res; + va_list va; + va_start(va, fmt); + res = bh_printf_err_va(fmt, va); + va_end(va); + return res; +} + +isize bh_printf_err_va(char const *fmt, va_list va) { + bh_file file; + bh_file_get_standard(&file, BH_FILE_STANDARD_ERROR); + return bh_fprintf_va(&file, fmt, va); +} + +isize bh_fprintf(bh_file* f, char const *fmt, ...) { + isize res; + va_list va; + va_start(va, fmt); + res = bh_fprintf_va(f, fmt, va); + va_end(va); + return res; +} + +isize bh_fprintf_va(bh_file* f, char const *fmt, va_list va) { + static char buffer[4096]; + isize len = bh_snprintf_va(buffer, sizeof(buffer), fmt, va); + bh_file_write(f, buffer, len - 1); + return len; +} + +char* bh_bprintf(char const *fmt, ...) { + char* res; + va_list va; + va_start(va, fmt); + res = bh_bprintf_va(fmt, va); + va_end(va); + return res; +} + +char* bh_bprintf_va(char const *fmt, va_list va) { + static char buffer[4096]; + isize len = bh_snprintf_va(buffer, sizeof(buffer), fmt, va); + buffer[len - 1] = 0; + return buffer; +} + +char* bh_aprintf(bh_allocator alloc, const char* fmt, ...) { + char* res; + va_list va; + va_start(va, fmt); + res = bh_aprintf_va(alloc, fmt, va); + va_end(va); + return res; +} + +char* bh_aprintf_va(bh_allocator alloc, const char* fmt, va_list va) { + static char buffer[4096]; + isize len = bh_snprintf_va(buffer, sizeof(buffer), fmt, va); + char* res = bh_alloc(alloc, len); + memcpy(res, buffer, len); + res[len - 1] = 0; + return res; +} + +isize bh_snprintf(char *str, isize n, char const *fmt, ...) { + isize res; + va_list va; + va_start(va, fmt); + res = bh_snprintf_va(str, n, fmt, va); + va_end(va); + return res; +} + +isize bh__print_string(char* dest, isize n, char* src) { + isize len = 0; + while (n-- && (*dest++ = *src++)) len++; + return len; +} + +isize bh__printu64(char* str, isize n, bh__print_format format, u64 value) { + char buf[128]; + buf[127] = 0; + char* walker = buf + 127; + u32 base = format.base ? format.base : 10, tmp; + + while (value > 0) { + tmp = value % base; + if (tmp > 9) { + switch (tmp) { + case 10: tmp = 'a'; break; + case 11: tmp = 'b'; break; + case 12: tmp = 'c'; break; + case 13: tmp = 'd'; break; + case 14: tmp = 'e'; break; + case 15: tmp = 'f'; break; + } + } else { + tmp += '0'; + } + + *--walker = tmp; + value /= base; + } + + if (format.base == 16) { + *--walker = 'x'; + *--walker = '0'; + } + + return bh__print_string(str, n, walker); +} + +isize bh__printi64(char* str, isize n, bh__print_format format, i64 value) { + char buf[128]; + buf[127] = 0; + char* walker = buf + 127; + u32 base = format.base ? format.base : 10, tmp; + + b32 negative = value < 0 ? 1 : 0; + if (negative) value = -value; + + if (value == 0) { + *--walker = '0'; + } else { + while (value > 0) { + tmp = value % base; + if (tmp > 9) { + switch (tmp) { + case 10: tmp = 'a'; break; + case 11: tmp = 'b'; break; + case 12: tmp = 'c'; break; + case 13: tmp = 'd'; break; + case 14: tmp = 'e'; break; + case 15: tmp = 'f'; break; + } + } else { + tmp += '0'; + } + + *--walker = tmp; + value /= base; + } + } + + if (negative) { + *--walker = '-'; + } + + if (format.base == 16) { + *--walker = 'x'; + *--walker = '0'; + } + + return bh__print_string(str, n, walker); +} + +// TODO: This implementation is VERY VERY BAD AND WRONG. Fix it. +isize bh__printf64(char* str, isize n, f64 value) { + fori (i, 0, 6) value *= 10.0; + i64 v = (i64) value; + + isize l1 = bh__printi64(str, n, ((bh__print_format) { .base = 10 }), v / 1000000); + str += l1; + n -= l1; + + *str = '.'; + str += 1; + n -= 1; + + isize l2 = bh__printi64(str, n, ((bh__print_format) { .base = 10 }), bh_abs(v) % 1000000); + + return l1 + l2 + 1; +} + +// TODO: This is very hacked together but for now it will work. +isize bh_snprintf_va(char *str, isize n, char const *fmt, va_list va) { + char const *text_start = str; + isize res; + + while (*fmt) { + bh__print_format format = { 0 }; + isize len = 0; + + while (*fmt && *fmt != '%') { + *(str++) = *(fmt++); + } + + if (!*fmt) goto end_of_format; + + fmt++; + + switch (*fmt++) { + case 'o': format.base = 8; break; + case 'x': format.base = 16; break; + default: fmt--; + } + + switch (*fmt) { + case 'c': { + char c = (char) va_arg(va, int); + *(str++) = c; + } break; + + case 'd': { + i64 value = (i64) va_arg(va, int); + len = bh__printi64(str, n, format, value); + } break; + + case 'l': { + i64 value = (i64) va_arg(va, long); + len = bh__printi64(str, n, format, value); + } break; + + case 'p': { + u64 value = (u64) va_arg(va, ptr); + format.base = 16; + len = bh__printu64(str, n, format, value); + } break; + + case 's': { + char* s = va_arg(va, char *); + len = bh__print_string(str, n, s); + } break; + + case 'b': { // String with a length (not null terminated) + char* s = va_arg(va, char *); + i32 l = va_arg(va, int); + len = bh__print_string(str, bh_min(l, n), s); + } break; + + case 'f': { + f64 f = va_arg(va, f64); + len = bh__printf64(str, n, f); + } break; + + default: fmt--; + } + + fmt++; + +end_of_format: + str += len; + n -= len; + } + + return str - text_start + 1; +} + + + + + +//------------------------------------------------------------------------------------- +// FLEXIBLE BUFFER IMPLEMENTATION +//------------------------------------------------------------------------------------- +#ifndef BH_NO_BUFFER + +void bh_buffer_init(bh_buffer* buffer, bh_allocator alloc, i32 init_size) { + buffer->allocator = alloc; + buffer->length = 0; + buffer->capacity = init_size; + buffer->data = bh_alloc(alloc, init_size); +} + +void bh_buffer_free(bh_buffer* buffer) { + bh_free(buffer->allocator, buffer->data); + buffer->length = 0; + buffer->capacity = 0; +} + +void bh_buffer_grow(bh_buffer* buffer, i32 length) { + if (buffer == NULL) return; + + if (buffer->capacity >= length) { + // NOTE: Already have enough room + return; + } + + i32 newcap = buffer->capacity; + while (newcap < length) newcap = BH_BUFFER_GROW_FORMULA(newcap); + + ptr new_data = bh_resize(buffer->allocator, buffer->data, newcap); + if (new_data == NULL) return; + + buffer->capacity = newcap; + buffer->data = new_data; +} + +void bh_buffer_append(bh_buffer* buffer, const void * data, i32 length) { + if (buffer == NULL) return; + + if (buffer->length + length > buffer->capacity) { + bh_buffer_grow(buffer, buffer->length + length); + } + + memcpy(buffer->data + buffer->length, data, length); + buffer->length += length; +} + +void bh_buffer_concat(bh_buffer* buffer, bh_buffer other) { + bh_buffer_append(buffer, other.data, other.length); +} + +void bh_buffer_write_byte(bh_buffer* buffer, u8 byte) { + bh_buffer_grow(buffer, buffer->length + 1); + buffer->data[buffer->length++] = byte; +} + +void bh_buffer_write_u32(bh_buffer* buffer, u32 i) { + bh_buffer_grow(buffer, buffer->length + 4); + *((u32 *) bh_pointer_add(buffer->data, buffer->length)) = i; + buffer->length += 4; +} + +void bh_buffer_write_u64(bh_buffer* buffer, u64 i) { + bh_buffer_grow(buffer, buffer->length + 8); + *((u64 *) bh_pointer_add(buffer->data, buffer->length)) = i; + buffer->length += 8; +} + +void bh_buffer_align(bh_buffer* buffer, u32 alignment) { + if (buffer->length % alignment != 0) { + u32 difference = alignment - (buffer->length % alignment); + buffer->length += difference; + + bh_buffer_grow(buffer, buffer->length); + } +} + + +#endif + + + + + + + + + + + + + + + + + + + + + +//------------------------------------------------------------------------------------- +// ARRAY IMPLEMENTATION +//------------------------------------------------------------------------------------- +#ifndef BH_NO_ARRAY + +b32 bh__arr_grow(bh_allocator alloc, void** arr, i32 elemsize, i32 cap) { + bh__arr* arrptr; + + if (*arr == NULL) { + if (cap == 0 && elemsize == 0) return 1; + + arrptr = (bh__arr *) bh_alloc(alloc, sizeof(*arrptr) + elemsize * cap); + if (arrptr == NULL) return 0; + + arrptr->allocator = alloc; + arrptr->capacity = cap; + arrptr->length = 0; + + } else { + arrptr = bh__arrhead(*arr); + + if (arrptr->capacity < cap) { + void* p; + i32 newcap = arrptr->capacity; + while (newcap < cap) newcap = BH_ARR_GROW_FORMULA(newcap); + + p = bh_resize(arrptr->allocator, arrptr, sizeof(*arrptr) + elemsize * newcap); + + if (p) { + arrptr = (bh__arr *) p; + arrptr->capacity = newcap; + } else { + return 0; + } + } + } + + *arr = arrptr + 1; + return 1; +} + +b32 bh__arr_shrink(void** arr, i32 elemsize, i32 cap) { + if (*arr == NULL) return 0; + + bh__arr* arrptr = bh__arrhead(*arr); + cap = bh_max(cap, arrptr->length); + + if (arrptr->capacity > cap) { + void* p = bh_resize(arrptr->allocator, arrptr, sizeof(*arrptr) + elemsize * cap); + + if (p) { + arrptr = (bh__arr *) p; + arrptr->capacity = cap; + } else { + return 0; + } + } + + *arr = arrptr + 1; + return 1; +} + +b32 bh__arr_free(void **arr) { + if (*arr == NULL) return 0; + + bh__arr* arrptr = bh__arrhead(*arr); + bh_free(arrptr->allocator, arrptr); + *arr = NULL; + return 1; +} + +void* bh__arr_copy(bh_allocator alloc, void *arr, i32 elemsize) { + bh__arr* arrptr = bh__arrhead(arr); + + const i32 cap = arrptr->length; + + void* newarr = NULL; + bh__arr_grow(alloc, &newarr, elemsize, cap); + bh__arrhead(newarr)->length = cap; + bh__arrhead(newarr)->capacity = cap; + memcpy(newarr, arr, elemsize * arrptr->length); + + return newarr; +} + +void bh__arr_deleten(void **arr, i32 elemsize, i32 index, i32 numelems) { + bh__arr* arrptr = bh__arrhead(*arr); + + if (index >= arrptr->length) return; // Can't delete past the end of the array + if (numelems <= 0) return; // Can't delete nothing + + memmove( + (char *)(*arr) + elemsize * index, // Target + (char *)(*arr) + elemsize * (index + numelems), // Source + elemsize * (arrptr->length - (index + numelems))); // Length + arrptr->length -= numelems; +} + +void bh__arr_insertn(void **arr, i32 elemsize, i32 index, i32 numelems) { + if (numelems) { + if (*arr == NULL) { + bh__arr_grow(bh_arr_allocator(arr), arr, elemsize, numelems); // Making a new array + return; + } + + bh__arr* arrptr = bh__arrhead(*arr); + if (!bh__arr_grow(bh_arr_allocator(arr), arr, elemsize, arrptr->length + numelems)) return; // Fail case + arrptr = bh__arrhead(*arr); + memmove( + (char *)(*arr) + elemsize * (index + numelems), + (char *)(*arr) + elemsize * index, + elemsize * (arrptr->length - index)); + arrptr->length += numelems; + } +} + +#endif // ifndef BH_NO_ARRAY + + + + + + + + + + + + + + + +//------------------------------------------------------------------------------------- +// TABLE IMPLEMENTATION +//------------------------------------------------------------------------------------- +#ifndef BH_NO_TABLE + +b32 bh__table_init(bh_allocator allocator, bh__table **table, i32 table_size) { + *table = bh_alloc(allocator, sizeof(bh__table) + sizeof(ptr) * table_size); + if (*table == NULL) return 0; + + (*table)->allocator = allocator; + (*table)->table_size = table_size; + + for (i32 i = 0; i < table_size; i++) { + (*table)->arrs[i] = NULL; + } + + return 1; +} + +b32 bh__table_free(bh__table **table) { + if (*table == NULL) return 0; + + for (u64 i = 0; i < (*table)->table_size; i++) { + if ((*table)->arrs[i] != NULL) { + bh_arr_free((*table)->arrs[i]); + } + } + + bh_free((*table)->allocator, *table); + *table = NULL; + return 1; +} + +// Assumes NULL terminated string for key +ptr bh__table_put(bh__table *table, i32 elemsize, char *key) { + elemsize += (elemsize & 1); + + u64 index = bh__table_hash_function(key, 0, table->table_size); + u8 arr_was_new = 0; + + ptr arrptr = table->arrs[index]; + if (arrptr == NULL) { + arr_was_new = 1; + goto add_new_element; + } + u64 len = *(u64 *) arrptr; + arrptr = bh_pointer_add(arrptr, sizeof(u64)); + + u16 key_length = 0; + while (len--) { + arrptr = bh_pointer_add(arrptr, elemsize); + key_length = *(u16 *) arrptr; + arrptr = bh_pointer_add(arrptr, sizeof(u16)); + if (strncmp(key, (char *) arrptr, key_length) == 0) goto found_matching; + arrptr = bh_pointer_add(arrptr, key_length); + } + +add_new_element: + arrptr = table->arrs[index]; + i32 byte_len = bh_arr_length(arrptr); + if (byte_len == 0) byte_len = sizeof(u64); + key_length = strlen(key) + 1; + + // NOTE: Align to 16 bytes + if ((key_length + 2) % 16 != 0) { + key_length = ((((key_length + 2) >> 4) + 1) << 4) - 2; + } + + bh__arr_grow(table->allocator, &arrptr, 1, byte_len + elemsize + sizeof(u16) + key_length); + bh__arrhead(arrptr)->length = byte_len + elemsize + sizeof(u16) + key_length; + table->arrs[index] = arrptr; + + if (arr_was_new) { + *(u64 *) arrptr = 1; + } else { + (*(u64 *) arrptr)++; + } + + arrptr = bh_pointer_add(arrptr, byte_len + elemsize); + *(u16 *) arrptr = key_length; + arrptr = bh_pointer_add(arrptr, sizeof(u16)); + strncpy(arrptr, key, key_length); + +found_matching: + return bh_pointer_add(arrptr, -(sizeof(u16) + elemsize)); +} + +b32 bh__table_has(bh__table *table, i32 elemsize, char *key) { + elemsize += (elemsize & 1); + + u64 index = bh__table_hash_function(key, 0, table->table_size); + + ptr arrptr = table->arrs[index]; + if (arrptr == NULL) return 0; + + u64 len = *(u64 *) arrptr; + arrptr = bh_pointer_add(arrptr, sizeof(u64)); + + u16 key_length = 0; + while (len--) { + arrptr = bh_pointer_add(arrptr, elemsize); + key_length = *(u16 *) arrptr; + arrptr = bh_pointer_add(arrptr, sizeof(u16)); + if (strncmp(key, (char *) arrptr, key_length) == 0) return 1; + arrptr = bh_pointer_add(arrptr, key_length); + } + + return 0; +} + +ptr bh__table_get(bh__table *table, i32 elemsize, char *key) { + elemsize += (elemsize & 1); + + u64 index = bh__table_hash_function(key, 0, table->table_size); + + ptr arrptr = table->arrs[index]; + if (arrptr == NULL) return 0; + + u64 len = *(u64 *) arrptr; + arrptr = bh_pointer_add(arrptr, sizeof(u64)); + + u16 key_length = 0; + while (len--) { + arrptr = bh_pointer_add(arrptr, elemsize); + key_length = *(u16 *) arrptr; + arrptr = bh_pointer_add(arrptr, sizeof(u16)); + if (strncmp(key, (char *) arrptr, key_length) == 0) { + return bh_pointer_add(arrptr, -(sizeof(u16) + elemsize)); + } + arrptr = bh_pointer_add(arrptr, key_length); + } + + return NULL; +} + +void bh__table_delete(bh__table *table, i32 elemsize, char *key) { + elemsize += (elemsize & 1); + + u64 index = bh__table_hash_function(key, 0, table->table_size); + + ptr arrptr = table->arrs[index], walker; + if (arrptr == NULL) return; // Didn't exist + walker = arrptr; + + i32 byte_offset = 8; + i32 delete_len = 0; + + u64 len = *(u64 *) walker; + walker = bh_pointer_add(walker, sizeof(u64)); + + u16 key_length = 0; + while (len--) { + walker = bh_pointer_add(walker, elemsize); + key_length = *(u16 *) walker; + walker = bh_pointer_add(walker, sizeof(u16)); + if (strncmp(key, (char *) walker, key_length) == 0) { + delete_len = elemsize + sizeof(u16) + key_length; + goto found_matching; + } + walker = bh_pointer_add(walker, key_length); + byte_offset += elemsize + sizeof(u16) + key_length; + } + + // NOTE: Already didn't exist + return; + +found_matching: + bh__arr_deleten((void **) &arrptr, 1, byte_offset, delete_len); + table->arrs[index] = arrptr; + (*(u64 *) arrptr)--; +} + +void bh__table_clear(bh__table *table) { + for (u64 i = 0; i < table->table_size; i++) { + if (table->arrs[i] != NULL) { + // NOTE: Set length property to 0 + *((u64 *) table->arrs[i]) = 0; + bh_arr_set_length(table->arrs[i], 0); + } + } +} + +bh_table_iterator bh__table_iter_setup(bh__table *table, i32 elemsize) { + elemsize += (elemsize & 1); + + bh_table_iterator it = { + .tab = table->arrs, + .endtab = table->arrs + table->table_size, + .elemsize = elemsize, + .entry = NULL + }; + return it; +} + +b32 bh_table_iter_next(bh_table_iterator* it) { + if (it->tab == NULL) return 0; + + if (it->entry != NULL) { + it->arrlen--; + if (it->arrlen <= 0) { + it->tab++; + goto step_to_next; + } + + it->entry = bh_pointer_add(it->entry, it->elemsize); + it->entry = bh_pointer_add(it->entry, sizeof(u16) + (*(u16 *) it->entry)); + return 1; + } + +step_to_next: + // Step forward to find next valid + while (it->tab != it->endtab && *it->tab == NULL) { + it->tab++; + } + + if (it->tab == it->endtab) return 0; + + it->entry = *it->tab; + it->arrlen = *(u64 *) it->entry; + it->entry = bh_pointer_add(it->entry, sizeof(u64)); + if (it->arrlen <= 0) { + it->tab++; + goto step_to_next; + } + return 1; +} + +#endif // ifndef BH_NO_HASHTABLE + + + +//------------------------------------------------------------------------------------- +// IMAP IMPLEMENTATION +//------------------------------------------------------------------------------------- +#ifndef BH_NO_IMAP +void bh_imap_init(bh_imap* imap, bh_allocator alloc, i32 hash_count) { + imap->allocator = alloc; + + imap->hashes = NULL; + imap->entries = NULL; + + bh_arr_new(alloc, imap->hashes, hash_count); + bh_arr_new(alloc, imap->entries, 4); + + fori(count, 0, hash_count) bh_arr_push(imap->hashes, -1); +} + +void bh_imap_free(bh_imap* imap) { + bh_arr_free(imap->hashes); + bh_arr_free(imap->entries); + + imap->hashes = NULL; + imap->entries = NULL; +} + +bh__imap_lookup_result bh__imap_lookup(bh_imap* imap, bh_imap_entry_t key) { + bh__imap_lookup_result lr = { -1, -1, -1 }; + + u64 hash = 0xcbf29ce484222325ull ^ key; + u64 n = bh_arr_capacity(imap->hashes); + + lr.hash_index = hash % n; + lr.entry_index = imap->hashes[lr.hash_index]; + while (lr.entry_index >= 0) { + if (imap->entries[lr.entry_index].key == key) { + return lr; + } + + lr.entry_prev = lr.entry_index; + lr.entry_index = imap->entries[lr.entry_index].next; + } + + return lr; +} + +void bh_imap_put(bh_imap* imap, bh_imap_entry_t key, bh_imap_entry_t value) { + bh__imap_lookup_result lr = bh__imap_lookup(imap, key); + + if (lr.entry_index >= 0) { + imap->entries[lr.entry_index].value = value; + return; + } + + bh__imap_entry entry; + entry.key = key; + entry.value = value; + entry.next = imap->hashes[lr.hash_index]; + bh_arr_push(imap->entries, entry); + + imap->hashes[lr.hash_index] = bh_arr_length(imap->entries) - 1; +} + +b32 bh_imap_has(bh_imap* imap, bh_imap_entry_t key) { + bh__imap_lookup_result lr = bh__imap_lookup(imap, key); + return lr.entry_index >= 0; +} + +bh_imap_entry_t bh_imap_get(bh_imap* imap, bh_imap_entry_t key) { + bh__imap_lookup_result lr = bh__imap_lookup(imap, key); + if (lr.entry_index >= 0) { + return imap->entries[lr.entry_index].value; + } else { + return 0; + } +} + +void bh_imap_delete(bh_imap* imap, bh_imap_entry_t key) { + bh__imap_lookup_result lr = bh__imap_lookup(imap, key); + if (lr.entry_index < 0) return; + + if (lr.entry_prev < 0) { + imap->hashes[lr.hash_index] = imap->entries[lr.entry_index].next; + } else { + imap->entries[lr.entry_prev].next = imap->entries[lr.entry_index].next; + } + + // If it's that last thing in the array, just pop off the end + if (lr.entry_index == bh_arr_length(imap->entries) - 1) { + bh_arr_pop(imap->entries); + return; + } + + bh_arr_fastdelete(imap->entries, lr.entry_index); + bh__imap_lookup_result last = bh__imap_lookup(imap, imap->entries[lr.entry_index].key); + if (last.entry_prev >= 0) { + imap->entries[last.entry_prev].next = lr.entry_index; + } else { + imap->hashes[last.hash_index] = lr.entry_index; + } +} + +void bh_imap_clear(bh_imap* imap) { + // NOTE: Does not clear out an of the data that was in the map + bh_arr_each(i64, hash, imap->hashes) *hash = -1; + bh_arr_set_length(imap->entries, 0); +} + +#endif // ifndef BH_NO_IMAP + + + + + +u64 bh_time_curr() { +#if defined(_BH_WINDOWS) + LARGE_INTEGER result; + QueryPerformanceCounter(&result); + return (u64) result.QuadPart; + +#elif defined(_BH_LINUX) + struct timespec spec; + clock_gettime(CLOCK_REALTIME, &spec); + + time_t sec = spec.tv_sec; + u64 ms = spec.tv_nsec / 1000000; + if (ms > 999) { + sec++; + ms = 0; + } + + return sec * 1000 + ms; +#endif +} + +u64 bh_time_duration(u64 old) { +#if defined(_BH_WINDOWS) + u64 curr = bh_time_curr(); + u64 duration = curr - old; + + LARGE_INTEGER freq; + QueryPerformanceFrequency(&freq); + duration *= 1000; + duration /= freq.QuadPart; + return duration; + +#elif defined(_BH_LINUX) + u64 curr = bh_time_curr(); + return curr - old; +#endif +} + +#endif // ifdef BH_DEFINE + +#endif // ifndef BH_H diff --git a/include/stb_ds.h b/include/stb_ds.h new file mode 100644 index 0000000..e84c82d --- /dev/null +++ b/include/stb_ds.h @@ -0,0 +1,1895 @@ +/* stb_ds.h - v0.67 - public domain data structures - Sean Barrett 2019 + + This is a single-header-file library that provides easy-to-use + dynamic arrays and hash tables for C (also works in C++). + + For a gentle introduction: + http://nothings.org/stb_ds + + To use this library, do this in *one* C or C++ file: + #define STB_DS_IMPLEMENTATION + #include "stb_ds.h" + +TABLE OF CONTENTS + + Table of Contents + Compile-time options + License + Documentation + Notes + Notes - Dynamic arrays + Notes - Hash maps + Credits + +COMPILE-TIME OPTIONS + + #define STBDS_NO_SHORT_NAMES + + This flag needs to be set globally. + + By default stb_ds exposes shorter function names that are not qualified + with the "stbds_" prefix. If these names conflict with the names in your + code, define this flag. + + #define STBDS_SIPHASH_2_4 + + This flag only needs to be set in the file containing #define STB_DS_IMPLEMENTATION. + + By default stb_ds.h hashes using a weaker variant of SipHash and a custom hash for + 4- and 8-byte keys. On 64-bit platforms, you can define the above flag to force + stb_ds.h to use specification-compliant SipHash-2-4 for all keys. Doing so makes + hash table insertion about 20% slower on 4- and 8-byte keys, 5% slower on + 64-byte keys, and 10% slower on 256-byte keys on my test computer. + + #define STBDS_REALLOC(context,ptr,size) better_realloc + #define STBDS_FREE(context,ptr) better_free + + These defines only need to be set in the file containing #define STB_DS_IMPLEMENTATION. + + By default stb_ds uses stdlib realloc() and free() for memory management. You can + substitute your own functions instead by defining these symbols. You must either + define both, or neither. Note that at the moment, 'context' will always be NULL. + @TODO add an array/hash initialization function that takes a memory context pointer. + + #define STBDS_UNIT_TESTS + + Defines a function stbds_unit_tests() that checks the functioning of the data structures. + + Note that on older versions of gcc (e.g. 5.x.x) you may need to build with '-std=c++0x' + (or equivalentally '-std=c++11') when using anonymous structures as seen on the web + page or in STBDS_UNIT_TESTS. + +LICENSE + + Placed in the public domain and also MIT licensed. + See end of file for detailed license information. + +DOCUMENTATION + + Dynamic Arrays + + Non-function interface: + + Declare an empty dynamic array of type T + T* foo = NULL; + + Access the i'th item of a dynamic array 'foo' of type T, T* foo: + foo[i] + + Functions (actually macros) + + arrfree: + void arrfree(T*); + Frees the array. + + arrlen: + ptrdiff_t arrlen(T*); + Returns the number of elements in the array. + + arrlenu: + size_t arrlenu(T*); + Returns the number of elements in the array as an unsigned type. + + arrpop: + T arrpop(T* a) + Removes the final element of the array and returns it. + + arrput: + T arrput(T* a, T b); + Appends the item b to the end of array a. Returns b. + + arrins: + T arrins(T* a, int p, T b); + Inserts the item b into the middle of array a, into a[p], + moving the rest of the array over. Returns b. + + arrinsn: + void arrinsn(T* a, int p, int n); + Inserts n uninitialized items into array a starting at a[p], + moving the rest of the array over. + + arraddnptr: + T* arraddnptr(T* a, int n) + Appends n uninitialized items onto array at the end. + Returns a pointer to the first uninitialized item added. + + arraddnindex: + size_t arraddnindex(T* a, int n) + Appends n uninitialized items onto array at the end. + Returns the index of the first uninitialized item added. + + arrdel: + void arrdel(T* a, int p); + Deletes the element at a[p], moving the rest of the array over. + + arrdeln: + void arrdeln(T* a, int p, int n); + Deletes n elements starting at a[p], moving the rest of the array over. + + arrdelswap: + void arrdelswap(T* a, int p); + Deletes the element at a[p], replacing it with the element from + the end of the array. O(1) performance. + + arrsetlen: + void arrsetlen(T* a, int n); + Changes the length of the array to n. Allocates uninitialized + slots at the end if necessary. + + arrsetcap: + size_t arrsetcap(T* a, int n); + Sets the length of allocated storage to at least n. It will not + change the length of the array. + + arrcap: + size_t arrcap(T* a); + Returns the number of total elements the array can contain without + needing to be reallocated. + + Hash maps & String hash maps + + Given T is a structure type: struct { TK key; TV value; }. Note that some + functions do not require TV value and can have other fields. For string + hash maps, TK must be 'char *'. + + Special interface: + + stbds_rand_seed: + void stbds_rand_seed(size_t seed); + For security against adversarially chosen data, you should seed the + library with a strong random number. Or at least seed it with time(). + + stbds_hash_string: + size_t stbds_hash_string(char *str, size_t seed); + Returns a hash value for a string. + + stbds_hash_bytes: + size_t stbds_hash_bytes(void *p, size_t len, size_t seed); + These functions hash an arbitrary number of bytes. The function + uses a custom hash for 4- and 8-byte data, and a weakened version + of SipHash for everything else. On 64-bit platforms you can get + specification-compliant SipHash-2-4 on all data by defining + STBDS_SIPHASH_2_4, at a significant cost in speed. + + Non-function interface: + + Declare an empty hash map of type T + T* foo = NULL; + + Access the i'th entry in a hash table T* foo: + foo[i] + + Function interface (actually macros): + + hmfree + shfree + void hmfree(T*); + void shfree(T*); + Frees the hashmap and sets the pointer to NULL. + + hmlen + shlen + ptrdiff_t hmlen(T*) + ptrdiff_t shlen(T*) + Returns the number of elements in the hashmap. + + hmlenu + shlenu + size_t hmlenu(T*) + size_t shlenu(T*) + Returns the number of elements in the hashmap. + + hmgeti + shgeti + hmgeti_ts + ptrdiff_t hmgeti(T*, TK key) + ptrdiff_t shgeti(T*, char* key) + ptrdiff_t hmgeti_ts(T*, TK key, ptrdiff_t tempvar) + Returns the index in the hashmap which has the key 'key', or -1 + if the key is not present. + + hmget + hmget_ts + shget + TV hmget(T*, TK key) + TV shget(T*, char* key) + TV hmget_ts(T*, TK key, ptrdiff_t tempvar) + Returns the value corresponding to 'key' in the hashmap. + The structure must have a 'value' field + + hmgets + shgets + T hmgets(T*, TK key) + T shgets(T*, char* key) + Returns the structure corresponding to 'key' in the hashmap. + + hmgetp + shgetp + hmgetp_ts + hmgetp_null + shgetp_null + T* hmgetp(T*, TK key) + T* shgetp(T*, char* key) + T* hmgetp_ts(T*, TK key, ptrdiff_t tempvar) + T* hmgetp_null(T*, TK key) + T* shgetp_null(T*, char *key) + Returns a pointer to the structure corresponding to 'key' in + the hashmap. Functions ending in "_null" return NULL if the key + is not present in the hashmap; the others return a pointer to a + structure holding the default value (but not the searched-for key). + + hmdefault + shdefault + TV hmdefault(T*, TV value) + TV shdefault(T*, TV value) + Sets the default value for the hashmap, the value which will be + returned by hmget/shget if the key is not present. + + hmdefaults + shdefaults + TV hmdefaults(T*, T item) + TV shdefaults(T*, T item) + Sets the default struct for the hashmap, the contents which will be + returned by hmgets/shgets if the key is not present. + + hmput + shput + TV hmput(T*, TK key, TV value) + TV shput(T*, char* key, TV value) + Inserts a pair into the hashmap. If the key is already + present in the hashmap, updates its value. + + hmputs + shputs + T hmputs(T*, T item) + T shputs(T*, T item) + Inserts a struct with T.key into the hashmap. If the struct is already + present in the hashmap, updates it. + + hmdel + shdel + int hmdel(T*, TK key) + int shdel(T*, char* key) + If 'key' is in the hashmap, deletes its entry and returns 1. + Otherwise returns 0. + + Function interface (actually macros) for strings only: + + sh_new_strdup + void sh_new_strdup(T*); + Overwrites the existing pointer with a newly allocated + string hashmap which will automatically allocate and free + each string key using realloc/free + + sh_new_arena + void sh_new_arena(T*); + Overwrites the existing pointer with a newly allocated + string hashmap which will automatically allocate each string + key to a string arena. Every string key ever used by this + hash table remains in the arena until the arena is freed. + Additionally, any key which is deleted and reinserted will + be allocated multiple times in the string arena. + +NOTES + + * These data structures are realloc'd when they grow, and the macro + "functions" write to the provided pointer. This means: (a) the pointer + must be an lvalue, and (b) the pointer to the data structure is not + stable, and you must maintain it the same as you would a realloc'd + pointer. For example, if you pass a pointer to a dynamic array to a + function which updates it, the function must return back the new + pointer to the caller. This is the price of trying to do this in C. + + * The following are the only functions that are thread-safe on a single data + structure, i.e. can be run in multiple threads simultaneously on the same + data structure + hmlen shlen + hmlenu shlenu + hmget_ts shget_ts + hmgeti_ts shgeti_ts + hmgets_ts shgets_ts + + * You iterate over the contents of a dynamic array and a hashmap in exactly + the same way, using arrlen/hmlen/shlen: + + for (i=0; i < arrlen(foo); ++i) + ... foo[i] ... + + * All operations except arrins/arrdel are O(1) amortized, but individual + operations can be slow, so these data structures may not be suitable + for real time use. Dynamic arrays double in capacity as needed, so + elements are copied an average of once. Hash tables double/halve + their size as needed, with appropriate hysteresis to maintain O(1) + performance. + +NOTES - DYNAMIC ARRAY + + * If you know how long a dynamic array is going to be in advance, you can avoid + extra memory allocations by using arrsetlen to allocate it to that length in + advance and use foo[n] while filling it out, or arrsetcap to allocate the memory + for that length and use arrput/arrpush as normal. + + * Unlike some other versions of the dynamic array, this version should + be safe to use with strict-aliasing optimizations. + +NOTES - HASH MAP + + * For compilers other than GCC and clang (e.g. Visual Studio), for hmput/hmget/hmdel + and variants, the key must be an lvalue (so the macro can take the address of it). + Extensions are used that eliminate this requirement if you're using C99 and later + in GCC or clang, or if you're using C++ in GCC. But note that this can make your + code less portable. + + * To test for presence of a key in a hashmap, just do 'hmgeti(foo,key) >= 0'. + + * The iteration order of your data in the hashmap is determined solely by the + order of insertions and deletions. In particular, if you never delete, new + keys are always added at the end of the array. This will be consistent + across all platforms and versions of the library. However, you should not + attempt to serialize the internal hash table, as the hash is not consistent + between different platforms, and may change with future versions of the library. + + * Use sh_new_arena() for string hashmaps that you never delete from. Initialize + with NULL if you're managing the memory for your strings, or your strings are + never freed (at least until the hashmap is freed). Otherwise, use sh_new_strdup(). + @TODO: make an arena variant that garbage collects the strings with a trivial + copy collector into a new arena whenever the table shrinks / rebuilds. Since + current arena recommendation is to only use arena if it never deletes, then + this can just replace current arena implementation. + + * If adversarial input is a serious concern and you're on a 64-bit platform, + enable STBDS_SIPHASH_2_4 (see the 'Compile-time options' section), and pass + a strong random number to stbds_rand_seed. + + * The default value for the hash table is stored in foo[-1], so if you + use code like 'hmget(T,k)->value = 5' you can accidentally overwrite + the value stored by hmdefault if 'k' is not present. + +CREDITS + + Sean Barrett -- library, idea for dynamic array API/implementation + Per Vognsen -- idea for hash table API/implementation + Rafael Sachetto -- arrpop() + github:HeroicKatora -- arraddn() reworking + + Bugfixes: + Andy Durdin + Shane Liesegang + Vinh Truong + Andreas Molzer + github:hashitaku + github:srdjanstipic + Macoy Madson + Andreas Vennstrom + Tobias Mansfield-Williams +*/ + +#ifdef STBDS_UNIT_TESTS +#define _CRT_SECURE_NO_WARNINGS +#endif + +#ifndef INCLUDE_STB_DS_H +#define INCLUDE_STB_DS_H + +#include +#include + +#ifndef STBDS_NO_SHORT_NAMES +#define arrlen stbds_arrlen +#define arrlenu stbds_arrlenu +#define arrput stbds_arrput +#define arrpush stbds_arrput +#define arrpop stbds_arrpop +#define arrfree stbds_arrfree +#define arraddn stbds_arraddn // deprecated, use one of the following instead: +#define arraddnptr stbds_arraddnptr +#define arraddnindex stbds_arraddnindex +#define arrsetlen stbds_arrsetlen +#define arrlast stbds_arrlast +#define arrins stbds_arrins +#define arrinsn stbds_arrinsn +#define arrdel stbds_arrdel +#define arrdeln stbds_arrdeln +#define arrdelswap stbds_arrdelswap +#define arrcap stbds_arrcap +#define arrsetcap stbds_arrsetcap + +#define hmput stbds_hmput +#define hmputs stbds_hmputs +#define hmget stbds_hmget +#define hmget_ts stbds_hmget_ts +#define hmgets stbds_hmgets +#define hmgetp stbds_hmgetp +#define hmgetp_ts stbds_hmgetp_ts +#define hmgetp_null stbds_hmgetp_null +#define hmgeti stbds_hmgeti +#define hmgeti_ts stbds_hmgeti_ts +#define hmdel stbds_hmdel +#define hmlen stbds_hmlen +#define hmlenu stbds_hmlenu +#define hmfree stbds_hmfree +#define hmdefault stbds_hmdefault +#define hmdefaults stbds_hmdefaults + +#define shput stbds_shput +#define shputi stbds_shputi +#define shputs stbds_shputs +#define shget stbds_shget +#define shgeti stbds_shgeti +#define shgets stbds_shgets +#define shgetp stbds_shgetp +#define shgetp_null stbds_shgetp_null +#define shdel stbds_shdel +#define shlen stbds_shlen +#define shlenu stbds_shlenu +#define shfree stbds_shfree +#define shdefault stbds_shdefault +#define shdefaults stbds_shdefaults +#define sh_new_arena stbds_sh_new_arena +#define sh_new_strdup stbds_sh_new_strdup + +#define stralloc stbds_stralloc +#define strreset stbds_strreset +#endif + +#if defined(STBDS_REALLOC) && !defined(STBDS_FREE) || !defined(STBDS_REALLOC) && defined(STBDS_FREE) +#error "You must define both STBDS_REALLOC and STBDS_FREE, or neither." +#endif +#if !defined(STBDS_REALLOC) && !defined(STBDS_FREE) +#include +#define STBDS_REALLOC(c,p,s) realloc(p,s) +#define STBDS_FREE(c,p) free(p) +#endif + +#ifdef _MSC_VER +#define STBDS_NOTUSED(v) (void)(v) +#else +#define STBDS_NOTUSED(v) (void)sizeof(v) +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// for security against attackers, seed the library with a random number, at least time() but stronger is better +extern void stbds_rand_seed(size_t seed); + +// these are the hash functions used internally if you want to test them or use them for other purposes +extern size_t stbds_hash_bytes(void *p, size_t len, size_t seed); +extern size_t stbds_hash_string(char *str, size_t seed); + +// this is a simple string arena allocator, initialize with e.g. 'stbds_string_arena my_arena={0}'. +typedef struct stbds_string_arena stbds_string_arena; +extern char * stbds_stralloc(stbds_string_arena *a, char *str); +extern void stbds_strreset(stbds_string_arena *a); + +// have to #define STBDS_UNIT_TESTS to call this +extern void stbds_unit_tests(void); + +/////////////// +// +// Everything below here is implementation details +// + +extern void * stbds_arrgrowf(void *a, size_t elemsize, size_t addlen, size_t min_cap); +extern void stbds_arrfreef(void *a); +extern void stbds_hmfree_func(void *p, size_t elemsize); +extern void * stbds_hmget_key(void *a, size_t elemsize, void *key, size_t keysize, int mode); +extern void * stbds_hmget_key_ts(void *a, size_t elemsize, void *key, size_t keysize, ptrdiff_t *temp, int mode); +extern void * stbds_hmput_default(void *a, size_t elemsize); +extern void * stbds_hmput_key(void *a, size_t elemsize, void *key, size_t keysize, int mode); +extern void * stbds_hmdel_key(void *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode); +extern void * stbds_shmode_func(size_t elemsize, int mode); + +#ifdef __cplusplus +} +#endif + +#if defined(__GNUC__) || defined(__clang__) +#define STBDS_HAS_TYPEOF +#ifdef __cplusplus +//#define STBDS_HAS_LITERAL_ARRAY // this is currently broken for clang +#endif +#endif + +#if !defined(__cplusplus) +#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L +#define STBDS_HAS_LITERAL_ARRAY +#endif +#endif + +// this macro takes the address of the argument, but on gcc/clang can accept rvalues +#if defined(STBDS_HAS_LITERAL_ARRAY) && defined(STBDS_HAS_TYPEOF) + #if __clang__ + #define STBDS_ADDRESSOF(typevar, value) ((__typeof__(typevar)[1]){value}) // literal array decays to pointer to value + #else + #define STBDS_ADDRESSOF(typevar, value) ((typeof(typevar)[1]){value}) // literal array decays to pointer to value + #endif +#else +#define STBDS_ADDRESSOF(typevar, value) &(value) +#endif + +#define STBDS_OFFSETOF(var,field) ((char *) &(var)->field - (char *) (var)) + +#define stbds_header(t) ((stbds_array_header *) (t) - 1) +#define stbds_temp(t) stbds_header(t)->temp +#define stbds_temp_key(t) (*(char **) stbds_header(t)->hash_table) + +#define stbds_arrsetcap(a,n) (stbds_arrgrow(a,0,n)) +#define stbds_arrsetlen(a,n) ((stbds_arrcap(a) < (size_t) (n) ? stbds_arrsetcap((a),(size_t)(n)),0 : 0), (a) ? stbds_header(a)->length = (size_t) (n) : 0) +#define stbds_arrcap(a) ((a) ? stbds_header(a)->capacity : 0) +#define stbds_arrlen(a) ((a) ? (ptrdiff_t) stbds_header(a)->length : 0) +#define stbds_arrlenu(a) ((a) ? stbds_header(a)->length : 0) +#define stbds_arrput(a,v) (stbds_arrmaybegrow(a,1), (a)[stbds_header(a)->length++] = (v)) +#define stbds_arrpush stbds_arrput // synonym +#define stbds_arrpop(a) (stbds_header(a)->length--, (a)[stbds_header(a)->length]) +#define stbds_arraddn(a,n) ((void)(stbds_arraddnindex(a, n))) // deprecated, use one of the following instead: +#define stbds_arraddnptr(a,n) (stbds_arrmaybegrow(a,n), (n) ? (stbds_header(a)->length += (n), &(a)[stbds_header(a)->length-(n)]) : (a)) +#define stbds_arraddnindex(a,n)(stbds_arrmaybegrow(a,n), (n) ? (stbds_header(a)->length += (n), stbds_header(a)->length-(n)) : stbds_arrlen(a)) +#define stbds_arraddnoff stbds_arraddnindex +#define stbds_arrlast(a) ((a)[stbds_header(a)->length-1]) +#define stbds_arrfree(a) ((void) ((a) ? STBDS_FREE(NULL,stbds_header(a)) : (void)0), (a)=NULL) +#define stbds_arrdel(a,i) stbds_arrdeln(a,i,1) +#define stbds_arrdeln(a,i,n) (memmove(&(a)[i], &(a)[(i)+(n)], sizeof *(a) * (stbds_header(a)->length-(n)-(i))), stbds_header(a)->length -= (n)) +#define stbds_arrdelswap(a,i) ((a)[i] = stbds_arrlast(a), stbds_header(a)->length -= 1) +#define stbds_arrinsn(a,i,n) (stbds_arraddn((a),(n)), memmove(&(a)[(i)+(n)], &(a)[i], sizeof *(a) * (stbds_header(a)->length-(n)-(i)))) +#define stbds_arrins(a,i,v) (stbds_arrinsn((a),(i),1), (a)[i]=(v)) + +#define stbds_arrmaybegrow(a,n) ((!(a) || stbds_header(a)->length + (n) > stbds_header(a)->capacity) \ + ? (stbds_arrgrow(a,n,0),0) : 0) + +#define stbds_arrgrow(a,b,c) ((a) = stbds_arrgrowf_wrapper((a), sizeof *(a), (b), (c))) + +#define stbds_hmput(t, k, v) \ + ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, 0), \ + (t)[stbds_temp((t)-1)].key = (k), \ + (t)[stbds_temp((t)-1)].value = (v)) + +#define stbds_hmputs(t, s) \ + ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), &(s).key, sizeof (s).key, STBDS_HM_BINARY), \ + (t)[stbds_temp((t)-1)] = (s)) + +#define stbds_hmgeti(t,k) \ + ((t) = stbds_hmget_key_wrapper((t), sizeof *(t), (void*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, STBDS_HM_BINARY), \ + stbds_temp((t)-1)) + +#define stbds_hmgeti_ts(t,k,temp) \ + ((t) = stbds_hmget_key_ts_wrapper((t), sizeof *(t), (void*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, &(temp), STBDS_HM_BINARY), \ + (temp)) + +#define stbds_hmgetp(t, k) \ + ((void) stbds_hmgeti(t,k), &(t)[stbds_temp((t)-1)]) + +#define stbds_hmgetp_ts(t, k, temp) \ + ((void) stbds_hmgeti_ts(t,k,temp), &(t)[temp]) + +#define stbds_hmdel(t,k) \ + (((t) = stbds_hmdel_key_wrapper((t),sizeof *(t), (void*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, STBDS_OFFSETOF((t),key), STBDS_HM_BINARY)),(t)?stbds_temp((t)-1):0) + +#define stbds_hmdefault(t, v) \ + ((t) = stbds_hmput_default_wrapper((t), sizeof *(t)), (t)[-1].value = (v)) + +#define stbds_hmdefaults(t, s) \ + ((t) = stbds_hmput_default_wrapper((t), sizeof *(t)), (t)[-1] = (s)) + +#define stbds_hmfree(p) \ + ((void) ((p) != NULL ? stbds_hmfree_func((p)-1,sizeof*(p)),0 : 0),(p)=NULL) + +#define stbds_hmgets(t, k) (*stbds_hmgetp(t,k)) +#define stbds_hmget(t, k) (stbds_hmgetp(t,k)->value) +#define stbds_hmget_ts(t, k, temp) (stbds_hmgetp_ts(t,k,temp)->value) +#define stbds_hmlen(t) ((t) ? (ptrdiff_t) stbds_header((t)-1)->length-1 : 0) +#define stbds_hmlenu(t) ((t) ? stbds_header((t)-1)->length-1 : 0) +#define stbds_hmgetp_null(t,k) (stbds_hmgeti(t,k) == -1 ? NULL : &(t)[stbds_temp((t)-1)]) + +#define stbds_shput(t, k, v) \ + ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) (k), sizeof (t)->key, STBDS_HM_STRING), \ + (t)[stbds_temp((t)-1)].value = (v)) + +#define stbds_shputi(t, k, v) \ + ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) (k), sizeof (t)->key, STBDS_HM_STRING), \ + (t)[stbds_temp((t)-1)].value = (v), stbds_temp((t)-1)) + +#define stbds_shputs(t, s) \ + ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) (s).key, sizeof (s).key, STBDS_HM_STRING), \ + (t)[stbds_temp((t)-1)] = (s), \ + (t)[stbds_temp((t)-1)].key = stbds_temp_key((t)-1)) // above line overwrites whole structure, so must rewrite key here if it was allocated internally + +#define stbds_pshput(t, p) \ + ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) (p)->key, sizeof (p)->key, STBDS_HM_PTR_TO_STRING), \ + (t)[stbds_temp((t)-1)] = (p)) + +#define stbds_shgeti(t,k) \ + ((t) = stbds_hmget_key_wrapper((t), sizeof *(t), (void*) (k), sizeof (t)->key, STBDS_HM_STRING), \ + stbds_temp((t)-1)) + +#define stbds_pshgeti(t,k) \ + ((t) = stbds_hmget_key_wrapper((t), sizeof *(t), (void*) (k), sizeof (*(t))->key, STBDS_HM_PTR_TO_STRING), \ + stbds_temp((t)-1)) + +#define stbds_shgetp(t, k) \ + ((void) stbds_shgeti(t,k), &(t)[stbds_temp((t)-1)]) + +#define stbds_pshget(t, k) \ + ((void) stbds_pshgeti(t,k), (t)[stbds_temp((t)-1)]) + +#define stbds_shdel(t,k) \ + (((t) = stbds_hmdel_key_wrapper((t),sizeof *(t), (void*) (k), sizeof (t)->key, STBDS_OFFSETOF((t),key), STBDS_HM_STRING)),(t)?stbds_temp((t)-1):0) +#define stbds_pshdel(t,k) \ + (((t) = stbds_hmdel_key_wrapper((t),sizeof *(t), (void*) (k), sizeof (*(t))->key, STBDS_OFFSETOF(*(t),key), STBDS_HM_PTR_TO_STRING)),(t)?stbds_temp((t)-1):0) + +#define stbds_sh_new_arena(t) \ + ((t) = stbds_shmode_func_wrapper(t, sizeof *(t), STBDS_SH_ARENA)) +#define stbds_sh_new_strdup(t) \ + ((t) = stbds_shmode_func_wrapper(t, sizeof *(t), STBDS_SH_STRDUP)) + +#define stbds_shdefault(t, v) stbds_hmdefault(t,v) +#define stbds_shdefaults(t, s) stbds_hmdefaults(t,s) + +#define stbds_shfree stbds_hmfree +#define stbds_shlenu stbds_hmlenu + +#define stbds_shgets(t, k) (*stbds_shgetp(t,k)) +#define stbds_shget(t, k) (stbds_shgetp(t,k)->value) +#define stbds_shgetp_null(t,k) (stbds_shgeti(t,k) == -1 ? NULL : &(t)[stbds_temp((t)-1)]) +#define stbds_shlen stbds_hmlen + +typedef struct +{ + size_t length; + size_t capacity; + void * hash_table; + ptrdiff_t temp; +} stbds_array_header; + +typedef struct stbds_string_block +{ + struct stbds_string_block *next; + char storage[8]; +} stbds_string_block; + +struct stbds_string_arena +{ + stbds_string_block *storage; + size_t remaining; + unsigned char block; + unsigned char mode; // this isn't used by the string arena itself +}; + +#define STBDS_HM_BINARY 0 +#define STBDS_HM_STRING 1 + +enum +{ + STBDS_SH_NONE, + STBDS_SH_DEFAULT, + STBDS_SH_STRDUP, + STBDS_SH_ARENA +}; + +#ifdef __cplusplus +// in C we use implicit assignment from these void*-returning functions to T*. +// in C++ these templates make the same code work +template static T * stbds_arrgrowf_wrapper(T *a, size_t elemsize, size_t addlen, size_t min_cap) { + return (T*)stbds_arrgrowf((void *)a, elemsize, addlen, min_cap); +} +template static T * stbds_hmget_key_wrapper(T *a, size_t elemsize, void *key, size_t keysize, int mode) { + return (T*)stbds_hmget_key((void*)a, elemsize, key, keysize, mode); +} +template static T * stbds_hmget_key_ts_wrapper(T *a, size_t elemsize, void *key, size_t keysize, ptrdiff_t *temp, int mode) { + return (T*)stbds_hmget_key_ts((void*)a, elemsize, key, keysize, temp, mode); +} +template static T * stbds_hmput_default_wrapper(T *a, size_t elemsize) { + return (T*)stbds_hmput_default((void *)a, elemsize); +} +template static T * stbds_hmput_key_wrapper(T *a, size_t elemsize, void *key, size_t keysize, int mode) { + return (T*)stbds_hmput_key((void*)a, elemsize, key, keysize, mode); +} +template static T * stbds_hmdel_key_wrapper(T *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode){ + return (T*)stbds_hmdel_key((void*)a, elemsize, key, keysize, keyoffset, mode); +} +template static T * stbds_shmode_func_wrapper(T *, size_t elemsize, int mode) { + return (T*)stbds_shmode_func(elemsize, mode); +} +#else +#define stbds_arrgrowf_wrapper stbds_arrgrowf +#define stbds_hmget_key_wrapper stbds_hmget_key +#define stbds_hmget_key_ts_wrapper stbds_hmget_key_ts +#define stbds_hmput_default_wrapper stbds_hmput_default +#define stbds_hmput_key_wrapper stbds_hmput_key +#define stbds_hmdel_key_wrapper stbds_hmdel_key +#define stbds_shmode_func_wrapper(t,e,m) stbds_shmode_func(e,m) +#endif + +#endif // INCLUDE_STB_DS_H + + +////////////////////////////////////////////////////////////////////////////// +// +// IMPLEMENTATION +// + +#ifdef STB_DS_IMPLEMENTATION +#include +#include + +#ifndef STBDS_ASSERT +#define STBDS_ASSERT_WAS_UNDEFINED +#define STBDS_ASSERT(x) ((void) 0) +#endif + +#ifdef STBDS_STATISTICS +#define STBDS_STATS(x) x +size_t stbds_array_grow; +size_t stbds_hash_grow; +size_t stbds_hash_shrink; +size_t stbds_hash_rebuild; +size_t stbds_hash_probes; +size_t stbds_hash_alloc; +size_t stbds_rehash_probes; +size_t stbds_rehash_items; +#else +#define STBDS_STATS(x) +#endif + +// +// stbds_arr implementation +// + +//int *prev_allocs[65536]; +//int num_prev; + +void *stbds_arrgrowf(void *a, size_t elemsize, size_t addlen, size_t min_cap) +{ + stbds_array_header temp={0}; // force debugging + void *b; + size_t min_len = stbds_arrlen(a) + addlen; + (void) sizeof(temp); + + // compute the minimum capacity needed + if (min_len > min_cap) + min_cap = min_len; + + if (min_cap <= stbds_arrcap(a)) + return a; + + // increase needed capacity to guarantee O(1) amortized + if (min_cap < 2 * stbds_arrcap(a)) + min_cap = 2 * stbds_arrcap(a); + else if (min_cap < 4) + min_cap = 4; + + //if (num_prev < 65536) if (a) prev_allocs[num_prev++] = (int *) ((char *) a+1); + //if (num_prev == 2201) + // num_prev = num_prev; + b = STBDS_REALLOC(NULL, (a) ? stbds_header(a) : 0, elemsize * min_cap + sizeof(stbds_array_header)); + //if (num_prev < 65536) prev_allocs[num_prev++] = (int *) (char *) b; + b = (char *) b + sizeof(stbds_array_header); + if (a == NULL) { + stbds_header(b)->length = 0; + stbds_header(b)->hash_table = 0; + stbds_header(b)->temp = 0; + } else { + STBDS_STATS(++stbds_array_grow); + } + stbds_header(b)->capacity = min_cap; + + return b; +} + +void stbds_arrfreef(void *a) +{ + STBDS_FREE(NULL, stbds_header(a)); +} + +// +// stbds_hm hash table implementation +// + +#ifdef STBDS_INTERNAL_SMALL_BUCKET +#define STBDS_BUCKET_LENGTH 4 +#else +#define STBDS_BUCKET_LENGTH 8 +#endif + +#define STBDS_BUCKET_SHIFT (STBDS_BUCKET_LENGTH == 8 ? 3 : 2) +#define STBDS_BUCKET_MASK (STBDS_BUCKET_LENGTH-1) +#define STBDS_CACHE_LINE_SIZE 64 + +#define STBDS_ALIGN_FWD(n,a) (((n) + (a) - 1) & ~((a)-1)) + +typedef struct +{ + size_t hash [STBDS_BUCKET_LENGTH]; + ptrdiff_t index[STBDS_BUCKET_LENGTH]; +} stbds_hash_bucket; // in 32-bit, this is one 64-byte cache line; in 64-bit, each array is one 64-byte cache line + +typedef struct +{ + char * temp_key; // this MUST be the first field of the hash table + size_t slot_count; + size_t used_count; + size_t used_count_threshold; + size_t used_count_shrink_threshold; + size_t tombstone_count; + size_t tombstone_count_threshold; + size_t seed; + size_t slot_count_log2; + stbds_string_arena string; + stbds_hash_bucket *storage; // not a separate allocation, just 64-byte aligned storage after this struct +} stbds_hash_index; + +#define STBDS_INDEX_EMPTY -1 +#define STBDS_INDEX_DELETED -2 +#define STBDS_INDEX_IN_USE(x) ((x) >= 0) + +#define STBDS_HASH_EMPTY 0 +#define STBDS_HASH_DELETED 1 + +static size_t stbds_hash_seed=0x31415926; + +void stbds_rand_seed(size_t seed) +{ + stbds_hash_seed = seed; +} + +#define stbds_load_32_or_64(var, temp, v32, v64_hi, v64_lo) \ + temp = v64_lo ^ v32, temp <<= 16, temp <<= 16, temp >>= 16, temp >>= 16, /* discard if 32-bit */ \ + var = v64_hi, var <<= 16, var <<= 16, /* discard if 32-bit */ \ + var ^= temp ^ v32 + +#define STBDS_SIZE_T_BITS ((sizeof (size_t)) * 8) + +static size_t stbds_probe_position(size_t hash, size_t slot_count, size_t slot_log2) +{ + size_t pos; + STBDS_NOTUSED(slot_log2); + pos = hash & (slot_count-1); + #ifdef STBDS_INTERNAL_BUCKET_START + pos &= ~STBDS_BUCKET_MASK; + #endif + return pos; +} + +static size_t stbds_log2(size_t slot_count) +{ + size_t n=0; + while (slot_count > 1) { + slot_count >>= 1; + ++n; + } + return n; +} + +static stbds_hash_index *stbds_make_hash_index(size_t slot_count, stbds_hash_index *ot) +{ + stbds_hash_index *t; + t = (stbds_hash_index *) STBDS_REALLOC(NULL,0,(slot_count >> STBDS_BUCKET_SHIFT) * sizeof(stbds_hash_bucket) + sizeof(stbds_hash_index) + STBDS_CACHE_LINE_SIZE-1); + t->storage = (stbds_hash_bucket *) STBDS_ALIGN_FWD((size_t) (t+1), STBDS_CACHE_LINE_SIZE); + t->slot_count = slot_count; + t->slot_count_log2 = stbds_log2(slot_count); + t->tombstone_count = 0; + t->used_count = 0; + + #if 0 // A1 + t->used_count_threshold = slot_count*12/16; // if 12/16th of table is occupied, grow + t->tombstone_count_threshold = slot_count* 2/16; // if tombstones are 2/16th of table, rebuild + t->used_count_shrink_threshold = slot_count* 4/16; // if table is only 4/16th full, shrink + #elif 1 // A2 + //t->used_count_threshold = slot_count*12/16; // if 12/16th of table is occupied, grow + //t->tombstone_count_threshold = slot_count* 3/16; // if tombstones are 3/16th of table, rebuild + //t->used_count_shrink_threshold = slot_count* 4/16; // if table is only 4/16th full, shrink + + // compute without overflowing + t->used_count_threshold = slot_count - (slot_count>>2); + t->tombstone_count_threshold = (slot_count>>3) + (slot_count>>4); + t->used_count_shrink_threshold = slot_count >> 2; + + #elif 0 // B1 + t->used_count_threshold = slot_count*13/16; // if 13/16th of table is occupied, grow + t->tombstone_count_threshold = slot_count* 2/16; // if tombstones are 2/16th of table, rebuild + t->used_count_shrink_threshold = slot_count* 5/16; // if table is only 5/16th full, shrink + #else // C1 + t->used_count_threshold = slot_count*14/16; // if 14/16th of table is occupied, grow + t->tombstone_count_threshold = slot_count* 2/16; // if tombstones are 2/16th of table, rebuild + t->used_count_shrink_threshold = slot_count* 6/16; // if table is only 6/16th full, shrink + #endif + // Following statistics were measured on a Core i7-6700 @ 4.00Ghz, compiled with clang 7.0.1 -O2 + // Note that the larger tables have high variance as they were run fewer times + // A1 A2 B1 C1 + // 0.10ms : 0.10ms : 0.10ms : 0.11ms : 2,000 inserts creating 2K table + // 0.96ms : 0.95ms : 0.97ms : 1.04ms : 20,000 inserts creating 20K table + // 14.48ms : 14.46ms : 10.63ms : 11.00ms : 200,000 inserts creating 200K table + // 195.74ms : 196.35ms : 203.69ms : 214.92ms : 2,000,000 inserts creating 2M table + // 2193.88ms : 2209.22ms : 2285.54ms : 2437.17ms : 20,000,000 inserts creating 20M table + // 65.27ms : 53.77ms : 65.33ms : 65.47ms : 500,000 inserts & deletes in 2K table + // 72.78ms : 62.45ms : 71.95ms : 72.85ms : 500,000 inserts & deletes in 20K table + // 89.47ms : 77.72ms : 96.49ms : 96.75ms : 500,000 inserts & deletes in 200K table + // 97.58ms : 98.14ms : 97.18ms : 97.53ms : 500,000 inserts & deletes in 2M table + // 118.61ms : 119.62ms : 120.16ms : 118.86ms : 500,000 inserts & deletes in 20M table + // 192.11ms : 194.39ms : 196.38ms : 195.73ms : 500,000 inserts & deletes in 200M table + + if (slot_count <= STBDS_BUCKET_LENGTH) + t->used_count_shrink_threshold = 0; + // to avoid infinite loop, we need to guarantee that at least one slot is empty and will terminate probes + STBDS_ASSERT(t->used_count_threshold + t->tombstone_count_threshold < t->slot_count); + STBDS_STATS(++stbds_hash_alloc); + if (ot) { + t->string = ot->string; + // reuse old seed so we can reuse old hashes so below "copy out old data" doesn't do any hashing + t->seed = ot->seed; + } else { + size_t a,b,temp; + memset(&t->string, 0, sizeof(t->string)); + t->seed = stbds_hash_seed; + // LCG + // in 32-bit, a = 2147001325 b = 715136305 + // in 64-bit, a = 2862933555777941757 b = 3037000493 + stbds_load_32_or_64(a,temp, 2147001325, 0x27bb2ee6, 0x87b0b0fd); + stbds_load_32_or_64(b,temp, 715136305, 0, 0xb504f32d); + stbds_hash_seed = stbds_hash_seed * a + b; + } + + { + size_t i,j; + for (i=0; i < slot_count >> STBDS_BUCKET_SHIFT; ++i) { + stbds_hash_bucket *b = &t->storage[i]; + for (j=0; j < STBDS_BUCKET_LENGTH; ++j) + b->hash[j] = STBDS_HASH_EMPTY; + for (j=0; j < STBDS_BUCKET_LENGTH; ++j) + b->index[j] = STBDS_INDEX_EMPTY; + } + } + + // copy out the old data, if any + if (ot) { + size_t i,j; + t->used_count = ot->used_count; + for (i=0; i < ot->slot_count >> STBDS_BUCKET_SHIFT; ++i) { + stbds_hash_bucket *ob = &ot->storage[i]; + for (j=0; j < STBDS_BUCKET_LENGTH; ++j) { + if (STBDS_INDEX_IN_USE(ob->index[j])) { + size_t hash = ob->hash[j]; + size_t pos = stbds_probe_position(hash, t->slot_count, t->slot_count_log2); + size_t step = STBDS_BUCKET_LENGTH; + STBDS_STATS(++stbds_rehash_items); + for (;;) { + size_t limit,z; + stbds_hash_bucket *bucket; + bucket = &t->storage[pos >> STBDS_BUCKET_SHIFT]; + STBDS_STATS(++stbds_rehash_probes); + + for (z=pos & STBDS_BUCKET_MASK; z < STBDS_BUCKET_LENGTH; ++z) { + if (bucket->hash[z] == 0) { + bucket->hash[z] = hash; + bucket->index[z] = ob->index[j]; + goto done; + } + } + + limit = pos & STBDS_BUCKET_MASK; + for (z = 0; z < limit; ++z) { + if (bucket->hash[z] == 0) { + bucket->hash[z] = hash; + bucket->index[z] = ob->index[j]; + goto done; + } + } + + pos += step; // quadratic probing + step += STBDS_BUCKET_LENGTH; + pos &= (t->slot_count-1); + } + } + done: + ; + } + } + } + + return t; +} + +#define STBDS_ROTATE_LEFT(val, n) (((val) << (n)) | ((val) >> (STBDS_SIZE_T_BITS - (n)))) +#define STBDS_ROTATE_RIGHT(val, n) (((val) >> (n)) | ((val) << (STBDS_SIZE_T_BITS - (n)))) + +size_t stbds_hash_string(char *str, size_t seed) +{ + size_t hash = seed; + while (*str) + hash = STBDS_ROTATE_LEFT(hash, 9) + (unsigned char) *str++; + + // Thomas Wang 64-to-32 bit mix function, hopefully also works in 32 bits + hash ^= seed; + hash = (~hash) + (hash << 18); + hash ^= hash ^ STBDS_ROTATE_RIGHT(hash,31); + hash = hash * 21; + hash ^= hash ^ STBDS_ROTATE_RIGHT(hash,11); + hash += (hash << 6); + hash ^= STBDS_ROTATE_RIGHT(hash,22); + return hash+seed; +} + +#ifdef STBDS_SIPHASH_2_4 +#define STBDS_SIPHASH_C_ROUNDS 2 +#define STBDS_SIPHASH_D_ROUNDS 4 +typedef int STBDS_SIPHASH_2_4_can_only_be_used_in_64_bit_builds[sizeof(size_t) == 8 ? 1 : -1]; +#endif + +#ifndef STBDS_SIPHASH_C_ROUNDS +#define STBDS_SIPHASH_C_ROUNDS 1 +#endif +#ifndef STBDS_SIPHASH_D_ROUNDS +#define STBDS_SIPHASH_D_ROUNDS 1 +#endif + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4127) // conditional expression is constant, for do..while(0) and sizeof()== +#endif + +static size_t stbds_siphash_bytes(void *p, size_t len, size_t seed) +{ + unsigned char *d = (unsigned char *) p; + size_t i,j; + size_t v0,v1,v2,v3, data; + + // hash that works on 32- or 64-bit registers without knowing which we have + // (computes different results on 32-bit and 64-bit platform) + // derived from siphash, but on 32-bit platforms very different as it uses 4 32-bit state not 4 64-bit + v0 = ((((size_t) 0x736f6d65 << 16) << 16) + 0x70736575) ^ seed; + v1 = ((((size_t) 0x646f7261 << 16) << 16) + 0x6e646f6d) ^ ~seed; + v2 = ((((size_t) 0x6c796765 << 16) << 16) + 0x6e657261) ^ seed; + v3 = ((((size_t) 0x74656462 << 16) << 16) + 0x79746573) ^ ~seed; + + #ifdef STBDS_TEST_SIPHASH_2_4 + // hardcoded with key material in the siphash test vectors + v0 ^= 0x0706050403020100ull ^ seed; + v1 ^= 0x0f0e0d0c0b0a0908ull ^ ~seed; + v2 ^= 0x0706050403020100ull ^ seed; + v3 ^= 0x0f0e0d0c0b0a0908ull ^ ~seed; + #endif + + #define STBDS_SIPROUND() \ + do { \ + v0 += v1; v1 = STBDS_ROTATE_LEFT(v1, 13); v1 ^= v0; v0 = STBDS_ROTATE_LEFT(v0,STBDS_SIZE_T_BITS/2); \ + v2 += v3; v3 = STBDS_ROTATE_LEFT(v3, 16); v3 ^= v2; \ + v2 += v1; v1 = STBDS_ROTATE_LEFT(v1, 17); v1 ^= v2; v2 = STBDS_ROTATE_LEFT(v2,STBDS_SIZE_T_BITS/2); \ + v0 += v3; v3 = STBDS_ROTATE_LEFT(v3, 21); v3 ^= v0; \ + } while (0) + + for (i=0; i+sizeof(size_t) <= len; i += sizeof(size_t), d += sizeof(size_t)) { + data = d[0] | (d[1] << 8) | (d[2] << 16) | (d[3] << 24); + data |= (size_t) (d[4] | (d[5] << 8) | (d[6] << 16) | (d[7] << 24)) << 16 << 16; // discarded if size_t == 4 + + v3 ^= data; + for (j=0; j < STBDS_SIPHASH_C_ROUNDS; ++j) + STBDS_SIPROUND(); + v0 ^= data; + } + data = len << (STBDS_SIZE_T_BITS-8); + switch (len - i) { + case 7: data |= ((size_t) d[6] << 24) << 24; // fall through + case 6: data |= ((size_t) d[5] << 20) << 20; // fall through + case 5: data |= ((size_t) d[4] << 16) << 16; // fall through + case 4: data |= (d[3] << 24); // fall through + case 3: data |= (d[2] << 16); // fall through + case 2: data |= (d[1] << 8); // fall through + case 1: data |= d[0]; // fall through + case 0: break; + } + v3 ^= data; + for (j=0; j < STBDS_SIPHASH_C_ROUNDS; ++j) + STBDS_SIPROUND(); + v0 ^= data; + v2 ^= 0xff; + for (j=0; j < STBDS_SIPHASH_D_ROUNDS; ++j) + STBDS_SIPROUND(); + +#ifdef STBDS_SIPHASH_2_4 + return v0^v1^v2^v3; +#else + return v1^v2^v3; // slightly stronger since v0^v3 in above cancels out final round operation? I tweeted at the authors of SipHash about this but they didn't reply +#endif +} + +size_t stbds_hash_bytes(void *p, size_t len, size_t seed) +{ +#ifdef STBDS_SIPHASH_2_4 + return stbds_siphash_bytes(p,len,seed); +#else + unsigned char *d = (unsigned char *) p; + + if (len == 4) { + unsigned int hash = d[0] | (d[1] << 8) | (d[2] << 16) | (d[3] << 24); + #if 0 + // HASH32-A Bob Jenkin's hash function w/o large constants + hash ^= seed; + hash -= (hash<<6); + hash ^= (hash>>17); + hash -= (hash<<9); + hash ^= seed; + hash ^= (hash<<4); + hash -= (hash<<3); + hash ^= (hash<<10); + hash ^= (hash>>15); + #elif 1 + // HASH32-BB Bob Jenkin's presumably-accidental version of Thomas Wang hash with rotates turned into shifts. + // Note that converting these back to rotates makes it run a lot slower, presumably due to collisions, so I'm + // not really sure what's going on. + hash ^= seed; + hash = (hash ^ 61) ^ (hash >> 16); + hash = hash + (hash << 3); + hash = hash ^ (hash >> 4); + hash = hash * 0x27d4eb2d; + hash ^= seed; + hash = hash ^ (hash >> 15); + #else // HASH32-C - Murmur3 + hash ^= seed; + hash *= 0xcc9e2d51; + hash = (hash << 17) | (hash >> 15); + hash *= 0x1b873593; + hash ^= seed; + hash = (hash << 19) | (hash >> 13); + hash = hash*5 + 0xe6546b64; + hash ^= hash >> 16; + hash *= 0x85ebca6b; + hash ^= seed; + hash ^= hash >> 13; + hash *= 0xc2b2ae35; + hash ^= hash >> 16; + #endif + // Following statistics were measured on a Core i7-6700 @ 4.00Ghz, compiled with clang 7.0.1 -O2 + // Note that the larger tables have high variance as they were run fewer times + // HASH32-A // HASH32-BB // HASH32-C + // 0.10ms // 0.10ms // 0.10ms : 2,000 inserts creating 2K table + // 0.96ms // 0.95ms // 0.99ms : 20,000 inserts creating 20K table + // 14.69ms // 14.43ms // 14.97ms : 200,000 inserts creating 200K table + // 199.99ms // 195.36ms // 202.05ms : 2,000,000 inserts creating 2M table + // 2234.84ms // 2187.74ms // 2240.38ms : 20,000,000 inserts creating 20M table + // 55.68ms // 53.72ms // 57.31ms : 500,000 inserts & deletes in 2K table + // 63.43ms // 61.99ms // 65.73ms : 500,000 inserts & deletes in 20K table + // 80.04ms // 77.96ms // 81.83ms : 500,000 inserts & deletes in 200K table + // 100.42ms // 97.40ms // 102.39ms : 500,000 inserts & deletes in 2M table + // 119.71ms // 120.59ms // 121.63ms : 500,000 inserts & deletes in 20M table + // 185.28ms // 195.15ms // 187.74ms : 500,000 inserts & deletes in 200M table + // 15.58ms // 14.79ms // 15.52ms : 200,000 inserts creating 200K table with varying key spacing + + return (((size_t) hash << 16 << 16) | hash) ^ seed; + } else if (len == 8 && sizeof(size_t) == 8) { + size_t hash = d[0] | (d[1] << 8) | (d[2] << 16) | (d[3] << 24); + hash |= (size_t) (d[4] | (d[5] << 8) | (d[6] << 16) | (d[7] << 24)) << 16 << 16; // avoid warning if size_t == 4 + hash ^= seed; + hash = (~hash) + (hash << 21); + hash ^= STBDS_ROTATE_RIGHT(hash,24); + hash *= 265; + hash ^= STBDS_ROTATE_RIGHT(hash,14); + hash ^= seed; + hash *= 21; + hash ^= STBDS_ROTATE_RIGHT(hash,28); + hash += (hash << 31); + hash = (~hash) + (hash << 18); + return hash; + } else { + return stbds_siphash_bytes(p,len,seed); + } +#endif +} +#ifdef _MSC_VER +#pragma warning(pop) +#endif + + +static int stbds_is_key_equal(void *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode, size_t i) +{ + if (mode >= STBDS_HM_STRING) + return 0==strcmp((char *) key, * (char **) ((char *) a + elemsize*i + keyoffset)); + else + return 0==memcmp(key, (char *) a + elemsize*i + keyoffset, keysize); +} + +#define STBDS_HASH_TO_ARR(x,elemsize) ((char*) (x) - (elemsize)) +#define STBDS_ARR_TO_HASH(x,elemsize) ((char*) (x) + (elemsize)) + +#define stbds_hash_table(a) ((stbds_hash_index *) stbds_header(a)->hash_table) + +void stbds_hmfree_func(void *a, size_t elemsize) +{ + if (a == NULL) return; + if (stbds_hash_table(a) != NULL) { + if (stbds_hash_table(a)->string.mode == STBDS_SH_STRDUP) { + size_t i; + // skip 0th element, which is default + for (i=1; i < stbds_header(a)->length; ++i) + STBDS_FREE(NULL, *(char**) ((char *) a + elemsize*i)); + } + stbds_strreset(&stbds_hash_table(a)->string); + } + STBDS_FREE(NULL, stbds_header(a)->hash_table); + STBDS_FREE(NULL, stbds_header(a)); +} + +static ptrdiff_t stbds_hm_find_slot(void *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode) +{ + void *raw_a = STBDS_HASH_TO_ARR(a,elemsize); + stbds_hash_index *table = stbds_hash_table(raw_a); + size_t hash = mode >= STBDS_HM_STRING ? stbds_hash_string((char*)key,table->seed) : stbds_hash_bytes(key, keysize,table->seed); + size_t step = STBDS_BUCKET_LENGTH; + size_t limit,i; + size_t pos; + stbds_hash_bucket *bucket; + + if (hash < 2) hash += 2; // stored hash values are forbidden from being 0, so we can detect empty slots + + pos = stbds_probe_position(hash, table->slot_count, table->slot_count_log2); + + for (;;) { + STBDS_STATS(++stbds_hash_probes); + bucket = &table->storage[pos >> STBDS_BUCKET_SHIFT]; + + // start searching from pos to end of bucket, this should help performance on small hash tables that fit in cache + for (i=pos & STBDS_BUCKET_MASK; i < STBDS_BUCKET_LENGTH; ++i) { + if (bucket->hash[i] == hash) { + if (stbds_is_key_equal(a, elemsize, key, keysize, keyoffset, mode, bucket->index[i])) { + return (pos & ~STBDS_BUCKET_MASK)+i; + } + } else if (bucket->hash[i] == STBDS_HASH_EMPTY) { + return -1; + } + } + + // search from beginning of bucket to pos + limit = pos & STBDS_BUCKET_MASK; + for (i = 0; i < limit; ++i) { + if (bucket->hash[i] == hash) { + if (stbds_is_key_equal(a, elemsize, key, keysize, keyoffset, mode, bucket->index[i])) { + return (pos & ~STBDS_BUCKET_MASK)+i; + } + } else if (bucket->hash[i] == STBDS_HASH_EMPTY) { + return -1; + } + } + + // quadratic probing + pos += step; + step += STBDS_BUCKET_LENGTH; + pos &= (table->slot_count-1); + } + /* NOTREACHED */ +} + +void * stbds_hmget_key_ts(void *a, size_t elemsize, void *key, size_t keysize, ptrdiff_t *temp, int mode) +{ + size_t keyoffset = 0; + if (a == NULL) { + // make it non-empty so we can return a temp + a = stbds_arrgrowf(0, elemsize, 0, 1); + stbds_header(a)->length += 1; + memset(a, 0, elemsize); + *temp = STBDS_INDEX_EMPTY; + // adjust a to point after the default element + return STBDS_ARR_TO_HASH(a,elemsize); + } else { + stbds_hash_index *table; + void *raw_a = STBDS_HASH_TO_ARR(a,elemsize); + // adjust a to point to the default element + table = (stbds_hash_index *) stbds_header(raw_a)->hash_table; + if (table == 0) { + *temp = -1; + } else { + ptrdiff_t slot = stbds_hm_find_slot(a, elemsize, key, keysize, keyoffset, mode); + if (slot < 0) { + *temp = STBDS_INDEX_EMPTY; + } else { + stbds_hash_bucket *b = &table->storage[slot >> STBDS_BUCKET_SHIFT]; + *temp = b->index[slot & STBDS_BUCKET_MASK]; + } + } + return a; + } +} + +void * stbds_hmget_key(void *a, size_t elemsize, void *key, size_t keysize, int mode) +{ + ptrdiff_t temp; + void *p = stbds_hmget_key_ts(a, elemsize, key, keysize, &temp, mode); + stbds_temp(STBDS_HASH_TO_ARR(p,elemsize)) = temp; + return p; +} + +void * stbds_hmput_default(void *a, size_t elemsize) +{ + // three cases: + // a is NULL <- allocate + // a has a hash table but no entries, because of shmode <- grow + // a has entries <- do nothing + if (a == NULL || stbds_header(STBDS_HASH_TO_ARR(a,elemsize))->length == 0) { + a = stbds_arrgrowf(a ? STBDS_HASH_TO_ARR(a,elemsize) : NULL, elemsize, 0, 1); + stbds_header(a)->length += 1; + memset(a, 0, elemsize); + a=STBDS_ARR_TO_HASH(a,elemsize); + } + return a; +} + +static char *stbds_strdup(char *str); + +void *stbds_hmput_key(void *a, size_t elemsize, void *key, size_t keysize, int mode) +{ + size_t keyoffset=0; + void *raw_a; + stbds_hash_index *table; + + if (a == NULL) { + a = stbds_arrgrowf(0, elemsize, 0, 1); + memset(a, 0, elemsize); + stbds_header(a)->length += 1; + // adjust a to point AFTER the default element + a = STBDS_ARR_TO_HASH(a,elemsize); + } + + // adjust a to point to the default element + raw_a = a; + a = STBDS_HASH_TO_ARR(a,elemsize); + + table = (stbds_hash_index *) stbds_header(a)->hash_table; + + if (table == NULL || table->used_count >= table->used_count_threshold) { + stbds_hash_index *nt; + size_t slot_count; + + slot_count = (table == NULL) ? STBDS_BUCKET_LENGTH : table->slot_count*2; + nt = stbds_make_hash_index(slot_count, table); + if (table) + STBDS_FREE(NULL, table); + else + nt->string.mode = mode >= STBDS_HM_STRING ? STBDS_SH_DEFAULT : 0; + stbds_header(a)->hash_table = table = nt; + STBDS_STATS(++stbds_hash_grow); + } + + // we iterate hash table explicitly because we want to track if we saw a tombstone + { + size_t hash = mode >= STBDS_HM_STRING ? stbds_hash_string((char*)key,table->seed) : stbds_hash_bytes(key, keysize,table->seed); + size_t step = STBDS_BUCKET_LENGTH; + size_t pos; + ptrdiff_t tombstone = -1; + stbds_hash_bucket *bucket; + + // stored hash values are forbidden from being 0, so we can detect empty slots to early out quickly + if (hash < 2) hash += 2; + + pos = stbds_probe_position(hash, table->slot_count, table->slot_count_log2); + + for (;;) { + size_t limit, i; + STBDS_STATS(++stbds_hash_probes); + bucket = &table->storage[pos >> STBDS_BUCKET_SHIFT]; + + // start searching from pos to end of bucket + for (i=pos & STBDS_BUCKET_MASK; i < STBDS_BUCKET_LENGTH; ++i) { + if (bucket->hash[i] == hash) { + if (stbds_is_key_equal(raw_a, elemsize, key, keysize, keyoffset, mode, bucket->index[i])) { + stbds_temp(a) = bucket->index[i]; + if (mode >= STBDS_HM_STRING) + stbds_temp_key(a) = * (char **) ((char *) raw_a + elemsize*bucket->index[i] + keyoffset); + return STBDS_ARR_TO_HASH(a,elemsize); + } + } else if (bucket->hash[i] == 0) { + pos = (pos & ~STBDS_BUCKET_MASK) + i; + goto found_empty_slot; + } else if (tombstone < 0) { + if (bucket->index[i] == STBDS_INDEX_DELETED) + tombstone = (ptrdiff_t) ((pos & ~STBDS_BUCKET_MASK) + i); + } + } + + // search from beginning of bucket to pos + limit = pos & STBDS_BUCKET_MASK; + for (i = 0; i < limit; ++i) { + if (bucket->hash[i] == hash) { + if (stbds_is_key_equal(raw_a, elemsize, key, keysize, keyoffset, mode, bucket->index[i])) { + stbds_temp(a) = bucket->index[i]; + return STBDS_ARR_TO_HASH(a,elemsize); + } + } else if (bucket->hash[i] == 0) { + pos = (pos & ~STBDS_BUCKET_MASK) + i; + goto found_empty_slot; + } else if (tombstone < 0) { + if (bucket->index[i] == STBDS_INDEX_DELETED) + tombstone = (ptrdiff_t) ((pos & ~STBDS_BUCKET_MASK) + i); + } + } + + // quadratic probing + pos += step; + step += STBDS_BUCKET_LENGTH; + pos &= (table->slot_count-1); + } + found_empty_slot: + if (tombstone >= 0) { + pos = tombstone; + --table->tombstone_count; + } + ++table->used_count; + + { + ptrdiff_t i = (ptrdiff_t) stbds_arrlen(a); + // we want to do stbds_arraddn(1), but we can't use the macros since we don't have something of the right type + if ((size_t) i+1 > stbds_arrcap(a)) + *(void **) &a = stbds_arrgrowf(a, elemsize, 1, 0); + raw_a = STBDS_ARR_TO_HASH(a,elemsize); + + STBDS_ASSERT((size_t) i+1 <= stbds_arrcap(a)); + stbds_header(a)->length = i+1; + bucket = &table->storage[pos >> STBDS_BUCKET_SHIFT]; + bucket->hash[pos & STBDS_BUCKET_MASK] = hash; + bucket->index[pos & STBDS_BUCKET_MASK] = i-1; + stbds_temp(a) = i-1; + + switch (table->string.mode) { + case STBDS_SH_STRDUP: stbds_temp_key(a) = *(char **) ((char *) a + elemsize*i) = stbds_strdup((char*) key); break; + case STBDS_SH_ARENA: stbds_temp_key(a) = *(char **) ((char *) a + elemsize*i) = stbds_stralloc(&table->string, (char*)key); break; + case STBDS_SH_DEFAULT: stbds_temp_key(a) = *(char **) ((char *) a + elemsize*i) = (char *) key; break; + default: memcpy((char *) a + elemsize*i, key, keysize); break; + } + } + return STBDS_ARR_TO_HASH(a,elemsize); + } +} + +void * stbds_shmode_func(size_t elemsize, int mode) +{ + void *a = stbds_arrgrowf(0, elemsize, 0, 1); + stbds_hash_index *h; + memset(a, 0, elemsize); + stbds_header(a)->length = 1; + stbds_header(a)->hash_table = h = (stbds_hash_index *) stbds_make_hash_index(STBDS_BUCKET_LENGTH, NULL); + h->string.mode = (unsigned char) mode; + return STBDS_ARR_TO_HASH(a,elemsize); +} + +void * stbds_hmdel_key(void *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode) +{ + if (a == NULL) { + return 0; + } else { + stbds_hash_index *table; + void *raw_a = STBDS_HASH_TO_ARR(a,elemsize); + table = (stbds_hash_index *) stbds_header(raw_a)->hash_table; + stbds_temp(raw_a) = 0; + if (table == 0) { + return a; + } else { + ptrdiff_t slot; + slot = stbds_hm_find_slot(a, elemsize, key, keysize, keyoffset, mode); + if (slot < 0) + return a; + else { + stbds_hash_bucket *b = &table->storage[slot >> STBDS_BUCKET_SHIFT]; + int i = slot & STBDS_BUCKET_MASK; + ptrdiff_t old_index = b->index[i]; + ptrdiff_t final_index = (ptrdiff_t) stbds_arrlen(raw_a)-1-1; // minus one for the raw_a vs a, and minus one for 'last' + STBDS_ASSERT(slot < (ptrdiff_t) table->slot_count); + --table->used_count; + ++table->tombstone_count; + stbds_temp(raw_a) = 1; + STBDS_ASSERT(table->used_count >= 0); + //STBDS_ASSERT(table->tombstone_count < table->slot_count/4); + b->hash[i] = STBDS_HASH_DELETED; + b->index[i] = STBDS_INDEX_DELETED; + + if (mode == STBDS_HM_STRING && table->string.mode == STBDS_SH_STRDUP) + STBDS_FREE(NULL, *(char**) ((char *) a+elemsize*old_index)); + + // if indices are the same, memcpy is a no-op, but back-pointer-fixup will fail, so skip + if (old_index != final_index) { + // swap delete + memmove((char*) a + elemsize*old_index, (char*) a + elemsize*final_index, elemsize); + + // now find the slot for the last element + if (mode == STBDS_HM_STRING) + slot = stbds_hm_find_slot(a, elemsize, *(char**) ((char *) a+elemsize*old_index + keyoffset), keysize, keyoffset, mode); + else + slot = stbds_hm_find_slot(a, elemsize, (char* ) a+elemsize*old_index + keyoffset, keysize, keyoffset, mode); + STBDS_ASSERT(slot >= 0); + b = &table->storage[slot >> STBDS_BUCKET_SHIFT]; + i = slot & STBDS_BUCKET_MASK; + STBDS_ASSERT(b->index[i] == final_index); + b->index[i] = old_index; + } + stbds_header(raw_a)->length -= 1; + + if (table->used_count < table->used_count_shrink_threshold && table->slot_count > STBDS_BUCKET_LENGTH) { + stbds_header(raw_a)->hash_table = stbds_make_hash_index(table->slot_count>>1, table); + STBDS_FREE(NULL, table); + STBDS_STATS(++stbds_hash_shrink); + } else if (table->tombstone_count > table->tombstone_count_threshold) { + stbds_header(raw_a)->hash_table = stbds_make_hash_index(table->slot_count , table); + STBDS_FREE(NULL, table); + STBDS_STATS(++stbds_hash_rebuild); + } + + return a; + } + } + } + /* NOTREACHED */ +} + +static char *stbds_strdup(char *str) +{ + // to keep replaceable allocator simple, we don't want to use strdup. + // rolling our own also avoids problem of strdup vs _strdup + size_t len = strlen(str)+1; + char *p = (char*) STBDS_REALLOC(NULL, 0, len); + memmove(p, str, len); + return p; +} + +#ifndef STBDS_STRING_ARENA_BLOCKSIZE_MIN +#define STBDS_STRING_ARENA_BLOCKSIZE_MIN 512u +#endif +#ifndef STBDS_STRING_ARENA_BLOCKSIZE_MAX +#define STBDS_STRING_ARENA_BLOCKSIZE_MAX (1u<<20) +#endif + +char *stbds_stralloc(stbds_string_arena *a, char *str) +{ + char *p; + size_t len = strlen(str)+1; + if (len > a->remaining) { + // compute the next blocksize + size_t blocksize = a->block; + + // size is 512, 512, 1024, 1024, 2048, 2048, 4096, 4096, etc., so that + // there are log(SIZE) allocations to free when we destroy the table + blocksize = (size_t) (STBDS_STRING_ARENA_BLOCKSIZE_MIN) << (blocksize>>1); + + // if size is under 1M, advance to next blocktype + if (blocksize < (size_t)(STBDS_STRING_ARENA_BLOCKSIZE_MAX)) + ++a->block; + + if (len > blocksize) { + // if string is larger than blocksize, then just allocate the full size. + // note that we still advance string_block so block size will continue + // increasing, so e.g. if somebody only calls this with 1000-long strings, + // eventually the arena will start doubling and handling those as well + stbds_string_block *sb = (stbds_string_block *) STBDS_REALLOC(NULL, 0, sizeof(*sb)-8 + len); + memmove(sb->storage, str, len); + if (a->storage) { + // insert it after the first element, so that we don't waste the space there + sb->next = a->storage->next; + a->storage->next = sb; + } else { + sb->next = 0; + a->storage = sb; + a->remaining = 0; // this is redundant, but good for clarity + } + return sb->storage; + } else { + stbds_string_block *sb = (stbds_string_block *) STBDS_REALLOC(NULL, 0, sizeof(*sb)-8 + blocksize); + sb->next = a->storage; + a->storage = sb; + a->remaining = blocksize; + } + } + + STBDS_ASSERT(len <= a->remaining); + p = a->storage->storage + a->remaining - len; + a->remaining -= len; + memmove(p, str, len); + return p; +} + +void stbds_strreset(stbds_string_arena *a) +{ + stbds_string_block *x,*y; + x = a->storage; + while (x) { + y = x->next; + STBDS_FREE(NULL, x); + x = y; + } + memset(a, 0, sizeof(*a)); +} + +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// UNIT TESTS +// + +#ifdef STBDS_UNIT_TESTS +#include +#ifdef STBDS_ASSERT_WAS_UNDEFINED +#undef STBDS_ASSERT +#endif +#ifndef STBDS_ASSERT +#define STBDS_ASSERT assert +#include +#endif + +typedef struct { int key,b,c,d; } stbds_struct; +typedef struct { int key[2],b,c,d; } stbds_struct2; + +static char buffer[256]; +char *strkey(int n) +{ +#if defined(_WIN32) && defined(__STDC_WANT_SECURE_LIB__) + sprintf_s(buffer, sizeof(buffer), "test_%d", n); +#else + sprintf(buffer, "test_%d", n); +#endif + return buffer; +} + +void stbds_unit_tests(void) +{ +#if defined(_MSC_VER) && _MSC_VER <= 1200 && defined(__cplusplus) + // VC6 C++ doesn't like the template<> trick on unnamed structures, so do nothing! + STBDS_ASSERT(0); +#else + const int testsize = 100000; + const int testsize2 = testsize/20; + int *arr=NULL; + struct { int key; int value; } *intmap = NULL; + struct { char *key; int value; } *strmap = NULL, s; + struct { stbds_struct key; int value; } *map = NULL; + stbds_struct *map2 = NULL; + stbds_struct2 *map3 = NULL; + stbds_string_arena sa = { 0 }; + int key3[2] = { 1,2 }; + ptrdiff_t temp; + + int i,j; + + STBDS_ASSERT(arrlen(arr)==0); + for (i=0; i < 20000; i += 50) { + for (j=0; j < i; ++j) + arrpush(arr,j); + arrfree(arr); + } + + for (i=0; i < 4; ++i) { + arrpush(arr,1); arrpush(arr,2); arrpush(arr,3); arrpush(arr,4); + arrdel(arr,i); + arrfree(arr); + arrpush(arr,1); arrpush(arr,2); arrpush(arr,3); arrpush(arr,4); + arrdelswap(arr,i); + arrfree(arr); + } + + for (i=0; i < 5; ++i) { + arrpush(arr,1); arrpush(arr,2); arrpush(arr,3); arrpush(arr,4); + stbds_arrins(arr,i,5); + STBDS_ASSERT(arr[i] == 5); + if (i < 4) + STBDS_ASSERT(arr[4] == 4); + arrfree(arr); + } + + i = 1; + STBDS_ASSERT(hmgeti(intmap,i) == -1); + hmdefault(intmap, -2); + STBDS_ASSERT(hmgeti(intmap, i) == -1); + STBDS_ASSERT(hmget (intmap, i) == -2); + for (i=0; i < testsize; i+=2) + hmput(intmap, i, i*5); + for (i=0; i < testsize; i+=1) { + if (i & 1) STBDS_ASSERT(hmget(intmap, i) == -2 ); + else STBDS_ASSERT(hmget(intmap, i) == i*5); + if (i & 1) STBDS_ASSERT(hmget_ts(intmap, i, temp) == -2 ); + else STBDS_ASSERT(hmget_ts(intmap, i, temp) == i*5); + } + for (i=0; i < testsize; i+=2) + hmput(intmap, i, i*3); + for (i=0; i < testsize; i+=1) + if (i & 1) STBDS_ASSERT(hmget(intmap, i) == -2 ); + else STBDS_ASSERT(hmget(intmap, i) == i*3); + for (i=2; i < testsize; i+=4) + hmdel(intmap, i); // delete half the entries + for (i=0; i < testsize; i+=1) + if (i & 3) STBDS_ASSERT(hmget(intmap, i) == -2 ); + else STBDS_ASSERT(hmget(intmap, i) == i*3); + for (i=0; i < testsize; i+=1) + hmdel(intmap, i); // delete the rest of the entries + for (i=0; i < testsize; i+=1) + STBDS_ASSERT(hmget(intmap, i) == -2 ); + hmfree(intmap); + for (i=0; i < testsize; i+=2) + hmput(intmap, i, i*3); + hmfree(intmap); + + #if defined(__clang__) || defined(__GNUC__) + #ifndef __cplusplus + intmap = NULL; + hmput(intmap, 15, 7); + hmput(intmap, 11, 3); + hmput(intmap, 9, 5); + STBDS_ASSERT(hmget(intmap, 9) == 5); + STBDS_ASSERT(hmget(intmap, 11) == 3); + STBDS_ASSERT(hmget(intmap, 15) == 7); + #endif + #endif + + for (i=0; i < testsize; ++i) + stralloc(&sa, strkey(i)); + strreset(&sa); + + { + s.key = "a", s.value = 1; + shputs(strmap, s); + STBDS_ASSERT(*strmap[0].key == 'a'); + STBDS_ASSERT(strmap[0].key == s.key); + STBDS_ASSERT(strmap[0].value == s.value); + shfree(strmap); + } + + { + s.key = "a", s.value = 1; + sh_new_strdup(strmap); + shputs(strmap, s); + STBDS_ASSERT(*strmap[0].key == 'a'); + STBDS_ASSERT(strmap[0].key != s.key); + STBDS_ASSERT(strmap[0].value == s.value); + shfree(strmap); + } + + { + s.key = "a", s.value = 1; + sh_new_arena(strmap); + shputs(strmap, s); + STBDS_ASSERT(*strmap[0].key == 'a'); + STBDS_ASSERT(strmap[0].key != s.key); + STBDS_ASSERT(strmap[0].value == s.value); + shfree(strmap); + } + + for (j=0; j < 2; ++j) { + STBDS_ASSERT(shgeti(strmap,"foo") == -1); + if (j == 0) + sh_new_strdup(strmap); + else + sh_new_arena(strmap); + STBDS_ASSERT(shgeti(strmap,"foo") == -1); + shdefault(strmap, -2); + STBDS_ASSERT(shgeti(strmap,"foo") == -1); + for (i=0; i < testsize; i+=2) + shput(strmap, strkey(i), i*3); + for (i=0; i < testsize; i+=1) + if (i & 1) STBDS_ASSERT(shget(strmap, strkey(i)) == -2 ); + else STBDS_ASSERT(shget(strmap, strkey(i)) == i*3); + for (i=2; i < testsize; i+=4) + shdel(strmap, strkey(i)); // delete half the entries + for (i=0; i < testsize; i+=1) + if (i & 3) STBDS_ASSERT(shget(strmap, strkey(i)) == -2 ); + else STBDS_ASSERT(shget(strmap, strkey(i)) == i*3); + for (i=0; i < testsize; i+=1) + shdel(strmap, strkey(i)); // delete the rest of the entries + for (i=0; i < testsize; i+=1) + STBDS_ASSERT(shget(strmap, strkey(i)) == -2 ); + shfree(strmap); + } + + { + struct { char *key; char value; } *hash = NULL; + char name[4] = "jen"; + shput(hash, "bob" , 'h'); + shput(hash, "sally" , 'e'); + shput(hash, "fred" , 'l'); + shput(hash, "jen" , 'x'); + shput(hash, "doug" , 'o'); + + shput(hash, name , 'l'); + shfree(hash); + } + + for (i=0; i < testsize; i += 2) { + stbds_struct s = { i,i*2,i*3,i*4 }; + hmput(map, s, i*5); + } + + for (i=0; i < testsize; i += 1) { + stbds_struct s = { i,i*2,i*3 ,i*4 }; + stbds_struct t = { i,i*2,i*3+1,i*4 }; + if (i & 1) STBDS_ASSERT(hmget(map, s) == 0); + else STBDS_ASSERT(hmget(map, s) == i*5); + if (i & 1) STBDS_ASSERT(hmget_ts(map, s, temp) == 0); + else STBDS_ASSERT(hmget_ts(map, s, temp) == i*5); + //STBDS_ASSERT(hmget(map, t.key) == 0); + } + + for (i=0; i < testsize; i += 2) { + stbds_struct s = { i,i*2,i*3,i*4 }; + hmputs(map2, s); + } + hmfree(map); + + for (i=0; i < testsize; i += 1) { + stbds_struct s = { i,i*2,i*3,i*4 }; + stbds_struct t = { i,i*2,i*3+1,i*4 }; + if (i & 1) STBDS_ASSERT(hmgets(map2, s.key).d == 0); + else STBDS_ASSERT(hmgets(map2, s.key).d == i*4); + //STBDS_ASSERT(hmgetp(map2, t.key) == 0); + } + hmfree(map2); + + for (i=0; i < testsize; i += 2) { + stbds_struct2 s = { { i,i*2 }, i*3,i*4, i*5 }; + hmputs(map3, s); + } + for (i=0; i < testsize; i += 1) { + stbds_struct2 s = { { i,i*2}, i*3, i*4, i*5 }; + stbds_struct2 t = { { i,i*2}, i*3+1, i*4, i*5 }; + if (i & 1) STBDS_ASSERT(hmgets(map3, s.key).d == 0); + else STBDS_ASSERT(hmgets(map3, s.key).d == i*5); + //STBDS_ASSERT(hmgetp(map3, t.key) == 0); + } +#endif +} +#endif + + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2019 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/src/cli.c b/src/cli.c index ec66421..f880067 100644 --- a/src/cli.c +++ b/src/cli.c @@ -1,7 +1,5 @@ -#include "stdio.h" - int main(int argc, char *argv[]) { - int embed_test(void); + int embed_test(); embed_test(); return 0; diff --git a/src/embedder.c b/src/embedder.c index e43ccff..1751d7b 100644 --- a/src/embedder.c +++ b/src/embedder.c @@ -1,6 +1,11 @@ -#include "stdio.h" +#define BH_DEFINE +#define BH_NO_TABLE +#define STB_DS_IMPLEMENTATION +#define BH_STATIC +#include "bh.h" +#include "stb_ds.h" -int embed_test(void) { +i32 embed_test() { printf("Build works.\n"); return 0; } \ No newline at end of file -- 2.25.1