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