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>
8.8 KiB
Phase 217: Multi-Carrier If-Sum Complete
Overview
Phase 217 validates that Pattern 3 if-sum implementation supports multiple accumulators (sum + count) with zero additional code.
Status: ✅ COMPLETE - Multi-carrier if-sum works out-of-the-box
The Surprise: Zero Implementation Needed
Expected Work (Task 217-3)
- Modify
loop_with_if_phi_if_sum.rsfor multi-carrier - Update AST extraction to handle 2+ updates
- Wire multiple carriers through JoinIR generation
Actual Work
ZERO LINES OF CODE CHANGED 🎉
Why It Just Worked
Phase 217 is the culmination of three previous phases working in perfect harmony:
-
Phase 195 (Multi-Carrier PHI Foundation)
CarrierInfotracks arbitrary number of carriers- Exit PHI generation handles N carriers
- ExitLineReconnector updates variable_map for all carriers
-
Phase 214 (Dynamic Join Inputs)
- Removed hardcoded 3-input assumption
join_inputs = (0..total_inputs).map(|i| ValueId(i))- Automatically scales: 1 loop_var + N carriers = N+1 inputs
-
Phase 215 (ExprResult Exit Contract)
- Marks first carrier as expr_result
- Propagates through Boundary → ExitLine → Return
- Works regardless of carrier count
Result: The "boxes" compose perfectly!
Test Case
Multi-Carrier If-Sum Pattern
File: apps/tests/phase217_if_sum_multi_min.hako
static box IfSumMultiTest {
sum_and_count(defs) {
local sum = 0
local count = 0
local i = 0
local len = 3
loop(i < len) {
if i > 0 {
sum = sum + 1 // Carrier 1 (conditional)
count = count + 1 // Carrier 2 (conditional)
print(sum)
print(count)
} else {
print(0)
}
i = i + 1
}
return sum // Returns first carrier
}
main() {
local result = IfSumMultiTest.sum_and_count(0)
return result
}
}
Pattern:
- Counter:
i(0 to 2) - Accumulator 1:
sum(incremented at i=1, i=2) - Accumulator 2:
count(incremented at i=1, i=2)
Expected: RC=2 (sum value) Actual: RC=2 ✅
Test Results
Primary Target
| Test File | Carriers | Loop Var | Expected | Actual | Status |
|---|---|---|---|---|---|
| phase217_if_sum_multi_min.hako | sum+count (2) | i | RC=2 | RC=2 | ✅ PASS |
Verification:
./target/release/nyash apps/tests/phase217_if_sum_multi_min.hako
# Output: (prints 1, 1, 2, 2)
# RC=2
Regression Tests (All Passing)
| Test File | Pattern | Carriers | Expected | Actual | Status |
|---|---|---|---|---|---|
| loop_if_phi.hako | P3 | sum (1) | RC=2 | RC=2 | ✅ PASS |
| phase212_if_sum_min.hako | P3 | sum (1) | RC=2 | RC=2 | ✅ PASS |
| loop_min_while.hako | P1 | i (1) | RC=2 | RC=2 | ✅ PASS |
Architecture Verification
Carrier Detection (Automatic)
Phase 195's CarrierInfo automatically detected:
- Carrier 1:
sum(UpdateKind::AccumulationLike) - Carrier 2:
count(UpdateKind::AccumulationLike) - Loop var:
i(UpdateKind::CounterLike)
Dynamic Input Generation (Phase 214)
// Automatic scaling
let total_inputs = 1 + exit_bindings.len(); // 1 + 2 = 3
let join_inputs: Vec<ValueId> = (0..total_inputs)
.map(|i| ValueId(i as u32))
.collect(); // [ValueId(0), ValueId(1), ValueId(2)]
let host_inputs = vec![
ctx.loop_var_id, // i
exit_binding_0, // sum
exit_binding_1, // count
];
Exit PHI Generation (Phase 195)
Loop Header (bb4):
%i_phi = phi [0, bb3], [%i_next, bb14] // Loop variable
%sum_phi = phi [0, bb3], [%sum_exit, bb14] // Carrier 1
%count_phi = phi [0, bb3], [%count_exit, bb14] // Carrier 2
Loop Body (bb5-bb13):
(if-else branches update sum/count conditionally)
Loop Exit (bb14):
%sum_exit = phi [%sum_then, bb_then], [%sum_phi, bb_else]
%count_exit = phi [%count_then, bb_then], [%count_phi, bb_else]
%i_next = add %i_phi, 1
branch bb4 // Loop back with all 3 values
ExprResult Selection (Phase 215)
// Phase 215: First carrier becomes expr_result
let fragment_meta = JoinFragmentMeta::with_expr_result(
sum_exit, // First carrier (sum)
exit_meta // Contains both sum and count
);
// Result: sum reaches return statement (RC=2)
// count updates variable_map only
Box Theory Validation
Phase 217 proves the "Everything is Box" philosophy:
Box Composition
┌─────────────────────────────────────────────┐
│ Phase 195: Multi-Carrier PHI (Box A) │
│ - Handles N carriers automatically │
│ - CarrierInfo + ExitMeta │
└─────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ Phase 214: Dynamic Inputs (Box B) │
│ - Scales to N+1 inputs automatically │
│ - join_inputs generation │
└─────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ Phase 215: ExprResult Contract (Box C) │
│ - Selects first carrier for return │
│ - Works regardless of carrier count │
└─────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ Phase 217: Multi-Carrier If-Sum │
│ - Boxes A + B + C compose perfectly │
│ - ZERO additional code needed │
└─────────────────────────────────────────────┘
Key Insight
Well-designed boxes compose without modification.
Each phase created a reusable box with clear contracts:
- Box A: "I handle N carriers"
- Box B: "I generate N+1 inputs"
- Box C: "I select expr_result from carriers"
When combined, these boxes just work for multi-carrier patterns.
Lessons Learned
1. Fail-Fast Design Pays Off
Phase 195's strict assertions caught problems early:
debug_assert_eq!(
join_inputs.len(),
host_inputs.len(),
"join_inputs != host_inputs"
);
This forced Phase 214 to fix the root cause (hardcoded inputs), which then made Phase 217 trivial.
2. Single Responsibility Principle
Each phase had one clear job:
- Phase 195: Multi-carrier detection and PHI generation
- Phase 214: Multi-carrier input scaling
- Phase 215: Multi-carrier expr_result selection
No phase tried to solve everything at once.
3. Box-First Development
By treating each capability as a "box" (module with clear interface), we:
- Avoided tight coupling
- Enabled composition
- Made testing independent
- Reduced implementation risk
Future Work
Phase 218: Nested If-Else Patterns
- Target:
esc_json()with nested conditions - Validates complex if-else inside loops
- Likely needs: ConditionEnv or BoolExprLowerer enhancements
Phase 219: JsonParser Integration
- Apply multi-carrier if-sum to actual JsonParser loops
- First target: numeric processing with flags
- Validates selfhost compiler real use case
Phase 220: Variable Limit Conditions
- Support:
loop(i < len)wherelenis variable (not literal) - Currently only integer literals supported in if-sum lowerer
- Needs: AST extraction enhancement for variable references
Files Modified
ZERO source code files modified in Phase 217.
Files Added:
apps/tests/phase217_if_sum_multi_min.hako(test case)docs/development/current/main/phase217-if-sum-multi.md(this doc)
Summary
Phase 217 is a validation phase that proves the correctness of the Phase 195/214/215 architecture:
- ✅ Multi-carrier if-sum works with zero additional code
- ✅ All regression tests passing
- ✅ Box composition working perfectly
- ✅ Ready for more complex patterns (Phase 218+)
The fact that Phase 217 required no implementation is not a bug—it's a feature of good box-first design. When boxes compose correctly, new capabilities emerge naturally.
「箱理論」の勝利! 🎉
Status: Active
Scope: If-sum multi ケース(JoinIR v2)