编译器如何从类型错误中恢复?


10

我已经阅读了几篇论文,文章和《编译器:原理,技巧和工具(第二版)(第2版)(又称“龙书”)第4章第4.1.4节,它们都讨论了语法编译器错误恢复的主题。但是,在尝试了几种现代编译器之后,我发现它们还可以从语义错误以及语法错误中恢复。

我非常了解编译器从句法相关的错误中恢复的算法和技术,但是我并不完全理解编译器如何从语义错误中恢复。

我目前使用的访问者模式略有变化,以从我的抽象语法树生成代码。考虑我的编译器编译以下表达式:

1 / (2 * (3 + "4"))

编译器将生成以下抽象语法树:

      op(/)
        |
     -------
    /       \ 
 int(1)    op(*)
             |
          -------
         /       \
       int(2)   op(+)
                  |
               -------
              /       \
           int(3)   str(4)

然后,代码生成阶段将使用访问者模式来递归地遍历抽象语法树并执行类型检查。将遍历抽象语法树,直到编译器到达表达式的最内层为止。(3 + "4")。然后,编译器会检查表达式的每一面,并发现它们在语义上不相等。编译器引发类型错误。这就是问题所在。编译器现在应该做什么

为了使编译器能够从此错误中恢复并继续对表达式的外部部分进行类型检查,它必须从评估表达式的最内部部分到表达式的下一个内部部分返回某种类型(intstr)。但是它根本没有返回的类型。由于发生类型错误,因此没有推断出任何类型。

我提出的一种可能的解决方案是,如果确实发生类型错误,则应该引发错误,并且应该将一个特殊值(表示发生类型错误)返回给以前的抽象语法树遍历调用。如果先前的遍历调用遇到此值,则他们知道在抽象语法树中更深处发生类型错误,因此应避免尝试推断类型。尽管此方法确实有效,但效率似乎很低。如果表达式的最内层部分位于抽象语法树的深处,则编译器将不得不进行许多递归调用,而仅是意识到无法完成任何实际工作,而只需从每个返回即可。

我是否使用了上述方法(我对此表示怀疑)。如果是这样,效率不高吗?如果不是,那么编译器从语义错误中恢复时将使用什么方法?


3
可以肯定的是使用了什么,为什么您不认为它足够有效?为了进行类型检查,编译器无论如何都要遍历整个树。语义故障效率更高,因为一旦发现错误,编译器便可以消除分支。
Telastyn

Answers:


8

您提出的想法本质上是正确的。

关键在于AST节点的类型仅计算一次,然后存储。每当再次需要该类型时,它仅检索存储的类型。如果解决方案以错误结尾,则将存储错误类型。


3

一种有趣的方法是使用特殊的错误类型。首次遇到此类错误时,将记录诊断,并将错误类型作为表达式的类型返回。此错误类型具有一些有趣的属性:

  • 对它执行的任何操作都将成功(为了防止一连串的错误消息都由相同的原始故障引起)
  • 对具有错误类型的对象执行的任何操作的结果也具有错误类型
  • 如果错误类型与代码生成息息相关,则代码生成器会发现用法并生成失败的代码(例如,引发异常,中止运行或任何适合您的语言的代码)

通过这种组合,您实际上可以成功地编译包含类型错误的代码,并且只要不实际使用该代码,就不会发生运行时错误。例如,这对于使您可以对不受影响的代码部分运行单元测试很有用。


感谢您的回答,Jules。有趣的是,这是我最终使用的确切方法。好主意也一样,是吗?;-)
Christian Dean

2

如果存在语义错误,则会向用户发出指示该错误的编译错误消息。

一旦完成,由于输入程序有错误,可以中止编译-它不是该语言中的合法程序,因此可以将其拒绝。

但是,这非常严酷,因此有更柔和的选择。中止所有代码生成和输出文件生成,但继续进行操作以查找更多错误。

例如,它可以简单地中止当前表达式树的任何进一步的类型分析,并继续处理后续语句中的表达式。


2

假设您的语言允许添加整数,并允许使用+运算符连接字符串。

由于int + string不允许,评估+会导致报告错误。编译器可能只是返回error类型。或者它可能更聪明,因为int + int -> int并且string + string -> string被允许,它可能返回“错误,可能是int或字符串”。

然后是*运算符,我们假设只int + int允许使用。然后,编译器可以决定+实际上是应该返回int,并为返回的类型*则是int,没有任何错误信息。


我想我会关注您,@ gnasher,但是“”运算符到底是什么意思?那是错字吗?
Christian Dean

@ChristianDean引号中有一个星号,它被解释为Markdown标记而不是被呈现。
JakeRobb '18

我已将答案提交给编辑,一旦我的编辑得到同行评审,它就会解决问题。
JakeRobb '18
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.