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 编译流水线
无架构变化。 流水线仍为 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 全流水线实现
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 实现机制
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 基线功能 (全部完成)
- 基本类型: i64, f64, bool, void, str
- 算术/比较/逻辑运算
- let/let mut 变量, 类型推断 + 可变性检查
- 控制流: if/else, while, for+range, return
- 函数定义与调用, 递归
- 内置函数: print_i64/f64/bool/str
- 复合赋值 += -= *= /= (parser 去糖)
- str+str 运行时拼接
- 注释: // 行注释, /* */ 块注释
4.2 v0.4 新增功能 (本次实现)
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 单元测试覆盖。