feat(joinir): Phase 170-B else-break pattern detection and negation
Expand Pattern 2 (loop with break) to handle else-break patterns:
- If condition is in else clause: `if (cond) { ... } else { break }`
- Extract and negate condition for proper break detection
- Added has_break_in_else_clause() helper in ast_feature_extractor
- Pattern2 now handles both then-break and else-break structures
Implementation:
- ast_feature_extractor: Added else-break pattern detection
- pattern2_with_break: Detect else-break case and wrap condition in UnaryOp Not
- Enables support for patterns like trim() with inverted break logic
Known limitation:
- Pattern 2 requires break conditions to only depend on:
* Loop parameter (e.g., 'start' in loop(start < end))
* Condition-only variables from outer scope (e.g., 'end')
- Does NOT support break conditions using loop-body variables (e.g., 'ch')
- Future Pattern 5+ will handle more complex break conditions
🤖 Generated with Claude Code
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
@ -203,9 +203,39 @@ fn has_break_node(node: &ASTNode) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
/// Phase 170-B: Check if break is in else clause
|
||||
///
|
||||
/// Helper function to determine if a break statement is in the else clause
|
||||
/// of an if-else statement, as opposed to the then clause.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `body` - Loop body statements to search
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// `true` if an `if ... else { break }` pattern is found
|
||||
pub fn has_break_in_else_clause(body: &[ASTNode]) -> bool {
|
||||
for stmt in body {
|
||||
if let ASTNode::If {
|
||||
else_body: Some(else_body),
|
||||
..
|
||||
} = stmt
|
||||
{
|
||||
if else_body.iter().any(|node| matches!(node, ASTNode::Break { .. })) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Phase 170-B: Extract break condition from loop body
|
||||
///
|
||||
/// Searches for the first `if <condition> { break }` pattern in the loop body.
|
||||
/// Searches for the first break pattern in an if statement:
|
||||
/// - `if <condition> { break }` - returns <condition>
|
||||
/// - `if <condition> { ... } else { break }` - returns `!<condition>` (negated)
|
||||
///
|
||||
/// This is used to delegate break condition lowering to `condition_to_joinir`.
|
||||
///
|
||||
/// # Arguments
|
||||
@ -214,29 +244,50 @@ fn has_break_node(node: &ASTNode) -> bool {
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// `Some(&ASTNode)` - The condition AST node from `if <condition> { break }`
|
||||
/// `Some(&ASTNode)` - The condition AST node (negated for else-break pattern)
|
||||
/// `None` - No break statement found or break is not in a simple if statement
|
||||
///
|
||||
/// # Example
|
||||
/// # Examples
|
||||
///
|
||||
/// ```nyash
|
||||
/// // Pattern 1: if condition { break }
|
||||
/// loop(i < 3) {
|
||||
/// if i >= 2 { break } // <- Returns the "i >= 2" condition
|
||||
/// i = i + 1
|
||||
/// }
|
||||
///
|
||||
/// // Pattern 2: if condition { ... } else { break }
|
||||
/// loop(start < end) {
|
||||
/// if ch == " " { start = start + 1 } else { break }
|
||||
/// // <- Returns the "!(ch == " ")" condition (negated)
|
||||
/// }
|
||||
/// ```
|
||||
pub fn extract_break_condition(body: &[ASTNode]) -> Option<&ASTNode> {
|
||||
for stmt in body {
|
||||
if let ASTNode::If {
|
||||
condition,
|
||||
then_body,
|
||||
else_body,
|
||||
..
|
||||
} = stmt
|
||||
{
|
||||
// Check if the then_body contains a break statement
|
||||
// Pattern 1: Check if the then_body contains a break statement
|
||||
if then_body.iter().any(|node| matches!(node, ASTNode::Break { .. })) {
|
||||
return Some(condition.as_ref());
|
||||
}
|
||||
|
||||
// Pattern 2: Check if the else_body contains a break statement
|
||||
// In this case, we need to negate the condition
|
||||
// However, we can't easily negate here without modifying the AST
|
||||
// Instead, we'll return the condition and document that the caller
|
||||
// must handle the negation (or we could return a wrapped node)
|
||||
if let Some(else_body) = else_body {
|
||||
if else_body.iter().any(|node| matches!(node, ASTNode::Break { .. })) {
|
||||
// For else-break pattern, return the condition
|
||||
// The caller (pattern2_with_break) must negate it
|
||||
return Some(condition.as_ref());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
|
||||
@ -138,12 +138,30 @@ impl MirBuilder {
|
||||
|
||||
// Phase 170-B: Extract break condition from loop body
|
||||
use super::ast_feature_extractor;
|
||||
let break_condition = ast_feature_extractor::extract_break_condition(_body)
|
||||
let break_condition_raw = ast_feature_extractor::extract_break_condition(_body)
|
||||
.ok_or_else(|| "[cf_loop/pattern2] Failed to extract break condition from loop body".to_string())?;
|
||||
|
||||
// Phase 170-B: Check if break is in else clause (requires negation)
|
||||
let break_in_else = ast_feature_extractor::has_break_in_else_clause(_body);
|
||||
|
||||
// Wrap condition in UnaryOp Not if break is in else clause
|
||||
use crate::ast::UnaryOperator;
|
||||
let break_condition_node = if break_in_else {
|
||||
// Extract span from the raw condition node (use unknown as default)
|
||||
let span = crate::ast::Span::unknown();
|
||||
|
||||
ASTNode::UnaryOp {
|
||||
operator: UnaryOperator::Not,
|
||||
operand: Box::new(break_condition_raw.clone()),
|
||||
span,
|
||||
}
|
||||
} else {
|
||||
break_condition_raw.clone()
|
||||
};
|
||||
|
||||
// Phase 169 / Phase 171-fix / Phase 172-3 / Phase 170-B: Call Pattern 2 lowerer with break_condition
|
||||
// Phase 33-14: Now returns (JoinModule, JoinFragmentMeta) for expr_result + carrier separation
|
||||
let (join_module, fragment_meta) = match lower_loop_with_break_minimal(scope, condition, break_condition, &env, &loop_var_name) {
|
||||
let (join_module, fragment_meta) = match lower_loop_with_break_minimal(scope, condition, &break_condition_node, &env, &loop_var_name) {
|
||||
Ok((module, meta)) => (module, meta),
|
||||
Err(e) => {
|
||||
// Phase 195: Use unified trace
|
||||
|
||||
Reference in New Issue
Block a user