no continue, just JUMP
This commit is contained in:
parent
8754afb536
commit
0e387fcfe1
11
SPEC.md
11
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
|
||||
|
||||
|
|
|
|||
|
|
@ -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}`)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,6 @@ export enum OpCode {
|
|||
JUMP_IF_FALSE,
|
||||
JUMP_IF_TRUE,
|
||||
BREAK,
|
||||
CONTINUE,
|
||||
|
||||
// exception handling
|
||||
PUSH_TRY,
|
||||
|
|
|
|||
25
src/vm.ts
25
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({
|
||||
|
|
|
|||
|
|
@ -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 })
|
||||
})
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user