分析器错误 - 自动生成错误处理的模式

时间:2015-11-14 13:17:46

标签: parsing compiler-construction grammar

是否有任何已知方法可以为机器生成的解析器实现良好的错误处理? 是"模式"还是存在这种问题的已知算法?

" good"我的意思是类似于手工制作的递归下降解析器和现代编译器可获得的结果: 解析器不会在第一次出错时停止,可以发出"有意义的"错误而不只是" xyz"行中无法识别的令牌一次一个错误。

理想情况下,这种方法也应该是自动化的,而不是  手工制作。

我不是在寻找一个库,我需要一种方法,可以在不同的平台上使用,理想情况下可以尽可能地与语言无关。

4 个答案:

答案 0 :(得分:5)

我对这个问题有不同的看法,即你不应该将语法错误视为内部编译器错误。任何实用的编译器实际上都是实现三种语言:

  1. 作为指定目标语言的语言 L 。正确的程序是这种语言的成员。
  2. L 组成的语言 M 以及编译器识别的所有错误。 M \ L 的成员收到信息性错误。
  3. 编译器正常终止的语言 Z 。这个集合应该是所有可能的输入字符串的集合,但是如果编译器在某些输入上崩溃,那么它就不会。 Z \ M 的成员接收有关编译器如何失败的通用消息,通常是#x;解析器在x行,char y"处失败。
  4. 如果您在解析器中指定语言 M 而不是语言 L ,则可以使用自动解析器生成器工具。这种方法的问题在于语言设计者总是指定 L 而不是 M 。我无法想到一个案例,就像 M 的标准一样。

    这不仅仅是抽象的废话。最近对C ++的更改很好地说明了这种区别。它曾经是那个

    template< class T > class X;
    template< class T > class Y;
    X<Y<int>> foo; // syntax in M
    

    第三行出错,因为字符&#34;&gt;&gt;&#34;是右移操作员的代币。那条线必须写成

    X<Y<int> > foo; // syntax in L
    

    标准改为不需要额外的空间。原因是所有主要编译器都已编写代码来识别这种情况,以便生成有意义的错误消息。换句话说,他们发现 M 语言已经在各地实施。一旦委员会确定,他们就将 M -syntax转移到 L 的新版本中。

    如果设计人员在使用 L 语言的同时考虑 M 语言,我们会有更好的语言设计。只是为了他们自己的理智,他们会努力减少 M 的规格大小,这对每个人都是好事。唉,世界还不存在。

    结果是你需要设计自己的语言 M 。这是一个难题。无论您是否使用自动化工具,都与此无关。它有所帮助,但它并没有摆脱最耗时的部分。

答案 1 :(得分:4)

自第一个问题以来,人们一直在试图报告和修复语法错误。关于如何做到这一点有很多技术论文。 寻找字符串&#34;语法错误修复&#34;在scholar.google.com上产生了57次点击。

确实有几个问题:

1)如何向读者报告有意义的错误。首先,那里 是解析器检测错误的位置,以及用户实际生成的位置 错误。例如,C程序可能有一个&#39; ++&#39;操作员在一个陌生的地方:

void p {
 x = y ++
     z = 0;
<EOF>

大多数解析器会在&#34; z&#34;遇到,并将其报告为错误的位置。然而,如果错误是使用&#39; ++&#39;当&#39; +&#39;有意,这份报告是错误的。不幸的是,要做到这一点,你需要能够阅读程序员的想法。

您还有报告错误上下文的问题。 您是否将错误报告为表达式[乍一看,似乎是这样]?在声明中?在一条线?在功能体中?在功能声明?您可能想要报告可以围绕错误点的最窄的句法类别。 (请注意,您不能将函数体或声明报告为&#34;围绕&#34;错误点,因为它们也不完整!) 如果错误在++之后真的丢失了分号怎么办?然后错误位置在表达式&#34;中确实不是#34;如果修复需要插入缺少的字符串引用怎么办?宏观延续字符?

所以你必须以某种方式决定实际错误的构成,这会让我们错误修复。

2)错误修复:要使工具以有意义的方式继续,它必须修复错误。 Presuambly这意味着修补输入令牌流以产生合法程序(如果源有多个错误,您可能无法做到)。如果有几个可能的补丁怎么办?很明显,最好的错误报告是&#34; yyyy错了,我怀疑你应该使用xxxx&#34;。一个修补程序应该考虑多大修复:只是触发错误的令牌,跟随它的令牌,它之前的令牌怎么样?

我注意到在手写解析器上执行自动的一般错误修复建议很困难,因为指导此类修复所需的语法在任何地方都没有明确可用。因此,您可以期望自动修复最适用于语法是显式工件的工具。

也可能是错误修复应该考虑到常见错误。如果人们倾向于离开&#39 ;;&#39;关闭语句,并插入一个修复文件,它可能是一个很好的修复。如果他们很少这样做,并且有多个修复(例如,替换&#34; ++&#34; by&#34; +),那么替代修复可能是更好的建议。

3)修复的语义影响。即使您修复了语法错误,修复后的程序也可能不合理。如果您的错误需要插入标识符,应使用什么标识符?

FWIW,我们的DMS软件再造工具包完全由语法驱动自动修复。它的运作假设应该删除错误点处的令牌,或者应该向其插入一些其他单个令牌。这导致失踪&#34;;&#34;和额外的加号;它经常成功地进行法律修复。通常它不是&#34;对&#34;一。至少它允许解析器继续执行其余的源代码。

我认为寻找良好的自动错误修复将持续很长时间。

FWIW,基于Java的Parser Generator的文章语法错误修复报告了Burke博士的博士学位。论文:

M.G。 Burke,1983,LR和LL句法错误诊断和恢复的实用方法,博士论文,纽约大学计算机科学系

非常好。特别是,它通过考虑和修改错误的左上下文以及错误范围来修复错误。看起来可以get it from ACM

答案 2 :(得分:3)

使用传统的YACC / bison生成器,您将获得yyerror / YYERROR框架,由于LALR解析器的无序回溯特性,使用该框架不容易生成非常有用的错误消息。 您甚至可以在那里添加错误恢复规则,因为您可能需要它们来抑制失败规则中的错误错误消息,其中您只想删除解析规则。

使用基于PEG的解析器,您可以使用更好的~{}后缀错误操作块语法。见例如。 peg manual

  rule = e1 e2 e3 ~{ error("e[12] ok; e3 has failed"); }
         | ...

  rule = (e1 e2 e3) ~{ error("one of e[123] has failed"); }
         | ...

您会在错误的实际位置获得出色的错误消息。但是你必须编写PEG规则,这些规则并不容易编写,尤其是。处理运算符优先级时。使用LALR解析器更容易。

使用更简单的recursive descent parser生成器,您可以获得相同的错误报告PEG的优势,但解析速度要慢得多。

请参阅http://lambda-the-ultimate.org/node/4781

上的相同讨论

答案 3 :(得分:2)

这可能不是你想听到的,但你最好还是编写解析器和词法分析器。

这不是一项特别艰巨的任务(特别是与编写语义分析器和代码生成器相比),并且在处理错误时会产生最佳效果。

但是不要相信我,相信Walter Bright是第一个本地C ++编译器的作者和D编程语言的发明者。

他在Dr.Dobbs上发表了一篇关于此问题的文章here。 (第2页上的错误恢复)