phase29aj(p1): remove legacy_rules via plan extractors

This commit is contained in:
2025-12-29 12:39:15 +09:00
parent 5988374ecf
commit ff3af90b4c
29 changed files with 1413 additions and 1418 deletions

View File

@ -18,6 +18,9 @@ strict/dev 時のみ `[plan/pattern2/promotion_hint:{TrimSeg|DigitPos}]` を観
**2025-12-29: Phase 29aj P0 COMPLETE (PlannerOutcome observability SSOT)**
planner outcomefacts+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)**
Pattern61-level nested loopの JoinIR→bridge→merge 経路で発生していた `undefined ValueId``vm step budget exceeded`(無限ループ)を解消。`apps/tests/phase1883_nested_minimal.hako` が RC=9 を返し、quick 154 PASS を維持。

View File

@ -2,7 +2,12 @@
## Current Focus: Phase 29ajPlannerOutcome SSOT
Next: Phase 29aj P1TBD: promotion hint を JoinIR 側の orchestrator へ配線、挙動不変)
Next: Phase 29aj P2TBD: 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 outcomefacts+planを SSOT 化して strict 観測の再スキャンを撤去(仕様不変)
@ -36,7 +41,7 @@ Next: Phase 29aj P1TBD: 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 完了**

View File

@ -19,7 +19,8 @@ Related:
- **Phase 29ajcandidate: PlannerOutcome observability SSOT**
- 入口: `docs/development/current/main/phases/phase-29aj/README.md`
- Next: Phase 29aj P1TBD: promotion hint を JoinIR 側の orchestrator へ配線、挙動不変)
- 状況: P0/P1 ✅ 完了
- Next: Phase 29aj P2TBD: promotion hint を JoinIR 側の orchestrator へ配線、挙動不変)
- **Phase 29aicandidate: Plan/Frag single-plannerFacts SSOT**
- 入口: `docs/development/current/main/phases/phase-29ai/README.md`

View File

@ -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"`

View File

@ -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_rulesPlan 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`

View File

@ -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::*;

View File

@ -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.

View File

@ -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<Option<Pattern1Parts>, 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<String> {
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: `<var> < <int_lit>`
/// - Loop increment: `<var> = <var> + <int_lit>`
///
/// Returns Ok(None) for unsupported patterns → legacy fallback
pub(crate) fn extract_pattern1_plan(
condition: &ASTNode,
body: &[ASTNode],
) -> Result<Option<crate::mir::builder::control_flow::plan::DomainPlan>, 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 `<var> < <int_lit>`
if !validate_loop_condition_plan(condition, &parts.loop_var) {
return Ok(None); // Unsupported condition format
}
// Step 3: Extract loop increment `<var> = <var> + <int_lit>`
// 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 `<var> < <int_lit>` 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::*;

View File

@ -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<Option<Pattern3Parts>, 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<Vec<String>> {
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<String> {
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<Option<crate::mir::builder::control_flow::plan::DomainPlan>, 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 = <expr>`
/// Returns the RHS expression AST
fn extract_single_update(body: &[ASTNode], carrier_var: &str) -> Result<ASTNode, String> {
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::*;

View File

@ -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<Option<Pattern4Parts>, 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: `<var> < <int_lit>`
/// - Continue condition: `<var> == <int_lit>` (NO Mod operator)
/// - Carrier update: `<var> = <var> + <var>` (accumulator pattern)
/// - Loop increment: `<var> = <var> + <int_lit>`
///
/// Returns Ok(None) for unsupported patterns → legacy fallback
pub(crate) fn extract_pattern4_plan(
condition: &ASTNode,
body: &[ASTNode],
) -> Result<Option<crate::mir::builder::control_flow::plan::DomainPlan>, 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 `<var> < <int_lit>` only
fn extract_loop_condition_plan(cond: &ASTNode) -> Result<Option<(String, i64)>, 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<Option<ASTNode>, 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: `<var> = <var> + <var>` pattern only
fn extract_carrier_updates_plan(
body: &[ASTNode],
loop_var: &str,
) -> Result<std::collections::BTreeMap<String, ASTNode>, 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::*;

View File

@ -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;

View File

@ -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<Option<Pattern1Parts>, 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<String> {
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: `<var> < <int_lit>`
/// - Loop increment: `<var> = <var> + <int_lit>`
///
/// Returns Ok(None) for unsupported patterns → legacy fallback
pub(crate) fn extract_pattern1_plan(
condition: &ASTNode,
body: &[ASTNode],
) -> Result<Option<crate::mir::builder::control_flow::plan::DomainPlan>, 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 `<var> < <int_lit>`
if !validate_loop_condition_plan(condition, &parts.loop_var) {
return Ok(None); // Unsupported condition format
}
// Step 3: Extract loop increment `<var> = <var> + <int_lit>`
// 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 `<var> < <int_lit>` 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)
}
}

View File

@ -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<Option<Pattern3Parts>, 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<Vec<String>> {
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<String> {
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<Option<crate::mir::builder::control_flow::plan::DomainPlan>, 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 = <expr>`
/// Returns the RHS expression AST
fn extract_single_update(body: &[ASTNode], carrier_var: &str) -> Result<ASTNode, String> {
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
))
}

View File

@ -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<Option<Pattern4Parts>, 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: `<var> < <int_lit>`
/// - Continue condition: `<var> == <int_lit>` (NO Mod operator)
/// - Carrier update: `<var> = <var> + <var>` (accumulator pattern)
/// - Loop increment: `<var> = <var> + <int_lit>`
///
/// Returns Ok(None) for unsupported patterns → legacy fallback
pub(crate) fn extract_pattern4_plan(
condition: &ASTNode,
body: &[ASTNode],
) -> Result<Option<crate::mir::builder::control_flow::plan::DomainPlan>, 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 `<var> < <int_lit>` only
fn extract_loop_condition_plan(cond: &ASTNode) -> Result<Option<(String, i64)>, 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<Option<ASTNode>, 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: `<var> = <var> + <var>` pattern only
fn extract_carrier_updates_plan(
body: &[ASTNode],
loop_var: &str,
) -> Result<std::collections::BTreeMap<String, ASTNode>, 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
}
}

View File

@ -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;

View File

@ -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;

View File

@ -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<Option<DomainPlan>, String> {
extract_pattern1_plan(ctx.condition, ctx.body)
}

View File

@ -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<Option<DomainPlan>, String> {
extract_pattern2_plan(ctx.condition, ctx.body)
}

View File

@ -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<Option<DomainPlan>, String> {
extract_pattern3_plan(ctx.condition, ctx.body)
}

View File

@ -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<Option<DomainPlan>, String> {
extract_pattern4_plan(ctx.condition, ctx.body)
}

View File

@ -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<Option<DomainPlan>, String> {
extract_pattern5_plan(ctx.condition, ctx.body)
}

View File

@ -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<Option<DomainPlan>, String> {
extract_scan_with_init_plan(ctx.condition, ctx.body, ctx.fn_body)
}

View File

@ -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<Option<DomainPlan>, String> {
extract_split_scan_plan(ctx.condition, ctx.body, &[])
}

View File

@ -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<Option<DomainPlan>, String> {
extract_pattern8_plan(ctx.condition, ctx.body)
}

View File

@ -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<Option<DomainPlan>, String> {
extract_pattern9_plan(ctx.condition, ctx.body)
}

View File

@ -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<Option<DomainPlan>, String> {
rules::try_build_domain_plan(ctx)
}

View File

@ -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<Option<D
},
RuleEntry {
name: "Pattern5_InfiniteEarlyExit (Phase 286 P3.2)",
kind: RuleKind::Simple(legacy_rules::pattern5::extract),
kind: RuleKind::Simple(extractors::pattern5::extract_pattern5_plan),
},
RuleEntry {
name: "Pattern8_BoolPredicateScan (Phase 286 P2.4)",
kind: RuleKind::Simple(legacy_rules::pattern8::extract),
kind: RuleKind::Simple(extractors::pattern8::extract_pattern8_plan),
},
RuleEntry {
name: "Pattern3_IfPhi (Phase 286 P2.6)",
kind: RuleKind::Simple(legacy_rules::pattern3::extract),
kind: RuleKind::Simple(extractors::pattern3::extract_pattern3_plan),
},
RuleEntry {
name: "Pattern4_Continue (Phase 286 P2)",
kind: RuleKind::Simple(legacy_rules::pattern4::extract),
kind: RuleKind::Simple(extractors::pattern4::extract_pattern4_plan),
},
RuleEntry {
name: "Pattern9_AccumConstLoop (Phase 286 P2.3)",
kind: RuleKind::Simple(legacy_rules::pattern9::extract),
kind: RuleKind::Simple(extractors::pattern9::extract_pattern9_plan),
},
RuleEntry {
name: "Pattern2_Break (Phase 286 P3.1)",
@ -59,7 +60,7 @@ pub(super) fn try_build_domain_plan(ctx: &LoopPatternContext) -> Result<Option<D
},
RuleEntry {
name: "Pattern1_SimpleWhile (Phase 286 P2.1)",
kind: RuleKind::Simple(legacy_rules::pattern1::extract),
kind: RuleKind::Simple(extractors::pattern1::extract_pattern1_plan),
},
];
@ -73,23 +74,40 @@ pub(super) fn try_build_domain_plan(ctx: &LoopPatternContext) -> Result<Option<D
}
let (plan_opt, log_none) = match entry.kind {
RuleKind::Simple(extract) => (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<Option<DomainPlan>, String>),
Simple(fn(&ASTNode, &[ASTNode]) -> Result<Option<DomainPlan>, String>),
Pattern6,
Pattern7,
Pattern2,