13 KiB
13 KiB
CLAUDE.md
项目概述
L Language v0.5 — 用 C17 实现的静态类型编译型编程语言,Rust 风格语法,LLVM 22.x 后端。经典 5 阶段流水线:词法 → 语法 → 语义 → IR → 可执行文件。145 单元测试 + 23 集成程序。
语言设计哲学
L 借鉴 Rust 语法骨架(let/mut/fn/struct/impl/match),但有自己的设计理念:
- 去糖优先 — 复杂语法在 parser 层去糖为简单原语,零 sema/codegen 改动。已验证: for/match/复合赋值。
- 博采众长,L 化适配 — 从多语言吸收优秀特性,但必须适配 L 的语法风格,不能直接照抄。每条特性要问:这个特性的核心价值是什么?在 L 里最自然的表达方式是什么?
- 渐进演进 — 先加语法糖(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 化版)
管道 |> — 数据流从左到右,保持 () 调用:
let result = 10 |> double() |> add(5);
// 去糖为: add(double(10), 5)
// 注意: RHS 必须是函数调用形式(带括号),不支持 `10 |> double`(与 Elixir 不同)
// 管道优先级低于所有运算符: a + b |> f() → f(a + b)
guard — 提前返回,保留 else 关键字 + 花括号:
guard x >= 0 else { return -1; }
// 去糖为: if !(x >= 0) { return -1; }
// guard 块内只允许 return/continue/break 等跳转语句
// else 块必须用花括号,即使只有一条语句
字符串插值 — 用 \(expr) 而非 {expr},避免与块语法冲突:
let name = "World";
let msg = "Hello, \(name)!";
// 去糖为: "Hello, " + name + "!"
// 转义: 字面量 \( 写作 \\\(,字面量 \ 写作 \\
// 与 Swift 的区别: Swift 用 \(),L 也采用此方案因为与 C 转义字符 \n \t 视觉一致
命名参数 — name: value 与类型标注 name: Type 视觉统一:
fn draw_rect(width: i64, height: i64) -> void { ... }
draw_rect(width: 10, height: 20); // 命名调用
draw_rect(10, 20); // 位置调用(等价)
// 命名参数与位置参数可混用: draw_rect(10, height: 20)
// 命名参数必须放在位置参数之后
// 与 Swift 的区别: 不需要外部/内部参数名区分,参数名即标签名
构建命令
# 配置(仅首次)
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 手写状态机,67 种 Token 类型
│ ├── parser/
│ │ └── parser.h/c Pratt 表达式 (9 级优先级) + 递归下降语句
│ ├── ast/
│ │ └── ast.h/c 14 种节点 (PROGRAM..IDENT_EXPR) + 工厂函数
│ ├── sema/
│ │ ├── symbol.h/c 作用域链 (Scope* parent 链表)
│ │ └── sema.h/c 类型推断 + 类型检查 + 3 个内建函数注册
│ ├── codegen/
│ │ └── codegen.h/c AST → LLVM-C API → LLVMModuleRef
│ ├── driver/
│ │ ├── main.c 入口 + 命令行解析 + 流水线串联
│ │ └── error.h/c ErrorInfo / ErrorList 错误报告
│ └── util/
│ └── arena.h/c Bump allocator (8MB, 8 字节对齐)
├── test/
│ ├── test_utils.h 断言宏 (ASSERT / TEST_RUN / test_summary)
│ ├── test_lexer.c 词法测试 (41 tests)
│ ├── test_parser.c 语法测试 (15 tests)
│ ├── test_sema.c 语义测试 (9 tests)
│ └── programs/ .l 集成测试 (5 个程序)
├── docs/
│ ├── PRD.md 产品需求文档
│ └── superpowers/plans/ 实现计划
├── CMakeLists.txt l_lang_lib (静态库) + l_lang (exe) + 测试
└── README.md
核心 API 参考
词法分析
// 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}
语法分析
// 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)
语义分析
// sema.h
void sema_analyze(AstNode* ast, ErrorList* errors, Arena* arena);
// 副作用: AST 节点填充 type 字段, errors 收集类型错误
// 内建: scope_insert_function(print_i64, print_f64, print_bool)
代码生成
// codegen.h
LLVMModuleRef codegen_module(AstNode* ast, const char* module_name,
const char** error_msg);
// 返回: 已验证的 LLVM Module,出错返回 NULL
// 内建 print_* 函数生成对应的 printf 调用
类型系统
| L 类型 | LLVM 类型 | C 常量创建 |
|---|---|---|
i64 |
LLVMInt64Type() |
LLVMConstInt(ty, val, true) |
f64 |
LLVMDoubleType() |
LLVMConstReal(ty, val) |
bool |
LLVMInt1Type() |
LLVMConstInt(ty, val, false) |
void |
LLVMVoidType() |
— |
类型推断规则:
- 字面量:
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 → 逐层检查 |
测试
# 单元测试 (每个 test_*.c 独立编译运行,各有自己的 main)
./l_lang_lexer_test.exe # 41 个断言
./l_lang_test.exe # 15 个断言
./l_lang_sema_test.exe # 9 个断言
# 集成测试 (编译 .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 预编译包缺少部分头文件) - 链接器:MinGW 环境用 gcc 链接(非 clang,避免 MSVC 依赖)
- Windows:仅支持 Windows 11 + MinGW-w64
- 错误消息:中文,格式
文件名:行号:列号: 描述
已知限制 (v0.5)
- 无泛型 (单态化)
- 无 trait / 接口
- 无模块系统(所有代码单文件)
- 无指针/引用类型
- 借用检查 (远期)
- 嵌套数组支持有限
match仅语句级(非表达式)
版本号升级清单
| 文件 | 字段 |
|---|---|
CMakeLists.txt |
VERSION 变量 |
README.md |
badges |
CHANGELOG.md |
版本标题 |