we can have negative numbers now
authorcassowarii <cassowary@cassowary.me>
Tue, 30 Jun 2026 14:28:53 +0000 (07:28 -0700)
committercassowarii <cassowary@cassowary.me>
Tue, 30 Jun 2026 14:28:53 +0000 (07:28 -0700)
src/compile/emit.c
src/data/integer.c
src/parse/scanner.c
src/vm/block.c
src/vm/block.h
src/vm/bytecode.h
src/vm/exec.c
src/vm/exec.h

index 7e906cf..8b07271 100644 (file)
@@ -32,19 +32,26 @@ void sbEmit_compile_program(sbVmProgram *vp, sbIrProgram *ir) {
 
 /* --- */
 
-void emit_arg(sbVmCompiler *cm, usize number) {
-  printf("    arg %zu\n", number);
+void emit_arg(sbVmCompiler *cm, i64 actual_number) {
+  printf("    arg %lld\n", actual_number);
   u8 buf[9];
-  if (number < 253) {
+  u64 number = actual_number;
+  if (0 <= actual_number && actual_number < 253) {
     buf[0] = number;
     sbVmCompiler_write_code(cm, buf, 1);
-  } else if (number < 65536) {
+  } else if (-32768 < actual_number && actual_number < 32768) {
     buf[0] = BC_LONG_NUM;
+    if (actual_number < 0) {
+      number = actual_number + 65536;
+    }
     buf[1] = number >> 8;
     buf[2] = number & 0xFF;
     sbVmCompiler_write_code(cm, buf, 3);
-  } else if (number < (1LL << 32)) {
+  } else if (-(1L << 31) < actual_number && actual_number < (1L << 31)) {
     buf[0] = BC_VLONG_NUM;
+    if (actual_number < 0) {
+      number = actual_number + (1LL << 32);
+    }
     buf[1] = (number >> 24) & 0xFF;;
     buf[2] = (number >> 16) & 0xFF;
     buf[3] = (number >> 8)  & 0xFF;
@@ -52,6 +59,7 @@ void emit_arg(sbVmCompiler *cm, usize number) {
     sbVmCompiler_write_code(cm, buf, 5);
   } else {
     buf[0] = BC_VVLONG_NUM;
+    number = (u64)actual_number;
     buf[1] = (number >> 56) & 0xFF;;
     buf[2] = (number >> 48) & 0xFF;;
     buf[3] = (number >> 40) & 0xFF;;
@@ -80,8 +88,10 @@ void compile_chunk(sbVmCompiler *cm, sbIrChunk *chunk) {
     EARG(chunk->num_args);
   }
 
-  EMIT(BC_ALLOC_VARS);
-  EARG(chunk->variable_count);
+  if (chunk->variable_count > 0) {
+    EMIT(BC_ALLOC_VARS);
+    EARG(chunk->variable_count);
+  }
 
   for (int i = 0; i < nstmts; i++) {
     sbIrStmt *stmt = ((sbIrStmt**)chunk->stmts.data)[i];
@@ -209,11 +219,17 @@ void compile_expr(sbVmCompiler *cm, sbIrExpr *expr) {
     case IR_E_VALUE:
       if (expr->value.type == IT_NIL) {
         EMIT(BC_LD_NIL);
-      } else if (expr->value.type == IT_INTEGER) {
+      } else if (expr->value.type == IT_BOOLEAN && expr->value.boolean) {
+        EMIT(BC_LD_TRUE);
+      } else if (expr->value.type == IT_BOOLEAN) {
+        EMIT(BC_LD_FALSE);
+      } else if (expr->value.type == IT_INTEGER && expr->value.integer < (2 << 16)) {
         EMIT(BC_LD_IMM);
         EARG(expr->value.integer);
       } else {
-        printf("(some value)\n");
+        u32 constant_index = sbVmCompiler_add_constant(cm, &expr->value);
+        EMIT(BC_LD_CONST);
+        EARG(constant_index);
       }
       break;
     default:
index 68dd5ee..337c853 100644 (file)
@@ -80,7 +80,10 @@ hInteger sbInteger_diff(hInteger a, hInteger b) {
 
 hInteger sbInteger_mul(hInteger a, hInteger b) {
   if (!is_bigint(a) && !is_bigint(b)) {
-    if (b < SARABANDE_INT_MAX / a && b > SARABANDE_INT_MIN / a) {
+    flag zero_ok = (a == 0);
+    flag pos_ok = (a > 0 && b < SARABANDE_INT_MAX / a && b > SARABANDE_INT_MIN / a);
+    flag neg_ok = (a < 0 && b < SARABANDE_INT_MIN / a && b > SARABANDE_INT_MAX / a);
+    if (zero_ok || pos_ok || neg_ok) {
       return sbInteger_new(a * b);
     }
   }
@@ -97,7 +100,10 @@ hInteger sbInteger_floordiv(hInteger a, hInteger b) {
 /* --- */
 
 flag is_bigint(hInteger n) {
-  return (n & FLAG_BIGINT);
+  /* bigint flag is the second bit after the sign bit, normally for smaller
+   * numbers this bit is equal to the sign bit, so we see if it's NOT equal
+   * as a flag that a number is a bigint */
+  return (n > 0 && (n & FLAG_BIGINT)) || (n < 0 && !(n & FLAG_BIGINT));
 }
 
 static intblk *alloc_new_block() {
index 344a8be..fadff08 100644 (file)
@@ -199,6 +199,59 @@ static usize read_symbol(hScanner sc) {
     return token_size;
 }
 
+static void read_integer(hScanner sc, sbLexToken *new_token, flag negative) {
+    new_token->type = T_INTEGER;
+
+    i64 intval = 0;
+    int base = 10;
+    int num_done = FALSE;
+    char ch = PEEK;
+
+    if (ch == '0') {
+        /* skip a leading zero, and see if it has some base indicator */
+        ch = NEXT;
+        if (ch == 'b') {
+            ch = NEXT;
+            base = 2;
+        } else if (ch == 'o') {
+            ch = NEXT;
+            base = 8;
+        } else if (ch == 'x') {
+            ch = NEXT;
+            base = 16;
+        } else if (is_space(ch) || ch == '\n') {
+            /* it was literally just the number 0 (probably) */
+            num_done = TRUE;
+        } else {
+            new_token->type = T_BADNUMBER;
+            num_done = TRUE;
+        }
+    }
+
+    if (!num_done) {
+        do {
+            /* TODO: promote to bigint, don't allow overflow */
+            intval *= base;
+            intval += base_digit_value(ch);
+            do {
+                /* skip over underscores in numeric literals */
+                ch = NEXT;
+            } while (ch == '_');
+        } while (is_base_digit(ch, base));
+
+        /* if we are now looking at still a character that's a letter or digit, throw error */
+        if (is_digit(ch) || is_alpha(ch)) {
+            new_token->type = T_BADNUMBER;
+            NEXT;
+        } else {
+            new_token->i = intval;
+            if (negative) {
+              new_token->i *= -1;
+            }
+        }
+    }
+}
+
 static sbLexToken compute_next_token(hScanner sc) {
     int ch_int = PEEK;
     unsigned char ch = (unsigned char)ch_int;
@@ -359,6 +412,9 @@ static sbLexToken compute_next_token(hScanner sc) {
         } else if (ch == '=') {
             new_token.type = T_MINUSEQUALS;
             NEXT;
+        } else if (is_digit(ch)) {
+            /* negative number literal */
+            read_integer(sc, &new_token, TRUE);
         } else {
             new_token.type = T_MINUS;
         }
@@ -448,52 +504,7 @@ static sbLexToken compute_next_token(hScanner sc) {
         new_token.symb = sbSymbol_from_bytes(sc->dynamic_buffer.data, sc->dynamic_buffer.size - 1);
         new_token.size = token_size;
     } else if (is_digit(ch)) {
-        new_token.type = T_INTEGER;
-
-        i64 intval = 0;
-        int base = 10;
-        int num_done = FALSE;
-
-        if (ch == '0') {
-            /* skip a leading zero, and see if it has some base indicator */
-            ch = NEXT;
-            if (ch == 'b') {
-                ch = NEXT;
-                base = 2;
-            } else if (ch == 'o') {
-                ch = NEXT;
-                base = 8;
-            } else if (ch == 'x') {
-                ch = NEXT;
-                base = 16;
-            } else if (is_space(ch) || ch == '\n') {
-                /* it was literally just the number 0 (probably) */
-                num_done = TRUE;
-            } else {
-                new_token.type = T_BADNUMBER;
-                num_done = TRUE;
-            }
-        }
-
-        if (!num_done) {
-            do {
-                /* TODO: promote to bigint, don't allow overflow */
-                intval *= base;
-                intval += base_digit_value(ch);
-                do {
-                    /* skip over underscores in numeric literals */
-                    ch = NEXT;
-                } while (ch == '_');
-            } while (is_base_digit(ch, base));
-
-            /* if we are now looking at still a character that's a letter or digit, throw error */
-            if (is_digit(ch) || is_alpha(ch)) {
-                new_token.type = T_BADNUMBER;
-                NEXT;
-            } else {
-                new_token.i = intval;
-            }
-        }
+        read_integer(sc, &new_token, FALSE);
     } else {
         new_token.type = T_ERROR;
         NEXT;
index 202b09f..fb6c834 100644 (file)
@@ -34,10 +34,10 @@ usize sbVmCompiler_get_position(sbVmCompiler *cm) {
   return cm->bytecode.size;
 }
 
-void sbVmCompiler_add_constant(sbVmCompiler *cm, hV *constant) {
+u32 sbVmCompiler_add_constant(sbVmCompiler *cm, hV *constant) {
   sbV_retain(constant);
-
   sbBuffer_append(&cm->constants, constant, sizeof(hV));
+  return cm->constants.size / sizeof(hV) - 1;
 }
 
 void sbVmProgram_initialize(sbVmProgram *pm, usize initial_arena_size) {
@@ -71,7 +71,7 @@ sbBlockId sbVmProgram_add_block(sbVmProgram *pm, sbVmCompiler *cm) {
 
   sbVmBlock bk = {
     .bytecode = bytecode,
-    .bytecode_length = bytecode_length,
+    .bytecode_end = bytecode + bytecode_length,
     .constants = (hV*)constants,
     .constants_count = constants_length / sizeof(hV),
   };
index c3e3745..c80286b 100644 (file)
@@ -21,7 +21,7 @@ typedef struct sbVmCompiler {
 /* fixed size / frozen block of bytecode */
 typedef struct sbVmBlock {
   const u8 *bytecode;
-  usize bytecode_length;
+  const u8 *bytecode_end;
   const hV *constants;
   usize constants_count;
 } sbVmBlock;
@@ -47,7 +47,7 @@ void sbVmCompiler_overwrite_code_at(sbVmCompiler *cm, usize offset, const u8 *da
 
 usize sbVmCompiler_get_position(sbVmCompiler *cm);
 
-void sbVmCompiler_add_constant(sbVmCompiler *pb, hV *constant);
+u32 sbVmCompiler_add_constant(sbVmCompiler *cm, hV *constant);
 
 void sbVmProgram_initialize(sbVmProgram *pm, usize initial_arena_size);
 
index ae42fe0..ee024ae 100644 (file)
@@ -12,6 +12,8 @@ typedef enum sbOpcode {
   BC_LD_UPVAL,          // push value onto stack from closure
   BC_LD_BLK,            // push reference to function onto stack
   BC_LD_NIL,            // push nil onto stack
+  BC_LD_TRUE,           // push true onto stack
+  BC_LD_FALSE,          // push false onto stack
   BC_ST_VAR,            // pop value from stack into variable
   BC_ST_UPVAL,          // pop value from stack into closure
   BC_ST_ARG,            // decrement TOS and store NOS in variable
index b51e653..3b70370 100644 (file)
@@ -117,93 +117,108 @@ hV *peek_stack(hVm vm, usize offset) {
 }
 
 sbOpcode get_opcode(hVm vm) {
+  if (vm->ip >= vm->fp->block->bytecode_end) PANIC("execution outside defined code area");
   return *(vm->ip++);
 }
 
+void next_byte(hVm vm, u64 *result) {
+  u8 byte = *(vm->ip++);
+#ifdef DEBUG
+  if (vm->debugmode) printf("%02X ", byte);
+#endif
+  *result <<= 8;
+  *result |= byte;
+}
+
 /* small numbers that are parameters to opcodes can
  * just be encoded directly as 1 byte. numbers of 16-bit
  * scale can be encoded as FC + bytebyte, numbers of
  * 32 bit scale can be FD + bytebytebytebyte,
  * 64 bit can be FE + bytebytebytebytebytebytebytebyte,
  * in big-endian format */
-u64 get_param(hVm vm) {
+i64 get_param(hVm vm) {
   u64 result = *((u8*)vm->ip++);
+#ifdef DEBUG
+  if (vm->debugmode) printf("%02llX ", result);
+#endif
+  i64 signed_result = result;
 
   if (result == BC_LONG_NUM) {
-    result = *(vm->ip++);
-    result <<= 8;
-    result |= *(vm->ip++);
+    result = 0;
+    next_byte(vm, &result);
+    next_byte(vm, &result);
+    signed_result = result;
+    if (result > (1 << 15)) signed_result = result - (1 << 16);
   } else if (result == BC_VLONG_NUM) {
-    result = *(vm->ip++);
-    result <<= 8;
-    result |= *(vm->ip++);
-    result <<= 8;
-    result |= *(vm->ip++);
-    result <<= 8;
-    result |= *(vm->ip++);
+    printf("here now\n");
+    result = 0;
+    next_byte(vm, &result); // 0
+    next_byte(vm, &result); // 1
+    next_byte(vm, &result); // 2
+    next_byte(vm, &result); // 3
+    signed_result = result;
+    if (result > (1L << 31)) signed_result = result - (1L << 32);
   } else if (result == BC_VVLONG_NUM) {
-    result = *(vm->ip++);
-    result <<= 8;
-    result |= *(vm->ip++);
-    result <<= 8;
-    result |= *(vm->ip++);
-    result <<= 8;
-    result |= *(vm->ip++);
-    result <<= 8;
-    result |= *(vm->ip++);
-    result <<= 8;
-    result |= *(vm->ip++);
-    result <<= 8;
-    result |= *(vm->ip++);
-    result <<= 8;
-    result |= *(vm->ip++);
+    result = 0;
+    next_byte(vm, &result); // 0
+    next_byte(vm, &result); // 1
+    next_byte(vm, &result); // 2
+    next_byte(vm, &result); // 3
+    next_byte(vm, &result); // 4
+    next_byte(vm, &result); // 5
+    next_byte(vm, &result); // 6
+    next_byte(vm, &result); // 7
+    signed_result = (i64)result;
   }
 
-  return result;
+  return signed_result;
 }
 
 /* execute one instruction! wow! */
 void execute_instruction(hVm vm) {
   sbOpcode op = get_opcode(vm);
+#ifdef DEBUG
+  if (vm->debugmode) printf("op %02X ", op);
+#endif
   u64 param;
   hV *v, *w, res;
 
   switch (op) {
     case BC_NOP:
-      return;
+      break;
     case BC_HALT:
       vm->running = FALSE;
-      return;
+      break;
     case BC_LD_IMM:
       param = get_param(vm);
       push_stack(vm, &HVINT(param));
-      return;
+      break;
     case BC_LD_CONST:
       param = get_param(vm);
       push_stack(vm, &vm->fp->block->constants[param]);
-      return;
+      break;
     case BC_LD_CTX:
       PANIC("todo");
     case BC_LD_VAR:
       param = get_param(vm);
       push_stack(vm, &vm->fp->locals[param]);
-      return;
+      break;
     case BC_LD_UPVAL:
       /* Hey, was there upval in there? */
       PANIC("todo");
     case BC_LD_BLK:
       param = get_param(vm);
       push_stack(vm, &HVFUNC(param));
-      return;
+      break;
     case BC_LD_NIL:
       push_stack(vm, &HVNIL);
-      return;
+      break;
     case BC_ST_VAR:
       param = get_param(vm);
       v = peek_stack(vm, 0);
       store_local(vm, param, v);
       pop_stack(vm);
-      return;
+      break;
     case BC_ST_UPVAL:
       PANIC("todo");
     case BC_ST_ARG:
@@ -221,14 +236,14 @@ void execute_instruction(hVm vm) {
         /* if last integer, don't put the 0 count back on the stack */
         push_stack(vm, v);
       }
-      return;
+      break;
     case BC_POP:
       v = pop_stack(vm);
-      return;
+      break;
     case BC_NPOP:
       param = get_param(vm);
       npop_stack(vm, param);
-      return;
+      break;
     case BC_CALL:
       v = pop_stack(vm);
       if (v->type != IT_FUNCTION) {
@@ -237,7 +252,7 @@ void execute_instruction(hVm vm) {
         PANIC("attempt to call a non-function value");
       }
       call_block(vm, v->data);
-      return;
+      break;
     case BC_NUMARG:
       param = get_param(vm);
       v = pop_stack(vm);
@@ -250,28 +265,28 @@ void execute_instruction(hVm vm) {
         PANIC("wrong number of arguments passed to function.");
       }
       push_stack(vm, v);
-      return;
+      break;
     case BC_JMP:
       param = get_param(vm);
       vm->ip = &vm->fp->block->bytecode[param];
-      return;
+      break;
     case BC_JT:
       param = get_param(vm);
       v = pop_stack(vm);
       if (!sbV_c_falsy(v)) {
         vm->ip = &vm->fp->block->bytecode[param];
       }
-      return;
+      break;
     case BC_JF:
       param = get_param(vm);
       v = pop_stack(vm);
       if (sbV_c_falsy(v)) {
         vm->ip = &vm->fp->block->bytecode[param];
       }
-      return;
+      break;
     case BC_RET:
       return_from_block(vm);
-      return;
+      break;
     case BC_SEND:
       PANIC("todo");
     case BC_OP_EQ:
@@ -280,7 +295,7 @@ void execute_instruction(hVm vm) {
       res = sbV_eq(v, w);
       npop_stack(vm, 2);
       push_stack(vm, &res);
-      return;
+      break;
     case BC_OP_NOT:
       v = pop_stack(vm);
       if (sbV_c_falsy(v)) {
@@ -288,42 +303,42 @@ void execute_instruction(hVm vm) {
       } else {
         push_stack(vm, &HVBOOL(FALSE));
       }
-      return;
+      break;
     case BC_OP_LT:
       v = peek_stack(vm, 1);
       w = peek_stack(vm, 0);
       res = sbV_lt(v, w);
       npop_stack(vm, 2);
       push_stack(vm, &res);
-      return;
+      break;
     case BC_OP_LE:
       v = peek_stack(vm, 1);
       w = peek_stack(vm, 0);
       res = sbV_le(v, w);
       npop_stack(vm, 2);
       push_stack(vm, &res);
-      return;
+      break;
     case BC_OP_ADD:
       v = peek_stack(vm, 1);
       w = peek_stack(vm, 0);
       res = sbV_add(v, w);
       npop_stack(vm, 2);
       push_stack(vm, &res);
-      return;
+      break;
     case BC_OP_SUB:
       v = peek_stack(vm, 1);
       w = peek_stack(vm, 0);
       res = sbV_sub(v, w);
       npop_stack(vm, 2);
       push_stack(vm, &res);
-      return;
+      break;
     case BC_OP_MUL:
       v = peek_stack(vm, 1);
       w = peek_stack(vm, 0);
       res = sbV_mul(v, w);
       npop_stack(vm, 2);
       push_stack(vm, &res);
-      return;
+      break;
     case BC_OP_DIV:
       PANIC("todo");
     case BC_OP_FLDIV:
@@ -332,7 +347,7 @@ void execute_instruction(hVm vm) {
       res = sbV_floordiv(v, w);
       npop_stack(vm, 2);
       push_stack(vm, &res);
-      return;
+      break;
     case BC_OP_NEG:
     case BC_OP_MOD:
     case BC_OP_POW:
@@ -342,16 +357,17 @@ void execute_instruction(hVm vm) {
       res = sbV_incr(v);
       pop_stack(vm);
       push_stack(vm, &res);
-      return;
+      break;
     case BC_OP_DECR:
       v = peek_stack(vm, 0);
       res = sbV_decr(v);
       pop_stack(vm);
       push_stack(vm, &res);
-      return;
+      break;
     case BC_OP_DEREF:
       PANIC("todo");
     case BC_ALLOC_VARS:
+      /* TODO check for overflow */
       param = get_param(vm);
       /* have to set new rstack space to 0 so we don't accidentally decrement
        * the ref count of variables from a previous stack frame (or of just garbage)  */
@@ -359,7 +375,7 @@ void execute_instruction(hVm vm) {
       /* we allocate one more variable at 0 for internal use */
       vm->rp += (param + 1) * sizeof(hV);
       vm->fp->num_locals += param;
-      return;
+      break;
     case BC_LIST_GATHER:
     case BC_HASH_GATHER:
       PANIC("todo");
@@ -369,4 +385,7 @@ void execute_instruction(hVm vm) {
     default:
       PANIC("unrecognized opcode $%02X at position $%016zX", op, (usize)vm->ip);
   }
+#ifdef DEBUG
+  if (vm->debugmode) printf("\n");
+#endif
 }
index 295d756..1c851d2 100644 (file)
@@ -52,13 +52,13 @@ typedef struct sbVmStackFrame {
 } sbVmStackFrame;
 
 typedef struct sbVm {
-  u8 *stack;        /* for calculations */
-  u8 *rstack;       /* for locals and return addresses, like FORTH */
+  u8 *stack;          /* for calculations */
+  u8 *rstack;         /* for locals and return addresses, like FORTH */
 
-  const u8 *ip;     /* instruction pointer */
+  const u8 *ip;       /* instruction pointer */
   sbVmStackFrame *fp; /* frame pointer */
-  u8 *sp;           /* stack pointer */
-  u8 *rp;           /* rstack pointer */
+  u8 *sp;             /* stack pointer */
+  u8 *rp;             /* rstack pointer */
 
   usize stacksize;    /* to detect overflow, save these */
   usize rstacksize;   /* ^^ */