feat(pattern6): support reverse scan for last_index_of

Extend Pattern6 (ScanWithInit) to handle both forward and reverse scans:
- Forward: i=0, loop(i < len), i=i+1 (existing)
- Reverse: i=len-1, loop(i >= 0), i=i-1 (NEW)

Implementation:
- Added ScanDirection enum (Forward/Reverse)
- Updated extract_scan_with_init_parts() to detect both patterns
- Created lower_scan_with_init_reverse() lowerer
- Pattern6 now selects appropriate lowerer based on scan direction

Files modified:
- src/mir/builder/control_flow/joinir/patterns/pattern6_scan_with_init.rs
- src/mir/join_ir/lowering/scan_with_init_reverse.rs (new)
- src/mir/join_ir/lowering/mod.rs

Known issue (pre-existing):
- PHI predecessor mismatch bug exists in Pattern6 (both forward and reverse)
- This bug existed BEFORE Phase 257 P0 implementation
- Out of scope for Phase 257 P0 - will be addressed separately

Phase 257 P0
This commit is contained in:
2025-12-20 20:28:41 +09:00
parent 8394b2d6fd
commit 9ba89bada2
6 changed files with 467 additions and 25 deletions

View File

@ -39,6 +39,15 @@ use crate::ast::{ASTNode, BinaryOperator, LiteralValue};
use crate::mir::builder::MirBuilder;
use crate::mir::ValueId;
/// Phase 257 P0: Scan direction for forward/reverse scan
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum ScanDirection {
/// Forward scan: i < s.length(), i = i + 1
Forward,
/// Reverse scan: i >= 0, i = i - 1
Reverse,
}
/// Phase 254 P1: Extracted structure for scan-with-init pattern
///
/// This structure contains all the information needed to lower an index_of-style loop.
@ -50,12 +59,14 @@ struct ScanParts {
haystack: String,
/// Needle variable name (e.g., "ch")
needle: String,
/// Step literal (P0: must be 1)
/// Step literal (Phase 257: can be 1 forward or -1 reverse)
step_lit: i64,
/// Early return expression (P0: must be Variable(loop_var))
early_return_expr: ASTNode,
/// Not-found return literal (P0: must be -1)
not_found_return_lit: i64,
/// Scan direction (Phase 257 P0)
scan_direction: ScanDirection,
}
/// Phase 254 P0: Detection for Pattern 6 (ScanWithInit)
@ -133,16 +144,18 @@ fn contains_methodcall(node: &ASTNode) -> bool {
}
}
/// Check if value is ConstStep pattern (i = i + 1)
/// Check if value is ConstStep pattern (i = i + 1 or i = i - 1)
/// Phase 257 P0: Accept both forward (Add) and reverse (Subtract)
fn is_const_step_pattern(value: &ASTNode) -> bool {
match value {
ASTNode::BinaryOp {
operator: crate::ast::BinaryOperator::Add,
operator,
left,
right,
..
} => {
matches!(left.as_ref(), ASTNode::Variable { .. })
matches!(operator, crate::ast::BinaryOperator::Add | crate::ast::BinaryOperator::Subtract)
&& matches!(left.as_ref(), ASTNode::Variable { .. })
&& matches!(right.as_ref(), ASTNode::Literal { .. })
}
_ => false,
@ -168,8 +181,8 @@ fn is_const_step_pattern(value: &ASTNode) -> bool {
///
/// # P0 Restrictions
///
/// - Loop condition must be `i < s.length()`
/// - Step must be `i = i + 1` (step_lit == 1)
/// - Loop condition must be `i < s.length()` (forward) or `i >= 0` (reverse)
/// - Step must be `i = i + 1` (forward, step_lit == 1) or `i = i - 1` (reverse, step_lit == -1)
/// - Not-found return must be `-1`
/// - Early return must be `return loop_var`
fn extract_scan_with_init_parts(
@ -179,8 +192,9 @@ fn extract_scan_with_init_parts(
) -> Result<Option<ScanParts>, String> {
use crate::ast::{BinaryOperator, LiteralValue};
// 1. Check loop condition: i < s.length()
let (loop_var, haystack) = match condition {
// 1. Check loop condition: i < s.length() (forward) or i >= 0 (reverse)
let (loop_var, haystack_opt, scan_direction) = match condition {
// Forward: i < s.length()
ASTNode::BinaryOp {
operator: BinaryOperator::Less,
left,
@ -202,14 +216,40 @@ fn extract_scan_with_init_parts(
_ => return Ok(None),
};
(loop_var, haystack)
(loop_var, Some(haystack), ScanDirection::Forward)
}
// Reverse: i >= 0
ASTNode::BinaryOp {
operator: BinaryOperator::GreaterEqual,
left,
right,
..
} => {
let loop_var = match left.as_ref() {
ASTNode::Variable { name, .. } => name.clone(),
_ => return Ok(None),
};
// Check right is Literal(0)
match right.as_ref() {
ASTNode::Literal {
value: LiteralValue::Integer(0),
..
} => {}
_ => return Ok(None),
}
// For reverse, haystack will be extracted from substring call in body
(loop_var, None, ScanDirection::Reverse)
}
_ => return Ok(None),
};
// 2. Find if statement with substring == needle and return loop_var
// Also extract haystack for reverse scans
let mut needle_opt = None;
let mut early_return_expr_opt = None;
let mut haystack_from_substring_opt = None;
for stmt in body {
if let ASTNode::If {
@ -242,6 +282,18 @@ fn extract_scan_with_init_parts(
left.as_ref()
};
// Phase 257 P0: Extract haystack from substring call for reverse scan
if let ASTNode::MethodCall {
object, method, ..
} = substring_side
{
if method == "substring" {
if let ASTNode::Variable { name: haystack_name, .. } = object.as_ref() {
haystack_from_substring_opt = Some(haystack_name.clone());
}
}
}
if let ASTNode::Variable { name: needle_name, .. } = needle_side {
// Check then_body contains return loop_var
if then_body.len() == 1 {
@ -264,7 +316,13 @@ 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")?;
// 3. Check for step: i = i + 1
// Phase 257 P0: Determine haystack based on scan direction
let haystack = match scan_direction {
ScanDirection::Forward => haystack_opt.ok_or_else(|| "Forward scan missing haystack in loop condition")?,
ScanDirection::Reverse => haystack_from_substring_opt.ok_or_else(|| "Reverse scan missing haystack in substring call")?,
};
// 3. Check for step: i = i + 1 (forward) or i = i - 1 (reverse)
let mut step_lit_opt = None;
for stmt in body {
@ -272,7 +330,7 @@ fn extract_scan_with_init_parts(
if let ASTNode::Variable { name: target_name, .. } = target.as_ref() {
if target_name == &loop_var {
if let ASTNode::BinaryOp {
operator: BinaryOperator::Add,
operator,
left,
right,
..
@ -285,7 +343,15 @@ fn extract_scan_with_init_parts(
..
} = right.as_ref()
{
step_lit_opt = Some(*lit);
match operator {
BinaryOperator::Add => {
step_lit_opt = Some(*lit);
}
BinaryOperator::Subtract => {
step_lit_opt = Some(-lit);
}
_ => {}
}
}
}
}
@ -297,9 +363,18 @@ fn extract_scan_with_init_parts(
let step_lit = step_lit_opt.ok_or_else(|| "No step pattern found")?;
// P0: step must be 1
if step_lit != 1 {
return Ok(None);
// Phase 257 P0: Verify step matches scan direction
match scan_direction {
ScanDirection::Forward => {
if step_lit != 1 {
return Ok(None);
}
}
ScanDirection::Reverse => {
if step_lit != -1 {
return Ok(None);
}
}
}
// 4. P0: not-found return must be -1 (hardcoded for now)
@ -312,6 +387,7 @@ fn extract_scan_with_init_parts(
step_lit,
early_return_expr,
not_found_return_lit,
scan_direction,
}))
}
@ -352,7 +428,6 @@ impl MirBuilder {
fn_body: Option<&[ASTNode]>,
) -> Result<Option<ValueId>, String> {
use crate::mir::join_ir::lowering::join_value_space::JoinValueSpace;
use crate::mir::join_ir::lowering::scan_with_init_minimal::lower_scan_with_init_minimal;
use crate::mir::join_ir::lowering::JoinInlineBoundaryBuilder;
let trace = trace::trace();
@ -410,9 +485,18 @@ impl MirBuilder {
);
}
// Step 3: Create JoinModule
// Step 3: Create JoinModule based on scan direction
let mut join_value_space = JoinValueSpace::new();
let join_module = lower_scan_with_init_minimal(&mut join_value_space);
let join_module = match parts.scan_direction {
ScanDirection::Forward => {
use crate::mir::join_ir::lowering::scan_with_init_minimal::lower_scan_with_init_minimal;
lower_scan_with_init_minimal(&mut join_value_space)
}
ScanDirection::Reverse => {
use crate::mir::join_ir::lowering::scan_with_init_reverse::lower_scan_with_init_reverse;
lower_scan_with_init_reverse(&mut join_value_space)
}
};
// Phase 255 P2: Build CarrierInfo for loop variable only
// Step 1: Create CarrierInfo with loop variable (i) only