if (cm->debugmode) debug("\n");
break;
case IR_S_ASSIGN:
- compile_expr(cm, stmt->assign.expr);
- if (stmt->assign.var->is_upvalue) {
- EMIT(BC_ST_UPVAL);
+ compile_expr(cm, stmt->assign.value);
+ if (stmt->assign.where->type == IR_E_VAR) {
+ /* direct assignment to some variable name or other */
+ sbIrVariable *var = stmt->assign.where->var;
+ if (var->is_reference) {
+ if (!var->is_upvalue) {
+ /* let &a = &b; a = 5 # <-- should set b, too! */
+ EMIT(BC_LD_VAR);
+ } else {
+ /* same, but for upvalue */
+ EMIT(BC_LD_UPVAL);
+ }
+ EARG(var->slot_id);
+ EMIT(BC_REF_PUT);
+ } else {
+ if (!var->is_upvalue) {
+ /* normal assignment to normal variable */
+ EMIT(BC_ST_VAR);
+ } else {
+ /* assignment to normal upvalue variable */
+ EMIT(BC_ST_UPVAL);
+ }
+ EARG(var->slot_id);
+ }
+ } else if (stmt->assign.where->type == IR_E_OP && stmt->assign.where->op.type == AST_OP_DEREF) {
+ /* assignment to like *<....> = <....> */
+ compile_expr(cm, stmt->assign.where->op.left);
+ /* this should leave us with a pointer to expr on top of stack */
+ EMIT(BC_REF_PUT);
} else {
- EMIT(BC_ST_VAR);
+ /* TODO: Now we can support stuff like index-assignment also if we want to.
+ * Probably do this next */
+ PANIC("This type of assignment operation is not supported!");
}
- EARG(stmt->assign.var->slot_id);
break;
case IR_S_BIND:
if (stmt->bind.values) {
void compile_bind_list(sbVmCompiler *cm, sbIrBindList *list) {
for (sbIrBindList *considering = list; considering; considering = considering->next) {
sbIrExpr *elem = considering->this;
- if (elem->type == IR_E_OP && elem->op.type == AST_OP_SPLAT) {
- /* okay. we want to leave some number of elements on the stack
- * for whatever other arguments there are. currently we have the
- * total count on top of the stack. so, we'll reduce the count
- * by the number we want to save, gather into a list, and assign
- * to that, then replace the count we wanted to keep on the stack */
- if (list->pre_splat_count > 0) {
- EMIT(BC_LD_IMM);
- EARG(list->pre_splat_count);
- EMIT(BC_OP_SUB);
- }
- /* create list of this length and store in thing */
- EMIT(BC_LIST_GATHER);
- /* TODO actually, vv THIS vv should be a recursive call. otherwise,
- * we don't actually check that it's a variable that the "..." is
- * attached to, and we may fail in weird cases like "...2". but we
- * need to restructure the BindList data structure. */
- EMIT(BC_ST_VAR);
- EARG(elem->op.left->var->slot_id);
- if (list->pre_splat_count > 0) {
- /* put pre splat count back if we need it, to bind the rest of
- * the variables */
- EMIT(BC_LD_IMM);
- EARG(list->pre_splat_count);
+ if (elem->type == IR_E_OP) {
+ if (elem->op.type == AST_OP_SPLAT) {
+ /* okay. we want to leave some number of elements on the stack
+ * for whatever other arguments there are. currently we have the
+ * total count on top of the stack. so, we'll reduce the count
+ * by the number we want to save, gather into a list, and assign
+ * to that, then replace the count we wanted to keep on the stack */
+ if (list->pre_splat_count > 0) {
+ EMIT(BC_LD_IMM);
+ EARG(list->pre_splat_count);
+ EMIT(BC_OP_SUB);
+ }
+ /* create list of this length and store in thing */
+ EMIT(BC_LIST_GATHER);
+ /* TODO actually, vv THIS vv should be a recursive call. otherwise,
+ * we don't actually check that it's a variable that the "..." is
+ * attached to, and we may fail in weird cases like "...2". but we
+ * need to restructure the BindList data structure. */
+ EMIT(BC_ST_VAR);
+ EARG(elem->op.left->var->slot_id);
+ if (list->pre_splat_count > 0) {
+ /* put pre splat count back if we need it, to bind the rest of
+ * the variables */
+ EMIT(BC_LD_IMM);
+ EARG(list->pre_splat_count);
+ }
+ } else if (elem->op.type == AST_OP_REF) {
+ /* this should also probably be some kind of recursive thing, to handle
+ * situations like let &&a = whatever. (if that's even doable...?)
+ * but right now we just only permit & before variable names on the
+ * left side of a let */
+ /* storing here is the same as normal, because the actual variable
+ * slot saves the reference. however, assignment etc. is statically
+ * known to work differently so we will adjust those */
+ EMIT(BC_ST_ARG);
+ EARG(elem->op.left->var->slot_id);
}
} else if (elem->type == IR_E_VAR) {
/* normal sequence, no splat (so far): top thing goes in this
}
}
+void compile_ref(sbVmCompiler *cm, sbIrExpr *expr);
void compile_op(sbVmCompiler *cm, sbAstOp op);
void compile_expr(sbVmCompiler *cm, sbIrExpr *expr) {
switch(expr->type) {
case IR_E_OP:
- compile_expr(cm, expr->op.left);
- if (expr->op.right) {
- compile_expr(cm, expr->op.right);
+ if (expr->op.type == AST_OP_REF) {
+ compile_ref(cm, expr->op.left);
+ } else {
+ compile_expr(cm, expr->op.left);
+ if (expr->op.right) {
+ compile_expr(cm, expr->op.right);
+ }
+ compile_op(cm, expr->op.type);
}
- compile_op(cm, expr->op.type);
break;
case IR_E_CALL:
/* calling convention: store argument count on stack */
EMIT(BC_LD_VAR);
}
EARG(expr->var->slot_id);
+
+ /* when a variable that is_reference is referenced
+ * in non-assignment context, it automatically dereferences */
+ if (expr->var->is_reference) {
+ EMIT(BC_OP_DEREF);
+ }
break;
case IR_E_FUNC:
if (expr->func.bound.size > 0) {
/* BC_LD_UPREF: closed over variables are always on
* the heap, so all upval refs are rrefs */
EMIT(BC_LD_UPREF);
- } else if ((*var)->closed_over) {
+ } else {
/* BC_LD_RREF: everything we are closing over from the
* current scope needs to move to the heap */
EMIT(BC_LD_RREF);
- } else {
- PANIC("cannot have a direct variable in closure!");
}
EARG((*var)->slot_id);
}
}
}
+/* compiling the left hand side of an assignment (that isn't straightforwardly
+ * a variable) to return some kind of reference */
+void compile_assign_left(sbVmCompiler *cm, sbIrExpr *expr) {
+}
+
+/* when we see an expression of the form '&expr', we have to
+ * handle this specially depending on what 'expr' is (and sometimes
+ * we just aren't allowed to do it) */
+void compile_ref(sbVmCompiler *cm, sbIrExpr *expr) {
+ if (expr->type == IR_E_VAR) {
+ EMIT(BC_LD_RREF);
+ EARG(expr->var->slot_id);
+ } else {
+ PANIC("cannot & non-variable-name! (todo)");
+ }
+}
+
void compile_op(sbVmCompiler *cm, sbAstOp op) {
switch (op) {
case AST_OP_ADD: EMIT(BC_OP_ADD); break;
case AST_OP_OR: EMIT(BC_OP_OR); break;
case AST_OP_INDEX: EMIT(BC_OP_INDEXVAL); break;
case AST_OP_RANGEINDEX: EMIT(BC_OP_RANGEINDEX); break;
+ case AST_OP_DEREF: EMIT(BC_OP_DEREF); break;
/* op range is currently only used in rangeindex; just pass them to it directly */
case AST_OP_RANGE: break;
case AST_OP_DIVBY: EMIT(BC_OP_MOD, BC_LD_IMM); EARG(0); EMIT(BC_OP_EQ); break;
* scope. */
new_var->introduced = v->introduced;
- /* Upvalues actually can be closed over, but when initially created they won't be. */
- new_var->closed_over = FALSE;
+ /* An upvalue can be to a refaliased variable! So we have to remember to treat it
+ * specially too, just like the original */
+ new_var->is_reference = v->is_reference;
/* This tells us where in the sequence of nested scopes this variable exists. */
new_var->mapping_index = v->mapping_index;
* value with that variable's value */
static sbIrVariable *register_upvalue(hIrChunk ck, usize variable_index) {
varmapentry *e = &BUFFER_INDEX(ck->program->varmapping, varmapentry, variable_index);
- e->var->closed_over = TRUE;
sbIrVariable *existing_upvalue = NULL;
BUFFER_ITER(ck->closed_vars, sbIrVariable*, var) {
static sbIrExpr *expr_func(hIrChunk ck, sbIrChunk *func) {
/* when creating a 'literal' of a function 'func' inside another chunk 'ck',
* 'func' tells us which variables it closes over from the outer scope. we need
- * to convert these into references to variables in ck's scope so that it knows
- * which of its variables to save for this particular function. (it also knows
- * to heap-allocate those variables as sbRef because their closed_over flag is
- * set, but if we have multiple functions closing over different variables we
- * need to know which is which, and also these closed variables might be upvalues
- * to ck as well. */
+ * to remember to move these variables to the heap and provide them when a
+ * closure is created (if any). */
sbIrExpr *e = new_expr(ck, &(sbIrExpr) {
.type = IR_E_FUNC,
.func.chunk = func,
});
}
-static void put_assign(hIrChunk ck, sbIrVariable *var, sbIrExpr *expr) {
+static void put_assign(hIrChunk ck, sbIrExpr *where, sbIrExpr *value) {
put_ir_stmt(ck, &(sbIrStmt) {
.type = IR_S_ASSIGN,
- .assign.var = var,
- .assign.expr = expr,
+ .assign.where = where,
+ .assign.value = value,
});
}
sbIrVariable *V1 = lookup_node_var(ck, node->seq.left);
sbIrChunk *C1 = compile_ast_function(ck->program, params, body);
sbIrExpr *E1 = expr_func(ck, C1);
- put_assign(ck, V1, E1);
+ put_assign(ck, expr_var(ck, V1), E1);
}
if (node->type == AST_NODE_LET) {
}
static void compile_ast_stmt(hIrChunk ck, sbAst node, flag implicit_return) {
- sbIrExpr *E1;
+ sbIrExpr *E1, *E2;
sbIrLabel *L1, *L2;
- sbIrVariable *V1;
sbAst N1, N2;
switch (node->type) {
case AST_NODE_RETURN:
N1 = node->seq.left; /* things to bind to */
N2 = node->seq.right; /* values to assign */
while (N1 != NO_NODE && N2 != NO_NODE) {
- V1 = compile_ast_var(ck, N1->seq.right);
- E1 = compile_ast_expr(ck, N2->seq.right, TRUE);
- put_assign(ck, V1, E1);
+ E1 = compile_ast_expr(ck, N1->seq.right, TRUE);
+ E2 = compile_ast_expr(ck, N2->seq.right, TRUE);
+ put_assign(ck, E1, E2);
N1 = N1->seq.left;
N2 = N2->seq.left;
}
return list;
}
-/* compile one individual element to bind to: might be a bare variable name,
- * or might be "...name", or might be some other expression, in which case
- * we bind by matching it exactly? (TODO) */
+/* compile one individual element to bind to: might be a bare variable name, or might
+ * be "...name" or "&name", or might be some other expression, in which case we bind
+ * by matching it exactly? (TODO) */
static sbIrExpr *compile_ast_binding(hIrChunk ck, sbAst node, flag should_create_var, sbIrNameIntroduceType type) {
if (node->type == AST_NODE_NAME) {
sbIrVariable *V1;
}
V1->introduced = type;
return expr_var(ck, V1);
+ } else if (node->type == AST_NODE_OP && node->op.type == AST_OP_REF) {
+ /* let &a = f() or some such: this actually makes 'a' a different type
+ * of variable, which is aliased to whatever the reference is. so,
+ * assigning to a for example is more like *a = whatever. so we actually
+ * have to save the reference itself in this variable slot, and remember
+ * that assigning to it, etc., does something different */
+ sbIrExpr *ref_to = compile_ast_binding(ck, node->op.left, should_create_var, type);
+ if (ref_to->type == IR_E_VAR) {
+ /* from the declaration, we know this variable is a reference-variable.
+ * so we can rewrite assignments etc to it at the EMIT stage. */
+ ref_to->var->is_reference = TRUE;
+ return expr_op(ck, AST_OP_REF, ref_to, NULL);
+ } else {
+ chunk_error(ck, "cannot destructure into a `&<...>`");
+ return NULL;
+ }
} else if (node->type == AST_NODE_OP) {
sbIrExpr *left = NULL, *right = NULL;
if (node->op.left != NO_NODE) {
PANIC("Pipe cannot currently be used in this context. I will fix it");
}
sbIrExpr *left = compile_ast_expr(ck, node->op.left, FALSE);
- put_assign(ck, pipe_var(ck), left);
+ put_assign(ck, expr_var(ck, pipe_var(ck)), left);
ck->pipe_var_in_use = TRUE;
sbIrExpr *right = compile_ast_expr(ck, node->op.right, FALSE);
ck->pipe_var_in_use = FALSE;
} sbIrNameIntroduceType;
typedef struct sbIrLabel {
- flag found_yet;
+ flag found_yet : 1;
+ i32 id : 31;
struct sbIrLabel *aliased_to;
- usize id;
- usize block_position;
+ i32 block_position;
} sbIrLabel;
typedef struct sbIrVariable {
- usize slot_id;
- sbIrNameIntroduceType introduced;
- flag closed_over;
- flag is_upvalue;
- usize mapping_index;
+ flag is_reference : 1;
+ flag is_upvalue : 1;
+ sbIrNameIntroduceType introduced : 4;
+ i32 mapping_index : 26;
+ i32 slot_id;
} sbIrVariable;
typedef struct sbIrJump {
} sbIrExpr;
typedef struct sbIrAssign {
- sbIrVariable *var;
- sbIrExpr *expr;
+ sbIrExpr *where;
+ sbIrExpr *value;
} sbIrAssign;
typedef struct sbIrBindList {
typedef struct sbIrChunk {
struct sbIrProgram *program;
flag pipe_var_in_use : 1;
- i32 id;
+ i32 id : 31;
i16 num_args;
i16 num_upvalues;
i32 label_count;
/* --- */
static void print_var(sbIrVariable *v) {
- if (v->closed_over) {
+ if (v->is_reference) {
debug("special ");
}
if (v->is_upvalue) {
- debug("upvalue %zu", v->slot_id);
+ debug("upvalue %d", v->slot_id);
} else {
- debug("variable %zu", v->slot_id);
+ debug("variable %d", v->slot_id);
}
}
debug("]\n");
break;
case IR_S_LABEL:
- debug("label %zu:\n", s->label->id);
+ debug("label %d:\n", s->label->id);
break;
case IR_S_JUMP:
- debug(" jump to label %zu", s->jump.label->id);
+ debug(" jump to label %d", s->jump.label->id);
if (s->jump.condition) {
if (s->jump.inverted) {
debug(" unless ");
break;
case IR_S_ASSIGN:
debug(" ");
- print_var(s->assign.var);
+ print_expr(s->assign.where);
debug(" = ");
- print_expr(s->assign.expr);
+ print_expr(s->assign.value);
debug("\n");
break;
default:
hSymbol S_OP_ADD, S_OP_SUB, S_OP_MUL, S_OP_DIV, S_OP_INTDIV, S_OP_MOD;
hSymbol S_OP_EQ, S_OP_LT, S_OP_LE;
hSymbol S_OP_CALL, S_OP_SET, S_OP_INDEX, S_OP_SETINDEX, S_OP_RANGEINDEX, S_OP_SETRANGEINDEX;
+hSymbol S_OP_DEREF, S_OP_REFSET;
hSymbol S_OP_TO_STRING, S_OP_TO_INT, S_OP_TO_FLOAT, S_OP_TO_LIST, S_OP_TO_HASH;
void sbLib_create_sentinels() {
S_OP_LE = SENTINEL("<op::le>");
S_OP_CALL = SENTINEL("<op::call>");
S_OP_SET = SENTINEL("<op::set>");
+ S_OP_DEREF = SENTINEL("<op::deref>");
+ S_OP_REFSET = SENTINEL("<op::refset>");
S_OP_INDEX = SENTINEL("<op::index>");
S_OP_SETINDEX = SENTINEL("<op::setindex>");
S_OP_RANGEINDEX = SENTINEL("<op::rangeindex>");
extern hSymbol S_OP_ADD, S_OP_SUB, S_OP_MUL, S_OP_DIV, S_OP_INTDIV, S_OP_MOD;
extern hSymbol S_OP_EQ, S_OP_LT, S_OP_LE;
extern hSymbol S_OP_CALL, S_OP_SET, S_OP_INDEX, S_OP_SETINDEX, S_OP_RANGEINDEX, S_OP_SETRANGEINDEX;
+extern hSymbol S_OP_DEREF, S_OP_REFSET;
extern hSymbol S_OP_TO_STRING, S_OP_TO_INT, S_OP_TO_FLOAT, S_OP_TO_LIST, S_OP_TO_HASH;
void sbLib_create_sentinels();
AST_OP_LT = '<',
AST_OP_GT = '>',
AST_OP_REF = '&',
- AST_OP_DEREF = '*',
AST_OP_PIPE = '|',
AST_OP_LE = 128,
AST_OP_GE,
AST_OP_IN,
AST_OP_SEND,
AST_OP_SPLAT,
+ AST_OP_DEREF,
} sbAstOp;
typedef struct sbAstNode {
/* dynamic sized block that we can more easily add
* things to while compiling */
typedef struct sbVmCompiler {
- flag debugmode;
+ flag debugmode : 1;
sbBuffer bytecode;
sbBuffer constants;
sbBuffer label_positions;
BC_JT, // jump if true
BC_JF, // jump if false
BC_SEND, // like call, but jump to object method
+ BC_REF_PUT, // assign through pointer
BC_OP_EQ, // ==
BC_OP_NOT, // boolean not
BC_OP_ADD, // add
case BC_SEND:
sbLib_resolve_method(vm);
break;
+ case BC_REF_PUT:
+ sbV_refput(vm);
+ break;
case BC_NUMARG:
param = get_param(vm);
v = pop_stack(vm);
push_stack_immediate(vm, &res);
break;
case BC_OP_DEREF:
- PANIC("todo");
+ sbV_deref(vm);
+ break;
case BC_ALLOC_VARS:
/* TODO check for overflow */
param = get_param(vm);
PANIC("todo %lld", (long long)obj->type);
}
}
+
+void sbV_deref(hVm vm) {
+ hVal *a = sbVm_peek(vm, 0);
+ if (a->type == IT_REF) {
+ hVar v = sbVar_deref(a);
+ sbVm_pop(vm);
+ hVal *result = sbVar_get_value_ptr(v);
+ sbVm_push(vm, result);
+ } else {
+ sbVm_push_immediate(vm, &HVSYM(S_OP_DEREF)); /* a op::deref */
+ sbVm_swap(vm); /* op::deref a */
+ sbVm_push_immediate(vm, &HVINT(1)); /* op::deref a 1 */
+ sbVm_swap(vm); /* op::deref 1 a */
+ sbLib_resolve_method(vm);
+ }
+}
+
+void sbV_refput(hVm vm) {
+ hVal *ref = sbVm_peek(vm, 0);
+ hVal *val = sbVm_peek(vm, 1);
+ if (ref->type == IT_REF) {
+ hVar v = sbVar_deref(ref);
+ sbVar_set_value(v, val);
+ sbVm_npop(vm, 2);
+ } else {
+ sbVm_push_immediate(vm, &HVSYM(S_OP_REFSET)); /* val ref op::refset */
+ sbVm_swap(vm); /* val op::refset ref */
+ sbVm_push_immediate(vm, &HVINT(2)); /* val op::refset ref 2 */
+ sbVm_swap(vm); /* val op::refset 2 a */
+ sbLib_resolve_method(vm);
+ }
+}
hVal sbV_rangeindex(hVal *a, hVal *b, hVal *c);
void sbV_index_set(hVal *obj, hVal *key, hVal *value);
+
+void sbV_deref(hVm vm);
+
+void sbV_refput(hVm vm);