Files
KDL_WORK/kdl-wasm/web/tests/grl/exceptionCompile.test.ts
2026-06-27 08:45:38 -04:00

126 lines
4.1 KiB
TypeScript

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" })
);
});
});