//! Pattern Router - Plan/Composer routing for loop patterns //! //! Phase 29ap P12: Legacy loop table removed (plan/composer SSOT only) //! //! # Architecture //! //! - single_planner derives a DomainPlan + facts outcome (SSOT) //! - composer adopts CorePlan (strict/dev shadow or release adopt) //! - PlanLowerer emits MIR from CorePlan (emit_frag SSOT) //! //! # Adding New Patterns //! //! 1. Add Facts/Planner extraction in plan layer //! 2. Normalize/verify in plan normalizer/verifier //! 3. Compose CorePlan in composer (shadow/release adopt as needed) //! 4. Keep router unchanged (it only delegates to plan/composer) 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; use crate::mir::builder::control_flow::plan::composer; use crate::mir::builder::control_flow::plan::single_planner; /// 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, /// In static box context? (affects Pattern8 routing) pub in_static_box: 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, in_static_box: 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, in_static_box, 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, in_static_box: bool, fn_body: &'a [ASTNode], ) -> Self { let mut ctx = Self::new(condition, body, func_name, debug, in_static_box); 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 Plan-based routing: /// 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 29ai P5: Plan extractor routing moved to `plan::single_planner`. /// Route loop patterns via plan/composer SSOT. /// /// Returns Ok(Some(value_id)) if a plan matched and lowered successfully. /// Returns Ok(None) if no plan matched. /// Returns Err if a plan matched but lowering failed. /// /// # Router Architecture (Plan/Composer SSOT) /// /// The plan line (Extractor → Normalizer → Verifier → Lowerer) is the /// operational SSOT for loop routing (Phase 273+). /// /// 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) pub(crate) fn route_loop_pattern( builder: &mut MirBuilder, ctx: &LoopPatternContext, ) -> Result, String> { use super::super::trace; // Phase 29ai P5: Single entrypoint for plan extraction (router has no rule table). let (domain_plan, outcome) = single_planner::try_build_domain_plan_with_outcome(ctx)?; let strict_or_dev = crate::config::env::joinir_dev::strict_enabled() || crate::config::env::joinir_dev_enabled(); if strict_or_dev { if let Some(adopt) = composer::try_shadow_adopt_nested_minimal(builder, ctx, &outcome)? { let composer::ShadowAdoptOutcome { core_plan, tag } = adopt; PlanVerifier::verify(&core_plan)?; eprintln!("{}", tag); return PlanLowerer::lower(builder, core_plan, ctx); } if let Some(err) = composer::strict_nested_loop_guard(&outcome, ctx) { eprintln!("{}", err); return Err(err); } } if !strict_or_dev { if let Some(core_plan) = composer::try_release_adopt_nested_minimal(builder, ctx, &outcome)? { PlanVerifier::verify(&core_plan)?; return PlanLowerer::lower(builder, core_plan, ctx); } } if let Some(domain_plan) = domain_plan { if let Some(adopt) = composer::try_shadow_adopt_core_plan( builder, ctx, strict_or_dev, &domain_plan, &outcome, )? { let composer::ShadowAdoptOutcome { core_plan, tag } = adopt; PlanVerifier::verify(&core_plan)?; eprintln!("{}", tag); return PlanLowerer::lower(builder, core_plan, ctx); } if !strict_or_dev { if let Some(core_plan) = composer::try_release_adopt_core_plan(builder, ctx, &domain_plan, &outcome)? { PlanVerifier::verify(&core_plan)?; return PlanLowerer::lower(builder, core_plan, ctx); } } return lower_via_plan(builder, domain_plan, 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) }