feat(joinir): Phase 286 P2/P2.1/P2.2 - JoinIR Line Absorption (Pattern1/4 Plan化 PoC + hygiene)
Phase 286 P2: Pattern4 (Loop with Continue) を Plan/Frag SSOT に移行 - DomainPlan::Pattern4Continue 追加 - PlanNormalizer::normalize_pattern4_continue() 実装(phi_bindings による PHI dst 優先参照) - Router integration(Plan line routing → legacy fallback) - Integration test PASS (output: 6), quick smoke 154/154 PASS Phase 286 P2.1: Pattern1 (SimpleWhile) を Plan/Frag SSOT に移行 - DomainPlan::Pattern1SimpleWhile 追加 - PlanNormalizer::normalize_pattern1_simple_while() 実装(4ブロック、1 PHI、phi_bindings 流用) - Router integration(Plan line routing → legacy fallback) - Integration test PASS (return: 3), quick smoke 154/154 PASS Phase 286 P2.2: hygiene(extractor重複排除 + router小整理) - extractor helper化: extract_loop_increment_plan を common_helpers.rs に統一 - Pattern1/Pattern4 が呼ぶだけに変更(重複排除 ~25行) - router helper化: lower_via_plan() を追加し Pattern6/7/4/1 で共用 - 3行パターン(normalize→verify→lower)を1関数に集約(ボイラープレート削減 ~40行) 成果物: - DomainPlan 2パターン新規追加(Pattern1SimpleWhile, Pattern4Continue) - Normalizer 2つの normalize 関数追加 - Router に Plan line ブロック追加 + lower_via_plan() helper - Extractor に extract_pattern1_plan() 追加 - Integration fixtures 2個 + smoke tests 2個 検証: - quick smoke: 154/154 PASS - integration: phase286_pattern1_frag_poc PASS, phase286_pattern4_frag_poc PASS - Plan line routing: route=plan strategy=extract で Pattern1/4 検出確認 🤖 Generated with Claude Code Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
@ -194,7 +194,44 @@ pub(crate) fn is_true_literal(condition: &ASTNode) -> bool {
|
||||
}
|
||||
|
||||
/// ============================================================
|
||||
/// Group 4: Pattern5-Specific Helpers (NOT generalized)
|
||||
/// Group 4: Loop Increment Extraction (Common for Plan line)
|
||||
/// ============================================================
|
||||
|
||||
/// Phase 286 P2.2: Extract loop increment for Plan line patterns
|
||||
///
|
||||
/// Supports `<var> = <var> + <int_lit>` pattern only (PoC safety).
|
||||
/// Used by Pattern1 and Pattern4 Plan line extractors.
|
||||
///
|
||||
/// # Returns
|
||||
/// - Ok(Some(ASTNode)) if valid increment found
|
||||
/// - Ok(None) if no increment or unsupported pattern
|
||||
/// - Err if malformed AST (rare)
|
||||
pub(crate) fn extract_loop_increment_plan(body: &[ASTNode], loop_var: &str) -> Result<Option<ASTNode>, String> {
|
||||
for stmt in body {
|
||||
if let ASTNode::Assignment { target, value, .. } = stmt {
|
||||
if let ASTNode::Variable { name, .. } = target.as_ref() {
|
||||
if name == loop_var {
|
||||
// Check for `i = i + <int>`
|
||||
if let ASTNode::BinaryOp { operator, left, right, .. } = value.as_ref() {
|
||||
if matches!(operator, BinaryOperator::Add) {
|
||||
if let ASTNode::Variable { name: lname, .. } = left.as_ref() {
|
||||
if lname == loop_var {
|
||||
if matches!(right.as_ref(), ASTNode::Literal { .. }) {
|
||||
return Ok(Some(value.as_ref().clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// ============================================================
|
||||
/// Group 5: Pattern5-Specific Helpers (NOT generalized)
|
||||
/// ============================================================
|
||||
///
|
||||
/// **IMPORTANT**: These helpers are Pattern5-specific and intentionally NOT generalized.
|
||||
|
||||
@ -145,6 +145,78 @@ 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::*;
|
||||
|
||||
@ -99,6 +99,146 @@ fn has_break_statement_recursive(body: &[ASTNode]) -> bool {
|
||||
// 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
|
||||
// ============================================================================
|
||||
|
||||
@ -135,6 +135,24 @@ impl<'a> LoopPatternContext<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Phase 286 P2.2: Common helper for Plan line lowering
|
||||
///
|
||||
/// Extracts the common 3-line pattern used by Pattern6/7/4/1:
|
||||
/// 1. Normalize DomainPlan → CorePlan
|
||||
/// 2. Verify CorePlan invariants (fail-fast)
|
||||
/// 3. Lower CorePlan → MIR
|
||||
///
|
||||
/// This eliminates repetition across 4 pattern routing blocks.
|
||||
fn lower_via_plan(
|
||||
builder: &mut MirBuilder,
|
||||
domain_plan: crate::mir::builder::control_flow::plan::DomainPlan,
|
||||
ctx: &LoopPatternContext,
|
||||
) -> Result<Option<ValueId>, String> {
|
||||
let core_plan = PlanNormalizer::normalize(builder, domain_plan, ctx)?;
|
||||
PlanVerifier::verify(&core_plan)?;
|
||||
PlanLowerer::lower(builder, core_plan, ctx)
|
||||
}
|
||||
|
||||
/// Phase 272 P0.2 Refactoring: can_lower() strategy classification
|
||||
///
|
||||
/// Clarifies the two main detection strategies used across patterns:
|
||||
@ -310,15 +328,8 @@ pub(crate) fn route_loop_pattern(
|
||||
Some(domain_plan) => {
|
||||
// DomainPlan extracted successfully
|
||||
trace::trace().pattern("route", "route=plan strategy=extract pattern=Pattern6_ScanWithInit (Phase 273)", true);
|
||||
|
||||
// Step 1: Normalize DomainPlan → CorePlan
|
||||
let core_plan = PlanNormalizer::normalize(builder, domain_plan, ctx)?;
|
||||
|
||||
// Step 2: Verify CorePlan invariants (fail-fast)
|
||||
PlanVerifier::verify(&core_plan)?;
|
||||
|
||||
// Step 3: Lower CorePlan → MIR
|
||||
return PlanLowerer::lower(builder, core_plan, ctx);
|
||||
// Phase 286 P2.2: Use common helper
|
||||
return lower_via_plan(builder, domain_plan, ctx);
|
||||
}
|
||||
None => {
|
||||
// Not Pattern6 - continue to other patterns
|
||||
@ -341,15 +352,8 @@ pub(crate) fn route_loop_pattern(
|
||||
Some(domain_plan) => {
|
||||
// DomainPlan extracted successfully
|
||||
trace::trace().pattern("route", "route=plan strategy=extract pattern=Pattern7_SplitScan (Phase 273)", true);
|
||||
|
||||
// Step 1: Normalize DomainPlan → CorePlan
|
||||
let core_plan = PlanNormalizer::normalize(builder, domain_plan, ctx)?;
|
||||
|
||||
// Step 2: Verify CorePlan invariants (fail-fast)
|
||||
PlanVerifier::verify(&core_plan)?;
|
||||
|
||||
// Step 3: Lower CorePlan → MIR
|
||||
return PlanLowerer::lower(builder, core_plan, ctx);
|
||||
// Phase 286 P2.2: Use common helper
|
||||
return lower_via_plan(builder, domain_plan, ctx);
|
||||
}
|
||||
None => {
|
||||
// Not Pattern7 - continue to other patterns
|
||||
@ -362,6 +366,52 @@ pub(crate) fn route_loop_pattern(
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 286 P2: Try Plan-based Pattern4 (Loop with Continue)
|
||||
// Flow: Extract → Normalize → Verify → Lower
|
||||
match super::extractors::pattern4::extract_pattern4_plan(
|
||||
ctx.condition,
|
||||
ctx.body,
|
||||
)? {
|
||||
Some(domain_plan) => {
|
||||
// DomainPlan extracted successfully
|
||||
trace::trace().pattern("route", "route=plan strategy=extract pattern=Pattern4_Continue (Phase 286 P2)", true);
|
||||
// Phase 286 P2.2: Use common helper
|
||||
return lower_via_plan(builder, domain_plan, ctx);
|
||||
}
|
||||
None => {
|
||||
// Not Pattern4 Plan - continue to legacy Pattern4
|
||||
if ctx.debug {
|
||||
trace::trace().debug(
|
||||
"route",
|
||||
"Pattern4 Plan extraction returned None, trying legacy Pattern4",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 286 P2.1: Try Plan-based Pattern1 (Simple While Loop)
|
||||
// Flow: Extract → Normalize → Verify → Lower
|
||||
match super::extractors::pattern1::extract_pattern1_plan(
|
||||
ctx.condition,
|
||||
ctx.body,
|
||||
)? {
|
||||
Some(domain_plan) => {
|
||||
// DomainPlan extracted successfully
|
||||
trace::trace().pattern("route", "route=plan strategy=extract pattern=Pattern1_SimpleWhile (Phase 286 P2.1)", true);
|
||||
// Phase 286 P2.2: Use common helper
|
||||
return lower_via_plan(builder, domain_plan, ctx);
|
||||
}
|
||||
None => {
|
||||
// Not Pattern1 Plan - continue to legacy Pattern1
|
||||
if ctx.debug {
|
||||
trace::trace().debug(
|
||||
"route",
|
||||
"Pattern1 Plan extraction returned None, trying legacy Pattern1",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 183: Route based on pre-classified pattern kind
|
||||
// Pattern kind was already determined by ctx.pattern_kind in LoopPatternContext::new()
|
||||
// This eliminates duplicate detection logic across routers.
|
||||
|
||||
Reference in New Issue
Block a user