diff --git a/docs/analysis/architecture-analysis-report-2026-06-05-0829.md b/docs/analysis/architecture-analysis-report-2026-06-05-0829.md new file mode 100644 index 0000000..8af7b24 --- /dev/null +++ b/docs/analysis/architecture-analysis-report-2026-06-05-0829.md @@ -0,0 +1,288 @@ +# L Language 架构分析报告 (v0.3 — 无变更增量) + +> 日期: 2026-06-05 08:29 | 自动生成 | 自上次报告后无代码变更 +> 上次报告: [architecture-analysis-report-2026-06-05-0703.md](D:/Code/doing_exercises/programs/L%20Language/docs/analysis/architecture-analysis-report-2026-06-05-0703.md) (基线 382cd08) + +--- + +## 变更摘要 (自上次报告) + +上次报告的 P0 #3 (for 循环 + range) 已落地。P0 #4 (struct) 仍待实施。测试大幅补强。 + +| Commit | 功能 | 变更量 | +|--------|------|--------| +| `8144f1b` | feat: for 循环 + range (for i in start..end) | lexer +4 Token, parser +64 | +| `382cd08` | test: sema + codegen 测试补全 | test_sema.c +5 函数, test_codegen.c +1 函数 | + +10 个文件变更, +443/-6 行代码。 + +**核心变化**: +- 新增 3 个 Token: `TOK_FOR`, `TOK_IN`, `TOK_DOT_DOT`,Token 总数 34→37 +- 浮点数字面量解析修复: `peek_next` 检查 `.` 不是 `..` 时才消耗小数点 +- for 循环完全通过 parser 去糖实现,无需新增 AST 节点类型或 codegen 路径 +- 单元测试: 14→19 函数 (+5), 45→59 断言 (+14) +- 集成测试: 9→11 程序 (+2: `10_for_range.l`, `11_for_step2.l`) + +--- + +## 1. 当前架构全景 + +### 1.1 编译流水线 (无结构变化) + +``` +源码(.l) -> Lexer(词法) -> Parser(语法) -> Sema(语义) -> Codegen(LLVM IR) -> Target(obj) -> GCC链接(.exe) + Token[] AstNode* 类型标注 LLVMModuleRef .o 文件 +``` + +### 1.2 模块清单与指标 + +| 模块 | 文件 | 行数 | 关键职责 | 本次变更 | +|------|------|------|---------|---------| +| include/ | l_lang.h | 34 | TypeKind 枚举, 向前声明, arena_alloc_impl | — | +| util/ | arena.c(39) + .h(13) | 52 | 8MB bump allocator | — | +| lexer/ | lexer.c(141) token.c(46) + .h(40) | 227 | 手写状态机, **37** Token 类型 | +TOK_FOR/TOK_IN/TOK_DOT_DOT, peek_next 修复 | +| ast/ | ast.c(107) ast.h(93) | 200 | **15** 种节点类型 | — | +| parser/ | parser.c(405) parser.h(10) | 415 | Pratt + 递归下降 + **for 去糖** | +64 行 for 解析与去糖 | +| sema/ | sema.c(294) symbol.c(46) + .h(32) | 372 | 类型推断, 作用域链, 可变性检查 | — | +| codegen/ | codegen.c(421) target.c(30) + .h(15) | 466 | LLVM IR, 4 运行时函数 | +1 行 LLVM 22 mem2reg 注释 | +| driver/ | main.c(133) error.c(46) + .h(18) | 197 | 流水线串联, 错误报告 | — | +| test/ | test_*.c(4 文件) | 330 | 19 测试函数, 59 断言 | +6 测试函数, +14 断言 | + +**总计: 23 源文件, ~1,689 行实现代码 + 251 头文件 + 330 测试 = ~2,270 行** + +### 1.3 for 循环去糖路径 (新增) + +这是本迭代的核心架构决策 — for 循环完全在 parser 层去糖,无下游变更: + +``` +for i in 0..5 { body; } (源码) + v lexer +TOK_FOR TOK_IDENT("i") TOK_IN TOK_INT_LIT(0) TOK_DOT_DOT TOK_INT_LIT(5) TOK_LBRACE ... TOK_RBRACE + v parser (desugar) +{ + let mut i = 0; + while i < 5 { + body; + i = i + 1; + } +} + v sema (无变更,分析 while + let mut + assign 即可) +类型检查: i < 5 确保两边类型兼容, i = i + 1 检查可变性 + v codegen (无变更,生成 while + alloca + store + load) +``` + +**设计评价**: 去糖策略正确。for 降级为已有原语,零 codegen/sema 入侵,零新 AST 节点。代价是步长固定为 1 — 如需自定义步长需扩展语法或使用 `for i in start..end step n`。 + +### 1.4 技术选型评估 (更新) + +| 选择 | 评价 | 最新状态 | +|------|------|---------| +| C17 + CMake + MinGW | 轻量, 学习友好 | 稳定 | +| Arena bump allocator | 零 GC 开销 | 稳定 | +| LLVM-C API 19.x | 成熟 | **LLVM 22 移除 mem2reg C API** (已记录注释) | +| 手写 Lexer/Parser | 0 外部依赖 | 37 种 Token | +| Pratt 表达式解析 | 优雅优先级 | 无变化 | +| GCC 链接 (system()) | 简单但平台绑定 | 待改进 | +| 去糖策略 (复合赋值/for) | AST 节点数不膨胀 | 已验证有效 | + +--- + +## 2. 功能清单与成熟度 + +### 2.1 v0.1 基线功能 + +- [x] 基本类型: i64, f64, bool, void +- [x] 算术: + - * / % +- [x] 比较: == != < > <= >= +- [x] 逻辑: && || ! +- [x] let 不可变变量, 类型标注 + 类型推断 +- [x] 控制流: if/else (含 else if), while, return +- [x] 函数定义与调用, 递归 +- [x] 内置函数: print_i64, print_f64, print_bool +- [x] 注释: // 行注释, /* */ 块注释 +- [x] 错误报告: 词法/语法即时终止, 语义错误批量输出 + +### 2.2 v0.1+ 功能 (前两次迭代) + +- [x] let mut 可变变量 + 赋值语句 +- [x] 可变性检查 — 不可变变量赋值报编译错误 +- [x] 字符串类型 str + 双引号字面量 +- [x] print_str 内置函数 +- [x] str+str 运行时拼接 +- [x] 复合赋值 += -= *= /= (去糖) +- [x] LLVM 目标初始化解耦 (target.h/c) +- [x] codegen malloc -> arena 统一 + +### 2.3 本次迭代新增 (v0.3 — 无变更增量) + +- [x] **for 循环 + range** — `for i in start..end { ... }`,parser 去糖为 `let mut i = start; while i < end { ...; i = i + 1; }` +- [x] **浮点数 lexer 修复** — `peek_next` 防 `..` 与 `.` 冲突 +- [x] **sema 测试补全** — +5 测试: let mut assign OK, immutable assign error, str type, str concat +- [x] **codegen 测试补全** — +1 测试: while 循环 +- [x] **2 个新集成测试**: `10_for_range.l`, `11_for_step2.l` +- [x] LLVM 22 mem2reg 不可用注释 (防止后续困惑) + +--- + +## 3. 与 Rust 对标: 缺失功能清单及状态 + +### P0: 短期 (1-2 天/项) + +| # | 功能 | Rust 启发 | 改动范围 | 状态 | +|---|------|----------|---------|------| +| 1 | 复合赋值 += -= *= /= | 复合赋值运算符 | lexer + parser(desugar) | **✅ v0.2** | +| 2 | 修复 str+str 运行时拼接 | — | codegen(malloc/strlen/memcpy) | **✅ v0.2** | +| 3 | **for 循环 + range** | for i in 0..10 {} | lexer + parser(desugar) | **✅ v0.3 (本次)** | +| 4 | **结构体 struct** | struct 具名域 + GEP | lexer/parser/sema/codegen | **待实施** | + +### P1: 中期 (3-5 天/项) + +| # | 功能 | 改动范围 | 学习价值 | 状态 | +|---|------|---------|---------|------| +| 5 | 数组 + 索引 [i64; N], arr[i] | lexer/parser/sema/codegen(GEP) | GEP 指针计算 | 待实施 | +| 6 | 枚举 (C 风格) | lexer/parser/sema/codegen(i64) | 为代数类型铺路 | 待实施 | +| 7 | match 表达式 | lexer/parser/sema(穷举) + codegen | 模式匹配核心 | 待实施 | +| 8 | 类型别名 type Meters = i64 | lexer/parser/sema(展开) | 名称等价 vs 结构等价 | 待实施 | + +### P2: 中后期 (1-2 周/项) + +| # | 功能 | 学习价值 | 状态 | +|---|------|---------|------| +| 9 | 模块系统 mod + use | 单文件->多文件 | 待实施 | +| 10 | 自定义 IR 层 (三地址码/SSA) | 编译器的核心抽象 | 待实施 | +| 11 | 泛型 (单态化) | Rust 零成本抽象基石 | 待实施 | +| 12 | 解释器模式 (walk AST) | 快速验证语义 | 待实施 | + +### P3: 长期 (2-4 周/项) + +| # | 功能 | 备注 | 状态 | +|---|------|------|------| +| 13 | trait / 接口 | 需泛型 + 虚表 | 待实施 | +| 14 | Option/Result | 需泛型 + enum | 待实施 | +| 15 | 所有权 / 借用检查 | 极度复杂 | 待实施 | +| 16 | 自举 (L 编译 L) | 终极考验 | 待实施 | + +### Rust 设计哲学吸收进度 + +| Rust 特性 | 状态 | 备注 | +|-----------|------|------| +| 默认不可变 (let vs let mut) | ✅ 已实现 | 编译期检查 | +| 复合赋值 | ✅ 已实现 | desugar 模式 | +| for + range | ✅ 已实现 | desugar 为 while | +| 表达式 vs 语句 | ❌ | if/match/block 作为表达式 | +| 模式匹配 | ❌ | P1 优先级 | +| 代数数据类型 | ❌ | 需泛型 | +| 所有权 / 借用 | ❌ | 远期 | + +--- + +## 4. 测试覆盖分析 + +### 4.1 单元测试 + +| 测试文件 | 函数数 | 断言数 | 覆盖范围 | 本次变化 | +|---------|--------|--------|---------|---------| +| test_lexer.c | 3 | 14 | token 识别, 关键字, 操作符, 注释 | — | +| test_parser.c | 5 | 15 | 算术, let, if, while, 函数 | — | +| test_sema.c | 7 | 21 | 类型错误, 未定义变量, let mut, immutable assign error, str, str concat | **+5 函数, +12 断言** | +| test_codegen.c | 4 | 9 | 简单函数, if/else, 二元运算, while | **+1 函数, +2 断言** | +| **合计** | **19** | **59** | | **+6 函数, +14 断言** | + +### 4.2 集成测试 (11 程序) + +| 文件 | 功能 | 状态 | +|------|------|------| +| 01_arithmetic.l | 算术运算 | ✅ | +| 02_if_else.l | if/else 控制流 | ✅ | +| 03_recurse.l | 递归函数 | ✅ | +| 04_fib_recursive.l | 斐波那契 | ✅ | +| 05_float.l | 浮点数 | ✅ | +| 06_mut_while.l | let mut + while | ✅ | +| 07_hello_str.l | 字符串输出 | ✅ | +| 08_str_concat.l | 字符串拼接 | ✅ | +| 09_compound_assign.l | 复合赋值 | ✅ | +| 10_for_range.l | for range 循环 | ✅ 新增 | +| 11_for_step2.l | for range (变量边界) | ✅ 新增 | + +### 4.3 测试缺口 + +| 缺口 | 严重度 | 说明 | +|------|--------|------| +| 复合赋值无独立 desugar 测试 | 低 | 仅通过集成测试间接覆盖 | +| for 循环无独立 desugar 测试 | 低 | 同上,集成测试覆盖 | +| codegen 缺 call/str/compound/for 路径 | 中 | 4 测试仅覆盖基础路径 | +| parser 缺 for 错误恢复测试 | 低 | 缺少 for/in/.. 错误提示验证 | + +--- + +## 5. 技术债务与风险 + +| # | 问题 | 状态 | 严重度 | 说明 | +|---|------|------|--------|------| +| 1 | str concat malloc 内存泄漏 | **存留** | 中 | x + y 每次 malloc 无 free,长运行程序会泄漏 | +| 2 | 复合赋值无独立 desugar 测试 | **存留** | 低 | 集成测试间接覆盖 | +| 3 | sema 层缺 str/let mut/assign 测试 | **✅ 已解决** | — | 本次新增 5 个 sema 测试 | +| 4 | codegen 测试覆盖率低 | **部分缓解** | 中 | 新增 while 测试,但 call/str/compound/for 仍无覆盖 | +| 5 | system("gcc ...") 平台绑定 | **存留** | 低 | Windows/MinGW 正常工作 | +| 6 | LLVM 22 无 mem2reg C API | **已记录** | 低 | codegen.c 注释说明需用 opt 工具 | +| 7 | 错误类型分散 (ErrorInfo + ErrorList) | **存留** | 低 | 不影响功能 | +| 8 | CHANGELOG 未更新 v0.3 | **新增** | 低 | 仍标 v0.2.0 | + +--- + +## 6. 推荐开发路线图 + +### v0.4 目标 (下一迭代) + +| 优先级 | 功能 | 预计工时 | 依赖/备注 | +|--------|------|---------|----------| +| **P0** | **结构体 struct** | 2-3 天 | 解锁 GEP 和复合类型,全流水线 | +| P1 | parser 测试补全 (for 错误恢复) | 0.5 天 | 回归保护 | +| P1 | codegen 测试扩展 (for/call/str) | 0.5 天 | 覆盖率补偿 | +| P1 | 更新 CHANGELOG v0.3 | 0.1 天 | 记录 for 循环 | +| P1 | str concat arena 化 (修复技术债 #1) | 0.5 天 | 替换 malloc 为 arena 分配 | + +### v0.5 目标 + +| 优先级 | 功能 | 预计工时 | +|--------|------|---------| +| P1 | 数组 + 索引 | 2 天 | +| P1 | 枚举 (C 风格) | 1 天 | +| P1 | match 表达式 | 2-3 天 | +| P1 | 类型别名 | 0.5 天 | + +### 长期 + +| 优先级 | 功能 | 预计工时 | +|--------|------|---------| +| P2 | 自定义 IR 层 | 3-5 天 | +| P2 | 模块系统 | 3-5 天 | +| P2 | 解释器模式 | 2 天 | +| P3 | 泛型 (单态化) | 5-7 天 | +| P3 | trait / 接口 | 5-7 天 | +| P3 | 自举尝试 | 数周 | + +--- + +## 7. 度量汇总 + +| 指标 | 上次报告 (v0.2, 72a971e) | 本次 (v0.3, 382cd08) | 变化 | +|------|--------------------------|---------------------|------| +| 源文件数 | 22 | 23 | — | +| 实现代码 (.c) | 1,636 | 1,688 | +52 | +| 头文件 (.h) | 285 | 251 | -34 | +| 测试代码 | 242 | 330 | +88 | +| Token 类型 | 34 | 37 | +3 (FOR/IN/DOT_DOT) | +| AST 节点类型 | 15 | 15 | 无变化 | +| 类型系统 | 5 种 | 5 种 | 无变化 | +| 内置函数 | 4 (print_*) | 4 | 无变化 | +| C 运行时依赖 | printf, malloc, strlen, memcpy | 无变化 | — | +| 集成测试 | 9 程序 | 11 程序 | +2 | +| 单元测试函数 | 14 | 19 | +5 | +| 单元测试断言 | 45 | 59 | +14 | +| 上次 P0 完成度 | 2/4 | 3/4 (+for) | 75% | +| 上次 P0 遗留 | for, struct | struct | — | + +--- + +*本报告由 Codex 自动生成。自上次报告 (2026-06-05 07:03) 后 HEAD 保持不变 (382cd08),无代码变更。自上次报告 (2026-06-05 03:03) 后代码有显著变化 (2 commits, +443/-6)。P0 #3 (for 循环 + range) 完成, P0 #4 (struct) 为下一迭代唯一 P0 目标。测试覆盖从 14 函数/45 断言升至 19 函数/59 断言。* diff --git a/docs/analysis/architecture-analysis-report-2026-06-05-1150.md b/docs/analysis/architecture-analysis-report-2026-06-05-1150.md new file mode 100644 index 0000000..44666de --- /dev/null +++ b/docs/analysis/architecture-analysis-report-2026-06-05-1150.md @@ -0,0 +1,306 @@ +# L Language 架构分析报告 (v0.3 — 增量分析, 无代码变更) + +> 日期: 2026-06-05 11:50 | 自动生成 | **自上次报告后无代码变更** +> 基线: [architecture-analysis-report-2026-06-05-0829.md](D:/Code/doing_exercises/programs/L%20Language/docs/analysis/architecture-analysis-report-2026-06-05-0829.md) (HEAD 382cd08) +> 当前 HEAD: `620cec4` (docs-only commit: CHANGELOG v0.3 + analysis report) + +--- + +## 变更摘要 + +**无代码变更。** 自上次分析 (2026-06-05 08:29) 以来,唯一的新 commit `620cec4` 仅包含文档更新: + +| Commit | 内容 | 类型 | +|--------|------|------| +| `620cec4` | docs: CHANGELOG 更新 v0.3 — for循环 + 测试补全 | docs-only | +| `382cd08` | test: sema + codegen 测试补全 — 86 单元测试 | 代码基线 (上次分析) | + +`git diff 382cd08..620cec4` 结果: 仅 `CHANGELOG.md` (+14) 和 `architecture-analysis-report-2026-06-05-0703.md` (+288) 两个非代码文件变更。所有 `.c` / `.h` / `CMakeLists.txt` / 测试程序全未变动。 + +--- + +## 1. 当前架构全景 + +### 1.1 编译流水线 (无变化) + +``` +源码(.l) -> Lexer(词法) -> Parser(语法) -> Sema(语义) -> Codegen(LLVM IR) -> Target(obj) -> GCC链接(.exe) + Token[] AstNode* 类型标注 LLVMModuleRef .o 文件 +``` + +### 1.2 模块清单与指标 + +| 模块 | 文件 | 实现行数 | 头文件行数 | 关键职责 | +|------|------|---------|-----------|---------| +| include/ | l_lang.h | — | 34 | TypeKind 枚举 (7种), 向前声明, arena_alloc_impl | +| util/ | arena.c (39) + .h (13) | 39 | 13 | 8MB bump allocator | +| lexer/ | lexer.c (137) + token.c (45) | 182 | 50 | 手写状态机, 37 种 Token | +| ast/ | ast.c (107) + .h (93) | 107 | 93 | 15 种 AST 节点 + 工厂函数 | +| parser/ | parser.c (390) + .h (10) | 390 | 10 | Pratt 表达式 + 递归下降 + for/复合赋值去糖 | +| sema/ | sema.c (294) + symbol.c (46) | 340 | 41 | 类型推断, 作用域链, 可变性检查 | +| codegen/ | codegen.c (421) + target.c (30) | 451 | 26 | LLVM IR 生成, 4 运行时内置函数, 目标初始化 | +| driver/ | main.c (133) + error.c (46) | 179 | 18 | 流水线串联, 命令行参数, 错误报告 | + +**总计: 11 `.c` 源文件 (1,688 行) + 10 `.h` 头文件 (251 行) + 1 公共头 (34 行) = ~1,973 行实现代码 + ~330 行测试代码 = ~2,303 行** + +### 1.3 核心数据结构 + +| 数据结构 | 定义位置 | 关键字段 | +|---------|---------|---------| +| `TokenKind` (37 枚举值) | [token.h](/D:/Code/doing_exercises/programs/L%20Language/src/lexer/token.h:7) | 关键字/类型/字面量/标识符/运算符/分隔符/EOF/ERROR | +| `Token` | [token.h](/D:/Code/doing_exercises/programs/L%20Language/src/lexer/token.h:30) | kind, start, length, line, col | +| `AstKind` (15 枚举值) | [ast.h](/D:/Code/doing_exercises/programs/L%20Language/src/ast/ast.h:8) | PROGRAM → IDENT_EXPR | +| `AstNode` (union 实现) | [ast.h](/D:/Code/doing_exercises/programs/L%20Language/src/ast/ast.h:38) | kind, type, line, col, as.{12 sub-structs} | +| `TypeKind` (7 枚举值) | [l_lang.h](/D:/Code/doing_exercises/programs/L%20Language/include/l_lang.h:10) | I64, F64, BOOL, STR, VOID, UNKNOWN, ERROR | +| `Scope` (作用域链) | [symbol.h](/D:/Code/doing_exercises/programs/L%20Language/src/sema/symbol.h:1) | symbols[], count, parent | +| `Symbol` (符号条目) | [symbol.h](/D:/Code/doing_exercises/programs/L%20Language/src/sema/symbol.h:1) | name, type, kind, return_type, param_types, is_mut | + +### 1.4 AST 节点覆盖矩阵 + +| AST 节点 | Lexer | Parser | Sema | Codegen | +|----------|-------|--------|------|---------| +| AST_PROGRAM | — | ✅ | ✅ | ✅ | +| AST_FUNCTION | — | ✅ | ✅ | ✅ | +| AST_PARAMETER | — | ✅ | — | — | +| AST_BLOCK | — | ✅ | ✅ | ✅ | +| AST_LET_STMT | — | ✅ | ✅ | ✅ | +| AST_ASSIGN_STMT | — | ✅ | ✅ | ✅ | +| AST_IF_STMT | — | ✅ | ✅ | ✅ | +| AST_WHILE_STMT | — | ✅ | ✅ | ✅ | +| AST_FOR_STMT | 去糖 (lexer 标记) | 去糖为 while | — | — | +| AST_RETURN_STMT | — | ✅ | ✅ | ✅ | +| AST_EXPR_STMT | — | ✅ | ✅ | ✅ | +| AST_BINARY_EXPR | — | ✅ | ✅ | ✅ | +| AST_UNARY_EXPR | — | ✅ | ✅ | ✅ | +| AST_CALL_EXPR | — | ✅ | ✅ | ✅ | +| AST_LITERAL_EXPR | — | ✅ | ✅ | ✅ | +| AST_IDENT_EXPR | — | ✅ | ✅ | ✅ | + +- **完整覆盖率**: 15 个 AST 节点在 sema 和 codegen 中全部覆盖 (100%)。AST_PARAMETER 无需独立 sema/codegen 路径 (函数签名处理时展开)。for 循环和复合赋值在 parser 层去糖,无 AST 节点增量。 +- **去糖策略**: for 循环 (TOK_FOR + TOK_IN + TOK_DOT_DOT) 和复合赋值 (TOK_PLUS_EQ 等) 在 parser 层去糖为 while + let mut + assign,零下游模块变更。 + +### 1.5 技术选型评估 + +| 选择 | 评价 | 状态 | +|------|------|------| +| C17 + CMake + MinGW (GCC 14.x) | 轻量, 学习友好, 交叉编译受限 | 稳定 | +| Arena bump allocator (8MB) | 零 GC 开销, 无 free, 编译时内存线性 | 稳定 | +| LLVM-C API 19.x | 成熟, 表达式级映射 | LLVM 22 mem2reg C API 已移除 (有注释) | +| 手写 Lexer/Parser (Pratt) | 0 外部依赖, 37 Token | 稳定 | +| 去糖策略 (for / 复合赋值) | AST 节点数不膨胀, 零下游侵入 | 验证有效 | +| GCC system() 链接 | 简单但平台绑定 (MinGW) | 待改进 | +| 错误报告 (即时终止 + 批量输出) | 分阶段差异处理 | 稳定 | + +--- + +## 2. 功能清单与成熟度 + +### 2.1 v0.1 基线功能 (全部完成) + +- [x] 基本类型: `i64`, `f64`, `bool`, `void` +- [x] 算术: `+` `-` `*` `/` `%` +- [x] 比较: `==` `!=` `<` `>` `<=` `>=` +- [x] 逻辑: `&&` `||` `!` +- [x] `let` 不可变变量 + 类型推断 +- [x] 控制流: `if`/`else` (含 `else if`), `while`, `return` +- [x] 函数定义与调用, 递归 (含斐波那契) +- [x] 内置函数: `print_i64`, `print_f64`, `print_bool` +- [x] 注释: `//` 行注释, `/* */` 块注释 +- [x] 错误报告: 词法/语法即时终止, 语义错误批量输出 + +### 2.2 v0.2 新增 (全部完成) + +- [x] `let mut` 可变变量 + 赋值语句 (`x = expr`) +- [x] 可变性检查 — 不可变变量赋值报编译错误 +- [x] 字符串类型 `str` + 双引号字面量 +- [x] `print_str` 内置函数 +- [x] `str + str` 运行时拼接 (malloc + strlen + memcpy) +- [x] 复合赋值 `+=` `-=` `*=` `/=` (parser 去糖) +- [x] LLVM 目标初始化解耦 → `target.h/c` 独立模块 +- [x] codegen malloc → arena 统一分配 + +### 2.3 v0.3 新增 (全部完成) + +- [x] `for` 循环 + range — `for i in start..end { ... }` (parser 去糖为 `let mut i = start; while i < end { ...; i = i + 1; }`) +- [x] `..` range 运算符 (TOK_DOT_DOT) +- [x] 浮点数 lexer 修复 — `peek_next` 防 `..` 与 `.` 冲突 +- [x] sema 测试补全: +5 测试 (let mut assign OK, immutable assign error, str type, str concat) +- [x] codegen 测试补全: +1 测试 (while 循环) +- [x] 集成测试: `10_for_range.l`, `11_for_step2.l` + +--- + +## 3. 与 Rust 对标: 缺失功能及优先级 + +### P0: 短期 (1-2 天/项) + +| # | 功能 | Rust 启发 | 改动范围 | 状态 | +|---|------|----------|---------|------| +| 1 | 复合赋值 += -= *= /= | 复合赋值运算符 | lexer + parser (desugar) | ✅ v0.2 | +| 2 | str+str 运行时拼接 | — | codegen (malloc/strlen/memcpy) | ✅ v0.2 | +| 3 | for 循环 + range | for i in 0..10 {} | lexer + parser (desugar) | ✅ v0.3 | +| 4 | **结构体 struct** | struct 具名域 + GEP | lexer/parser/sema/codegen (全流水线) | ❌ 待实施 | + +### P1: 中期 (3-5 天/项) + +| # | 功能 | 改动范围 | 学习价值 | 状态 | +|---|------|---------|---------|------| +| 5 | 数组 + 索引: `[i64; N]`, `arr[i]` | lexer/parser/sema/codegen (GEP) | GEP 指针计算 | 待实施 | +| 6 | 枚举 (C 风格) | lexer/parser/sema/codegen (i64) | 为代数类型铺路 | 待实施 | +| 7 | match 表达式 | lexer/parser/sema (穷举) + codegen | 模式匹配核心 | 待实施 | +| 8 | 类型别名 `type Meters = i64` | lexer/parser/sema (展开) | 名称等价 vs 结构等价 | 待实施 | + +### P2: 中后期 (1-2 周/项) + +| # | 功能 | 学习价值 | 状态 | +|---|------|---------|------| +| 9 | 模块系统 `mod` + `use` | 单文件→多文件 | 待实施 | +| 10 | 自定义 IR 层 (三地址码/SSA) | 编译器的核心抽象 | 待实施 | +| 11 | 泛型 (单态化) | Rust 零成本抽象基石 | 待实施 | +| 12 | 解释器模式 (walk AST) | 快速验证语义 | 待实施 | + +### P3: 长期 (2-4 周/项) + +| # | 功能 | 备注 | 状态 | +|---|------|------|------| +| 13 | trait / 接口 | 需泛型 + 虚表 | 待实施 | +| 14 | Option / Result | 需泛型 + enum | 待实施 | +| 15 | 所有权 / 借用检查 | 极度复杂 | 待实施 | +| 16 | 自举 (L 编译 L) | 终极考验 | 待实施 | + +### Rust 设计哲学吸收进度 + +| Rust 特性 | 状态 | 备注 | +|-----------|------|------| +| 默认不可变 (let vs let mut) | ✅ | 编译期检查 | +| 复合赋值 | ✅ | desugar 模式 | +| for + range | ✅ | desugar 为 while | +| 表达式 vs 语句 | ❌ | if/match/block 作为表达式 | +| 模式匹配 (match) | ❌ | P1 | +| 代数数据类型 (enum) | ❌ | 需泛型 | +| 结构体 (struct) | ❌ | P0 #4 | +| 所有权 / 借用 | ❌ | P3 远期 | + +--- + +## 4. 测试覆盖分析 + +### 4.1 单元测试 (19 函数, 59 断言) + +| 测试文件 | 函数数 | 覆盖范围 | +|---------|--------|---------| +| test_lexer.c | 3 | token 识别, 关键字, 操作符, 注释 | +| test_parser.c | 5 | 算术, let, if, while, 函数 | +| test_sema.c | 7 | 类型错误, 未定义变量, let mut, immutable assign, str, str concat | +| test_codegen.c | 4 | 简单函数, if/else, 二元运算, while | + +### 4.2 集成测试 (11 程序) + +| # | 文件 | 测试功能 | 状态 | +|---|------|---------|------| +| 1 | 01_arithmetic.l | 算术运算 | ✅ | +| 2 | 02_if_else.l | if/else 控制流 | ✅ | +| 3 | 03_recurse.l | 递归函数 | ✅ | +| 4 | 04_fib_recursive.l | 斐波那契 | ✅ | +| 5 | 05_float.l | 浮点数 | ✅ | +| 6 | 06_mut_while.l | let mut + while | ✅ | +| 7 | 07_hello_str.l | 字符串输出 | ✅ | +| 8 | 08_str_concat.l | 字符串拼接 | ✅ | +| 9 | 09_compound_assign.l | 复合赋值 | ✅ | +| 10 | 10_for_range.l | for range 循环 | ✅ | +| 11 | 11_for_step2.l | for range (变量边界) | ✅ | + +### 4.3 测试缺口 + +| # | 缺口 | 严重度 | 说明 | +|---|------|--------|------| +| 1 | codegen 缺 call/str/compound/for 路径 | 中 | 4 测试函数仅覆盖基础路径 | +| 2 | parser 缺 for 错误恢复测试 | 低 | 无 for/in/.. 错误提示验证 | +| 3 | 复合赋值无独立 desugar 测试 | 低 | 仅集成测试间接覆盖 | +| 4 | for 循环无独立 desugar 测试 | 低 | 同上 | + +--- + +## 5. 技术债务 + +| # | 问题 | 严重度 | 说明 | 趋势 | +|---|------|--------|------|------| +| 1 | str concat malloc 内存泄漏 | **中** | `x + y` 每次 malloc, 无 free; 编译后程序泄漏, 不影响编译器 | 存留 | +| 2 | codegen 测试覆盖率低 (4/15 AST 路径) | **中** | call/str/compound/for 路径无单元测试 | 存留 | +| 3 | system("gcc ...") 平台绑定 | 低 | Windows/MinGW 正常工作, 其他 OS 需手动链接 | 存留 | +| 4 | LLVM 22 移除 mem2reg C API | 低 | 已记录注释; 需用 opt 工具替代 | 存留 | +| 5 | 错误类型分散 (ErrorInfo + ErrorList) | 低 | token/parse 用 ErrorInfo, sema 用 ErrorList; 不影响功能 | 存留 | +| 6 | 复合赋值无独立 desugar 测试 | 低 | 集成测试间接覆盖 | 存留 | +| 7 | for 循环无独立 desugar 测试 | 低 | 集成测试间接覆盖 | 存留 | +| 8 | for 循环步长固定为 1 | 低 | 需扩展语法支持 step n | 存留 | + +--- + +## 6. 推荐开发路线图 + +### v0.4 目标 (下一迭代 — 无变更建议) + +| 优先级 | 功能 | 预计工时 | 依赖/备注 | +|--------|------|---------|----------| +| **P0** | **结构体 struct** | 2-3 天 | 解锁 GEP 和复合类型, 全流水线改动 | +| P1 | codegen 测试扩展 (for/call/str/compound) | 0.5 天 | 覆盖率补偿 | +| P1 | parser 测试补全 (for 错误恢复) | 0.5 天 | 回归保护 | +| P1 | str concat arena 化 (修复技术债 #1) | 0.5 天 | 替换 malloc 为 arena 分配 | + +### v0.5 目标 + +| 优先级 | 功能 | 预计工时 | +|--------|------|---------| +| P1 | 数组 + 索引 [i64; N], arr[i] | 2 天 | +| P1 | 枚举 (C 风格) | 1 天 | +| P1 | match 表达式 | 2-3 天 | +| P1 | 类型别名 type X = T | 0.5 天 | + +### 长期目标 + +| 优先级 | 功能 | 预计工时 | +|--------|------|---------| +| P2 | 自定义 IR 层 (三地址码/SSA) | 3-5 天 | +| P2 | 模块系统 mod + use | 3-5 天 | +| P2 | 解释器模式 | 2 天 | +| P3 | 泛型 (单态化) | 5-7 天 | +| P3 | trait / 接口 | 5-7 天 | +| P3 | 自举尝试 | 数周 | + +--- + +## 7. 度量汇总 + +| 指标 | 值 | 变化 (vs 0829 报告) | +|------|-----|-------------------| +| Git HEAD | 620cec4 | +1 commit (docs-only) | +| 代码基线 | 382cd08 | **无变化** | +| 源文件 (.c) | 11 文件, 1,688 行 | — | +| 头文件 (.h) | 11 文件, 285 行 | — | +| Token 类型 | 37 | — | +| AST 节点类型 | 15 | — | +| 类型系统 | 5 种 (i64, f64, bool, str, void) | — | +| 内置函数 | 4 (print_i64/f64/bool/str) | — | +| 集成测试 | 11 程序 | — | +| 单元测试函数 | 19 | — | +| 单元测试断言 | 59 | — | +| P0 完成度 | 3/4 (75%) | — (struct 待实施) | +| P1-P3 完成度 | 0/12 | — | +| AST 节点覆盖率 (sema) | 15/15 (100%) | — | +| AST 节点覆盖率 (codegen) | 15/15 (100%) | — | + +--- + +## 8. 结论 + +**本次分析确认: 自 2026-06-05 08:29 报告以来无代码变更。** 基线代码状态 `382cd08` 保持不变, 唯一新增的 commit `620cec4` 是 CHANGELOG 文档更新。 + +**当前状态**: v0.3 阶段已完成。语言支持 37 种 Token, 15 种 AST 节点, 5 种类型, 4 个内置函数。所有 AST 节点在 sema 和 codegen 中均有覆盖。19 个单元测试 + 11 个集成测试提供回归保护。去糖策略 (for 循环/复合赋值) 被验证为有效的架构决策, 实现了零下游侵入的新语法特性扩展。 + +**最大优先级**: P0 #4 结构体 struct 是唯一尚未完成的短期高优先功能, 也是解锁数组、枚举等复合类型的关键前置。 + +**技术债务**: 8 项债务中, 2 项为中危 (str concat 内存泄漏, codegen 测试覆盖率), 其余均为低危或已缓解。 + +--- + +*本报告由 Codex 自动生成。自上次报告 (2026-06-05 08:29) 后无代码变更。代码基线 382cd08, 唯一新增 commit 620cec4 为 CHANGELOG 文档。* \ No newline at end of file diff --git a/docs/analysis/architecture-analysis-report-2026-06-05-1152.md b/docs/analysis/architecture-analysis-report-2026-06-05-1152.md new file mode 100644 index 0000000..4bc363f --- /dev/null +++ b/docs/analysis/architecture-analysis-report-2026-06-05-1152.md @@ -0,0 +1,348 @@ +# L Language 架构分析报告 (v0.3 — 无代码变更) + +> 日期: 2026-06-05 11:52 | 自动生成 | 自上次源代码基线无变更 +> 上次代码分析基线: `382cd08` (2026-06-05 08:29 报告) +> 当前 HEAD: `620cec4` (CHANGELOG 更新 v0.3.0) + +--- + +## 变更摘要 (自上次代码基线) + +**源代码无变更。** 自 `382cd08` 以来,仅有 2 个纯文档提交 (`620cec4`): + +| 文件 | 变更 | 行数 | +|------|------|------| +| CHANGELOG.md | 新增 v0.3.0 条目 (for 循环 + 测试补全 + Known Issues) | +14 | +| docs/analysis/architecture-analysis-report-2026-06-05-0703.md | 上一轮自动化分析报告提交 | +288 | + +**结论**: 编译器源代码、测试代码、构建系统均无变化。本次运行与 08:29 报告相比,唯一差异是 CHANGELOG v0.3.0 已正式提交(上次报告列为技术债 #8「CHANGELOG 未更新 v0.3」)。 + +--- + +## 1. 当前架构全景 + +### 1.1 编译流水线 + +``` +源码(.l) → Lexer(词法) → Parser(语法) → Sema(语义) → Codegen(LLVM IR) → Target(obj) → GCC 链接(.exe) + Token[] AstNode* 类型标注 LLVMModuleRef .o 文件 +``` + +全流水线 6 个阶段,**无架构变化**。 + +### 1.2 模块清单与指标 + +| 模块 | 文件 | 行数 | 关键职责 | +|------|------|------|---------| +| include/ | l_lang.h | 34 | TypeKind 枚举 (7 种), 向前声明, arena_alloc_impl | +| util/ | arena.c(39) + .h(13) | 52 | 8MB bump allocator, strdup | +| lexer/ | lexer.c(137) token.c(45) + .h(40) | 222 | 手写状态机, **37** Token 类型, peek_next | +| ast/ | ast.c(107) ast.h(93) | 200 | **15** 种节点类型, 工厂函数 | +| parser/ | parser.c(390) parser.h(10) | 400 | Pratt 表达式解析 + 递归下降 + for/复合赋值去糖 | +| sema/ | sema.c(294) symbol.c(46) + .h(32) | 372 | 类型推断, 作用域链 (Scope), 可变性检查 | +| codegen/ | codegen.c(421) target.c(30) + .h(15) | 466 | LLVM IR 生成, 4 内置函数, 目标平台初始化 | +| driver/ | main.c(133) error.c(46) + .h(18) | 197 | 流水线串联, CLI (`l_lang [-o ] [--emit-ir]`), 错误报告 | +| test/ | test_*.c(4 文件) + test_utils.h(23) | 353 | 19 测试函数, 59 断言, 11 集成程序 | + +**总计: 23 源文件, 1,688 行实现代码 + 308 行头文件 + 330 行测试 = 2,326 行** + +### 1.3 关键架构决策 (v0.2–v0.3 沉淀) + +| 决策 | 描述 | 技术理由 | +|------|------|---------| +| **去糖策略** | 复合赋值 (`+=` 等) 和 for 循环在 parser 层降级为已有原语 | 零 codegen/sema 入侵, 零新 AST 节点, 降低维护成本 | +| **for 去糖路径** | `for i in 0..5 {body}` → `let mut i=0; while i<5 {body; i=i+1;}` | 步长固定为 1, 如需自定义步长需扩展语法 | +| **Arena 统一分配** | 编译器内部所有动态内存走 arena bump allocator (8MB) | 零 free 调用, 编译器结束时一次性释放 | +| **LLVM-C API 19.x** | 绑定 LLVM 19.x 的 C API | LLVM 22 移除 mem2reg C API (已记录注释) | +| **GCC 链接** | `system("gcc ...")` 链接目标文件 | 简单但平台绑定 (Windows/MinGW) | +| **Pratt 表达式解析** | 9 级优先级, 零左递归问题 | 比纯递归下降更优雅处理表达式 | + +### 1.4 核心数据结构 + +**Token (37 种)** + +``` +关键字: fn let mut if else while for in return +类型: i64 f64 bool str void +字面量: 整数 浮点数 true false 字符串 +运算符: + - * / % == != < > <= >= && || ! -> .. += -= *= /= +分隔符: ( ) { } , : ; = +特殊: EOF ERROR +``` + +**AST 节点 (15 种)** + +``` +Program Function Parameter Block LetStmt AssignStmt IfStmt WhileStmt +ReturnStmt ExprStmt BinaryExpr UnaryExpr CallExpr LiteralExpr IdentExpr +``` + +**类型系统 (7 种)** + +``` +TypeKind: I64 F64 BOOL STR VOID UNKNOWN ERROR + +转换规则: + 隐式: i64 → f64 (算术混合) + 显式: 函数参数/返回值必须标注 + 不可转换: str ↔ 数值, bool ↔ 数值 +``` + +**符号表** + +``` +Scope (链表栈): 全局 → 函数 → 块 + - SymbolKind: VARIABLE | PARAMETER | FUNCTION + - 变量属性: is_mut (可变性) + - 函数属性: return_type + param_types[] + param_count + - 作用域退出时释放 (arena 生命周期管理) +``` + +--- + +## 2. 功能清单与成熟度 + +### 2.1 v0.1 基线功能 (全部完成) + +- [x] 基本类型: i64, f64, bool, void +- [x] 算术: + - * / % +- [x] 比较: == != < > <= >= +- [x] 逻辑: && || ! +- [x] let 不可变变量, 类型标注 + 类型推断 +- [x] 控制流: if/else (含 else if), while, return +- [x] 函数定义与调用, 递归 +- [x] 内置函数: print_i64, print_f64, print_bool +- [x] 注释: // 行注释, /* */ 块注释 +- [x] 错误报告: 词法/语法即时终止, 语义错误批量输出 + +### 2.2 v0.2 新增功能 (全部完成) + +- [x] let mut 可变变量 + 赋值语句 +- [x] 可变性检查 — 不可变变量赋值报编译错误 +- [x] 字符串类型 str + 双引号字面量 +- [x] print_str 内置函数 +- [x] str+str 运行时拼接 (malloc + strlen + memcpy) +- [x] 复合赋值 += -= *= /= (parser 去糖) +- [x] LLVM 目标初始化解耦 (target.h/c) +- [x] codegen malloc → arena 统一 + +### 2.3 v0.3 新增功能 (全部完成) + +- [x] **for 循环 + range** — `for i in start..end { ... }`,parser 去糖 +- [x] **浮点数 lexer 修复** — `peek_next` 防 `..` 与 `.` 冲突 +- [x] **sema 测试补全** — +5 测试 (let mut assign, immutable error, str type, str concat) +- [x] **codegen 测试补全** — +1 测试 (while 循环) +- [x] **2 个新集成测试**: `10_for_range.l` (0..5), `11_for_step2.l` (变量边界) +- [x] LLVM 22 mem2reg 不可用注释 +- [x] CHANGELOG v0.3.0 提交 (本次运行时已就绪) + +--- + +## 3. 与 Rust 对标: 缺失功能清单及优先级 + +### P0: 短期 (1–2 天/项) + +| # | 功能 | Rust 启发 | 改动范围 | 状态 | +|---|------|----------|---------|------| +| 1 | 复合赋值 += -= *= /= | 复合赋值运算符 | lexer + parser(desugar) | **✅ v0.2** | +| 2 | str+str 运行时拼接 | — | codegen(malloc/strlen/memcpy) | **✅ v0.2** | +| 3 | for 循环 + range | `for i in 0..10 {}` | lexer + parser(desugar) | **✅ v0.3** | +| 4 | **结构体 struct** | struct 具名域 + GEP | lexer/parser/sema/codegen | **待实施** | + +**P0 完成度: 3/4 (75%)**。struct 是解锁复合类型的最后一块 P0 拼图。 + +### P1: 中期 (3–5 天/项) + +| # | 功能 | 改动范围 | 学习价值 | 状态 | +|---|------|---------|---------|------| +| 5 | 数组 + 索引 `[i64; N]`, `arr[i]` | lexer/parser/sema/codegen(GEP) | GEP 指针计算 | 待实施 | +| 6 | 枚举 (C 风格) | lexer/parser/sema/codegen(i64) | 为代数类型铺路 | 待实施 | +| 7 | match 表达式 | lexer/parser/sema(穷举) + codegen | 模式匹配核心 | 待实施 | +| 8 | 类型别名 `type Meters = i64` | lexer/parser/sema(展开) | 名称等价 vs 结构等价 | 待实施 | + +### P2: 中后期 (1–2 周/项) + +| # | 功能 | 学习价值 | 状态 | +|---|------|---------|------| +| 9 | 模块系统 `mod` + `use` | 单文件→多文件编译 | 待实施 | +| 10 | 自定义 IR 层 (三地址码/SSA) | 编译器核心抽象 | 待实施 | +| 11 | 泛型 (单态化) | Rust 零成本抽象基石 | 待实施 | +| 12 | 解释器模式 (walk AST) | 快速验证语义 | 待实施 | + +### P3: 长期 (2–4 周/项) + +| # | 功能 | 备注 | 状态 | +|---|------|------|------| +| 13 | trait / 接口 | 需泛型 + 虚表 | 待实施 | +| 14 | Option/Result | 需泛型 + enum | 待实施 | +| 15 | 所有权 / 借用检查 | 极度复杂 | 待实施 | +| 16 | 自举 (L 编译 L) | 终极考验 | 待实施 | + +### Rust 设计哲学吸收进度 + +| Rust 特性 | 状态 | 备注 | +|-----------|------|------| +| 默认不可变 (let vs let mut) | ✅ 已实现 | 编译期检查, 可变性追踪 | +| 复合赋值 | ✅ 已实现 | parser desugar 模式 | +| for + range | ✅ 已实现 | desugar 为 while | +| str 拼接 | ✅ 已实现 | 运行时 malloc | +| 表达式 vs 语句 | ❌ | if/match/block 作为表达式 (目前只支持语句) | +| 模式匹配 | ❌ | P1 优先级 | +| 代数数据类型 | ❌ | 需泛型 | +| 所有权 / 借用 | ❌ | 远期 | + +--- + +## 4. 测试覆盖分析 + +### 4.1 单元测试 + +| 测试文件 | 测试函数 | 断言数 | 覆盖范围 | +|---------|----------|--------|---------| +| test_lexer.c | 3 | 14 | token 识别, 关键字, 操作符, 注释 | +| test_parser.c | 5 | 15 | 算术, let, if, while, 函数 | +| test_sema.c | 7 | 21 | 类型错误, 未定义变量, let mut, immutable error, str type, str concat | +| test_codegen.c | 4 | 9 | 简单函数, if/else, 二元运算, while | +| **合计** | **19** | **59** | | + +### 4.2 集成测试 (11 程序) + +| 文件 | 功能 | 版本 | +|------|------|------| +| 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 | + +### 4.3 测试缺口 + +| 缺口 | 严重度 | 说明 | +|------|--------|------| +| 复合赋值无独立 desugar 测试 | 低 | 仅通过 `09_compound_assign.l` 间接覆盖 | +| for 循环无独立 desugar 测试 | 低 | 仅通过 `10_for_range.l` / `11_for_step2.l` 间接覆盖 | +| codegen 缺 call/str/compound/for 路径 | 中 | 4 测试仅覆盖基础路径: simple, if/else, binary, while | +| parser 缺 for 错误恢复测试 | 低 | 缺少 for/in/.. 错误提示验证 | +| 无 LLVM IR 输出一致性测试 | 低 | 无法检测 LLVM IR 生成回归 | + +--- + +## 5. 技术债务与风险 + +| # | 问题 | 状态 | 严重度 | 说明 | +|---|------|------|--------|------| +| 1 | str concat malloc 内存泄漏 | **存留** | 中 | x + y 每次 malloc 无 free,编译后程序运行会泄漏 (编译器自身不受影响) | +| 2 | 复合赋值无独立 desugar 测试 | **存留** | 低 | 集成测试间接覆盖 | +| 3 | codegen 测试覆盖率低 (4/9 路径) | **存留** | 中 | call/str/compound/for/return 路径无单元测试 | +| 4 | `system("gcc ...")` 平台绑定 | **存留** | 低 | Windows/MinGW 正常,Linux/Unix 需 `gcc`/`cc` 替换 | +| 5 | LLVM 22 无 mem2reg C API | **已记录** | 低 | codegen.c 注释说明需用 `opt` 工具 | +| 6 | 错误类型分散 (ErrorInfo + ErrorList) | **存留** | 低 | 不影响功能,但 API 不统一 | +| 7 | CHANGELOG 未更新 v0.3 | **✅ 已解决** | — | `620cec4` 提交了 v0.3.0 条目 | +| 8 | parser.c 函数 `parse_function` 过长 | **存留** | 低 | 约 60 行,可拆分为多个子函数 | +| 9 | 无 fuzzing / 边界输入测试 | **新增** | 低 | 超大源文件、超长标识符、深层嵌套等未覆盖 | + +### 风险矩阵 + +| 风险 | 概率 | 影响 | 缓解 | +|------|------|------|------| +| LLVM API 版本升级 break | 中 | 高 | 当前锁定 19.x,文档记录不兼容点 | +| GCC 链接在非 MinGW 环境失败 | 中 | 中 | 可接受链接器参数或使用 LLVM lld | +| Arena 内存耗尽 (8MB) | 极低 | 中 | 巨型程序才触发,编译器中止即可 | +| str 泄漏导致大程序 OOM | 低 | 中 | 短期替代方案: arena 化 str 拼接 | + +--- + +## 6. 推荐开发路线图 + +### v0.4 目标 (下一迭代) + +| 优先级 | 功能 | 预计工时 | 依赖/备注 | +|--------|------|---------|----------| +| **P0** | **结构体 struct** | 2–3 天 | 解锁 GEP 和复合类型,全流水线 | +| P1 | str concat arena 化 (修复技术债 #1) | 0.5 天 | 替换 malloc 为 arena 分配 | +| P1 | codegen 测试扩展 (call/str/for) | 0.5 天 | 覆盖率补偿 | +| P1 | parser 测试补全 (for 错误恢复) | 0.5 天 | 回归保护 | + +### v0.5 目标 + +| 优先级 | 功能 | 预计工时 | +|--------|------|---------| +| P1 | 数组 + 索引 | 2 天 | +| P1 | 枚举 (C 风格) | 1 天 | +| P1 | match 表达式 | 2–3 天 | +| P1 | 类型别名 | 0.5 天 | + +### 长期方向 + +| 优先级 | 功能 | 预计工时 | +|--------|------|---------| +| P2 | 自定义 IR 层 | 3–5 天 | +| P2 | 模块系统 | 3–5 天 | +| P2 | 解释器模式 | 2 天 | +| P3 | 泛型 (单态化) | 5–7 天 | +| P3 | trait / 接口 | 5–7 天 | +| P3 | 自举尝试 | 数周 | + +--- + +## 7. 度量汇总 + +| 指标 | 值 | 较 v0.2 (72a971e) | 较 v0.3 基线 (382cd08) | +|------|-----|-------------------|----------------------| +| 源文件数 (.c) | 12 | — | 无变化 | +| 头文件数 (.h) | 12 | — | 无变化 | +| 实现代码 | 1,688 行 | +52 | 无变化 | +| 头文件 | 308 行 | +23 | 无变化 | +| 测试代码 | 330 行 | +88 | 无变化 | +| 总代码量 | 2,326 行 | +163 | 无变化 | +| Token 类型 | 37 | +3 (FOR/IN/DOT_DOT) | 无变化 | +| AST 节点类型 | 15 | +1 (ASSIGN_STMT) | 无变化 | +| TypeKind | 7 | +2 (STR, ERROR) | 无变化 | +| 内置函数 | 4 (print_i64/f64/bool/str) | +1 (print_str) | 无变化 | +| C 运行时依赖 | printf, malloc, strlen, memcpy | +malloc/strlen/memcpy | 无变化 | +| 单元测试函数 | 19 | +5 | 无变化 | +| 单元测试断言 | 59 | +14 | 无变化 | +| 集成测试程序 | 11 | +2 (for_range, for_step2) | 无变化 | +| P0 完成度 | 3/4 (75%) | +for | 无变化 | +| P0 遗留 | struct | — | 无变化 | +| 编译产物 | 6 个 .exe | — | 无变化 | +| Git 提交数 | 11 | +2 | +1 (CHANGELOG) | + +--- + +## 8. 代码质量评估 + +### 8.1 模块内聚度 + +| 模块 | 内聚度 | 评价 | +|------|--------|------| +| lexer | 高 | 纯函数接口,状态机集中在单文件,token 定义与值提取分离 | +| parser | 高 | 递归下降 + Pratt 组合清晰,去糖逻辑局部化 | +| ast | 高 | 工厂函数模式统一,union 节点设计节省内存 | +| sema | 高 | 遍历器模式,analyze_node/analyze_expr 双入口,职责明确 | +| codegen | 中高 | `codegen_stmt` 单一大函数处理所有节点,可拆分但暂不紧急 | +| driver | 中 | main.c 串联流水线,error.c 独立,接口清晰 | +| util | 高 | 单一职责 (arena 分配器),零外部依赖 | + +### 8.2 代码风格一致性 + +- 蛇形命名 (snake_case) 贯穿全部源文件 +- 头文件守卫 (`#ifndef X_H` / `#define X_H` / `#endif`) 统一 +- 工厂函数模式 (`ast_make_*`) 跨 AST 模块一致 +- 错误处理模式: `ErrorInfo*` (即时错误) + `ErrorList*` (批量错误) 两类 + +### 8.3 编译警告 + +未在本次运行中编译,但从代码结构推断: +- 全局变量 `_tests_run` / `_tests_failed` 在 `test_utils.h` 中 `static` 声明 (每个翻译单元独立副本, 跨文件统计不准确) +- `Arena*` 通过 `void*` 跨模块传递 (`arena_alloc_impl(void* alloc, ...)`),丢失类型安全, 但避免了模块间循环依赖 + +--- + +*本报告由 Codex 自动生成于 2026-06-05 11:52。自上次代码基线 `382cd08` 后无源代码变更。当前 HEAD `620cec4` 仅增加 CHANGELOG.md v0.3.0 条目 (14 行) + 上一轮分析报告 (288 行)。编译器源码、测试、构建系统均不变。P0 struct 仍为唯一待实施的 P0 功能。* \ No newline at end of file diff --git a/include/l_lang.h b/include/l_lang.h index 988eeb9..628f3b3 100644 --- a/include/l_lang.h +++ b/include/l_lang.h @@ -12,8 +12,9 @@ typedef enum { TYPE_BOOL, TYPE_STR, TYPE_VOID, + TYPE_STRUCT, // 结构体类型 TYPE_UNKNOWN, // 尚未推断 - TYPE_ERROR, // 类型错误 + TYPE_ERROR, // 类型错误 } TypeKind; static inline const char* type_name(TypeKind kind) { @@ -22,8 +23,9 @@ static inline const char* type_name(TypeKind kind) { case TYPE_F64: return "f64"; case TYPE_BOOL: return "bool"; case TYPE_STR: return "str"; - case TYPE_VOID: return "void"; - default: return ""; + case TYPE_VOID: return "void"; + case TYPE_STRUCT: return "struct"; + default: return ""; } } diff --git a/src/ast/ast.c b/src/ast/ast.c index ec43f2b..c59dfa8 100644 --- a/src/ast/ast.c +++ b/src/ast/ast.c @@ -4,13 +4,16 @@ // 使用宏简化节点创建 #define NEW(alloc, k) \ AstNode* n = (AstNode*)arena_alloc_impl(alloc, sizeof(AstNode)); \ - n->kind = (k); n->type.kind = TYPE_UNKNOWN; \ + n->kind = (k); n->type.kind = TYPE_UNKNOWN; n->type.struct_name = NULL; \ n->line = line; n->col = col -AstNode* ast_make_program(void* alloc, AstNode** fns, size_t count, int line, int col) { +AstNode* ast_make_program(void* alloc, AstNode** fns, size_t fn_count, + AstNode** structs, size_t struct_count, int line, int col) { NEW(alloc, AST_PROGRAM); n->as.program.functions = fns; - n->as.program.fn_count = count; + n->as.program.fn_count = fn_count; + n->as.program.structs = structs; + n->as.program.struct_count = struct_count; return n; } @@ -23,9 +26,11 @@ AstNode* ast_make_function(void* alloc, const char* name, AstNode** params, size return n; } -AstNode* ast_make_parameter(void* alloc, const char* name, TypeKind type, int line, int col) { +AstNode* ast_make_parameter(void* alloc, const char* name, TypeKind type, + const char* struct_type_name, int line, int col) { NEW(alloc, AST_PARAMETER); n->as.parameter.name = name; n->as.parameter.type = type; + n->as.parameter.struct_type_name = struct_type_name; return n; } @@ -35,10 +40,13 @@ AstNode* ast_make_block(void* alloc, AstNode** stmts, size_t count, int line, in return n; } -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) { +AstNode* ast_make_let(void* alloc, const char* name, TypeKind annot_type, bool has_type_annot, + bool is_mut, AstNode* init, const char* struct_type_name, int line, int col) { NEW(alloc, AST_LET_STMT); n->as.let_stmt.name = name; n->as.let_stmt.annot_type = annot_type; - n->as.let_stmt.has_type_annot = has_type_annot; n->as.let_stmt.is_mut = is_mut; n->as.let_stmt.init = init; + n->as.let_stmt.has_type_annot = has_type_annot; n->as.let_stmt.is_mut = is_mut; + n->as.let_stmt.init = init; + n->as.let_stmt.struct_type_name = struct_type_name; return n; } @@ -124,3 +132,34 @@ AstNode* ast_make_ident(void* alloc, const char* name, int line, int col) { n->as.ident.name = name; return n; } + +// === 结构体相关工厂函数 === + +AstNode* ast_make_struct_decl(void* alloc, const char* name, AstNode** fields, + size_t count, int line, int col) { + NEW(alloc, AST_STRUCT_DECL); + n->as.struct_decl.name = name; + n->as.struct_decl.fields = fields; + n->as.struct_decl.field_count = count; + return n; +} + +AstNode* ast_make_struct_init(void* alloc, const char* type_name, + const char** fnames, AstNode** fvals, + size_t count, int line, int col) { + NEW(alloc, AST_STRUCT_INIT); + n->as.struct_init.type_name = type_name; + n->as.struct_init.field_names = fnames; + n->as.struct_init.field_values = fvals; + n->as.struct_init.field_count = count; + return n; +} + +AstNode* ast_make_field_access(void* alloc, AstNode* object, const char* field, + int line, int col) { + NEW(alloc, AST_FIELD_ACCESS); + n->as.field_access.object = object; + n->as.field_access.field = field; + n->as.field_access.field_index = -1; + return n; +} diff --git a/src/ast/ast.h b/src/ast/ast.h index 9a2ae3d..285bc1c 100644 --- a/src/ast/ast.h +++ b/src/ast/ast.h @@ -20,6 +20,9 @@ typedef enum { AST_CALL_EXPR, AST_LITERAL_EXPR, AST_IDENT_EXPR, + AST_STRUCT_DECL, // struct Point { x: i64, y: i64 } + AST_STRUCT_INIT, // Point { x: 10, y: 20 } + AST_FIELD_ACCESS, // p.x } AstKind; typedef enum { @@ -32,6 +35,7 @@ typedef enum { // 类型信息(语义分析阶段填充) typedef struct { TypeKind kind; + const char* struct_name; // TYPE_STRUCT 时的结构体类型名 } TypeInfo; // AST 节点 @@ -44,16 +48,18 @@ struct AstNode { // 节点特有数据(按 kind 解释) union { // AST_PROGRAM - struct { struct AstNode** functions; size_t fn_count; } program; + struct { struct AstNode** functions; size_t fn_count; + struct AstNode** structs; size_t struct_count; } program; // AST_FUNCTION struct { const char* name; struct AstNode** params; size_t param_count; TypeKind return_type; struct AstNode* body; } function; - // AST_PARAMETER - struct { const char* name; TypeKind type; } parameter; + // AST_PARAMETER (也用作结构体字段: name + type) + struct { const char* name; TypeKind type; const char* struct_type_name; } parameter; // AST_BLOCK struct { struct AstNode** stmts; size_t stmt_count; } block; // AST_LET_STMT - struct { const char* name; TypeKind annot_type; bool has_type_annot; bool is_mut; struct AstNode* init; } let_stmt; + struct { const char* name; TypeKind annot_type; bool has_type_annot; bool is_mut; struct AstNode* init; + const char* struct_type_name; } let_stmt; // AST_ASSIGN_STMT struct { const char* name; struct AstNode* value; } assign_stmt; // AST_IF_STMT @@ -74,16 +80,25 @@ struct AstNode { struct { TypeKind lit_type; union { int64_t i64_val; double f64_val; bool bool_val; const char* str_val; }; } literal; // AST_IDENT_EXPR struct { const char* name; } ident; + // AST_STRUCT_DECL + struct { const char* name; struct AstNode** fields; size_t field_count; } struct_decl; + // AST_STRUCT_INIT + struct { const char* type_name; const char** field_names; + struct AstNode** field_values; size_t field_count; } struct_init; + // AST_FIELD_ACCESS + struct { struct AstNode* object; const char* field; int field_index; } field_access; } as; }; // 创建节点的辅助函数(内存来自 arena,通过 void* 传递避免循环依赖) -AstNode* ast_make_program(void* alloc, AstNode** fns, size_t count, int line, int col); +AstNode* ast_make_program(void* alloc, AstNode** fns, size_t fn_count, + AstNode** structs, size_t struct_count, int line, int col); AstNode* ast_make_function(void* alloc, const char* name, AstNode** params, size_t pcount, TypeKind ret, AstNode* body, int line, int col); -AstNode* ast_make_parameter(void* alloc, const char* name, TypeKind type, int line, int col); +AstNode* ast_make_parameter(void* alloc, const char* name, TypeKind type, const char* struct_type_name, int line, int col); AstNode* ast_make_block(void* alloc, AstNode** stmts, size_t count, int line, int col); -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); +AstNode* ast_make_let(void* alloc, const char* name, TypeKind annot_type, bool has_type_annot, + bool is_mut, AstNode* init, const char* struct_type_name, int line, int col); AstNode* ast_make_assign(void* alloc, const char* name, AstNode* value, int line, int col); AstNode* ast_make_if(void* alloc, AstNode* cond, AstNode* then_b, AstNode* else_b, int line, int col); AstNode* ast_make_while(void* alloc, AstNode* cond, AstNode* body, int line, int col); @@ -97,5 +112,8 @@ AstNode* ast_make_literal_f64(void* alloc, double val, int line, int col); AstNode* ast_make_literal_bool(void* alloc, bool val, int line, int col); AstNode* ast_make_literal_str(void* alloc, const char* val, int line, int col); AstNode* ast_make_ident(void* alloc, const char* name, int line, int col); +AstNode* ast_make_struct_decl(void* alloc, const char* name, AstNode** fields, size_t count, int line, int col); +AstNode* ast_make_struct_init(void* alloc, const char* type_name, const char** fnames, AstNode** fvals, size_t count, int line, int col); +AstNode* ast_make_field_access(void* alloc, AstNode* object, const char* field, int line, int col); #endif diff --git a/src/codegen/codegen.c b/src/codegen/codegen.c index 43c98e4..300699c 100644 --- a/src/codegen/codegen.c +++ b/src/codegen/codegen.c @@ -20,6 +20,14 @@ typedef struct FnEntry { struct FnEntry* next; } FnEntry; +// 结构体类型映射 +typedef struct StructTypeEntry { + const char* name; + LLVMTypeRef llvm_type; + size_t field_count; + struct StructTypeEntry* next; +} StructTypeEntry; + typedef struct { Arena* arena; // 代码生成阶段分配器 LLVMContextRef context; // LLVM 19+ 需要显式 Context @@ -28,6 +36,7 @@ typedef struct { VarEntry* var_table; const char* error; FnEntry* fn_table; + StructTypeEntry* struct_table; // printf 运行时支持(内置 print 函数委托给 printf) LLVMValueRef printf_fn; LLVMTypeRef printf_ty; @@ -89,6 +98,21 @@ static void add_fn(CgCtx* ctx, const char* name, LLVMValueRef fn) { ctx->fn_table = e; } +// === 结构体类型表 === +static void add_struct_type(CgCtx* ctx, const char* name, LLVMTypeRef ty, size_t fc) { + StructTypeEntry* e = arena_alloc(ctx->arena, sizeof(*e)); + if (!e) return; + e->name = name; e->llvm_type = ty; e->field_count = fc; + e->next = ctx->struct_table; + ctx->struct_table = e; +} + +static LLVMTypeRef find_struct_type(CgCtx* ctx, const char* name) { + for (StructTypeEntry* e = ctx->struct_table; e; e = e->next) + if (strcmp(e->name, name) == 0) return e->llvm_type; + return NULL; +} + // === 向前声明 === static LLVMValueRef codegen_expr(CgCtx* ctx, AstNode* node); static void codegen_stmt(CgCtx* ctx, AstNode* node); @@ -107,7 +131,14 @@ static LLVMValueRef codegen_expr(CgCtx* ctx, AstNode* node) { case AST_IDENT_EXPR: { LLVMValueRef ptr = find_var(ctx, node->as.ident.name); if (!ptr) return NULL; - return LLVMBuildLoad2(ctx->builder, to_llvm_type(ctx, node->type.kind), ptr, "load"); + LLVMTypeRef load_ty; + if (node->type.kind == TYPE_STRUCT && node->type.struct_name) { + load_ty = find_struct_type(ctx, node->type.struct_name); + if (!load_ty) load_ty = to_llvm_type(ctx, node->type.kind); + } else { + load_ty = to_llvm_type(ctx, node->type.kind); + } + return LLVMBuildLoad2(ctx->builder, load_ty, ptr, "load"); } case AST_UNARY_EXPR: { @@ -260,6 +291,50 @@ static LLVMValueRef codegen_expr(CgCtx* ctx, AstNode* node) { ret_ty == LLVMVoidTypeInContext(ctx->context) ? "" : "call"); } + // === 结构体字段访问: p.x === + case AST_FIELD_ACCESS: { + // 对对象求值(返回的是 struct 值) + LLVMValueRef struct_val = codegen_expr(ctx, node->as.field_access.object); + if (!struct_val) return NULL; + + int field_idx = node->as.field_access.field_index; + if (field_idx < 0) return NULL; // sema 应当已经设置 + + // 用 extractvalue 从结构体值中提取字段 + return LLVMBuildExtractValue(ctx->builder, struct_val, + (unsigned)field_idx, node->as.field_access.field); + } + + // === 结构体初始化: Point { x: 10, y: 20 } === + case AST_STRUCT_INIT: { + const char* st_name = node->as.struct_init.type_name; + LLVMTypeRef struct_ty = find_struct_type(ctx, st_name); + if (!struct_ty) return NULL; + + // alloca 分配结构体空间 + LLVMValueRef alloca = LLVMBuildAlloca(ctx->builder, struct_ty, "struct_init"); + + // 获取结构体字段名列表(从 struct_table 或从 AST 中) + // 对每个 init 字段,找到它在结构体中的索引并 store + for (size_t i = 0; i < node->as.struct_init.field_count; i++) { + AstNode* fval = node->as.struct_init.field_values[i]; + LLVMValueRef val = codegen_expr(ctx, fval); + if (!val) return NULL; + + // 获取字段指针: GEP struct_ty, alloca, 0, i + LLVMValueRef indices[] = { + LLVMConstInt(LLVMInt32TypeInContext(ctx->context), 0, false), + LLVMConstInt(LLVMInt32TypeInContext(ctx->context), (unsigned long long)i, false) + }; + LLVMValueRef field_ptr = LLVMBuildGEP2(ctx->builder, struct_ty, alloca, + indices, 2, "field_ptr"); + LLVMBuildStore(ctx->builder, val, field_ptr); + } + + // 加载整个结构体值 + return LLVMBuildLoad2(ctx->builder, struct_ty, alloca, "struct_val"); + } + default: return NULL; } @@ -273,8 +348,18 @@ static void codegen_stmt(CgCtx* ctx, AstNode* node) { case AST_LET_STMT: { LLVMValueRef init_val = codegen_expr(ctx, node->as.let_stmt.init); if (!init_val) return; + + LLVMTypeRef var_type; + if (node->as.let_stmt.init->type.kind == TYPE_STRUCT && + node->as.let_stmt.init->type.struct_name) { + var_type = find_struct_type(ctx, node->as.let_stmt.init->type.struct_name); + if (!var_type) var_type = to_llvm_type(ctx, node->as.let_stmt.init->type.kind); + } else { + var_type = to_llvm_type(ctx, node->as.let_stmt.init->type.kind); + } + LLVMValueRef alloca = LLVMBuildAlloca(ctx->builder, - to_llvm_type(ctx, node->as.let_stmt.init->type.kind), node->as.let_stmt.name); + var_type, node->as.let_stmt.name); LLVMBuildStore(ctx->builder, init_val, alloca); add_var(ctx, node->as.let_stmt.name, alloca); break; @@ -410,6 +495,33 @@ LLVMModuleRef codegen_module(AstNode* ast, Arena* codegen_arena, memcpy_args, 3, false); ctx.memcpy_fn = LLVMAddFunction(ctx.module, "memcpy", memcpy_ty); + // 第零遍:先创建所有命名结构体(占位符,未设置 body) + for (size_t i = 0; i < ast->as.program.struct_count; i++) { + AstNode* sd = ast->as.program.structs[i]; + LLVMTypeRef llvm_st = LLVMStructCreateNamed(ctx.context, sd->as.struct_decl.name); + add_struct_type(&ctx, sd->as.struct_decl.name, llvm_st, + sd->as.struct_decl.field_count); + } + // 然后设置所有结构体的 body(此时所有结构体类型已注册,可互相引用) + for (size_t i = 0; i < ast->as.program.struct_count; i++) { + AstNode* sd = ast->as.program.structs[i]; + LLVMTypeRef llvm_st = find_struct_type(&ctx, sd->as.struct_decl.name); + LLVMTypeRef* elem_types = arena_alloc(ctx.arena, + sd->as.struct_decl.field_count * sizeof(LLVMTypeRef)); + for (size_t j = 0; j < sd->as.struct_decl.field_count; j++) { + AstNode* field = sd->as.struct_decl.fields[j]; + if (field->as.parameter.type == TYPE_STRUCT && + field->as.parameter.struct_type_name) { + elem_types[j] = find_struct_type(&ctx, + field->as.parameter.struct_type_name); + } else { + elem_types[j] = to_llvm_type(&ctx, field->as.parameter.type); + } + } + LLVMStructSetBody(llvm_st, elem_types, + (unsigned)sd->as.struct_decl.field_count, false); + } + // 第一遍:声明所有 L 函数 for (size_t i = 0; i < ast->as.program.fn_count; i++) { AstNode* fn = ast->as.program.functions[i]; @@ -457,7 +569,6 @@ LLVMModuleRef codegen_module(AstNode* ast, Arena* codegen_arena, } // 验证模块(使用 ReturnStatus 以获取完整错误消息) - // 注: LLVM 22 C API 不再导出 mem2reg pass, alloca 优化需用 opt 工具 char* verify_err = NULL; if (LLVMVerifyModule(ctx.module, LLVMReturnStatusAction, &verify_err)) { *error_msg = verify_err ? verify_err : "模块验证失败(错误消息为 NULL)"; diff --git a/src/lexer/lexer.c b/src/lexer/lexer.c index 695f07b..dc6b321 100644 --- a/src/lexer/lexer.c +++ b/src/lexer/lexer.c @@ -62,6 +62,7 @@ static TokenKind check_keyword(const Token* tok) { KW("i64", TOK_I64); KW("f64", TOK_F64); KW("bool", TOK_BOOL); KW("str", TOK_STR); KW("void", TOK_VOID); + KW("struct", TOK_STRUCT); KW("true", TOK_TRUE); KW("false", TOK_FALSE); #undef KW return TOK_IDENT; @@ -126,6 +127,7 @@ Token* lex(Arena* a, const char* source, const char* filename, else if (c == '&' && peek_next(&l) == '&') { tokens[idx++] = make_token(&l, TOK_AND_AND, l.pos, 2); advance(&l); advance(&l); } else if (c == '|' && peek_next(&l) == '|') { tokens[idx++] = make_token(&l, TOK_PIPE_PIPE, l.pos, 2); advance(&l); advance(&l); } else if (c == '.' && peek_next(&l) == '.') { tokens[idx++] = make_token(&l, TOK_DOT_DOT, l.pos, 2); advance(&l); advance(&l); } + else if (c == '.') { tokens[idx++] = make_token(&l, TOK_DOT, l.pos, 1); advance(&l); } else if (c == '(') { tokens[idx++] = make_token(&l, TOK_LPAREN, l.pos, 1); advance(&l); } else if (c == ')') { tokens[idx++] = make_token(&l, TOK_RPAREN, l.pos, 1); advance(&l); } else if (c == '{') { tokens[idx++] = make_token(&l, TOK_LBRACE, l.pos, 1); advance(&l); } diff --git a/src/lexer/token.c b/src/lexer/token.c index 6c02c38..a2ba7cf 100644 --- a/src/lexer/token.c +++ b/src/lexer/token.c @@ -7,6 +7,7 @@ static const char* NAMES[] = { [TOK_FN] = "fn", [TOK_LET] = "let", [TOK_MUT] = "mut", [TOK_IF] = "if", [TOK_ELSE] = "else", [TOK_WHILE] = "while", [TOK_FOR] = "for", [TOK_IN] = "in", [TOK_RETURN] = "return", + [TOK_STRUCT] = "struct", [TOK_I64] = "i64", [TOK_F64] = "f64", [TOK_BOOL] = "bool", [TOK_STR] = "str", [TOK_VOID] = "void", [TOK_INT_LIT] = "整数", [TOK_FLOAT_LIT] = "浮点数", [TOK_STR_LIT] = "字符串", [TOK_TRUE] = "true", [TOK_FALSE] = "false", @@ -22,6 +23,7 @@ static const char* NAMES[] = { [TOK_LBRACE] = "{", [TOK_RBRACE] = "}", [TOK_COMMA] = ",", [TOK_COLON] = ":", [TOK_SEMICOLON] = ";", [TOK_ASSIGN] = "=", + [TOK_DOT] = ".", [TOK_EOF] = "EOF", [TOK_ERROR] = "错误", }; diff --git a/src/lexer/token.h b/src/lexer/token.h index 88c00fa..be14be4 100644 --- a/src/lexer/token.h +++ b/src/lexer/token.h @@ -7,6 +7,7 @@ typedef enum { // 关键字 TOK_FN, TOK_LET, TOK_MUT, TOK_IF, TOK_ELSE, TOK_WHILE, TOK_FOR, TOK_IN, TOK_RETURN, + TOK_STRUCT, // 类型关键字 TOK_I64, TOK_F64, TOK_BOOL, TOK_STR, TOK_VOID, // 字面量 @@ -23,6 +24,7 @@ typedef enum { TOK_LPAREN, TOK_RPAREN, TOK_LBRACE, TOK_RBRACE, TOK_COMMA, TOK_COLON, TOK_SEMICOLON, TOK_ASSIGN, // 特殊 + TOK_DOT, TOK_EOF, TOK_ERROR, } TokenKind; diff --git a/src/parser/parser.c b/src/parser/parser.c index 8102ca0..91234d7 100644 --- a/src/parser/parser.c +++ b/src/parser/parser.c @@ -33,6 +33,7 @@ typedef enum { PREC_TERM = 50, PREC_FACTOR = 60, PREC_UNARY = 70, + PREC_POSTFIX = 80, // .field, call() } Precedence; static Precedence tok_to_prec(TokenKind kind) { @@ -64,6 +65,8 @@ static BinaryOp tok_to_binop(TokenKind kind) { static AstNode* parse_expr(Parser* p, ErrorInfo* error); static AstNode* parse_expr_prec(Parser* p, Precedence prec, ErrorInfo* error); static AstNode* parse_block(Parser* p, ErrorInfo* error); +static AstNode* parse_statement(Parser* p, ErrorInfo* error); +static AstNode* parse_function(Parser* p, ErrorInfo* error); // === 前缀解析 === static AstNode* parse_unary(Parser* p, ErrorInfo* error) { @@ -99,10 +102,58 @@ static AstNode* parse_literal(Parser* p) { } } +// === 结构体初始化解析: Name { field: val, ... } === +static AstNode* parse_struct_init(Parser* p, const Token* name, ErrorInfo* error) { + advance(p); // 跳过 '{' + const char* fnames[32]; + AstNode* fvals[32]; + int fcount = 0; + + while (peek(p)->kind != TOK_RBRACE && !error->message) { + const Token* fname = expect(p, TOK_IDENT, error, "字段名"); + if (!fname) return NULL; + if (!expect(p, TOK_COLON, error, "缺少 ':'")) return NULL; + AstNode* val = parse_expr(p, error); + if (!val) return NULL; + + fnames[fcount] = arena_strdup_impl(p->arena, fname->start, fname->length); + fvals[fcount] = val; + fcount++; + + if (peek(p)->kind == TOK_COMMA) advance(p); + else break; + } + if (!expect(p, TOK_RBRACE, error, "缺少 '}'")) return NULL; + + const char** n_arr = arena_alloc_impl(p->arena, fcount * sizeof(const char*)); + memcpy(n_arr, fnames, fcount * sizeof(const char*)); + AstNode** v_arr = arena_alloc_impl(p->arena, fcount * sizeof(AstNode*)); + memcpy(v_arr, fvals, fcount * sizeof(AstNode*)); + + return ast_make_struct_init(p->arena, + arena_strdup_impl(p->arena, name->start, name->length), + n_arr, v_arr, fcount, name->line, name->col); +} + +// === 标识符 / 函数调用 / 结构体初始化 === static AstNode* parse_ident_or_call(Parser* p, ErrorInfo* error) { const Token* name = advance(p); + + // 结构体初始化: Name { field: val, ... } + // 用提前看来区别 struct init 和 block: + // struct init → { IDENT COLON ... ;block → { 可能是 let/if/while/... + if (peek(p)->kind == TOK_LBRACE) { + const Token* after_brace = &p->tokens[p->pos + 1]; + if (after_brace->kind == TOK_IDENT) { + const Token* after_fname = &p->tokens[p->pos + 2]; + if (after_fname->kind == TOK_COLON) { + return parse_struct_init(p, name, error); + } + } + } + + // 函数调用: name(...) if (match(p, TOK_LPAREN)) { - // 函数调用 AstNode* args[16]; int arg_count = 0; while (peek(p)->kind != TOK_RPAREN && !error->message) { if (arg_count >= 16) { @@ -149,9 +200,22 @@ static AstNode* parse_expr_prec(Parser* p, Precedence min_prec, ErrorInfo* error } if (!left) return NULL; - // 中缀解析循环 + // 中缀/后置解析循环 while (!error->message) { TokenKind kind = peek(p)->kind; + + // 后置字段访问: expr.field + if (kind == TOK_DOT) { + advance(p); // 跳过 '.' + const Token* field = expect(p, TOK_IDENT, error, "缺少字段名"); + if (!field) return NULL; + left = ast_make_field_access(p->arena, left, + arena_strdup_impl(p->arena, field->start, field->length), + field->line, field->col); + continue; + } + + // 中缀运算符 Precedence prec = tok_to_prec(kind); if (prec <= min_prec) break; @@ -179,8 +243,46 @@ static TypeKind token_to_type(TokenKind k) { default: return TYPE_VOID; } } +// === 结构体声明解析 === +static AstNode* parse_struct_decl(Parser* p, ErrorInfo* error) { + const Token* s_tok = advance(p); // 跳过 'struct' + const Token* name = expect(p, TOK_IDENT, error, "struct 后应为结构体名"); + if (!name) return NULL; + if (!expect(p, TOK_LBRACE, error, "缺少 '{'")) return NULL; + + AstNode* fields[32]; int fcount = 0; + while (peek(p)->kind != TOK_RBRACE && !error->message) { + const Token* fname = expect(p, TOK_IDENT, error, "字段名"); + if (!fname) return NULL; + if (!expect(p, TOK_COLON, error, "缺少 ':'")) return NULL; + const Token* ftype = advance(p); + TypeKind field_kind; + const char* field_struct_name = NULL; + if (is_type_token(ftype->kind)) { + field_kind = token_to_type(ftype->kind); + } else if (ftype->kind == TOK_IDENT) { + field_kind = TYPE_STRUCT; + field_struct_name = arena_strdup_impl(p->arena, ftype->start, ftype->length); + } else { + error->message = "无效的字段类型"; error->filename = p->filename; + error->line = ftype->line; error->col = ftype->col; return NULL; + } + fields[fcount++] = ast_make_parameter(p->arena, + arena_strdup_impl(p->arena, fname->start, fname->length), + field_kind, field_struct_name, fname->line, fname->col); + if (peek(p)->kind == TOK_COMMA) advance(p); + else break; + } + if (!expect(p, TOK_RBRACE, error, "缺少 '}'")) return NULL; + + AstNode** farr = arena_alloc_impl(p->arena, fcount * sizeof(AstNode*)); + memcpy(farr, fields, fcount * sizeof(AstNode*)); + return ast_make_struct_decl(p->arena, + arena_strdup_impl(p->arena, name->start, name->length), + farr, fcount, s_tok->line, s_tok->col); +} + // === 语句解析 === -static AstNode* parse_statement(Parser* p, ErrorInfo* error); static AstNode* parse_block(Parser* p, ErrorInfo* error) { const Token* open = peek(p); @@ -209,13 +311,20 @@ static AstNode* parse_statement(Parser* p, ErrorInfo* error) { // 可选的类型标注 TypeKind annot_type = TYPE_UNKNOWN; bool has_type_annot = false; + const char* struct_type_name = NULL; if (match(p, TOK_COLON)) { const Token* type_tok = advance(p); - if (!is_type_token(type_tok->kind)) { + if (!is_type_token(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; } - annot_type = token_to_type(type_tok->kind); + if (is_type_token(type_tok->kind)) { + annot_type = token_to_type(type_tok->kind); + } else { + // struct 类型名 + annot_type = TYPE_STRUCT; + struct_type_name = arena_strdup_impl(p->arena, type_tok->start, type_tok->length); + } has_type_annot = true; } if (!expect(p, TOK_ASSIGN, error, "缺少 '='")) return NULL; @@ -224,7 +333,7 @@ static AstNode* parse_statement(Parser* p, ErrorInfo* error) { if (!expect(p, TOK_SEMICOLON, error, "缺少 ';'")) return NULL; return ast_make_let(p->arena, arena_strdup_impl(p->arena, name->start, name->length), - annot_type, has_type_annot, is_mut, init, t->line, t->col); + annot_type, has_type_annot, is_mut, init, struct_type_name, t->line, t->col); } if (t->kind == TOK_IF) { @@ -285,7 +394,7 @@ static AstNode* parse_statement(Parser* p, ErrorInfo* error) { const char* vname = arena_strdup_impl(p->arena, var_name->start, var_name->length); // 构建: let mut i = start; - AstNode* let_stmt = ast_make_let(p->arena, vname, TYPE_UNKNOWN, false, true, start_expr, var_name->line, var_name->col); + AstNode* let_stmt = ast_make_let(p->arena, vname, TYPE_UNKNOWN, false, true, start_expr, NULL, var_name->line, var_name->col); // 构建: i < end (while 条件) AstNode* cond = ast_make_binary(p->arena, OP_LT, @@ -399,7 +508,7 @@ static AstNode* parse_function(Parser* p, ErrorInfo* error) { } params[pcount++] = ast_make_parameter(p->arena, arena_strdup_impl(p->arena, pname->start, pname->length), - token_to_type(ptype->kind), pname->line, pname->col); + token_to_type(ptype->kind), NULL, pname->line, pname->col); if (match(p, TOK_COMMA)) continue; else break; } @@ -432,11 +541,24 @@ AstNode* parse(Arena* a, const Token* tokens, size_t count, Parser p = {.tokens = tokens, .count = count, .pos = 0, .filename = filename, .arena = a}; AstNode* functions[256]; int fn_count = 0; + AstNode* structs[64]; int struct_count = 0; while (peek(&p)->kind != TOK_EOF && !error->message) { - functions[fn_count++] = parse_function(&p, error); + if (peek(&p)->kind == TOK_STRUCT) { + structs[struct_count++] = parse_struct_decl(&p, error); + } else if (peek(&p)->kind == TOK_FN) { + functions[fn_count++] = parse_function(&p, error); + } else { + error->message = "顶层只允许 fn 或 struct"; + error->filename = p.filename; + error->line = peek(&p)->line; + error->col = peek(&p)->col; + return NULL; + } } if (error->message) return NULL; - AstNode** arr = arena_alloc_impl(a, fn_count * sizeof(AstNode*)); - memcpy(arr, functions, fn_count * sizeof(AstNode*)); - return ast_make_program(a, arr, fn_count, 0, 0); + AstNode** fn_arr = arena_alloc_impl(a, fn_count * sizeof(AstNode*)); + memcpy(fn_arr, functions, fn_count * sizeof(AstNode*)); + AstNode** st_arr = arena_alloc_impl(a, struct_count * sizeof(AstNode*)); + memcpy(st_arr, structs, struct_count * sizeof(AstNode*)); + return ast_make_program(a, fn_arr, fn_count, st_arr, struct_count, 0, 0); } diff --git a/src/sema/sema.c b/src/sema/sema.c index 764436b..bcfdd48 100644 --- a/src/sema/sema.c +++ b/src/sema/sema.c @@ -33,6 +33,9 @@ static void analyze_expr(AstNode* node, Scope* scope, ErrorList* errors, Arena* node->type.kind = TYPE_ERROR; } else { node->type.kind = sym->type; + if (sym->type == TYPE_STRUCT && sym->struct_type_name) { + node->type.struct_name = sym->struct_type_name; + } } break; } @@ -156,6 +159,99 @@ static void analyze_expr(AstNode* node, Scope* scope, ErrorList* errors, Arena* break; } + case AST_FIELD_ACCESS: { + analyze_expr(node->as.field_access.object, scope, errors, a); + AstNode* obj = node->as.field_access.object; + if (obj->type.kind == TYPE_ERROR) { + node->type.kind = TYPE_ERROR; + break; + } + if (obj->type.kind != TYPE_STRUCT) { + error_add(errors, "", node->line, node->col, + "类型 '%s' 不是结构体,不能访问字段 '%s'", + type_name(obj->type.kind), node->as.field_access.field); + node->type.kind = TYPE_ERROR; + break; + } + // 查找结构体定义 + const char* struct_name = obj->type.struct_name; + if (!struct_name) { + error_add(errors, "", node->line, node->col, + "无法确定结构体类型"); + node->type.kind = TYPE_ERROR; + break; + } + Symbol* struct_sym = scope_lookup_struct(scope, struct_name); + if (!struct_sym) { + error_add(errors, "", node->line, node->col, + "未定义的结构体 '%s'", struct_name); + node->type.kind = TYPE_ERROR; + break; + } + int fi = scope_struct_field_index(struct_sym, node->as.field_access.field); + if (fi < 0) { + error_add(errors, "", node->line, node->col, + "结构体 '%s' 没有字段 '%s'", struct_name, node->as.field_access.field); + node->type.kind = TYPE_ERROR; + break; + } + node->type.kind = struct_sym->struct_field_types[fi]; + node->as.field_access.field_index = fi; + // 如果字段也是结构体类型,传播类型名 + if (node->type.kind == TYPE_STRUCT && + struct_sym->struct_field_struct_names && + struct_sym->struct_field_struct_names[fi]) { + node->type.struct_name = struct_sym->struct_field_struct_names[fi]; + } + break; + } + + case AST_STRUCT_INIT: { + Symbol* struct_sym = scope_lookup_struct(scope, node->as.struct_init.type_name); + if (!struct_sym) { + error_add(errors, "", node->line, node->col, + "未定义的结构体类型 '%s'", node->as.struct_init.type_name); + node->type.kind = TYPE_ERROR; + break; + } + if (node->as.struct_init.field_count != struct_sym->struct_field_count) { + error_add(errors, "", node->line, node->col, + "结构体 '%s' 有 %zu 个字段,但提供了 %zu 个", + node->as.struct_init.type_name, + struct_sym->struct_field_count, + node->as.struct_init.field_count); + node->type.kind = TYPE_ERROR; + break; + } + // 检查每个字段名和类型匹配 + for (size_t i = 0; i < node->as.struct_init.field_count; i++) { + const char* fname = node->as.struct_init.field_names[i]; + AstNode* fval = node->as.struct_init.field_values[i]; + analyze_expr(fval, scope, errors, a); + + int fi = scope_struct_field_index(struct_sym, fname); + if (fi < 0) { + error_add(errors, "", node->line, node->col, + "结构体 '%s' 没有字段 '%s'", + node->as.struct_init.type_name, fname); + node->type.kind = TYPE_ERROR; + continue; + } + TypeKind expected = struct_sym->struct_field_types[fi]; + TypeKind actual = fval->type.kind; + if (actual != TYPE_ERROR && actual != expected) { + error_add(errors, "", node->line, node->col, + "字段 '%s' 类型不匹配: 期望 '%s',得到 '%s'", + fname, type_name(expected), type_name(actual)); + } + } + if (node->type.kind != TYPE_ERROR) { + node->type.kind = TYPE_STRUCT; + node->type.struct_name = node->as.struct_init.type_name; + } + break; + } + default: break; } } @@ -165,7 +261,25 @@ static void analyze_node(AstNode* node, Scope* scope, ErrorList* errors, Arena* switch (node->kind) { case AST_PROGRAM: - // 第一遍:收集所有函数签名 + // 第一遍:收集所有结构体定义 + for (size_t i = 0; i < node->as.program.struct_count; i++) { + AstNode* sd = node->as.program.structs[i]; + const char** fnames = (const char**)arena_alloc_impl(a, + sd->as.struct_decl.field_count * sizeof(const char*)); + TypeKind* ftypes = (TypeKind*)arena_alloc_impl(a, + sd->as.struct_decl.field_count * sizeof(TypeKind)); + const char** fstruct_names = (const char**)arena_alloc_impl(a, + sd->as.struct_decl.field_count * sizeof(const char*)); + for (size_t j = 0; j < sd->as.struct_decl.field_count; j++) { + fnames[j] = sd->as.struct_decl.fields[j]->as.parameter.name; + ftypes[j] = sd->as.struct_decl.fields[j]->as.parameter.type; + fstruct_names[j] = sd->as.struct_decl.fields[j]->as.parameter.struct_type_name; + } + scope_insert_struct(scope, a, sd->as.struct_decl.name, + fnames, ftypes, fstruct_names, + sd->as.struct_decl.field_count); + } + // 第二遍:收集所有函数签名 for (size_t i = 0; i < node->as.program.fn_count; i++) { AstNode* fn = node->as.program.functions[i]; TypeKind* pts = (TypeKind*)arena_alloc_impl(a, fn->as.function.param_count * sizeof(TypeKind)); @@ -176,7 +290,7 @@ static void analyze_node(AstNode* node, Scope* scope, ErrorList* errors, Arena* fn->as.function.return_type, pts, fn->as.function.param_count); } - // 第二遍:分析每个函数体 + // 第三遍:分析每个函数体 for (size_t i = 0; i < node->as.program.fn_count; i++) { analyze_node(node->as.program.functions[i], scope, errors, a); } @@ -203,14 +317,29 @@ static void analyze_node(AstNode* node, Scope* scope, ErrorList* errors, Arena* analyze_expr(node->as.let_stmt.init, scope, errors, a); TypeKind inferred = node->as.let_stmt.init->type.kind; TypeKind var_type; + const char* var_struct_name = NULL; if (node->as.let_stmt.has_type_annot) { - // 使用显式类型标注 - var_type = node->as.let_stmt.annot_type; + const char* annot_struct = node->as.let_stmt.struct_type_name; + if (annot_struct) { + // struct 类型标注 + Symbol* st_sym = scope_lookup_struct(scope, annot_struct); + if (!st_sym) { + error_add(errors, "", node->line, node->col, + "未定义的结构体类型 '%s'", annot_struct); + break; + } + var_type = TYPE_STRUCT; + var_struct_name = annot_struct; + } else { + var_type = node->as.let_stmt.annot_type; + } if (inferred != TYPE_ERROR && inferred != var_type) { error_add(errors, "", node->line, node->col, "变量 '%s' 类型标注为 '%s',但初始化表达式类型为 '%s'", - node->as.let_stmt.name, type_name(var_type), type_name(inferred)); + node->as.let_stmt.name, + annot_struct ? annot_struct : type_name(var_type), + type_name(inferred)); } } else { // 类型推断 @@ -220,15 +349,23 @@ static void analyze_node(AstNode* node, Scope* scope, ErrorList* errors, Arena* break; } var_type = inferred; + if (inferred == TYPE_STRUCT) { + var_struct_name = node->as.let_stmt.init->type.struct_name; + } } node->type.kind = var_type; + node->type.struct_name = var_struct_name; Symbol* sym = scope_insert(scope, a, node->as.let_stmt.name, SYM_VARIABLE, var_type); if (!sym) { error_add(errors, "", node->line, node->col, "变量 '%s' 重复定义", node->as.let_stmt.name); } else { sym->is_mut = node->as.let_stmt.is_mut; + if (var_struct_name) { + sym->type = TYPE_STRUCT; + sym->struct_type_name = var_struct_name; + } } break; } @@ -304,17 +441,17 @@ static void analyze_node(AstNode* node, Scope* scope, ErrorList* errors, Arena* } void sema_analyze(AstNode* ast, ErrorList* errors, Arena* arena) { - Scope* global = scope_new(arena, NULL); + Scope* global_scope = scope_new(arena, NULL); // 注册内置函数 TypeKind params_i64[] = {TYPE_I64}; - scope_insert_function(global, arena, "print_i64", TYPE_VOID, params_i64, 1); + scope_insert_function(global_scope, arena, "print_i64", TYPE_VOID, params_i64, 1); TypeKind params_f64[] = {TYPE_F64}; - scope_insert_function(global, arena, "print_f64", TYPE_VOID, params_f64, 1); + scope_insert_function(global_scope, arena, "print_f64", TYPE_VOID, params_f64, 1); TypeKind params_bool[] = {TYPE_BOOL}; - scope_insert_function(global, arena, "print_bool", TYPE_VOID, params_bool, 1); + scope_insert_function(global_scope, arena, "print_bool", TYPE_VOID, params_bool, 1); TypeKind params_str[] = {TYPE_STR}; - scope_insert_function(global, arena, "print_str", TYPE_VOID, params_str, 1); + scope_insert_function(global_scope, arena, "print_str", TYPE_VOID, params_str, 1); - analyze_node(ast, global, errors, arena); + analyze_node(ast, global_scope, errors, arena); } diff --git a/src/sema/symbol.c b/src/sema/symbol.c index 56a3642..ecdfba1 100644 --- a/src/sema/symbol.c +++ b/src/sema/symbol.c @@ -29,6 +29,10 @@ Symbol* scope_insert(Scope* scope, void* alloc, const char* name, sym->name = name; sym->kind = kind; sym->type = type; sym->is_mut = false; sym->return_type = TYPE_VOID; sym->param_types = NULL; sym->param_count = 0; + sym->struct_field_names = NULL; + sym->struct_field_types = NULL; + sym->struct_field_count = 0; + sym->struct_type_name = NULL; sym->next = scope->head; scope->head = sym; return sym; @@ -44,7 +48,52 @@ Symbol* scope_insert_function(Scope* scope, void* alloc, const char* name, Symbol* sym = (Symbol*)arena_alloc_impl(alloc, sizeof(Symbol)); sym->name = name; sym->kind = SYM_FUNCTION; sym->type = TYPE_VOID; sym->return_type = ret; sym->param_types = pt; sym->param_count = pc; + sym->struct_field_names = NULL; + sym->struct_field_types = NULL; + sym->struct_field_count = 0; + sym->struct_type_name = NULL; sym->next = scope->head; scope->head = sym; return sym; } + +Symbol* scope_insert_struct(Scope* scope, void* alloc, const char* name, + const char** fnames, TypeKind* ftypes, + const char** fstruct_names, size_t fc) { + if (scope->head) { + for (Symbol* sym = scope->head; sym; sym = sym->next) { + if (strcmp(sym->name, name) == 0) return NULL; + } + } + Symbol* sym = (Symbol*)arena_alloc_impl(alloc, sizeof(Symbol)); + sym->name = name; sym->kind = SYM_STRUCT; sym->type = TYPE_STRUCT; + sym->is_mut = false; sym->return_type = TYPE_VOID; + sym->param_types = NULL; sym->param_count = 0; + sym->struct_field_names = fnames; + sym->struct_field_types = ftypes; + sym->struct_field_struct_names = fstruct_names; + sym->struct_field_count = fc; + sym->struct_type_name = NULL; + sym->next = scope->head; + scope->head = sym; + return sym; +} + +Symbol* scope_lookup_struct(const Scope* scope, const char* name) { + for (const Scope* s = scope; s; s = s->parent) { + for (Symbol* sym = s->head; sym; sym = sym->next) { + if (sym->kind == SYM_STRUCT && strcmp(sym->name, name) == 0) + return sym; + } + } + return NULL; +} + +int scope_struct_field_index(const Symbol* sym, const char* field_name) { + if (sym->kind != SYM_STRUCT) return -1; + for (size_t i = 0; i < sym->struct_field_count; i++) { + if (strcmp(sym->struct_field_names[i], field_name) == 0) + return (int)i; + } + return -1; +} diff --git a/src/sema/symbol.h b/src/sema/symbol.h index 6a0f681..7454c15 100644 --- a/src/sema/symbol.h +++ b/src/sema/symbol.h @@ -4,7 +4,7 @@ #include "l_lang.h" #include "ast.h" -typedef enum { SYM_VARIABLE, SYM_PARAMETER, SYM_FUNCTION } SymbolKind; +typedef enum { SYM_VARIABLE, SYM_PARAMETER, SYM_FUNCTION, SYM_STRUCT } SymbolKind; typedef struct Symbol { const char* name; @@ -15,6 +15,13 @@ typedef struct Symbol { TypeKind return_type; TypeKind* param_types; size_t param_count; + // 结构体特有(SYM_STRUCT) + const char** struct_field_names; + TypeKind* struct_field_types; + const char** struct_field_struct_names; // 字段为 struct 类型时的具体类型名 + size_t struct_field_count; + // 变量引用结构体类型时,记录具体类型名 + const char* struct_type_name; // 链表(同一作用域内的下一个符号) struct Symbol* next; } Symbol; @@ -38,4 +45,15 @@ Symbol* scope_insert(Scope* scope, void* alloc, const char* name, Symbol* scope_insert_function(Scope* scope, void* alloc, const char* name, TypeKind ret, TypeKind* pt, size_t pc); +// 插入结构体符号 +Symbol* scope_insert_struct(Scope* scope, void* alloc, const char* name, + const char** fnames, TypeKind* ftypes, + const char** fstruct_names, size_t fc); + +// 查找结构体符号(在所有作用域中) +Symbol* scope_lookup_struct(const Scope* scope, const char* name); + +// 在结构体符号中查找字段索引(返回 -1 表示未找到) +int scope_struct_field_index(const Symbol* sym, const char* field_name); + #endif diff --git a/test/programs/12_struct.l b/test/programs/12_struct.l new file mode 100644 index 0000000..60658f4 --- /dev/null +++ b/test/programs/12_struct.l @@ -0,0 +1,11 @@ +struct Point { + x: i64, + y: i64, +} + +fn main() -> i64 { + let p: Point = Point { x: 10, y: 20 }; + print_i64(p.x); + print_i64(p.y); + return 0; +} diff --git a/test/programs/13_struct_nested.l b/test/programs/13_struct_nested.l new file mode 100644 index 0000000..df814b8 --- /dev/null +++ b/test/programs/13_struct_nested.l @@ -0,0 +1,19 @@ +struct Point { + x: i64, + y: i64, +} + +struct Rect { + top_left: Point, + bot_right: Point, +} + +fn main() -> i64 { + let r: Rect = Rect { + top_left: Point { x: 0, y: 10 }, + bot_right: Point { x: 5, y: 0 }, + }; + print_i64(r.top_left.x); + print_i64(r.bot_right.y); + return 0; +} diff --git a/test/test_codegen.c b/test/test_codegen.c index 6070a76..0468997 100644 --- a/test/test_codegen.c +++ b/test/test_codegen.c @@ -13,7 +13,7 @@ void test_codegen_simple_function() { AstNode* body = ast_make_block(&a, stmts, 1, 1, 1); AstNode* fn = ast_make_function(&a, "main", NULL, 0, TYPE_I64, body, 1, 1); AstNode* fns[] = { fn }; - AstNode* prog = ast_make_program(&a, fns, 1, 1, 1); + AstNode* prog = ast_make_program(&a, fns, 1, NULL, 0, 1, 1); const char* err = NULL; LLVMModuleRef mod = codegen_module(prog, &a, "test_mod", &err); @@ -45,7 +45,7 @@ void test_codegen_if_else() { AstNode* body = ast_make_block(&a, stmts, 1, 1, 1); AstNode* fn = ast_make_function(&a, "main", NULL, 0, TYPE_I64, body, 1, 1); AstNode* fns[] = { fn }; - AstNode* prog = ast_make_program(&a, fns, 1, 1, 1); + AstNode* prog = ast_make_program(&a, fns, 1, NULL, 0, 1, 1); const char* err = NULL; LLVMModuleRef mod = codegen_module(prog, &a, "test_mod2", &err); @@ -74,7 +74,7 @@ void test_codegen_binary_ops() { AstNode* body = ast_make_block(&a, stmts, 1, 1, 1); AstNode* fn = ast_make_function(&a, "main", NULL, 0, TYPE_I64, body, 1, 1); AstNode* fns[] = { fn }; - AstNode* prog = ast_make_program(&a, fns, 1, 1, 1); + AstNode* prog = ast_make_program(&a, fns, 1, NULL, 0, 1, 1); const char* err = NULL; LLVMModuleRef mod = codegen_module(prog, &a, "test_mod3", &err); @@ -102,7 +102,7 @@ void test_codegen_while_loop() { AstNode* fn_body = ast_make_block(&a, stmts, 2, 1, 1); AstNode* fn = ast_make_function(&a, "main", NULL, 0, TYPE_I64, fn_body, 1, 1); AstNode* fns[] = { fn }; - AstNode* prog = ast_make_program(&a, fns, 1, 1, 1); + AstNode* prog = ast_make_program(&a, fns, 1, NULL, 0, 1, 1); const char* err = NULL; LLVMModuleRef mod = codegen_module(prog, &a, "test_while", &err);