我正在解析vba代码,其中......
此代码在索引a
输出数组i
的第一维的内容:
Debug.Print a(i, 1)
此代码输出函数a
的结果,给定参数i
和1
:
Debug.Print a(i, 1)
此代码在将DoSomething
评估为值并将其按值传递给过程时调用过程foo
(无论签名是否将其作为“按引用”参数):
DoSomething (foo)
此代码调用过程DoSomething
而不将foo
评估为值,如果签名采用参数“by reference”,则通过引用传递它:
Call DoSomething(foo)
所以我有这个lExpression
解析器规则是有问题的,因为第一个替代(#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
;
我正在尝试解决的具体问题,最好通过堆栈跟踪来描述我从这个代码抛出的解析异常中得出:
Sub Test()
DoSomething (foo), bar
End Sub
我可以看到callStmt()
规则应该按原样启动,但是那个匹配expression
的{{1}}匹配一个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)
;
callStmt
规则,即只接听过程调用(可能会或可能不会以Call
关键字开头):
callStmt :
CALL whiteSpace expression
| expression (whiteSpace argumentList)?
;
答案 0 :(得分:3)
(我已经构建了VB6 / VBA解析器)。
不,你无法区分 parse 时间,正是因为函数调用和数组访问的语法是相同的,使用纯无上下文解析引擎。
简单的做法是简单地将构造解析为array_access_or_function_call,并在通过对树进行后处理解析后,发现实体的声明(例如构建符号表),其范围包含引用(咨询)符号表),并用它来决定。
此问题并非VB独有; C and C++ famously have a similar problem。大多数C / C ++解析器中使用的解决方案是让解析器在解析时收集声明信息作为副作用,然后在遇到要确定的实例语法时查阅该信息。
此方法将解析器更改为上下文相关的解析器。缺点是它使用解析来纠缠(至少部分)符号表构建,并且您的解析引擎可能会或可能不会合作,使得这或多或少地难以实现。
(我认为ANTLR会让你在解析过程中的各个点调用任意代码,可以用来保存声明信息,而ANTLR会让你调用解析时谓词来帮助指导解析器;这些应该足够了]
我更喜欢使用parse-then-resolve方法,因为它更干净,更易于维护。
答案 1 :(得分:3)
你无法通过程序调用告诉数组。即使在解决时,您仍然无法知道,因为变量的子类型可能会在运行时发生变化。
此示例显示接受可选参数的默认成员的影响
Dim var As Variant
Set var = Range("A1:B2")
Debug.Print var(1, 1) 'Access the _Default/Item property with indice arguments
var = var 'Accesses the _Default/Item property without arguments
Debug.Print var(1, 1) 'Array indices
您甚至无法可靠地判断过程的结果是过程调用还是数组索引:
Dim var1 As Variant
Set var1 = New Dictionary
Dim var2 As Variant
Set var2 = New Dictionary
var2.Add 0, "Foo"
var1.Add 0, var2
Debug.Print var1(0)(0) 'Accesses the default/Item of the default/Item
var1 = Array(Array(1))
Debug.Print var1(0)(0) 'Accesses the first index of the first index
您需要将跟随变量名称的带括号的块视为可能属于过程或数组。实际上,考虑访问数组成员甚至可能有用,就好像 默认的 Item 成员一样。这样,数组与具有默认成员的对象没有什么不同,该默认成员需要恰好是索引的参数(并且恰好具有专用的构造函数语法)。