forked from defunkt/ReefVM
BREAK
This commit is contained in:
parent
4b3c9e8bfc
commit
8754afb536
|
|
@ -148,14 +148,17 @@ export class VM {
|
|||
|
||||
case OpCode.BREAK:
|
||||
// Unwind call stack until we find a frame marked as break target
|
||||
let foundBreakTarget = false
|
||||
while (this.callStack.length) {
|
||||
const frame = this.callStack.pop()!
|
||||
this.scope = frame.returnScope
|
||||
this.pc = frame.returnAddress
|
||||
if (frame.isBreakTarget)
|
||||
if (frame.isBreakTarget) {
|
||||
foundBreakTarget = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if (this.callStack.length === 0)
|
||||
if (!foundBreakTarget)
|
||||
throw new Error('BREAK: no break target found')
|
||||
break
|
||||
|
||||
|
|
|
|||
|
|
@ -517,66 +517,57 @@ test("DICT_HAS - checks key missing", async () => {
|
|||
expect(await run(toBytecode(str))).toEqual({ type: 'boolean', value: false })
|
||||
})
|
||||
|
||||
test("BREAK - exits from iterator function", async () => {
|
||||
// Simulate a loop with iterator pattern: function calls can be broken out of
|
||||
// We'll need to manually construct bytecode since we need CALL/RETURN
|
||||
const { VM } = await import("#vm")
|
||||
const { OpCode } = await import("#opcode")
|
||||
const { toValue } = await import("#value")
|
||||
test("BREAK - throws error when no break target", async () => {
|
||||
// BREAK requires a break target frame on the call stack
|
||||
// A single function call has no previous frame to mark as break target
|
||||
const bytecode = toBytecode(`
|
||||
MAKE_FUNCTION () #3
|
||||
CALL #0
|
||||
HALT
|
||||
BREAK
|
||||
`)
|
||||
|
||||
const vm = new VM({
|
||||
instructions: [
|
||||
// 0: Push initial value
|
||||
{ op: OpCode.PUSH, operand: 0 }, // counter = 0
|
||||
{ op: OpCode.STORE, operand: 'counter' },
|
||||
|
||||
// 2: Create a simple "iterator" function
|
||||
{ op: OpCode.MAKE_FUNCTION, operand: 0 },
|
||||
{ op: OpCode.STORE, operand: 'iter' },
|
||||
{ op: OpCode.JUMP, operand: 7 }, // skip function body
|
||||
|
||||
// 5: Function body (will be called in a loop)
|
||||
{ op: OpCode.LOAD, operand: 'counter' },
|
||||
{ op: OpCode.PUSH, operand: 1 }, // index 1 = number 5
|
||||
{ op: OpCode.GTE },
|
||||
{ op: OpCode.JUMP_IF_TRUE, operand: 1 }, // if counter >= 5, break
|
||||
{ op: OpCode.BREAK },
|
||||
{ op: OpCode.LOAD, operand: 'counter' },
|
||||
{ op: OpCode.PUSH, operand: 2 }, // index 2 = number 1
|
||||
{ op: OpCode.ADD },
|
||||
{ op: OpCode.STORE, operand: 'counter' },
|
||||
{ op: OpCode.RETURN },
|
||||
|
||||
// 12: Main code - call iterator in a loop
|
||||
{ op: OpCode.LOAD, operand: 'iter' },
|
||||
{ op: OpCode.CALL, operand: 0 }, // Call with 0 args
|
||||
{ op: OpCode.JUMP, operand: -2 }, // loop back
|
||||
|
||||
// After break, we end up here
|
||||
{ op: OpCode.LOAD, operand: 'counter' },
|
||||
],
|
||||
constants: [
|
||||
toValue(0), // index 0
|
||||
toValue(5), // index 1
|
||||
toValue(1), // index 2
|
||||
{ // index 3 - function definition
|
||||
type: 'function_def',
|
||||
params: [],
|
||||
defaults: {},
|
||||
body: 5,
|
||||
variadic: false,
|
||||
kwargs: false
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
// Note: This test would require CALL/RETURN to be implemented
|
||||
// For now, let's skip this and test BREAK with a simpler approach
|
||||
expect(true).toBe(true) // placeholder
|
||||
try {
|
||||
await run(bytecode)
|
||||
expect(true).toBe(false) // Should not reach here
|
||||
} catch (e: any) {
|
||||
expect(e.message).toContain('no break target found')
|
||||
}
|
||||
})
|
||||
|
||||
test("CONTINUE - skips to loop start", async () => {
|
||||
// CONTINUE requires function call frames with continueAddress set
|
||||
// This will be tested once we implement CALL/RETURN
|
||||
expect(true).toBe(true) // placeholder
|
||||
test("BREAK - exits from nested function call", async () => {
|
||||
// BREAK unwinds to the break target (the outer function's frame)
|
||||
// Main calls outer, outer calls inner, inner BREAKs back to outer's caller (main)
|
||||
const bytecode = toBytecode(`
|
||||
MAKE_FUNCTION () #4
|
||||
CALL #0
|
||||
PUSH 42
|
||||
HALT
|
||||
MAKE_FUNCTION () #7
|
||||
CALL #0
|
||||
PUSH 99
|
||||
RETURN
|
||||
BREAK
|
||||
`)
|
||||
|
||||
const result = await run(bytecode)
|
||||
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
|
||||
const bytecode = toBytecode(`
|
||||
MAKE_FUNCTION () #3
|
||||
CALL #0
|
||||
HALT
|
||||
CONTINUE
|
||||
`)
|
||||
|
||||
try {
|
||||
await run(bytecode)
|
||||
expect(true).toBe(false) // Should not reach here
|
||||
} catch (e: any) {
|
||||
expect(e.message).toContain('no continue target found')
|
||||
}
|
||||
})
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user