198 lines
5.4 KiB
TypeScript
198 lines
5.4 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import type { GrlProcedureDeclaration } from "../../src/grl/ast/index.js";
|
|
import { parseGrl } from "../../src/grl/parser/index.js";
|
|
import { parseProcedureControlFlow } from "../../src/grl/semantic/index.js";
|
|
|
|
function procedure(source: string): GrlProcedureDeclaration {
|
|
return parseGrl(source).module.declarations.find(
|
|
(decl): decl is GrlProcedureDeclaration => decl.kind === "ProcedureDeclaration"
|
|
)!;
|
|
}
|
|
|
|
describe("GRL control-flow compilation", () => {
|
|
it("compiles if, elseif, else, while, for, switch, labels, and jumps", () => {
|
|
const proc = procedure(`language grl 0.1
|
|
module Main
|
|
proc main()
|
|
label retry
|
|
if ready == true
|
|
wait io.di[1] == true
|
|
elseif fault == true
|
|
jump recovery
|
|
else
|
|
jump retry
|
|
end
|
|
while all(io.di[1] == true, io.di[2] == false)
|
|
continue
|
|
end
|
|
for i = 1 to 3 step 1
|
|
movej home
|
|
end
|
|
switch mode
|
|
case 1
|
|
break
|
|
case 2
|
|
jump done
|
|
default
|
|
jump recovery
|
|
end
|
|
label recovery
|
|
label done
|
|
end
|
|
end
|
|
`);
|
|
|
|
const flow = parseProcedureControlFlow(proc);
|
|
|
|
expect(flow).toEqual([
|
|
expect.objectContaining({ kind: "LABEL", name: "retry", scopePath: [] }),
|
|
expect.objectContaining({
|
|
kind: "IF",
|
|
branches: [
|
|
expect.objectContaining({
|
|
branchKind: "if",
|
|
condition: expect.objectContaining({ text: "ready == true" }),
|
|
body: [expect.objectContaining({ kind: "RAW_STATEMENT", text: "wait io . di [ 1 ] == true" })]
|
|
}),
|
|
expect.objectContaining({
|
|
branchKind: "elseif",
|
|
condition: expect.objectContaining({ text: "fault == true" }),
|
|
body: [expect.objectContaining({ kind: "JUMP", label: "recovery" })]
|
|
}),
|
|
expect.objectContaining({
|
|
branchKind: "else",
|
|
body: [expect.objectContaining({ kind: "JUMP", label: "retry" })]
|
|
})
|
|
]
|
|
}),
|
|
expect.objectContaining({
|
|
kind: "WHILE",
|
|
condition: expect.objectContaining({
|
|
text: "all ( io . di [ 1 ] == true , io . di [ 2 ] == false )"
|
|
}),
|
|
body: [expect.objectContaining({ kind: "CONTINUE" })]
|
|
}),
|
|
expect.objectContaining({
|
|
kind: "FOR",
|
|
iterator: "i",
|
|
from: expect.objectContaining({ text: "1" }),
|
|
to: expect.objectContaining({ text: "3" }),
|
|
step: expect.objectContaining({ text: "1" }),
|
|
body: [expect.objectContaining({ kind: "RAW_STATEMENT", text: "movej home" })]
|
|
}),
|
|
expect.objectContaining({
|
|
kind: "SWITCH",
|
|
expression: expect.objectContaining({ text: "mode" }),
|
|
cases: [
|
|
expect.objectContaining({ caseKind: "case", value: 1, body: [expect.objectContaining({ kind: "BREAK" })] }),
|
|
expect.objectContaining({ caseKind: "case", value: 2, body: [expect.objectContaining({ kind: "JUMP", label: "done" })] }),
|
|
expect.objectContaining({ caseKind: "default", body: [expect.objectContaining({ kind: "JUMP", label: "recovery" })] })
|
|
]
|
|
}),
|
|
expect.objectContaining({ kind: "LABEL", name: "recovery", scopePath: [] }),
|
|
expect.objectContaining({ kind: "LABEL", name: "done", scopePath: [] })
|
|
]);
|
|
});
|
|
|
|
it("reports non-boolean control conditions", () => {
|
|
const proc = procedure(`language grl 0.1
|
|
module Main
|
|
proc main()
|
|
if 1
|
|
end
|
|
end
|
|
end
|
|
`);
|
|
|
|
expect(() => parseProcedureControlFlow(proc)).toThrowError(
|
|
expect.objectContaining({ code: "GRL_CONTROL_CONDITION_NOT_BOOL" })
|
|
);
|
|
});
|
|
|
|
it("reports break and continue outside valid blocks", () => {
|
|
const breakProc = procedure(`language grl 0.1
|
|
module Main
|
|
proc main()
|
|
break
|
|
end
|
|
end
|
|
`);
|
|
const continueProc = procedure(`language grl 0.1
|
|
module Main
|
|
proc main()
|
|
switch mode
|
|
case 1
|
|
continue
|
|
end
|
|
end
|
|
end
|
|
`);
|
|
|
|
expect(() => parseProcedureControlFlow(breakProc)).toThrowError(
|
|
expect.objectContaining({ code: "GRL_BREAK_OUTSIDE_FLOW" })
|
|
);
|
|
expect(() => parseProcedureControlFlow(continueProc)).toThrowError(
|
|
expect.objectContaining({ code: "GRL_CONTINUE_OUTSIDE_LOOP" })
|
|
);
|
|
});
|
|
|
|
it("reports duplicate or non-constant switch cases", () => {
|
|
const duplicateProc = procedure(`language grl 0.1
|
|
module Main
|
|
proc main()
|
|
switch mode
|
|
case 1
|
|
break
|
|
case 1
|
|
break
|
|
end
|
|
end
|
|
end
|
|
`);
|
|
const nonConstantProc = procedure(`language grl 0.1
|
|
module Main
|
|
proc main()
|
|
switch mode
|
|
case mode + 1
|
|
break
|
|
end
|
|
end
|
|
end
|
|
`);
|
|
|
|
expect(() => parseProcedureControlFlow(duplicateProc)).toThrowError(
|
|
expect.objectContaining({ code: "GRL_SWITCH_CASE_DUPLICATE" })
|
|
);
|
|
expect(() => parseProcedureControlFlow(nonConstantProc)).toThrowError(
|
|
expect.objectContaining({ code: "GRL_SWITCH_CASE_NOT_CONSTANT" })
|
|
);
|
|
});
|
|
|
|
it("reports labels that cannot be reached by jump", () => {
|
|
const intoBlockProc = procedure(`language grl 0.1
|
|
module Main
|
|
proc main()
|
|
jump inner
|
|
if ready == true
|
|
label inner
|
|
end
|
|
end
|
|
end
|
|
`);
|
|
const missingLabelProc = procedure(`language grl 0.1
|
|
module Main
|
|
proc main()
|
|
jump missing
|
|
end
|
|
end
|
|
`);
|
|
|
|
expect(() => parseProcedureControlFlow(intoBlockProc)).toThrowError(
|
|
expect.objectContaining({ code: "GRL_JUMP_INTO_BLOCK" })
|
|
);
|
|
expect(() => parseProcedureControlFlow(missingLabelProc)).toThrowError(
|
|
expect.objectContaining({ code: "GRL_LABEL_NOT_FOUND" })
|
|
);
|
|
});
|
|
});
|