PyParsing 简单语言表达式
我正在尝试写一个可以解析代码的程序。我已经成功解析了 foo(spam)
和 spam+eggs
,但是 foo(spam+eggs)
这个就不行了(好像是递归下降?我对编译器的术语有点生疏)。
我有以下代码:
from pyparsing_py3 import *
myVal = Word(alphas+nums+'_')
myFunction = myVal + '(' + delimitedList( myVal ) + ')'
myExpr = Forward()
mySubExpr = ( \
myVal \
| (Suppress('(') + Group(myExpr) + Suppress(')')) \
| myFunction \
)
myExpr << Group( mySubExpr + ZeroOrMore( oneOf('+ - / * =') + mySubExpr ) )
# SHOULD return: [blah, [foo, +, bar]]
# but actually returns: [blah]
print(myExpr.parseString('blah(foo+bar)'))
2 个回答
我发现一个好习惯,就是在使用'<<'这个操作符和Forward时,总是把右边的部分用括号括起来。也就是说:
myExpr << mySubExpr + ZeroOrMore( oneOf('+ - / * =') + mySubExpr )
这样写比:
myExpr << ( mySubExpr + ZeroOrMore( oneOf('+ - / * =') + mySubExpr ) )
要好。这是因为我不幸地选择了'<<'作为“插入”操作符,用来把表达式放进Forward里。虽然在这个特定的例子中,括号其实是多余的,但在下面这个例子里:
integer = Word(nums)
myExpr << mySubExpr + ZeroOrMore( oneOf('+ - / * =') + mySubExpr ) | integer
我们就能明白我为什么说这是个不幸的选择。如果我把它简化成“A << B | C”,我们很容易看到操作的优先级导致计算是按照“(A << B) | C”来进行的,因为'<<'的优先级比'|'高。结果是,Forward A 只会把表达式 B 插入进去。而“| C”部分虽然会执行,但结果是“A | C”,这会创建一个 MatchFirst 对象,但因为没有把它赋值给任何变量,所以这个对象会被立刻丢弃。解决这个问题的方法是把语句用括号括起来,变成“A << (B | C)”。在只使用'+'操作的表达式中,实际上不需要括号,因为'+'的优先级比'<<'高。但这只是运气好,后面如果有人添加了一个使用'|'的替代表达式而没有意识到优先级的问题,就会造成麻烦。所以我建议大家都采用“A << (表达式)”这种写法,以避免混淆。
(总有一天我会写出pyparsing 2.0——这将让我打破与现有代码的兼容性——并把这个改成使用'<<='操作符,这样就解决了所有优先级的问题,因为'<<='的优先级比pyparsing使用的其他操作符都低。)
这里有几个问题:delimitedList 需要的是一个用逗号分隔的 myVal 列表,也就是标识符,所以它当然无法匹配 'foo+bar'(这不是一个用逗号分隔的 myVal 列表!)。解决了这个问题后,又发现了另一个问题——myVal 和 myFunction 的开头是一样的,所以它们在 mySubExpr 中的顺序很重要。再解决这个问题,又发现了一个新问题——这里有两个层级的嵌套,而不是一个。这个版本看起来还不错……:
myVal = Word(alphas+nums+'_')
myExpr = Forward()
mySubExpr = (
(Suppress('(') + Group(myExpr) + Suppress(')'))
| myVal + Suppress('(') + Group(delimitedList(myExpr)) + Suppress(')')
| myVal
)
myExpr << mySubExpr + ZeroOrMore( oneOf('+ - / * =') + mySubExpr )
print(myExpr.parseString('blah(foo+bar)'))
它输出了 ['blah', ['foo', '+', 'bar']]
,这是我们想要的结果。我还去掉了一些多余的反斜杠,因为在括号内逻辑行的延续本来就会发生;这些反斜杠并没有什么实际影响,但确实影响了可读性。