# 符号表构建

这一阶段对应于代码中的 `Namer`，具体任务是：解析出所有程序中出现的标识符，并为合法的那些标识符创建对应符号，并存储到符号表中。 具体来说，我们需要考虑的标识符有：类名、方法名和（各种）变量名。 在遍历 AST 的过程中，如表达式和语句这些我们暂时不关心的部分会被跳过。 符号表构建完成后，只有在没有发现任何错误的情况下，才进入下一个阶段——类型检查。

与语法分析阶段不同的是，在语义分析阶段，我们并不是发现错误就立即退出，而是在完整一个“大步骤”之后，一次性报告完所发现的所有错误才退出。 输出时，错误信息按照行号（行号相同的比较列号）从小到大排序。

## 解析类定义

注意到在类成员的定义中，Decaf 允许我们引用后面才声明的类，例如：

```
class A {
    class B b; // (*)
}

class B { }
```

注意在 `(*)` 处我们还没有声明类 `B`。为了处理这种情况，我们在构建完整的符号表之前，会预先对所有程序中出现的类进行解析，并暂时忽略掉每个类中的成员。 具体来说，分为以下三个步骤：

1. 检查类名是否有冲突（重复定义）。若有，忽略那些后面定义的。
2. 对每个类，检查其父类（若继承了）是否存在。若不存在，临时修改其为不继承。
3. 将类间的继承关系抽象成一个有向图，其中结点表示类，边 `(u, v)` 表示 `u` 代表的类继承了 `v` 代表的类。检查图中是否有环。若有，则去掉环上的一条边。
4. 为每个类创建类符号并加入到全局作用域中。注意这个时候，类符号的信息是不完善的——因为我们还没有开始访问类中的成员。

这些步骤完成后，进入本阶段的首个检查点 (checkpoint)。若发现任何错误，报告并退出。否则，所有类定义均解析完毕。

## 解析类的成员

接下来，我们依次对每个类的成员进行解析，创建相应符号，并填到该类的符号表中。 为了确保继承能够得到正确的处理，我们在解析一个类之前，会要求它所有的父类都解析完毕。 具体来说，每遇到一个成员变量，我们会先检查符号是否有冲突。若无冲突，再检查其类型，创建一个变量符号并添加到该类的符号表中。 这里对 `TypeLit` 进行类型检查的代码位于 trait `Util` 中。这里主要的工作是，检查用到的类名是否有定义，即是否能在全局作用域中找到。 由于 Decaf 不允许成员变量重载，因此只要它与任何当前已经声明了的标识符冲突，就算是冲突。详见 `visitVarDef` 的实现。

类似地，每遇到一个成员方法，我们仍先检查符号是否有冲突。若无冲突，再依次检查其各参数的声明，创建参数变量符号并添加到参数作用域的符号表中。 签名检查完毕后，打开一个局部作用域，依次访问其中定义的局部变量，检查这些定义是否有冲突，并创建相应变量符号添加至符号表中。 整个方法体遍历结束后，我们最终为该方法创建一个符号并添加到该类的符号表中。 由于 Decaf 允许成员方法重载，因此我们要特判这种情况，尤其要注意检查该方法的类型是否为重载的子类型。 如果是静态方法，那么重载是不被允许的。详见 `visitMethodDef` 的实现。

这些步骤完成后，符号表构建阶段就结束了。若发现任何错误，报告并退出。否则，进入下一个阶段——类型检查。

## 访问者模式

~~很遗憾~~ Scala 框架在这一阶段没有采用访问者模式，你只需要写几个很大的模式匹配就能完成任务。 此外，`Namer` 和 `Typer` 本质上全都在做 AST 转换，因此你会发现有很多签名类似于

```scala
def resolveField(field: Field)(implicit ctx: ScopeContext): Option[Typed.Field]
```

的函数，它们均返回 `TypedTree` 上的某类结点（或者 `None`）。 同时我们将上下文作为隐式参数传入，这样在递归调用时，只要这个参数不变，就无需指定——这能有效减少代码的冗余。
