forked from defunkt/ReefVM
313 lines
6.7 KiB
TypeScript
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 })
|
|
})
|
|
})
|