# 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 单元测试覆盖。*