203 lines
4.8 KiB
TypeScript
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")
|
|
})
|