first pass at pointers. they do work! mostly!
authorcassowarii <2374677+cassowarii@users.noreply.github.com>
Sun, 19 Jul 2026 23:45:43 +0000 (16:45 -0700)
committercassowarii <2374677+cassowarii@users.noreply.github.com>
Sun, 19 Jul 2026 23:45:43 +0000 (16:45 -0700)
12 files changed:
src/compile/emit.c
src/compile/ir.c
src/compile/ir.h
src/compile/print_ir.c
src/lib/sentinel.c
src/lib/sentinel.h
src/parse/ast.h
src/vm/block.h
src/vm/bytecode.h
src/vm/exec.c
src/vm/operations.c
src/vm/operations.h

index 84b5377..fb38ba6 100644 (file)
@@ -173,13 +173,40 @@ void compile_stmt(sbVmCompiler *cm, sbIrStmt *stmt) {
       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) {
@@ -270,30 +297,42 @@ void compile_hash(sbVmCompiler *cm, sbIrExpr *expr) {
 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
@@ -306,15 +345,20 @@ void compile_bind_list(sbVmCompiler *cm, sbIrBindList *list) {
   }
 }
 
+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 */
@@ -338,6 +382,12 @@ void compile_expr(sbVmCompiler *cm, sbIrExpr *expr) {
         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) {
@@ -346,12 +396,10 @@ void compile_expr(sbVmCompiler *cm, sbIrExpr *expr) {
             /* 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);
         }
@@ -394,6 +442,23 @@ void compile_expr(sbVmCompiler *cm, sbIrExpr *expr) {
   }
 }
 
+/* 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;
@@ -412,6 +477,7 @@ void compile_op(sbVmCompiler *cm, sbAstOp op) {
     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;
index 9461593..232610d 100644 (file)
@@ -142,8 +142,9 @@ static sbIrVariable *create_upvalue(sbIrChunk *ck, sbIrVariable *v, usize slot_i
    * 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;
@@ -158,7 +159,6 @@ static sbIrVariable *create_upvalue(sbIrChunk *ck, sbIrVariable *v, usize slot_i
  * 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) {
@@ -315,12 +315,8 @@ static sbIrExpr *expr_value(hIrChunk ck, hVal *value) {
 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,
@@ -490,11 +486,11 @@ static void put_expr(hIrChunk ck, sbIrExpr *expr) {
   });
 }
 
-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,
   });
 }
 
@@ -619,7 +615,7 @@ static usize compile_ast_stmtseq_open(hIrChunk ck, sbAst seqast, flag implicit_r
       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) {
@@ -704,9 +700,8 @@ static void compile_ast_stmtseq(hIrChunk ck, sbAst seqast, flag implicit_return)
 }
 
 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:
@@ -768,9 +763,9 @@ static void compile_ast_stmt(hIrChunk ck, sbAst node, flag implicit_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;
       }
@@ -944,9 +939,9 @@ static sbIrExpr *compile_ast_hash(hIrChunk ck, sbAst node) {
   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;
@@ -957,6 +952,22 @@ static sbIrExpr *compile_ast_binding(hIrChunk ck, sbAst node, flag should_create
     }
     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) {
@@ -1071,7 +1082,7 @@ static sbIrExpr *compile_ast_expr(hIrChunk ck, sbAst node, flag list_context) {
           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;
index 8b9c107..bbba211 100644 (file)
@@ -62,18 +62,18 @@ typedef enum sbIrNameIntroduceType {
 } 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 {
@@ -113,8 +113,8 @@ typedef struct sbIrExpr {
 } sbIrExpr;
 
 typedef struct sbIrAssign {
-  sbIrVariable *var;
-  sbIrExpr *expr;
+  sbIrExpr *where;
+  sbIrExpr *value;
 } sbIrAssign;
 
 typedef struct sbIrBindList {
@@ -143,7 +143,7 @@ struct sbIrProgram;
 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;
index 096cbd9..9f3ef8b 100644 (file)
@@ -28,14 +28,14 @@ void sbIrProgram_print(hIrProgram ir) {
 /* --- */
 
 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);
   }
 }
 
@@ -134,10 +134,10 @@ static void print_stmt(sbIrStmt *s) {
       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 ");
@@ -164,9 +164,9 @@ static void print_stmt(sbIrStmt *s) {
       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:
index 1a1261e..e7bc792 100644 (file)
@@ -5,6 +5,7 @@
 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() {
@@ -19,6 +20,8 @@ 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>");
index 4a394d8..b0dfa9b 100644 (file)
@@ -3,6 +3,7 @@
 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();
index dc317dc..415403a 100644 (file)
@@ -54,7 +54,6 @@ typedef enum sbAstOp {
   AST_OP_LT = '<',
   AST_OP_GT = '>',
   AST_OP_REF = '&',
-  AST_OP_DEREF = '*',
   AST_OP_PIPE = '|',
   AST_OP_LE = 128,
   AST_OP_GE,
@@ -76,6 +75,7 @@ typedef enum sbAstOp {
   AST_OP_IN,
   AST_OP_SEND,
   AST_OP_SPLAT,
+  AST_OP_DEREF,
 } sbAstOp;
 
 typedef struct sbAstNode {
index 05bfe3b..b3be746 100644 (file)
@@ -13,7 +13,7 @@
 /* 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;
index bff3a32..364aa9d 100644 (file)
@@ -31,6 +31,7 @@ typedef enum sbOpcode {
   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
index 84568ba..da484a4 100644 (file)
@@ -432,6 +432,9 @@ void execute_instruction(hVm vm) {
     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);
@@ -572,7 +575,8 @@ void execute_instruction(hVm 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);
index d299bfe..fddb958 100644 (file)
@@ -194,3 +194,35 @@ void sbV_index_set(hVal *obj, hVal *key, hVal *value) {
     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);
+  }
+}
index c8b9c00..ddccd00 100644 (file)
@@ -29,3 +29,7 @@ void sbV_index_value(hVm 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);