Extends DigitPos promotion to generate TWO carriers for Pattern A/B support: - Boolean carrier (is_digit_pos) for break conditions - Integer carrier (digit_value) for NumberAccumulation ## Implementation 1. **DigitPosPromoter** (loop_body_digitpos_promoter.rs) - Generates dual carriers: is_<var> (bool) + <base>_value (int) - Smart naming: "digit_pos" → "digit" (removes "_pos" suffix) 2. **UpdateEnv** (update_env.rs) - Context-aware promoted variable resolution - Priority: <base>_value (int) → is_<var> (bool) → standard - Pass promoted_loopbodylocals from CarrierInfo 3. **Integration** (loop_with_break_minimal.rs) - UpdateEnv constructor updated to pass promoted list ## Test Results - **Before**: 925 tests PASS - **After**: 931 tests PASS (+6 new tests, 0 failures) ## New Tests - test_promoted_variable_resolution_digit_pos - Full dual-value - test_promoted_variable_resolution_fallback_to_bool - Fallback - test_promoted_variable_not_a_carrier - Error handling ## Impact | Pattern | Before | After | |---------|--------|-------| | _parse_number | ✅ Works (bool only) | ✅ Works (bool used, int unused) | | _atoi | ❌ Failed (missing int) | ✅ READY (int carrier available!) | 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
8.4 KiB
Phase 247-EX: DigitPos Dual-Value Architecture - IMPLEMENTATION COMPLETE
Date: 2025-12-11 Status: ✅ IMPLEMENTATION COMPLETE - All tests passing (931/931) Scope: Dual-value carrier generation for DigitPos pattern - resolves Phase 246-EX _atoi NumberAccumulation failure
🎯 Problem Statement
Phase 246-EX Discovery: DigitPos promotion loses integer digit value needed for NumberAccumulation.
Two Different Patterns
Pattern A (_parse_number): ✅ Works with Phase 224
loop(p < s.length()) {
local ch = s.substring(p, p+1)
local digit_pos = digits.indexOf(ch)
if digit_pos < 0 { break } // Only needs boolean (found/not found)
num_str = num_str + ch // Uses ch, NOT digit_pos
p = p + 1
}
Pattern B (_atoi): ❌ Failed with Phase 224 (bool only)
loop(i < n) {
local ch = s.substring(i, i+1)
local pos = digits.indexOf(ch)
if pos < 0 { break } // Needs boolean (break condition)
v = v * 10 + pos // Needs INTEGER value for NumberAccumulation!
i = i + 1
}
Root Cause: Phase 224 DigitPos promotion only generated boolean carrier (is_digit_pos), losing the integer digit value needed for accumulation.
✅ Solution: Dual-Value Model
One Question → Two Outputs:
indexOf(ch) → -1 or 0-9
↓ (DigitPosPromoter Phase 247-EX)
Output A: is_digit_pos (boolean) ← Break condition: "Is ch in digits?"
Output B: digit_value (integer) ← Accumulation: "What digit value?"
Naming Convention
Phase 247-EX naming (Design Option A - Separate naming):
"digit_pos" → "is_digit_pos" (boolean) + "digit_value" (integer)
"pos" → "is_pos" (boolean) + "pos_value" (integer)
Base name extraction: "digit_pos" → "digit" (remove "_pos" suffix) → "digit_value"
📦 Implementation Details
1. DigitPosPromoter Extension
File: src/mir/loop_pattern_detection/loop_body_digitpos_promoter.rs
Changes:
- Generate two carriers instead of one
- Carrier 1:
is_<var>(boolean, ConditionOnly role) - Carrier 2:
<base>_value(integer, LoopState role) - Base name extraction: remove
_possuffix
Code:
// Boolean carrier (condition-only, for break)
let promoted_carrier_bool = CarrierVar {
name: format!("is_{}", var_in_cond),
role: CarrierRole::ConditionOnly,
init: CarrierInit::BoolConst(false),
// ...
};
// Integer carrier (loop-state, for NumberAccumulation)
let base_name = if var_in_cond.ends_with("_pos") {
&var_in_cond[..var_in_cond.len() - 4]
} else {
var_in_cond.as_str()
};
let promoted_carrier_int = CarrierVar {
name: format!("{}_value", base_name),
role: CarrierRole::LoopState,
init: CarrierInit::FromHost,
// ...
};
carrier_info.carriers = vec![promoted_carrier_bool, promoted_carrier_int];
2. UpdateEnv Resolution Logic
File: src/mir/join_ir/lowering/update_env.rs
Changes:
- Added
promoted_loopbodylocals: &'a [String]field - Enhanced
resolve()method with promoted variable logic - Resolution priority:
- Try
<base>_value(integer carrier) - Fall back to
is_<var>(boolean carrier, rare in updates) - Standard resolution
- Try
Code:
pub fn resolve(&self, name: &str) -> Option<ValueId> {
if self.promoted_loopbodylocals.iter().any(|v| v == name) {
// Extract base name: "digit_pos" → "digit"
let base_name = if name.ends_with("_pos") {
&name[..name.len() - 4]
} else {
name
};
// Try <base>_value (integer carrier for NumberAccumulation)
let int_carrier_name = format!("{}_value", base_name);
if let Some(value_id) = self.condition_env.get(&int_carrier_name) {
return Some(value_id);
}
// Fall back to is_<name> (boolean carrier)
let bool_carrier_name = format!("is_{}", name);
if let Some(value_id) = self.condition_env.get(&bool_carrier_name) {
return Some(value_id);
}
}
// Standard resolution
self.condition_env.get(name).or_else(|| self.body_local_env.get(name))
}
3. Call Site Updates
File: src/mir/join_ir/lowering/loop_with_break_minimal.rs
Change: Pass promoted_loopbodylocals to UpdateEnv constructor:
let update_env = UpdateEnv::new(env, body_env, &carrier_info.promoted_loopbodylocals);
🧪 Testing
Unit Tests (3 new tests)
File: src/mir/join_ir/lowering/update_env.rs (tests module)
test_promoted_variable_resolution_digit_pos- Full dual-value resolutiontest_promoted_variable_resolution_fallback_to_bool- Boolean-only fallbacktest_promoted_variable_not_a_carrier- Error handling (variable not found)
All tests pass: ✅
Regression Tests
Before Phase 247-EX: 925 tests PASS After Phase 247-EX: 931 tests PASS (+6 new tests)
Result: ✅ 0 FAILURES, 0 REGRESSIONS
📊 Impact Summary
| Aspect | Before Phase 247-EX | After Phase 247-EX |
|---|---|---|
| DigitPos carriers | 1 (boolean only) | 2 (boolean + integer) |
| _parse_number | ✅ Works (bool sufficient) | ✅ Works (bool used, int unused) |
| _atoi | ❌ Fails (missing int value) | ✅ READY (int carrier available) |
| Test count | 925 PASS | 931 PASS (+6) |
| Lines changed | - | +130 net (implementation + tests) |
🎯 Next Steps
Phase 247-EX Completion Tasks
- DigitPosPromoter dual-carrier generation
- UpdateEnv promoted variable resolution
- Unit tests for dual-value logic
- Regression tests (931/931 PASS)
- E2E Tests: Test actual _atoi and _parse_number loops
- Commit: Phase 247-EX implementation
Future E2E Verification
Test _parse_number (Pattern A - bool only):
./target/release/hakorune apps/tests/phase189_parse_number_mini.hako
# Expected: Works with is_digit_pos (boolean), digit_value unused
Test _atoi (Pattern B - bool + int):
./target/release/hakorune apps/tests/phase246ex_atoi_e2e_42.hako
# Expected: Works with is_pos (bool for break) + pos_value (int for accumulation)
🏗️ Architecture Principles
Box-First Design
Single Responsibility:
- DigitPosPromoter: Detects indexOf patterns, generates dual carriers
- UpdateEnv: Resolves variables with context-aware promoted logic
- ConditionEnv: Stores carrier ValueIds
- CarrierUpdateEmitter: Emits JoinIR using resolved ValueIds
Fail-Safe:
- Unused carriers (e.g.,
digit_valuein _parse_number) are harmless - Resolution failures logged with warnings
- Falls back to boolean carrier if integer carrier missing
Boundary Clarity:
Input: digit_pos = indexOf(ch) (AST)
↓ DigitPosPromoter
Output: is_digit_pos (bool) + digit_value (int) (CarrierInfo)
↓ Pattern2 lowerer → ConditionEnv stores both carriers
↓ UpdateEnv resolves
Break: digit_pos → is_digit_pos (bool, via DigitPosConditionNormalizer)
Update: digit_pos → digit_value (int, via UpdateEnv promoted logic)
📚 References
Design Documents:
- phase247-digitpos-dual-value-design.md - Complete design spec
- phase246-jsonparser-atoi-joinir-integration.md - _atoi integration plan
- phase245-jsonparser-parse-number-joinir-integration.md - _parse_number context
Related Phases:
- Phase 224: DigitPos promotion (boolean-only, foundation)
- Phase 224-E: DigitPosConditionNormalizer (AST transformation)
- Phase 227: CarrierRole enum (LoopState vs ConditionOnly)
- Phase 228: CarrierInit enum (FromHost vs BoolConst)
- Phase 246-EX: _atoi pattern discovery (triggered Phase 247-EX)
Implementation Files:
src/mir/loop_pattern_detection/loop_body_digitpos_promoter.rs- Dual-carrier generationsrc/mir/join_ir/lowering/update_env.rs- Promoted variable resolutionsrc/mir/join_ir/lowering/loop_with_break_minimal.rs- Pattern2 integration
🎉 Success Metrics
✅ All objectives achieved:
- Dual-carrier generation implemented
- Context-aware resolution working
- Zero regressions (931/931 tests PASS)
- Clean architecture (Box-First principles)
- Comprehensive unit tests (+3 new tests)
Phase 247-EX Status: ✅ IMPLEMENTATION COMPLETE - Ready for E2E validation and commit