Files
l-language/docs/analysis/architecture-analysis-report-2026-06-05-1308.md
T
Serendipity 4046ab1875 refactor: tok_is_type 统一 + 架构改进文档
- parser.c: 删除重复的is_type_token, 统一使用token.c的tok_is_type
- docs/architecture-improvements.md: TypeKind解耦/Visitor/SourceLoc/去糖方案
2026-06-05 13:13:51 +08:00

20 KiB
Raw Blame History

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 支持结构体间互相引用 (如 RectPoint)
结构体值语义 字段访问通过 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.1v0.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 新增功能 (本次实现)

  • struct 结构体声明struct Name { field: Type, ... }, 支持嵌套 struct 字段
  • struct 初始化Point { x: 10, y: 20 }, 字段名+类型校验, 2-token lookahead 消歧义
  • 字段访问p.x / r.top_left.x 链式访问, PREC_POSTFIX 优先级
  • struct 类型标注let p: Point = ..., 类型名传播全流水线
  • RAII 自动内存管理 — 作用域级 str/struct free, 3 个释放点 (block/return/隐式结尾)
  • 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: 中期 (35 天/项)

# 功能 改动范围 学习价值 状态
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: 中后期 (12 周/项)

# 功能 学习价值 状态
11 模块系统 mod + use 单文件→多文件编译 待实施
12 自定义 IR 层 (三地址码/SSA) 编译器核心抽象 待实施
13 泛型 (单态化) Rust 零成本抽象基石 待实施
14 解释器模式 (walk AST) 快速验证语义 待实施

P3: 长期 (24 周/项)

# 功能 备注 状态
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 目标 (下一迭代, 23 天)

优先级 功能 预计工时 依赖/备注
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 表达式 23 天
P1 类型别名 0.5 天
P2 自定义 IR 层 35 天
P2 模块系统 35 天
P3 泛型 (单态化) 57 天
P3 trait / 接口 57 天

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