import { describe, expect, it } from "vitest"; import type { GrlOperationDeclaration, GrlPathDeclaration, GrlProcedureDeclaration } from "../../src/grl/ast/index.js"; import { parseGrl } from "../../src/grl/parser/index.js"; import { compileOperation, expandRunOperation, parseProcedureRunOperationStatements } from "../../src/grl/semantic/index.js"; const PROGRAM = `language grl 0.1 module Main const speed v = joint(50 %) const zone zf = fine target home = joint_target { joints: [0 deg] } path weld_path { defaults { speed: v, zone: zf } point p0 movej home } operation weld_op_01 { kind: arc_welding path: weld_path process { weld_id: "WELD_1" voltage: 24.0 current: 180.0 weave: none } start_action: io.do[20] = true end_action: io.do[20] = false } proc main() run_operation weld_op_01 end end `; function declarations() { return parseGrl(PROGRAM).module.declarations; } function pathsByName(paths: GrlPathDeclaration[]) { return new Map(paths.map((path) => [path.name, path])); } describe("GRL operation compilation", () => { it("parses operation kind, path, process, and action blocks", () => { const operation = declarations().find( (decl): decl is GrlOperationDeclaration => decl.kind === "OperationDeclaration" )!; expect(operation).toMatchObject({ kind: "OperationDeclaration", name: "weld_op_01", operationKind: "arc_welding", pathName: "weld_path", items: [ { kind: "OperationProcessBlock" }, { kind: "OperationActionBlock", actionKind: "start_action" }, { kind: "OperationActionBlock", actionKind: "end_action" } ] }); }); it("compiles operation process metadata and action statements", () => { const decls = declarations(); const operation = decls.find((decl): decl is GrlOperationDeclaration => decl.kind === "OperationDeclaration")!; const paths = decls.filter((decl): decl is GrlPathDeclaration => decl.kind === "PathDeclaration"); const compiled = compileOperation(operation, pathsByName(paths)); expect(compiled).toMatchObject({ operationId: "weld_op_01", kind: "arc_welding", pathId: "weld_path", process: { weld_id: "WELD_1", voltage: 24, current: 180, weave: "none" }, startActions: [ { kind: "ACTION", actionKind: "start_action", operationId: "weld_op_01", statement: "io . do [ 20 ] = true" } ], endActions: [ { kind: "ACTION", actionKind: "end_action", operationId: "weld_op_01", statement: "io . do [ 20 ] = false" } ] }); }); it("extracts run_operation and expands to start action, path, and end action", () => { const decls = declarations(); const procedure = decls.find((decl): decl is GrlProcedureDeclaration => decl.kind === "ProcedureDeclaration")!; const operation = decls.find((decl): decl is GrlOperationDeclaration => decl.kind === "OperationDeclaration")!; const paths = decls.filter((decl): decl is GrlPathDeclaration => decl.kind === "PathDeclaration"); const compiled = compileOperation(operation, pathsByName(paths)); const [run] = parseProcedureRunOperationStatements(procedure); expect(run).toEqual({ kind: "RUN_OPERATION", operationId: "weld_op_01", sourceMap: { line: 25, column: 5 } }); expect(expandRunOperation(run!, new Map([[compiled.operationId, compiled]]))).toEqual([ expect.objectContaining({ kind: "ACTION", actionKind: "start_action" }), { kind: "RUN_PATH", pathId: "weld_path", sourceMap: { line: 25, column: 5 } }, expect.objectContaining({ kind: "ACTION", actionKind: "end_action" }) ]); }); it("reports operations that reference missing paths and missing run_operation targets", () => { const missingPathOperation = parseGrl(`language grl 0.1 module Main operation bad_op { kind: handling path: missing_path } end `).module.declarations.find((decl): decl is GrlOperationDeclaration => decl.kind === "OperationDeclaration")!; expect(() => compileOperation(missingPathOperation, new Map())).toThrowError( expect.objectContaining({ code: "GRL_OPERATION_PATH_NOT_FOUND" }) ); expect(() => expandRunOperation({ kind: "RUN_OPERATION", operationId: "missing_op" }, new Map()) ).toThrowError(expect.objectContaining({ code: "GRL_OPERATION_NOT_FOUND" })); }); });