Proper HOST↔JoinIR ValueId separation for condition variables: - Add ConditionEnv struct (name → JoinIR-local ValueId mapping) - Add ConditionBinding struct (HOST/JoinIR ValueId pairs) - Modify condition_to_joinir to use ConditionEnv instead of builder.variable_map - Update Pattern2 lowerer to build ConditionEnv and ConditionBindings - Extend JoinInlineBoundary with condition_bindings field - Update BoundaryInjector to inject Copy instructions for condition variables This fixes the undefined ValueId errors where HOST ValueIds were being used directly in JoinIR instructions. Programs now execute (RC: 0), though loop variable exit values still need Phase 172 work. Key invariants established: 1. JoinIR uses ONLY JoinIR-local ValueIds 2. HOST↔JoinIR bridging is ONLY through JoinInlineBoundary 3. condition_to_joinir NEVER accesses builder.variable_map 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
33 KiB
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 Orinstructions
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
-
Simple OR (2 terms):
if a == 1 || a == 2 { ... } -
OR Chain (4 terms) - _trim pattern:
if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" { ... } -
Nested in Loop - Full _trim pattern:
loop(start < end) { if (ch == " ") || (ch == "\t") || ... { ... } else { break } }
Success Criteria
✅ Phase 167 Complete When:
- BoolExprLowerer API designed and documented
- Responsibility separation (Loop vs Expression) clearly defined
_trimand_skip_whitespacepatterns analyzed and documented- Expected SSA form for OR chains specified
- Integration point with Pattern2 identified
🚀 Next Phase (168): BoolExprLowerer Implementation
- Actual AST → SSA translation code
- Integration with Pattern2_WithBreak lowerer
- Test with
_trimand_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
loweringpackage → 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
_trimand_skip_whitespace
Phase 169 (Future)
- 🔜 Add AND support
- 🔜 Add NOT support
- 🔜 Support mixed AND/OR expressions
Success Criteria for Task 167-2
✅ Complete When:
- API signature defined (
lower_conditionmethod) - Supported expression types documented (OR chains for Phase 167)
- Integration points with loop patterns identified
- Responsibility boundaries clearly defined
- Error handling strategy established
- 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.conditioninternal structure - It just calls
lower_condition(&ctx.condition)and gets back aValueId - This
ValueIdis 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:
- Architectural diagram showing separation of concerns
- Concrete code example of how Pattern2 calls BoolExprLowerer
- "What each box sees" perspective documented
- Explanation of why separation matters (complexity management)
- Demonstration that no "Pattern5" is needed
- Call graph showing who calls whom
- Testing strategy leveraging separation
Key Takeaways
- Loop Patterns = Structure: break, continue, PHI, exit bindings
- BoolExprLowerer = Expression: OR, AND, NOT, comparison
- Interface =
lower_condition(&ast) -> ValueId: Clean, simple, extensible - No Pattern5: Enhance BoolExprLowerer, don't add loop patterns
- 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
-
Core Module (
src/mir/join_ir/lowering/bool_expr_lowerer.rs)- 436 lines of implementation including comprehensive tests
- Public API:
BoolExprLowerer::new()andlower_condition() - Integrated with
mod.rsfor module visibility
-
Supported Operators
-
Comparisons (all 6):
<,==,!=,<=,>=,>- Emits
MirInstruction::Comparewith appropriateCompareOp - Returns
ValueIdwithMirType::Boolannotation
- Emits
-
Logical OR (
||)- Recursively lowers left and right sides
- Emits
MirInstruction::BinOpwithBinaryOp::BitOr - Handles chains:
a || b || c || d
-
Logical AND (
&&)- Recursively lowers left and right sides
- Emits
MirInstruction::BinOpwithBinaryOp::BitAnd - Supports complex mixed conditions
-
Logical NOT (
!)- Emits
MirInstruction::UnaryOpwithUnaryOp::Not - Handles negated complex expressions
- Emits
-
Variables and Literals
- Delegates to
MirBuilder::build_expression() - Preserves existing behavior for simple expressions
- Delegates to
-
-
Test Coverage (4 tests in module)
test_simple_comparison: Validatesi < 10test_or_chain: Validatesch == " " || ch == "\t"test_complex_mixed_condition: Validatesi < len && (c == " " || c == "\t")test_not_operator: Validates!(i < 10)
-
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:
- When Pattern2/4 are enhanced to handle complex break/continue conditions
- When JsonParserBox
_trim/_skip_whitespaceare ported to JoinIR - 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(addedpub 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 ✅
- ✅ Module Created:
bool_expr_lowerer.rswith working implementation - ✅ Minimal Support Set:
<,==,&&,||,!all implemented - ✅ Integration Ready: Module structure allows easy future integration
- ✅ Unit Tests Pass: All 4 tests validate correct behavior
- ✅ Regression Tests Pass: Existing patterns still work
- ✅ 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!