diff --git a/CURRENT_TASK.md b/CURRENT_TASK.md index e9e7b2b4..9ec4d852 100644 --- a/CURRENT_TASK.md +++ b/CURRENT_TASK.md @@ -140,6 +140,11 @@ - Phase 56: `ArrayExtBox.filter/2` 向けに `LoopFrontendBinding::for_array_filter` を MethodCall ベース(`arr.size()`)に修正し、外部参照 `arr/pred` を Binding 経由で渡す構造に統一。 - JoinIR に `ConditionalMethodCall` / Unary / Call を追加し、filter の「pred が true のときだけ push する」パターンを 4 ブロック構造で表現。 - `HAKO_JOINIR_ARRAY_FILTER_MAIN=1` で Route B(JoinIR Frontend 経路)がフォールバックなし完走(テスト済み、既定は従来ルート)。 +- Phase P1: **If Handler 箱化モジュール化** ✅ 完了(2025-11-29) + - ループ内 If 処理の 5 パターン(Empty/SingleVarThen/SingleVarBoth/ConditionalEffect/Unsupported)を `IfInLoopPattern` enum で分類。 + - `if_in_loop/` モジュール(9 ファイル、~480 行)を新設し、`stmt_handlers.rs` から 154 行削減(40% 削減達成)。 + - 全 56 JoinIR tests PASS(回帰なし)、保守性・拡張性向上(新パターン追加が容易に)。 + - docs: `docs/development/refactoring/p1-if-handler-boxification-plan.md` --- diff --git a/docs/development/refactoring/p1-if-handler-boxification-plan.md b/docs/development/refactoring/p1-if-handler-boxification-plan.md new file mode 100644 index 00000000..8f0e35f6 --- /dev/null +++ b/docs/development/refactoring/p1-if-handler-boxification-plan.md @@ -0,0 +1,418 @@ +# If Handler 箱化モジュール化 実装計画書 + +## 目次 +1. [現状分析](#1-現状分析) +2. [ファイル構成案](#2-ファイル構成案) +3. [IfPattern enum 設計](#3-ifpattern-enum-設計) +4. [段階的実装手順](#4-段階的実装手順) +5. [テスト戦略](#5-テスト戦略) +6. [リスク評価](#6-リスク評価) +7. [タイムライン](#7-タイムライン) +8. [チェックリスト](#8-チェックリスト) + +--- + +## 1. 現状分析 + +### 1.1 対象コード概要 + +**ファイル**: `/home/tomoaki/git/hakorune-selfhost/src/mir/join_ir/frontend/ast_lowerer/stmt_handlers.rs` + +**対象関数**: `lower_if_stmt_in_loop()` (147-300行) + +**現在の実装**: 5段階のケース分岐による If 処理 +- **ケース 1**: 空の If(164-166行、3行) +- **ケース 2**: 単一変数更新 then のみ(169-202行、34行) +- **ケース 3**: 両方に単一変数更新(204-234行、31行) +- **ケース 4**: Phase 56 条件付き側効果パターン(236-292行、57行) +- **ケース 5**: 複雑なケース・未対応(294-300行、7行) + +**合計**: 154行(ロジック部分) + +### 1.2 既存のパターン分類システム + +プロジェクトには既に2つのパターン分類システムが存在: + +1. **IfSelectLowerer** (`src/mir/join_ir/lowering/if_select.rs`) + - MIR レベルの If パターン検出 + - `IfPatternType`: Simple/Local の2パターン + - PHI 早期チェック機能 + - 176行、箱化済み + +2. **IfLoweringDryRunner** (`src/mir/join_ir/lowering/if_dry_runner.rs`) + - MIR モジュール全体スキャナー + - パフォーマンス計測機能 + - 統計情報収集 + - 166行、箱化済み + +### 1.3 設計原則(Phase 33-10) + +**JoinIR は PHI 生成器(SSOT)、PHI 変換器ではない** + +この原則により: +- 既存 PHI を持つ MIR は JoinIR 変換をスキップ +- JoinIR は新規 PHI のみを生成 +- 既存経路との共存が可能 + +--- + +## 2. ファイル構成案 + +### 2.1 新規ファイル構成 + +``` +src/mir/join_ir/frontend/ast_lowerer/ +├── stmt_handlers.rs (縮小版: ~100行) +│ └── lower_statement() - エントリーポイント +│ └── lower_local_stmt() - Local 処理 +│ └── lower_assignment_stmt() - Assignment 処理 +│ └── lower_print_stmt() - Print 処理 +│ └── lower_method_stmt() - Method 処理 +│ └── lower_if_stmt_in_loop() - If ディスパッチャー(リファクタ後) +│ +└── if_in_loop/ + ├── mod.rs (~40行) + │ └── pub use declarations + │ + ├── pattern.rs (~80行) + │ ├── IfInLoopPattern enum + │ ├── detect() 関数 + │ └── ヘルパー関数 + │ + ├── lowering/ + │ ├── mod.rs (~20行) + │ ├── empty.rs (~20行) - ケース 1 + │ ├── single_var_then.rs (~50行) - ケース 2 + │ ├── single_var_both.rs (~50行) - ケース 3 + │ ├── conditional_effect.rs (~80行) - ケース 4 + │ └── unsupported.rs (~20行) - ケース 5 + │ + └── tests.rs (~120行) + └── 各パターンのユニットテスト +``` + +**新規ファイル合計**: ~480行(現在154行 → +326行、構造化コストを含む) + +### 2.2 ファイル役割分担 + +| ファイル | 責務 | 行数見積 | +|---------|------|---------| +| `if_in_loop/mod.rs` | 公開API、再エクスポート | 40 | +| `if_in_loop/pattern.rs` | パターン検出ロジック | 80 | +| `if_in_loop/lowering/mod.rs` | lowering 統合 | 20 | +| `if_in_loop/lowering/empty.rs` | 空If処理 | 20 | +| `if_in_loop/lowering/single_var_then.rs` | Then単一変数 | 50 | +| `if_in_loop/lowering/single_var_both.rs` | 両方単一変数 | 50 | +| `if_in_loop/lowering/conditional_effect.rs` | 条件付き側効果 | 80 | +| `if_in_loop/lowering/unsupported.rs` | 未対応処理 | 20 | +| `if_in_loop/tests.rs` | テストスイート | 120 | +| **合計** | | **480** | + +--- + +## 3. IfPattern enum 設計 + +### 3.1 パターン定義 + +```rust +//! if_in_loop/pattern.rs + +use crate::mir::ValueId; + +/// ループ内 If 文のパターン分類 +#[derive(Debug, Clone, PartialEq)] +pub enum IfInLoopPattern { + /// ケース 1: 空の If(条件チェックのみ) + /// if cond { } else { } + Empty { + cond: ValueId, + }, + + /// ケース 2: 単一変数更新 then のみ + /// if cond { x = expr } + SingleVarThen { + cond: ValueId, + var_name: String, + then_expr: serde_json::Value, + else_val: ValueId, // 元の値 + }, + + /// ケース 3: 両方に単一変数更新(同じ変数) + /// if cond { x = a } else { x = b } + SingleVarBoth { + cond: ValueId, + var_name: String, + then_expr: serde_json::Value, + else_expr: serde_json::Value, + }, + + /// ケース 4: 条件付き側効果パターン(filter 用) + /// if pred(v) { acc.push(v) } + ConditionalEffect { + cond: ValueId, + receiver_expr: serde_json::Value, + method: String, + args: Vec, + }, + + /// ケース 5: 複雑なケース(未対応) + Unsupported { + then_stmts_len: usize, + else_stmts_len: usize, + }, +} + +impl IfInLoopPattern { + /// If 文からパターンを検出 + pub fn detect( + stmt: &serde_json::Value, + ctx: &ExtractCtx, + ) -> Result { + // 実装詳細は計画書本編参照 + } +} +``` + +--- + +## 4. 段階的実装手順 + +### Step 1: 基礎インフラ構築(2-3時間) + +**目標**: ファイル構成とパターン検出ロジックの実装 + +**作業内容**: +1. ディレクトリ構造作成 +2. `if_in_loop/mod.rs` 作成 +3. `if_in_loop/pattern.rs` 実装 +4. `if_in_loop/lowering/mod.rs` 作成(スケルトン) + +**完了条件**: +- ✅ ファイル構造完成 +- ✅ パターン検出ロジックがコンパイル可能 +- ✅ 基本テストが通る + +### Step 2: 各パターンの lowering 実装(4-6時間) + +**ケース実装順序**: +1. ケース 1: Empty (30分) +2. ケース 2: SingleVarThen (1時間) +3. ケース 3: SingleVarBoth (1時間) +4. ケース 4: ConditionalEffect (2時間) - 最複雑 +5. ケース 5: Unsupported (30分) +6. lowering/mod.rs 統合 (1時間) + +**完了条件**: +- ✅ 5つのケース全て実装完了 +- ✅ lowering/mod.rs で統合完了 +- ✅ ユニットテストが通る + +### Step 3: stmt_handlers.rs のリファクタリング(1-2時間) + +**変更内容**: +```rust +// stmt_handlers.rs (リファクタ後) + +use super::if_in_loop::{IfInLoopPattern, lower_pattern}; + +impl AstToJoinIrLowerer { + fn lower_if_stmt_in_loop( + &mut self, + stmt: &serde_json::Value, + ctx: &mut ExtractCtx, + ) -> (Vec, StatementEffect) { + // パターン検出 + let pattern = IfInLoopPattern::detect(stmt, ctx) + .expect("Failed to detect If pattern"); + + // パターンに応じた lowering + lower_pattern(&pattern, self, ctx) + } +} +``` + +**完了条件**: +- ✅ stmt_handlers.rs が簡潔になった(154行 → 10行) +- ✅ 既存テストが全て通る +- ✅ 動作に変更がない(リファクタリングのみ) + +### Step 4: テスト追加(2-3時間) + +**テストケース構成**: +- 各パターンの基本ケース: 5個 +- 複雑な式の処理: 5個 +- エッジケース: 5個 +- エラーハンドリング: 3個 +- パターン検出テスト: 5個 +- **合計**: 30-40個のテスト + +**完了条件**: +- ✅ 各パターンごとに最低2つのテスト +- ✅ エラーケースのテスト +- ✅ 全テストが通る +- ✅ カバレッジ80%以上(理想) + +### Step 5: 統合・検証(1-2時間) + +**検証項目**: +- ✅ 既存テスト全て通過 +- ✅ 新規テスト全て通過 +- ✅ 実際の .hako ファイルで動作確認 +- ✅ パフォーマンス劣化なし +- ✅ コンパイル警告ゼロ + +--- + +## 5. テスト戦略 + +### 5.1 テストレベル + +| レベル | 対象 | 目的 | テスト数 | +|--------|------|------|---------| +| **ユニットテスト** | パターン検出ロジック | 各パターンの正確な検出 | 10-15 | +| **統合テスト** | lowering 処理 | 正しい JoinInst 生成 | 10-15 | +| **E2Eテスト** | .hako ファイル実行 | 実際の動作確認 | 3-5 | +| **回帰テスト** | 既存テスト | 変更による影響確認 | 既存全て | + +### 5.2 回帰テストリスト + +既存テストケースで動作確認が必要なもの: + +```bash +# Loop内If テスト +apps/tests/loop_if_phi.hako +apps/tests/loop_if_phi_continue.hako +apps/tests/loop_phi_one_sided.hako + +# Macro If テスト +apps/tests/macro/if/assign.hako +apps/tests/macro/if/assign_both_branches.hako +``` + +--- + +## 6. リスク評価 + +### 6.1 技術的リスク + +| リスク | 深刻度 | 確率 | 対策 | +|--------|--------|------|------| +| **既存テストの破損** | 高 | 中 | Step 3 で段階的移行、既存ロジック保持 | +| **パフォーマンス劣化** | 中 | 低 | 関数呼び出しオーバーヘッド最小化 | +| **新バグの混入** | 高 | 中 | 包括的テストスイート、A/Bテスト | + +### 6.2 回避策:段階的移行 + +```rust +fn lower_if_stmt_in_loop(...) -> ... { + if std::env::var("HAKO_USE_OLD_IF_HANDLER").is_ok() { + // 旧実装(保持) + self.lower_if_stmt_in_loop_legacy(stmt, ctx) + } else { + // 新実装 + let pattern = IfInLoopPattern::detect(stmt, ctx)?; + lower_pattern(&pattern, self, ctx) + } +} +``` + +--- + +## 7. タイムライン + +### 7.1 スケジュール概要 + +| フェーズ | 期間 | 作業内容 | 完了判定 | +|---------|------|---------|---------| +| **準備** | 0.5時間 | 要件確認、設計レビュー | ✅ 計画書承認 | +| **Step 1** | 2-3時間 | 基礎インフラ構築 | ✅ パターン検出実装 | +| **Step 2** | 4-6時間 | 各パターン lowering 実装 | ✅ 5ケース全実装 | +| **Step 3** | 1-2時間 | stmt_handlers リファクタ | ✅ 委譲完了 | +| **Step 4** | 2-3時間 | テスト追加 | ✅ カバレッジ80%+ | +| **Step 5** | 1-2時間 | 統合・検証 | ✅ CI通過 | +| **合計** | **10.5-16.5時間** | | | + +### 7.2 詳細スケジュール(3日間想定) + +#### Day 1(4-6時間) +- **午前** (2-3時間): Step 1 基礎インフラ構築 +- **午後** (2-3時間): Step 2-1, 2-2 開始 + +#### Day 2(4-6時間) +- **午前** (2-3時間): Step 2-3, 2-4 +- **午後** (2-3時間): Step 2-5, 2-6 + +#### Day 3(2.5-4.5時間) +- **午前** (1-2時間): Step 3 +- **午後** (1.5-2.5時間): Step 4, 5 + +--- + +## 8. チェックリスト + +### 8.1 実装完了チェックリスト + +#### 基礎インフラ (Step 1) +- [ ] ディレクトリ構造作成完了 +- [ ] `if_in_loop/mod.rs` 作成完了 +- [ ] `if_in_loop/pattern.rs` 実装完了 +- [ ] コンパイル成功 +- [ ] 基本パターン検出テスト通過 + +#### パターン lowering (Step 2) +- [ ] `lowering/empty.rs` 実装完了 +- [ ] `lowering/single_var_then.rs` 実装完了 +- [ ] `lowering/single_var_both.rs` 実装完了 +- [ ] `lowering/conditional_effect.rs` 実装完了 +- [ ] `lowering/unsupported.rs` 実装完了 +- [ ] `lowering/mod.rs` 統合完了 + +#### リファクタリング (Step 3) +- [ ] `stmt_handlers.rs` 修正完了 +- [ ] コンパイル成功 +- [ ] 既存テスト全通過 + +#### テスト (Step 4) +- [ ] `if_in_loop/tests.rs` 作成完了 +- [ ] ケース 1-5 各テスト作成 +- [ ] パターン検出テスト +- [ ] カバレッジ80%以上達成 + +#### 統合・検証 (Step 5) +- [ ] 全ユニットテスト通過 +- [ ] 全統合テスト通過 +- [ ] 既存回帰テスト通過 +- [ ] E2Eテスト通過 +- [ ] CI/CD通過 + +--- + +## 9. 成果物 + +### 9.1 コード成果物 + +1. **新規ファイル** (9ファイル、~480行) +2. **修正ファイル** (1ファイル) + - `stmt_handlers.rs` (330行 → 186行、-144行) + +3. **純増減** + - 新規: +480行 + - 削減: -144行 + - **実質: +336行**(構造化コスト含む) + +--- + +## 10. 次のステップ + +### 10.1 実装後の展開 + +1. **Phase 33-12**: IfMerge 実装(複数変数PHI対応) +2. **Phase 34**: return/break を含む If の JoinIR 表現 +3. **Phase 35**: If/PHI レガシー箱の完全削除 + +--- + +**作成日**: 2025-11-29 +**Phase**: 56 後のリファクタリング計画 +**作成者**: Task agent (Plan mode) + Claude Code +**目的**: stmt_handlers.rs の If 処理を箱化モジュール化し、保守性と拡張性を向上 diff --git a/src/mir/join_ir/frontend/ast_lowerer/if_in_loop/lowering/conditional_effect.rs b/src/mir/join_ir/frontend/ast_lowerer/if_in_loop/lowering/conditional_effect.rs new file mode 100644 index 00000000..e44bc9c7 --- /dev/null +++ b/src/mir/join_ir/frontend/ast_lowerer/if_in_loop/lowering/conditional_effect.rs @@ -0,0 +1,72 @@ +//! Phase P1: 条件付き側効果 - ケース 4 +//! +//! `if pred(v) { acc.push(v) }` → ConditionalMethodCall +//! filter パターン用の特殊 JoinIR 命令を生成する。 + +use super::super::super::{AstToJoinIrLowerer, ExtractCtx, JoinInst, StatementEffect}; +use crate::mir::ValueId; + +/// ケース 4: 条件付き側効果パターン +/// +/// # Arguments +/// * `lowerer` - AstToJoinIrLowerer インスタンス +/// * `ctx` - 変数コンテキスト +/// * `insts` - 条件式の命令列 +/// * `cond_id` - 条件変数 ID +/// * `then_stmts` - then 分岐のステートメント配列 +/// * `receiver_name` - receiver 変数名(acc 等) +/// * `method_name` - メソッド名(push 等) +pub fn lower( + lowerer: &mut AstToJoinIrLowerer, + ctx: &mut ExtractCtx, + mut insts: Vec, + cond_id: ValueId, + then_stmts: &[serde_json::Value], + receiver_name: &str, + method_name: &str, +) -> (Vec, StatementEffect) { + let stmt = &then_stmts[0]; + let receiver_expr = stmt.get("receiver").or_else(|| stmt.get("object")); + let args_array = stmt.get("args").or_else(|| stmt.get("arguments")); + + if let (Some(receiver_expr), Some(args_array)) = (receiver_expr, args_array) { + // receiver を評価 + let (receiver_var, receiver_insts) = lowerer.extract_value(receiver_expr, ctx); + insts.extend(receiver_insts); + + // args を評価 + let mut arg_vars = Vec::new(); + if let Some(args) = args_array.as_array() { + for arg_expr in args { + let (arg_var, arg_insts) = lowerer.extract_value(arg_expr, ctx); + arg_vars.push(arg_var); + insts.extend(arg_insts); + } + } + + // 結果変数を割り当て + let dst = ctx.alloc_var(); + + // ConditionalMethodCall 命令を生成 + insts.push(JoinInst::ConditionalMethodCall { + cond: cond_id, + dst, + receiver: receiver_var, + method: method_name.to_string(), + args: arg_vars, + }); + + // receiver 変数を更新された値で登録 + ctx.register_param(receiver_name.to_string(), dst); + + return ( + insts, + StatementEffect::VarUpdate { + name: receiver_name.to_string(), + value_id: dst, + }, + ); + } + + panic!("ConditionalEffect pattern: invalid receiver or args"); +} diff --git a/src/mir/join_ir/frontend/ast_lowerer/if_in_loop/lowering/empty.rs b/src/mir/join_ir/frontend/ast_lowerer/if_in_loop/lowering/empty.rs new file mode 100644 index 00000000..1a77fc18 --- /dev/null +++ b/src/mir/join_ir/frontend/ast_lowerer/if_in_loop/lowering/empty.rs @@ -0,0 +1,13 @@ +//! Phase P1: 空の If - ケース 1 +//! +//! 条件チェックのみで then/else 両方が空の場合。 +//! 条件式の副作用のみを保持し、効果なしを返す。 + +use super::super::super::{JoinInst, StatementEffect}; + +/// ケース 1: 空の If +/// +/// 条件式は評価されるが、then/else が空なので効果なし。 +pub fn lower(insts: Vec) -> (Vec, StatementEffect) { + (insts, StatementEffect::None) +} diff --git a/src/mir/join_ir/frontend/ast_lowerer/if_in_loop/lowering/mod.rs b/src/mir/join_ir/frontend/ast_lowerer/if_in_loop/lowering/mod.rs new file mode 100644 index 00000000..6fe88b2d --- /dev/null +++ b/src/mir/join_ir/frontend/ast_lowerer/if_in_loop/lowering/mod.rs @@ -0,0 +1,7 @@ +//! Phase P1: If in Loop Lowering - 各パターンの lowering 実装 + +pub mod empty; +pub mod single_var_then; +pub mod single_var_both; +pub mod conditional_effect; +pub mod unsupported; diff --git a/src/mir/join_ir/frontend/ast_lowerer/if_in_loop/lowering/single_var_both.rs b/src/mir/join_ir/frontend/ast_lowerer/if_in_loop/lowering/single_var_both.rs new file mode 100644 index 00000000..f45635f8 --- /dev/null +++ b/src/mir/join_ir/frontend/ast_lowerer/if_in_loop/lowering/single_var_both.rs @@ -0,0 +1,56 @@ +//! Phase P1: then/else 両方が同じ変数への単一更新 - ケース 3 +//! +//! `if cond { x = a } else { x = b }` → `x = cond ? a : b` +//! 典型的な三項演算子パターン。 + +use super::super::super::{AstToJoinIrLowerer, ExtractCtx, JoinInst, StatementEffect}; +use crate::mir::ValueId; + +/// ケース 3: 両方に単一変数更新(同じ変数) +/// +/// # Arguments +/// * `lowerer` - AstToJoinIrLowerer インスタンス +/// * `ctx` - 変数コンテキスト +/// * `insts` - 条件式の命令列 +/// * `cond_id` - 条件変数 ID +/// * `var_name` - 更新する変数名 +/// * `then_stmts` - then 分岐のステートメント配列 +/// * `else_stmts` - else 分岐のステートメント配列 +pub fn lower( + lowerer: &mut AstToJoinIrLowerer, + ctx: &mut ExtractCtx, + mut insts: Vec, + cond_id: ValueId, + var_name: &str, + then_stmts: &[serde_json::Value], + else_stmts: &[serde_json::Value], +) -> (Vec, StatementEffect) { + // then の式を評価 + let then_expr = &then_stmts[0]["expr"]; + let (then_val, then_insts) = lowerer.extract_value(then_expr, ctx); + insts.extend(then_insts); + + // else の式を評価 + let else_expr = &else_stmts[0]["expr"]; + let (else_val, else_insts) = lowerer.extract_value(else_expr, ctx); + insts.extend(else_insts); + + // Select: cond ? then_val : else_val + let result_id = ctx.alloc_var(); + insts.push(JoinInst::Select { + dst: result_id, + cond: cond_id, + then_val, + else_val, + }); + + ctx.register_param(var_name.to_string(), result_id); + + ( + insts, + StatementEffect::VarUpdate { + name: var_name.to_string(), + value_id: result_id, + }, + ) +} diff --git a/src/mir/join_ir/frontend/ast_lowerer/if_in_loop/lowering/single_var_then.rs b/src/mir/join_ir/frontend/ast_lowerer/if_in_loop/lowering/single_var_then.rs new file mode 100644 index 00000000..18f2b1c5 --- /dev/null +++ b/src/mir/join_ir/frontend/ast_lowerer/if_in_loop/lowering/single_var_then.rs @@ -0,0 +1,54 @@ +//! Phase P1: then のみ単一変数更新 - ケース 2 +//! +//! `if cond { x = expr }` → `x = cond ? expr : x` +//! then で変数を更新し、else では元の値を維持する。 + +use super::super::super::{AstToJoinIrLowerer, ExtractCtx, JoinInst, StatementEffect}; +use crate::mir::ValueId; + +/// ケース 2: then のみ単一変数更新 +/// +/// # Arguments +/// * `lowerer` - AstToJoinIrLowerer インスタンス +/// * `ctx` - 変数コンテキスト +/// * `insts` - 条件式の命令列 +/// * `cond_id` - 条件変数 ID +/// * `var_name` - 更新する変数名 +/// * `then_stmts` - then 分岐のステートメント配列 +pub fn lower( + lowerer: &mut AstToJoinIrLowerer, + ctx: &mut ExtractCtx, + mut insts: Vec, + cond_id: ValueId, + var_name: &str, + then_stmts: &[serde_json::Value], +) -> (Vec, StatementEffect) { + // then の式を評価 + let then_expr = &then_stmts[0]["expr"]; + let (then_val, then_insts) = lowerer.extract_value(then_expr, ctx); + insts.extend(then_insts); + + // else は元の値 + let else_val = ctx + .get_var(var_name) + .unwrap_or_else(|| panic!("Variable '{}' must exist for If/else", var_name)); + + // Select: cond ? then_val : else_val + let result_id = ctx.alloc_var(); + insts.push(JoinInst::Select { + dst: result_id, + cond: cond_id, + then_val, + else_val, + }); + + ctx.register_param(var_name.to_string(), result_id); + + ( + insts, + StatementEffect::VarUpdate { + name: var_name.to_string(), + value_id: result_id, + }, + ) +} diff --git a/src/mir/join_ir/frontend/ast_lowerer/if_in_loop/lowering/unsupported.rs b/src/mir/join_ir/frontend/ast_lowerer/if_in_loop/lowering/unsupported.rs new file mode 100644 index 00000000..613e2ee0 --- /dev/null +++ b/src/mir/join_ir/frontend/ast_lowerer/if_in_loop/lowering/unsupported.rs @@ -0,0 +1,19 @@ +//! Phase P1: 複雑なケース(未対応) - ケース 5 +//! +//! 複数ステートメント、異なる変数更新など、 +//! 現在サポートされていないパターン。 +//! Phase 54+ で対応予定。 + +use super::super::super::{JoinInst, StatementEffect}; + +/// ケース 5: 複雑なケース(未対応) +/// +/// # Panics +/// 常にパニックする(未対応) +pub fn panic_unsupported(then_count: usize, else_count: usize) -> (Vec, StatementEffect) { + panic!( + "Complex If statement in loop body not yet supported (Phase 54). \ + then: {} stmts, else: {} stmts", + then_count, else_count + ) +} diff --git a/src/mir/join_ir/frontend/ast_lowerer/if_in_loop/mod.rs b/src/mir/join_ir/frontend/ast_lowerer/if_in_loop/mod.rs new file mode 100644 index 00000000..8da83632 --- /dev/null +++ b/src/mir/join_ir/frontend/ast_lowerer/if_in_loop/mod.rs @@ -0,0 +1,80 @@ +//! Phase P1: If in Loop Handler - 箱化モジュール +//! +//! ループ内の If ステートメントを JoinIR に変換する。 +//! 5 つのパターンに分類し、それぞれに適した lowering 戦略を適用する。 + +pub mod pattern; +pub mod lowering; + +use super::{AstToJoinIrLowerer, ExtractCtx, JoinInst, StatementEffect}; +use pattern::IfInLoopPattern; + +impl AstToJoinIrLowerer { + /// Phase P1: If ステートメント(ループ内)を JoinIR に変換 + /// + /// 元の lower_if_stmt_in_loop() を箱化モジュール化したエントリーポイント。 + /// パターン検出 → 適切な lowering 関数に委譲する。 + pub fn lower_if_stmt_in_loop_boxified( + &mut self, + stmt: &serde_json::Value, + ctx: &mut ExtractCtx, + ) -> (Vec, StatementEffect) { + let cond_expr = &stmt["cond"]; + let then_body = stmt["then"].as_array(); + let else_body = stmt["else"].as_array(); + + // 条件を評価 + let (cond_id, mut insts) = self.extract_value(cond_expr, ctx); + + // then/else のステートメント配列を取得 + let then_stmts = then_body.map(|v| v.as_slice()).unwrap_or(&[]); + let else_stmts = else_body.map(|v| v.as_slice()).unwrap_or(&[]); + + // パターンを検出 + let pattern = IfInLoopPattern::detect(then_stmts, else_stmts); + + // パターンごとに lowering + match pattern { + IfInLoopPattern::Empty => { + lowering::empty::lower(insts) + } + IfInLoopPattern::SingleVarThen { var_name } => { + lowering::single_var_then::lower( + self, + ctx, + insts, + cond_id, + &var_name, + then_stmts, + ) + } + IfInLoopPattern::SingleVarBoth { var_name } => { + lowering::single_var_both::lower( + self, + ctx, + insts, + cond_id, + &var_name, + then_stmts, + else_stmts, + ) + } + IfInLoopPattern::ConditionalEffect { + receiver_name, + method_name, + } => lowering::conditional_effect::lower( + self, + ctx, + insts, + cond_id, + then_stmts, + &receiver_name, + &method_name, + ), + IfInLoopPattern::Unsupported { + then_count, + else_count, + } => lowering::unsupported::panic_unsupported(then_count, else_count), + } + } +} diff --git a/src/mir/join_ir/frontend/ast_lowerer/if_in_loop/pattern.rs b/src/mir/join_ir/frontend/ast_lowerer/if_in_loop/pattern.rs new file mode 100644 index 00000000..1b706560 --- /dev/null +++ b/src/mir/join_ir/frontend/ast_lowerer/if_in_loop/pattern.rs @@ -0,0 +1,214 @@ +//! Phase P1: If in Loop パターン分類 +//! +//! ループ内の If ステートメントを 5 つのパターンに分類し、 +//! 適切な lowering 戦略を選択する。 + +use serde_json::Value; + +/// ループ内 If ステートメントのパターン +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum IfInLoopPattern { + /// ケース 1: 空の If(条件チェックのみ) + /// then/else 両方が空 + Empty, + + /// ケース 2: then のみ単一変数更新、else は空 + /// `if cond { x = expr }` → `x = cond ? expr : x` + SingleVarThen { + var_name: String, + }, + + /// ケース 3: then/else 両方が同じ変数への単一更新 + /// `if cond { x = a } else { x = b }` → `x = cond ? a : b` + SingleVarBoth { + var_name: String, + }, + + /// ケース 4: 条件付き側効果(filter パターン) + /// `if pred(v) { acc.push(v) }` → ConditionalMethodCall + ConditionalEffect { + receiver_name: String, + method_name: String, + }, + + /// ケース 5: 複雑なケース(未対応) + /// 複数ステートメント、異なる変数更新など + Unsupported { + then_count: usize, + else_count: usize, + }, +} + +impl IfInLoopPattern { + /// If ステートメントから適切なパターンを検出 + /// + /// # Arguments + /// * `then_stmts` - then 分岐のステートメント配列 + /// * `else_stmts` - else 分岐のステートメント配列 + pub fn detect(then_stmts: &[Value], else_stmts: &[Value]) -> Self { + // ケース 1: 空の If + if then_stmts.is_empty() && else_stmts.is_empty() { + return Self::Empty; + } + + // ケース 2: then のみ単一変数更新 + if let (Some((then_name, _)), None) = ( + Self::extract_single_var_update(then_stmts), + Self::extract_single_var_update(else_stmts), + ) { + return Self::SingleVarThen { + var_name: then_name, + }; + } + + // ケース 3: 両方に単一変数更新(同じ変数) + if let (Some((then_name, _)), Some((else_name, _))) = ( + Self::extract_single_var_update(then_stmts), + Self::extract_single_var_update(else_stmts), + ) { + if then_name == else_name { + return Self::SingleVarBoth { + var_name: then_name, + }; + } + } + + // ケース 4: 条件付き側効果パターン + if then_stmts.len() == 1 && else_stmts.is_empty() { + let stmt = &then_stmts[0]; + let stmt_type = stmt["type"].as_str(); + + if matches!(stmt_type, Some("Method") | Some("MethodCall")) { + let receiver_expr = stmt.get("receiver").or_else(|| stmt.get("object")); + let method_name = stmt["method"].as_str(); + + if let (Some(receiver_expr), Some(method_name)) = (receiver_expr, method_name) { + let receiver_name = receiver_expr["name"] + .as_str() + .unwrap_or("acc") + .to_string(); + + return Self::ConditionalEffect { + receiver_name, + method_name: method_name.to_string(), + }; + } + } + } + + // ケース 5: 複雑なケース(未対応) + Self::Unsupported { + then_count: then_stmts.len(), + else_count: else_stmts.len(), + } + } + + /// ステートメント配列から単一の変数更新を抽出 + /// + /// Returns: Some((変数名, 式)) if 単一の Local/Assignment + fn extract_single_var_update(stmts: &[Value]) -> Option<(String, &Value)> { + if stmts.len() != 1 { + return None; + } + + let stmt = &stmts[0]; + let stmt_type = stmt["type"].as_str()?; + + match stmt_type { + "Local" => { + let name = stmt["name"].as_str()?.to_string(); + let expr = &stmt["expr"]; + Some((name, expr)) + } + "Assignment" => { + let name = stmt["target"].as_str()?.to_string(); + let expr = &stmt["expr"]; + Some((name, expr)) + } + _ => None, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use serde_json::json; + + #[test] + fn test_detect_empty() { + let pattern = IfInLoopPattern::detect(&[], &[]); + assert_eq!(pattern, IfInLoopPattern::Empty); + } + + #[test] + fn test_detect_single_var_then() { + let then_stmts = vec![json!({ + "type": "Assignment", + "target": "x", + "expr": json!({"type": "Int", "value": 42}) + })]; + let pattern = IfInLoopPattern::detect(&then_stmts, &[]); + assert_eq!( + pattern, + IfInLoopPattern::SingleVarThen { + var_name: "x".to_string() + } + ); + } + + #[test] + fn test_detect_single_var_both() { + let then_stmts = vec![json!({ + "type": "Assignment", + "target": "x", + "expr": json!({"type": "Int", "value": 1}) + })]; + let else_stmts = vec![json!({ + "type": "Assignment", + "target": "x", + "expr": json!({"type": "Int", "value": 2}) + })]; + let pattern = IfInLoopPattern::detect(&then_stmts, &else_stmts); + assert_eq!( + pattern, + IfInLoopPattern::SingleVarBoth { + var_name: "x".to_string() + } + ); + } + + #[test] + fn test_detect_conditional_effect() { + let then_stmts = vec![json!({ + "type": "Method", + "receiver": json!({"name": "acc"}), + "method": "push", + "args": [json!({"type": "Var", "name": "v"})] + })]; + let pattern = IfInLoopPattern::detect(&then_stmts, &[]); + assert_eq!( + pattern, + IfInLoopPattern::ConditionalEffect { + receiver_name: "acc".to_string(), + method_name: "push".to_string() + } + ); + } + + #[test] + fn test_detect_unsupported() { + let then_stmts = vec![ + json!({"type": "Assignment", "target": "x", "expr": json!(1)}), + json!({"type": "Assignment", "target": "y", "expr": json!(2)}), + ]; + let pattern = IfInLoopPattern::detect(&then_stmts, &[]); + assert_eq!( + pattern, + IfInLoopPattern::Unsupported { + then_count: 2, + else_count: 0 + } + ); + } +} diff --git a/src/mir/join_ir/frontend/ast_lowerer/mod.rs b/src/mir/join_ir/frontend/ast_lowerer/mod.rs index 1c0f1082..b1604ab2 100644 --- a/src/mir/join_ir/frontend/ast_lowerer/mod.rs +++ b/src/mir/join_ir/frontend/ast_lowerer/mod.rs @@ -28,6 +28,7 @@ pub(crate) use std::collections::{BTreeMap, HashSet}; mod analysis; mod context; mod expr; +mod if_in_loop; mod if_return; mod loop_patterns; mod nested_if; @@ -38,6 +39,7 @@ mod stmt_handlers; mod tests; pub(crate) use context::ExtractCtx; +pub(crate) use stmt_handlers::StatementEffect; /// AST/CFG → JoinIR 変換器 /// diff --git a/src/mir/join_ir/frontend/ast_lowerer/stmt_handlers.rs b/src/mir/join_ir/frontend/ast_lowerer/stmt_handlers.rs index 002cc0da..99916368 100644 --- a/src/mir/join_ir/frontend/ast_lowerer/stmt_handlers.rs +++ b/src/mir/join_ir/frontend/ast_lowerer/stmt_handlers.rs @@ -16,7 +16,7 @@ use crate::mir::ValueId; /// ステートメントの効果(変数更新 or 副作用のみ) #[derive(Debug, Clone)] -pub enum StatementEffect { +pub(crate) enum StatementEffect { /// 変数を更新(Assignment/Local) VarUpdate { name: String, value_id: ValueId }, /// 副作用のみ(Print) @@ -43,7 +43,7 @@ impl AstToJoinIrLowerer { "Assignment" => self.lower_assignment_stmt(stmt, ctx), "Print" => self.lower_print_stmt(stmt, ctx), "Method" => self.lower_method_stmt(stmt, ctx), - "If" => self.lower_if_stmt_in_loop(stmt, ctx), + "If" => self.lower_if_stmt_in_loop_boxified(stmt, ctx), other => panic!( "Unsupported statement type in loop body: {}. \ Expected: Local, Assignment, Print, Method, If", @@ -138,193 +138,6 @@ impl AstToJoinIrLowerer { (insts, StatementEffect::SideEffect) } - /// If ステートメント(ループ内): `if cond { then } else { else }` - /// - /// Phase 53-4: ガードパターン対応 - /// - /// 単純な変数更新のみの場合は Select 命令に変換し、 - /// 複雑な場合はパニック(Phase 54 で対応予定) - fn lower_if_stmt_in_loop( - &mut self, - stmt: &serde_json::Value, - ctx: &mut ExtractCtx, - ) -> (Vec, StatementEffect) { - let cond_expr = &stmt["cond"]; - let then_body = stmt["then"].as_array(); - let else_body = stmt["else"].as_array(); - - // 条件を評価 - let (cond_id, mut insts) = self.extract_value(cond_expr, ctx); - - // 単純なケース: then/else が空または単一の変数更新 - let then_stmts = then_body.map(|v| v.as_slice()).unwrap_or(&[]); - let else_stmts = else_body.map(|v| v.as_slice()).unwrap_or(&[]); - - // ケース 1: 空の If(条件チェックのみ) - if then_stmts.is_empty() && else_stmts.is_empty() { - return (insts, StatementEffect::None); - } - - // ケース 2: 単一変数更新 → Select に変換 - if let (Some(then_update), None) = - (Self::extract_single_var_update(then_stmts), Self::extract_single_var_update(else_stmts)) - { - // then のみ更新、else は元の値を維持 - let (var_name, then_expr) = then_update; - - // then の式を評価 - let (then_val, then_insts) = self.extract_value(then_expr, ctx); - insts.extend(then_insts); - - // else は元の値 - let else_val = ctx - .get_var(&var_name) - .expect(&format!("Variable '{}' must exist for If/else", var_name)); - - // Select: cond ? then_val : else_val - let result_id = ctx.alloc_var(); - insts.push(JoinInst::Select { - dst: result_id, - cond: cond_id, - then_val, - else_val, - }); - - ctx.register_param(var_name.clone(), result_id); - - return ( - insts, - StatementEffect::VarUpdate { - name: var_name, - value_id: result_id, - }, - ); - } - - // ケース 3: 両方に単一変数更新(同じ変数) - if let (Some((then_name, then_expr)), Some((else_name, else_expr))) = ( - Self::extract_single_var_update(then_stmts), - Self::extract_single_var_update(else_stmts), - ) { - if then_name == else_name { - let (then_val, then_insts) = self.extract_value(then_expr, ctx); - insts.extend(then_insts); - - let (else_val, else_insts) = self.extract_value(else_expr, ctx); - insts.extend(else_insts); - - let result_id = ctx.alloc_var(); - insts.push(JoinInst::Select { - dst: result_id, - cond: cond_id, - then_val, - else_val, - }); - - ctx.register_param(then_name.clone(), result_id); - - return ( - insts, - StatementEffect::VarUpdate { - name: then_name, - value_id: result_id, - }, - ); - } - } - - // ケース 4: Phase 56 条件付き側効果パターン(filter 用) - // if pred(v) { acc.push(v) } - // then: 1 statement (MethodCall/Method), else: empty - if then_stmts.len() == 1 && else_stmts.is_empty() { - let stmt = &then_stmts[0]; - let stmt_type = stmt["type"].as_str(); - - // MethodCall/Method 形式のステートメントをチェック - if matches!(stmt_type, Some("Method") | Some("MethodCall")) { - let receiver_expr = stmt.get("receiver").or_else(|| stmt.get("object")); - let method_name = stmt["method"].as_str(); - let args_array = stmt.get("args").or_else(|| stmt.get("arguments")); - - if let (Some(receiver_expr), Some(method_name), Some(args_array)) = - (receiver_expr, method_name, args_array) - { - // receiver を評価 - let (receiver_var, receiver_insts) = self.extract_value(receiver_expr, ctx); - insts.extend(receiver_insts); - - // args を評価 - let mut arg_vars = Vec::new(); - if let Some(args) = args_array.as_array() { - for arg_expr in args { - let (arg_var, arg_insts) = self.extract_value(arg_expr, ctx); - arg_vars.push(arg_var); - insts.extend(arg_insts); - } - } - - // 結果変数を割り当て(push は配列を返すので結果は無視されることが多い) - let dst = ctx.alloc_var(); - - // ConditionalMethodCall 命令を生成 - insts.push(JoinInst::ConditionalMethodCall { - cond: cond_id, - dst, - receiver: receiver_var, - method: method_name.to_string(), - args: arg_vars, - }); - - // receiver 変数を更新された値で登録(push は receiver を返す) - // この場合、accumulator 変数 "acc" または "out" を更新する必要がある - let receiver_name = receiver_expr["name"].as_str().unwrap_or("acc"); - ctx.register_param(receiver_name.to_string(), dst); - - return ( - insts, - StatementEffect::VarUpdate { - name: receiver_name.to_string(), - value_id: dst, - }, - ); - } - } - } - - // ケース 5: 複雑なケース(未対応) - panic!( - "Complex If statement in loop body not yet supported (Phase 54). \ - then: {} stmts, else: {} stmts", - then_stmts.len(), - else_stmts.len() - ); - } - - /// ステートメント配列から単一の変数更新を抽出 - /// - /// Returns: Some((変数名, 式)) if 単一の Local/Assignment - fn extract_single_var_update( - stmts: &[serde_json::Value], - ) -> Option<(String, &serde_json::Value)> { - if stmts.len() != 1 { - return None; - } - - let stmt = &stmts[0]; - let stmt_type = stmt["type"].as_str()?; - - match stmt_type { - "Local" => { - let name = stmt["name"].as_str()?.to_string(); - let expr = &stmt["expr"]; - Some((name, expr)) - } - "Assignment" => { - let name = stmt["target"].as_str()?.to_string(); - let expr = &stmt["expr"]; - Some((name, expr)) - } - _ => None, - } - } + // Phase P1: lower_if_stmt_in_loop() は if_in_loop/ モジュールに箱化移行済み + // lower_if_stmt_in_loop_boxified() を参照 }