【问题标题】:Implementing "cut" in a recursive descent parser在递归下降解析器中实现“cut”
【发布时间】: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 可以通过namedterm 访问,但我希望解析器在看到: 后提交到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


【解决方案1】:

在带有 ISO Prolog 异常处理(catch/3throw/1)的 Prolog 中,可以将剪切实现为:

cut. % Simply succeeds
cut :-
   throw(cut). % on backtracking throws an exception

这需要在适当的地方捕获该异常。例如,用户定义谓词的每个目标(非终端)现在都可以用:

catchcut(Goal) :-
   catch(Goal,cut,fail).

这不是实现 cut 的最有效方法,因为它不会在 ! 成功时释放资源,但它可能足以满足您的目的。此外,此方法现在可能会干扰用户定义的 catch/3 使用。但是您可能无论如何都不想模仿整个 Prolog 语言。

另外,考虑直接使用 Prolog 的-grammars。在用另一种语言实现时,有很多细节并不明显。

【讨论】:

  • 请记住我正在使用 Python!您所描述的是我尝试的那种:将剪切后的序列包装在传/掷结构中,但它不起作用,可能是因为生成的代码太复杂了。一旦通过剪切提交的选项失败,我所需要的只是选择失败。
  • @Apalala:这是一个明显的误解:不是“剪切后的序列”受到异常处理程序的保护,而是剪切发生的非终端。
  • 如果我理解正确,您在考虑 Prolog,并且剪切保护特定子句免于回溯,因为非终结符可能是多个子句的左侧。我想我需要发布一个例子。
  • @Apalala:你需要在你的实现中有一些回溯机制。请注意,在过程语言中使用循环实现回溯通常是微不足道的——只要不因剪切而中断。用我给你看的方法,你甚至可以解决这个问题。效率不高,但可能足以解析。
  • 我明白,但这是一个 PEG 解析器生成器。一旦A(A|B) 中成功,即使前面有失败,也没有尝试B。我需要的是A 的剪切失败以避免尝试B
【解决方案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。

【讨论】:

    【解决方案3】:

    请阅读。

    我建议使用深度 cut_seen(例如修改解析器的状态)以及使用局部变量保存和恢复状态。这将线程的堆栈用作“cut_seen stack”。

    但是你有另一个解决方案,我很确定你已经很好了。

    顺便说一句:不错的编译器——它与我使用 pyPEG 所做的正好相反,所以我可以学到很多东西 ;-)

    【讨论】:

    • 我虽然想让cut_seen 成为一个类属性,但是我会失去所需的局部效果:cut+fail 应该只影响运行时堆栈中最接近的选择,而@987654327 @ 应该最终清除切口。我正在使用与解决方案相关的代码更新问题。
    • Grako 生成解析器模型,如pyPEG,该模型生成自顶向下解析器源代码。模型也可以解析,但是节点自己进行解析,这非常有效,不像在 pyPEG 中,外部解析器使用isinstance() 访问模型以决定如何处理每个节点。见thisGrako 生成功能性自上而下解析器的原因是基于模型的解析引擎太难调试。
    • 我没有生成解析器的原因是我需要在运行时修改语法。这将需要在每次修改时重新生成解析器。不过,调试并不太难,因为只有一个通用的解析函数。无需调试生成的函数。
    • 我明白,但是如果您让语法模型对象解析它们自己的东西,而不是让 parse() 函数根据它们的类型做出决定,那么您将获得很多性能。
    • 你完全正确。但是 pyPEG 有一个特殊的用途:我需要它来引导 Intrinsic,这是一种要求我将 pyPEG 作为解释器而不产生任何东西的语言。这使得 pyPEG 非常灵活和简单,而 Grako 毫无疑问会更快。不过,我喜欢你削减 PEG 语法的想法。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-08-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-05-21
    相关资源
    最近更新 更多