我正在解析vba代码,其中......
此代码a
在index处输出数组第一维的内容i
:
Debug.Print a(i, 1)
Run Code Online (Sandbox Code Playgroud)此代码输出a
给定参数的函数结果,i
并且1
:
Debug.Print a(i, 1)
Run Code Online (Sandbox Code Playgroud)此代码DoSomething
在foo
作为值进行求值时调用过程,并将值按值传递给过程(无论签名是否将其作为"by reference"参数):
DoSomething (foo)
Run Code Online (Sandbox Code Playgroud)此代码调用过程DoSomething
而不foo
作为值进行评估,如果签名采用参数"by reference",则通过引用传递它:
Call DoSomething(foo)
Run Code Online (Sandbox Code Playgroud)所以我有这个lExpression
解析器规则是有问题的,因为第一个alternative(#indexExpr
)匹配数组和过程调用:
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
;
Run Code Online (Sandbox Code Playgroud)
我试图在这里解决的具体问题最好用堆栈跟踪来描述我从这个代码抛出的解析异常中得出的结论:
Sub Test()
DoSomething (foo), bar
End Sub
Run Code Online (Sandbox Code Playgroud)
我可以看到callStmt()
规则正在应用,但那时expression
匹配的DoSomething
是匹配一个#lExpr
捕获什么应该是"参数列表",而是被选为数组索引.
我所尝试的一切,从#parenthesizedExpr
提升到更高优先级#lExpr
,再到制定memberExpression
规则并使用它而不是expression
在callStmt
规则中,都失败了(项目构建,但我最终得到1500次失败的测试,因为没有任何解析).
#lExpr
匹配的原因DoSomething (foo)
特别是因为,拥有它是完全合法的indexExpr
- 就好像我需要一些方法来忽略解析中的规则,但只有当我知道callStmt
在血统中有一个规则时.
甚至可以a(i, 1)
从a(i, 1)
(函数调用)消除歧义(数组调用)?
如果是这样......怎么样?
以下是调用expression
规则的lExpression
规则:
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)
;
Run Code Online (Sandbox Code Playgroud)
而callStmt
规则,这意味着只接受过程调用(可能会或可能不会在Call
关键字之前):
callStmt :
CALL whiteSpace expression
| expression (whiteSpace argumentList)?
;
Run Code Online (Sandbox Code Playgroud)
(我已经构建了 VB6/VBA 解析器)。
不,您无法在解析时进行区分,正是因为函数调用和数组访问的语法是相同的,使用纯粹的上下文无关解析引擎。
简单的做法是简单地将构造解析为 array_access_or_function_call ,并在通过后处理树进行解析后消除歧义,发现其范围包含引用的实体的声明(例如构建符号表)(查阅符号表) ),并用它来决定。
这个问题并不是 VB 所独有的。C 和 C++ 也有类似的问题。大多数 C/C++ 解析器使用的解决方案是让解析器在解析时收集声明信息作为副作用,然后在遇到实例语法时参考该信息来做出决定。
这种方法将解析器更改为上下文相关的解析器。缺点是它使(至少部分)符号表构建与解析纠缠在一起,并且您的解析引擎可能会或可能不会合作,这或多或少难以实现。
(我认为 ANTLR 将允许您在解析过程中的各个点调用任意代码,这些代码可用于保存声明信息,并且 ANTLR 将允许您调用解析时谓词来帮助指导解析器;这些应该足够了]。
我更喜欢解析然后解析方法,因为它更干净且更易于维护。