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) {
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) {
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;
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;
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;
* 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);
*/
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);
*/
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);
default:
/* probably an expr node */
- put_expr(ck, compile_ast_expr(ck, node));
+ put_expr(ck, compile_ast_expr(ck, node, FALSE));
}
}
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;
}
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;
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) {
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) {
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];
}
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
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
#include "vm/exec.h"
#include "vm/operations.h"
+#include "data/list.h"
#include "data/reference.h"
#include "data/closure.h"
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) {
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:
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);
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");
}
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:
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;
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));
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) {
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: