import { describe, expect, it } from "vitest"; import { generateGrlProgram, type GrlProgramGenerationSpec } from "../../../src/grl/generator/index.js"; import { postProcessAllBrands } from "../../../src/grl/post/index.js"; import type { GrlOperationDeclaration, GrlPathDeclaration, GrlTargetDeclaration } from "../../../src/grl/ast/index.js"; import { parseGrl } from "../../../src/grl/parser/index.js"; import { compileSemanticProgram } from "../../../src/grl/semantic/index.js"; const SPEC: GrlProgramGenerationSpec = { moduleName: "GeneratedCell", speeds: { v_linear: "linear(200 mm/s)", v_joint: "joint(50 %)" }, zones: { z10: "z(10 mm)", zf: "fine" }, targets: [ { name: "pick", kind: "pose", values: [500, 0, 0, 0, 0, 0] }, { name: "home", kind: "joint", values: [0] }, { name: "place", kind: "pose", values: [600, 0, 0, 0, 0, 0] } ], path: { name: "generated_path", source: { type: "cad_curve", id: "edge_001", sample_distance: 5 }, defaults: { speed: "v_linear", zone: "z10" }, points: [ { motion: "movej", target: "home", speed: "v_joint", zone: "zf" }, { motion: "movel", target: "pick" }, { id: "place_point", motion: "movel", target: "place", zone: "zf" } ] }, operation: { name: "generated_op", kind: "handling", path: "generated_path", startAction: "io.do[1] = true", endAction: "io.do[1] = false" } }; describe("GRL generator and roundtrip", () => { it("generates stable expanded GRL with target/path/operation first", () => { const first = generateGrlProgram(SPEC, "expanded"); const second = generateGrlProgram(SPEC, "expanded"); expect(first).toEqual(second); expect(first.stableIds).toEqual({ targets: ["home", "pick", "place"], points: ["p00", "p01", "place_point"], path: "generated_path", operation: "generated_op" }); expect(first.text).toBe(`language grl 0.1 module GeneratedCell const speed v_joint = joint(50 %) const speed v_linear = linear(200 mm/s) const zone z10 = z(10 mm) const zone zf = fine target home = joint_target { joints: [0 deg] } target pick = pose_target { pose: pose(500 mm, 0 mm, 0 mm, 0 deg, 0 deg, 0 deg) } target place = pose_target { pose: pose(600 mm, 0 mm, 0 mm, 0 deg, 0 deg, 0 deg) } path generated_path { source { id: "edge_001" sample_distance: 5 type: cad_curve } defaults { speed: v_linear zone: z10 } point p00 movej home speed v_joint zone zf point p01 movel pick point place_point movel place zone zf } operation generated_op { kind: handling path: generated_path start_action: io.do[1] = true end_action: io.do[1] = false } proc main() run_operation generated_op end end`); }); it("supports compact output that remains parseable", () => { const compact = generateGrlProgram(SPEC, "compact"); expect(compact.text).toContain("path generated_path { source { id: \"edge_001\""); expect(parseGrl(compact.text).module.name).toBe("GeneratedCell"); }); it("roundtrips through parser, semantic IR, and postprocessors", () => { const generated = generateGrlProgram(SPEC, "expanded"); const ast = parseGrl(generated.text); const declarations = ast.module.declarations; const targets = declarations.filter((decl): decl is GrlTargetDeclaration => decl.kind === "TargetDeclaration"); const path = declarations.find((decl): decl is GrlPathDeclaration => decl.kind === "PathDeclaration")!; const operation = declarations.find((decl): decl is GrlOperationDeclaration => decl.kind === "OperationDeclaration")!; const ir = compileSemanticProgram(ast, { startJoints: [0], sampleTime: 0.004 }); const post = postProcessAllBrands(ir); expect(targets.map((target) => target.name)).toEqual(["home", "pick", "place"]); expect(path.items).toEqual( expect.arrayContaining([ expect.objectContaining({ kind: "PathSourceBlock" }), expect.objectContaining({ kind: "PathDefaultsBlock" }), expect.objectContaining({ kind: "PathPoint", id: "p00" }), expect.objectContaining({ kind: "PathPoint", id: "p01" }), expect.objectContaining({ kind: "PathPoint", id: "place_point" }) ]) ); expect(operation).toMatchObject({ name: "generated_op", operationKind: "handling", pathName: "generated_path" }); expect(ir.symbols).toEqual( expect.arrayContaining([ expect.objectContaining({ kind: "path", name: "generated_path" }), expect.objectContaining({ kind: "operation", name: "generated_op" }) ]) ); expect(post.outputs.abb.text).toContain("MODULE GeneratedCell"); expect(post.outputs.fanuc.text).toContain("/PROG MAIN"); expect(post.outputs.kuka.text).toContain("DEF Main()"); }); });