no continue, just JUMP

This commit is contained in:
Chris Wanstrath 2025-10-05 20:22:03 -07:00
parent 8754afb536
commit 0e387fcfe1
5 changed files with 20 additions and 49 deletions

11
SPEC.md
View File

@ -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

View File

@ -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}`)
}

View File

@ -31,7 +31,6 @@ export enum OpCode {
JUMP_IF_FALSE,
JUMP_IF_TRUE,
BREAK,
CONTINUE,
// exception handling
PUSH_TRY,

View File

@ -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({

View File

@ -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 })
})