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") }) test("detects JUMP without # or .label", () => { const source = ` JUMP 5 HALT ` const result = validateBytecode(source) expect(result.valid).toBe(false) expect(result.errors[0]!.message).toContain("JUMP requires immediate (#number) or label (.label)") }) test("detects JUMP_IF_TRUE without # or .label", () => { const source = ` PUSH true JUMP_IF_TRUE 2 HALT ` const result = validateBytecode(source) expect(result.valid).toBe(false) expect(result.errors[0]!.message).toContain("JUMP_IF_TRUE requires immediate (#number) or label (.label)") }) test("detects JUMP_IF_FALSE without # or .label", () => { const source = ` PUSH false JUMP_IF_FALSE 2 HALT ` const result = validateBytecode(source) expect(result.valid).toBe(false) expect(result.errors[0]!.message).toContain("JUMP_IF_FALSE requires immediate (#number) or label (.label)") }) test("allows JUMP with immediate number", () => { const source = ` JUMP #2 PUSH 999 HALT ` const result = validateBytecode(source) expect(result.valid).toBe(true) }) test("detects MAKE_ARRAY without #", () => { const source = ` PUSH 1 PUSH 2 MAKE_ARRAY 2 HALT ` const result = validateBytecode(source) expect(result.valid).toBe(false) expect(result.errors[0]!.message).toContain("MAKE_ARRAY requires immediate number (#count)") }) test("detects MAKE_DICT without #", () => { const source = ` PUSH "key" PUSH "value" MAKE_DICT 1 HALT ` const result = validateBytecode(source) expect(result.valid).toBe(false) expect(result.errors[0]!.message).toContain("MAKE_DICT requires immediate number (#count)") }) test("allows MAKE_ARRAY with immediate number", () => { const source = ` PUSH 1 PUSH 2 MAKE_ARRAY #2 HALT ` const result = validateBytecode(source) expect(result.valid).toBe(true) }) test("detects PUSH_TRY without # or .label", () => { const source = ` PUSH_TRY 5 HALT ` const result = validateBytecode(source) expect(result.valid).toBe(false) expect(result.errors[0]!.message).toContain("PUSH_TRY requires immediate (#number) or label (.label)") }) test("detects PUSH_FINALLY without # or .label", () => { const source = ` PUSH_FINALLY 5 HALT ` const result = validateBytecode(source) expect(result.valid).toBe(false) expect(result.errors[0]!.message).toContain("PUSH_FINALLY requires immediate (#number) or label (.label)") }) test("allows PUSH_TRY with label", () => { const source = ` PUSH_TRY .catch PUSH 42 HALT .catch: PUSH null HALT ` const result = validateBytecode(source) expect(result.valid).toBe(true) })