Context
I'm parsing vba code, where...
This code outputs the contents of the first dimension of array
a
at indexi
:Debug.Print a(i, 1)
This code outputs the result of function
a
given parametersi
and1
:Debug.Print a(i, 1)
This code calls procedure
DoSomething
while evaluatingfoo
as a value and passing it by value to the procedure (regardless of whether the signature has it as a "by reference" parameter):DoSomething (foo)
This code calls procedure
DoSomething
without evaluatingfoo
as a value, and passing it by reference if the signature takes the parameter "by reference":Call DoSomething(foo)
So I have this lExpression
parser rule that's problematic, because the first alternative (#indexExpr
) matches both the array and the procedure call:
lExpression :
lExpression whiteSpace? LPAREN whiteSpace? argumentList? whiteSpace? RPAREN # indexExpr
| lExpression mandatoryLineContinuation? DOT mandatoryLineContinuation? unrestrictedIdentifier # memberAccessExpr
| lExpression mandatoryLineContinuation? EXCLAMATIONPOINT mandatoryLineContinuation? unrestrictedIdentifier # dictionaryAccessExpr
| ME # instanceExpr
| identifier # simpleNameExpr
| DOT mandatoryLineContinuation? unrestrictedIdentifier # withMemberAccessExpr
| EXCLAMATIONPOINT mandatoryLineContinuation? unrestrictedIdentifier # withDictionaryAccessExpr
;
The problem
The specific issue I'm trying to fix here, is best depicted by the stack trace I'm getting out of the parse exception that's thrown with this code:
Sub Test()
DoSomething (foo), bar
End Sub
I can see the callStmt()
rule kicking in as it should, but then the expression
that's meant to match DoSomething
is matching a #lExpr
that captures what should be the "argument list", but instead gets picked up as an array index.
Everything I've tried, from moving the #parenthesizedExpr
up to a higher priority than #lExpr
, to making a memberExpression
rule and use that instead of expression
in the callStmt
rule, has failed (project builds, but I end up with 1500 failing tests because nothing parses anymore).
The reason #lExpr
matches DoSomething (foo)
is specifically because, well, it's perfectly legal to have an indexExpr
there - it's as if I needed some way to ignore a rule in the parsing, but only when I know that there's a callStmt
in the lineage.
Is it even possible to disambiguate a(i, 1)
(the array call) from a(i, 1)
(the function call)?
If so... how?
Additional context
Here's the expression
rule from which the lExpression
rule is called:
expression :
// Literal Expression has to come before lExpression, otherwise it'll be classified as simple name expression instead.
literalExpression # literalExpr
| lExpression # lExpr
| builtInType # builtInTypeExpr
| LPAREN whiteSpace? expression whiteSpace? RPAREN # parenthesizedExpr
| TYPEOF whiteSpace expression # typeofexpr // To make the grammar SLL, the type-of-is-expression is actually the child of an IS relational op.
| NEW whiteSpace expression # newExpr
| expression whiteSpace? POW whiteSpace? expression # powOp
| MINUS whiteSpace? expression # unaryMinusOp
| expression whiteSpace? (MULT | DIV) whiteSpace? expression # multOp
| expression whiteSpace? INTDIV whiteSpace? expression # intDivOp
| expression whiteSpace? MOD whiteSpace? expression # modOp
| expression whiteSpace? (PLUS | MINUS) whiteSpace? expression # addOp
| expression whiteSpace? AMPERSAND whiteSpace? expression # concatOp
| expression whiteSpace? (EQ | NEQ | LT | GT | LEQ | GEQ | LIKE | IS) whiteSpace? expression # relationalOp
| NOT whiteSpace? expression # logicalNotOp
| expression whiteSpace? AND whiteSpace? expression # logicalAndOp
| expression whiteSpace? OR whiteSpace? expression # logicalOrOp
| expression whiteSpace? XOR whiteSpace? expression # logicalXorOp
| expression whiteSpace? EQV whiteSpace? expression # logicalEqvOp
| expression whiteSpace? IMP whiteSpace? expression # logicalImpOp
| HASH expression # markedFileNumberExpr // Added to support special forms such as Input(file1, #file1)
;
And the callStmt
rule, which means to only pick up procedure calls (which may or may not be preceded by a Call
keyword):
callStmt :
CALL whiteSpace expression
| expression (whiteSpace argumentList)?
;