resolve methods with hash table
authorcassowarii <2374677+cassowarii@users.noreply.github.com>
Thu, 9 Jul 2026 01:46:15 +0000 (18:46 -0700)
committercassowarii <2374677+cassowarii@users.noreply.github.com>
Thu, 9 Jul 2026 01:46:15 +0000 (18:46 -0700)
14 files changed:
sample/project_euler/pe04.sa [new file with mode: 0644]
src/data/data.c
src/data/integer.c
src/data/list.c
src/data/string.c
src/lib/integer.c [new file with mode: 0644]
src/lib/libtable.c [new file with mode: 0644]
src/lib/libtable.h [new file with mode: 0644]
src/lib/list.c [new file with mode: 0644]
src/lib/string.c [new file with mode: 0644]
src/mem/debug.c
src/mem/debug.h
src/vm/exec.c
src/vm/operations.c

diff --git a/sample/project_euler/pe04.sa b/sample/project_euler/pe04.sa
new file mode 100644 (file)
index 0000000..9b8ef33
--- /dev/null
@@ -0,0 +1,18 @@
+def is_palindrome num {
+  num.to_string == num.to_string.split.reverse.join
+}
+
+let max = 0
+
+let a = 100
+while a < 1000 {
+  let b = 100
+  while b < 1000 {
+    let product = a * b
+    max = product if is_palindrome product and product > max
+    b = b + 1
+  }
+  a = a + 1
+}
+
+max
index 49817ce..f274e2d 100644 (file)
@@ -7,6 +7,8 @@
 #include "data/reference.h"
 #include "data/closure.h"
 
+#include "lib/libtable.h"
+
 extern sbPool g_closure_pool;
 
 void data_sys_init() {
@@ -16,13 +18,17 @@ void data_sys_init() {
   sbClosure_sys_init();
   sbSymbol_sys_init();
   sbList_sys_init();
+
+  sbLib_sys_init();
 }
 
 void data_sys_deinit() {
-  sbList_sys_deinit();
+  sbLib_sys_deinit();
+
   sbSymbol_sys_deinit();
-  sbRef_sys_deinit();
+  sbList_sys_deinit();
   sbClosure_sys_deinit();
+  sbRef_sys_deinit();
   sbHash_sys_deinit();
   sbString_sys_deinit();
 }
index 94524c0..7af907b 100644 (file)
@@ -125,18 +125,6 @@ void sbInteger_method(hVm vm) {
   /* TODO: Need a better way of resolving these */
   /* also TODO shouldn't be 'to_string' */
   if (METHOD_IS("to_string")) {
-    sbVm_pop(vm); /* remove method name */
-    char stackbuf[1024];
-    char *buf = stackbuf;
-    usize length = snprintf(buf, 1024, "%lld", (long long)target->integer);
-    if (length + 1 >= sizeof(stackbuf)) {
-      buf = malloc(length + 1);
-      snprintf(buf, length, "%lld", (long long)target->integer);
-    }
-    sbVm_push_immediate(vm, &HVSTR(sbString_new(buf, length)));
-    if (buf != stackbuf) {
-      free(buf);
-    }
   } else {
     PANIC("unknown method name for integer");
   }
index e55875b..467e9ea 100644 (file)
@@ -2,18 +2,10 @@
 
 #include "gc/gcinfo.h"
 #include "vm/exec.h"
-#include "data/symbol.h"
-#include "data/string.h"
 
 #define LIST_PER_BLOCK 256
 #define METHOD_IS(name) (!sbstrncmp(method_name, name, sizeof(name)))
 
-void list_each_cfunc(hVm vm, flag init);
-void list_map_cfunc(hVm vm, flag init);
-void list_filter_cfunc(hVm vm, flag init);
-void list_any_cfunc(hVm vm, flag init);
-void list_all_cfunc(hVm vm, flag init);
-
 /* TODO etc etc */
 sbPool g_list_pool;
 
@@ -59,299 +51,8 @@ hV *sbList_index(hList list, usize index) {
   return item;
 }
 
-void sbList_method(hVm vm) {
-  hV *list = sbVm_pop(vm);
-  if (list->type != IT_LIST) {
-    CHECK("can't call sbList_method on something that isn't a list");
-  }
-  hV *argc = sbVm_pop(vm);
-  if (argc->type != IT_INTEGER) {
-    CHECK("argc of send should be integer!");
-  }
-  /* subtract 1 because the method name is itself a param */
-  usize num_params = argc->integer - 1;
-  hV *method_name_val = sbVm_peek(vm, num_params);
-  if (method_name_val->type != IT_SYMBOL) {
-    /* TODO this may become not true */
-    PANIC("method name for list must be symbol!");
-  }
-
-  const char *method_name = sbSymbol_name(method_name_val->symbol);
-  /* TODO: Need a better way of resolving these */
-  if (METHOD_IS("length")) {
-    if (num_params != 0) {
-      PANIC("list#length takes no arguments!");
-    }
-    sbVm_pop(vm); /* remove method name */
-    usize length;
-    sbList_get_value(list->list, &length);
-    sbVm_push_immediate(vm, &HVINT(length));
-  } else if (METHOD_IS("push")) {
-    if (num_params != 1) {
-      PANIC("list#push expects 1 argument!");
-    }
-    hV *to_append = sbVm_pop(vm);
-    sbVm_pop(vm); /* remove method name */
-    sbList_append(list->list, to_append);
-    sbVm_push_immediate(vm, &HVNIL);
-  } else if (METHOD_IS("each")) {
-    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);
-  } else if (METHOD_IS("map")) {
-    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);
-  } else if (METHOD_IS("filter")) {
-    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);
-  } else if (METHOD_IS("any?")) {
-    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);
-  } else if (METHOD_IS("all?")) {
-    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);
-  } else if (METHOD_IS("reverse")) {
-    if (num_params != 0) {
-      PANIC("list#reverse takes no arguments!");
-    }
-    /* TODO maybe mutate in place if no other refs */
-    sbVm_pop(vm); /* remove method name */
-    usize length;
-    hV *elems = sbList_get_value(list->list, &length);
-    hList new_list = sbList_new(length);
-    for (usize i = length - 1; ; i--) {
-      sbList_append(new_list, &elems[i]);
-      if (i == 0) break;
-    }
-    sbList_get_value(new_list, &length);
-    sbVm_push_immediate(vm, &HVLIST(new_list));
-  } else if (METHOD_IS("join")) {
-    if (num_params > 1) {
-      PANIC("list#join takes no arguments or 1 argument!");
-    }
-    flag join_with = FALSE;
-    hString delimiter;
-    if (num_params == 1) {
-      hV *delimiter_v = sbVm_pop(vm);
-      if (delimiter_v->type != IT_STRING) {
-        PANIC("list#join parameter should be string!");
-      }
-      join_with = TRUE;
-      delimiter = delimiter_v->string;
-    }
-    sbVm_pop(vm); /* remove method name */
-    usize length;
-    hV *elems = sbList_get_value(list->list, &length);
-    hString joined = sbString_new("", 0);
-    for (usize i = 0; i < length; i++) {
-      if (elems[i].type != IT_STRING) {
-        PANIC("need string to join");
-      }
-      joined = sbString_concat(joined, elems[i].string);
-      if (join_with && i < length - 1) {
-        joined = sbString_concat(joined, delimiter);
-      }
-    }
-    sbVm_push_immediate(vm, &HVSTR(joined));
-  } else {
-    PANIC("unknown method name for list");
-  }
-}
-
 /* --- */
 
 sbList *get_list_by_handle(hList handle) {
   return sbPool_get_entry(&g_list_pool, handle);
 }
-
-void list_each_cfunc(hVm vm, flag init) {
-  if (init) {
-    /* store three state variables: the list we're iterating, the index into it, and the callback function */
-    sbVm_request_var_space(vm, 3);
-    hV *iterating_list = sbVm_pop(vm);
-    hV *loop_func = sbVm_pop(vm);
-    sbVm_pop(vm); /* remove method name */
-    hV index = HVINT(0);
-    vm->fp->locals[0] = *iterating_list;
-    vm->fp->locals[1] = index;
-    vm->fp->locals[2] = *loop_func;
-  } else {
-    /* loop func must have finished, so remove its result */
-    sbVm_pop(vm);
-  }
-
-  usize current_index = vm->fp->locals[1].integer++;
-  usize length;
-  hV *iter_values = sbList_get_value(vm->fp->locals[0].list, &length);
-  if (current_index < length) {
-    sbVm_push(vm, &iter_values[current_index]);
-    sbVm_push_immediate(vm, &HVINT(1));
-    sbVm_call_func(vm, &vm->fp->locals[2]);
-  } else {
-    /* if index was past the end, callback function won't be called, and we'll exit. return nil */
-    sbVm_push_immediate(vm, &HVNIL);
-  }
-}
-
-void list_map_cfunc(hVm vm, flag init) {
-  if (init) {
-    /* state: list being mapped, index, callback, result */
-    sbVm_request_var_space(vm, 4);
-    hV *iterating_list = sbVm_pop(vm);
-    hV *map_func = sbVm_pop(vm);
-    sbVm_pop(vm); /* remove method name */
-    usize length;
-    sbList_get_value(iterating_list->list, &length);
-    hV index = HVINT(0);
-    hV result = sbV_empty_list(length);
-
-    vm->fp->locals[0] = *iterating_list;
-    vm->fp->locals[1] = index;
-    vm->fp->locals[2] = *map_func;
-    vm->fp->locals[3] = result;
-  } else {
-    /* get result of map function and append its result to result list */
-    hV *mapped = sbVm_pop(vm);
-    sbList_append(vm->fp->locals[3].list, mapped);
-  }
-
-  usize current_index = vm->fp->locals[1].integer++;
-  usize length;
-  hV *iter_values = sbList_get_value(vm->fp->locals[0].list, &length);
-  if (current_index < length) {
-    sbVm_push(vm, &iter_values[current_index]);
-    sbVm_push_immediate(vm, &HVINT(1));
-    sbVm_call_func(vm, &vm->fp->locals[2]);
-  } else {
-    /* return result list */
-    sbVm_push_immediate(vm, &vm->fp->locals[3]);
-  }
-}
-
-void list_filter_cfunc(hVm vm, flag init) {
-  if (init) {
-    /* state: list being filtered, index, callback, result */
-    sbVm_request_var_space(vm, 4);
-    hV *iterating_list = sbVm_pop(vm);
-    hV *filter_func = sbVm_pop(vm);
-    sbVm_pop(vm); /* remove method name */
-    usize length;
-    sbList_get_value(iterating_list->list, &length);
-    hV index = HVINT(0);
-    hV result = sbV_empty_list(length);
-
-    vm->fp->locals[0] = *iterating_list;
-    vm->fp->locals[1] = index;
-    vm->fp->locals[2] = *filter_func;
-    vm->fp->locals[3] = result;
-  } else {
-    /* get result of filter function and append element to result if true */
-    hV *mapped = sbVm_pop(vm);
-    hV *element = sbVm_pop(vm);
-    if (!sbV_c_falsy(mapped)) {
-      sbList_append(vm->fp->locals[3].list, element);
-    }
-  }
-
-  usize current_index = vm->fp->locals[1].integer++;
-  usize length;
-  hV *iter_values = sbList_get_value(vm->fp->locals[0].list, &length);
-  if (current_index < length) {
-    /* put current value on stack twice: once as parameter for function, once to save for ourselves */
-    sbVm_push(vm, &iter_values[current_index]);
-    sbVm_push(vm, &iter_values[current_index]);
-    sbVm_push_immediate(vm, &HVINT(1));
-    sbVm_call_func(vm, &vm->fp->locals[2]);
-  } else {
-    /* return result list */
-    sbVm_push_immediate(vm, &vm->fp->locals[3]);
-  }
-}
-
-void list_any_cfunc(hVm vm, flag init) {
-  if (init) {
-    /* state: list being filtered, index, callback */
-    sbVm_request_var_space(vm, 3);
-    hV *iterating_list = sbVm_pop(vm);
-    hV *pred_func = sbVm_pop(vm);
-    sbVm_pop(vm); /* remove method name */
-    hV index = HVINT(0);
-
-    vm->fp->locals[0] = *iterating_list;
-    vm->fp->locals[1] = index;
-    vm->fp->locals[2] = *pred_func;
-  } else {
-    /* get result of predicate function */
-    hV *mapped = sbVm_pop(vm);
-    if (!sbV_c_falsy(mapped)) {
-      /* one was true! return true */
-      sbVm_push_immediate(vm, &HVBOOL(TRUE));
-      return;
-    }
-  }
-
-  /* haven't found any yet... */
-  usize current_index = vm->fp->locals[1].integer++;
-  usize length;
-  hV *iter_values = sbList_get_value(vm->fp->locals[0].list, &length);
-  if (current_index < length) {
-    /* try next element */
-    sbVm_push(vm, &iter_values[current_index]);
-    sbVm_push_immediate(vm, &HVINT(1));
-    sbVm_call_func(vm, &vm->fp->locals[2]);
-  } else {
-    /* no result found */
-    sbVm_push_immediate(vm, &HVBOOL(FALSE));
-  }
-}
-
-void list_all_cfunc(hVm vm, flag init) {
-  if (init) {
-    /* state: list being filtered, index, callback */
-    sbVm_request_var_space(vm, 3);
-    hV *iterating_list = sbVm_pop(vm);
-    hV *pred_func = sbVm_pop(vm);
-    sbVm_pop(vm); /* remove method name */
-    hV index = HVINT(0);
-
-    vm->fp->locals[0] = *iterating_list;
-    vm->fp->locals[1] = index;
-    vm->fp->locals[2] = *pred_func;
-  } else {
-    /* get result of predicate function */
-    hV *mapped = sbVm_pop(vm);
-    if (sbV_c_falsy(mapped)) {
-      /* one was false; return false */
-      sbVm_push_immediate(vm, &HVBOOL(FALSE));
-      return;
-    }
-  }
-
-  /* all true so far */
-  usize current_index = vm->fp->locals[1].integer++;
-  usize length;
-  hV *iter_values = sbList_get_value(vm->fp->locals[0].list, &length);
-  if (current_index < length) {
-    /* try next element */
-    sbVm_push(vm, &iter_values[current_index]);
-    sbVm_push_immediate(vm, &HVINT(1));
-    sbVm_call_func(vm, &vm->fp->locals[2]);
-  } else {
-    /* all passed */
-    sbVm_push_immediate(vm, &HVBOOL(TRUE));
-  }
-}
index 13fc006..950af1d 100644 (file)
@@ -255,15 +255,6 @@ void sbString_method(hVm vm) {
   const char *method_name = sbSymbol_name(method_name_val->symbol);
   /* TODO: Need a better way of resolving these */
   if (METHOD_IS("split")) {
-    sbVm_pop(vm); /* remove method name */
-    usize length;
-    char scratch[8];
-    const char *buf = sbString_get_value(target->string, scratch, &length);
-    hList l = sbList_new(length);
-    for (usize i = 0; i < length; i++) {
-      sbList_append(l, &HVSTR(sbString_new(&buf[i], 1)));
-    }
-    sbVm_push_immediate(vm, &HVLIST(l));
   }
 }
 
diff --git a/src/lib/integer.c b/src/lib/integer.c
new file mode 100644 (file)
index 0000000..76045b7
--- /dev/null
@@ -0,0 +1,32 @@
+#include "lib/libtable.h"
+
+#include "data/list.h"
+#include "data/string.h"
+#include "vm/exec.h"
+
+void list_each_cfunc(hVm vm, flag init);
+void list_map_cfunc(hVm vm, flag init);
+void list_filter_cfunc(hVm vm, flag init);
+void list_any_cfunc(hVm vm, flag init);
+void list_all_cfunc(hVm vm, flag init);
+
+sbLibTable g_integer_methods;
+
+static void to_string(hVm vm, hV *target, usize num_params) {
+  sbVm_pop(vm); /* remove method name */
+  char stackbuf[1024];
+  char *buf = stackbuf;
+  usize length = snprintf(buf, 1024, "%lld", (long long)target->integer);
+  if (length + 1 >= sizeof(stackbuf)) {
+    buf = malloc(length + 1);
+    snprintf(buf, length, "%lld", (long long)target->integer);
+  }
+  sbVm_push_immediate(vm, &HVSTR(sbString_new(buf, length)));
+  if (buf != stackbuf) {
+    free(buf);
+  }
+}
+
+void sbInteger_create_methods(void) {
+  LIB_REGISTER(&g_integer_methods, "to_string", to_string);
+}
diff --git a/src/lib/libtable.c b/src/lib/libtable.c
new file mode 100644 (file)
index 0000000..94ab7e1
--- /dev/null
@@ -0,0 +1,144 @@
+#include "lib/libtable.h"
+
+#include "data/hashtable.h"
+#include "vm/exec.h"
+#include "mem/debug.h"
+
+/*
+typedef sbLibTable {
+  usize used;
+  usize capacity;
+  hSymbol *keys;
+  sbLibFunc *funcs;
+} sbLibTable;
+*/
+
+extern void sbList_create_methods();
+extern void sbString_create_methods();
+extern void sbInteger_create_methods();
+
+static void insert_func(hSymbol *keys, sbLibFunc *funcs, usize capacity, hSymbol key, sbLibFunc func);
+
+void sbLibTable_initialize(hLibTable t, usize capacity) {
+  *t = (sbLibTable) {0};
+  if (capacity < 16) {
+    capacity = 16;
+  }
+  t->keys = calloc(capacity, sizeof(hSymbol));
+  t->funcs = calloc(capacity, sizeof(sbLibFunc));
+  t->capacity = capacity;
+}
+
+void sbLibTable_deinitialize(hLibTable t) {
+  free(t->keys);
+  free(t->funcs);
+  *t = (sbLibTable) {0};
+}
+
+void sbLib_sys_init() {
+  sbLibTable_initialize(&g_list_methods, 16);
+  sbLibTable_initialize(&g_string_methods, 16);
+  sbLibTable_initialize(&g_integer_methods, 16);
+
+  sbList_create_methods();
+  sbString_create_methods();
+  sbInteger_create_methods();
+}
+
+void sbLib_sys_deinit() {
+  sbLibTable_deinitialize(&g_list_methods);
+  sbLibTable_deinitialize(&g_string_methods);
+  sbLibTable_deinitialize(&g_integer_methods);
+}
+
+void sbLib_resolve_method(hVm vm) {
+  hV *target = sbVm_pop(vm);
+  hV *argc = sbVm_pop(vm);
+  if (argc->type != IT_INTEGER) {
+    CHECK("argc of send should be integer!");
+  }
+  /* subtract 1 because the method name is itself a param */
+  usize num_params = argc->integer - 1;
+  hV *method_name_val = sbVm_peek(vm, num_params);
+  if (method_name_val->type != IT_SYMBOL) {
+    /* TODO this may become not true */
+    PANIC("method name must be symbol!");
+  }
+
+  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;
+    default:
+      PANIC("Have not implemented this method table yet!");
+  }
+
+  sbLibFunc f = sbLibTable_find(table_to_use, method_name_val->symbol);
+  if (f) {
+    f(vm, target, num_params);
+  } else {
+    PANIC("invalid method name for type %lld: %s", (long long)target->type, sbSymbol_name(method_name_val->symbol));
+  }
+}
+
+sbLibFunc sbLibTable_find(hLibTable t, hSymbol key) {
+  sbHashValue hash = sbHash_hash_bytes((const char*)&key, sizeof(hSymbol));
+  usize index = hash % t->capacity;
+  while (t->keys[index] && t->keys[index] != key) {
+    index ++;
+    index %= t->capacity;
+  }
+
+  if (t->keys[index]) {
+    return t->funcs[index];
+  } else {
+    return NULL;
+  }
+}
+
+void sbLibTable_register(hLibTable t, const char *method_name, usize method_name_length, sbLibFunc behavior) {
+  if (t->used > t->capacity / 4 * 3) {
+    /* grow table and rehash */
+    usize new_capacity = t->capacity * 2;
+    hSymbol *new_keys = calloc(new_capacity, sizeof(hSymbol));
+    sbLibFunc *new_funcs = calloc(new_capacity, sizeof(sbLibFunc));
+
+    for (usize i = 0; i < t->capacity; i++) {
+      if (t->keys[i] != 0) {
+        insert_func(new_keys, new_funcs, new_capacity, t->keys[i], t->funcs[i]);
+      }
+    }
+
+    free(t->keys);
+    free(t->funcs);
+    t->keys = new_keys;
+    t->funcs = new_funcs;
+    t->capacity = new_capacity;
+  }
+
+  hSymbol sym = sbSymbol_from_bytes(method_name, method_name_length);
+  insert_func(t->keys, t->funcs, t->capacity, sym, behavior);
+  t->used ++;
+}
+
+/* --- */
+
+static void insert_func(hSymbol *keys, sbLibFunc *funcs, usize capacity, hSymbol key, sbLibFunc func) {
+  sbHashValue hash = sbHash_hash_bytes((const char*)&key, sizeof(hSymbol));
+  usize index = hash % capacity;
+
+  while (keys[index] != 0) {
+    index ++;
+    index %= capacity;
+  }
+
+  keys[index] = key;
+  funcs[index] = func;
+}
diff --git a/src/lib/libtable.h b/src/lib/libtable.h
new file mode 100644 (file)
index 0000000..3f11410
--- /dev/null
@@ -0,0 +1,36 @@
+#include "common.h"
+
+#include "data/symbol.h"
+
+#define LIB_REGISTER(t, n, b) (sbLibTable_register(t, n, sizeof(n) - 1, b))
+
+typedef void (*sbLibFunc)(hVm, hV*, usize);
+
+typedef struct sbLibTable {
+  usize used;
+  usize capacity;
+  hSymbol *keys;
+  sbLibFunc *funcs;
+} sbLibTable;
+
+extern sbLibTable g_list_methods;
+extern sbLibTable g_string_methods;
+extern sbLibTable g_integer_methods;
+
+typedef sbLibTable *hLibTable;
+
+void sbLibTable_initialize(hLibTable t, usize capacity);
+
+void sbLibTable_deinitialize(hLibTable t);
+
+sbLibFunc sbLibTable_find(hLibTable t, hSymbol key);
+
+void sbLibTable_register(hLibTable t, const char *method_name, usize method_name_length, sbLibFunc behavior);
+
+void sbLib_resolve_method(hVm vm);
+
+void sbLib_sys_init();
+
+void sbLib_sys_deinit();
+
+void sbList_method(hVm vm);
diff --git a/src/lib/list.c b/src/lib/list.c
new file mode 100644 (file)
index 0000000..ba9ba38
--- /dev/null
@@ -0,0 +1,312 @@
+#include "lib/libtable.h"
+
+#include "data/list.h"
+#include "data/string.h"
+#include "vm/exec.h"
+
+void list_each_cfunc(hVm vm, flag init);
+void list_map_cfunc(hVm vm, flag init);
+void list_filter_cfunc(hVm vm, flag init);
+void list_any_cfunc(hVm vm, flag init);
+void 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!");
+  }
+  sbVm_pop(vm); /* remove method name */
+  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);
+  sbVm_pop(vm); /* remove method name */
+  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 */
+  sbVm_pop(vm); /* remove method name */
+  usize length;
+  hV *elems = sbList_get_value(list->list, &length);
+  hList new_list = sbList_new(length);
+  for (usize i = length - 1; ; i--) {
+    sbList_append(new_list, &elems[i]);
+    if (i == 0) break;
+  }
+  sbList_get_value(new_list, &length);
+  sbVm_push_immediate(vm, &HVLIST(new_list));
+}
+
+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) {
+    hV *delimiter_v = sbVm_pop(vm);
+    if (delimiter_v->type != IT_STRING) {
+      PANIC("list#join parameter should be string!");
+    }
+    join_with = TRUE;
+    delimiter = delimiter_v->string;
+  }
+  sbVm_pop(vm); /* remove method name */
+  usize length;
+  hV *elems = sbList_get_value(list->list, &length);
+  hString joined = sbString_new("", 0);
+  for (usize i = 0; i < length; i++) {
+    if (elems[i].type != IT_STRING) {
+      PANIC("need string to join");
+    }
+    joined = sbString_concat(joined, elems[i].string);
+    if (join_with && i < length - 1) {
+      joined = sbString_concat(joined, delimiter);
+    }
+  }
+  sbVm_push_immediate(vm, &HVSTR(joined));
+}
+
+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 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) {
+  LIB_REGISTER(&g_list_methods, "length", length);
+  LIB_REGISTER(&g_list_methods, "push", push);
+  LIB_REGISTER(&g_list_methods, "reverse", reverse);
+  LIB_REGISTER(&g_list_methods, "join", join);
+  LIB_REGISTER(&g_list_methods, "each", each);
+  LIB_REGISTER(&g_list_methods, "map", map);
+  LIB_REGISTER(&g_list_methods, "filter", filter);
+  LIB_REGISTER(&g_list_methods, "any?", any_p);
+  LIB_REGISTER(&g_list_methods, "all?", all_p);
+}
+
+/* --- */
+
+void list_each_cfunc(hVm vm, flag init) {
+  if (init) {
+    /* store three state variables: the list we're iterating, the index into it, and the callback function */
+    sbVm_request_var_space(vm, 3);
+    hV *iterating_list = sbVm_pop(vm);
+    hV *loop_func = sbVm_pop(vm);
+    sbVm_pop(vm); /* remove method name */
+    hV index = HVINT(0);
+    vm->fp->locals[0] = *iterating_list;
+    vm->fp->locals[1] = index;
+    vm->fp->locals[2] = *loop_func;
+  } else {
+    /* loop func must have finished, so remove its result */
+    sbVm_pop(vm);
+  }
+
+  usize current_index = vm->fp->locals[1].integer++;
+  usize length;
+  hV *iter_values = sbList_get_value(vm->fp->locals[0].list, &length);
+  if (current_index < length) {
+    sbVm_push(vm, &iter_values[current_index]);
+    sbVm_push_immediate(vm, &HVINT(1));
+    sbVm_call_func(vm, &vm->fp->locals[2]);
+  } else {
+    /* if index was past the end, callback function won't be called, and we'll exit. return nil */
+    sbVm_push_immediate(vm, &HVNIL);
+  }
+}
+
+void list_map_cfunc(hVm vm, flag init) {
+  if (init) {
+    /* state: list being mapped, index, callback, result */
+    sbVm_request_var_space(vm, 4);
+    hV *iterating_list = sbVm_pop(vm);
+    hV *map_func = sbVm_pop(vm);
+    sbVm_pop(vm); /* remove method name */
+    usize length;
+    sbList_get_value(iterating_list->list, &length);
+    hV index = HVINT(0);
+    hV result = sbV_empty_list(length);
+
+    vm->fp->locals[0] = *iterating_list;
+    vm->fp->locals[1] = index;
+    vm->fp->locals[2] = *map_func;
+    vm->fp->locals[3] = result;
+  } else {
+    /* get result of map function and append its result to result list */
+    hV *mapped = sbVm_pop(vm);
+    sbList_append(vm->fp->locals[3].list, mapped);
+  }
+
+  usize current_index = vm->fp->locals[1].integer++;
+  usize length;
+  hV *iter_values = sbList_get_value(vm->fp->locals[0].list, &length);
+  if (current_index < length) {
+    sbVm_push(vm, &iter_values[current_index]);
+    sbVm_push_immediate(vm, &HVINT(1));
+    sbVm_call_func(vm, &vm->fp->locals[2]);
+  } else {
+    /* return result list */
+    sbVm_push_immediate(vm, &vm->fp->locals[3]);
+  }
+}
+
+void list_filter_cfunc(hVm vm, flag init) {
+  if (init) {
+    /* state: list being filtered, index, callback, result */
+    sbVm_request_var_space(vm, 4);
+    hV *iterating_list = sbVm_pop(vm);
+    hV *filter_func = sbVm_pop(vm);
+    sbVm_pop(vm); /* remove method name */
+    usize length;
+    sbList_get_value(iterating_list->list, &length);
+    hV index = HVINT(0);
+    hV result = sbV_empty_list(length);
+
+    vm->fp->locals[0] = *iterating_list;
+    vm->fp->locals[1] = index;
+    vm->fp->locals[2] = *filter_func;
+    vm->fp->locals[3] = result;
+  } else {
+    /* get result of filter function and append element to result if true */
+    hV *mapped = sbVm_pop(vm);
+    hV *element = sbVm_pop(vm);
+    if (!sbV_c_falsy(mapped)) {
+      sbList_append(vm->fp->locals[3].list, element);
+    }
+  }
+
+  usize current_index = vm->fp->locals[1].integer++;
+  usize length;
+  hV *iter_values = sbList_get_value(vm->fp->locals[0].list, &length);
+  if (current_index < length) {
+    /* put current value on stack twice: once as parameter for function, once to save for ourselves */
+    sbVm_push(vm, &iter_values[current_index]);
+    sbVm_push(vm, &iter_values[current_index]);
+    sbVm_push_immediate(vm, &HVINT(1));
+    sbVm_call_func(vm, &vm->fp->locals[2]);
+  } else {
+    /* return result list */
+    sbVm_push_immediate(vm, &vm->fp->locals[3]);
+  }
+}
+
+void list_any_cfunc(hVm vm, flag init) {
+  if (init) {
+    /* state: list being filtered, index, callback */
+    sbVm_request_var_space(vm, 3);
+    hV *iterating_list = sbVm_pop(vm);
+    hV *pred_func = sbVm_pop(vm);
+    sbVm_pop(vm); /* remove method name */
+    hV index = HVINT(0);
+
+    vm->fp->locals[0] = *iterating_list;
+    vm->fp->locals[1] = index;
+    vm->fp->locals[2] = *pred_func;
+  } else {
+    /* get result of predicate function */
+    hV *mapped = sbVm_pop(vm);
+    if (!sbV_c_falsy(mapped)) {
+      /* one was true! return true */
+      sbVm_push_immediate(vm, &HVBOOL(TRUE));
+      return;
+    }
+  }
+
+  /* haven't found any yet... */
+  usize current_index = vm->fp->locals[1].integer++;
+  usize length;
+  hV *iter_values = sbList_get_value(vm->fp->locals[0].list, &length);
+  if (current_index < length) {
+    /* try next element */
+    sbVm_push(vm, &iter_values[current_index]);
+    sbVm_push_immediate(vm, &HVINT(1));
+    sbVm_call_func(vm, &vm->fp->locals[2]);
+  } else {
+    /* no result found */
+    sbVm_push_immediate(vm, &HVBOOL(FALSE));
+  }
+}
+
+void list_all_cfunc(hVm vm, flag init) {
+  if (init) {
+    /* state: list being filtered, index, callback */
+    sbVm_request_var_space(vm, 3);
+    hV *iterating_list = sbVm_pop(vm);
+    hV *pred_func = sbVm_pop(vm);
+    sbVm_pop(vm); /* remove method name */
+    hV index = HVINT(0);
+
+    vm->fp->locals[0] = *iterating_list;
+    vm->fp->locals[1] = index;
+    vm->fp->locals[2] = *pred_func;
+  } else {
+    /* get result of predicate function */
+    hV *mapped = sbVm_pop(vm);
+    if (sbV_c_falsy(mapped)) {
+      /* one was false; return false */
+      sbVm_push_immediate(vm, &HVBOOL(FALSE));
+      return;
+    }
+  }
+
+  /* all true so far */
+  usize current_index = vm->fp->locals[1].integer++;
+  usize length;
+  hV *iter_values = sbList_get_value(vm->fp->locals[0].list, &length);
+  if (current_index < length) {
+    /* try next element */
+    sbVm_push(vm, &iter_values[current_index]);
+    sbVm_push_immediate(vm, &HVINT(1));
+    sbVm_call_func(vm, &vm->fp->locals[2]);
+  } else {
+    /* all passed */
+    sbVm_push_immediate(vm, &HVBOOL(TRUE));
+  }
+}
diff --git a/src/lib/string.c b/src/lib/string.c
new file mode 100644 (file)
index 0000000..e90c98f
--- /dev/null
@@ -0,0 +1,29 @@
+#include "lib/libtable.h"
+
+#include "data/list.h"
+#include "data/string.h"
+#include "vm/exec.h"
+
+void list_each_cfunc(hVm vm, flag init);
+void list_map_cfunc(hVm vm, flag init);
+void list_filter_cfunc(hVm vm, flag init);
+void list_any_cfunc(hVm vm, flag init);
+void list_all_cfunc(hVm vm, flag init);
+
+sbLibTable g_string_methods;
+
+static void split(hVm vm, hV *target, usize num_params) {
+  sbVm_pop(vm); /* remove method name */
+  usize length;
+  char scratch[8];
+  const char *buf = sbString_get_value(target->string, scratch, &length);
+  hList l = sbList_new(length);
+  for (usize i = 0; i < length; i++) {
+    sbList_append(l, &HVSTR(sbString_new(&buf[i], 1)));
+  }
+  sbVm_push_immediate(vm, &HVLIST(l));
+}
+
+void sbString_create_methods(void) {
+  LIB_REGISTER(&g_string_methods, "split", split);
+}
index 15bec01..2f316a5 100644 (file)
@@ -1,8 +1,8 @@
 #include "mem/debug.h"
 
-void print_memory_bytes(void *where, usize how_many) {
+void print_memory_bytes(const char *where, usize how_many) {
   for (usize i = 0; i < how_many; i++) {
-    printf("%02X ", ((u8*)(char*)where)[i]);
+    printf("%02X ", ((u8*)where)[i]);
   }
   printf("\n");
 }
index d8e9627..a8fcaa4 100644 (file)
@@ -1,3 +1,3 @@
 #include "common.h"
 
-void print_memory_bytes(void *where, usize how_many);
+void print_memory_bytes(const char *where, usize how_many);
index bf9acdf..34c49a4 100644 (file)
@@ -4,6 +4,7 @@
 #include "data/list.h"
 #include "data/reference.h"
 #include "data/closure.h"
+#include "lib/libtable.h"
 
 void call_block(hVm vm, usize block_id, hClosure closure);
 void return_from_block(hVm vm);
@@ -440,7 +441,7 @@ void execute_instruction(hVm vm) {
       call_block(vm, v->type, v->closure);
       break;
     case BC_SEND:
-      sbV_message_handler(vm);
+      sbLib_resolve_method(vm);
       break;
     case BC_NUMARG:
       param = get_param(vm);
index 6d89274..9208eec 100644 (file)
@@ -6,33 +6,6 @@
 #include "data/hashtable.h"
 #include "data/string.h"
 
-void sbV_message_handler(hVm vm) {
-  hV *target = sbVm_peek(vm, 0);
-  switch(target->type) {
-    case IT_LIST:
-      sbList_method(vm);
-      break;
-    case IT_INTEGER:
-      sbInteger_method(vm);
-      break;
-    case IT_STRING:
-      sbString_method(vm);
-      break;
-    default:
-      if (target->type < 0) {
-        PANIC("haven't implemented method calling for this intrinsic type!");
-      }
-      if (target->type & FLAG_SQUIGGLY) {
-        /* squiggle function */
-        sbVm_call_func(vm, target);
-      } else {
-        /* normal function */
-        PANIC("haven't implemented method calling for functions yet!");
-        //sbFunction_method(target, vm);
-      }
-  }
-}
-
 hV sbV_add(const hV *a, const hV *b) {
   if (a->type == IT_INTEGER && b->type == IT_INTEGER) {
     return sbV_int(sbInteger_sum(a->integer, b->integer));