我喜欢这个图书馆的简朴。遗憾的是,我还没有弄清楚如何创建递归定义:
考虑这个最小的人为例子:
import parsec as psc
digit = psc.regex("[0-9]")
number = lambda: digit ^ (digit + number())
_ = number().parse("42")
正如所料,
number()
的计算是无限递归的: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
我知道在这个具体的例子中,可以通过将第3行改为
number = lambda: psc.many1(digit)
但我不确定一般情况下都有可能。
是否可以将数字的解析递归为
<number> ::= <digit><number> | <digit>
最佳答案
(更新)免责声明:正如我们在评论中发现的,这只适用于3.3以上的parsec.py版本(截至2018年8月),这是PyPI上可用的最新版本,因此现在您必须从GitHub手动安装开发版本才能使此解决方案工作。
更新2:parsec.py v3.4终于发布并修复了该问题。
在这种情况下,我发现“手动扩展”parsec
提供的原语并编写一个自己的“低级”解析器(即一个接受text
和index
参数的解析器,而不是一个由parsec原语构建的解析器)通常是有帮助的,只是看看发生了什么。通过从parsec源代码中获取try_choice
(^
)的定义并手动插入表达式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'))))
当然,这是因为“parser returning function”
number()
只能在实际的解析器中有条件地调用,而不是(无条件地)在其自身中调用。一种更简单的方法是完全不使用“parser returning function”,而直接编写解析器本身:
@psc.Parser
def number(text, index):
return ((digit + number) ^ digit)(text, index)
用法从
number().parse("42423")
变为number.parse("42423")
,但它的作用是一样的。最后,parsec.py中是否有一些功能允许您在不使用显式
text
和index
参数的情况下执行此操作,从而使解析器完全由其他parsec.py原语构建?结果发现parsec.generate
很适合这个法案!从它的in-source documentation:构造解析器的最强大的方法是使用
装饰工。
generate
从一个应该产生分析器。这些解析器依次应用
结果使用
.send()
协议发送回生成器。这个生成器应返回结果或另一个解析器,即
相当于应用它并返回其结果。
您可以找到示例用法here,从中我们可以清楚地看到:
@psc.generate
def number():
r = yield (digit + number) ^ digit
return r
这是因为除了它所做的所有花哨的生成器魔术之外,
generate
decorator返回一个函数,该函数本身用@parsec.Parser
修饰(参见上面链接的源代码),因此得到的完全修饰的number
函数将与第二个解决方案中的函数具有相同的类型。因此,我们可以以与digit
等相同的方式使用它,而不必调用它或任何东西,这就是我们在yield
表达式中所做的。用法又是
number.parse("42423")
,它返回的结果与其他两个解决方案相同。也许有更好的解决办法,但这是我能想到的。
我不得不把顺序从
digit ^ (digit + number())
颠倒到(digit + number()) ^ digit
,因为第一个只返回第一个数字,然后认为自己完成了。我希望这样可以吗?