diff --git a/docs/development/current/main/joinir-architecture-overview.md b/docs/development/current/main/joinir-architecture-overview.md index ba6b1998..38f92836 100644 --- a/docs/development/current/main/joinir-architecture-overview.md +++ b/docs/development/current/main/joinir-architecture-overview.md @@ -833,6 +833,8 @@ Pattern2/4 への統合(実際に Body-local 更新を使うループを JoinI (P2 + P4 + P5 の組み合わせで表現可能なことを設計上確認済み) - 低優先度だが理論上は P1–P4 からの拡張で吸収可能: - `_unescape_string` など、複雑な continue / 条件付き更新を含むループ + - Phase 88(dev-only)で、`i+=2 + continue`(かつ continue 分岐側で `acc` 更新)を最小フィクスチャとして抽出し、 + frontend の continue pattern を「`i = i + const` の差分加算」に限定して段階拡張した。 方針: diff --git a/docs/private b/docs/private index 00e3e637..fd8368f4 160000 --- a/docs/private +++ b/docs/private @@ -1 +1 @@ -Subproject commit 00e3e63780ecccd5e8c9a4773f07c54629431eb8 +Subproject commit fd8368f491be4dcb2c25d95a831f11771a142598 diff --git a/src/mir/join_ir/frontend/ast_lowerer/loop_patterns/continue_pattern.rs b/src/mir/join_ir/frontend/ast_lowerer/loop_patterns/continue_pattern.rs index 5654cc69..533d208d 100644 --- a/src/mir/join_ir/frontend/ast_lowerer/loop_patterns/continue_pattern.rs +++ b/src/mir/join_ir/frontend/ast_lowerer/loop_patterns/continue_pattern.rs @@ -83,6 +83,7 @@ pub fn lower( &parsed.func_name, loop_cond_expr, continue_cond_expr, + continue_if_stmt, loop_body, )?; @@ -136,6 +137,7 @@ fn create_loop_step_function_continue( func_name: &str, loop_cond_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; @@ -188,6 +190,67 @@ fn create_loop_step_function_continue( body.extend(i_insts); step_ctx.register_param("i".to_string(), i_next); + // Phase 88: Continue 分岐で i を追加更新できるようにする(例: i += 2) + // + // `continue_if` の then 内に `Local i = i + K` がある場合、 + // loop_body の通常更新 `Local i = i + K0` との差分 (K-K0) を i_next に加算して + // continue パスの次 i を構成する(Linear increment のみ対応)。 + fn extract_add_i_const(expr: &serde_json::Value) -> Option { + if expr["type"].as_str()? != "Binary" || expr["op"].as_str()? != "+" { + return None; + } + let lhs = &expr["lhs"]; + let rhs = &expr["rhs"]; + // i + const + if lhs["type"].as_str()? == "Var" && lhs["name"].as_str()? == "i" { + if rhs["type"].as_str()? == "Int" { + return rhs["value"].as_i64(); + } + } + // const + i + if rhs["type"].as_str()? == "Var" && rhs["name"].as_str()? == "i" { + if lhs["type"].as_str()? == "Int" { + return lhs["value"].as_i64(); + } + } + None + } + + let mut i_next_continue = i_next; + let continue_then = continue_if_stmt["then"] + .as_array() + .ok_or_else(|| LoweringError::InvalidLoopBody { + message: "Continue pattern If must have 'then' array".to_string(), + })?; + if let Some(then_i_local) = continue_then + .iter() + .find(|stmt| stmt["type"].as_str() == Some("Local") && stmt["name"].as_str() == Some("i")) + { + let base_k = extract_add_i_const(i_expr).ok_or_else(|| LoweringError::InvalidLoopBody { + message: "Continue pattern requires i update of form (i + const)".to_string(), + })?; + let then_k = + extract_add_i_const(&then_i_local["expr"]).ok_or_else(|| LoweringError::InvalidLoopBody { + message: "Continue pattern requires then i update of form (i + const)".to_string(), + })?; + let delta = 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; + } + } + // 3. Continue 条件を評価 let (continue_cond_var, continue_cond_insts) = lowerer.extract_value(continue_cond_expr, &mut step_ctx); @@ -205,21 +268,41 @@ fn create_loop_step_function_continue( let (acc_increment, acc_insts) = lowerer.extract_value(acc_expr, &mut step_ctx); body.extend(acc_insts); - // 5. Select: Continue なら acc そのまま、そうでなければ更新 + // Phase 88: 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; + } + + // 5. Select: Continue/通常 で acc を切り替える let acc_next = step_ctx.alloc_var(); body.push(JoinInst::Select { dst: acc_next, cond: continue_cond_var, - then_val: step_acc, // Continue: 更新しない - else_val: acc_increment, // 通常: 更新 + then_val: acc_then_val, + else_val: acc_increment, type_hint: None, // Phase 63-3 }); + // Phase 88: Continue/通常 で次 i を切り替える + 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, + }); + // 6. 末尾再帰 let recurse_result = step_ctx.alloc_var(); body.push(JoinInst::Call { func: ctx.loop_step_id, - args: vec![i_next, acc_next, step_n], + args: vec![i_next_selected, acc_next, step_n], k_next: None, dst: Some(recurse_result), }); diff --git a/src/mir/join_ir/frontend/ast_lowerer/mod.rs b/src/mir/join_ir/frontend/ast_lowerer/mod.rs index 8c095db9..1a6d8472 100644 --- a/src/mir/join_ir/frontend/ast_lowerer/mod.rs +++ b/src/mir/join_ir/frontend/ast_lowerer/mod.rs @@ -89,6 +89,11 @@ fn resolve_function_route(func_name: &str) -> Result { "jsonparser_parse_object_continue_skip_ws", FunctionRoute::LoopFrontend, ), + // Phase 88: JsonParser _unescape_string core (step2 + continue) minimal fixture + ( + "jsonparser_unescape_string_step2_min", + 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 f6c9f36e..7ab0056a 100644 --- a/src/mir/join_ir/normalized/fixtures.rs +++ b/src/mir/join_ir/normalized/fixtures.rs @@ -796,6 +796,33 @@ pub fn build_jsonparser_parse_object_continue_skip_ws_structured_for_normalized_ module } +/// JsonParser _unescape_string の「i+=2 + continue」コアを Structured で組み立てるヘルパー(dev-only)。 +/// +/// 実ループ(`tools/hako_shared/json_parser.hako::_unescape_string`)から、 +/// 文字列処理を除いて制御構造(continue + 可変ステップ更新)だけを抽出した最小フィクスチャ。 +/// +/// Fixture: docs/private/roadmap2/phases/normalized_dev/fixtures/jsonparser_unescape_string_step2_min.program.json +pub fn build_jsonparser_unescape_string_step2_min_structured_for_normalized_dev() -> JoinModule { + const FIXTURE: &str = include_str!( + "../../../../docs/private/roadmap2/phases/normalized_dev/fixtures/jsonparser_unescape_string_step2_min.program.json" + ); + + let program_json: serde_json::Value = serde_json::from_str(FIXTURE) + .expect("jsonparser_unescape_string_step2_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] jsonparser_unescape_string_step2_min structured module: {:#?}", + module + ); + } + + module +} + /// まとめて import したいとき用のプレリュード。 pub mod prelude { pub use super::{ @@ -806,6 +833,7 @@ pub mod prelude { build_jsonparser_parse_object_continue_skip_ws_structured_for_normalized_dev, build_jsonparser_skip_ws_real_structured_for_normalized_dev, build_jsonparser_skip_ws_structured_for_normalized_dev, + build_jsonparser_unescape_string_step2_min_structured_for_normalized_dev, build_pattern2_break_fixture_structured, build_pattern2_minimal_structured, build_pattern3_if_sum_min_structured_for_normalized_dev, build_pattern3_if_sum_multi_min_structured_for_normalized_dev, diff --git a/tests/normalized_joinir_min.rs b/tests/normalized_joinir_min.rs index 805783a6..e5799649 100644 --- a/tests/normalized_joinir_min.rs +++ b/tests/normalized_joinir_min.rs @@ -12,6 +12,7 @@ use nyash_rust::mir::join_ir::normalized::fixtures::{ build_jsonparser_parse_object_continue_skip_ws_structured_for_normalized_dev, build_jsonparser_skip_ws_real_structured_for_normalized_dev, build_jsonparser_skip_ws_structured_for_normalized_dev, + build_jsonparser_unescape_string_step2_min_structured_for_normalized_dev, build_pattern2_break_fixture_structured, build_pattern2_minimal_structured, build_pattern3_if_sum_min_structured_for_normalized_dev, build_pattern3_if_sum_multi_min_structured_for_normalized_dev, diff --git a/tests/normalized_joinir_min/shapes.rs b/tests/normalized_joinir_min/shapes.rs index ea25fdca..737f5eca 100644 --- a/tests/normalized_joinir_min/shapes.rs +++ b/tests/normalized_joinir_min/shapes.rs @@ -295,6 +295,24 @@ fn test_normalized_pattern4_jsonparser_parse_object_continue_skip_ws_vm_bridge_d } } +/// Phase 88: JsonParser _unescape_string コア(i+=2 + continue)を canonical route で固定する。 +#[test] +fn test_phase88_jsonparser_unescape_string_step2_min_canonical_matches_structured() { + let structured = build_jsonparser_unescape_string_step2_min_structured_for_normalized_dev(); + let entry = structured.entry.expect("structured entry required"); + + // n=10 → i=0,2,4,6,8 で acc++ → 5 + let args = [JoinValue::Int(10)]; + let structured_res = run_joinir_vm_bridge_structured_only(&structured, entry, &args); + let canonical = run_joinir_vm_bridge(&structured, entry, &args, false); + + assert_eq!( + structured_res, canonical, + "canonical unescape(step2) mismatch" + ); + assert_eq!(canonical, JoinValue::Int(5)); +} + /// Phase 48-C: JsonParser _parse_object continue skip_ws canonical route should match Structured #[test] fn test_normalized_pattern4_jsonparser_parse_object_continue_skip_ws_canonical_matches_structured()