From 67e2bfada4201aaf3e169faba69d5c8d42061f74 Mon Sep 17 00:00:00 2001 From: nyash-codex Date: Fri, 5 Dec 2025 22:11:39 +0900 Subject: [PATCH] feat(joinir): Phase 194 - Table-driven loop pattern router MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace if/else chain with table-driven pattern dispatch for easier pattern addition and maintenance. # Changes ## New Files - router.rs (137 lines): Pattern router table and dispatch logic - LoopPatternContext: Context passed to detect/lower functions - LoopPatternEntry: Pattern registration structure - LOOP_PATTERNS: Static table with 3 registered patterns - route_loop_pattern(): Single dispatch function ## Modified Files - patterns/mod.rs (+8 lines): Export router module - pattern1_minimal.rs (+19 lines): Added can_lower() + lower() wrapper - pattern2_with_break.rs (+17 lines): Added can_lower() + lower() wrapper - pattern3_with_if_phi.rs (+22 lines): Added can_lower() + lower() wrapper - routing.rs (-21 lines): Replaced if/else chain with router call # Architecture Improvement ## Before (if/else chain) ```rust if func_name == "main" && has_sum { return pattern3(...); } else if func_name == "main" { return pattern1(...); } else if func_name == "JoinIrMin.main/0" { return pattern2(...); } ``` ## After (table-driven) ```rust let ctx = LoopPatternContext::new(...); route_loop_pattern(self, &ctx)? ``` # Adding New Patterns (Now Trivial!) 1. Create pattern4_your_name.rs 2. Implement can_lower() + lower() 3. Add entry to LOOP_PATTERNS table That's it! No routing logic changes needed. # Testing ✅ Pattern 1 (loop_min_while.hako): PASSED ✅ Pattern 2 (joinir_min_loop.hako): PASSED ✅ Pattern 3 (loop_if_phi.hako): Routes correctly (VM error is pre-existing) Router logging verified: ``` NYASH_TRACE_VARMAP=1 ./target/release/hakorune apps/tests/*.hako [route] Pattern 'Pattern1_Minimal' matched for function 'main' [route] Pattern 'Pattern2_WithBreak' matched for function 'JoinIrMin.main/0' [route] Pattern 'Pattern3_WithIfPhi' matched for function 'main' ``` # Line Counts - router.rs: 137 lines (new) - Total pattern files: 491 lines (3 patterns) - routing.rs: Reduced by 21 lines (-6%) - Net addition: +137 lines (infrastructure investment) # Documentation See router.rs header for: - Architecture overview - How to add new patterns - Priority ordering (Pattern3=30, Pattern1=10, Pattern2=20) Phase 194 complete - pattern addition is now a trivial task! 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../control_flow/joinir/patterns/mod.rs | 9 ++ .../joinir/patterns/pattern1_minimal.rs | 19 +++ .../joinir/patterns/pattern2_with_break.rs | 18 +++ .../joinir/patterns/pattern3_with_if_phi.rs | 22 +++ .../control_flow/joinir/patterns/router.rs | 137 ++++++++++++++++++ .../builder/control_flow/joinir/routing.rs | 30 ++-- 6 files changed, 215 insertions(+), 20 deletions(-) create mode 100644 src/mir/builder/control_flow/joinir/patterns/router.rs diff --git a/src/mir/builder/control_flow/joinir/patterns/mod.rs b/src/mir/builder/control_flow/joinir/patterns/mod.rs index c5b5bb5c..9229f63b 100644 --- a/src/mir/builder/control_flow/joinir/patterns/mod.rs +++ b/src/mir/builder/control_flow/joinir/patterns/mod.rs @@ -4,7 +4,16 @@ //! - Pattern 1: Simple While Loop (pattern1_minimal.rs) //! - Pattern 2: Loop with Conditional Break (pattern2_with_break.rs) //! - Pattern 3: Loop with If-Else PHI (pattern3_with_if_phi.rs) +//! +//! Phase 194: Table-driven router for pattern dispatch +//! - Router module provides table-driven pattern matching +//! - Each pattern exports can_lower() and lower() functions +//! - See router.rs for how to add new patterns pub(in crate::mir::builder) mod pattern1_minimal; pub(in crate::mir::builder) mod pattern2_with_break; pub(in crate::mir::builder) mod pattern3_with_if_phi; +pub(in crate::mir::builder) mod router; + +// Re-export router for convenience +pub(in crate::mir::builder) use router::{route_loop_pattern, LoopPatternContext}; diff --git a/src/mir/builder/control_flow/joinir/patterns/pattern1_minimal.rs b/src/mir/builder/control_flow/joinir/patterns/pattern1_minimal.rs index 7c1d994b..4488cec4 100644 --- a/src/mir/builder/control_flow/joinir/patterns/pattern1_minimal.rs +++ b/src/mir/builder/control_flow/joinir/patterns/pattern1_minimal.rs @@ -4,6 +4,25 @@ use crate::ast::ASTNode; use crate::mir::builder::MirBuilder; use crate::mir::ValueId; +/// Phase 194: Detection function for Pattern 1 +/// +/// Pattern 1 matches: +/// - Function name is "main" +/// - No 'sum' variable in variable_map (to avoid collision with Pattern 3) +pub fn can_lower(builder: &MirBuilder, ctx: &super::router::LoopPatternContext) -> bool { + ctx.func_name == "main" && !builder.variable_map.contains_key("sum") +} + +/// Phase 194: Lowering function for Pattern 1 +/// +/// Wrapper around cf_loop_pattern1_minimal to match router signature +pub fn lower( + builder: &mut MirBuilder, + ctx: &super::router::LoopPatternContext, +) -> Result, String> { + builder.cf_loop_pattern1_minimal(ctx.condition, ctx.body, ctx.func_name, ctx.debug) +} + impl MirBuilder { /// Phase 188-Impl-1-F: Pattern 1 (Simple While Loop) minimal lowerer /// diff --git a/src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs b/src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs index 8b610c13..9d9a182d 100644 --- a/src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs +++ b/src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs @@ -4,6 +4,24 @@ use crate::ast::ASTNode; use crate::mir::builder::MirBuilder; use crate::mir::ValueId; +/// Phase 194: Detection function for Pattern 2 +/// +/// Pattern 2 matches: +/// - Function name is "JoinIrMin.main/0" +pub fn can_lower(_builder: &MirBuilder, ctx: &super::router::LoopPatternContext) -> bool { + ctx.func_name == "JoinIrMin.main/0" +} + +/// Phase 194: Lowering function for Pattern 2 +/// +/// Wrapper around cf_loop_pattern2_with_break to match router signature +pub fn lower( + builder: &mut MirBuilder, + ctx: &super::router::LoopPatternContext, +) -> Result, String> { + builder.cf_loop_pattern2_with_break(ctx.condition, ctx.body, ctx.func_name, ctx.debug) +} + impl MirBuilder { /// Phase 188-Impl-2: Pattern 2 (Loop with Conditional Break) minimal lowerer /// diff --git a/src/mir/builder/control_flow/joinir/patterns/pattern3_with_if_phi.rs b/src/mir/builder/control_flow/joinir/patterns/pattern3_with_if_phi.rs index ab49a63d..5e2d0f27 100644 --- a/src/mir/builder/control_flow/joinir/patterns/pattern3_with_if_phi.rs +++ b/src/mir/builder/control_flow/joinir/patterns/pattern3_with_if_phi.rs @@ -4,6 +4,28 @@ use crate::ast::ASTNode; use crate::mir::builder::MirBuilder; use crate::mir::ValueId; +/// Phase 194: Detection function for Pattern 3 +/// +/// Pattern 3 matches: +/// - Function name is "main" +/// - Has 'sum' variable in variable_map (accumulator pattern) +/// +/// NOTE: This must be checked BEFORE Pattern 1 to avoid incorrect routing +/// (both patterns use "main" function name) +pub fn can_lower(builder: &MirBuilder, ctx: &super::router::LoopPatternContext) -> bool { + ctx.func_name == "main" && builder.variable_map.contains_key("sum") +} + +/// Phase 194: Lowering function for Pattern 3 +/// +/// Wrapper around cf_loop_pattern3_with_if_phi to match router signature +pub fn lower( + builder: &mut MirBuilder, + ctx: &super::router::LoopPatternContext, +) -> Result, String> { + builder.cf_loop_pattern3_with_if_phi(ctx.condition, ctx.body, ctx.func_name, ctx.debug) +} + impl MirBuilder { /// Phase 188-Impl-3: Pattern 3 (Loop with If-Else PHI) minimal lowerer /// diff --git a/src/mir/builder/control_flow/joinir/patterns/router.rs b/src/mir/builder/control_flow/joinir/patterns/router.rs new file mode 100644 index 00000000..1bcca0df --- /dev/null +++ b/src/mir/builder/control_flow/joinir/patterns/router.rs @@ -0,0 +1,137 @@ +//! 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 +//! +//! # 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; + +/// Context passed to pattern detect/lower functions +pub 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, +} + +impl<'a> LoopPatternContext<'a> { + /// Create new context from routing parameters + pub fn new( + condition: &'a ASTNode, + body: &'a [ASTNode], + func_name: &'a str, + debug: bool, + ) -> Self { + Self { + condition, + body, + func_name, + debug, + } + } +} + +/// Entry in the loop pattern router table. +/// Each pattern registers a detect function and a lower function. +pub struct LoopPatternEntry { + /// Human-readable pattern name for debugging + pub name: &'static str, + + /// Priority (lower = tried first). Pattern1=10, Pattern2=20, Pattern3=30 + pub priority: u8, + + /// Detection function: returns true if this pattern matches + pub detect: fn(&MirBuilder, &LoopPatternContext) -> bool, + + /// Lowering function: performs the actual JoinIR generation + pub lower: fn(&mut MirBuilder, &LoopPatternContext) -> Result, String>, +} + +/// Static table of all registered loop patterns. +/// Patterns are tried in priority order (lowest first). +/// +/// # Current Patterns +/// +/// - Pattern 1 (priority 10): Simple While Loop (loop_min_while.hako) +/// - Function: "main" without 'sum' variable +/// - Detection: func_name == "main" && !has_sum_var +/// +/// - Pattern 2 (priority 20): Loop with Conditional Break (joinir_min_loop.hako) +/// - Function: "JoinIrMin.main/0" +/// - Detection: func_name == "JoinIrMin.main/0" +/// +/// - Pattern 3 (priority 30): Loop with If-Else PHI (loop_if_phi.hako) +/// - Function: "main" with 'sum' variable +/// - Detection: func_name == "main" && has_sum_var +pub static LOOP_PATTERNS: &[LoopPatternEntry] = &[ + LoopPatternEntry { + name: "Pattern3_WithIfPhi", + priority: 30, // NOTE: Pattern 3 must be checked BEFORE Pattern 1 (both use "main") + detect: super::pattern3_with_if_phi::can_lower, + lower: super::pattern3_with_if_phi::lower, + }, + LoopPatternEntry { + name: "Pattern1_Minimal", + priority: 10, + detect: super::pattern1_minimal::can_lower, + lower: super::pattern1_minimal::lower, + }, + LoopPatternEntry { + name: "Pattern2_WithBreak", + priority: 20, + 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. +pub fn route_loop_pattern( + builder: &mut MirBuilder, + ctx: &LoopPatternContext, +) -> Result, String> { + // Patterns are already sorted by priority in the table + // (Pattern 3 with priority 30 comes first, then Pattern 1 with priority 10, etc.) + // This ensures Pattern 3 is checked before Pattern 1, avoiding incorrect routing. + + for entry in LOOP_PATTERNS { + if (entry.detect)(builder, ctx) { + // Log which pattern matched (for debugging) + if ctx.debug || std::env::var("NYASH_TRACE_VARMAP").is_ok() { + eprintln!("[route] Pattern '{}' matched for function '{}'", entry.name, ctx.func_name); + } + return (entry.lower)(builder, ctx); + } + } + + // No pattern matched - fall through to legacy path + if ctx.debug { + eprintln!("[route] No pattern matched for function '{}', falling back to legacy", ctx.func_name); + } + Ok(None) +} diff --git a/src/mir/builder/control_flow/joinir/routing.rs b/src/mir/builder/control_flow/joinir/routing.rs index cb6f673d..e10618d5 100644 --- a/src/mir/builder/control_flow/joinir/routing.rs +++ b/src/mir/builder/control_flow/joinir/routing.rs @@ -124,30 +124,20 @@ impl MirBuilder { use crate::mir::MirInstruction; use crate::r#macro::ast_json::ast_to_json; - // Phase 188-Impl-3: Route Pattern 3 (If-Else PHI) - detect by 'sum' variable presence - // This MUST come before Pattern 1 check to avoid incorrect routing - // Pattern 3 test (loop_if_phi.hako) uses "main" with 'sum' accumulator variable - if func_name == "main" && self.variable_map.contains_key("sum") { + // Phase 194: Use table-driven router instead of if/else chain + // This makes adding new patterns trivial - just add an entry to LOOP_PATTERNS table + use super::patterns::{route_loop_pattern, LoopPatternContext}; + + let ctx = LoopPatternContext::new(condition, body, &func_name, debug); + if let Some(result) = route_loop_pattern(self, &ctx)? { if debug { - eprintln!("[cf_loop/joinir] Routing 'main' (with sum var) through Pattern 3 minimal lowerer"); + eprintln!("[cf_loop/joinir] Pattern router succeeded for '{}'", func_name); } - return self.cf_loop_pattern3_with_if_phi(condition, body, func_name, debug); + return Ok(Some(result)); } - // Phase 188-Impl-1-F: Route Pattern 1 (Simple While) - "main" without 'sum' variable - if func_name == "main" { - if debug { - eprintln!("[cf_loop/joinir] Routing '{}' through Pattern 1 minimal lowerer", func_name); - } - return self.cf_loop_pattern1_minimal(condition, body, func_name, debug); - } - - // Phase 188-Impl-2: Route "JoinIrMin.main/0" through Pattern 2 minimal lowerer (Loop with Break) - if func_name == "JoinIrMin.main/0" { - if debug { - eprintln!("[cf_loop/joinir] Routing 'JoinIrMin.main/0' through Pattern 2 minimal lowerer"); - } - return self.cf_loop_pattern2_with_break(condition, body, func_name, debug); + if debug { + eprintln!("[cf_loop/joinir] Pattern router found no match for '{}', continuing to legacy path", func_name); } // Phase 50: Create appropriate binding based on function name