3b7bab1e1b
5 阶段编译流水线: 词法分析 → 语法分析(Pratt) → 语义分析(类型推断) → LLVM IR → .exe 模块: - lexer: 手写状态机, 40 种 Token, // 和 /* */ 注释 - parser: Pratt 表达式解析(9 级优先级) + 递归下降语句/函数 - ast: 14 种节点类型 + 工厂函数 - sema: 作用域链符号表 + 类型推断 + 类型检查 - codegen: AST → LLVM-C API, print_i64/f64/bool 内建 - driver: 命令行 + 流水线串联 + 错误报告 - util: Arena bump allocator (8MB) 测试: 65 单元测试(词法41+语法15+语义9) + 5 集成测试 全部通过 语言特性: i64/f64/bool/void, let不可变变量, if/else, while, 递归函数
406 lines
13 KiB
Markdown
406 lines
13 KiB
Markdown
# L Language PRD(产品需求文档)
|
||
|
||
> 版本: v0.1 | 日期: 2026-06-04 | 作者: 刘航宇 (AI 辅助)
|
||
|
||
---
|
||
|
||
## 1. 项目概述
|
||
|
||
### 1.1 一句话描述
|
||
|
||
用 C 语言实现一门静态类型、Rust 风格语法、多范式混合的编译型编程语言 "L Language"。
|
||
|
||
### 1.2 目标
|
||
|
||
| | 短期(v0.1) | 远期 |
|
||
|---|-------------|------|
|
||
| 定位 | 学习编译器全流程 | 真正能用的通用编程语言 |
|
||
| 能力 | 计算器级 — 基本类型、算术、if/while、函数 | 模块、泛型、trait、所有权等 |
|
||
| 标准 | 跑通全流水线就是胜利 | 自举 |
|
||
|
||
### 1.3 非目标(v0.1 不做)
|
||
|
||
- 字符串类型(只有字面量用于 `print`)
|
||
- 数组 / 切片 / 结构体
|
||
- 模块系统和多文件编译
|
||
- 泛型、trait、模式匹配
|
||
- 任何标准库
|
||
- 垃圾回收或自动内存管理
|
||
|
||
---
|
||
|
||
## 2. 语言规范(v0.1)
|
||
|
||
### 2.1 类型系统
|
||
|
||
| 类型 | 关键字 | 占位 | 示例 |
|
||
|------|--------|------|------|
|
||
| 有符号 64 位整数 | `i64` | 64 bit | `42`、`-7` |
|
||
| 64 位浮点数 | `f64` | 64 bit | `3.14`、`-0.5` |
|
||
| 布尔值 | `bool` | 1 bit | `true`、`false` |
|
||
| 无返回值 | `void` | — | 函数不返回值时使用 |
|
||
|
||
类型推断规则:
|
||
- `let` 声明时从初始化表达式推断类型,无需显式标注
|
||
- 函数参数和返回值必须显式标注类型
|
||
- 变量一旦推断出类型就固定(强类型、静态类型)
|
||
|
||
### 2.2 语法(EBNF 摘要)
|
||
|
||
```ebnf
|
||
program = { function }
|
||
function = "fn" IDENT "(" [params] ")" ["->" type] block
|
||
params = param { "," param }
|
||
param = IDENT ":" type
|
||
type = "i64" | "f64" | "bool" | "void"
|
||
|
||
block = "{" { statement } [expression] "}"
|
||
statement = let_stmt | if_stmt | while_stmt | return_stmt | expr_stmt
|
||
let_stmt = "let" IDENT "=" expression ";" (* 变量不可变,无赋值语句 *)
|
||
if_stmt = "if" expression block ["else" (if_stmt | block)]
|
||
while_stmt = "while" expression block
|
||
return_stmt = "return" [expression] ";"
|
||
expr_stmt = expression ";"
|
||
|
||
expression = logical_or
|
||
logical_or = logical_and { "||" logical_and }
|
||
logical_and = comparison { "&&" comparison }
|
||
comparison = term { ("==" | "!=" | "<" | ">" | "<=" | ">=") term }
|
||
term = factor { ("+" | "-") factor }
|
||
factor = unary { ("*" | "/" | "%") unary }
|
||
unary = ("-" | "!") unary | primary
|
||
primary = NUMBER | BOOL | IDENT | call | "(" expression ")"
|
||
call = IDENT "(" [args] ")"
|
||
args = expression { "," expression }
|
||
```
|
||
|
||
### 2.3 内置函数(编译器提供,非语言特性)
|
||
|
||
| 函数 | 说明 |
|
||
|------|------|
|
||
| `print_i64(x: i64) -> void` | 打印整数并换行 |
|
||
| `print_f64(x: f64) -> void` | 打印浮点数并换行 |
|
||
| `print_bool(x: bool) -> void` | 打印布尔值并换行 |
|
||
|
||
### 2.4 示例程序
|
||
|
||
```rust
|
||
fn fib(n: i64) -> i64 {
|
||
if n < 2 {
|
||
return n;
|
||
}
|
||
return fib(n - 1) + fib(n - 2);
|
||
}
|
||
|
||
fn main() -> i64 {
|
||
let result = fib(10);
|
||
print_i64(result); // 输出: 55
|
||
return 0;
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 3. 编译器架构
|
||
|
||
### 3.1 整体流水线
|
||
|
||
```
|
||
源文件(.l) ──▶ 词法分析 ──▶ 语法分析 ──▶ 语义分析 ──▶ IR生成 ──▶ 可执行文件(.exe)
|
||
Token流 AST 带类型AST LLVM Module 机器码
|
||
```
|
||
|
||
### 3.2 各阶段输入输出
|
||
|
||
| 阶段 | 输入 | 输出 | 关键数据结构 |
|
||
|------|------|------|-------------|
|
||
| 词法分析 | `char*` 源码 | Token 数组 | `Token`(类型 + 行号 + 列号 + 值)|
|
||
| 语法分析 | Token 数组 | AST 根节点 | `AstNode`(递归树,每个节点有类型枚举 + 子节点)|
|
||
| 语义分析 | AST 根节点 | 带类型标注的 AST | 在 `AstNode` 上附加 `TypeInfo` |
|
||
| IR 生成 | 带类型 AST | LLVM Module | `LLVMModuleRef`、`LLVMValueRef` 等 |
|
||
| 代码生成 | LLVM Module | `.exe` 可执行文件 | LLVM 的 `LLVMTargetMachineEmitToFile` |
|
||
|
||
### 3.3 错误处理策略
|
||
|
||
- 词法/语法错误:打印 `文件:行号:列号: 错误信息` 后立即终止
|
||
- 语义错误(类型不匹配等):收集当前阶段所有错误后统一输出,再终止
|
||
- 不尝试错误恢复,不做增量编译
|
||
|
||
---
|
||
|
||
## 4. 模块详细设计
|
||
|
||
### 4.1 词法分析器(Lexer)
|
||
|
||
**职责**:将源代码字符串转换为 Token 流
|
||
|
||
**Token 类型清单**:
|
||
|
||
| 类别 | Token |
|
||
|------|-------|
|
||
| 关键字 | `fn` `let` `if` `else` `while` `return` `true` `false` |
|
||
| 类型 | `i64` `f64` `bool` `void` |
|
||
| 字面量 | 整数、浮点数 |
|
||
| 标识符 | 用户定义的变量名、函数名 |
|
||
| 运算符/分隔 | `+` `-` `*` `/` `%` `==` `!=` `<` `>` `<=` `>=` `&&` `||` `!` `=` `->` `(` `)` `{` `}` `,` `:` `;` |
|
||
|
||
**实现要点**:
|
||
- 手写状态机,不依赖 flex/lex
|
||
- 跳过空白(空格、`\t`、`\r`)和注释(`//` 行注释 + `/* */` 块注释)
|
||
- 每个 Token 记录行号和列号,用于错误报告
|
||
- 关键字通过哈希表或完美哈希识别
|
||
|
||
**关键函数签名**:
|
||
```c
|
||
Token* lex(const char* source, size_t* token_count, ErrorInfo* error);
|
||
```
|
||
|
||
### 4.2 语法分析器(Parser)
|
||
|
||
**职责**:将 Token 流转换为抽象语法树
|
||
|
||
**实现方式**:手写递归下降解析器(Pratt parsing 处理表达式)
|
||
|
||
**AST 节点类型**:
|
||
|
||
```
|
||
Program — 程序根节点,包含多个函数
|
||
Function — 函数定义(名称、参数列表、返回类型、函数体)
|
||
Parameter — 函数参数(名称、类型)
|
||
Block — 代码块,包含语句列表
|
||
LetStmt — let 声明(不可变变量)
|
||
IfStmt — if 语句(条件、then块、可选的else块)
|
||
WhileStmt — while 循环
|
||
ReturnStmt — return 语句
|
||
BinaryExpr — 二元运算(运算符 + 左右操作数)
|
||
UnaryExpr — 一元运算(-、!)
|
||
CallExpr — 函数调用
|
||
LiteralExpr — 字面量(整数、浮点、布尔)
|
||
IdentifierExpr — 标识符引用
|
||
```
|
||
|
||
**关键函数签名**:
|
||
```c
|
||
AstNode* parse(const Token* tokens, size_t token_count, ErrorInfo* error);
|
||
```
|
||
|
||
### 4.3 语义分析器(Sema / Semantic Analyzer)
|
||
|
||
**职责**:类型推断和类型检查
|
||
|
||
**核心工作**:
|
||
1. **符号表管理** — 作用域栈(全局作用域 → 函数作用域 → 块作用域)
|
||
2. **类型推断** — 从 `let x = 42` 推断出 `x: i64`
|
||
3. **类型检查** — `if`/`while` 条件必须是 `bool`;二元运算两边类型必须一致
|
||
4. **隐式类型转换** — 整数可自动提升为浮点数(`i64` → `f64`)
|
||
5. **函数签名检查** — 调用时参数数量和类型必须匹配声明
|
||
6. **未定义检查** — 所有引用的标识符必须在作用域内已定义
|
||
|
||
**数据结构**:
|
||
```c
|
||
typedef struct {
|
||
const char* name; // 符号名称
|
||
TypeKind type; // 推断出的类型
|
||
SymbolKind kind; // 变量 / 参数 / 函数
|
||
// 函数符号额外信息
|
||
TypeKind return_type;
|
||
TypeKind* param_types;
|
||
size_t param_count;
|
||
} Symbol;
|
||
|
||
typedef struct Scope {
|
||
Symbol* symbols; // 当前作用域的符号表
|
||
size_t count;
|
||
struct Scope* parent; // 上级作用域
|
||
} Scope;
|
||
```
|
||
|
||
**关键函数签名**:
|
||
```c
|
||
void analyze(AstNode* ast, ErrorList* errors);
|
||
```
|
||
|
||
### 4.4 LLVM IR 生成器(Codegen)
|
||
|
||
**职责**:遍历带类型的 AST,调用 LLVM-C API 生成 LLVM IR
|
||
|
||
**类型映射**:
|
||
|
||
| L 类型 | LLVM 类型 |
|
||
|--------|-----------|
|
||
| `i64` | `LLVMInt64Type()` |
|
||
| `f64` | `LLVMDoubleType()` |
|
||
| `bool` | `LLVMInt1Type()` |
|
||
| `void` | `LLVMVoidType()` |
|
||
|
||
**各 AST 节点的生成策略**:
|
||
|
||
| AST 节点 | IR 生成策略 |
|
||
|----------|------------|
|
||
| `Function` | 创建 `LLVMAddFunction`,分配 entry BB,生成函数体 |
|
||
| `Block` | 顺序生成每条语句/表达式 |
|
||
| `LetStmt` | `alloca` 分配栈空间,计算初始化表达式,`store` |
|
||
| `BinaryExpr` | 生成左右操作数,按运算符选 `LLVMBuildAdd`/`LLVMBuildSub`/... |
|
||
| `IfStmt` | 创建 3 个 BB: then/else/merge,`LLVMBuildCondBr` |
|
||
| `WhileStmt` | 创建 cond/body/merge 三个 BB,`LLVMBuildCondBr` + `LLVMBuildBr` |
|
||
| `CallExpr` | 查找函数,`LLVMBuildCall2` |
|
||
| `ReturnStmt` | `LLVMBuildRet` |
|
||
| `LiteralExpr` | `LLVMConstInt`/`LLVMConstReal` |
|
||
| 标识符读取 | 从 `alloca` 地址 `LLVMBuildLoad2` |
|
||
|
||
**内置函数实现**:`print_i64`/`print_f64`/`print_bool` 在编译器内部用 C `printf` 实现,生成时直接映射到 LLVM IR 调用 `printf`。
|
||
|
||
**关键函数签名**:
|
||
```c
|
||
LLVMModuleRef codegen(AstNode* ast, const char* module_name);
|
||
```
|
||
|
||
### 4.5 驱动层(Driver)
|
||
|
||
**职责**:串联各阶段,处理命令行参数
|
||
|
||
```
|
||
l-language.exe <source.l> [-o <output>] [--emit-ir]
|
||
--emit-ir 输出 LLVM IR 文本(.ll),不生成可执行文件
|
||
-o <file> 指定输出文件名(默认 a.exe)
|
||
```
|
||
|
||
**流程**:
|
||
1. 读取源文件到内存
|
||
2. 调用 `lex()` → 检查词法错误
|
||
3. 调用 `parse()` → 检查语法错误
|
||
4. 调用 `analyze()` → 检查语义错误
|
||
5. 调用 `codegen()` → 生成 LLVM Module
|
||
6. 调用 `LLVMTargetMachineEmitToFile()` → 输出目标文件
|
||
7. 调用系统链接器(`clang` 或 `gcc`)→ 生成可执行文件
|
||
|
||
---
|
||
|
||
## 5. 开发阶段划分
|
||
|
||
### Phase 1:基础设施(预计 2-3 天)
|
||
|
||
- [ ] CMake 构建系统(查找 LLVM、配置编译选项)
|
||
- [ ] 词法分析器:完整的 Token 识别 + 注释跳过
|
||
- [ ] 单元测试框架搭建(CUnit 或手写断言宏)
|
||
- [ ] 错误报告基础设施(行号/列号 + 彩色输出)
|
||
|
||
### Phase 2:表达式计算(预计 2-3 天)
|
||
|
||
- [ ] AST 数据结构定义
|
||
- [ ] Pratt 表达式解析器(算术、比较、逻辑)
|
||
- [ ] 字面量 + 一元运算 + 二元运算的 IR 生成
|
||
- [ ] 生成第一个可执行文件:`print_i64(1 + 2 * 3)`
|
||
|
||
### Phase 3:变量和控制流(预计 3-4 天)
|
||
|
||
- [ ] `let` 声明 + 标识符引用
|
||
- [ ] 语义分析:符号表 + 类型推断
|
||
- [ ] `if` / `else` 语句
|
||
- [ ] `while` 循环
|
||
- [ ] `return` 语句
|
||
|
||
### Phase 4:函数(预计 3-4 天)
|
||
|
||
- [ ] 函数定义解析(参数 + 返回类型)
|
||
- [ ] 函数调用 IR 生成
|
||
- [ ] 作用域链管理
|
||
- [ ] 完整的斐波那契程序跑通
|
||
|
||
### Phase 5:集成验证(预计 1-2 天)
|
||
|
||
- [ ] 端到端测试(多个 `.l` 程序编译运行验证结果)
|
||
- [ ] README 文档
|
||
- [ ] 编译错误信息完善
|
||
|
||
**总预计工期**:约 2-3 周(每天投入 2-4 小时)
|
||
|
||
---
|
||
|
||
## 6. 技术依赖
|
||
|
||
| 依赖 | 版本 | 用途 |
|
||
|------|------|------|
|
||
| C 编译器 | GCC 14.x (MinGW) | 编译编译器自身 |
|
||
| CMake | ≥ 3.20 | 构建系统 |
|
||
| LLVM | 19.x | IR 生成 + 目标代码输出 |
|
||
| 操作系统 | Windows 11 | 开发和运行 |
|
||
|
||
LLVM 安装路径:`D:\settings\Language\LLVM`
|
||
|
||
---
|
||
|
||
## 7. 目录结构
|
||
|
||
```
|
||
L Language/
|
||
├── docs/
|
||
│ └── PRD.md 本文档
|
||
├── src/
|
||
│ ├── lexer/
|
||
│ │ ├── lexer.c 词法分析器主逻辑
|
||
│ │ ├── token.c Token 数据结构
|
||
│ │ └── lexer.h
|
||
│ ├── parser/
|
||
│ │ ├── parser.c 递归下降 + Pratt 解析
|
||
│ │ └── parser.h
|
||
│ ├── ast/
|
||
│ │ ├── ast.c AST 节点创建/销毁
|
||
│ │ └── ast.h
|
||
│ ├── sema/
|
||
│ │ ├── sema.c 语义分析 + 类型检查
|
||
│ │ ├── symbol.c 符号表管理
|
||
│ │ └── sema.h
|
||
│ ├── codegen/
|
||
│ │ ├── codegen.c LLVM IR 生成
|
||
│ │ └── codegen.h
|
||
│ ├── driver/
|
||
│ │ ├── main.c 入口 + 命令行参数
|
||
│ │ └── error.c 错误报告
|
||
│ └── util/
|
||
│ └── arena.c 内存池(简化内存管理)
|
||
├── include/
|
||
│ └── l_lang.h 公共头文件(类型定义等)
|
||
├── test/
|
||
│ ├── test_lexer.c
|
||
│ ├── test_parser.c
|
||
│ ├── test_sema.c
|
||
│ ├── test_codegen.c
|
||
│ └── programs/ .l 测试程序
|
||
│ ├── hello.l
|
||
│ ├── fib.l
|
||
│ └── ...
|
||
├── CMakeLists.txt
|
||
└── README.md
|
||
```
|
||
|
||
---
|
||
|
||
## 8. 成功标准
|
||
|
||
v0.1 完成的判定标准:
|
||
|
||
1. 斐波那契程序(递归 + 循环两个版本)编译并输出正确结果
|
||
2. 至少 3 个不同算例编译运行通过
|
||
3. 以下语法元素均有覆盖:
|
||
- [x] `let` 变量声明和类型推断
|
||
- [x] 算术运算(`+` `-` `*` `/` `%`)
|
||
- [x] 比较运算(`==` `!=` `<` `>` `<=` `>=`)
|
||
- [x] 逻辑运算(`&&` `||` `!`)
|
||
- [x] `if` / `else` 控制流
|
||
- [x] `while` 循环
|
||
- [x] 函数定义和调用
|
||
- [x] 递归
|
||
4. 类型错误能被正确检测并给出可读的错误信息
|
||
|
||
---
|
||
|
||
## 9. 风险与缓解
|
||
|
||
| 风险 | 概率 | 缓解措施 |
|
||
|------|------|----------|
|
||
| LLVM-C API 复杂度过高 | 中 | 先用 LLVM 官方 Kaleidoscope 教程预热,理解核心 API |
|
||
| 类型推断实现困难 | 中 | v0.1 只做最简单的 "从初始化表达式推断",不涉及 Hindley-Milner 或泛型 |
|
||
| 递归函数 IR 栈管理出错 | 中 | 所有变量用 `alloca`(栈分配),LLVM 的 `mem2reg` pass 自动优化 |
|
||
| Windows/MinGW + LLVM 兼容问题 | 低 | 提前验证 LLVM 安装和 CMake 能找到 LLVM |
|