Files
l-language/CLAUDE.md
T

322 lines
15 KiB
Markdown
Raw 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.
# 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` | 版本标题 |