ReefVM/tests/validator.test.ts
2025-10-06 09:55:30 -07:00

203 lines
4.8 KiB
TypeScript

import { test, expect } from "bun:test"
import { validateBytecode, formatValidationErrors } from "#validator"
test("valid bytecode passes validation", () => {
const source = `
PUSH 1
PUSH 2
ADD
HALT
`
const result = validateBytecode(source)
expect(result.valid).toBe(true)
expect(result.errors).toHaveLength(0)
})
test("valid bytecode with labels passes validation", () => {
const source = `
JUMP .end
PUSH 999
.end:
PUSH 42
HALT
`
const result = validateBytecode(source)
expect(result.valid).toBe(true)
expect(result.errors).toHaveLength(0)
})
test("detects unknown opcode", () => {
const source = `
PUSH 1
INVALID_OP
HALT
`
const result = validateBytecode(source)
expect(result.valid).toBe(false)
expect(result.errors).toHaveLength(1)
expect(result.errors[0]!.message).toContain("Unknown opcode: INVALID_OP")
})
test("detects undefined label", () => {
const source = `
JUMP .nowhere
HALT
`
const result = validateBytecode(source)
expect(result.valid).toBe(false)
expect(result.errors).toHaveLength(1)
expect(result.errors[0]!.message).toContain("Undefined label: .nowhere")
})
test("detects duplicate labels", () => {
const source = `
.loop:
PUSH 1
.loop:
PUSH 2
`
const result = validateBytecode(source)
expect(result.valid).toBe(false)
expect(result.errors).toHaveLength(1)
expect(result.errors[0]!.message).toContain("Duplicate label: .loop")
})
test("detects missing operand", () => {
const source = `
PUSH
HALT
`
const result = validateBytecode(source)
expect(result.valid).toBe(false)
expect(result.errors).toHaveLength(1)
expect(result.errors[0]!.message).toContain("PUSH requires an operand")
})
test("detects unexpected operand", () => {
const source = `
ADD 42
HALT
`
const result = validateBytecode(source)
expect(result.valid).toBe(false)
expect(result.errors).toHaveLength(1)
expect(result.errors[0]!.message).toContain("ADD does not take an operand")
})
test("detects invalid MAKE_FUNCTION syntax", () => {
const source = `
MAKE_FUNCTION x y .body
HALT
`
const result = validateBytecode(source)
expect(result.valid).toBe(false)
expect(result.errors[0]!.message).toContain("MAKE_FUNCTION requires parameter list")
})
test("detects invalid parameter order", () => {
const source = `
MAKE_FUNCTION (x ...rest y) .body
HALT
.body:
RETURN
`
const result = validateBytecode(source)
expect(result.valid).toBe(false)
expect(result.errors[0]!.message).toContain("variadic parameter")
})
test("detects invalid parameter name", () => {
const source = `
MAKE_FUNCTION (123invalid) .body
HALT
.body:
RETURN
`
const result = validateBytecode(source)
expect(result.valid).toBe(false)
expect(result.errors[0]!.message).toContain("Invalid parameter name")
})
test("detects invalid variable name", () => {
const source = `
LOAD 123invalid
HALT
`
const result = validateBytecode(source)
expect(result.valid).toBe(false)
expect(result.errors[0]!.message).toContain("Invalid variable name")
})
test("detects unterminated string", () => {
const source = `
PUSH "unterminated
HALT
`
const result = validateBytecode(source)
expect(result.valid).toBe(false)
expect(result.errors[0]!.message).toContain("Unterminated string")
})
test("detects invalid immediate number", () => {
const source = `
MAKE_ARRAY #abc
`
const result = validateBytecode(source)
expect(result.valid).toBe(false)
expect(result.errors[0]!.message).toContain("Invalid immediate number")
})
test("handles comments correctly", () => {
const source = `
PUSH 1 ; this is a comment
; this entire line is a comment
PUSH 2
ADD ; another comment
`
const result = validateBytecode(source)
expect(result.valid).toBe(true)
})
test("validates function with label reference", () => {
const source = `
MAKE_FUNCTION (x y) .body
JUMP .skip
.body:
LOAD x
LOAD y
ADD
RETURN
.skip:
HALT
`
const result = validateBytecode(source)
expect(result.valid).toBe(true)
})
test("detects multiple errors and sorts by line", () => {
const source = `
UNKNOWN_OP
PUSH
JUMP .undefined
`
const result = validateBytecode(source)
expect(result.valid).toBe(false)
expect(result.errors.length).toBeGreaterThanOrEqual(2)
// Check that errors are sorted by line number
for (let i = 1; i < result.errors.length; i++) {
expect(result.errors[i]!.line).toBeGreaterThanOrEqual(result.errors[i-1]!.line)
}
})
test("formatValidationErrors produces readable output", () => {
const source = `
PUSH 1
UNKNOWN
`
const result = validateBytecode(source)
const formatted = formatValidationErrors(result)
expect(formatted).toContain("error")
expect(formatted).toContain("Line")
expect(formatted).toContain("UNKNOWN")
})