diff --git a/src/mir/join_ir/frontend/ast_lowerer/loop_patterns/continue_return_pattern.rs b/src/mir/join_ir/frontend/ast_lowerer/loop_patterns/continue_return_pattern.rs new file mode 100644 index 00000000..a572015f --- /dev/null +++ b/src/mir/join_ir/frontend/ast_lowerer/loop_patterns/continue_return_pattern.rs @@ -0,0 +1,457 @@ +//! Phase 89 P1: ContinueReturn パターン lowering +//! +//! ## 責務(1行で表現) +//! **continue + early return 両方を持つループを複合処理で JoinIR に落とす** +//! +//! ## パターン例 +//! ```nyash +//! loop(i < n) { +//! if i == 5 { return acc } // Early return +//! if i == 2 { +//! i = i + 1 +//! continue +//! } +//! acc = acc + 1 +//! i = i + 1 +//! } +//! ``` +//! +//! ## 生成する JoinIR 構造 +//! - entry 関数: Call(loop_step) +//! - loop_step 関数: +//! - exit 条件チェック (i >= n で k_exit) +//! - early return 条件チェック (i == 5 で Jump(k_exit, acc)) +//! - continue 条件チェック +//! - Select: continue なら acc そのまま、そうでなければ更新 +//! - Select: continue なら i 追加更新、そうでなければ通常更新 +//! - 再帰 +//! - k_exit 関数: Return(acc) +//! +//! ## 設計原則 +//! - **StepCalculator 再利用**: Phase 89-2 の成果活用 +//! - **Fail-Fast**: 複数 Return/Continue、Return が then 以外などは明示エラー +//! - **境界明確**: continue_pattern.rs の設計を継承 + +use super::common::{ + build_join_module, create_k_exit_function, create_loop_context, parse_program_json, + process_local_inits, +}; +use super::step_calculator::StepCalculator; +use super::{AstToJoinIrLowerer, JoinModule, LoweringError}; +use crate::mir::join_ir::{CompareOp, ConstValue, JoinFunction, JoinInst, MirLikeInst}; +use crate::mir::ValueId; + +/// ContinueReturn パターンを JoinModule に変換 +/// +/// # Arguments +/// * `lowerer` - AstToJoinIrLowerer インスタンス +/// * `program_json` - Program(JSON v0) +pub fn lower( + lowerer: &mut AstToJoinIrLowerer, + program_json: &serde_json::Value, +) -> Result { + // 1. Program(JSON) をパース + let parsed = parse_program_json(program_json); + + // 2. LoopContext と entry_ctx を作成 + let (ctx, mut entry_ctx) = create_loop_context(lowerer, &parsed); + + // 3. Local 初期化を処理 + let init_insts = process_local_inits(lowerer, &parsed, &mut entry_ctx); + + // 4. Loop の cond を取得 + let loop_node = &parsed.stmts[parsed.loop_node_idx]; + let loop_cond_expr = &loop_node["cond"]; + + // 5. Loop body から Return If と Continue If を探す + let loop_body = loop_node["body"] + .as_array() + .ok_or_else(|| LoweringError::InvalidLoopBody { + message: "Loop must have 'body' array".to_string(), + })?; + + // 5-1. Return If を探す(Fail-Fast: 複数あればエラー) + let return_if_stmts: Vec<_> = loop_body + .iter() + .filter(|stmt| { + stmt["type"].as_str() == Some("If") + && stmt["then"].as_array().map_or(false, |then| { + then.iter().any(|s| s["type"].as_str() == Some("Return")) + }) + }) + .collect(); + + if return_if_stmts.is_empty() { + return Err(LoweringError::InvalidLoopBody { + message: "ContinueReturn pattern must have If + Return".to_string(), + }); + } + + if return_if_stmts.len() > 1 { + return Err(LoweringError::InvalidLoopBody { + message: format!( + "ContinueReturn pattern validation failed: multiple Return statements found.\n\ + Expected: Exactly one 'if {{ return ... }}' statement in loop body.\n\ + Found: {} Return statements.\n\ + Hint: Combine multiple return conditions into a single if statement.", + return_if_stmts.len() + ), + }); + } + + let return_if_stmt = return_if_stmts[0]; + + // 5-2. Return が then にあることを確認(Fail-Fast) + let return_then = return_if_stmt["then"] + .as_array() + .ok_or_else(|| LoweringError::InvalidLoopBody { + message: "Return If must have 'then' array".to_string(), + })?; + + let return_stmt = return_then + .iter() + .find(|s| s["type"].as_str() == Some("Return")) + .ok_or_else(|| LoweringError::InvalidLoopBody { + message: "Return If 'then' must contain Return statement".to_string(), + })?; + + // else に Return があったら Fail-Fast + if let Some(else_branch) = return_if_stmt["else"].as_array() { + if else_branch + .iter() + .any(|s| s["type"].as_str() == Some("Return")) + { + return Err(LoweringError::InvalidLoopBody { + message: "ContinueReturn pattern validation failed: Return in 'else' branch.\n\ + Expected: Return statement only in 'then' branch.\n\ + Found: Return in 'else' branch.\n\ + Hint: Move Return statement to 'then' branch or invert condition." + .to_string(), + }); + } + } + + let return_cond_expr = &return_if_stmt["cond"]; + let return_expr = &return_stmt["expr"]; + + // 5-3. Continue If を探す(Fail-Fast: 複数あればエラー) + let continue_if_stmts: Vec<_> = loop_body + .iter() + .filter(|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")) + }) + }) + .collect(); + + if continue_if_stmts.is_empty() { + return Err(LoweringError::InvalidLoopBody { + message: "ContinueReturn pattern must have If + Continue".to_string(), + }); + } + + if continue_if_stmts.len() > 1 { + return Err(LoweringError::InvalidLoopBody { + message: format!( + "ContinueReturn pattern validation failed: multiple Continue statements found.\n\ + Expected: Exactly one 'if {{ continue }}' statement in loop body.\n\ + Found: {} Continue statements.\n\ + Hint: Combine multiple continue conditions into a single if statement.", + continue_if_stmts.len() + ), + }); + } + + let continue_if_stmt = continue_if_stmts[0]; + let continue_cond_expr = &continue_if_stmt["cond"]; + + // 6. entry 関数を生成 + let entry_func = + create_entry_function_continue_return(&ctx, &parsed, init_insts, &mut entry_ctx); + + // 7. loop_step 関数を生成 + let loop_step_func = create_loop_step_function_continue_return( + lowerer, + &ctx, + &parsed.func_name, + loop_cond_expr, + return_cond_expr, + return_expr, + continue_cond_expr, + continue_if_stmt, + loop_body, + )?; + + // 8. k_exit 関数を生成 + let k_exit_func = create_k_exit_function(&ctx, &parsed.func_name); + + // 9. JoinModule を構築 + Ok(build_join_module(entry_func, loop_step_func, k_exit_func)) +} + +/// ContinueReturn パターン用 entry 関数を生成 +fn create_entry_function_continue_return( + ctx: &super::common::LoopContext, + parsed: &super::common::ParsedProgram, + init_insts: Vec, + entry_ctx: &mut super::super::context::ExtractCtx, +) -> JoinFunction { + // i, acc, n を取得 + let i_init = entry_ctx.get_var("i").expect("i must be initialized"); + let acc_init = entry_ctx.get_var("acc").expect("acc must be initialized"); + let n_param = entry_ctx.get_var("n").expect("n must be parameter"); + + let loop_result = entry_ctx.alloc_var(); + + let mut body = init_insts; + body.push(JoinInst::Call { + func: ctx.loop_step_id, + args: vec![i_init, acc_init, n_param], + k_next: None, + dst: Some(loop_result), + }); + body.push(JoinInst::Ret { + value: Some(loop_result), + }); + + JoinFunction { + id: ctx.entry_id, + name: parsed.func_name.clone(), + params: (0..parsed.param_names.len()) + .map(|i| ValueId(i as u32)) + .collect(), + body, + exit_cont: None, + } +} + +/// ContinueReturn パターン用 loop_step 関数を生成 +#[allow(clippy::too_many_arguments)] +fn create_loop_step_function_continue_return( + lowerer: &mut AstToJoinIrLowerer, + ctx: &super::common::LoopContext, + func_name: &str, + loop_cond_expr: &serde_json::Value, + return_cond_expr: &serde_json::Value, + return_expr: &serde_json::Value, + continue_cond_expr: &serde_json::Value, + continue_if_stmt: &serde_json::Value, + loop_body: &[serde_json::Value], +) -> Result { + use super::super::context::ExtractCtx; + + let step_i = ValueId(0); + let step_acc = ValueId(1); + let step_n = 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 body = Vec::new(); + + // 1. exit 条件チェック(!(i < n) = i >= n で抜ける) + let (cond_var, cond_insts) = lowerer.extract_value(loop_cond_expr, &mut step_ctx); + body.extend(cond_insts); + + let false_const = step_ctx.alloc_var(); + let exit_cond = step_ctx.alloc_var(); + + body.push(JoinInst::Compute(MirLikeInst::Const { + dst: false_const, + value: ConstValue::Bool(false), + })); + body.push(JoinInst::Compute(MirLikeInst::Compare { + dst: exit_cond, + op: CompareOp::Eq, + lhs: cond_var, + rhs: false_const, + })); + + body.push(JoinInst::Jump { + cont: ctx.k_exit_id.as_cont(), + args: vec![step_acc], + cond: Some(exit_cond), + }); + + // 2. Early return 条件チェック(i == 5 で Jump(k_exit, return_val)) + let (return_cond_var, return_cond_insts) = + lowerer.extract_value(return_cond_expr, &mut step_ctx); + body.extend(return_cond_insts); + + let (return_val, return_val_insts) = lowerer.extract_value(return_expr, &mut step_ctx); + body.extend(return_val_insts); + + body.push(JoinInst::Jump { + cont: ctx.k_exit_id.as_cont(), + args: vec![return_val], + cond: Some(return_cond_var), + }); + + // 3. Continue pattern: i のインクリメント処理 + // Continue If の then 内に i の更新がある場合、それを使う(例: i = i + 1) + let continue_then = continue_if_stmt["then"] + .as_array() + .ok_or_else(|| LoweringError::InvalidLoopBody { + message: "Continue If must have 'then' array".to_string(), + })?; + + let i_update_in_continue = continue_then + .iter() + .find(|stmt| stmt["type"].as_str() == Some("Local") && stmt["name"].as_str() == Some("i")) + .ok_or_else(|| LoweringError::InvalidLoopBody { + message: format!( + "ContinueReturn pattern validation failed: missing 'i' increment in continue block.\n\ + Expected: Continue block must contain 'local i = i + K' where K is a constant.\n\ + Found: Continue block does not contain 'i' update.\n\ + Hint: Add 'i = i + 1' inside the 'if {{ continue }}' block." + ), + })?; + + let i_expr = &i_update_in_continue["expr"]; + let (i_next, i_insts) = lowerer.extract_value(i_expr, &mut step_ctx); + body.extend(i_insts); + step_ctx.register_param("i".to_string(), i_next); + + // 通常の i 更新を探す(ループ末尾) + let i_update_normal = loop_body + .iter() + .filter(|stmt| stmt["type"].as_str() == Some("Local") && stmt["name"].as_str() == Some("i")) + .last() // Continue の後にある最後の i 更新を使う + .ok_or_else(|| LoweringError::InvalidLoopBody { + message: format!( + "ContinueReturn pattern validation failed: missing normal 'i' increment.\n\ + Expected: Loop body must contain 'local i = i + K' outside continue block.\n\ + Found: No 'i' update found in loop body.\n\ + Hint: Add 'i = i + 1' at the end of the loop body." + ), + })?; + + // Continue パスと通常パスの i 更新の差分計算(StepCalculator 活用) + let mut i_next_continue = i_next; + let base_k = StepCalculator::extract_linear_increment(&i_update_normal["expr"], "i") + .ok_or_else(|| { + let expr_debug = serde_json::to_string(&i_update_normal["expr"]) + .unwrap_or_else(|_| "".to_string()); + LoweringError::InvalidLoopBody { + message: format!( + "ContinueReturn pattern validation failed: invalid step increment form.\n\ + Expected: i = i + const (e.g., 'i = i + 1', 'i = i + 2').\n\ + Found: {}\n\ + Hint: Change the 'i' update to addition form 'i = i + K' where K is a constant integer.", + expr_debug + ), + } + })?; + + let then_k = StepCalculator::extract_linear_increment(i_expr, "i").ok_or_else(|| { + let expr_debug = + serde_json::to_string(i_expr).unwrap_or_else(|_| "".to_string()); + LoweringError::InvalidLoopBody { + message: format!( + "ContinueReturn pattern validation failed: invalid continue branch step increment.\n\ + Expected: In continue block, 'i = i + const' (e.g., 'i = i + 2').\n\ + Found: {}\n\ + Hint: Ensure the continue block updates 'i' using addition form 'i = i + K'.", + expr_debug + ), + } + })?; + + let delta = StepCalculator::calculate_step_difference(then_k, base_k); + if delta != 0 { + let delta_const = step_ctx.alloc_var(); + body.push(JoinInst::Compute(MirLikeInst::Const { + dst: delta_const, + value: ConstValue::Integer(delta), + })); + let bumped = step_ctx.alloc_var(); + body.push(JoinInst::Compute(MirLikeInst::BinOp { + dst: bumped, + op: crate::mir::join_ir::BinOpKind::Add, + lhs: i_next, + rhs: delta_const, + })); + i_next_continue = bumped; + } + + // 4. Continue 条件を評価 + let (continue_cond_var, continue_cond_insts) = + lowerer.extract_value(continue_cond_expr, &mut step_ctx); + body.extend(continue_cond_insts); + + // 5. acc の更新値を計算(通常パス) + let acc_update_local = loop_body + .iter() + .find(|stmt| { + stmt["type"].as_str() == Some("Local") && stmt["name"].as_str() == Some("acc") + }) + .ok_or_else(|| LoweringError::InvalidLoopBody { + message: format!( + "ContinueReturn pattern validation failed: missing accumulator update.\n\ + Expected: Loop body must contain 'local acc = ...' statement.\n\ + Found: No 'acc' update found in loop body.\n\ + Hint: Add 'acc = acc + ' or similar accumulator update." + ), + })?; + + let acc_expr = &acc_update_local["expr"]; + let (acc_increment, acc_insts) = lowerer.extract_value(acc_expr, &mut step_ctx); + body.extend(acc_insts); + + // Continue 分岐側でも acc を更新できる場合(例: acc += 1) + let mut acc_then_val = step_acc; + if let Some(then_acc_local) = continue_then.iter().find(|stmt| { + stmt["type"].as_str() == Some("Local") && stmt["name"].as_str() == Some("acc") + }) { + let (acc_then, acc_then_insts) = + lowerer.extract_value(&then_acc_local["expr"], &mut step_ctx); + body.extend(acc_then_insts); + acc_then_val = acc_then; + } + + // 6. Select: Continue/通常 で acc を切り替える + let acc_next = step_ctx.alloc_var(); + body.push(JoinInst::Select { + dst: acc_next, + cond: continue_cond_var, + then_val: acc_then_val, + else_val: acc_increment, + type_hint: None, + }); + + // 7. Select: Continue/通常 で次 i を切り替える + // Note: 通常パスは i_next (continue の i 更新ベース)、continue パスは i_next_continue (差分加算済み) + // しかし、この fixture では continue 時に i = i + 1、通常時も i = i + 1 なので差分なし + // → 本実装では差分があっても動作するよう設計 + let i_next_selected = step_ctx.alloc_var(); + body.push(JoinInst::Select { + dst: i_next_selected, + cond: continue_cond_var, + then_val: i_next_continue, + else_val: i_next, + type_hint: None, + }); + + // 8. 末尾再帰 + let recurse_result = step_ctx.alloc_var(); + body.push(JoinInst::Call { + func: ctx.loop_step_id, + args: vec![i_next_selected, acc_next, step_n], + k_next: None, + dst: Some(recurse_result), + }); + body.push(JoinInst::Ret { + value: Some(recurse_result), + }); + + Ok(JoinFunction { + id: ctx.loop_step_id, + name: format!("{}_loop_step", func_name), + params: vec![step_i, step_acc, step_n], + body, + exit_cont: None, + }) +} diff --git a/src/mir/join_ir/frontend/ast_lowerer/loop_patterns/mod.rs b/src/mir/join_ir/frontend/ast_lowerer/loop_patterns/mod.rs index 22437778..69d65b0b 100644 --- a/src/mir/join_ir/frontend/ast_lowerer/loop_patterns/mod.rs +++ b/src/mir/join_ir/frontend/ast_lowerer/loop_patterns/mod.rs @@ -18,6 +18,7 @@ use super::{AstToJoinIrLowerer, JoinModule}; pub mod break_pattern; pub mod common; pub mod continue_pattern; +pub mod continue_return_pattern; pub mod filter; #[cfg(feature = "normalized_dev")] pub mod if_sum_break_pattern; @@ -128,9 +129,6 @@ pub fn lower_loop_with_pattern( LoopPattern::Simple => simple::lower(lowerer, program_json), LoopPattern::Break => break_pattern::lower(lowerer, program_json), LoopPattern::Continue => continue_pattern::lower(lowerer, program_json), - LoopPattern::ContinueReturn => Err(LoweringError::UnimplementedPattern { - pattern: LoopPattern::ContinueReturn, - reason: "ContinueReturn pattern Phase 89 implementation in progress (Step 3)".to_string(), - }), + LoopPattern::ContinueReturn => continue_return_pattern::lower(lowerer, program_json), } } diff --git a/src/mir/join_ir/frontend/ast_lowerer/mod.rs b/src/mir/join_ir/frontend/ast_lowerer/mod.rs index 1a6d8472..48bb4fea 100644 --- a/src/mir/join_ir/frontend/ast_lowerer/mod.rs +++ b/src/mir/join_ir/frontend/ast_lowerer/mod.rs @@ -94,6 +94,11 @@ fn resolve_function_route(func_name: &str) -> Result { "jsonparser_unescape_string_step2_min", FunctionRoute::LoopFrontend, ), + // Phase 89 P1: ContinueReturn pattern minimal fixture + ( + "pattern_continue_return_minimal", + FunctionRoute::LoopFrontend, + ), ]; if let Some((_, route)) = TABLE.iter().find(|(name, _)| *name == func_name) { diff --git a/src/mir/join_ir/normalized/fixtures.rs b/src/mir/join_ir/normalized/fixtures.rs index 7ab0056a..6b653dc5 100644 --- a/src/mir/join_ir/normalized/fixtures.rs +++ b/src/mir/join_ir/normalized/fixtures.rs @@ -823,6 +823,30 @@ pub fn build_jsonparser_unescape_string_step2_min_structured_for_normalized_dev( module } +/// Pattern Continue + Return minimal を Structured で組み立てるヘルパー +/// +/// Fixture: docs/private/roadmap2/phases/normalized_dev/fixtures/pattern_continue_return_min.program.json +pub fn build_pattern_continue_return_min_structured_for_normalized_dev() -> JoinModule { + const FIXTURE: &str = include_str!( + "../../../../docs/private/roadmap2/phases/normalized_dev/fixtures/pattern_continue_return_min.program.json" + ); + + let program_json: serde_json::Value = + serde_json::from_str(FIXTURE).expect("pattern_continue_return_min fixture should be valid JSON"); + + let mut lowerer = AstToJoinIrLowerer::new(); + let module = lowerer.lower_program_json(&program_json); + + if joinir_dev_enabled() && joinir_test_debug_enabled() { + eprintln!( + "[joinir/normalized-dev] pattern_continue_return_min structured module: {:#?}", + module + ); + } + + module +} + /// まとめて import したいとき用のプレリュード。 pub mod prelude { pub use super::{ @@ -839,6 +863,7 @@ pub mod prelude { build_pattern3_if_sum_multi_min_structured_for_normalized_dev, build_pattern3_json_if_sum_min_structured_for_normalized_dev, build_pattern4_continue_min_structured_for_normalized_dev, + build_pattern_continue_return_min_structured_for_normalized_dev, build_selfhost_if_sum_p3_ext_structured_for_normalized_dev, build_selfhost_if_sum_p3_structured_for_normalized_dev, build_selfhost_token_scan_p2_accum_structured_for_normalized_dev, diff --git a/tests/normalized_joinir_min.rs b/tests/normalized_joinir_min.rs index e5799649..79d1759c 100644 --- a/tests/normalized_joinir_min.rs +++ b/tests/normalized_joinir_min.rs @@ -18,6 +18,7 @@ use nyash_rust::mir::join_ir::normalized::fixtures::{ build_pattern3_if_sum_multi_min_structured_for_normalized_dev, build_pattern3_json_if_sum_min_structured_for_normalized_dev, build_pattern4_continue_min_structured_for_normalized_dev, + build_pattern_continue_return_min_structured_for_normalized_dev, build_selfhost_args_parse_p2_structured_for_normalized_dev, build_selfhost_if_sum_p3_ext_structured_for_normalized_dev, build_selfhost_if_sum_p3_structured_for_normalized_dev, diff --git a/tests/normalized_joinir_min/shapes.rs b/tests/normalized_joinir_min/shapes.rs index cbb84a7b..477f0ab9 100644 --- a/tests/normalized_joinir_min/shapes.rs +++ b/tests/normalized_joinir_min/shapes.rs @@ -537,3 +537,34 @@ fn test_phase54_structural_axis_discrimination_p3() { ); } } + +/// Phase 89 P1: Pattern Continue + Return minimal - vm_bridge direct vs structured +#[test] +fn test_normalized_pattern_continue_return_min_vm_bridge_direct_matches_structured() { + let _ctx = normalized_dev_test_ctx(); + let structured = build_pattern_continue_return_min_structured_for_normalized_dev(); + let entry = structured.entry.expect("structured entry required"); + + let input = [JoinValue::Int(10)]; // n = 10 + let base = run_joinir_vm_bridge(&structured, entry, &input, false); + let dev = run_joinir_vm_bridge(&structured, entry, &input, true); + + assert_eq!(base, dev, "vm bridge mismatch for pattern continue+return min"); +} + +/// Phase 89 P1: Pattern Continue + Return minimal - expected output test +#[test] +fn test_normalized_pattern_continue_return_min_expected_output() { + let _ctx = normalized_dev_test_ctx(); + let structured = build_pattern_continue_return_min_structured_for_normalized_dev(); + let entry = structured.entry.expect("structured entry required"); + + let input = [JoinValue::Int(10)]; // n = 10 + let result = run_joinir_vm_bridge(&structured, entry, &input, true); + + assert_eq!( + result, + JoinValue::Int(4), + "Expected acc=4 for n=10 (i=0,1,3,4 increments acc, i=2 skipped by continue, i=5 early return)" + ); +}