Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions Source/DelphiAST.Classes.pas
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ TCompoundSyntaxNode = class(TSyntaxNode)
FEndLine: Integer;
public
function Clone: TSyntaxNode; override;
//counterpart to AssignPositionFrom, for a node rebuilt from a parsed one
procedure AssignEndPositionFrom(const Node: TCompoundSyntaxNode);

property EndCol: Integer read FEndCol write FEndCol;
property EndLine: Integer read FEndLine write FEndLine;
Expand Down Expand Up @@ -629,6 +631,12 @@ procedure TSyntaxNode.AssignPositionFrom(const Node: TSyntaxNode);

{ TCompoundSyntaxNode }

procedure TCompoundSyntaxNode.AssignEndPositionFrom(const Node: TCompoundSyntaxNode);
begin
FEndCol := Node.EndCol;
FEndLine := Node.EndLine;
end;

function TCompoundSyntaxNode.Clone: TSyntaxNode;
begin
Result := inherited;
Expand Down
18 changes: 14 additions & 4 deletions Source/DelphiAST.pas
Original file line number Diff line number Diff line change
Expand Up @@ -1195,9 +1195,11 @@ procedure TPasSyntaxTreeBuilder.CompoundStatement;

procedure TPasSyntaxTreeBuilder.ConstantDeclaration;
begin
FStack.Push(ntConstant);
//compound: a constant's value can span lines, so record where it ends. as TypeDeclaration.
FStack.PushCompoundSyntaxNode(ntConstant);
try
inherited;
SetCurrentCompoundNodesEndPosition;
finally
FStack.Pop;
end;
Expand Down Expand Up @@ -1329,9 +1331,12 @@ procedure TPasSyntaxTreeBuilder.ConstSection;
if Constant.Typ <> ntName then
Continue;

Temp := FStack.Push(ConstList.Typ);
//compound: start from the name, end from the ConstList that measured the value.
Temp := FStack.PushCompoundSyntaxNode(ConstList.Typ);
try
Temp.AssignPositionFrom(Constant);
if ConstList is TCompoundSyntaxNode then
TCompoundSyntaxNode(Temp).AssignEndPositionFrom(TCompoundSyntaxNode(ConstList));

FStack.AddChild(Constant.Clone);
if Assigned(TypeInfo) then
Expand Down Expand Up @@ -3205,9 +3210,11 @@ procedure TPasSyntaxTreeBuilder.VarAbsolute;

procedure TPasSyntaxTreeBuilder.VarDeclaration;
begin
FStack.Push(ntVariables);
//compound: a variable's type or initialiser can span lines, so record where it ends.
FStack.PushCompoundSyntaxNode(ntVariables);
try
inherited;
SetCurrentCompoundNodesEndPosition;
finally
FStack.Pop;
end;
Expand Down Expand Up @@ -3298,9 +3305,12 @@ procedure TPasSyntaxTreeBuilder.RearrangeVarSection(const VarSect: TSyntaxNode);
begin
if Variable.Typ <> ntName then
Continue;
Temp := FStack.Push(ntVariable);
//compound: start from the name, end from the VarList that measured the declaration.
Temp := FStack.PushCompoundSyntaxNode(ntVariable);
try
Temp.AssignPositionFrom(Variable);
if VarList is TCompoundSyntaxNode then
TCompoundSyntaxNode(Temp).AssignEndPositionFrom(TCompoundSyntaxNode(VarList));
FStack.AddChild(Variable.Clone);
if Assigned(TypeInfo) then
FStack.AddChild(TypeInfo.Clone);
Expand Down
10 changes: 10 additions & 0 deletions Source/SimpleParser/SimpleParser.pas
Original file line number Diff line number Diff line change
Expand Up @@ -3648,6 +3648,16 @@ procedure TmwSimplePasPar.OrdinalType;
begin
ConstantExpression;
end;
{ An operator after the identifier means the bound is a constant
expression, not a type name: array[mlab + 1..mlog]. Reading it as a
type name consumes the identifier alone and leaves the caller at the
operator, where it can only report an error. These are the operators
SimpleExpression and Term accept. }
ptAnd, ptDiv, ptMinus, ptMod, ptOr, ptPlus, ptShl, ptShr, ptSlash,
ptStar, ptXor:
begin
ConstantExpression;
end;
else
begin
TypeID;
Expand Down
22 changes: 22 additions & 0 deletions Test/Snippets/arrayboundexpression.pas
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
unit arrayboundexpression;

interface

const
mlab = 4;
mlog = 12;

type
{ An array bound is a constant EXPRESSION, not just a constant or a type name.
OrdinalType decided on one token of lookahead and read `mlab` as a type name,
so the whole unit failed to parse at the `+`. }
TRanges = record
iu: array[mlab + 1..mlog] of Integer;
du: array[mlab * 2..mlog - 1, 0..mlab shl 1] of Byte;
end;

TSetOfExpression = set of mlab + 1..mlog;

implementation

end.
66 changes: 66 additions & 0 deletions Test/UnitTests/DelphiAST.Tests.pas
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,70 @@ procedure TestSourcePositions;
end;
end;

procedure TestConstantEndPosition;
var
Root, ConstsNode, Node, Spanning, Single: TSyntaxNode;
begin
Root := ParseSource('unit Consts;' + sLineBreak + 'interface' + sLineBreak + 'const' +
sLineBreak + ' Spanning = ''first part '' +' + sLineBreak + ' ''second part'';' +
sLineBreak + ' Single = 42;' + sLineBreak + 'implementation' + sLineBreak + 'end.');
try
ConstsNode := FindDescendant(Root, ntConstants);
AssertNotNil(ConstsNode, 'No const section was produced');
Spanning := nil;
Single := nil;
for Node in ConstsNode.ChildNodes do
if Node.Typ = ntConstant then
if Node.Line = 4 then
Spanning := Node
else if Node.Line = 6 then
Single := Node;

AssertNotNil(Spanning, 'No constant starting on line 4');
AssertTrue(Spanning is TCompoundSyntaxNode,
'Constant node must be compound so it can carry an end position');
AssertEquals(5, TCompoundSyntaxNode(Spanning).EndLine,
'A multi-line constant must reach the last line of its value');

AssertNotNil(Single, 'No constant starting on line 6');
AssertEquals(6, TCompoundSyntaxNode(Single).EndLine,
'A single-line constant must end on its own line, not run on to what follows');
finally
Root.Free;
end;
end;

procedure TestVariableEndPosition;
var
Root, Node, Spanning, Single: TSyntaxNode;
begin
Root := ParseSource('unit Vars;' + sLineBreak + 'interface' + sLineBreak + 'var' +
sLineBreak + ' Spanning: array[0..1] of' + sLineBreak + ' Integer;' + sLineBreak +
' Single: Integer;' + sLineBreak + 'implementation' + sLineBreak + 'end.');
try
Spanning := nil;
Single := nil;
for Node in FindDescendant(Root, ntVariables).ChildNodes do
if Node.Typ = ntVariable then
if Node.Line = 4 then
Spanning := Node
else if Node.Line = 6 then
Single := Node;

AssertNotNil(Spanning, 'No variable starting on line 4');
AssertTrue(Spanning is TCompoundSyntaxNode,
'Variable node must be compound so it can carry an end position');
AssertEquals(5, TCompoundSyntaxNode(Spanning).EndLine,
'A variable whose declaration spans lines must reach its last line');

AssertNotNil(Single, 'No variable starting on line 6');
AssertEquals(6, TCompoundSyntaxNode(Single).EndLine,
'A single-line variable must end on its own line, not run on to what follows');
finally
Root.Free;
end;
end;

procedure TestInvalidSyntax;
var
Root: TSyntaxNode;
Expand Down Expand Up @@ -229,6 +293,8 @@ procedure RunAllTests;
RunTest('AST.GenericRecordAndProperty', TestGenericRecordAndProperty);
RunTest('Writer.LiteralsUnicodeAndXmlEscaping', TestLiteralsAndUnicode);
RunTest('AST.SourcePositions', TestSourcePositions);
RunTest('AST.ConstantEndPosition', TestConstantEndPosition);
RunTest('AST.VariableEndPosition', TestVariableEndPosition);
RunTest('Parser.InvalidSyntax', TestInvalidSyntax);
{$IFNDEF FPC}
RunTest('Serialization.BinaryRoundTrip', TestBinarySerializationRoundTrip);
Expand Down