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. Rust 框架分阶段指导
  2. PA1-A:语法分析器的自动构造
  3. lalr1使用指导

产生式和语法动作

在impl块内部将产生式和语义动作结合起来,以函数的形式编写代码,一个例子如下:

#[rule(Expr -> Sub Expr)]
#[prec(UMinus)] // 可选
fn expr_neg(s: Token, r: Expr<'p>) -> Expr<'p> {
  mk_expr(s.loc(), Unary { op: UnOp::Neg, r: Box::new(r) }.into())
}

以下几点需要注意:

  1. 这个生命周期'p是在impl处提供的,p是硬编码的,这意味着你把所有的'p都换成'a或者别的什么的,是不能work的。当然,我可以在过程宏中更加精细地提取代码中的这些信息,但是我想了一下,感觉收益明显小于付出,不多费事了

  2. 它虽然看起来是一个函数,但是其实不是,事实上它的函数体被提取出来当成代码段直接嵌在一个大函数中,所以不要在里面写return之类可以改变控制流的语句

  3. lalr1会对参数进行类型检查,保证一个非终结符只能对应于一个类型,保证一个终结符一定对应一个Token类型,但是这检查完全是基于字符串比较的,并不会考虑任何rust层面的语义信息(如类型别名),所以误判可能会给你带来一些困扰。编写类型也可能带来相当程度的代码冗余,但是我认为这是有必要的,这是为了最大程度的保证生成器生成出来的代码可以被rustc编译。如果把生成器看成一个编译器,而把rustc的运行看成它的结果的运行,这就是在尝试把错误在编译期检查出来

关于第三点还可以补充一些。java框架中采用SemValue作为lr分析中value stack的内容,SemValue就是一堆value stack中可能出现的类型组成的一个class,除了依赖于文档(不存在的)或者观察+记忆,使用者没办法知道哪个成员在这个产生式中是有效的,哪个成员是需要写入的,这带来了很大的不便,编译器也不能利用类型信息来检查代码。lalr1中的value stack是所有可能出现的类型的一个enum,在传递给语法动作前,会先取出栈中对应类型的成员(如果类型不对就panic!()),使用者可以很好地利用编译器的检查和ide的提示。我认为这个好处完全足以弥补编写类型带来的麻烦。

此外,对于产生式还可以添加一个额外的配置,#[prec(Term)],这是指定这条产生式的优先级与终结符Term相同。具体的会在"解决冲突"一节描述。

Previousimpl块的可选属性Next解决冲突

Last updated 5 years ago

Was this helpful?