diff --git a/src/mir/join_ir/lowering/generic_case_a/trim.rs b/src/mir/join_ir/lowering/generic_case_a/trim.rs new file mode 100644 index 00000000..4cb70465 --- /dev/null +++ b/src/mir/join_ir/lowering/generic_case_a/trim.rs @@ -0,0 +1,537 @@ +//! String Trim Loop Lowering (Case A) +//! +//! Phase 192: Extracted from generic_case_a.rs monolith. +//! +//! ## Responsibility +//! +//! Lowers FuncScannerBox.trim/1 string trimming loop to JoinIR. +//! +//! ## Pattern Structure +//! +//! ```text +//! entry: trim_main(s) +//! str = "" + s // Copy string +//! n = str.length() +//! b = skip_leading(str, 0, n) // Find first non-space +//! e = n + 0 +//! call loop_step(str, b, e) +//! +//! loop_step(str, b, e): +//! if e <= b { return str.substring(b, e) } // Empty or done +//! ch = str.substring(e-1, e) +//! if ch not in [' ', '\t', '\n', '\r'] { +//! return str.substring(b, e) // Non-space found +//! } +//! continue loop_step(str, b, e-1) // Skip trailing space +//! +//! skip_leading(s, i, n): +//! if i >= n { return i } +//! ch = s.substring(i, i+1) +//! if ch not in [' ', '\t', '\n', '\r'] { +//! return i // Non-space found +//! } +//! continue skip_leading(s, i+1, n) // Skip leading space +//! ``` +//! +//! ## ValueId Allocation +//! +//! - Entry range: 5000-5999 (`value_id_ranges::funcscanner_trim::entry`) +//! - Loop range: 6000-6999 (`value_id_ranges::funcscanner_trim::loop_step`) +//! - Skip range: 7000-7999 (hardcoded ValueId for skip_leading) +//! +//! ## See Also +//! +//! - `value_id_ranges::funcscanner_trim` - ValueId allocation strategy +//! - `whitespace_check` - Whitespace detection helper (shared with skip_ws) + +use std::collections::BTreeMap; + +use crate::mir::join_ir::lowering::loop_scope_shape::CaseAContext; +use crate::mir::join_ir::lowering::value_id_ranges; +use crate::mir::join_ir::{ + BinOpKind, CompareOp, ConstValue, JoinContId, JoinFuncId, JoinFunction, JoinInst, JoinModule, + LoopExitShape, LoopHeaderShape, MirLikeInst, +}; +use crate::mir::ValueId; + +use super::entry_builder::EntryFunctionBuilder; + +/// Phase 30 F-3.0.3: LoopScopeShape を直接受け取る trim lowerer +/// +/// 呼び出し元で LoopScopeShape を明示的に構築し、この関数に渡す。 +/// CaseAContext::from_scope() 経由で ctx を作成。 +pub(crate) fn lower_case_a_trim_with_scope( + scope: crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape, +) -> Option { + let ctx = CaseAContext::from_scope(scope, "trim", |offset| { + value_id_ranges::funcscanner_trim::loop_step(offset) + })?; + lower_case_a_trim_core(&ctx) +} + +/// trim JoinModule 構築のコア実装 +/// +/// CaseAContext から JoinModule を構築する共通ロジック。 +/// `_for_trim_minimal` と `_with_scope` の両方から呼ばれる。 +fn lower_case_a_trim_core(ctx: &CaseAContext) -> Option { + let string_key = ctx.pinned_name_or_first(0)?; + let base_key = ctx + .pinned_name_or_first(1) + .unwrap_or_else(|| string_key.clone()); + let carrier_key = ctx.carrier_name_or_first(0)?; + + let s_loop = ctx.get_loop_id(&string_key)?; + let b_loop = ctx.get_loop_id(&base_key)?; + let e_loop = ctx.get_loop_id(&carrier_key)?; + + let mut join_module = JoinModule::new(); + + // entry: trim_main(s_param) + let trim_main_id = JoinFuncId::new(0); + let s_param = value_id_ranges::funcscanner_trim::entry(0); + let mut trim_main_func = + JoinFunction::new(trim_main_id, "trim_main".to_string(), vec![s_param]); + + let str_val = value_id_ranges::funcscanner_trim::entry(1); + let n_val = value_id_ranges::funcscanner_trim::entry(2); + let b_val = value_id_ranges::funcscanner_trim::entry(3); + let e_init = value_id_ranges::funcscanner_trim::entry(4); + let const_empty = value_id_ranges::funcscanner_trim::entry(5); + let const_zero = value_id_ranges::funcscanner_trim::entry(6); + + trim_main_func + .body + .push(JoinInst::Compute(MirLikeInst::Const { + dst: const_empty, + value: ConstValue::String("".to_string()), + })); + trim_main_func + .body + .push(JoinInst::Compute(MirLikeInst::BinOp { + dst: str_val, + lhs: const_empty, + rhs: s_param, + op: BinOpKind::Add, + })); + trim_main_func + .body + .push(JoinInst::Compute(MirLikeInst::BoxCall { + dst: Some(n_val), + box_name: "StringBox".to_string(), + method: "length".to_string(), + args: vec![str_val], + })); + trim_main_func + .body + .push(JoinInst::Compute(MirLikeInst::Const { + dst: const_zero, + value: ConstValue::Integer(0), + })); + + let skip_leading_id = JoinFuncId::new(2); + trim_main_func.body.push(JoinInst::Call { + func: skip_leading_id, + args: vec![str_val, const_zero, n_val], + k_next: None, + dst: Some(b_val), + }); + + trim_main_func + .body + .push(JoinInst::Compute(MirLikeInst::BinOp { + dst: e_init, + op: BinOpKind::Add, + lhs: n_val, + rhs: const_zero, + })); + + // Phase 192: Use EntryFunctionBuilder for boilerplate initialization + let mut entry_builder = EntryFunctionBuilder::new(); + entry_builder.add_var(string_key.clone(), str_val); + entry_builder.add_var(base_key.clone(), b_val); + entry_builder.add_var(carrier_key.clone(), e_init); + let entry_name_to_id = entry_builder.get_map().clone(); + + let loop_call_args: Vec = ctx + .ordered_pinned + .iter() + .chain(ctx.ordered_carriers.iter()) + .map(|name| entry_name_to_id.get(name).copied()) + .collect::>()?; + + let loop_step_id = JoinFuncId::new(1); + trim_main_func.body.push(JoinInst::Call { + func: loop_step_id, + args: loop_call_args, + k_next: None, + dst: None, + }); + + join_module.entry = Some(trim_main_id); + join_module.add_function(trim_main_func); + + // loop_step(str, b, e) + let header_shape = LoopHeaderShape::new_manual(ctx.pinned_ids.clone(), ctx.carrier_ids.clone()); + let loop_params = header_shape.to_loop_step_params(); + let mut loop_step_func = + JoinFunction::new(loop_step_id, "loop_step".to_string(), loop_params.clone()); + + let cond = value_id_ranges::funcscanner_trim::loop_step(3); + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::Compare { + dst: cond, + lhs: e_loop, + rhs: b_loop, + op: CompareOp::Gt, + })); + + let bool_false = value_id_ranges::funcscanner_trim::loop_step(19); + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::Const { + dst: bool_false, + value: ConstValue::Bool(false), + })); + + let trimmed_base = value_id_ranges::funcscanner_trim::loop_step(4); + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::BoxCall { + dst: Some(trimmed_base), + box_name: "StringBox".to_string(), + method: "substring".to_string(), + args: vec![s_loop, b_loop, e_loop], + })); + + let cond_is_false = value_id_ranges::funcscanner_trim::loop_step(20); + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::Compare { + dst: cond_is_false, + lhs: cond, + rhs: bool_false, + op: CompareOp::Eq, + })); + + let _exit_shape_trim = if ctx.exit_args.is_empty() { + LoopExitShape::new_manual(vec![e_loop]) + } else { + LoopExitShape::new_manual(ctx.exit_args.clone()) + }; + + loop_step_func.body.push(JoinInst::Jump { + cont: JoinContId::new(0), + args: vec![trimmed_base], + cond: Some(cond_is_false), + }); + + let const_1 = value_id_ranges::funcscanner_trim::loop_step(5); + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::Const { + dst: const_1, + value: ConstValue::Integer(1), + })); + + let e_minus_1 = value_id_ranges::funcscanner_trim::loop_step(6); + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::BinOp { + dst: e_minus_1, + lhs: e_loop, + rhs: const_1, + op: BinOpKind::Sub, + })); + + let ch = value_id_ranges::funcscanner_trim::loop_step(7); + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::BoxCall { + dst: Some(ch), + box_name: "StringBox".to_string(), + method: "substring".to_string(), + args: vec![s_loop, e_minus_1, e_loop], + })); + + let cmp_space = value_id_ranges::funcscanner_trim::loop_step(8); + let cmp_tab = value_id_ranges::funcscanner_trim::loop_step(9); + let cmp_newline = value_id_ranges::funcscanner_trim::loop_step(10); + let cmp_cr = value_id_ranges::funcscanner_trim::loop_step(11); + + let const_space = value_id_ranges::funcscanner_trim::loop_step(12); + let const_tab = value_id_ranges::funcscanner_trim::loop_step(13); + let const_newline = value_id_ranges::funcscanner_trim::loop_step(14); + let const_cr = value_id_ranges::funcscanner_trim::loop_step(15); + + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::Const { + dst: const_space, + value: ConstValue::String(" ".to_string()), + })); + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::Compare { + dst: cmp_space, + lhs: ch, + rhs: const_space, + op: CompareOp::Eq, + })); + + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::Const { + dst: const_tab, + value: ConstValue::String("\\t".to_string()), + })); + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::Compare { + dst: cmp_tab, + lhs: ch, + rhs: const_tab, + op: CompareOp::Eq, + })); + + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::Const { + dst: const_newline, + value: ConstValue::String("\\n".to_string()), + })); + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::Compare { + dst: cmp_newline, + lhs: ch, + rhs: const_newline, + op: CompareOp::Eq, + })); + + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::Const { + dst: const_cr, + value: ConstValue::String("\\r".to_string()), + })); + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::Compare { + dst: cmp_cr, + lhs: ch, + rhs: const_cr, + op: CompareOp::Eq, + })); + + let or1 = value_id_ranges::funcscanner_trim::loop_step(16); + let or2 = value_id_ranges::funcscanner_trim::loop_step(17); + let is_space = value_id_ranges::funcscanner_trim::loop_step(18); + + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::BinOp { + dst: or1, + lhs: cmp_space, + rhs: cmp_tab, + op: BinOpKind::Or, + })); + + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::BinOp { + dst: or2, + lhs: or1, + rhs: cmp_newline, + op: BinOpKind::Or, + })); + + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::BinOp { + dst: is_space, + lhs: or2, + rhs: cmp_cr, + op: BinOpKind::Or, + })); + + let is_space_false = value_id_ranges::funcscanner_trim::loop_step(21); + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::Compare { + dst: is_space_false, + lhs: is_space, + rhs: bool_false, + op: CompareOp::Eq, + })); + + loop_step_func.body.push(JoinInst::Jump { + cont: JoinContId::new(1), + args: vec![trimmed_base], + cond: Some(is_space_false), + }); + + let e_next = value_id_ranges::funcscanner_trim::loop_step(22); + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::BinOp { + dst: e_next, + lhs: e_loop, + rhs: const_1, + op: BinOpKind::Sub, + })); + + loop_step_func.body.push(JoinInst::Call { + func: loop_step_id, + args: vec![s_loop, b_loop, e_next], + k_next: None, + dst: None, + }); + + join_module.add_function(loop_step_func); + + // skip_leading 関数(共通ロジックを手書き版と合わせる) + let mut skip_func = JoinFunction::new( + skip_leading_id, + "skip_leading".to_string(), + vec![ValueId(7000), ValueId(7001), ValueId(7002)], + ); + let s_skip = ValueId(7000); + let i_skip = ValueId(7001); + let n_skip = ValueId(7002); + let cmp_len = ValueId(7003); + let const_1_skip = ValueId(7004); + let i_plus_1_skip = ValueId(7005); + let ch_skip = ValueId(7006); + let cmp_space_skip = ValueId(7007); + let cmp_tab_skip = ValueId(7008); + let cmp_newline_skip = ValueId(7009); + let cmp_cr_skip = ValueId(7010); + let const_space_skip = ValueId(7011); + let const_tab_skip = ValueId(7012); + let const_newline_skip = ValueId(7013); + let const_cr_skip = ValueId(7014); + let or1_skip = ValueId(7015); + let or2_skip = ValueId(7016); + let is_space_skip = ValueId(7017); + let bool_false_skip = ValueId(7018); + let is_space_false_skip = ValueId(7019); + + skip_func.body.push(JoinInst::Compute(MirLikeInst::Compare { + dst: cmp_len, + lhs: i_skip, + rhs: n_skip, + op: CompareOp::Ge, + })); + skip_func.body.push(JoinInst::Jump { + cont: JoinContId::new(2), + args: vec![i_skip], + cond: Some(cmp_len), + }); + skip_func.body.push(JoinInst::Compute(MirLikeInst::Const { + dst: const_1_skip, + value: ConstValue::Integer(1), + })); + skip_func.body.push(JoinInst::Compute(MirLikeInst::BinOp { + dst: i_plus_1_skip, + lhs: i_skip, + rhs: const_1_skip, + op: BinOpKind::Add, + })); + skip_func.body.push(JoinInst::Compute(MirLikeInst::BoxCall { + dst: Some(ch_skip), + box_name: "StringBox".to_string(), + method: "substring".to_string(), + args: vec![s_skip, i_skip, i_plus_1_skip], + })); + skip_func.body.push(JoinInst::Compute(MirLikeInst::Const { + dst: const_space_skip, + value: ConstValue::String(" ".to_string()), + })); + skip_func.body.push(JoinInst::Compute(MirLikeInst::Compare { + dst: cmp_space_skip, + lhs: ch_skip, + rhs: const_space_skip, + op: CompareOp::Eq, + })); + skip_func.body.push(JoinInst::Compute(MirLikeInst::Const { + dst: const_tab_skip, + value: ConstValue::String("\\t".to_string()), + })); + skip_func.body.push(JoinInst::Compute(MirLikeInst::Compare { + dst: cmp_tab_skip, + lhs: ch_skip, + rhs: const_tab_skip, + op: CompareOp::Eq, + })); + skip_func.body.push(JoinInst::Compute(MirLikeInst::Const { + dst: const_newline_skip, + value: ConstValue::String("\\n".to_string()), + })); + skip_func.body.push(JoinInst::Compute(MirLikeInst::Compare { + dst: cmp_newline_skip, + lhs: ch_skip, + rhs: const_newline_skip, + op: CompareOp::Eq, + })); + skip_func.body.push(JoinInst::Compute(MirLikeInst::Const { + dst: const_cr_skip, + value: ConstValue::String("\\r".to_string()), + })); + skip_func.body.push(JoinInst::Compute(MirLikeInst::Compare { + dst: cmp_cr_skip, + lhs: ch_skip, + rhs: const_cr_skip, + op: CompareOp::Eq, + })); + skip_func.body.push(JoinInst::Compute(MirLikeInst::BinOp { + dst: or1_skip, + lhs: cmp_space_skip, + rhs: cmp_tab_skip, + op: BinOpKind::Or, + })); + skip_func.body.push(JoinInst::Compute(MirLikeInst::BinOp { + dst: or2_skip, + lhs: or1_skip, + rhs: cmp_newline_skip, + op: BinOpKind::Or, + })); + skip_func.body.push(JoinInst::Compute(MirLikeInst::BinOp { + dst: is_space_skip, + lhs: or2_skip, + rhs: cmp_cr_skip, + op: BinOpKind::Or, + })); + skip_func.body.push(JoinInst::Compute(MirLikeInst::Const { + dst: bool_false_skip, + value: ConstValue::Bool(false), + })); + skip_func.body.push(JoinInst::Compute(MirLikeInst::Compare { + dst: is_space_false_skip, + lhs: is_space_skip, + rhs: bool_false_skip, + op: CompareOp::Eq, + })); + skip_func.body.push(JoinInst::Jump { + cont: JoinContId::new(3), + args: vec![i_skip], + cond: Some(is_space_false_skip), + }); + skip_func.body.push(JoinInst::Call { + func: skip_leading_id, + args: vec![s_skip, i_plus_1_skip, n_skip], + k_next: None, + dst: None, + }); + + join_module.add_function(skip_func); + + eprintln!( + "[joinir/generic_case_a/trim] ✅ constructed JoinIR (functions={}, value_range={}..{})", + join_module.functions.len(), + value_id_ranges::base::FUNCSCANNER_TRIM, + value_id_ranges::base::FUNCSCANNER_TRIM + 1999 + ); + + Some(join_module) +}