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:
@ -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
|
||||
|
||||
Reference in New Issue
Block a user