我喜欢这个库的简单性。可悲的是我还没有弄清楚如何进行递归定义:
考虑这个最小的人为例子:
"""
[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)
答案 0 :(得分:1)
(更新)免责声明:正如我们在评论中发现的那样,这仅适用于parsec.py 高于 3.3的版本,该版本(截至2018年8月)为PyPI上提供了最新版本,因此现在您必须从GitHub手动安装开发版本,以便该解决方案起作用。
更新2: parsec.py v3.4最终已发布并解决了该问题。
在这种情况下,我发现“手动扩展” parsec
提供的基元并编写我们自己的“低级”解析器(即使用{{1} }和text
参数,而不是由parsec原语构建的参数),只是为了看看发生了什么。通过从parsec源代码中获取try_choice
(index
)的定义,并手动插入表达式 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')))))