phase29aj(p1): remove legacy_rules via plan extractors
This commit is contained in:
@ -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::*;
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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::*;
|
||||
|
||||
@ -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::*;
|
||||
|
||||
@ -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::*;
|
||||
|
||||
@ -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: トップレベル末尾continue(body.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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user