The Decaf Book
  • Introduction
  • 语言规范
  • Java 框架分阶段指导
    • 本章导引
    • PA1-A:语法分析器的自动构造
      • 阶段任务
      • 词法分析
      • 抽象语法树
      • 文法分析
    • PA1-B:基于 LL(1) 的语法分析器半自动构造
      • 阶段任务
      • LL(1) 文法分析
      • 错误恢复
    • PA2:语义分析
      • 阶段任务
      • 类型、符号和作用域
      • 访问者模式
      • 符号表构建
      • 类型检查
    • PA3:中间代码生成
      • 阶段任务
      • TAC 程序
      • 面向对象机制
      • 控制流的翻译
    • PA4:中间代码优化
      • 实验内容
      • 基本块
      • 数据流分析概述
      • 数据流优化概述
      • 公共表达式提取
      • 复写传播
      • 常量传播
      • 死代码消除
  • Scala 框架分阶段指导
    • 本章导引
    • PA1:语法分析器的自动构造
      • 阶段任务
      • 词法分析
      • 抽象语法树
      • 文法分析
      • 访问者模式
    • PA2:语义分析
      • 阶段任务
      • 类型、符号和作用域
      • 符号表构建
      • 类型检查
    • PA3:中间代码生成
      • 阶段任务
      • TAC 程序
      • 面向对象机制
      • 控制流的翻译
    • PA3-JVM:JVM 字节码生成
      • 阶段任务
      • JVM 字节码简介
      • 翻译过程
    • PA4:中间代码优化
      • 实验内容
      • 基本块
      • 数据流分析概述
      • 数据流优化概述
      • 公共表达式提取
      • 复写传播
      • 常量传播
      • 死代码消除
  • Rust 框架分阶段指导
    • 本章导引
    • PA1-A:语法分析器的自动构造
      • 实验内容
      • lalr1使用指导
        • 编写lexer
        • impl块的可选属性
        • 产生式和语法动作
        • 解决冲突
        • 一个完整的例子
      • 抽象语法树
      • 框架中部分实现的解释
      • 文件结构
    • PA1-B:基于 LL(1) 的语法分析器半自动构造
      • 实验内容
      • lalr1使用指导
      • 错误恢复
      • 文件结构
    • PA2:语义分析
      • 实验内容
      • 语义分析
      • 符号表
      • visitor模式
    • PA3:中间代码生成
      • 实验内容
      • 中间代码
      • 中间代码中的类型信息
      • 运行时存储布局
      • 面向对象机制
      • tacvm简述
    • PA4:中间代码优化
      • 基本块
      • 数据流分析概述
      • 数据流优化概述
      • 公共表达式提取
      • 复写传播
      • 常量传播
      • 死代码消除
    • PA5:寄存器分配
      • 实验内容
      • 图着色基本原理
      • 着色算法
      • 预着色节点
      • 干涉图节点合并
      • 调用约定
Powered by GitBook
On this page
  • 解析类定义
  • 解析类的成员
  • 访问者模式

Was this helpful?

  1. Java 框架分阶段指导
  2. PA2:语义分析

符号表构建

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

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

解析类定义

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

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

class B { }

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

  1. 检查类名是否有冲突(重复定义)。若有,忽略那些后面定义的。

  2. 对每个类,检查其父类(若继承了)是否存在。若不存在,临时修改其为不继承。

  3. 将类间的继承关系抽象成一个有向图,其中结点表示类,边 (u, v) 表示 u 代表的类继承了 v 代表的类。检查图中是否有环。若有,则去掉环上的一条边。

  4. 为每个类创建类符号并加入到全局作用域中。注意这个时候,类符号的信息是不完善的——因为我们还没有开始访问类中的成员。

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

解析类的成员

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

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

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

访问者模式

请留意 Namer 和 Typer 的主干代码均采用访问者模式来维护。 不熟悉该设计模式的同学请先学习上一节,并查阅 decaf.frontend.tree.Visitor 中的接口。

Previous访问者模式Next类型检查

Last updated 5 years ago

Was this helpful?