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

@ -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,499 +0,0 @@
//! Pattern5 (Infinite Loop with Early Exit) Extractor
//!
//! Phase 282 P7: ExtractionBased migration for Pattern5
//! Phase 282 P9a: Integrated with common_helpers
//!
//! # Pattern5 Characteristics
//!
//! - Infinite loop: `loop(true)`
//! - Exactly 1 break statement (in simple if pattern)
//! - Exactly 1 continue statement (at loop body end)
//! - No nested loops
//! - No return statements (Err for fail-fast)
//!
//! # USER GUIDANCE Applied
//!
//! - Q1: break-only, return → Err (not Ok(None))
//! - Q2: Test order - Integration → Quick → early_return
//! - return Err 文言統一 (Phase 284 参照)
//! - continue/break 両方必須チェック (再帰カウント + トップレベル末尾)
use crate::ast::ASTNode;
// Phase 282 P9a: Use common_helpers
use super::common_helpers::{
count_control_flow, is_true_literal, validate_break_in_simple_if, validate_continue_at_end,
ControlFlowDetector,
};
/// Pattern5 extracted parts (lightweight, no AST copies)
#[derive(Debug, Clone)]
pub(crate) struct Pattern5Parts {
pub break_count: usize, // Must be 1 for Pattern5
pub continue_count: usize, // Must be 1 for Pattern5
pub return_count: usize, // Must be 0 (Err if > 0, but kept for debug logging)
pub has_nested_loop: bool, // Must be false
pub continue_at_end: bool, // Must be true
pub break_in_simple_if: bool, // Must be true
}
/// Extract Pattern5 (Infinite Loop with Early Exit) parts
///
/// # Pattern5 Shape Guard (from existing implementation)
///
/// - Condition MUST be `true` literal (infinite loop)
/// - MUST have exactly 1 break statement
/// - MUST have exactly 1 continue statement
/// - Continue MUST be at loop body end
/// - Break MUST be in simple if pattern
/// - NO nested loops
/// - NO return statements (Err for fail-fast)
///
/// # Validation Steps (9 checks, grouped into 4-5 blocks for readability)
///
/// **Phase 1**: Check condition is `true` literal
/// **Phase 2**: Count break/continue/return recursively
/// **Phase 3**: Check for return (Err if found - USER GUIDANCE!)
/// **Phase 4**: Validate break count == 1
/// **Phase 5**: Validate continue count == 1 (recursive, with nested loop exclusion)
/// **Phase 6**: Check for nested loops (Ok(None) if found)
/// **Phase 7**: Validate continue position (must be at end, top-level)
/// **Phase 8**: Validate break pattern (simple if)
/// **Phase 9**: Return extracted parts
///
/// Note: Implementation groups these into 4-5 logical blocks for readability
///
/// # Fail-Fast Rules (USER GUIDANCE)
///
/// - `Ok(Some(parts))`: Pattern5 match confirmed
/// - `Ok(None)`: Not Pattern5 (structural mismatch, try other patterns)
/// - `Err(msg)`: Close-but-unsupported (e.g., return found)
pub(crate) fn extract_infinite_early_exit_parts(
condition: &ASTNode,
body: &[ASTNode],
) -> Result<Option<Pattern5Parts>, String> {
// ========================================================================
// Block 1: Check condition is `true` literal (infinite loop)
// ========================================================================
if !is_true_literal(condition) {
return Ok(None); // Not infinite loop → Not Pattern5
}
// ========================================================================
// Block 2: Count control flow recursively (break/continue/return/nested)
// ========================================================================
let (break_count, continue_count, return_count, has_nested_loop) =
count_control_flow_recursive(body);
// Phase 284 P1: Return check removed - now handled by return_collector SSOT
// in conversion_pipeline.rs (JoinIR line common entry point)
// Note: return_count is still tracked for debug logging
// ========================================================================
// Block 3: Validate break/continue counts (exactly 1 each)
// ========================================================================
if break_count != 1 {
return Ok(None); // Not exactly 1 break → Not Pattern5
}
// CRITICAL: 再帰カウントif内continueも含める、nested loop内は除外
// Phase 7 とセットで両方必須count==1 AND 末尾continue
if continue_count != 1 {
return Ok(None); // Not exactly 1 continue (recursive) → Not Pattern5
}
if has_nested_loop {
return Ok(None); // Nested loops not supported in Pattern5
}
// ========================================================================
// Block 4: Validate continue position & break pattern
// ========================================================================
// CRITICAL: トップレベル末尾continuebody.last()==Continue
// Phase 5 とセットで両方必須count==1 AND 末尾continue
let continue_at_end = validate_continue_at_end(body);
if !continue_at_end {
return Ok(None); // Continue not at end (top-level) → Not Pattern5 shape
}
let break_in_simple_if = validate_break_in_simple_if(body);
if !break_in_simple_if {
return Ok(None); // Break not in simple if → Not Pattern5 shape
}
// ========================================================================
// Block 5: Return extracted parts (all checks passed)
// ========================================================================
Ok(Some(Pattern5Parts {
break_count,
continue_count,
return_count,
has_nested_loop,
continue_at_end,
break_in_simple_if,
}))
}
// ============================================================================
// Helper Functions (Internal)
// ============================================================================
// Phase 282 P9a: is_true_literal moved to common_helpers
/// Count break/continue/return recursively (Pattern5 version)
///
/// # Phase 282 P9a: Delegates to common_helpers::count_control_flow
///
/// - Uses ControlFlowDetector with count_returns=true
/// - Skips nested loop bodies (default behavior)
fn count_control_flow_recursive(body: &[ASTNode]) -> (usize, usize, usize, bool) {
let mut detector = ControlFlowDetector::default();
detector.count_returns = true;
let counts = count_control_flow(body, detector);
(
counts.break_count,
counts.continue_count,
counts.return_count,
counts.has_nested_loop,
)
}
// Phase 282 P9a: validate_continue_at_end moved to common_helpers
// Phase 282 P9a: validate_break_in_simple_if moved to common_helpers
// ============================================================================
// Phase 286 P3.2: Plan line extractor (PoC subset)
// ============================================================================
use crate::mir::builder::control_flow::plan::{
DomainPlan, Pattern5InfiniteEarlyExitPlan, Pattern5ExitKind,
};
/// Extract variable name and increment expression from assignment
///
/// Returns (variable_name, increment_expr) for `var = expr` form
fn extract_assignment_parts(stmt: &ASTNode) -> Option<(String, ASTNode)> {
if let ASTNode::Assignment { target, value, .. } = stmt {
if let ASTNode::Variable { name, .. } = target.as_ref() {
return Some((name.clone(), value.as_ref().clone()));
}
}
None
}
/// Extract Pattern5 Plan (Phase 286 P3.2)
///
/// # PoC Subset (strict)
///
/// - `loop(true)` literal ONLY (not `loop(1)` or truthy)
/// - Return version: `if (cond) { return <expr> }` + `i = i + 1`
/// - Break version: `if (cond) { break }` + `sum = sum + 1` + `i = i + 1` (carrier_update required)
///
/// # Returns
///
/// - `Ok(Some(DomainPlan))`: Pattern5 Plan match
/// - `Ok(None)`: Not PoC subset (fall back to legacy or other patterns)
/// - `Err(msg)`: Close-but-unsupported (Fail-Fast)
pub(crate) fn extract_pattern5_plan(
condition: &ASTNode,
body: &[ASTNode],
) -> Result<Option<DomainPlan>, String> {
// ========================================================================
// Block 1: Check condition is `true` literal (loop(true) ONLY)
// ========================================================================
if !is_true_literal(condition) {
return Ok(None); // Not loop(true) → Not Pattern5 Plan
}
// ========================================================================
// Block 2: Find exit statement (if at first position)
// ========================================================================
// PoC subset: first statement must be `if (cond) { return/break }`
if body.is_empty() {
return Ok(None);
}
let first_stmt = &body[0];
let (exit_kind, exit_condition, exit_value) = match first_stmt {
ASTNode::If { condition: if_cond, then_body, else_body: None, .. } => {
// Must be single-statement then body
if then_body.len() != 1 {
return Ok(None);
}
match &then_body[0] {
ASTNode::Return { value, .. } => {
(Pattern5ExitKind::Return, if_cond.as_ref().clone(), value.clone())
}
ASTNode::Break { .. } => {
(Pattern5ExitKind::Break, if_cond.as_ref().clone(), None)
}
_ => return Ok(None),
}
}
_ => return Ok(None),
};
// ========================================================================
// Block 3: Parse remaining body for carrier update and loop increment
// ========================================================================
let remaining = &body[1..];
match exit_kind {
Pattern5ExitKind::Return => {
// Return version: just need loop increment
// Expected: [increment]
if remaining.len() != 1 {
return Ok(None);
}
let (loop_var, loop_increment) = match extract_assignment_parts(&remaining[0]) {
Some(result) => result,
None => return Ok(None),
};
// Unbox exit_value if present
let exit_value_unboxed = exit_value.map(|boxed| boxed.as_ref().clone());
Ok(Some(DomainPlan::Pattern5InfiniteEarlyExit(Pattern5InfiniteEarlyExitPlan {
loop_var,
exit_kind,
exit_condition,
exit_value: exit_value_unboxed,
carrier_var: None,
carrier_update: None,
loop_increment,
})))
}
Pattern5ExitKind::Break => {
// Break version: need carrier update + loop increment
// Expected: [carrier_update, increment]
if remaining.len() != 2 {
return Ok(None);
}
// Parse carrier update (sum = sum + 1)
let (carrier_var, carrier_update) = match extract_assignment_parts(&remaining[0]) {
Some(result) => result,
None => return Ok(None),
};
// Parse loop increment (i = i + 1)
let (loop_var, loop_increment) = match extract_assignment_parts(&remaining[1]) {
Some(result) => result,
None => return Ok(None),
};
Ok(Some(DomainPlan::Pattern5InfiniteEarlyExit(Pattern5InfiniteEarlyExitPlan {
loop_var,
exit_kind,
exit_condition,
exit_value: None,
carrier_var: Some(carrier_var),
carrier_update: Some(carrier_update),
loop_increment,
})))
}
}
}
// ============================================================================
// Unit Tests
// ============================================================================
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::{ASTNode, BinaryOperator, LiteralValue, Span};
#[test]
fn test_pattern5_infinite_loop_success() {
// loop(true) { if (counter == 3) { break } counter = counter + 1; continue }
let condition = make_true_literal();
let body = vec![
make_if_break_on_counter(), // if (counter == 3) { break }
make_increment("counter"), // counter = counter + 1
make_continue(), // continue
];
let result = extract_infinite_early_exit_parts(&condition, &body);
assert!(result.is_ok());
let parts = result.unwrap();
assert!(parts.is_some());
let parts = parts.unwrap();
assert_eq!(parts.break_count, 1);
assert_eq!(parts.continue_count, 1);
assert_eq!(parts.return_count, 0);
assert!(!parts.has_nested_loop);
assert!(parts.continue_at_end);
assert!(parts.break_in_simple_if);
}
#[test]
fn test_pattern5_not_infinite_returns_none() {
// loop(i < 10) { ... } → Not Pattern5 (not infinite)
let condition = make_condition("i", 10);
let body = vec![make_if_break(), make_continue()];
let result = extract_infinite_early_exit_parts(&condition, &body);
assert!(result.is_ok());
assert!(result.unwrap().is_none()); // Not infinite → Not Pattern5
}
#[test]
fn test_pattern5_with_return_is_allowed() {
// Phase 284 P1: loop(true) { if (...) { return } if (done) { break } continue }
// Return check moved to return_collector SSOT in conversion_pipeline.rs
// Note: Pattern5 requires exactly 1 break, so we need to add one
let condition = make_true_literal();
let body = vec![
make_if_return(), // if (...) { return }
make_if_break(), // if (done) { break } - required for Pattern5
make_continue(),
];
let result = extract_infinite_early_exit_parts(&condition, &body);
assert!(result.is_ok()); // Phase 284 P1: Now allowed at extractor level
let parts = result.unwrap();
assert!(parts.is_some()); // Pattern5 extraction succeeds
let parts = parts.unwrap();
assert_eq!(parts.break_count, 1);
assert_eq!(parts.continue_count, 1);
assert_eq!(parts.return_count, 1); // Return is now tracked, not rejected
}
#[test]
fn test_pattern5_multiple_breaks_returns_none() {
// loop(true) { if (...) { break } if (...) { break } continue }
// → Not Pattern5 (break_count != 1)
let condition = make_true_literal();
let body = vec![make_if_break(), make_if_break(), make_continue()];
let result = extract_infinite_early_exit_parts(&condition, &body);
assert!(result.is_ok());
assert!(result.unwrap().is_none()); // break_count != 1 → Not Pattern5
}
#[test]
fn test_pattern5_continue_not_at_end_returns_none() {
// loop(true) { if (...) { break } continue; i = i + 1 }
// → Not Pattern5 (continue not at end)
let condition = make_true_literal();
let body = vec![
make_if_break(),
make_continue(),
make_increment("i"), // continue が末尾じゃない
];
let result = extract_infinite_early_exit_parts(&condition, &body);
assert!(result.is_ok());
assert!(result.unwrap().is_none()); // continue not at end → Not Pattern5
}
// ========================================================================
// Helper functions for test fixtures
// ========================================================================
fn make_true_literal() -> ASTNode {
ASTNode::Literal {
value: LiteralValue::Bool(true),
span: Span::unknown(),
}
}
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(),
}
}
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(),
}
}
fn make_if_break_on_counter() -> ASTNode {
ASTNode::If {
condition: Box::new(ASTNode::BinaryOp {
operator: BinaryOperator::Equal,
left: Box::new(ASTNode::Variable {
name: "counter".to_string(),
span: Span::unknown(),
}),
right: Box::new(ASTNode::Literal {
value: LiteralValue::Integer(3),
span: Span::unknown(),
}),
span: Span::unknown(),
}),
then_body: vec![ASTNode::Break {
span: Span::unknown(),
}],
else_body: None,
span: Span::unknown(),
}
}
fn make_if_return() -> ASTNode {
ASTNode::If {
condition: Box::new(ASTNode::Variable {
name: "error".to_string(),
span: Span::unknown(),
}),
then_body: vec![ASTNode::Return {
value: None,
span: Span::unknown(),
}],
else_body: None,
span: Span::unknown(),
}
}
fn make_continue() -> ASTNode {
ASTNode::Continue {
span: Span::unknown(),
}
}
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(),
}
}
}

View File

@ -1,450 +0,0 @@
//! Phase 286 P2.4: Pattern8 (BoolPredicateScan) Extraction
//!
//! Minimal subset extractor for Pattern8 Plan line.
//!
//! # Supported subset (PoC safety)
//!
//! - Loop condition: `i < s.length()`
//! - Body: if statement with predicate check + loop increment
//! - If condition: `not me.is_digit(s.substring(i, i + 1))`
//! - Then branch: `return false`
//! - Loop increment: `i = i + 1`
//! - Post-loop: `return true` (enforced by caller)
//! - Step literal: 1 (forward scan only)
//!
//! Returns Ok(None) for unsupported patterns → legacy fallback
use crate::ast::{ASTNode, BinaryOperator, LiteralValue, UnaryOperator};
use crate::mir::builder::control_flow::plan::{DomainPlan, Pattern8BoolPredicateScanPlan};
/// Phase 286 P2.4: Minimal subset extractor for Pattern8 Plan line
///
/// # Detection Criteria
///
/// 1. **Condition**: `i < s.length()` (forward scan)
/// 2. **Body**: if statement with predicate check
/// - Condition: `not me.is_digit(s.substring(i, i + 1))`
/// - Then branch: `return false`
/// 3. **Body**: loop increment `i = i + 1`
/// 4. **Step literal**: 1 (P0: forward scan only)
///
/// # Returns
///
/// - `Ok(Some(plan))`: Pattern8 match confirmed
/// - `Ok(None)`: Not Pattern8 (構造不一致 or unsupported)
/// - `Err(msg)`: Logic bug (malformed AST)
pub(crate) fn extract_pattern8_plan(
condition: &ASTNode,
body: &[ASTNode],
) -> Result<Option<DomainPlan>, String> {
// Step 1: Validate loop condition: i < s.length()
let (loop_var, haystack) = match validate_loop_condition_plan(condition) {
Some((var, hay)) => (var, hay),
None => return Ok(None), // Unsupported condition format
};
// Step 2: Extract predicate check (if not predicate() { return false })
let (predicate_receiver, predicate_method) = match extract_predicate_check(body, &loop_var, &haystack) {
Some((receiver, method)) => (receiver, method),
None => return Ok(None), // No predicate pattern found
};
// Step 3: Extract loop increment (i = i + 1)
let step_lit = match extract_loop_increment(body, &loop_var) {
Some(lit) => lit,
None => return Ok(None), // No increment found
};
// P0: Step must be 1 (forward scan only)
if step_lit != 1 {
return Ok(None);
}
Ok(Some(DomainPlan::Pattern8BoolPredicateScan(
Pattern8BoolPredicateScanPlan {
loop_var,
haystack,
predicate_receiver,
predicate_method,
condition: condition.clone(),
step_lit,
},
)))
}
/// Validate loop condition: supports `i < s.length()` only
fn validate_loop_condition_plan(cond: &ASTNode) -> Option<(String, String)> {
if let ASTNode::BinaryOp {
operator: BinaryOperator::Less,
left,
right,
..
} = cond
{
// Left must be a variable (loop_var)
let loop_var = if let ASTNode::Variable { name, .. } = left.as_ref() {
name.clone()
} else {
return None;
};
// Right must be s.length()
let haystack = if let ASTNode::MethodCall {
object,
method,
..
} = right.as_ref()
{
if method == "length" {
if let ASTNode::Variable { name, .. } = object.as_ref() {
name.clone()
} else {
return None;
}
} else {
return None;
}
} else {
return None;
};
Some((loop_var, haystack))
} else {
None
}
}
/// Extract predicate check from body
///
/// Looks for: `if not receiver.method(s.substring(i, i + 1)) { return false }`
///
/// Returns: Some((receiver, method)) or None
fn extract_predicate_check(
body: &[ASTNode],
loop_var: &str,
haystack: &str,
) -> Option<(String, String)> {
for stmt in body.iter() {
if let ASTNode::If {
condition: if_cond,
then_body,
..
} = stmt
{
// Check if condition is: not receiver.method(...)
if let ASTNode::UnaryOp {
operator: UnaryOperator::Not,
operand,
..
} = if_cond.as_ref()
{
// Operand must be MethodCall
if let ASTNode::MethodCall {
object,
method,
arguments,
..
} = operand.as_ref()
{
// Extract receiver (e.g., "me")
let receiver = match object.as_ref() {
ASTNode::Variable { name, .. } => name.clone(),
ASTNode::Me { .. } => "me".to_string(),
_ => continue,
};
// P0: Expect 1 argument: s.substring(i, i + 1)
if arguments.len() != 1 {
continue;
}
// Validate argument is substring call
if !validate_substring_call(&arguments[0], haystack, loop_var) {
continue;
}
// Check then_body contains: return false
if then_body.len() == 1 {
if let ASTNode::Return { value, .. } = &then_body[0] {
if let Some(ret_val) = value {
if matches!(
ret_val.as_ref(),
ASTNode::Literal {
value: LiteralValue::Bool(false),
..
}
) {
return Some((receiver, method.clone()));
}
}
}
}
}
}
}
}
None
}
/// Validate substring call: s.substring(i, i + 1)
fn validate_substring_call(arg: &ASTNode, haystack: &str, loop_var: &str) -> bool {
if let ASTNode::MethodCall {
object: substr_obj,
method: substr_method,
arguments: substr_args,
..
} = arg
{
if substr_method != "substring" {
return false;
}
// Object must be haystack
if let ASTNode::Variable { name, .. } = substr_obj.as_ref() {
if name != haystack {
return false;
}
} else {
return false;
}
// Args: (i, i + 1)
if substr_args.len() != 2 {
return false;
}
// Arg 0: loop_var
if !matches!(
&substr_args[0],
ASTNode::Variable { name, .. } if name == loop_var
) {
return false;
}
// Arg 1: loop_var + 1
if let ASTNode::BinaryOp {
operator: BinaryOperator::Add,
left,
right,
..
} = &substr_args[1]
{
// Left: loop_var
if !matches!(left.as_ref(), ASTNode::Variable { name, .. } if name == loop_var) {
return false;
}
// Right: Literal(1)
if !matches!(
right.as_ref(),
ASTNode::Literal {
value: LiteralValue::Integer(1),
..
}
) {
return false;
}
true
} else {
false
}
} else {
false
}
}
/// Extract loop increment from body
///
/// Looks for: `i = i + 1`
///
/// Returns: Some(step_lit) or None
fn extract_loop_increment(body: &[ASTNode], loop_var: &str) -> Option<i64> {
for stmt in body {
if let ASTNode::Assignment { target, value, .. } = stmt {
if let ASTNode::Variable {
name: target_name, ..
} = target.as_ref()
{
if target_name == loop_var {
if let ASTNode::BinaryOp {
operator: BinaryOperator::Add,
left,
right,
..
} = value.as_ref()
{
if let ASTNode::Variable { name: left_name, .. } = left.as_ref() {
if left_name == loop_var {
if let ASTNode::Literal {
value: LiteralValue::Integer(lit),
..
} = right.as_ref()
{
return Some(*lit);
}
}
}
}
}
}
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::Span;
fn make_condition(var: &str, haystack: &str) -> ASTNode {
ASTNode::BinaryOp {
operator: BinaryOperator::Less,
left: Box::new(ASTNode::Variable {
name: var.to_string(),
span: Span::unknown(),
}),
right: Box::new(ASTNode::MethodCall {
object: Box::new(ASTNode::Variable {
name: haystack.to_string(),
span: Span::unknown(),
}),
method: "length".to_string(),
arguments: vec![],
span: Span::unknown(),
}),
span: Span::unknown(),
}
}
fn make_predicate_if(receiver: &str, method: &str, haystack: &str, loop_var: &str) -> ASTNode {
ASTNode::If {
condition: Box::new(ASTNode::UnaryOp {
operator: UnaryOperator::Not,
operand: Box::new(ASTNode::MethodCall {
object: Box::new(if receiver == "me" {
ASTNode::Me {
span: Span::unknown(),
}
} else {
ASTNode::Variable {
name: receiver.to_string(),
span: Span::unknown(),
}
}),
method: method.to_string(),
arguments: vec![ASTNode::MethodCall {
object: Box::new(ASTNode::Variable {
name: haystack.to_string(),
span: Span::unknown(),
}),
method: "substring".to_string(),
arguments: vec![
ASTNode::Variable {
name: loop_var.to_string(),
span: Span::unknown(),
},
ASTNode::BinaryOp {
operator: BinaryOperator::Add,
left: Box::new(ASTNode::Variable {
name: loop_var.to_string(),
span: Span::unknown(),
}),
right: Box::new(ASTNode::Literal {
value: LiteralValue::Integer(1),
span: Span::unknown(),
}),
span: Span::unknown(),
},
],
span: Span::unknown(),
}],
span: Span::unknown(),
}),
span: Span::unknown(),
}),
then_body: vec![ASTNode::Return {
value: Some(Box::new(ASTNode::Literal {
value: LiteralValue::Bool(false),
span: Span::unknown(),
})),
span: Span::unknown(),
}],
else_body: None,
span: Span::unknown(),
}
}
fn make_loop_increment(var: &str, step: i64) -> 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(step),
span: Span::unknown(),
}),
span: Span::unknown(),
}),
span: Span::unknown(),
}
}
#[test]
fn test_extract_pattern8_success() {
// loop(i < s.length()) { if not me.is_digit(s.substring(i, i + 1)) { return false } i = i + 1 }
let condition = make_condition("i", "s");
let body = vec![
make_predicate_if("me", "is_digit", "s", "i"),
make_loop_increment("i", 1),
];
let result = extract_pattern8_plan(&condition, &body);
assert!(result.is_ok());
let plan = result.unwrap();
assert!(plan.is_some());
if let Some(DomainPlan::Pattern8BoolPredicateScan(p)) = plan {
assert_eq!(p.loop_var, "i");
assert_eq!(p.haystack, "s");
assert_eq!(p.predicate_receiver, "me");
assert_eq!(p.predicate_method, "is_digit");
assert_eq!(p.step_lit, 1);
} else {
panic!("Expected Pattern8BoolPredicateScan");
}
}
#[test]
fn test_extract_pattern8_wrong_step_returns_none() {
// loop(i < s.length()) { ... i = i + 2 } <- wrong step
let condition = make_condition("i", "s");
let body = vec![
make_predicate_if("me", "is_digit", "s", "i"),
make_loop_increment("i", 2),
];
let result = extract_pattern8_plan(&condition, &body);
assert!(result.is_ok());
assert!(result.unwrap().is_none()); // Wrong step → None
}
#[test]
fn test_extract_pattern8_no_predicate_returns_none() {
// loop(i < s.length()) { i = i + 1 } <- no predicate check
let condition = make_condition("i", "s");
let body = vec![make_loop_increment("i", 1)];
let result = extract_pattern8_plan(&condition, &body);
assert!(result.is_ok());
assert!(result.unwrap().is_none()); // No predicate → None
}
}

View File

@ -1,318 +0,0 @@
//! Phase 286 P2.3: Pattern9 (AccumConstLoop) Extraction
//!
//! Minimal subset extractor for Pattern9 Plan line.
//!
//! # Supported subset (PoC safety)
//!
//! - Loop condition: `<var> < <int_lit>`
//! - Body: 2 assignments only (順序固定)
//! 1. `<acc_var> = <acc_var> + <expr>` (const OR var accumulation)
//! 2. `<loop_var> = <loop_var> + <int_lit>` (increment)
//! - No break/continue/if/return
//!
//! Returns Ok(None) for unsupported patterns → legacy fallback
use crate::ast::{ASTNode, BinaryOperator, LiteralValue};
use crate::mir::builder::control_flow::plan::{DomainPlan, Pattern9AccumConstLoopPlan};
// Phase 286 P2.3: Use common_helpers
use super::common_helpers::{extract_loop_increment_plan, has_control_flow_statement};
/// Phase 286 P2.3: Minimal subset extractor for Pattern9 Plan line
///
/// # Detection Criteria
///
/// 1. **Condition**: `<var> < <int_lit>`
/// 2. **Body**: Exactly 2 assignments in fixed order
/// - 1st: `<acc_var> = <acc_var> + <expr>` (accumulation)
/// - 2nd: `<loop_var> = <loop_var> + <int_lit>` (increment)
/// 3. **No control flow**: No break/continue/if-else
///
/// # Returns
///
/// - `Ok(Some(plan))`: Pattern9 match confirmed
/// - `Ok(None)`: Not Pattern9 (構造不一致 or unsupported)
/// - `Err(msg)`: Logic bug (malformed AST)
pub(crate) fn extract_pattern9_plan(
condition: &ASTNode,
body: &[ASTNode],
) -> Result<Option<DomainPlan>, String> {
// Step 1: Validate loop condition is `<var> < <int_lit>`
let loop_var = match validate_loop_condition_plan(condition) {
Some(var) => var,
None => return Ok(None), // Unsupported condition format
};
// Step 2: Reject control flow (break/continue/if-else)
if has_control_flow_statement(body) {
return Ok(None); // Has break/continue → Not Pattern9 for Plan line
}
// Step 3: Validate body has exactly 2 assignments
if body.len() != 2 {
return Ok(None); // Must be exactly 2 statements
}
// Step 4: Extract accumulator update (1st statement)
// Supports: `<acc_var> = <acc_var> + <expr>` (const OR var)
let (acc_var, acc_update) = match extract_accum_update(&body[0], &loop_var) {
Some((var, update)) => (var, update),
None => return Ok(None), // Not a valid accumulator update
};
// Step 5: Extract loop increment (2nd statement)
// Uses common_helpers::extract_loop_increment_plan
let loop_increment = match extract_loop_increment_plan(&body[1..], &loop_var)? {
Some(inc) => inc,
None => return Ok(None), // No loop increment found
};
Ok(Some(DomainPlan::Pattern9AccumConstLoop(Pattern9AccumConstLoopPlan {
loop_var,
acc_var,
condition: condition.clone(),
acc_update,
loop_increment,
})))
}
/// Validate loop condition: supports `<var> < <int_lit>` only
fn validate_loop_condition_plan(cond: &ASTNode) -> Option<String> {
if let ASTNode::BinaryOp { operator, left, right, .. } = cond {
if !matches!(operator, BinaryOperator::Less) {
return None; // Only < supported for PoC
}
// Left must be a variable
let var_name = if let ASTNode::Variable { name, .. } = left.as_ref() {
name.clone()
} else {
return None;
};
// Right must be integer literal
if !matches!(right.as_ref(), ASTNode::Literal { value: LiteralValue::Integer(_), .. }) {
return None;
}
Some(var_name)
} else {
None
}
}
/// Extract accumulator update from assignment
///
/// Supports: `<acc_var> = <acc_var> + <expr>` where <expr> is const OR var
///
/// Returns: Some((acc_var, update_expr)) or None
fn extract_accum_update(stmt: &ASTNode, loop_var: &str) -> Option<(String, ASTNode)> {
if let ASTNode::Assignment { target, value, .. } = stmt {
// target must be a variable (different from loop_var)
if let ASTNode::Variable { name: target_var, .. } = target.as_ref() {
if target_var == loop_var {
return None; // This is the loop increment, not accumulator
}
// value must be `<acc_var> + <expr>`
if let ASTNode::BinaryOp { operator, left, right, .. } = value.as_ref() {
if !matches!(operator, BinaryOperator::Add) {
return None; // Only + supported
}
// Left must be same as target (acc_var = acc_var + ...)
if let ASTNode::Variable { name: left_var, .. } = left.as_ref() {
if left_var != target_var {
return None; // Not a self-update
}
} else {
return None;
}
// Right can be const OR var (ChatGPT feedback: both OK)
// Accept: Literal (const) or Variable (var)
match right.as_ref() {
ASTNode::Literal { value: LiteralValue::Integer(_), .. } => {
// const accumulation: sum = sum + 1
return Some((target_var.clone(), value.as_ref().clone()));
}
ASTNode::Variable { .. } => {
// var accumulation: sum = sum + i
return Some((target_var.clone(), value.as_ref().clone()));
}
_ => return None, // Complex expression not supported
}
}
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::Span;
fn make_condition(var: &str, bound: 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(bound),
span: Span::unknown(),
}),
span: Span::unknown(),
}
}
fn make_accum_const_update(acc_var: &str, val: i64) -> ASTNode {
ASTNode::Assignment {
target: Box::new(ASTNode::Variable {
name: acc_var.to_string(),
span: Span::unknown(),
}),
value: Box::new(ASTNode::BinaryOp {
operator: BinaryOperator::Add,
left: Box::new(ASTNode::Variable {
name: acc_var.to_string(),
span: Span::unknown(),
}),
right: Box::new(ASTNode::Literal {
value: LiteralValue::Integer(val),
span: Span::unknown(),
}),
span: Span::unknown(),
}),
span: Span::unknown(),
}
}
fn make_accum_var_update(acc_var: &str, rhs_var: &str) -> ASTNode {
ASTNode::Assignment {
target: Box::new(ASTNode::Variable {
name: acc_var.to_string(),
span: Span::unknown(),
}),
value: Box::new(ASTNode::BinaryOp {
operator: BinaryOperator::Add,
left: Box::new(ASTNode::Variable {
name: acc_var.to_string(),
span: Span::unknown(),
}),
right: Box::new(ASTNode::Variable {
name: rhs_var.to_string(),
span: Span::unknown(),
}),
span: Span::unknown(),
}),
span: Span::unknown(),
}
}
fn make_loop_increment(var: &str, step: i64) -> 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(step),
span: Span::unknown(),
}),
span: Span::unknown(),
}),
span: Span::unknown(),
}
}
#[test]
fn test_extract_pattern9_const_accum_success() {
// loop(i < 3) { sum = sum + 1; i = i + 1 }
let condition = make_condition("i", 3);
let body = vec![
make_accum_const_update("sum", 1),
make_loop_increment("i", 1),
];
let result = extract_pattern9_plan(&condition, &body);
assert!(result.is_ok());
let plan = result.unwrap();
assert!(plan.is_some());
if let Some(DomainPlan::Pattern9AccumConstLoop(p)) = plan {
assert_eq!(p.loop_var, "i");
assert_eq!(p.acc_var, "sum");
} else {
panic!("Expected Pattern9AccumConstLoop");
}
}
#[test]
fn test_extract_pattern9_var_accum_success() {
// loop(i < 3) { sum = sum + i; i = i + 1 }
let condition = make_condition("i", 3);
let body = vec![
make_accum_var_update("sum", "i"),
make_loop_increment("i", 1),
];
let result = extract_pattern9_plan(&condition, &body);
assert!(result.is_ok());
let plan = result.unwrap();
assert!(plan.is_some());
if let Some(DomainPlan::Pattern9AccumConstLoop(p)) = plan {
assert_eq!(p.loop_var, "i");
assert_eq!(p.acc_var, "sum");
} else {
panic!("Expected Pattern9AccumConstLoop");
}
}
#[test]
fn test_extract_pattern9_wrong_order_returns_none() {
// loop(i < 3) { i = i + 1; sum = sum + 1 } <- wrong order
let condition = make_condition("i", 3);
let body = vec![
make_loop_increment("i", 1),
make_accum_const_update("sum", 1),
];
let result = extract_pattern9_plan(&condition, &body);
assert!(result.is_ok());
assert!(result.unwrap().is_none()); // Wrong order → None
}
#[test]
fn test_extract_pattern9_with_break_returns_none() {
let condition = make_condition("i", 3);
let body = vec![
ASTNode::Break { span: Span::unknown() },
make_accum_const_update("sum", 1),
make_loop_increment("i", 1),
];
let result = extract_pattern9_plan(&condition, &body);
assert!(result.is_ok());
assert!(result.unwrap().is_none()); // Has break → None
}
#[test]
fn test_extract_pattern9_single_stmt_returns_none() {
let condition = make_condition("i", 3);
let body = vec![make_loop_increment("i", 1)];
let result = extract_pattern9_plan(&condition, &body);
assert!(result.is_ok());
assert!(result.unwrap().is_none()); // Only 1 statement → None
}
}