Upload project files
This commit is contained in:
141
kdl-wasm/web/tests/grl/expressionCompile.test.ts
Normal file
141
kdl-wasm/web/tests/grl/expressionCompile.test.ts
Normal file
@@ -0,0 +1,141 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { GrlDataDeclaration, GrlPathDeclaration, GrlProcedureDeclaration, GrlTargetDeclaration } from "../../src/grl/ast/index.js";
|
||||
import { parseGrl } from "../../src/grl/parser/index.js";
|
||||
import { parseGrlExpression } from "../../src/grl/parser/expressionParser.js";
|
||||
import {
|
||||
buildMotionContext,
|
||||
compileGrlDataDeclaration,
|
||||
compileGrlTargetDeclaration,
|
||||
compilePathToPlanRequest,
|
||||
parseIoFlowStatements
|
||||
} from "../../src/grl/semantic/index.js";
|
||||
import { evaluateNumberExpression } from "../../src/grl/semantic/constantExpression.js";
|
||||
import { lexGrl } from "../../src/grl/lexer/index.js";
|
||||
|
||||
function expression(source: string) {
|
||||
return parseGrlExpression(lexGrl(source, { preserveComments: false }).filter((token) => token.kind !== "eof"));
|
||||
}
|
||||
|
||||
describe("GRL expression arithmetic and constant folding", () => {
|
||||
it("parses arithmetic, comparison, and logical precedence into AST nodes", () => {
|
||||
expect(expression("1 + 2 * 3")).toMatchObject({
|
||||
kind: "BinaryExpression",
|
||||
operator: "+",
|
||||
right: {
|
||||
kind: "BinaryExpression",
|
||||
operator: "*"
|
||||
}
|
||||
});
|
||||
expect(expression("(1 + 2) * 3")).toMatchObject({
|
||||
kind: "BinaryExpression",
|
||||
operator: "*",
|
||||
left: {
|
||||
kind: "BinaryExpression",
|
||||
operator: "+"
|
||||
}
|
||||
});
|
||||
expect(expression("not (a == b) or c != d")).toMatchObject({
|
||||
kind: "BinaryExpression",
|
||||
operator: "or",
|
||||
left: {
|
||||
kind: "UnaryExpression",
|
||||
operator: "not"
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("evaluates math functions, trigonometry, units, and diagnostics", () => {
|
||||
expect(evaluateNumberExpression(expression("100 + 50 mm/s"), {
|
||||
expectedKind: "linear_velocity",
|
||||
defaultUnit: "mm/s"
|
||||
})).toBeCloseTo(0.15);
|
||||
expect(evaluateNumberExpression(expression("(10 + 5) deg"), {
|
||||
expectedKind: "angle",
|
||||
defaultUnit: "deg"
|
||||
})).toBeCloseTo(Math.PI / 12);
|
||||
expect(evaluateNumberExpression(expression("clamp(20 mm, 1 mm, 10 mm)"), {
|
||||
expectedKind: "length",
|
||||
defaultUnit: "mm"
|
||||
})).toBeCloseTo(0.01);
|
||||
});
|
||||
|
||||
it("reports stable expression errors", () => {
|
||||
expect(() => evaluateNumberExpression(expression("sqrt(-1)"))).toThrowError(expect.objectContaining({
|
||||
code: "GRL_EXPR_DOMAIN"
|
||||
}));
|
||||
expect(() => evaluateNumberExpression(expression("10 mm + 2 s"))).toThrowError(expect.objectContaining({
|
||||
code: "GRL_EXPR_UNIT_MISMATCH"
|
||||
}));
|
||||
expect(() => evaluateNumberExpression(expression("1 / 0"))).toThrowError(expect.objectContaining({
|
||||
code: "GRL_EXPR_DIV_ZERO"
|
||||
}));
|
||||
});
|
||||
|
||||
it("folds speed, zone, target, path event, wait, and pulse expressions", () => {
|
||||
const program = parseGrl(`language grl 0.1
|
||||
module MathMotion
|
||||
const num blend_base = 5 + 5
|
||||
const speed v_pick = linear(100 + 50 mm/s)
|
||||
const speed v_safe = linear(max(50 mm/s, 200 mm/s / 2))
|
||||
const zone z_app = z(clamp(blend_base mm, 1 mm, 50 mm))
|
||||
target home = joint_target {
|
||||
joints: [0 deg, (10 + 5) deg, -90 deg]
|
||||
}
|
||||
target pick = pose_target {
|
||||
pose: pose(400 + 50 mm, 20 * 2 mm, sqrt(90000) mm, 0 deg, 0 deg, atan2(1, 1))
|
||||
}
|
||||
path main_path {
|
||||
defaults { speed: v_pick, zone: z_app }
|
||||
point p0 movej home speed linear(100 + 50 mm/s) zone z(5 + 5 mm)
|
||||
event at p0 distance 5 + 5 mm pulse io.do[1] duration 50 + 50 ms
|
||||
}
|
||||
proc main()
|
||||
wait io.di[1] == true timeout 1 + 1 s
|
||||
pulse io.do[2] duration 50 + 50 ms
|
||||
end
|
||||
end
|
||||
`);
|
||||
const declarations = program.module.declarations;
|
||||
const [blendBase, vPick, vSafe] = declarations.filter(
|
||||
(decl): decl is GrlDataDeclaration => decl.kind === "DataDeclaration"
|
||||
);
|
||||
expect(compileGrlDataDeclaration(blendBase!)).toMatchObject({ value: 10 });
|
||||
expect(compileGrlDataDeclaration(vPick!).value).toMatchObject({ kind: "linear" });
|
||||
expect((compileGrlDataDeclaration(vPick!).value as { velocity: number }).velocity).toBeCloseTo(0.15);
|
||||
expect(compileGrlDataDeclaration(vSafe!).value).toMatchObject({ kind: "linear" });
|
||||
expect((compileGrlDataDeclaration(vSafe!).value as { velocity: number }).velocity).toBeCloseTo(0.1);
|
||||
|
||||
const [home, pick] = declarations.filter((decl): decl is GrlTargetDeclaration => decl.kind === "TargetDeclaration");
|
||||
expect(compileGrlTargetDeclaration(home!)).toMatchObject({
|
||||
target: {
|
||||
joints: [0, Math.PI / 12, -Math.PI / 2]
|
||||
}
|
||||
});
|
||||
const pickTarget = compileGrlTargetDeclaration(pick!);
|
||||
expect("pose" in pickTarget.target && pickTarget.target.pose.position).toEqual([0.45, 0.04, 0.3]);
|
||||
|
||||
const context = buildMotionContext(
|
||||
declarations.filter(
|
||||
(decl): decl is GrlDataDeclaration | GrlTargetDeclaration =>
|
||||
decl.kind === "DataDeclaration" || decl.kind === "TargetDeclaration"
|
||||
)
|
||||
);
|
||||
const path = declarations.find((decl): decl is GrlPathDeclaration => decl.kind === "PathDeclaration")!;
|
||||
const compiledPath = compilePathToPlanRequest(path, context, {
|
||||
startJoints: [0, 0, 0],
|
||||
sampleTime: 0.004
|
||||
});
|
||||
expect(compiledPath.request.segments[0]).toMatchObject({
|
||||
speed: { kind: "linear" },
|
||||
zone: { kind: "distance", value: 0.01 }
|
||||
});
|
||||
expect((compiledPath.request.segments[0]?.speed as { velocity: number }).velocity).toBeCloseTo(0.15);
|
||||
expect(compiledPath.events[0]).toMatchObject({ distance: 0.01 });
|
||||
|
||||
const procedure = declarations.find((decl): decl is GrlProcedureDeclaration => decl.kind === "ProcedureDeclaration")!;
|
||||
expect(parseIoFlowStatements(procedure.bodyTokens)).toEqual([
|
||||
expect.objectContaining({ kind: "WAIT", timeout: 2 }),
|
||||
expect.objectContaining({ kind: "PULSE", duration: 0.1 })
|
||||
]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user