[toc]
本项目实现了一个给定文法的一遍扫描编译程序,并同步开源到 github 。项目包括
graph LR;
A(符号表管理)---B1[词法分析程序];
A---B2[语法分析程序];
A---B3[语义分析程序];
A---B4[代码优化程序];
A---B5[目标代码生成程序];
B1---C(错误处理);
B2---C;
B3---C;
B4---C;
B5---C;
在一个学期的时间中,笔者分阶段完成了上述各个部分,并最终完成编译器的构建
graph LR
subgraph frontend
A((S.P.))-->B[词法分析]
C[语法分析]-->|取词|B
B-->C
C-->|语法成分|D[语义分析]
D-->|分析结果|C
end
C-->E((中间代码))
E-->|代码优化程序|E
E-->G[目标代码生成程序]
subgraph backend
G-->H((O.P.))
end
本文及之后各节将以编译器设计架构为主线,详细说明在完成本次课程设计时的设计思路及相关问题的解决方法。本文之后各节以超链接形式附于本文档最后。
编译原理课程设计是一门自主性较强的课设,意味着我们需要自己设计整个项目的架构和接口。一开始的作业内容还比较简单,设计难度并不大。但是进展到中间代码生成之后,设计难度开始急剧增加。这是因为中间代码和符号表的设计不仅需要便于前端程序调用,还要考虑到没有完成的后端代码。因此对于项目整体有一个清晰的认识就显得极为重要。
在项目实现时,出现不合理的设计是一定的。这时我们往往会有两种选择
- 在已有代码的基础上进行封装或优化设计,尽量不修改原有代码
- 对原有代码进行重构,以便从项目整体出发重新设计
本项目虽然只有 6000 行代码,但是各部分之间的依赖关系较强。所以在实际遇到这样的问题时,笔者倾向于重构。这里我们暂不讨论重构的种种风险,重构本身的确可以加深我们对于编译原理的理解、夯实编程基础。本项目在迭代过程中经历过大大小小 10 余次重构,其中最彻底的一次发生在代码优化部分。因为种种原因,笔者当时决定将符号表、词法符号等全局变量全部进行封装,应用单例设计模式。那时的编译器已经接近完成,可想重构难度之大。
为了降低重构的风险,笔者首先梳理了所有需要重构的部分,并找到他们之间的依赖关系。然后从依赖最少的部分出发,依次修改并进行回归测试。当重构完成时,笔者已经阅读了多遍代码,更加熟悉自己的项目。
工欲善其事,必先利其器
除了设计之外,笔者认为最重要的就是熟悉 C/C++ 的编程技巧。而学习一门语言最快的方式就是阅读他人的代码和参考文档,这里推荐 cppreference 作为 API 手册。尽管 C/C++ 是较为古老的编程语言,但并不意味着某个功能一定要使用复杂而原始的方法实现。在 C++11 中已经包括了许多现代语言的特性,例如 lambda 表达式等。熟练使用这些语言特性必然可以提高编程的效率和质量。
另外,工程项目的架构规则也很重要。这里列出笔者认为最重要的几条
- 函数或类的声明与实现分离,除了模版不要在头文件中对函数进行实现
- 对于头文件中的重要接口给出使用注释,包括实现时引入的 feature
- 将不同的函数分成模块保存在不同的文件中,相同文件可以组织在一个目录下
- clean code
这些规则不仅仅可以帮助他人理解我们的项目,更重要的是记录我们自己的设计思路,不至于过一段时间就看不懂自己的代码。
编译器可以算得上第一个由笔者自己设计的中型项目,能够完成到现在这个程度还是很有成就感的。整个项目从设计、实现、测试、重构到最终完成,教会了笔者许多搭建工程项目的思想与方法。积跬步以致千里,相信编译课设会成为笔者成长路上的重要基石。
本项目使用 Cmake 进行自动化编译,支持跨平台。下载时请访问 github 项目地址
git clone [email protected]:LutingWang/Compiler.git
下载完成后,需要手动执行下述脚本进行配置
mkdir build
mkdir judge
mkdir xcode
这三个文件夹会被根目录下的 Makefile 访问,并分别生成项目二进制文件、自动测试 judge 平台提交文件、跨平台 Xcode 支持。如果不需要某个功能可以选择不执行。最后可以通过
make
make run
来启动项目。