diff --git a/docs/analysis/architecture-analysis-report-2026-06-05-1308.md b/docs/analysis/architecture-analysis-report-2026-06-05-1308.md new file mode 100644 index 0000000..0b5478f --- /dev/null +++ b/docs/analysis/architecture-analysis-report-2026-06-05-1308.md @@ -0,0 +1,427 @@ +# L Language 架构分析报告 — 结构体 + 自动内存管理 (v0.4) + +> 日期: 2026-06-05 13:08 | 自动生成 | **有源代码变更** +> 上次代码分析基线: `382cd08` (2026-06-05 11:52 报告) +> 当前 HEAD: `1d4fb27` feat: 自动内存管理 — 作用域级 RAII for str +> +> 新增提交 (自上次源代码基线 `382cd08`): +> - `b390d39` feat: 结构体 struct — 最后一项 P0 功能 +> - `1d4fb27` feat: 自动内存管理 — 作用域级 RAII for str + +--- + +## 变更摘要 (自上次代码基线) + +**19 个文件变更, +1882 / -54 行。** 两项重大功能落地: + +| 变更类别 | 详情 | 影响范围 | +|---------|------|---------| +| **struct 结构体** | 全流水线: lexer → parser → sema → codegen | 12 源文件, 2 集成测试 | +| **RAII 自动内存管理** | 作用域级 str free: `cleanup_list` + `malloc`/`free` 配对 | codegen.c (+~120 行) | +| **基础设施扩展** | 3 新 AST 节点, 2 新 Token, 1 新 SymbolKind, 1 新 TypeKind | ast/token/symbol/l_lang.h | +| **测试更新** | 2 新集成程序 + test_codegen.c API 兼容修复 | test/ | + +--- + +## 1. 当前架构全景 + +### 1.1 编译流水线 + +``` +源码(.l) → Lexer(词法) → Parser(语法) → Sema(语义) → Codegen(LLVM IR) → Target(obj) → GCC 链接(.exe) + 39 Token 18 AST 节点 类型标注 LLVMModuleRef .o 文件 + (含 struct) +``` + +**无架构变化。** 流水线仍为 6 阶段, struct 和 RAII 均在现有模块内实现。 + +### 1.2 模块清单与指标 + +| 模块 | 文件 | 行数 | Δ | 关键职责 | +|------|------|------|---|---------| +| include/ | l_lang.h | 36 | +2 | TypeKind 枚举 (8 种), 新增 TYPE_STRUCT | +| util/ | arena.c(39) + .h(13) | 52 | 0 | 8MB bump allocator, strdup | +| lexer/ | lexer.c(139) token.c(47) + .h(52) | 238 | +4 | 手写状态机, **39** Token 类型 (+2: TOK_STRUCT, TOK_DOT) | +| ast/ | ast.c(143) ast.h(111) | 254 | +54 | **18** 种节点类型 (+3: STRUCT_DECL, STRUCT_INIT, FIELD_ACCESS) | +| parser/ | parser.c(514) parser.h(10) | 524 | +124 | 递归下降 + Pratt, 新增 struct 声明/初始化/字段访问解析 | +| sema/ | sema.c(442) symbol.c(92) + .h(56) | 590 | +218 | 类型推断, 4 种 SymbolKind (+SYM_STRUCT), 结构体字段类型检查 | +| codegen/ | codegen.c(588) target.c(30) + .h(27) | 645 | +179 | LLVM 命名结构体, GEP/extractvalue, RAII cleanup 机制 | +| driver/ | main.c(141) error.c(46) + .h(18) | 205 | 0 | 流水线串联, 结构体解析结果自动流入 codegen | +| test/ | test_*.c(4 文件) + test_utils.h(23) | 361 | +31 | 19 测试函数, **61 断言**, **13** 集成程序 (+2) | + +| 汇总指标 | 上次 (382cd08) | 当前 (1d4fb27) | Δ | +|---------|---------------|---------------|----| +| 实现代码 (.c) | 1,688 行 | 2,221 行 | +533 | +| 头文件 (.h) | 308 行 | 323 行 | +15 | +| 测试代码 | 330 行 | 361 行 | +31 | +| **总代码量** | **2,326 行** | **2,905 行** | **+579** | +| Token 类型 | 37 | **39** | +2 | +| AST 节点类型 | 15 | **18** | +3 | +| TypeKind | 7 | **8** | +1 | +| SymbolKind | 3 | **4** | +1 | +| 内置函数 | 4 | 4 | 0 | +| 单元测试函数 | 19 | 19 | 0 | +| 单元测试断言 | 59 | **61** | +2 | +| 集成测试程序 | 11 | **13** | +2 | +| P0 完成度 | 3/4 (75%) | **4/4 (100%)** | ✅ | +| Git 提交数 | 11 | 13 | +2 | + +### 1.3 关键架构决策 (v0.4 新增) + +| 决策 | 描述 | 技术理由 | +|------|------|---------| +| **LLVM 命名结构体** | 先用 `LLVMStructCreateNamed` 创建占位符, 遍历完所有 struct 后再 `LLVMStructSetBody` | 支持结构体间互相引用 (如 `Rect` 含 `Point`) | +| **结构体值语义** | 字段访问通过 `extractvalue`(寄存器内) 而非 `GEP`(指针), init 用 alloca+store+load | 接近 Rust 的 move 语义雏形, 避免指针间接 | +| **RAII cleanup_list** | `CgCtx` 内维护固定大小 64 槽数组, `cleanup_add`/`cleanup_emit` 配对使用 | 零 malloc overhead, 作用域退出时 FIFO 释放 | +| **cleanup 触发范围** | BINARY_EXPR(拼接)、STRUCT_INIT、CALL_EXPR 产生的 str; struct 变量整体追踪 | 保守策略: 宁可泄漏简单 str 字面量也不误 free 栈数据 | +| **struct init 前瞻消歧义** | `Name { IDENT : ...` 模式判断 struct init vs block: 用 2-token lookahead | 避免 `{` 的 block/struct-init 二义性 | + +--- + +## 2. struct 结构体功能详解 + +### 2.1 全流水线实现 + +``` +源码: struct Point { x: i64, y: i64 } + │ + ▼ Lexer: TOK_STRUCT TOK_IDENT("Point") TOK_LBRACE TOK_IDENT("x") TOK_COLON TOK_I64 ... + │ + ▼ Parser: parse_struct_decl() → AST_STRUCT_DECL { name="Point", fields=[x:i64, y:i64] } + │ parse_struct_init() → Point { x: 10, y: 20 } + │ parse_expr_prec() → p.x (PREC_POSTFIX, TOK_DOT) + │ + ▼ Sema: scope_insert_struct() → SYM_STRUCT, 字段名校验, 类型匹配 + │ AST_FIELD_ACCESS → field_index 解析 + struct_name 传播 + │ + ▼ Codegen: + Phase 0: LLVMStructCreateNamed("Point") → forward decl + LLVMStructSetBody([i64, i64]) → body definition + Init: alloca(Point) + GEP(0,i) + store + load → struct value + Access: extractvalue(struct_val, field_idx) → 字段值 +``` + +### 2.2 新增 AST 节点 + +| AST 节点 | 用途 | 关键字段 | +|----------|------|---------| +| `AST_STRUCT_DECL` | 结构体声明 | `name`, `fields[]` (AST_PARAMETER 复用), `field_count` | +| `AST_STRUCT_INIT` | 结构体初始化表达式 | `type_name`, `field_names[]`, `field_values[]`, `field_count` | +| `AST_FIELD_ACCESS` | 点号字段访问 (p.x) | `object`, `field`, `field_index` (sema 填充) | + +### 2.3 类型传播链 + +`TypeInfo.struct_name` 字段在整个类型系统中传播: +- lexer: 将 `TOK_IDENT` 在类型位置识别为 struct 类型名 +- parser: `let p: Point = ...` 中 `Point` 解析为 `TYPE_STRUCT` + struct_type_name +- sema: 赋值/表达式推断时传播 `struct_name`; 字段访问时从 `struct_field_struct_names` 获取嵌套 struct 名 +- codegen: 通过 `find_struct_type()` 获取 `LLVMTypeRef` + +### 2.4 集成测试覆盖 + +| 程序 | 覆盖场景 | +|------|---------| +| `12_struct.l` | 基础 struct: 声明 Point, init, 字段访问 p.x/p.y, print_i64 | +| `13_struct_nested.l` | 嵌套 struct: Rect { top_left: Point, bot_right: Point }, 链式访问 r.top_left.x | + +--- + +## 3. RAII 自动内存管理详解 + +### 3.1 问题背景 + +v0.2 引入 str+str 拼接时使用 `malloc`, 导致编译后程序产生内存泄漏 (技术债 #1)。本次实现作用域级的自动 `free`, 不引入 GC, 不依赖 LLVM 优化 pass。 + +### 3.2 实现机制 + +``` +CgCtx { + cleanup_list[64]; // str 变量的 alloca 指针 + cleanup_count; // 当前追踪数量 + free_fn; // 声明的 C 标准库 free() +} + +cleanup_add(ctx, alloca): + cleanup_list[count++] = alloca + +cleanup_emit(ctx, from_mark): + for j in from_mark..count: + val = load(cleanup_list[j]) // 加载 str 指针 + call free(val) + count = from_mark // 收缩 +``` + +### 3.3 触发点 + +| 位置 | 触发逻辑 | +|------|---------| +| `LET_STMT` | 当 init 表达式类型为 `TYPE_STR` 且节点为 BINARY_EXPR/STRUCT_INIT/CALL_EXPR, 或类型为 `TYPE_STRUCT` (可能含 str 字段) → `cleanup_add` | +| `BLOCK` 出口 | `cleanup_emit(block_mark)` — 块作用域内所有 str 变量 | +| `RETURN_STMT` | `cleanup_emit(0)` *先于* `LLVMBuildRet` — 函数退出前释放所有 | +| 隐式函数结尾 | `cleanup_emit(0)` — 无 return 的函数自动尾部释放 | + +### 3.4 设计权衡 + +| 方面 | 决策 | 理由 | +|------|------|------| +| 槽位上限 | 固定 64 | 正常函数内不会声明超过 64 个 str/struct; 溢出静默忽略 | +| 未追踪字面量 | str 字面量 (如 `"hello"`) 不追踪 | 字面量存储在 .rodata, free 会导致段错误 | +| struct 整体追踪 | struct 一律追踪 (非仅 str 字段) | 简单安全; 未来可按字段粒度优化 | +| `free` 而非 `GC_free` | 直接调用 libc free() | 无 GC 依赖, 链接到 C 运行时 | + +--- + +## 4. 功能清单与成熟度 + +### 4.1 v0.1–v0.3 基线功能 (全部完成) + +- [x] 基本类型: i64, f64, bool, void, str +- [x] 算术/比较/逻辑运算 +- [x] let/let mut 变量, 类型推断 + 可变性检查 +- [x] 控制流: if/else, while, for+range, return +- [x] 函数定义与调用, 递归 +- [x] 内置函数: print_i64/f64/bool/str +- [x] 复合赋值 += -= *= /= (parser 去糖) +- [x] str+str 运行时拼接 +- [x] 注释: // 行注释, /* */ 块注释 + +### 4.2 v0.4 新增功能 (本次实现) + +- [x] **struct 结构体声明** — `struct Name { field: Type, ... }`, 支持嵌套 struct 字段 +- [x] **struct 初始化** — `Point { x: 10, y: 20 }`, 字段名+类型校验, 2-token lookahead 消歧义 +- [x] **字段访问** — `p.x` / `r.top_left.x` 链式访问, PREC_POSTFIX 优先级 +- [x] **struct 类型标注** — `let p: Point = ...`, 类型名传播全流水线 +- [x] **RAII 自动内存管理** — 作用域级 str/struct free, 3 个释放点 (block/return/隐式结尾) +- [x] **LLVM 命名结构体互相引用** — forward decl → body 二阶段创建 + +### 4.3 当前已知限制 (struct 相关) + +| 限制 | 说明 | 严重度 | +|------|------|--------| +| struct 不能作为函数参数 | parser 函数参数解析未处理 struct 类型名 (`token_to_type` 无 Ident 分支) | 中 | +| struct 不能作为函数返回值 | 同上, 返回类型标注仅支持 `i64`/`f64`/`bool`/`void`/`str` | 中 | +| 无 struct 方法 (impl) | struct 只有数据, 无关联函数 | 低 | +| 空 struct (零字段) 未测试 | 边界情况 | 低 | +| struct 字段无默认值 | 不支持 `Point { x: 10, ..Default::default() }` | 低 | + +--- + +## 5. 与 Rust 对标: 缺失功能清单及优先级 + +### P0: 短期 (4/4 完成 ✅) + +| # | 功能 | Rust 启发 | 状态 | +|---|------|----------|------| +| 1 | 复合赋值 += -= *= /= | 复合赋值运算符 | ✅ v0.2 | +| 2 | str+str 运行时拼接 | — | ✅ v0.2 | +| 3 | for 循环 + range | `for i in 0..10 {}` | ✅ v0.3 | +| 4 | **结构体 struct** | struct 具名域 | ✅ v0.4 | + +### P1: 中期 (3–5 天/项) + +| # | 功能 | 改动范围 | 学习价值 | 状态 | +|---|------|---------|---------|------| +| 5 | struct 作为函数参数/返回值 | parser (param/return 类型标注) + sema + codegen | 值传递 vs 引用传递语义 | 待实施 | +| 6 | 数组 + 索引 `[T; N]`, `arr[i]` | lexer/parser/sema/codegen(GEP) | GEP 指针计算 | 待实施 | +| 7 | 枚举 (C 风格) | lexer/parser/sema/codegen(i64) | 为代数类型铺路 | 待实施 | +| 8 | match 表达式 | lexer/parser/sema(穷举) + codegen | 模式匹配核心 | 待实施 | +| 9 | struct 方法 (impl) | parser + sema (self 参数) + codegen | Rust 方法语法糖 | 待实施 | +| 10 | 类型别名 `type Meters = i64` | lexer/parser/sema(展开) | 名称等价 vs 结构等价 | 待实施 | + +### P2: 中后期 (1–2 周/项) + +| # | 功能 | 学习价值 | 状态 | +|---|------|---------|------| +| 11 | 模块系统 `mod` + `use` | 单文件→多文件编译 | 待实施 | +| 12 | 自定义 IR 层 (三地址码/SSA) | 编译器核心抽象 | 待实施 | +| 13 | 泛型 (单态化) | Rust 零成本抽象基石 | 待实施 | +| 14 | 解释器模式 (walk AST) | 快速验证语义 | 待实施 | + +### P3: 长期 (2–4 周/项) + +| # | 功能 | 备注 | 状态 | +|---|------|------|------| +| 15 | trait / 接口 | 需泛型 + 虚表 | 待实施 | +| 16 | Option/Result | 需泛型 + enum | 待实施 | +| 17 | 所有权 / 借用检查 | 极度复杂 | 待实施 | +| 18 | 自举 (L 编译 L) | 终极考验 | 待实施 | + +### Rust 设计哲学吸收进度 + +| Rust 特性 | 状态 | 备注 | +|-----------|------|------| +| 默认不可变 (let vs let mut) | ✅ | 编译期检查, 可变性追踪 | +| 复合赋值 | ✅ | parser desugar 模式 | +| for + range | ✅ | desugar 为 while | +| str 基本支持 | ✅ | 字面量 + 拼接 + RAII 释放 | +| struct 具名域 + 字段访问 | ✅ | LLVM 命名结构体 + extractvalue | +| 表达式 vs 语句 | ❌ | if/match/block 作为表达式 (目前只支持语句) | +| struct 方法 (impl) | ❌ | P1 优先级 | +| 模式匹配 | ❌ | P1 优先级 | +| 代数数据类型 | ❌ | 需泛型 | +| 所有权 / 借用 | ❌ | RAII 雏形已就位, 但无借用检查 | + +--- + +## 6. 测试覆盖分析 + +### 6.1 单元测试 + +| 测试文件 | 测试函数 | 断言数 | 覆盖范围 | +|---------|----------|--------|---------| +| test_lexer.c | 3 | 14 | token 识别, 关键字, 操作符, 注释 | +| test_parser.c | 5 | 15 | 算术, let, if, while, 函数 | +| test_sema.c | 7 | 23 | 类型错误, 未定义变量, let mut, immutable error, str type, str concat | +| test_codegen.c | 4 | 9 | 简单函数, if/else, 二元运算, while | +| **合计** | **19** | **61** | (+2 断言) | + +### 6.2 集成测试 (13 程序, +2) + +| 文件 | 功能 | 版本 | +|------|------|------| +| 01_arithmetic.l | 算术运算 | v0.1 | +| 02_if_else.l | if/else 控制流 | v0.1 | +| 03_recurse.l | 递归函数 | v0.1 | +| 04_fib_recursive.l | 斐波那契 (递归) | v0.1 | +| 05_float.l | 浮点数 | v0.1 | +| 06_mut_while.l | let mut + while | v0.2 | +| 07_hello_str.l | 字符串输出 | v0.2 | +| 08_str_concat.l | 字符串拼接 | v0.2 | +| 09_compound_assign.l | 复合赋值 | v0.2 | +| 10_for_range.l | for range 循环 | v0.3 | +| 11_for_step2.l | for range (变量边界) | v0.3 | +| **12_struct.l** | **结构体声明+初始化+字段访问** | **v0.4** | +| **13_struct_nested.l** | **嵌套结构体+链式访问** | **v0.4** | + +### 6.3 测试缺口 (更新) + +| 缺口 | 严重度 | 说明 | +|------|--------|------| +| struct/sema 无独立单元测试 | **高** | struct 仅在集成测试间接覆盖; 缺 struct 字段类型错误/字段数不匹配/未定义 struct 等测试 | +| codegen struct 路径无单元测试 | **高** | LLVM struct 创建/GEP/extractvalue 无单元级验证 | +| RAII cleanup 无验证 | **高** | cleanup_add/cleanup_emit 逻辑仅在集成测试间接运行, 无法检测内存泄漏 | +| parser struct 错误恢复无测试 | 中 | 缺 `struct {` 不闭合、字段缺类型等错误提示验证 | +| 复合赋值/for 无独立 desugar 测试 | 低 | 仅集成测试间接覆盖 | +| codegen 缺 call/str/compound/for 路径 | 中 | 4 测试仅覆盖基础路径 | + +--- + +## 7. 技术债务与风险 + +| # | 问题 | 状态 | 严重度 | 说明 | +|---|------|------|--------|------| +| 1 | str concat malloc 内存泄漏 | **✅ 已解决** | — | v0.4 RAII 自动 free, cleanup_list 在作用域退出时调用 free() | +| 2 | struct 不可作函数参数/返回 | **新增** | 中 | parser 参数类型解析未扩展 struct 类型名 | +| 3 | struct/sema/codegen 单元测试缺失 | **新增** | 高 | struct 全流水线无单元级测试, 回归风险高 | +| 4 | RAII cleanup 无验证手段 | **新增** | 中 | 无法自动化检测 str 泄漏是否真正修复 | +| 5 | 复合赋值无独立 desugar 测试 | 存留 | 低 | 集成测试间接覆盖 | +| 6 | codegen 测试覆盖率低 (4/12 路径) | 存留 | 中 | call/str/compound/for/return/struct 路径无单元测试 | +| 7 | `system("gcc ...")` 平台绑定 | 存留 | 低 | Windows/MinGW 正常 | +| 8 | LLVM 22 无 mem2reg C API | 已记录 | 低 | codegen.c 注释说明需用 `opt` 工具 | +| 9 | 错误类型分散 (ErrorInfo + ErrorList) | 存留 | 低 | API 不统一 | +| 10 | parser.c `parse_function` 过长 | 存留 | 低 | 约 60 行 | +| 11 | **CHANGELOG 未更新 v0.4** | **新增** | 低 | struct + RAII 无 CHANGELOG 条目 | +| 12 | struct 空字段/边界测试缺失 | **新增** | 低 | 零字段 struct、仅含 struct 字段等 | + +### 风险矩阵 + +| 风险 | 概率 | 影响 | 缓解 | +|------|------|------|------| +| LLVM API 版本升级 break | 中 | 高 | 当前锁定 19.x, 文档记录不兼容点 | +| struct 全流水线无单元测试 → 回归 | 中 | 高 | struct 是核心抽象, 后续改动可能破坏 | +| GCC 链接在非 MinGW 环境失败 | 中 | 中 | 可接受链接器参数或使用 LLVM lld | +| RAII cleanup_list 溢出 (64 槽) | 低 | 低 | 正常函数不会声明 64+ struct/str | +| Arena 内存耗尽 (8MB) | 极低 | 中 | 巨型程序才触发 | + +--- + +## 8. 推荐开发路线图 + +### v0.5 目标 (下一迭代, 2–3 天) + +| 优先级 | 功能 | 预计工时 | 依赖/备注 | +|--------|------|---------|----------| +| **P1** | **struct 作为函数参数/返回值** | 1 天 | 补完 struct 可用性, parser param/return 类型标注扩展 | +| **P1** | **struct/sema 单元测试** | 0.5 天 | 覆盖: 字段类型错误, 字段数不匹配, 未定义 struct, 嵌套 struct | +| **P1** | **codegen struct 单元测试** | 0.5 天 | LLVM struct 创建, GEP, extractvalue | +| P2 | CHANGELOG 更新 v0.4 | 0.5 天 | struct + RAII 条目 | + +### v0.6 目标 + +| 优先级 | 功能 | 预计工时 | +|--------|------|---------| +| P1 | 数组 + 索引 `[T; N]` | 2 天 | +| P1 | struct 方法 (impl) | 2 天 | +| P1 | 枚举 (C 风格) | 1 天 | + +### 长期方向 + +| 优先级 | 功能 | 预计工时 | +|--------|------|---------| +| P1 | match 表达式 | 2–3 天 | +| P1 | 类型别名 | 0.5 天 | +| P2 | 自定义 IR 层 | 3–5 天 | +| P2 | 模块系统 | 3–5 天 | +| P3 | 泛型 (单态化) | 5–7 天 | +| P3 | trait / 接口 | 5–7 天 | + +--- + +## 9. 代码质量评估 + +### 9.1 模块内聚度 + +| 模块 | 内聚度 | 评价 | +|------|--------|------| +| lexer | 高 | 纯函数接口, 新增 TOK_STRUCT/TOK_DOT 一字插入 | +| parser | 高 | struct 解析逻辑独立为 `parse_struct_decl`/`parse_struct_init`, 未侵入现有流程; 2-token lookahead 消歧义清晰 | +| ast | 高 | 3 新节点遵循工厂函数模式, TypeInfo.struct_name 向后兼容 (NULL for 非 struct) | +| sema | 中高 | struct 检查独立分支, struct_name 传播链完整; 但 analyze_expr/analyze_node switch 需关注增长 | +| codegen | 中 | cleanup_list 机制与现有 `codegen_stmt` 耦合; struct 类型映射独立为 `struct_table` 查找 | +| driver | 中 | 无变更 | +| util | 高 | 无变更 | + +### 9.2 新增代码风格 + +- cleanup_list 64 槽硬编码合理 (以现有代码规模) +- struct_name 传播链使用 `const char*` (arena 分配), 零拷贝负担 +- `extractvalue` 而非 `GEP+load` 的选择与 AST 值语义一致 +- 2-token lookahead 是 parser 中唯一的非 LL(1) 点, 注释说明了消歧义逻辑 + +### 9.3 架构债务 (新增) + +| 债务 | 说明 | +|------|------| +| analyze_expr 持续膨胀 | 当前 200+ 行 switch, 新增 FIELD_ACCESS/STRUCT_INIT 两个 case; 考虑拆分为子函数 | +| codegen_stmt 单一大函数 | 当前 ~280 行, cleanup 逻辑嵌入 let/block/return 三个 case | +| struct_name 空指针传播 | 多处 `if (xxx && xxx->struct_name)` 检查, 若遗漏则静默 fallback | + +--- + +## 10. 度量汇总 (对比) + +| 指标 | v0.3 基线 (382cd08) | **v0.4 (1d4fb27)** | Δ | +|------|---------------------|---------------------|----| +| 源文件数 (.c) | 12 | 12 | 0 | +| 头文件数 (.h) | 12 | 12 | 0 | +| 实现代码 | 1,688 行 | **2,221 行** | +533 | +| 头文件 | 308 行 | **323 行** | +15 | +| 测试代码 | 330 行 | **361 行** | +31 | +| 总代码量 | 2,326 行 | **2,905 行** | +579 | +| Token 类型 | 37 | **39** | +2 | +| AST 节点类型 | 15 | **18** | +3 | +| TypeKind | 7 | **8** | +1 | +| SymbolKind | 3 | **4** | +1 | +| 内置函数 | 4 | 4 | 0 | +| C 运行时依赖 | printf, malloc, strlen, memcpy | **+free** | +1 | +| 单元测试函数 | 19 | 19 | 0 | +| 单元测试断言 | 59 | **61** | +2 | +| 集成测试程序 | 11 | **13** | +2 | +| P0 完成度 | 3/4 (75%) | **4/4 (100%)** | ✅ | +| P0 遗留 | struct | **无** | — | +| 编译产物 | 6 个 .exe | 6 个 .exe | 0 | +| Git 提交数 | 11 | **13** | +2 | + +--- + +*本报告由 Codex 自动生成于 2026-06-05 13:08。自上次代码基线 `382cd08` 后有 2 个源代码提交 (+1882/-54 行): struct 结构体 (P0 收官) 和 RAII 自动内存管理 (作用域级 free)。P0 全线完成, struct 集成度已完成但缺函数参数/返回值支持。下一优先级: struct 参数/返回值 + struct 单元测试覆盖。* diff --git a/docs/architecture-improvements.md b/docs/architecture-improvements.md new file mode 100644 index 0000000..6600c0f --- /dev/null +++ b/docs/architecture-improvements.md @@ -0,0 +1,166 @@ +# L Language 架构改进路线图 + +> 2026-06-05 | 基于架构审查报告 + +--- + +## 1. TypeKind 耦合问题 (HIGH) + +**当前状态**: 新增一个类型需要修改 7+ 个文件。 + +``` +TypeKind 修改 → l_lang.h (枚举 + type_name switch) + → token.h (TokenKind 枚举) + → token.c (tok_is_type) + → lexer.c (KW 宏) + → parser.c (is_type_token + token_to_type) + → sema.c (promote, is_numeric, 各运算符类型检查) + → codegen.c (to_llvm_type, to_llvm_const) +``` + +**建议方案**: 引入类型注册表。 + +```c +// types.h +typedef struct { + const char* name; // "i64" + TypeKind kind; // TYPE_I64 + TokenKind tok; // TOK_I64 + LLVMTypeRef (*to_llvm)(LLVMContextRef); // 代码生成回调 + bool is_numeric; // 可用于算术 + bool is_comparable; // 可用于比较 + bool promote_to; // 可隐式转换的目标类型 +} TypeDef; + +// 集中定义所有类型 +static const TypeDef TYPES[] = { + { "i64", TYPE_I64, TOK_I64, /*...*/, .is_numeric=true, .is_comparable=true }, + { "f64", TYPE_F64, TOK_F64, /*...*/, .is_numeric=true, .is_comparable=true }, + { "bool", TYPE_BOOL, TOK_BOOL, /*...*/, .is_comparable=true }, + { "str", TYPE_STR, TOK_STR, /*...*/ }, + { "void", TYPE_VOID, TOK_VOID, /*...*/ }, +}; +``` + +加新类型只需往 `TYPES[]` 数组加一行。预计改动量: 新增 types.h/c,各模块改为查表。工时: 2 天。 + +--- + +## 2. AST Visitor 模式缺失 (HIGH) + +**当前状态**: 每个模块手写 switch-case 遍历 AST。新 AST 节点需要修改所有 switch。 + +``` +新节点 AstNewNode → sema.c:analyze_expr() switch + → sema.c:analyze_node() switch + → codegen.c:codegen_expr() switch + → codegen.c:codegen_stmt() switch + 至少 4 处 +``` + +**建议方案**: 宏驱动的 Visitor。 + +```c +// ast_visitor.h +#define AST_VISIT_CASES(V) \ + V(AST_PROGRAM) V(AST_FUNCTION) V(AST_PARAMETER) \ + V(AST_BLOCK) V(AST_LET_STMT) V(AST_ASSIGN_STMT) \ + ... + +// sema.c +switch (node->kind) { + #define V(k) case k: analyze_##k(node, scope, errors); break; + AST_VISIT_CASES(V) + #undef V +} +``` + +更完整的方案: 传函数指针的 `ast_walk(AstNode*, Visitor* v)` 接口。工时: 3 天。 + +--- + +## 3. SourceLoc 抽象缺失 (HIGH) + +**当前状态**: 每个 AST 工厂函数有独立的 `int line, int col` 参数。 + +```c +// 当前: 参数膨胀 +AstNode* ast_make_let(void* alloc, const char* name, + TypeKind annot_type, bool has_type_annot, + bool is_mut, AstNode* init, int line, int col); // 8 个参数 + +// 建议: SourceLoc 聚合 +typedef struct { int line; int col; const char* file; } SourceLoc; + +AstNode* ast_make_let(void* alloc, const char* name, + TypeKind annot_type, bool has_type_annot, + bool is_mut, AstNode* init, SourceLoc loc); // 7 个参数 +``` + +额外收益: 错误报告可以统一使用 `SourceLoc`,不再分别传 file/line/col。工时: 1 天。 + +--- + +## 4. 去糖逻辑应独立 (MEDIUM) + +**当前状态**: for 循环和复合赋值的去糖逻辑直接在 `parser.c` 中。 + +``` +parser.c:parse_statement() + ├── TOK_FOR → 直接构造 let mut + while + assign AST (~60行) + └── TOK_IDENT += → 直接构造 assign(binary) AST (~25行) +``` + +**建议方案**: 独立 `parser_desugar.c` pass。 + +```c +// parser_desugar.h +AstNode* desugar(AstNode* raw_ast, Arena* a, ErrorInfo* error); + +// 职责: 将 FOR_STMT、COMPOUND_ASSIGN 等语法糖节点 +// 转换为已有原语节点 (let mut + while + assign 等) +// 在 sema 之前运行 +``` + +架构收益: +- parser.c 保持纯解析逻辑,缩短 ~85 行 +- 去糖逻辑可独立测试 +- 新增语法糖只需改 desugar.c,不动 parser + +工时: 1 天。 + +--- + +## 5. tok_is_type 重复定义 (MEDIUM) + +**当前状态**: + +```c +// token.c:34 +bool tok_is_type(TokenKind kind) { + return kind == TOK_I64 || kind == TOK_F64 || kind == TOK_BOOL || kind == TOK_VOID; +} + +// parser.c:236 — 完全相同的逻辑 +static bool is_type_token(TokenKind k) { + return k == TOK_I64 || k == TOK_F64 || k == TOK_BOOL || k == TOK_VOID || k == TOK_STR; +} +``` + +两处不同步 (parser 多了 TOK_STR,token 少了 TOK_STR)。若加 TOK_U8 很可能只改一处。 + +**修复**: 删除 `is_type_token()`,统一使用 `tok_is_type()`,并在 token.c 中补上 TOK_STR。工时: 10 分钟。 + +--- + +## 6. 实施优先级 + +| 优先级 | 项目 | 工时 | 收益 | +|--------|------|------|------| +| 1 | tok_is_type 统一 | 10 分钟 | 消除 bug 源 | +| 2 | SourceLoc 抽象 | 1 天 | 减少参数,可加文件名 | +| 3 | 去糖独立 pass | 1 天 | parser 解耦 | +| 4 | TypeDef 注册表 | 2 天 | 新类型本地化 | +| 5 | AST Visitor | 3 天 | 新节点安全 | + +建议 v0.5 按 1→2→3 顺序,v0.6 做 4→5。 diff --git a/src/parser/parser.c b/src/parser/parser.c index 66e3a2d..559bf4a 100644 --- a/src/parser/parser.c +++ b/src/parser/parser.c @@ -238,10 +238,6 @@ static AstNode* parse_expr(Parser* p, ErrorInfo* error) { } // === 类型工具 === -static bool is_type_token(TokenKind k) { - return k == TOK_I64 || k == TOK_F64 || k == TOK_BOOL || k == TOK_STR || k == TOK_VOID; -} - static TypeKind token_to_type(TokenKind k) { switch (k) { case TOK_I64: return TYPE_I64; case TOK_F64: return TYPE_F64; case TOK_BOOL: return TYPE_BOOL; case TOK_STR: return TYPE_STR; @@ -264,7 +260,7 @@ static AstNode* parse_struct_decl(Parser* p, ErrorInfo* error) { const Token* ftype = advance(p); TypeKind field_kind; const char* field_struct_name = NULL; - if (is_type_token(ftype->kind)) { + if (tok_is_type(ftype->kind)) { field_kind = token_to_type(ftype->kind); } else if (ftype->kind == TOK_IDENT) { field_kind = TYPE_STRUCT; @@ -327,11 +323,11 @@ static AstNode* parse_statement(Parser* p, ErrorInfo* error) { const char* struct_type_name = NULL; if (match(p, TOK_COLON)) { const Token* type_tok = advance(p); - if (!is_type_token(type_tok->kind) && type_tok->kind != TOK_IDENT) { + if (!tok_is_type(type_tok->kind) && type_tok->kind != TOK_IDENT) { error->message = "无效的类型标注"; error->filename = p->filename; error->line = type_tok->line; error->col = type_tok->col; return NULL; } - if (is_type_token(type_tok->kind)) { + if (tok_is_type(type_tok->kind)) { annot_type = token_to_type(type_tok->kind); } else { // struct 类型名 @@ -516,7 +512,7 @@ static AstNode* parse_function(Parser* p, ErrorInfo* error) { if (!pname) return NULL; if (!expect(p, TOK_COLON, error, "缺少 ':'")) return NULL; const Token* ptype = advance(p); - if (!is_type_token(ptype->kind)) { + if (!tok_is_type(ptype->kind)) { error->message = "无效的参数类型"; error->filename = p->filename; error->line = ptype->line; error->col = ptype->col; return NULL; } @@ -532,7 +528,7 @@ static AstNode* parse_function(Parser* p, ErrorInfo* error) { TypeKind ret = TYPE_VOID; if (match(p, TOK_ARROW)) { const Token* rt = advance(p); - if (!is_type_token(rt->kind)) { + if (!tok_is_type(rt->kind)) { error->message = "无效的返回类型"; error->filename = p->filename; error->line = rt->line; error->col = rt->col; return NULL; }