用pyparsing写递归解析器

时间:2013-01-19 23:46:04

标签: python parsing pyparsing

我正在尝试从pyparsing中理解Forward()元素。假设我有这个简单的BNF:

identifier  =
  "a..z,$,_"  <  "a..z,$,_,0..9"  >

package_name = 
  identifier 
  /  ( package_name  "." identifier )

我尝试解析一个像java.lang.String之类的简单包。我得到的结果只是java或者永远不会从递归中返回。 我试过这样的话:

from pyparsing import alphas,alphanums, Word, Forward, ZeroOrMore, Group, Literal

identifier=Word(alphas+"$_",alphanums+"$_")
dot=Literal(".")

package_name = Forward()
definition = package_name+dot+identifier
package_name << Group(identifier+ZeroOrMore(definition))

package_name.parseString("java.lang.String")

将打印[['java']]

from pyparsing import alphas,alphanums, Word, Forward, ZeroOrMore, Group, Literal

identifier=Word(alphas+"$_",alphanums+"$_")
dot=Literal(".")

package_name = Forward()
definition = identifier^package_name+dot+identifier
package_name << definition

package_name.parseString("java.lang.String")

将达到递归限制

这个Forward占位符是如何工作的?

1 个答案:

答案 0 :(得分:13)

问题不在于Forward,而在于你的语法,它本身要么过早限制,要么以一种像Pyparsing这样天真的递归下降解析器不可判断的方式递归。

你有这个:

package_name = identifier | (package_name "." identifier )

如果从左到右匹配,这将始终匹配单个标识符,然后停止,而不尝试匹配以下时间段。如果您将订单切换为与identifier last:

匹配
package_name = (package_name "." identifier) | identifier

。 。 。然后它将无限递归,因为为了确定package_name是否匹配,它要做的第一件事是决定package_name是否匹配。这是一个left-recursive语法,像Pyparsing这样的简单递归下降解析器无法处理。 Pyparsing不会向前看,看看比赛将如何影响后续比赛。它只是从左到右尝试比赛。

您可以通过更改语法递归的方式,获得Forward如何工作的简单示例:

identifier  = pyp.Word(pyp.alphas+"$_", pyp.alphanums+"$_")
package_name = pyp.Forward()
package_name << ((identifier + '.' + package_name) | identifier)

>>> package_name.parseString("java.lang.String")
[u'java', u'.', u'lang', u'.', u'String'], {})

此处,递归发生在右侧,而不是左侧,因此Pyparsing可以逐渐匹配。

(你使用ZeroOrMore是一个红色的鲱鱼。如果你想要这样的递归语法,你不想使用ZeroOrMore,因为递归定义已经允许你的子表达式多次匹配。正如我在评论中所建议的那样,无论如何都可以更简单地定义这种语法而无需递归。)