Files
hakorune/docs/archive/phases/phase-150-167/phase167_boolexpr_lowerer_design.md
nyash-codex d4f90976da refactor(joinir): Phase 244 - ConditionLoweringBox trait unification
Unify condition lowering logic across Pattern 2/4 with trait-based API.

New infrastructure:
- condition_lowering_box.rs: ConditionLoweringBox trait + ConditionContext (293 lines)
- ExprLowerer implements ConditionLoweringBox trait (+51 lines)

Pattern migrations:
- Pattern 2 (loop_with_break_minimal.rs): Use trait API
- Pattern 4 (loop_with_continue_minimal.rs): Use trait API

Benefits:
- Unified condition lowering interface
- Extensible for future lowering strategies
- Clean API boundary between patterns and lowering logic
- Zero code duplication

Test results: 911/911 PASS (+2 new tests)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-11 02:35:31 +09:00

33 KiB
Raw Blame History

Phase 167: BoolExprLowerer Design - 条件式 Lowerer 設計

Date: 2025-12-06 Status: Design Phase Goal: ループ骨格Pattern1-4を維持しつつ、複雑な論理式条件を別箱で処理する設計を確立


Design Philosophy: 箱理論による責務分離

┌─────────────────────────────────────────────────┐
│  Loop Pattern Boxes (Pattern1-4)               │
│  - 制御構造の骨格のみを扱う                      │
│  - break/continue/PHI/ExitBinding              │
│  - 条件式の内部構造は一切見ない                  │
└─────────────────────────────────────────────────┘
                      ↓ lower_condition()
┌─────────────────────────────────────────────────┐
│  BoolExprLowerer Box                           │
│  - 式の構造のみを扱う                           │
│  - AND/OR/NOT/比較演算                          │
│  - 出力: 単一 ValueId (bool 0/1)                │
└─────────────────────────────────────────────────┘

Key Insight: Pattern5 を作らない! → ループパターンを増やすのではなく、BoolExprLowerer の能力を上げる


Task 167-1: 対象条件式の具体化

Target: JsonParserBox の複雑条件ループ

1. _trim メソッドLeading Whitespace

Original Code:

loop(start < end) {
  local ch = s.substring(start, start+1)
  if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" {
    start = start + 1
  } else {
    break
  }
}

Pseudo-code (条件式の構造):

loop_condition: start < end
if_condition: (ch == " ") || (ch == "\t") || (ch == "\n") || (ch == "\r")

AST 構造:

Loop {
  condition: BinOp { op: "<", left: "start", right: "end" },
  body: [
    LocalDecl { name: "ch", value: MethodCall(...) },
    If {
      condition: BinOp {
        op: "||",
        left: BinOp {
          op: "||",
          left: BinOp {
            op: "||",
            left: BinOp { op: "==", left: "ch", right: " " },
            right: BinOp { op: "==", left: "ch", right: "\t" }
          },
          right: BinOp { op: "==", left: "ch", right: "\n" }
        },
        right: BinOp { op: "==", left: "ch", right: "\r" }
      },
      then: [ Assignment { target: "start", value: BinOp(...) } ],
      else: [ Break ]
    }
  ]
}

Expected SSA/JoinIR Form:

// Loop condition (simple comparison - already supported)
%cond_loop = Compare Lt %start %end

// If condition (complex OR chain - needs BoolExprLowerer)
%ch = BoxCall StringBox.substring %s %start %start_plus_1
%cmp1 = Compare Eq %ch " "
%cmp2 = Compare Eq %ch "\t"
%cmp3 = Compare Eq %ch "\n"
%cmp4 = Compare Eq %ch "\r"
%or1 = BinOp Or %cmp1 %cmp2
%or2 = BinOp Or %or1 %cmp3
%cond_if = BinOp Or %or2 %cmp4

Pattern Classification:

  • Loop Pattern: Pattern2_WithBreak (break in else branch)
  • Condition Complexity: OR chain with 4 comparisons

2. _trim メソッドTrailing Whitespace

Original Code:

loop(end > start) {
  local ch = s.substring(end-1, end)
  if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" {
    end = end - 1
  } else {
    break
  }
}

Pseudo-code:

loop_condition: end > start
if_condition: (ch == " ") || (ch == "\t") || (ch == "\n") || (ch == "\r")

Pattern Classification:

  • Loop Pattern: Pattern2_WithBreak (same as leading)
  • Condition Complexity: OR chain with 4 comparisons (identical to leading)

3. _skip_whitespace メソッド

Original Code (from Phase 161 inventory):

loop(p < s.length()) {
  local ch = s.substring(p, p+1)
  if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" {
    p = p + 1
  } else {
    break
  }
}

Pattern Classification:

  • Loop Pattern: Pattern2_WithBreak
  • Condition Complexity: OR chain with 4 comparisons (same pattern as _trim)

Other JsonParserBox Loops (Simple Conditions - Already Supported)

Method Loop Condition If Condition Pattern BoolExprLowerer Needed?
_parse_string p < s.length() Simple comparisons Pattern4 No (simple)
_parse_array p < s.length() Simple comparisons Pattern4 No (simple)
_parse_object p < s.length() Simple comparisons Pattern4 No (simple)
_match_literal i < len Simple comparison Pattern1 No (simple)
_unescape_string i < s.length() Simple comparisons Pattern4 No (simple)
_trim (leading) start < end OR chain (4 terms) Pattern2 YES
_trim (trailing) end > start OR chain (4 terms) Pattern2 YES
_skip_whitespace p < s.length() OR chain (4 terms) Pattern2 YES

Condition Expression Taxonomy

Phase 167 Scope (MVP)

Target: JsonParserBox の実際のパターン_trim, _skip_whitespace

1. Simple Comparison (Already Supported)

a < b
a == b
a != b
  • Status: Already handled by existing MIR builder
  • No BoolExprLowerer needed

2. Logical OR Chain (Phase 167 Target)

(ch == " ") || (ch == "\t") || (ch == "\n") || (ch == "\r")
  • Structure: N-ary OR of equality comparisons
  • Common in: Whitespace checking, character set matching
  • SSA Output: Chain of Compare + BinOp Or instructions

3. Logical AND (Future)

(i < len) && (ch != '\0')
  • Status: 🔜 Phase 168+ (not in current JsonParserBox)

4. Logical NOT (Future)

!(finished)
  • Status: 🔜 Phase 168+ (if needed)

Phase 167 Deliverables

Concrete Examples for BoolExprLowerer

Input (AST):

BinOp {
  op: "||",
  left: BinOp { op: "==", left: "ch", right: " " },
  right: BinOp { op: "==", left: "ch", right: "\t" }
}

Output (SSA/JoinIR):

%cmp1 = Compare Eq %ch " "
%cmp2 = Compare Eq %ch "\t"
%result = BinOp Or %cmp1 %cmp2
// Returns: %result (ValueId)

Test Cases

  1. Simple OR (2 terms):

    if a == 1 || a == 2 { ... }
    
  2. OR Chain (4 terms) - _trim pattern:

    if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" { ... }
    
  3. Nested in Loop - Full _trim pattern:

    loop(start < end) {
      if (ch == " ") || (ch == "\t") || ... { ... } else { break }
    }
    

Success Criteria

Phase 167 Complete When:

  1. BoolExprLowerer API designed and documented
  2. Responsibility separation (Loop vs Expression) clearly defined
  3. _trim and _skip_whitespace patterns analyzed and documented
  4. Expected SSA form for OR chains specified
  5. Integration point with Pattern2 identified

🚀 Next Phase (168): BoolExprLowerer Implementation

  • Actual AST → SSA translation code
  • Integration with Pattern2_WithBreak lowerer
  • Test with _trim and _skip_whitespace

Notes

  • No Pattern5: Complex conditions are handled by enhancing BoolExprLowerer, NOT by adding new loop patterns
  • Backward Compatible: Pattern1-4 remain unchanged, only delegate condition lowering
  • Incremental: Start with OR chains (Phase 167), add AND/NOT later if needed

Task 167-2: BoolExprLowerer の責務と API

Module Location

src/mir/join_ir/lowering/bool_expr_lowerer.rs

Rationale:

  • Lives alongside other lowering modules (if_select, loop_patterns, etc.)
  • Part of the lowering package → clear separation from loop pattern logic
  • Single responsibility: Convert AST boolean expressions to SSA form

API Design

Core Structure

use crate::ast::ASTNode;
use crate::mir::{ValueId, MirBuilder};

pub struct BoolExprLowerer<'a> {
    builder: &'a mut MirBuilder,
}

impl<'a> BoolExprLowerer<'a> {
    /// Create a new BoolExprLowerer
    pub fn new(builder: &'a mut MirBuilder) -> Self {
        BoolExprLowerer { builder }
    }

    /// Lower a boolean expression to SSA form
    /// 
    /// # Arguments
    /// * `cond_ast` - AST node representing the condition expression
    /// 
    /// # Returns
    /// * `ValueId` - Register holding the result (bool 0/1)
    /// 
    /// # Supported Expressions (Phase 167 MVP)
    /// - Simple comparison: `a < b`, `a == b`, `a != b`
    /// - Logical OR: `a || b || c || ...`
    /// - Nested: `(a == 1) || (b == 2) || (c == 3)`
    /// 
    /// # Future (Phase 168+)
    /// - Logical AND: `a && b`
    /// - Logical NOT: `!a`
    /// - Mixed: `(a && b) || (c && d)`
    pub fn lower_condition(&mut self, cond_ast: &ASTNode) -> ValueId;
}

Supported Expression Types (Phase 167 Scope)

1. Simple Comparison (Pass-through)

Input AST:

BinOp { op: "<", left: "start", right: "end" }

Implementation:

// Delegate to existing MIR builder's comparison logic
// Already supported - no new code needed
self.builder.emit_compare(op, left_val, right_val)

Output:

%result = Compare Lt %start %end

2. Logical OR Chain (New Logic)

Input AST:

BinOp {
  op: "||",
  left: BinOp { op: "==", left: "ch", right: " " },
  right: BinOp {
    op: "||",
    left: BinOp { op: "==", left: "ch", right: "\t" },
    right: BinOp { op: "==", left: "ch", right: "\n" }
  }
}

Implementation Strategy:

fn lower_logical_or(&mut self, left: &ASTNode, right: &ASTNode) -> ValueId {
    // Recursively lower left and right
    let left_val = self.lower_condition(left);
    let right_val = self.lower_condition(right);
    
    // Emit BinOp Or instruction
    self.builder.emit_binop(BinOpKind::Or, left_val, right_val)
}

Output SSA:

%cmp1 = Compare Eq %ch " "
%cmp2 = Compare Eq %ch "\t"
%cmp3 = Compare Eq %ch "\n"
%or1 = BinOp Or %cmp1 %cmp2
%result = BinOp Or %or1 %cmp3

Integration with Loop Patterns

Before (Pattern2_WithBreak - Direct AST Access)

// In loop_with_break_minimal.rs
let cond = ctx.condition;  // AST node
// ... directly process condition ...

After (Pattern2_WithBreak - Delegate to BoolExprLowerer)

// In loop_with_break_minimal.rs
let mut bool_lowerer = BoolExprLowerer::new(self.builder);
let cond_val = bool_lowerer.lower_condition(&ctx.condition);
// ... use cond_val (ValueId) ...

Key: Loop pattern boxes don't change structure, only delegate condition lowering!


Responsibility Boundaries

Component Responsibility Does NOT Handle
BoolExprLowerer - Convert AST expressions to SSA
- Logical operators (OR, AND, NOT)
- Comparison operators
- Return ValueId
- Loop structure
- Break/Continue
- PHI nodes
- Carrier tracking
Loop Pattern Boxes - Loop structure (header/body/exit)
- Break/Continue handling
- PHI carrier tracking
- ExitBinding generation
- Expression internal structure
- Logical operator expansion
- Comparison semantics

Error Handling

pub enum BoolExprError {
    /// Expression type not yet supported (e.g., AND in Phase 167)
    UnsupportedOperator(String),
    
    /// Invalid AST structure for boolean expression
    InvalidExpression(String),
}

impl BoolExprLowerer<'_> {
    pub fn lower_condition(&mut self, cond_ast: &ASTNode) -> Result<ValueId, BoolExprError>;
}

Phase 167 MVP: Return error for AND/NOT operators Phase 168+: Implement AND/NOT support


Testing Strategy

Unit Tests (bool_expr_lowerer.rs)

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_simple_comparison() {
        // a < b
        let ast = /* ... */;
        let result = lowerer.lower_condition(&ast).unwrap();
        // Assert: result is Compare instruction
    }

    #[test]
    fn test_or_two_terms() {
        // (a == 1) || (b == 2)
        let ast = /* ... */;
        let result = lowerer.lower_condition(&ast).unwrap();
        // Assert: result is BinOp Or of two Compare
    }

    #[test]
    fn test_or_four_terms_trim_pattern() {
        // ch == " " || ch == "\t" || ch == "\n" || ch == "\r"
        let ast = /* ... */;
        let result = lowerer.lower_condition(&ast).unwrap();
        // Assert: chain of BinOp Or instructions
    }

    #[test]
    fn test_and_not_supported_yet() {
        // (a && b) - should return error in Phase 167
        let ast = /* ... */;
        assert!(lowerer.lower_condition(&ast).is_err());
    }
}

Integration Tests (with Pattern2)

#[test]
fn test_pattern2_with_complex_or_condition() {
    // Full _trim-style loop
    let hako_code = r#"
        loop(start < end) {
          if ch == " " || ch == "\t" || ch == "\n" {
            start = start + 1
          } else {
            break
          }
        }
    "#;
    // Assert: Pattern2 matched, BoolExprLowerer called, execution correct
}

Implementation Phases

Phase 167 (Current)

  • Design API
  • Define responsibility boundaries
  • Document integration points
  • 🔜 Stub implementation (return error for all expressions)

Phase 168 (Next)

  • 🔜 Implement OR chain lowering
  • 🔜 Integrate with Pattern2_WithBreak
  • 🔜 Test with _trim and _skip_whitespace

Phase 169 (Future)

  • 🔜 Add AND support
  • 🔜 Add NOT support
  • 🔜 Support mixed AND/OR expressions

Success Criteria for Task 167-2

Complete When:

  1. API signature defined (lower_condition method)
  2. Supported expression types documented (OR chains for Phase 167)
  3. Integration points with loop patterns identified
  4. Responsibility boundaries clearly defined
  5. Error handling strategy established
  6. Test strategy outlined

Task 167-3: LoopLowerer との分業の明文化

Architectural Principle: Single Responsibility Separation

┌──────────────────────────────────────────────────────────┐
│           Loop Pattern Responsibility                     │
│  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━  │
│  • Loop Header (entry block, condition jump)             │
│  • Loop Body (execution path)                            │
│  • Break Handling (exit path, merge point)               │
│  • Continue Handling (restart path)                      │
│  • PHI Carrier Tracking (variables modified in loop)     │
│  • Exit Binding Generation (final values to outer scope) │
│  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━  │
│                                                           │
│  🚫 DOES NOT TOUCH: Condition expression structure       │
└──────────────────────────────────────────────────────────┘
                            ↓
                   lower_condition(&ast)
                            ↓
┌──────────────────────────────────────────────────────────┐
│        BoolExprLowerer Responsibility                     │
│  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━  │
│  • AST → SSA Conversion (boolean expressions)            │
│  • Logical Operators (OR, AND, NOT)                      │
│  • Comparison Operators (<, <=, ==, !=, >, >=)           │
│  • Short-circuit Evaluation (future: AND/OR semantics)   │
│  • Return: ValueId (single register holding bool 0/1)    │
│  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━  │
│                                                           │
│  🚫 DOES NOT TOUCH: Loop structure, break, continue, PHI │
└──────────────────────────────────────────────────────────┘

Concrete Interface Contract

Loop Pattern Box Calls BoolExprLowerer

Example: Pattern2_WithBreak (loop_with_break_minimal.rs)

pub struct Pattern2MinimalLowerer<'a> {
    builder: &'a mut MirBuilder,
    // ... other fields ...
}

impl<'a> Pattern2MinimalLowerer<'a> {
    pub fn lower(&mut self, ctx: &LoopContext) -> Result<ValueId, LoweringError> {
        // 1. Pattern2 handles loop structure
        let entry_block = self.builder.create_block();
        let body_block = self.builder.create_block();
        let exit_block = self.builder.create_block();
        
        // 2. Delegate condition lowering to BoolExprLowerer
        let mut bool_lowerer = BoolExprLowerer::new(self.builder);
        let cond_val = bool_lowerer.lower_condition(&ctx.condition)?;
        //     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        //     This is the ONLY place Pattern2 touches the condition!
        //     It doesn't care if it's simple (a < b) or complex (a || b || c)
        
        // 3. Pattern2 uses the result (cond_val: ValueId) 
        //    to build loop control flow
        self.builder.emit_branch(cond_val, body_block, exit_block);
        
        // 4. Pattern2 handles break/continue/PHI/ExitBinding
        // ... (Pattern2 logic continues) ...
    }
}

Key Point:

  • Pattern2 doesn't inspect ctx.condition internal structure
  • It just calls lower_condition(&ctx.condition) and gets back a ValueId
  • This ValueId is used to build the conditional branch

What Each Box "Sees"

Loop Pattern Box Perspective

Input: LoopContext

struct LoopContext {
    condition: ASTNode,      // <- Opaque! Don't look inside!
    body: Vec<ASTNode>,
    carriers: Vec<String>,
    // ...
}

Processing:

// ✅ Loop pattern's job
let cond_val = delegate_to_bool_lowerer(condition);  // Get ValueId
emit_loop_header(cond_val);
handle_break_continue();
track_phi_carriers();
generate_exit_bindings();

// ❌ NOT loop pattern's job
// Don't do this:
match condition {
    BinOp { op: "||", .. } => { /* handle OR */ },
    BinOp { op: "&&", .. } => { /* handle AND */ },
    // ...
}

Output: JoinIR function with loop structure + exit metadata


BoolExprLowerer Perspective

Input: AST node (condition expression)

BinOp {
  op: "||",
  left: BinOp { op: "==", left: "ch", right: " " },
  right: BinOp { op: "==", left: "ch", right: "\t" }
}

Processing:

// ✅ BoolExprLowerer's job
fn lower_condition(&mut self, ast: &ASTNode) -> ValueId {
    match ast {
        BinOp { op: "||", left, right } => {
            let left_val = self.lower_condition(left);   // Recurse
            let right_val = self.lower_condition(right); // Recurse
            self.builder.emit_binop(BinOpKind::Or, left_val, right_val)
        },
        BinOp { op: "==", left, right } => {
            let left_val = self.lower_expr(left);
            let right_val = self.lower_expr(right);
            self.builder.emit_compare(CompareOp::Eq, left_val, right_val)
        },
        // ...
    }
}

// ❌ NOT BoolExprLowerer's job
// Don't do this:
create_loop_blocks();
handle_break();
track_carriers();

Output: Single ValueId (bool 0/1)


Why This Separation Matters

Problem Without Separation (Old Approach)

// Pattern2 lowerer tries to handle everything
fn lower_pattern2(&mut self, ctx: &LoopContext) {
    // ❌ Pattern2 tries to understand condition
    match &ctx.condition {
        BinOp { op: "<", .. } => { /* simple case */ },
        BinOp { op: "||", left, right } => {
            // ❌ Now Pattern2 needs to know about OR logic!
            // ❌ What if we add AND? Pattern2 needs to change!
            // ❌ Mixed AND/OR? Pattern2 gets even more complex!
        },
        // ❌ Pattern2 code grows exponentially with expression types
    }
    
    // Pattern2 also handles loop structure
    // ... hundreds of lines ...
}

Result:

  • Pattern2 becomes bloated (handles both loop + expression logic)
  • Adding new operators requires changing all loop patterns
  • Hard to test expression logic in isolation

Solution With Separation (New Approach)

// Pattern2: Only loop structure (stays small & focused)
fn lower_pattern2(&mut self, ctx: &LoopContext) {
    // ✅ Delegate expression to specialist
    let cond = BoolExprLowerer::new(self.builder)
        .lower_condition(&ctx.condition)?;
    
    // ✅ Pattern2 just uses the result
    self.build_loop_structure(cond);
}

// BoolExprLowerer: Only expressions (separate file, separate tests)
fn lower_condition(&mut self, ast: &ASTNode) -> ValueId {
    match ast {
        BinOp { op: "||", .. } => { /* OR logic here */ },
        BinOp { op: "&&", .. } => { /* AND logic here */ },
        BinOp { op: "!", .. } => { /* NOT logic here */ },
        // Easy to extend with new operators!
    }
}

Result:

  • Pattern2 stays clean (only loop logic)
  • BoolExprLowerer is reusable (can be used by if-lowering too!)
  • Easy to test: unit test BoolExprLowerer separately
  • Easy to extend: add new operators without touching loop code

Future Extension: No "Pattern5" Needed!

Wrong Approach (Adding Pattern5)

// ❌ Don't do this
enum LoopPattern {
    Pattern1_Simple,
    Pattern2_WithBreak,
    Pattern3_WithIfPhi,
    Pattern4_WithContinue,
    Pattern5_ComplexCondition,  // ❌ Bad!
}

// Now we have to duplicate all logic for Pattern5!
match pattern {
    Pattern2_WithBreak => { /* ... */ },
    Pattern5_ComplexCondition => { 
        // ❌ Copy-paste Pattern2 logic but with complex condition handling?
    },
}

Right Approach (Enhance BoolExprLowerer)

// ✅ Pattern2 stays the same
fn lower_pattern2(&mut self, ctx: &LoopContext) {
    let cond = BoolExprLowerer::new(self.builder)
        .lower_condition(&ctx.condition)?;
    // ... rest of Pattern2 logic unchanged ...
}

// ✅ Just enhance BoolExprLowerer
impl BoolExprLowerer {
    // Phase 167: Support OR
    // Phase 168: Add AND support here
    // Phase 169: Add NOT support here
    // Phase 170: Add mixed AND/OR support here
    
    fn lower_condition(&mut self, ast: &ASTNode) -> ValueId {
        match ast {
            BinOp { op: "||", .. } => { /* Phase 167 ✅ */ },
            BinOp { op: "&&", .. } => { /* Phase 168 🔜 */ },
            BinOp { op: "!", .. } => { /* Phase 169 🔜 */ },
            // ...
        }
    }
}

Key Insight:

  • Loop patterns (Pattern1-4) are structural categories (break/continue/phi)
  • Expression complexity is orthogonal to loop structure
  • Solve orthogonal concerns with separate boxes!

Call Graph (Who Calls Whom)

┌─────────────────────┐
│  Pattern Router     │  (Decides which pattern)
│  (routing.rs)       │
└──────────┬──────────┘
           │
           ├─→ Pattern1_Minimal  ──┐
           ├─→ Pattern2_WithBreak ─┤
           ├─→ Pattern3_WithIfPhi ─┼─→ BoolExprLowerer.lower_condition()
           └─→ Pattern4_WithContinue┘        │
                                              │
                        ┌─────────────────────┘
                        ↓
              ┌──────────────────────┐
              │  BoolExprLowerer     │
              │  (bool_expr_lowerer.rs)│
              └──────────┬───────────┘
                         │
                         ├─→ lower_logical_or()
                         ├─→ lower_logical_and()  (Phase 168+)
                         ├─→ lower_logical_not()  (Phase 169+)
                         └─→ lower_comparison()   (delegate to MirBuilder)

Observation:

  • All 4 loop patterns call the same BoolExprLowerer
  • BoolExprLowerer has NO knowledge of which pattern called it
  • Perfect separation of concerns!

Testing Strategy with Separation

Unit Tests (BoolExprLowerer in isolation)

// Test ONLY expression lowering, no loop involved
#[test]
fn test_or_chain() {
    let ast = parse("ch == ' ' || ch == '\t'");
    let mut lowerer = BoolExprLowerer::new(&mut builder);
    let result = lowerer.lower_condition(&ast).unwrap();
    
    // Assert: correct SSA generated
    assert_mir_has_binop_or(builder, result);
}

Integration Tests (Loop + Expression together)

// Test Pattern2 WITH complex condition
#[test]
fn test_pattern2_with_or_condition() {
    let code = "loop(i < n) { if ch == ' ' || ch == '\t' { i = i + 1 } else { break } }";
    let result = compile_and_run(code);
    
    // Assert: Pattern2 matched + BoolExprLowerer called + correct execution
    assert_pattern_matched(LoopPattern::Pattern2_WithBreak);
    assert_execution_correct(result);
}

Benefit: Can test expression logic separately from loop logic!


Success Criteria for Task 167-3

Complete When:

  1. Architectural diagram showing separation of concerns
  2. Concrete code example of how Pattern2 calls BoolExprLowerer
  3. "What each box sees" perspective documented
  4. Explanation of why separation matters (complexity management)
  5. Demonstration that no "Pattern5" is needed
  6. Call graph showing who calls whom
  7. Testing strategy leveraging separation

Key Takeaways

  1. Loop Patterns = Structure: break, continue, PHI, exit bindings
  2. BoolExprLowerer = Expression: OR, AND, NOT, comparison
  3. Interface = lower_condition(&ast) -> ValueId: Clean, simple, extensible
  4. No Pattern5: Enhance BoolExprLowerer, don't add loop patterns
  5. Testability: Separate concerns → separate tests → easier debugging

Phase 168 Implementation - Minimal Set (2025-12-06)

Status: COMPLETE - BoolExprLowerer fully implemented and tested!

What Was Implemented

  1. Core Module (src/mir/join_ir/lowering/bool_expr_lowerer.rs)

    • 436 lines of implementation including comprehensive tests
    • Public API: BoolExprLowerer::new() and lower_condition()
    • Integrated with mod.rs for module visibility
  2. Supported Operators

    • Comparisons (all 6): <, ==, !=, <=, >=, >

      • Emits MirInstruction::Compare with appropriate CompareOp
      • Returns ValueId with MirType::Bool annotation
    • Logical OR (||)

      • Recursively lowers left and right sides
      • Emits MirInstruction::BinOp with BinaryOp::BitOr
      • Handles chains: a || b || c || d
    • Logical AND (&&)

      • Recursively lowers left and right sides
      • Emits MirInstruction::BinOp with BinaryOp::BitAnd
      • Supports complex mixed conditions
    • Logical NOT (!)

      • Emits MirInstruction::UnaryOp with UnaryOp::Not
      • Handles negated complex expressions
    • Variables and Literals

      • Delegates to MirBuilder::build_expression()
      • Preserves existing behavior for simple expressions
  3. Test Coverage (4 tests in module)

    • test_simple_comparison: Validates i < 10
    • test_or_chain: Validates ch == " " || ch == "\t"
    • test_complex_mixed_condition: Validates i < len && (c == " " || c == "\t")
    • test_not_operator: Validates !(i < 10)
  4. Architecture

    • Recursive AST traversal: Handles arbitrarily nested boolean expressions
    • ValueId return: Clean interface - returns register holding bool result
    • Type safety: All results properly annotated with MirType::Bool
    • Separation of concerns: BoolExprLowerer knows NOTHING about loop patterns

Implementation Details

Recursive Lowering Strategy

pub fn lower_condition(&mut self, cond_ast: &ASTNode) -> Result<ValueId, String> {
    match cond_ast {
        // Comparisons: emit Compare instruction
        ASTNode::BinaryOp { operator: Equal, left, right, .. } => {
            let lhs = self.lower_condition(left)?;  // Recursive!
            let rhs = self.lower_condition(right)?;
            let dst = self.builder.next_value_id();
            self.builder.emit_instruction(MirInstruction::Compare {
                dst, op: CompareOp::Eq, lhs, rhs
            })?;
            self.builder.value_types.insert(dst, MirType::Bool);
            Ok(dst)
        },
        
        // Logical OR: emit BinOp Or
        ASTNode::BinaryOp { operator: Or, left, right, .. } => {
            let lhs = self.lower_condition(left)?;  // Recursive!
            let rhs = self.lower_condition(right)?;
            let dst = self.builder.next_value_id();
            self.builder.emit_instruction(MirInstruction::BinOp {
                dst, op: BinaryOp::BitOr, lhs, rhs
            })?;
            self.builder.value_types.insert(dst, MirType::Bool);
            Ok(dst)
        },
        
        // Variables/Literals: delegate to builder
        ASTNode::Variable { .. } | ASTNode::Literal { .. } => {
            self.builder.build_expression(cond_ast.clone())
        },
        
        // Other nodes: delegate
        _ => self.builder.build_expression(cond_ast.clone())
    }
}

Example Transformation

Input AST:

ch == " " || ch == "\t" || ch == "\n" || ch == "\r"

Generated SSA (BoolExprLowerer output):

%1 = Variable "ch"
%2 = Const " "
%3 = Compare Eq %1 %2          // ch == " "
%4 = Variable "ch"
%5 = Const "\t"
%6 = Compare Eq %4 %5          // ch == "\t"
%7 = BinOp Or %3 %6            // (ch == " ") || (ch == "\t")
%8 = Variable "ch"
%9 = Const "\n"
%10 = Compare Eq %8 %9         // ch == "\n"
%11 = BinOp Or %7 %10          // prev || (ch == "\n")
%12 = Variable "ch"
%13 = Const "\r"
%14 = Compare Eq %12 %13       // ch == "\r"
%result = BinOp Or %11 %14     // final result

Integration Status

Current State: BoolExprLowerer is ready for use but not yet integrated into live patterns.

Why?: Current loop patterns (Pattern1-4) use minimal lowerers that generate hardcoded JoinIR. They don't process condition AST directly - they only extract loop variable names.

Future Integration Points:

  1. When Pattern2/4 are enhanced to handle complex break/continue conditions
  2. When JsonParserBox _trim / _skip_whitespace are ported to JoinIR
  3. Any new pattern that needs to evaluate boolean expressions dynamically

How to Use:

// In future enhanced patterns:
use crate::mir::join_ir::lowering::bool_expr_lowerer::BoolExprLowerer;

let mut bool_lowerer = BoolExprLowerer::new(builder);
let cond_val = bool_lowerer.lower_condition(&ctx.condition)?;
// cond_val is now a ValueId holding the boolean result

Files Modified

  • Created: src/mir/join_ir/lowering/bool_expr_lowerer.rs (436 lines)
  • Modified: src/mir/join_ir/lowering/mod.rs (added pub mod bool_expr_lowerer;)

Regression Testing

  • Library compiles successfully (cargo build --release --lib)
  • Binary compiles successfully (cargo build --release --bin hakorune)
  • Existing loop pattern tests work (verified with loop_min_while.hako)
  • No regressions in Pattern1-4 behavior

Success Criteria - ALL MET

  1. Module Created: bool_expr_lowerer.rs with working implementation
  2. Minimal Support Set: <, ==, &&, ||, ! all implemented
  3. Integration Ready: Module structure allows easy future integration
  4. Unit Tests Pass: All 4 tests validate correct behavior
  5. Regression Tests Pass: Existing patterns still work
  6. Documentation Updated: CURRENT_TASK.md and this design doc

Next Steps

Phase 169+: Potential enhancements (NOT required for Phase 168):

  • Short-circuit evaluation for && / || (currently both sides always evaluated)
  • Operator precedence handling for mixed expressions
  • Error messages with better diagnostics
  • Performance optimizations

Integration: When JsonParserBox or enhanced patterns need complex condition processing, BoolExprLowerer is ready to use immediately.


Conclusion: Phase 168 successfully implemented BoolExprLowerer with full support for _trim and _skip_whitespace requirements. The module is production-ready and demonstrates the "No Pattern5" design philosophy - enhance expression handling, don't add loop patterns! Status: Historical