我正在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
然后,为选择(|
)运算符生成的代码将在捕获FailedCut
时跳过以下选项。我所说的缺少地方性的意思是,捕捉FailedCut
的选择可能会在调用中深入,因此影响太难以辨别。
不是为序列生成代码,而是尝试通知封闭的 cut 选项,我可以让选择生成的代码提防它们。这将使裁剪的范围非常局部,与Prolog不同,但足以满足我在PEG解析器中的需要,即在看到某个令牌序列后提交选项,因此错误报告指的是该位置在源中,而不是另一个可能有其他选项的位置。
我刚刚想到,如果为规则/谓词生成的代码捕获FailedCut
并将其转换为正常的FailedParse
异常,那么削减将具有正确的范围。
关于@false的问题,这里有一个我想要工作的完整例子:
start = expre ;
expre = named | term ;
named = word ':' ! term;
term = word ;
在该语法中,word
可以通过named
或term
到达,但我希望解析器在看到named
分支后提交:
分支{1}}。
公平地说,到目前为止我已经在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找到。
答案 0 :(得分:3)
在Prolog中使用ISO Prolog的异常处理(catch/3
和throw/1
),可以实现切割:
cut. % Simply succeeds
cut :-
throw(cut). % on backtracking throws an exception
这需要在适当的地方捕获该异常。例如,用户定义的谓词的每个目标(即非终端)现在都可以包含:
catchcut(Goal) :-
catch(Goal,cut,fail).
这不是实现切割的最有效方法,因为!
成功后它不会释放资源,但它可能足以满足您的需要。此外,此方法现在可能会干扰用户定义的catch/3
用法。但是你可能不想在任何情况下模仿整个Prolog语言。
另外,请考虑直接使用Prolog的dcg - 语法。当用另一种语言实现这一点时,有很多细则不明显。
答案 1 :(得分:2)
我的问题最后提出的解决方案有效:
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
然后,无论何时评估选项或可选项,代码都如下所示:
p = self.pos
try:
# code for the expression
except FailedCut:
raise
except FailedParse:
self.goto(p)
修改强>
实际解决方案需要保持“切割堆栈”。 source code是int Bitbucket。
答案 2 :(得分:1)
请阅读。
我建议使用深cut_seen
(与修改解析器的状态一样)以及带有局部变量的保存和恢复状态。这使用线程的堆栈作为" cut_seen
堆栈"。
但是你有另一个解决方案,我很确定你已经很好了。
顺便说一句:好的编译器 - 它与我用pyPEG做的正好相反,所以我可以学到很多东西; - )