2025-12-31 08:07:58 +09:00
|
|
|
//! Pattern Router - Plan/Composer routing for loop patterns
|
2025-12-05 22:11:39 +09:00
|
|
|
//!
|
2025-12-31 08:07:58 +09:00
|
|
|
//! Phase 29ap P12: Legacy loop table removed (plan/composer SSOT only)
|
2025-12-05 22:11:39 +09:00
|
|
|
//!
|
|
|
|
|
//! # Architecture
|
|
|
|
|
//!
|
2025-12-31 08:07:58 +09:00
|
|
|
//! - 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)
|
2025-12-05 22:11:39 +09:00
|
|
|
//!
|
|
|
|
|
//! # Adding New Patterns
|
|
|
|
|
//!
|
2025-12-31 08:07:58 +09:00
|
|
|
//! 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)
|
2025-12-05 22:11:39 +09:00
|
|
|
|
|
|
|
|
use crate::ast::ASTNode;
|
|
|
|
|
use crate::mir::builder::MirBuilder;
|
|
|
|
|
use crate::mir::ValueId;
|
|
|
|
|
|
2025-12-06 03:30:03 +09:00
|
|
|
use crate::mir::loop_pattern_detection::{LoopFeatures, LoopPatternKind};
|
|
|
|
|
|
2025-12-22 22:42:56 +09:00
|
|
|
// 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;
|
2025-12-30 14:28:42 +09:00
|
|
|
use crate::mir::builder::control_flow::plan::composer;
|
2025-12-29 08:04:00 +09:00
|
|
|
use crate::mir::builder::control_flow::plan::single_planner;
|
2025-12-22 22:42:56 +09:00
|
|
|
|
2025-12-23 05:35:42 +09:00
|
|
|
/// AST Feature Extractor (declared in mod.rs as pub module, import from parent)
|
2025-12-06 03:30:03 +09:00
|
|
|
use super::ast_feature_extractor as ast_features;
|
|
|
|
|
|
2025-12-16 15:22:29 +09:00
|
|
|
/// Phase 92 P0-2: Import LoopSkeleton for Option A
|
|
|
|
|
use crate::mir::loop_canonicalizer::LoopSkeleton;
|
|
|
|
|
|
2025-12-05 22:11:39 +09:00
|
|
|
/// Context passed to pattern detect/lower functions
|
2025-12-11 19:11:26 +09:00
|
|
|
pub(crate) struct LoopPatternContext<'a> {
|
2025-12-05 22:11:39 +09:00
|
|
|
/// 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,
|
2025-12-06 00:10:27 +09:00
|
|
|
|
2025-12-29 08:04:00 +09:00
|
|
|
/// In static box context? (affects Pattern8 routing)
|
|
|
|
|
pub in_static_box: bool,
|
|
|
|
|
|
2025-12-06 00:10:27 +09:00
|
|
|
/// Has continue statement(s) in body? (Phase 194+)
|
2025-12-10 00:01:53 +09:00
|
|
|
#[allow(dead_code)]
|
2025-12-06 00:10:27 +09:00
|
|
|
pub has_continue: bool,
|
|
|
|
|
|
|
|
|
|
/// Has break statement(s) in body? (Phase 194+)
|
2025-12-10 00:01:53 +09:00
|
|
|
#[allow(dead_code)]
|
2025-12-06 00:10:27 +09:00
|
|
|
pub has_break: bool,
|
2025-12-06 03:30:03 +09:00
|
|
|
|
|
|
|
|
/// Phase 192: Loop features extracted from AST
|
2025-12-10 00:01:53 +09:00
|
|
|
#[allow(dead_code)]
|
2025-12-06 03:30:03 +09:00
|
|
|
pub features: LoopFeatures,
|
|
|
|
|
|
|
|
|
|
/// Phase 192: Pattern classification based on features
|
|
|
|
|
pub pattern_kind: LoopPatternKind,
|
2025-12-09 18:32:03 +09:00
|
|
|
|
|
|
|
|
/// 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]>,
|
2025-12-16 15:22:29 +09:00
|
|
|
|
|
|
|
|
/// 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>,
|
2025-12-27 05:45:12 +09:00
|
|
|
|
|
|
|
|
/// 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>,
|
2025-12-05 22:11:39 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<'a> LoopPatternContext<'a> {
|
|
|
|
|
/// Create new context from routing parameters
|
2025-12-06 00:10:27 +09:00
|
|
|
///
|
2025-12-23 05:35:42 +09:00
|
|
|
/// 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
|
2025-12-11 19:11:26 +09:00
|
|
|
pub(crate) fn new(
|
2025-12-05 22:11:39 +09:00
|
|
|
condition: &'a ASTNode,
|
|
|
|
|
body: &'a [ASTNode],
|
|
|
|
|
func_name: &'a str,
|
|
|
|
|
debug: bool,
|
2025-12-29 08:04:00 +09:00
|
|
|
in_static_box: bool,
|
2025-12-05 22:11:39 +09:00
|
|
|
) -> Self {
|
2025-12-23 05:35:42 +09:00
|
|
|
// Use AST Feature Extractor for break/continue detection
|
2025-12-06 03:30:03 +09:00
|
|
|
let has_continue = ast_features::detect_continue_in_body(body);
|
|
|
|
|
let has_break = ast_features::detect_break_in_body(body);
|
|
|
|
|
|
2025-12-23 05:35:42 +09:00
|
|
|
// Extract features (includes infinite loop detection)
|
2025-12-14 09:59:34 +09:00
|
|
|
let features = ast_features::extract_features(condition, body, has_continue, has_break);
|
2025-12-06 03:30:03 +09:00
|
|
|
|
2025-12-16 07:37:23 +09:00
|
|
|
// 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);
|
2025-12-06 00:10:27 +09:00
|
|
|
|
2025-12-05 22:11:39 +09:00
|
|
|
Self {
|
|
|
|
|
condition,
|
|
|
|
|
body,
|
|
|
|
|
func_name,
|
|
|
|
|
debug,
|
2025-12-29 08:04:00 +09:00
|
|
|
in_static_box,
|
2025-12-06 00:10:27 +09:00
|
|
|
has_continue,
|
|
|
|
|
has_break,
|
2025-12-06 03:30:03 +09:00
|
|
|
features,
|
|
|
|
|
pattern_kind,
|
2025-12-09 18:32:03 +09:00
|
|
|
fn_body: None, // Phase 200-C: Default to None
|
2025-12-16 15:22:29 +09:00
|
|
|
skeleton: None, // Phase 92 P0-2: Default to None
|
2025-12-27 05:45:12 +09:00
|
|
|
step_tree_max_loop_depth: None, // Phase 188.3: Default to None
|
2025-12-06 00:10:27 +09:00
|
|
|
}
|
|
|
|
|
}
|
2025-12-09 18:32:03 +09:00
|
|
|
|
|
|
|
|
/// Phase 200-C: Create context with fn_body for capture analysis
|
2025-12-11 19:11:26 +09:00
|
|
|
pub(crate) fn with_fn_body(
|
2025-12-09 18:32:03 +09:00
|
|
|
condition: &'a ASTNode,
|
|
|
|
|
body: &'a [ASTNode],
|
|
|
|
|
func_name: &'a str,
|
|
|
|
|
debug: bool,
|
2025-12-29 08:04:00 +09:00
|
|
|
in_static_box: bool,
|
2025-12-09 18:32:03 +09:00
|
|
|
fn_body: &'a [ASTNode],
|
|
|
|
|
) -> Self {
|
2025-12-29 08:04:00 +09:00
|
|
|
let mut ctx = Self::new(condition, body, func_name, debug, in_static_box);
|
2025-12-09 18:32:03 +09:00
|
|
|
ctx.fn_body = Some(fn_body);
|
|
|
|
|
ctx
|
|
|
|
|
}
|
2025-12-16 15:22:29 +09:00
|
|
|
|
|
|
|
|
/// 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
|
|
|
|
|
}
|
2025-12-06 00:10:27 +09:00
|
|
|
}
|
|
|
|
|
|
2025-12-26 02:03:22 +09:00
|
|
|
/// Phase 286 P2.2: Common helper for Plan line lowering
|
|
|
|
|
///
|
2025-12-31 06:49:41 +09:00
|
|
|
/// Extracts the common 3-line pattern used by Plan-based routing:
|
2025-12-26 02:03:22 +09:00
|
|
|
/// 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)
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-29 08:04:00 +09:00
|
|
|
// Phase 29ai P5: Plan extractor routing moved to `plan::single_planner`.
|
2025-12-26 02:38:09 +09:00
|
|
|
|
2025-12-31 08:07:58 +09:00
|
|
|
/// Route loop patterns via plan/composer SSOT.
|
2025-12-15 03:27:47 +09:00
|
|
|
///
|
2025-12-31 08:07:58 +09:00
|
|
|
/// 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.
|
2025-12-05 22:11:39 +09:00
|
|
|
///
|
2025-12-31 08:07:58 +09:00
|
|
|
/// # Router Architecture (Plan/Composer SSOT)
|
2025-12-06 03:30:03 +09:00
|
|
|
///
|
2025-12-31 08:07:58 +09:00
|
|
|
/// The plan line (Extractor → Normalizer → Verifier → Lowerer) is the
|
|
|
|
|
/// operational SSOT for loop routing (Phase 273+).
|
2025-12-23 00:34:38 +09:00
|
|
|
///
|
|
|
|
|
/// Plan-based architecture (Phase 273 P1-P3):
|
|
|
|
|
/// - extract_*_plan() → DomainPlan (pure extraction, no builder)
|
|
|
|
|
/// - PlanNormalizer::normalize() → CorePlan (pattern knowledge expansion, SSOT)
|
2025-12-22 22:42:56 +09:00
|
|
|
/// - PlanVerifier::verify() → fail-fast validation
|
2025-12-23 00:34:38 +09:00
|
|
|
/// - 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)
|
2025-12-11 19:11:26 +09:00
|
|
|
pub(crate) fn route_loop_pattern(
|
2025-12-05 22:11:39 +09:00
|
|
|
builder: &mut MirBuilder,
|
|
|
|
|
ctx: &LoopPatternContext,
|
|
|
|
|
) -> Result<Option<ValueId>, String> {
|
feat(joinir): Phase 195 - Unified JoinLoopTrace for all JoinIR debug output
Created centralized tracing module for JoinIR loop lowering operations,
consolidating scattered eprintln! calls into a single SSOT interface.
# Implementation
1. **Created trace.rs module** (~233 lines)
- JoinLoopTrace struct with env var controls
- Unified API: pattern(), varmap(), joinir_stats(), phi(), merge(), etc.
- Global singleton via trace() function
- Supports 5 env vars: NYASH_TRACE_VARMAP, NYASH_JOINIR_DEBUG,
NYASH_OPTION_C_DEBUG, NYASH_JOINIR_MAINLINE_DEBUG, NYASH_LOOPFORM_DEBUG
2. **Updated debug.rs** - Delegates trace_varmap() to JoinLoopTrace
3. **Updated routing.rs** - All eprintln! replaced with trace calls (10 instances)
4. **Updated pattern routers** - All 3 patterns now use unified trace
- pattern1_minimal.rs: 6 replacements
- pattern2_with_break.rs: 6 replacements
- pattern3_with_if_phi.rs: 6 replacements
- router.rs: 2 replacements
5. **Updated merge/block_allocator.rs** - 6 eprintln! → trace calls
# Benefits
- **Single Source of Truth**: All trace control through environment variables
- **Consistent Format**: Unified [trace:*] prefix for easy filtering
- **Zero Overhead**: No output when env vars unset
- **Easy Extension**: Add new trace points via existing API
- **Better Debug Experience**: Structured output with clear categories
# Testing
✅ cargo build --release - Success
✅ NYASH_TRACE_VARMAP=1 - Shows varmap traces only
✅ NYASH_JOINIR_DEBUG=1 - Shows joinir + blocks + routing traces
✅ No env vars - No debug output
✅ apps/tests/loop_min_while.hako - All tests pass
# Related
- Phase 191-194 groundwork (modular merge structure)
- NYASH_TRACE_VARMAP added today for variable_map debugging
- Consolidates ~80 scattered eprintln! calls across JoinIR
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-05 22:23:51 +09:00
|
|
|
use super::super::trace;
|
|
|
|
|
|
2025-12-29 08:04:00 +09:00
|
|
|
// Phase 29ai P5: Single entrypoint for plan extraction (router has no rule table).
|
2025-12-31 07:12:06 +09:00
|
|
|
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 {
|
2025-12-31 07:49:48 +09:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-31 07:12:06 +09:00
|
|
|
if let Some(err) = composer::strict_nested_loop_guard(&outcome, ctx) {
|
|
|
|
|
eprintln!("{}", err);
|
|
|
|
|
return Err(err);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-31 07:56:14 +09:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-31 07:12:06 +09:00
|
|
|
if let Some(domain_plan) = domain_plan {
|
2025-12-30 08:52:21 +09:00
|
|
|
|
2025-12-30 14:47:44 +09:00
|
|
|
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;
|
2025-12-30 08:52:21 +09:00
|
|
|
PlanVerifier::verify(&core_plan)?;
|
2025-12-30 14:47:44 +09:00
|
|
|
eprintln!("{}", tag);
|
2025-12-30 13:30:28 +09:00
|
|
|
return PlanLowerer::lower(builder, core_plan, ctx);
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-30 17:22:14 +09:00
|
|
|
if !strict_or_dev {
|
2025-12-30 18:36:24 +09:00
|
|
|
if let Some(core_plan) =
|
2025-12-30 20:12:11 +09:00
|
|
|
composer::try_release_adopt_core_plan(builder, ctx, &domain_plan, &outcome)?
|
2025-12-30 18:36:24 +09:00
|
|
|
{
|
|
|
|
|
PlanVerifier::verify(&core_plan)?;
|
|
|
|
|
return PlanLowerer::lower(builder, core_plan, ctx);
|
|
|
|
|
}
|
2025-12-30 17:22:14 +09:00
|
|
|
}
|
|
|
|
|
|
2025-12-29 08:04:00 +09:00
|
|
|
return lower_via_plan(builder, domain_plan, ctx);
|
2025-12-26 02:03:22 +09:00
|
|
|
}
|
|
|
|
|
|
2025-12-08 18:36:13 +09:00
|
|
|
// No pattern matched - return None (caller will handle error)
|
2025-12-05 22:11:39 +09:00
|
|
|
if ctx.debug {
|
2025-12-11 19:11:26 +09:00
|
|
|
trace::trace().debug(
|
|
|
|
|
"route",
|
|
|
|
|
&format!(
|
2025-12-23 05:03:25 +09:00
|
|
|
"route=none (no pattern matched) func='{}' pattern_kind={:?} (exhausted: plan+joinir)",
|
2025-12-11 19:11:26 +09:00
|
|
|
ctx.func_name, ctx.pattern_kind
|
|
|
|
|
),
|
|
|
|
|
);
|
2025-12-05 22:11:39 +09:00
|
|
|
}
|
|
|
|
|
Ok(None)
|
|
|
|
|
}
|