--- /dev/null
+#include "data/handle.h"
+
+#include "data/string.h"
+#include "data/hashtable.h"
+
+#define FLAG_NONINTRINSIC (1ULL << 63)
+
+hV sbV_nil() {
+ return (hV) {0};
+}
+
+hV sbV_string(hString str) {
+ return (hV) {
+ .type = IT_STRING,
+ .string = str,
+ };
+}
+
+flag sbV_eq(hV *a, hV *b) {
+ if (a->type != b->type) return FALSE;
+ if (a->type == ITX_TOMBSTONE || b->type == ITX_TOMBSTONE) return FALSE;
+ if (a->type == IT_NOTHING || b->type == IT_NOTHING) return FALSE;
+
+ if (a->type == IT_STRING) {
+ return sbString_eq(a->string, b->string);
+ } else {
+ return a == b;
+ }
+}
+
+void sbV_retain(hV *a) {
+ /* for some classes (e.g. nil, float), we don't need to retain them at all
+ * because they are trivially copiable. some classes (integer, string) need
+ * to sometimes be retained but sometimes not, so we delegate to them to
+ * figure out if they need to or not. */
+ if (a->type == IT_STRING) {
+ sbString_clone(a->string);
+ }
+}
+
+void sbV_release(hV *a) {
+ if (a->type == IT_STRING) {
+ sbString_release(a->string);
+ }
+}
+
+/* --- */
+
+flag val_is_intrinsic(hV val) {
+ return (val.type & FLAG_NONINTRINSIC) != 0;
+}
--- /dev/null
+#ifndef __SARABANDE_HANDLE_H__
+#define __SARABANDE_HANDLE_H__
+
+#include "common.h"
+
+typedef u64 hHash;
+typedef u64 hString;
+typedef u64 hSymbol;
+
+typedef enum intrinsic_type {
+ IT_NOTHING, // sentinel for "no value here"
+ IT_NIL, // nil ("there is a value here, but it's nil")
+ IT_BOOLEAN, // true / false
+ IT_STRING, // `abcdefg`
+ IT_SYMBOL, // :hello
+ IT_INTEGER, // 324892
+ IT_FLOAT, // 0.0345
+ IT_DATETIME, // unix timestamp
+ IT_REF, // pointer \abc
+ IT_LIST, // list [1, 3, 5, 7]
+ IT_HASH, // hash {a: 1, b: 2}
+ IT_FUNCTION, // function => a, b { a + b }
+ N_INTRINSIC_TYPES,
+ ITX_TOMBSTONE, // <hashtable_tombstone>
+} intrinsic_type;
+
+typedef struct hV {
+ u64 type;
+ union {
+ hString string;
+ hSymbol symbol;
+ hHash hash;
+ };
+} hV;
+
+hV sbV_nil();
+hV sbV_string(hString str);
+
+flag sbV_eq(hV *a, hV *b);
+
+void sbV_retain(hV *a);
+void sbV_release(hV *a);
+
+#endif
--- /dev/null
+#include "data/hashtable.h"
+
+#include "data/string.h"
+#include "data/handle.h"
+
+#define HASHES_PER_BLOCK 256
+#define INLINE_TABLE_LENGTH 64
+
+/* TODO eliminate globals */
+sbBuffer g_hashtable_blocks = {0};
+
+typedef struct hashentry {
+ hV key;
+ hV value;
+} hashentry;
+
+typedef struct hashtbl {
+ usize size;
+ usize used;
+ hHash handle;
+ flag allocated;
+ flag is_inline;
+ union {
+ hashentry inline_entries[INLINE_TABLE_LENGTH];
+ hashentry *external_entries;
+ };
+} hashtbl;
+
+typedef struct hashblk {
+ usize id;
+ usize used_count;
+ usize last_index;
+ hashtbl tables[HASHES_PER_BLOCK];
+} hashblk;
+
+static hashblk *alloc_new_block();
+static hashtbl *new_tbl(usize initial_size);
+static hashentry *get_entry_ptr_for_tbl(hashtbl *t, usize *length_out);
+static hashtbl *find_tbl_for_handle(hHash handle);
+static hashentry *set_key(hashtbl *t, hV *key, hV *value);
+static hashentry delete_key(hashtbl *t, hV *key);
+static void set_hashtbl_size(hashtbl *t, usize new_size, flag rehash_all);
+static hashentry *set_key_in_array(hashentry *entries, usize length, hV *key, hV *value);
+static hashentry *find_entry_by_key(hashtbl *t, hV *key);
+
+void sbHash_sys_init() {
+ sbBuffer_initialize(&g_hashtable_blocks, 8 * sizeof(hashblk*));
+ alloc_new_block();
+}
+
+void sbHash_sys_deinit() {
+ //sbBuffer_deinitialize(&g_hashtable_blocks);
+}
+
+/* https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function */
+#define FNV_OFFSET_BASIS 0xcbf29ce484222325ULL
+#define FNV_PRIME 0x100000001b3ULL
+sbHashValue sbHash_hash_bytes(const char *bytes, usize length) {
+ sbHashValue result = FNV_OFFSET_BASIS;
+ for (usize i = 0; i < length; i++) {
+ result ^= bytes[i];
+ result *= FNV_PRIME;
+ }
+ return result;
+}
+
+sbHashValue sbHash_hash_obj(hV *obj) {
+ if (obj->type == IT_NIL) {
+ char zero = 0;
+ return sbHash_hash_bytes(&zero, 1);
+ } else if (obj->type == IT_STRING) {
+ char scratch[8];
+ usize length;
+ const char *strval = sbString_get_value(obj->string, scratch, &length);
+ return sbHash_hash_bytes(strval, length);
+ } else {
+ PANIC("don't know how to hash object of type %lld", obj->type);
+ }
+}
+
+hHash sbHash_create(usize initial_size) {
+ hashtbl *t = new_tbl(initial_size);
+ return t->handle;
+}
+
+void sbHash_insert(hHash h, hV *key, hV *value) {
+ hashtbl *t = find_tbl_for_handle(h);
+ set_key(t, key, value);
+}
+
+hV sbHash_find(hHash h, hV *key) {
+ hashtbl *t = find_tbl_for_handle(h);
+ hashentry *e = find_entry_by_key(t, key);
+ if (e->key.type == IT_NOTHING) {
+ return (hV) { .type = IT_NOTHING };
+ } else {
+ return e->value;
+ }
+}
+
+hV sbHash_find_or_insert(hHash h, hV *key, hV *value) {
+ hashtbl *t = find_tbl_for_handle(h);
+ hashentry *e = find_entry_by_key(t, key);
+ if (e->key.type == IT_NOTHING) {
+ e->value = *value;
+ }
+ return e->value;
+}
+
+void sbHash_delete(hHash h, hV *key, hV *value) {
+ hashtbl *t = find_tbl_for_handle(h);
+ delete_key(t, key);
+}
+
+/* --- */
+
+static hashblk *alloc_new_block() {
+ usize nblocks = g_hashtable_blocks.size / sizeof(hashblk*);
+ hashblk *new_block = calloc(1, sizeof(hashblk));
+ new_block->id = nblocks;
+ sbBuffer_append(&g_hashtable_blocks, &new_block, sizeof(hashblk*));
+ return new_block;
+}
+
+static hashblk *find_free_block() {
+ usize nblocks = g_hashtable_blocks.size / sizeof(hashblk*);
+ hashblk *free_block = NULL;
+ for (usize i = 0; i < nblocks; i++) {
+ if (((hashblk**)g_hashtable_blocks.data)[i]->used_count < HASHES_PER_BLOCK) {
+ free_block = ((hashblk**)g_hashtable_blocks.data)[i];
+ break;
+ }
+ }
+
+ if (free_block == NULL) {
+ free_block = alloc_new_block();
+ }
+
+ return free_block;
+}
+
+static hashblk *get_block(usize index) {
+ usize nblocks = g_hashtable_blocks.size / sizeof(hashblk*);
+ if (index > nblocks) PANIC("request for a hash block (#%zu) that does not exist!", index);
+ return ((hashblk**)g_hashtable_blocks.data)[index];
+}
+
+static hashtbl *find_free_entry(hashblk *blk) {
+ if (blk->used_count >= HASHES_PER_BLOCK) PANIC("request for new table in full hashblk!");
+
+ while (blk->tables[blk->last_index].allocated) {
+ blk->last_index++;
+ blk->last_index %= HASHES_PER_BLOCK;
+ }
+ blk->tables[blk->last_index].allocated = 1;
+ blk->used_count ++;
+
+ hashtbl *result = &blk->tables[blk->last_index];
+
+ result->handle = blk->id * HASHES_PER_BLOCK + blk->last_index;
+
+ return result;
+}
+
+static void set_hashtbl_size(hashtbl *t, usize new_size, flag rehash_all) {
+ if (new_size < INLINE_TABLE_LENGTH) new_size = INLINE_TABLE_LENGTH;
+ if (new_size < t->size) PANIC("cannot shrink hashtable allocation");
+
+ if (new_size == INLINE_TABLE_LENGTH) {
+ t->is_inline = 1;
+ } else {
+ hashentry *new_data = calloc(new_size, sizeof(hashentry));
+ if (rehash_all) {
+ hashentry *current_data = get_entry_ptr_for_tbl(t, NULL);
+ for (usize i = 0; i < t->size; i++) {
+ if (current_data[i].key.type != IT_NOTHING && current_data[i].key.type != ITX_TOMBSTONE) {
+ set_key_in_array(new_data, new_size, ¤t_data[i].key, ¤t_data[i].value);
+ /* adding a new k/v pair will retain the values, so we need to release them
+ * from the previous allocation */
+ sbV_release(¤t_data[i].key);
+ sbV_release(¤t_data[i].value);
+ }
+ }
+ /* now that we've migrated all our things to the new version, we can free the old
+ * version (unless it was not alloc'd to begin with) */
+ if (!t->is_inline) free(current_data);
+ }
+ t->external_entries = new_data;
+ t->is_inline = 0;
+ }
+ t->size = new_size;
+}
+
+static hashentry *get_entry_ptr_for_tbl(hashtbl *t, usize *length_out) {
+ if (length_out) *length_out = t->size;
+ if (t->is_inline) {
+ return t->inline_entries;
+ } else {
+ return t->external_entries;
+ }
+}
+
+static hashentry *find_entry_in_array(hashentry *entries, usize length, hV *key) {
+ sbHashValue hash = sbHash_hash_obj(key);
+ u32 start = (hash & 0xFFFFFFFF);
+ u32 move = (hash >> 32);
+ while (length % move == 0) {
+ move ++;
+ }
+ usize index = start % length;
+ while (!sbV_eq(&entries[index].key, key) && entries[index].key.type != IT_NOTHING) {
+ index += move;
+ index %= length;
+ }
+ return &entries[index];
+}
+
+static hashentry *find_entry_by_key(hashtbl *t, hV *key) {
+ return find_entry_in_array(get_entry_ptr_for_tbl(t, NULL), t->size, key);
+}
+
+static hashentry *set_key_in_array(hashentry *entries, usize length, hV *key, hV *value) {
+ hashentry *location = find_entry_in_array(entries, length, key);
+
+ /* now, we either found the current entry for this key,
+ * or we found an empty slot that fits this key. */
+ if (location->key.type == IT_NOTHING) {
+ /* key was not here before, so we need to retain the key
+ * as well so it doesn't change out from under us */
+ sbV_retain(key);
+ location->key = *key;
+ } else {
+ /* replacing something that already exists. we can keep
+ * the key the same, but need to release the previous value. */
+ sbV_release(&location->value);
+ }
+
+ sbV_retain(value);
+ location->value = *value;
+
+ return location;
+}
+
+static hashentry *set_key(hashtbl *t, hV *key, hV *value) {
+ hashentry *location = find_entry_by_key(t, key);
+
+ /* now, we either found the current entry for this key,
+ * or we found an empty slot that fits this key. */
+ if (location->key.type == IT_NOTHING) {
+ /* key was not here before, so we need to retain the key
+ * as well so it doesn't change out from under us */
+ sbV_retain(key);
+ location->key = *key;
+ t->used ++;
+ if (t->used >= t->size * 3 / 4) {
+ set_hashtbl_size(t, t->size * 2, TRUE);
+ }
+ } else {
+ /* replacing something that already exists. we can keep
+ * the key the same, but need to release the previous value. */
+ sbV_release(&location->value);
+ }
+
+ sbV_retain(value);
+ location->value = *value;
+
+ return location;
+}
+
+static hashentry delete_key(hashtbl *t, hV *key) {
+ hashentry *location = find_entry_by_key(t, key);
+ hashentry to_return = *location;
+ location->key.type = ITX_TOMBSTONE;
+ return to_return;
+}
+
+static hashtbl *find_tbl_for_handle(hHash handle) {
+ hashblk *block = get_block(handle / HASHES_PER_BLOCK);
+ return &block->tables[handle % HASHES_PER_BLOCK];
+}
+
+static hashtbl *new_tbl(usize initial_size) {
+ hashtbl *t = find_free_entry(find_free_block());
+ set_hashtbl_size(t, initial_size, FALSE);
+ return t;
+}
--- /dev/null
+#include "common.h"
+
+#include "data/handle.h"
+
+/* Open-hashing hash table of object handle keys -> object handle values. */
+
+typedef u64 sbHashValue;
+
+void sbHash_sys_init();
+
+void sbHash_sys_deinit();
+
+sbHashValue sbHash_hash_bytes(const char *bytes, usize length);
+
+sbHashValue sbHash_hash_obj(hV *obj);
+
+hHash sbHash_create(usize initial_size);
+
+hV sbHash_find(hHash h, hV *key);
+
+void sbHash_insert(hHash h, hV *key, hV *value);
+
+hV sbHash_find_or_insert(hHash h, hV *key, hV *value);
+
+void sbHash_delete(hHash h, hV *key, hV *value);
#define OBJSL(value) (sbString_new(value, sizeof(value) - 1))
-typedef u64 hString;
+#include "data/handle.h"
/* create hString:
*
--- /dev/null
+#include "symbol.h"
+
+#include "data/string.h"
+#include "data/hashtable.h"
+#include <string.h>
+#include "mem/mem.h"
+
+#define NAME_BLOCK_SIZE 4096
+
+/* TODO globals. though, this might be ok? */
+hHash symbol_table;
+u64 next_symbol_id = 0;
+sbArena symbol_text_arena;
+
+hSymbol new_symbol(const char *name, usize length);
+
+hSymbol sbSymbol_from_bytes(const char *text, usize length) {
+ hV symkey = {
+ .type = IT_STRING,
+ .string = sbString_new(text, length),
+ };
+
+ hV result = sbHash_find(symbol_table, &symkey);
+
+ if (result.type == IT_NOTHING) {
+ /* add new symbol */
+ hV symval = {
+ .type = IT_SYMBOL,
+ .symbol = new_symbol(text, length),
+ };
+
+ sbHash_insert(symbol_table, &symkey, &symval);
+ return symval.symbol;
+ } else if (result.type == IT_SYMBOL) {
+ /* return already existing symbol */
+ return result.symbol;
+ } else {
+ PANIC("Only symbol values are allowed in the symbol table!");
+ }
+}
+
+hSymbol sbSymbol_from_string(hString str) {
+ char scratch[8];
+ usize length;
+ const char *data = sbString_get_value(str, scratch, &length);
+ return sbSymbol_from_bytes(data, length);
+}
+
+void sbSymbol_sys_init() {
+ symbol_table = sbHash_create(256);
+ sbArena_initialize(&symbol_text_arena, NAME_BLOCK_SIZE);
+ /* create symbol 0 as empty symbol */
+ sbSymbol_from_bytes("", 0);
+}
+
+char *sbSymbol_name(hSymbol sym) {
+ return (char*)sym;
+}
+
+/* --- */
+
+hSymbol new_symbol(const char *name, usize length) {
+ char *name_loc = sbArena_alloc(&symbol_text_arena, length + 1);
+ memcpy(name_loc, name, length);
+ name_loc[length] = '\0';
+ return (hSymbol)name_loc;
+}
+
--- /dev/null
+#include "common.h"
+
+#include "handle.h"
+
+void sbSymbol_sys_init();
+
+hSymbol sbSymbol_from_string(hString str);
+
+hSymbol sbSymbol_from_bytes(const char *text, usize length);
+
+char *sbSymbol_name(hSymbol sym);
#include "parse/filereader.h"
#include "parse/lexer.h"
#include "data/string.h"
+#include "data/hashtable.h"
+#include "data/symbol.h"
int main(int argc, char **argv) {
if (argc == 2) {
sbLexer_initialize(&lx, fr);
sbString_sys_init();
+ sbHash_sys_init();
+ sbSymbol_sys_init();
sbLexToken t;
char scratch[8];
char data[];
};
-struct sbArena {
- struct block *first;
- struct block *current;
- struct block *last;
-};
-
-hArena sbArena_create(usize initial_size) {
- hArena arena = malloc(sizeof(struct sbArena));
-
+void sbArena_initialize(hArena arena, usize initial_size) {
while (initial_size % ALIGN != 0) initial_size++;
struct block *block = calloc(sizeof(struct block) + initial_size, 1);
arena->first = block;
arena->current = block;
arena->last = block;
-
- return arena;
}
void *sbArena_alloc(hArena arena, usize size) {
blk = blk_next;
} while (blk);
- free(arena);
+ *arena = (sbArena) {0};
}
* For a flexible-length contiguous buffer that may need to grow in length, use sbBuffer instead.
* (however, sbBuffer doesn't have the 'pointer validity' guarantee.) */
+typedef struct sbArena {
+ struct block *first;
+ struct block *current;
+ struct block *last;
+} sbArena;
+
typedef struct sbArena *hArena;
-hArena sbArena_create(usize initial_size);
+void sbArena_initialize(hArena arena, usize initial_size);
void *sbArena_alloc(hArena arena, usize size);
}
}
-void sbBuffer_append(hBuffer buf, const char *data, usize data_length) {
+void sbBuffer_append(hBuffer buf, const void *data, usize data_length) {
if (data_length == 0) return;
char *put = sbBuffer_expand(buf, data_length);
void sbBuffer_set_size(hBuffer buf, usize new_size);
-void sbBuffer_append(hBuffer buf, const char *data, usize data_length);
+void sbBuffer_append(hBuffer buf, const void *data, usize data_length);
void sbBuffer_reset(hBuffer buf);
#include "scanner.h"
+#include "data/string.h"
+#include "data/symbol.h"
+
/* This is like, the pre lexing stage. I mean, this does a lot of the tokenizing,
* but it includes stuff like spaces and newlines, too. Think of this as 'normalizing'
* the input. The lexer in the second stage will use some additional context and state
static sbLexToken compute_next_token(hScanner sc);
void sbScanner_initialize(hScanner sc, hFileReader fr) {
- sc->arena = sbArena_create(MEM_BLOCK_SIZE);
+ sbArena_initialize(&sc->arena, MEM_BLOCK_SIZE);
sc->file_reader = fr;
sc->next_token = (sbLexToken) {0};
}
void sbScanner_deinitialize(hScanner sc) {
- sbArena_destroy(sc->arena);
+ sbArena_destroy(&sc->arena);
sbBuffer_deinitialize(&sc->dynamic_buffer);
}
}
}
-static void *save_buffer(hScanner sc) {
- char *storage = sbArena_alloc(sc->arena, sc->dynamic_buffer.size);
- sbstrncpy(storage, sc->dynamic_buffer.data, sc->dynamic_buffer.size);
- return storage;
-}
-
static usize read_identifier(hScanner sc) {
usize token_size = 0;
char ch = PEEK;
new_token.type = T_SYMBOL;
token_size = read_symbol(sc);
- char *storage = save_buffer(sc);
-
- new_token.cstr = storage;
+ new_token.symb = sbSymbol_from_bytes(sc->dynamic_buffer.data, sc->dynamic_buffer.size - 1);
+ printf("%s -> %p\n", (char*)new_token.symb, (void*)new_token.symb);
new_token.size = token_size;
} else {
/* : */
new_token.type = T_IDENTIFIER;
token_size = read_identifier(sc);
- char *storage = save_buffer(sc);
-
- new_token.cstr = storage;
+ new_token.symb = sbSymbol_from_bytes(sc->dynamic_buffer.data, sc->dynamic_buffer.size - 1);
+ printf("%s -> %p\n", (char*)new_token.symb, (void*)new_token.symb);
new_token.size = token_size;
} else if (is_digit(ch)) {
new_token.type = T_INTEGER;
typedef struct sbScanner {
sbLexToken next_token;
hFileReader file_reader;
- hArena arena;
+ sbArena arena;
sbBuffer dynamic_buffer;
} sbScanner;
#include "common.h"
-#include "data/string.h"
+#include "data/handle.h"
typedef enum sbTokenType {
T_NULL = 0,
union {
char *cstr;
hString hstr;
+ hSymbol symb;
float fl;
int i;
};