feat(joinir): Phase 258 P0 dynamic needle window scan
This commit is contained in:
@ -67,6 +67,8 @@ struct ScanParts {
|
||||
not_found_return_lit: i64,
|
||||
/// Scan direction (Phase 257 P0)
|
||||
scan_direction: ScanDirection,
|
||||
/// Phase 258 P0: True if dynamic needle (substr.length()), false if fixed (ch)
|
||||
dynamic_needle: bool,
|
||||
}
|
||||
|
||||
/// Phase 254 P0: Detection for Pattern 6 (ScanWithInit)
|
||||
@ -146,6 +148,77 @@ fn is_const_step_pattern(value: &ASTNode) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
/// Phase 258 P0: Extract and validate substring window arguments
|
||||
///
|
||||
/// Checks if the substring call uses a dynamic window or fixed window:
|
||||
/// - Fixed: `substring(i, i + 1)` → returns `false`
|
||||
/// - Dynamic: `substring(i, i + substr.length())` → returns `true`
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `substring_call` - The MethodCall AST node for substring()
|
||||
/// * `loop_var` - The loop index variable name (e.g., "i")
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Ok(true)` - Dynamic needle (variable.length())
|
||||
/// * `Ok(false)` - Fixed needle (literal 1)
|
||||
/// * `Err(String)` - Invalid substring pattern (not this pattern)
|
||||
fn extract_substring_window(
|
||||
substring_call: &ASTNode,
|
||||
loop_var: &str,
|
||||
) -> Result<bool, String> {
|
||||
use crate::ast::{BinaryOperator, LiteralValue};
|
||||
|
||||
// Extract arguments from substring(start, end)
|
||||
let args = match substring_call {
|
||||
ASTNode::MethodCall { method, arguments, .. } if method == "substring" => arguments,
|
||||
_ => return Err("Not a substring call".to_string()),
|
||||
};
|
||||
|
||||
if args.len() != 2 {
|
||||
return Err(format!("substring expects 2 args, got {}", args.len()));
|
||||
}
|
||||
|
||||
// Check arg[0] is loop_var
|
||||
match &args[0] {
|
||||
ASTNode::Variable { name, .. } if name == loop_var => {}
|
||||
_ => return Err("substring start must be loop_var".to_string()),
|
||||
}
|
||||
|
||||
// Check arg[1] is loop_var + <expr>
|
||||
match &args[1] {
|
||||
ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::Add,
|
||||
left,
|
||||
right,
|
||||
..
|
||||
} => {
|
||||
// Left must be loop_var
|
||||
match left.as_ref() {
|
||||
ASTNode::Variable { name, .. } if name == loop_var => {}
|
||||
_ => return Err("substring end must be loop_var + <expr>".to_string()),
|
||||
}
|
||||
|
||||
// Right determines mode
|
||||
match right.as_ref() {
|
||||
// Fixed: substring(i, i + 1)
|
||||
ASTNode::Literal {
|
||||
value: LiteralValue::Integer(1),
|
||||
..
|
||||
} => Ok(false), // Fixed window (ch)
|
||||
|
||||
// Dynamic: substring(i, i + substr.length())
|
||||
ASTNode::MethodCall { method, .. } if method == "length" => Ok(true), // Dynamic window (substr)
|
||||
|
||||
// Other patterns not supported
|
||||
_ => Err("substring window must be 1 or variable.length()".to_string()),
|
||||
}
|
||||
}
|
||||
_ => Err("substring end must be loop_var + <expr>".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Phase 254 P1: Extract scan-with-init pattern parts from loop AST
|
||||
///
|
||||
/// This function analyzes the loop structure and extracts all necessary information
|
||||
@ -177,8 +250,9 @@ fn extract_scan_with_init_parts(
|
||||
use crate::ast::{BinaryOperator, LiteralValue};
|
||||
|
||||
// 1. Check loop condition: i < s.length() (forward) or i >= 0 (reverse)
|
||||
// Phase 258 P0: Also accept i <= s.length() - substr.length() (dynamic needle)
|
||||
let (loop_var, haystack_opt, scan_direction) = match condition {
|
||||
// Forward: i < s.length()
|
||||
// Forward (Fixed): i < s.length()
|
||||
ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::Less,
|
||||
left,
|
||||
@ -202,6 +276,52 @@ fn extract_scan_with_init_parts(
|
||||
|
||||
(loop_var, Some(haystack), ScanDirection::Forward)
|
||||
}
|
||||
// Forward (Dynamic): i <= s.length() - substr.length()
|
||||
// Phase 258 P0: Accept dynamic needle form for index_of_string
|
||||
ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::LessEqual,
|
||||
left,
|
||||
right,
|
||||
..
|
||||
} => {
|
||||
let loop_var = match left.as_ref() {
|
||||
ASTNode::Variable { name, .. } => name.clone(),
|
||||
_ => return Ok(None),
|
||||
};
|
||||
|
||||
// Right side must be: s.length() - substr.length()
|
||||
let haystack = match right.as_ref() {
|
||||
ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::Subtract,
|
||||
left: sub_left,
|
||||
right: sub_right,
|
||||
..
|
||||
} => {
|
||||
// Left of subtraction: s.length()
|
||||
let haystack = match sub_left.as_ref() {
|
||||
ASTNode::MethodCall {
|
||||
object, method, ..
|
||||
} if method == "length" => match object.as_ref() {
|
||||
ASTNode::Variable { name, .. } => name.clone(),
|
||||
_ => return Ok(None),
|
||||
},
|
||||
_ => return Ok(None),
|
||||
};
|
||||
|
||||
// Right of subtraction: substr.length()
|
||||
match sub_right.as_ref() {
|
||||
ASTNode::MethodCall { method, .. } if method == "length" => {
|
||||
// Valid: s.length() - substr.length()
|
||||
haystack
|
||||
}
|
||||
_ => return Ok(None),
|
||||
}
|
||||
}
|
||||
_ => return Ok(None),
|
||||
};
|
||||
|
||||
(loop_var, Some(haystack), ScanDirection::Forward)
|
||||
}
|
||||
// Reverse: i >= 0
|
||||
ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::GreaterEqual,
|
||||
@ -234,6 +354,7 @@ fn extract_scan_with_init_parts(
|
||||
let mut needle_opt = None;
|
||||
let mut early_return_expr_opt = None;
|
||||
let mut haystack_from_substring_opt = None;
|
||||
let mut dynamic_needle_opt: Option<bool> = None; // Phase 258 P0: Track window mode
|
||||
|
||||
for stmt in body {
|
||||
if let ASTNode::If {
|
||||
@ -278,6 +399,17 @@ fn extract_scan_with_init_parts(
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 258 P0: Validate substring arguments and extract window mode
|
||||
match extract_substring_window(substring_side, &loop_var) {
|
||||
Ok(dynamic_needle) => {
|
||||
dynamic_needle_opt = Some(dynamic_needle);
|
||||
}
|
||||
Err(_) => {
|
||||
// Not a valid substring pattern, fall through
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if let ASTNode::Variable { name: needle_name, .. } = needle_side {
|
||||
// Check then_body contains return loop_var
|
||||
if then_body.len() == 1 {
|
||||
@ -364,6 +496,9 @@ fn extract_scan_with_init_parts(
|
||||
// 4. P0: not-found return must be -1 (hardcoded for now)
|
||||
let not_found_return_lit = -1;
|
||||
|
||||
// Phase 258 P0: Extract dynamic_needle (default to false for backward compat)
|
||||
let dynamic_needle = dynamic_needle_opt.unwrap_or(false);
|
||||
|
||||
Ok(Some(ScanParts {
|
||||
loop_var,
|
||||
haystack,
|
||||
@ -372,6 +507,7 @@ fn extract_scan_with_init_parts(
|
||||
early_return_expr,
|
||||
not_found_return_lit,
|
||||
scan_direction,
|
||||
dynamic_needle,
|
||||
}))
|
||||
}
|
||||
|
||||
@ -474,10 +610,12 @@ impl MirBuilder {
|
||||
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)
|
||||
// Phase 258 P0: Pass dynamic_needle to forward lowerer
|
||||
lower_scan_with_init_minimal(&mut join_value_space, parts.dynamic_needle)
|
||||
}
|
||||
ScanDirection::Reverse => {
|
||||
use crate::mir::join_ir::lowering::scan_with_init_reverse::lower_scan_with_init_reverse;
|
||||
// P0: Reverse lowerer does not support dynamic needle yet
|
||||
lower_scan_with_init_reverse(&mut join_value_space)
|
||||
}
|
||||
};
|
||||
@ -494,12 +632,13 @@ impl MirBuilder {
|
||||
);
|
||||
|
||||
// Phase 255 P2: Create loop_invariants for ch and s
|
||||
// CRITICAL: Order MUST match JoinModule loop_step params: [i, ch, s]
|
||||
// CRITICAL: Order MUST match JoinModule loop_step params: [i, needle, haystack]
|
||||
// carrier_order is built as: [loop_var] + loop_invariants
|
||||
// So loop_invariants order determines param-to-PHI mapping for invariants!
|
||||
// Phase 258 P0: In both fixed and dynamic modes, order is [needle, haystack]
|
||||
let loop_invariants = vec![
|
||||
(parts.needle.clone(), ch_host), // ch: needle (JoinIR param 1)
|
||||
(parts.haystack.clone(), s_host), // s: haystack (JoinIR param 2)
|
||||
(parts.needle.clone(), ch_host), // needle (ch or substr) → JoinIR param 1
|
||||
(parts.haystack.clone(), s_host), // haystack (s) → JoinIR param 2
|
||||
];
|
||||
|
||||
if debug {
|
||||
|
||||
Reference in New Issue
Block a user