322 lines
15 KiB
Markdown
322 lines
15 KiB
Markdown
# CLAUDE.md
|
||
|
||
## 项目概述
|
||
|
||
L Language v0.7 — 用 C17 实现的静态类型编译型编程语言,Rust 风格语法,LLVM 22.x 后端。经典 5 阶段流水线:词法 → 语法(+去糖)→ 语义 → IR → 可执行文件。197 单元测试 + 37 集成程序。
|
||
|
||
## 语言设计哲学
|
||
|
||
L 借鉴 Rust 语法骨架(let/mut/fn/struct/impl/match),但有自己的设计理念:
|
||
|
||
1. **去糖优先** — 复杂语法在 parser 层去糖为简单原语,零 sema/codegen 改动。已验证: for/match/复合赋值。
|
||
2. **博采众长,L 化适配** — 从多语言吸收优秀特性,但必须适配 L 的语法风格,**不能直接照抄**。每条特性要问:这个特性的核心价值是什么?在 L 里最自然的表达方式是什么?
|
||
3. **渐进演进** — 先加语法糖(parser 层),后加强类型(sema/codegen 层)
|
||
|
||
### L 化适配原则
|
||
|
||
从其他语言借鉴特性时,必须按 L 的风格加以改造:
|
||
|
||
| 规则 | 说明 | 反例(直接照抄) | 正例(L 化后) |
|
||
|------|------|-----------------|---------------|
|
||
| **花括号块** | 所有代码块用 `{}`,不引入缩进块 | Python `for x in arr:` + 缩进 | `for x in arr { ... }` |
|
||
| **类型后置** | 类型标注用 `name: Type`,不用前置类型 | C `int x`、Go `func f() int` | `let x: i64`、`fn f() -> i64` |
|
||
| **snake_case** | 函数/变量用 snake_case,类型用 PascalCase | `drawRect()`、`MAX_SIZE` | `draw_rect()`、类型 `MaxSize`(但常量仍 `MAX_SIZE`) |
|
||
| **显式 return** | 返回值必须写 `return`,不用隐式尾表达式 | Rust `fn f() -> i64 { 42 }` | `fn f() -> i64 { return 42; }` |
|
||
| **关键字优先** | 新特性优先用关键字,不引入符号噪音 | `x?.y`(空安全链) | 暂不引入,等类型系统成熟后用 `?` 也可接受 |
|
||
| **保持对称** | 开闭符号成对,不用 `end`/`done` 等结尾词 | Ruby `if ... end` | `if ... { }` |
|
||
| **语义透明** | 语法糖的去糖结果必须可预测,文档里写清楚 | 隐藏的隐式转换 | 每个 sugar 标注 `// 去糖为: ...` |
|
||
| **一致缩写** | 关键字缩写风格统一,不混用缩写与全称 | `func` + `def` + `fn` 混用 | 全部用短关键字: `fn`/`let`/`mut` |
|
||
|
||
### 各语言特性借鉴分析
|
||
|
||
分析每种语言时,区分"核心思想"和"表面语法",取其思想、适配语法:
|
||
|
||
| 来源语言 | 特性 | 核心思想 | L 化语法 | 适配要点 |
|
||
|----------|------|---------|---------|---------|
|
||
| **Elixir/F#** | 管道 | 数据从左到右流经函数 | `10 \|> double() \|> add(5)` | 保持 `()` 调用、不用空格传参 |
|
||
| **Swift** | guard | 提前返回(早退模式) | `guard x >= 0 else { return -1; }` | 保留 `else` 关键字 + 花括号块 |
|
||
| **Swift** | 字符串插值 | 表达式嵌入字符串 | `"Hello, \\(name)!"` | 用 `\\(...)` 而非 `{...}`,避免与块语法冲突 |
|
||
| **Swift/Kotlin** | 命名参数 | 调用点标注参数名 | `draw_rect(width: 10, height: 20)` | 用 `:` 分隔(与类型标注一致) |
|
||
| **Python** | 列表推导式 | 声明式生成列表 | `[for x in arr if x > 0: x * 2]` | 用 L 的 `for-in` 语法,不用 Python 后缀式 |
|
||
| **Python** | 装饰器 | 函数/类型的元编程包装 | 慎用 `@` 符号,考虑用 `#[attr]`(Rust 风格更搭 L) | 语法需与 attribute 体系统一设计 |
|
||
| **Go** | defer | 作用域退出时执行 | `defer cleanup();` | 关键字 + 常规调用语法 |
|
||
| **Go** | 多返回值 | 函数返回多个值 | `fn div(a: i64, b: i64) -> (i64, i64)` | 用元组括号,不用 Go 的裸逗号 |
|
||
| **Zig** | comptime | 编译期求值 | `const x = comptime 1 + 2;` | `const` 表明编译期常量 |
|
||
| **Kotlin** | 扩展函数 | 为已有类型添加方法 | `impl str { fn is_email(self: str) -> bool { ... } }` | 复用 `impl` 块,不用 `Type.method` 语法 |
|
||
| **Kotlin** | when 表达式 | 多分支条件 + 返回值 | 已有 `match`,将 match 升级为表达式 | 不用新关键字,扩展现有语法 |
|
||
| **Carbon/Cpp2** | in/out 参数 | 明确参数传递方向 | `fn f(in x: i64, out y: i64)` | 复用类型标注位置,前缀修饰 |
|
||
|
||
### 候选特性优先级
|
||
|
||
| 优先级 | 特性 | 来源 | 实现层 | 预计行数 | L 化关键 |
|
||
|--------|------|------|--------|---------|---------|
|
||
| **P0** | 管道 `\|>` | Elixir/F# | parser | ~30 | 保持 `()` 调用风格 |
|
||
| **P0** | guard 语句 | Swift | parser | ~15 | `else` + 花括号块 |
|
||
| **P0** | 字符串插值 | Swift | lexer+parser | ~60 | `\\(expr)` 避免 `{}` 歧义 |
|
||
| **P0** | 命名参数 | Swift | parser | ~50 | `name: val` 与类型标注统一 |
|
||
| P1 | 列表推导式 | Python | parser | ~50 | 用 `[for x in arr if cond: expr]`,不用 Python 后缀式 |
|
||
| P1 | 装饰器 | Python | parser | ~40 | 考虑 `#[attr]` 而非裸 `@` |
|
||
| P1 | defer | Go | parser+codegen | ~60 | 关键字 + 花括号/单语句 |
|
||
| P1 | 扩展函数 | Kotlin | parser+sema | ~80 | 复用 `impl` 块,不造新语法 |
|
||
| P1 | match 表达式 | Kotlin | parser | ~30 | 扩展现有 match,不引入 `when` |
|
||
| P2 | 多返回值 | Go | 全流水线 | ~200 | `(T, U)` 元组语法,不用裸逗号 |
|
||
| P2 | 空安全 `?.` / `??` | Kotlin | sema+codegen | ~150 | 保留符号运算符(符号在此场景比关键字更清晰) |
|
||
| P2 | comptime 求值 | Zig | sema+codegen | ~200 | `comptime` 作为表达式前缀 |
|
||
| P3 | 隐式接口 | Go | sema+codegen | ~300 | 暂远,与 trait 体系统一设计 |
|
||
| P3 | in/out 参数 | Carbon/Cpp2 | sema+codegen | ~150 | 前缀修饰符,放在 `name: Type` 之前 |
|
||
|
||
### P0 特性设计(L 化版)
|
||
|
||
**管道 `|>`** — 数据流从左到右,保持 `()` 调用:
|
||
```rust
|
||
let result = 10 |> double() |> add(5);
|
||
// 去糖为: add(double(10), 5)
|
||
// 注意: RHS 必须是函数调用形式(带括号),不支持 `10 |> double`(与 Elixir 不同)
|
||
// 管道优先级低于所有运算符: a + b |> f() → f(a + b)
|
||
```
|
||
|
||
**guard** — 提前返回,保留 `else` 关键字 + 花括号:
|
||
```rust
|
||
guard x >= 0 else { return -1; }
|
||
// 去糖为: if !(x >= 0) { return -1; }
|
||
// guard 块内只允许 return/continue/break 等跳转语句
|
||
// else 块必须用花括号,即使只有一条语句
|
||
```
|
||
|
||
**字符串插值** — 用 `\(expr)` 而非 `{expr}`,避免与块语法冲突:
|
||
```rust
|
||
let name = "World";
|
||
let msg = "Hello, \(name)!";
|
||
// 去糖为: "Hello, " + name + "!"
|
||
// 转义: 字面量 \( 写作 \\\(,字面量 \ 写作 \\
|
||
// 与 Swift 的区别: Swift 用 \(),L 也采用此方案因为与 C 转义字符 \n \t 视觉一致
|
||
```
|
||
|
||
**命名参数** — `name: value` 与类型标注 `name: Type` 视觉统一:
|
||
```rust
|
||
fn draw_rect(width: i64, height: i64) -> void { ... }
|
||
|
||
draw_rect(width: 10, height: 20); // 命名调用
|
||
draw_rect(10, 20); // 位置调用(等价)
|
||
// 命名参数与位置参数可混用: draw_rect(10, height: 20)
|
||
// 命名参数必须放在位置参数之后
|
||
// 与 Swift 的区别: 不需要外部/内部参数名区分,参数名即标签名
|
||
```
|
||
|
||
## 构建命令
|
||
|
||
```bash
|
||
# 配置(仅首次)
|
||
cd build
|
||
cmake .. -G "MinGW Makefiles" -DCMAKE_PREFIX_PATH="D:/settings/Language/LLVM"
|
||
|
||
# 编译
|
||
mingw32-make -j4
|
||
|
||
# 编译单个目标
|
||
mingw32-make l_lang
|
||
mingw32-make l_lang_lib
|
||
```
|
||
|
||
## 架构
|
||
|
||
```
|
||
源文件(.l) → 词法分析 → 语法分析 → 语义分析 → IR 生成 → 可执行文件
|
||
Token[] AstNode* 带类型AST LLVM Module .exe
|
||
```
|
||
|
||
```
|
||
L Language/
|
||
├── include/
|
||
│ └── l_lang.h 公共头文件 (TypeKind 枚举, 向前声明)
|
||
├── src/
|
||
│ ├── lexer/
|
||
│ │ ├── token.h/c Token {kind, start, length, line, col}
|
||
│ │ └── lexer.h/c 手写状态机,72 种 Token 类型
|
||
│ ├── parser/
|
||
│ │ ├── parse_internal.h 共享 Parser struct + 内联辅助
|
||
│ │ ├── parser.h/c 语句/声明/程序入口 (603 行)
|
||
│ │ ├── expr.c 表达式/类型解析 (485 行, Pratt 主循环)
|
||
│ │ ├── desugar.h/c 独立去糖 pass (109 行, 5 种去糖)
|
||
│ │ └── (parser.h) 公开接口
|
||
│ ├── ast/
|
||
│ │ ├── ast.h/c 27 种 AST 节点 + 工厂函数
|
||
│ │ ├── visit.h/c Visitor 统一 dispatch 框架
|
||
│ │ └── (ast.h) 公开接口
|
||
│ ├── sema/
|
||
│ │ ├── sema_internal.h 共享声明 + 向前声明
|
||
│ │ ├── symbol.h/c 作用域链 (Scope* parent 链表)
|
||
│ │ ├── sema.h/c 语义分析入口 + analyze_node (500 行)
|
||
│ │ ├── typeck.c 表达式类型检查 (559 行, Visitor dispatch)
|
||
│ │ ├── mono.c 泛型单态化 (97 行, AST 类型替换)
|
||
│ │ └── type_table.h/c 数据驱动类型描述表 (14 种)
|
||
│ ├── codegen/
|
||
│ │ ├── codegen_internal.h 共享 CgCtx + 表操作声明
|
||
│ │ ├── codegen.h/c 语句生成 + 模块入口 (454 行)
|
||
│ │ ├── cg_expr.c LLVM 表达式生成 (426 行, Visitor dispatch)
|
||
│ │ └── target.h/c 目标平台输出 (obj 文件)
|
||
│ ├── driver/
|
||
│ │ ├── main.c 入口 + 命令行解析 + 流水线串联
|
||
│ │ └── error.h/c ErrorInfo / ErrorList (arena 分配)
|
||
│ └── util/
|
||
│ └── arena.h/c Bump allocator (8MB, 8 字节对齐)
|
||
├── test/
|
||
│ ├── test_utils.h 断言宏 (ASSERT / TEST_RUN / test_summary)
|
||
│ ├── test_lexer.c 词法测试 (41 tests, 3 functions)
|
||
│ ├── test_parser.c 语法测试 (54 tests, 20 functions)
|
||
│ ├── test_sema.c 语义测试 (74 tests, 24 functions)
|
||
│ ├── test_codegen.c 代码生成测试 (28 tests, 10 functions)
|
||
│ └── programs/ .l 集成测试 (37 个程序)
|
||
├── docs/
|
||
│ ├── PRD.md 产品需求文档
|
||
│ └── superpowers/plans/ 实现计划
|
||
├── CMakeLists.txt l_lang_lib (静态库) + l_lang (exe) + 测试
|
||
└── README.md
|
||
```
|
||
|
||
## 核心 API 参考
|
||
|
||
### 词法分析
|
||
|
||
```c
|
||
// lexer.h
|
||
Token* lex(Arena* a, const char* source, const char* filename,
|
||
size_t* count, ErrorInfo* error);
|
||
// 返回: Token 数组(分配在 arena),出错返回 NULL
|
||
// Token: {TokenKind kind, const char* start, int length, int line, int col}
|
||
```
|
||
|
||
### 语法分析
|
||
|
||
```c
|
||
// parser.h
|
||
AstNode* parse(Arena* a, const Token* tokens, size_t count,
|
||
const char* filename, ErrorInfo* error);
|
||
// 返回: AST_PROGRAM 节点,出错返回 NULL
|
||
// 支持: 所有语句 (let/if/while/return) + 表达式 (Pratt precedence climbing)
|
||
```
|
||
|
||
### 语义分析
|
||
|
||
```c
|
||
// sema.h
|
||
void sema_analyze(AstNode* ast, ErrorList* errors, Arena* arena);
|
||
// 副作用: AST 节点填充 type 字段, errors 收集类型错误
|
||
// 内建: scope_insert_function(print_i64, print_f64, print_bool)
|
||
```
|
||
|
||
### 代码生成
|
||
|
||
```c
|
||
// codegen.h
|
||
LLVMModuleRef codegen_module(AstNode* ast, const char* module_name,
|
||
const char** error_msg);
|
||
// 返回: 已验证的 LLVM Module,出错返回 NULL
|
||
// 内建 print_* 函数生成对应的 printf 调用
|
||
```
|
||
|
||
## 类型系统
|
||
|
||
| L 类型 | LLVM 类型 | 说明 |
|
||
|--------|-----------|------|
|
||
| `i32` | `LLVMInt32Type()` | 有符号 32-bit |
|
||
| `i64` | `LLVMInt64Type()` | 有符号 64-bit (默认整数) |
|
||
| `u64` | `LLVMInt64Type()` | 无符号 64-bit |
|
||
| `f64` | `LLVMDoubleType()` | 双精度浮点 |
|
||
| `bool` | `LLVMInt1Type()` | 布尔 |
|
||
| `char` | `LLVMInt8Type()` | 字符 |
|
||
| `str` | `i8*` (指针) | 字符串,拼接用 malloc+memcpy |
|
||
| `void` | `LLVMVoidType()` | 无返回值 |
|
||
| `struct` | `LLVMStructType()` | 结构体,字段 GEP |
|
||
| `enum` | `{i64, i64}` | tagged union |
|
||
| `array` | `LLVMArrayType()` | 固定大小 `T[N]`,GEP 索引 |
|
||
|
||
类型表统一管理在 `type_table.c`(promote/convert/numeric 查表驱动)。
|
||
|
||
类型推断规则:
|
||
- 字面量:`42` → `i64`, `3.14` → `f64`, `true` → `bool`
|
||
- `let x = expr` → 从 expr 推断
|
||
- `let x: i64 = expr` → 显式标注优先
|
||
- 算术运算:i64 + i64 → i64, i64 + f64 → f64 (提升)
|
||
- 比较运算:返回 `bool`
|
||
|
||
## 运算符优先级
|
||
|
||
| 优先级 | 运算符 |
|
||
|--------|--------|
|
||
| 70 (最高) | `-` (一元负), `!` (一元非) |
|
||
| 60 | `*` `/` `%` |
|
||
| 50 | `+` `-` |
|
||
| 40 | `==` `!=` `<` `>` `<=` `>=` |
|
||
| 30 | `&&` |
|
||
| 20 | `\|\|` |
|
||
| 10 (最低) | — |
|
||
|
||
## 错误处理
|
||
|
||
| 阶段 | 策略 |
|
||
|------|------|
|
||
| 词法分析 | 首个非法字符 → 立即终止,返回 ErrorInfo |
|
||
| 语法分析 | 首个语法错误 → 立即终止,返回 ErrorInfo |
|
||
| 语义分析 | 收集所有类型错误到 ErrorList → 批量输出 (ANSI 红色) |
|
||
| IR 生成 | LLVMVerifyModule → 返回 char* 错误消息 |
|
||
| 链接 | system() 返回值检查 → 打印 exit code |
|
||
| 分配失败 | arena_alloc 返回 NULL → 逐层检查 |
|
||
|
||
|
||
## 测试
|
||
|
||
```bash
|
||
# 单元测试 (每个 test_*.c 独立编译运行,各有自己的 main)
|
||
./l_lang_lexer_test.exe # 41 tests (3 functions)
|
||
./l_lang_test.exe # 54 tests (20 functions)
|
||
./l_lang_sema_test.exe # 74 tests (24 functions)
|
||
./l_lang_codegen_test.exe # 28 tests (10 functions)
|
||
|
||
# 集成测试 (编译 .l → 运行 .exe → 检查输出)
|
||
for f in ../test/programs/*.l; do
|
||
echo "=== $f ==="
|
||
./l_lang.exe "$f" -o /tmp/out.exe && /tmp/out.exe
|
||
done
|
||
```
|
||
|
||
## 关键约束
|
||
|
||
- **C17 标准**:`-Wall -Wextra -g`,零编译警告
|
||
- **Arena 分配**:Token、AST、符号表全部从 arena 分配,无 malloc/free 散落
|
||
- **LLVM 路径**:`D:\settings\Language\LLVM`,C API 头文件手动补充(v22.1.7 预编译包缺少部分头文件)
|
||
- **链接器**:自包含 clang+lld(优先),gcc fallback。CreateProcess 替代 system()
|
||
- **Windows**:仅支持 Windows 11 + MinGW-w64
|
||
- **错误消息**:中文,格式 `文件名:行号:列号: 描述`
|
||
|
||
## 已知限制 (v0.7)
|
||
|
||
- 无指针/引用类型(`&T`, `*T`)
|
||
- 无借用检查(远期)
|
||
- 嵌套数组支持有限
|
||
- 无闭包 / lambda
|
||
- 无多返回值(`(T, U)` 元组)
|
||
- 无标准库
|
||
- 仅单文件+mod 模块,无包管理器
|
||
- Windows 独占(LLVM 22 + MinGW-w64)
|
||
|
||
### 近期已解决 (v0.5→v0.7)
|
||
|
||
- ✅ 泛型单态化 (`fn id<T>(x: T) -> T`)
|
||
- ✅ trait / 接口 (`trait Show` + `extend Trait Struct`)
|
||
- ✅ 模块系统 (`mod` + `use` + `pub`)
|
||
- ✅ ADT 枚举关联数据 (`enum Option { Some(T), None }`)
|
||
- ✅ `if let` 模式匹配语法糖
|
||
- ✅ 表达式作为值 (`if-expr`, block 最后表达式)
|
||
- ✅ 独立 desugar pass (match/guard/for/if-let/复合赋值)
|
||
- ✅ TypeTable 数据驱动 + AST Visitor dispatch
|
||
- ✅ 全部源文件 <800 行,197 单元 + 37 集成测试
|
||
|
||
## 版本号升级清单
|
||
|
||
| 文件 | 字段 |
|
||
|------|------|
|
||
| `CMakeLists.txt` | `VERSION` 变量 |
|
||
| `README.md` | badges |
|
||
| `CHANGELOG.md` | 版本标题 |
|