method dispatch cleanup + 'properties' (builtin methods w/o ())
authorcassowarii <cassowary@cassowary.me>
Mon, 13 Jul 2026 01:34:57 +0000 (18:34 -0700)
committercassowarii <cassowary@cassowary.me>
Mon, 13 Jul 2026 01:34:57 +0000 (18:34 -0700)
sample/project_euler/pe03.sa
src/lib/lib.c
src/lib/lib.h
src/lib/method/float.c
src/lib/method/integer.c
src/lib/method/list.c
src/lib/method/string.c
src/lib/table.c
src/lib/table.h
src/vm/exec.c

index 9336183..5be690a 100644 (file)
@@ -10,7 +10,7 @@ def factors num {
 }
 
 def prime? num {
-  factors(num).length() == 0
+  factors(num).length == 0
 }
 
 factors 600851475143
index 2f293d2..11dcc28 100644 (file)
@@ -7,7 +7,8 @@
 #include "lib/method.h"
 #include "lib/module.h"
 
-sbCFuncStatus please_call_again(hVm vm, flag init);
+static sbLibTable *find_method_table(hV *target);
+static sbCFuncStatus please_call_again(hVm vm, flag init);
 
 void sbLib_sys_init() {
   sbLib_create_sentinels();
@@ -35,30 +36,19 @@ void sbLib_resolve_method(hVm vm) {
   if (argc->type != IT_INTEGER) {
     CHECK("argc of send should be integer!");
   }
+  if (argc->integer == 0) {
+    PANIC("Method selection is required!");
+  }
+  /* subtract 1 because the method name is itself a param */
+  usize num_params = argc->integer - 1;
+
   hV *method_name_val = sbVm_pop(vm);
   if (method_name_val->type != IT_SYMBOL) {
-    /* TODO this may become not true */
     PANIC("method name must be symbol!");
   }
   hSymbol method_sym = method_name_val->symbol;
 
-  /* subtract 1 because the method name is itself a param */
-  usize num_params = argc->integer - 1;
-  hLibTable table_to_use = NULL;
-  switch(target->type) {
-    case IT_LIST:
-      table_to_use = &g_list_methods;
-      break;
-    case IT_STRING:
-      table_to_use = &g_string_methods;
-      break;
-    case IT_INTEGER:
-      table_to_use = &g_integer_methods;
-      break;
-    case IT_FLOAT:
-      table_to_use = &g_float_methods;
-      break;
-  }
+  sbLibTable *table_to_use = find_method_table(target);
 
   if (table_to_use == NULL) {
     /* did not find built in method table */
@@ -78,9 +68,72 @@ void sbLib_resolve_method(hVm vm) {
     }
   } else {
     /* built in type that has a method table defined: find method */
-    sbLibMethod f = sbLibTable_find_method(table_to_use, method_name_val->symbol);
+    sbLibMethod *f = sbLibTable_find_method(table_to_use, method_name_val->symbol);
+    if (f) {
+      if (f->is_property) {
+        /* uhhh... this is theoretically possible but weird. it's like,
+         * saying "list.length(string::convert)". fortunately we can just
+         * sort of treat it as a normal method call again */
+        f->behavior(vm, target, 0);
+        if (num_params > 0) {
+          sbVm_push(vm, &HVINT(num_params));
+          sbLib_resolve_method(vm);
+        }
+      } else {
+        if (num_params >= f->min_args && (num_params <= f->max_args || f->max_args == -1)) {
+          f->behavior(vm, target, num_params);
+        } else {
+          PANIC("Wrong number of arguments passed to method '%s'! (expected %d~%d)", sbSymbol_name(method_name_val->symbol), f->min_args, f->max_args);
+        }
+      }
+    } else {
+      PANIC("invalid method name for type %lld: %s", (long long)target->type, sbSymbol_name(method_name_val->symbol));
+    }
+  }
+}
+
+/* function that resolves constructs like 'a.b' or 'a(:b)' for intrinsic types.
+ * (non-intrinsic types are always functions, so they can be resolved just by
+ *  calling them)*/
+void sbLib_resolve_property(hVm vm) {
+  printf("Resolving property\n");
+  sbVm_print_stack(vm);
+  hV *target = sbVm_pop(vm);
+  hV *argc = sbVm_pop(vm);
+  if (argc->type != IT_INTEGER) {
+    CHECK("argc of send should be integer!");
+  }
+  if (argc->integer != 1) {
+    PANIC("Illegal number of arguments!");
+  }
+
+  hV *method_name_val = sbVm_pop(vm);
+  if (method_name_val->type != IT_SYMBOL) {
+    PANIC("method name must be symbol!");
+  }
+
+  sbLibTable *table_to_use = find_method_table(target);
+
+  if (table_to_use == NULL) {
+    /* did not find built in method table */
+    if (target->type <= 0) {
+      /* for built in type, just fail, it's not done yet */
+      PANIC("Have not implemented this method table yet! (%lld)", (long long)target->type);
+    } else {
+      CHECK("This should never be reached!");
+    }
+  } else {
+    /* built in type that has a method table defined: find method */
+    sbLibMethod *f = sbLibTable_find_method(table_to_use, method_name_val->symbol);
     if (f) {
-      f(vm, target, num_params);
+      if (f->is_property) {
+        /* we found a property! great, just compute it and be done */
+        f->behavior(vm, target, 0);
+      } else {
+        /* it is a method, but it is being called without parameters.
+         * annoyingly, we need to construct a bound version of this method. */
+        PANIC("I haven't done this yet!");
+      }
     } else {
       PANIC("invalid method name for type %lld: %s", (long long)target->type, sbSymbol_name(method_name_val->symbol));
     }
@@ -102,10 +155,26 @@ void sbLib_resolve_global(hVm vm) {
 
 /* --- */
 
+/* function that looks up something in a built-in method table */
+static sbLibTable *find_method_table(hV *target) {
+  switch(target->type) {
+    case IT_LIST:
+      return &g_list_methods;
+    case IT_STRING:
+      return &g_string_methods;
+    case IT_INTEGER:
+      return &g_integer_methods;
+    case IT_FLOAT:
+      return &g_float_methods;
+    default:
+      return NULL;
+  }
+}
+
 /* function that resolves methods from BC_SEND for non intrinsic types (aka functions) */
 /* we have something like a.b(c,d,e), we need to call twice to get result of a(:b)(c,d,e) */
 /* resolve_method should have set us up so we can just call in twice */
-sbCFuncStatus please_call_again(hVm vm, flag init) {
+static sbCFuncStatus please_call_again(hVm vm, flag init) {
   hV *target = sbVm_pop(vm);
   if (target->type <= 0) {
     PANIC("i have not implemented partial methods for builtin types yet! i'll get to it");
@@ -125,3 +194,4 @@ sbCFuncStatus please_call_again(hVm vm, flag init) {
     return CFUNC_TAILCALL;
   }
 }
+
index 97697a6..bc10dfc 100644 (file)
@@ -1,10 +1,20 @@
 #ifndef __SARABANDE_LIB_H__
 #define __SARABANDE_LIB_H__
 
-typedef void (*sbLibMethod)(hVm, hV*, usize);
+#define METHOD(f, min, max) (sbLibMethod) { .behavior = f, .is_property = FALSE, .min_args = min, .max_args = max }
+#define PROPERTY(f) (sbLibMethod) { .behavior = f, .is_property = TRUE }
+
+typedef struct sbLibMethod {
+  void (*behavior)(hVm, hV*, usize);
+  flag is_property : 1;
+  i16 min_args : 15;
+  i16 max_args;
+} sbLibMethod;
 
 void sbLib_resolve_method(hVm vm);
 
+void sbLib_resolve_property(hVm vm);
+
 void sbLib_resolve_global(hVm vm);
 
 void sbLib_sys_init();
index a7304cb..146cd38 100644 (file)
@@ -24,5 +24,5 @@ static void to_string(hVm vm, hV *target, usize num_params) {
 
 void sbFloat_create_methods(void) {
   sbLibTable_initialize(&g_float_methods, 16, TRUE);
-  REGISTER_METHOD_SYM(&g_float_methods, S_OP_TO_STRING, to_string);
+  REGISTER_METHOD_SYM(&g_float_methods, S_OP_TO_STRING, &PROPERTY(to_string));
 }
index 6b94af7..d00640c 100644 (file)
@@ -25,5 +25,5 @@ static void to_string(hVm vm, hV *target, usize num_params) {
 
 void sbInteger_create_methods(void) {
   sbLibTable_initialize(&g_integer_methods, 16, TRUE);
-  REGISTER_METHOD_SYM(&g_integer_methods, S_OP_TO_STRING, to_string);
+  REGISTER_METHOD_SYM(&g_integer_methods, S_OP_TO_STRING, &PROPERTY(to_string));
 }
index c55feca..9c2dcf7 100644 (file)
@@ -16,27 +16,18 @@ sbCFuncStatus list_all_cfunc(hVm vm, flag init);
 sbLibTable g_list_methods;
 
 static void length(hVm vm, hV *list, usize num_params) {
-  if (num_params != 0) {
-    PANIC("list#length takes no arguments!");
-  }
   usize length;
   sbList_get_value(list->list, &length);
   sbVm_push_immediate(vm, &HVINT(length));
 }
 
 static void push(hVm vm, hV *list, usize num_params) {
-  if (num_params != 1) {
-    PANIC("list#push expects 1 argument!");
-  }
   hV *to_append = sbVm_pop(vm);
   sbList_append(list->list, to_append);
   sbVm_push_immediate(vm, &HVNIL);
 }
 
 static void reverse(hVm vm, hV *list, usize num_params) {
-  if (num_params != 0) {
-    PANIC("list#reverse takes no arguments!");
-  }
   /* TODO maybe mutate in place if no other refs */
   usize length;
   hV *elems = sbList_get_value(list->list, &length);
@@ -50,9 +41,6 @@ static void reverse(hVm vm, hV *list, usize num_params) {
 }
 
 static void join(hVm vm, hV *list, usize num_params) {
-  if (num_params > 1) {
-    PANIC("list#join takes no arguments or 1 argument!");
-  }
   flag join_with = FALSE;
   hString delimiter;
   if (num_params == 1) {
@@ -79,65 +67,47 @@ static void join(hVm vm, hV *list, usize num_params) {
 }
 
 static void each(hVm vm, hV *list, usize num_params) {
-  if (num_params != 1) {
-    PANIC("wrong number of arguments passed to list#each");
-  }
   sbVm_push_immediate(vm, list);
   sbVm_call_c_func(vm, list_each_cfunc);
 }
 
 static void map(hVm vm, hV *list, usize num_params) {
-  if (num_params != 1) {
-    PANIC("wrong number of arguments passed to list#map");
-  }
   sbVm_push_immediate(vm, list);
   sbVm_call_c_func(vm, list_map_cfunc);
 }
 
 static void filter(hVm vm, hV *list, usize num_params) {
-  if (num_params != 1) {
-    PANIC("wrong number of arguments passed to list#filter");
-  }
   sbVm_push_immediate(vm, list);
   sbVm_call_c_func(vm, list_filter_cfunc);
 }
 
 static void reduce(hVm vm, hV *list, usize num_params) {
-  if (num_params != 2) {
-    PANIC("wrong number of arguments passed to list#reduce");
-  }
   sbVm_push_immediate(vm, list);
   sbVm_call_c_func(vm, list_reduce_cfunc);
 }
 
 static void any_p(hVm vm, hV *list, usize num_params) {
-  if (num_params != 1) {
-    PANIC("wrong number of arguments passed to list#any?");
-  }
   sbVm_push_immediate(vm, list);
   sbVm_call_c_func(vm, list_any_cfunc);
 }
 
 static void all_p(hVm vm, hV *list, usize num_params) {
-  if (num_params != 1) {
-    PANIC("wrong number of arguments passed to list#any?");
-  }
   sbVm_push_immediate(vm, list);
   sbVm_call_c_func(vm, list_all_cfunc);
 }
 
 void sbList_create_methods(void) {
   sbLibTable_initialize(&g_list_methods, 16, TRUE);
-  REGISTER_METHOD(&g_list_methods, "length", length);
-  REGISTER_METHOD(&g_list_methods, "push", push);
-  REGISTER_METHOD(&g_list_methods, "reverse", reverse);
-  REGISTER_METHOD(&g_list_methods, "join", join);
-  REGISTER_METHOD(&g_list_methods, "each", each);
-  REGISTER_METHOD(&g_list_methods, "map", map);
-  REGISTER_METHOD(&g_list_methods, "filter", filter);
-  REGISTER_METHOD(&g_list_methods, "reduce", reduce);
-  REGISTER_METHOD(&g_list_methods, "any?", any_p);
-  REGISTER_METHOD(&g_list_methods, "all?", all_p);
+  REGISTER_METHOD(&g_list_methods, "length", &PROPERTY(length));
+  REGISTER_METHOD(&g_list_methods, "push", &METHOD(push, 1, 1));
+  REGISTER_METHOD(&g_list_methods, "reverse", &METHOD(reverse, 0, 0));
+  REGISTER_METHOD(&g_list_methods, "join", &METHOD(join, 0, 1));
+  REGISTER_METHOD(&g_list_methods, "each", &METHOD(each, 1, 1));
+  REGISTER_METHOD(&g_list_methods, "map", &METHOD(map, 1, 1));
+  REGISTER_METHOD(&g_list_methods, "filter", &METHOD(filter, 1, 1));
+  REGISTER_METHOD(&g_list_methods, "reduce", &METHOD(reduce, 2, 2));
+  REGISTER_METHOD(&g_list_methods, "any?", &METHOD(any_p, 1, 1));
+  REGISTER_METHOD(&g_list_methods, "all?", &METHOD(all_p, 1, 1));
 }
 
 /* --- */
index 614156d..2ae655d 100644 (file)
@@ -36,6 +36,6 @@ static void to_string(hVm vm, hV *target, usize num_params) {
 
 void sbString_create_methods(void) {
   sbLibTable_initialize(&g_string_methods, 16, TRUE);
-  REGISTER_METHOD(&g_string_methods, "split", split);
-  REGISTER_METHOD_SYM(&g_string_methods, S_OP_TO_STRING, to_string);
+  REGISTER_METHOD(&g_string_methods, "split", &METHOD(split, 0, 1));
+  REGISTER_METHOD_SYM(&g_string_methods, S_OP_TO_STRING, &PROPERTY(to_string));
 }
index f266116..bf68559 100644 (file)
@@ -17,7 +17,7 @@ typedef sbLibTable {
 } sbLibTable;
 */
 
-static void insert_method(hSymbol *keys, sbLibMethod *methods, usize capacity, hSymbol key, sbLibMethod method);
+static void insert_method(hSymbol *keys, sbLibMethod *methods, usize capacity, hSymbol key, sbLibMethod *method);
 static void insert_value(hSymbol *keys, hV *values, usize capacity, hSymbol key, hV *value);
 static void check_grow_table(hLibTable t);
 
@@ -46,7 +46,7 @@ void sbLibTable_deinitialize(hLibTable t) {
   *t = (sbLibTable) {0};
 }
 
-sbLibMethod sbLibTable_find_method(hLibTable t, hSymbol key) {
+sbLibMethod *sbLibTable_find_method(hLibTable t, hSymbol key) {
   if (!t->method_table) CHECK("cannot find method in non-method table");
 
   sbHashValue hash = sbHash_hash_bytes((const char*)&key, sizeof(hSymbol));
@@ -57,7 +57,7 @@ sbLibMethod sbLibTable_find_method(hLibTable t, hSymbol key) {
   }
 
   if (t->keys[index]) {
-    return t->methods[index];
+    return &t->methods[index];
   } else {
     return NULL;
   }
@@ -80,7 +80,7 @@ hV *sbLibTable_find_value(hLibTable t, hSymbol key) {
   }
 }
 
-void sbLibTable_register_method_sym(hLibTable t, hSymbol key, sbLibMethod method) {
+void sbLibTable_register_method_sym(hLibTable t, hSymbol key, sbLibMethod *method) {
   if (!t->method_table) CHECK("cannot put method in non-method table");
 
   check_grow_table(t);
@@ -89,7 +89,7 @@ void sbLibTable_register_method_sym(hLibTable t, hSymbol key, sbLibMethod method
   t->used ++;
 }
 
-void sbLibTable_register_method(hLibTable t, const char *method_name, usize method_name_length, sbLibMethod method) {
+void sbLibTable_register_method(hLibTable t, const char *method_name, usize method_name_length, sbLibMethod *method) {
   hSymbol sym = sbSymbol_from_bytes(method_name, method_name_length);
   sbLibTable_register_method_sym(t, sym, method);
 }
@@ -110,7 +110,7 @@ void sbLibTable_register_value(hLibTable t, const char *value_name, usize value_
 
 /* --- */
 
-static void insert_method(hSymbol *keys, sbLibMethod *methods, usize capacity, hSymbol key, sbLibMethod method) {
+static void insert_method(hSymbol *keys, sbLibMethod *methods, usize capacity, hSymbol key, sbLibMethod *method) {
   sbHashValue hash = sbHash_hash_bytes((const char*)&key, sizeof(hSymbol));
   usize index = hash % capacity;
 
@@ -120,7 +120,7 @@ static void insert_method(hSymbol *keys, sbLibMethod *methods, usize capacity, h
   }
 
   keys[index] = key;
-  methods[index] = method;
+  methods[index] = *method;
 }
 
 static void insert_value(hSymbol *keys, hV *values, usize capacity, hSymbol key, hV *value) {
@@ -147,7 +147,7 @@ static void check_grow_table(hLibTable t) {
 
       for (usize i = 0; i < t->capacity; i++) {
         if (t->keys[i] != 0) {
-          insert_method(new_keys, new_methods, new_capacity, t->keys[i], t->methods[i]);
+          insert_method(new_keys, new_methods, new_capacity, t->keys[i], &t->methods[i]);
         }
       }
 
index 46cb685..a8c9bfd 100644 (file)
@@ -23,13 +23,13 @@ void sbLibTable_initialize(hLibTable t, usize capacity, flag method_table);
 
 void sbLibTable_deinitialize(hLibTable t);
 
-sbLibMethod sbLibTable_find_method(hLibTable t, hSymbol key);
+sbLibMethod *sbLibTable_find_method(hLibTable t, hSymbol key);
 
 hV *sbLibTable_find_value(hLibTable t, hSymbol key);
 
-void sbLibTable_register_method(hLibTable t, const char *method_name, usize method_name_length, sbLibMethod method);
+void sbLibTable_register_method(hLibTable t, const char *method_name, usize method_name_length, sbLibMethod *method);
 
-void sbLibTable_register_method_sym(hLibTable t, hSymbol key, sbLibMethod method);
+void sbLibTable_register_method_sym(hLibTable t, hSymbol key, sbLibMethod *method);
 
 void sbLibTable_register_value(hLibTable t, const char *value_name, usize value_name_length, hV *value);
 
index 9b8ac03..f4dd780 100644 (file)
@@ -486,9 +486,9 @@ void execute_instruction(hVm vm) {
       if (v->type == IT_BUILTIN) {
         call_builtin(vm, v);
       } else if (v->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");
+        /* intrinsic type: resolve a property on it */
+        push_stack(vm, v);
+        sbLib_resolve_property(vm);
       } else {
         call_block(vm, v->type, v->closure);
       }