diff --git a/CURRENT_TASK.md b/CURRENT_TASK.md index 950f0b42..05b80141 100644 --- a/CURRENT_TASK.md +++ b/CURRENT_TASK.md @@ -28,8 +28,17 @@ - [x] Phase 171‑C-2: Trim パターン昇格ロジック実装 ✅ → `find_definition_in_body()`, `is_substring_method_call()`, `extract_equality_literals()` 実装。 → `TrimPatternInfo` で検出結果を返す。10 unit tests。 - - [ ] Phase 171‑C-3: Pattern 2/4 ルーティングとの統合 + - [x] Phase 171‑C-3: Pattern 2/4 ルーティングとの統合 ✅ → routing.rs で LoopBodyCarrierPromoter を呼び出し、昇格可能なら Pattern2 へルート。 + - [x] Phase 171‑C-4: CarrierInfo への統合 ✅ (2025-12-07) + → `CarrierInfo::merge_from()` で昇格後のキャリアをマージ。 + → `TrimPatternInfo::to_carrier_info()` で変換ヘルパ実装。 + → Pattern2/4 lowerer で `Promoted` ブランチをマージに更新。7 unit tests。 + - [x] Phase 171‑C-5: TrimLoopHelper 設計 ✅ (2025-12-07) + → `TrimLoopHelper` struct で Trim 専用ロジックを一箇所に集約。 + → `CarrierInfo::trim_helper()` アクセサ追加。4 unit tests。 + - [ ] Phase 171‑impl-Trim: Trim 用 JoinIR 生成 + → `CarrierInfo::trim_helper()` を使って bool carrier の初期化・更新・exit PHI を生成。 --- diff --git a/docs/development/current/main/phase171-pattern5-loop-inventory.md b/docs/development/current/main/phase171-pattern5-loop-inventory.md index 92a1746e..8c8801fd 100644 --- a/docs/development/current/main/phase171-pattern5-loop-inventory.md +++ b/docs/development/current/main/phase171-pattern5-loop-inventory.md @@ -254,4 +254,171 @@ loop(start < end && is_whitespace) { - `is_substring_method_call()`: Detects `substring()` method calls - `extract_equality_literals()`: Extracts string literals from OR chains - `TrimPatternInfo`: Captures pattern details for carrier promotion -- Phase 171-C-3: ⏳ Integration with Pattern 2/4 routing +- Phase 171-C-3: ✅ Integration with Pattern 2/4 routing complete +- Phase 171-C-4: ✅ CarrierInfo integration complete (2025-12-07) + - `CarrierInfo::merge_from()`: Deduplicated carrier merging with deterministic sorting + - `TrimPatternInfo::to_carrier_info()`: Conversion to CarrierInfo with TrimLoopHelper + - Pattern 2/4 lowerers: Promoted carrier merging in `Promoted` branch + - 7 unit tests: Merge success/failure/duplication/determinism validation +- Phase 171-C-5: ✅ TrimLoopHelper design complete (2025-12-07) + - `TrimLoopHelper` struct: Encapsulates Trim pattern lowering logic + - `CarrierInfo::trim_helper()`: Accessor for pattern-specific helper + - Module export: `mod.rs` updated with `pub use TrimLoopHelper` + - 4 unit tests: Helper creation and accessor validation + +--- + +## Phase 171-C-3/4/5: Responsibility Positions and Data Flow + +### Responsibility Separation Principle + +The promotion system follows Box Theory's single responsibility principle: + +1. **router.rs**: Pattern table + `can_lower()`/`lower()` call abstraction (no Scope/condition logic) +2. **Pattern 2/4 lowerer**: Holds LoopScope / ConditionScope / CarrierInfo / Promoter +3. **LoopBodyCarrierPromoter**: LoopBodyLocal handling specialist box +4. **TrimLoopHelper**: Trim pattern-specific helper (future extensibility) + +### Data Flow Diagram + +``` +LoopConditionScopeBox::analyze() + ↓ + has_loop_body_local()? + ↓ true +LoopBodyCarrierPromoter::try_promote() + ↓ Promoted { trim_info } + TrimPatternInfo::to_carrier_info() + ↓ + CarrierInfo::merge_from() + ↓ + TrimLoopHelper (attached to CarrierInfo) + ↓ + Pattern 2/4 lowerer (JoinIR generation) +``` + +### Implementation Locations + +**Phase 171-C-4 Changes**: +- `src/mir/join_ir/lowering/carrier_info.rs`: Added `merge_from()`, `trim_helper()`, `trim_helper` field +- `src/mir/loop_pattern_detection/loop_body_carrier_promoter.rs`: Updated `to_carrier_info()` to attach TrimLoopHelper +- `src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs`: Promoted branch now merges carriers +- `src/mir/builder/control_flow/joinir/patterns/pattern4_with_continue.rs`: Promoted branch now merges carriers + +**Phase 171-C-5 Changes**: +- `src/mir/loop_pattern_detection/trim_loop_helper.rs`: NEW - TrimLoopHelper struct with 4 unit tests +- `src/mir/loop_pattern_detection/mod.rs`: Export TrimLoopHelper module + +--- + +## Phase 171-impl-Trim: Trim 特例の実戦投入 + +**Date**: 2025-12-08 +**Status**: ✅ Validation complete +**Purpose**: Safely integrate Trim pattern detection into JoinIR pipeline with validation-only implementation + +### 設計原則 + +> **「LoopBodyLocal を全面解禁」ではなく、Trim パターンだけを箱経由でホワイトリストに載せる** + +### 安全機構 + +1. `TrimLoopHelper::is_safe_trim()` - 構造的に安全か判定 +2. `TrimLoopHelper::is_trim_like()` - Trim パターンに合致するか判定 +3. `TrimLoopHelper::has_valid_structure()` - 構造チェック + +### データフロー(Trim 特例) + +``` +LoopConditionScopeBox::analyze() + ↓ + has_loop_body_local() == true + ↓ +LoopBodyCarrierPromoter::try_promote() + ↓ Promoted { trim_info } + TrimPatternInfo::to_carrier_info() + ↓ + CarrierInfo::merge_from() + ↓ + carrier_info.trim_helper()?.is_safe_trim() + ↓ true + ✅ Validation Success (TODO: JoinIR lowering in Phase 172) +``` + +### 実装状況 + +- [x] Phase 171-impl-Trim-1: 受け入れ条件を 1 箇所に ✅ + - `TrimLoopHelper::is_safe_trim()` implemented + - `Pattern2/4` で Trim 特例ルート実装 + - Fail-Fast on unsafe patterns +- [x] Phase 171-impl-Trim-2: TrimLoopHelper 判定メソッド ✅ + - `is_trim_like()`, `has_valid_structure()` implemented + - 4+ ユニットテスト追加 (9 tests total, all passing) +- [x] Phase 171-impl-Trim-3: E2E テスト ✅ + - `local_tests/test_trim_main_pattern.hako` validated + - 出力: `[pattern2/trim] Safe Trim pattern detected` + - 出力: `✅ Trim pattern validation successful!` + - Status: Validation-only (JoinIR lowering deferred to Phase 172) +- [x] Phase 171-impl-Trim-4: ドキュメント更新 ✅ + - `phase171-pattern5-loop-inventory.md` updated + - `CURRENT_TASK.md` status tracking + +### 実装詳細 + +**ファイル変更**: +1. `src/mir/loop_pattern_detection/trim_loop_helper.rs` + - `is_safe_trim()`, `is_trim_like()`, `has_valid_structure()` methods + - 4 new unit tests for safety validation + +2. `src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs` + - Trim exception route with safety check + - `body_locals` extraction from loop body AST + - Validation message for successful detection + +3. `src/mir/builder/control_flow/joinir/patterns/pattern4_with_continue.rs` + - Same Trim exception logic as Pattern2 + - `body_locals` extraction from normalized loop body + +4. `src/mir/loop_pattern_detection/loop_body_carrier_promoter.rs` + - `ASTNode::Local { variables, initial_values, .. }` handler + - Support for `local ch = expr` pattern recognition + +### テスト結果 + +**ユニットテスト**: ✅ 9/9 passing +- `test_is_safe_trim` +- `test_is_safe_trim_empty_carrier` +- `test_is_safe_trim_no_whitespace` +- `test_has_valid_structure` +- (+ 5 existing tests) + +**E2E テスト**: ✅ Validation successful +``` +[pattern2/check] Analyzing condition scope: 3 variables +[pattern2/check] 'ch': LoopBodyLocal +[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: ["\r", "\n", "\t", " "] +✅ Trim pattern validation successful! Carrier 'is_ch_match' ready for Phase 172 implementation. +(Pattern detection: PASS, Safety check: PASS, JoinIR lowering: TODO) +``` + +**ビルド結果**: ✅ Success +- `cargo build --release`: Success +- `cargo test --lib trim_loop_helper`: 9/9 passing + +### 重要な発見 + +1. **AST構造の理解**: `local ch = expr` は `ASTNode::Local { variables, initial_values, .. }` として表現される +2. **body_locals 抽出**: Pattern2/4 で `LoopScopeShape.body_locals` を AST から抽出する必要があった +3. **段階的実装**: Validation-only approach で安全性を先に確立し、JoinIR lowering は Phase 172 に分離 + +### 次のステップ (Phase 172) + +- [ ] Phase 172-1: Trim pattern JoinIR generation + - Carrier initialization code + - Carrier update logic (substring + OR chain → bool) + - Exit PHI mapping +- [ ] Phase 172-2: JsonParser loops への展開 + - Similar pattern recognition + - Generalized carrier promotion diff --git a/src/mir/builder/control_flow/joinir/patterns/common_init.rs b/src/mir/builder/control_flow/joinir/patterns/common_init.rs index b29b1a73..40269bb5 100644 --- a/src/mir/builder/control_flow/joinir/patterns/common_init.rs +++ b/src/mir/builder/control_flow/joinir/patterns/common_init.rs @@ -110,6 +110,7 @@ impl CommonPatternInitializer { loop_var_name: loop_var_name.clone(), loop_var_id, carriers, + trim_helper: None, // Phase 171-C-5: No Trim pattern by default }; Ok((loop_var_name, loop_var_id, carrier_info)) 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 3e5cb21a..2b10b675 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 @@ -53,8 +53,9 @@ impl MirBuilder { trace::trace().debug("pattern2", "Calling Pattern 2 minimal lowerer"); // Phase 33-22: Use CommonPatternInitializer for loop variable extraction + // Phase 171-C-4: carrier_info is now mutable for promotion merging use super::common_init::CommonPatternInitializer; - let (loop_var_name, loop_var_id, _carrier_info) = + let (loop_var_name, loop_var_id, mut carrier_info) = CommonPatternInitializer::initialize_pattern( self, condition, @@ -122,8 +123,19 @@ impl MirBuilder { } // Create a minimal LoopScopeShape (Phase 188: hardcoded for joinir_min_loop.hako) - // Pattern 2 lowerer ignores the scope anyway, so this is just a placeholder + // Phase 171-impl-Trim: Extract body_locals from loop body AST for proper variable classification use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape; + let mut body_locals = BTreeSet::new(); + + // Extract local variable declarations from loop body + for stmt in _body { + if let ASTNode::Local { variables, .. } = stmt { + for var_name in variables { + body_locals.insert(var_name.clone()); + } + } + } + let scope = LoopScopeShape { header: BasicBlockId(0), body: BasicBlockId(0), @@ -131,7 +143,7 @@ impl MirBuilder { exit: BasicBlockId(0), pinned: BTreeSet::new(), carriers: BTreeSet::new(), - body_locals: BTreeSet::new(), + body_locals, // Phase 171-impl-Trim: Now populated from AST exit_live: BTreeSet::new(), progress_carrier: None, variable_definitions: BTreeMap::new(), @@ -175,7 +187,15 @@ impl MirBuilder { Some(&scope), ); + eprintln!("[pattern2/check] Analyzing condition scope: {} variables", cond_scope.vars.len()); + for var in &cond_scope.vars { + eprintln!("[pattern2/check] '{}': {:?}", var.name, var.scope); + } + eprintln!("[pattern2/check] has_loop_body_local() = {}", cond_scope.has_loop_body_local()); + if cond_scope.has_loop_body_local() { + eprintln!("[pattern2/promotion] LoopBodyLocal detected in condition scope"); + // Phase 171-C-3: Try promotion let request = PromotionRequest { scope: &scope, @@ -190,8 +210,46 @@ impl MirBuilder { "[pattern2/promoter] LoopBodyLocal '{}' promoted to carrier '{}'", trim_info.var_name, trim_info.carrier_name ); - // Phase 171-C-3: Detection only - CarrierInfo merge is future work - // For now, we just log successful detection and continue normal flow + + // Phase 171-C-4: Convert to CarrierInfo and merge + let promoted_carrier = trim_info.to_carrier_info(); + carrier_info.merge_from(&promoted_carrier); + + eprintln!( + "[pattern2/promoter] Phase 171-C-4: Merged carrier '{}' into CarrierInfo (total carriers: {})", + trim_info.carrier_name, + 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 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 + )); + } else { + return Err(format!( + "[cf_loop/pattern2] Trim pattern detected but not safe: carrier='{}', whitespace_count={}", + helper.carrier_name, + helper.whitespace_count() + )); + } + } else { + return Err(format!( + "[cf_loop/pattern2] Promoted but no TrimLoopHelper attached (carrier: '{}')", + trim_info.carrier_name + )); + } } PromotionResult::CannotPromote { reason, vars } => { // Phase 171-C-3: Fail-Fast on promotion failure diff --git a/src/mir/builder/control_flow/joinir/patterns/pattern4_with_continue.rs b/src/mir/builder/control_flow/joinir/patterns/pattern4_with_continue.rs index 5b36bfef..94f1cdfb 100644 --- a/src/mir/builder/control_flow/joinir/patterns/pattern4_with_continue.rs +++ b/src/mir/builder/control_flow/joinir/patterns/pattern4_with_continue.rs @@ -158,6 +158,7 @@ impl MirBuilder { // Phase 33-19: Build CarrierInfo with ONLY updated variables as carriers // This prevents constant variables (like M, args) from being treated as carriers + // Phase 171-C-4: carrier_info is now mutable for promotion merging let mut carriers = Vec::new(); for temp_carrier in &temp_carriers { if carrier_updates.contains_key(&temp_carrier.name) { @@ -165,10 +166,11 @@ impl MirBuilder { } } - let carrier_info = CarrierInfo { + let mut carrier_info = CarrierInfo { loop_var_name: loop_var_name.clone(), loop_var_id, carriers: carriers.clone(), + trim_helper: None, // Phase 171-C-5: No Trim pattern by default }; trace::trace().debug( @@ -195,8 +197,19 @@ impl MirBuilder { trace::trace().varmap("pattern4_start", &self.variable_map); // Create a minimal LoopScopeShape (Phase 195: hardcoded for loop_continue_pattern4.hako) - // Pattern 4 lowerer ignores the scope anyway, so this is just a placeholder + // Phase 171-impl-Trim: Extract body_locals from loop body AST for proper variable classification use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape; + let mut body_locals = BTreeSet::new(); + + // Extract local variable declarations from loop body + for stmt in body_to_analyze { + if let ASTNode::Local { variables, .. } = stmt { + for var_name in variables { + body_locals.insert(var_name.clone()); + } + } + } + let scope = LoopScopeShape { header: BasicBlockId(0), body: BasicBlockId(0), @@ -204,7 +217,7 @@ impl MirBuilder { exit: BasicBlockId(0), pinned: BTreeSet::new(), carriers: BTreeSet::new(), - body_locals: BTreeSet::new(), + body_locals, // Phase 171-impl-Trim: Now populated from AST exit_live: BTreeSet::new(), progress_carrier: None, variable_definitions: BTreeMap::new(), @@ -241,8 +254,46 @@ impl MirBuilder { "[pattern4/promoter] LoopBodyLocal '{}' promoted to carrier '{}'", trim_info.var_name, trim_info.carrier_name ); - // Phase 171-C-3: Detection only - CarrierInfo merge is future work - // For now, we just log successful detection and continue normal flow + + // Phase 171-C-4: Convert to CarrierInfo and merge + let promoted_carrier = trim_info.to_carrier_info(); + carrier_info.merge_from(&promoted_carrier); + + eprintln!( + "[pattern4/promoter] Phase 171-C-4: Merged carrier '{}' into CarrierInfo (total carriers: {})", + trim_info.carrier_name, + 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!("[pattern4/trim] Safe Trim pattern detected, bypassing LoopBodyLocal restriction"); + eprintln!("[pattern4/trim] Carrier: '{}', original var: '{}', whitespace chars: {:?}", + helper.carrier_name, helper.original_var, helper.whitespace_chars); + + // 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/pattern4] ✅ Trim pattern validation successful! \ + Carrier '{}' ready for Phase 172 implementation. \ + (Pattern detection: PASS, Safety check: PASS, JoinIR lowering: TODO)", + helper.carrier_name + )); + } else { + return Err(format!( + "[cf_loop/pattern4] Trim pattern detected but not safe: carrier='{}', whitespace_count={}", + helper.carrier_name, + helper.whitespace_count() + )); + } + } else { + return Err(format!( + "[cf_loop/pattern4] Promoted but no TrimLoopHelper attached (carrier: '{}')", + trim_info.carrier_name + )); + } } PromotionResult::CannotPromote { reason, vars } => { // Phase 171-C-3: Fail-Fast on promotion failure diff --git a/src/mir/join_ir/lowering/carrier_info.rs b/src/mir/join_ir/lowering/carrier_info.rs index ad6ca8b1..3e32a390 100644 --- a/src/mir/join_ir/lowering/carrier_info.rs +++ b/src/mir/join_ir/lowering/carrier_info.rs @@ -27,6 +27,8 @@ pub struct CarrierInfo { pub loop_var_id: ValueId, /// Additional carrier variables (e.g., sum, printed) pub carriers: Vec, + /// Phase 171-C-5: Trim pattern helper (if this CarrierInfo was created from Trim promotion) + pub trim_helper: Option, } impl CarrierInfo { @@ -85,6 +87,7 @@ impl CarrierInfo { loop_var_name, loop_var_id, carriers, + trim_helper: None, // Phase 171-C-5: No Trim pattern by default }) } @@ -140,6 +143,7 @@ impl CarrierInfo { loop_var_name, loop_var_id, carriers, + trim_helper: None, // Phase 171-C-5: No Trim pattern by default }) } @@ -165,6 +169,7 @@ impl CarrierInfo { loop_var_name, loop_var_id, carriers, + trim_helper: None, // Phase 171-C-5: No Trim pattern by default } } @@ -188,6 +193,58 @@ impl CarrierInfo { pub fn find_carrier(&self, name: &str) -> Option<&CarrierVar> { self.carriers.iter().find(|c| c.name == name) } + + /// Phase 171-C-4: Merge carriers from another CarrierInfo + /// + /// Deduplicates by carrier name. If a carrier with the same name already exists, + /// it will not be added again. + /// + /// # Arguments + /// + /// * `other` - Another CarrierInfo to merge from + /// + /// # Example + /// + /// ```ignore + /// let mut carrier_info = CarrierInfo::from_variable_map("i", &variable_map)?; + /// let promoted_carrier = TrimPatternInfo::to_carrier_info(); + /// carrier_info.merge_from(&promoted_carrier); + /// ``` + pub fn merge_from(&mut self, other: &CarrierInfo) { + for carrier in &other.carriers { + if !self.carriers.iter().any(|c| c.name == carrier.name) { + self.carriers.push(carrier.clone()); + } + } + // Maintain sorted order for determinism + self.carriers.sort_by(|a, b| a.name.cmp(&b.name)); + + // Phase 171-C-5: Also merge trim_helper if present + if other.trim_helper.is_some() { + self.trim_helper = other.trim_helper.clone(); + } + } + + /// Phase 171-C-5: Get Trim pattern helper + /// + /// Returns the TrimLoopHelper if this CarrierInfo was created from Trim promotion. + /// + /// # Returns + /// + /// * `Some(&TrimLoopHelper)` - If this CarrierInfo contains Trim pattern information + /// * `None` - If this is a regular CarrierInfo (not from Trim promotion) + /// + /// # Example + /// + /// ```ignore + /// if let Some(helper) = carrier_info.trim_helper() { + /// eprintln!("Trim pattern detected: {}", helper.carrier_name); + /// eprintln!("Whitespace chars: {:?}", helper.whitespace_chars); + /// } + /// ``` + pub fn trim_helper(&self) -> Option<&crate::mir::loop_pattern_detection::trim_loop_helper::TrimLoopHelper> { + self.trim_helper.as_ref() + } } /// Exit metadata returned by lowerers @@ -365,3 +422,194 @@ impl ExitMeta { self } } + +#[cfg(test)] +mod tests { + use super::*; + + // Helper: Create a CarrierVar for testing + fn test_carrier(name: &str, id: u32) -> CarrierVar { + CarrierVar { + name: name.to_string(), + host_id: ValueId(id), + } + } + + // Helper: Create a CarrierInfo for testing + fn test_carrier_info(loop_var: &str, loop_id: u32, carriers: Vec) -> CarrierInfo { + CarrierInfo::with_carriers( + loop_var.to_string(), + ValueId(loop_id), + carriers, + ) + } + + #[test] + fn test_merge_from_empty() { + // Merge empty CarrierInfo should not change anything + let mut carrier_info = test_carrier_info( + "i", + 5, + vec![test_carrier("sum", 10)], + ); + + let other = test_carrier_info("j", 20, vec![]); + + carrier_info.merge_from(&other); + + assert_eq!(carrier_info.carrier_count(), 1); + assert_eq!(carrier_info.carriers[0].name, "sum"); + } + + #[test] + fn test_merge_from_new_carrier() { + // Merge a new carrier that doesn't exist yet + let mut carrier_info = test_carrier_info( + "i", + 5, + vec![test_carrier("sum", 10)], + ); + + let other = test_carrier_info( + "j", + 20, + vec![test_carrier("count", 15)], + ); + + carrier_info.merge_from(&other); + + assert_eq!(carrier_info.carrier_count(), 2); + // Should be sorted by name + assert_eq!(carrier_info.carriers[0].name, "count"); // 'c' < 's' + assert_eq!(carrier_info.carriers[1].name, "sum"); + } + + #[test] + fn test_merge_from_duplicate_carrier() { + // Merge a carrier with the same name should NOT duplicate + let mut carrier_info = test_carrier_info( + "i", + 5, + vec![test_carrier("sum", 10)], + ); + + let other = test_carrier_info( + "j", + 20, + vec![test_carrier("sum", 999)], // Same name, different ID + ); + + carrier_info.merge_from(&other); + + // Should still have only 1 carrier (no duplication) + assert_eq!(carrier_info.carrier_count(), 1); + assert_eq!(carrier_info.carriers[0].name, "sum"); + // Original ID should be preserved + assert_eq!(carrier_info.carriers[0].host_id, ValueId(10)); + } + + #[test] + fn test_merge_from_multiple_carriers() { + // Merge multiple carriers + let mut carrier_info = test_carrier_info( + "i", + 5, + vec![test_carrier("sum", 10)], + ); + + let other = test_carrier_info( + "j", + 20, + vec![ + test_carrier("count", 15), + test_carrier("product", 18), + ], + ); + + carrier_info.merge_from(&other); + + assert_eq!(carrier_info.carrier_count(), 3); + // Should be sorted by name + assert_eq!(carrier_info.carriers[0].name, "count"); + assert_eq!(carrier_info.carriers[1].name, "product"); + assert_eq!(carrier_info.carriers[2].name, "sum"); + } + + #[test] + fn test_merge_from_preserves_determinism() { + // Test that merge maintains sorted order + let mut carrier_info = test_carrier_info( + "i", + 5, + vec![ + test_carrier("zebra", 30), + test_carrier("alpha", 10), + ], + ); + + let other = test_carrier_info( + "j", + 20, + vec![ + test_carrier("beta", 15), + test_carrier("gamma", 18), + ], + ); + + carrier_info.merge_from(&other); + + assert_eq!(carrier_info.carrier_count(), 4); + // Should be sorted alphabetically + assert_eq!(carrier_info.carriers[0].name, "alpha"); + assert_eq!(carrier_info.carriers[1].name, "beta"); + assert_eq!(carrier_info.carriers[2].name, "gamma"); + assert_eq!(carrier_info.carriers[3].name, "zebra"); + } + + #[test] + fn test_merge_from_with_trim_helper() { + // Test that trim_helper is merged + use crate::mir::loop_pattern_detection::trim_loop_helper::TrimLoopHelper; + + let mut carrier_info = test_carrier_info("i", 5, vec![]); + + let mut other = test_carrier_info("j", 20, vec![]); + other.trim_helper = Some(TrimLoopHelper { + original_var: "ch".to_string(), + carrier_name: "is_whitespace".to_string(), + whitespace_chars: vec![" ".to_string(), "\t".to_string()], + }); + + carrier_info.merge_from(&other); + + // trim_helper should be copied + assert!(carrier_info.trim_helper.is_some()); + let helper = carrier_info.trim_helper.as_ref().unwrap(); + assert_eq!(helper.original_var, "ch"); + assert_eq!(helper.carrier_name, "is_whitespace"); + assert_eq!(helper.whitespace_count(), 2); + } + + #[test] + fn test_trim_helper_accessor() { + // Test the trim_helper() accessor method + use crate::mir::loop_pattern_detection::trim_loop_helper::TrimLoopHelper; + + let mut carrier_info = test_carrier_info("i", 5, vec![]); + + // Initially None + assert!(carrier_info.trim_helper().is_none()); + + // Add trim_helper + carrier_info.trim_helper = Some(TrimLoopHelper { + original_var: "ch".to_string(), + carrier_name: "is_whitespace".to_string(), + whitespace_chars: vec![" ".to_string()], + }); + + // Now Some + assert!(carrier_info.trim_helper().is_some()); + let helper = carrier_info.trim_helper().unwrap(); + assert_eq!(helper.original_var, "ch"); + } +} diff --git a/src/mir/loop_pattern_detection/loop_body_carrier_promoter.rs b/src/mir/loop_pattern_detection/loop_body_carrier_promoter.rs index 26883999..5e3b5a46 100644 --- a/src/mir/loop_pattern_detection/loop_body_carrier_promoter.rs +++ b/src/mir/loop_pattern_detection/loop_body_carrier_promoter.rs @@ -47,6 +47,45 @@ pub struct TrimPatternInfo { pub carrier_name: String, } +impl TrimPatternInfo { + /// Phase 171-C-4: Convert to CarrierInfo with a bool carrier for the pattern + /// + /// Creates a CarrierInfo containing a single bool carrier representing + /// the Trim pattern match condition (e.g., "is_whitespace"). + /// + /// # Returns + /// + /// CarrierInfo with: + /// - loop_var_name: The promoted carrier name (e.g., "is_ch_match") + /// - loop_var_id: Placeholder ValueId(0) (will be remapped by JoinInlineBoundary) + /// - carriers: Empty (the carrier itself is the loop variable) + /// + /// # Design Note + /// + /// The returned CarrierInfo uses a placeholder ValueId(0) because: + /// - This is JoinIR-local ID space (not host ValueId space) + /// - The actual host ValueId will be assigned during merge_joinir_mir_blocks + /// - JoinInlineBoundary will handle the boundary mapping + pub fn to_carrier_info(&self) -> crate::mir::join_ir::lowering::carrier_info::CarrierInfo { + use crate::mir::join_ir::lowering::carrier_info::CarrierInfo; + use crate::mir::ValueId; + use super::trim_loop_helper::TrimLoopHelper; + + // Phase 171-C-4/5: Create CarrierInfo with promoted carrier as loop variable + // and attach TrimLoopHelper for future lowering + let mut carrier_info = CarrierInfo::with_carriers( + self.carrier_name.clone(), // "is_ch_match" becomes the loop variable + ValueId(0), // Placeholder (will be remapped) + vec![], // No additional carriers + ); + + // Phase 171-C-5: Attach TrimLoopHelper for pattern-specific lowering logic + carrier_info.trim_helper = Some(TrimLoopHelper::from_pattern_info(self)); + + carrier_info + } +} + /// 昇格結果 pub enum PromotionResult { /// 昇格成功: Trim パターン情報を返す @@ -204,6 +243,18 @@ impl LoopBodyCarrierPromoter { } } + // Phase 171-impl-Trim: Handle Local with initial values + // local ch = s.substring(...) + ASTNode::Local { variables, initial_values, .. } if initial_values.len() == variables.len() => { + for (i, var) in variables.iter().enumerate() { + if var == var_name { + if let Some(Some(init_expr)) = initial_values.get(i) { + return Some(init_expr.as_ref()); + } + } + } + } + // その他のノードは無視 _ => {} } diff --git a/src/mir/loop_pattern_detection/mod.rs b/src/mir/loop_pattern_detection/mod.rs index 4d05e665..4b600236 100644 --- a/src/mir/loop_pattern_detection/mod.rs +++ b/src/mir/loop_pattern_detection/mod.rs @@ -761,3 +761,7 @@ pub mod error_messages; // Phase 171-C: LoopBodyLocal Carrier Promotion pub mod loop_body_carrier_promoter; + +// Phase 171-C-5: Trim Pattern Helper +pub mod trim_loop_helper; +pub use trim_loop_helper::TrimLoopHelper; diff --git a/src/mir/loop_pattern_detection/trim_loop_helper.rs b/src/mir/loop_pattern_detection/trim_loop_helper.rs new file mode 100644 index 00000000..ab0564c8 --- /dev/null +++ b/src/mir/loop_pattern_detection/trim_loop_helper.rs @@ -0,0 +1,338 @@ +//! Phase 171-C-5: TrimLoopHelper - Trim Pattern Lowering Helper +//! +//! This module provides a helper struct for Trim pattern lowering. +//! It encapsulates pattern-specific logic for converting LoopBodyLocal-based +//! conditions to carrier-based conditions. +//! +//! ## Purpose +//! +//! When a Trim pattern is detected (e.g., trim leading/trailing whitespace), +//! the LoopBodyCarrierPromoter promotes the LoopBodyLocal variable (like `ch`) +//! to a bool carrier (like `is_whitespace`). +//! +//! TrimLoopHelper stores the pattern information needed to: +//! 1. Generate carrier initialization code +//! 2. Generate carrier update code +//! 3. Map the promoted carrier back to the original variable semantics +//! +//! ## Example Use Case +//! +//! **Original pattern**: +//! ```nyash +//! loop(start < end) { +//! local ch = s.substring(start, start+1) +//! if ch == " " || ch == "\t" { start = start + 1 } else { break } +//! } +//! ``` +//! +//! **After promotion**: +//! - Original variable: `ch` +//! - Promoted carrier: `is_whitespace` (bool) +//! - Comparison literals: `[" ", "\t"]` +//! +//! **TrimLoopHelper usage**: +//! ```rust +//! let helper = TrimLoopHelper { +//! original_var: "ch".to_string(), +//! carrier_name: "is_whitespace".to_string(), +//! whitespace_chars: vec![" ".to_string(), "\t".to_string()], +//! }; +//! +//! // Generate carrier initialization: is_whitespace = true +//! let init_value = helper.initial_value(); // true +//! +//! // Generate carrier update: is_whitespace = (ch == " " || ch == "\t") +//! let carrier_type = helper.carrier_type(); // "Bool" +//! ``` + +use super::loop_body_carrier_promoter::TrimPatternInfo; + +/// Helper for Trim pattern lowering +/// +/// Encapsulates the pattern-specific logic for converting +/// LoopBodyLocal-based conditions to carrier-based conditions. +/// +/// # Fields +/// +/// * `original_var` - The original LoopBodyLocal variable name (e.g., "ch") +/// * `carrier_name` - The promoted carrier name (e.g., "is_whitespace") +/// * `whitespace_chars` - The whitespace characters to compare against (e.g., [" ", "\t", "\n", "\r"]) +/// +/// # Design Philosophy +/// +/// This struct follows Box Theory principles: +/// - **Single Responsibility**: Only handles Trim pattern lowering logic +/// - **Reusability**: Can be used by both Pattern2 and Pattern4 lowerers +/// - **Testability**: Pure data structure with simple accessors +#[derive(Debug, Clone)] +pub struct TrimLoopHelper { + /// The original variable name (e.g., "ch") + pub original_var: String, + + /// The promoted carrier name (e.g., "is_whitespace") + pub carrier_name: String, + + /// Whitespace characters to compare against (e.g., [" ", "\t", "\n", "\r"]) + pub whitespace_chars: Vec, +} + +impl TrimLoopHelper { + /// Create TrimLoopHelper from TrimPatternInfo + /// + /// # Arguments + /// + /// * `info` - The TrimPatternInfo from LoopBodyCarrierPromoter + /// + /// # Returns + /// + /// A new TrimLoopHelper with the same information + /// + /// # Example + /// + /// ```ignore + /// let trim_info = TrimPatternInfo { + /// var_name: "ch".to_string(), + /// comparison_literals: vec![" ".to_string(), "\t".to_string()], + /// carrier_name: "is_whitespace".to_string(), + /// }; + /// + /// let helper = TrimLoopHelper::from_pattern_info(&trim_info); + /// assert_eq!(helper.original_var, "ch"); + /// assert_eq!(helper.carrier_name, "is_whitespace"); + /// ``` + pub fn from_pattern_info(info: &TrimPatternInfo) -> Self { + TrimLoopHelper { + original_var: info.var_name.clone(), + carrier_name: info.carrier_name.clone(), + whitespace_chars: info.comparison_literals.clone(), + } + } + + /// Get the carrier type (always Bool for Trim pattern) + /// + /// Trim patterns always use bool carriers to represent + /// "does the character match the whitespace set?" + /// + /// # Returns + /// + /// "Bool" - the carrier type name + pub fn carrier_type(&self) -> &str { + "Bool" + } + + /// Get initial carrier value (true = continue looping) + /// + /// The carrier is initialized to `true` to represent + /// "keep looping initially". When the character doesn't match + /// whitespace, the carrier becomes `false` and the loop breaks. + /// + /// # Returns + /// + /// `true` - initial value for the carrier + pub fn initial_value(&self) -> bool { + true + } + + /// Get the number of whitespace characters in the comparison set + /// + /// Useful for diagnostics and code generation. + /// + /// # Returns + /// + /// The count of whitespace characters (e.g., 4 for [" ", "\t", "\n", "\r"]) + pub fn whitespace_count(&self) -> usize { + self.whitespace_chars.len() + } + + /// Check if a specific character is in the whitespace set + /// + /// # Arguments + /// + /// * `ch` - The character to check (as a string slice) + /// + /// # Returns + /// + /// `true` if the character is in the whitespace set, `false` otherwise + /// + /// # Example + /// + /// ```ignore + /// let helper = TrimLoopHelper { + /// whitespace_chars: vec![" ".to_string(), "\t".to_string()], + /// ..Default::default() + /// }; + /// + /// assert!(helper.is_whitespace(" ")); + /// assert!(helper.is_whitespace("\t")); + /// assert!(!helper.is_whitespace("a")); + /// ``` + pub fn is_whitespace(&self, ch: &str) -> bool { + self.whitespace_chars.iter().any(|wc| wc == ch) + } + + /// Check if this is a safe Trim pattern that can bypass LoopBodyLocal restrictions + /// + /// A safe Trim pattern must: + /// 1. Have a valid carrier name + /// 2. Have at least one whitespace character to compare + /// 3. Have the expected structure (substring + OR chain + break) + /// + /// # Returns + /// + /// `true` if this is a safe Trim pattern, `false` otherwise + /// + /// # Example + /// + /// ```ignore + /// let helper = TrimLoopHelper { + /// original_var: "ch".to_string(), + /// carrier_name: "is_whitespace".to_string(), + /// whitespace_chars: vec![" ".to_string(), "\t".to_string()], + /// }; + /// assert!(helper.is_safe_trim()); + /// ``` + pub fn is_safe_trim(&self) -> bool { + // Basic validation + !self.carrier_name.is_empty() && !self.whitespace_chars.is_empty() + } + + /// Alias for is_safe_trim() - checks if this follows the Trim-like pattern + /// + /// This method provides a semantic alias for safety checks. + /// + /// # Returns + /// + /// `true` if this pattern is Trim-like, `false` otherwise + pub fn is_trim_like(&self) -> bool { + self.is_safe_trim() + } + + /// Check if this pattern has the expected Trim structure: + /// - substring() method call + /// - OR chain of equality comparisons + /// - break on non-match + /// + /// # Returns + /// + /// `true` if the pattern has valid structure, `false` otherwise + /// + /// # Implementation Note + /// + /// For now, just check basic requirements. + /// The full structure was already validated by LoopBodyCarrierPromoter. + pub fn has_valid_structure(&self) -> bool { + !self.original_var.is_empty() + && !self.carrier_name.is_empty() + && !self.whitespace_chars.is_empty() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_from_pattern_info() { + let trim_info = TrimPatternInfo { + var_name: "ch".to_string(), + comparison_literals: vec![" ".to_string(), "\t".to_string()], + carrier_name: "is_ch_match".to_string(), + }; + + let helper = TrimLoopHelper::from_pattern_info(&trim_info); + + assert_eq!(helper.original_var, "ch"); + assert_eq!(helper.carrier_name, "is_ch_match"); + assert_eq!(helper.whitespace_chars.len(), 2); + assert!(helper.whitespace_chars.contains(&" ".to_string())); + assert!(helper.whitespace_chars.contains(&"\t".to_string())); + } + + #[test] + fn test_carrier_type() { + let helper = TrimLoopHelper { + original_var: "ch".to_string(), + carrier_name: "is_whitespace".to_string(), + whitespace_chars: vec![], + }; + + assert_eq!(helper.carrier_type(), "Bool"); + } + + #[test] + fn test_initial_value() { + let helper = TrimLoopHelper { + original_var: "ch".to_string(), + carrier_name: "is_whitespace".to_string(), + whitespace_chars: vec![], + }; + + assert_eq!(helper.initial_value(), true); + } + + #[test] + fn test_whitespace_count() { + let helper = TrimLoopHelper { + original_var: "ch".to_string(), + carrier_name: "is_whitespace".to_string(), + whitespace_chars: vec![" ".to_string(), "\t".to_string(), "\n".to_string(), "\r".to_string()], + }; + + assert_eq!(helper.whitespace_count(), 4); + } + + #[test] + fn test_is_whitespace() { + let helper = TrimLoopHelper { + original_var: "ch".to_string(), + carrier_name: "is_whitespace".to_string(), + whitespace_chars: vec![" ".to_string(), "\t".to_string()], + }; + + assert!(helper.is_whitespace(" ")); + assert!(helper.is_whitespace("\t")); + assert!(!helper.is_whitespace("\n")); + assert!(!helper.is_whitespace("a")); + } + + #[test] + fn test_is_safe_trim() { + let helper = TrimLoopHelper { + original_var: "ch".to_string(), + carrier_name: "is_whitespace".to_string(), + whitespace_chars: vec![" ".to_string(), "\t".to_string()], + }; + assert!(helper.is_safe_trim()); + assert!(helper.is_trim_like()); + } + + #[test] + fn test_is_safe_trim_empty_carrier() { + let helper = TrimLoopHelper { + original_var: "ch".to_string(), + carrier_name: "".to_string(), // Empty! + whitespace_chars: vec![" ".to_string()], + }; + assert!(!helper.is_safe_trim()); + } + + #[test] + fn test_is_safe_trim_no_whitespace() { + let helper = TrimLoopHelper { + original_var: "ch".to_string(), + carrier_name: "is_whitespace".to_string(), + whitespace_chars: vec![], // Empty! + }; + assert!(!helper.is_safe_trim()); + } + + #[test] + fn test_has_valid_structure() { + let helper = TrimLoopHelper { + original_var: "ch".to_string(), + carrier_name: "is_whitespace".to_string(), + whitespace_chars: vec![" ".to_string()], + }; + assert!(helper.has_valid_structure()); + } +}