refactor: tok_is_type 统一 + 架构改进文档
- parser.c: 删除重复的is_type_token, 统一使用token.c的tok_is_type - docs/architecture-improvements.md: TypeKind解耦/Visitor/SourceLoc/去糖方案
This commit is contained in:
@@ -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 单元测试覆盖。*
|
||||
@@ -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。
|
||||
Reference in New Issue
Block a user