Files
hakorune/docs/development/current/main/phase247-digitpos-dual-value-design.md
nyash-codex 8900a3cc44 feat(joinir): Phase 247-EX - DigitPos dual-value architecture
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>
2025-12-11 15:08:14 +09:00

16 KiB
Raw Blame History

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_posis_digit_pos (ConditionEnv)
  • Update 式: digit_posdigit_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_posis_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_posdigit_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 + posv = 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 ASeparate 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)

実装方針:

  1. CarrierInfo に両方の carrier を登録
  2. ScopeManager が context 依存で正しい carrier を返す
    • ConditionEnv: promoted_loopbodylocals に含まれる名前 → is_* carrier
    • UpdateEnv: promoted_loopbodylocals に含まれる名前 → *_value carrier

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_posis_digit_pos 解決(既存)
  • 🆕 UpdateEnv: digit_posdigit_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 < 0is_digit_pos で動作
  • digit_value carrier は生成されるが未使用(害なし)
  • 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 < 0is_pos で動作
  • NumberAccumulation: v = v * 10 + posv = 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_pos carrier が引き続き動作
  • 新規の digit_value carrier が追加されても退行なし

7. 成功条件

7.1 ビルド

  • cargo build --release 成功0 errors, 0 warnings

7.2 単体テスト

  • DigitPosPromoter テスト: 2つの carrier 生成を確認
  • CarrierInfo テスト: 両方の carrier が含まれることを確認
  • UpdateEnv テスト: digit_posdigit_value 解決を確認

7.3 E2E テスト

  • _parse_number: digit_value 未使用でも正常動作
  • _atoi: digit_value を NumberAccumulation で使用
  • Phase 224/245/246 既存テスト: 退行なし

7.4 ドキュメント

  • このドキュメント完成
  • 箱の責務が明確に書かれている
  • テスト戦略が具体的

8. 将来への展開

8.1 他のパターンへの適用

同じ二重値化モデルを以下に適用可能:

_atof_loop (小数パース):

  • digit_posis_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_posdigit_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 ライン)