Align the QBE backend unit names with the dotted naming convention already used by the native backend (blaise.codegen.native.*). uCodeGen.pas → blaise.codegen.pas uCodeGenQBE.pas → blaise.codegen.qbe.pas Updated all uses clauses (~70 test files, main compiler units), documentation references (README + 5 docs/*.adoc files), and comment references in runtime/stdlib.
498 lines
13 KiB
ObjectPascal
498 lines
13 KiB
ObjectPascal
{
|
|
Blaise - An Object Pascal Compiler
|
|
Copyright (c) 2026 Graeme Geldenhuys
|
|
SPDX-License-Identifier: Apache-2.0 WITH Swift-exception
|
|
Licensed under the Apache License v2.0 with Runtime Library Exception.
|
|
See LICENSE file in the project root for full license terms.
|
|
}
|
|
|
|
unit cp.test.functions;
|
|
|
|
interface
|
|
|
|
uses
|
|
Classes, SysUtils, blaise.testing,
|
|
uLexer, uParser, uAST, uSymbolTable, uSemantic, blaise.codegen.qbe;
|
|
|
|
type
|
|
TFunctionTests = class(TTestCase)
|
|
private
|
|
function ParseSrc(const ASrc: string): TProgram;
|
|
function AnalyseSrc(const ASrc: string): TProgram;
|
|
function GenIR(const ASrc: string): string;
|
|
procedure AnalyseExpectError(const ASrc: string);
|
|
published
|
|
{ ------------------------------------------------------------------ }
|
|
{ Lexer }
|
|
{ ------------------------------------------------------------------ }
|
|
procedure TestLexer_Function_Keyword;
|
|
|
|
{ ------------------------------------------------------------------ }
|
|
{ Parser }
|
|
{ ------------------------------------------------------------------ }
|
|
procedure TestParse_FunctionMethod_InClass;
|
|
procedure TestParse_FunctionMethod_Name;
|
|
procedure TestParse_FunctionMethod_ReturnTypeName;
|
|
procedure TestParse_FunctionMethod_WithParams;
|
|
procedure TestParse_FunctionCall_Expr;
|
|
procedure TestParse_FunctionCall_Expr_ObjectName;
|
|
procedure TestParse_FunctionCall_Expr_WithArgs;
|
|
procedure TestParse_FunctionCall_Expr_ArgCount;
|
|
|
|
{ ------------------------------------------------------------------ }
|
|
{ Semantic }
|
|
{ ------------------------------------------------------------------ }
|
|
procedure TestSemantic_FunctionReturn_TypeResolved;
|
|
procedure TestSemantic_FunctionCall_Resolves;
|
|
procedure TestSemantic_FunctionCall_ReturnTypeAssign_OK;
|
|
procedure TestSemantic_FunctionCall_ReturnTypeMismatch_RaisesError;
|
|
procedure TestSemantic_FunctionCall_WrongArgCount_RaisesError;
|
|
procedure TestSemantic_Result_AvailableInBody;
|
|
procedure TestSemantic_Result_WrongType_RaisesError;
|
|
|
|
{ ------------------------------------------------------------------ }
|
|
{ Code generation }
|
|
{ ------------------------------------------------------------------ }
|
|
procedure TestCodegen_Function_EmitsReturnType;
|
|
procedure TestCodegen_Function_ReturnTypeW;
|
|
procedure TestCodegen_Function_HasResultVar;
|
|
procedure TestCodegen_FunctionCall_EmitsCall;
|
|
procedure TestCodegen_FunctionCall_ResultLoaded;
|
|
procedure TestCodegen_Function_ReturnsResult;
|
|
end;
|
|
|
|
implementation
|
|
|
|
{ ------------------------------------------------------------------ }
|
|
{ Helpers }
|
|
{ ------------------------------------------------------------------ }
|
|
|
|
function TFunctionTests.ParseSrc(const ASrc: string): TProgram;
|
|
var
|
|
L: TLexer;
|
|
P: TParser;
|
|
begin
|
|
L := TLexer.Create(ASrc);
|
|
P := TParser.Create(L);
|
|
try
|
|
Result := P.Parse();
|
|
finally
|
|
P.Free();
|
|
L.Free();
|
|
end;
|
|
end;
|
|
|
|
function TFunctionTests.AnalyseSrc(const ASrc: string): TProgram;
|
|
var
|
|
A: TSemanticAnalyser;
|
|
begin
|
|
Result := ParseSrc(ASrc);
|
|
A := TSemanticAnalyser.Create();
|
|
try
|
|
A.Analyse(Result);
|
|
finally
|
|
A.Free();
|
|
end;
|
|
end;
|
|
|
|
function TFunctionTests.GenIR(const ASrc: string): string;
|
|
var
|
|
Prog: TProgram;
|
|
CG: TCodeGenQBE;
|
|
begin
|
|
Prog := AnalyseSrc(ASrc);
|
|
try
|
|
CG := TCodeGenQBE.Create();
|
|
try
|
|
CG.Generate(Prog);
|
|
Result := CG.GetOutput();
|
|
finally
|
|
CG.Free();
|
|
end;
|
|
finally
|
|
Prog.Free();
|
|
end;
|
|
end;
|
|
|
|
procedure TFunctionTests.AnalyseExpectError(const ASrc: string);
|
|
var
|
|
Prog: TProgram;
|
|
begin
|
|
try
|
|
Prog := AnalyseSrc(ASrc);
|
|
Prog.Free();
|
|
Fail('Expected ESemanticError');
|
|
except
|
|
on E: ESemanticError do ;
|
|
end;
|
|
end;
|
|
|
|
{ ------------------------------------------------------------------ }
|
|
{ Shared source snippets }
|
|
{ ------------------------------------------------------------------ }
|
|
|
|
const
|
|
SrcGetterClass =
|
|
'''
|
|
program P;
|
|
type
|
|
TBox = class
|
|
Value: Integer;
|
|
procedure SetValue(AVal: Integer);
|
|
begin
|
|
Self.Value := AVal
|
|
end;
|
|
function GetValue: Integer;
|
|
begin
|
|
Result := Self.Value
|
|
end;
|
|
end;
|
|
var B: TBox; N: Integer;
|
|
begin
|
|
B := TBox.Create();
|
|
B.SetValue(42);
|
|
N := B.GetValue()
|
|
end.
|
|
''';
|
|
|
|
SrcAdderClass =
|
|
'''
|
|
program P;
|
|
type
|
|
TCalc = class
|
|
function Add(A: Integer; B: Integer): Integer;
|
|
begin
|
|
Result := A + B
|
|
end;
|
|
end;
|
|
var C: TCalc; N: Integer;
|
|
begin
|
|
C := TCalc.Create();
|
|
N := C.Add(3, 4)
|
|
end.
|
|
''';
|
|
|
|
{ ------------------------------------------------------------------ }
|
|
{ Lexer }
|
|
{ ------------------------------------------------------------------ }
|
|
|
|
procedure TFunctionTests.TestLexer_Function_Keyword;
|
|
var
|
|
L: TLexer;
|
|
T: TToken;
|
|
begin
|
|
L := TLexer.Create('function');
|
|
try
|
|
T := L.Next();
|
|
AssertEquals('function token', Ord(tkFunction), Ord(T.Kind));
|
|
finally
|
|
L.Free();
|
|
end;
|
|
end;
|
|
|
|
{ ------------------------------------------------------------------ }
|
|
{ Parser }
|
|
{ ------------------------------------------------------------------ }
|
|
|
|
procedure TFunctionTests.TestParse_FunctionMethod_InClass;
|
|
var
|
|
Prog: TProgram;
|
|
CD: TClassTypeDef;
|
|
begin
|
|
Prog := ParseSrc(SrcGetterClass);
|
|
try
|
|
CD := TClassTypeDef(TTypeDecl(Prog.Block.TypeDecls[0]).Def);
|
|
AssertEquals('two methods (proc + func)', 2, CD.Methods.Count);
|
|
finally
|
|
Prog.Free();
|
|
end;
|
|
end;
|
|
|
|
procedure TFunctionTests.TestParse_FunctionMethod_Name;
|
|
var
|
|
Prog: TProgram;
|
|
CD: TClassTypeDef;
|
|
MD: TMethodDecl;
|
|
begin
|
|
Prog := ParseSrc(SrcGetterClass);
|
|
try
|
|
CD := TClassTypeDef(TTypeDecl(Prog.Block.TypeDecls[0]).Def);
|
|
MD := TMethodDecl(CD.Methods[1]); { second method = GetValue }
|
|
AssertEquals('func name', 'GetValue', MD.Name);
|
|
finally
|
|
Prog.Free();
|
|
end;
|
|
end;
|
|
|
|
procedure TFunctionTests.TestParse_FunctionMethod_ReturnTypeName;
|
|
var
|
|
Prog: TProgram;
|
|
CD: TClassTypeDef;
|
|
MD: TMethodDecl;
|
|
begin
|
|
Prog := ParseSrc(SrcGetterClass);
|
|
try
|
|
CD := TClassTypeDef(TTypeDecl(Prog.Block.TypeDecls[0]).Def);
|
|
MD := TMethodDecl(CD.Methods[1]);
|
|
AssertEquals('return type name', 'Integer', MD.ReturnTypeName);
|
|
finally
|
|
Prog.Free();
|
|
end;
|
|
end;
|
|
|
|
procedure TFunctionTests.TestParse_FunctionMethod_WithParams;
|
|
var
|
|
Prog: TProgram;
|
|
CD: TClassTypeDef;
|
|
MD: TMethodDecl;
|
|
begin
|
|
Prog := ParseSrc(SrcAdderClass);
|
|
try
|
|
CD := TClassTypeDef(TTypeDecl(Prog.Block.TypeDecls[0]).Def);
|
|
MD := TMethodDecl(CD.Methods[0]);
|
|
AssertEquals('two params', 2, MD.Params.Count);
|
|
AssertEquals('return type', 'Integer', MD.ReturnTypeName);
|
|
finally
|
|
Prog.Free();
|
|
end;
|
|
end;
|
|
|
|
procedure TFunctionTests.TestParse_FunctionCall_Expr;
|
|
var
|
|
Prog: TProgram;
|
|
Assign: TAssignment;
|
|
begin
|
|
Prog := ParseSrc(SrcGetterClass);
|
|
try
|
|
{ third stmt: N := B.GetValue() }
|
|
Assign := TAssignment(Prog.Block.Stmts[2]);
|
|
AssertTrue('RHS is TMethodCallExpr', Assign.Expr is TMethodCallExpr);
|
|
finally
|
|
Prog.Free();
|
|
end;
|
|
end;
|
|
|
|
procedure TFunctionTests.TestParse_FunctionCall_Expr_ObjectName;
|
|
var
|
|
Prog: TProgram;
|
|
Expr: TMethodCallExpr;
|
|
begin
|
|
Prog := ParseSrc(SrcGetterClass);
|
|
try
|
|
Expr := TMethodCallExpr(TAssignment(Prog.Block.Stmts[2]).Expr);
|
|
AssertEquals('object name', 'B', Expr.ObjectName);
|
|
AssertEquals('method name', 'GetValue', Expr.Name);
|
|
finally
|
|
Prog.Free();
|
|
end;
|
|
end;
|
|
|
|
procedure TFunctionTests.TestParse_FunctionCall_Expr_WithArgs;
|
|
var
|
|
Prog: TProgram;
|
|
Assign: TAssignment;
|
|
Expr: TMethodCallExpr;
|
|
begin
|
|
Prog := ParseSrc(SrcAdderClass);
|
|
try
|
|
{ second stmt: N := C.Add(3, 4) }
|
|
Assign := TAssignment(Prog.Block.Stmts[1]);
|
|
AssertTrue('RHS is TMethodCallExpr', Assign.Expr is TMethodCallExpr);
|
|
Expr := TMethodCallExpr(Assign.Expr);
|
|
AssertEquals('object', 'C', Expr.ObjectName);
|
|
AssertEquals('method', 'Add', Expr.Name);
|
|
finally
|
|
Prog.Free();
|
|
end;
|
|
end;
|
|
|
|
procedure TFunctionTests.TestParse_FunctionCall_Expr_ArgCount;
|
|
var
|
|
Prog: TProgram;
|
|
Expr: TMethodCallExpr;
|
|
begin
|
|
Prog := ParseSrc(SrcAdderClass);
|
|
try
|
|
Expr := TMethodCallExpr(TAssignment(Prog.Block.Stmts[1]).Expr);
|
|
AssertEquals('two args', 2, Expr.Args.Count);
|
|
finally
|
|
Prog.Free();
|
|
end;
|
|
end;
|
|
|
|
{ ------------------------------------------------------------------ }
|
|
{ Semantic }
|
|
{ ------------------------------------------------------------------ }
|
|
|
|
procedure TFunctionTests.TestSemantic_FunctionReturn_TypeResolved;
|
|
var
|
|
Prog: TProgram;
|
|
CD: TClassTypeDef;
|
|
MD: TMethodDecl;
|
|
begin
|
|
Prog := AnalyseSrc(SrcGetterClass);
|
|
try
|
|
CD := TClassTypeDef(TTypeDecl(Prog.Block.TypeDecls[0]).Def);
|
|
MD := TMethodDecl(CD.Methods[1]);
|
|
AssertNotNull('return type resolved', MD.ResolvedReturnType);
|
|
AssertEquals('return type is Integer',
|
|
Ord(tyInteger), Ord(MD.ResolvedReturnType.Kind));
|
|
finally
|
|
Prog.Free();
|
|
end;
|
|
end;
|
|
|
|
procedure TFunctionTests.TestSemantic_FunctionCall_Resolves;
|
|
begin
|
|
AnalyseSrc(SrcGetterClass).Free();
|
|
end;
|
|
|
|
procedure TFunctionTests.TestSemantic_FunctionCall_ReturnTypeAssign_OK;
|
|
begin
|
|
AnalyseSrc(SrcGetterClass).Free();
|
|
end;
|
|
|
|
procedure TFunctionTests.TestSemantic_FunctionCall_ReturnTypeMismatch_RaisesError;
|
|
begin
|
|
AnalyseExpectError(
|
|
'''
|
|
program P;
|
|
type
|
|
TFoo = class
|
|
function GetVal: Integer;
|
|
begin
|
|
Result := 1
|
|
end;
|
|
end;
|
|
var F: TFoo; S: string;
|
|
begin
|
|
F := TFoo.Create();
|
|
S := F.GetVal()
|
|
end.
|
|
''');
|
|
end;
|
|
|
|
procedure TFunctionTests.TestSemantic_FunctionCall_WrongArgCount_RaisesError;
|
|
begin
|
|
AnalyseExpectError(
|
|
'''
|
|
program P;
|
|
type
|
|
TFoo = class
|
|
function GetVal: Integer;
|
|
begin
|
|
Result := 0
|
|
end;
|
|
end;
|
|
var F: TFoo; N: Integer;
|
|
begin
|
|
F := TFoo.Create();
|
|
N := F.GetVal(1)
|
|
end.
|
|
''');
|
|
end;
|
|
|
|
procedure TFunctionTests.TestSemantic_Result_AvailableInBody;
|
|
begin
|
|
{ If Result is not in scope, analysing it as an identifier would raise
|
|
an ESemanticError. A clean pass proves Result is defined. }
|
|
AnalyseSrc(
|
|
'''
|
|
program P;
|
|
type
|
|
TFoo = class
|
|
function GetOne: Integer;
|
|
begin
|
|
Result := 1
|
|
end;
|
|
end;
|
|
var F: TFoo; N: Integer;
|
|
begin
|
|
F := TFoo.Create();
|
|
N := F.GetOne()
|
|
end.
|
|
'''
|
|
).Free();
|
|
end;
|
|
|
|
procedure TFunctionTests.TestSemantic_Result_WrongType_RaisesError;
|
|
begin
|
|
AnalyseExpectError(
|
|
'''
|
|
program P;
|
|
type
|
|
TFoo = class
|
|
function GetInt: Integer;
|
|
begin
|
|
Result := 'not an int'
|
|
end;
|
|
end;
|
|
begin end.
|
|
''');
|
|
end;
|
|
|
|
{ ------------------------------------------------------------------ }
|
|
{ Code generation }
|
|
{ ------------------------------------------------------------------ }
|
|
|
|
procedure TFunctionTests.TestCodegen_Function_EmitsReturnType;
|
|
var
|
|
IR: string;
|
|
begin
|
|
IR := GenIR(SrcGetterClass);
|
|
AssertTrue('function keyword with return type',
|
|
Pos('function w $TBox_GetValue', IR) > 0);
|
|
end;
|
|
|
|
procedure TFunctionTests.TestCodegen_Function_ReturnTypeW;
|
|
var
|
|
IR: string;
|
|
begin
|
|
IR := GenIR(SrcAdderClass);
|
|
AssertTrue('Add returns w (Integer)',
|
|
Pos('function w $TCalc_Add', IR) > 0);
|
|
end;
|
|
|
|
procedure TFunctionTests.TestCodegen_Function_HasResultVar;
|
|
var
|
|
IR: string;
|
|
begin
|
|
IR := GenIR(SrcGetterClass);
|
|
AssertTrue('_var_Result allocated',
|
|
Pos('%_var_Result', IR) > 0);
|
|
end;
|
|
|
|
procedure TFunctionTests.TestCodegen_FunctionCall_EmitsCall;
|
|
var
|
|
IR: string;
|
|
begin
|
|
IR := GenIR(SrcGetterClass);
|
|
AssertTrue('call to TBox_GetValue',
|
|
Pos('call $TBox_GetValue', IR) > 0);
|
|
end;
|
|
|
|
procedure TFunctionTests.TestCodegen_FunctionCall_ResultLoaded;
|
|
var
|
|
IR: string;
|
|
begin
|
|
IR := GenIR(SrcGetterClass);
|
|
{ N is a program-level global; the call result is stored into $N }
|
|
AssertTrue('result stored into N',
|
|
Pos('$N', IR) > 0);
|
|
end;
|
|
|
|
procedure TFunctionTests.TestCodegen_Function_ReturnsResult;
|
|
var
|
|
IR: string;
|
|
begin
|
|
IR := GenIR(SrcGetterClass);
|
|
{ Function must emit 'ret %_tN' (not just 'ret') }
|
|
AssertTrue('function has ret with value',
|
|
Pos('ret %_t', IR) > 0);
|
|
end;
|
|
|
|
initialization
|
|
RegisterTest(TFunctionTests);
|
|
|
|
end.
|