import { describe, expect, it } from "vitest"; import type { SemanticProgramIr } from "../../src/grl/ir/index.js"; import { IrExecutionRuntime, VirtualControllerStateMachine } from "../../src/controller/index.js"; function program(): SemanticProgramIr { return { moduleName: "Main", symbols: [], semanticChecks: [], paths: [], operations: [], diagnostics: [], sourceMap: [ { kind: "CALL", id: "call_helper", procedureId: "main", sourceMap: { file: "main.grl", line: 2, column: 5 } }, { kind: "ALARM", id: "helper_alarm", procedureId: "helper", sourceMap: { file: "main.grl", line: 6, column: 5 } } ], procedures: [ { name: "main", sourceMap: { file: "main.grl", line: 1, column: 1 }, instructions: [ { kind: "RAW_STATEMENT", text: "ready = true", sourceMap: { file: "main.grl", line: 1, column: 5 } }, { kind: "CALL", target: "helper", args: [], sourceMap: { file: "main.grl", line: 2, column: 5 } }, { kind: "RETURN", sourceMap: { file: "main.grl", line: 3, column: 5 } } ] }, { name: "helper", sourceMap: { file: "main.grl", line: 5, column: 1 }, instructions: [ { kind: "ALARM", alarmId: "A1", message: "helper alarm", severity: "warning", sourceMap: { file: "main.grl", line: 6, column: 5 } }, { kind: "RETURN", sourceMap: { file: "main.grl", line: 7, column: 5 } } ] } ], kdlBridge: { motionRequests: [], pathRequests: [] } }; } describe("virtual controller state machine and IR execution runtime", () => { it("reports legal and illegal controller command transitions", () => { const machine = new VirtualControllerStateMachine(); expect(machine.dispatch("run")).toMatchObject({ ok: false, state: "unloaded", diagnostic: { code: "VC_INVALID_STATE_TRANSITION" } }); expect(machine.dispatch("load")).toMatchObject({ ok: true, previous: "unloaded", state: "stopped" }); expect(machine.dispatch("run")).toMatchObject({ ok: true, previous: "stopped", state: "running" }); expect(machine.dispatch("pause")).toMatchObject({ ok: true, previous: "running", state: "paused" }); expect(machine.dispatch("step")).toMatchObject({ ok: true, previous: "paused", state: "paused" }); expect(machine.dispatch("stop")).toMatchObject({ ok: true, previous: "paused", state: "stopped" }); expect(machine.snapshot().diagnostics).toHaveLength(1); }); it("executes PC, calls, scopes, alarms, trace, source map, and breakpoints", () => { const runtime = new IrExecutionRuntime(); runtime.load(program()); expect(runtime.snapshot()).toMatchObject({ procedure: "main", pc: 0, scopeStack: [{ id: "global" }, { id: "main" }] }); runtime.setBreakpoint({ id: "helper-alarm", procedure: "helper", source: { line: 6 }, enabled: true }); expect(runtime.step()).toMatchObject({ status: "executed", instruction: { kind: "RAW_STATEMENT" } }); expect(runtime.getVariable("ready")).toBe(true); expect(runtime.step()).toMatchObject({ status: "executed", instruction: { kind: "CALL" } }); expect(runtime.snapshot()).toMatchObject({ procedure: "helper", pc: 0, callStack: [expect.objectContaining({ procedure: "helper", returnTo: { procedure: "main", pc: 2 } })] }); expect(runtime.step()).toMatchObject({ status: "breakpoint", instruction: { kind: "ALARM" } }); expect(runtime.removeBreakpoint("helper-alarm")).toBe(true); expect(runtime.step()).toMatchObject({ status: "executed", instruction: { kind: "ALARM" } }); expect(runtime.snapshot().alarmQueue).toEqual([ expect.objectContaining({ id: "A1", message: "helper alarm", severity: "warning" }) ]); expect(runtime.currentSource()).toMatchObject({ sourceMap: { line: 7 } }); expect(runtime.run()).toMatchObject({ status: "completed" }); expect(runtime.snapshot().trace).toEqual( expect.arrayContaining([ expect.objectContaining({ kind: "load" }), expect.objectContaining({ kind: "breakpoint", data: { breakpointId: "helper-alarm" } }), expect.objectContaining({ kind: "alarm", data: { alarmId: "A1", severity: "warning" } }) ]) ); }); });