From 7b83d214aee58bffb43dd97c2e13f8099a44c68e Mon Sep 17 00:00:00 2001 From: nyash-codex Date: Sun, 7 Dec 2025 21:09:01 +0900 Subject: [PATCH] feat(joinir): Phase 170-B else-break pattern detection and negation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../joinir/patterns/ast_feature_extractor.rs | 59 +++++++++++++++++-- .../joinir/patterns/pattern2_with_break.rs | 22 ++++++- 2 files changed, 75 insertions(+), 6 deletions(-) diff --git a/src/mir/builder/control_flow/joinir/patterns/ast_feature_extractor.rs b/src/mir/builder/control_flow/joinir/patterns/ast_feature_extractor.rs index 7c93ffa7..71d637d3 100644 --- a/src/mir/builder/control_flow/joinir/patterns/ast_feature_extractor.rs +++ b/src/mir/builder/control_flow/joinir/patterns/ast_feature_extractor.rs @@ -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 { break }` pattern in the loop body. +/// Searches for the first break pattern in an if statement: +/// - `if { break }` - returns +/// - `if { ... } else { break }` - returns `!` (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 { 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 diff --git a/src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs b/src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs index 2d6d9fcd..4f8f6628 100644 --- a/src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs +++ b/src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs @@ -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