import { test, expect } from "bun:test" import { toBytecode } from "#bytecode" import { OpCode } from "#opcode" import { VM } from "#vm" test("string compilation", () => { const str = ` PUSH 1 PUSH 5 ADD ` expect(toBytecode(str)).toEqual({ instructions: [ { op: OpCode.PUSH, operand: 0 }, { op: OpCode.PUSH, operand: 1 }, { op: OpCode.ADD }, ], constants: [ { type: 'number', value: 1 }, { type: 'number', value: 5 } ] }) }) test("MAKE_FUNCTION - basic function", async () => { const bytecode = toBytecode(` MAKE_FUNCTION () .body PUSH 0 PUSH 0 CALL HALT .body: PUSH 42 RETURN `) const vm = new VM(bytecode) const result = await vm.run() expect(result).toEqual({ type: 'number', value: 42 }) }) test("MAKE_FUNCTION - function with parameters", async () => { const bytecode = toBytecode(` MAKE_FUNCTION (x y) .add PUSH 10 PUSH 20 PUSH 2 PUSH 0 CALL HALT .add: LOAD x LOAD y ADD RETURN `) const vm = new VM(bytecode) const result = await vm.run() expect(result).toEqual({ type: 'number', value: 30 }) }) test("MAKE_FUNCTION - function with default parameters", async () => { const bytecode = toBytecode(` MAKE_FUNCTION (x y=100) .add PUSH 10 PUSH 1 PUSH 0 CALL HALT .add: LOAD x LOAD y ADD RETURN `) const vm = new VM(bytecode) const result = await vm.run() expect(result).toEqual({ type: 'number', value: 110 }) }) test("MAKE_FUNCTION - tail recursive countdown", async () => { const bytecode = toBytecode(` MAKE_FUNCTION (n) .countdown STORE countdown LOAD countdown PUSH 5 PUSH 1 PUSH 0 CALL HALT .countdown: LOAD n PUSH 0 EQ JUMP_IF_FALSE .recurse PUSH "done" RETURN .recurse: LOAD countdown LOAD n PUSH 1 SUB PUSH 1 PUSH 0 TAIL_CALL `) const vm = new VM(bytecode) const result = await vm.run() expect(result).toEqual({ type: 'string', value: 'done' }) }) test("MAKE_FUNCTION - multiple default values", async () => { const bytecode = toBytecode(` MAKE_FUNCTION (a=1 b=2 c=3) .sum PUSH 0 PUSH 0 CALL HALT .sum: LOAD a LOAD b LOAD c ADD ADD RETURN `) const vm = new VM(bytecode) const result = await vm.run() expect(result).toEqual({ type: 'number', value: 6 }) }) test("MAKE_FUNCTION - default with string", async () => { const bytecode = toBytecode(` MAKE_FUNCTION (name="World") .greet PUSH 0 PUSH 0 CALL HALT .greet: LOAD name RETURN `) const vm = new VM(bytecode) const result = await vm.run() expect(result).toEqual({ type: 'string', value: 'World' }) }) test("semicolon comments are ignored", () => { const bytecode = toBytecode(` ; This is a comment PUSH 1 ; push one PUSH 5 ; push five ADD ; add them `) expect(bytecode).toEqual({ instructions: [ { op: OpCode.PUSH, operand: 0 }, { op: OpCode.PUSH, operand: 1 }, { op: OpCode.ADD }, ], constants: [ { type: 'number', value: 1 }, { type: 'number', value: 5 } ] }) }) test("semicolon comments work with functions", async () => { const bytecode = toBytecode(` MAKE_FUNCTION (x y) .add ; function with two params PUSH 10 ; first arg PUSH 20 ; second arg PUSH 2 ; positional count PUSH 0 ; named count CALL ; call the function HALT .add: ; Function body starts here LOAD x ; load first param LOAD y ; load second param ADD ; add them RETURN ; return result `) const vm = new VM(bytecode) const result = await vm.run() expect(result).toEqual({ type: 'number', value: 30 }) }) test("labels - basic jump", async () => { const bytecode = toBytecode(` JUMP .end PUSH 999 .end: PUSH 42 `) const vm = new VM(bytecode) const result = await vm.run() expect(result).toEqual({ type: 'number', value: 42 }) }) test("labels - conditional jump", async () => { const bytecode = toBytecode(` PUSH 1 JUMP_IF_FALSE .else PUSH 10 JUMP .end .else: PUSH 20 .end: `) const vm = new VM(bytecode) const result = await vm.run() expect(result).toEqual({ type: 'number', value: 10 }) }) test("labels - loop", async () => { const bytecode = toBytecode(` PUSH 0 STORE i .loop: LOAD i PUSH 3 LT JUMP_IF_FALSE .end LOAD i PUSH 1 ADD STORE i JUMP .loop .end: LOAD i `) const vm = new VM(bytecode) const result = await vm.run() expect(result).toEqual({ type: 'number', value: 3 }) })