Files
l-language/CLAUDE.md
T

15 KiB
Raw Blame History

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: i64fn 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            手写状态机,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 参考

词法分析

// 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 类型 说明
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.cpromote/convert/numeric 查表驱动)。

类型推断规则:

  • 字面量:42i64, 3.14f64, truebool
  • 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 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 版本标题