202 lines
5.9 KiB
TypeScript
202 lines
5.9 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import type { GrlFunctionDeclaration, GrlProcedureDeclaration } from "../../src/grl/ast/index.js";
|
|
import { parseGrl } from "../../src/grl/parser/index.js";
|
|
import { analyzeProcFunctionSemantics } from "../../src/grl/semantic/index.js";
|
|
|
|
function declarations(source: string) {
|
|
return parseGrl(source).module.declarations;
|
|
}
|
|
|
|
describe("GRL proc, func, call, return, and scope semantics", () => {
|
|
it("parses function declarations and analyzes proc/func signatures, calls, returns, and warnings", () => {
|
|
const decls = declarations(`language grl 0.1
|
|
module Main
|
|
var int global_count = 0
|
|
proc read_sensor(out bool ok)
|
|
ok = true
|
|
end
|
|
proc main(in bool start, out bool done, inout int count)
|
|
call read_sensor(done)
|
|
call helper(count)
|
|
call self_check()
|
|
return
|
|
end
|
|
proc self_check()
|
|
call self_check()
|
|
end
|
|
func int helper(inout int value)
|
|
var int global_count = 1
|
|
return value
|
|
end
|
|
end
|
|
`);
|
|
const func = decls.find((decl): decl is GrlFunctionDeclaration => decl.kind === "FunctionDeclaration")!;
|
|
|
|
expect(func).toMatchObject({
|
|
kind: "FunctionDeclaration",
|
|
returnType: "int",
|
|
name: "helper",
|
|
bodyTokens: [
|
|
{ raw: "var" },
|
|
{ raw: "int" },
|
|
{ raw: "global_count" },
|
|
{ raw: "=" },
|
|
{ raw: "1" },
|
|
{ raw: "return" },
|
|
{ raw: "value" }
|
|
]
|
|
});
|
|
|
|
const analysis = analyzeProcFunctionSemantics(decls);
|
|
|
|
expect(analysis.procedures).toEqual([
|
|
expect.objectContaining({
|
|
name: "read_sensor",
|
|
parameters: [expect.objectContaining({ name: "ok", typeName: "bool", direction: "out" })]
|
|
}),
|
|
expect.objectContaining({
|
|
name: "main",
|
|
parameters: [
|
|
expect.objectContaining({ name: "start", typeName: "bool", direction: "in" }),
|
|
expect.objectContaining({ name: "done", typeName: "bool", direction: "out" }),
|
|
expect.objectContaining({ name: "count", typeName: "int", direction: "inout" })
|
|
]
|
|
}),
|
|
expect.objectContaining({ name: "self_check", parameters: [] })
|
|
]);
|
|
expect(analysis.functions).toEqual([
|
|
expect.objectContaining({
|
|
name: "helper",
|
|
returnType: "int",
|
|
parameters: [expect.objectContaining({ name: "value", typeName: "int", direction: "inout" })]
|
|
})
|
|
]);
|
|
expect(analysis.calls).toEqual([
|
|
expect.objectContaining({ kind: "CALL", target: "read_sensor", args: [expect.objectContaining({ text: "done" })] }),
|
|
expect.objectContaining({ kind: "CALL", target: "helper", args: [expect.objectContaining({ text: "count" })] }),
|
|
expect.objectContaining({ kind: "CALL", target: "self_check", args: [] }),
|
|
expect.objectContaining({ kind: "CALL", target: "self_check", args: [] })
|
|
]);
|
|
expect(analysis.returns).toEqual([
|
|
expect.objectContaining({ kind: "RETURN" }),
|
|
expect.objectContaining({ kind: "RETURN", value: expect.objectContaining({ text: "value" }) })
|
|
]);
|
|
expect(analysis.diagnostics).toEqual([
|
|
expect.objectContaining({ severity: "warning", code: "GRL_RECURSIVE_CALL" }),
|
|
expect.objectContaining({ severity: "warning", code: "GRL_NAME_SHADOWS_OUTER_SCOPE" })
|
|
]);
|
|
});
|
|
|
|
it("reports out parameters that are not assigned on all normal return paths", () => {
|
|
const decls = declarations(`language grl 0.1
|
|
module Main
|
|
proc main(out bool done)
|
|
if ready == true
|
|
done = true
|
|
end
|
|
return
|
|
end
|
|
end
|
|
`);
|
|
|
|
expect(() => analyzeProcFunctionSemantics(decls)).toThrowError(
|
|
expect.objectContaining({ code: "GRL_OUT_PARAM_NOT_ASSIGNED" })
|
|
);
|
|
});
|
|
|
|
it("reports out and inout call arguments that are not lvalues", () => {
|
|
const decls = declarations(`language grl 0.1
|
|
module Main
|
|
proc set_done(out bool done)
|
|
done = true
|
|
end
|
|
proc main()
|
|
call set_done(true)
|
|
end
|
|
end
|
|
`);
|
|
|
|
expect(() => analyzeProcFunctionSemantics(decls)).toThrowError(
|
|
expect.objectContaining({ code: "GRL_ARGUMENT_NOT_LVALUE" })
|
|
);
|
|
});
|
|
|
|
it("reports missing or incompatible function returns", () => {
|
|
const missingReturn = declarations(`language grl 0.1
|
|
module Main
|
|
func int bad(in bool ready)
|
|
if ready == true
|
|
return 1
|
|
end
|
|
end
|
|
end
|
|
`);
|
|
const wrongReturn = declarations(`language grl 0.1
|
|
module Main
|
|
func bool bad()
|
|
return 1
|
|
end
|
|
end
|
|
`);
|
|
|
|
expect(() => analyzeProcFunctionSemantics(missingReturn)).toThrowError(
|
|
expect.objectContaining({ code: "GRL_FUNC_MISSING_RETURN" })
|
|
);
|
|
expect(() => analyzeProcFunctionSemantics(wrongReturn)).toThrowError(
|
|
expect.objectContaining({ code: "GRL_RETURN_TYPE_MISMATCH" })
|
|
);
|
|
});
|
|
|
|
it("reports illegal function side effects and procedure return values", () => {
|
|
const functionSideEffect = declarations(`language grl 0.1
|
|
module Main
|
|
func bool bad()
|
|
wait io.di[1] == true
|
|
return true
|
|
end
|
|
end
|
|
`);
|
|
const procedureReturnValue = declarations(`language grl 0.1
|
|
module Main
|
|
proc main()
|
|
return true
|
|
end
|
|
end
|
|
`);
|
|
|
|
expect(() => analyzeProcFunctionSemantics(functionSideEffect)).toThrowError(
|
|
expect.objectContaining({ code: "GRL_FUNC_SIDE_EFFECT" })
|
|
);
|
|
expect(() => analyzeProcFunctionSemantics(procedureReturnValue)).toThrowError(
|
|
expect.objectContaining({ code: "GRL_RETURN_VALUE_IN_PROC" })
|
|
);
|
|
});
|
|
|
|
it("reports call target and argument type errors", () => {
|
|
const missingCall = declarations(`language grl 0.1
|
|
module Main
|
|
proc main()
|
|
call missing()
|
|
end
|
|
end
|
|
`);
|
|
const typeMismatch = declarations(`language grl 0.1
|
|
module Main
|
|
proc expects_int(in int value)
|
|
return
|
|
end
|
|
proc main()
|
|
call expects_int("bad")
|
|
end
|
|
end
|
|
`);
|
|
|
|
expect(() => analyzeProcFunctionSemantics(missingCall)).toThrowError(
|
|
expect.objectContaining({ code: "GRL_CALL_TARGET_NOT_FOUND" })
|
|
);
|
|
expect(() => analyzeProcFunctionSemantics(typeMismatch)).toThrowError(
|
|
expect.objectContaining({ code: "GRL_CALL_ARGUMENT_TYPE" })
|
|
);
|
|
});
|
|
});
|