diff --git a/docs/development/current/main/joinir-architecture-overview.md b/docs/development/current/main/joinir-architecture-overview.md index 020a75c4..6a349d83 100644 --- a/docs/development/current/main/joinir-architecture-overview.md +++ b/docs/development/current/main/joinir-architecture-overview.md @@ -247,3 +247,7 @@ JoinIR は Rust 側だけでなく、将来的に .hako selfhost コンパイラ - 185–188: Strict mode / LoopBuilder 削除 / Pattern1–4 基盤 - 189–193: Multi-function merge / Select bridge / ExitLine 箱化 - 171–172 / 33‑10/13: ConditionEnv, ConditionBinding, JoinFragmentMeta, ExitLineRefactor 等 +- `docs/development/current/main/loop_pattern_space.md` + - JoinIR ループパターン空間の整理メモ。 + どの軸(継続条件 / break / continue / PHI / 条件変数スコープ / 更新パターン)でパターンを分けるか、 + そして P1–P4 / Trim(P5) の位置づけと、今後追加候補のパターン一覧がまとまっている。 diff --git a/docs/development/current/main/loop_pattern_space.md b/docs/development/current/main/loop_pattern_space.md new file mode 100644 index 00000000..09e61b7b --- /dev/null +++ b/docs/development/current/main/loop_pattern_space.md @@ -0,0 +1,170 @@ +# JoinIR Loop Pattern Space (Phase 171 Ultra‑Think Memo) + +このメモは「JoinIR のループパターン空間」を構造的に整理したものだよ。 +総当たりではなく、**直交する少数の軸**を組み合わせることで有限のパターンに収束することを確認する。 + +--- + +## 1. ループを構成する直交軸 + +ループはだいたい次の 6 軸の組み合わせで記述できる: + +| 軸 | 選択肢 | 説明 | +|-------------------|------------------------------------|-------------------------------| +| A. 継続条件 | ① なし ② 単純 ③ 複合 | `loop(cond)` の `cond` | +| B. 早期終了 | ① なし ② break ③ 条件付き break | ループを「抜ける」経路 | +| C. スキップ | ① なし ② continue ③ 条件付き cont | 次のイテレーションに「飛ぶ」 | +| D. PHI 分岐 | ① なし ② if‑PHI ③ match‑PHI | 条件に応じて値が変わるパターン | +| E. 条件変数のスコープ | ① OuterLocal ② LoopBodyLocal | 条件で参照される変数の定義位置 | +| F. キャリア更新 | ① 単一 ② 複数 ③ 条件付き | ループ内での状態更新パターン | + +この 6 軸は、それぞれ 2〜3 通りしかないので、理論上は最大 3×3×3×3×2×3=486 通りの組み合わせになるが、 +実際に意味のあるパターンはずっと少ない(10〜20 程度)ことが分かった。 + +--- + +## 2. 現在 JoinIR が正規化済みのパターン + +代表ループと対応する Pattern はだいたいこうなっている: + +| Pattern | 代表例 | A継続 | B終了 | Cスキップ | D‑PHI | E変数 | F更新 | +|----------------|------------------------------|---------|--------------|-----------------|---------|------------|----------| +| P1: Minimal | `loop_min_while.hako` | 単純 | なし | なし | なし | Outer | 単一 | +| P2: Break | `joinir_min_loop.hako` | 単純 | 条件付きbreak | なし | なし | Outer | 単一 | +| P3: If‑PHI | `loop_if_phi.hako` | 単純 | なし | なし | if‑PHI | Outer | 条件付き | +| P4: Continue | `loop_continue_pattern4` | 単純 | なし | 条件付きcont | なし | Outer | 単一 | +| P5: Trim‑like* | `TrimTest.trim`(設計中) | 単純 | 条件付きbreak | なし | なし | BodyLocal | 単一 | + +\*P5 は Phase 171 時点では「検出+安全性判定」まで。実際の JoinIR lower は Phase 172 以降の仕事。 + +ここまでで: + +- OuterLocal 条件(関数パラメータや外側ローカル)を持つ基本的な while/break/continue/if‑PHI は JoinIR で正規化済み。 +- LoopBodyLocal 条件(`local ch = ...; if ch == ' ' { break }`)は **LoopConditionScopeBox で Fail‑Fast** し、 + Trim パターンだけを LoopBodyCarrierPromoter/TrimLoopHelper で特例として扱う設計にしている。 + +--- + +## 3. これから増やす候補パターン + +実際の Nyash コードを見た上で、「出そうだな」と分かっているパターンをいくつか挙げておく。 + +### P6: break + continue 同時 + +```hako +loop (cond) { + if x { continue } + if y { break } + ... +} +``` + +- A: 単純 +- B: 条件付き break +- C: 条件付き continue +- E: OuterLocal 条件(が理想) + +### P7: 複数キャリア + 条件付き更新 + +```hako +loop (i < n) { + if arr[i] > max { max = arr[i] } + if arr[i] < min { min = arr[i] } + i = i + 1 +} +``` + +- A: 単純 +- B: なし +- C: なし +- D: なし or if‑PHI に相当 +- F: 複数+条件付き + +### P8: ネストループ(外側ループへの break/continue) + +```hako +loop (i < n) { + loop (j < m) { + if cond { break outer } // labeled break + } +} +``` + +※ Nyash 言語仕様で outer break をどう扱うか次第で、JoinIR 側も変わる。 + +### P9: match‑PHI + +```hako +loop (i < n) { + result = match state { + "A" => compute_a() + "B" => compute_b() + } +} +``` + +これは if‑PHI を複数ケースに拡張した形。JoinIR の If/Select/PHI パスを再利用できるはず。 + +### P10: 無限ループ + 内部 break + +```hako +loop (true) { + if done { break } +} +``` + +構造としては P2 の特殊ケース(A: 継続条件なし+B: break)として扱える。 + +### P11: 複合継続条件 + LoopBodyLocal + +```hako +loop (i < n && is_valid) { + local x = compute() + is_valid = check(x) +} +``` + +これは LoopBodyLocal 状態を継続条件に折り込むパターン。 +BoolExprLowerer + LoopBodyCarrierPromoter の拡張で扱える可能性がある。 + +### P12: early return 内包 + +```hako +loop (i < n) { + if error { return null } // ループを抜けて関数終了 +} +``` + +ループ exit と関数 exit が混ざるパターン。ExitLine とは別に「関数全体の戻り値ライン」とどう噛ませるかの設計が必要。 + +--- + +## 4. 収束性について + +- 各軸が有限個の状態しか取らないこと、 +- 多くの組み合わせが意味を持たない/禁止パターンであること(無限ループ、進まないループなど) + +から、JoinIR で真面目に扱うべきループ形は **高々数十種類** に収束する。 + +実運用上の優先順としては: + +1. P1–P4: 基本パターン(すでに実装済み) +2. P5: Trim / JsonParser で現実に必要な LoopBodyLocal 条件の一部(昇格可能なもの) +3. P6, P7, P12: パーサ/集計/エラー処理で頻出 +4. P8, P9, P11: 言語仕様や実アプリのニーズを見ながら段階的に + +という順で Box を増やしていけば、「パターン爆発」にはならずに済む想定だよ。 + +--- + +## 5. 関連ドキュメント + +- `joinir-architecture-overview.md` + JoinIR 全体の箱と契約。Loop/If/ExitLine/Boundary/条件式ラインの全体図。 +- `phase33-16-design.md`, `PHASE_33_16_SUMMARY.md` + Loop header PHI / ExitLine / Boundary 再設計の詳細。 +- `phase166-jsonparser-loop-recheck.md` + JsonParserBox / Trim 系ループのインベントリと、どの Pattern に入るかの観測ログ。 +- `phase171-pattern5-loop-inventory.md` + Trim/JsonParser 向け Pattern5(LoopBodyLocal 条件)設計の進捗。 + diff --git a/docs/development/current/main/phase172-trim-lowering-impl.md b/docs/development/current/main/phase172-trim-lowering-impl.md new file mode 100644 index 00000000..d3b16a61 --- /dev/null +++ b/docs/development/current/main/phase172-trim-lowering-impl.md @@ -0,0 +1,345 @@ +# Phase 172: Trim Pattern JoinIR Lowering Implementation + +## Objective + +Implement actual JoinIR → MIR lowering for Trim pattern loops, making `test_trim_main_pattern.hako` work end-to-end. + +## Target Loop + +**File**: `local_tests/test_trim_main_pattern.hako` + +**Leading whitespace trim loop**: +```nyash +loop(start < end) { + local ch = s.substring(start, start+1) + if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" { + start = start + 1 + } else { + break + } +} +``` + +## Using Boxes from Phase 171 + +### Phase 171 Infrastructure +- ✅ **LoopConditionScopeBox**: Detects `ch` as LoopBodyLocal +- ✅ **LoopBodyCarrierPromoter**: Promotes `ch` to bool carrier `is_ch_match` +- ✅ **TrimPatternInfo**: Contains promotion metadata +- ✅ **TrimLoopHelper**: Attached to CarrierInfo for lowering guidance +- ✅ **Pattern2 validation**: Currently returns informative error at line 234-239 + +## Design Philosophy + +### Responsibility Separation + +**Host MIR Side** (before/after JoinIR): +- Execute `substring()` method calls +- Evaluate OR chain comparisons (`ch == " " || ch == "\t" || ...`) +- Generate bool result for carrier +- BoxCall operations + +**JoinIR Side** (pure control flow): +- bool carrier-based loop control +- break condition: `!is_ch_match` +- No knowledge of substring/OR chain +- No new JoinIR instructions + +### Key Insight + +The Trim pattern transformation: +``` +Original: + local ch = s.substring(start, start+1) + if (ch == " " || ch == "\t" || ...) { start++ } else { break } + +Transformed: + is_ch_match = (s.substring(start, start+1) == " " || ...) # Host MIR + if (!is_ch_match) { break } # JoinIR +``` + +## Implementation Steps + +### Step 1: Loop Pre-Initialization (Task 172-2) + +**Location**: `pattern2_with_break.rs`, before JoinIR generation + +**Goal**: Initialize bool carrier before entering loop + +**Implementation**: +```rust +// After Line 264 (after carrier_info.merge_from) +if let Some(helper) = carrier_info.trim_helper() { + if helper.is_safe_trim() { + // Generate initial whitespace check + // ch0 = s.substring(start, start+1) + let ch0 = self.emit_method_call("substring", s_id, vec![start_id, start_plus_1_id])?; + + // is_ch_match0 = (ch0 == " " || ch0 == "\t" || ...) + let is_ch_match0 = emit_whitespace_check(self, ch0, &helper.whitespace_chars)?; + + // Update carrier_info initial value + // (Will be used in JoinIR input mapping) + } +} +``` + +**Helper Function** (new): +```rust +fn emit_whitespace_check( + builder: &mut MirBuilder, + ch_value: ValueId, + whitespace_chars: &[String], +) -> Result { + // Build OR chain: ch == " " || ch == "\t" || ... + let mut result = None; + for ws_char in whitespace_chars { + let ws_const = emit_string(builder, ws_char.clone()); + let eq_check = emit_eq_to(builder, ch_value, ws_const)?; + + result = Some(if let Some(prev) = result { + // prev || eq_check + emit_binary_op(builder, BinaryOp::Or, prev, eq_check)? + } else { + eq_check + }); + } + result.ok_or("Empty whitespace_chars".to_string()) +} +``` + +### Step 2: Loop Body Update Logic (Task 172-3) + +**Location**: Pattern2 lowerer's latch generation (after `start = start + 1`) + +**Goal**: Update bool carrier for next iteration + +**Implementation**: +```rust +// In latch block generation (conceptually after line 267+) +if let Some(helper) = carrier_info.trim_helper() { + if helper.is_safe_trim() { + // ch_next = s.substring(start_next, start_next+1) + let ch_next = self.emit_method_call("substring", s_id, vec![start_next, start_next_plus_1])?; + + // is_ch_match_next = (ch_next == " " || ...) + let is_ch_match_next = emit_whitespace_check(self, ch_next, &helper.whitespace_chars)?; + + // Store for JoinIR latch → header PHI + // (Will be used in carrier update mapping) + } +} +``` + +**Note**: The actual MIR emission happens in **host space**, not JoinIR space. The JoinIR only sees the bool carrier as a loop parameter. + +### Step 3: JoinIR break Condition Replacement (Task 172-4) + +**Location**: `lower_loop_with_break_minimal` call at line 267 + +**Goal**: Replace break condition with `!is_ch_match` + +**Current Code**: +```rust +let (join_module, fragment_meta) = match lower_loop_with_break_minimal( + scope, + condition, // loop condition: start < end + &break_condition_node, // if condition: ch == " " || ... + &env, + &loop_var_name +) { ... } +``` + +**Modified Approach**: +```rust +// Phase 172: Trim pattern special route +if let Some(helper) = carrier_info.trim_helper() { + if helper.is_safe_trim() { + // Create negated carrier check: !is_ch_match + let carrier_var_node = ASTNode::Variable { + name: helper.carrier_name.clone(), // "is_ch_match" + span: Span::unknown(), + }; + + let negated_carrier_check = ASTNode::UnaryOp { + operator: UnaryOperator::Not, + operand: Box::new(carrier_var_node), + span: Span::unknown(), + }; + + // Use negated carrier as break condition + let (join_module, fragment_meta) = match lower_loop_with_break_minimal( + scope, + condition, // start < end (unchanged) + &negated_carrier_check, // !is_ch_match (REPLACED) + &env, + &loop_var_name + ) { ... } + + // Continue with normal merge flow... + } +} +``` + +**Critical Design Decision**: +- The break condition AST is replaced, but the loop condition (`start < end`) remains unchanged +- JoinIR sees bool carrier as just another loop parameter +- No new JoinIR instructions needed! + +### Step 4: Carrier Mapping Integration + +**Location**: Boundary setup (line 283-291) + +**Goal**: Ensure bool carrier is properly mapped between host and JoinIR + +**Current boundary setup**: +```rust +let mut boundary = JoinInlineBoundary::new_inputs_only( + vec![ValueId(0)], // JoinIR: loop param + vec![loop_var_id], // Host: "start" +); +boundary.condition_bindings = condition_bindings; +boundary.exit_bindings = exit_bindings.clone(); +``` + +**Enhanced with Trim carrier**: +```rust +// Phase 172: Add bool carrier to condition_bindings +if let Some(helper) = carrier_info.trim_helper() { + let carrier_host_id = self.variable_map.get(&helper.carrier_name) + .copied() + .ok_or_else(|| format!("Carrier '{}' not in variable_map", helper.carrier_name))?; + + let carrier_join_id = alloc_join_value(); // Allocate JoinIR-local ID + + env.insert(helper.carrier_name.clone(), carrier_join_id); + condition_bindings.push(ConditionBinding { + name: helper.carrier_name.clone(), + host_value: carrier_host_id, + join_value: carrier_join_id, + }); +} +``` + +## Implementation Constraints + +### What We DON'T Change + +1. **ExitLine Architecture**: No changes to ExitLineReconnector/ExitMetaCollector +2. **Header PHI Generation**: Existing LoopHeaderPhiBuilder handles bool carrier automatically +3. **JoinIR Instruction Set**: No new instructions added +4. **Pattern2 Basic Logic**: Core control flow unchanged + +### What We DO Change + +1. **Pre-loop initialization**: Add bool carrier setup +2. **Latch update logic**: Add bool carrier update +3. **Break condition AST**: Replace with `!is_ch_match` +4. **Condition bindings**: Add bool carrier mapping + +## Testing Strategy + +### E2E Test + +**Command**: +```bash +./target/release/hakorune local_tests/test_trim_main_pattern.hako +``` + +**Expected Behavior**: +- **Before**: Error message "✅ Trim pattern validation successful! ... JoinIR lowering: TODO" +- **After**: `Result: [hello]` and `PASS: Trimmed correctly` + +### Debug Traces + +**With JoinIR debug**: +```bash +NYASH_JOINIR_DEBUG=1 ./target/release/hakorune local_tests/test_trim_main_pattern.hako 2>&1 | grep -E "\[pattern2\]|\[trim\]" +``` + +**Expected log patterns**: +``` +[pattern2/promotion] LoopBodyLocal detected in condition scope +[pattern2/promoter] LoopBodyLocal 'ch' promoted to carrier 'is_ch_match' +[pattern2/trim] Safe Trim pattern detected, bypassing LoopBodyLocal restriction +[pattern2/trim] Carrier: 'is_ch_match', original var: 'ch', whitespace chars: [" ", "\t", "\n", "\r"] +[pattern2/trim] Emitting whitespace check initialization +[pattern2/trim] Replacing break condition with !is_ch_match +``` + +### MIR Validation + +**Dump MIR**: +```bash +./target/release/hakorune --dump-mir local_tests/test_trim_main_pattern.hako 2>&1 | less +``` + +**Check for**: +- Pre-loop: `substring()` call + OR chain comparison +- Loop header: PHI for `is_ch_match` +- Loop body: Conditional branch on `!is_ch_match` +- Loop latch: `substring()` call + OR chain update +- Exit block: Proper carrier value propagation + +## Success Criteria + +1. ✅ `test_trim_main_pattern.hako` executes without errors +2. ✅ Output: `Result: [hello]` (correctly trimmed) +3. ✅ Test result: `PASS: Trimmed correctly` +4. ✅ `cargo build --release` succeeds with 0 errors +5. ✅ `cargo test` all tests pass +6. ✅ No new warnings introduced +7. ✅ MIR dump shows proper SSA form + +## Implementation Status + +- [ ] Task 172-1: Design document (THIS FILE) +- [ ] Task 172-2: Loop pre-initialization +- [ ] Task 172-3: Loop body update logic +- [ ] Task 172-4: JoinIR break condition replacement +- [ ] Task 172-5: E2E test validation +- [ ] Task 172-6: Documentation update + +## Future Work (Out of Scope for Phase 172) + +### Pattern 4 Support +- Apply same Trim lowering to Pattern 4 (loop with continue) +- Reuse `emit_whitespace_check()` helper + +### JsonParser Integration +- Apply to all trim loops in JsonParser._trim +- Validate with JSON parsing smoke tests + +### P6+ Patterns +- Complex control flow with Trim-like patterns +- Multi-carrier bool promotion + +## Notes + +### Why This Design Works + +1. **Minimal Invasiveness**: Changes only affect Trim-specific paths +2. **Reusability**: `emit_whitespace_check()` can be shared with Pattern 4 +3. **Maintainability**: Clear separation between host MIR and JoinIR concerns +4. **Testability**: Each step can be validated independently + +### Phase 171 Validation Hook + +Current code (line 234-239) already validates Trim pattern and returns early: +```rust +return Err(format!( + "[cf_loop/pattern2] ✅ Trim pattern validation successful! \ + Carrier '{}' ready for Phase 172 implementation. \ + (Pattern detection: PASS, Safety check: PASS, JoinIR lowering: TODO)", + helper.carrier_name +)); +``` + +Phase 172 will **replace this early return** with actual lowering logic. + +## References + +- **Phase 171-C**: LoopBodyCarrierPromoter implementation +- **Phase 171-C-5**: TrimLoopHelper design +- **Pattern2 Lowerer**: `src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs` +- **Test File**: `local_tests/test_trim_main_pattern.hako` diff --git a/src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs b/src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs index 2b10b675..41ed582d 100644 --- a/src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs +++ b/src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs @@ -5,6 +5,99 @@ use crate::mir::builder::MirBuilder; use crate::mir::ValueId; use super::super::trace; +// Phase 172: Helper function for Trim pattern whitespace checking +/// Generate MIR for OR chain of whitespace character comparisons +/// +/// Creates: ch == " " || ch == "\t" || ch == "\n" || ch == "\r" ... +/// +/// # Arguments +/// +/// * `builder` - MirBuilder for emitting instructions +/// * `ch_value` - ValueId of character to check +/// * `whitespace_chars` - List of whitespace characters to compare against +/// +/// # Returns +/// +/// ValueId of bool result (true if ch matches any whitespace char) +fn emit_whitespace_check( + builder: &mut MirBuilder, + ch_value: ValueId, + whitespace_chars: &[String], +) -> Result { + use crate::mir::builder::emission::constant::emit_string; + use crate::mir::builder::emission::compare::emit_eq_to; + use crate::mir::types::BinaryOp; + use crate::mir::instruction::MirInstruction; + + if whitespace_chars.is_empty() { + return Err("[emit_whitespace_check] Empty whitespace_chars".to_string()); + } + + let mut result_opt: Option = None; + + for ws_char in whitespace_chars { + // ws_const = const " " (or "\t", etc.) + let ws_const = emit_string(builder, ws_char.clone()); + + // eq_check = ch == ws_const + let eq_dst = builder.value_gen.next(); + emit_eq_to(builder, eq_dst, ch_value, ws_const)?; + + result_opt = Some(if let Some(prev_result) = result_opt { + // result = prev_result || eq_check + let dst = builder.value_gen.next(); + builder.emit_instruction(MirInstruction::BinOp { + dst, + op: BinaryOp::Or, + lhs: prev_result, + rhs: eq_dst, + })?; + dst + } else { + // First comparison, no OR needed yet + eq_dst + }); + } + + result_opt.ok_or_else(|| { + "[emit_whitespace_check] Internal error: result should be Some".to_string() + }) +} + +// Phase 172-2: Extract substring arguments from Trim pattern +/// Extract the substring method call arguments from loop body +/// +/// Looks for pattern: local ch = s.substring(start, start+1) +/// +/// # Returns +/// +/// (object_name, start_expr) tuple if found +fn extract_substring_args(loop_body: &[ASTNode], var_name: &str) -> Option<(String, Box)> { + for stmt in loop_body { + // Look for: local ch = ... + if let ASTNode::Local { variables, initial_values, .. } = stmt { + for (i, var) in variables.iter().enumerate() { + if var == var_name { + if let Some(Some(init_expr)) = initial_values.get(i) { + // Check if it's a substring method call + if let ASTNode::MethodCall { object, method, arguments, .. } = init_expr.as_ref() { + if method == "substring" && arguments.len() == 2 { + // Extract object name + if let ASTNode::Variable { name, .. } = object.as_ref() { + // Return object name and start expression + // (We assume second arg is start+1, first arg is start) + return Some((name.clone(), Box::new(arguments[0].clone()))); + } + } + } + } + } + } + } + } + None +} + /// Phase 194: Detection function for Pattern 2 /// /// Phase 192: Updated to structure-based detection @@ -221,32 +314,90 @@ impl MirBuilder { carrier_info.carrier_count() ); - // Phase 171-impl-Trim: Check if this is a safe Trim pattern - if let Some(helper) = carrier_info.trim_helper() { - if helper.is_safe_trim() { - eprintln!("[pattern2/trim] Safe Trim pattern detected, bypassing LoopBodyLocal restriction"); - eprintln!("[pattern2/trim] Carrier: '{}', original var: '{}', whitespace chars: {:?}", - helper.carrier_name, helper.original_var, helper.whitespace_chars); + // Phase 172: Implement Trim pattern lowering + // Clone helper data to avoid borrow conflicts + let trim_helper_data = carrier_info.trim_helper().map(|h| { + (h.carrier_name.clone(), h.original_var.clone(), h.whitespace_chars.clone(), h.is_safe_trim()) + }); - // Phase 171-impl-Trim: Validation successful! - // Phase 172+ will implement the actual JoinIR generation for Trim patterns - // For now, return an informative message that the pattern is recognized but not yet lowered - return Err(format!( - "[cf_loop/pattern2] ✅ Trim pattern validation successful! \ - Carrier '{}' ready for Phase 172 implementation. \ - (Pattern detection: PASS, Safety check: PASS, JoinIR lowering: TODO)", - helper.carrier_name - )); + if let Some((carrier_name, original_var, whitespace_chars, is_safe)) = trim_helper_data { + if is_safe { + eprintln!("[pattern2/trim] Safe Trim pattern detected, implementing lowering"); + eprintln!("[pattern2/trim] Carrier: '{}', original var: '{}', whitespace chars: {:?}", + carrier_name, original_var, whitespace_chars); + + // Phase 172-2: Extract substring pattern and generate initial check + let (s_name, start_expr) = extract_substring_args(_body, &original_var) + .ok_or_else(|| { + format!( + "[cf_loop/pattern2] Failed to extract substring pattern for Trim carrier '{}'", + carrier_name + ) + })?; + + eprintln!("[pattern2/trim] Extracted substring pattern: s='{}', start={:?}", s_name, start_expr); + + // Get ValueIds for string and start + let s_id = self.variable_map.get(&s_name) + .copied() + .ok_or_else(|| format!("[pattern2/trim] String variable '{}' not found", s_name))?; + + // Compile start expression to get ValueId + let start_id = self.build_expression_impl(*start_expr)?; + + // Generate: start + 1 + use crate::mir::builder::emission::constant::emit_integer; + use crate::mir::types::BinaryOp; + use crate::mir::instruction::MirInstruction; + let one = emit_integer(self, 1); + let start_plus_1 = self.value_gen.next(); + self.emit_instruction(MirInstruction::BinOp { + dst: start_plus_1, + op: BinaryOp::Add, + lhs: start_id, + rhs: one, + })?; + + // Generate: ch0 = s.substring(start, start+1) + let ch0 = self.value_gen.next(); + self.emit_method_call( + Some(ch0), + s_id, + "substring".to_string(), + vec![start_id, start_plus_1], + )?; + + eprintln!("[pattern2/trim] Generated initial substring call: ch0 = {:?}", ch0); + + // Generate: is_ch_match0 = (ch0 == " " || ch0 == "\t" || ...) + let is_ch_match0 = emit_whitespace_check(self, ch0, &whitespace_chars)?; + + eprintln!("[pattern2/trim] Generated initial whitespace check: is_ch_match0 = {:?}", is_ch_match0); + + // Register carrier in variable_map + self.variable_map.insert(carrier_name.clone(), is_ch_match0); + + eprintln!("[pattern2/trim] Registered carrier '{}' in variable_map", carrier_name); + + // Update carrier_info with actual ValueId + carrier_info.loop_var_id = is_ch_match0; + carrier_info.loop_var_name = carrier_name.clone(); + + eprintln!("[pattern2/trim] Updated carrier_info: loop_var='{}', loop_var_id={:?}", + carrier_info.loop_var_name, carrier_info.loop_var_id); + + // Phase 172-4: Break condition will be replaced below after JoinIR generation + eprintln!("[pattern2/trim] Trim pattern lowering enabled, proceeding to JoinIR generation"); } else { return Err(format!( "[cf_loop/pattern2] Trim pattern detected but not safe: carrier='{}', whitespace_count={}", - helper.carrier_name, - helper.whitespace_count() + carrier_name, + whitespace_chars.len() )); } - } else { + } else if carrier_info.trim_helper().is_some() { return Err(format!( - "[cf_loop/pattern2] Promoted but no TrimLoopHelper attached (carrier: '{}')", + "[cf_loop/pattern2] Promoted but TrimLoopHelper check failed (carrier: '{}')", trim_info.carrier_name )); } @@ -262,9 +413,52 @@ impl MirBuilder { } } + // Phase 172-4: Replace break condition for Trim pattern and add carrier to ConditionEnv + let effective_break_condition = if let Some(helper) = carrier_info.trim_helper() { + if helper.is_safe_trim() { + // Add carrier to ConditionEnv + let carrier_host_id = self.variable_map.get(&helper.carrier_name) + .copied() + .ok_or_else(|| format!("[pattern2/trim] Carrier '{}' not in variable_map", helper.carrier_name))?; + + let carrier_join_id = alloc_join_value(); // Allocate JoinIR-local ValueId + + env.insert(helper.carrier_name.clone(), carrier_join_id); + condition_bindings.push(ConditionBinding { + name: helper.carrier_name.clone(), + host_value: carrier_host_id, + join_value: carrier_join_id, + }); + + eprintln!("[pattern2/trim] Added carrier '{}' to ConditionEnv: HOST {:?} → JoinIR {:?}", + helper.carrier_name, carrier_host_id, carrier_join_id); + + // Create negated carrier check: !is_ch_match + use crate::ast::{Span, UnaryOperator}; + + let carrier_var_node = ASTNode::Variable { + name: helper.carrier_name.clone(), + span: Span::unknown(), + }; + + let negated_carrier = ASTNode::UnaryOp { + operator: UnaryOperator::Not, + operand: Box::new(carrier_var_node), + span: Span::unknown(), + }; + + eprintln!("[pattern2/trim] Replaced break condition with !{}", helper.carrier_name); + negated_carrier + } else { + break_condition_node.clone() + } + } else { + break_condition_node.clone() + }; + // Phase 169 / Phase 171-fix / Phase 172-3 / Phase 170-B: Call Pattern 2 lowerer with break_condition // Phase 33-14: Now returns (JoinModule, JoinFragmentMeta) for expr_result + carrier separation - let (join_module, fragment_meta) = match lower_loop_with_break_minimal(scope, condition, &break_condition_node, &env, &loop_var_name) { + let (join_module, fragment_meta) = match lower_loop_with_break_minimal(scope, condition, &effective_break_condition, &env, &loop_var_name) { Ok((module, meta)) => (module, meta), Err(e) => { // Phase 195: Use unified trace