262 lines
9.2 KiB
Rust
262 lines
9.2 KiB
Rust
//! 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<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,
|
|
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<Option<ValueId>, 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<Option<ValueId>, 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)
|
|
}
|