feat: for 循环 + range (for i in start..end)

- lexer: TOK_FOR, TOK_IN, TOK_DOT_DOT + 修复数字中 .. 解析
- parser: for-in-range → desugar to {let mut, while, assign}
- 零 sema/codegen 改动,复用现有机制
- 新增 2 个集成测试 (10_for_range.l, 11_for_step2.l)
- mem2reg: LLVM 22 C API 不导出,标注已知限制

基于 Codex 分析报告 §6 P0 #3。
This commit is contained in:
2026-06-05 04:42:44 +08:00
parent 72a971e5bf
commit 8144f1bfd7
8 changed files with 349 additions and 6 deletions
@@ -0,0 +1,262 @@
# L Language 架构分析报告 (v0.2)
> 日期: 2026-06-05 03:03 | 自动生成 | 自上次报告后有 3 个提交
> 上次报告: [architecture-analysis-report-2026-06-05-0100.md](/D:/Code/doing_exercises/programs/L%20Language/docs/analysis/architecture-analysis-report-2026-06-05-0100.md) (基线 9ff2990)
---
## 变更摘要 (自上次报告)
上次报告提出的 P0 #1 (复合赋值) 和 P0 #2 (修复 str+str 拼接) 已全部落地。另新增 codegen 层单元测试,覆盖了技术债 #4
| Commit | 功能 | 变更量 |
|--------|------|--------|
| `9e41b09` | fix: str+str 运行时拼接 — malloc + strlen + memcpy | codegen.c +62 |
| `d5a94d4` | feat: 复合赋值 += -= *= /= + CHANGELOG 更新 | lexer/parser/token +41 |
| `72a971e` | test: codegen 层单元测试 (3 函数, 7 断言) | test_codegen.c +96, CMakeLists +10 |
11 个文件变更, +516/−4 行代码。
---
## 1. 当前架构全景
### 1.1 编译流水线 (无变化)
```
源码(.l) -> Lexer(词法) -> Parser(语法) -> Sema(语义) -> Codegen(LLVM IR) -> Target(obj) -> GCC链接(.exe)
Token[] AstNode* 类型标注 LLVMModuleRef .o 文件
```
整条流水线手写实现,零生成器依赖。Codegen 与 Target 通过 `target.h/c` 解耦。
### 1.2 模块清单与指标
| 模块 | 文件 | 行数 | 关键职责 | 新增项 |
|------|------|------|---------|--------|
| include/ | l_lang.h | 34 | TypeKind 枚举, 向前声明, arena_alloc_impl | — |
| util/ | arena.c(39) + .h(13) | 52 | 8MB bump allocator | — |
| lexer/ | lexer.c(135) token.c(45) + .h(40) | 220 | 手写状态机, 40 Token 类型 | TOK_PLUS_EQ TOK_MINUS_EQ TOK_STAR_EQ TOK_SLASH_EQ |
| ast/ | ast.c(107) ast.h(93) | 200 | 15 种节点类型 (含 AST_ASSIGN_STMT) | — |
| parser/ | parser.c(341) parser.h(10) | 351 | Pratt 解析 + 递归下降语句/函数 | 复合赋值 desugar (ident += expr -> ident = ident + expr) |
| sema/ | sema.c(294) symbol.c(46) + .h(32) | 372 | 类型推断, 作用域链, 可变性检查 | 复合赋值的 mut 检查 (通过 desugar 后路径) |
| codegen/ | codegen.c(420) target.c(30) + .h(15) | 465 | LLVM IR 生成, 4 个运行时函数 | malloc/strlen/memcpy 声明, str+str concat 实现 |
| driver/ | main.c(133) error.c(46) + .h(18) | 197 | 流水线串联, 错误报告 | — |
**总计: 22 源文件, ~2,163 行代码 (1,636 实现 .c + 285 头文件 .h + 242 测试)**
### 1.3 技术选型评估
| 选择 | 评价 | 最新状态 |
|------|------|---------|
| C17 + CMake + MinGW | 轻量, 学习友好 | 稳定 |
| Arena bump allocator (8MB) | 极简, 零 GC 开销 | 已统一, 全流水线 arena |
| LLVM-C API 19.x | 成熟, 版本差异已适配 | 声明 4 个 C 运行时函数 (printf, malloc, strlen, memcpy) |
| 手写 Lexer/Parser | 0 外部依赖 | 稳定, 40 种 Token (新增 4 种复合赋值) |
| Pratt 表达式解析 | 优雅优先级处理 | 无变化 |
| GCC 链接 (system()) | 简单但平台绑定 | **待改进** |
| target.h/c 解耦 | 关注点分离 | 无变化 |
---
## 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 = expr;
- [x] 可变性检查 — 不可变变量赋值报编译错误
- [x] 字符串类型 str + 双引号字面量
- [x] print_str 内置函数
- [x] LLVM 目标初始化解耦 (target.h/c)
- [x] codegen malloc -> arena 统一
- [x] 6 个集成测试 (01-06)
### 2.3 本次迭代新增 (v0.2 完成项)
- [x] **str+str 运行时拼接** — codegen 中调用 malloc+strlen+memcpy 实现 (此前语义层通过但运行时返回左操作数)
- [x] **复合赋值** += -= *= /= — 4 个新 Token, parser 去糖为 x = x op expr, sema 通过去糖路径自动获得可变性检查
- [x] **codegen 层单元测试** — 3 个测试函数 (简单函数 / if-else / 二元运算), 7 个断言, 均含 LLVMVerifyModule 验证
- [x] **2 个新集成测试**: 08_str_concat.l (str+str 拼接), 09_compound_assign.l (四种复合赋值)
- [x] CHANGELOG 更新为 v0.2.0
### 2.4 实现细节: 复合赋值去糖路径
```
x += expr; (源码)
v lexer
TOK_IDENT("x") TOK_PLUS_EQ TOK_INT_LIT(5) TOK_SEMICOLON
v parser (desugar)
AST_ASSIGN_STMT { name="x", value=AST_BINARY_EXPR { OP_ADD, AST_IDENT("x"), AST_LITERAL(5) } }
v sema (分析 assign_stmt)
检查 x 的可变性, 分析 value 表达式 (x + 5), 检查类型匹配
v codegen (生成 assign_stmt -> LLVMBuildStore)
```
---
## 3. 与 Rust 对标: 缺失功能清单及状态
按优先级排序。本次迭代完成项标注 ✅。
### P0: 短期 (1-2 天/项)
| # | 功能 | Rust 启发 | 改动范围 | 状态 |
|---|------|----------|---------|------|
| 1 | 复合赋值 += -= *= /= | 复合赋值运算符 | lexer(4 Token) + parser(desugar) | **✅ 已完成** (d5a94d4) |
| 2 | 修复 str+str 运行时拼接 | — | codegen(malloc/strlen/memcpy) | **✅ 已完成** (9e41b09) |
| 3 | **for 循环 + Range** | for i in 0..10 {} | lexer(for/in/..) + parser + sema + codegen->while desugar | **待实施** |
| 4 | **结构体 struct** | struct 具名域 | lexer(struct/.) + parser + sema + codegen(GEP) | **待实施** |
### 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 模式 |
| 表达式 vs 语句 | ❌ | if/match/block 作为表达式 |
| 模式匹配 | ❌ | P1 优先级 |
| 代数数据类型 | ❌ | 需泛型 |
| 所有权 / 借用 | ❌ | 远期 |
---
## 4. 架构改进: 已完成 vs 待实施
### 4.1 已完成 (本次)
1. **str+str 运行时拼接** (来自上次报告 4.2-6)
- codegen.c 声明 malloc/strlen/memcpy 三个 C 运行时函数
- AST_BINARY_EXPR 对 TYPE_STR 使用 LLVMBuildCall2 调用 malloc + memcpy 实现拼接
- 运行时行为: strlen(l) + strlen(r) + 1 分配, 两次 memcpy
2. **复合赋值去糖** (来自上次报告 P0 #1)
- lexer: TOK_PLUS_EQ TOK_MINUS_EQ TOK_STAR_EQ TOK_SLASH_EQ 4 个新 Token
- parser: x += expr -> ast_make_assign("x", ast_make_binary(OP_ADD, ident("x"), expr))
- sema: 自然复用 AST_ASSIGN_STMT 的可变性检查逻辑
3. **Codegen 层测试** (来自上次报告 4.2-4, 技术债 #4)
- test_codegen.c: 3 个测试函数, 每个均调用 LLVMVerifyModule 验证 IR 合法性
- CMakeLists.txt 新增 l_lang_codegen_test 可执行文件
- 覆盖: 简单函数、if/else、二元运算 — **缺少 while/call/let mut/str/concat/compound/print**
### 4.2 待实施 (继承自上次报告)
| # | 改进 | 优先级 | 说明 |
|---|------|--------|------|
| 1 | 错误类型统一 (ErrorInfo + ErrorList -> CompilerError) | P1 | 当前两个错误结构体分散多个模块 |
| 2 | 调用约定抽象 (abi.h) | P2 | 封装参数传递和返回值处理 |
| 3 | system("gcc ...") -> Linker 接口 | P2 | 平台解耦, 支持 ld/lld |
| 4 | 优化 pass (mem2reg) | P1 | 当前 alloca 效率低, 加一个 pass 即可显著提升 |
---
## 5. 技术债务与风险
| # | 问题 | 状态 | 严重度 | 说明 |
|---|------|------|--------|------|
| 1 | str concat malloc 内存泄漏 | **新增** | 中 | x + y 每次都 malloc 但无 free, 长运行程序会泄漏 |
| 2 | 复合赋值无独立测试 | **新增** | 低 | parser desugar 路径无针对性测试 (sema 的 assign 测试可作为间接覆盖) |
| 3 | sema 层无 str/let mut/assign 专用测试 | **存留** | 中 | 仅 3 个 sema 测试, 新功能无回归保护 |
| 4 | codegen 测试覆盖率低 | **部分缓解** | 中 | 新增 3 个测试, 但 while/call/str/print/compound 仍无覆盖 |
| 5 | system("gcc ...") 平台绑定 | **存留** | 低 | Windows/MinGW 正常工作, 但不可移植 |
| 6 | 无优化 pass | **存留** | 低 | 功能可用但 alloca 密集, mem2reg 能显著改善 |
| 7 | 错误类型分散 (ErrorInfo + ErrorList) | **存留** | 低 | 技术债, 不影响功能 |
---
## 6. 推荐开发路线图
### v0.3 目标 (下一个迭代)
| 优先级 | 功能 | 预计工时 | 依赖/备注 |
|--------|------|---------|----------|
| P0 | for 循环 + range | 1 天 | 去糖为 while, 改动面集中在 parser |
| P0 | 结构体 struct | 2-3 天 | 解锁 GEP 和复合类型, 全流水线 |
| P1 | sema 层测试补全 (str/let mut/assign) | 0.5 天 | 回归保护 |
| P1 | 优化 pass (mem2reg) | 0.25 天 | 一行调用 |
| P1 | codegen 测试补全 (while/call/str) | 0.5 天 | 覆盖率补偿 |
### v0.4 目标
| 优先级 | 功能 | 预计工时 |
|--------|------|---------|
| 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.1+, 9ff2990) | 本次 (v0.2, 72a971e) | 变化 |
|------|--------------------------|---------------------|------|
| 源文件数 | 22 | 23 (+test_codegen.c) | +1 |
| 实现代码 (.c) | ~1,530 | 1,636 | +106 |
| 头文件 (.h) | ~303 | 285 | -18 (token.h 精简) |
| 测试代码 | ~146 | 242 | +96 |
| Token 类型 | 30 | 34 | +4 (复合赋值) |
| AST 节点类型 | 15 | 15 | 无变化 |
| 类型系统 | 5 种 (i64/f64/bool/str/void) | 5 种 | 无变化 |
| 内置函数 | 4 (print_*) | 4 | 无变化 |
| 集成测试 | 7 程序 | 9 程序 | +2 |
| 单元测试函数 | 11 | 14 | +3 (codegen) |
| 单元测试断言 | 38 | 45 | +7 |
| C 运行时依赖 | printf | printf + malloc + strlen + memcpy | +3 |
| LLVM 模块 | codegen.c + target.c | 无变化 | — |
| 报告 P0 完成度 | — | 2/2 (本次目标) | 100% |
---
*本报告由 Codex 自动生成。自上次报告 (2026-06-05 01:00) 后代码有显著变化 (3 commits, +516/-4 行)。上次报告的 P0 #1 #2 和 技术债 #4 全部落地, P0 #3 (for) 和 P0 #4 (struct) 为下一迭代目标。*
+1
View File
@@ -457,6 +457,7 @@ LLVMModuleRef codegen_module(AstNode* ast, Arena* codegen_arena,
} }
// 验证模块(使用 ReturnStatus 以获取完整错误消息) // 验证模块(使用 ReturnStatus 以获取完整错误消息)
// 注: LLVM 22 C API 不再导出 mem2reg pass, alloca 优化需用 opt 工具
char* verify_err = NULL; char* verify_err = NULL;
if (LLVMVerifyModule(ctx.module, LLVMReturnStatusAction, &verify_err)) { if (LLVMVerifyModule(ctx.module, LLVMReturnStatusAction, &verify_err)) {
*error_msg = verify_err ? verify_err : "模块验证失败(错误消息为 NULL"; *error_msg = verify_err ? verify_err : "模块验证失败(错误消息为 NULL";
+4 -2
View File
@@ -45,7 +45,7 @@ static Token lex_number(Lexer* l) {
int start = l->pos; int start = l->pos;
TokenKind kind = TOK_INT_LIT; TokenKind kind = TOK_INT_LIT;
while (isdigit(peek(l))) advance(l); while (isdigit(peek(l))) advance(l);
if (peek(l) == '.') { if (peek(l) == '.' && peek_next(l) != '.') {
kind = TOK_FLOAT_LIT; advance(l); kind = TOK_FLOAT_LIT; advance(l);
while (isdigit(peek(l))) advance(l); while (isdigit(peek(l))) advance(l);
} }
@@ -57,7 +57,8 @@ static TokenKind check_keyword(const Token* tok) {
KW("fn", TOK_FN); KW("let", TOK_LET); KW("fn", TOK_FN); KW("let", TOK_LET);
KW("mut", TOK_MUT); KW("mut", TOK_MUT);
KW("if", TOK_IF); KW("else", TOK_ELSE); KW("if", TOK_IF); KW("else", TOK_ELSE);
KW("while", TOK_WHILE); KW("return", TOK_RETURN); KW("while", TOK_WHILE); KW("for", TOK_FOR); KW("in", TOK_IN);
KW("return", TOK_RETURN);
KW("i64", TOK_I64); KW("f64", TOK_F64); KW("i64", TOK_I64); KW("f64", TOK_F64);
KW("bool", TOK_BOOL); KW("str", TOK_STR); KW("bool", TOK_BOOL); KW("str", TOK_STR);
KW("void", TOK_VOID); KW("void", TOK_VOID);
@@ -124,6 +125,7 @@ Token* lex(Arena* a, const char* source, const char* filename,
else if (c == '>') { tokens[idx++] = make_token(&l, TOK_GT, l.pos, 1); advance(&l); } else if (c == '>') { tokens[idx++] = make_token(&l, TOK_GT, l.pos, 1); advance(&l); }
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_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_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_LPAREN, 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_RPAREN, l.pos, 1); advance(&l); }
else if (c == '{') { tokens[idx++] = make_token(&l, TOK_LBRACE, l.pos, 1); advance(&l); } else if (c == '{') { tokens[idx++] = make_token(&l, TOK_LBRACE, l.pos, 1); advance(&l); }
+2 -2
View File
@@ -6,7 +6,7 @@
static const char* NAMES[] = { static const char* NAMES[] = {
[TOK_FN] = "fn", [TOK_LET] = "let", [TOK_MUT] = "mut", [TOK_IF] = "if", [TOK_FN] = "fn", [TOK_LET] = "let", [TOK_MUT] = "mut", [TOK_IF] = "if",
[TOK_ELSE] = "else", [TOK_WHILE] = "while", [TOK_RETURN] = "return", [TOK_ELSE] = "else", [TOK_WHILE] = "while", [TOK_FOR] = "for", [TOK_IN] = "in", [TOK_RETURN] = "return",
[TOK_I64] = "i64", [TOK_F64] = "f64", [TOK_BOOL] = "bool", [TOK_STR] = "str", [TOK_VOID] = "void", [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_INT_LIT] = "整数", [TOK_FLOAT_LIT] = "浮点数", [TOK_STR_LIT] = "字符串",
[TOK_TRUE] = "true", [TOK_FALSE] = "false", [TOK_TRUE] = "true", [TOK_FALSE] = "false",
@@ -16,7 +16,7 @@ static const char* NAMES[] = {
[TOK_EQ_EQ] = "==", [TOK_BANG_EQ] = "!=", [TOK_EQ_EQ] = "==", [TOK_BANG_EQ] = "!=",
[TOK_LT] = "<", [TOK_GT] = ">", [TOK_LT_EQ] = "<=", [TOK_GT_EQ] = ">=", [TOK_LT] = "<", [TOK_GT] = ">", [TOK_LT_EQ] = "<=", [TOK_GT_EQ] = ">=",
[TOK_AND_AND] = "&&", [TOK_PIPE_PIPE] = "||", [TOK_BANG] = "!", [TOK_AND_AND] = "&&", [TOK_PIPE_PIPE] = "||", [TOK_BANG] = "!",
[TOK_ARROW] = "->", [TOK_ARROW] = "->", [TOK_DOT_DOT] = "..",
[TOK_PLUS_EQ] = "+=", [TOK_MINUS_EQ] = "-=", [TOK_STAR_EQ] = "*=", [TOK_SLASH_EQ] = "/=", [TOK_PLUS_EQ] = "+=", [TOK_MINUS_EQ] = "-=", [TOK_STAR_EQ] = "*=", [TOK_SLASH_EQ] = "/=",
[TOK_LPAREN] = "(", [TOK_RPAREN] = ")", [TOK_LPAREN] = "(", [TOK_RPAREN] = ")",
[TOK_LBRACE] = "{", [TOK_RBRACE] = "}", [TOK_LBRACE] = "{", [TOK_RBRACE] = "}",
+2 -2
View File
@@ -6,7 +6,7 @@
// === Token 类型枚举 === // === Token 类型枚举 ===
typedef enum { typedef enum {
// 关键字 // 关键字
TOK_FN, TOK_LET, TOK_MUT, TOK_IF, TOK_ELSE, TOK_WHILE, TOK_RETURN, TOK_FN, TOK_LET, TOK_MUT, TOK_IF, TOK_ELSE, TOK_WHILE, TOK_FOR, TOK_IN, TOK_RETURN,
// 类型关键字 // 类型关键字
TOK_I64, TOK_F64, TOK_BOOL, TOK_STR, TOK_VOID, TOK_I64, TOK_F64, TOK_BOOL, TOK_STR, TOK_VOID,
// 字面量 // 字面量
@@ -17,7 +17,7 @@ typedef enum {
TOK_PLUS, TOK_MINUS, TOK_STAR, TOK_SLASH, TOK_PERCENT, TOK_PLUS, TOK_MINUS, TOK_STAR, TOK_SLASH, TOK_PERCENT,
TOK_EQ_EQ, TOK_BANG_EQ, TOK_LT, TOK_GT, TOK_LT_EQ, TOK_GT_EQ, TOK_EQ_EQ, TOK_BANG_EQ, TOK_LT, TOK_GT, TOK_LT_EQ, TOK_GT_EQ,
TOK_AND_AND, TOK_PIPE_PIPE, TOK_BANG, TOK_AND_AND, TOK_PIPE_PIPE, TOK_BANG,
TOK_ARROW, TOK_ARROW, TOK_DOT_DOT,
TOK_PLUS_EQ, TOK_MINUS_EQ, TOK_STAR_EQ, TOK_SLASH_EQ, TOK_PLUS_EQ, TOK_MINUS_EQ, TOK_STAR_EQ, TOK_SLASH_EQ,
// 分隔符 // 分隔符
TOK_LPAREN, TOK_RPAREN, TOK_LBRACE, TOK_RBRACE, TOK_LPAREN, TOK_RPAREN, TOK_LBRACE, TOK_RBRACE,
+64
View File
@@ -254,6 +254,70 @@ static AstNode* parse_statement(Parser* p, ErrorInfo* error) {
return ast_make_while(p->arena, cond, body, t->line, t->col); return ast_make_while(p->arena, cond, body, t->line, t->col);
} }
if (t->kind == TOK_FOR) {
advance(p); // 跳过 'for'
// 解析循环变量名
const Token* var_name = expect(p, TOK_IDENT, error, "for 后应为变量名");
if (!var_name) return NULL;
// 解析 'in'
if (!expect(p, TOK_IN, error, "缺少 'in'")) return NULL;
// 解析起始表达式
AstNode* start_expr = parse_expr(p, error);
if (!start_expr) return NULL;
// 解析 '..'
if (!expect(p, TOK_DOT_DOT, error, "缺少 '..'")) return NULL;
// 解析结束表达式
AstNode* end_expr = parse_expr(p, error);
if (!end_expr) return NULL;
// 解析循环体
AstNode* body = parse_block(p, error);
if (!body) return NULL;
// 脱糖: for i in start..end { body; }
// → { let mut i = start; while i < end { body; i = i + 1; } }
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);
// 构建: i < end (while 条件)
AstNode* cond = ast_make_binary(p->arena, OP_LT,
ast_make_ident(p->arena, vname, var_name->line, var_name->col),
end_expr, var_name->line, var_name->col);
// 构建: i = i + 1 (循环增量)
AstNode* incr = ast_make_assign(p->arena, vname,
ast_make_binary(p->arena, OP_ADD,
ast_make_ident(p->arena, vname, var_name->line, var_name->col),
ast_make_literal_i64(p->arena, 1, var_name->line, var_name->col),
var_name->line, var_name->col),
var_name->line, var_name->col);
// 将增量追加到循环体末尾
AstNode** new_stmts = arena_alloc_impl(p->arena,
(body->as.block.stmt_count + 1) * sizeof(AstNode*));
memcpy(new_stmts, body->as.block.stmts, body->as.block.stmt_count * sizeof(AstNode*));
new_stmts[body->as.block.stmt_count] = incr;
AstNode* new_body = ast_make_block(p->arena, new_stmts,
body->as.block.stmt_count + 1, body->line, body->col);
// 构建: while i < end { ... body ... ; i = i + 1; }
AstNode* while_loop = ast_make_while(p->arena, cond, new_body, t->line, t->col);
// 包装: { let mut i = start; while i < end { ... } }
AstNode* stmts_arr[2] = { let_stmt, while_loop };
AstNode** stmts = arena_alloc_impl(p->arena, 2 * sizeof(AstNode*));
memcpy(stmts, stmts_arr, 2 * sizeof(AstNode*));
return ast_make_block(p->arena, stmts, 2, t->line, t->col);
}
if (t->kind == TOK_RETURN) { if (t->kind == TOK_RETURN) {
advance(p); advance(p);
if (match(p, TOK_SEMICOLON)) { if (match(p, TOK_SEMICOLON)) {
+6
View File
@@ -0,0 +1,6 @@
fn main() -> i64 {
for i in 0..5 {
print_i64(i);
}
return 0;
}
+8
View File
@@ -0,0 +1,8 @@
fn main() -> i64 {
let start: i64 = 10;
let end: i64 = 15;
for n in start..end {
print_i64(n);
}
return 0;
}