decaf-doc
  • Introduction
  • pa1a
    • 实验内容
    • lalr1使用指导
      • 编写lexer
      • impl块的可选属性
      • 产生式和语法动作
      • 解决冲突
      • 一个完整的例子
    • 抽象语法树
    • 框架中部分实现的解释
    • 文件结构
  • pa1b
    • 实验内容
    • lalr1使用指导
    • 错误恢复
    • 文件结构
  • pa2
    • 实验内容
    • 语义分析
    • 符号表
    • 语句的返回类型
    • visitor模式
    • 框架中部分实现的解释
  • pa3
    • 实验内容
    • 中间代码
    • 中间代码中的类型信息
    • 运行时存储布局
    • 面向对象机制
    • tacvm介绍
  • pa4
    • 实验内容
    • 基本块
    • 数据流分析概述
    • 数据流优化概述
    • 公共表达式提取
    • 复写传播
    • 常量传播
    • 死代码消除
  • pa5
    • 实验内容
    • 图着色基本原理
    • 改进干涉图节点
    • 着色算法
    • 预着色节点
    • 干涉图节点合并
    • 调用约定
Powered by GitBook
On this page

Was this helpful?

  1. pa2

visitor模式

Previous语句的返回类型Next框架中部分实现的解释

Last updated 5 years ago

Was this helpful?

在找一个类似于Visitor之类的trait吗?没有的,别想了。

rust中当然也可以有visitor模式这样的东西,比如 中给出的例子,不过我认为把Visitor像这个文章里一样作为一个明确的trait写出来,一是没有必要,二是限制了灵活性。

先说为什么没有必要。对于java或者是c++的面向对象子集那样的面向对象的语言,ast节点往往表示成继承的关系,例如Call继承自Expr之类的。如果不写类似于Visitor的interface或者是抽象类的话,要想判断并处理不同类型的ast节点,也许只能使用动态的类型判断/转换,也就是java中的instanceof+强制类型转换,c++中的dynamic_cast。以前的文档(不确定现在还有没有)称这是"一种比较龌龊但确实可行的方法",其实我个人觉得这并没有什么道理,这并不比visitor模式更不优雅一些,尤其是考虑到类似scala等基于jvm但支pattern matching的语言,本质上pattern matching也是被编译器翻译成instanceof+强制类型转换。

总之,对于rust的enum来说,match是它最自然也最直接的使用方式,不必有任何心理负担,毕竟即使写了个visitor,里面实现的时候不也只能用match吗。

大家会发现代码中有一些非常巨大的函数,例如把所有对于Expr的处理全部放在了一个函数里面。我认为这并不会造成什么困扰,因为各个match的分支之间是独立的,把它们看成不同的函数也可以,但是我并不认为如果把它真的拆分成不同的函数,在可读性上比现在会有任何的优越性。

再说灵活性。一个trait/interface/抽象类都限定了函数的类型,这对我们来说是完全是没有意义的约束,会带来很多麻烦。例如函数的返回值可能必须为空,那么为了表示visitor从这个节点中获取的信息,就必须把信息存在节点里面,访问完后再取出来;例如函数只能接受节点作为输入参数,那么为了传递一些临时的状态,就必须把这个状态作为struct/class的一个成员。

有人可能认为新增了一种ast节点之后就会出现很多编译错误,这是不灵活的表现,而如果用visitor的话只要在trait里加几个默认的空函数即可。对此我的看法是,编译错误本来就不是坏事,它直接就可以提醒你哪些地方需要修改,这并不比默默的编译通过了但是结果不对要差。如果修改某个地方的工作量的确比较大,又想尽快测试已经修改好的部分,那么填上几个unimplemented!()即可。

不写visitor也的确有一些劣势,例如为了判断节点类型,match在每个地方都得出现一次,产生了一些重复的代码。这是个取舍的问题,我个人不觉得这是很大的负担。

https://github.com/rust-unofficial/patterns/blob/master/patterns/visitor.md