Skip to content

Commit e7079f1

Browse files
authored
Merge pull request #4760 from MDoerner/CureBangNotationParsing
Fix bang notation in the SLL mode for the most common case
2 parents 603de48 + 0119a34 commit e7079f1

File tree

2 files changed

+104
-1
lines changed

2 files changed

+104
-1
lines changed

Rubberduck.Parsing/Grammar/VBAParser.g4

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -579,7 +579,15 @@ subscript : (expression whiteSpace TO whiteSpace)? expression;
579579

580580
unrestrictedIdentifier : identifier | statementKeyword | markerKeyword;
581581
legalLabelIdentifier : { !(new[]{DOEVENTS,END,CLOSE,ELSE,LOOP,NEXT,RANDOMIZE,REM,RESUME,RETURN,STOP,WEND}).Contains(_input.La(1))}? identifier | markerKeyword;
582-
identifier : typedIdentifier | untypedIdentifier;
582+
//The predicate in the following rule has been introduced to lessen the problem that VBA uses the same characters used as type hints in other syntactical constructs,
583+
//e.g. in the bang notation (see withDictionaryAccessExpr). Generally, it is not legal to have an identifier or opening bracket follow immediately after a type hint.
584+
//The first part of the predicate tries to exclude these two situations. Unfortunately, predicates have to be at the start of a rule. So, an assumption about the number
585+
//of tokens in the identifier is made. All untypedIdentifers not a foreignNames consist of exactly one token and a typedIdentifier is an untyped one followed by a typeHint,
586+
//again a single token. So, in the majority of situations, the third token is the token following the potential type hint.
587+
//For foreignNames, no assumption can be made because they consist of a pair of brackets containing arbitrarily many tokens.
588+
//That is why the second part of the predicate looks at the first character in order to determine whether the identifier is a foreignName.
589+
identifier : {_input.La(3) != IDENTIFIER && _input.La(3) != L_SQUARE_BRACKET || _input.La(1) == L_SQUARE_BRACKET}? typedIdentifier
590+
| untypedIdentifier;
583591
untypedIdentifier : identifierValue;
584592
typedIdentifier : untypedIdentifier typeHint;
585593
identifierValue : IDENTIFIER | keyword | foreignName;

RubberduckTests/Grammar/VBAParserTests.cs

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2953,6 +2953,101 @@ End Sub
29532953
var parseResult = Parse(code);
29542954
}
29552955

2956+
[Category("Parser")]
2957+
[Test]
2958+
public void ParserDoesNotFailDoubleBracketForgeignIdentifierWithTypeHint()
2959+
{
2960+
const string code = @"
2961+
Sub Test()
2962+
Dim x
2963+
x = [[bar]]!
2964+
x = [bar]!
2965+
End Sub
2966+
";
2967+
var parseResult = Parse(code);
2968+
AssertTree(parseResult.Item1, parseResult.Item2, "//typeHint", matches => matches.Count == 2);
2969+
}
2970+
2971+
[Category("Parser")]
2972+
[Test]
2973+
public void ParserDoesNotFailOnBangOperatorFollowedByForeignIdentifier()
2974+
{
2975+
const string code = @"
2976+
Sub Test()
2977+
Dim dict As Scripting.Dictionary
2978+
2979+
Dim x
2980+
x = dict![a]
2981+
End Sub
2982+
";
2983+
var parseResult = Parse(code);
2984+
AssertTree(parseResult.Item1, parseResult.Item2, "//typeHint", matches => matches.Count == 0);
2985+
}
2986+
2987+
[Category("Parser")]
2988+
[Test]
2989+
public void ParserDoesNotFailOnBangOperator()
2990+
{
2991+
const string code = @"
2992+
Sub Test()
2993+
Dim dict As Scripting.Dictionary
2994+
2995+
Dim x
2996+
x = dict!a
2997+
End Sub
2998+
";
2999+
var parseResult = Parse(code);
3000+
AssertTree(parseResult.Item1, parseResult.Item2, "//typeHint", matches => matches.Count == 0);
3001+
}
3002+
3003+
[Category("Parser")]
3004+
[Test]
3005+
[Ignore("This cannot work with the current setup of identifiers bacause the SLL parser confuses the bang for a type hint.")]
3006+
public void ParserDoesNotFailOnBangOperatorOnForeignIdentifier()
3007+
{
3008+
const string code = @"
3009+
Sub Test()
3010+
Dim x
3011+
x = [dict]!a
3012+
End Sub
3013+
";
3014+
var parseResult = Parse(code);
3015+
AssertTree(parseResult.Item1, parseResult.Item2, "//typeHint", matches => matches.Count == 0);
3016+
}
3017+
3018+
[Category("Parser")]
3019+
[Test]
3020+
public void ParserDoesNotFailOnStackedBangOperator()
3021+
{
3022+
const string code = @"
3023+
Sub Test()
3024+
Dim dict As Scripting.Dictionary
3025+
3026+
Dim x
3027+
x = dict!a!b!c
3028+
End Sub
3029+
";
3030+
var parseResult = Parse(code);
3031+
AssertTree(parseResult.Item1, parseResult.Item2, "//typeHint", matches => matches.Count == 0);
3032+
}
3033+
3034+
[Category("Parser")]
3035+
[Test]
3036+
[Ignore("This cannot work with the current setup of identifiers bacause the SLL parser confuses the bang for a type hint.")]
3037+
public void ParserDoesNotFailOnStackedBangOperator_ForeignIdentifier()
3038+
{
3039+
const string code = @"
3040+
Sub Test()
3041+
Dim dict As Scripting.Dictionary
3042+
3043+
Dim x
3044+
x = dict![a]!b!c
3045+
End Sub
3046+
";
3047+
var parseResult = Parse(code);
3048+
AssertTree(parseResult.Item1, parseResult.Item2, "//typeHint", matches => matches.Count == 0);
3049+
}
3050+
29563051
[Category("Parser")]
29573052
[Test]
29583053
public void ParserDoesNotFailOnLineContinuedBangOperator1()

0 commit comments

Comments
 (0)