Add try/finally, try/except, and raise statement support

Adds tkTry/tkFinally/tkExcept/tkRaise tokens, TTryFinallyStmt,
TTryExceptStmt, and TRaiseStmt AST nodes, recursive-descent parser
for all three forms (bare raise supported), semantic validation
(raise requires a class expression), and QBE codegen.

try/finally emits both bodies sequentially (simplified; full
stack unwinding requires RTL). try/except emits the try body then
skips to an except_handler label (reachable via raise dispatch in
a future phase). raise emits call $_Raise(l <obj>).

26 new tests in cp.test.exceptions (378 total, all passing).
This commit is contained in:
Graeme Geldenhuys 2026-04-20 22:57:10 +01:00
parent d7b42180f1
commit 90d9ac1ef1
7 changed files with 753 additions and 2 deletions

View file

@ -106,6 +106,26 @@ type
destructor Destroy; override;
end;
TTryFinallyStmt = class(TASTStmt)
public
TryBody: TCompoundStmt; { owned }
FinallyBody: TCompoundStmt; { owned }
destructor Destroy; override;
end;
TTryExceptStmt = class(TASTStmt)
public
TryBody: TCompoundStmt; { owned }
ExceptBody: TCompoundStmt; { owned }
destructor Destroy; override;
end;
TRaiseStmt = class(TASTStmt)
public
Expr: TASTExpr; { owned; nil = bare re-raise }
destructor Destroy; override;
end;
TFieldAssignment = class(TASTStmt)
public
RecordName: string;
@ -325,6 +345,32 @@ begin
inherited Destroy;
end;
{ TTryFinallyStmt }
destructor TTryFinallyStmt.Destroy;
begin
TryBody.Free;
FinallyBody.Free;
inherited Destroy;
end;
{ TTryExceptStmt }
destructor TTryExceptStmt.Destroy;
begin
TryBody.Free;
ExceptBody.Free;
inherited Destroy;
end;
{ TRaiseStmt }
destructor TRaiseStmt.Destroy;
begin
Expr.Free;
inherited Destroy;
end;
{ TBinaryExpr }
destructor TBinaryExpr.Destroy;

View file

@ -41,6 +41,9 @@ type
procedure EmitIfStmt(AStmt: TIfStmt);
procedure EmitWhileStmt(AStmt: TWhileStmt);
procedure EmitForStmt(AStmt: TForStmt);
procedure EmitTryFinallyStmt(AStmt: TTryFinallyStmt);
procedure EmitTryExceptStmt(AStmt: TTryExceptStmt);
procedure EmitRaiseStmt(AStmt: TRaiseStmt);
procedure EmitCompoundStmt(AStmt: TCompoundStmt);
procedure EmitAssignment(AAssign: TAssignment);
procedure EmitFieldAssignment(AAssign: TFieldAssignment);
@ -237,7 +240,13 @@ end;
procedure TCodeGenQBE.EmitStmt(AStmt: TASTStmt);
begin
if AStmt is TForStmt then
if AStmt is TTryFinallyStmt then
EmitTryFinallyStmt(TTryFinallyStmt(AStmt))
else if AStmt is TTryExceptStmt then
EmitTryExceptStmt(TTryExceptStmt(AStmt))
else if AStmt is TRaiseStmt then
EmitRaiseStmt(TRaiseStmt(AStmt))
else if AStmt is TForStmt then
EmitForStmt(TForStmt(AStmt))
else if AStmt is TWhileStmt then
EmitWhileStmt(TWhileStmt(AStmt))
@ -291,6 +300,57 @@ begin
EmitLine('@' + LblEnd);
end;
procedure TCodeGenQBE.EmitTryFinallyStmt(AStmt: TTryFinallyStmt);
var
I: Integer;
begin
{ Simplified model: emit try body then finally body sequentially.
Full stack-unwinding requires RTL support (future phase). }
for I := 0 to AStmt.TryBody.Stmts.Count - 1 do
EmitStmt(TASTStmt(AStmt.TryBody.Stmts[I]));
for I := 0 to AStmt.FinallyBody.Stmts.Count - 1 do
EmitStmt(TASTStmt(AStmt.FinallyBody.Stmts[I]));
end;
procedure TCodeGenQBE.EmitTryExceptStmt(AStmt: TTryExceptStmt);
var
LblExcept: string;
LblEnd: string;
I: Integer;
begin
LblExcept := AllocLabel('except_handler');
LblEnd := AllocLabel('except_end');
{ Emit try body — exception dispatch requires RTL support (future phase) }
for I := 0 to AStmt.TryBody.Stmts.Count - 1 do
EmitStmt(TASTStmt(AStmt.TryBody.Stmts[I]));
{ Skip except handler in normal execution }
EmitLine(Format(' jmp @%s', [LblEnd]));
{ Except handler block — reachable only via raise dispatch (future) }
EmitLine('@' + LblExcept);
for I := 0 to AStmt.ExceptBody.Stmts.Count - 1 do
EmitStmt(TASTStmt(AStmt.ExceptBody.Stmts[I]));
EmitLine(Format(' jmp @%s', [LblEnd]));
EmitLine('@' + LblEnd);
end;
procedure TCodeGenQBE.EmitRaiseStmt(AStmt: TRaiseStmt);
var
ObjTemp: string;
begin
if AStmt.Expr <> nil then
begin
ObjTemp := EmitExpr(AStmt.Expr);
EmitLine(Format(' call $_Raise(l %s)', [ObjTemp]));
end
else
{ Bare re-raise: pass null; RTL retrieves current exception }
EmitLine(' call $_Raise(l 0)');
end;
procedure TCodeGenQBE.EmitForStmt(AStmt: TForStmt);
var
LblCond: string;

View file

@ -36,6 +36,10 @@ type
tkFor,
tkTo,
tkDownto,
tkTry,
tkFinally,
tkExcept,
tkRaise,
tkNil,
{ Identifier }
tkIdent,
@ -116,6 +120,10 @@ begin
else if AUpper = 'FOR' then Result := tkFor
else if AUpper = 'TO' then Result := tkTo
else if AUpper = 'DOWNTO' then Result := tkDownto
else if AUpper = 'TRY' then Result := tkTry
else if AUpper = 'FINALLY' then Result := tkFinally
else if AUpper = 'EXCEPT' then Result := tkExcept
else if AUpper = 'RAISE' then Result := tkRaise
else if AUpper = 'NIL' then Result := tkNil
else
Result := tkIdent; { keyword outside Phase 1 grammar treated as ident }

View file

@ -65,6 +65,8 @@ type
function ParseIfStmt: TIfStmt;
function ParseWhileStmt: TWhileStmt;
function ParseForStmt: TForStmt;
function ParseTryStmt: TASTStmt;
function ParseRaiseStmt: TRaiseStmt;
function ParseCompoundStmt: TCompoundStmt;
function ParseExpr: TASTExpr;
function ParseAddSub: TASTExpr;
@ -516,6 +518,18 @@ begin
Exit;
end;
if Check(tkTry) then
begin
Result := ParseTryStmt;
Exit;
end;
if Check(tkRaise) then
begin
Result := ParseRaiseStmt;
Exit;
end;
if Check(tkBegin) then
begin
Result := ParseCompoundStmt;
@ -672,6 +686,113 @@ begin
end;
end;
function TParser.ParseTryStmt: TASTStmt;
var
TryBody: TCompoundStmt;
FinallyBody: TCompoundStmt;
ExceptBody: TCompoundStmt;
Stmt: TASTStmt;
TFS: TTryFinallyStmt;
TES: TTryExceptStmt;
Line, Col: Integer;
procedure ParseBodyInto(ATarget: TCompoundStmt;
AStop1, AStop2: TTokenKind);
var S: TASTStmt;
begin
while not (Check(AStop1) or Check(AStop2) or
Check(tkEnd) or Check(tkEOF)) do
begin
S := ParseStmt;
if S <> nil then
ATarget.Stmts.Add(S);
if Check(tkSemicolon) then
Advance
else
Break;
end;
end;
begin
Line := FCurrent.Line;
Col := FCurrent.Col;
Expect(tkTry);
TryBody := TCompoundStmt.Create;
TryBody.Line := Line;
TryBody.Col := Col;
try
ParseBodyInto(TryBody, tkFinally, tkExcept);
if Check(tkFinally) then
begin
Advance;
FinallyBody := TCompoundStmt.Create;
try
ParseBodyInto(FinallyBody, tkEnd, tkEnd);
Expect(tkEnd);
TFS := TTryFinallyStmt.Create;
TFS.Line := Line;
TFS.Col := Col;
TFS.TryBody := TryBody;
TFS.FinallyBody := FinallyBody;
TryBody := nil;
FinallyBody := nil;
Result := TFS;
except
FinallyBody.Free;
raise;
end;
end
else if Check(tkExcept) then
begin
Advance;
ExceptBody := TCompoundStmt.Create;
try
ParseBodyInto(ExceptBody, tkEnd, tkEnd);
Expect(tkEnd);
TES := TTryExceptStmt.Create;
TES.Line := Line;
TES.Col := Col;
TES.TryBody := TryBody;
TES.ExceptBody := ExceptBody;
TryBody := nil;
ExceptBody := nil;
Result := TES;
except
ExceptBody.Free;
raise;
end;
end
else
begin
raise EParseError.CreateFmt(
'Expected ''finally'' or ''except'' after try body at line %d col %d',
[FCurrent.Line, FCurrent.Col]);
end;
except
TryBody.Free;
raise;
end;
end;
function TParser.ParseRaiseStmt: TRaiseStmt;
begin
Result := TRaiseStmt.Create;
try
Result.Line := FCurrent.Line;
Result.Col := FCurrent.Col;
Expect(tkRaise);
{ Bare raise has no expression; detect by checking statement terminators }
if not (Check(tkSemicolon) or Check(tkEnd) or Check(tkEOF) or
Check(tkFinally) or Check(tkExcept) or Check(tkElse)) then
Result.Expr := ParseExpr;
except
Result.Free;
raise;
end;
end;
function TParser.ParseCompoundStmt: TCompoundStmt;
var
Stmt: TASTStmt;

View file

@ -46,6 +46,7 @@ type
function AnalyseBinaryExpr(ABin: TBinaryExpr): TTypeDesc;
function AnalyseFieldAccess(AAccess: TFieldAccessExpr): TTypeDesc;
procedure AnalyseCompoundBody(ABody: TCompoundStmt);
function FindMethodDecl(const ATypeName, AMethodName: string): TMethodDecl;
procedure SemanticError(const AMsg: string; ALine, ACol: Integer);
@ -495,6 +496,14 @@ begin
end;
end;
procedure TSemanticAnalyser.AnalyseCompoundBody(ABody: TCompoundStmt);
var
I: Integer;
begin
for I := 0 to ABody.Stmts.Count - 1 do
AnalyseStmt(TASTStmt(ABody.Stmts[I]));
end;
procedure TSemanticAnalyser.AnalyseStmts(ABlock: TBlock);
var
I: Integer;
@ -569,6 +578,37 @@ begin
for I := 0 to CmpS.Stmts.Count - 1 do
AnalyseStmt(TASTStmt(CmpS.Stmts[I]));
end
else if AStmt is TTryFinallyStmt then
begin
with TTryFinallyStmt(AStmt) do
begin
AnalyseCompoundBody(TryBody);
AnalyseCompoundBody(FinallyBody);
end;
end
else if AStmt is TTryExceptStmt then
begin
with TTryExceptStmt(AStmt) do
begin
AnalyseCompoundBody(TryBody);
AnalyseCompoundBody(ExceptBody);
end;
end
else if AStmt is TRaiseStmt then
begin
with TRaiseStmt(AStmt) do
begin
if Expr <> nil then
begin
CondType := AnalyseExpr(Expr);
if CondType.Kind <> tyClass then
SemanticError(
Format('raise expression must be a class instance, got ''%s''',
[CondType.Name]),
AStmt.Line, AStmt.Col);
end;
end;
end
else if AStmt is TFieldAssignment then
AnalyseFieldAssignment(TFieldAssignment(AStmt))
else if AStmt is TAssignment then

View file

@ -23,7 +23,8 @@ uses
cp.test.procs,
cp.test.control,
cp.test.inherit,
cp.test.forloop;
cp.test.forloop,
cp.test.exceptions;
var
Application: TTestRunner;

View file

@ -0,0 +1,475 @@
unit cp.test.exceptions;
{$mode objfpc}{$H+}
{ Tests for try/finally, try/except, and raise statements. }
interface
uses
Classes, SysUtils, fpcunit, testregistry,
uLexer, uParser, uAST, uSymbolTable, uSemantic, uCodeGenQBE;
type
TExceptionTests = 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_Try_Keyword;
procedure TestLexer_Finally_Keyword;
procedure TestLexer_Except_Keyword;
procedure TestLexer_Raise_Keyword;
{ ------------------------------------------------------------------ }
{ Parser — try/finally }
{ ------------------------------------------------------------------ }
procedure TestParse_TryFinally_IsTTryFinallyStmt;
procedure TestParse_TryFinally_TryBodyStmtCount;
procedure TestParse_TryFinally_FinallyBodyStmtCount;
procedure TestParse_TryFinally_MultipleStmtsInTryBody;
procedure TestParse_TryFinally_MultipleStmtsInFinallyBody;
{ ------------------------------------------------------------------ }
{ Parser — try/except }
{ ------------------------------------------------------------------ }
procedure TestParse_TryExcept_IsTTryExceptStmt;
procedure TestParse_TryExcept_TryBodyStmtCount;
procedure TestParse_TryExcept_ExceptBodyStmtCount;
{ ------------------------------------------------------------------ }
{ Parser — raise }
{ ------------------------------------------------------------------ }
procedure TestParse_Raise_IsTRaiseStmt;
procedure TestParse_Raise_HasExpr;
procedure TestParse_Raise_Bare_HasNilExpr;
{ ------------------------------------------------------------------ }
{ Semantic }
{ ------------------------------------------------------------------ }
procedure TestSemantic_TryFinally_OK;
procedure TestSemantic_TryExcept_OK;
procedure TestSemantic_Raise_ClassExpr_OK;
procedure TestSemantic_Raise_NonClass_RaisesError;
procedure TestSemantic_Raise_Bare_OK;
{ ------------------------------------------------------------------ }
{ Codegen — try/finally }
{ ------------------------------------------------------------------ }
procedure TestCodegen_TryFinally_TryBodyInIR;
procedure TestCodegen_TryFinally_FinallyBodyInIR;
procedure TestCodegen_TryFinally_FinallyAfterTry;
{ ------------------------------------------------------------------ }
{ Codegen — try/except }
{ ------------------------------------------------------------------ }
procedure TestCodegen_TryExcept_TryBodyInIR;
procedure TestCodegen_TryExcept_ExceptLabelPresent;
{ ------------------------------------------------------------------ }
{ Codegen — raise }
{ ------------------------------------------------------------------ }
procedure TestCodegen_Raise_CallsRTL;
end;
implementation
{ ------------------------------------------------------------------ }
{ Helpers }
{ ------------------------------------------------------------------ }
function TExceptionTests.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 TExceptionTests.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 TExceptionTests.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 TExceptionTests.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
SrcTryFinally =
'program P;' + LineEnding +
'var X: Integer;' + LineEnding +
'begin' + LineEnding +
' X := 0;' + LineEnding +
' try' + LineEnding +
' X := 1' + LineEnding +
' finally' + LineEnding +
' X := 2' + LineEnding +
' end' + LineEnding +
'end.';
SrcTryFinallyMulti =
'program P;' + LineEnding +
'var X: Integer;' + LineEnding +
'var Y: Integer;' + LineEnding +
'begin' + LineEnding +
' try' + LineEnding +
' X := 1;' + LineEnding +
' Y := 2' + LineEnding +
' finally' + LineEnding +
' X := 0;' + LineEnding +
' Y := 0' + LineEnding +
' end' + LineEnding +
'end.';
SrcTryExcept =
'program P;' + LineEnding +
'var X: Integer;' + LineEnding +
'begin' + LineEnding +
' X := 0;' + LineEnding +
' try' + LineEnding +
' X := 1' + LineEnding +
' except' + LineEnding +
' X := 99' + LineEnding +
' end' + LineEnding +
'end.';
SrcRaise =
'program P;' + LineEnding +
'type' + LineEnding +
' TError = class' + LineEnding +
' Code: Integer;' + LineEnding +
' end;' + LineEnding +
'var E: TError;' + LineEnding +
'begin' + LineEnding +
' E := TError.Create;' + LineEnding +
' raise E' + LineEnding +
'end.';
SrcBareRaise =
'program P;' + LineEnding +
'var X: Integer;' + LineEnding +
'begin' + LineEnding +
' try' + LineEnding +
' X := 1' + LineEnding +
' except' + LineEnding +
' raise' + LineEnding +
' end' + LineEnding +
'end.';
{ ------------------------------------------------------------------ }
{ Lexer tests }
{ ------------------------------------------------------------------ }
procedure TExceptionTests.TestLexer_Try_Keyword;
var L: TLexer; T: TToken;
begin
L := TLexer.Create('try');
try
T := L.Next;
AssertEquals('try token', Ord(tkTry), Ord(T.Kind));
finally L.Free; end;
end;
procedure TExceptionTests.TestLexer_Finally_Keyword;
var L: TLexer; T: TToken;
begin
L := TLexer.Create('finally');
try
T := L.Next;
AssertEquals('finally token', Ord(tkFinally), Ord(T.Kind));
finally L.Free; end;
end;
procedure TExceptionTests.TestLexer_Except_Keyword;
var L: TLexer; T: TToken;
begin
L := TLexer.Create('except');
try
T := L.Next;
AssertEquals('except token', Ord(tkExcept), Ord(T.Kind));
finally L.Free; end;
end;
procedure TExceptionTests.TestLexer_Raise_Keyword;
var L: TLexer; T: TToken;
begin
L := TLexer.Create('raise');
try
T := L.Next;
AssertEquals('raise token', Ord(tkRaise), Ord(T.Kind));
finally L.Free; end;
end;
{ ------------------------------------------------------------------ }
{ Parser — try/finally }
{ ------------------------------------------------------------------ }
procedure TExceptionTests.TestParse_TryFinally_IsTTryFinallyStmt;
var Prog: TProgram;
begin
Prog := ParseSrc(SrcTryFinally);
try
AssertTrue('stmt is TTryFinallyStmt',
Prog.Block.Stmts[1] is TTryFinallyStmt);
finally Prog.Free; end;
end;
procedure TExceptionTests.TestParse_TryFinally_TryBodyStmtCount;
var Prog: TProgram; TS: TTryFinallyStmt;
begin
Prog := ParseSrc(SrcTryFinally);
try
TS := TTryFinallyStmt(Prog.Block.Stmts[1]);
AssertEquals('try body has 1 stmt', 1, TS.TryBody.Stmts.Count);
finally Prog.Free; end;
end;
procedure TExceptionTests.TestParse_TryFinally_FinallyBodyStmtCount;
var Prog: TProgram; TS: TTryFinallyStmt;
begin
Prog := ParseSrc(SrcTryFinally);
try
TS := TTryFinallyStmt(Prog.Block.Stmts[1]);
AssertEquals('finally body has 1 stmt', 1, TS.FinallyBody.Stmts.Count);
finally Prog.Free; end;
end;
procedure TExceptionTests.TestParse_TryFinally_MultipleStmtsInTryBody;
var Prog: TProgram; TS: TTryFinallyStmt;
begin
Prog := ParseSrc(SrcTryFinallyMulti);
try
TS := TTryFinallyStmt(Prog.Block.Stmts[0]);
AssertEquals('multi try body has 2 stmts', 2, TS.TryBody.Stmts.Count);
finally Prog.Free; end;
end;
procedure TExceptionTests.TestParse_TryFinally_MultipleStmtsInFinallyBody;
var Prog: TProgram; TS: TTryFinallyStmt;
begin
Prog := ParseSrc(SrcTryFinallyMulti);
try
TS := TTryFinallyStmt(Prog.Block.Stmts[0]);
AssertEquals('multi finally body has 2 stmts', 2, TS.FinallyBody.Stmts.Count);
finally Prog.Free; end;
end;
{ ------------------------------------------------------------------ }
{ Parser — try/except }
{ ------------------------------------------------------------------ }
procedure TExceptionTests.TestParse_TryExcept_IsTTryExceptStmt;
var Prog: TProgram;
begin
Prog := ParseSrc(SrcTryExcept);
try
AssertTrue('stmt is TTryExceptStmt',
Prog.Block.Stmts[1] is TTryExceptStmt);
finally Prog.Free; end;
end;
procedure TExceptionTests.TestParse_TryExcept_TryBodyStmtCount;
var Prog: TProgram; TS: TTryExceptStmt;
begin
Prog := ParseSrc(SrcTryExcept);
try
TS := TTryExceptStmt(Prog.Block.Stmts[1]);
AssertEquals('try body has 1 stmt', 1, TS.TryBody.Stmts.Count);
finally Prog.Free; end;
end;
procedure TExceptionTests.TestParse_TryExcept_ExceptBodyStmtCount;
var Prog: TProgram; TS: TTryExceptStmt;
begin
Prog := ParseSrc(SrcTryExcept);
try
TS := TTryExceptStmt(Prog.Block.Stmts[1]);
AssertEquals('except body has 1 stmt', 1, TS.ExceptBody.Stmts.Count);
finally Prog.Free; end;
end;
{ ------------------------------------------------------------------ }
{ Parser — raise }
{ ------------------------------------------------------------------ }
procedure TExceptionTests.TestParse_Raise_IsTRaiseStmt;
var Prog: TProgram;
begin
Prog := ParseSrc(SrcRaise);
try
AssertTrue('stmt is TRaiseStmt', Prog.Block.Stmts[1] is TRaiseStmt);
finally Prog.Free; end;
end;
procedure TExceptionTests.TestParse_Raise_HasExpr;
var Prog: TProgram; RS: TRaiseStmt;
begin
Prog := ParseSrc(SrcRaise);
try
RS := TRaiseStmt(Prog.Block.Stmts[1]);
AssertNotNull('raise has non-nil expression', RS.Expr);
finally Prog.Free; end;
end;
procedure TExceptionTests.TestParse_Raise_Bare_HasNilExpr;
var Prog: TProgram; TS: TTryExceptStmt; RS: TRaiseStmt;
begin
Prog := ParseSrc(SrcBareRaise);
try
TS := TTryExceptStmt(Prog.Block.Stmts[0]);
AssertTrue('except body stmt is TRaiseStmt',
TS.ExceptBody.Stmts[0] is TRaiseStmt);
RS := TRaiseStmt(TS.ExceptBody.Stmts[0]);
AssertNull('bare raise has nil expression', RS.Expr);
finally Prog.Free; end;
end;
{ ------------------------------------------------------------------ }
{ Semantic tests }
{ ------------------------------------------------------------------ }
procedure TExceptionTests.TestSemantic_TryFinally_OK;
begin
AnalyseSrc(SrcTryFinally).Free;
end;
procedure TExceptionTests.TestSemantic_TryExcept_OK;
begin
AnalyseSrc(SrcTryExcept).Free;
end;
procedure TExceptionTests.TestSemantic_Raise_ClassExpr_OK;
begin
AnalyseSrc(SrcRaise).Free;
end;
procedure TExceptionTests.TestSemantic_Raise_NonClass_RaisesError;
begin
AnalyseExpectError(
'program P;' + LineEnding +
'var X: Integer;' + LineEnding +
'begin' + LineEnding +
' X := 1;' + LineEnding +
' raise X' + LineEnding +
'end.');
end;
procedure TExceptionTests.TestSemantic_Raise_Bare_OK;
begin
AnalyseSrc(SrcBareRaise).Free;
end;
{ ------------------------------------------------------------------ }
{ Codegen — try/finally }
{ ------------------------------------------------------------------ }
procedure TExceptionTests.TestCodegen_TryFinally_TryBodyInIR;
var IR: string;
begin
IR := GenIR(SrcTryFinally);
{ X := 1 inside try block }
AssertTrue('try body copy 1 in IR', Pos('copy 1', IR) > 0);
end;
procedure TExceptionTests.TestCodegen_TryFinally_FinallyBodyInIR;
var IR: string;
begin
IR := GenIR(SrcTryFinally);
{ X := 2 inside finally block }
AssertTrue('finally body copy 2 in IR', Pos('copy 2', IR) > 0);
end;
procedure TExceptionTests.TestCodegen_TryFinally_FinallyAfterTry;
var IR: string; PosTry, PosFinally: Integer;
begin
IR := GenIR(SrcTryFinally);
{ finally code (copy 2) must appear after try code (copy 1) }
PosTry := Pos('copy 1', IR);
PosFinally := Pos('copy 2', IR);
AssertTrue('try body present', PosTry > 0);
AssertTrue('finally body present', PosFinally > 0);
AssertTrue('finally appears after try in IR', PosFinally > PosTry);
end;
{ ------------------------------------------------------------------ }
{ Codegen — try/except }
{ ------------------------------------------------------------------ }
procedure TExceptionTests.TestCodegen_TryExcept_TryBodyInIR;
var IR: string;
begin
IR := GenIR(SrcTryExcept);
{ X := 1 inside try block }
AssertTrue('try body in IR', Pos('copy 1', IR) > 0);
end;
procedure TExceptionTests.TestCodegen_TryExcept_ExceptLabelPresent;
var IR: string;
begin
IR := GenIR(SrcTryExcept);
{ Except handler block has a label }
AssertTrue('except handler label in IR', Pos('except_handler', IR) > 0);
end;
{ ------------------------------------------------------------------ }
{ Codegen — raise }
{ ------------------------------------------------------------------ }
procedure TExceptionTests.TestCodegen_Raise_CallsRTL;
var IR: string;
begin
IR := GenIR(SrcRaise);
{ raise emits a call to the RTL raise function }
AssertTrue('call $_Raise in IR', Pos('call $_Raise', IR) > 0);
end;
initialization
RegisterTest(TExceptionTests);
end.