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>
16 KiB
Phase 247-EX: DigitPos Dual-Value Design
Status: Active Scope: DigitPos パターンを「1つの質問→2つの値」に整理し、break 条件と NumberAccumulation 両方に対応。
1. DigitPos の二重値化モデル
1.1 問題の本質
Phase 224 の現状:
// Phase 224: digit_pos (integer) → is_digit_pos (boolean) に変換
digit_pos = digits.indexOf(ch) // -1 or 0-9
↓ (DigitPosPromoter)
is_digit_pos (boolean carrier) // indexOf() の成否のみ
何が失われるか:
- indexOf() の戻り値(0-9 の digit 値)が消える
- break 条件
digit_pos < 0は!is_digit_posで対応できる(Phase 224-E で実装済み) - しかし NumberAccumulation
result = result * 10 + digit_posで digit 値が必要!
具体例:
// _atoi ループ (_parse_number も同様)
loop(i < n) {
local ch = s.substring(i, i+1)
local digit_pos = digits.indexOf(ch) // -1 (not found) or 0-9 (digit value)
if digit_pos < 0 { break } // Break 用途: 成否判定(boolean)
v = v * 10 + digit_pos // Accumulation 用途: digit 値(integer)
i = i + 1
}
1.2 解決: 二重値化アーキテクチャ
digit_pos = digits.indexOf(ch)
↓ (DigitPosPromoter 拡張)
Output A: is_digit_pos (boolean carrier) ← break 条件用
Output B: digit_value (integer carrier) ← accumulation 用
質問と出力の対応:
- 質問: この ch は digits に含まれているか? index はいくつか?
- 出力A:
is_digit_pos: bool- 含まれているか(condition 側) - 出力B:
digit_value: i64- index 値(accumulation 側)
両方を CarrierInfo に含める:
// Phase 247-EX: DigitPos dual-value promotion
CarrierInfo {
carriers: vec![
CarrierVar {
name: "is_digit_pos",
role: CarrierRole::ConditionOnly, // Exit PHI 不要
init: CarrierInit::BoolConst(false),
},
CarrierVar {
name: "digit_value",
role: CarrierRole::LoopState, // Exit PHI 必要
init: CarrierInit::FromHost,
},
],
promoted_loopbodylocals: vec!["digit_pos".to_string()],
}
2. 箱の責務分割
2.1 DigitPosPromoter (拡張)
Phase 224 の責務:
- Input:
digit_pos = digits.indexOf(ch)(LoopBodyLocal) - Output:
is_digit_pos(boolean carrier)
Phase 247-EX の拡張:
- Input: 同上
- Output:
is_digit_pos(boolean carrier) - 既存digit_value(integer carrier) - 新規追加
実装方針:
// Phase 247-EX: Dual-value carrier creation
let promoted_carrier_bool = CarrierVar {
name: format!("is_{}", var_in_cond), // "is_digit_pos"
host_id: ValueId(0),
join_id: None,
role: CarrierRole::ConditionOnly,
init: CarrierInit::BoolConst(false),
};
let promoted_carrier_int = CarrierVar {
name: format!("{}_value", var_in_cond), // "digit_pos_value" or "digit_value"
host_id: ValueId(0),
join_id: None,
role: CarrierRole::LoopState,
init: CarrierInit::FromHost,
};
let mut carrier_info = CarrierInfo::with_carriers(
"__dummy_loop_var__".to_string(),
ValueId(0),
vec![promoted_carrier_bool, promoted_carrier_int],
);
2.2 CarrierInfo
Phase 247-EX で含まれるもの:
CarrierInfo {
carriers: vec![
// Boolean carrier (condition 用)
CarrierVar {
name: "is_digit_pos",
role: ConditionOnly,
init: BoolConst(false),
},
// Integer carrier (accumulation 用)
CarrierVar {
name: "digit_value",
role: LoopState,
init: FromHost,
},
],
promoted_loopbodylocals: vec!["digit_pos"],
}
ScopeManager による解決:
- Break 条件:
digit_pos→is_digit_pos(ConditionEnv) - Update 式:
digit_pos→digit_value(UpdateEnv)
2.3 ConditionEnv / ExprLowerer
Phase 224-E の既存動作:
// Break condition: digit_pos < 0
// ↓ (DigitPosConditionNormalizer)
// !is_digit_pos
Phase 247-EX で不変:
- Break 条件は引き続き
is_digit_posを参照 - ConditionEnv は
digit_pos→is_digit_posを解決
2.4 UpdateEnv / NumberAccumulation
Phase 247-EX の新規動作:
// Update expression: v = v * 10 + digit_pos
// ↓ (UpdateEnv resolution)
// v = v * 10 + digit_value
UpdateEnv の変更:
digit_pos参照時にdigit_valueを解決- NumberAccumulation 検出時に
digit_var: "digit_pos"→"digit_value"に変換
CarrierUpdateEmitter の変更:
UpdateRhs::NumberAccumulation { digit_var }でdigit_varを UpdateEnv から解決- UpdateEnv が既に
digit_pos→digit_valueを解決済みと仮定
3. 影響範囲(パターン別)
3.1 Pattern2: _parse_number
ループ構造:
loop(p < s.length()) {
local ch = s.substring(p, p+1)
local digit_pos = digits.indexOf(ch)
if digit_pos < 0 { break } // Break 条件
num_str = num_str + ch // StringAppend (digit_pos 不使用)
p = p + 1
}
変数の役割:
| Variable | Type | Usage | Carrier Type |
|---|---|---|---|
p |
position | loop counter | LoopState |
num_str |
string | accumulator | LoopState |
ch |
string | LoopBodyLocal | - |
digit_pos |
integer | LoopBodyLocal | → is_digit_pos (ConditionOnly) + digit_value (unused) |
Phase 247-EX での扱い:
- Header:
p < s.length()(ExprLowerer) - Break:
digit_pos < 0→!is_digit_pos(ConditionEnv + Normalizer) - Update:
p = p + 1(Increment)num_str = num_str + ch(StringAppend)
- digit_value は生成されるが使用されない(無駄だが害なし)
3.2 Pattern2: _atoi
ループ構造:
loop(i < n) {
local ch = s.substring(i, i+1)
local pos = digits.indexOf(ch)
if pos < 0 { break } // Break 条件
v = v * 10 + pos // NumberAccumulation (pos 使用!)
i = i + 1
}
変数の役割:
| Variable | Type | Usage | Carrier Type |
|---|---|---|---|
i |
position | loop counter | LoopState |
v |
integer | number accumulator | LoopState |
ch |
string | LoopBodyLocal | - |
pos |
integer | LoopBodyLocal | → is_pos (ConditionOnly) + pos_value (LoopState) |
Phase 247-EX での扱い:
- Header:
i < n(ExprLowerer) - Break:
pos < 0→!is_pos(ConditionEnv + Normalizer) - Update:
i = i + 1(Increment)v = v * 10 + pos→v = v * 10 + pos_value(NumberAccumulation)
- 両方の carrier が必要!
3.3 Pattern2: _atof_loop [Future]
ループ構造 (想定):
loop(i < n) {
local ch = s.substring(i, i+1)
local digit_pos = digits.indexOf(ch)
if digit_pos < 0 { break }
result = result + digit_pos * place_value
place_value = place_value / 10
i = i + 1
}
Phase 247-EX での扱い:
- 同じ二重値化モデルが適用可能
digit_valueを乗算・累積に使用
4. 実装アーキテクチャ
4.1 データフロー図
DigitPosPromoter
├→ is_digit_pos (boolean)
│ ↓
│ ConditionEnv (Phase 245C capture)
│ ├→ ExprLowerer (break condition)
│ └→ is_digit_pos → boolean in scope
│
└→ digit_value (integer)
↓
UpdateEnv
├→ digit_pos reference → digit_value (integer)
└→ NumberAccumulation pattern resolution
4.2 命名規則
Phase 247-EX の命名戦略:
// Option A: Separate naming (推奨)
is_digit_pos // boolean carrier (condition 用)
digit_value // integer carrier (accumulation 用)
// Option B: Prefix naming
is_digit_pos // boolean
digit_pos_value // integer (冗長だが明示的)
// Option C: Short naming
is_digit_pos // boolean
digit_pos // integer (元の名前を保持、混乱の恐れ)
採用: Option A(Separate naming)
- 理由: 用途が明確、混乱が少ない、既存の
is_*命名規則と整合
4.3 ScopeManager の解決ルール
ConditionEnv (break 条件):
// digit_pos 参照 → is_digit_pos (boolean carrier)
env.get("digit_pos") → Some(is_digit_pos_value_id)
UpdateEnv (update 式):
// digit_pos 参照 → digit_value (integer carrier)
env.resolve("digit_pos") → Some(digit_value_value_id)
実装方針:
- CarrierInfo に両方の carrier を登録
- ScopeManager が context 依存で正しい carrier を返す
- ConditionEnv:
promoted_loopbodylocalsに含まれる名前 →is_*carrier - UpdateEnv:
promoted_loopbodylocalsに含まれる名前 →*_valuecarrier
- ConditionEnv:
5. 責務の明確化
| Component | Input | Output | 用途 |
|---|---|---|---|
| DigitPosPromoter | indexOf() result | is_digit_pos + digit_value |
二重値化 |
| CarrierInfo | DigitPos values | Both as LoopState | キャリア登録 |
| ConditionEnv | Promoted carriers | is_digit_pos (bool) |
Break 条件 |
| UpdateEnv | Promoted carriers | digit_value (i64) |
Accumulation |
| ExprLowerer | Condition AST | boolean ValueId | Lowering |
| DigitPosConditionNormalizer | digit_pos < 0 |
!is_digit_pos |
AST 変換 |
| CarrierUpdateEmitter | UpdateExpr | JoinIR instructions | Update 生成 |
Phase 247-EX の変更箇所:
- ✅ DigitPosPromoter: 2つの carrier を生成
- ✅ CarrierInfo: 両方を含める
- ✅ ConditionEnv:
digit_pos→is_digit_pos解決(既存) - 🆕 UpdateEnv:
digit_pos→digit_value解決(新規) - ✅ DigitPosConditionNormalizer:
digit_pos < 0→!is_digit_pos(既存) - 🆕 CarrierUpdateEmitter:
digit_valueを UpdateEnv から解決(新規)
6. テスト戦略
6.1 DigitPos Promoter 単体テスト
既存テスト (loop_body_digitpos_promoter.rs):
- ✅ Phase 224:
is_digit_pos側の生成を確認 - 🆕 Phase 247-EX:
digit_value側も生成されることを確認
新規テスト:
#[test]
fn test_digitpos_dual_value_carriers() {
// digit_pos = indexOf(ch) → is_digit_pos + digit_value
let result = DigitPosPromoter::try_promote(req);
match result {
Promoted { carrier_info, .. } => {
assert_eq!(carrier_info.carriers.len(), 2);
// Boolean carrier
let bool_carrier = &carrier_info.carriers[0];
assert_eq!(bool_carrier.name, "is_digit_pos");
assert_eq!(bool_carrier.role, CarrierRole::ConditionOnly);
// Integer carrier
let int_carrier = &carrier_info.carriers[1];
assert_eq!(int_carrier.name, "digit_value");
assert_eq!(int_carrier.role, CarrierRole::LoopState);
}
_ => panic!("Expected Promoted"),
}
}
6.2 _parse_number (Pattern2) E2E テスト
テストファイル: apps/tests/phase189_parse_number_mini.hako
期待される動作:
- Break 条件:
digit_pos < 0がis_digit_posで動作 digit_valuecarrier は生成されるが未使用(害なし)- String 累積(
num_str)は別の carrier で処理
検証コマンド:
NYASH_JOINIR_DEBUG=1 ./target/release/hakorune apps/tests/phase189_parse_number_mini.hako
期待出力:
[digitpos_promoter] A-4 DigitPos pattern promoted: digit_pos → is_digit_pos + digit_value
[pattern2/lowering] Using promoted carrier: is_digit_pos (condition)
[pattern2/lowering] Carrier 'digit_value' registered but unused
6.3 _atoi (Pattern2) E2E テスト
テストファイル: apps/tests/phase189_atoi_mini.hako または tests/phase246_json_atoi.rs
期待される動作:
- Break 条件:
pos < 0がis_posで動作 - NumberAccumulation:
v = v * 10 + posがv = v * 10 + pos_valueで動作 - 両方の carrier が正しく使用される
検証コマンド:
NYASH_JOINIR_DEBUG=1 cargo test --release phase246_json_atoi -- --nocapture
期待出力:
[digitpos_promoter] A-4 DigitPos pattern promoted: pos → is_pos + pos_value
[pattern2/lowering] Using promoted carrier: is_pos (condition)
[pattern2/lowering] Using promoted carrier: pos_value (accumulation)
[carrier_update] NumberAccumulation: digit_var='pos' resolved to pos_value
6.4 Regression テスト
対象:
- Phase 224 系テスト(DigitPos promotion 基本動作)
- Phase 245 系テスト(_parse_number 系)
- Phase 246 系テスト(_atoi 系)
確認項目:
- 既存の
is_digit_poscarrier が引き続き動作 - 新規の
digit_valuecarrier が追加されても退行なし
7. 成功条件
7.1 ビルド
cargo build --release成功(0 errors, 0 warnings)
7.2 単体テスト
- DigitPosPromoter テスト: 2つの carrier 生成を確認
- CarrierInfo テスト: 両方の carrier が含まれることを確認
- UpdateEnv テスト:
digit_pos→digit_value解決を確認
7.3 E2E テスト
- _parse_number:
digit_value未使用でも正常動作 - _atoi:
digit_valueを NumberAccumulation で使用 - Phase 224/245/246 既存テスト: 退行なし
7.4 ドキュメント
- このドキュメント完成
- 箱の責務が明確に書かれている
- テスト戦略が具体的
8. 将来への展開
8.1 他のパターンへの適用
同じ二重値化モデルを以下に適用可能:
_atof_loop (小数パース):
digit_pos→is_digit_pos+digit_value- 小数点以下の桁数計算にも使用可能
汎用的な indexOf パターン:
- 任意の indexOf() 呼び出しで同様の二重値化を適用
- condition-only 用途と value 用途を自動判定
8.2 Pattern3/4 への拡張
Pattern3 (if-sum):
- 複数の条件分岐でも同じ二重値化モデルを適用
- Exit PHI で両方の carrier を適切に扱う
Pattern4 (continue):
- continue 条件でも boolean carrier を使用
- update 式で integer carrier を使用
8.3 最適化の可能性
未使用 carrier の削除:
_parse_numberのようにdigit_valueが未使用の場合、最適化で削除可能- CarrierInfo に "used" フラグを追加して不要な carrier を省略
命名の統一化:
digit_pos→digit_pos(integer) +is_digit_pos(boolean)- UpdateEnv/ConditionEnv が型情報から自動判定
9. Box-First 原則の適用
9.1 Single Responsibility
- DigitPosPromoter: indexOf パターン検出と二重値化のみ
- CarrierInfo: Carrier メタデータ保持のみ
- ConditionEnv: Condition 側の解決のみ
- UpdateEnv: Update 側の解決のみ
9.2 Fail-Safe Design
- 未使用の carrier も生成(害なし)
- 解決失敗時は明示的エラー(panic なし)
- 退行テストで既存動作を保証
9.3 Boundary Clarity
Input: digit_pos = indexOf(ch) (AST)
↓
Output: is_digit_pos (bool) + digit_value (i64) (CarrierInfo)
↓
Usage: ConditionEnv → is_digit_pos
UpdateEnv → digit_value
10. 参考資料
10.1 Phase ドキュメント
- Phase 224:
phase224-digitpos-promoter-design.md- DigitPos promotion 基本設計 - Phase 224-E:
phase224-digitpos-condition-normalizer.md- AST 変換設計 - Phase 245:
phase245-jsonparser-parse-number-joinir-integration.md- _parse_number 統合 - Phase 246:
phase246-jsonparser-atoi-joinir-integration.md- _atoi 統合 - Phase 227: CarrierRole 導入(LoopState vs ConditionOnly)
- Phase 228: CarrierInit 導入(FromHost vs BoolConst)
10.2 コード参照
- DigitPosPromoter:
src/mir/loop_pattern_detection/loop_body_digitpos_promoter.rs - CarrierInfo:
src/mir/join_ir/lowering/carrier_info.rs - ConditionEnv:
src/mir/join_ir/lowering/condition_env.rs - UpdateEnv:
src/mir/join_ir/lowering/update_env.rs - CarrierUpdateEmitter:
src/mir/join_ir/lowering/carrier_update_emitter.rs - LoopUpdateAnalyzer:
src/mir/join_ir/lowering/loop_update_analyzer.rs
10.3 テストファイル
- _parse_number:
apps/tests/phase189_parse_number_mini.hako,tests/phase245_json_parse_number.rs - _atoi:
apps/tests/phase189_atoi_mini.hako,tests/phase246_json_atoi.rs - DigitPos unit:
src/mir/loop_pattern_detection/loop_body_digitpos_promoter.rs(tests module)
11. Revision History
- 2025-12-11: Phase 247-EX 設計ドキュメント作成
- Status: Active - 設計確定、実装準備完了
- Scope: DigitPos 二重値化設計(ExprLowerer + NumberAccumulation ライン)