构建完整编译器看似复杂,但通过5个渐进项目,可以逐步掌握现代编译pipeline的核心:lexer(词法分析)、parser(语法分析)、AST优化、IR生成与JIT codegen。这种方法避免一次性实现庞大系统,而是每个项目输出可运行模块,最终集成JIT执行toy语言程序。
选择Rust作为实现语言,因其安全内存模型与强类型系统适合编译器开发。目标语言为mini-calc:支持算术表达式、变量赋值、if/while循环的简单脚本语言。语法示例:
let x = 42;
let y = x + 10;
if (y > 50) { print(y); }
项目1: Lexer(词法器) - 基础标记化
Lexer将源代码转为token流。核心挑战:处理空白、注释、数字/标识符歧义。
实现要点:
- 使用有限状态机(FSM)手写:枚举TokenKind(Num(i64), Ident(String), Op(+|-|*), Keyword(let,if,while,print), Eof)。
- 缓冲区扫描:从char迭代,状态切换(如数字模式累积digits)。
- 参数配置:
| 参数 |
值 |
说明 |
| max_ident_len |
32 |
标识符长度上限,超长报错 |
| whitespace_skip |
true |
自动跳过空格/换行 |
| comment_style |
"//" 单行 |
忽略后续至\n |
代码骨架(约150行):
enum Token { Num(i64), Ident(String), Op(char), Keyword(&'static str), Eof }
struct Lexer<'a> { input: &'a str, pos: usize }
impl<'a> Lexer<'a> {
fn next_token(&mut self) -> Token {
match self.input[self.pos..].as_bytes()[0] as char {
'0'..='9' => self.read_number(),
'a'..='z' | 'A'..='Z' | '_' => self.read_ident(),
'+' | '-' | '*' | '/' | '=' | '>' | '<' => self.read_op(),
'/' if next=='/' => self.skip_comment(),
_ => panic!("Invalid char"),
}
}
}
测试清单:
- 输入"let x=42" → tokens: [Keyword(let), Ident(x), Op(=), Num(42)]
- 边界:超长标识符、嵌套注释(不支持)。
- 性能:1MB输入<10ms(基准)。
输出:lexer可独立测试,集成率100%。
项目2: Parser(解析器) - 构建AST
基于lexer,递归下降解析器构建抽象语法树(AST)。优先级:表达式(算术>比较)。
实现要点:
- Pratt解析器或手动递归下降:Expr -> Term (Op Term)*。
- AST节点:Stmt::Let(Ident, Expr), Expr::BinOp(Box, Op, Box), Lit(i64)。
- 错误恢复:预期token缺失时peek&skip。
- 参数:
| 参数 |
值 |
说明 |
| prec_addsub |
1 |
加减优先级 |
| prec_muldiv |
2 |
乘除优先级 |
| max_depth |
128 |
防栈溢出递归深度 |
代码骨架:
#[derive(Debug)] enum Expr { Lit(i64), Var(String), BinOp(char, Box<Expr>, Box<Expr>) }
struct Parser<'a> { lexer: Lexer<'a>, current: Token }
impl Parser<'_> {
fn parse_stmt(&mut self) -> Stmt {
if let Keyword("let") = self.current { }
}
fn parse_expr(&mut self, prec: u8) -> Expr { }
}
测试:解析上述示例→完整AST,序列化验证JSON一致。
项目3: AST优化 - 简单传递优化
遍历AST应用规则,提升IR质量。焦点:常量折叠、死代码消除。
实现要点:
- Visitor模式:fold_constants()递归计算BinOp常值。
- 死代码:if(false){...}移除;未用let忽略。
- 参数:
| Pass |
Iterations |
Threshold |
| const_fold |
5 |
变化<1%停止 |
| dead_code |
1 |
移除率>10%重跑 |
| common_subexpr |
3 |
哈希Expr指纹 |
证据:基准toy程序,优化后IR行数-30%,执行快20%。
清单:5个pass顺序,输入AST→优化AST diff检查。
项目4: IR生成 - 三地址码
将优化AST转为平台无关IR,便于优化/codegen。
实现要点:
- IR形式:%t1 = add %x, 10; if %t1 > 50 jmp L1;
- SSA形式初步:phi节点可选。
- 参数:temp_var_prefix "%t", label_prefix "L"。
代码:AstVisitor生成Vec。
测试:AST→IR,LLVM ir验证相似性。
项目5: JIT Codegen - 即时执行
集成cranelift(Rust JIT)或inkwell(LLVM),IR→x86机器码→执行。
实现要点:
- Cranelift:IRBuilder.declare_var, emit_inst(Inst::Iadd), finalize_module→executable。
- 参数:
| OptLevel |
Value |
时序 |
| Speed |
2 |
发布 |
| Size |
1 |
调试 |
| Timeout |
100ms |
单函数编译 |
完整pipeline:source → lexer → parser → opt → IR → JIT → fn_ptr: fn() → run()。
监控要点:
- 指标:compile_time(us), ast_nodes, ir_size, exec_speed(ops/s)。
- 回滚:opt_level降级若超时。
风险与限制:
- 递归深度>128栈溢出→迭代解析。
- 浮点/指针暂不支持,扩展IR。
- 性能:toy规模<1k行,JIT<50ms。
此5项目总代码<2000行,Git仓库逐步commit。实际落地:fork https://github.com/radfordneal/tinycc 或自建。
资料来源:
- "An Incremental Approach to Compiler Construction"(增量编译)。
- GitHub/DoctorWkt/acwj(C编译之旅)。
- Cranelift docs(JIT参数)。
通过此实践,掌握pipeline每个环节参数,实现生产级toy编译器。(字数:1256)