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, 递归函数
13 KiB
13 KiB
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 摘要)
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 示例程序
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 记录行号和列号,用于错误报告
- 关键字通过哈希表或完美哈希识别
关键函数签名:
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 — 标识符引用
关键函数签名:
AstNode* parse(const Token* tokens, size_t token_count, ErrorInfo* error);
4.3 语义分析器(Sema / Semantic Analyzer)
职责:类型推断和类型检查
核心工作:
- 符号表管理 — 作用域栈(全局作用域 → 函数作用域 → 块作用域)
- 类型推断 — 从
let x = 42推断出x: i64 - 类型检查 —
if/while条件必须是bool;二元运算两边类型必须一致 - 隐式类型转换 — 整数可自动提升为浮点数(
i64→f64) - 函数签名检查 — 调用时参数数量和类型必须匹配声明
- 未定义检查 — 所有引用的标识符必须在作用域内已定义
数据结构:
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;
关键函数签名:
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。
关键函数签名:
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)
流程:
- 读取源文件到内存
- 调用
lex()→ 检查词法错误 - 调用
parse()→ 检查语法错误 - 调用
analyze()→ 检查语义错误 - 调用
codegen()→ 生成 LLVM Module - 调用
LLVMTargetMachineEmitToFile()→ 输出目标文件 - 调用系统链接器(
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 完成的判定标准:
- 斐波那契程序(递归 + 循环两个版本)编译并输出正确结果
- 至少 3 个不同算例编译运行通过
- 以下语法元素均有覆盖:
let变量声明和类型推断- 算术运算(
+-*/%) - 比较运算(
==!=<><=>=) - 逻辑运算(
&&||!) if/else控制流while循环- 函数定义和调用
- 递归
- 类型错误能被正确检测并给出可读的错误信息
9. 风险与缓解
| 风险 | 概率 | 缓解措施 |
|---|---|---|
| LLVM-C API 复杂度过高 | 中 | 先用 LLVM 官方 Kaleidoscope 教程预热,理解核心 API |
| 类型推断实现困难 | 中 | v0.1 只做最简单的 "从初始化表达式推断",不涉及 Hindley-Milner 或泛型 |
| 递归函数 IR 栈管理出错 | 中 | 所有变量用 alloca(栈分配),LLVM 的 mem2reg pass 自动优化 |
| Windows/MinGW + LLVM 兼容问题 | 低 | 提前验证 LLVM 安装和 CMake 能找到 LLVM |