//! Pattern Router - Table-driven dispatch for loop patterns //! //! Phase 194: Replace if/else chain with table-driven routing //! //! # Architecture //! //! - Each pattern registers a detect function and a lower function //! - Patterns are tried in priority order (lower = tried first) //! - First matching pattern wins //! - Feature extraction delegated to ast_feature_extractor module //! //! # Adding New Patterns //! //! 1. Create a new module in `patterns/` (e.g., `pattern4_your_name.rs`) //! 2. Implement `pub fn can_lower(ctx: &LoopPatternContext) -> bool` //! 3. Implement `pub fn lower(builder: &mut MirBuilder, ctx: &LoopPatternContext) -> Result, String>` //! 4. Add entry to `LOOP_PATTERNS` table below //! //! That's it! No need to modify routing logic. use crate::ast::ASTNode; use crate::mir::builder::MirBuilder; use crate::mir::ValueId; use crate::mir::loop_pattern_detection::{LoopFeatures, LoopPatternKind}; // Phase 273 P1: Import Plan components (DomainPlan → Normalizer → Verifier → Lowerer) use crate::mir::builder::control_flow::plan::lowerer::PlanLowerer; use crate::mir::builder::control_flow::plan::normalizer::PlanNormalizer; use crate::mir::builder::control_flow::plan::verifier::PlanVerifier; /// AST Feature Extractor (declared in mod.rs as pub module, import from parent) use super::ast_feature_extractor as ast_features; /// Phase 92 P0-2: Import LoopSkeleton for Option A use crate::mir::loop_canonicalizer::LoopSkeleton; /// Context passed to pattern detect/lower functions pub(crate) struct LoopPatternContext<'a> { /// Loop condition AST node pub condition: &'a ASTNode, /// Loop body statements pub body: &'a [ASTNode], /// Current function name (for routing) pub func_name: &'a str, /// Debug logging enabled pub debug: bool, /// Has continue statement(s) in body? (Phase 194+) #[allow(dead_code)] pub has_continue: bool, /// Has break statement(s) in body? (Phase 194+) #[allow(dead_code)] pub has_break: bool, /// Phase 192: Loop features extracted from AST #[allow(dead_code)] pub features: LoopFeatures, /// Phase 192: Pattern classification based on features pub pattern_kind: LoopPatternKind, /// Phase 200-C: Optional function body AST for capture analysis /// None if not available, Some(&[ASTNode]) if function body is accessible pub fn_body: Option<&'a [ASTNode]>, /// Phase 92 P0-2: Optional LoopSkeleton from canonicalizer /// This provides ConditionalStep information for Pattern2 lowering. /// None if canonicalizer hasn't run yet (backward compatibility). /// SSOT Principle: Avoid re-detecting ConditionalStep in lowering phase. #[allow(dead_code)] pub skeleton: Option<&'a LoopSkeleton>, /// Phase 188.3: Cached StepTree max_loop_depth for Pattern6 /// None if not computed, Some(depth) if Pattern6 candidate /// Avoids re-building StepTree in lowering phase pub step_tree_max_loop_depth: Option, } impl<'a> LoopPatternContext<'a> { /// Create new context from routing parameters /// /// Automatically detects continue/break statements in body /// Extracts features and classifies pattern from AST /// Detects infinite loop condition /// Uses choose_pattern_kind() SSOT entry point pub(crate) fn new( condition: &'a ASTNode, body: &'a [ASTNode], func_name: &'a str, debug: bool, ) -> Self { // Use AST Feature Extractor for break/continue detection let has_continue = ast_features::detect_continue_in_body(body); let has_break = ast_features::detect_break_in_body(body); // Extract features (includes infinite loop detection) let features = ast_features::extract_features(condition, body, has_continue, has_break); // Phase 137-6-S1: Use SSOT pattern selection entry point use crate::mir::builder::control_flow::joinir::routing::choose_pattern_kind; let pattern_kind = choose_pattern_kind(condition, body); Self { condition, body, func_name, debug, has_continue, has_break, features, pattern_kind, fn_body: None, // Phase 200-C: Default to None skeleton: None, // Phase 92 P0-2: Default to None step_tree_max_loop_depth: None, // Phase 188.3: Default to None } } /// Phase 200-C: Create context with fn_body for capture analysis pub(crate) fn with_fn_body( condition: &'a ASTNode, body: &'a [ASTNode], func_name: &'a str, debug: bool, fn_body: &'a [ASTNode], ) -> Self { let mut ctx = Self::new(condition, body, func_name, debug); ctx.fn_body = Some(fn_body); ctx } /// Phase 92 P0-2: Set skeleton (for canonicalizer integration) #[allow(dead_code)] pub(crate) fn with_skeleton(mut self, skeleton: &'a LoopSkeleton) -> Self { self.skeleton = Some(skeleton); self } } /// Phase 286 P2.2: Common helper for Plan line lowering /// /// Extracts the common 3-line pattern used by Pattern6/7/4/1: /// 1. Normalize DomainPlan → CorePlan /// 2. Verify CorePlan invariants (fail-fast) /// 3. Lower CorePlan → MIR /// /// This eliminates repetition across 4 pattern routing blocks. fn lower_via_plan( builder: &mut MirBuilder, domain_plan: crate::mir::builder::control_flow::plan::DomainPlan, ctx: &LoopPatternContext, ) -> Result, String> { let core_plan = PlanNormalizer::normalize(builder, domain_plan, ctx)?; PlanVerifier::verify(&core_plan)?; PlanLowerer::lower(builder, core_plan, ctx) } /// Phase 287: Plan extractor function type for routing table /// /// Extractors have two signatures: /// - Simple: (condition, body) - Pattern1/4/9 /// - WithFnBody: (condition, body, fn_body) - Pattern6/7 type SimplePlanExtractor = fn(&ASTNode, &[ASTNode]) -> Result, String>; /// Phase 287: Plan extractor table entry struct PlanExtractorEntry { /// Pattern name for debug logging name: &'static str, /// Extractor function extractor: PlanExtractorVariant, } /// Phase 287: Extractor function variant (handles different signatures) enum PlanExtractorVariant { /// Simple extractor: (condition, body) Simple(SimplePlanExtractor), /// Extractor with fn_body: (condition, body, fn_body) - Pattern6 WithFnBody(fn(&ASTNode, &[ASTNode], Option<&[ASTNode]>) -> Result, String>), /// Extractor with post_loop_code: (condition, body, post_loop_code) - Pattern7 /// /// NOTE (Phase 286): Currently always called with &[] for post_loop_code. /// This variant is kept for future extension (post-loop segment analysis). /// The _post_loop_code parameter in Pattern7 extractor is intentionally unused. WithPostLoop(fn(&ASTNode, &[ASTNode], &[ASTNode]) -> Result, String>), } /// Phase 287: Plan extractor routing table (SSOT) /// /// Order is important: more specific patterns first /// - Pattern6/7: Need fn_body for capture analysis /// - Pattern8: Bool predicate scan (early-exit return, more specific) /// - Pattern3: If-phi merge (carrier with conditional update, more specific than Pattern4) /// - Pattern4: Continue loop (more specific than Pattern1) /// - Pattern9: Accum const loop (2 carriers, more specific than Pattern1) /// - Pattern1: Simple while (fallback) static PLAN_EXTRACTORS: &[PlanExtractorEntry] = &[ PlanExtractorEntry { name: "Pattern6_ScanWithInit (Phase 273)", extractor: PlanExtractorVariant::WithFnBody(super::pattern6_scan_with_init::extract_scan_with_init_plan), }, PlanExtractorEntry { name: "Pattern7_SplitScan (Phase 273)", extractor: PlanExtractorVariant::WithPostLoop(super::pattern7_split_scan::extract_split_scan_plan), }, PlanExtractorEntry { name: "Pattern5_InfiniteEarlyExit (Phase 286 P3.2)", extractor: PlanExtractorVariant::Simple(super::extractors::pattern5::extract_pattern5_plan), }, PlanExtractorEntry { name: "Pattern8_BoolPredicateScan (Phase 286 P2.4)", extractor: PlanExtractorVariant::Simple(super::extractors::pattern8::extract_pattern8_plan), }, PlanExtractorEntry { name: "Pattern3_IfPhi (Phase 286 P2.6)", extractor: PlanExtractorVariant::Simple(super::extractors::pattern3::extract_pattern3_plan), }, PlanExtractorEntry { name: "Pattern4_Continue (Phase 286 P2)", extractor: PlanExtractorVariant::Simple(super::extractors::pattern4::extract_pattern4_plan), }, PlanExtractorEntry { name: "Pattern9_AccumConstLoop (Phase 286 P2.3)", extractor: PlanExtractorVariant::Simple(super::extractors::pattern9::extract_pattern9_plan), }, PlanExtractorEntry { name: "Pattern2_Break (Phase 286 P3.1)", extractor: PlanExtractorVariant::Simple(super::extractors::pattern2::extract_pattern2_plan), }, PlanExtractorEntry { name: "Pattern1_SimpleWhile (Phase 286 P2.1)", extractor: PlanExtractorVariant::Simple(super::extractors::pattern1::extract_pattern1_plan), }, ]; /// Phase 287: Try all plan extractors in priority order /// /// Returns Ok(Some(value_id)) if any extractor succeeds /// Returns Ok(None) if no extractor matches /// Returns Err if extraction or lowering fails fn try_plan_extractors( builder: &mut MirBuilder, ctx: &LoopPatternContext, ) -> Result, String> { use super::super::trace; for entry in PLAN_EXTRACTORS { // Phase 286 P2.6: Pattern1 Plan guard (structural Fail-Fast) // Pattern1 should only match Pattern1SimpleWhile pattern_kind // This prevents Pattern1 from incorrectly matching Pattern3 fixtures if entry.name.contains("Pattern1") { use crate::mir::loop_pattern_detection::LoopPatternKind; if ctx.pattern_kind != LoopPatternKind::Pattern1SimpleWhile { continue; } } // Try extraction based on variant let plan_opt = match &entry.extractor { PlanExtractorVariant::Simple(extractor) => { extractor(ctx.condition, ctx.body)? } PlanExtractorVariant::WithFnBody(extractor) => { // Pattern6: needs fn_body for capture analysis extractor(ctx.condition, ctx.body, ctx.fn_body)? } PlanExtractorVariant::WithPostLoop(extractor) => { // Pattern7: uses empty slice for post_loop_code (3rd param) extractor(ctx.condition, ctx.body, &[])? } }; // If extraction succeeded, check if we can lower it if let Some(domain_plan) = plan_opt { // Phase 286 P3: Pattern8 static box filtering // Plan extractors are pure (no builder access), so we filter here // Static box loops with `me.method()` should be handled by ReceiverNormalizeBox, not Pattern8 if entry.name.contains("Pattern8") && builder.comp_ctx.current_static_box.is_some() { if ctx.debug { trace::trace().debug( "route/plan", &format!("{} extracted but rejected: static box context (fallback to legacy)", entry.name), ); } // Skip this pattern, try next extractor continue; } let log_msg = format!("route=plan strategy=extract pattern={}", entry.name); trace::trace().pattern("route", &log_msg, true); // Phase 286 P2.6.1: Fail-Fast 統一 - extract 成功 → normalize/lower 失敗は即 Err // Pattern3 stub fallback 撤去(normalizer 実装済み) return lower_via_plan(builder, domain_plan, ctx); } else { // Extraction returned None - try next extractor if ctx.debug { let debug_msg = format!("{} extraction returned None, trying next pattern", entry.name); trace::trace().debug("route", &debug_msg); } } } // No extractor matched Ok(None) } /// Phase 272 P0.2 Refactoring: can_lower() strategy classification /// /// Clarifies the two main detection strategies used across patterns: /// /// ## ExtractionBased (SSOT Approach) /// - Used by: Pattern6, Pattern7 /// - Strategy: Try pattern extraction, if successful → match /// - Pros: Single source of truth (extract function defines pattern) /// - Cons: Extraction can be expensive (but amortized over lowering) /// /// ## StructureBased (Feature Classification) /// - Used by: Pattern1, Pattern2, Pattern3, Pattern4, Pattern5, Pattern8, Pattern9 /// - Strategy: Check pattern_kind (from LoopPatternContext), plus optional structural checks /// - Pros: Fast classification, reuses centralized feature detection /// - Cons: Two sources of truth (classify + structural checks) /// /// ## Rationale for Dual Strategy: /// - Pattern6/7: Complex extraction logic (variable step, carrier tracking) /// → ExtractionBased avoids duplication between detect and extract /// - Other patterns: Simple structural features (break/continue/if-phi) /// → StructureBased leverages centralized LoopFeatures classification /// /// This documentation prevents bugs like Phase 272 P0.2's Pattern7 issue /// (pattern_kind check was too restrictive, extraction-based approach fixed it). #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[allow(dead_code)] // Documentation purpose - not enforced in code yet pub(crate) enum CanLowerStrategy { /// Extraction-based detection: Try extract(), success → match /// Used by Pattern6, Pattern7 ExtractionBased, /// Structure-based detection: Check pattern_kind from LoopPatternContext /// Used by Pattern1, Pattern2, Pattern3, Pattern4, Pattern5, Pattern8, Pattern9 StructureBased, } /// Entry in the loop pattern router table. /// Each pattern registers a detect function and a lower function. pub(crate) struct LoopPatternEntry { /// Human-readable pattern name for debugging pub(crate) name: &'static str, /// Detection function: returns true if this pattern matches pub(crate) detect: fn(&MirBuilder, &LoopPatternContext) -> bool, /// Lowering function: performs the actual JoinIR generation pub(crate) lower: fn(&mut MirBuilder, &LoopPatternContext) -> Result, String>, } /// Static table of all registered loop patterns. /// /// **IMPORTANT**: Patterns are tried in array order (SSOT). /// Array order defines priority - earlier entries are tried first. /// Pattern4 → Pattern8 → Pattern9 → Pattern3 → Pattern1 → Pattern2 /// /// # Current Patterns (Structure-based detection, established Phase 131-11+) /// /// Pattern detection strategies (updated Phase 286): /// - Pattern6/7: ExtractionBased (Plan line, Phase 273+) /// - Pattern5/8/9: ExtractionBased (extraction functions, Plan line Phase 286+) /// - Pattern1-4: StructureBased (LoopFeatures classification, legacy) /// /// - Pattern 4: Loop with Continue (loop_continue_pattern4.hako) /// - Detection: pattern_kind == Pattern4Continue /// - Structure: has_continue && !has_break /// /// - Pattern 3: Loop with If-Else PHI (loop_if_phi.hako) /// - Detection: pattern_kind == Pattern3IfPhi /// - Structure: has_if_else_phi && !has_break && !has_continue /// /// - Pattern 1: Simple While Loop (loop_min_while.hako) /// - Detection: pattern_kind == Pattern1SimpleWhile /// - Structure: !has_break && !has_continue && !has_if_else_phi /// /// - Pattern 2: Loop with Conditional Break (joinir_min_loop.hako) /// - Detection: pattern_kind == Pattern2Break /// - Structure: has_break && !has_continue /// /// Note: func_name is now only used for debug logging, not pattern detection /// Phase 286: Pattern5 removed (migrated to Plan-based routing) pub(crate) static LOOP_PATTERNS: &[LoopPatternEntry] = &[ LoopPatternEntry { name: "Pattern6_NestedLoopMinimal", // Phase 188.3: 1-level nested loop detect: super::pattern6_nested_minimal::can_lower, lower: super::pattern6_nested_minimal::lower, }, LoopPatternEntry { name: "Pattern4_WithContinue", detect: super::pattern4_with_continue::can_lower, lower: super::pattern4_with_continue::lower, }, // Phase 273 P0.1: Pattern6 entry removed (migrated to Plan-based routing) // Pattern6_ScanWithInit now handled via extract_scan_with_init_plan() + PlanLowerer // Phase 273 P2: Pattern7 entry removed (migrated to Plan-based routing) // Pattern7_SplitScan now handled via extract_split_scan_plan() + PlanLowerer LoopPatternEntry { name: "Pattern8_BoolPredicateScan", // Phase 259 P0: boolean predicate scan (is_integer/is_valid) detect: super::pattern8_scan_bool_predicate::can_lower, lower: super::pattern8_scan_bool_predicate::lower, }, LoopPatternEntry { name: "Pattern9_AccumConstLoop", // Phase 270 P1: accumulator const loop (橋渡しパターン, before P1) detect: super::pattern9_accum_const_loop::can_lower, lower: super::pattern9_accum_const_loop::lower, }, LoopPatternEntry { name: "Pattern3_WithIfPhi", detect: super::pattern3_with_if_phi::can_lower, lower: super::pattern3_with_if_phi::lower, }, LoopPatternEntry { name: "Pattern1_Minimal", detect: super::pattern1_minimal::can_lower, lower: super::pattern1_minimal::lower, }, LoopPatternEntry { name: "Pattern2_WithBreak", detect: super::pattern2_with_break::can_lower, lower: super::pattern2_with_break::lower, }, ]; /// Try all registered patterns in priority order. /// /// Returns Ok(Some(value_id)) if a pattern matched and lowered successfully. /// Returns Ok(None) if no pattern matched. /// Returns Err if a pattern matched but lowering failed. /// /// # Router Architecture (Structure-based routing established Phase 183) /// /// This router uses multiple detection strategies: /// - Plan-based (Pattern6/7): extract_*_plan() → DomainPlan (Phase 273+ SSOT) /// - Extraction-based (Pattern8/9): extract_*() functions (already implemented) /// - Structure-based (Pattern1-5): ctx.pattern_kind classification (legacy) /// /// # Plan Line SSOT for Pattern6/7 (Phase 273+) /// /// This function implements the following routing strategy: /// 1. Try Plan-based Pattern6 (extract_scan_with_init_plan) → DomainPlan /// 2. Try Plan-based Pattern7 (extract_split_scan_plan) → DomainPlan /// 3. Fall through to legacy Pattern1-5 table for other patterns /// /// The Plan line (Extractor → Normalizer → Verifier → Lowerer) is the /// current operational SSOT for Pattern6/7. Legacy patterns (1-5) use /// the traditional LoopPatternContext-based routing. /// /// Plan-based architecture (Phase 273 P1-P3): /// - extract_*_plan() → DomainPlan (pure extraction, no builder) /// - PlanNormalizer::normalize() → CorePlan (pattern knowledge expansion, SSOT) /// - PlanVerifier::verify() → fail-fast validation /// - PlanLowerer::lower() → MIR emission (pattern-agnostic, emit_frag SSOT) /// /// SSOT Entry Points: /// - Pattern6: src/mir/builder/control_flow/plan/normalizer.rs (ScanWithInit normalization) /// - Pattern7: src/mir/builder/control_flow/plan/normalizer.rs (SplitScan normalization) /// - Pattern1-5: src/mir/builder/control_flow/joinir/patterns/pattern*.rs (direct lowering) pub(crate) fn route_loop_pattern( builder: &mut MirBuilder, ctx: &LoopPatternContext, ) -> Result, String> { use super::super::trace; // Phase 287: Try all Plan extractors in priority order (Pattern6/7/4/9/1) // This replaces ~100 lines of repetitive extraction blocks if let Some(value_id) = try_plan_extractors(builder, ctx)? { return Ok(Some(value_id)); } // Phase 183: Route based on pre-classified pattern kind // Pattern kind was already determined by ctx.pattern_kind in LoopPatternContext::new() // This eliminates duplicate detection logic across routers. // Find matching pattern entry based on pattern_kind // Phase 273 P0.1: Pattern6 skip logic removed (entry no longer in LOOP_PATTERNS) for entry in LOOP_PATTERNS { if (entry.detect)(builder, ctx) { let log_msg = format!("route=joinir strategy=extract pattern={} (Phase 194+)", entry.name); trace::trace().pattern("route", &log_msg, true); return (entry.lower)(builder, ctx); } } // No pattern matched - return None (caller will handle error) if ctx.debug { trace::trace().debug( "route", &format!( "route=none (no pattern matched) func='{}' pattern_kind={:?} (exhausted: plan+joinir)", ctx.func_name, ctx.pattern_kind ), ); } Ok(None) }