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
  • AST 就是一棵树
  • AST 各结点的定义

Was this helpful?

  1. Java 框架分阶段指导
  2. PA1-A:语法分析器的自动构造

抽象语法树

抽象语法树 (Abstract Syntax Tree, AST) 是贯穿 Decaf 编译器整个前端的重要数据结构。 “抽象语法”是相对于“具体语法”而言的。在具体语法树 (Concrete Syntax Tree) 上,所有语法符号都会被记录。 而在 AST 上,那些我们在编译器后面的阶段不再需要关心的符号(如逗号等)则被忽略。 简而言之,AST 是一种在忽略冗余语法细节的前提下,对源代码结构的忠实表达。

AST 就是一棵树

从名称来看,AST 就是一棵树。在本框架的实现中,我们也贯彻了这一想法,即让每一个 AST 结点都具有树状的结构。 具体来说,对于任何一个 AST 结点,我们需要知道它有几棵子树,以及这些子树分别是什么。 特别地,如果一个 AST 结点不包含任何子树,那么它就是叶子结点。

为达到上述目的,我们在 TreeNode.java 定义了所有 AST 结点的基类:

public abstract class TreeNode implements Iterable<Object> {
    public final Tree.Kind kind;
    public final Pos pos;

    public final String displayName;
    public abstract int treeArity();
    public abstract Object treeElementAt(int index);

    public abstract <C> void accept(Visitor<C> v, C ctx);

    @Override
    public Iterator<Object> iterator() { /* code */ }

    @Override
    public String toString() { /* code */ }
}

我们要求子类必须要有

  • kind:结点类型

  • pos:在源码中的位置

  • displayName:结点名称

并实现接口

  • treeArity:返回这个结点有几个子树

  • treeElementAt:返回下标为 index(从0开始)的那个子树

  • accept:如何接受一个 Visitor 的访问(将会在 PA2 用到,本阶段你只需要照猫画虎地给个实现)

不难发现,这里的 treeArity 和 treeElementAt 包含了树的信息。根据它们,我们容易实现一个子树迭代器 iterator。 在本框架中,这样设计的最大好处在于——大幅简化格式化打印 AST 的代码量!打印策略的实现(见 PrettyTree.java)非常简单:

  1. 打印名称 (displayName)

  2. 增加缩进

  3. 遍历所有子树 (iterator()),依次递归打印它们

  4. 减少缩进

注意到 displayName 和 iterator() 已经是基类的成员,我们无需对不同类型的 AST 结点再分别实现一套打印方法。 借助此,我们还顺便重写了 toString() 方法,让打印结果看起来更加友好,而不是 Java 默认的那个谜一般的地址。

AST 各结点的定义

在 Java 这种 OO 语言中,人们常用不同的类来定义 AST 上不同类型的结点,并用继承把结点间的关系刻画出来。 一般而言,产生式右部对应的 AST 结点是其左部的子类。 本框架中,各 AST 结点定义在 Tree.java 中。

除了标识符 (Id) 和修饰符 (Modifiers) 外,其他 AST 结点都继承自 TreeNode。 特别地,还可能存储一些在语义分析和中间代码生成阶段与该结点关联的信息(“标注”),如对于表达式 Expr:

public abstract static class Expr extends TreeNode {
    // For type check
    public Type type;
    // For tac gen
    public Temp val;
    // ...
}

在进行语义分析时,type 用于记录该表达式的类型。在进行中间代码生成时,val 用于记录该表达式在 TAC 中分配得到的临时变量(伪寄存器)。 在语法分析阶段,你无需关注它们。

Previous词法分析Next文法分析

Last updated 5 years ago

Was this helpful?