import { describe, expect, it } from "vitest"; import type { GrlProcedureDeclaration } from "../../src/grl/ast/index.js"; import { parseGrl } from "../../src/grl/parser/index.js"; import { parseProcedureControlFlow } from "../../src/grl/semantic/index.js"; function procedure(source: string): GrlProcedureDeclaration { return parseGrl(source).module.declarations.find( (decl): decl is GrlProcedureDeclaration => decl.kind === "ProcedureDeclaration" )!; } describe("GRL control-flow compilation", () => { it("compiles if, elseif, else, while, for, switch, labels, and jumps", () => { const proc = procedure(`language grl 0.1 module Main proc main() label retry if ready == true wait io.di[1] == true elseif fault == true jump recovery else jump retry end while all(io.di[1] == true, io.di[2] == false) continue end for i = 1 to 3 step 1 movej home end switch mode case 1 break case 2 jump done default jump recovery end label recovery label done end end `); const flow = parseProcedureControlFlow(proc); expect(flow).toEqual([ expect.objectContaining({ kind: "LABEL", name: "retry", scopePath: [] }), expect.objectContaining({ kind: "IF", branches: [ expect.objectContaining({ branchKind: "if", condition: expect.objectContaining({ text: "ready == true" }), body: [expect.objectContaining({ kind: "RAW_STATEMENT", text: "wait io . di [ 1 ] == true" })] }), expect.objectContaining({ branchKind: "elseif", condition: expect.objectContaining({ text: "fault == true" }), body: [expect.objectContaining({ kind: "JUMP", label: "recovery" })] }), expect.objectContaining({ branchKind: "else", body: [expect.objectContaining({ kind: "JUMP", label: "retry" })] }) ] }), expect.objectContaining({ kind: "WHILE", condition: expect.objectContaining({ text: "all ( io . di [ 1 ] == true , io . di [ 2 ] == false )" }), body: [expect.objectContaining({ kind: "CONTINUE" })] }), expect.objectContaining({ kind: "FOR", iterator: "i", from: expect.objectContaining({ text: "1" }), to: expect.objectContaining({ text: "3" }), step: expect.objectContaining({ text: "1" }), body: [expect.objectContaining({ kind: "RAW_STATEMENT", text: "movej home" })] }), expect.objectContaining({ kind: "SWITCH", expression: expect.objectContaining({ text: "mode" }), cases: [ expect.objectContaining({ caseKind: "case", value: 1, body: [expect.objectContaining({ kind: "BREAK" })] }), expect.objectContaining({ caseKind: "case", value: 2, body: [expect.objectContaining({ kind: "JUMP", label: "done" })] }), expect.objectContaining({ caseKind: "default", body: [expect.objectContaining({ kind: "JUMP", label: "recovery" })] }) ] }), expect.objectContaining({ kind: "LABEL", name: "recovery", scopePath: [] }), expect.objectContaining({ kind: "LABEL", name: "done", scopePath: [] }) ]); }); it("reports non-boolean control conditions", () => { const proc = procedure(`language grl 0.1 module Main proc main() if 1 end end end `); expect(() => parseProcedureControlFlow(proc)).toThrowError( expect.objectContaining({ code: "GRL_CONTROL_CONDITION_NOT_BOOL" }) ); }); it("reports break and continue outside valid blocks", () => { const breakProc = procedure(`language grl 0.1 module Main proc main() break end end `); const continueProc = procedure(`language grl 0.1 module Main proc main() switch mode case 1 continue end end end `); expect(() => parseProcedureControlFlow(breakProc)).toThrowError( expect.objectContaining({ code: "GRL_BREAK_OUTSIDE_FLOW" }) ); expect(() => parseProcedureControlFlow(continueProc)).toThrowError( expect.objectContaining({ code: "GRL_CONTINUE_OUTSIDE_LOOP" }) ); }); it("reports duplicate or non-constant switch cases", () => { const duplicateProc = procedure(`language grl 0.1 module Main proc main() switch mode case 1 break case 1 break end end end `); const nonConstantProc = procedure(`language grl 0.1 module Main proc main() switch mode case mode + 1 break end end end `); expect(() => parseProcedureControlFlow(duplicateProc)).toThrowError( expect.objectContaining({ code: "GRL_SWITCH_CASE_DUPLICATE" }) ); expect(() => parseProcedureControlFlow(nonConstantProc)).toThrowError( expect.objectContaining({ code: "GRL_SWITCH_CASE_NOT_CONSTANT" }) ); }); it("reports labels that cannot be reached by jump", () => { const intoBlockProc = procedure(`language grl 0.1 module Main proc main() jump inner if ready == true label inner end end end `); const missingLabelProc = procedure(`language grl 0.1 module Main proc main() jump missing end end `); expect(() => parseProcedureControlFlow(intoBlockProc)).toThrowError( expect.objectContaining({ code: "GRL_JUMP_INTO_BLOCK" }) ); expect(() => parseProcedureControlFlow(missingLabelProc)).toThrowError( expect.objectContaining({ code: "GRL_LABEL_NOT_FOUND" }) ); }); });