编译器构造:处理对无序符号的引用

Compiler construction: Handle references to unordered symbols

本文关键字:无序 符号 引用 处理 编译器      更新时间:2023-10-16

我有dragonbook,但它似乎无法处理这个主题。。。

在大多数现代语言中,即使某些变量在代码中的出现是无序的,也可以使用它们。

示例

class Foo {
    void bar() {
        plonk = 42;
    }
    int plonk;
}

变量plonk在函数之后声明并不重要。

问题
是否有任何最佳实践/有用的模式来实现这一点?我脑海中浮现出两种方法:

  1. 解析时,为看不见的符号添加伪符号。当解析声明时,这些伪符号将被它们的真实符号所取代。解析后,我们可以检查是否还有假人,如果有,则输出一个错误。

  2. 解析时不要做任何符号的事情,只创建AST。解析步骤后通过AST并根据节点添加符号。例如,类节点添加子节点的符号,然后对其进行处理。例如,语句块遍历子语句块,并在处理子语句块之前立即添加符号。

我期待方法1。对于"导入其他编译单元"这样的东西来说更容易,也更有用。

编辑:
我在方法1中看到的一个问题是,需要对有序符号进行某种处理。例如,在函数中,在使用本地符号之前不可能使用它。

如果可以,只需在解析过程中构建AST和符号表。然后通过AST将符号与符号表条目关联起来。这基本上是你的第二个策略。

在一般情况下,策略#1的问题是,在看到所有声明之前,您不一定知道同一名称的两个实例绑定到同一符号。例如,考虑一种类似javascript的语言,其中符号的绑定域是一个功能块(IMHO是一个错误,但品味各不相同),但符号在使用前不需要声明。在这种情况下,我们将只考虑命名函数的符号。

伪代码(事实证明是合法的javascript):

function outer() {
  return foo();
  function inner() {
    return foo();
    function foo() {
      return "inner's foo";
    }
  }
  function foo() {
     return "outer's foo";
  }
}

foo的两个用法指的是不同的符号,这是在你达到foo的最后一个定义之前你无法知道的。

策略#2的问题在于,在不了解所使用的符号的情况下,构建AST并不总是可能的。例如,在C中,如果不知道x是一个类型名还是一个可以解引用到函数中的东西,就无法真正解析像(x)(y)这样的表达式。(同样是个错误,IMHO,但我是谁?)。在C++中,您还需要知道给定的符号是否是模板。通常,这被描述为符号的"种类",而不是"类型"。在C++中,解析(x)(y)不需要知道x的"类型"是什么;你只需要知道它是否有。因此,C++允许在声明之前使用某些符号,但如果声明是typedef,则不允许。

抛开病理病例和宏处理器不谈,通常可以在解析过程中定义作用域,并将每个声明附加到一个作用域。通常情况下,作用域以一种相当简单的方式嵌套,因此一旦构建了作用域树,就可以查找给定当前作用域节点的任何符号,只需在树上走一走,直到找到符号。

在某些语言(如python)中,声明是可选的和隐式的;在这种情况下,如果找不到符号,可以在第二次传递中将新定义附加到当前作用域。