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

554 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Phase 247-EX: DigitPos Dual-Value Design
Status: Active
Scope: DigitPos パターンを「1つの質問→2つの値」に整理し、break 条件と NumberAccumulation 両方に対応。
## 1. DigitPos の二重値化モデル
### 1.1 問題の本質
**Phase 224 の現状**:
```rust
// 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 値が必要!
**具体例**:
```nyash
// _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 に含める**:
```rust
// 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) - **新規追加**
**実装方針**:
```rust
// 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 で含まれるもの**:
```rust
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 の既存動作**:
```rust
// 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 の新規動作**:
```rust
// 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
**ループ構造**:
```nyash
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
**ループ構造**:
```nyash
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]
**ループ構造** (想定):
```nyash
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 の命名戦略**:
```rust
// 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 条件)**:
```rust
// digit_pos 参照 → is_digit_pos (boolean carrier)
env.get("digit_pos") Some(is_digit_pos_value_id)
```
**UpdateEnv (update 式)**:
```rust
// 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_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` 側も生成されることを確認
**新規テスト**:
```rust
#[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_value` carrier は生成されるが未使用(害なし)
- String 累積(`num_str`)は別の carrier で処理
**検証コマンド**:
```bash
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 が正しく使用される
**検証コマンド**:
```bash
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_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 ライン)