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
|
/// 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`.
|
/// This is used to delegate break condition lowering to `condition_to_joinir`.
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
@ -214,29 +244,50 @@ fn has_break_node(node: &ASTNode) -> bool {
|
|||||||
///
|
///
|
||||||
/// # Returns
|
/// # 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
|
/// `None` - No break statement found or break is not in a simple if statement
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```nyash
|
/// ```nyash
|
||||||
|
/// // Pattern 1: if condition { break }
|
||||||
/// loop(i < 3) {
|
/// loop(i < 3) {
|
||||||
/// if i >= 2 { break } // <- Returns the "i >= 2" condition
|
/// if i >= 2 { break } // <- Returns the "i >= 2" condition
|
||||||
/// i = i + 1
|
/// 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> {
|
pub fn extract_break_condition(body: &[ASTNode]) -> Option<&ASTNode> {
|
||||||
for stmt in body {
|
for stmt in body {
|
||||||
if let ASTNode::If {
|
if let ASTNode::If {
|
||||||
condition,
|
condition,
|
||||||
then_body,
|
then_body,
|
||||||
|
else_body,
|
||||||
..
|
..
|
||||||
} = stmt
|
} = 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 { .. })) {
|
if then_body.iter().any(|node| matches!(node, ASTNode::Break { .. })) {
|
||||||
return Some(condition.as_ref());
|
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
|
None
|
||||||
|
|||||||
@ -138,12 +138,30 @@ impl MirBuilder {
|
|||||||
|
|
||||||
// Phase 170-B: Extract break condition from loop body
|
// Phase 170-B: Extract break condition from loop body
|
||||||
use super::ast_feature_extractor;
|
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())?;
|
.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 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
|
// 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),
|
Ok((module, meta)) => (module, meta),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
// Phase 195: Use unified trace
|
// Phase 195: Use unified trace
|
||||||
|
|||||||
Reference in New Issue
Block a user