我正在用 Python 实现一个 PEG 解析器生成器,到目前为止我已经取得了成功,除了“剪切”功能,任何知道 Prolog 的人都必须知道。
这个想法是,在!
解析了 cut ( ) 符号之后,不应在同一级别尝试其他选项。
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
然后,为choice( |
) 运算符生成的代码如果捕获到a 将跳过以下选择FailedCut
。我所说的缺乏局部性的意思是,捕捉到的选择FailedCut
可能在电话中很深,因此产生的效果很难辨别。
我可以让为选择生成的代码提防它们,而不是让为序列生成的代码尝试通知封闭选择cut 。与 Prolog 不同,这将使削减的范围非常本地化,但对于我在 PEG 解析器中想要的东西来说已经足够了,即在看到某个标记序列后提交一个选项,因此错误报告是指该位置在源中,而不是到另一个可能有其他选项的位置。
我突然想到,如果为规则/谓词生成的代码捕获FailedCut
并将其转换为正常FailedParse
异常,那么剪切将具有正确的范围。
关于@false的问题,这是我想要工作的完整示例:
start = expre ;
expre = named | term ;
named = word ':' ! term;
term = word ;
在该语法中,word
可以通过named
or到达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上找到。