ReefVM/tests/programmatic.test.ts
2025-10-07 12:53:40 -07:00

313 lines
6.7 KiB
TypeScript

import { describe, expect, test } from "bun:test"
import { toBytecode, run } from "#reef"
describe("Programmatic Bytecode API", () => {
test("basic stack operations", async () => {
const bytecode = toBytecode([
["PUSH", 42],
["DUP"],
["POP"],
["HALT"]
])
const result = await run(bytecode)
expect(result).toEqual({ type: "number", value: 42 })
})
test("arithmetic operations", async () => {
const bytecode = toBytecode([
["PUSH", 5],
["PUSH", 10],
["ADD"],
["HALT"]
])
const result = await run(bytecode)
expect(result).toEqual({ type: "number", value: 15 })
})
test("variables", async () => {
const bytecode = toBytecode([
["PUSH", 100],
["STORE", "x"],
["LOAD", "x"],
["PUSH", 50],
["SUB"],
["HALT"]
])
const result = await run(bytecode)
expect(result).toEqual({ type: "number", value: 50 })
})
test("labels and jumps", async () => {
const bytecode = toBytecode([
["PUSH", 5],
["PUSH", 10],
["GT"],
["JUMP_IF_TRUE", ".skip"],
["PUSH", 999],
[".skip:"],
["PUSH", 42],
["HALT"]
])
const result = await run(bytecode)
expect(result).toEqual({ type: "number", value: 42 })
})
test("loop with labels", 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"],
["HALT"]
])
const result = await run(bytecode)
expect(result).toEqual({ type: "number", value: 3 })
})
test("simple function", async () => {
const bytecode = toBytecode([
["MAKE_FUNCTION", ["x", "y"], ".add_body"],
["STORE", "add"],
["JUMP", ".after_fn"],
[".add_body:"],
["LOAD", "x"],
["LOAD", "y"],
["ADD"],
["RETURN"],
[".after_fn:"],
["LOAD", "add"],
["PUSH", 5],
["PUSH", 10],
["PUSH", 2],
["PUSH", 0],
["CALL"],
["HALT"]
])
const result = await run(bytecode)
expect(result).toEqual({ type: "number", value: 15 })
})
test("function with defaults", async () => {
const bytecode = toBytecode([
["MAKE_FUNCTION", ["x", "y=10"], ".fn_body"],
["STORE", "fn"],
["JUMP", ".after"],
[".fn_body:"],
["LOAD", "x"],
["LOAD", "y"],
["ADD"],
["RETURN"],
[".after:"],
["LOAD", "fn"],
["PUSH", 5],
["PUSH", 1],
["PUSH", 0],
["CALL"],
["HALT"]
])
const result = await run(bytecode)
expect(result).toEqual({ type: "number", value: 15 })
})
test("variadic function", async () => {
const bytecode = toBytecode([
["MAKE_FUNCTION", ["...args"], ".sum_body"],
["STORE", "sum"],
["JUMP", ".after"],
[".sum_body:"],
["PUSH", 0],
["STORE", "total"],
["LOAD", "args"],
["ARRAY_LEN"],
["STORE", "len"],
["PUSH", 0],
["STORE", "i"],
[".loop:"],
["LOAD", "i"],
["LOAD", "len"],
["LT"],
["JUMP_IF_FALSE", ".done"],
["LOAD", "total"],
["LOAD", "args"],
["LOAD", "i"],
["ARRAY_GET"],
["ADD"],
["STORE", "total"],
["LOAD", "i"],
["PUSH", 1],
["ADD"],
["STORE", "i"],
["JUMP", ".loop"],
[".done:"],
["LOAD", "total"],
["RETURN"],
[".after:"],
["LOAD", "sum"],
["PUSH", 1],
["PUSH", 2],
["PUSH", 3],
["PUSH", 3],
["PUSH", 0],
["CALL"],
["HALT"]
])
const result = await run(bytecode)
expect(result).toEqual({ type: "number", value: 6 })
})
test("named parameters", async () => {
const bytecode = toBytecode([
["MAKE_FUNCTION", ["x", "@opts"], ".fn_body"],
["STORE", "fn"],
["JUMP", ".after"],
[".fn_body:"],
["LOAD", "x"],
["RETURN"],
[".after:"],
["LOAD", "fn"],
["PUSH", 42],
["PUSH", "verbose"],
["PUSH", true],
["PUSH", 1],
["PUSH", 1],
["CALL"],
["HALT"]
])
const result = await run(bytecode)
expect(result).toEqual({ type: "number", value: 42 })
})
test("arrays", async () => {
const bytecode = toBytecode([
["PUSH", 1],
["PUSH", 2],
["PUSH", 3],
["MAKE_ARRAY", 3],
["HALT"]
])
const result = await run(bytecode)
expect(result.type).toBe("array")
if (result.type === "array") {
expect(result.value).toHaveLength(3)
}
})
test("dicts", async () => {
const bytecode = toBytecode([
["PUSH", "name"],
["PUSH", "Alice"],
["PUSH", "age"],
["PUSH", 30],
["MAKE_DICT", 2],
["HALT"]
])
const result = await run(bytecode)
expect(result.type).toBe("dict")
})
test("exception handling", async () => {
const bytecode = toBytecode([
["PUSH_TRY", ".catch"],
["PUSH", "error!"],
["THROW"],
["POP_TRY"],
[".catch:"],
["STORE", "err"],
["LOAD", "err"],
["HALT"]
])
const result = await run(bytecode)
expect(result).toEqual({ type: "string", value: "error!" })
})
test("string values", async () => {
const bytecode = toBytecode([
["PUSH", "hello"],
["HALT"]
])
const result = await run(bytecode)
expect(result).toEqual({ type: "string", value: "hello" })
})
test("boolean values", async () => {
const bytecode = toBytecode([
["PUSH", true],
["NOT"],
["HALT"]
])
const result = await run(bytecode)
expect(result).toEqual({ type: "boolean", value: false })
})
test("null values", async () => {
const bytecode = toBytecode([
["PUSH", null],
["HALT"]
])
const result = await run(bytecode)
expect(result).toEqual({ type: "null", value: null })
})
test("tail call", async () => {
const bytecode = toBytecode([
["MAKE_FUNCTION", ["n", "acc"], ".factorial_body"],
["STORE", "factorial"],
["JUMP", ".main"],
[".factorial_body:"],
["LOAD", "n"],
["PUSH", 0],
["LTE"],
["JUMP_IF_FALSE", ".recurse"],
["LOAD", "acc"],
["RETURN"],
[".recurse:"],
["LOAD", "factorial"],
["LOAD", "n"],
["PUSH", 1],
["SUB"],
["LOAD", "n"],
["LOAD", "acc"],
["MUL"],
["PUSH", 2],
["PUSH", 0],
["TAIL_CALL"],
[".main:"],
["LOAD", "factorial"],
["PUSH", 5],
["PUSH", 1],
["PUSH", 2],
["PUSH", 0],
["CALL"],
["HALT"]
])
const result = await run(bytecode)
expect(result).toEqual({ type: "number", value: 120 })
})
})