fix(parser): forward; routine in a program declaration section (issue #130 bug2)
A `forward;`-declared routine in a program's declaration section swallowed the following separate implementation (and everything up to the program's `end.`) as its own body, producing "Expected ';' but got '.'". Root cause: in ParseMethodDecl, when ACanHaveNestedProcs is true (the standalone-proc context used for program/unit blocks), a bare `procedure`/`function` keyword after the signature triggers body parsing so a nested sub-routine is absorbed into the enclosing routine. A forward decl has no body, so the keyword that follows it is the SEPARATE implementation — but the body-trigger fired anyway and consumed it. Fix: track a local IsForward flag (set when the `forward` directive is seen) and suppress the body-parse trigger for it, exactly as IsExternal already does. Mutually-recursive routines declared with forward now parse and run. Tests: a parser test asserts the forward decl and its implementation are two separate ProcDecls (forward has no body, impl has one); a dual-backend e2e test runs mutually-recursive IsEven/IsOdd and checks the computed parity. Verified: FIXPOINT_OK, NATIVE_FIXPOINT_OK, NATIVE_INTERNAL_OK; full suite green on both the QBE-built and native-built runners (3693 tests).
This commit is contained in:
parent
8c68b09245
commit
33db0f699e
|
|
@ -1874,9 +1874,11 @@ var
|
|||
TempParams: TStringList;
|
||||
TempConstraints: TStringList;
|
||||
Constraint: string;
|
||||
IsForward: Boolean; { 'forward;' directive seen — no body follows }
|
||||
begin
|
||||
TempParams := nil;
|
||||
TempConstraints := nil;
|
||||
IsForward := False;
|
||||
Result := TMethodDecl.Create();
|
||||
try
|
||||
Result.Line := FCurrent.Line;
|
||||
|
|
@ -2087,6 +2089,8 @@ begin
|
|||
begin
|
||||
if SameText(FCurrent.Value, 'inline') then
|
||||
Result.IsInline := True
|
||||
else if SameText(FCurrent.Value, 'forward') then
|
||||
IsForward := True
|
||||
else if SameText(FCurrent.Value, 'cdecl') or
|
||||
SameText(FCurrent.Value, 'stdcall') or
|
||||
SameText(FCurrent.Value, 'register') or
|
||||
|
|
@ -2103,8 +2107,13 @@ begin
|
|||
absent for class forward declarations, external declarations, etc.
|
||||
When ACanHaveNestedProcs is True (standalone proc context), a bare
|
||||
'procedure'/'function' keyword triggers body parsing so that a nested
|
||||
sub-procedure declaration is parsed as part of the enclosing routine. }
|
||||
if (not Result.IsExternal) and
|
||||
sub-procedure declaration is parsed as part of the enclosing routine.
|
||||
A 'forward;'-declared routine has NO body here — the following
|
||||
'procedure'/'function' is the SEPARATE implementation, not a nested
|
||||
proc. Without this guard the forward decl swallowed the real
|
||||
implementation (and everything up to the program's 'end.') as its body
|
||||
(issue #130 bug2). }
|
||||
if (not Result.IsExternal) and (not IsForward) and
|
||||
(Check(tkBegin) or Check(tkVar) or Check(tkType) or Check(tkConst) or
|
||||
(ACanHaveNestedProcs and (Check(tkProcedure) or Check(tkFunction)))) then
|
||||
begin
|
||||
|
|
|
|||
|
|
@ -118,6 +118,10 @@ type
|
|||
standard integer type and carries no range checking. }
|
||||
procedure TestRun_Subrange_NamedType;
|
||||
procedure TestRun_Subrange_InRecordAndArray;
|
||||
|
||||
{ forward; in a program decl section (issue #130 bug2): the forward decl
|
||||
used to swallow the following implementation as a nested-proc body. }
|
||||
procedure TestRun_Forward_MutualRecursion;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
|
@ -1220,6 +1224,27 @@ begin
|
|||
AssertRunsOnAll(Src, '200 -7 250' + LE, 0);
|
||||
end;
|
||||
|
||||
procedure TE2EMiscTests.TestRun_Forward_MutualRecursion;
|
||||
const
|
||||
{ Mutually-recursive routines via a forward; declaration in the program's
|
||||
decl section. Asserts the recursion actually computes parity, not just
|
||||
that it compiles. }
|
||||
Src = '''
|
||||
program P;
|
||||
function IsEven(n: Integer): Boolean; forward;
|
||||
function IsOdd(n: Integer): Boolean;
|
||||
begin if n = 0 then Result := False else Result := IsEven(n - 1) end;
|
||||
function IsEven(n: Integer): Boolean;
|
||||
begin if n = 0 then Result := True else Result := IsOdd(n - 1) end;
|
||||
begin
|
||||
WriteLn(IsEven(10), ' ', IsEven(7), ' ', IsOdd(7), ' ', IsOdd(4))
|
||||
end.
|
||||
''';
|
||||
begin
|
||||
if not ToolchainAvailable() then begin Ignore('toolchain unavailable'); Exit; end;
|
||||
AssertRunsOnAll(Src, 'True False True False' + LE, 0);
|
||||
end;
|
||||
|
||||
initialization
|
||||
RegisterTest(TE2EMiscTests);
|
||||
|
||||
|
|
|
|||
|
|
@ -59,6 +59,9 @@ type
|
|||
procedure TestSubrange_NegativeBounds_Parses;
|
||||
procedure TestSubrange_Descending_Error;
|
||||
|
||||
{ forward; in a program declaration section (issue #130 bug2) }
|
||||
procedure TestForward_InProgram_Parses;
|
||||
|
||||
{ Error cases }
|
||||
procedure TestError_MissingProgramKeyword;
|
||||
procedure TestError_MissingDot;
|
||||
|
|
@ -555,6 +558,30 @@ begin
|
|||
end;
|
||||
end;
|
||||
|
||||
procedure TParserTests.TestForward_InProgram_Parses;
|
||||
var
|
||||
Prog: TProgram;
|
||||
begin
|
||||
{ A forward; routine in a program's decl section, then its separate
|
||||
implementation, must parse — the forward decl must NOT swallow the
|
||||
implementation as a nested-proc body (issue #130 bug2). }
|
||||
Prog := ParseSource(
|
||||
'program P; ' +
|
||||
'function F(n: Integer): Boolean; forward; ' +
|
||||
'function F(n: Integer): Boolean; begin Result := n > 0 end; ' +
|
||||
'begin WriteLn(F(5)) end.');
|
||||
try
|
||||
{ Two ProcDecls: the forward (no body) and the implementation (with body). }
|
||||
AssertEquals('two proc decls', 2, Prog.Block.ProcDecls.Count);
|
||||
AssertTrue('forward decl has no body',
|
||||
TMethodDecl(Prog.Block.ProcDecls.Items[0]).Body = nil);
|
||||
AssertTrue('impl decl has a body',
|
||||
TMethodDecl(Prog.Block.ProcDecls.Items[1]).Body <> nil);
|
||||
finally
|
||||
Prog.Free();
|
||||
end;
|
||||
end;
|
||||
|
||||
initialization
|
||||
RegisterTest(TParserTests);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue