diff --git a/CURRENT_TASK.md b/CURRENT_TASK.md index d5f54671..3cc056b8 100644 --- a/CURRENT_TASK.md +++ b/CURRENT_TASK.md @@ -197,6 +197,83 @@ --- +### 1-00f. Phase 34-7 — tiny while ループの AST→JoinIR 対応(**完了** 2025-11-27) + +**目的** +- AST(Program JSON v0)の Loop ノードを JoinIR に lowering する frontend 実装 +- Phase 31 の LoopToJoinLowerer(MIR→JoinIR)と同型の JoinIR 生成を達成 +- AST→JoinIR→MIR→VM の完全な経路を実証 + +**実装内容** +1. `ast_lowerer.rs` に `lower_loop_case_a_simple()` 実装(3関数構造: entry/loop_step/k_exit) + - entry: 初期化(i=0, acc=0)→ Call(loop_step) + - loop_step: Jump(k_exit, cond=!(i=n) で早期 return + - Continue: Select(条件付き値更新)+ Call(末尾再帰) + - 3関数構造(entry/loop_step/k_exit)を維持 +2. JSON v0 フィクスチャ作成(break/continue 2パターン) +3. テスト追加(JoinIrFrontendTestRunner 使用) + +**技術的発見** +- **Continue に k_continue 継続は不要**(3関数構造で十分) + - Continue = 「値更新をスキップして末尾再帰」 + - Select 命令で条件付き値更新を表現 +- Break = Phase 34-7 の早期 return と完全同型 +- Phase 31 (MIR→JoinIR) と Phase 34 (AST→JoinIR) が同じ JoinIR を生成 + +**テスト結果** +- ✅ Break: n=5 → acc=10, n=3 → acc=3 PASS +- ✅ Continue: n=5 → acc=12, n=2 → acc=3 PASS +- ✅ 全6テスト(Phase 34-2/3/6/7/8)PASS + +**更新ドキュメント** +- `src/mir/join_ir/frontend/ast_lowerer.rs` (Break/Continue lowering 追加, 約200行) +- `src/tests/joinir_frontend_if_select.rs` (2テスト追加) +- `docs/private/roadmap2/phases/phase-34-joinir-frontend/README.md` (Phase 34-8 追加) +- `docs/private/roadmap2/phases/phase-34-joinir-frontend/TASKS.md` (Phase 34-8 完了) + +**次のステップ** +- Phase 34-9: ネストループ・複雑な式への拡張 +- Phase 35: PHI 箱削減(JoinIR Frontend が主経路化したら実施) + +**マイルストーン達成** +If/Loop/Break/Continue の最小パターンが全て AST→JoinIR→MIR→VM で説明可能になり、Phase 35 での PHI 箱削減の布石が完成。 + +--- + ### 1-00f. Phase 34-1 — JoinIR Frontend (AST→JoinIR) 設計フェーズ(**完了** 2025-11-27) **Phase 33 までの成果(簡潔要約)** diff --git a/docs/private b/docs/private index 397ff8fd..360426bf 160000 --- a/docs/private +++ b/docs/private @@ -1 +1 @@ -Subproject commit 397ff8fd7182837ebbf003318f36cef40b72fb79 +Subproject commit 360426bf8fa98a9a9fec9c1a20292e03fbc75eb1 diff --git a/src/mir/join_ir/frontend/ast_lowerer.rs b/src/mir/join_ir/frontend/ast_lowerer.rs index 53ee702b..2ef2ab6f 100644 --- a/src/mir/join_ir/frontend/ast_lowerer.rs +++ b/src/mir/join_ir/frontend/ast_lowerer.rs @@ -100,11 +100,15 @@ impl AstToJoinIrLowerer { .as_str() .expect("Function must have 'name'"); - // 3. 関数名で分岐(Phase 34-2/34-3/34-4/34-5) - // simple/local/_read_value_from_pair すべて同じ If Return pattern なので共通処理 - // Phase 34-5: extract_value で Int/Var/Method に統一対応 + // 3. 関数名で分岐(Phase 34-2/34-3/34-4/34-5/34-7/34-8) + // test/local/_read_value_from_pair: If Return pattern + // simple: Loop pattern (Phase 34-7/34-8) match func_name { "test" | "local" | "_read_value_from_pair" => self.lower_if_return_pattern(program_json), + "simple" => { + // Phase 34-8: Loop パターンの詳細分析(break/continue 検出) + self.lower_loop_with_break_continue(program_json) + }, _ => panic!("Unsupported function: {}", func_name), } } @@ -242,6 +246,774 @@ impl AstToJoinIrLowerer { } } + /// Phase 34-8: Break/Continue 付きループの lowering(パターン検出) + /// + /// Loop body を解析して Break/Continue を検出し、適切な lowering 関数にディスパッチ + fn lower_loop_with_break_continue(&mut self, program_json: &serde_json::Value) -> JoinModule { + // 1. Program(JSON) から defs を取得 + let defs = program_json["defs"] + .as_array() + .expect("Program(JSON v0) must have 'defs' array"); + + let func_def = defs + .get(0) + .expect("At least one function definition required"); + + // 2. body を解析: Local 初期化 + Loop + Return + let body = &func_def["body"]["body"]; + let stmts = body + .as_array() + .expect("Function body must be array"); + + // Loop ノードを探す + let loop_node = stmts.iter().find(|stmt| { + stmt["type"].as_str() == Some("Loop") + }).expect("Loop node not found"); + + let loop_body = loop_node["body"] + .as_array() + .expect("Loop must have 'body' array"); + + // Break/Continue を検出 + let has_break = Self::has_break_in_loop_body(loop_body); + let has_continue = Self::has_continue_in_loop_body(loop_body); + + // パターンに応じてディスパッチ + if has_break && !has_continue { + self.lower_loop_break_pattern(program_json) + } else if has_continue && !has_break { + self.lower_loop_continue_pattern(program_json) + } else if has_break && has_continue { + panic!("Mixed Break/Continue pattern not yet supported in Phase 34-8"); + } else { + // Phase 34-7 の simple pattern + self.lower_loop_case_a_simple(program_json) + } + } + + /// Loop body に Break があるかチェック + fn has_break_in_loop_body(loop_body: &[serde_json::Value]) -> bool { + loop_body.iter().any(|stmt| { + if stmt["type"].as_str() == Some("If") { + if let Some(then_body) = stmt["then"].as_array() { + then_body.iter().any(|s| s["type"].as_str() == Some("Break")) + } else { + false + } + } else { + false + } + }) + } + + /// Loop body に Continue があるかチェック + fn has_continue_in_loop_body(loop_body: &[serde_json::Value]) -> bool { + loop_body.iter().any(|stmt| { + if stmt["type"].as_str() == Some("If") { + if let Some(then_body) = stmt["then"].as_array() { + then_body.iter().any(|s| s["type"].as_str() == Some("Continue")) + } else { + false + } + } else { + false + } + }) + } + + /// Phase 34-7: Loop pattern の lowering(Case-A: tiny while loop) + /// + /// 対象: `LoopFrontendTest.simple(n)` 相当 + /// - local i = 0; local acc = 0; + /// - loop(i < n) { acc = acc + 1; i = i + 1; } + /// - return acc + /// + /// 目標 JoinIR: Phase 31 の LoopToJoinLowerer と同型 + /// - entry: 初期化 → loop_step 呼び出し + /// - loop_step: 条件チェック → 再帰 or k_exit + /// - k_exit: 結果を return + /// + /// Phase 34-7.4b: Local ノード処理(同名再宣言 = 再代入) + fn lower_loop_case_a_simple(&mut self, program_json: &serde_json::Value) -> JoinModule { + // 1. Program(JSON) から defs を取得 + let defs = program_json["defs"] + .as_array() + .expect("Program(JSON v0) must have 'defs' array"); + + let func_def = defs + .get(0) + .expect("At least one function definition required"); + + let func_name = func_def["name"] + .as_str() + .expect("Function must have 'name'"); + + let params = func_def["params"] + .as_array() + .expect("Function must have 'params' array"); + + // 2. ExtractCtx 作成とパラメータ登録 + let mut ctx = ExtractCtx::new(params.len() as u32); + for (i, param) in params.iter().enumerate() { + let param_name = param + .as_str() + .expect("Parameter must be string") + .to_string(); + ctx.register_param(param_name, crate::mir::ValueId(i as u32)); + } + + // 3. body を解析: Local 初期化 + Loop + Return + let body = &func_def["body"]["body"]; + let stmts = body + .as_array() + .expect("Function body must be array"); + + // Phase 34-7.4b: Local ノード処理(初期化) + // stmts[0]: Local i = 0 + // stmts[1]: Local acc = 0 + // stmts[2]: Loop { cond, body } + // stmts[3]: Return acc + + let mut init_insts = Vec::new(); + let mut loop_node_idx = None; + + for (idx, stmt) in stmts.iter().enumerate() { + let stmt_type = stmt["type"].as_str().expect("Statement must have type"); + + match stmt_type { + "Local" => { + // Phase 34-7.4b: Local ノード処理 + let var_name = stmt["name"] + .as_str() + .expect("Local must have 'name'") + .to_string(); + let expr = &stmt["expr"]; + + // extract_value で式を評価 + let (var_id, insts) = self.extract_value(expr, &mut ctx); + init_insts.extend(insts); + + // 同名再宣言 = var_map を更新(再代入の意味論) + ctx.register_param(var_name, var_id); + } + "Loop" => { + loop_node_idx = Some(idx); + break; // Loop 以降は別処理 + } + _ => panic!("Unexpected statement type before Loop: {}", stmt_type), + } + } + + let loop_node_idx = loop_node_idx.expect("Loop node not found"); + let loop_node = &stmts[loop_node_idx]; + + // 4. Loop の cond と body を抽出 + let loop_cond_expr = &loop_node["cond"]; + let loop_body_stmts = loop_node["body"] + .as_array() + .expect("Loop must have 'body' array"); + + // 5. Return 文を抽出(Loop の次) + let return_stmt = &stmts[loop_node_idx + 1]; + assert_eq!( + return_stmt["type"].as_str(), + Some("Return"), + "Expected Return after Loop" + ); + + // 6. JoinIR 生成: entry / loop_step / k_exit(Phase 31 と同じパターン) + // + // Phase 34-7: Jump = 早期 return、Call = 末尾再帰 + // - entry: 初期化 → Call(loop_step) + // - loop_step: + // Jump(k_exit, cond=exit_cond) // 条件が true なら抜ける + // body 処理 + // Call(loop_step) 末尾再帰 + // - k_exit: 結果を返す + + let entry_id = self.next_func_id(); + let loop_step_id = self.next_func_id(); + let k_exit_id = self.next_func_id(); + + // entry 関数: 初期化命令 + Call(loop_step) + let i_init = ctx.get_var("i").expect("i must be initialized"); + let acc_init = ctx.get_var("acc").expect("acc must be initialized"); + let n_param = ctx.get_var("n").expect("n must be parameter"); + + let loop_result = ctx.alloc_var(); + + let mut entry_body = init_insts; + entry_body.push(JoinInst::Call { + func: loop_step_id, + args: vec![i_init, acc_init, n_param], + k_next: None, + dst: Some(loop_result), + }); + entry_body.push(JoinInst::Ret { + value: Some(loop_result), + }); + + let entry_func = JoinFunction { + id: entry_id, + name: func_name.to_string(), + params: (0..params.len()) + .map(|i| crate::mir::ValueId(i as u32)) + .collect(), + body: entry_body, + exit_cont: None, + }; + + // loop_step 関数: (i, acc, n) → Jump(k_exit, cond=!(i= n なら抜ける) + let false_const = step_ctx.alloc_var(); + let exit_cond = step_ctx.alloc_var(); + + let mut loop_step_body = cond_insts; + loop_step_body.push(JoinInst::Compute(crate::mir::join_ir::MirLikeInst::Const { + dst: false_const, + value: ConstValue::Bool(false), + })); + loop_step_body.push(JoinInst::Compute(crate::mir::join_ir::MirLikeInst::Compare { + dst: exit_cond, + op: crate::mir::join_ir::CompareOp::Eq, + lhs: cond_var, + rhs: false_const, + })); + + // 早期 return: exit_cond が true(i >= n)なら k_exit へ Jump + loop_step_body.push(JoinInst::Jump { + cont: k_exit_id.as_cont(), // Phase 34-7.5: 型変換ヘルパー使用 + args: vec![step_acc], + cond: Some(exit_cond), + }); + + // loop body を処理(Jump で抜けなかった場合のみ実行される) + for body_stmt in loop_body_stmts { + assert_eq!( + body_stmt["type"].as_str(), + Some("Local"), + "Loop body must contain only Local statements" + ); + + let var_name = body_stmt["name"] + .as_str() + .expect("Local must have 'name'") + .to_string(); + let expr = &body_stmt["expr"]; + + let (var_id, insts) = self.extract_value(expr, &mut step_ctx); + loop_step_body.extend(insts); + step_ctx.register_param(var_name, var_id); + } + + // body 処理後の i_next, acc_next を取得 + let i_next = step_ctx.get_var("i").expect("i must be updated in loop body"); + let acc_next = step_ctx.get_var("acc").expect("acc must be updated in loop body"); + + // loop_step を再帰的に Call(末尾再帰) + let recurse_result = step_ctx.alloc_var(); + loop_step_body.push(JoinInst::Call { + func: loop_step_id, + args: vec![i_next, acc_next, step_n], + k_next: None, + dst: Some(recurse_result), + }); + + loop_step_body.push(JoinInst::Ret { + value: Some(recurse_result), + }); + + let loop_step_func = JoinFunction { + id: loop_step_id, + name: format!("{}_loop_step", func_name), + params: vec![step_i, step_acc, step_n], + body: loop_step_body, + exit_cont: None, + }; + + // k_exit 関数: (acc) → Ret acc + let k_exit_acc = crate::mir::ValueId(0); + + let k_exit_func = JoinFunction { + id: k_exit_id, + name: format!("{}_k_exit", func_name), + params: vec![k_exit_acc], + body: vec![JoinInst::Ret { + value: Some(k_exit_acc), + }], + exit_cont: None, + }; + + // JoinModule を構築 + let mut functions = BTreeMap::new(); + functions.insert(entry_id, entry_func); + functions.insert(loop_step_id, loop_step_func); + functions.insert(k_exit_id, k_exit_func); + + JoinModule { + functions, + entry: Some(entry_id), + } + } + + /// Phase 34-8: Break pattern の lowering + /// + /// パターン: `loop { if i >= n { break }; acc = acc + i; i = i + 1 }` + /// 目標: Jump(k_exit, cond=i>=n) で早期 return を実現 + fn lower_loop_break_pattern(&mut self, program_json: &serde_json::Value) -> JoinModule { + // 1. Program(JSON) から基本情報を取得(Phase 34-7 と同じ) + let defs = program_json["defs"] + .as_array() + .expect("Program(JSON v0) must have 'defs' array"); + + let func_def = defs + .get(0) + .expect("At least one function definition required"); + + let func_name = func_def["name"] + .as_str() + .expect("Function must have 'name'"); + + let params = func_def["params"] + .as_array() + .expect("Function must have 'params' array"); + + // 2. ExtractCtx 作成とパラメータ登録 + let mut ctx = ExtractCtx::new(params.len() as u32); + for (i, param) in params.iter().enumerate() { + let param_name = param + .as_str() + .expect("Parameter must be string") + .to_string(); + ctx.register_param(param_name, crate::mir::ValueId(i as u32)); + } + + // 3. body を解析: Local 初期化 + Loop + Return + let body = &func_def["body"]["body"]; + let stmts = body + .as_array() + .expect("Function body must be array"); + + // Local ノード処理(初期化) + let mut init_insts = Vec::new(); + let mut loop_node_idx = None; + + for (idx, stmt) in stmts.iter().enumerate() { + let stmt_type = stmt["type"].as_str().expect("Statement must have type"); + + match stmt_type { + "Local" => { + let var_name = stmt["name"] + .as_str() + .expect("Local must have 'name'") + .to_string(); + let expr = &stmt["expr"]; + + let (var_id, insts) = self.extract_value(expr, &mut ctx); + init_insts.extend(insts); + ctx.register_param(var_name, var_id); + } + "Loop" => { + loop_node_idx = Some(idx); + break; + } + _ => panic!("Unexpected statement type before Loop: {}", stmt_type), + } + } + + let loop_node_idx = loop_node_idx.expect("Loop node not found"); + let loop_node = &stmts[loop_node_idx]; + + // 4. Loop body から Break If を探す + let loop_body = loop_node["body"] + .as_array() + .expect("Loop must have 'body' array"); + + let break_if_stmt = loop_body.iter().find(|stmt| { + stmt["type"].as_str() == Some("If") && + stmt["then"].as_array().map_or(false, |then| { + then.iter().any(|s| s["type"].as_str() == Some("Break")) + }) + }).expect("Break pattern must have If + Break"); + + let break_cond_expr = &break_if_stmt["cond"]; + + // 5. JoinIR 生成: entry / loop_step / k_exit(3関数構造) + let entry_id = self.next_func_id(); + let loop_step_id = self.next_func_id(); + let k_exit_id = self.next_func_id(); + + // entry 関数 + let i_init = ctx.get_var("i").expect("i must be initialized"); + let acc_init = ctx.get_var("acc").expect("acc must be initialized"); + let n_param = ctx.get_var("n").expect("n must be parameter"); + + let loop_result = ctx.alloc_var(); + + let mut entry_body = init_insts; + entry_body.push(JoinInst::Call { + func: loop_step_id, + args: vec![i_init, acc_init, n_param], + k_next: None, + dst: Some(loop_result), + }); + entry_body.push(JoinInst::Ret { + value: Some(loop_result), + }); + + let entry_func = JoinFunction { + id: entry_id, + name: func_name.to_string(), + params: (0..params.len()) + .map(|i| crate::mir::ValueId(i as u32)) + .collect(), + body: entry_body, + exit_cont: None, + }; + + // loop_step 関数: (i, acc, n) → Jump(k_exit, cond=break_cond) → body → Call(loop_step) + let step_i = crate::mir::ValueId(0); + let step_acc = crate::mir::ValueId(1); + let step_n = crate::mir::ValueId(2); + + let mut step_ctx = ExtractCtx::new(3); + step_ctx.register_param("i".to_string(), step_i); + step_ctx.register_param("acc".to_string(), step_acc); + step_ctx.register_param("n".to_string(), step_n); + + // Break 条件を評価 + let (break_cond_var, break_cond_insts) = self.extract_value(break_cond_expr, &mut step_ctx); + + let mut loop_step_body = break_cond_insts; + + // 早期 return: break_cond が true なら k_exit へ Jump + loop_step_body.push(JoinInst::Jump { + cont: k_exit_id.as_cont(), + args: vec![step_acc], + cond: Some(break_cond_var), + }); + + // loop body を処理(Break の後の Local 命令群) + for body_stmt in loop_body { + // If + Break はスキップ(Jump で処理済み) + if body_stmt["type"].as_str() == Some("If") { + continue; + } + + assert_eq!( + body_stmt["type"].as_str(), + Some("Local"), + "Loop body must contain only Local statements after If" + ); + + let var_name = body_stmt["name"] + .as_str() + .expect("Local must have 'name'") + .to_string(); + let expr = &body_stmt["expr"]; + + let (var_id, insts) = self.extract_value(expr, &mut step_ctx); + loop_step_body.extend(insts); + step_ctx.register_param(var_name, var_id); + } + + // body 処理後の i_next, acc_next を取得 + let i_next = step_ctx.get_var("i").expect("i must be updated in loop body"); + let acc_next = step_ctx.get_var("acc").expect("acc must be updated in loop body"); + + // loop_step を再帰的に Call(末尾再帰) + let recurse_result = step_ctx.alloc_var(); + loop_step_body.push(JoinInst::Call { + func: loop_step_id, + args: vec![i_next, acc_next, step_n], + k_next: None, + dst: Some(recurse_result), + }); + + loop_step_body.push(JoinInst::Ret { + value: Some(recurse_result), + }); + + let loop_step_func = JoinFunction { + id: loop_step_id, + name: format!("{}_loop_step", func_name), + params: vec![step_i, step_acc, step_n], + body: loop_step_body, + exit_cont: None, + }; + + // k_exit 関数 + let k_exit_acc = crate::mir::ValueId(0); + + let k_exit_func = JoinFunction { + id: k_exit_id, + name: format!("{}_k_exit", func_name), + params: vec![k_exit_acc], + body: vec![JoinInst::Ret { + value: Some(k_exit_acc), + }], + exit_cont: None, + }; + + // JoinModule を構築 + let mut functions = BTreeMap::new(); + functions.insert(entry_id, entry_func); + functions.insert(loop_step_id, loop_step_func); + functions.insert(k_exit_id, k_exit_func); + + JoinModule { + functions, + entry: Some(entry_id), + } + } + + /// Phase 34-8: Continue pattern の lowering + /// + /// パターン: `loop { i = i + 1; if i == 3 { continue }; acc = acc + i }` + /// 目標: Select(条件付き値更新)+ Call(末尾再帰)で実現 + fn lower_loop_continue_pattern(&mut self, program_json: &serde_json::Value) -> JoinModule { + // 1. Program(JSON) から基本情報を取得 + let defs = program_json["defs"] + .as_array() + .expect("Program(JSON v0) must have 'defs' array"); + + let func_def = defs + .get(0) + .expect("At least one function definition required"); + + let func_name = func_def["name"] + .as_str() + .expect("Function must have 'name'"); + + let params = func_def["params"] + .as_array() + .expect("Function must have 'params' array"); + + // 2. ExtractCtx 作成とパラメータ登録 + let mut ctx = ExtractCtx::new(params.len() as u32); + for (i, param) in params.iter().enumerate() { + let param_name = param + .as_str() + .expect("Parameter must be string") + .to_string(); + ctx.register_param(param_name, crate::mir::ValueId(i as u32)); + } + + // 3. body を解析: Local 初期化 + Loop + Return + let body = &func_def["body"]["body"]; + let stmts = body + .as_array() + .expect("Function body must be array"); + + // Local ノード処理(初期化) + let mut init_insts = Vec::new(); + let mut loop_node_idx = None; + + for (idx, stmt) in stmts.iter().enumerate() { + let stmt_type = stmt["type"].as_str().expect("Statement must have type"); + + match stmt_type { + "Local" => { + let var_name = stmt["name"] + .as_str() + .expect("Local must have 'name'") + .to_string(); + let expr = &stmt["expr"]; + + let (var_id, insts) = self.extract_value(expr, &mut ctx); + init_insts.extend(insts); + ctx.register_param(var_name, var_id); + } + "Loop" => { + loop_node_idx = Some(idx); + break; + } + _ => panic!("Unexpected statement type before Loop: {}", stmt_type), + } + } + + let loop_node_idx = loop_node_idx.expect("Loop node not found"); + let loop_node = &stmts[loop_node_idx]; + + // 4. Loop の cond を取得 + let loop_cond_expr = &loop_node["cond"]; + + // 5. Loop body から Continue If を探す + let loop_body = loop_node["body"] + .as_array() + .expect("Loop must have 'body' array"); + + let continue_if_stmt = loop_body.iter().find(|stmt| { + stmt["type"].as_str() == Some("If") && + stmt["then"].as_array().map_or(false, |then| { + then.iter().any(|s| s["type"].as_str() == Some("Continue")) + }) + }).expect("Continue pattern must have If + Continue"); + + let continue_cond_expr = &continue_if_stmt["cond"]; + + // 6. JoinIR 生成: entry / loop_step / k_exit(3関数構造) + let entry_id = self.next_func_id(); + let loop_step_id = self.next_func_id(); + let k_exit_id = self.next_func_id(); + + // entry 関数 + let i_init = ctx.get_var("i").expect("i must be initialized"); + let acc_init = ctx.get_var("acc").expect("acc must be initialized"); + let n_param = ctx.get_var("n").expect("n must be parameter"); + + let loop_result = ctx.alloc_var(); + + let mut entry_body = init_insts; + entry_body.push(JoinInst::Call { + func: loop_step_id, + args: vec![i_init, acc_init, n_param], + k_next: None, + dst: Some(loop_result), + }); + entry_body.push(JoinInst::Ret { + value: Some(loop_result), + }); + + let entry_func = JoinFunction { + id: entry_id, + name: func_name.to_string(), + params: (0..params.len()) + .map(|i| crate::mir::ValueId(i as u32)) + .collect(), + body: entry_body, + exit_cont: None, + }; + + // loop_step 関数: (i, acc, n) → exit check → i++ → Select → Call(loop_step) + let step_i = crate::mir::ValueId(0); + let step_acc = crate::mir::ValueId(1); + let step_n = crate::mir::ValueId(2); + + let mut step_ctx = ExtractCtx::new(3); + step_ctx.register_param("i".to_string(), step_i); + step_ctx.register_param("acc".to_string(), step_acc); + step_ctx.register_param("n".to_string(), step_n); + + let mut loop_step_body = Vec::new(); + + // 1. exit 条件チェック(!(i < n) = i >= n で抜ける) + let (cond_var, cond_insts) = self.extract_value(loop_cond_expr, &mut step_ctx); + loop_step_body.extend(cond_insts); + + let false_const = step_ctx.alloc_var(); + let exit_cond = step_ctx.alloc_var(); + + loop_step_body.push(JoinInst::Compute(crate::mir::join_ir::MirLikeInst::Const { + dst: false_const, + value: ConstValue::Bool(false), + })); + loop_step_body.push(JoinInst::Compute(crate::mir::join_ir::MirLikeInst::Compare { + dst: exit_cond, + op: crate::mir::join_ir::CompareOp::Eq, + lhs: cond_var, + rhs: false_const, + })); + + loop_step_body.push(JoinInst::Jump { + cont: k_exit_id.as_cont(), + args: vec![step_acc], + cond: Some(exit_cond), + }); + + // 2. Continue pattern 特有の処理: i のインクリメントが先 + // Loop body の最初の Local(i) を処理 + let first_local = loop_body.iter().find(|stmt| { + stmt["type"].as_str() == Some("Local") && + stmt["name"].as_str() == Some("i") + }).expect("Continue pattern must have i increment as first Local"); + + let i_expr = &first_local["expr"]; + let (i_next, i_insts) = self.extract_value(i_expr, &mut step_ctx); + loop_step_body.extend(i_insts); + step_ctx.register_param("i".to_string(), i_next); + + // 3. Continue 条件を評価 + let (continue_cond_var, continue_cond_insts) = self.extract_value(continue_cond_expr, &mut step_ctx); + loop_step_body.extend(continue_cond_insts); + + // 4. acc の更新値を計算(If の後の Local(acc) から) + let acc_update_local = loop_body.iter().find(|stmt| { + stmt["type"].as_str() == Some("Local") && + stmt["name"].as_str() == Some("acc") + }).expect("Continue pattern must have acc update Local"); + + let acc_expr = &acc_update_local["expr"]; + let (acc_increment, acc_insts) = self.extract_value(acc_expr, &mut step_ctx); + loop_step_body.extend(acc_insts); + + // 5. Select: Continue なら acc そのまま、そうでなければ acc の更新値 + let acc_next = step_ctx.alloc_var(); + loop_step_body.push(JoinInst::Select { + dst: acc_next, + cond: continue_cond_var, + then_val: step_acc, // Continue: 更新しない + else_val: acc_increment, // 通常: 更新 + }); + + // 6. 末尾再帰 + let recurse_result = step_ctx.alloc_var(); + loop_step_body.push(JoinInst::Call { + func: loop_step_id, + args: vec![i_next, acc_next, step_n], + k_next: None, + dst: Some(recurse_result), + }); + + loop_step_body.push(JoinInst::Ret { + value: Some(recurse_result), + }); + + let loop_step_func = JoinFunction { + id: loop_step_id, + name: format!("{}_loop_step", func_name), + params: vec![step_i, step_acc, step_n], + body: loop_step_body, + exit_cont: None, + }; + + // k_exit 関数 + let k_exit_acc = crate::mir::ValueId(0); + + let k_exit_func = JoinFunction { + id: k_exit_id, + name: format!("{}_k_exit", func_name), + params: vec![k_exit_acc], + body: vec![JoinInst::Ret { + value: Some(k_exit_acc), + }], + exit_cont: None, + }; + + // JoinModule を構築 + let mut functions = BTreeMap::new(); + functions.insert(entry_id, entry_func); + functions.insert(loop_step_id, loop_step_func); + functions.insert(k_exit_id, k_exit_func); + + JoinModule { + functions, + entry: Some(entry_id), + } + } + /// 次の関数 ID を生成 fn next_func_id(&mut self) -> JoinFuncId { let id = JoinFuncId::new(self.next_func_id); @@ -303,6 +1075,21 @@ impl AstToJoinIrLowerer { (dst, vec![inst]) } + // Phase 34-8: Bool literal 対応 + "Bool" => { + let value = expr["value"] + .as_bool() + .expect("Bool value must be boolean"); + + let dst = ctx.alloc_var(); + let inst = JoinInst::Compute(crate::mir::join_ir::MirLikeInst::Const { + dst, + value: ConstValue::Bool(value), + }); + + (dst, vec![inst]) + } + // 段階 1: Var 参照対応 "Var" => { let var_name = expr["name"] @@ -356,6 +1143,88 @@ impl AstToJoinIrLowerer { (dst, insts) } + // Phase 34-7.4a: Binary 演算対応(i + 1 など) + "Binary" => { + let op_str = expr["op"] + .as_str() + .expect("Binary must have 'op' field"); + let lhs_expr = &expr["lhs"]; + let rhs_expr = &expr["rhs"]; + + // op 文字列を BinOpKind に変換 + let op = match op_str { + "+" => crate::mir::join_ir::BinOpKind::Add, + "-" => crate::mir::join_ir::BinOpKind::Sub, + "*" => crate::mir::join_ir::BinOpKind::Mul, + "/" => crate::mir::join_ir::BinOpKind::Div, + _ => panic!("Unsupported binary op: {}", op_str), + }; + + // lhs と rhs を再帰的に extract_value + let (lhs_var, lhs_insts) = self.extract_value(lhs_expr, ctx); + let (rhs_var, rhs_insts) = self.extract_value(rhs_expr, ctx); + + // 結果変数を割り当て + let dst = ctx.alloc_var(); + + // BinOp 命令を生成 + let binop_inst = JoinInst::Compute(crate::mir::join_ir::MirLikeInst::BinOp { + dst, + op, + lhs: lhs_var, + rhs: rhs_var, + }); + + // すべての命令を結合(lhs → rhs → BinOp の順) + let mut insts = lhs_insts; + insts.extend(rhs_insts); + insts.push(binop_inst); + + (dst, insts) + } + + // Phase 34-7.4a: Compare 演算対応(i < n など) + "Compare" => { + let op_str = expr["op"] + .as_str() + .expect("Compare must have 'op' field"); + let lhs_expr = &expr["lhs"]; + let rhs_expr = &expr["rhs"]; + + // op 文字列を CompareOp に変換 + let op = match op_str { + "<" => crate::mir::join_ir::CompareOp::Lt, + "<=" => crate::mir::join_ir::CompareOp::Le, + ">" => crate::mir::join_ir::CompareOp::Gt, + ">=" => crate::mir::join_ir::CompareOp::Ge, + "==" => crate::mir::join_ir::CompareOp::Eq, + "!=" => crate::mir::join_ir::CompareOp::Ne, + _ => panic!("Unsupported compare op: {}", op_str), + }; + + // lhs と rhs を再帰的に extract_value + let (lhs_var, lhs_insts) = self.extract_value(lhs_expr, ctx); + let (rhs_var, rhs_insts) = self.extract_value(rhs_expr, ctx); + + // 結果変数を割り当て + let dst = ctx.alloc_var(); + + // Compare 命令を生成 + let compare_inst = JoinInst::Compute(crate::mir::join_ir::MirLikeInst::Compare { + dst, + op, + lhs: lhs_var, + rhs: rhs_var, + }); + + // すべての命令を結合(lhs → rhs → Compare の順) + let mut insts = lhs_insts; + insts.extend(rhs_insts); + insts.push(compare_inst); + + (dst, insts) + } + _ => panic!("Unsupported expr type: {}", expr_type), } } @@ -371,4 +1240,6 @@ impl Default for AstToJoinIrLowerer { // Phase 34-3: local pattern 対応(simple と同じ JoinIR 出力) // Phase 34-4: Stage-1/meta 実用関数対応(JsonShapeToMap._read_value_from_pair/1) // Phase 34-5: extract_value 統一化(Int/Var/Method 構造まで) -// Phase 34-6 以降: MethodCall 構造の JoinIR への明示と JoinIR→MIR 変換側での Method/Call 意味論実装、その後 Loop/Break/Continue への拡張 +// Phase 34-6: MethodCall 構造の JoinIR への明示と JoinIR→MIR 変換側での Method/Call 意味論実装 +// Phase 34-7: tiny while loop 対応(Case-A simple pattern) +// Phase 34-8: Break/Continue 付きループ対応(Select + Jump で制御フロー表現) diff --git a/src/mir/join_ir/mod.rs b/src/mir/join_ir/mod.rs index 6bbe4a16..d142c630 100644 --- a/src/mir/join_ir/mod.rs +++ b/src/mir/join_ir/mod.rs @@ -52,6 +52,23 @@ impl JoinFuncId { pub fn new(id: u32) -> Self { JoinFuncId(id) } + + /// JoinFuncId を JoinContId に変換 + /// + /// # Use Case + /// Jump 命令で関数を continuation として使う場合 + /// ```rust + /// let func_id = JoinFuncId(42); + /// let cont_id = func_id.as_cont(); + /// Jump { cont: cont_id, args: vec![], cond: None } + /// ``` + /// + /// # Phase 34-7 Note + /// JoinFuncId と JoinContId は別の newtype だが、内部的には同じ u32 ID を共有する。 + /// この変換は型レベルでの役割の明示(関数 vs 継続)を可能にする。 + pub fn as_cont(self) -> JoinContId { + JoinContId(self.0) + } } /// 継続(join / ループ step / exit continuation)を識別するID @@ -62,6 +79,21 @@ impl JoinContId { pub fn new(id: u32) -> Self { JoinContId(id) } + + /// JoinContId を JoinFuncId に変換 + /// + /// # Use Case + /// 継続 ID を関数 ID として参照する場合(JoinModule の functions map でルックアップ時など) + /// ```rust + /// let cont_id = JoinContId(42); + /// let func = join_module.functions.get(&cont_id.as_func())?; + /// ``` + /// + /// # Phase 34-7 Note + /// JoinIR では継続も関数として実装されるため、この変換が必要になる。 + pub fn as_func(self) -> JoinFuncId { + JoinFuncId(self.0) + } } /// 変数ID(Phase 26-H では MIR の ValueId を再利用) @@ -179,7 +211,31 @@ pub struct MergePair { /// JoinIR 命令セット(最小版) #[derive(Debug, Clone)] pub enum JoinInst { - /// 通常の関数呼び出し: f(args..., k_next) + /// 通常の関数呼び出し(末尾再帰): f(args..., k_next) + /// + /// # Semantics + /// - 他の JoinIR 関数を呼び出す(MIR の Call に変換) + /// - ループでは末尾再帰として使うのが典型的 + /// + /// # MIR 変換 + /// - `MirInstruction::Call { func, args, ... }` を生成 + /// + /// # Constraints (Phase 31/34 時点) + /// - **k_next は常に None にすること!** + /// JoinIR→MIR bridge が `k_next: Some(...)` 未対応 + /// → エラー: "Call with k_next is not yet supported" + /// - 典型的な使い方: `Call { func, args, k_next: None, dst: Some(...) }` + /// + /// # Loop Pattern での使い方 (Phase 34-7) + /// ```rust + /// // ✅ 正解: 末尾再帰 + /// Call { + /// func: loop_step_id, + /// args: vec![i_next, acc_next, n], + /// k_next: None, // ⚠️ 必須: None にすること + /// dst: Some(result), + /// } + /// ``` Call { func: JoinFuncId, args: Vec, @@ -188,7 +244,46 @@ pub enum JoinInst { dst: Option, }, - /// 継続呼び出し(join / exit 継続など) + /// 継続呼び出し(早期 return / exit 継続) + /// + /// # Semantics + /// - **「早期 return」(条件付き関数脱出)として使う!** + /// - cond=Some(v): v が true なら cont に Jump、false なら次の命令へ + /// - cond=None: 無条件 Jump + /// + /// # MIR 変換 + /// - cond=Some(v): `Branch(v, exit_block[Return], continue_block)` を生成 + /// - exit_block: cont 関数を Call して Return + /// - continue_block: 次の JoinInst に続く + /// - cond=None: 無条件に cont を Call して Return + /// + /// # Loop Pattern での使い方 (Phase 34-7) + /// ```rust + /// // ✅ 正解: 条件付き早期 return + /// Jump { + /// cont: k_exit_id.as_cont(), + /// args: vec![acc], + /// cond: Some(exit_cond), // exit_cond が true なら k_exit へ + /// } + /// // ↑ exit_cond が false なら次の命令(body 処理)へ進む + /// + /// // ❌ 間違い: Call で条件分岐しようとする + /// Call { + /// func: k_exit_id, + /// cond: Some(exit_cond), // こんなフィールドはない! + /// } + /// ``` + /// + /// # 典型的なパターン + /// ```text + /// loop_step(i, acc, n): + /// exit_cond = !(i < n) + /// Jump(k_exit, [acc], cond=exit_cond) // 🔑 早期 return + /// // ↓ Jump で抜けなかった場合のみ実行 + /// acc_next = acc + 1 + /// i_next = i + 1 + /// Call(loop_step, [i_next, acc_next, n]) // 🔑 末尾再帰 + /// ``` Jump { cont: JoinContId, args: Vec, diff --git a/src/mir/join_ir_vm_bridge.rs b/src/mir/join_ir_vm_bridge.rs index 843c23c7..e718c376 100644 --- a/src/mir/join_ir_vm_bridge.rs +++ b/src/mir/join_ir_vm_bridge.rs @@ -235,9 +235,30 @@ fn convert_join_function_to_mir( // - k_next=Some: Not yet supported if k_next.is_some() { + let call_target_name = join_func_name(*func); return Err(JoinIrVmBridgeError::new(format!( - "Call with k_next is not yet supported (k_next={:?})", - k_next + "Call with k_next is not yet supported\n\ +\n\ +Current function: {}\n\ +Call target: {} (JoinFuncId: {:?})\n\ +Arguments: {} args\n\ +k_next: {:?}\n\ +\n\ +💡 Fix: Change `k_next: Some(...)` to `k_next: None`\n\ +💡 Note: Phase 31 and Phase 34 both use k_next=None (bridge limitation)\n\ +\n\ +Example:\n\ +Call {{\n\ + func: loop_step_id,\n\ + args: vec![i_next, acc_next, n],\n\ + k_next: None, // ⚠️ Must be None\n\ + dst: Some(result),\n\ +}}", + join_func.name, + call_target_name, + func, + args.len(), + k_next, ))); } diff --git a/src/tests/helpers/joinir_frontend.rs b/src/tests/helpers/joinir_frontend.rs new file mode 100644 index 00000000..b9cc9675 --- /dev/null +++ b/src/tests/helpers/joinir_frontend.rs @@ -0,0 +1,119 @@ +//! Phase 34-7.5: JoinIR Frontend テストヘルパー箱 +//! +//! 目的: フィクスチャベースの AST→JoinIR テストを簡潔に書けるようにする + +use crate::mir::join_ir::frontend::AstToJoinIrLowerer; +use crate::mir::join_ir::{JoinModule, JoinFuncId}; +use crate::mir::join_ir_ops::JoinValue; +use crate::mir::join_ir_vm_bridge::run_joinir_via_vm; + +/// JoinIR Frontend テストランナー箱 +/// +/// フィクスチャ読み込み → lowering → 実行 → 検証の共通処理を提供 +pub struct JoinIrFrontendTestRunner { + fixture_path: String, + join_module: Option, + debug_enabled: bool, +} + +impl JoinIrFrontendTestRunner { + /// フィクスチャからテストランナーを作成 + pub fn from_fixture(fixture_path: &str) -> Self { + Self { + fixture_path: fixture_path.to_string(), + join_module: None, + debug_enabled: std::env::var("JOINIR_TEST_DEBUG").is_ok(), + } + } + + /// デバッグモードを有効化(JoinIR Module をダンプ) + pub fn with_debug(mut self) -> Self { + self.debug_enabled = true; + self + } + + /// フィクスチャを lowering + pub fn lower(mut self) -> Result { + let fixture_json = std::fs::read_to_string(&self.fixture_path) + .map_err(|e| format!("Failed to read fixture {}: {}", self.fixture_path, e))?; + + let program_json: serde_json::Value = serde_json::from_str(&fixture_json) + .map_err(|e| format!("Failed to parse JSON {}: {}", self.fixture_path, e))?; + + let mut lowerer = AstToJoinIrLowerer::new(); + let join_module = lowerer.lower_program_json(&program_json); + + if self.debug_enabled { + self.dump_joinir_module(&join_module); + } + + self.join_module = Some(join_module); + Ok(self) + } + + /// JoinIR Module をダンプ(デバッグ用) + fn dump_joinir_module(&self, module: &JoinModule) { + eprintln!("=== JoinIR Module ==="); + eprintln!("Entry: {:?}", module.entry); + for (func_id, func) in &module.functions { + eprintln!("\nFunction {:?}: {}", func_id, func.name); + eprintln!(" Params: {:?}", func.params); + eprintln!(" Instructions:"); + for (i, inst) in func.body.iter().enumerate() { + eprintln!(" {}: {:?}", i, inst); + } + } + } + + /// テストケースを実行(単一入力・単一出力) + pub fn run_case( + &self, + inputs: &[JoinValue], + expected: JoinValue, + ) -> Result<(), String> { + let module = self.join_module.as_ref() + .ok_or("Module not lowered. Call .lower() first")?; + + let result = run_joinir_via_vm( + module, + module.entry.unwrap(), + inputs, + ).map_err(|e| { + format!( + "JoinIR execution failed\n\ + Inputs: {:?}\n\ + Error: {:?}\n\ + \n\ + === JoinIR Module Dump ===\n\ + {:?}", + inputs, e, module + ) + })?; + + if result != expected { + return Err(format!( + "Assertion failed\n\ + Inputs: {:?}\n\ + Expected: {:?}\n\ + Actual: {:?}\n\ + \n\ + === JoinIR Module Dump ===\n\ + {:?}", + inputs, expected, result, module + )); + } + + Ok(()) + } + + /// 複数テストケースを一括実行 + pub fn run_cases( + &self, + cases: &[(Vec, JoinValue)], + ) -> Result<(), String> { + for (inputs, expected) in cases { + self.run_case(inputs, expected.clone())?; + } + Ok(()) + } +} diff --git a/src/tests/helpers/mod.rs b/src/tests/helpers/mod.rs new file mode 100644 index 00000000..0b4f500c --- /dev/null +++ b/src/tests/helpers/mod.rs @@ -0,0 +1,3 @@ +//! Phase 34-7.5: テストヘルパーモジュール + +pub mod joinir_frontend; diff --git a/src/tests/joinir_frontend_if_select.rs b/src/tests/joinir_frontend_if_select.rs index 2582d98b..c039e2e0 100644 --- a/src/tests/joinir_frontend_if_select.rs +++ b/src/tests/joinir_frontend_if_select.rs @@ -3,9 +3,8 @@ //! Route A: 既存経路(AST→MIR→PHI→VM) //! Route B: 新経路(AST→JoinIR→MIR'→VM) -use crate::mir::join_ir::frontend::AstToJoinIrLowerer; -use crate::mir::join_ir_runner::{run_joinir_function, JoinValue}; -use crate::mir::join_ir_vm_bridge::run_joinir_via_vm; +use crate::tests::helpers::joinir_frontend::JoinIrFrontendTestRunner; +use crate::mir::join_ir_ops::JoinValue; /// Phase 34-2: IfSelect simple pattern の A/B テスト /// @@ -14,63 +13,16 @@ use crate::mir::join_ir_vm_bridge::run_joinir_via_vm; /// 期待: Route A と Route B の結果が一致 #[test] fn joinir_frontend_if_select_simple_ab_test() { - // フィクスチャ読み込み - let fixture_path = "docs/private/roadmap2/phases/phase-34-joinir-frontend/fixtures/joinir_if_select_simple.program.json"; - let fixture_json = std::fs::read_to_string(fixture_path) - .expect("Failed to read fixture JSON"); - let program_json: serde_json::Value = serde_json::from_str(&fixture_json) - .expect("Failed to parse JSON"); - - // Route B: JoinIR Frontend 経路 - let mut lowerer = AstToJoinIrLowerer::new(); - let join_module = lowerer.lower_program_json(&program_json); - - // デバッグ: JoinIR Module の内容を確認 - eprintln!("=== JoinIR Module ==="); - eprintln!("Entry: {:?}", join_module.entry); - for (func_id, func) in &join_module.functions { - eprintln!("\nFunction {:?}: {}", func_id, func.name); - eprintln!(" Params: {:?}", func.params); - eprintln!(" Instructions:"); - for (i, inst) in func.body.iter().enumerate() { - eprintln!(" {}: {:?}", i, inst); - } - } - - // JoinIR Runner で実行 - let mut vm = crate::backend::mir_interpreter::MirInterpreter::new(); - - // cond = 1 (true) の場合 - let result_true = run_joinir_function( - &mut vm, - &join_module, - join_module.entry.unwrap(), - &[JoinValue::Int(1)], + JoinIrFrontendTestRunner::from_fixture( + "docs/private/roadmap2/phases/phase-34-joinir-frontend/fixtures/joinir_if_select_simple.program.json" ) - .expect("Failed to run JoinIR function (cond=1)"); - - // cond = 0 (false) の場合 - let result_false = run_joinir_function( - &mut vm, - &join_module, - join_module.entry.unwrap(), - &[JoinValue::Int(0)], - ) - .expect("Failed to run JoinIR function (cond=0)"); - - // 検証: cond=1 → 10, cond=0 → 20 - match result_true { - JoinValue::Int(v) => assert_eq!(v, 10, "cond=1 should return 10"), - _ => panic!("Expected Int, got {:?}", result_true), - } - - match result_false { - JoinValue::Int(v) => assert_eq!(v, 20, "cond=0 should return 20"), - _ => panic!("Expected Int, got {:?}", result_false), - } - - // Phase 34-2: Route A (既存経路) との比較は後続フェーズで実装 - // 現時点では Route B(JoinIR Frontend)単体の動作確認のみ + .lower() + .expect("Failed to lower fixture") + .run_cases(&[ + (vec![JoinValue::Int(1)], JoinValue::Int(10)), + (vec![JoinValue::Int(0)], JoinValue::Int(20)), + ]) + .expect("Test cases failed"); } /// Phase 34-3: IfSelect local pattern の A/B テスト @@ -80,63 +32,16 @@ fn joinir_frontend_if_select_simple_ab_test() { /// 期待: simple と同じ JoinIR 出力(Select ベース) #[test] fn joinir_frontend_if_select_local_ab_test() { - // フィクスチャ読み込み - let fixture_path = "docs/private/roadmap2/phases/phase-34-joinir-frontend/fixtures/joinir_if_select_local.program.json"; - let fixture_json = std::fs::read_to_string(fixture_path) - .expect("Failed to read fixture JSON"); - let program_json: serde_json::Value = serde_json::from_str(&fixture_json) - .expect("Failed to parse JSON"); - - // Route B: JoinIR Frontend 経路 - let mut lowerer = AstToJoinIrLowerer::new(); - let join_module = lowerer.lower_program_json(&program_json); - - // デバッグ: JoinIR Module の内容を確認 - eprintln!("=== JoinIR Module (local pattern) ==="); - eprintln!("Entry: {:?}", join_module.entry); - for (func_id, func) in &join_module.functions { - eprintln!("\nFunction {:?}: {}", func_id, func.name); - eprintln!(" Params: {:?}", func.params); - eprintln!(" Instructions:"); - for (i, inst) in func.body.iter().enumerate() { - eprintln!(" {}: {:?}", i, inst); - } - } - - // JoinIR Runner で実行 - let mut vm = crate::backend::mir_interpreter::MirInterpreter::new(); - - // cond = 1 (true) の場合 - let result_true = run_joinir_function( - &mut vm, - &join_module, - join_module.entry.unwrap(), - &[JoinValue::Int(1)], + JoinIrFrontendTestRunner::from_fixture( + "docs/private/roadmap2/phases/phase-34-joinir-frontend/fixtures/joinir_if_select_local.program.json" ) - .expect("Failed to run JoinIR function (cond=1)"); - - // cond = 0 (false) の場合 - let result_false = run_joinir_function( - &mut vm, - &join_module, - join_module.entry.unwrap(), - &[JoinValue::Int(0)], - ) - .expect("Failed to run JoinIR function (cond=0)"); - - // 検証: cond=1 → 10, cond=0 → 20 (simple と同じ) - match result_true { - JoinValue::Int(v) => assert_eq!(v, 10, "cond=1 should return 10"), - _ => panic!("Expected Int, got {:?}", result_true), - } - - match result_false { - JoinValue::Int(v) => assert_eq!(v, 20, "cond=0 should return 20"), - _ => panic!("Expected Int, got {:?}", result_false), - } - - // Phase 34-3: simple と local で JoinIR 出力が同じことを実証 - // 「値としての if」の本質が Select であることを確認 + .lower() + .expect("Failed to lower fixture") + .run_cases(&[ + (vec![JoinValue::Int(1)], JoinValue::Int(10)), + (vec![JoinValue::Int(0)], JoinValue::Int(20)), + ]) + .expect("Test cases failed"); } /// Phase 34-6: JsonShapeToMap._read_value_from_pair/1 の完全実装テスト @@ -146,58 +51,80 @@ fn joinir_frontend_if_select_local_ab_test() { /// 期待: 本物の substring 呼び出しが JoinIR MethodCall → MIR BoxCall で実行される #[test] fn joinir_frontend_json_shape_read_value_ab_test() { - // フィクスチャ読み込み - let fixture_path = "docs/private/roadmap2/phases/phase-34-joinir-frontend/fixtures/json_shape_read_value.program.json"; - let fixture_json = std::fs::read_to_string(fixture_path) - .expect("Failed to read fixture JSON"); - let program_json: serde_json::Value = serde_json::from_str(&fixture_json) - .expect("Failed to parse JSON"); - - // Route B: JoinIR Frontend 経路 - let mut lowerer = AstToJoinIrLowerer::new(); - let join_module = lowerer.lower_program_json(&program_json); - - // デバッグ: JoinIR Module の内容を確認 - eprintln!("=== JoinIR Module (json_shape_read_value - Phase 34-6) ==="); - eprintln!("Entry: {:?}", join_module.entry); - for (func_id, func) in &join_module.functions { - eprintln!("\nFunction {:?}: {}", func_id, func.name); - eprintln!(" Params: {:?}", func.params); - eprintln!(" Instructions:"); - for (i, inst) in func.body.iter().enumerate() { - eprintln!(" {}: {:?}", i, inst); - } - } - - // Phase 34-6: JoinIR→MIR→VM ブリッジ経由で実行(MethodCall サポート) - - // テストケース 1: v="hello", at=3 → "hel" (substring) - let result_substring = run_joinir_via_vm( - &join_module, - join_module.entry.unwrap(), - &[JoinValue::Str("hello".to_string()), JoinValue::Int(3)], + JoinIrFrontendTestRunner::from_fixture( + "docs/private/roadmap2/phases/phase-34-joinir-frontend/fixtures/json_shape_read_value.program.json" ) - .expect("Failed to run JoinIR function (v=\"hello\", at=3)"); - - // テストケース 2: v="world", at=0 → "world" (v そのまま) - let result_original = run_joinir_via_vm( - &join_module, - join_module.entry.unwrap(), - &[JoinValue::Str("world".to_string()), JoinValue::Int(0)], - ) - .expect("Failed to run JoinIR function (v=\"world\", at=0)"); - - // 検証: substring 呼び出し結果 - match result_substring { - JoinValue::Str(s) => assert_eq!(s, "hel", "v.substring(0, 3) should return \"hel\""), - _ => panic!("Expected Str, got {:?}", result_substring), - } - - // 検証: 元の文字列そのまま - match result_original { - JoinValue::Str(s) => assert_eq!(s, "world", "v should return \"world\""), - _ => panic!("Expected Str, got {:?}", result_original), - } - - // Phase 34-6: 本物の Method 呼び出し意味論が JoinIR MethodCall → MIR BoxCall で実行されることを実証 + .lower() + .expect("Failed to lower fixture") + .run_cases(&[ + ( + vec![JoinValue::Str("hello".to_string()), JoinValue::Int(3)], + JoinValue::Str("hel".to_string()), + ), + ( + vec![JoinValue::Str("world".to_string()), JoinValue::Int(0)], + JoinValue::Str("world".to_string()), + ), + ]) + .expect("Test cases failed"); +} + +/// Phase 34-7: tiny while loop の A/B テスト +/// +/// 入力: `fixtures/loop_frontend_simple.program.json` +/// パターン: `local i = 0; local acc = 0; loop(i < n) { acc = acc + 1; i = i + 1; } return acc` +/// 期待: Case-A な JoinIR (entry → loop_step → return) が生成され、正しく実行される +#[test] +fn joinir_frontend_loop_simple_ab_test() { + JoinIrFrontendTestRunner::from_fixture( + "docs/private/roadmap2/phases/phase-34-joinir-frontend/fixtures/loop_frontend_simple.program.json" + ) + .lower() + .expect("Failed to lower fixture") + .run_cases(&[ + (vec![JoinValue::Int(0)], JoinValue::Int(0)), + (vec![JoinValue::Int(3)], JoinValue::Int(3)), + (vec![JoinValue::Int(5)], JoinValue::Int(5)), + ]) + .expect("Test cases failed"); +} + +/// Phase 34-8: Break pattern の A/B テスト +/// +/// 入力: `fixtures/loop_frontend_break.program.json` +/// パターン: `loop { if i >= n { break }; acc = acc + i; i = i + 1 }` +/// 期待: n=5 → acc=10 (0+1+2+3+4) +#[test] +fn joinir_frontend_loop_break_ab_test() { + JoinIrFrontendTestRunner::from_fixture( + "docs/private/roadmap2/phases/phase-34-joinir-frontend/fixtures/loop_frontend_break.program.json" + ) + .lower() + .expect("Failed to lower fixture") + .run_cases(&[ + (vec![JoinValue::Int(0)], JoinValue::Int(0)), // n=0 → 0 + (vec![JoinValue::Int(5)], JoinValue::Int(10)), // n=5 → 10 (0+1+2+3+4) + (vec![JoinValue::Int(3)], JoinValue::Int(3)), // n=3 → 3 (0+1+2) + ]) + .expect("Test cases failed"); +} + +/// Phase 34-8: Continue pattern の A/B テスト +/// +/// 入力: `fixtures/loop_frontend_continue.program.json` +/// パターン: `loop { i = i + 1; if i == 3 { continue }; acc = acc + i }` +/// 期待: n=5 → acc=12 (1+2+4+5, i==3 スキップ) +#[test] +fn joinir_frontend_loop_continue_ab_test() { + JoinIrFrontendTestRunner::from_fixture( + "docs/private/roadmap2/phases/phase-34-joinir-frontend/fixtures/loop_frontend_continue.program.json" + ) + .lower() + .expect("Failed to lower fixture") + .run_cases(&[ + (vec![JoinValue::Int(0)], JoinValue::Int(0)), // n=0 → 0 + (vec![JoinValue::Int(5)], JoinValue::Int(12)), // n=5 → 12 (1+2+4+5) + (vec![JoinValue::Int(2)], JoinValue::Int(3)), // n=2 → 3 (1+2) + ]) + .expect("Test cases failed"); } diff --git a/src/tests/mod.rs b/src/tests/mod.rs index 1a6ae094..302ebf91 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -1,3 +1,5 @@ +mod helpers; + #[cfg(feature = "aot-plan-import")] pub mod aot_plan_import; pub mod box_tests;