Python lrparsing模块;无法解析简单的递归语法

时间:2014-01-04 20:39:40

标签: python lr

expr = Ref('expr')
block = '{' + Repeat(expr) + '}'
expr = block | Token(re='[0-9]')
START = expr

这是使用Python的lrparsing模块的语法。该模块报告语法中没有冲突。

无法使用错误解析字符串{{0}} lrparsing.ParseError: line 1 column 5: Got '}' when expecting __end_of_input__ while trying to match block in state 11

堆叠状态一步一步是:

shift  '{'; ItemSet:5='{'
shift  '{'; ItemSet:5='{' ItemSet:5='{'
shift  /[0-9]/; ItemSet:4=/[0-9]/ ItemSet:5='{' ItemSet:5='{'
reduce '}'; ItemSet:4=/[0-9]/ -- ItemSet:7=expr ItemSet:5='{' ItemSet:5='{'
reduce '}'; ItemSet:7=expr -- ItemSet:9=expr ItemSet:5='{' ItemSet:5='{'
shift  '}'; ItemSet:11='}' ItemSet:9=expr ItemSet:5='{' ItemSet:5='{'

其中afaik意味着它正在转移{{0,然后看到}0缩减为expr,然后再次降低} 没有先移动它,这使bajeezus混淆了我。

这是一个错误,还是我在做一些无限简单和愚蠢的事情?

如果这是我的语法,我将如何重构它以满足我永恒的热情和热情的欲望?如果它是一个错误,有人可以指引我使用语法最相似的python模块,以便 工作吗?

编辑: 重构为:

blocks = Ref('blocks')
block = Ref('block')
expr = Ref('expr')
blocks = blocks + block | THIS*0 # THIS*0 is the idiomatic way of getting the empty string
block = '{' + expr + '}'
expr = blocks | Token(re='[0-9]')
START = expr

允许正确解析。我现在的问题是......为什么?我觉得lrparsing早些时候我会抱怨任何解析问题...... Repeat根本没有按照我预期的方式工作吗?

3 个答案:

答案 0 :(得分:4)

Lrparsing作者在这里。正如塞尔说这是一个错误,并在1.0.8修复。这只发生是因为Serge在Source Forge上报告了它,但是追踪者 - 否则我不会知道。谢谢Serge。

关于它的评论可能是Repeat()中的一个错误,暗示不了解lrparsing的作用。 Lrparsing是一个相当复杂的野兽。它允许您以一种我希望对Python程序员来说很自然的方式输入语法。然后编译成LR(1)解析器生成器可以理解的东西,这是一系列的产品。然后它从这些产生中生成LR(1)解析表。最后,它将您的输入语言和解析表提供给LR(1)解析器以生成解析树。对于它的价值,该错误出现在生成解析表的部分。

如果我看不出每个步骤产生的内容,那么调试这样一系列转换对我来说几乎是不可能的。因此,lrparsing具有repr_xxxx()函数,用于显示每个步骤的输出。第一个转换是解析你的语法。结果显示为repr_grammar()

<G> = START + __end_of_input__
START = expr
block = '{' + expr * () + '}'
expr = block | /[0-9]/

这看起来与问题中提出的原始语法非常相似。下一步是在生产中编译这些规则,这是LR(1)解析器生成器可以理解的。这些由repr_productions()打印:

<G> = START __end_of_input__
START = expr
block = '{' '}'
block = '{' block.Sequence.Repeat '}'
block.Sequence.Repeat = expr
block.Sequence.Repeat = block.Sequence.Repeat expr
expr = block
expr = /[0-9]/

block.Sequence.Repeat是为了处理Repeat()而引入的新的非终结lrparsing。这些作品看起来像是对我原始语法的忠实表达。

Lrparsing摒弃了它引入的非终结者block.Sequence.Repeat。例如,它们不会出现在输出解析树中。这意味着没有必要让lrparsing用户关心它们 - 除了2个案例。这2个案例是错误恢复并试图理解解析引擎的日志输出。前者是一种最不会尝试的复杂技术。但有些人在这里看了后者,试图了解lrparsing正在做什么。除非你能看到LR(1)解析器试图识别的产品,否则日志不会有多大意义。但如果你看过它们,你就会知道Repeat()中没有错误。

您也可以转储生成的LR(1)解析表。如果您 确实 想要了解LR(1)解析器的工作原理,那就是您应该尝试解决的问题。除非您碰巧找到解析一个非常有趣的话题,否则我不推荐它。

答案 1 :(得分:2)

我在浏览此页面时向lrparsing作者报告了您的问题 - 这确实是一个错误,并且已在版本1.0.8中修复。如果它在将来有用,可以找到lrparsing bug跟踪器here

答案 2 :(得分:1)

lrparsing有一个bug;它没有正确考虑递归重复。

您的实际问题可以通过简单的递归来解决,就像您在扩展编辑中所做的那样,虽然杂乱少了。

block = Ref('block')
block = '{' + block + '}' | Token(re='[0-9]')
START = block

另请注意,您的原始语法允许输入{{0{1}}}。 (原因是可重复部分可以再次扩展为简单数字或expr。)考虑到你的第二个语法,你可能根本不想要那样。

我确实使用pyparsing完成了一些工作,但语法却大相径庭。类似的例子:

from pyparsing import Forward, Literal, nums, oneOf, Word

l = lambda c: Literal(c).suppress()
block = Forward()
block << (Word(nums, exact=1) ^ l('{') + block + l('}'))
print(block.parseString("{{0}}"))

输出:

['0']

希望有所帮助。