【发布时间】:2013-01-03 18:15:16
【问题描述】:
我正在用 Python 实现一个 PEG 解析器生成器,到目前为止我已经取得了成功,除了“剪切”功能,任何知道 Prolog 的人都必须知道。
这个想法是,在解析了一个剪切 (!) 符号之后,不应在同一级别尝试其他选项。
expre = '(' ! list ')' | atom.
表示看到(后,解析一定成功,否则不尝试第二个选项解析失败。
我正在使用 Python 的(非常有效的)异常系统来强制回溯,因此我尝试使用特殊的 FailedCut 异常来中止封闭选择,但这没有用。
任何有关如何在其他解析器生成器中实现此功能的指针都会有所帮助。
也许我遇到的问题是缺乏地方性。为规则的左侧部分生成的代码类似于:
cut_seen = False
try:
self.token('(')
cut_seen = True
self.call('list')
self.token(')')
except FailedParse as e:
if cut_seen:
raise FailedCut(e)
raise
然后,为选择 (|) 运算符生成的代码如果捕获到 FailedCut,将跳过以下选择。我所说的缺乏局部性的意思是,捕捉FailedCut 的选择可能在调用中很深,因此效果很难辨别。
我可以让为选择生成的代码提防它们,而不是让为序列生成的代码尝试通知封闭的选择cuts。与 Prolog 不同,这将使削减的范围非常本地化,但对于我在 PEG 解析器中想要的东西来说已经足够了,即在看到某个标记序列后提交一个选项,因此错误报告是指该位置在源中,而不是到另一个可能有其他选项的位置。
我突然想到,如果为规则/谓词生成的代码捕获 FailedCut 并将其转换为正常的 FailedParse 异常,那么剪切将具有正确的范围。
关于@false 的问题,这是我想要工作的完整示例:
start = expre ;
expre = named | term ;
named = word ':' ! term;
term = word ;
在该语法中,word 可以通过named 或term 访问,但我希望解析器在看到: 后提交到named 分支。
解决方案
公平地说,到目前为止,我已经在 https://bitbucket.org/apalala/grako/ 上发表了我的作品。
在最终的解决方案中,序列包含在这个上下文管理器中:
@contextmanager
def _sequence(self):
self._push_cut()
try:
yield
except FailedParse as e:
if self._cut():
self.error(e, FailedCut)
else:
raise
finally:
self._pop_cut()
选择函数中的选项包含在以下内容中:
@contextmanager
def _option(self):
p = self._pos
try:
self._push_ast()
try:
yield
ast = self.ast
finally:
self._pop_ast()
self.ast.update(ast)
except FailedCut as e:
self._goto(p)
raise e.nested
except FailedParse:
self._goto(p)
这会强制退出选择而不是返回尝试下一个选项。
切割本身是这样实现的:
def _cut(self):
self._cut_stack[-1] = True
完整的源代码可以在Bitbucket找到。
【问题讨论】:
-
我不知道你是如何使用异常的,因此我的建议可能毫无用处……看看YieldProlog 的剪切实现。您认为与您相关吗?
-
我的解析器生成器以直接的方式使用异常:语句要么解析预期的输入,要么引发异常。我的想法是该方法允许更直接的代码(没有
if-and-and-and-elses),并在可以完成恢复的点上更快地恢复。
标签: parsing prolog backtracking peg prolog-cut