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:
2025-12-26 02:03:22 +09:00
parent 55d30c9845
commit a824346e30
11 changed files with 1108 additions and 22 deletions

View File

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

View File

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

View File

@ -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
// ============================================================================

View File

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