feat(plan): Phase 273 P2 Step 3-6 - Pattern7 (SplitScan) to Plan line

Migrate Pattern7 from legacy lowering to Plan architecture:

DomainPlan (mod.rs):
- Added SplitScan(SplitScanPlan) variant
- SplitScanPlan: s_var, sep_var, result_var, i_var, start_var

Extractor (pattern7_split_scan.rs):
- extract_split_scan_plan() returning DomainPlan
- Reuses existing extract_split_scan_parts()

Router (router.rs):
- Pattern7 now uses Plan line (Normalize→Verify→Lower)
- Removed from LOOP_PATTERNS table

Normalizer (normalizer.rs):
- normalize_split_scan() - 400+ lines migrated from impl
- 6 blocks: preheader/header/body/then/else/step/after
- 4 PHIs: header(2) + step(2) for i/start carriers
- Side effect: push with EffectMask::MUT

Bug fixes:
- Pattern6 extractor returns Ok(None) for non-match (allows fallback)
- Reverse scan filtered early in extractor (P1 scope)

Tests:
- phase256_p0_split_vm: PASS (exit=3)
- phase258_p0_index_of_string_vm: PASS (exit=6)

Lowerer no longer contains "split" - pattern-agnostic achieved!

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-22 23:35:43 +09:00
parent d16800741f
commit e4b5a8e832
5 changed files with 515 additions and 9 deletions

View File

@ -85,6 +85,14 @@ pub(crate) fn extract_scan_with_init_plan(
// Call internal extraction helper
let parts = extract_scan_with_init_parts(condition, body, fn_body)?;
// Phase 273 P1: Filter out patterns not supported by Plan-based normalizer
if let Some(ref p) = parts {
// P1 scope: Only forward scan (step=1) supported
if p.step_lit != 1 {
return Ok(None); // Let legacy path handle reverse scans
}
}
// Wrap in DomainPlan if extracted successfully
Ok(parts.map(|p| {
DomainPlan::ScanWithInit(ScanWithInitPlan {
@ -416,8 +424,15 @@ fn extract_scan_with_init_parts(
}
}
let needle = needle_opt.ok_or_else(|| "No matching needle pattern found")?;
let early_return_expr = early_return_expr_opt.ok_or_else(|| "No early return found")?;
// Phase 273 P2: Return Ok(None) if pattern doesn't match (allow Pattern7 to try)
let needle = match needle_opt {
Some(n) => n,
None => return Ok(None), // Not Pattern6, try next pattern
};
let early_return_expr = match early_return_expr_opt {
Some(e) => e,
None => return Ok(None), // Not Pattern6, try next pattern
};
// Phase 257 P0: Determine haystack based on scan direction
let haystack = match scan_direction {

View File

@ -54,6 +54,32 @@ struct SplitScanParts {
post_push_ast: Option<ASTNode>, // result.push(s.substring(start, s.length()))
}
/// Phase 273 P2: Extract SplitScanPlan from AST (pure)
///
/// Returns DomainPlan if pattern matches, Ok(None) if not.
pub(crate) fn extract_split_scan_plan(
condition: &ASTNode,
body: &[ASTNode],
_post_loop_code: &[ASTNode],
) -> Result<Option<crate::mir::builder::control_flow::plan::DomainPlan>, String> {
use crate::mir::builder::control_flow::plan::{DomainPlan, SplitScanPlan};
// Try to extract using existing implementation
match extract_split_scan_parts(condition, body, &[]) {
Ok(parts) => {
let plan = SplitScanPlan {
s_var: parts.s_var,
sep_var: parts.sep_var,
result_var: parts.result_var,
i_var: parts.i_var,
start_var: parts.start_var,
};
Ok(Some(DomainPlan::SplitScan(plan)))
}
Err(_) => Ok(None), // Pattern doesn't match
}
}
/// Phase 256 P0: Extract SplitScanParts from AST
///
/// **P0 Strategy**: Fixed-form parser (Fail-Fast on mismatch)

View File

@ -236,11 +236,8 @@ pub(crate) static LOOP_PATTERNS: &[LoopPatternEntry] = &[
},
// Phase 273 P0.1: Pattern6 entry removed (migrated to Plan-based routing)
// Pattern6_ScanWithInit now handled via extract_scan_with_init_plan() + PlanLowerer
LoopPatternEntry {
name: "Pattern7_SplitScan", // Phase 256 P0: split/tokenization with variable step (before P3)
detect: super::pattern7_split_scan::can_lower,
lower: super::pattern7_split_scan::lower,
},
// Phase 273 P2: Pattern7 entry removed (migrated to Plan-based routing)
// Pattern7_SplitScan now handled via extract_split_scan_plan() + PlanLowerer
LoopPatternEntry {
name: "Pattern8_BoolPredicateScan", // Phase 259 P0: boolean predicate scan (is_integer/is_valid)
detect: super::pattern8_scan_bool_predicate::can_lower,
@ -325,6 +322,37 @@ pub(crate) fn route_loop_pattern(
}
}
// Phase 273 P2: Try Plan-based Pattern7 (SplitScan)
// Flow: Extract → Normalize → Verify → Lower
match super::pattern7_split_scan::extract_split_scan_plan(
ctx.condition,
ctx.body,
&[],
)? {
Some(domain_plan) => {
// DomainPlan extracted successfully
trace::trace().pattern("route", "Pattern7_SplitScan (DomainPlan)", 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);
}
None => {
// Not Pattern7 - continue to other patterns
if ctx.debug {
trace::trace().debug(
"route",
"Pattern7 Plan extraction returned None, trying other patterns",
);
}
}
}
// 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.