From 0e387fcfe143281b9e58dd080408075d21f45af6 Mon Sep 17 00:00:00 2001 From: Chris Wanstrath Date: Sun, 5 Oct 2025 20:22:03 -0700 Subject: [PATCH] no continue, just JUMP --- SPEC.md | 11 +---------- src/bytecode.ts | 2 +- src/opcode.ts | 1 - src/vm.ts | 25 ------------------------- tests/basic.test.ts | 30 ++++++++++++++++++------------ 5 files changed, 20 insertions(+), 49 deletions(-) diff --git a/SPEC.md b/SPEC.md index f0b86b7..c6946b4 100644 --- a/SPEC.md +++ b/SPEC.md @@ -250,16 +250,7 @@ end: 3. Stop when finding frame with `isBreakTarget = true` 4. Resume execution at that frame's return address -#### CONTINUE -**Operand**: None -**Effect**: Unwind to nearest frame with `continueAddress`, jump there -**Stack**: No change -**Errors**: Throws if no continue target found - -**Behavior**: -1. Search call stack (without popping) for frame with `continueAddress` -2. When found, restore scope and jump to `continueAddress` -3. Pop all frames above the continue target +**Note on CONTINUE**: There is no CONTINUE opcode. Compilers implement continue behavior using JUMP with negative offsets to jump back to the loop start. ### Exception Handling diff --git a/src/bytecode.ts b/src/bytecode.ts index f98b96b..dd6b487 100644 --- a/src/bytecode.ts +++ b/src/bytecode.ts @@ -121,7 +121,7 @@ export function toBytecode(str: string): Bytecode /* throws */ { // Special handling for MAKE_FUNCTION with paren syntax if (opCode === OpCode.MAKE_FUNCTION && operand.startsWith('(')) { // Parse: MAKE_FUNCTION (params) #body - const match = operand.match(/^(\(.*?\))\s+(#\d+)$/) + const match = operand.match(/^(\(.*?\))\s+(#-?\d+)$/) if (!match) { throw new Error(`Invalid MAKE_FUNCTION syntax: ${operand}`) } diff --git a/src/opcode.ts b/src/opcode.ts index a00f0c0..098183b 100644 --- a/src/opcode.ts +++ b/src/opcode.ts @@ -31,7 +31,6 @@ export enum OpCode { JUMP_IF_FALSE, JUMP_IF_TRUE, BREAK, - CONTINUE, // exception handling PUSH_TRY, diff --git a/src/vm.ts b/src/vm.ts index 0bd522c..7f31673 100644 --- a/src/vm.ts +++ b/src/vm.ts @@ -162,31 +162,6 @@ export class VM { throw new Error('BREAK: no break target found') break - case OpCode.CONTINUE: - // Search for frame with continueAddress (don't pop yet) - let continueFrame: Frame | undefined - let frameIndex = this.callStack.length - 1 - while (frameIndex >= 0) { - const frame = this.callStack[frameIndex]! - if (frame.continueAddress !== undefined) { - continueFrame = frame - break - } - frameIndex-- - } - - if (!continueFrame) - throw new Error('CONTINUE: no continue target found') - - // Pop all frames above the continue target - while (this.callStack.length > frameIndex + 1) - this.callStack.pop() - - // Restore scope and jump to continue address - this.scope = continueFrame.returnScope - this.pc = continueFrame.continueAddress! - 1 // -1 because PC will be incremented - break - case OpCode.PUSH_TRY: const catchAddress = instruction.operand as number this.exceptionHandlers.push({ diff --git a/tests/basic.test.ts b/tests/basic.test.ts index 4d81f7f..4c164ec 100644 --- a/tests/basic.test.ts +++ b/tests/basic.test.ts @@ -554,20 +554,26 @@ test("BREAK - exits from nested function call", async () => { expect(result).toEqual({ type: 'number', value: 42 }) }) -test("CONTINUE - throws error when no continue target", async () => { - // CONTINUE requires continueAddress to be set on a frame - // Since we have no instruction to set it yet, this should throw +test("JUMP backward - simple loop", async () => { + // Very simple: counter starts at 0, loops 3 times incrementing + // On 3rd iteration (counter==3), exits and returns counter const bytecode = toBytecode(` - MAKE_FUNCTION () #3 - CALL #0 + PUSH 0 + STORE counter + LOAD counter + PUSH 3 + EQ + JUMP_IF_FALSE #2 + LOAD counter HALT - CONTINUE + LOAD counter + PUSH 1 + ADD + STORE counter + JUMP #-11 `) - try { - await run(bytecode) - expect(true).toBe(false) // Should not reach here - } catch (e: any) { - expect(e.message).toContain('no continue target found') - } + const result = await run(bytecode) + expect(result).toEqual({ type: 'number', value: 3 }) }) +