Files
l-language/docs/PRD.md
T
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

13 KiB
Raw Permalink Blame History

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 truefalse
无返回值 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 LLVMModuleRefLLVMValueRef
代码生成 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

职责:类型推断和类型检查

核心工作

  1. 符号表管理 — 作用域栈(全局作用域 → 函数作用域 → 块作用域)
  2. 类型推断 — 从 let x = 42 推断出 x: i64
  3. 类型检查if/while 条件必须是 bool;二元运算两边类型必须一致
  4. 隐式类型转换 — 整数可自动提升为浮点数(i64f64
  5. 函数签名检查 — 调用时参数数量和类型必须匹配声明
  6. 未定义检查 — 所有引用的标识符必须在作用域内已定义

数据结构

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/mergeLLVMBuildCondBr
WhileStmt 创建 cond/body/merge 三个 BBLLVMBuildCondBr + 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)

流程

  1. 读取源文件到内存
  2. 调用 lex() → 检查词法错误
  3. 调用 parse() → 检查语法错误
  4. 调用 analyze() → 检查语义错误
  5. 调用 codegen() → 生成 LLVM Module
  6. 调用 LLVMTargetMachineEmitToFile() → 输出目标文件
  7. 调用系统链接器(clanggcc)→ 生成可执行文件

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. 以下语法元素均有覆盖:
    • let 变量声明和类型推断
    • 算术运算(+ - * / %
    • 比较运算(== != < > <= >=
    • 逻辑运算(&& || !
    • if / else 控制流
    • while 循环
    • 函数定义和调用
    • 递归
  4. 类型错误能被正确检测并给出可读的错误信息

9. 风险与缓解

风险 概率 缓解措施
LLVM-C API 复杂度过高 先用 LLVM 官方 Kaleidoscope 教程预热,理解核心 API
类型推断实现困难 v0.1 只做最简单的 "从初始化表达式推断",不涉及 Hindley-Milner 或泛型
递归函数 IR 栈管理出错 所有变量用 alloca(栈分配),LLVM 的 mem2reg pass 自动优化
Windows/MinGW + LLVM 兼容问题 提前验证 LLVM 安装和 CMake 能找到 LLVM