an object field with '.' should return a reference
authorcassowarii <2374677+cassowarii@users.noreply.github.com>
Thu, 23 Jul 2026 01:17:50 +0000 (18:17 -0700)
committercassowarii <2374677+cassowarii@users.noreply.github.com>
Thu, 23 Jul 2026 01:17:50 +0000 (18:17 -0700)
src/compile/emit.c
src/compile/ir.c
src/compile/ir.h
src/compile/print_ir.c
src/vm/bytecode.h
src/vm/exec.c
src/vm/operations.c
src/vm/operations.h

index 2c61541..bfe0728 100644 (file)
@@ -210,7 +210,7 @@ void compile_stmt(sbVmCompiler *cm, sbIrStmt *stmt) {
          * then we assign through the reference we receive, i.e.
          *    a[b] = c
          * really means
-         *    *(a{op::refindex}(b)) = c
+         *    *((&a)->.[op::index]->(b)) = c
          * The reason for this has to do with assigning through nested data structures.
          * Doing it this way just makes the whole semantics feel more natural. It's similar
          * to returning a reference from indexing in C++, I guess. */
@@ -218,6 +218,15 @@ void compile_stmt(sbVmCompiler *cm, sbIrStmt *stmt) {
         compile_expr(cm, stmt->assign.where->op.right);     /* index we are using */
         EMIT(BC_OP_INDEXLREF);
         EMIT(BC_REF_PUT);
+      } else if (stmt->assign.where->type == IR_E_DOT) {
+        /* same as op::index, essentially. the dot returns a reference to the inside of the
+         * thing, then we can assign to that reference */
+        compile_expr(cm, stmt->assign.where->op.right);
+        EMIT(BC_LD_IMM);
+        EARG(1);
+        compile_ref(cm, stmt->assign.where->op.left, TRUE);
+        EMIT(BC_CALL_IND);
+        EMIT(BC_REF_PUT);
       } else {
         PANIC("This type of assignment operation is not supported!");
       }
@@ -363,6 +372,8 @@ void compile_expr(sbVmCompiler *cm, sbIrExpr *expr) {
   switch(expr->type) {
     case IR_E_OP:
       if (expr->op.type == AST_OP_REF) {
+        /* in expr context, the & operator generally prevents 'collapsing'
+         * of references to values when applied to a[b] or a.b or a::b */
         compile_ref(cm, expr->op.left, FALSE);
       } else {
         compile_expr(cm, expr->op.left);
@@ -375,13 +386,30 @@ void compile_expr(sbVmCompiler *cm, sbIrExpr *expr) {
     case IR_E_CALL:
       /* calling convention: store argument count on stack */
       compile_list(cm, expr->call.param);
-      compile_expr(cm, expr->call.func);
-      EMIT(BC_CALL);
+      if (expr->call.func->type == IR_E_DOT
+            || (expr->call.func->type == IR_E_OP && expr->call.func->op.type == AST_OP_INDEX)) {
+        /* for expressions with reference type, call 'through' the reference */
+        compile_ref(cm, expr->call.func, TRUE);
+        EMIT(BC_CALL_IND);
+      } else if (expr->call.func->type == IR_E_OP && expr->call.func->op.type == AST_OP_DEREF) {
+        /* for something like (*a)(...), call 'through' the reference as well
+         * (that is, evaluate the thing inside the * and call indirectly, instead
+         * of dereferencing it first) */
+        compile_expr(cm, expr->call.func->op.left);
+        EMIT(BC_CALL_IND);
+      } else {
+        compile_expr(cm, expr->call.func);
+        EMIT(BC_CALL);
+      }
       break;
-    case IR_E_SEND:
-      compile_list(cm, expr->send.message);
-      compile_expr(cm, expr->send.target);
-      EMIT(BC_SEND);
+    case IR_E_DOT:
+      compile_expr(cm, expr->dot.param);
+      /* TODO dot should always take 1 parameter, so this is not needed */
+      EMIT(BC_LD_IMM);
+      EARG(1);
+      compile_expr(cm, expr->dot.target);
+      EMIT(BC_DOT);
+      EMIT(BC_OP_DEREF);
       break;
     case IR_E_CONTEXT:
       EMIT(BC_LD_CTX);
@@ -478,6 +506,12 @@ void compile_ref(sbVmCompiler *cm, sbIrExpr *expr, flag is_lref) {
       /* &a[0] / &a::b */
       EMIT(BC_OP_INDEXRREF);
     }
+  } else if (expr->type == IR_E_DOT) {
+    compile_expr(cm, expr->dot.param);
+    EMIT(BC_LD_IMM);
+    EARG(1);
+    compile_ref(cm, expr->dot.target, is_lref);
+    EMIT(BC_DOT);
   } else {
     PANIC("cannot & non-variable-name! (todo)");
   }
index 8f5fc27..d7f4813 100644 (file)
@@ -361,11 +361,11 @@ static sbIrExpr *expr_call(hIrChunk ck, sbIrExpr *to_call, sbIrExpr *param) {
   });
 }
 
-static sbIrExpr *expr_send(hIrChunk ck, sbIrExpr *target, sbIrExpr *message) {
+static sbIrExpr *expr_dot(hIrChunk ck, sbIrExpr *target, sbIrExpr *param) {
   return new_expr(ck, &(sbIrExpr) {
-    .type = IR_E_SEND,
-    .send.target = target,
-    .send.message = message,
+    .type = IR_E_DOT,
+    .dot.target = target,
+    .dot.param = param,
   });
 }
 
@@ -998,22 +998,13 @@ static sbIrExpr *compile_ast_expr(hIrChunk ck, sbAst node, flag list_context) {
   if (node == NO_NODE) return NULL;
 
   if (node->type == AST_NODE_FUNCCALL) {
-    if (node->seq.left->type == AST_NODE_DOT) {
-      /* For a.b(c,d,e), we can optimize a bit to avoid allocating closures
-       * for built-in types that get methods called on them */
-      sbIrExpr *called = compile_ast_expr(ck, node->seq.left->seq.left, FALSE);
-      sbIrExpr *method = expr_list(ck, compile_ast_expr(ck, node->seq.left->seq.right, FALSE));
-      sbIrExpr *params = compile_ast_list_after(ck, node->seq.right, method);
-      return expr_send(ck, called, params);
-    } else {
-      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);
-    }
+    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_NODE_DOT) {
     sbIrExpr *target = compile_ast_expr(ck, node->seq.left, FALSE);
-    sbIrExpr *param = expr_list(ck, compile_ast_expr(ck, node->seq.right, FALSE));
-    return expr_call(ck, target, param);
+    sbIrExpr *param = compile_ast_expr(ck, node->seq.right, FALSE);
+    return expr_dot(ck, target, param);
   } else if (node->type == AST_VAL_FUNC) {
     sbIrChunk *func = compile_ast_function(ck->program, node->seq.left, node->seq.right);
     return expr_func(ck, func);
index bbba211..2f3bbbe 100644 (file)
@@ -47,7 +47,7 @@ typedef enum sbIrExprType {
   IR_E_CONTEXT,
   IR_E_FUNC,
   IR_E_CALL,
-  IR_E_SEND,
+  IR_E_DOT,
   IR_E_LIST,
   IR_E_HASH,
 } sbIrExprType;
@@ -107,8 +107,8 @@ typedef struct sbIrExpr {
     } list;
     struct {
       struct sbIrExpr *target;
-      struct sbIrExpr *message;
-    } send;
+      struct sbIrExpr *param;
+    } dot;
   };
 } sbIrExpr;
 
@@ -177,4 +177,6 @@ void sbIrProgram_compile_ast(hIrProgram ir, sbAst ast);
 
 void sbIrProgram_print(hIrProgram ir);
 
+void sbIr_print_expr(sbIrExpr *e);
+
 #endif
index 9f3ef8b..070b0c1 100644 (file)
@@ -3,6 +3,7 @@
 #include "data/symbol.h"
 
 static void print_stmt(sbIrStmt *s);
+static void print_expr(sbIrExpr *e);
 
 void sbIrProgram_print(hIrProgram ir) {
   usize nchunks = ir->chunks.size / sizeof(sbIrChunk*);
@@ -25,6 +26,10 @@ void sbIrProgram_print(hIrProgram ir) {
   }
 }
 
+void sbIr_print_expr(sbIrExpr *e) {
+  print_expr(e);
+}
+
 /* --- */
 
 static void print_var(sbIrVariable *v) {
@@ -85,12 +90,12 @@ static void print_expr(sbIrExpr *e) {
       }
       debug(")");
       break;
-    case IR_E_SEND:
-      debug("SEND: ");
-      print_expr(e->send.target);
-      debug(" <~( ");
-      print_expr(e->send.message);
-      debug(" )");
+    case IR_E_DOT:
+      debug("(");
+      print_expr(e->dot.target);
+      debug(" . ");
+      print_expr(e->dot.param);
+      debug(")");
       break;
     case IR_E_LIST:
       if (e->list.next) {
index 364aa9d..c246edd 100644 (file)
@@ -24,6 +24,8 @@ typedef enum sbOpcode {
   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_CALL_IND,          // 'a->(...)' indirect call that can return a value
+  BC_DOT,               // 'a.b' indirect call that should return a reference
   BC_RET,               // return to calling block
   BC_NUMARG,            // check number of function arguments = this
   BC_MINARG,            // check number of function arguments > this
index abdeafc..f68f119 100644 (file)
@@ -461,6 +461,15 @@ void execute_instruction(hVm vm) {
       v = pop_stack(vm);
       sbVm_call_func(vm, v);
       break;
+    case BC_DOT:
+    case BC_CALL_IND:
+      v = pop_stack(vm);
+      if (v->type != IT_REF) {
+        PANIC("object illegally returned non-reference value");
+      }
+      vars = sbVar_deref(v);
+      sbVm_call_func(vm, sbVar_get_value_ptr(vars));
+      break;
     case BC_SEND:
       sbLib_resolve_method(vm);
       break;
@@ -598,6 +607,12 @@ void execute_instruction(hVm vm) {
     case BC_OP_INDEXVAL:
       sbV_index_value(vm);
       break;
+    case BC_OP_INDEXLREF:
+      sbV_index_lref(vm);
+      break;
+    case BC_OP_INDEXRREF:
+      sbV_index_rref(vm);
+      break;
     case BC_OP_RANGEINDEX:
       v = peek_stack(vm, 2);
       w = peek_stack(vm, 1);
index fddb958..fcd4e33 100644 (file)
@@ -148,6 +148,62 @@ void sbV_index_value(hVm vm) {
   }
 }
 
+void sbV_index_lref(hVm vm) {
+  hVal *a = sbVm_peek(vm, 1);
+  hVal *b = sbVm_peek(vm, 0);
+  if (a->type != IT_REF) {
+    PANIC("Object illegally returned non-reference!");
+  }
+  a = sbVar_get_value_ptr(sbVar_deref(a));
+
+  if (a->type == IT_LIST && b->type == IT_INTEGER) {
+    sbVm_npop(vm, 2);
+    hVal result = sbList_index_lvalue_ref(a->list, b->integer);
+    sbVm_push(vm, &result);
+  } else if (a->type == IT_HASH) {
+    sbVm_npop(vm, 2);
+    hVal result = sbHash_find_lvalue_ref(a->hash, b);
+    sbVm_push(vm, &result);
+  } else if (a->type == IT_MODULE && b->type == IT_SYMBOL) {
+    PANIC("TODO");
+  } else {
+    sbVm_swap(vm);                                      /* b a */
+    sbVm_push_immediate(vm, &HVSYM(S_OP_INDEX));        /* b a op::index */
+    sbVm_swap(vm);                                      /* b op::index a */
+    sbVm_push_immediate(vm, &HVINT(2));                 /* b op::index a 2 */
+    sbVm_swap(vm);                                      /* b op::index 2 a */
+    sbLib_resolve_method(vm);
+  }
+}
+
+void sbV_index_rref(hVm vm) {
+  hVal *a = sbVm_peek(vm, 1);
+  hVal *b = sbVm_peek(vm, 0);
+  if (a->type != IT_REF) {
+    PANIC("Object illegally returned non-reference!");
+  }
+  a = sbVar_get_value_ptr(sbVar_deref(a));
+
+  if (a->type == IT_LIST && b->type == IT_INTEGER) {
+    sbVm_npop(vm, 2);
+    hVal result = sbList_index_rvalue_ref(a->list, b->integer);
+    sbVm_push(vm, &result);
+  } else if (a->type == IT_HASH) {
+    sbVm_npop(vm, 2);
+    hVal result = sbHash_find_rvalue_ref(a->hash, b);
+    sbVm_push(vm, &result);
+  } else if (a->type == IT_MODULE && b->type == IT_SYMBOL) {
+    PANIC("TODO");
+  } else {
+    sbVm_swap(vm);                                      /* b a */
+    sbVm_push_immediate(vm, &HVSYM(S_OP_INDEX));        /* b a op::index */
+    sbVm_swap(vm);                                      /* b op::index a */
+    sbVm_push_immediate(vm, &HVINT(2));                 /* b op::index a 2 */
+    sbVm_swap(vm);                                      /* b op::index 2 a */
+    sbLib_resolve_method(vm);
+  }
+}
+
 hVal sbV_rangeindex(hVal *a, hVal *b, hVal *c) {
   if (b->type == IT_INTEGER && c->type == IT_INTEGER) {
     usize length;
index ddccd00..12270e9 100644 (file)
@@ -26,6 +26,10 @@ hVal sbV_append(hVal *a, hVal *b);
 
 void sbV_index_value(hVm vm);
 
+void sbV_index_lref(hVm vm);
+
+void sbV_index_rref(hVm vm);
+
 hVal sbV_rangeindex(hVal *a, hVal *b, hVal *c);
 
 void sbV_index_set(hVal *obj, hVal *key, hVal *value);