From: cassowarii Date: Fri, 3 Jul 2026 00:23:08 +0000 (-0700) Subject: list splatting X-Git-Url: https://www.git.cassowary.me/gitweb.cgi?a=commitdiff_plain;h=c0eae7ac63648b3d92eefbe40bd5c83b21bf2679;p=sarabande.git list splatting --- diff --git a/src/compile/emit.c b/src/compile/emit.c index 071edfd..f6fe64d 100644 --- a/src/compile/emit.c +++ b/src/compile/emit.c @@ -193,13 +193,44 @@ void compile_stmt(sbVmCompiler *cm, sbIrStmt *stmt) { void compile_list(sbVmCompiler *cm, sbIrExpr *expr) { sbIrExpr *considering = expr; usize count = 0; + flag was_splat = FALSE; while (considering) { + sbIrExpr *elem = considering->list.this; + if (elem->type == IR_E_OP && elem->op.type == AST_OP_SPLAT) { + if (!was_splat) { + /* first splatted thing in list: emit non-splat argument count, + * then spill list onto it (LIST_SPILL takes a list and a number, + * and unpacks the list onto the stack, then adds the length of + * the list to the number) */ + was_splat = TRUE; + EMIT(BC_LD_IMM); + EARG(count); + } + /* compile whatever's behind the splat, and splat it */ + compile_expr(cm, elem->op.left); + EMIT(BC_LIST_SPILL); + } else if (was_splat) { + /* if was splat (but not this one), we can no longer rely on the + * number of commas in the list to say how many arguments (or + * whatever) we are passing. so we have to start counting one + * by one */ + compile_expr(cm, elem); + /* swap to put the number on top, then increment */ + EMIT(BC_SWAP, BC_OP_INCR); + } else { + /* blissfully unaware of the splat */ + compile_expr(cm, elem); + } count ++; - compile_expr(cm, considering->list.this); considering = considering->list.next; } - EMIT(BC_LD_IMM); - EARG(count); /* calling convention: store argument count on stack */ + + if (!was_splat) { + /* if no splat, just put the total count here. if there was a splat, + * we counted them already manually */ + EMIT(BC_LD_IMM); + EARG(count); + } } void compile_hash(sbVmCompiler *cm, sbIrExpr *expr) { diff --git a/src/compile/ir.c b/src/compile/ir.c index d5fea84..c761109 100644 --- a/src/compile/ir.c +++ b/src/compile/ir.c @@ -466,7 +466,7 @@ static void put_implicit_return(hIrChunk ck) { static void compile_ast_stmt(hIrChunk ck, sbAst stmtast, flag implicit_return); static void compile_ast_stmtseq(hIrChunk ck, sbAst seqast, flag implicit_return); -static sbIrExpr *compile_ast_expr(hIrChunk ck, sbAst exprast); +static sbIrExpr *compile_ast_expr(hIrChunk ck, sbAst exprast, flag list_context); static sbIrVariable *compile_ast_var(hIrChunk ck, sbAst node); static sbIrChunk *compile_ast_function(hIrProgram ir, sbAst paramsAst, sbAst seqast) { @@ -651,7 +651,7 @@ static void compile_ast_stmt(hIrChunk ck, sbAst node, flag implicit_return) { E1 = NULL; } else { /* TODO handle multival here */ - E1 = compile_ast_expr(ck, node->seq.left->seq.left); + E1 = compile_ast_expr(ck, node->seq.left->seq.left, FALSE); } put_return(ck, E1); break; @@ -685,7 +685,7 @@ static void compile_ast_stmt(hIrChunk ck, sbAst node, flag implicit_return) { const char *vname = sbSymbol_name(N1->seq.left->symb); V1 = var_name(ck, vname, strlen(vname)); V1->initialized = BY_LET; - E1 = compile_ast_expr(ck, N2->seq.left); + E1 = compile_ast_expr(ck, N2->seq.left, TRUE); put_assign(ck, V1, E1); N1 = N1->seq.right; N2 = N2->seq.right; @@ -720,7 +720,7 @@ static void compile_ast_stmt(hIrChunk ck, sbAst node, flag implicit_return) { N2 = node->seq.right; /* values to assign */ while (N1 != NO_NODE && N2 != NO_NODE) { V1 = compile_ast_var(ck, N1->seq.left); - E1 = compile_ast_expr(ck, N2->seq.left); + E1 = compile_ast_expr(ck, N2->seq.left, TRUE); put_assign(ck, V1, E1); N1 = N1->seq.right; N2 = N2->seq.right; @@ -758,7 +758,7 @@ static void compile_ast_stmt(hIrChunk ck, sbAst node, flag implicit_return) { * context" struct that captures all of these. */ L1 = new_label(ck); L2 = new_label(ck); - E1 = compile_ast_expr(ck, node->seq.left); + E1 = compile_ast_expr(ck, node->seq.left, FALSE); put_jump_unconditional(ck, L2); put_label(ck, L1); compile_ast_stmtseq(ck, node->seq.right, FALSE); @@ -787,14 +787,14 @@ static void compile_ast_stmt(hIrChunk ck, sbAst node, flag implicit_return) { */ if (node->tri.right == NO_NODE) { /* no else branch */ - E1 = compile_ast_expr(ck, node->tri.left); + E1 = compile_ast_expr(ck, node->tri.left, FALSE); L1 = new_label(ck); put_jump_if_no(ck, E1, L1); compile_ast_stmtseq(ck, node->tri.center, implicit_return); put_label(ck, L1); } else { /* yes else branch */ - E1 = compile_ast_expr(ck, node->tri.left); + E1 = compile_ast_expr(ck, node->tri.left, FALSE); L1 = new_label(ck); L2 = new_label(ck); put_jump_if_no(ck, E1, L1); @@ -825,7 +825,7 @@ static void compile_ast_stmt(hIrChunk ck, sbAst node, flag implicit_return) { */ L1 = new_label(ck); if (node->seq.right) { - E1 = compile_ast_expr(ck, node->seq.right); + E1 = compile_ast_expr(ck, node->seq.right, FALSE); put_label(ck, L1); compile_ast_stmtseq(ck, node->seq.left, FALSE); put_jump_if_yes(ck, E1, L1); @@ -838,7 +838,7 @@ static void compile_ast_stmt(hIrChunk ck, sbAst node, flag implicit_return) { default: /* probably an expr node */ - put_expr(ck, compile_ast_expr(ck, node)); + put_expr(ck, compile_ast_expr(ck, node, FALSE)); } } @@ -857,7 +857,7 @@ static sbIrExpr *compile_ast_list(hIrChunk ck, sbAst node) { sbIrExpr *list = NULL; sbIrExpr **place_here = &list; while (considering != NO_NODE) { - *place_here = expr_list(ck, compile_ast_expr(ck, considering->seq.left)); + *place_here = expr_list(ck, compile_ast_expr(ck, considering->seq.left, TRUE)); place_here = &(*place_here)->list.next; considering = considering->seq.right; } @@ -874,8 +874,8 @@ static sbIrExpr *compile_ast_hash(hIrChunk ck, sbAst node) { 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 + sbIrExpr *compiled_entry = expr_list(ck, compile_ast_expr(ck, hash_entry->seq.left, FALSE)); // key + compiled_entry->list.next = compile_ast_expr(ck, hash_entry->seq.right, FALSE); // value *place_here = expr_hash(ck, compiled_entry); place_here = &(*place_here)->list.next; @@ -884,11 +884,11 @@ static sbIrExpr *compile_ast_hash(hIrChunk ck, sbAst node) { return list; } -static sbIrExpr *compile_ast_expr(hIrChunk ck, sbAst node) { +static sbIrExpr *compile_ast_expr(hIrChunk ck, sbAst node, flag list_context) { if (node == NO_NODE) return NULL; if (node->type == AST_NODE_FUNCCALL) { - sbIrExpr *called = compile_ast_expr(ck, node->seq.left); + sbIrExpr *called = compile_ast_expr(ck, node->seq.left, FALSE); sbIrExpr *params = compile_ast_list(ck, node->seq.right); return expr_call(ck, called, params); } else if (node->type == AST_VAL_FUNC) { @@ -901,14 +901,23 @@ static sbIrExpr *compile_ast_expr(hIrChunk ck, sbAst node) { 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) { - left = compile_ast_expr(ck, node->op.left); - } - if (node->op.right != NO_NODE) { - right = compile_ast_expr(ck, node->op.right); + if (node->op.type == AST_OP_SPLAT) { + if (!list_context) { + chunk_error(ck, "'...' not allowed outside a list"); + return NULL; + } else { + return expr_op(ck, AST_OP_SPLAT, compile_ast_expr(ck, node->op.left, FALSE), NULL); + } + } else { + sbIrExpr *left = NULL, *right = NULL; + if (node->op.left != NO_NODE) { + left = compile_ast_expr(ck, node->op.left, FALSE); + } + if (node->op.right != NO_NODE) { + right = compile_ast_expr(ck, node->op.right, FALSE); + } + return expr_op(ck, node->op.type, left, right); } - return expr_op(ck, node->op.type, left, right); } else if (node->type == AST_VAL_INT) { return expr_value(ck, &HVINT(node->i)); } else if (node->type == AST_VAL_STRING) { diff --git a/src/mem/pool.c b/src/mem/pool.c index 7fe1b37..eab380b 100644 --- a/src/mem/pool.c +++ b/src/mem/pool.c @@ -78,6 +78,9 @@ void *sbPool_alloc(hPool pl, usize *id_out) { void *sbPool_get_entry(hPool pl, usize index) { usize block_id = index / pl->block_size; usize elem_id = index % pl->block_size; + if (!BLOCK_ALLOCATION_FLAGS(pl, block_id)[elem_id]) { + PANIC("attempt to get unallocated index from pool"); + } return &BLOCK_ALLOCATED_DATA(pl, block_id)[elem_id * pl->elem_size]; } diff --git a/src/vm/bytecode.h b/src/vm/bytecode.h index d6ae47b..89cb296 100644 --- a/src/vm/bytecode.h +++ b/src/vm/bytecode.h @@ -24,6 +24,7 @@ typedef enum sbOpcode { BC_ST_ARG_IND, // decrement TOS and store NOS in variable indirectly BC_POP, // pop value from stack BC_NPOP, // pop N values from stack given by top number + BC_SWAP, // swap top two values on stack BC_CALL, // push return address and jump to new block BC_RET, // return to calling block BC_NUMARG, // check number of function arguments = this @@ -53,6 +54,7 @@ typedef enum sbOpcode { BC_CLOSURE, // create closure from top elements of stack 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 + BC_LIST_SPILL, // splat a list into a context with a count BC_LONG_NUM = 0xFC, // next two bytes are a 16-bit number BC_VLONG_NUM = 0xFD, // next four bytes are a 32-bit number BC_VVLONG_NUM = 0xFE, // next four bytes are a 32-bit number diff --git a/src/vm/exec.c b/src/vm/exec.c index bef5fa2..1c4934a 100644 --- a/src/vm/exec.c +++ b/src/vm/exec.c @@ -1,6 +1,7 @@ #include "vm/exec.h" #include "vm/operations.h" +#include "data/list.h" #include "data/reference.h" #include "data/closure.h" @@ -101,10 +102,10 @@ void push_stack(hVm vm, const hV *value) { vm->sp += sizeof(hV); } -hV *pop_stack(hVm vm) { +hV pop_stack(hVm vm) { vm->sp -= sizeof(hV); sbV_release((hV*)vm->sp); - return (hV*)vm->sp; + return *(hV*)vm->sp; } hV *npop_stack(hVm vm, usize count) { @@ -177,7 +178,9 @@ void execute_instruction(hVm vm) { sbOpcode op = get_opcode(vm); if (vm->debugmode) debug("op %02X ", op); u64 param; + usize count; hV *v, *w, *x, res; + hV vv, ww, xx; switch (op) { case BC_NOP: @@ -244,7 +247,8 @@ void execute_instruction(hVm vm) { break; case BC_ST_UPVAL: param = get_param(vm); - sbClosure_set_var(vm->fp->closure, param, pop_stack(vm)); + vv = pop_stack(vm); + sbClosure_set_var(vm->fp->closure, param, &vv); break; case BC_ST_IND: param = get_param(vm); @@ -262,24 +266,24 @@ void execute_instruction(hVm vm) { break; case BC_ST_ARG: param = get_param(vm); - v = pop_stack(vm); /* argument count */ - if (v->type != IT_INTEGER) { + vv = pop_stack(vm); /* argument count */ + if (vv.type != IT_INTEGER) { /* internal error! this should be generated correctly */ CHECK("calling convention violation: number of args should be an integer"); } - w = pop_stack(vm); /* argument value */ - store_local(vm, param, w); + ww = pop_stack(vm); /* argument value */ + store_local(vm, param, &ww); /* we track the number of arguments remaining, for variadic functions later */ - v->integer --; - if (v->integer > 0) { + vv.integer --; + if (vv.integer > 0) { /* if last integer, don't put the 0 count back on the stack */ - push_stack(vm, v); + push_stack(vm, &vv); } break; case BC_ST_ARG_IND: param = get_param(vm); - x = pop_stack(vm); /* argument count */ - if (x->type != IT_INTEGER) { + xx = pop_stack(vm); /* argument count */ + if (xx.type != IT_INTEGER) { /* internal error! this should be generated correctly */ CHECK("calling convention violation: number of args should be an integer"); } @@ -295,42 +299,48 @@ void execute_instruction(hVm vm) { CHECK("indirect variables should be of type reference! (%lld)", v->type); } pop_stack(vm); - x->integer --; - if (x->integer > 0) { + xx.integer --; + if (xx.integer > 0) { /* if last integer, don't put the 0 count back on the stack */ - push_stack(vm, x); + push_stack(vm, &xx); } break; case BC_POP: - v = pop_stack(vm); + pop_stack(vm); break; case BC_NPOP: param = get_param(vm); npop_stack(vm, param); break; + case BC_SWAP: + vv = pop_stack(vm); + ww = pop_stack(vm); + push_stack(vm, &vv); + push_stack(vm, &ww); + break; case BC_CALL: - v = pop_stack(vm); - if (v->type <= 0) { + vv = pop_stack(vm); + if (vv.type <= 0) { /* We need to figure out exception support or some such. * User error should not panic. */ PANIC("attempt to call a non-function value"); } - call_block(vm, v->type, v->closure); + call_block(vm, vv.type, vv.closure); break; case BC_NUMARG: param = get_param(vm); - v = pop_stack(vm); - if (v->type != IT_INTEGER) { + vv = pop_stack(vm); + if (vv.type != IT_INTEGER) { /* internal error! this should be generated correctly */ CHECK("calling convention violation: number of args should be an integer"); } - if (v->integer != param) { + if (vv.integer != param) { /* This should be an exception. */ PANIC("wrong number of arguments passed to function."); } - if (v->integer > 0) { + if (vv.integer > 0) { /* when 0 arguments, leave this off */ - push_stack(vm, v); + push_stack(vm, &vv); } break; case BC_JMP: @@ -339,15 +349,15 @@ void execute_instruction(hVm vm) { break; case BC_JT: param = get_param(vm); - v = pop_stack(vm); - if (!sbV_c_falsy(v)) { + vv = pop_stack(vm); + if (!sbV_c_falsy(&vv)) { vm->ip = &vm->fp->block->bytecode[param]; } break; case BC_JF: param = get_param(vm); - v = pop_stack(vm); - if (sbV_c_falsy(v)) { + vv = pop_stack(vm); + if (sbV_c_falsy(&vv)) { vm->ip = &vm->fp->block->bytecode[param]; } break; @@ -364,8 +374,8 @@ void execute_instruction(hVm vm) { push_stack(vm, &res); break; case BC_OP_NOT: - v = pop_stack(vm); - if (sbV_c_falsy(v)) { + vv = pop_stack(vm); + if (sbV_c_falsy(&vv)) { push_stack(vm, &HVBOOL(TRUE)); } else { push_stack(vm, &HVBOOL(FALSE)); @@ -460,8 +470,8 @@ void execute_instruction(hVm vm) { case BC_CLOSURE: param = get_param(vm); hClosure c = sbClosure_create(param); - v = pop_stack(vm); /* function to close with */ - if (v->type <= 0) { + vv = pop_stack(vm); /* function to close with */ + if (vv.type <= 0) { CHECK("internal violation: a function is required to create a closure!"); } while (param > 0) { @@ -475,41 +485,62 @@ void execute_instruction(hVm vm) { sbClosure_set_ref(c, param, w); pop_stack(vm); } - v->closure = c; - push_stack(vm, v); + vv.closure = c; + push_stack(vm, &vv); break; case BC_LIST_GATHER: - v = pop_stack(vm); - if (v->type != IT_INTEGER) { + vv = pop_stack(vm); + if (vv.type != IT_INTEGER) { /* internal error! this should be generated correctly */ CHECK("internal violation: LIST_GATHER should receive an integer on top of stack"); } - param = v->integer; + param = vv.integer; + count = param; res = sbV_empty_list(param); - while (param > 0) { - w = pop_stack(vm); + while (count > 0) { + /* list gets built from bottom up, because first thing pushed is lower + * on the stack */ + w = peek_stack(vm, count - 1); sbV_append(&res, w); - param --; + count --; } + npop_stack(vm, param); push_stack(vm, &res); break; case BC_HASH_GATHER: - v = pop_stack(vm); - if (v->type != IT_INTEGER) { + vv = pop_stack(vm); + if (vv.type != IT_INTEGER) { /* internal error! this should be generated correctly */ CHECK("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) { + count = vv.integer; + res = sbV_empty_hash(count * 3 / 2); + while (count > 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 --; + xx = pop_stack(vm); + ww = pop_stack(vm); + sbV_scope_set(&res, &ww, &xx); + count --; } push_stack(vm, &res); break; + case BC_LIST_SPILL: + vv = pop_stack(vm); /* list to spill */ + if (vv.type != IT_LIST) { + /* user error */ + PANIC("cannot use '...' operator on something that isn't a list"); + } + ww = pop_stack(vm); /* current count */ + if (ww.type != IT_INTEGER) { + /* internal error! this should be generated correctly */ + CHECK("internal violation: LIST_SPILL should receive an integer on top of stack"); + } + x = sbList_get_value(vv.list, &count); + for (usize i = 0; i < count; i++) { + push_stack(vm, &x[i]); + } + ww.integer += count; + push_stack(vm, &ww); break; case BC_LONG_NUM: case BC_VLONG_NUM: