# 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 [-o ] [--emit-ir] --emit-ir 输出 LLVM IR 文本(.ll),不生成可执行文件 -o 指定输出文件名(默认 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 |