Skip to content

整体架构与两条编译路径

这篇文档讲什么

这篇先讲全局,不急着扎进某个函数细节里。它主要回答三个问题:

  • 这个编译器整体分几层
  • 数据是怎么从源码流到可执行文件的
  • 为什么当前实现会有两条不同的后端路径

编译器不只是“翻译代码”

初学者常把编译器理解成一句话:“把高级语言翻译成低级语言。”

这句话没错,但还是太粗了。真写编译器时,没人会傻乎乎把源码一把直接翻到底。中间一定要拆层,因为每一层都在接不同的活:

  • 词法层解决“字符怎么切块”
  • 语法层解决“这些块怎么组成程序结构”
  • 语义层解决“这个结构是否合法”
  • 中间层解决“怎样把高层结构变成后端更容易处理的动作”
  • 后端层解决“怎样产出可构建的目标代码”

这个项目现在就是按这个思路搭的,所以你看到的不是一坨大脚本,而是一层一层往下传。

当前项目的总体数据流

基础 int 子集的主路径是:

源代码
  -> Lexer
  -> Tokens
  -> Parser
  -> AST
  -> Semantic Analyzer
  -> IR Builder
  -> Optimizer
  -> Portable C Backend
  -> clang / cc
  -> 可执行文件

如果程序使用了更高级的语言特性,比如:

  • char
  • 字符串
  • 数组
  • 指针

那么路径会变成:

源代码
  -> Lexer
  -> Tokens
  -> Parser
  -> AST
  -> Semantic Analyzer
  -> AST Backend
  -> clang / cc
  -> 可执行文件

为什么会有两条路径

原因其实很朴素:当前这套 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 的 lowering
  • optimizer.py:保守优化
  • codegen/:后端代码生成
  • toolchain.py:调用系统工具链
  • pipeline.py:把各阶段统一串起来
  • cli.py:命令行入口

为什么 pipeline.py 很重要

很多项目会把编译流程散落在 CLI、测试和脚本里,各自拼一遍。

这个项目专门有一个 pipeline.py,作用是把词法、语法、语义、IR、优化统一串起来。这样做的好处是:

  • CLI 不需要自己重复管理流程
  • 测试也不用重复拼装阶段调用
  • 编译链路更容易维护

这事听上去不花哨,但很管用。少一层重复拼装,后面调 CLI、调测试都会顺很多。

当前结构最核心的优点

  • 每一层职责比较清楚
  • 主路径可以逐层观察
  • 后端与前端边界明确
  • 能在不推倒前端的前提下替换后端

所以这个项目虽然还不是那种“所有特性一锅端”的统一编译器,但骨架已经像回事了,不是随便堆出来的演示脚本。

你接下来应该读什么

如果你想继续往下看,推荐顺序是:

  1. 词法分析与 Token
  2. 语法分析与 AST
  3. 语义分析与符号表
  4. IR 与 Lowering

先理解前端和中间层,再看优化与后端,会更顺。