> For the complete documentation index, see [llms.txt](https://mashplant.gitbook.io/decaf-doc/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://mashplant.gitbook.io/decaf-doc/pa2/kuang-jia-zhong-bu-fen-shi-xian-de-jie-shi.md).

# 框架中部分实现的解释

虽然现有的语义检查只是针对decaf的基本语法的，但是了解一下其中比较tricky的实现也是有必要的，因为新语法有可能需要修改和拓展原有的语义检查。

## SymbolPass::var\_def

这个函数处理了类的字段，函数参数和局部变量的定义。

```rust
fn var_def(&mut self, v: &'a VarDef<'a>) {
  v.ty.set(self.ty(&v.syn_ty, false));
  if v.ty.get() == Ty::void() { self.issue(v.loc, VoidVar(v.name)) }
  let ok = if let Some((sym, owner)) = self.scopes.lookup(v.name) {
    // 在类中定义变量可能产生两种错误，分别为：OverrideVar(与父类定义的变量重名)和ConflictDeclaration(重名的所有其它情形))
    // 在函数参数和局部变量中定义时，只要发现旧的定义是参数或局部变量中定义的，就一定是一个ConflictDeclaration
    // 这是因为decaf并不允许块级别的variable shadowing(我个人觉得这并不合理，如果允许，写程序会更方便一些，像rust一样的同一个语句块也可以shadow就更方便了)
    // 上面说的这些在新语法中都未必成立，所以你可以酌情考虑利用这段代码，但不保证不需要修改
    match (self.scopes.cur_owner(), owner) {
      (ScopeOwner::Class(c1), ScopeOwner::Class(c2)) if Ref(c1) != Ref(c2) && sym.is_var() =>
        self.issue(sym.loc(), OverrideVar(v.name)),
      (ScopeOwner::Class(_), ScopeOwner::Class(_)) | (_, ScopeOwner::Param(_)) | (_, ScopeOwner::Local(_)) =>
        self.issue(v.loc, ConflictDeclaration { prev: sym.loc(), name: v.name }),
      _ => true,
    }
  } else { true };
  if ok {
    v.owner.set(Some(self.scopes.cur_owner()));
    self.scopes.declare(Symbol::Var(v));
  }
}
```

## TypeCk::cur\_var\_def

当当前语句是一条`VarDef`时，会将`self.cur_var_def`设置成这条语句。目前，它存在的唯一意义是用于帮助拒绝这样的代码：

```java
int a = a;
```

对此我们的编译器会汇报一个"undeclared variable"的错误。

但是，因为全局就只有这一个`self.cur_var_def`，容易发现假如`VarDef`的右端项中也有`VarDef`时，这个方法可能不能奏效。基础的框架中不可能有这样的情形，但是在引入lambda表达式之后，这样的情形是可能存在的，例如：

```java
var f = fun() {
  var a = f; // 应该汇报undeclared variable，但是cur_var_def已经被设置成本条语句，所以会认为f已经被定义
};
```

为了在新语法中解决这个问题，我个人推荐的方案是：

1. 在`VarDef`中额外维护一个`finish_loc`，表示这条语句的结束位置
   * 提示：**按照现在的实现**，在lalr1的lalr(1)模式下，语法动作中你可以访问变量`lexer`，它包含了当前的行号和列号信息
   * 如果你不想维护pa1b的结果的话，可以不维护，只要保证编译通过即可
2. 修改`ScopeStack::lookup_before`，使用上面定义的`finish_loc`来过滤符号
3. 删除`cur_var_def`及其相关操作，因为它已经没用了

并非要求一定要这么实现，如果你觉得有更简单优雅的方法(我也的确觉得这个不太优雅)，可以随意发挥。

## 符号表的输出

java/scala版的实验框架中保存了很多无用的信息，这些信息在我看来就是纯粹为输出服务的。我不想在我的框架中保存这些信息，例如`Scope`就是最简化的定义：

```rust
pub type Scope<'a> = HashMap<&'a str, Symbol<'a>>;
```

信息已经不能再简化了。与之相反，java版的框架中的`LocalScope`中保存了所有嵌套在它里面的作用域：

```java
private List<LocalScope> nested = new ArrayList<>();
```

我读过完整的代码了，这个就是只用来输出的。我不喜欢这样的设计，这不符合"Don't pay for whay you don't use."的原则。在我的设计中，为了在输出时找出所有嵌套在它里面的作用域，我会遍历一遍所有的语句。

大家可以按照自己喜欢的方式实现新特性的符号表的输出，不过因为我目前的设计，我相信实现起来一定是会比其它版本难度要大一些的，希望大家理解。
