本篇为 mini-dog-c 编译器开发系列第一篇,介绍编译器的功能需求和总体设计。
1. 什么是编译器?
编译器是一种将高级语言 翻译成低级语言(如机器码或汇编)的程序。它的核心工作分为三个阶段:
-
词法分析 (Lexical Analysis):将源代码字符串分解成一个个 token
-
语法分析 (Syntax Analysis):将 token 流组织成树形结构 (AST)
-
语义分析与代码生成:遍历 AST,执行相应操作或生成目标代码
2. mini-dog-c 功能需求
2.1 支持的 Token 类型
| Token 类型 | 说明 | 示例 |
|---|---|---|
IDENT |
标识符 | foo, my_var, test123 |
CHAR_LITERAL |
字符字面量 | 'a', 'x' |
BOOL_LITERAL |
布尔字面量 | true, false |
INT_LITERAL |
整数字面量 | 42, 0, 1024 |
DOUBLE_LITERAL |
浮点字面量 | 3.14, 0.5 |
STRING_LITERAL |
字符串字面量 | "hello world" |
ASSIGN |
赋值 = |
|
PLUS |
加法 + |
|
MINUS |
减法 - |
|
STAR |
乘法 * |
|
SLASH |
除法 / |
|
NOT |
逻辑非 ! |
|
EQ |
相等 == |
|
NE |
不等 != |
|
LE |
小于等于 <= |
|
GE |
大于等于 >= |
|
LT |
小于 < |
|
GT |
大于 > |
|
COMMA |
逗号 , |
|
SEMICOLON |
分号 ; |
|
COLON |
冒号 : |
|
LPAREN |
左圆括号 ( |
|
RPAREN |
右圆括号 ) |
|
LBRACE |
左花括号 { |
|
RBRACE |
右花括号 } |
|
LBRACKET |
左方括号 [ |
|
RBRACKET |
右方括号 ] |
|
FN |
关键词 fn |
|
LET |
关键词 let |
|
IF |
关键词 if |
|
ELSE |
关键词 else |
|
RETURN |
关键词 return |
|
EOF |
文件结束 |
2.2 语法规则 (BNF 表示)
program ::= statement*
statement ::= let_decl | fn_decl | return_stmt | expr_stmt
let_decl ::= "let" IDENT "=" expr ";"
fn_decl ::= "fn" IDENT "(" params? ")" block
params ::= IDENT ("," IDENT)*
block ::= "{" statement* "}"
return_stmt ::= "return" expr ";"
if_stmt ::= "if" "(" expr ")" block ("else" block)?
expr_stmt ::= expr ";"
expr ::= assign_expr
assign_expr ::= IDENT "=" assign_expr | logical_or_expr
logical_or_expr ::= logical_and_expr ("||" logical_and_expr)*
logical_and_expr ::= equality_expr ("&&" equality_expr)*
equality_expr ::= relational_expr (("==" | "!=") relational_expr)*
relational_expr ::= additive_expr (("<" | ">" | "<=" | ">=") additive_expr)*
additive_expr ::= multiplicative_expr (("+" | "-") multiplicative_expr)*
multiplicative_expr ::= unary_expr (("*" | "/") unary_expr)*
unary_expr ::= ("!" | "-") unary_expr | primary
primary ::= IDENT | literal | "(" expr ")"
literal ::= INT_LITERAL | DOUBLE_LITERAL | CHAR_LITERAL | BOOL_LITERAL | STRING_LITERAL
2.3 示例程序
fn add(a, b) {
let result = a + b;
return result;
}
fn main() {
let x = 10;
let y = 20;
let sum = add(x, y);
if (sum > 0) {
println("sum is positive:", sum);
} else {
println("sum is not positive");
}
}
2.4 内建函数
| 函数 | 说明 | 示例 |
|---|---|---|
print(...) |
打印若干值(无换行) | print("x =", x) |
println(...) |
打印若干值(末尾换行) | println("Hello!") |
3. 编译器架构设计
3.1 目录结构
mini-dog-c/
├── include/
│ └── common.h # 公共类型和宏定义
├── src/
│ ├── token.h / token.c # Token 定义
│ ├── lexer.h / lexer.c # 词法分析器
│ ├── ast.h / ast.c # 抽象语法树
│ ├── parser.h / parser.c # 语法分析器
│ ├── evaluator.h / evaluator.c # 解释器
│ └── main.c # 主程序
├── tests/
│ ├── test_lexer.c # 词法分析器测试
│ ├── test_parser.c # 语法分析器测试
│ └── test_evaluator.c # 解释器测试
└── Makefile
3.2 各模块职责
| 模块 | 职责 |
|---|---|
token |
定义 Token 类型和 Token 结构 |
lexer |
将源代码字符串转换为 Token 流 |
ast |
定义 AST 节点结构 |
parser |
将 Token 流解析为 AST |
evaluator |
遍历 AST,执行解释执行 |
3.3 数据流
源代码字符串
↓
Lexer: 逐字符扫描,识别 Token
↓
Token 流 (Token[])
↓
Parser: 递归下降解析,构建 AST
↓
AST (节点树)
↓
Evaluator: 深度优先遍历,执行操作
↓
输出结果
4. 设计决策
4.1 为什么选择解释器而非代码生成?
mini-dog-c 是一个教学用的编译器,目标是通过解释执行来展示编译器的工作原理。解释器更简单,无需处理目标平台的寄存器、内存布局等问题。
4.2 为什么选择递归下降解析?
递归下降解析器:
-
简单直观,易于理解和实现
-
对于 mini-dog-c 的简单语法足够用
-
不需要复杂的外部工具(如 yacc/bison)
4.3 错误处理策略
-
词法错误:跳过非法字符,继续扫描
-
语法错误:记录错误位置,尝试继续解析
-
运行时错误:在解释执行时检测
5. 开发计划
-
第一阶段:实现 Token 定义和词法分析器
-
第二阶段:实现 AST 节点定义
-
第三阶段:实现递归下降语法分析器
-
第四阶段:实现解释执行器
-
第五阶段:编写测试用例
6. 小结
本文档定义了 mini-dog-c 编译器的功能需求和总体架构。接下来我们将实现词法分析器。