## Phase 188.3 進捗: Phase 2 完了 (6/13 tasks) ### 実装完了 ✅ **Phase 1: Fixture作成** - apps/tests/phase1883_nested_minimal.hako 追加 - Add/Compare のみ(乗算なし) - 期待 exit code: 9 (3×3 nested loops) - 既存 lowering で fallback 動作確認 **Phase 2: 選択ロジック (SSOT)** - LoopPatternContext に step_tree_max_loop_depth フィールド追加 - choose_pattern_kind() に Pattern6 選択ロジック実装: 1. Cheap check (has_inner_loop) 2. StepTree 構築 (max_loop_depth 取得) 3. AST validation (is_pattern6_lowerable) - pattern6_nested_minimal.rs モジュール作成 (stub) - LOOP_PATTERNS に Pattern6 entry 追加 - **検証**: Pattern6 が正しく選択される ✅ ### 設計原則 (確認済み) 1. **Fail-Fast**: Pattern6 選択後は Ok(None) で逃げない 2. **outer 変数 write-back 検出 → validation false** (Phase 188.4+) 3. **最小実装**: inner local だけ、Pattern1 モデル二重化 4. **cfg! 依存なし**: production で動作 ### 検証結果 ``` [choose_pattern_kind] has_inner_loop=true [choose_pattern_kind] max_loop_depth=2 [choose_pattern_kind] is_pattern6_lowerable=true ✅ Pattern6 SELECTED! ``` Stub からの期待エラー: ``` [ERROR] ❌ [Pattern6] Nested loop lowering not yet implemented ``` ### 次: Phase 3 (Lowering 実装 - 推定4時間) 残りタスク: - Phase 3-1: AST 抽出ヘルパー - Phase 3-2: Validation ヘルパー - Phase 3-3: Continuation 生成 (outer_step, inner_step, k_inner_exit) - Phase 3-4: fixture が exit=9 を返すことを検証 ### 変更ファイル **新規**: - apps/tests/phase1883_nested_minimal.hako - src/mir/builder/control_flow/joinir/patterns/pattern6_nested_minimal.rs - docs/development/current/main/phases/phase-188.{1,2,3}/README.md **変更**: - src/mir/builder/control_flow/joinir/routing.rs (Pattern6 選択) - src/mir/builder/control_flow/joinir/patterns/router.rs (Context 拡張) - src/mir/builder/control_flow/joinir/patterns/mod.rs (module 宣言) 🎯 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
508 lines
21 KiB
Rust
508 lines
21 KiB
Rust
//! 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<Option<ValueId>, 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<u32>,
|
||
}
|
||
|
||
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<Option<ValueId>, 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<Option<crate::mir::builder::control_flow::plan::DomainPlan>, 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<Option<crate::mir::builder::control_flow::plan::DomainPlan>, 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<Option<crate::mir::builder::control_flow::plan::DomainPlan>, 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<Option<ValueId>, 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<Option<ValueId>, 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<Option<ValueId>, 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)
|
||
}
|