Files
Serendipity 3b7bab1e1b feat: L Language v0.1 编译器完整实现
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, 递归函数
2026-06-05 00:26:59 +08:00

406 lines
13 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 |