experimenting with writing a nontrivial amount of code
authorcassowarii <2374677+cassowarii@users.noreply.github.com>
Tue, 14 Jul 2026 06:21:18 +0000 (23:21 -0700)
committercassowarii <2374677+cassowarii@users.noreply.github.com>
Tue, 14 Jul 2026 06:21:18 +0000 (23:21 -0700)
17 files changed:
sample/generator.sbd [new file with mode: 0644]
sample/project_euler/pe08.sa
src/data/hashtable.c
src/data/hashtable.h
src/lib/lib.c
src/lib/method.h
src/lib/method/hash.c [new file with mode: 0644]
src/lib/module.h
src/lib/module/global.c
src/lib/module/op.c [new file with mode: 0644]
src/lib/sentinel.c
src/lib/sentinel.h
src/parse/lexer.c
src/vm/exec.c
src/vm/exec.h
src/vm/operations.c
src/vm/operations.h

diff --git a/sample/generator.sbd b/sample/generator.sbd
new file mode 100644 (file)
index 0000000..0687f95
--- /dev/null
@@ -0,0 +1,128 @@
+let generator = (=> {
+  let instance_methods = {
+    filter: => f {
+      => pred {
+        generator => {
+          let result = nil
+          # boolean operators don't short circuit currently, oops
+          let done = false
+          repeat {
+            result = f()
+            if result == nil {
+              done = true
+            } else if pred result {
+              done = true
+            }
+          } while not done
+          result
+        }
+      }
+    },
+
+    map: => f {
+      => transform {
+        generator => {
+          let result = f()
+          if result != nil {
+            transform(result)
+          } else {
+            nil
+          }
+        }
+      }
+    },
+
+    continue: => f {
+      => make_next {
+        let last = nil
+        let ended = false
+        generator => {
+          if not ended {
+            let this_one = f()
+            if this_one != nil {
+              last = this_one
+            } else {
+              ended = true
+            }
+          }
+
+          if ended {
+            last = make_next(last)
+          }
+          last
+        }
+      }
+    },
+
+    upuntil: => f {
+      => cond {
+        generator => {
+          let result = f()
+          if result == nil or cond result {
+            nil
+          } else {
+            result
+          }
+        }
+      }
+    },
+
+    reduce: => f {
+      => init, combine {
+        let value = init
+        repeat {
+          let result = f()
+          value = combine(result, value) unless result == nil
+        } while result != nil
+        value
+      }
+    },
+
+    each: => f {
+      => callback {
+        repeat {
+          let result = f()
+          callback(result) unless result == nil
+        } while result != nil
+      }
+    },
+  }
+
+  let class_methods = {
+    of: => ...list {
+      let index = 0
+      generator => {
+        if index >= list.length {
+          nil
+        } else {
+          let result = list[index]
+          index = index + 1
+          result
+        }
+      }
+    }
+  }
+
+  => f {
+    if f == op::index {
+      class_methods(op::index)
+    } else {
+      => method {
+        if method == :next {
+          f
+        } else {
+          instance_methods[method](f)
+        }
+      }
+    }
+  }
+})()
+
+let sum_odd_squares_under_1_000_000 = generator::of(1)
+  .continue(=> a { a + 1 })
+  .map(=> a { a * a })
+  .filter(=> a { not a %% 2 })
+  .upuntil(=> a { a >= 1_000_000 })
+  .reduce(0, => a, b { a + b })
+
+println sum_odd_squares_under_1_000_000
index 0485fe6..855cb15 100644 (file)
@@ -4,3 +4,5 @@ let span = 13
 println list::iota(0, digits.length - span + 1).map(=> index {
   digits[index..index + span].split().map(integer::from).reduce(1, => a, b { a * b })
 }).reduce(0, math::max)
+
+println list::iota(0, digits.length - span + 1).map(| digits[_.._ + span].split().map(integer::from).reduce(1, {*})).reduce(0, math::max)
index 93978e5..5166904 100644 (file)
@@ -9,8 +9,9 @@
 sbPool g_hashtable_pool = {0};
 
 typedef struct hashtbl {
-  usize size;
+  usize capacity;
   usize used;
+  usize n_elems;
   hHash handle;
   flag allocated;
   flag is_inline;
@@ -115,11 +116,16 @@ void sbHash_delete(hHash h, hV *key, hV *value) {
   delete_key(t, key);
 }
 
+usize sbHash_get_size(hHash h) {
+  hashtbl *t = find_tbl_for_handle(h);
+  return t->n_elems;
+}
+
 /* --- */
 
 static void set_hashtbl_size(hashtbl *t, usize new_size, flag rehash_all) {
   if (new_size < INLINE_TABLE_LENGTH) new_size = INLINE_TABLE_LENGTH;
-  if (new_size < t->size) PANIC("cannot shrink hashtable allocation");
+  if (new_size < t->capacity) PANIC("cannot shrink hashtable allocation");
 
   if (new_size == INLINE_TABLE_LENGTH) {
     t->is_inline = 1;
@@ -129,7 +135,7 @@ static void set_hashtbl_size(hashtbl *t, usize new_size, flag rehash_all) {
     if (rehash_all) {
       hV *current_keys, *current_values;
       get_ptrs_for_tbl(t, &current_keys, &current_values);
-      for (usize i = 0; i < t->size; i++) {
+      for (usize i = 0; i < t->capacity; i++) {
         if (current_keys[i].type == IT_NOTHING || current_keys[i].type == ITX_TOMBSTONE) continue;
         set_key_in_array(new_keys, new_values, new_size, &current_keys[i], &current_values[i]);
         /* adding a new k/v pair will retain the values, so we need to release them
@@ -148,11 +154,11 @@ static void set_hashtbl_size(hashtbl *t, usize new_size, flag rehash_all) {
     t->external.values = new_values;
     t->is_inline = 0;
   }
-  t->size = new_size;
+  t->capacity = new_size;
 }
 
 static hV *get_key_ptr_for_tbl(hashtbl *t, usize *length_out) {
-  if (length_out) *length_out = t->size;
+  if (length_out) *length_out = t->capacity;
   if (t->is_inline) {
     return t->internal.keys;
   } else {
@@ -186,7 +192,7 @@ static usize find_key_index_in_array(hV *keys, usize length, hV *key) {
 }
 
 static usize find_index_by_key(hashtbl *t, hV *key) {
-  return find_key_index_in_array(get_key_ptr_for_tbl(t, NULL), t->size, key);
+  return find_key_index_in_array(get_key_ptr_for_tbl(t, NULL), t->capacity, key);
 }
 
 static usize set_key_in_array(hV *keys, hV *values, usize length, hV *key, hV *value) {
@@ -226,8 +232,9 @@ static usize set_key(hashtbl *t, hV *key, hV *value) {
     keys[index] = *key;
     values[index] = *value;
     t->used ++;
-    if (t->used >= t->size * 3 / 4) {
-      set_hashtbl_size(t, t->size * 2, TRUE);
+    t->n_elems ++;
+    if (t->used >= t->capacity * 3 / 4) {
+      set_hashtbl_size(t, t->capacity * 3 / 2, TRUE);
     }
   } else {
     /* replacing something that already exists. we can keep
@@ -247,6 +254,7 @@ static hV delete_key(hashtbl *t, hV *key) {
   get_ptrs_for_tbl(t, &keys, &values);
   hV to_return = values[index];
   keys[index].type = ITX_TOMBSTONE;
+  t->n_elems --;
   return to_return;
 }
 
index b2b4f09..78b35eb 100644 (file)
@@ -23,3 +23,5 @@ void sbHash_insert(hHash h, hV *key, hV *value);
 hV *sbHash_find_or_insert(hHash h, hV *key, hV *value);
 
 void sbHash_delete(hHash h, hV *key, hV *value);
+
+usize sbHash_get_size(hHash h);
index c0dea9d..20fddd1 100644 (file)
@@ -21,6 +21,7 @@ void sbLib_sys_init() {
   sbString_create_methods();
   sbInteger_create_methods();
   sbFloat_create_methods();
+  sbHash_create_methods();
 }
 
 void sbLib_sys_deinit() {
@@ -28,6 +29,7 @@ void sbLib_sys_deinit() {
   sbLibTable_deinitialize(&g_string_methods);
   sbLibTable_deinitialize(&g_integer_methods);
   sbLibTable_deinitialize(&g_float_methods);
+  sbLibTable_deinitialize(&g_hash_methods);
 
   sbLibTable_deinitialize(&g_global_module);
 }
@@ -85,7 +87,9 @@ void sbLib_resolve_method(hVm vm) {
         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);
+          sbVm_print_stack(vm);
+          PANIC("Wrong number of arguments passed to method '%s'! (expected %d~%d, got %lld)",
+              sbSymbol_name(method_name_val->symbol), f->min_args, f->max_args, (long long)num_params);
         }
       }
     } else {
@@ -109,7 +113,7 @@ void sbLib_resolve_property(hVm vm) {
 
   hV *method_name_val = sbVm_pop(vm);
   if (method_name_val->type != IT_SYMBOL) {
-    PANIC("method name must be symbol!");
+    PANIC("method name must be symbol! (is %zd, target %zd)", method_name_val->type, target->type);
   }
 
   sbLibTable *table_to_use = find_method_table(target);
@@ -167,6 +171,8 @@ static sbLibTable *find_method_table(hV *target) {
       return &g_integer_methods;
     case IT_FLOAT:
       return &g_float_methods;
+    case IT_HASH:
+      return &g_hash_methods;
     default:
       return NULL;
   }
index 36ed223..3b1ebcd 100644 (file)
@@ -2,8 +2,10 @@ extern sbLibTable g_list_methods;
 extern sbLibTable g_string_methods;
 extern sbLibTable g_integer_methods;
 extern sbLibTable g_float_methods;
+extern sbLibTable g_hash_methods;
 
 void sbList_create_methods(void);
 void sbString_create_methods(void);
 void sbInteger_create_methods(void);
 void sbFloat_create_methods(void);
+void sbHash_create_methods(void);
diff --git a/src/lib/method/hash.c b/src/lib/method/hash.c
new file mode 100644 (file)
index 0000000..2e20ddc
--- /dev/null
@@ -0,0 +1,36 @@
+#include "common.h"
+
+#include "lib/table.h"
+#include "lib/sentinel.h"
+#include "data/integer.h"
+#include "data/hashtable.h"
+#include "vm/exec.h"
+
+sbLibTable g_hash_methods;
+
+static void to_hash(hVm vm, hV *target, usize num_params) {
+  /* to_hash for a hash just returns itself */
+  sbVm_push_immediate(vm, target);
+}
+
+static void length(hVm vm, hV *target, usize num_params) {
+  usize length = sbHash_get_size(target->hash);
+  sbVm_push_immediate(vm, &HVINT(length));
+}
+
+static void get_index(hVm vm, hV *target, usize num_params) {
+  hV *key = sbVm_pop(vm);
+  hV *result = sbHash_find(target->hash, key);
+  if (result == IT_NOTHING) {
+    sbVm_push_immediate(vm, &HVNIL);
+  } else {
+    sbVm_push(vm, result);
+  }
+}
+
+void sbHash_create_methods(void) {
+  sbLibTable_initialize(&g_hash_methods, 16, TRUE);
+  REGISTER_METHOD(&g_hash_methods, "length", &PROPERTY(length));
+  REGISTER_METHOD_SYM(&g_hash_methods, S_OP_INDEX, &METHOD(get_index, 1, 1));
+  REGISTER_METHOD_SYM(&g_hash_methods, S_OP_TO_HASH, &PROPERTY(to_hash));
+}
index b0797dc..e477e22 100644 (file)
@@ -1,10 +1,12 @@
 extern sbLibTable g_global_module;
+extern sbLibTable g_op_module;
 extern sbLibTable g_math_module;
 extern sbLibTable g_list_module;
 extern sbLibTable g_string_module;
 extern sbLibTable g_integer_module;
 
 void sbLib_loadmodule_global();
+void sbLib_loadmodule_op();
 void sbLib_loadmodule_math();
 void sbLib_loadmodule_list();
 void sbLib_loadmodule_string();
index f47a87b..2524594 100644 (file)
@@ -37,6 +37,9 @@ void sbLib_loadmodule_global() {
   sbLib_loadmodule_list();
   REGISTER_VALUE(&g_global_module, "list", &HVMODULE(&g_list_module));
 
+  sbLib_loadmodule_op();
+  REGISTER_VALUE(&g_global_module, "op", &HVMODULE(&g_op_module));
+
   sbLib_loadmodule_math();
   REGISTER_VALUE(&g_global_module, "math", &HVMODULE(&g_math_module));
 }
diff --git a/src/lib/module/op.c b/src/lib/module/op.c
new file mode 100644 (file)
index 0000000..7401492
--- /dev/null
@@ -0,0 +1,28 @@
+#include "common.h"
+
+#include "lib/table.h"
+#include "lib/sentinel.h"
+#include "data/list.h"
+#include "data/symbol.h"
+#include "vm/exec.h"
+
+sbLibTable g_op_module;
+
+void sbLib_loadmodule_op() {
+  sbLibTable_initialize(&g_op_module, 16, FALSE);
+  REGISTER_VALUE(&g_op_module, "add", &HVSYM(S_OP_ADD));
+  REGISTER_VALUE(&g_op_module, "sub", &HVSYM(S_OP_SUB));
+  REGISTER_VALUE(&g_op_module, "mul", &HVSYM(S_OP_MUL));
+  REGISTER_VALUE(&g_op_module, "div", &HVSYM(S_OP_DIV));
+  REGISTER_VALUE(&g_op_module, "intdiv", &HVSYM(S_OP_INTDIV));
+  REGISTER_VALUE(&g_op_module, "mod", &HVSYM(S_OP_MOD));
+  REGISTER_VALUE(&g_op_module, "eq", &HVSYM(S_OP_EQ));
+  REGISTER_VALUE(&g_op_module, "lt", &HVSYM(S_OP_LT));
+  REGISTER_VALUE(&g_op_module, "le", &HVSYM(S_OP_LE));
+  REGISTER_VALUE(&g_op_module, "call", &HVSYM(S_OP_CALL));
+  REGISTER_VALUE(&g_op_module, "set", &HVSYM(S_OP_SET));
+  REGISTER_VALUE(&g_op_module, "index", &HVSYM(S_OP_INDEX));
+  REGISTER_VALUE(&g_op_module, "setindex", &HVSYM(S_OP_SETINDEX));
+  REGISTER_VALUE(&g_op_module, "rangeindex", &HVSYM(S_OP_RANGEINDEX));
+  REGISTER_VALUE(&g_op_module, "setrangeindex", &HVSYM(S_OP_SETRANGEINDEX));
+}
index dab8ba5..1a1261e 100644 (file)
@@ -2,9 +2,9 @@
 
 #include "data/symbol.h"
 
-hSymbol S_OP_ADD, S_OP_SUB, S_OP_MUL, S_OP_DIV, S_OP_FLDIV, S_OP_MOD;
+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_INDEX_SET;
+hSymbol S_OP_CALL, S_OP_SET, S_OP_INDEX, S_OP_SETINDEX, S_OP_RANGEINDEX, S_OP_SETRANGEINDEX;
 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() {
@@ -12,7 +12,7 @@ void sbLib_create_sentinels() {
   S_OP_SUB = SENTINEL("<op::sub>");
   S_OP_MUL = SENTINEL("<op::mul>");
   S_OP_DIV = SENTINEL("<op::div>");
-  S_OP_FLDIV = SENTINEL("<op::intdiv>");
+  S_OP_INTDIV = SENTINEL("<op::intdiv>");
   S_OP_MOD = SENTINEL("<op::mod>");
   S_OP_EQ = SENTINEL("<op::eq>");
   S_OP_LT = SENTINEL("<op::lt>");
@@ -20,7 +20,9 @@ void sbLib_create_sentinels() {
   S_OP_CALL = SENTINEL("<op::call>");
   S_OP_SET = SENTINEL("<op::set>");
   S_OP_INDEX = SENTINEL("<op::index>");
-  S_OP_INDEX_SET = SENTINEL("<op::set_index>");
+  S_OP_SETINDEX = SENTINEL("<op::setindex>");
+  S_OP_RANGEINDEX = SENTINEL("<op::rangeindex>");
+  S_OP_SETRANGEINDEX = SENTINEL("<op::setrangeindex>");
   S_OP_TO_STRING = SENTINEL("<string::convert>");
   S_OP_TO_INT = SENTINEL("<integer::convert>");
   S_OP_TO_FLOAT = SENTINEL("<float::convert>");
index fca6174..4a394d8 100644 (file)
@@ -1,8 +1,8 @@
 #include "common.h"
 
-extern hSymbol S_OP_ADD, S_OP_SUB, S_OP_MUL, S_OP_DIV, S_OP_FLDIV, S_OP_MOD;
+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_INDEX_SET;
+extern hSymbol S_OP_CALL, S_OP_SET, S_OP_INDEX, S_OP_SETINDEX, S_OP_RANGEINDEX, S_OP_SETRANGEINDEX;
 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 7ec339a..be57d9e 100644 (file)
@@ -390,8 +390,20 @@ static void compute_next_token(hLexer lx) {
         unstack_all_invisible_parentheses(lx);
     }
 
+    flag at_line_start;
+    switch (lx->last_token_seen.type) {
+      case ';':
+      case '{':
+      case '}':
+      case T_rELSE:
+        at_line_start = TRUE;
+        break;
+      default:
+        at_line_start = FALSE;
+    }
+
     if (begins_brace_terminated_state(token.type)
-        || (begins_brace_terminated_state_at_line_start(token.type) && lx->last_token_seen.type == ';')) {
+          || (begins_brace_terminated_state_at_line_start(token.type) && at_line_start)) {
         /* don't start brace-terminated state if we are directly after a '}' (otherwise it gets
          * confused by things like repeat..until */
         if (lx->last_token_seen.type != '}') {
index 24be18b..2d1f0a2 100644 (file)
@@ -5,6 +5,7 @@
 #include "data/reference.h"
 #include "data/closure.h"
 #include "lib/lib.h"
+#include "lib/sentinel.h"
 
 void call_block(hVm vm, usize block_id, hClosure closure);
 void call_builtin(hVm vm, hV *to_call);
@@ -72,6 +73,10 @@ hV *sbVm_pop(hVm vm) {
   return pop_stack(vm);
 }
 
+hV *sbVm_npop(hVm vm, usize how_many) {
+  return npop_stack(vm, how_many);
+}
+
 hV *sbVm_peek(hVm vm, usize where) {
   return peek_stack(vm, where);
 }
@@ -97,7 +102,7 @@ void sbVm_call_func(hVm vm, hV *func) {
 void sbVm_transfer_to_func(hVm vm, hV *func) {
   /* tail call */
   return_from_block(vm);
-  call_block(vm, func->type, func->closure);
+  sbVm_call_func(vm, func);
 }
 
 void sbVm_call_c_func(hVm vm, sbRuntimeCFunc func) {
@@ -189,6 +194,8 @@ void call_bound_method(hVm vm, hV *to_call) {
   }
   peek_stack(vm, 0)->integer ++;
   push_stack(vm, sbRef_deref(ref));
+
+  /* resolve method normally */
   sbLib_resolve_method(vm);
 }
 
@@ -645,11 +652,7 @@ void execute_instruction(hVm vm) {
       sbV_decr(v);
       break;
     case BC_OP_INDEX:
-      v = peek_stack(vm, 1);
-      w = peek_stack(vm, 0);
-      x = sbV_index(v, w);
-      npop_stack(vm, 2);
-      push_stack(vm, x);
+      sbV_index(vm);
       break;
     case BC_OP_RANGEINDEX:
       v = peek_stack(vm, 2);
index 6041238..2e4067d 100644 (file)
@@ -97,6 +97,8 @@ void sbVm_push_immediate(hVm vm, hV *value);
 
 hV *sbVm_pop(hVm vm);
 
+hV *sbVm_npop(hVm vm, usize how_many);
+
 hV *sbVm_peek(hVm vm, usize where);
 
 void sbVm_swap(hVm vm);
index bd406ae..0904670 100644 (file)
@@ -6,6 +6,7 @@
 #include "data/hashtable.h"
 #include "data/string.h"
 #include "lib/table.h"
+#include "lib/sentinel.h"
 
 hV sbV_add(const hV *a, const hV *b) {
   if (a->type == IT_INTEGER && b->type == IT_INTEGER) {
@@ -43,7 +44,7 @@ hV sbV_mod(const hV *a, const hV *b) {
   if (a->type == IT_INTEGER && b->type == IT_INTEGER) {
     return sbV_int(a->integer % b->integer);
   } else {
-    PANIC("todo");
+    PANIC("todo %zd", a->type);
   }
 }
 
@@ -126,19 +127,24 @@ hV sbV_append(hV *a, hV *b) {
   }
 }
 
-hV *sbV_index(hV *a, hV *b) {
+void sbV_index(hVm vm) {
+  hV *a = sbVm_peek(vm, 1);
+  hV *b = sbVm_peek(vm, 0);
   if (a->type == IT_LIST && b->type == IT_INTEGER) {
-    return sbList_index(a->list, b->integer);
-  } else if (a->type == IT_HASH) {
-    return sbHash_find(a->hash, b);
-  } else if (a->type == IT_MODULE) {
-    if (b->type == IT_SYMBOL) {
-      return sbLibTable_find_value(a->module, b->symbol);
-    } else {
-      PANIC("module cannot be indexed by non-symbol");
-    }
+    sbVm_npop(vm, 2);
+    hV *result = sbList_index(a->list, b->integer);
+    sbVm_push(vm, result);
+  } else if (a->type == IT_MODULE && b->type == IT_SYMBOL) {
+    sbVm_npop(vm, 2);
+    hV *result = sbLibTable_find_value(a->module, b->symbol);
+    sbVm_push(vm, result);
   } else {
-    PANIC("todo %lld %lld", (long long)a->type, (long long)b->type);
+    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);
   }
 }
 
index 5683aa4..56c69b2 100644 (file)
@@ -24,7 +24,7 @@ hV sbV_le(const hV *a, const hV *b);
 
 hV sbV_append(hV *a, hV *b);
 
-hV *sbV_index(hV *a, hV *b);
+void sbV_index(hVm vm);
 
 hV sbV_rangeindex(hV *a, hV *b, hV *c);