整体架构与两条编译路径¶
这篇文档讲什么¶
这篇先讲全局,不急着扎进某个函数细节里。它主要回答三个问题:
- 这个编译器整体分几层
- 数据是怎么从源码流到可执行文件的
- 为什么当前实现会有两条不同的后端路径
编译器不只是“翻译代码”¶
初学者常把编译器理解成一句话:“把高级语言翻译成低级语言。”
这句话没错,但还是太粗了。真写编译器时,没人会傻乎乎把源码一把直接翻到底。中间一定要拆层,因为每一层都在接不同的活:
- 词法层解决“字符怎么切块”
- 语法层解决“这些块怎么组成程序结构”
- 语义层解决“这个结构是否合法”
- 中间层解决“怎样把高层结构变成后端更容易处理的动作”
- 后端层解决“怎样产出可构建的目标代码”
这个项目现在就是按这个思路搭的,所以你看到的不是一坨大脚本,而是一层一层往下传。
当前项目的总体数据流¶
基础 int 子集的主路径是:
源代码
-> Lexer
-> Tokens
-> Parser
-> AST
-> Semantic Analyzer
-> IR Builder
-> Optimizer
-> Portable C Backend
-> clang / cc
-> 可执行文件
如果程序使用了更高级的语言特性,比如:
char- 字符串
- 数组
- 指针
那么路径会变成:
为什么会有两条路径¶
原因其实很朴素:当前这套 IR 主要就是先把基础 int 子集管好。
一旦进入这些能力:
- 非
int基础类型 - 指针层级
- 数组声明和下标
- 字符串字面量
- 取地址与解引用
就会触发 AST 直出后端路径。
这不是设计翻车,更像是故意先划地盘:
- 先把主干链路打通
- 先让复杂特性可用
- 再决定何时把它们统一降到 IR
为什么第一代先输出规范化 C¶
当前后端不直接生成原生汇编,而是生成规范化 C。
这样做的好处很实在:
- 跨平台更容易打通
- 可以借用现成的系统编译器
- 中间结果更可读
- 更适合测试与教学
“规范化 C”不是说它看起来多漂亮,而是说它写得更规矩、更统一,例如:
- 把控制流展开成更显式的形式
- 把临时值单独命名
- 让后端输出尽量少依赖高层语法糖
模块大致怎么分¶
从代码组织上看,当前项目大致分成这些模块:
lexer.py:词法分析parser.py:递归下降语法分析ast_nodes.py:AST 数据结构semantic.py:语义检查symbol_table.py:作用域和符号管理ir.py:IR 数据结构ir_builder.py:AST 到 IR 的 loweringoptimizer.py:保守优化codegen/:后端代码生成toolchain.py:调用系统工具链pipeline.py:把各阶段统一串起来cli.py:命令行入口
为什么 pipeline.py 很重要¶
很多项目会把编译流程散落在 CLI、测试和脚本里,各自拼一遍。
这个项目专门有一个 pipeline.py,作用是把词法、语法、语义、IR、优化统一串起来。这样做的好处是:
- CLI 不需要自己重复管理流程
- 测试也不用重复拼装阶段调用
- 编译链路更容易维护
这事听上去不花哨,但很管用。少一层重复拼装,后面调 CLI、调测试都会顺很多。
当前结构最核心的优点¶
- 每一层职责比较清楚
- 主路径可以逐层观察
- 后端与前端边界明确
- 能在不推倒前端的前提下替换后端
所以这个项目虽然还不是那种“所有特性一锅端”的统一编译器,但骨架已经像回事了,不是随便堆出来的演示脚本。
你接下来应该读什么¶
如果你想继续往下看,推荐顺序是:
先理解前端和中间层,再看优化与后端,会更顺。