range indexing a[b..c] (i reserve the right to change this syntax)
authorcassowarii <2374677+cassowarii@users.noreply.github.com>
Mon, 13 Jul 2026 04:42:26 +0000 (21:42 -0700)
committercassowarii <2374677+cassowarii@users.noreply.github.com>
Mon, 13 Jul 2026 04:42:26 +0000 (21:42 -0700)
15 files changed:
src/compile/emit.c
src/compile/ir.c
src/data/integer.c
src/data/integer.h
src/data/list.c
src/data/list.h
src/data/value.c
src/data/value.h
src/parse/ast.h
src/parse/parser.c
src/parse/scanner.c
src/vm/bytecode.h
src/vm/exec.c
src/vm/operations.c
src/vm/operations.h

index ccd02e4..37638ba 100644 (file)
@@ -435,6 +435,9 @@ void compile_op(sbVmCompiler *cm, sbAstOp op) {
     case AST_OP_AND: EMIT(BC_OP_AND); break;
     case AST_OP_OR: EMIT(BC_OP_OR); break;
     case AST_OP_INDEX: EMIT(BC_OP_INDEX); break;
+    case AST_OP_RANGEINDEX: EMIT(BC_OP_RANGEINDEX); 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;
     default:
       PANIC("unknown operation! (%lld / %c)", (long long)op, op);
index 88807fe..c625403 100644 (file)
@@ -1039,9 +1039,20 @@ static sbIrExpr *compile_ast_expr(hIrChunk ck, sbAst node, flag list_context) {
     sbIrExpr *hash = compile_ast_hash(ck, node->seq.left);
     return hash;
   } else if (node->type == AST_NODE_OP) {
-    if (node->op.type == AST_OP_SPLAT) {
+    if (node->op.type == AST_OP_RANGE) {
+      chunk_error(ck, "'..' operator not permitted in this context\n");
+      return NULL;
+    } else if (node->op.type == AST_OP_RANGEINDEX) {
+      return expr_op(ck, AST_OP_RANGEINDEX,
+          compile_ast_expr(ck, node->op.left, FALSE),
+          expr_op(ck, AST_OP_RANGE,
+            compile_ast_expr(ck, node->op.right->op.left, FALSE),
+            compile_ast_expr(ck, node->op.right->op.right, FALSE)
+          )
+        );
+    } else if (node->op.type == AST_OP_SPLAT) {
       if (!list_context) {
-        chunk_error(ck, "'...' not allowed outside a list");
+        chunk_error(ck, "'...' not allowed outside a list\n");
         return NULL;
       } else {
         return expr_op(ck, AST_OP_SPLAT, compile_ast_expr(ck, node->op.left, FALSE), NULL);
index b06ed74..bd12d06 100644 (file)
@@ -87,6 +87,14 @@ void sbInteger_release(hInteger a) {
   }
 }
 
+i64 sbInteger_get_value(hInteger a) {
+  if (!is_bigint(a)) {
+    return a;
+  } else {
+    PANIC("Integer value out of range");
+  }
+}
+
 hInteger sbInteger_sum(hInteger a, hInteger b) {
   if (!is_bigint(a) && !is_bigint(b)) {
     return sbInteger_new(a + b);
index b7beed3..ce092ef 100644 (file)
@@ -11,6 +11,8 @@ void sbInteger_sys_deinit();
 
 hInteger sbInteger_new(i64 value);
 
+i64 sbInteger_get_value(hInteger a);
+
 void sbInteger_retain(hInteger a);
 
 void sbInteger_release(hInteger a);
index 467e9ea..cd4b438 100644 (file)
@@ -2,6 +2,7 @@
 
 #include "gc/gcinfo.h"
 #include "vm/exec.h"
+#include "mem/debug.h"
 
 #define LIST_PER_BLOCK 256
 #define METHOD_IS(name) (!sbstrncmp(method_name, name, sizeof(name)))
@@ -33,6 +34,17 @@ hList sbList_new(usize capacity) {
   return index;
 }
 
+hList sbList_of(usize length, hV *items) {
+  usize index;
+  usize capacity = length;
+  sbList *l = sbPool_alloc(&g_list_pool, &index);
+  if (capacity < 4) capacity = 4;
+  sbBuffer_initialize(&l->items, capacity * sizeof(hV));
+  sbBuffer_set_size(&l->items, length * sizeof(hV));
+  memcpy(l->items.data, items, length * sizeof(hV));
+  return index;
+}
+
 void sbList_append(hList list, hV *item) {
   sbList *l = get_list_by_handle(list);
   sbV_retain(item);
index 36b32a0..14432d6 100644 (file)
@@ -6,6 +6,8 @@ void sbList_sys_deinit();
 
 hList sbList_new(usize capacity);
 
+hList sbList_of(usize length, hV *items);
+
 void sbList_append(hList list, hV *item);
 
 hV *sbList_get_value(hList list, usize *length);
index 68517cd..97b491f 100644 (file)
@@ -67,6 +67,10 @@ hV sbV_empty_hash(usize capacity) {
   };
 }
 
+hV sbV_empty_string() {
+  return sbV_string((hString)0);
+}
+
 flag sbV_c_eq(const hV *a, const hV *b) {
   if (a->type != b->type) return FALSE;
   if (a->type == ITX_TOMBSTONE || b->type == ITX_TOMBSTONE) return FALSE;
index 6aa6a63..1c28d80 100644 (file)
@@ -75,6 +75,7 @@ hV sbV_int(hInteger i);
 hV sbV_boolean(flag b);
 hV sbV_empty_list(usize capacity);
 hV sbV_empty_hash(usize capacity);
+hV sbV_empty_string();
 
 flag sbV_c_eq(const hV *a, const hV *b);
 flag sbV_c_falsy(const hV *a);
index c870e7a..dc317dc 100644 (file)
@@ -66,6 +66,7 @@ typedef enum sbAstOp {
   AST_OP_RANGE,
   AST_OP_DIVBY,
   AST_OP_INDEX,
+  AST_OP_RANGEINDEX,
   AST_OP_SCOPE,
   AST_OP_UNPLUS,
   AST_OP_UNMINUS,
index 639fec7..897d6a3 100644 (file)
@@ -118,8 +118,6 @@ static tokenspelling token_spellings[] = {
   { T_BACKSLASH, "'\\'" },
   { T_ARROW, "'->'" },
   { T_FATARROW, "'=>'" },
-  { T_SQUIGARROW, "'~>'" },
-  { T_BACKSQUIGARROW, "'<~'" },
   { T_COLONBRACE, "':{'" },
   { T_PAAMAYIM_NEKUDOTAYIM, "'::'" },
   { T_DOUBLEEQUALS, "'=='" },
@@ -146,7 +144,6 @@ static const char *const TOKEN_SPELLING_UNKNOWN = "<unknown-token>";
 
 static binop binops[] = {
   { T_PIPE, 6, 7, AST_OP_PIPE },
-  { T_BACKSQUIGARROW, 8, 9, AST_OP_SEND },
   { T_rOR, 15, 16, AST_OP_OR },
   { T_rAND, 20, 21, AST_OP_AND },
   { T_rIN, 25, 26, AST_OP_IN },
@@ -164,7 +161,6 @@ static binop binops[] = {
   { T_PERCENT, 60, 61, AST_OP_MOD },
   { T_DOUBLESLASH, 60, 61, AST_OP_FLDIV },
   { T_DOUBLEASTERISK, 71, 70, AST_OP_POW },
-  { T_TWODOT, 80, 81, AST_OP_RANGE },
   { T_DOT, 90, 91, AST_OP_NULL },
   { T_ARROW, 90, 91, AST_OP_NULL },
   { T_LPAREN, 90, 91, AST_OP_NULL },
@@ -531,13 +527,6 @@ static sbAst parse_expr(hParser pr, u8 min_precedence) {
     if (!expect(pr, T_RPAREN)) return syntax_error(pr);
     sbAst body = parse_block(pr);
     lhs = seq_node(pr, AST_VAL_FUNC, params, body);
-  } else if (t.type == T_SQUIGARROW) {
-    next_token(pr);
-    if (!expect(pr, T_LPAREN)) return syntax_error(pr);
-    sbAst params = parse_comma_exprs(pr, NULL);
-    if (!expect(pr, T_RPAREN)) return syntax_error(pr);
-    sbAst body = parse_block(pr);
-    lhs = seq_node(pr, AST_VAL_OBJ, params, body);
   } else if (t.type == T_COLONBRACE) {
     next_token(pr);
     sbAst body = parse_stmtseq(pr);
@@ -600,6 +589,8 @@ static sbAst parse_expr(hParser pr, u8 min_precedence) {
       break;
     }
 
+    sbAstOp ast_op = infix->ast_op;
+
     next_token(pr); /* consume operator token */
 
     sbAst rhs;
@@ -611,6 +602,16 @@ static sbAst parse_expr(hParser pr, u8 min_precedence) {
     } else if (op.type == T_LBRACKET) {
       /* indexing */
       rhs = parse_expr(pr, 0);
+      if (rhs == NO_NODE) return syntax_error(pr);
+
+      if (expect(pr, T_TWODOT)) {
+        /* Range indexing */
+        ast_op = AST_OP_RANGEINDEX;
+        sbAst end_range = parse_expr(pr, 0);
+        if (end_range == NO_NODE) return syntax_error(pr);
+        rhs = binop_node(pr, AST_OP_RANGE, rhs, end_range);
+      }
+
       if (!expect(pr, T_RBRACKET)) return syntax_error(pr);
     } else if (op.type == T_DOT || op.type == T_ARROW) {
       /* . and -> parse an identifier to their right as a symbol that will
@@ -641,7 +642,7 @@ static sbAst parse_expr(hParser pr, u8 min_precedence) {
     }
 
     if (ast_type == AST_NODE_OP) {
-      lhs = binop_node(pr, infix->ast_op, lhs, rhs);
+      lhs = binop_node(pr, ast_op, lhs, rhs);
     } else {
       lhs = seq_node(pr, ast_type, lhs, rhs);
     }
index 5808532..0e210ba 100644 (file)
@@ -305,15 +305,6 @@ static sbLexToken compute_next_token(hScanner sc) {
             /* ! on its own is not an operator */
             new_token.type = T_ERROR;
         }
-    } else if (ch == '~') {
-        ch = NEXT;
-        if (ch == '>') {
-            new_token.type = T_SQUIGARROW;
-            NEXT;
-        } else {
-            /* ~ on its own is not an operator */
-            new_token.type = T_ERROR;
-        }
     } else if (ch == '>') {
         ch = NEXT;
         if (ch == '>') {
@@ -346,9 +337,6 @@ static sbLexToken compute_next_token(hScanner sc) {
         } else if (ch == '=') {
             new_token.type = T_LESSEQUALS;
             NEXT;
-        } else if (ch == '~') {
-            new_token.type = T_BACKSQUIGARROW;
-            NEXT;
         } else {
             new_token.type = T_LESS;
         }
index 54368eb..b994d95 100644 (file)
@@ -51,6 +51,7 @@ typedef enum sbOpcode {
   BC_OP_AND,            // logical and
   BC_OP_OR,             // logical or
   BC_OP_INDEX,          // index [] or ::
+  BC_OP_RANGEINDEX,     // index [a..b]
   BC_ALLOC_VARS,        // create space in rstack for local variables
   BC_CLOSURE,           // create closure from top elements of stack
   BC_LIST_GATHER,       // create list from count + list of values on stack
index 2d2ee7c..24be18b 100644 (file)
@@ -651,6 +651,14 @@ void execute_instruction(hVm vm) {
       npop_stack(vm, 2);
       push_stack(vm, x);
       break;
+    case BC_OP_RANGEINDEX:
+      v = peek_stack(vm, 2);
+      w = peek_stack(vm, 1);
+      x = peek_stack(vm, 0);
+      res = sbV_rangeindex(v, w, x);
+      npop_stack(vm, 3);
+      push_stack_immediate(vm, &res);
+      break;
     case BC_OP_DEREF:
       PANIC("todo");
     case BC_ALLOC_VARS:
index 8eb5973..bd406ae 100644 (file)
@@ -142,6 +142,43 @@ hV *sbV_index(hV *a, hV *b) {
   }
 }
 
+hV sbV_rangeindex(hV *a, hV *b, hV *c) {
+  if (b->type == IT_INTEGER && c->type == IT_INTEGER) {
+    usize length;
+    i64 min = sbInteger_get_value(b->integer);
+    i64 max = sbInteger_get_value(c->integer);
+    if (a->type == IT_LIST) {
+      hV *elements = sbList_get_value(a->list, &length);
+      if (min >= max || min >= length || max < 0) {
+        /* backwards or out of range */
+        return sbV_empty_list(0);
+      } else {
+        /* now we know 0 <= max, min < max, min < length. clip min and max to bounds */
+        if (max >= length) max = length;
+        if (min < 0) min = 0;
+        return HVLIST(sbList_of(max - min, &elements[min]));
+      }
+    } else if (a->type == IT_STRING) {
+      /* substring operation */
+      char scratch[8];
+      const char *strdata = sbString_get_value(a->string, scratch, &length);
+      if (min >= max || min >= length || max < 0) {
+        /* backwards or out of range */
+        return sbV_empty_string(0);
+      } else {
+        /* now we know 0 <= max, min < max, min < length. clip min and max to bounds */
+        if (max >= length) max = length;
+        if (min < 0) min = 0;
+        return HVSTR(sbString_new(&strdata[min], max - min));
+      }
+    } else {
+      PANIC("todo %lld %lld %lld", (long long)a->type, (long long)b->type, (long long)c->type);
+    }
+  } else {
+    PANIC("Only integers are supported for range indexing!");
+  }
+}
+
 void sbV_index_set(hV *obj, hV *key, hV *value) {
   if (obj->type == IT_LIST) {
     PANIC("todo");
index cc40d61..5683aa4 100644 (file)
@@ -26,4 +26,6 @@ hV sbV_append(hV *a, hV *b);
 
 hV *sbV_index(hV *a, hV *b);
 
+hV sbV_rangeindex(hV *a, hV *b, hV *c);
+
 void sbV_index_set(hV *obj, hV *key, hV *value);