From: cassowarii Date: Wed, 1 Jul 2026 04:56:44 +0000 (-0700) Subject: hash literals!! + read indexing X-Git-Url: https://www.git.cassowary.me/gitweb.cgi?a=commitdiff_plain;h=64259ffef769c7f4824bd85e5f5cf4c02195071e;p=sarabande.git hash literals!! + read indexing --- diff --git a/Makefile b/Makefile index 85b4716..a52d086 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ CC=gcc -CFLAGS=-Wall -g -DDEBUG -fsanitize=undefined +CFLAGS=-Wall -g -Og -DDEBUG -fsanitize=undefined #CFLAGS=-Wall -O3 -flto LIBS= diff --git a/src/compile/emit.c b/src/compile/emit.c index 46847d9..f208e3a 100644 --- a/src/compile/emit.c +++ b/src/compile/emit.c @@ -192,6 +192,21 @@ void compile_list(sbVmCompiler *cm, sbIrExpr *expr) { EARG(count); /* calling convention: store argument count on stack */ } +void compile_hash(sbVmCompiler *cm, sbIrExpr *expr) { + sbIrExpr *considering = expr; + usize count = 0; + while (considering) { + count ++; + /* alternate keys and values on stack. push keys first, + * then values */ + compile_expr(cm, considering->list.this->list.this); + compile_expr(cm, considering->list.this->list.next); + considering = considering->list.next; + } + EMIT(BC_LD_IMM); + EARG(count); /* calling convention: store argument count on stack */ +} + void compile_op(sbVmCompiler *cm, sbAstOp op); void compile_expr(sbVmCompiler *cm, sbIrExpr *expr) { switch(expr->type) { @@ -220,6 +235,10 @@ void compile_expr(sbVmCompiler *cm, sbIrExpr *expr) { compile_list(cm, expr); EMIT(BC_LIST_GATHER); break; + case IR_E_HASH: + compile_hash(cm, expr); + EMIT(BC_HASH_GATHER); + break; case IR_E_VALUE: if (expr->value.type == IT_NIL) { EMIT(BC_LD_NIL); @@ -254,6 +273,7 @@ void compile_op(sbVmCompiler *cm, sbAstOp op) { case AST_OP_LE: EMIT(BC_OP_LE); break; case AST_OP_GE: EMIT(BC_OP_LT, BC_OP_NOT); break; case AST_OP_INDEX: EMIT(BC_OP_INDEX); break; + case AST_OP_SCOPE: EMIT(BC_OP_SCOPE); break; default: debug("unknown operation!\n"); } diff --git a/src/compile/ir.c b/src/compile/ir.c index 413e4f2..c635d44 100644 --- a/src/compile/ir.c +++ b/src/compile/ir.c @@ -249,6 +249,13 @@ static sbIrExpr *expr_list(hIrChunk ck, sbIrExpr *value) { }); } +static sbIrExpr *expr_hash(hIrChunk ck, sbIrExpr *value) { + return new_expr(ck, &(sbIrExpr) { + .type = IR_E_HASH, + .list.this = value, + }); +} + static void put_ir_stmt(hIrChunk ck, sbIrStmt *stmt) { sbIrStmt *where_to_put = sbArena_alloc(&ck->program->arena, sizeof(sbIrStmt)); memcpy(where_to_put, stmt, sizeof(sbIrStmt)); @@ -739,6 +746,26 @@ static sbIrExpr *compile_ast_list(hIrChunk ck, sbAst node) { return list; } +static sbIrExpr *compile_ast_hash(hIrChunk ck, sbAst node) { + sbAst considering = node; + sbIrExpr *list = NULL; + sbIrExpr **place_here = &list; + while (considering != NO_NODE) { + /* we'll just make a list of "lists" that are actually just cons-cells of a key and value */ + sbAst hash_entry = considering->seq.left; + if (hash_entry->type != AST_NODE_HASHENTRY) { + PANIC("Hash table literals should only contain hashentries (%d)", hash_entry->type); + } + sbIrExpr *compiled_entry = expr_list(ck, compile_ast_expr(ck, hash_entry->seq.left)); // key + compiled_entry->list.next = compile_ast_expr(ck, hash_entry->seq.right); // value + + *place_here = expr_hash(ck, compiled_entry); + place_here = &(*place_here)->list.next; + considering = considering->seq.right; + } + return list; +} + static sbIrExpr *compile_ast_expr(hIrChunk ck, sbAst node) { if (node == NO_NODE) return NULL; @@ -752,6 +779,9 @@ static sbIrExpr *compile_ast_expr(hIrChunk ck, sbAst node) { } else if (node->type == AST_VAL_LIST) { sbIrExpr *list = compile_ast_list(ck, node->seq.left); return list; + } else if (node->type == AST_VAL_HASH) { + sbIrExpr *hash = compile_ast_hash(ck, node->seq.left); + return hash; } else if (node->type == AST_NODE_OP) { sbIrExpr *left = NULL, *right = NULL; if (node->op.left != NO_NODE) { @@ -765,6 +795,8 @@ static sbIrExpr *compile_ast_expr(hIrChunk ck, sbAst node) { return expr_value(ck, &HVINT(node->i)); } else if (node->type == AST_VAL_STRING) { return expr_value(ck, &HVSTR(node->str)); + } else if (node->type == AST_VAL_SYMBOL) { + return expr_value(ck, &HVSYM(node->symb)); } else if (node->type == AST_NODE_NAME) { /* TODO: I don't want to use strlen. Can we remember the lengths of symbols? */ sbIrVariable *var = compile_ast_var(ck, node); diff --git a/src/compile/ir.h b/src/compile/ir.h index d6f22f7..057948e 100644 --- a/src/compile/ir.h +++ b/src/compile/ir.h @@ -47,6 +47,7 @@ typedef enum sbIrExprType { IR_E_FUNC, IR_E_CALL, IR_E_LIST, + IR_E_HASH, } sbIrExprType; typedef struct sbIrLabel { diff --git a/src/data/hashtable.c b/src/data/hashtable.c index 355e119..0fe91fe 100644 --- a/src/data/hashtable.c +++ b/src/data/hashtable.c @@ -8,11 +8,6 @@ /* TODO eliminate globals */ sbPool g_hashtable_pool = {0}; -typedef struct hashentry { - hV key; - hV value; -} hashentry; - typedef struct hashtbl { usize size; usize used; @@ -20,20 +15,26 @@ typedef struct hashtbl { flag allocated; flag is_inline; union { - /* TODO should be SoA instead of AoS */ - hashentry inline_entries[INLINE_TABLE_LENGTH]; - hashentry *external_entries; + struct { + hV keys[INLINE_TABLE_LENGTH]; + hV values[INLINE_TABLE_LENGTH]; + } internal; + struct { + hV *keys; + hV *values; + } external; }; } hashtbl; static hashtbl *new_tbl(usize initial_size); -static hashentry *get_entry_ptr_for_tbl(hashtbl *t, usize *length_out); +static hV *get_key_ptr_for_tbl(hashtbl *t, usize *length_out); +static void get_ptrs_for_tbl(hashtbl *t, hV **keys, hV **values); 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 usize set_key(hashtbl *t, hV *key, hV *value); +static hV 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); +static usize set_key_in_array(hV *keys, hV *values, usize length, hV *key, hV *value); +static usize find_index_by_key(hashtbl *t, hV *key); void sbHash_sys_init() { sbPool_initialize(&g_hashtable_pool, sizeof(hashtbl), HASHES_PER_BLOCK); @@ -59,6 +60,11 @@ 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_SYMBOL) { + return sbHash_hash_bytes((char*)&obj->symbol, sizeof(obj->symbol)); + } else if (obj->type == IT_INTEGER) { + /* TODO bigint should be different i think */ + return sbHash_hash_bytes((char*)&obj->integer, sizeof(obj->integer)); } else if (obj->type == IT_STRING) { char scratch[8]; usize length; @@ -81,21 +87,25 @@ void sbHash_insert(hHash h, hV *key, hV *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 }; + usize index = find_index_by_key(t, key); + hV *keys, *values; + get_ptrs_for_tbl(t, &keys, &values); + if (keys[index].type == IT_NOTHING) { + return HVNOTHING; } else { - return e->value; + return values[index]; } } 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; + usize index = find_index_by_key(t, key); + hV *keys, *values; + get_ptrs_for_tbl(t, &keys, &values); + if (keys[index].type == IT_NOTHING) { + values[index] = *value; } - return e->value; + return values[index]; } void sbHash_delete(hHash h, hV *key, hV *value) { @@ -112,38 +122,53 @@ static void set_hashtbl_size(hashtbl *t, usize new_size, flag rehash_all) { if (new_size == INLINE_TABLE_LENGTH) { t->is_inline = 1; } else { - hashentry *new_data = calloc(new_size, sizeof(hashentry)); + hV *new_keys = calloc(new_size, sizeof(hV)); + hV *new_values = calloc(new_size, sizeof(hV)); if (rehash_all) { - hashentry *current_data = get_entry_ptr_for_tbl(t, NULL); + hV *current_keys, *current_values; + get_ptrs_for_tbl(t, ¤t_keys, ¤t_values); 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); - } + if (current_keys[i].type == IT_NOTHING || current_keys[i].type == ITX_TOMBSTONE) continue; + set_key_in_array(new_keys, new_values, new_size, ¤t_keys[i], ¤t_values[i]); + /* adding a new k/v pair will retain the values, so we need to release them + * from the previous allocation */ + sbV_release(¤t_keys[i]); + sbV_release(¤t_values[i]); } /* 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); + if (!t->is_inline) { + free(current_keys); + free(current_values); + } } - t->external_entries = new_data; + t->external.keys = new_keys; + t->external.values = new_values; t->is_inline = 0; } t->size = new_size; } -static hashentry *get_entry_ptr_for_tbl(hashtbl *t, usize *length_out) { +static hV *get_key_ptr_for_tbl(hashtbl *t, usize *length_out) { if (length_out) *length_out = t->size; if (t->is_inline) { - return t->inline_entries; + return t->internal.keys; + } else { + return t->external.keys; + } +} + +static void get_ptrs_for_tbl(hashtbl *t, hV **keys, hV **values) { + if (t->is_inline) { + if (keys) *keys = t->internal.keys; + if (values) *values = t->internal.values; } else { - return t->external_entries; + if (keys) *keys = t->external.keys; + if (values) *values = t->external.values; } } -static hashentry *find_entry_in_array(hashentry *entries, usize length, hV *key) { +static usize find_key_index_in_array(hV *keys, usize length, hV *key) { sbHashValue hash = sbHash_hash_obj(key); u32 start = (hash & 0xFFFFFFFF); u32 move = (hash >> 32); @@ -151,49 +176,53 @@ static hashentry *find_entry_in_array(hashentry *entries, usize length, hV *key) move ++; } usize index = start % length; - while (!sbV_c_eq(&entries[index].key, key) && entries[index].key.type != IT_NOTHING) { + while (!sbV_c_eq(&keys[index], key) && keys[index].type != IT_NOTHING) { index += move; index %= length; } - return &entries[index]; + return 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 usize find_index_by_key(hashtbl *t, hV *key) { + return find_key_index_in_array(get_key_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); +static usize set_key_in_array(hV *keys, hV *values, usize length, hV *key, hV *value) { + usize index = find_key_index_in_array(keys, 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) { + if (keys[index].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; + keys[index] = *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_release(&values[index]); } sbV_retain(value); - location->value = *value; + values[index] = *value; - return location; + return index; } -static hashentry *set_key(hashtbl *t, hV *key, hV *value) { - hashentry *location = find_entry_by_key(t, key); +static usize set_key(hashtbl *t, hV *key, hV *value) { + usize index = find_index_by_key(t, key); + + hV *keys, *values; + get_ptrs_for_tbl(t, &keys, &values); /* 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) { + if (keys[index].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; + keys[index] = *key; + values[index] = *value; t->used ++; if (t->used >= t->size * 3 / 4) { set_hashtbl_size(t, t->size * 2, TRUE); @@ -201,19 +230,21 @@ static hashentry *set_key(hashtbl *t, hV *key, hV *value) { } 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_release(&values[index]); } sbV_retain(value); - location->value = *value; + values[index] = *value; - return location; + return index; } -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; +static hV delete_key(hashtbl *t, hV *key) { + usize index = find_index_by_key(t, key); + hV *keys, *values; + get_ptrs_for_tbl(t, &keys, &values); + hV to_return = values[index]; + keys[index].type = ITX_TOMBSTONE; return to_return; } diff --git a/src/data/symbol.c b/src/data/symbol.c index f2b5bf2..d4838d7 100644 --- a/src/data/symbol.c +++ b/src/data/symbol.c @@ -33,7 +33,7 @@ hSymbol sbSymbol_from_bytes(const char *text, usize length) { /* return already existing symbol */ return result.symbol; } else { - PANIC("Only symbol values are allowed in the symbol table!"); + PANIC("Only symbol values are allowed in the symbol table! (%lld)", result.type); } } diff --git a/src/data/value.c b/src/data/value.c index 1c99267..3563e1d 100644 --- a/src/data/value.c +++ b/src/data/value.c @@ -66,6 +66,13 @@ hV sbV_empty_list(usize capacity) { }; } +hV sbV_empty_hash(usize capacity) { + return (hV) { + .type = IT_HASH, + .hash = sbHash_create(capacity), + }; +} + flag sbV_c_eq(const hV *a, const hV *b) { if (a->type != b->type) return FALSE; if (a->type == ITX_TOMBSTONE || b->type == ITX_TOMBSTONE) return FALSE; diff --git a/src/data/value.h b/src/data/value.h index 1375e7f..9c615e0 100644 --- a/src/data/value.h +++ b/src/data/value.h @@ -5,10 +5,12 @@ #define HVINT(n) ((hV) { .type = IT_INTEGER, .integer = n }) #define HVSTR(s) ((hV) { .type = IT_STRING, .string = s }) +#define HVSYM(s) ((hV) { .type = IT_SYMBOL, .symbol = s }) #define HVBOOL(b) ((hV) { .type = IT_BOOLEAN, .boolean = b }) #define HVFUNC(i) ((hV) { .type = IT_FUNCTION, .data = i }) #define HVLIST(l) ((hV) { .type = IT_LIST, .list = l }) #define HVNIL ((hV) { .type = IT_NIL }) +#define HVNOTHING ((hV) {0}) typedef u64 hHash; typedef u64 hString; @@ -55,6 +57,7 @@ hV sbV_int(hInteger i); hV sbV_boolean(flag b); hV sbV_function(u64 id); hV sbV_empty_list(usize capacity); +hV sbV_empty_hash(usize capacity); flag sbV_c_eq(const hV *a, const hV *b); flag sbV_c_falsy(const hV *a); diff --git a/src/parse/parser.c b/src/parse/parser.c index cc17a33..0690d1d 100644 --- a/src/parse/parser.c +++ b/src/parse/parser.c @@ -462,11 +462,6 @@ static sbAst parse_name_as_sym(hParser pr) { } static sbAst parse_hash_key(hParser pr) { - sbLexToken t = peek_ahead(pr, 0); - if (t.type == T_IDENTIFIER) { - return parse_name(pr); - } - sbAst literal = parse_literal(pr); if (literal != NO_NODE) { return literal; @@ -475,7 +470,7 @@ static sbAst parse_hash_key(hParser pr) { if (!expect(pr, ']')) return syntax_error(pr); return key; } else { - return NO_NODE; + return parse_name_as_sym(pr); } } diff --git a/src/vm/bytecode.h b/src/vm/bytecode.h index b830490..769a364 100644 --- a/src/vm/bytecode.h +++ b/src/vm/bytecode.h @@ -43,6 +43,7 @@ typedef enum sbOpcode { BC_OP_LT, // less than BC_OP_LE, // less than or equal to BC_OP_INDEX, // index [] + BC_OP_SCOPE, // index :: BC_ALLOC_VARS, // create space in rstack for local variables BC_LIST_GATHER, // create list from count + list of values on stack BC_HASH_GATHER, // create hash from count + list of pairs of keys/values on stack diff --git a/src/vm/exec.c b/src/vm/exec.c index 8d8a450..04d2a63 100644 --- a/src/vm/exec.c +++ b/src/vm/exec.c @@ -174,7 +174,7 @@ void execute_instruction(hVm vm) { sbOpcode op = get_opcode(vm); if (vm->debugmode) debug("op %02X ", op); u64 param; - hV *v, *w, res; + hV *v, *w, *x, res; switch (op) { case BC_NOP: @@ -364,6 +364,13 @@ void execute_instruction(hVm vm) { npop_stack(vm, 2); push_stack(vm, &res); break; + case BC_OP_SCOPE: + v = peek_stack(vm, 1); + w = peek_stack(vm, 0); + res = sbV_scope_get(v, w); + npop_stack(vm, 2); + push_stack(vm, &res); + break; case BC_OP_DEREF: PANIC("todo"); case BC_ALLOC_VARS: @@ -392,7 +399,23 @@ void execute_instruction(hVm vm) { push_stack(vm, &res); break; case BC_HASH_GATHER: - PANIC("todo"); + v = pop_stack(vm); + if (v->type != IT_INTEGER) { + /* internal error! this should be generated correctly */ + PANIC("internal violation: HASH_GATHER should receive an integer on top of stack"); + } + param = v->integer; + res = sbV_empty_hash(param * 3 / 2); + while (param > 0) { + /* values come first, then keys, because of execution order */ + x = pop_stack(vm); + w = pop_stack(vm); + sbV_scope_set(&res, w, x); + param --; + } + push_stack(vm, &res); + break; + break; case BC_LONG_NUM: case BC_VLONG_NUM: PANIC("illegal opcode $%02X at position $%016zX", op, (usize)vm->ip); diff --git a/src/vm/operations.c b/src/vm/operations.c index b3a8f21..3504db4 100644 --- a/src/vm/operations.c +++ b/src/vm/operations.c @@ -2,6 +2,7 @@ #include "data/integer.h" #include "data/list.h" +#include "data/hashtable.h" hV sbV_add(const hV *a, const hV *b) { if (a->type == IT_INTEGER && b->type == IT_INTEGER) { @@ -99,3 +100,19 @@ hV sbV_index(hV *a, hV *b) { PANIC("todo %lld %lld", a->type, b->type); } } + +hV sbV_scope_get(hV *obj, hV *key) { + if (obj->type == IT_HASH) { + return sbHash_find(obj->hash, key); + } else { + PANIC("todo %lld", obj->type); + } +} + +void sbV_scope_set(hV *obj, hV *key, hV *value) { + if (obj->type == IT_HASH) { + sbHash_insert(obj->hash, key, value); + } else { + PANIC("todo %lld", obj->type); + } +} diff --git a/src/vm/operations.h b/src/vm/operations.h index c4ba868..672fa8b 100644 --- a/src/vm/operations.h +++ b/src/vm/operations.h @@ -21,3 +21,7 @@ hV sbV_le(const hV *a, const hV *b); hV sbV_append(hV *a, hV *b); hV sbV_index(hV *a, hV *b); + +hV sbV_scope_get(hV *hash, hV *key); + +void sbV_scope_set(hV *hash, hV *key, hV *value);