diff --git a/CURRENT_TASK.md b/CURRENT_TASK.md index 39a28f43..fa5fddb6 100644 --- a/CURRENT_TASK.md +++ b/CURRENT_TASK.md @@ -18,6 +18,9 @@ strict/dev 時のみ `[plan/pattern2/promotion_hint:{TrimSeg|DigitPos}]` を観 **2025-12-29: Phase 29aj P0 COMPLETE (PlannerOutcome observability SSOT)** planner outcome(facts+plan)を SSOT 化し、single_planner の観測が planner facts のみに依存するように統一。 +**2025-12-29: Phase 29aj P1 COMPLETE (Remove legacy_rules)** +single_planner の legacy_rules を撤去し、Pattern1/3/4/5/8/9 の抽出を plan/extractors に統一。 + **2025-12-27: Phase 188.3 / Phase 287 P2 COMPLETE (Pattern6 nested loop: merge/latch fixes)** Pattern6(1-level nested loop)の JoinIR→bridge→merge 経路で発生していた `undefined ValueId` と `vm step budget exceeded`(無限ループ)を解消。`apps/tests/phase1883_nested_minimal.hako` が RC=9 を返し、quick 154 PASS を維持。 diff --git a/docs/development/current/main/10-Now.md b/docs/development/current/main/10-Now.md index 9009d569..84bd425f 100644 --- a/docs/development/current/main/10-Now.md +++ b/docs/development/current/main/10-Now.md @@ -2,7 +2,12 @@ ## Current Focus: Phase 29aj(PlannerOutcome SSOT) -Next: Phase 29aj P1(TBD: promotion hint を JoinIR 側の orchestrator へ配線、挙動不変) +Next: Phase 29aj P2(TBD: promotion hint を JoinIR 側の orchestrator へ配線、挙動不変) + +**2025-12-29: Phase 29aj P1 完了** ✅ +- 目的: single_planner の legacy_rules を撤去し、plan extractor を SSOT に集約(仕様不変) +- 実装: `src/mir/builder/control_flow/plan/extractors/pattern{1,3,4,5,8,9}.rs` / `src/mir/builder/control_flow/plan/single_planner/rules.rs` +- 検証: `cargo build --release` / `./tools/smokes/v2/run.sh --profile quick` / `./tools/smokes/v2/profiles/integration/joinir/phase29ae_regression_pack_vm.sh` PASS **2025-12-29: Phase 29aj P0 完了** ✅ - 目的: planner outcome(facts+plan)を SSOT 化して strict 観測の再スキャンを撤去(仕様不変) @@ -36,7 +41,7 @@ Next: Phase 29aj P1(TBD: promotion hint を JoinIR 側の orchestrator へ配 **2025-12-29: Phase 29ai P10 完了** ✅ - 目的: Pattern2 extractor を plan 層へ移設して依存方向を固定(仕様不変) -- 実装: `src/mir/builder/control_flow/plan/extractors/pattern2_break.rs` / `src/mir/builder/control_flow/joinir/patterns/extractors/pattern2.rs` / `src/mir/builder/control_flow/plan/single_planner/legacy_rules/pattern2.rs` +- 実装: `src/mir/builder/control_flow/plan/extractors/pattern2_break.rs` / `src/mir/builder/control_flow/joinir/patterns/extractors/pattern2.rs` / `src/mir/builder/control_flow/plan/single_planner/rules.rs` - 検証: `cargo build --release` / `./tools/smokes/v2/run.sh --profile quick` / `./tools/smokes/v2/profiles/integration/joinir/phase29ae_regression_pack_vm.sh` PASS **2025-12-29: Phase 29ai P9 完了** ✅ diff --git a/docs/development/current/main/30-Backlog.md b/docs/development/current/main/30-Backlog.md index 3b0a255d..aad77c9e 100644 --- a/docs/development/current/main/30-Backlog.md +++ b/docs/development/current/main/30-Backlog.md @@ -19,7 +19,8 @@ Related: - **Phase 29aj(candidate): PlannerOutcome observability SSOT** - 入口: `docs/development/current/main/phases/phase-29aj/README.md` - - Next: Phase 29aj P1(TBD: promotion hint を JoinIR 側の orchestrator へ配線、挙動不変) + - 状況: P0/P1 ✅ 完了 + - Next: Phase 29aj P2(TBD: promotion hint を JoinIR 側の orchestrator へ配線、挙動不変) - **Phase 29ai(candidate): Plan/Frag single-planner(Facts SSOT)** - 入口: `docs/development/current/main/phases/phase-29ai/README.md` diff --git a/docs/development/current/main/phases/phase-29aj/P1-REMOVE-LEGACY-RULES-INSTRUCTIONS.md b/docs/development/current/main/phases/phase-29aj/P1-REMOVE-LEGACY-RULES-INSTRUCTIONS.md new file mode 100644 index 00000000..7f424ccc --- /dev/null +++ b/docs/development/current/main/phases/phase-29aj/P1-REMOVE-LEGACY-RULES-INSTRUCTIONS.md @@ -0,0 +1,59 @@ +# Phase 29aj P1: Remove single_planner legacy_rules (Plan extractor SSOT) + +Date: 2025-12-29 +Status: Ready for execution +Scope: plan extractor ownership + single_planner cleanup(仕様不変) +Goal: plan 層が抽出 SSOT を持ち、single_planner が JoinIR 依存を持たない + +## Objective + +- single_planner の legacy_rules を撤去し、plan/extractors を直接参照する +- Pattern1/3/4/5/8/9 の抽出実装を plan 層へ移し、JoinIR 側は wrapper のみ +- 既定挙動・エラー文字列は不変(抽出実装は移設のみ) + +## Implementation Steps + +### Step 1: plan/extractors へ移設(SSOT 化) + +Files: +- `src/mir/builder/control_flow/plan/extractors/pattern1.rs` +- `src/mir/builder/control_flow/plan/extractors/pattern3.rs` +- `src/mir/builder/control_flow/plan/extractors/pattern4.rs` +- `src/mir/builder/control_flow/plan/extractors/pattern5.rs` +- `src/mir/builder/control_flow/plan/extractors/pattern8.rs` +- `src/mir/builder/control_flow/plan/extractors/pattern9.rs` + +やること: +- JoinIR 側の実装を plan 層へ移動 +- `plan/extractors/mod.rs` に module 登録 + +### Step 2: JoinIR 側は wrapper のみに縮退 + +Files: +- `src/mir/builder/control_flow/joinir/patterns/extractors/pattern{1,3,4,5,8,9}.rs` + +やること: +- `pub(crate) use crate::mir::builder::control_flow::plan::extractors::patternX::*;` + +### Step 3: single_planner の legacy_rules 撤去 + +Files: +- `src/mir/builder/control_flow/plan/single_planner/mod.rs` +- `src/mir/builder/control_flow/plan/single_planner/rules.rs` +- `src/mir/builder/control_flow/plan/single_planner/legacy_rules/*`(削除) + +やること: +- RuleKind::Simple を `(condition, body)` 署名に変更 +- plan/extractors を直接参照 +- Pattern2/6/7 の fallback も plan/extractors へ直結 + +## Acceptance Criteria + +- `cargo build --release` +- `./tools/smokes/v2/run.sh --profile quick` +- `./tools/smokes/v2/profiles/integration/joinir/phase29ae_regression_pack_vm.sh` +- single_planner に `legacy_rules` 参照が残っていない + +## Commit + +- `git add -A && git commit -m "phase29aj(p1): remove legacy_rules via plan extractors"` diff --git a/docs/development/current/main/phases/phase-29aj/README.md b/docs/development/current/main/phases/phase-29aj/README.md index c380c95f..575570ed 100644 --- a/docs/development/current/main/phases/phase-29aj/README.md +++ b/docs/development/current/main/phases/phase-29aj/README.md @@ -9,6 +9,13 @@ Goal: planner の facts/plan を 1 本の outcome に集約し、観測の SSOT - 完了: build_plan_with_facts を追加し、single_planner のタグ出力は outcome.facts 参照に収束 - 検証: `cargo build --release` / `./tools/smokes/v2/run.sh --profile quick` / `./tools/smokes/v2/profiles/integration/joinir/phase29ae_regression_pack_vm.sh` +## P1: Remove single_planner legacy_rules(Plan extractor SSOT) + +- 指示書: `docs/development/current/main/phases/phase-29aj/P1-REMOVE-LEGACY-RULES-INSTRUCTIONS.md` +- ねらい: plan 層に extractor を集約し、single_planner の JoinIR 依存を撤去 +- 完了: Pattern1/3/4/5/8/9 を plan/extractors へ移設、legacy_rules を削除 +- 検証: `cargo build --release` / `./tools/smokes/v2/run.sh --profile quick` / `./tools/smokes/v2/profiles/integration/joinir/phase29ae_regression_pack_vm.sh` + ## Verification (SSOT) - `cargo build --release` diff --git a/src/mir/builder/control_flow/joinir/patterns/extractors/common_helpers.rs b/src/mir/builder/control_flow/joinir/patterns/extractors/common_helpers.rs deleted file mode 100644 index 6708fd44..00000000 --- a/src/mir/builder/control_flow/joinir/patterns/extractors/common_helpers.rs +++ /dev/null @@ -1,10 +0,0 @@ -//! Phase 29ai P10: Wrapper for plan-layer common_helpers (SSOT) -//! -//! Legacy path preserved: -//! `crate::mir::builder::control_flow::joinir::patterns::extractors::common_helpers::*` -//! -//! SSOT implementation lives in: -//! `crate::mir::builder::control_flow::plan::extractors::common_helpers::*` - -pub(crate) use crate::mir::builder::control_flow::plan::extractors::common_helpers::*; - diff --git a/src/mir/builder/control_flow/joinir/patterns/extractors/mod.rs b/src/mir/builder/control_flow/joinir/patterns/extractors/mod.rs index d953b70a..12900574 100644 --- a/src/mir/builder/control_flow/joinir/patterns/extractors/mod.rs +++ b/src/mir/builder/control_flow/joinir/patterns/extractors/mod.rs @@ -24,15 +24,4 @@ pub(crate) mod pattern1; pub(crate) mod pattern2; // Phase 282 P4: Pattern2 extraction pub(crate) mod pattern3; // Phase 282 P5: Pattern3 extraction pub(crate) mod pattern4; // Phase 282 P6: Pattern4 extraction -pub(crate) mod pattern5; // Phase 282 P7: Pattern5 extraction -pub(crate) mod pattern8; // Phase 286 P2.4: Pattern8 Plan extraction -pub(crate) mod pattern9; // Phase 286 P2.3: Pattern9 Plan extraction - -// Phase 282 P9a: Common extraction helpers -// Provides shared utilities for Pattern1, 2, 4, 5 (~400 lines reduction): -// - Control Flow Counting: count_control_flow() - Universal counter -// - Statement Detection: has_break_statement(), has_continue_statement(), etc. -// - Condition Validation: extract_loop_variable(), is_true_literal() -// - Pattern5-Specific Helpers: validate_continue_at_end(), validate_break_in_simple_if() -// Pattern3 deferred to P9b (pattern-specific interpretation logic excluded) -pub(crate) mod common_helpers; +// Pattern5/8/9 extractors moved to plan layer; joinir uses plan::single_planner routing. diff --git a/src/mir/builder/control_flow/joinir/patterns/extractors/pattern1.rs b/src/mir/builder/control_flow/joinir/patterns/extractors/pattern1.rs index 37337952..d5e833ba 100644 --- a/src/mir/builder/control_flow/joinir/patterns/extractors/pattern1.rs +++ b/src/mir/builder/control_flow/joinir/patterns/extractors/pattern1.rs @@ -1,302 +1,3 @@ -//! Phase 282 P3: Pattern1 (Simple While Loop) Extraction -//! Phase 282 P9a: Integrated with common_helpers +//! Phase 29aj P1: Plan-layer extractor wrapper (SSOT) -use crate::ast::{ASTNode, BinaryOperator}; - -// Phase 282 P9a: Use common_helpers -use super::common_helpers::has_control_flow_statement as common_has_control_flow; - -#[derive(Debug, Clone)] -pub(crate) struct Pattern1Parts { - pub loop_var: String, - // Note: condition/body は ctx から再利用(AST 丸コピー不要) -} - -/// Extract Pattern1 (Simple While Loop) parts -/// -/// # Detection Criteria (誤マッチ防止強化版) -/// -/// 1. **Condition**: 比較演算(<, <=, >, >=, ==, !=)で左辺が変数 -/// 2. **Body**: No break/continue/if-else-phi (return is allowed - it's not loop control flow) -/// 3. **Step**: 単純な増減パターン (i = i + 1, i = i - 1 など) -/// -/// # Four-Phase Validation -/// -/// **Phase 1**: Validate condition structure (比較 + 左が変数) -/// **Phase 2**: Validate body (control flow check) -/// **Phase 3**: Validate step pattern (単純増減のみ) -/// **Phase 4**: Extract loop variable -/// -/// # Fail-Fast Rules -/// -/// - `Ok(Some(parts))`: Pattern1 match confirmed -/// - `Ok(None)`: Not Pattern1 (構造不一致 or control flow) -/// - `Err(msg)`: Logic bug (malformed AST) -pub(crate) fn extract_simple_while_parts( - condition: &ASTNode, - body: &[ASTNode], -) -> Result, String> { - // Phase 1: Validate condition structure (比較 + 左が変数) - let loop_var = match validate_condition_structure(condition) { - Some(var) => var, - None => return Ok(None), // 条件が Pattern1 形式でない - }; - - // Phase 2: Validate body (reject control flow) - if has_control_flow_statement(body) { - // Has break/continue/return → Not Pattern1 - return Ok(None); - } - - // Phase 286 P2.6: Reject if-else statements (Pattern3 territory) - // Pattern1 allows simple if without else, but not if-else (which is Pattern3) - if super::common_helpers::has_if_else_statement(body) { - // Has if-else statement → Pattern3 (if-phi merge) - return Ok(None); - } - - // Phase 3: Validate step pattern (単純増減のみ) - if !has_simple_step_pattern(body, &loop_var) { - // Step が複雑 or 存在しない → Not Pattern1 - return Ok(None); - } - - // Phase 4: Return extracted info - Ok(Some(Pattern1Parts { loop_var })) -} - -/// Validate condition: 比較演算 (左辺が変数) -/// -/// Exported for reuse by Pattern3 (Phase 282 P5) -pub(crate) fn validate_condition_structure(condition: &ASTNode) -> Option { - match condition { - ASTNode::BinaryOp { operator, left, .. } => { - // 比較演算子チェック - if !matches!( - operator, - BinaryOperator::Less - | BinaryOperator::LessEqual - | BinaryOperator::Greater - | BinaryOperator::GreaterEqual - | BinaryOperator::Equal - | BinaryOperator::NotEqual - ) { - return None; // 比較でない(算術演算など) - } - - // 左辺が変数であることを確認 - if let ASTNode::Variable { name, .. } = left.as_ref() { - return Some(name.clone()); - } - - None // 左辺が変数でない - } - _ => None, // 比較演算でない - } -} - -/// Validate step: 単純増減パターン (i = i ± const) -fn has_simple_step_pattern(body: &[ASTNode], loop_var: &str) -> bool { - for stmt in body { - if let ASTNode::Assignment { target, value, .. } = stmt { - // target が loop_var か確認 - if let ASTNode::Variable { name, .. } = target.as_ref() { - if name != loop_var { - continue; // 他の変数への代入 - } - - // value が "loop_var ± const" 形式か確認 - if let ASTNode::BinaryOp { - operator, - left, - right, - .. - } = value.as_ref() - { - // 演算子が + or - - if !matches!(operator, BinaryOperator::Add | BinaryOperator::Subtract) { - return false; // 複雑な演算 - } - - // 左辺が loop_var - if let ASTNode::Variable { name: left_var, .. } = left.as_ref() { - if left_var != loop_var { - return false; // i = j + 1 みたいなパターン - } - } else { - return false; // 左辺が変数でない - } - - // 右辺が定数 - if !matches!(right.as_ref(), ASTNode::Literal { .. }) { - return false; // i = i + j みたいなパターン - } - - return true; // ✅ 単純増減パターン確認 - } - - // value が他の形式(複雑な代入) - return false; - } - } - } - - // Step が見つからない → 単純ループでない - false -} - -/// Check if body has control flow statements -/// -/// # Phase 282 P9a: Delegates to common_helpers::has_control_flow_statement -fn has_control_flow_statement(body: &[ASTNode]) -> bool { - common_has_control_flow(body) -} - -// ============================================================================ -// Phase 286 P2.1: Pattern1 → Plan/Frag SSOT extractor -// ============================================================================ - -/// Phase 286 P2.1: Minimal subset extractor for Pattern1 Plan line -/// -/// Supported subset (PoC safety - Compare + Add only): -/// - Loop condition: ` < ` -/// - Loop increment: ` = + ` -/// -/// Returns Ok(None) for unsupported patterns → legacy fallback -pub(crate) fn extract_pattern1_plan( - condition: &ASTNode, - body: &[ASTNode], -) -> Result, String> { - use crate::mir::builder::control_flow::plan::{DomainPlan, Pattern1SimpleWhilePlan}; - - // Step 1: Validate via existing extractor - let parts = extract_simple_while_parts(condition, body)?; - if parts.is_none() { - return Ok(None); // Not Pattern1 → legacy fallback - } - let parts = parts.unwrap(); - - // Step 2: Validate loop condition is ` < ` - if !validate_loop_condition_plan(condition, &parts.loop_var) { - return Ok(None); // Unsupported condition format - } - - // Step 3: Extract loop increment ` = + ` - // Phase 286 P2.2: Use common helper from common_helpers - let loop_increment = match super::common_helpers::extract_loop_increment_plan(body, &parts.loop_var)? { - Some(inc) => inc, - None => return Ok(None), // No loop increment found - }; - - Ok(Some(DomainPlan::Pattern1SimpleWhile(Pattern1SimpleWhilePlan { - loop_var: parts.loop_var, - condition: condition.clone(), - loop_increment, - }))) -} - -/// Validate loop condition: supports ` < ` only -fn validate_loop_condition_plan(cond: &ASTNode, loop_var: &str) -> bool { - use crate::ast::LiteralValue; - - if let ASTNode::BinaryOp { operator, left, right, .. } = cond { - if !matches!(operator, BinaryOperator::Less) { - return false; // Only < supported for PoC - } - - // Left must be the loop variable - if let ASTNode::Variable { name, .. } = left.as_ref() { - if name != loop_var { - return false; - } - } else { - return false; - } - - // Right must be integer literal - if !matches!(right.as_ref(), ASTNode::Literal { value: LiteralValue::Integer(_), .. }) { - return false; - } - - true - } else { - false - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::ast::{LiteralValue, Span}; - - #[test] - fn test_extract_simple_while_success() { - // loop(i < 10) { i = i + 1 } - let condition = ASTNode::BinaryOp { - operator: BinaryOperator::Less, - left: Box::new(ASTNode::Variable { - name: "i".to_string(), - span: Span::unknown(), - }), - right: Box::new(ASTNode::Literal { - value: LiteralValue::Integer(10), - span: Span::unknown(), - }), - span: Span::unknown(), - }; - - let body = vec![ASTNode::Assignment { - target: Box::new(ASTNode::Variable { - name: "i".to_string(), - span: Span::unknown(), - }), - value: Box::new(ASTNode::BinaryOp { - operator: BinaryOperator::Add, - left: Box::new(ASTNode::Variable { - name: "i".to_string(), - span: Span::unknown(), - }), - right: Box::new(ASTNode::Literal { - value: LiteralValue::Integer(1), - span: Span::unknown(), - }), - span: Span::unknown(), - }), - span: Span::unknown(), - }]; - - let result = extract_simple_while_parts(&condition, &body); - assert!(result.is_ok()); - assert!(result.unwrap().is_some()); - } - - #[test] - fn test_extract_with_break_returns_none() { - let condition = ASTNode::Variable { - name: "x".to_string(), - span: Span::unknown(), - }; - let body = vec![ASTNode::Break { - span: Span::unknown(), - }]; - - let result = extract_simple_while_parts(&condition, &body); - assert!(result.is_ok()); - assert!(result.unwrap().is_none()); // Has break → Ok(None) - } - - #[test] - fn test_extract_with_continue_returns_none() { - let condition = ASTNode::Variable { - name: "x".to_string(), - span: Span::unknown(), - }; - let body = vec![ASTNode::Continue { - span: Span::unknown(), - }]; - - let result = extract_simple_while_parts(&condition, &body); - assert!(result.is_ok()); - assert!(result.unwrap().is_none()); // Has continue → Ok(None) - } -} +pub(crate) use crate::mir::builder::control_flow::plan::extractors::pattern1::*; diff --git a/src/mir/builder/control_flow/joinir/patterns/extractors/pattern3.rs b/src/mir/builder/control_flow/joinir/patterns/extractors/pattern3.rs index 1af4b953..95486078 100644 --- a/src/mir/builder/control_flow/joinir/patterns/extractors/pattern3.rs +++ b/src/mir/builder/control_flow/joinir/patterns/extractors/pattern3.rs @@ -1,564 +1,3 @@ -//! Phase 282 P5: Pattern3 (Loop with If-Else PHI) Extraction +//! Phase 29aj P1: Plan-layer extractor wrapper (SSOT) -use crate::ast::ASTNode; - -#[derive(Debug, Clone)] -pub(crate) struct Pattern3Parts { - pub loop_var: String, // Loop variable name (e.g., "i") - pub merged_var: String, // Primary PHI carrier (e.g., "sum") - pub carrier_count: usize, // Validation: 1-2 accumulators - // Note: has_else (always true), phi_like_merge (implicit) omitted - // AST reused from ctx - no duplication -} - -/// Extract Pattern3 (Loop with If-Else PHI) parts -/// -/// # Detection Criteria -/// -/// 1. **Condition**: 比較演算 (left=variable) -/// 2. **Body**: At least one if-else statement (else REQUIRED) -/// 3. **Assignments**: Both branches assign to same variable(s) -/// 4. **Control Flow**: NO break/continue/nested-if (return → Ok(None)) -/// -/// # Four-Phase Validation -/// -/// **Phase 1**: Validate condition structure (reuse Pattern1) -/// **Phase 2**: Find if-else statement (else branch REQUIRED) -/// **Phase 3**: Validate PHI assignments (intersection of then/else) -/// **Phase 4**: Validate NO control flow -/// -/// # Fail-Fast Rules -/// -/// - `Ok(Some(parts))`: Pattern3 confirmed -/// - `Ok(None)`: Not Pattern3 (structural mismatch) -/// - `Err(msg)`: Logic bug (malformed AST) -pub(crate) fn extract_loop_with_if_phi_parts( - condition: &ASTNode, - body: &[ASTNode], -) -> Result, String> { - // Phase 1: Validate condition (reuse Pattern1) - use super::pattern1::validate_condition_structure; - let loop_var = match validate_condition_structure(condition) { - Some(var) => var, - None => return Ok(None), - }; - - // Phase 2: Find if-else statement - let if_stmt = match super::common_helpers::find_if_else_statement(body) { - Some(stmt) => stmt, - None => return Ok(None), // No if-else → Not Pattern3 - }; - - // Phase 3: Validate PHI assignments - let merged_vars = match extract_phi_assignments(if_stmt) { - Some(vars) if !vars.is_empty() => vars, - _ => return Ok(None), // No matching assignments → Not Pattern3 - }; - - // Phase 4a: Check for return (early Ok(None) - let other patterns try) - if super::common_helpers::has_return_statement(body) { - return Ok(None); // Has return → delegate to other patterns - } - - // Phase 4b: Validate NO forbidden control flow (break/continue/nested-if only) - // Pattern3 allows ONE if-else (the PHI pattern) but rejects nested if - if has_forbidden_control_flow_for_pattern3(body) { - return Ok(None); // Has break/continue/nested-if → Not Pattern3 - } - - // Extract primary carrier (first merged var) - let merged_var = merged_vars[0].clone(); - let carrier_count = merged_vars.len(); - - Ok(Some(Pattern3Parts { - loop_var, - merged_var, - carrier_count, - })) -} - -/// Extract variables assigned in BOTH then and else branches -fn extract_phi_assignments(if_stmt: &ASTNode) -> Option> { - let (then_body, else_body) = match if_stmt { - ASTNode::If { - then_body, - else_body: Some(else_body), - .. - } => (then_body, else_body), - _ => return None, - }; - - let then_assignments = extract_assignment_targets(then_body); - let else_assignments = extract_assignment_targets(else_body); - - // Find intersection - let mut merged_vars = Vec::new(); - for var in &then_assignments { - if else_assignments.contains(var) { - merged_vars.push(var.clone()); - } - } - - if merged_vars.is_empty() { - return None; - } - - // Use first occurrence (AST order) - deterministic and meaningful SSOT - // Don't sort alphabetically - preserve natural appearance order - Some(merged_vars) -} - -fn extract_assignment_targets(body: &[ASTNode]) -> Vec { - let mut targets = Vec::new(); - for stmt in body { - if let ASTNode::Assignment { target, .. } = stmt { - if let ASTNode::Variable { name, .. } = target.as_ref() { - targets.push(name.clone()); - } - } - } - targets -} - -/// Check for forbidden control flow (Pattern3-specific) -/// -/// Pattern3 allows ONE if-else (that's the PHI pattern) but rejects: -/// - break/continue statements -/// - NESTED if statements (if inside then/else branches) -/// -/// Return is checked separately (not forbidden, just delegated to other patterns). -fn has_forbidden_control_flow_for_pattern3(body: &[ASTNode]) -> bool { - for stmt in body { - if has_forbidden_control_flow_recursive_p3(stmt) { - return true; - } - } - false -} - -fn has_forbidden_control_flow_recursive_p3(node: &ASTNode) -> bool { - match node { - ASTNode::Break { .. } | ASTNode::Continue { .. } => true, - // Return removed - checked separately - ASTNode::If { - then_body, - else_body, - .. - } => { - // Check for NESTED if (reject) - let has_nested_then = then_body.iter().any(|n| matches!(n, ASTNode::If { .. })); - let has_nested_else = else_body - .as_ref() - .map_or(false, |b| b.iter().any(|n| matches!(n, ASTNode::If { .. }))); - - if has_nested_then || has_nested_else { - return true; - } - - // Check control flow INSIDE branches - then_body.iter().any(has_forbidden_control_flow_recursive_p3) - || else_body - .as_ref() - .map_or(false, |b| b.iter().any(has_forbidden_control_flow_recursive_p3)) - } - - _ => false, - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::ast::{BinaryOperator, LiteralValue, Span}; - - #[test] - fn test_extract_if_phi_success() { - // loop(i < 3) { if (i > 0) { sum = sum + 1 } else { sum = sum + 0 } i = i + 1 } - let condition = ASTNode::BinaryOp { - operator: BinaryOperator::Less, - left: Box::new(ASTNode::Variable { - name: "i".to_string(), - span: Span::unknown(), - }), - right: Box::new(ASTNode::Literal { - value: LiteralValue::Integer(3), - span: Span::unknown(), - }), - span: Span::unknown(), - }; - - let body = vec![ - ASTNode::If { - condition: Box::new(ASTNode::BinaryOp { - operator: BinaryOperator::Greater, - left: Box::new(ASTNode::Variable { - name: "i".to_string(), - span: Span::unknown(), - }), - right: Box::new(ASTNode::Literal { - value: LiteralValue::Integer(0), - span: Span::unknown(), - }), - span: Span::unknown(), - }), - then_body: vec![ASTNode::Assignment { - target: Box::new(ASTNode::Variable { - name: "sum".to_string(), - span: Span::unknown(), - }), - value: Box::new(ASTNode::BinaryOp { - operator: BinaryOperator::Add, - left: Box::new(ASTNode::Variable { - name: "sum".to_string(), - span: Span::unknown(), - }), - right: Box::new(ASTNode::Literal { - value: LiteralValue::Integer(1), - span: Span::unknown(), - }), - span: Span::unknown(), - }), - span: Span::unknown(), - }], - else_body: Some(vec![ASTNode::Assignment { - target: Box::new(ASTNode::Variable { - name: "sum".to_string(), - span: Span::unknown(), - }), - value: Box::new(ASTNode::BinaryOp { - operator: BinaryOperator::Add, - left: Box::new(ASTNode::Variable { - name: "sum".to_string(), - span: Span::unknown(), - }), - right: Box::new(ASTNode::Literal { - value: LiteralValue::Integer(0), - span: Span::unknown(), - }), - span: Span::unknown(), - }), - span: Span::unknown(), - }]), - span: Span::unknown(), - }, - ASTNode::Assignment { - target: Box::new(ASTNode::Variable { - name: "i".to_string(), - span: Span::unknown(), - }), - value: Box::new(ASTNode::BinaryOp { - operator: BinaryOperator::Add, - left: Box::new(ASTNode::Variable { - name: "i".to_string(), - span: Span::unknown(), - }), - right: Box::new(ASTNode::Literal { - value: LiteralValue::Integer(1), - span: Span::unknown(), - }), - span: Span::unknown(), - }), - span: Span::unknown(), - }, - ]; - - let result = extract_loop_with_if_phi_parts(&condition, &body); - assert!(result.is_ok()); - let parts = result.unwrap(); - assert!(parts.is_some()); - let parts = parts.unwrap(); - assert_eq!(parts.loop_var, "i"); - assert_eq!(parts.merged_var, "sum"); - assert_eq!(parts.carrier_count, 1); - } - - #[test] - fn test_extract_no_else_returns_none() { - // loop(i < 3) { if (i > 0) { sum = sum + 1 } i = i + 1 } // No else - let condition = ASTNode::BinaryOp { - operator: BinaryOperator::Less, - left: Box::new(ASTNode::Variable { - name: "i".to_string(), - span: Span::unknown(), - }), - right: Box::new(ASTNode::Literal { - value: LiteralValue::Integer(3), - span: Span::unknown(), - }), - span: Span::unknown(), - }; - - let body = vec![ASTNode::If { - condition: Box::new(ASTNode::BinaryOp { - operator: BinaryOperator::Greater, - left: Box::new(ASTNode::Variable { - name: "i".to_string(), - span: Span::unknown(), - }), - right: Box::new(ASTNode::Literal { - value: LiteralValue::Integer(0), - span: Span::unknown(), - }), - span: Span::unknown(), - }), - then_body: vec![ASTNode::Assignment { - target: Box::new(ASTNode::Variable { - name: "sum".to_string(), - span: Span::unknown(), - }), - value: Box::new(ASTNode::BinaryOp { - operator: BinaryOperator::Add, - left: Box::new(ASTNode::Variable { - name: "sum".to_string(), - span: Span::unknown(), - }), - right: Box::new(ASTNode::Literal { - value: LiteralValue::Integer(1), - span: Span::unknown(), - }), - span: Span::unknown(), - }), - span: Span::unknown(), - }], - else_body: None, // ← No else - span: Span::unknown(), - }]; - - let result = extract_loop_with_if_phi_parts(&condition, &body); - assert!(result.is_ok()); - assert!(result.unwrap().is_none()); // No else → Not Pattern3 - } - - #[test] - fn test_extract_different_vars_returns_none() { - // loop(i < 3) { if (i > 0) { sum = 1 } else { count = 1 } i = i + 1 } - let condition = ASTNode::BinaryOp { - operator: BinaryOperator::Less, - left: Box::new(ASTNode::Variable { - name: "i".to_string(), - span: Span::unknown(), - }), - right: Box::new(ASTNode::Literal { - value: LiteralValue::Integer(3), - span: Span::unknown(), - }), - span: Span::unknown(), - }; - - let body = vec![ASTNode::If { - condition: Box::new(ASTNode::BinaryOp { - operator: BinaryOperator::Greater, - left: Box::new(ASTNode::Variable { - name: "i".to_string(), - span: Span::unknown(), - }), - right: Box::new(ASTNode::Literal { - value: LiteralValue::Integer(0), - span: Span::unknown(), - }), - span: Span::unknown(), - }), - then_body: vec![ASTNode::Assignment { - target: Box::new(ASTNode::Variable { - name: "sum".to_string(), // then: sum - span: Span::unknown(), - }), - value: Box::new(ASTNode::Literal { - value: LiteralValue::Integer(1), - span: Span::unknown(), - }), - span: Span::unknown(), - }], - else_body: Some(vec![ASTNode::Assignment { - target: Box::new(ASTNode::Variable { - name: "count".to_string(), // else: count (different!) - span: Span::unknown(), - }), - value: Box::new(ASTNode::Literal { - value: LiteralValue::Integer(1), - span: Span::unknown(), - }), - span: Span::unknown(), - }]), - span: Span::unknown(), - }]; - - let result = extract_loop_with_if_phi_parts(&condition, &body); - assert!(result.is_ok()); - assert!(result.unwrap().is_none()); // Different vars → Not Pattern3 - } - - #[test] - fn test_extract_with_break_returns_none() { - // loop(i < 3) { if (i > 0) { sum = sum + 1; break } else { sum = sum + 0 } i = i + 1 } - let condition = ASTNode::BinaryOp { - operator: BinaryOperator::Less, - left: Box::new(ASTNode::Variable { - name: "i".to_string(), - span: Span::unknown(), - }), - right: Box::new(ASTNode::Literal { - value: LiteralValue::Integer(3), - span: Span::unknown(), - }), - span: Span::unknown(), - }; - - let body = vec![ASTNode::If { - condition: Box::new(ASTNode::BinaryOp { - operator: BinaryOperator::Greater, - left: Box::new(ASTNode::Variable { - name: "i".to_string(), - span: Span::unknown(), - }), - right: Box::new(ASTNode::Literal { - value: LiteralValue::Integer(0), - span: Span::unknown(), - }), - span: Span::unknown(), - }), - then_body: vec![ - ASTNode::Assignment { - target: Box::new(ASTNode::Variable { - name: "sum".to_string(), - span: Span::unknown(), - }), - value: Box::new(ASTNode::BinaryOp { - operator: BinaryOperator::Add, - left: Box::new(ASTNode::Variable { - name: "sum".to_string(), - span: Span::unknown(), - }), - right: Box::new(ASTNode::Literal { - value: LiteralValue::Integer(1), - span: Span::unknown(), - }), - span: Span::unknown(), - }), - span: Span::unknown(), - }, - ASTNode::Break { - span: Span::unknown(), - }, // ← break - ], - else_body: Some(vec![ASTNode::Assignment { - target: Box::new(ASTNode::Variable { - name: "sum".to_string(), - span: Span::unknown(), - }), - value: Box::new(ASTNode::BinaryOp { - operator: BinaryOperator::Add, - left: Box::new(ASTNode::Variable { - name: "sum".to_string(), - span: Span::unknown(), - }), - right: Box::new(ASTNode::Literal { - value: LiteralValue::Integer(0), - span: Span::unknown(), - }), - span: Span::unknown(), - }), - span: Span::unknown(), - }]), - span: Span::unknown(), - }]; - - let result = extract_loop_with_if_phi_parts(&condition, &body); - assert!(result.is_ok()); - assert!(result.unwrap().is_none()); // Has break → Not Pattern3 - } -} - -// ============================================================================ -// Phase 286 P2.6: Pattern3 → Plan/Frag SSOT extractor -// ============================================================================ - -/// Phase 286 P2.6: Extract Pattern3 (Loop with If-Phi) Plan -/// -/// Leverages existing `extract_loop_with_if_phi_parts()` for validation, -/// then extracts detailed AST components for Plan normalization. -/// -/// # Returns -/// - Ok(Some(DomainPlan::Pattern3IfPhi)) if Pattern3 match confirmed -/// - Ok(None) if not Pattern3 (structural mismatch) -/// - Err if malformed AST (rare) -pub(crate) fn extract_pattern3_plan( - condition: &ASTNode, - body: &[ASTNode], -) -> Result, String> { - use crate::mir::builder::control_flow::plan::{DomainPlan, Pattern3IfPhiPlan}; - - // Step 1: Validate via existing extractor - let parts = extract_loop_with_if_phi_parts(condition, body)?; - if parts.is_none() { - return Ok(None); // Not Pattern3 → fallback - } - let parts = parts.unwrap(); - - // Step 2: Extract if-else statement (validated to exist) - let if_stmt = super::common_helpers::find_if_else_statement(body) - .ok_or_else(|| "Pattern3: if-else statement not found after validation".to_string())?; - - // Step 3: Extract if condition and branch updates - let (if_condition, then_update, else_update) = match if_stmt { - ASTNode::If { - condition: if_cond, - then_body, - else_body: Some(else_body), - .. - } => { - // Extract carrier update from then branch - let then_update = extract_single_update(then_body, &parts.merged_var)?; - - // Extract carrier update from else branch - let else_update = extract_single_update(else_body, &parts.merged_var)?; - - (if_cond.as_ref().clone(), then_update, else_update) - } - _ => { - return Err("Pattern3: if-else structure mismatch after validation".to_string()); - } - }; - - // Step 4: Extract loop increment - let loop_increment = match super::common_helpers::extract_loop_increment_plan(body, &parts.loop_var)? { - Some(inc) => inc, - None => { - return Err(format!( - "Pattern3: loop increment not found for variable '{}'", - parts.loop_var - )); - } - }; - - Ok(Some(DomainPlan::Pattern3IfPhi(Pattern3IfPhiPlan { - loop_var: parts.loop_var, - carrier_var: parts.merged_var, - condition: condition.clone(), - if_condition, - then_update, - else_update, - loop_increment, - }))) -} - -/// Extract update expression from a branch body -/// -/// Expects a single assignment: `carrier = ` -/// Returns the RHS expression AST -fn extract_single_update(body: &[ASTNode], carrier_var: &str) -> Result { - for stmt in body { - if let ASTNode::Assignment { target, value, .. } = stmt { - if let ASTNode::Variable { name, .. } = target.as_ref() { - if name == carrier_var { - return Ok(value.as_ref().clone()); - } - } - } - } - - Err(format!( - "Pattern3: carrier update not found for variable '{}' in branch", - carrier_var - )) -} +pub(crate) use crate::mir::builder::control_flow::plan::extractors::pattern3::*; diff --git a/src/mir/builder/control_flow/joinir/patterns/extractors/pattern4.rs b/src/mir/builder/control_flow/joinir/patterns/extractors/pattern4.rs index d64cb785..1cf2aa70 100644 --- a/src/mir/builder/control_flow/joinir/patterns/extractors/pattern4.rs +++ b/src/mir/builder/control_flow/joinir/patterns/extractors/pattern4.rs @@ -1,418 +1,3 @@ -//! Phase 282 P6: Pattern4 (Loop with Continue) Extraction -//! Phase 282 P9a: Integrated with common_helpers +//! Phase 29aj P1: Plan-layer extractor wrapper (SSOT) -use crate::ast::ASTNode; - -// Phase 282 P9a: Use common_helpers -// Phase 284 P1: has_return_statement removed (now handled by return_collector SSOT) -use super::common_helpers::{count_control_flow, has_break_statement, ControlFlowDetector}; - -#[derive(Debug, Clone)] -pub(crate) struct Pattern4Parts { - pub loop_var: String, // Extracted from condition (i < n) - pub continue_count: usize, // Must be >= 1 for Pattern4 -} - -/// Extract Pattern4 (Loop with Continue) parts -/// -/// # Detection Criteria -/// -/// 1. **Condition**: Comparison operator with left=variable (Pattern1 reuse) -/// 2. **Body**: At least 1 continue statement (Pattern4 必要条件) -/// 3. **Reject**: break → Pattern2 territory -/// 4. **Fail-Fast**: return → Err (close-but-unsupported, Phase 142 P2 pending) -/// -/// # Five-Phase Validation (USER CORRECTION: Recursive detection required!) -/// -/// **Phase 1**: Validate condition structure (reuse Pattern1) -/// **Phase 2**: Validate HAS continue (count >= 1, **recursive**) -/// **Phase 3**: Validate NO break (fail-fast or Ok(None), **recursive**) -/// **Phase 4**: Check for return (Err if found, **recursive**) -/// **Phase 5**: Return extracted parts -/// -/// # Fail-Fast Rules -/// -/// - `Ok(Some(parts))`: Pattern4 match confirmed -/// - `Ok(None)`: Not Pattern4 (wrong structure) -/// - `Err(msg)`: Close-but-unsupported (e.g., return found) or malformed AST -/// -/// # Note -/// -/// - USER CORRECTION: All detection is recursive (handles `if { continue }` patterns) -/// - USER CORRECTION: No loop_var update validation (deferred to Pattern4CarrierAnalyzer) -/// - Nested loop's continue/break statements are NOT counted (only current loop) -pub(crate) fn extract_loop_with_continue_parts( - condition: &ASTNode, - body: &[ASTNode], -) -> Result, String> { - // Phase 1: Validate condition structure (reuse Pattern1's validator) - use super::pattern1::validate_condition_structure; - - let loop_var = match validate_condition_structure(condition) { - Some(var) => var, - None => return Ok(None), // Not comparison pattern → Other patterns - }; - - // Phase 2: Count continue statements (USER CORRECTION: Recursive!) - let continue_count = count_continue_statements_recursive(body); - if continue_count == 0 { - return Ok(None); // No continue → Pattern1/2 territory - } - - // Phase 3: Check for break (USER CORRECTION: Recursive!) - if has_break_statement_recursive(body) { - // Has break → Pattern2 territory (break takes priority) - return Ok(None); - } - - // Phase 284 P1: Return check removed - now handled by return_collector SSOT - // in conversion_pipeline.rs (JoinIR line common entry point) - - // Phase 5: Return extracted parts - // USER CORRECTION: No loop_var update validation - defer to Pattern4CarrierAnalyzer - Ok(Some(Pattern4Parts { - loop_var, - continue_count, - })) -} - -// ============================================================================ -// Helper Functions (Internal) - USER CORRECTION: All must be recursive! -// ============================================================================ - -/// Count continue statements recursively -/// -/// # Phase 282 P9a: Delegates to common_helpers::count_control_flow -/// -/// - Skips nested loop bodies (default behavior) -fn count_continue_statements_recursive(body: &[ASTNode]) -> usize { - count_control_flow(body, ControlFlowDetector::default()).continue_count -} - -/// Check recursively if body has break statement -/// -/// # Phase 282 P9a: Delegates to common_helpers::has_break_statement -fn has_break_statement_recursive(body: &[ASTNode]) -> bool { - has_break_statement(body) -} - -// Phase 284 P1: has_return_statement_recursive removed -// Return detection now handled by return_collector SSOT in conversion_pipeline.rs - -// ============================================================================ -// Phase 286 P2: Pattern4 → Plan/Frag SSOT extractor -// ============================================================================ - -/// Phase 286 P2: Minimal subset extractor for Pattern4 Plan line -/// -/// Supported subset (PoC safety - Compare + Add only): -/// - Loop condition: ` < ` -/// - Continue condition: ` == ` (NO Mod operator) -/// - Carrier update: ` = + ` (accumulator pattern) -/// - Loop increment: ` = + ` -/// -/// Returns Ok(None) for unsupported patterns → legacy fallback -pub(crate) fn extract_pattern4_plan( - condition: &ASTNode, - body: &[ASTNode], -) -> Result, String> { - use crate::mir::builder::control_flow::plan::{DomainPlan, Pattern4ContinuePlan}; - - // Step 1: Validate via existing extractor - let parts = extract_loop_with_continue_parts(condition, body)?; - if parts.is_none() { - return Ok(None); // Not Pattern4 → legacy fallback - } - - // Step 2: Extract loop variable from condition `i < N` - let loop_var = match extract_loop_condition_plan(condition)? { - Some((var, _bound)) => var, - None => return Ok(None), // Unsupported condition - }; - - // Step 3: Find continue condition from body - let continue_cond = match find_continue_condition_plan(body)? { - Some(cond) => cond, - None => return Ok(None), // No continue condition found - }; - - // Step 4: Extract carrier updates (accumulator pattern) - let carrier_updates = extract_carrier_updates_plan(body, &loop_var)?; - if carrier_updates.is_empty() { - return Ok(None); // No carrier → unsupported - } - - // Step 5: Extract loop increment `i = i + 1` - // Phase 286 P2.2: Use common helper from common_helpers - let loop_increment = match super::common_helpers::extract_loop_increment_plan(body, &loop_var)? { - Some(inc) => inc, - None => return Ok(None), // No loop increment found - }; - - Ok(Some(DomainPlan::Pattern4Continue(Pattern4ContinuePlan { - loop_var: loop_var.clone(), - carrier_vars: carrier_updates.keys().cloned().collect(), - condition: condition.clone(), - continue_condition: continue_cond, - carrier_updates, - loop_increment, - }))) -} - -/// Extract loop condition: supports ` < ` only -fn extract_loop_condition_plan(cond: &ASTNode) -> Result, String> { - use crate::ast::{BinaryOperator, LiteralValue}; - - if let ASTNode::BinaryOp { operator, left, right, .. } = cond { - if !matches!(operator, BinaryOperator::Less) { - return Ok(None); // Only < supported - } - - let var_name = if let ASTNode::Variable { name, .. } = left.as_ref() { - name.clone() - } else { - return Ok(None); - }; - - let bound = if let ASTNode::Literal { value: LiteralValue::Integer(n), .. } = right.as_ref() { - *n - } else { - return Ok(None); - }; - - Ok(Some((var_name, bound))) - } else { - Ok(None) - } -} - -/// Find continue condition in if statement -fn find_continue_condition_plan(body: &[ASTNode]) -> Result, String> { - for stmt in body { - if let ASTNode::If { condition, then_body, .. } = stmt { - for then_stmt in then_body { - if matches!(then_stmt, ASTNode::Continue { .. }) { - return Ok(Some(condition.as_ref().clone())); - } - } - } - } - Ok(None) -} - -/// Extract carrier updates: ` = + ` pattern only -fn extract_carrier_updates_plan( - body: &[ASTNode], - loop_var: &str, -) -> Result, String> { - use crate::ast::BinaryOperator; - use std::collections::BTreeMap; - - let mut updates = BTreeMap::new(); - - for stmt in body { - if let ASTNode::Assignment { target, value, .. } = stmt { - let var_name = if let ASTNode::Variable { name, .. } = target.as_ref() { - name.clone() - } else { - continue; - }; - - // Skip loop variable itself - if var_name == loop_var { - continue; - } - - // Check for accumulator pattern: `var = var + x` - if let ASTNode::BinaryOp { operator, left, .. } = value.as_ref() { - if matches!(operator, BinaryOperator::Add) { - if let ASTNode::Variable { name, .. } = left.as_ref() { - if name == &var_name { - updates.insert(var_name, value.as_ref().clone()); - } - } - } - } - } - } - - Ok(updates) -} - -// ============================================================================ -// Unit Tests -// ============================================================================ - -#[cfg(test)] -mod tests { - use super::*; - use crate::ast::{BinaryOperator, LiteralValue, Span}; - - /// Helper: Create comparison condition (var < limit) - fn make_condition(var: &str, limit: i64) -> ASTNode { - ASTNode::BinaryOp { - operator: BinaryOperator::Less, - left: Box::new(ASTNode::Variable { - name: var.to_string(), - span: Span::unknown(), - }), - right: Box::new(ASTNode::Literal { - value: LiteralValue::Integer(limit), - span: Span::unknown(), - }), - span: Span::unknown(), - } - } - - /// Helper: Create if-continue block (if (skip) { continue }) - fn make_if_continue() -> ASTNode { - ASTNode::If { - condition: Box::new(ASTNode::Variable { - name: "skip".to_string(), - span: Span::unknown(), - }), - then_body: vec![ASTNode::Continue { - span: Span::unknown(), - }], - else_body: None, - span: Span::unknown(), - } - } - - /// Helper: Create if-break block (if (done) { break }) - fn make_if_break() -> ASTNode { - ASTNode::If { - condition: Box::new(ASTNode::Variable { - name: "done".to_string(), - span: Span::unknown(), - }), - then_body: vec![ASTNode::Break { - span: Span::unknown(), - }], - else_body: None, - span: Span::unknown(), - } - } - - /// Helper: Create increment (i = i + 1) - fn make_increment(var: &str) -> ASTNode { - ASTNode::Assignment { - target: Box::new(ASTNode::Variable { - name: var.to_string(), - span: Span::unknown(), - }), - value: Box::new(ASTNode::BinaryOp { - operator: BinaryOperator::Add, - left: Box::new(ASTNode::Variable { - name: var.to_string(), - span: Span::unknown(), - }), - right: Box::new(ASTNode::Literal { - value: LiteralValue::Integer(1), - span: Span::unknown(), - }), - span: Span::unknown(), - }), - span: Span::unknown(), - } - } - - #[test] - fn test_pattern4_continue_success() { - // loop(i < 10) { if (skip) { continue } i = i + 1 } - let condition = make_condition("i", 10); - let body = vec![make_if_continue(), make_increment("i")]; - - let result = extract_loop_with_continue_parts(&condition, &body); - assert!(result.is_ok()); - let parts = result.unwrap(); - assert!(parts.is_some()); - - let parts = parts.unwrap(); - assert_eq!(parts.loop_var, "i"); - assert_eq!(parts.continue_count, 1); - } - - #[test] - fn test_pattern4_no_continue_returns_none() { - // loop(i < 10) { i = i + 1 } → Not Pattern4 (Pattern1 territory) - let condition = make_condition("i", 10); - let body = vec![make_increment("i")]; - - let result = extract_loop_with_continue_parts(&condition, &body); - assert!(result.is_ok()); - assert!(result.unwrap().is_none()); // No continue → Not Pattern4 - } - - #[test] - fn test_pattern4_with_break_returns_none() { - // loop(i < 10) { if (done) { break } i = i + 1 } → Pattern2 - let condition = make_condition("i", 10); - let body = vec![make_if_break(), make_increment("i")]; - - let result = extract_loop_with_continue_parts(&condition, &body); - assert!(result.is_ok()); - assert!(result.unwrap().is_none()); // Has break → Pattern2 - } - - #[test] - fn test_pattern4_with_return_is_allowed() { - // Phase 284 P1: loop(i < 10) { if (done) { return } i = i + 1 } → Ok(Some(...)) - // Return check moved to return_collector SSOT in conversion_pipeline.rs - let condition = make_condition("i", 10); - let body = vec![ - ASTNode::If { - condition: Box::new(ASTNode::Variable { - name: "done".to_string(), - span: Span::unknown(), - }), - then_body: vec![ASTNode::Return { - value: None, - span: Span::unknown(), - }], - else_body: None, - span: Span::unknown(), - }, - make_if_continue(), - make_increment("i"), - ]; - - let result = extract_loop_with_continue_parts(&condition, &body); - assert!(result.is_ok()); // Phase 284 P1: Now allowed at extractor level - let parts = result.unwrap(); - assert!(parts.is_some()); // Pattern4 extraction succeeds - let parts = parts.unwrap(); - assert_eq!(parts.loop_var, "i"); - assert_eq!(parts.continue_count, 1); - } - - #[test] - fn test_pattern4_multi_continue_count() { - // loop(i < 10) { if (a) { continue } if (b) { continue } i = i + 1 } - let condition = make_condition("i", 10); - let body = vec![ - make_if_continue(), // First continue - ASTNode::If { - condition: Box::new(ASTNode::Variable { - name: "b".to_string(), - span: Span::unknown(), - }), - then_body: vec![ASTNode::Continue { - span: Span::unknown(), - }], - else_body: None, - span: Span::unknown(), - }, // Second continue - make_increment("i"), - ]; - - let result = extract_loop_with_continue_parts(&condition, &body); - assert!(result.is_ok()); - let parts = result.unwrap(); - assert!(parts.is_some()); - - let parts = parts.unwrap(); - assert_eq!(parts.loop_var, "i"); - assert_eq!(parts.continue_count, 2); // Two continue statements detected - } -} +pub(crate) use crate::mir::builder::control_flow::plan::extractors::pattern4::*; diff --git a/src/mir/builder/control_flow/plan/extractors/mod.rs b/src/mir/builder/control_flow/plan/extractors/mod.rs index c621c4d2..75aa8b29 100644 --- a/src/mir/builder/control_flow/plan/extractors/mod.rs +++ b/src/mir/builder/control_flow/plan/extractors/mod.rs @@ -1,6 +1,12 @@ -//! Phase 29ai P6: Plan-layer extractors (Pattern6/7) +//! Phase 29ai P6+: Plan-layer extractors (Pattern1-9) pub(in crate::mir::builder) mod common_helpers; +pub(in crate::mir::builder) mod pattern1; +pub(in crate::mir::builder) mod pattern3; +pub(in crate::mir::builder) mod pattern4; +pub(in crate::mir::builder) mod pattern5; pub(in crate::mir::builder) mod pattern6_scan_with_init; pub(in crate::mir::builder) mod pattern7_split_scan; pub(in crate::mir::builder) mod pattern2_break; +pub(in crate::mir::builder) mod pattern8; +pub(in crate::mir::builder) mod pattern9; diff --git a/src/mir/builder/control_flow/plan/extractors/pattern1.rs b/src/mir/builder/control_flow/plan/extractors/pattern1.rs new file mode 100644 index 00000000..37337952 --- /dev/null +++ b/src/mir/builder/control_flow/plan/extractors/pattern1.rs @@ -0,0 +1,302 @@ +//! Phase 282 P3: Pattern1 (Simple While Loop) Extraction +//! Phase 282 P9a: Integrated with common_helpers + +use crate::ast::{ASTNode, BinaryOperator}; + +// Phase 282 P9a: Use common_helpers +use super::common_helpers::has_control_flow_statement as common_has_control_flow; + +#[derive(Debug, Clone)] +pub(crate) struct Pattern1Parts { + pub loop_var: String, + // Note: condition/body は ctx から再利用(AST 丸コピー不要) +} + +/// Extract Pattern1 (Simple While Loop) parts +/// +/// # Detection Criteria (誤マッチ防止強化版) +/// +/// 1. **Condition**: 比較演算(<, <=, >, >=, ==, !=)で左辺が変数 +/// 2. **Body**: No break/continue/if-else-phi (return is allowed - it's not loop control flow) +/// 3. **Step**: 単純な増減パターン (i = i + 1, i = i - 1 など) +/// +/// # Four-Phase Validation +/// +/// **Phase 1**: Validate condition structure (比較 + 左が変数) +/// **Phase 2**: Validate body (control flow check) +/// **Phase 3**: Validate step pattern (単純増減のみ) +/// **Phase 4**: Extract loop variable +/// +/// # Fail-Fast Rules +/// +/// - `Ok(Some(parts))`: Pattern1 match confirmed +/// - `Ok(None)`: Not Pattern1 (構造不一致 or control flow) +/// - `Err(msg)`: Logic bug (malformed AST) +pub(crate) fn extract_simple_while_parts( + condition: &ASTNode, + body: &[ASTNode], +) -> Result, String> { + // Phase 1: Validate condition structure (比較 + 左が変数) + let loop_var = match validate_condition_structure(condition) { + Some(var) => var, + None => return Ok(None), // 条件が Pattern1 形式でない + }; + + // Phase 2: Validate body (reject control flow) + if has_control_flow_statement(body) { + // Has break/continue/return → Not Pattern1 + return Ok(None); + } + + // Phase 286 P2.6: Reject if-else statements (Pattern3 territory) + // Pattern1 allows simple if without else, but not if-else (which is Pattern3) + if super::common_helpers::has_if_else_statement(body) { + // Has if-else statement → Pattern3 (if-phi merge) + return Ok(None); + } + + // Phase 3: Validate step pattern (単純増減のみ) + if !has_simple_step_pattern(body, &loop_var) { + // Step が複雑 or 存在しない → Not Pattern1 + return Ok(None); + } + + // Phase 4: Return extracted info + Ok(Some(Pattern1Parts { loop_var })) +} + +/// Validate condition: 比較演算 (左辺が変数) +/// +/// Exported for reuse by Pattern3 (Phase 282 P5) +pub(crate) fn validate_condition_structure(condition: &ASTNode) -> Option { + match condition { + ASTNode::BinaryOp { operator, left, .. } => { + // 比較演算子チェック + if !matches!( + operator, + BinaryOperator::Less + | BinaryOperator::LessEqual + | BinaryOperator::Greater + | BinaryOperator::GreaterEqual + | BinaryOperator::Equal + | BinaryOperator::NotEqual + ) { + return None; // 比較でない(算術演算など) + } + + // 左辺が変数であることを確認 + if let ASTNode::Variable { name, .. } = left.as_ref() { + return Some(name.clone()); + } + + None // 左辺が変数でない + } + _ => None, // 比較演算でない + } +} + +/// Validate step: 単純増減パターン (i = i ± const) +fn has_simple_step_pattern(body: &[ASTNode], loop_var: &str) -> bool { + for stmt in body { + if let ASTNode::Assignment { target, value, .. } = stmt { + // target が loop_var か確認 + if let ASTNode::Variable { name, .. } = target.as_ref() { + if name != loop_var { + continue; // 他の変数への代入 + } + + // value が "loop_var ± const" 形式か確認 + if let ASTNode::BinaryOp { + operator, + left, + right, + .. + } = value.as_ref() + { + // 演算子が + or - + if !matches!(operator, BinaryOperator::Add | BinaryOperator::Subtract) { + return false; // 複雑な演算 + } + + // 左辺が loop_var + if let ASTNode::Variable { name: left_var, .. } = left.as_ref() { + if left_var != loop_var { + return false; // i = j + 1 みたいなパターン + } + } else { + return false; // 左辺が変数でない + } + + // 右辺が定数 + if !matches!(right.as_ref(), ASTNode::Literal { .. }) { + return false; // i = i + j みたいなパターン + } + + return true; // ✅ 単純増減パターン確認 + } + + // value が他の形式(複雑な代入) + return false; + } + } + } + + // Step が見つからない → 単純ループでない + false +} + +/// Check if body has control flow statements +/// +/// # Phase 282 P9a: Delegates to common_helpers::has_control_flow_statement +fn has_control_flow_statement(body: &[ASTNode]) -> bool { + common_has_control_flow(body) +} + +// ============================================================================ +// Phase 286 P2.1: Pattern1 → Plan/Frag SSOT extractor +// ============================================================================ + +/// Phase 286 P2.1: Minimal subset extractor for Pattern1 Plan line +/// +/// Supported subset (PoC safety - Compare + Add only): +/// - Loop condition: ` < ` +/// - Loop increment: ` = + ` +/// +/// Returns Ok(None) for unsupported patterns → legacy fallback +pub(crate) fn extract_pattern1_plan( + condition: &ASTNode, + body: &[ASTNode], +) -> Result, String> { + use crate::mir::builder::control_flow::plan::{DomainPlan, Pattern1SimpleWhilePlan}; + + // Step 1: Validate via existing extractor + let parts = extract_simple_while_parts(condition, body)?; + if parts.is_none() { + return Ok(None); // Not Pattern1 → legacy fallback + } + let parts = parts.unwrap(); + + // Step 2: Validate loop condition is ` < ` + if !validate_loop_condition_plan(condition, &parts.loop_var) { + return Ok(None); // Unsupported condition format + } + + // Step 3: Extract loop increment ` = + ` + // Phase 286 P2.2: Use common helper from common_helpers + let loop_increment = match super::common_helpers::extract_loop_increment_plan(body, &parts.loop_var)? { + Some(inc) => inc, + None => return Ok(None), // No loop increment found + }; + + Ok(Some(DomainPlan::Pattern1SimpleWhile(Pattern1SimpleWhilePlan { + loop_var: parts.loop_var, + condition: condition.clone(), + loop_increment, + }))) +} + +/// Validate loop condition: supports ` < ` only +fn validate_loop_condition_plan(cond: &ASTNode, loop_var: &str) -> bool { + use crate::ast::LiteralValue; + + if let ASTNode::BinaryOp { operator, left, right, .. } = cond { + if !matches!(operator, BinaryOperator::Less) { + return false; // Only < supported for PoC + } + + // Left must be the loop variable + if let ASTNode::Variable { name, .. } = left.as_ref() { + if name != loop_var { + return false; + } + } else { + return false; + } + + // Right must be integer literal + if !matches!(right.as_ref(), ASTNode::Literal { value: LiteralValue::Integer(_), .. }) { + return false; + } + + true + } else { + false + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::ast::{LiteralValue, Span}; + + #[test] + fn test_extract_simple_while_success() { + // loop(i < 10) { i = i + 1 } + let condition = ASTNode::BinaryOp { + operator: BinaryOperator::Less, + left: Box::new(ASTNode::Variable { + name: "i".to_string(), + span: Span::unknown(), + }), + right: Box::new(ASTNode::Literal { + value: LiteralValue::Integer(10), + span: Span::unknown(), + }), + span: Span::unknown(), + }; + + let body = vec![ASTNode::Assignment { + target: Box::new(ASTNode::Variable { + name: "i".to_string(), + span: Span::unknown(), + }), + value: Box::new(ASTNode::BinaryOp { + operator: BinaryOperator::Add, + left: Box::new(ASTNode::Variable { + name: "i".to_string(), + span: Span::unknown(), + }), + right: Box::new(ASTNode::Literal { + value: LiteralValue::Integer(1), + span: Span::unknown(), + }), + span: Span::unknown(), + }), + span: Span::unknown(), + }]; + + let result = extract_simple_while_parts(&condition, &body); + assert!(result.is_ok()); + assert!(result.unwrap().is_some()); + } + + #[test] + fn test_extract_with_break_returns_none() { + let condition = ASTNode::Variable { + name: "x".to_string(), + span: Span::unknown(), + }; + let body = vec![ASTNode::Break { + span: Span::unknown(), + }]; + + let result = extract_simple_while_parts(&condition, &body); + assert!(result.is_ok()); + assert!(result.unwrap().is_none()); // Has break → Ok(None) + } + + #[test] + fn test_extract_with_continue_returns_none() { + let condition = ASTNode::Variable { + name: "x".to_string(), + span: Span::unknown(), + }; + let body = vec![ASTNode::Continue { + span: Span::unknown(), + }]; + + let result = extract_simple_while_parts(&condition, &body); + assert!(result.is_ok()); + assert!(result.unwrap().is_none()); // Has continue → Ok(None) + } +} diff --git a/src/mir/builder/control_flow/plan/extractors/pattern3.rs b/src/mir/builder/control_flow/plan/extractors/pattern3.rs new file mode 100644 index 00000000..1af4b953 --- /dev/null +++ b/src/mir/builder/control_flow/plan/extractors/pattern3.rs @@ -0,0 +1,564 @@ +//! Phase 282 P5: Pattern3 (Loop with If-Else PHI) Extraction + +use crate::ast::ASTNode; + +#[derive(Debug, Clone)] +pub(crate) struct Pattern3Parts { + pub loop_var: String, // Loop variable name (e.g., "i") + pub merged_var: String, // Primary PHI carrier (e.g., "sum") + pub carrier_count: usize, // Validation: 1-2 accumulators + // Note: has_else (always true), phi_like_merge (implicit) omitted + // AST reused from ctx - no duplication +} + +/// Extract Pattern3 (Loop with If-Else PHI) parts +/// +/// # Detection Criteria +/// +/// 1. **Condition**: 比較演算 (left=variable) +/// 2. **Body**: At least one if-else statement (else REQUIRED) +/// 3. **Assignments**: Both branches assign to same variable(s) +/// 4. **Control Flow**: NO break/continue/nested-if (return → Ok(None)) +/// +/// # Four-Phase Validation +/// +/// **Phase 1**: Validate condition structure (reuse Pattern1) +/// **Phase 2**: Find if-else statement (else branch REQUIRED) +/// **Phase 3**: Validate PHI assignments (intersection of then/else) +/// **Phase 4**: Validate NO control flow +/// +/// # Fail-Fast Rules +/// +/// - `Ok(Some(parts))`: Pattern3 confirmed +/// - `Ok(None)`: Not Pattern3 (structural mismatch) +/// - `Err(msg)`: Logic bug (malformed AST) +pub(crate) fn extract_loop_with_if_phi_parts( + condition: &ASTNode, + body: &[ASTNode], +) -> Result, String> { + // Phase 1: Validate condition (reuse Pattern1) + use super::pattern1::validate_condition_structure; + let loop_var = match validate_condition_structure(condition) { + Some(var) => var, + None => return Ok(None), + }; + + // Phase 2: Find if-else statement + let if_stmt = match super::common_helpers::find_if_else_statement(body) { + Some(stmt) => stmt, + None => return Ok(None), // No if-else → Not Pattern3 + }; + + // Phase 3: Validate PHI assignments + let merged_vars = match extract_phi_assignments(if_stmt) { + Some(vars) if !vars.is_empty() => vars, + _ => return Ok(None), // No matching assignments → Not Pattern3 + }; + + // Phase 4a: Check for return (early Ok(None) - let other patterns try) + if super::common_helpers::has_return_statement(body) { + return Ok(None); // Has return → delegate to other patterns + } + + // Phase 4b: Validate NO forbidden control flow (break/continue/nested-if only) + // Pattern3 allows ONE if-else (the PHI pattern) but rejects nested if + if has_forbidden_control_flow_for_pattern3(body) { + return Ok(None); // Has break/continue/nested-if → Not Pattern3 + } + + // Extract primary carrier (first merged var) + let merged_var = merged_vars[0].clone(); + let carrier_count = merged_vars.len(); + + Ok(Some(Pattern3Parts { + loop_var, + merged_var, + carrier_count, + })) +} + +/// Extract variables assigned in BOTH then and else branches +fn extract_phi_assignments(if_stmt: &ASTNode) -> Option> { + let (then_body, else_body) = match if_stmt { + ASTNode::If { + then_body, + else_body: Some(else_body), + .. + } => (then_body, else_body), + _ => return None, + }; + + let then_assignments = extract_assignment_targets(then_body); + let else_assignments = extract_assignment_targets(else_body); + + // Find intersection + let mut merged_vars = Vec::new(); + for var in &then_assignments { + if else_assignments.contains(var) { + merged_vars.push(var.clone()); + } + } + + if merged_vars.is_empty() { + return None; + } + + // Use first occurrence (AST order) - deterministic and meaningful SSOT + // Don't sort alphabetically - preserve natural appearance order + Some(merged_vars) +} + +fn extract_assignment_targets(body: &[ASTNode]) -> Vec { + let mut targets = Vec::new(); + for stmt in body { + if let ASTNode::Assignment { target, .. } = stmt { + if let ASTNode::Variable { name, .. } = target.as_ref() { + targets.push(name.clone()); + } + } + } + targets +} + +/// Check for forbidden control flow (Pattern3-specific) +/// +/// Pattern3 allows ONE if-else (that's the PHI pattern) but rejects: +/// - break/continue statements +/// - NESTED if statements (if inside then/else branches) +/// +/// Return is checked separately (not forbidden, just delegated to other patterns). +fn has_forbidden_control_flow_for_pattern3(body: &[ASTNode]) -> bool { + for stmt in body { + if has_forbidden_control_flow_recursive_p3(stmt) { + return true; + } + } + false +} + +fn has_forbidden_control_flow_recursive_p3(node: &ASTNode) -> bool { + match node { + ASTNode::Break { .. } | ASTNode::Continue { .. } => true, + // Return removed - checked separately + ASTNode::If { + then_body, + else_body, + .. + } => { + // Check for NESTED if (reject) + let has_nested_then = then_body.iter().any(|n| matches!(n, ASTNode::If { .. })); + let has_nested_else = else_body + .as_ref() + .map_or(false, |b| b.iter().any(|n| matches!(n, ASTNode::If { .. }))); + + if has_nested_then || has_nested_else { + return true; + } + + // Check control flow INSIDE branches + then_body.iter().any(has_forbidden_control_flow_recursive_p3) + || else_body + .as_ref() + .map_or(false, |b| b.iter().any(has_forbidden_control_flow_recursive_p3)) + } + + _ => false, + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::ast::{BinaryOperator, LiteralValue, Span}; + + #[test] + fn test_extract_if_phi_success() { + // loop(i < 3) { if (i > 0) { sum = sum + 1 } else { sum = sum + 0 } i = i + 1 } + let condition = ASTNode::BinaryOp { + operator: BinaryOperator::Less, + left: Box::new(ASTNode::Variable { + name: "i".to_string(), + span: Span::unknown(), + }), + right: Box::new(ASTNode::Literal { + value: LiteralValue::Integer(3), + span: Span::unknown(), + }), + span: Span::unknown(), + }; + + let body = vec![ + ASTNode::If { + condition: Box::new(ASTNode::BinaryOp { + operator: BinaryOperator::Greater, + left: Box::new(ASTNode::Variable { + name: "i".to_string(), + span: Span::unknown(), + }), + right: Box::new(ASTNode::Literal { + value: LiteralValue::Integer(0), + span: Span::unknown(), + }), + span: Span::unknown(), + }), + then_body: vec![ASTNode::Assignment { + target: Box::new(ASTNode::Variable { + name: "sum".to_string(), + span: Span::unknown(), + }), + value: Box::new(ASTNode::BinaryOp { + operator: BinaryOperator::Add, + left: Box::new(ASTNode::Variable { + name: "sum".to_string(), + span: Span::unknown(), + }), + right: Box::new(ASTNode::Literal { + value: LiteralValue::Integer(1), + span: Span::unknown(), + }), + span: Span::unknown(), + }), + span: Span::unknown(), + }], + else_body: Some(vec![ASTNode::Assignment { + target: Box::new(ASTNode::Variable { + name: "sum".to_string(), + span: Span::unknown(), + }), + value: Box::new(ASTNode::BinaryOp { + operator: BinaryOperator::Add, + left: Box::new(ASTNode::Variable { + name: "sum".to_string(), + span: Span::unknown(), + }), + right: Box::new(ASTNode::Literal { + value: LiteralValue::Integer(0), + span: Span::unknown(), + }), + span: Span::unknown(), + }), + span: Span::unknown(), + }]), + span: Span::unknown(), + }, + ASTNode::Assignment { + target: Box::new(ASTNode::Variable { + name: "i".to_string(), + span: Span::unknown(), + }), + value: Box::new(ASTNode::BinaryOp { + operator: BinaryOperator::Add, + left: Box::new(ASTNode::Variable { + name: "i".to_string(), + span: Span::unknown(), + }), + right: Box::new(ASTNode::Literal { + value: LiteralValue::Integer(1), + span: Span::unknown(), + }), + span: Span::unknown(), + }), + span: Span::unknown(), + }, + ]; + + let result = extract_loop_with_if_phi_parts(&condition, &body); + assert!(result.is_ok()); + let parts = result.unwrap(); + assert!(parts.is_some()); + let parts = parts.unwrap(); + assert_eq!(parts.loop_var, "i"); + assert_eq!(parts.merged_var, "sum"); + assert_eq!(parts.carrier_count, 1); + } + + #[test] + fn test_extract_no_else_returns_none() { + // loop(i < 3) { if (i > 0) { sum = sum + 1 } i = i + 1 } // No else + let condition = ASTNode::BinaryOp { + operator: BinaryOperator::Less, + left: Box::new(ASTNode::Variable { + name: "i".to_string(), + span: Span::unknown(), + }), + right: Box::new(ASTNode::Literal { + value: LiteralValue::Integer(3), + span: Span::unknown(), + }), + span: Span::unknown(), + }; + + let body = vec![ASTNode::If { + condition: Box::new(ASTNode::BinaryOp { + operator: BinaryOperator::Greater, + left: Box::new(ASTNode::Variable { + name: "i".to_string(), + span: Span::unknown(), + }), + right: Box::new(ASTNode::Literal { + value: LiteralValue::Integer(0), + span: Span::unknown(), + }), + span: Span::unknown(), + }), + then_body: vec![ASTNode::Assignment { + target: Box::new(ASTNode::Variable { + name: "sum".to_string(), + span: Span::unknown(), + }), + value: Box::new(ASTNode::BinaryOp { + operator: BinaryOperator::Add, + left: Box::new(ASTNode::Variable { + name: "sum".to_string(), + span: Span::unknown(), + }), + right: Box::new(ASTNode::Literal { + value: LiteralValue::Integer(1), + span: Span::unknown(), + }), + span: Span::unknown(), + }), + span: Span::unknown(), + }], + else_body: None, // ← No else + span: Span::unknown(), + }]; + + let result = extract_loop_with_if_phi_parts(&condition, &body); + assert!(result.is_ok()); + assert!(result.unwrap().is_none()); // No else → Not Pattern3 + } + + #[test] + fn test_extract_different_vars_returns_none() { + // loop(i < 3) { if (i > 0) { sum = 1 } else { count = 1 } i = i + 1 } + let condition = ASTNode::BinaryOp { + operator: BinaryOperator::Less, + left: Box::new(ASTNode::Variable { + name: "i".to_string(), + span: Span::unknown(), + }), + right: Box::new(ASTNode::Literal { + value: LiteralValue::Integer(3), + span: Span::unknown(), + }), + span: Span::unknown(), + }; + + let body = vec![ASTNode::If { + condition: Box::new(ASTNode::BinaryOp { + operator: BinaryOperator::Greater, + left: Box::new(ASTNode::Variable { + name: "i".to_string(), + span: Span::unknown(), + }), + right: Box::new(ASTNode::Literal { + value: LiteralValue::Integer(0), + span: Span::unknown(), + }), + span: Span::unknown(), + }), + then_body: vec![ASTNode::Assignment { + target: Box::new(ASTNode::Variable { + name: "sum".to_string(), // then: sum + span: Span::unknown(), + }), + value: Box::new(ASTNode::Literal { + value: LiteralValue::Integer(1), + span: Span::unknown(), + }), + span: Span::unknown(), + }], + else_body: Some(vec![ASTNode::Assignment { + target: Box::new(ASTNode::Variable { + name: "count".to_string(), // else: count (different!) + span: Span::unknown(), + }), + value: Box::new(ASTNode::Literal { + value: LiteralValue::Integer(1), + span: Span::unknown(), + }), + span: Span::unknown(), + }]), + span: Span::unknown(), + }]; + + let result = extract_loop_with_if_phi_parts(&condition, &body); + assert!(result.is_ok()); + assert!(result.unwrap().is_none()); // Different vars → Not Pattern3 + } + + #[test] + fn test_extract_with_break_returns_none() { + // loop(i < 3) { if (i > 0) { sum = sum + 1; break } else { sum = sum + 0 } i = i + 1 } + let condition = ASTNode::BinaryOp { + operator: BinaryOperator::Less, + left: Box::new(ASTNode::Variable { + name: "i".to_string(), + span: Span::unknown(), + }), + right: Box::new(ASTNode::Literal { + value: LiteralValue::Integer(3), + span: Span::unknown(), + }), + span: Span::unknown(), + }; + + let body = vec![ASTNode::If { + condition: Box::new(ASTNode::BinaryOp { + operator: BinaryOperator::Greater, + left: Box::new(ASTNode::Variable { + name: "i".to_string(), + span: Span::unknown(), + }), + right: Box::new(ASTNode::Literal { + value: LiteralValue::Integer(0), + span: Span::unknown(), + }), + span: Span::unknown(), + }), + then_body: vec![ + ASTNode::Assignment { + target: Box::new(ASTNode::Variable { + name: "sum".to_string(), + span: Span::unknown(), + }), + value: Box::new(ASTNode::BinaryOp { + operator: BinaryOperator::Add, + left: Box::new(ASTNode::Variable { + name: "sum".to_string(), + span: Span::unknown(), + }), + right: Box::new(ASTNode::Literal { + value: LiteralValue::Integer(1), + span: Span::unknown(), + }), + span: Span::unknown(), + }), + span: Span::unknown(), + }, + ASTNode::Break { + span: Span::unknown(), + }, // ← break + ], + else_body: Some(vec![ASTNode::Assignment { + target: Box::new(ASTNode::Variable { + name: "sum".to_string(), + span: Span::unknown(), + }), + value: Box::new(ASTNode::BinaryOp { + operator: BinaryOperator::Add, + left: Box::new(ASTNode::Variable { + name: "sum".to_string(), + span: Span::unknown(), + }), + right: Box::new(ASTNode::Literal { + value: LiteralValue::Integer(0), + span: Span::unknown(), + }), + span: Span::unknown(), + }), + span: Span::unknown(), + }]), + span: Span::unknown(), + }]; + + let result = extract_loop_with_if_phi_parts(&condition, &body); + assert!(result.is_ok()); + assert!(result.unwrap().is_none()); // Has break → Not Pattern3 + } +} + +// ============================================================================ +// Phase 286 P2.6: Pattern3 → Plan/Frag SSOT extractor +// ============================================================================ + +/// Phase 286 P2.6: Extract Pattern3 (Loop with If-Phi) Plan +/// +/// Leverages existing `extract_loop_with_if_phi_parts()` for validation, +/// then extracts detailed AST components for Plan normalization. +/// +/// # Returns +/// - Ok(Some(DomainPlan::Pattern3IfPhi)) if Pattern3 match confirmed +/// - Ok(None) if not Pattern3 (structural mismatch) +/// - Err if malformed AST (rare) +pub(crate) fn extract_pattern3_plan( + condition: &ASTNode, + body: &[ASTNode], +) -> Result, String> { + use crate::mir::builder::control_flow::plan::{DomainPlan, Pattern3IfPhiPlan}; + + // Step 1: Validate via existing extractor + let parts = extract_loop_with_if_phi_parts(condition, body)?; + if parts.is_none() { + return Ok(None); // Not Pattern3 → fallback + } + let parts = parts.unwrap(); + + // Step 2: Extract if-else statement (validated to exist) + let if_stmt = super::common_helpers::find_if_else_statement(body) + .ok_or_else(|| "Pattern3: if-else statement not found after validation".to_string())?; + + // Step 3: Extract if condition and branch updates + let (if_condition, then_update, else_update) = match if_stmt { + ASTNode::If { + condition: if_cond, + then_body, + else_body: Some(else_body), + .. + } => { + // Extract carrier update from then branch + let then_update = extract_single_update(then_body, &parts.merged_var)?; + + // Extract carrier update from else branch + let else_update = extract_single_update(else_body, &parts.merged_var)?; + + (if_cond.as_ref().clone(), then_update, else_update) + } + _ => { + return Err("Pattern3: if-else structure mismatch after validation".to_string()); + } + }; + + // Step 4: Extract loop increment + let loop_increment = match super::common_helpers::extract_loop_increment_plan(body, &parts.loop_var)? { + Some(inc) => inc, + None => { + return Err(format!( + "Pattern3: loop increment not found for variable '{}'", + parts.loop_var + )); + } + }; + + Ok(Some(DomainPlan::Pattern3IfPhi(Pattern3IfPhiPlan { + loop_var: parts.loop_var, + carrier_var: parts.merged_var, + condition: condition.clone(), + if_condition, + then_update, + else_update, + loop_increment, + }))) +} + +/// Extract update expression from a branch body +/// +/// Expects a single assignment: `carrier = ` +/// Returns the RHS expression AST +fn extract_single_update(body: &[ASTNode], carrier_var: &str) -> Result { + for stmt in body { + if let ASTNode::Assignment { target, value, .. } = stmt { + if let ASTNode::Variable { name, .. } = target.as_ref() { + if name == carrier_var { + return Ok(value.as_ref().clone()); + } + } + } + } + + Err(format!( + "Pattern3: carrier update not found for variable '{}' in branch", + carrier_var + )) +} diff --git a/src/mir/builder/control_flow/plan/extractors/pattern4.rs b/src/mir/builder/control_flow/plan/extractors/pattern4.rs new file mode 100644 index 00000000..d64cb785 --- /dev/null +++ b/src/mir/builder/control_flow/plan/extractors/pattern4.rs @@ -0,0 +1,418 @@ +//! Phase 282 P6: Pattern4 (Loop with Continue) Extraction +//! Phase 282 P9a: Integrated with common_helpers + +use crate::ast::ASTNode; + +// Phase 282 P9a: Use common_helpers +// Phase 284 P1: has_return_statement removed (now handled by return_collector SSOT) +use super::common_helpers::{count_control_flow, has_break_statement, ControlFlowDetector}; + +#[derive(Debug, Clone)] +pub(crate) struct Pattern4Parts { + pub loop_var: String, // Extracted from condition (i < n) + pub continue_count: usize, // Must be >= 1 for Pattern4 +} + +/// Extract Pattern4 (Loop with Continue) parts +/// +/// # Detection Criteria +/// +/// 1. **Condition**: Comparison operator with left=variable (Pattern1 reuse) +/// 2. **Body**: At least 1 continue statement (Pattern4 必要条件) +/// 3. **Reject**: break → Pattern2 territory +/// 4. **Fail-Fast**: return → Err (close-but-unsupported, Phase 142 P2 pending) +/// +/// # Five-Phase Validation (USER CORRECTION: Recursive detection required!) +/// +/// **Phase 1**: Validate condition structure (reuse Pattern1) +/// **Phase 2**: Validate HAS continue (count >= 1, **recursive**) +/// **Phase 3**: Validate NO break (fail-fast or Ok(None), **recursive**) +/// **Phase 4**: Check for return (Err if found, **recursive**) +/// **Phase 5**: Return extracted parts +/// +/// # Fail-Fast Rules +/// +/// - `Ok(Some(parts))`: Pattern4 match confirmed +/// - `Ok(None)`: Not Pattern4 (wrong structure) +/// - `Err(msg)`: Close-but-unsupported (e.g., return found) or malformed AST +/// +/// # Note +/// +/// - USER CORRECTION: All detection is recursive (handles `if { continue }` patterns) +/// - USER CORRECTION: No loop_var update validation (deferred to Pattern4CarrierAnalyzer) +/// - Nested loop's continue/break statements are NOT counted (only current loop) +pub(crate) fn extract_loop_with_continue_parts( + condition: &ASTNode, + body: &[ASTNode], +) -> Result, String> { + // Phase 1: Validate condition structure (reuse Pattern1's validator) + use super::pattern1::validate_condition_structure; + + let loop_var = match validate_condition_structure(condition) { + Some(var) => var, + None => return Ok(None), // Not comparison pattern → Other patterns + }; + + // Phase 2: Count continue statements (USER CORRECTION: Recursive!) + let continue_count = count_continue_statements_recursive(body); + if continue_count == 0 { + return Ok(None); // No continue → Pattern1/2 territory + } + + // Phase 3: Check for break (USER CORRECTION: Recursive!) + if has_break_statement_recursive(body) { + // Has break → Pattern2 territory (break takes priority) + return Ok(None); + } + + // Phase 284 P1: Return check removed - now handled by return_collector SSOT + // in conversion_pipeline.rs (JoinIR line common entry point) + + // Phase 5: Return extracted parts + // USER CORRECTION: No loop_var update validation - defer to Pattern4CarrierAnalyzer + Ok(Some(Pattern4Parts { + loop_var, + continue_count, + })) +} + +// ============================================================================ +// Helper Functions (Internal) - USER CORRECTION: All must be recursive! +// ============================================================================ + +/// Count continue statements recursively +/// +/// # Phase 282 P9a: Delegates to common_helpers::count_control_flow +/// +/// - Skips nested loop bodies (default behavior) +fn count_continue_statements_recursive(body: &[ASTNode]) -> usize { + count_control_flow(body, ControlFlowDetector::default()).continue_count +} + +/// Check recursively if body has break statement +/// +/// # Phase 282 P9a: Delegates to common_helpers::has_break_statement +fn has_break_statement_recursive(body: &[ASTNode]) -> bool { + has_break_statement(body) +} + +// Phase 284 P1: has_return_statement_recursive removed +// Return detection now handled by return_collector SSOT in conversion_pipeline.rs + +// ============================================================================ +// Phase 286 P2: Pattern4 → Plan/Frag SSOT extractor +// ============================================================================ + +/// Phase 286 P2: Minimal subset extractor for Pattern4 Plan line +/// +/// Supported subset (PoC safety - Compare + Add only): +/// - Loop condition: ` < ` +/// - Continue condition: ` == ` (NO Mod operator) +/// - Carrier update: ` = + ` (accumulator pattern) +/// - Loop increment: ` = + ` +/// +/// Returns Ok(None) for unsupported patterns → legacy fallback +pub(crate) fn extract_pattern4_plan( + condition: &ASTNode, + body: &[ASTNode], +) -> Result, String> { + use crate::mir::builder::control_flow::plan::{DomainPlan, Pattern4ContinuePlan}; + + // Step 1: Validate via existing extractor + let parts = extract_loop_with_continue_parts(condition, body)?; + if parts.is_none() { + return Ok(None); // Not Pattern4 → legacy fallback + } + + // Step 2: Extract loop variable from condition `i < N` + let loop_var = match extract_loop_condition_plan(condition)? { + Some((var, _bound)) => var, + None => return Ok(None), // Unsupported condition + }; + + // Step 3: Find continue condition from body + let continue_cond = match find_continue_condition_plan(body)? { + Some(cond) => cond, + None => return Ok(None), // No continue condition found + }; + + // Step 4: Extract carrier updates (accumulator pattern) + let carrier_updates = extract_carrier_updates_plan(body, &loop_var)?; + if carrier_updates.is_empty() { + return Ok(None); // No carrier → unsupported + } + + // Step 5: Extract loop increment `i = i + 1` + // Phase 286 P2.2: Use common helper from common_helpers + let loop_increment = match super::common_helpers::extract_loop_increment_plan(body, &loop_var)? { + Some(inc) => inc, + None => return Ok(None), // No loop increment found + }; + + Ok(Some(DomainPlan::Pattern4Continue(Pattern4ContinuePlan { + loop_var: loop_var.clone(), + carrier_vars: carrier_updates.keys().cloned().collect(), + condition: condition.clone(), + continue_condition: continue_cond, + carrier_updates, + loop_increment, + }))) +} + +/// Extract loop condition: supports ` < ` only +fn extract_loop_condition_plan(cond: &ASTNode) -> Result, String> { + use crate::ast::{BinaryOperator, LiteralValue}; + + if let ASTNode::BinaryOp { operator, left, right, .. } = cond { + if !matches!(operator, BinaryOperator::Less) { + return Ok(None); // Only < supported + } + + let var_name = if let ASTNode::Variable { name, .. } = left.as_ref() { + name.clone() + } else { + return Ok(None); + }; + + let bound = if let ASTNode::Literal { value: LiteralValue::Integer(n), .. } = right.as_ref() { + *n + } else { + return Ok(None); + }; + + Ok(Some((var_name, bound))) + } else { + Ok(None) + } +} + +/// Find continue condition in if statement +fn find_continue_condition_plan(body: &[ASTNode]) -> Result, String> { + for stmt in body { + if let ASTNode::If { condition, then_body, .. } = stmt { + for then_stmt in then_body { + if matches!(then_stmt, ASTNode::Continue { .. }) { + return Ok(Some(condition.as_ref().clone())); + } + } + } + } + Ok(None) +} + +/// Extract carrier updates: ` = + ` pattern only +fn extract_carrier_updates_plan( + body: &[ASTNode], + loop_var: &str, +) -> Result, String> { + use crate::ast::BinaryOperator; + use std::collections::BTreeMap; + + let mut updates = BTreeMap::new(); + + for stmt in body { + if let ASTNode::Assignment { target, value, .. } = stmt { + let var_name = if let ASTNode::Variable { name, .. } = target.as_ref() { + name.clone() + } else { + continue; + }; + + // Skip loop variable itself + if var_name == loop_var { + continue; + } + + // Check for accumulator pattern: `var = var + x` + if let ASTNode::BinaryOp { operator, left, .. } = value.as_ref() { + if matches!(operator, BinaryOperator::Add) { + if let ASTNode::Variable { name, .. } = left.as_ref() { + if name == &var_name { + updates.insert(var_name, value.as_ref().clone()); + } + } + } + } + } + } + + Ok(updates) +} + +// ============================================================================ +// Unit Tests +// ============================================================================ + +#[cfg(test)] +mod tests { + use super::*; + use crate::ast::{BinaryOperator, LiteralValue, Span}; + + /// Helper: Create comparison condition (var < limit) + fn make_condition(var: &str, limit: i64) -> ASTNode { + ASTNode::BinaryOp { + operator: BinaryOperator::Less, + left: Box::new(ASTNode::Variable { + name: var.to_string(), + span: Span::unknown(), + }), + right: Box::new(ASTNode::Literal { + value: LiteralValue::Integer(limit), + span: Span::unknown(), + }), + span: Span::unknown(), + } + } + + /// Helper: Create if-continue block (if (skip) { continue }) + fn make_if_continue() -> ASTNode { + ASTNode::If { + condition: Box::new(ASTNode::Variable { + name: "skip".to_string(), + span: Span::unknown(), + }), + then_body: vec![ASTNode::Continue { + span: Span::unknown(), + }], + else_body: None, + span: Span::unknown(), + } + } + + /// Helper: Create if-break block (if (done) { break }) + fn make_if_break() -> ASTNode { + ASTNode::If { + condition: Box::new(ASTNode::Variable { + name: "done".to_string(), + span: Span::unknown(), + }), + then_body: vec![ASTNode::Break { + span: Span::unknown(), + }], + else_body: None, + span: Span::unknown(), + } + } + + /// Helper: Create increment (i = i + 1) + fn make_increment(var: &str) -> ASTNode { + ASTNode::Assignment { + target: Box::new(ASTNode::Variable { + name: var.to_string(), + span: Span::unknown(), + }), + value: Box::new(ASTNode::BinaryOp { + operator: BinaryOperator::Add, + left: Box::new(ASTNode::Variable { + name: var.to_string(), + span: Span::unknown(), + }), + right: Box::new(ASTNode::Literal { + value: LiteralValue::Integer(1), + span: Span::unknown(), + }), + span: Span::unknown(), + }), + span: Span::unknown(), + } + } + + #[test] + fn test_pattern4_continue_success() { + // loop(i < 10) { if (skip) { continue } i = i + 1 } + let condition = make_condition("i", 10); + let body = vec![make_if_continue(), make_increment("i")]; + + let result = extract_loop_with_continue_parts(&condition, &body); + assert!(result.is_ok()); + let parts = result.unwrap(); + assert!(parts.is_some()); + + let parts = parts.unwrap(); + assert_eq!(parts.loop_var, "i"); + assert_eq!(parts.continue_count, 1); + } + + #[test] + fn test_pattern4_no_continue_returns_none() { + // loop(i < 10) { i = i + 1 } → Not Pattern4 (Pattern1 territory) + let condition = make_condition("i", 10); + let body = vec![make_increment("i")]; + + let result = extract_loop_with_continue_parts(&condition, &body); + assert!(result.is_ok()); + assert!(result.unwrap().is_none()); // No continue → Not Pattern4 + } + + #[test] + fn test_pattern4_with_break_returns_none() { + // loop(i < 10) { if (done) { break } i = i + 1 } → Pattern2 + let condition = make_condition("i", 10); + let body = vec![make_if_break(), make_increment("i")]; + + let result = extract_loop_with_continue_parts(&condition, &body); + assert!(result.is_ok()); + assert!(result.unwrap().is_none()); // Has break → Pattern2 + } + + #[test] + fn test_pattern4_with_return_is_allowed() { + // Phase 284 P1: loop(i < 10) { if (done) { return } i = i + 1 } → Ok(Some(...)) + // Return check moved to return_collector SSOT in conversion_pipeline.rs + let condition = make_condition("i", 10); + let body = vec![ + ASTNode::If { + condition: Box::new(ASTNode::Variable { + name: "done".to_string(), + span: Span::unknown(), + }), + then_body: vec![ASTNode::Return { + value: None, + span: Span::unknown(), + }], + else_body: None, + span: Span::unknown(), + }, + make_if_continue(), + make_increment("i"), + ]; + + let result = extract_loop_with_continue_parts(&condition, &body); + assert!(result.is_ok()); // Phase 284 P1: Now allowed at extractor level + let parts = result.unwrap(); + assert!(parts.is_some()); // Pattern4 extraction succeeds + let parts = parts.unwrap(); + assert_eq!(parts.loop_var, "i"); + assert_eq!(parts.continue_count, 1); + } + + #[test] + fn test_pattern4_multi_continue_count() { + // loop(i < 10) { if (a) { continue } if (b) { continue } i = i + 1 } + let condition = make_condition("i", 10); + let body = vec![ + make_if_continue(), // First continue + ASTNode::If { + condition: Box::new(ASTNode::Variable { + name: "b".to_string(), + span: Span::unknown(), + }), + then_body: vec![ASTNode::Continue { + span: Span::unknown(), + }], + else_body: None, + span: Span::unknown(), + }, // Second continue + make_increment("i"), + ]; + + let result = extract_loop_with_continue_parts(&condition, &body); + assert!(result.is_ok()); + let parts = result.unwrap(); + assert!(parts.is_some()); + + let parts = parts.unwrap(); + assert_eq!(parts.loop_var, "i"); + assert_eq!(parts.continue_count, 2); // Two continue statements detected + } +} diff --git a/src/mir/builder/control_flow/joinir/patterns/extractors/pattern5.rs b/src/mir/builder/control_flow/plan/extractors/pattern5.rs similarity index 98% rename from src/mir/builder/control_flow/joinir/patterns/extractors/pattern5.rs rename to src/mir/builder/control_flow/plan/extractors/pattern5.rs index da9b0499..ed9335c5 100644 --- a/src/mir/builder/control_flow/joinir/patterns/extractors/pattern5.rs +++ b/src/mir/builder/control_flow/plan/extractors/pattern5.rs @@ -21,13 +21,16 @@ use crate::ast::ASTNode; // Phase 282 P9a: Use common_helpers +use super::common_helpers::is_true_literal; + +#[cfg(test)] use super::common_helpers::{ - count_control_flow, is_true_literal, validate_break_in_simple_if, validate_continue_at_end, - ControlFlowDetector, + count_control_flow, validate_break_in_simple_if, validate_continue_at_end, ControlFlowDetector, }; /// Pattern5 extracted parts (lightweight, no AST copies) #[derive(Debug, Clone)] +#[cfg(test)] pub(crate) struct Pattern5Parts { pub break_count: usize, // Must be 1 for Pattern5 pub continue_count: usize, // Must be 1 for Pattern5 @@ -68,6 +71,7 @@ pub(crate) struct Pattern5Parts { /// - `Ok(Some(parts))`: Pattern5 match confirmed /// - `Ok(None)`: Not Pattern5 (structural mismatch, try other patterns) /// - `Err(msg)`: Close-but-unsupported (e.g., return found) +#[cfg(test)] pub(crate) fn extract_infinite_early_exit_parts( condition: &ASTNode, body: &[ASTNode], @@ -147,6 +151,7 @@ pub(crate) fn extract_infinite_early_exit_parts( /// /// - Uses ControlFlowDetector with count_returns=true /// - Skips nested loop bodies (default behavior) +#[cfg(test)] fn count_control_flow_recursive(body: &[ASTNode]) -> (usize, usize, usize, bool) { let mut detector = ControlFlowDetector::default(); detector.count_returns = true; diff --git a/src/mir/builder/control_flow/joinir/patterns/extractors/pattern8.rs b/src/mir/builder/control_flow/plan/extractors/pattern8.rs similarity index 100% rename from src/mir/builder/control_flow/joinir/patterns/extractors/pattern8.rs rename to src/mir/builder/control_flow/plan/extractors/pattern8.rs diff --git a/src/mir/builder/control_flow/joinir/patterns/extractors/pattern9.rs b/src/mir/builder/control_flow/plan/extractors/pattern9.rs similarity index 100% rename from src/mir/builder/control_flow/joinir/patterns/extractors/pattern9.rs rename to src/mir/builder/control_flow/plan/extractors/pattern9.rs diff --git a/src/mir/builder/control_flow/plan/single_planner/legacy_rules/mod.rs b/src/mir/builder/control_flow/plan/single_planner/legacy_rules/mod.rs deleted file mode 100644 index d71fb6ac..00000000 --- a/src/mir/builder/control_flow/plan/single_planner/legacy_rules/mod.rs +++ /dev/null @@ -1,14 +0,0 @@ -//! Phase 29ai P5: Legacy rule wrappers (thin bridge) -//! -//! These wrappers must preserve the legacy extractor behavior and error strings. - -pub(in crate::mir::builder) mod pattern1; -pub(in crate::mir::builder) mod pattern2; -pub(in crate::mir::builder) mod pattern3; -pub(in crate::mir::builder) mod pattern4; -pub(in crate::mir::builder) mod pattern5; -pub(in crate::mir::builder) mod pattern6; -pub(in crate::mir::builder) mod pattern7; -pub(in crate::mir::builder) mod pattern8; -pub(in crate::mir::builder) mod pattern9; - diff --git a/src/mir/builder/control_flow/plan/single_planner/legacy_rules/pattern1.rs b/src/mir/builder/control_flow/plan/single_planner/legacy_rules/pattern1.rs deleted file mode 100644 index 4dfd0002..00000000 --- a/src/mir/builder/control_flow/plan/single_planner/legacy_rules/pattern1.rs +++ /dev/null @@ -1,9 +0,0 @@ -use crate::mir::builder::control_flow::joinir::patterns::router::LoopPatternContext; -use crate::mir::builder::control_flow::plan::DomainPlan; -use crate::mir::builder::control_flow::joinir::patterns::extractors::pattern1::extract_pattern1_plan; - -pub(in crate::mir::builder) fn extract( - ctx: &LoopPatternContext, -) -> Result, String> { - extract_pattern1_plan(ctx.condition, ctx.body) -} diff --git a/src/mir/builder/control_flow/plan/single_planner/legacy_rules/pattern2.rs b/src/mir/builder/control_flow/plan/single_planner/legacy_rules/pattern2.rs deleted file mode 100644 index 1b27fbef..00000000 --- a/src/mir/builder/control_flow/plan/single_planner/legacy_rules/pattern2.rs +++ /dev/null @@ -1,9 +0,0 @@ -use crate::mir::builder::control_flow::joinir::patterns::router::LoopPatternContext; -use crate::mir::builder::control_flow::plan::DomainPlan; -use crate::mir::builder::control_flow::plan::extractors::pattern2_break::extract_pattern2_plan; - -pub(in crate::mir::builder) fn extract( - ctx: &LoopPatternContext, -) -> Result, String> { - extract_pattern2_plan(ctx.condition, ctx.body) -} diff --git a/src/mir/builder/control_flow/plan/single_planner/legacy_rules/pattern3.rs b/src/mir/builder/control_flow/plan/single_planner/legacy_rules/pattern3.rs deleted file mode 100644 index f46684b9..00000000 --- a/src/mir/builder/control_flow/plan/single_planner/legacy_rules/pattern3.rs +++ /dev/null @@ -1,9 +0,0 @@ -use crate::mir::builder::control_flow::joinir::patterns::router::LoopPatternContext; -use crate::mir::builder::control_flow::plan::DomainPlan; -use crate::mir::builder::control_flow::joinir::patterns::extractors::pattern3::extract_pattern3_plan; - -pub(in crate::mir::builder) fn extract( - ctx: &LoopPatternContext, -) -> Result, String> { - extract_pattern3_plan(ctx.condition, ctx.body) -} diff --git a/src/mir/builder/control_flow/plan/single_planner/legacy_rules/pattern4.rs b/src/mir/builder/control_flow/plan/single_planner/legacy_rules/pattern4.rs deleted file mode 100644 index 21e58e5e..00000000 --- a/src/mir/builder/control_flow/plan/single_planner/legacy_rules/pattern4.rs +++ /dev/null @@ -1,9 +0,0 @@ -use crate::mir::builder::control_flow::joinir::patterns::router::LoopPatternContext; -use crate::mir::builder::control_flow::plan::DomainPlan; -use crate::mir::builder::control_flow::joinir::patterns::extractors::pattern4::extract_pattern4_plan; - -pub(in crate::mir::builder) fn extract( - ctx: &LoopPatternContext, -) -> Result, String> { - extract_pattern4_plan(ctx.condition, ctx.body) -} diff --git a/src/mir/builder/control_flow/plan/single_planner/legacy_rules/pattern5.rs b/src/mir/builder/control_flow/plan/single_planner/legacy_rules/pattern5.rs deleted file mode 100644 index 6c428281..00000000 --- a/src/mir/builder/control_flow/plan/single_planner/legacy_rules/pattern5.rs +++ /dev/null @@ -1,9 +0,0 @@ -use crate::mir::builder::control_flow::joinir::patterns::router::LoopPatternContext; -use crate::mir::builder::control_flow::plan::DomainPlan; -use crate::mir::builder::control_flow::joinir::patterns::extractors::pattern5::extract_pattern5_plan; - -pub(in crate::mir::builder) fn extract( - ctx: &LoopPatternContext, -) -> Result, String> { - extract_pattern5_plan(ctx.condition, ctx.body) -} diff --git a/src/mir/builder/control_flow/plan/single_planner/legacy_rules/pattern6.rs b/src/mir/builder/control_flow/plan/single_planner/legacy_rules/pattern6.rs deleted file mode 100644 index 68994fdc..00000000 --- a/src/mir/builder/control_flow/plan/single_planner/legacy_rules/pattern6.rs +++ /dev/null @@ -1,9 +0,0 @@ -use crate::mir::builder::control_flow::joinir::patterns::router::LoopPatternContext; -use crate::mir::builder::control_flow::plan::DomainPlan; -use crate::mir::builder::control_flow::plan::extractors::pattern6_scan_with_init::extract_scan_with_init_plan; - -pub(in crate::mir::builder) fn extract( - ctx: &LoopPatternContext, -) -> Result, String> { - extract_scan_with_init_plan(ctx.condition, ctx.body, ctx.fn_body) -} diff --git a/src/mir/builder/control_flow/plan/single_planner/legacy_rules/pattern7.rs b/src/mir/builder/control_flow/plan/single_planner/legacy_rules/pattern7.rs deleted file mode 100644 index fdecec6b..00000000 --- a/src/mir/builder/control_flow/plan/single_planner/legacy_rules/pattern7.rs +++ /dev/null @@ -1,9 +0,0 @@ -use crate::mir::builder::control_flow::joinir::patterns::router::LoopPatternContext; -use crate::mir::builder::control_flow::plan::DomainPlan; -use crate::mir::builder::control_flow::plan::extractors::pattern7_split_scan::extract_split_scan_plan; - -pub(in crate::mir::builder) fn extract( - ctx: &LoopPatternContext, -) -> Result, String> { - extract_split_scan_plan(ctx.condition, ctx.body, &[]) -} diff --git a/src/mir/builder/control_flow/plan/single_planner/legacy_rules/pattern8.rs b/src/mir/builder/control_flow/plan/single_planner/legacy_rules/pattern8.rs deleted file mode 100644 index 7ab14e06..00000000 --- a/src/mir/builder/control_flow/plan/single_planner/legacy_rules/pattern8.rs +++ /dev/null @@ -1,9 +0,0 @@ -use crate::mir::builder::control_flow::joinir::patterns::router::LoopPatternContext; -use crate::mir::builder::control_flow::plan::DomainPlan; -use crate::mir::builder::control_flow::joinir::patterns::extractors::pattern8::extract_pattern8_plan; - -pub(in crate::mir::builder) fn extract( - ctx: &LoopPatternContext, -) -> Result, String> { - extract_pattern8_plan(ctx.condition, ctx.body) -} diff --git a/src/mir/builder/control_flow/plan/single_planner/legacy_rules/pattern9.rs b/src/mir/builder/control_flow/plan/single_planner/legacy_rules/pattern9.rs deleted file mode 100644 index 68043a57..00000000 --- a/src/mir/builder/control_flow/plan/single_planner/legacy_rules/pattern9.rs +++ /dev/null @@ -1,9 +0,0 @@ -use crate::mir::builder::control_flow::joinir::patterns::router::LoopPatternContext; -use crate::mir::builder::control_flow::plan::DomainPlan; -use crate::mir::builder::control_flow::joinir::patterns::extractors::pattern9::extract_pattern9_plan; - -pub(in crate::mir::builder) fn extract( - ctx: &LoopPatternContext, -) -> Result, String> { - extract_pattern9_plan(ctx.condition, ctx.body) -} diff --git a/src/mir/builder/control_flow/plan/single_planner/mod.rs b/src/mir/builder/control_flow/plan/single_planner/mod.rs index cc7ebc52..0a567fbf 100644 --- a/src/mir/builder/control_flow/plan/single_planner/mod.rs +++ b/src/mir/builder/control_flow/plan/single_planner/mod.rs @@ -7,7 +7,6 @@ use crate::mir::builder::control_flow::joinir::patterns::router::LoopPatternCont use super::DomainPlan; -pub(in crate::mir::builder) mod legacy_rules; mod rules; pub(in crate::mir::builder) fn try_build_domain_plan( @@ -15,4 +14,3 @@ pub(in crate::mir::builder) fn try_build_domain_plan( ) -> Result, String> { rules::try_build_domain_plan(ctx) } - diff --git a/src/mir/builder/control_flow/plan/single_planner/rules.rs b/src/mir/builder/control_flow/plan/single_planner/rules.rs index b6c2c451..7e70b4b0 100644 --- a/src/mir/builder/control_flow/plan/single_planner/rules.rs +++ b/src/mir/builder/control_flow/plan/single_planner/rules.rs @@ -3,10 +3,11 @@ //! IMPORTANT: Keep rule order identical to the legacy `PLAN_EXTRACTORS` table //! (observability/behavior must not change). +use crate::ast::ASTNode; use crate::mir::builder::control_flow::joinir::patterns::router::LoopPatternContext; use crate::mir::loop_pattern_detection::LoopPatternKind; -use super::legacy_rules; +use crate::mir::builder::control_flow::plan::extractors; use crate::mir::builder::control_flow::plan::facts::pattern2_loopbodylocal_facts::LoopBodyLocalShape; use crate::mir::builder::control_flow::plan::planner; use crate::mir::builder::control_flow::plan::DomainPlan; @@ -35,23 +36,23 @@ pub(super) fn try_build_domain_plan(ctx: &LoopPatternContext) -> Result Result Result (extract(ctx)?, true), + RuleKind::Simple(extract) => (extract(ctx.condition, ctx.body)?, true), RuleKind::Pattern6 => { match planner_opt.as_ref() { Some(DomainPlan::ScanWithInit(_)) => (planner_opt.clone(), false), - _ => (legacy_rules::pattern6::extract(ctx)?, true), + _ => ( + extractors::pattern6_scan_with_init::extract_scan_with_init_plan( + ctx.condition, + ctx.body, + ctx.fn_body, + )?, + true, + ), } } RuleKind::Pattern7 => { match planner_opt.as_ref() { Some(DomainPlan::SplitScan(_)) => (planner_opt.clone(), false), - _ => (legacy_rules::pattern7::extract(ctx)?, true), + _ => ( + extractors::pattern7_split_scan::extract_split_scan_plan( + ctx.condition, + ctx.body, + &[], + )?, + true, + ), } } RuleKind::Pattern2 => { match planner_opt.as_ref() { Some(DomainPlan::Pattern2Break(_)) => (planner_opt.clone(), false), - _ => (legacy_rules::pattern2::extract(ctx)?, true), + _ => ( + extractors::pattern2_break::extract_pattern2_plan(ctx.condition, ctx.body)?, + true, + ), } } }; @@ -147,7 +165,7 @@ struct RuleEntry { #[derive(Debug, Clone, Copy)] enum RuleKind { - Simple(fn(&LoopPatternContext) -> Result, String>), + Simple(fn(&ASTNode, &[ASTNode]) -> Result, String>), Pattern6, Pattern7, Pattern2,