parsec.py递归定义

时间:2018-08-20 22:13:35

标签: python recursion parsec

我喜欢这个库的简单性。可悲的是我还没有弄清楚如何进行递归定义:

考虑这个最小的人为例子:

"""
[0, 0, 4]  -->  [3, 3, 3, 3]  -->  [3, 4, 5, 6]
[0, 1, 3]  -->  [2, 3, 3, 3]  -->  [2, 4, 5, 6]
[0, 2, 2]  -->  [2, 2, 3, 3]  -->  [2, 3, 5, 6]
[0, 3, 1]  -->  [2, 2, 2, 3]  -->  [2, 3, 4, 6]
[0, 4, 0]  -->  [2, 2, 2, 2]  -->  [2, 3, 4, 5]
[1, 0, 3]  -->  [1, 3, 3, 3]  -->  [1, 4, 5, 6]
[1, 1, 2]  -->  [1, 2, 3, 3]  -->  [1, 3, 5, 6]
[1, 2, 1]  -->  [1, 2, 2, 3]  -->  [1, 3, 4, 6]
[1, 3, 0]  -->  [1, 2, 2, 2]  -->  [1, 3, 4, 5]
[2, 0, 2]  -->  [1, 1, 3, 3]  -->  [1, 2, 5, 6]
[2, 1, 1]  -->  [1, 1, 2, 3]  -->  [1, 2, 4, 6]
[2, 2, 0]  -->  [1, 1, 2, 2]  -->  [1, 2, 4, 5]
[3, 0, 1]  -->  [1, 1, 1, 3]  -->  [1, 2, 3, 6]
[3, 1, 0]  -->  [1, 1, 1, 2]  -->  [1, 2, 3, 5]
[4, 0, 0]  -->  [1, 1, 1, 1]  -->  [1, 2, 3, 4]
"""

如预期的那样,对import parsec as psc digit = psc.regex("[0-9]") number = lambda: digit ^ (digit + number()) _ = number().parse("42") 的求值是无限递归的:

  
number()

在此特定示例中,我知道可以通过将第3行更改为

来解决
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 1, in <lambda>
  File "<stdin>", line 1, in <lambda>
  File "<stdin>", line 1, in <lambda>
  [Previous line repeated 995 more times]
RecursionError: maximum recursion depth exceeded

但我不确定一般情况下总是有可能的。

是否可以像

中那样递归解析数字
number = lambda: psc.many1(digit)

2 个答案:

答案 0 :(得分:1)

(更新)免责声明:正如我们在评论中发现的那样,这仅适用于parsec.py 高于 3.3的版本,该版本(截至2018年8月)为PyPI上提供了最新版本,因此现在您必须从GitHub手动安装开发版本,以便该解决方案起作用。

更新2: parsec.py v3.4最终已发布并解决了该问题。


在这种情况下,我发现“手动扩展” parsec提供的基元并编写我们自己的“低级”解析器(即使用{{1} }和text参数,而不是由parsec原语构建的参数),只是为了看看发生了什么。通过从parsec源代码中获取try_choiceindex)的定义,并手动插入表达式 1 的组成部分,我们可以编写一个解析器,我认为它可以做什么你想要的:

^

输出:

import parsec as psc
digit = psc.regex("[0-9]")

def number():
  @psc.Parser
  def number_parser(text, index):
    res = (digit + number())(text, index)
    return res if res.status else digit(text, index)
  return number_parser

_ = number().parse("42423")
print(_)

之所以可行,是因为“解析器返回函数” ('4', ('2', ('4', ('2', '3')))) 仅在实际的解析器中有条件地被调用,而不是在其自身中(无条件地)被调用。

一种更简单的编写方法是根本不使用“解析器返回函数”,而直接直接编写解析器本身:

number()

用法从@psc.Parser def number(text, index): return ((digit + number) ^ digit)(text, index) 更改为number().parse("42423"),但作用相同。

最后,parsec.py中是否有一些功能可以让您在没有显式number.parse("42423")text参数的情况下执行此操作,从而使解析器完全由其他parsec.py原语构建而成?事实证明,parsec.generate非常适合该法案!从其in-source documentation

  

构造解析器的最强大方法是使用generate   装饰。 index从生成器创建一个解析器,该生成器   应该产生解析器。这些解析器被依次应用,并且它们   使用generate协议将结果发送回生成器。的   生成器应返回结果或另一个解析器,即   等同于应用它并返回其结果。

您可以找到示例用法here,从中可以明显看出我们可以编写:

.send()

之所以起作用,是因为@psc.generate def number(): r = yield (digit + number) ^ digit return r 装饰器除了完成所有花哨的生成器魔术之外,还返回一个本身用generate装饰的函数(请参见上面的链接源),因此,得到的结果是经过完全装饰的{ {1}}函数将与第二个解决方案中的函数相同。因此,我们可以以与@parsec.Parser等相同的方式使用它,而不必调用它或进行任何操作,这就是我们在number表达式中所做的事情。

用法再次只是digit,它返回的结果与其他两种解决方案相同。

也许有更好的解决方案,但这就是我能想到的。


1 我不得不将顺序从yield反转为number.parse("42423"),因为第一个仅返回第一个数字,然后认为自己完成了。我希望可以吗?

答案 1 :(得分:0)

如果已经有很多 parser-returning 函数或者只是想保持样式一致,则解决此问题的另一种快速方法是使函数懒惰求值:

import parsec as psc

def lazy(fn):
  @psc.Parser
  def _lazy(text, index):
    return fn()(text, index)
  return _lazy

digit = lambda: psc.regex("[0-9]")
number = lambda: (digit() + lazy(number)) ^ digit()
print(number().parse("423242"))

打印:

('4', ('2', ('3', ('2', ('4', '2')))))