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

150 lines
4.8 KiB
TypeScript

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