import { describe, expect, it } from "vitest"; import type { GrlProcedureDeclaration, GrlRawTopLevelDeclaration } from "../../src/grl/ast/index.js"; import { parseGrl } from "../../src/grl/parser/index.js"; import { analyzeExceptionSemantics, parseProcedureExceptionFlow } from "../../src/grl/semantic/index.js"; const PROGRAM = `language grl 0.1 module Main trap recover_trap() raise E_STOP end task background cycle 10 ms call monitor() end proc main() alarm E_STOP "Emergency stop" severity fatal try raise E_STOP catch E_STOP alarm RECOVER "Recovering" severity warning finally alarm CLEANUP "Cleanup" end enable interrupt guard disable interrupt guard end end `; function declarations() { return parseGrl(PROGRAM).module.declarations; } describe("GRL alarm, raise, try/catch, interrupt, and task semantics", () => { it("keeps trap and task as parsed raw declarations for P1 diagnostics", () => { const raw = declarations().filter( (decl): decl is GrlRawTopLevelDeclaration => decl.kind === "RawTopLevelDeclaration" ); expect(raw[0]?.declarationType).toBe("trap"); expect(raw[0]?.tokens[0]).toMatchObject({ raw: "trap" }); expect(raw[0]?.tokens[1]).toMatchObject({ raw: "recover_trap" }); expect(raw[1]?.declarationType).toBe("task"); expect(raw[1]?.tokens[0]).toMatchObject({ raw: "task" }); expect(raw[1]?.tokens[1]).toMatchObject({ raw: "background" }); }); it("compiles alarm, raise, try/catch/finally, and interrupt diagnostics from procedure body", () => { const procedure = declarations().find( (decl): decl is GrlProcedureDeclaration => decl.kind === "ProcedureDeclaration" )!; expect(parseProcedureExceptionFlow(procedure)).toEqual([ expect.objectContaining({ kind: "ALARM", alarmId: "E_STOP", message: "Emergency stop", severity: "fatal" }), expect.objectContaining({ kind: "TRY", body: [expect.objectContaining({ kind: "RAISE", alarmId: "E_STOP" })], catches: [ expect.objectContaining({ alarmId: "E_STOP", body: [ expect.objectContaining({ kind: "ALARM", alarmId: "RECOVER", message: "Recovering", severity: "warning" }) ] }) ], finally: expect.objectContaining({ body: [expect.objectContaining({ kind: "ALARM", alarmId: "CLEANUP", message: "Cleanup" })] }) }), expect.objectContaining({ kind: "UNSUPPORTED_RUNTIME", feature: "interrupt" }), expect.objectContaining({ kind: "UNSUPPORTED_RUNTIME", feature: "interrupt" }) ]); }); it("reports P1 trap/task semantics as explicit unsupported diagnostics", () => { const analysis = analyzeExceptionSemantics(declarations()); expect(analysis.unsupported).toEqual([ expect.objectContaining({ kind: "UNSUPPORTED_RUNTIME", feature: "trap" }), expect.objectContaining({ kind: "UNSUPPORTED_RUNTIME", feature: "task" }) ]); expect(analysis.diagnostics).toEqual([ expect.objectContaining({ severity: "warning", code: "GRL_P1_UNIMPLEMENTED" }), expect.objectContaining({ severity: "warning", code: "GRL_P1_UNIMPLEMENTED" }) ]); }); it("reports missing alarm ids and try blocks without handlers", () => { const missingAlarmId = parseGrl(`language grl 0.1 module Main proc main() alarm end end `).module.declarations.find((decl): decl is GrlProcedureDeclaration => decl.kind === "ProcedureDeclaration")!; const tryWithoutHandler = parseGrl(`language grl 0.1 module Main proc main() try raise E_STOP end end end `).module.declarations.find((decl): decl is GrlProcedureDeclaration => decl.kind === "ProcedureDeclaration")!; expect(() => parseProcedureExceptionFlow(missingAlarmId)).toThrowError( expect.objectContaining({ code: "GRL_ALARM_ID_MISSING" }) ); expect(() => parseProcedureExceptionFlow(tryWithoutHandler)).toThrowError( expect.objectContaining({ code: "GRL_TRY_HANDLER_MISSING" }) ); }); });