diff --git a/compiler/src/main/pascal/uParser.pas b/compiler/src/main/pascal/uParser.pas index c53c269..1cb81ab 100644 --- a/compiler/src/main/pascal/uParser.pas +++ b/compiler/src/main/pascal/uParser.pas @@ -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 diff --git a/compiler/src/test/pascal/cp.test.e2e.misc.pas b/compiler/src/test/pascal/cp.test.e2e.misc.pas index 999fc37..aff9a35 100644 --- a/compiler/src/test/pascal/cp.test.e2e.misc.pas +++ b/compiler/src/test/pascal/cp.test.e2e.misc.pas @@ -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); diff --git a/compiler/src/test/pascal/cp.test.parser.pas b/compiler/src/test/pascal/cp.test.parser.pas index b85a418..9359283 100644 --- a/compiler/src/test/pascal/cp.test.parser.pas +++ b/compiler/src/test/pascal/cp.test.parser.pas @@ -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);