126 lines
4.1 KiB
TypeScript
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" })
|
|
);
|
|
});
|
|
});
|