本章导引
在这一章中,我们将介绍 Java 框架与各阶段实验相关的内容。包括各阶段实验的任务、实验框架中已有代码的设计以及实验中需要注意的事项。
分阶段设计
作为系统软件的重要一员,编译器是连接用户与机器的桥梁。编译器的核心功能是: 将某种对人类(相对)友好的高级语言语义保持地(但很多编译器并不能完全做到)转换为机器可执行的低级语言。 随着计算机学科的飞速发展,编译器的功能已远不止于此,代码优化 (optimization)、静态分析 (static analysis/lint),乃至属性验证 (verification) 和程序综合 (synthesis),已逐渐成为现代工业级编译器的标配。
编译器是一个复杂的软件,复杂到即便用上很多现代软件工程的技术手段,也远不是任何普通团队和个人能轻易掌控的。 但是,编译器也不神秘,它不就是个把高级语言翻译成低级语言的程序吗?这里最大的挑战是:高级语言往往很高级,低级语言往往很低级。 如何打破这跨越时代的变迁?分层!
分层这个概念大家并不陌生:计算机的软硬件分层、计算机网络的 OSI 七层模型与 TCP/IP 五层模型、商业网站的开后端分离、甚至深度学习框架也开始搞开后端分离。 既然高级语言和低级语言差异很大,不如设计一系列中间语言,逐个击破,多次翻译,那么就在有限的步数内解决问题。 Decaf 的编译器也是如此,在 Decaf 源码和 Mips 汇编之间,我们设计了抽象语法树、带类型标注的抽象语法树、TAC 这三种中间语言。
在语法分析阶段,Decaf 源码被转换为抽象语法树(PA1)
在语义分析阶段,抽象语法树被转换为带类型标注的抽象语法树(若程序无类型错误)(PA2)
在中间代码生成阶段,带类型标注的抽象语法树被转换为 TAC(PA3)
在汇编码生成阶段,TAC 被最终转换为 Mips 汇编(PA5)
此外,在生成汇编码前,我们还可以对 TAC 进行一系列优化,以生成执行效率更高的 TAC(PA4)。
在 Java 框架中,上述每个阶段都被定义为一个 Phase
:
每个阶段都是一个可能失败的函数 Function<In, Optional<Out>>
,其中 In
为该阶段作为输入的某种语言表示,而 Out
为该阶段作为输出的某种语言表示。 例如,文法分析阶段就是从某个输入流读取 Decaf 源码,输出语法树:
而一次完整的编译过程,无非就是把这些阶段像流水线一样给拼接 (pipe) 起来。由于每个阶段都是一个有可能失败的函数,这种拼接就对应于函数的某种组合:
具体来说,f.then(g)
就是,先做 f
,如果成功,把输出的结果作为输入传给 g
,返回 g
的结果(成功或者失败)。 如果 f
已经失败了(执行 f
的过程中产生错误),那么直接终止整个流程,返回失败。 用 Haskell 的话讲,这 (then
) 不就是个 Kleisli composition 吗?
借助这个 then
函数,我们能方便地定义出各阶段编译器要执行的任务:
代码结构
本框架源码部分的包组织如下:
此外:
src/main/jflex
包含了 JFlex 需要使用的词法描述文件(PA1 共用)src/main/jacc
包含了 Jacc 需要使用的文法描述文件(PA1-A)src/main/ll1pg
包含了 ll1pg 需要使用的文法描述文件(PA1-B)
项目构建
目前本框架基于 gradle 构建。该工具以 Groovy(默认)或 Kotlin(可配置)作为脚本语言。 由于 Groovy/Kotlin 已经是通用编程语言,因此你可以在脚本中编写任意复杂的业务逻辑。 项目中提供的 gradle.build
已经完成了基本配置。你可以自行添加新的库依赖 (如果不是 maven central 仓库请设置 repositories
并保证该库可公开下载),或者按需求增加自定义的 task。
工程引入了两个插件:
JFlex 插件能在编译时进行词法分析器代码生成
Jacc 插件能在编译时进行文法分析器代码生成
请使用 gradle jar
生成独立的 .jar
包。运行时请加上 --enable-preview
选项以开启 JDK 12 的新特性。
此外,你还可以使用 gradle javadoc
生成 JavaDoc 风格的 API 文档,以便查阅和搜索。
Last updated