From e83aa797ca6ecae9ba6dac804ffeebf5d18eea0e Mon Sep 17 00:00:00 2001 From: Jonah Jeleniewski Date: Mon, 10 Feb 2025 15:11:10 +1100 Subject: [PATCH] Fix parsing ambiguity around adjacent `>` and `=` tokens --- CHANGELOG.md | 4 +++ .../au/com/integradev/delphi/antlr/Delphi.g | 25 ++++++++++++---- .../integradev/delphi/antlr/GrammarTest.java | 5 ++++ .../grammar/GreaterThanEqualAmbiguity.pas | 30 +++++++++++++++++++ 4 files changed, 59 insertions(+), 5 deletions(-) create mode 100644 delphi-frontend/src/test/resources/au/com/integradev/delphi/grammar/GreaterThanEqualAmbiguity.pas diff --git a/CHANGELOG.md b/CHANGELOG.md index 176f6a712..ea070b554 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed + +- Parsing errors where adjacent `>` and `=` tokens were wrongly interpreted as the `>=` operator. + ## [1.13.0] - 2025-02-05 ### Added diff --git a/delphi-frontend/src/main/antlr3/au/com/integradev/delphi/antlr/Delphi.g b/delphi-frontend/src/main/antlr3/au/com/integradev/delphi/antlr/Delphi.g index 510565e6c..e6a633975 100644 --- a/delphi-frontend/src/main/antlr3/au/com/integradev/delphi/antlr/Delphi.g +++ b/delphi-frontend/src/main/antlr3/au/com/integradev/delphi/antlr/Delphi.g @@ -71,6 +71,8 @@ tokens { TkArgument; TkAnonymousMethod; TkAnonymousMethodHeading; + TkLessThanEqual; + TkGreaterThanEqual; } @header @@ -328,13 +330,19 @@ import org.apache.commons.lang3.StringUtils; return t; } - private Token combineLastNTokens(int count) { + private Token combineLastNTokens(int type, int count) { CommonToken firstToken = (CommonToken) input.LT(-count); CommonToken lastToken = (CommonToken) input.LT(-1); + lastToken.setType(type); lastToken.setStartIndex(firstToken.getStartIndex()); return lastToken; } + private BinaryExpressionNodeImpl createBinaryExpression(Object operator) { + Token token = adaptor.getToken(operator); + return new BinaryExpressionNodeImpl(token); + } + @Override public void reportError(RecognitionException e) { String hdr = this.getErrorHeader(e); @@ -965,12 +973,21 @@ unaryOperator : NOT relationalOperator : '=' | '>' | '<' - | '<=' - | '>=' + | op=lessThanEqualOperator -> {createBinaryExpression(op.getTree())} + | op=greaterThanEqualOperator -> {createBinaryExpression(op.getTree())} | '<>' | IN | IS ; +// We're only doing this for symmetry with greaterThanEqualOperator. (see comment below) +lessThanEqualOperator : '<' '=' -> ^({combineLastNTokens(TkLessThanEqual, 2)}) + ; +// We construct the "greater than equal" tokens while parsing binary expressions to preserve the +// individual '>' and '=' tokens in other cases like `const Foo: TArray=[1, 2, 3];`, which +// we otherwise couldn't parse since the `>=` token would consume the closing angle bracket of the +// generic type arguments and the const assignment operator. +greaterThanEqualOperator : '>' '=' -> ^({combineLastNTokens(TkGreaterThanEqual, 2)}) + ; constExpression : expression | recordExpression | arrayExpression @@ -1372,8 +1389,6 @@ COLON : ':' ; EQUAL : '=' ; NOT_EQUAL : '<>' ; LESS_THAN : '<' ; -LESS_THAN_EQUAL : '<=' ; -GREATER_THAN_EQUAL : '>=' ; GREATER_THAN : '>' ; SQUARE_BRACKET_LEFT : '[' ; SQUARE_BRACKET_RIGHT : ']' ; diff --git a/delphi-frontend/src/test/java/au/com/integradev/delphi/antlr/GrammarTest.java b/delphi-frontend/src/test/java/au/com/integradev/delphi/antlr/GrammarTest.java index 14bb8722f..8836b8e43 100644 --- a/delphi-frontend/src/test/java/au/com/integradev/delphi/antlr/GrammarTest.java +++ b/delphi-frontend/src/test/java/au/com/integradev/delphi/antlr/GrammarTest.java @@ -353,4 +353,9 @@ void testConditionalAsm() { void testSemicolonSeparatedGenericArguments() { assertParsed("SemicolonSeparatedGenericArguments.pas"); } + + @Test + void testGreaterThanEqualAmbiguity() { + assertParsed("GreaterThanEqualAmbiguity.pas"); + } } diff --git a/delphi-frontend/src/test/resources/au/com/integradev/delphi/grammar/GreaterThanEqualAmbiguity.pas b/delphi-frontend/src/test/resources/au/com/integradev/delphi/grammar/GreaterThanEqualAmbiguity.pas new file mode 100644 index 000000000..106bd4564 --- /dev/null +++ b/delphi-frontend/src/test/resources/au/com/integradev/delphi/grammar/GreaterThanEqualAmbiguity.pas @@ -0,0 +1,30 @@ +unit GreaterThanEqualAmbiguity; + +interface + +type + TFoo = class(TObject) + procedure Bar(Baz: IBaz=nil); + end; + +implementation + +procedure TFoo.Bar(Baz: IBaz=nil); +begin + // do nothing +end; + +function Flarp: TArray; +const + Bytes: TArray=[1, 2, 3]; +begin + Result := Bytes; +end; + +function FlimFlam: TArray; +begin + const Bytes: TArray=[1, 2, 3]; + Result := Bytes; +end; + +end. \ No newline at end of file