feat(joinir): Phase 34-7.5 helpers + Phase 34-8 Break/Continue implementation
Phase 34-7.5: Code organization improvements
- Added type conversion helpers (as_cont/as_func) in join_ir/mod.rs
- Enhanced docstrings for JoinCall/JoinJump with usage examples
- Improved error messages in join_ir_vm_bridge.rs
JoinIrFrontendTestRunner box implementation
- Created src/tests/helpers/joinir_frontend.rs (119 lines)
- Reduced test code by 83% (70 lines → 12 lines per test)
- 26% overall reduction in test file (284 → 209 lines)
Phase 34-8: Break/Continue pattern implementation
- Extended ast_lowerer.rs (+630 lines)
- lower_loop_break_pattern(): Break as Jump (early return)
- lower_loop_continue_pattern(): Continue as Select + Call
- Added Bool literal support in extract_value()
- Created 2 fixtures: loop_frontend_{break,continue}.program.json
- Added 2 A/B tests (all 6 Phase 34 tests PASS)
Technical achievements:
- Break = Jump (early return pattern)
- Continue = Select + Call (NOT Jump) - critical discovery
- 3-function structure sufficient (no k_continue needed)
- SSA-style re-assignment with natural var_map updates
Test results:
✅ joinir_frontend_if_select_simple_ab_test
✅ joinir_frontend_if_select_local_ab_test
✅ joinir_frontend_json_shape_read_value_ab_test
✅ joinir_frontend_loop_simple_ab_test
✅ joinir_frontend_loop_break_ab_test
✅ joinir_frontend_loop_continue_ab_test
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@ -197,6 +197,83 @@
|
||||
|
||||
---
|
||||
|
||||
### 1-00f. Phase 34-7 — tiny while ループの AST→JoinIR 対応(**完了** 2025-11-27)
|
||||
|
||||
**目的**
|
||||
- AST(Program JSON v0)の Loop ノードを JoinIR に lowering する frontend 実装
|
||||
- Phase 31 の LoopToJoinLowerer(MIR→JoinIR)と同型の JoinIR 生成を達成
|
||||
- AST→JoinIR→MIR→VM の完全な経路を実証
|
||||
|
||||
**実装内容**
|
||||
1. `ast_lowerer.rs` に `lower_loop_case_a_simple()` 実装(3関数構造: entry/loop_step/k_exit)
|
||||
- entry: 初期化(i=0, acc=0)→ Call(loop_step)
|
||||
- loop_step: Jump(k_exit, cond=!(i<n)) で早期 return → body 処理 → Call(loop_step) で末尾再帰
|
||||
- k_exit: Ret acc
|
||||
2. `extract_value()` に Binary/Compare 対応追加(算術演算・比較演算)
|
||||
3. Local ノード処理実装(SSA スタイルの再代入)
|
||||
|
||||
**技術的発見**
|
||||
- **Jump vs Call セマンティクス**: Jump = 早期 return(条件付き関数脱出)、Call = 末尾再帰(関数呼び出し)
|
||||
- **k_next パラメータ**: Phase 31 でも常に `None`(JoinIR→MIR bridge が未対応)
|
||||
- **SSA スタイル再代入**: Program(JSON v0) の同名 Local 宣言は新 ValueId を生成(var_map 自然更新)
|
||||
|
||||
**テスト結果**
|
||||
- ✅ n=0 → 0, n=3 → 3, n=5 → 5 すべて PASS
|
||||
- ✅ `joinir_frontend_loop_simple_ab_test` 実行成功(`run_joinir_via_vm` 経由)
|
||||
|
||||
**更新ドキュメント**
|
||||
- `src/mir/join_ir/frontend/ast_lowerer.rs` (lower_loop_case_a_simple 実装, 246行)
|
||||
- `src/tests/joinir_frontend_if_select.rs` (test 追加)
|
||||
- `docs/private/roadmap2/phases/phase-34-joinir-frontend/TASKS.md` (Phase 34-7 完了記録)
|
||||
|
||||
**次のステップ**
|
||||
- Phase 34-8: Break/Continue 付きループへの拡張
|
||||
- Phase 34 完了後: AST→JoinIR frontend を標準経路に統合
|
||||
|
||||
---
|
||||
|
||||
### 1-00g. Phase 34-8 — Break/Continue 付きループの AST→JoinIR 対応(**完了** 2025-11-27)
|
||||
|
||||
**目的**
|
||||
- Break/Continue を含む tiny while loop を AST→JoinIR に lowering
|
||||
- 制御フロー構造を完全に JoinIR で表現
|
||||
- Phase 31 の LoopToJoinLowerer と同型の JoinIR 生成
|
||||
|
||||
**実装内容**
|
||||
1. `ast_lowerer.rs` に Break/Continue pattern の lowering 実装
|
||||
- Break: Jump(k_exit, cond=i>=n) で早期 return
|
||||
- Continue: Select(条件付き値更新)+ Call(末尾再帰)
|
||||
- 3関数構造(entry/loop_step/k_exit)を維持
|
||||
2. JSON v0 フィクスチャ作成(break/continue 2パターン)
|
||||
3. テスト追加(JoinIrFrontendTestRunner 使用)
|
||||
|
||||
**技術的発見**
|
||||
- **Continue に k_continue 継続は不要**(3関数構造で十分)
|
||||
- Continue = 「値更新をスキップして末尾再帰」
|
||||
- Select 命令で条件付き値更新を表現
|
||||
- Break = Phase 34-7 の早期 return と完全同型
|
||||
- Phase 31 (MIR→JoinIR) と Phase 34 (AST→JoinIR) が同じ JoinIR を生成
|
||||
|
||||
**テスト結果**
|
||||
- ✅ Break: n=5 → acc=10, n=3 → acc=3 PASS
|
||||
- ✅ Continue: n=5 → acc=12, n=2 → acc=3 PASS
|
||||
- ✅ 全6テスト(Phase 34-2/3/6/7/8)PASS
|
||||
|
||||
**更新ドキュメント**
|
||||
- `src/mir/join_ir/frontend/ast_lowerer.rs` (Break/Continue lowering 追加, 約200行)
|
||||
- `src/tests/joinir_frontend_if_select.rs` (2テスト追加)
|
||||
- `docs/private/roadmap2/phases/phase-34-joinir-frontend/README.md` (Phase 34-8 追加)
|
||||
- `docs/private/roadmap2/phases/phase-34-joinir-frontend/TASKS.md` (Phase 34-8 完了)
|
||||
|
||||
**次のステップ**
|
||||
- Phase 34-9: ネストループ・複雑な式への拡張
|
||||
- Phase 35: PHI 箱削減(JoinIR Frontend が主経路化したら実施)
|
||||
|
||||
**マイルストーン達成**
|
||||
If/Loop/Break/Continue の最小パターンが全て AST→JoinIR→MIR→VM で説明可能になり、Phase 35 での PHI 箱削減の布石が完成。
|
||||
|
||||
---
|
||||
|
||||
### 1-00f. Phase 34-1 — JoinIR Frontend (AST→JoinIR) 設計フェーズ(**完了** 2025-11-27)
|
||||
|
||||
**Phase 33 までの成果(簡潔要約)**
|
||||
|
||||
Submodule docs/private updated: 397ff8fd71...360426bf8f
@ -100,11 +100,15 @@ impl AstToJoinIrLowerer {
|
||||
.as_str()
|
||||
.expect("Function must have 'name'");
|
||||
|
||||
// 3. 関数名で分岐(Phase 34-2/34-3/34-4/34-5)
|
||||
// simple/local/_read_value_from_pair すべて同じ If Return pattern なので共通処理
|
||||
// Phase 34-5: extract_value で Int/Var/Method に統一対応
|
||||
// 3. 関数名で分岐(Phase 34-2/34-3/34-4/34-5/34-7/34-8)
|
||||
// test/local/_read_value_from_pair: If Return pattern
|
||||
// simple: Loop pattern (Phase 34-7/34-8)
|
||||
match func_name {
|
||||
"test" | "local" | "_read_value_from_pair" => self.lower_if_return_pattern(program_json),
|
||||
"simple" => {
|
||||
// Phase 34-8: Loop パターンの詳細分析(break/continue 検出)
|
||||
self.lower_loop_with_break_continue(program_json)
|
||||
},
|
||||
_ => panic!("Unsupported function: {}", func_name),
|
||||
}
|
||||
}
|
||||
@ -242,6 +246,774 @@ impl AstToJoinIrLowerer {
|
||||
}
|
||||
}
|
||||
|
||||
/// Phase 34-8: Break/Continue 付きループの lowering(パターン検出)
|
||||
///
|
||||
/// Loop body を解析して Break/Continue を検出し、適切な lowering 関数にディスパッチ
|
||||
fn lower_loop_with_break_continue(&mut self, program_json: &serde_json::Value) -> JoinModule {
|
||||
// 1. Program(JSON) から defs を取得
|
||||
let defs = program_json["defs"]
|
||||
.as_array()
|
||||
.expect("Program(JSON v0) must have 'defs' array");
|
||||
|
||||
let func_def = defs
|
||||
.get(0)
|
||||
.expect("At least one function definition required");
|
||||
|
||||
// 2. body を解析: Local 初期化 + Loop + Return
|
||||
let body = &func_def["body"]["body"];
|
||||
let stmts = body
|
||||
.as_array()
|
||||
.expect("Function body must be array");
|
||||
|
||||
// Loop ノードを探す
|
||||
let loop_node = stmts.iter().find(|stmt| {
|
||||
stmt["type"].as_str() == Some("Loop")
|
||||
}).expect("Loop node not found");
|
||||
|
||||
let loop_body = loop_node["body"]
|
||||
.as_array()
|
||||
.expect("Loop must have 'body' array");
|
||||
|
||||
// Break/Continue を検出
|
||||
let has_break = Self::has_break_in_loop_body(loop_body);
|
||||
let has_continue = Self::has_continue_in_loop_body(loop_body);
|
||||
|
||||
// パターンに応じてディスパッチ
|
||||
if has_break && !has_continue {
|
||||
self.lower_loop_break_pattern(program_json)
|
||||
} else if has_continue && !has_break {
|
||||
self.lower_loop_continue_pattern(program_json)
|
||||
} else if has_break && has_continue {
|
||||
panic!("Mixed Break/Continue pattern not yet supported in Phase 34-8");
|
||||
} else {
|
||||
// Phase 34-7 の simple pattern
|
||||
self.lower_loop_case_a_simple(program_json)
|
||||
}
|
||||
}
|
||||
|
||||
/// Loop body に Break があるかチェック
|
||||
fn has_break_in_loop_body(loop_body: &[serde_json::Value]) -> bool {
|
||||
loop_body.iter().any(|stmt| {
|
||||
if stmt["type"].as_str() == Some("If") {
|
||||
if let Some(then_body) = stmt["then"].as_array() {
|
||||
then_body.iter().any(|s| s["type"].as_str() == Some("Break"))
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Loop body に Continue があるかチェック
|
||||
fn has_continue_in_loop_body(loop_body: &[serde_json::Value]) -> bool {
|
||||
loop_body.iter().any(|stmt| {
|
||||
if stmt["type"].as_str() == Some("If") {
|
||||
if let Some(then_body) = stmt["then"].as_array() {
|
||||
then_body.iter().any(|s| s["type"].as_str() == Some("Continue"))
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Phase 34-7: Loop pattern の lowering(Case-A: tiny while loop)
|
||||
///
|
||||
/// 対象: `LoopFrontendTest.simple(n)` 相当
|
||||
/// - local i = 0; local acc = 0;
|
||||
/// - loop(i < n) { acc = acc + 1; i = i + 1; }
|
||||
/// - return acc
|
||||
///
|
||||
/// 目標 JoinIR: Phase 31 の LoopToJoinLowerer と同型
|
||||
/// - entry: 初期化 → loop_step 呼び出し
|
||||
/// - loop_step: 条件チェック → 再帰 or k_exit
|
||||
/// - k_exit: 結果を return
|
||||
///
|
||||
/// Phase 34-7.4b: Local ノード処理(同名再宣言 = 再代入)
|
||||
fn lower_loop_case_a_simple(&mut self, program_json: &serde_json::Value) -> JoinModule {
|
||||
// 1. Program(JSON) から defs を取得
|
||||
let defs = program_json["defs"]
|
||||
.as_array()
|
||||
.expect("Program(JSON v0) must have 'defs' array");
|
||||
|
||||
let func_def = defs
|
||||
.get(0)
|
||||
.expect("At least one function definition required");
|
||||
|
||||
let func_name = func_def["name"]
|
||||
.as_str()
|
||||
.expect("Function must have 'name'");
|
||||
|
||||
let params = func_def["params"]
|
||||
.as_array()
|
||||
.expect("Function must have 'params' array");
|
||||
|
||||
// 2. ExtractCtx 作成とパラメータ登録
|
||||
let mut ctx = ExtractCtx::new(params.len() as u32);
|
||||
for (i, param) in params.iter().enumerate() {
|
||||
let param_name = param
|
||||
.as_str()
|
||||
.expect("Parameter must be string")
|
||||
.to_string();
|
||||
ctx.register_param(param_name, crate::mir::ValueId(i as u32));
|
||||
}
|
||||
|
||||
// 3. body を解析: Local 初期化 + Loop + Return
|
||||
let body = &func_def["body"]["body"];
|
||||
let stmts = body
|
||||
.as_array()
|
||||
.expect("Function body must be array");
|
||||
|
||||
// Phase 34-7.4b: Local ノード処理(初期化)
|
||||
// stmts[0]: Local i = 0
|
||||
// stmts[1]: Local acc = 0
|
||||
// stmts[2]: Loop { cond, body }
|
||||
// stmts[3]: Return acc
|
||||
|
||||
let mut init_insts = Vec::new();
|
||||
let mut loop_node_idx = None;
|
||||
|
||||
for (idx, stmt) in stmts.iter().enumerate() {
|
||||
let stmt_type = stmt["type"].as_str().expect("Statement must have type");
|
||||
|
||||
match stmt_type {
|
||||
"Local" => {
|
||||
// Phase 34-7.4b: Local ノード処理
|
||||
let var_name = stmt["name"]
|
||||
.as_str()
|
||||
.expect("Local must have 'name'")
|
||||
.to_string();
|
||||
let expr = &stmt["expr"];
|
||||
|
||||
// extract_value で式を評価
|
||||
let (var_id, insts) = self.extract_value(expr, &mut ctx);
|
||||
init_insts.extend(insts);
|
||||
|
||||
// 同名再宣言 = var_map を更新(再代入の意味論)
|
||||
ctx.register_param(var_name, var_id);
|
||||
}
|
||||
"Loop" => {
|
||||
loop_node_idx = Some(idx);
|
||||
break; // Loop 以降は別処理
|
||||
}
|
||||
_ => panic!("Unexpected statement type before Loop: {}", stmt_type),
|
||||
}
|
||||
}
|
||||
|
||||
let loop_node_idx = loop_node_idx.expect("Loop node not found");
|
||||
let loop_node = &stmts[loop_node_idx];
|
||||
|
||||
// 4. Loop の cond と body を抽出
|
||||
let loop_cond_expr = &loop_node["cond"];
|
||||
let loop_body_stmts = loop_node["body"]
|
||||
.as_array()
|
||||
.expect("Loop must have 'body' array");
|
||||
|
||||
// 5. Return 文を抽出(Loop の次)
|
||||
let return_stmt = &stmts[loop_node_idx + 1];
|
||||
assert_eq!(
|
||||
return_stmt["type"].as_str(),
|
||||
Some("Return"),
|
||||
"Expected Return after Loop"
|
||||
);
|
||||
|
||||
// 6. JoinIR 生成: entry / loop_step / k_exit(Phase 31 と同じパターン)
|
||||
//
|
||||
// Phase 34-7: Jump = 早期 return、Call = 末尾再帰
|
||||
// - entry: 初期化 → Call(loop_step)
|
||||
// - loop_step:
|
||||
// Jump(k_exit, cond=exit_cond) // 条件が true なら抜ける
|
||||
// body 処理
|
||||
// Call(loop_step) 末尾再帰
|
||||
// - k_exit: 結果を返す
|
||||
|
||||
let entry_id = self.next_func_id();
|
||||
let loop_step_id = self.next_func_id();
|
||||
let k_exit_id = self.next_func_id();
|
||||
|
||||
// entry 関数: 初期化命令 + Call(loop_step)
|
||||
let i_init = ctx.get_var("i").expect("i must be initialized");
|
||||
let acc_init = ctx.get_var("acc").expect("acc must be initialized");
|
||||
let n_param = ctx.get_var("n").expect("n must be parameter");
|
||||
|
||||
let loop_result = ctx.alloc_var();
|
||||
|
||||
let mut entry_body = init_insts;
|
||||
entry_body.push(JoinInst::Call {
|
||||
func: loop_step_id,
|
||||
args: vec![i_init, acc_init, n_param],
|
||||
k_next: None,
|
||||
dst: Some(loop_result),
|
||||
});
|
||||
entry_body.push(JoinInst::Ret {
|
||||
value: Some(loop_result),
|
||||
});
|
||||
|
||||
let entry_func = JoinFunction {
|
||||
id: entry_id,
|
||||
name: func_name.to_string(),
|
||||
params: (0..params.len())
|
||||
.map(|i| crate::mir::ValueId(i as u32))
|
||||
.collect(),
|
||||
body: entry_body,
|
||||
exit_cont: None,
|
||||
};
|
||||
|
||||
// loop_step 関数: (i, acc, n) → Jump(k_exit, cond=!(i<n)) → body → Call(loop_step)
|
||||
let step_i = crate::mir::ValueId(0);
|
||||
let step_acc = crate::mir::ValueId(1);
|
||||
let step_n = crate::mir::ValueId(2);
|
||||
|
||||
let mut step_ctx = ExtractCtx::new(3);
|
||||
step_ctx.register_param("i".to_string(), step_i);
|
||||
step_ctx.register_param("acc".to_string(), step_acc);
|
||||
step_ctx.register_param("n".to_string(), step_n);
|
||||
|
||||
// 条件式を評価(i < n)
|
||||
let (cond_var, cond_insts) = self.extract_value(loop_cond_expr, &mut step_ctx);
|
||||
|
||||
// !cond を計算(i >= n なら抜ける)
|
||||
let false_const = step_ctx.alloc_var();
|
||||
let exit_cond = step_ctx.alloc_var();
|
||||
|
||||
let mut loop_step_body = cond_insts;
|
||||
loop_step_body.push(JoinInst::Compute(crate::mir::join_ir::MirLikeInst::Const {
|
||||
dst: false_const,
|
||||
value: ConstValue::Bool(false),
|
||||
}));
|
||||
loop_step_body.push(JoinInst::Compute(crate::mir::join_ir::MirLikeInst::Compare {
|
||||
dst: exit_cond,
|
||||
op: crate::mir::join_ir::CompareOp::Eq,
|
||||
lhs: cond_var,
|
||||
rhs: false_const,
|
||||
}));
|
||||
|
||||
// 早期 return: exit_cond が true(i >= n)なら k_exit へ Jump
|
||||
loop_step_body.push(JoinInst::Jump {
|
||||
cont: k_exit_id.as_cont(), // Phase 34-7.5: 型変換ヘルパー使用
|
||||
args: vec![step_acc],
|
||||
cond: Some(exit_cond),
|
||||
});
|
||||
|
||||
// loop body を処理(Jump で抜けなかった場合のみ実行される)
|
||||
for body_stmt in loop_body_stmts {
|
||||
assert_eq!(
|
||||
body_stmt["type"].as_str(),
|
||||
Some("Local"),
|
||||
"Loop body must contain only Local statements"
|
||||
);
|
||||
|
||||
let var_name = body_stmt["name"]
|
||||
.as_str()
|
||||
.expect("Local must have 'name'")
|
||||
.to_string();
|
||||
let expr = &body_stmt["expr"];
|
||||
|
||||
let (var_id, insts) = self.extract_value(expr, &mut step_ctx);
|
||||
loop_step_body.extend(insts);
|
||||
step_ctx.register_param(var_name, var_id);
|
||||
}
|
||||
|
||||
// body 処理後の i_next, acc_next を取得
|
||||
let i_next = step_ctx.get_var("i").expect("i must be updated in loop body");
|
||||
let acc_next = step_ctx.get_var("acc").expect("acc must be updated in loop body");
|
||||
|
||||
// loop_step を再帰的に Call(末尾再帰)
|
||||
let recurse_result = step_ctx.alloc_var();
|
||||
loop_step_body.push(JoinInst::Call {
|
||||
func: loop_step_id,
|
||||
args: vec![i_next, acc_next, step_n],
|
||||
k_next: None,
|
||||
dst: Some(recurse_result),
|
||||
});
|
||||
|
||||
loop_step_body.push(JoinInst::Ret {
|
||||
value: Some(recurse_result),
|
||||
});
|
||||
|
||||
let loop_step_func = JoinFunction {
|
||||
id: loop_step_id,
|
||||
name: format!("{}_loop_step", func_name),
|
||||
params: vec![step_i, step_acc, step_n],
|
||||
body: loop_step_body,
|
||||
exit_cont: None,
|
||||
};
|
||||
|
||||
// k_exit 関数: (acc) → Ret acc
|
||||
let k_exit_acc = crate::mir::ValueId(0);
|
||||
|
||||
let k_exit_func = JoinFunction {
|
||||
id: k_exit_id,
|
||||
name: format!("{}_k_exit", func_name),
|
||||
params: vec![k_exit_acc],
|
||||
body: vec![JoinInst::Ret {
|
||||
value: Some(k_exit_acc),
|
||||
}],
|
||||
exit_cont: None,
|
||||
};
|
||||
|
||||
// JoinModule を構築
|
||||
let mut functions = BTreeMap::new();
|
||||
functions.insert(entry_id, entry_func);
|
||||
functions.insert(loop_step_id, loop_step_func);
|
||||
functions.insert(k_exit_id, k_exit_func);
|
||||
|
||||
JoinModule {
|
||||
functions,
|
||||
entry: Some(entry_id),
|
||||
}
|
||||
}
|
||||
|
||||
/// Phase 34-8: Break pattern の lowering
|
||||
///
|
||||
/// パターン: `loop { if i >= n { break }; acc = acc + i; i = i + 1 }`
|
||||
/// 目標: Jump(k_exit, cond=i>=n) で早期 return を実現
|
||||
fn lower_loop_break_pattern(&mut self, program_json: &serde_json::Value) -> JoinModule {
|
||||
// 1. Program(JSON) から基本情報を取得(Phase 34-7 と同じ)
|
||||
let defs = program_json["defs"]
|
||||
.as_array()
|
||||
.expect("Program(JSON v0) must have 'defs' array");
|
||||
|
||||
let func_def = defs
|
||||
.get(0)
|
||||
.expect("At least one function definition required");
|
||||
|
||||
let func_name = func_def["name"]
|
||||
.as_str()
|
||||
.expect("Function must have 'name'");
|
||||
|
||||
let params = func_def["params"]
|
||||
.as_array()
|
||||
.expect("Function must have 'params' array");
|
||||
|
||||
// 2. ExtractCtx 作成とパラメータ登録
|
||||
let mut ctx = ExtractCtx::new(params.len() as u32);
|
||||
for (i, param) in params.iter().enumerate() {
|
||||
let param_name = param
|
||||
.as_str()
|
||||
.expect("Parameter must be string")
|
||||
.to_string();
|
||||
ctx.register_param(param_name, crate::mir::ValueId(i as u32));
|
||||
}
|
||||
|
||||
// 3. body を解析: Local 初期化 + Loop + Return
|
||||
let body = &func_def["body"]["body"];
|
||||
let stmts = body
|
||||
.as_array()
|
||||
.expect("Function body must be array");
|
||||
|
||||
// Local ノード処理(初期化)
|
||||
let mut init_insts = Vec::new();
|
||||
let mut loop_node_idx = None;
|
||||
|
||||
for (idx, stmt) in stmts.iter().enumerate() {
|
||||
let stmt_type = stmt["type"].as_str().expect("Statement must have type");
|
||||
|
||||
match stmt_type {
|
||||
"Local" => {
|
||||
let var_name = stmt["name"]
|
||||
.as_str()
|
||||
.expect("Local must have 'name'")
|
||||
.to_string();
|
||||
let expr = &stmt["expr"];
|
||||
|
||||
let (var_id, insts) = self.extract_value(expr, &mut ctx);
|
||||
init_insts.extend(insts);
|
||||
ctx.register_param(var_name, var_id);
|
||||
}
|
||||
"Loop" => {
|
||||
loop_node_idx = Some(idx);
|
||||
break;
|
||||
}
|
||||
_ => panic!("Unexpected statement type before Loop: {}", stmt_type),
|
||||
}
|
||||
}
|
||||
|
||||
let loop_node_idx = loop_node_idx.expect("Loop node not found");
|
||||
let loop_node = &stmts[loop_node_idx];
|
||||
|
||||
// 4. Loop body から Break If を探す
|
||||
let loop_body = loop_node["body"]
|
||||
.as_array()
|
||||
.expect("Loop must have 'body' array");
|
||||
|
||||
let break_if_stmt = loop_body.iter().find(|stmt| {
|
||||
stmt["type"].as_str() == Some("If") &&
|
||||
stmt["then"].as_array().map_or(false, |then| {
|
||||
then.iter().any(|s| s["type"].as_str() == Some("Break"))
|
||||
})
|
||||
}).expect("Break pattern must have If + Break");
|
||||
|
||||
let break_cond_expr = &break_if_stmt["cond"];
|
||||
|
||||
// 5. JoinIR 生成: entry / loop_step / k_exit(3関数構造)
|
||||
let entry_id = self.next_func_id();
|
||||
let loop_step_id = self.next_func_id();
|
||||
let k_exit_id = self.next_func_id();
|
||||
|
||||
// entry 関数
|
||||
let i_init = ctx.get_var("i").expect("i must be initialized");
|
||||
let acc_init = ctx.get_var("acc").expect("acc must be initialized");
|
||||
let n_param = ctx.get_var("n").expect("n must be parameter");
|
||||
|
||||
let loop_result = ctx.alloc_var();
|
||||
|
||||
let mut entry_body = init_insts;
|
||||
entry_body.push(JoinInst::Call {
|
||||
func: loop_step_id,
|
||||
args: vec![i_init, acc_init, n_param],
|
||||
k_next: None,
|
||||
dst: Some(loop_result),
|
||||
});
|
||||
entry_body.push(JoinInst::Ret {
|
||||
value: Some(loop_result),
|
||||
});
|
||||
|
||||
let entry_func = JoinFunction {
|
||||
id: entry_id,
|
||||
name: func_name.to_string(),
|
||||
params: (0..params.len())
|
||||
.map(|i| crate::mir::ValueId(i as u32))
|
||||
.collect(),
|
||||
body: entry_body,
|
||||
exit_cont: None,
|
||||
};
|
||||
|
||||
// loop_step 関数: (i, acc, n) → Jump(k_exit, cond=break_cond) → body → Call(loop_step)
|
||||
let step_i = crate::mir::ValueId(0);
|
||||
let step_acc = crate::mir::ValueId(1);
|
||||
let step_n = crate::mir::ValueId(2);
|
||||
|
||||
let mut step_ctx = ExtractCtx::new(3);
|
||||
step_ctx.register_param("i".to_string(), step_i);
|
||||
step_ctx.register_param("acc".to_string(), step_acc);
|
||||
step_ctx.register_param("n".to_string(), step_n);
|
||||
|
||||
// Break 条件を評価
|
||||
let (break_cond_var, break_cond_insts) = self.extract_value(break_cond_expr, &mut step_ctx);
|
||||
|
||||
let mut loop_step_body = break_cond_insts;
|
||||
|
||||
// 早期 return: break_cond が true なら k_exit へ Jump
|
||||
loop_step_body.push(JoinInst::Jump {
|
||||
cont: k_exit_id.as_cont(),
|
||||
args: vec![step_acc],
|
||||
cond: Some(break_cond_var),
|
||||
});
|
||||
|
||||
// loop body を処理(Break の後の Local 命令群)
|
||||
for body_stmt in loop_body {
|
||||
// If + Break はスキップ(Jump で処理済み)
|
||||
if body_stmt["type"].as_str() == Some("If") {
|
||||
continue;
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
body_stmt["type"].as_str(),
|
||||
Some("Local"),
|
||||
"Loop body must contain only Local statements after If"
|
||||
);
|
||||
|
||||
let var_name = body_stmt["name"]
|
||||
.as_str()
|
||||
.expect("Local must have 'name'")
|
||||
.to_string();
|
||||
let expr = &body_stmt["expr"];
|
||||
|
||||
let (var_id, insts) = self.extract_value(expr, &mut step_ctx);
|
||||
loop_step_body.extend(insts);
|
||||
step_ctx.register_param(var_name, var_id);
|
||||
}
|
||||
|
||||
// body 処理後の i_next, acc_next を取得
|
||||
let i_next = step_ctx.get_var("i").expect("i must be updated in loop body");
|
||||
let acc_next = step_ctx.get_var("acc").expect("acc must be updated in loop body");
|
||||
|
||||
// loop_step を再帰的に Call(末尾再帰)
|
||||
let recurse_result = step_ctx.alloc_var();
|
||||
loop_step_body.push(JoinInst::Call {
|
||||
func: loop_step_id,
|
||||
args: vec![i_next, acc_next, step_n],
|
||||
k_next: None,
|
||||
dst: Some(recurse_result),
|
||||
});
|
||||
|
||||
loop_step_body.push(JoinInst::Ret {
|
||||
value: Some(recurse_result),
|
||||
});
|
||||
|
||||
let loop_step_func = JoinFunction {
|
||||
id: loop_step_id,
|
||||
name: format!("{}_loop_step", func_name),
|
||||
params: vec![step_i, step_acc, step_n],
|
||||
body: loop_step_body,
|
||||
exit_cont: None,
|
||||
};
|
||||
|
||||
// k_exit 関数
|
||||
let k_exit_acc = crate::mir::ValueId(0);
|
||||
|
||||
let k_exit_func = JoinFunction {
|
||||
id: k_exit_id,
|
||||
name: format!("{}_k_exit", func_name),
|
||||
params: vec![k_exit_acc],
|
||||
body: vec![JoinInst::Ret {
|
||||
value: Some(k_exit_acc),
|
||||
}],
|
||||
exit_cont: None,
|
||||
};
|
||||
|
||||
// JoinModule を構築
|
||||
let mut functions = BTreeMap::new();
|
||||
functions.insert(entry_id, entry_func);
|
||||
functions.insert(loop_step_id, loop_step_func);
|
||||
functions.insert(k_exit_id, k_exit_func);
|
||||
|
||||
JoinModule {
|
||||
functions,
|
||||
entry: Some(entry_id),
|
||||
}
|
||||
}
|
||||
|
||||
/// Phase 34-8: Continue pattern の lowering
|
||||
///
|
||||
/// パターン: `loop { i = i + 1; if i == 3 { continue }; acc = acc + i }`
|
||||
/// 目標: Select(条件付き値更新)+ Call(末尾再帰)で実現
|
||||
fn lower_loop_continue_pattern(&mut self, program_json: &serde_json::Value) -> JoinModule {
|
||||
// 1. Program(JSON) から基本情報を取得
|
||||
let defs = program_json["defs"]
|
||||
.as_array()
|
||||
.expect("Program(JSON v0) must have 'defs' array");
|
||||
|
||||
let func_def = defs
|
||||
.get(0)
|
||||
.expect("At least one function definition required");
|
||||
|
||||
let func_name = func_def["name"]
|
||||
.as_str()
|
||||
.expect("Function must have 'name'");
|
||||
|
||||
let params = func_def["params"]
|
||||
.as_array()
|
||||
.expect("Function must have 'params' array");
|
||||
|
||||
// 2. ExtractCtx 作成とパラメータ登録
|
||||
let mut ctx = ExtractCtx::new(params.len() as u32);
|
||||
for (i, param) in params.iter().enumerate() {
|
||||
let param_name = param
|
||||
.as_str()
|
||||
.expect("Parameter must be string")
|
||||
.to_string();
|
||||
ctx.register_param(param_name, crate::mir::ValueId(i as u32));
|
||||
}
|
||||
|
||||
// 3. body を解析: Local 初期化 + Loop + Return
|
||||
let body = &func_def["body"]["body"];
|
||||
let stmts = body
|
||||
.as_array()
|
||||
.expect("Function body must be array");
|
||||
|
||||
// Local ノード処理(初期化)
|
||||
let mut init_insts = Vec::new();
|
||||
let mut loop_node_idx = None;
|
||||
|
||||
for (idx, stmt) in stmts.iter().enumerate() {
|
||||
let stmt_type = stmt["type"].as_str().expect("Statement must have type");
|
||||
|
||||
match stmt_type {
|
||||
"Local" => {
|
||||
let var_name = stmt["name"]
|
||||
.as_str()
|
||||
.expect("Local must have 'name'")
|
||||
.to_string();
|
||||
let expr = &stmt["expr"];
|
||||
|
||||
let (var_id, insts) = self.extract_value(expr, &mut ctx);
|
||||
init_insts.extend(insts);
|
||||
ctx.register_param(var_name, var_id);
|
||||
}
|
||||
"Loop" => {
|
||||
loop_node_idx = Some(idx);
|
||||
break;
|
||||
}
|
||||
_ => panic!("Unexpected statement type before Loop: {}", stmt_type),
|
||||
}
|
||||
}
|
||||
|
||||
let loop_node_idx = loop_node_idx.expect("Loop node not found");
|
||||
let loop_node = &stmts[loop_node_idx];
|
||||
|
||||
// 4. Loop の cond を取得
|
||||
let loop_cond_expr = &loop_node["cond"];
|
||||
|
||||
// 5. Loop body から Continue If を探す
|
||||
let loop_body = loop_node["body"]
|
||||
.as_array()
|
||||
.expect("Loop must have 'body' array");
|
||||
|
||||
let continue_if_stmt = loop_body.iter().find(|stmt| {
|
||||
stmt["type"].as_str() == Some("If") &&
|
||||
stmt["then"].as_array().map_or(false, |then| {
|
||||
then.iter().any(|s| s["type"].as_str() == Some("Continue"))
|
||||
})
|
||||
}).expect("Continue pattern must have If + Continue");
|
||||
|
||||
let continue_cond_expr = &continue_if_stmt["cond"];
|
||||
|
||||
// 6. JoinIR 生成: entry / loop_step / k_exit(3関数構造)
|
||||
let entry_id = self.next_func_id();
|
||||
let loop_step_id = self.next_func_id();
|
||||
let k_exit_id = self.next_func_id();
|
||||
|
||||
// entry 関数
|
||||
let i_init = ctx.get_var("i").expect("i must be initialized");
|
||||
let acc_init = ctx.get_var("acc").expect("acc must be initialized");
|
||||
let n_param = ctx.get_var("n").expect("n must be parameter");
|
||||
|
||||
let loop_result = ctx.alloc_var();
|
||||
|
||||
let mut entry_body = init_insts;
|
||||
entry_body.push(JoinInst::Call {
|
||||
func: loop_step_id,
|
||||
args: vec![i_init, acc_init, n_param],
|
||||
k_next: None,
|
||||
dst: Some(loop_result),
|
||||
});
|
||||
entry_body.push(JoinInst::Ret {
|
||||
value: Some(loop_result),
|
||||
});
|
||||
|
||||
let entry_func = JoinFunction {
|
||||
id: entry_id,
|
||||
name: func_name.to_string(),
|
||||
params: (0..params.len())
|
||||
.map(|i| crate::mir::ValueId(i as u32))
|
||||
.collect(),
|
||||
body: entry_body,
|
||||
exit_cont: None,
|
||||
};
|
||||
|
||||
// loop_step 関数: (i, acc, n) → exit check → i++ → Select → Call(loop_step)
|
||||
let step_i = crate::mir::ValueId(0);
|
||||
let step_acc = crate::mir::ValueId(1);
|
||||
let step_n = crate::mir::ValueId(2);
|
||||
|
||||
let mut step_ctx = ExtractCtx::new(3);
|
||||
step_ctx.register_param("i".to_string(), step_i);
|
||||
step_ctx.register_param("acc".to_string(), step_acc);
|
||||
step_ctx.register_param("n".to_string(), step_n);
|
||||
|
||||
let mut loop_step_body = Vec::new();
|
||||
|
||||
// 1. exit 条件チェック(!(i < n) = i >= n で抜ける)
|
||||
let (cond_var, cond_insts) = self.extract_value(loop_cond_expr, &mut step_ctx);
|
||||
loop_step_body.extend(cond_insts);
|
||||
|
||||
let false_const = step_ctx.alloc_var();
|
||||
let exit_cond = step_ctx.alloc_var();
|
||||
|
||||
loop_step_body.push(JoinInst::Compute(crate::mir::join_ir::MirLikeInst::Const {
|
||||
dst: false_const,
|
||||
value: ConstValue::Bool(false),
|
||||
}));
|
||||
loop_step_body.push(JoinInst::Compute(crate::mir::join_ir::MirLikeInst::Compare {
|
||||
dst: exit_cond,
|
||||
op: crate::mir::join_ir::CompareOp::Eq,
|
||||
lhs: cond_var,
|
||||
rhs: false_const,
|
||||
}));
|
||||
|
||||
loop_step_body.push(JoinInst::Jump {
|
||||
cont: k_exit_id.as_cont(),
|
||||
args: vec![step_acc],
|
||||
cond: Some(exit_cond),
|
||||
});
|
||||
|
||||
// 2. Continue pattern 特有の処理: i のインクリメントが先
|
||||
// Loop body の最初の Local(i) を処理
|
||||
let first_local = loop_body.iter().find(|stmt| {
|
||||
stmt["type"].as_str() == Some("Local") &&
|
||||
stmt["name"].as_str() == Some("i")
|
||||
}).expect("Continue pattern must have i increment as first Local");
|
||||
|
||||
let i_expr = &first_local["expr"];
|
||||
let (i_next, i_insts) = self.extract_value(i_expr, &mut step_ctx);
|
||||
loop_step_body.extend(i_insts);
|
||||
step_ctx.register_param("i".to_string(), i_next);
|
||||
|
||||
// 3. Continue 条件を評価
|
||||
let (continue_cond_var, continue_cond_insts) = self.extract_value(continue_cond_expr, &mut step_ctx);
|
||||
loop_step_body.extend(continue_cond_insts);
|
||||
|
||||
// 4. acc の更新値を計算(If の後の Local(acc) から)
|
||||
let acc_update_local = loop_body.iter().find(|stmt| {
|
||||
stmt["type"].as_str() == Some("Local") &&
|
||||
stmt["name"].as_str() == Some("acc")
|
||||
}).expect("Continue pattern must have acc update Local");
|
||||
|
||||
let acc_expr = &acc_update_local["expr"];
|
||||
let (acc_increment, acc_insts) = self.extract_value(acc_expr, &mut step_ctx);
|
||||
loop_step_body.extend(acc_insts);
|
||||
|
||||
// 5. Select: Continue なら acc そのまま、そうでなければ acc の更新値
|
||||
let acc_next = step_ctx.alloc_var();
|
||||
loop_step_body.push(JoinInst::Select {
|
||||
dst: acc_next,
|
||||
cond: continue_cond_var,
|
||||
then_val: step_acc, // Continue: 更新しない
|
||||
else_val: acc_increment, // 通常: 更新
|
||||
});
|
||||
|
||||
// 6. 末尾再帰
|
||||
let recurse_result = step_ctx.alloc_var();
|
||||
loop_step_body.push(JoinInst::Call {
|
||||
func: loop_step_id,
|
||||
args: vec![i_next, acc_next, step_n],
|
||||
k_next: None,
|
||||
dst: Some(recurse_result),
|
||||
});
|
||||
|
||||
loop_step_body.push(JoinInst::Ret {
|
||||
value: Some(recurse_result),
|
||||
});
|
||||
|
||||
let loop_step_func = JoinFunction {
|
||||
id: loop_step_id,
|
||||
name: format!("{}_loop_step", func_name),
|
||||
params: vec![step_i, step_acc, step_n],
|
||||
body: loop_step_body,
|
||||
exit_cont: None,
|
||||
};
|
||||
|
||||
// k_exit 関数
|
||||
let k_exit_acc = crate::mir::ValueId(0);
|
||||
|
||||
let k_exit_func = JoinFunction {
|
||||
id: k_exit_id,
|
||||
name: format!("{}_k_exit", func_name),
|
||||
params: vec![k_exit_acc],
|
||||
body: vec![JoinInst::Ret {
|
||||
value: Some(k_exit_acc),
|
||||
}],
|
||||
exit_cont: None,
|
||||
};
|
||||
|
||||
// JoinModule を構築
|
||||
let mut functions = BTreeMap::new();
|
||||
functions.insert(entry_id, entry_func);
|
||||
functions.insert(loop_step_id, loop_step_func);
|
||||
functions.insert(k_exit_id, k_exit_func);
|
||||
|
||||
JoinModule {
|
||||
functions,
|
||||
entry: Some(entry_id),
|
||||
}
|
||||
}
|
||||
|
||||
/// 次の関数 ID を生成
|
||||
fn next_func_id(&mut self) -> JoinFuncId {
|
||||
let id = JoinFuncId::new(self.next_func_id);
|
||||
@ -303,6 +1075,21 @@ impl AstToJoinIrLowerer {
|
||||
(dst, vec![inst])
|
||||
}
|
||||
|
||||
// Phase 34-8: Bool literal 対応
|
||||
"Bool" => {
|
||||
let value = expr["value"]
|
||||
.as_bool()
|
||||
.expect("Bool value must be boolean");
|
||||
|
||||
let dst = ctx.alloc_var();
|
||||
let inst = JoinInst::Compute(crate::mir::join_ir::MirLikeInst::Const {
|
||||
dst,
|
||||
value: ConstValue::Bool(value),
|
||||
});
|
||||
|
||||
(dst, vec![inst])
|
||||
}
|
||||
|
||||
// 段階 1: Var 参照対応
|
||||
"Var" => {
|
||||
let var_name = expr["name"]
|
||||
@ -356,6 +1143,88 @@ impl AstToJoinIrLowerer {
|
||||
(dst, insts)
|
||||
}
|
||||
|
||||
// Phase 34-7.4a: Binary 演算対応(i + 1 など)
|
||||
"Binary" => {
|
||||
let op_str = expr["op"]
|
||||
.as_str()
|
||||
.expect("Binary must have 'op' field");
|
||||
let lhs_expr = &expr["lhs"];
|
||||
let rhs_expr = &expr["rhs"];
|
||||
|
||||
// op 文字列を BinOpKind に変換
|
||||
let op = match op_str {
|
||||
"+" => crate::mir::join_ir::BinOpKind::Add,
|
||||
"-" => crate::mir::join_ir::BinOpKind::Sub,
|
||||
"*" => crate::mir::join_ir::BinOpKind::Mul,
|
||||
"/" => crate::mir::join_ir::BinOpKind::Div,
|
||||
_ => panic!("Unsupported binary op: {}", op_str),
|
||||
};
|
||||
|
||||
// lhs と rhs を再帰的に extract_value
|
||||
let (lhs_var, lhs_insts) = self.extract_value(lhs_expr, ctx);
|
||||
let (rhs_var, rhs_insts) = self.extract_value(rhs_expr, ctx);
|
||||
|
||||
// 結果変数を割り当て
|
||||
let dst = ctx.alloc_var();
|
||||
|
||||
// BinOp 命令を生成
|
||||
let binop_inst = JoinInst::Compute(crate::mir::join_ir::MirLikeInst::BinOp {
|
||||
dst,
|
||||
op,
|
||||
lhs: lhs_var,
|
||||
rhs: rhs_var,
|
||||
});
|
||||
|
||||
// すべての命令を結合(lhs → rhs → BinOp の順)
|
||||
let mut insts = lhs_insts;
|
||||
insts.extend(rhs_insts);
|
||||
insts.push(binop_inst);
|
||||
|
||||
(dst, insts)
|
||||
}
|
||||
|
||||
// Phase 34-7.4a: Compare 演算対応(i < n など)
|
||||
"Compare" => {
|
||||
let op_str = expr["op"]
|
||||
.as_str()
|
||||
.expect("Compare must have 'op' field");
|
||||
let lhs_expr = &expr["lhs"];
|
||||
let rhs_expr = &expr["rhs"];
|
||||
|
||||
// op 文字列を CompareOp に変換
|
||||
let op = match op_str {
|
||||
"<" => crate::mir::join_ir::CompareOp::Lt,
|
||||
"<=" => crate::mir::join_ir::CompareOp::Le,
|
||||
">" => crate::mir::join_ir::CompareOp::Gt,
|
||||
">=" => crate::mir::join_ir::CompareOp::Ge,
|
||||
"==" => crate::mir::join_ir::CompareOp::Eq,
|
||||
"!=" => crate::mir::join_ir::CompareOp::Ne,
|
||||
_ => panic!("Unsupported compare op: {}", op_str),
|
||||
};
|
||||
|
||||
// lhs と rhs を再帰的に extract_value
|
||||
let (lhs_var, lhs_insts) = self.extract_value(lhs_expr, ctx);
|
||||
let (rhs_var, rhs_insts) = self.extract_value(rhs_expr, ctx);
|
||||
|
||||
// 結果変数を割り当て
|
||||
let dst = ctx.alloc_var();
|
||||
|
||||
// Compare 命令を生成
|
||||
let compare_inst = JoinInst::Compute(crate::mir::join_ir::MirLikeInst::Compare {
|
||||
dst,
|
||||
op,
|
||||
lhs: lhs_var,
|
||||
rhs: rhs_var,
|
||||
});
|
||||
|
||||
// すべての命令を結合(lhs → rhs → Compare の順)
|
||||
let mut insts = lhs_insts;
|
||||
insts.extend(rhs_insts);
|
||||
insts.push(compare_inst);
|
||||
|
||||
(dst, insts)
|
||||
}
|
||||
|
||||
_ => panic!("Unsupported expr type: {}", expr_type),
|
||||
}
|
||||
}
|
||||
@ -371,4 +1240,6 @@ impl Default for AstToJoinIrLowerer {
|
||||
// Phase 34-3: local pattern 対応(simple と同じ JoinIR 出力)
|
||||
// Phase 34-4: Stage-1/meta 実用関数対応(JsonShapeToMap._read_value_from_pair/1)
|
||||
// Phase 34-5: extract_value 統一化(Int/Var/Method 構造まで)
|
||||
// Phase 34-6 以降: MethodCall 構造の JoinIR への明示と JoinIR→MIR 変換側での Method/Call 意味論実装、その後 Loop/Break/Continue への拡張
|
||||
// Phase 34-6: MethodCall 構造の JoinIR への明示と JoinIR→MIR 変換側での Method/Call 意味論実装
|
||||
// Phase 34-7: tiny while loop 対応(Case-A simple pattern)
|
||||
// Phase 34-8: Break/Continue 付きループ対応(Select + Jump で制御フロー表現)
|
||||
|
||||
@ -52,6 +52,23 @@ impl JoinFuncId {
|
||||
pub fn new(id: u32) -> Self {
|
||||
JoinFuncId(id)
|
||||
}
|
||||
|
||||
/// JoinFuncId を JoinContId に変換
|
||||
///
|
||||
/// # Use Case
|
||||
/// Jump 命令で関数を continuation として使う場合
|
||||
/// ```rust
|
||||
/// let func_id = JoinFuncId(42);
|
||||
/// let cont_id = func_id.as_cont();
|
||||
/// Jump { cont: cont_id, args: vec![], cond: None }
|
||||
/// ```
|
||||
///
|
||||
/// # Phase 34-7 Note
|
||||
/// JoinFuncId と JoinContId は別の newtype だが、内部的には同じ u32 ID を共有する。
|
||||
/// この変換は型レベルでの役割の明示(関数 vs 継続)を可能にする。
|
||||
pub fn as_cont(self) -> JoinContId {
|
||||
JoinContId(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// 継続(join / ループ step / exit continuation)を識別するID
|
||||
@ -62,6 +79,21 @@ impl JoinContId {
|
||||
pub fn new(id: u32) -> Self {
|
||||
JoinContId(id)
|
||||
}
|
||||
|
||||
/// JoinContId を JoinFuncId に変換
|
||||
///
|
||||
/// # Use Case
|
||||
/// 継続 ID を関数 ID として参照する場合(JoinModule の functions map でルックアップ時など)
|
||||
/// ```rust
|
||||
/// let cont_id = JoinContId(42);
|
||||
/// let func = join_module.functions.get(&cont_id.as_func())?;
|
||||
/// ```
|
||||
///
|
||||
/// # Phase 34-7 Note
|
||||
/// JoinIR では継続も関数として実装されるため、この変換が必要になる。
|
||||
pub fn as_func(self) -> JoinFuncId {
|
||||
JoinFuncId(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// 変数ID(Phase 26-H では MIR の ValueId を再利用)
|
||||
@ -179,7 +211,31 @@ pub struct MergePair {
|
||||
/// JoinIR 命令セット(最小版)
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum JoinInst {
|
||||
/// 通常の関数呼び出し: f(args..., k_next)
|
||||
/// 通常の関数呼び出し(末尾再帰): f(args..., k_next)
|
||||
///
|
||||
/// # Semantics
|
||||
/// - 他の JoinIR 関数を呼び出す(MIR の Call に変換)
|
||||
/// - ループでは末尾再帰として使うのが典型的
|
||||
///
|
||||
/// # MIR 変換
|
||||
/// - `MirInstruction::Call { func, args, ... }` を生成
|
||||
///
|
||||
/// # Constraints (Phase 31/34 時点)
|
||||
/// - **k_next は常に None にすること!**
|
||||
/// JoinIR→MIR bridge が `k_next: Some(...)` 未対応
|
||||
/// → エラー: "Call with k_next is not yet supported"
|
||||
/// - 典型的な使い方: `Call { func, args, k_next: None, dst: Some(...) }`
|
||||
///
|
||||
/// # Loop Pattern での使い方 (Phase 34-7)
|
||||
/// ```rust
|
||||
/// // ✅ 正解: 末尾再帰
|
||||
/// Call {
|
||||
/// func: loop_step_id,
|
||||
/// args: vec![i_next, acc_next, n],
|
||||
/// k_next: None, // ⚠️ 必須: None にすること
|
||||
/// dst: Some(result),
|
||||
/// }
|
||||
/// ```
|
||||
Call {
|
||||
func: JoinFuncId,
|
||||
args: Vec<VarId>,
|
||||
@ -188,7 +244,46 @@ pub enum JoinInst {
|
||||
dst: Option<VarId>,
|
||||
},
|
||||
|
||||
/// 継続呼び出し(join / exit 継続など)
|
||||
/// 継続呼び出し(早期 return / exit 継続)
|
||||
///
|
||||
/// # Semantics
|
||||
/// - **「早期 return」(条件付き関数脱出)として使う!**
|
||||
/// - cond=Some(v): v が true なら cont に Jump、false なら次の命令へ
|
||||
/// - cond=None: 無条件 Jump
|
||||
///
|
||||
/// # MIR 変換
|
||||
/// - cond=Some(v): `Branch(v, exit_block[Return], continue_block)` を生成
|
||||
/// - exit_block: cont 関数を Call して Return
|
||||
/// - continue_block: 次の JoinInst に続く
|
||||
/// - cond=None: 無条件に cont を Call して Return
|
||||
///
|
||||
/// # Loop Pattern での使い方 (Phase 34-7)
|
||||
/// ```rust
|
||||
/// // ✅ 正解: 条件付き早期 return
|
||||
/// Jump {
|
||||
/// cont: k_exit_id.as_cont(),
|
||||
/// args: vec![acc],
|
||||
/// cond: Some(exit_cond), // exit_cond が true なら k_exit へ
|
||||
/// }
|
||||
/// // ↑ exit_cond が false なら次の命令(body 処理)へ進む
|
||||
///
|
||||
/// // ❌ 間違い: Call で条件分岐しようとする
|
||||
/// Call {
|
||||
/// func: k_exit_id,
|
||||
/// cond: Some(exit_cond), // こんなフィールドはない!
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// # 典型的なパターン
|
||||
/// ```text
|
||||
/// loop_step(i, acc, n):
|
||||
/// exit_cond = !(i < n)
|
||||
/// Jump(k_exit, [acc], cond=exit_cond) // 🔑 早期 return
|
||||
/// // ↓ Jump で抜けなかった場合のみ実行
|
||||
/// acc_next = acc + 1
|
||||
/// i_next = i + 1
|
||||
/// Call(loop_step, [i_next, acc_next, n]) // 🔑 末尾再帰
|
||||
/// ```
|
||||
Jump {
|
||||
cont: JoinContId,
|
||||
args: Vec<VarId>,
|
||||
|
||||
@ -235,9 +235,30 @@ fn convert_join_function_to_mir(
|
||||
// - k_next=Some: Not yet supported
|
||||
|
||||
if k_next.is_some() {
|
||||
let call_target_name = join_func_name(*func);
|
||||
return Err(JoinIrVmBridgeError::new(format!(
|
||||
"Call with k_next is not yet supported (k_next={:?})",
|
||||
k_next
|
||||
"Call with k_next is not yet supported\n\
|
||||
\n\
|
||||
Current function: {}\n\
|
||||
Call target: {} (JoinFuncId: {:?})\n\
|
||||
Arguments: {} args\n\
|
||||
k_next: {:?}\n\
|
||||
\n\
|
||||
💡 Fix: Change `k_next: Some(...)` to `k_next: None`\n\
|
||||
💡 Note: Phase 31 and Phase 34 both use k_next=None (bridge limitation)\n\
|
||||
\n\
|
||||
Example:\n\
|
||||
Call {{\n\
|
||||
func: loop_step_id,\n\
|
||||
args: vec![i_next, acc_next, n],\n\
|
||||
k_next: None, // ⚠️ Must be None\n\
|
||||
dst: Some(result),\n\
|
||||
}}",
|
||||
join_func.name,
|
||||
call_target_name,
|
||||
func,
|
||||
args.len(),
|
||||
k_next,
|
||||
)));
|
||||
}
|
||||
|
||||
|
||||
119
src/tests/helpers/joinir_frontend.rs
Normal file
119
src/tests/helpers/joinir_frontend.rs
Normal file
@ -0,0 +1,119 @@
|
||||
//! Phase 34-7.5: JoinIR Frontend テストヘルパー箱
|
||||
//!
|
||||
//! 目的: フィクスチャベースの AST→JoinIR テストを簡潔に書けるようにする
|
||||
|
||||
use crate::mir::join_ir::frontend::AstToJoinIrLowerer;
|
||||
use crate::mir::join_ir::{JoinModule, JoinFuncId};
|
||||
use crate::mir::join_ir_ops::JoinValue;
|
||||
use crate::mir::join_ir_vm_bridge::run_joinir_via_vm;
|
||||
|
||||
/// JoinIR Frontend テストランナー箱
|
||||
///
|
||||
/// フィクスチャ読み込み → lowering → 実行 → 検証の共通処理を提供
|
||||
pub struct JoinIrFrontendTestRunner {
|
||||
fixture_path: String,
|
||||
join_module: Option<JoinModule>,
|
||||
debug_enabled: bool,
|
||||
}
|
||||
|
||||
impl JoinIrFrontendTestRunner {
|
||||
/// フィクスチャからテストランナーを作成
|
||||
pub fn from_fixture(fixture_path: &str) -> Self {
|
||||
Self {
|
||||
fixture_path: fixture_path.to_string(),
|
||||
join_module: None,
|
||||
debug_enabled: std::env::var("JOINIR_TEST_DEBUG").is_ok(),
|
||||
}
|
||||
}
|
||||
|
||||
/// デバッグモードを有効化(JoinIR Module をダンプ)
|
||||
pub fn with_debug(mut self) -> Self {
|
||||
self.debug_enabled = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// フィクスチャを lowering
|
||||
pub fn lower(mut self) -> Result<Self, String> {
|
||||
let fixture_json = std::fs::read_to_string(&self.fixture_path)
|
||||
.map_err(|e| format!("Failed to read fixture {}: {}", self.fixture_path, e))?;
|
||||
|
||||
let program_json: serde_json::Value = serde_json::from_str(&fixture_json)
|
||||
.map_err(|e| format!("Failed to parse JSON {}: {}", self.fixture_path, e))?;
|
||||
|
||||
let mut lowerer = AstToJoinIrLowerer::new();
|
||||
let join_module = lowerer.lower_program_json(&program_json);
|
||||
|
||||
if self.debug_enabled {
|
||||
self.dump_joinir_module(&join_module);
|
||||
}
|
||||
|
||||
self.join_module = Some(join_module);
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// JoinIR Module をダンプ(デバッグ用)
|
||||
fn dump_joinir_module(&self, module: &JoinModule) {
|
||||
eprintln!("=== JoinIR Module ===");
|
||||
eprintln!("Entry: {:?}", module.entry);
|
||||
for (func_id, func) in &module.functions {
|
||||
eprintln!("\nFunction {:?}: {}", func_id, func.name);
|
||||
eprintln!(" Params: {:?}", func.params);
|
||||
eprintln!(" Instructions:");
|
||||
for (i, inst) in func.body.iter().enumerate() {
|
||||
eprintln!(" {}: {:?}", i, inst);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// テストケースを実行(単一入力・単一出力)
|
||||
pub fn run_case(
|
||||
&self,
|
||||
inputs: &[JoinValue],
|
||||
expected: JoinValue,
|
||||
) -> Result<(), String> {
|
||||
let module = self.join_module.as_ref()
|
||||
.ok_or("Module not lowered. Call .lower() first")?;
|
||||
|
||||
let result = run_joinir_via_vm(
|
||||
module,
|
||||
module.entry.unwrap(),
|
||||
inputs,
|
||||
).map_err(|e| {
|
||||
format!(
|
||||
"JoinIR execution failed\n\
|
||||
Inputs: {:?}\n\
|
||||
Error: {:?}\n\
|
||||
\n\
|
||||
=== JoinIR Module Dump ===\n\
|
||||
{:?}",
|
||||
inputs, e, module
|
||||
)
|
||||
})?;
|
||||
|
||||
if result != expected {
|
||||
return Err(format!(
|
||||
"Assertion failed\n\
|
||||
Inputs: {:?}\n\
|
||||
Expected: {:?}\n\
|
||||
Actual: {:?}\n\
|
||||
\n\
|
||||
=== JoinIR Module Dump ===\n\
|
||||
{:?}",
|
||||
inputs, expected, result, module
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 複数テストケースを一括実行
|
||||
pub fn run_cases(
|
||||
&self,
|
||||
cases: &[(Vec<JoinValue>, JoinValue)],
|
||||
) -> Result<(), String> {
|
||||
for (inputs, expected) in cases {
|
||||
self.run_case(inputs, expected.clone())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
3
src/tests/helpers/mod.rs
Normal file
3
src/tests/helpers/mod.rs
Normal file
@ -0,0 +1,3 @@
|
||||
//! Phase 34-7.5: テストヘルパーモジュール
|
||||
|
||||
pub mod joinir_frontend;
|
||||
@ -3,9 +3,8 @@
|
||||
//! Route A: 既存経路(AST→MIR→PHI→VM)
|
||||
//! Route B: 新経路(AST→JoinIR→MIR'→VM)
|
||||
|
||||
use crate::mir::join_ir::frontend::AstToJoinIrLowerer;
|
||||
use crate::mir::join_ir_runner::{run_joinir_function, JoinValue};
|
||||
use crate::mir::join_ir_vm_bridge::run_joinir_via_vm;
|
||||
use crate::tests::helpers::joinir_frontend::JoinIrFrontendTestRunner;
|
||||
use crate::mir::join_ir_ops::JoinValue;
|
||||
|
||||
/// Phase 34-2: IfSelect simple pattern の A/B テスト
|
||||
///
|
||||
@ -14,63 +13,16 @@ use crate::mir::join_ir_vm_bridge::run_joinir_via_vm;
|
||||
/// 期待: Route A と Route B の結果が一致
|
||||
#[test]
|
||||
fn joinir_frontend_if_select_simple_ab_test() {
|
||||
// フィクスチャ読み込み
|
||||
let fixture_path = "docs/private/roadmap2/phases/phase-34-joinir-frontend/fixtures/joinir_if_select_simple.program.json";
|
||||
let fixture_json = std::fs::read_to_string(fixture_path)
|
||||
.expect("Failed to read fixture JSON");
|
||||
let program_json: serde_json::Value = serde_json::from_str(&fixture_json)
|
||||
.expect("Failed to parse JSON");
|
||||
|
||||
// Route B: JoinIR Frontend 経路
|
||||
let mut lowerer = AstToJoinIrLowerer::new();
|
||||
let join_module = lowerer.lower_program_json(&program_json);
|
||||
|
||||
// デバッグ: JoinIR Module の内容を確認
|
||||
eprintln!("=== JoinIR Module ===");
|
||||
eprintln!("Entry: {:?}", join_module.entry);
|
||||
for (func_id, func) in &join_module.functions {
|
||||
eprintln!("\nFunction {:?}: {}", func_id, func.name);
|
||||
eprintln!(" Params: {:?}", func.params);
|
||||
eprintln!(" Instructions:");
|
||||
for (i, inst) in func.body.iter().enumerate() {
|
||||
eprintln!(" {}: {:?}", i, inst);
|
||||
}
|
||||
}
|
||||
|
||||
// JoinIR Runner で実行
|
||||
let mut vm = crate::backend::mir_interpreter::MirInterpreter::new();
|
||||
|
||||
// cond = 1 (true) の場合
|
||||
let result_true = run_joinir_function(
|
||||
&mut vm,
|
||||
&join_module,
|
||||
join_module.entry.unwrap(),
|
||||
&[JoinValue::Int(1)],
|
||||
JoinIrFrontendTestRunner::from_fixture(
|
||||
"docs/private/roadmap2/phases/phase-34-joinir-frontend/fixtures/joinir_if_select_simple.program.json"
|
||||
)
|
||||
.expect("Failed to run JoinIR function (cond=1)");
|
||||
|
||||
// cond = 0 (false) の場合
|
||||
let result_false = run_joinir_function(
|
||||
&mut vm,
|
||||
&join_module,
|
||||
join_module.entry.unwrap(),
|
||||
&[JoinValue::Int(0)],
|
||||
)
|
||||
.expect("Failed to run JoinIR function (cond=0)");
|
||||
|
||||
// 検証: cond=1 → 10, cond=0 → 20
|
||||
match result_true {
|
||||
JoinValue::Int(v) => assert_eq!(v, 10, "cond=1 should return 10"),
|
||||
_ => panic!("Expected Int, got {:?}", result_true),
|
||||
}
|
||||
|
||||
match result_false {
|
||||
JoinValue::Int(v) => assert_eq!(v, 20, "cond=0 should return 20"),
|
||||
_ => panic!("Expected Int, got {:?}", result_false),
|
||||
}
|
||||
|
||||
// Phase 34-2: Route A (既存経路) との比較は後続フェーズで実装
|
||||
// 現時点では Route B(JoinIR Frontend)単体の動作確認のみ
|
||||
.lower()
|
||||
.expect("Failed to lower fixture")
|
||||
.run_cases(&[
|
||||
(vec![JoinValue::Int(1)], JoinValue::Int(10)),
|
||||
(vec![JoinValue::Int(0)], JoinValue::Int(20)),
|
||||
])
|
||||
.expect("Test cases failed");
|
||||
}
|
||||
|
||||
/// Phase 34-3: IfSelect local pattern の A/B テスト
|
||||
@ -80,63 +32,16 @@ fn joinir_frontend_if_select_simple_ab_test() {
|
||||
/// 期待: simple と同じ JoinIR 出力(Select ベース)
|
||||
#[test]
|
||||
fn joinir_frontend_if_select_local_ab_test() {
|
||||
// フィクスチャ読み込み
|
||||
let fixture_path = "docs/private/roadmap2/phases/phase-34-joinir-frontend/fixtures/joinir_if_select_local.program.json";
|
||||
let fixture_json = std::fs::read_to_string(fixture_path)
|
||||
.expect("Failed to read fixture JSON");
|
||||
let program_json: serde_json::Value = serde_json::from_str(&fixture_json)
|
||||
.expect("Failed to parse JSON");
|
||||
|
||||
// Route B: JoinIR Frontend 経路
|
||||
let mut lowerer = AstToJoinIrLowerer::new();
|
||||
let join_module = lowerer.lower_program_json(&program_json);
|
||||
|
||||
// デバッグ: JoinIR Module の内容を確認
|
||||
eprintln!("=== JoinIR Module (local pattern) ===");
|
||||
eprintln!("Entry: {:?}", join_module.entry);
|
||||
for (func_id, func) in &join_module.functions {
|
||||
eprintln!("\nFunction {:?}: {}", func_id, func.name);
|
||||
eprintln!(" Params: {:?}", func.params);
|
||||
eprintln!(" Instructions:");
|
||||
for (i, inst) in func.body.iter().enumerate() {
|
||||
eprintln!(" {}: {:?}", i, inst);
|
||||
}
|
||||
}
|
||||
|
||||
// JoinIR Runner で実行
|
||||
let mut vm = crate::backend::mir_interpreter::MirInterpreter::new();
|
||||
|
||||
// cond = 1 (true) の場合
|
||||
let result_true = run_joinir_function(
|
||||
&mut vm,
|
||||
&join_module,
|
||||
join_module.entry.unwrap(),
|
||||
&[JoinValue::Int(1)],
|
||||
JoinIrFrontendTestRunner::from_fixture(
|
||||
"docs/private/roadmap2/phases/phase-34-joinir-frontend/fixtures/joinir_if_select_local.program.json"
|
||||
)
|
||||
.expect("Failed to run JoinIR function (cond=1)");
|
||||
|
||||
// cond = 0 (false) の場合
|
||||
let result_false = run_joinir_function(
|
||||
&mut vm,
|
||||
&join_module,
|
||||
join_module.entry.unwrap(),
|
||||
&[JoinValue::Int(0)],
|
||||
)
|
||||
.expect("Failed to run JoinIR function (cond=0)");
|
||||
|
||||
// 検証: cond=1 → 10, cond=0 → 20 (simple と同じ)
|
||||
match result_true {
|
||||
JoinValue::Int(v) => assert_eq!(v, 10, "cond=1 should return 10"),
|
||||
_ => panic!("Expected Int, got {:?}", result_true),
|
||||
}
|
||||
|
||||
match result_false {
|
||||
JoinValue::Int(v) => assert_eq!(v, 20, "cond=0 should return 20"),
|
||||
_ => panic!("Expected Int, got {:?}", result_false),
|
||||
}
|
||||
|
||||
// Phase 34-3: simple と local で JoinIR 出力が同じことを実証
|
||||
// 「値としての if」の本質が Select であることを確認
|
||||
.lower()
|
||||
.expect("Failed to lower fixture")
|
||||
.run_cases(&[
|
||||
(vec![JoinValue::Int(1)], JoinValue::Int(10)),
|
||||
(vec![JoinValue::Int(0)], JoinValue::Int(20)),
|
||||
])
|
||||
.expect("Test cases failed");
|
||||
}
|
||||
|
||||
/// Phase 34-6: JsonShapeToMap._read_value_from_pair/1 の完全実装テスト
|
||||
@ -146,58 +51,80 @@ fn joinir_frontend_if_select_local_ab_test() {
|
||||
/// 期待: 本物の substring 呼び出しが JoinIR MethodCall → MIR BoxCall で実行される
|
||||
#[test]
|
||||
fn joinir_frontend_json_shape_read_value_ab_test() {
|
||||
// フィクスチャ読み込み
|
||||
let fixture_path = "docs/private/roadmap2/phases/phase-34-joinir-frontend/fixtures/json_shape_read_value.program.json";
|
||||
let fixture_json = std::fs::read_to_string(fixture_path)
|
||||
.expect("Failed to read fixture JSON");
|
||||
let program_json: serde_json::Value = serde_json::from_str(&fixture_json)
|
||||
.expect("Failed to parse JSON");
|
||||
|
||||
// Route B: JoinIR Frontend 経路
|
||||
let mut lowerer = AstToJoinIrLowerer::new();
|
||||
let join_module = lowerer.lower_program_json(&program_json);
|
||||
|
||||
// デバッグ: JoinIR Module の内容を確認
|
||||
eprintln!("=== JoinIR Module (json_shape_read_value - Phase 34-6) ===");
|
||||
eprintln!("Entry: {:?}", join_module.entry);
|
||||
for (func_id, func) in &join_module.functions {
|
||||
eprintln!("\nFunction {:?}: {}", func_id, func.name);
|
||||
eprintln!(" Params: {:?}", func.params);
|
||||
eprintln!(" Instructions:");
|
||||
for (i, inst) in func.body.iter().enumerate() {
|
||||
eprintln!(" {}: {:?}", i, inst);
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 34-6: JoinIR→MIR→VM ブリッジ経由で実行(MethodCall サポート)
|
||||
|
||||
// テストケース 1: v="hello", at=3 → "hel" (substring)
|
||||
let result_substring = run_joinir_via_vm(
|
||||
&join_module,
|
||||
join_module.entry.unwrap(),
|
||||
&[JoinValue::Str("hello".to_string()), JoinValue::Int(3)],
|
||||
JoinIrFrontendTestRunner::from_fixture(
|
||||
"docs/private/roadmap2/phases/phase-34-joinir-frontend/fixtures/json_shape_read_value.program.json"
|
||||
)
|
||||
.expect("Failed to run JoinIR function (v=\"hello\", at=3)");
|
||||
.lower()
|
||||
.expect("Failed to lower fixture")
|
||||
.run_cases(&[
|
||||
(
|
||||
vec![JoinValue::Str("hello".to_string()), JoinValue::Int(3)],
|
||||
JoinValue::Str("hel".to_string()),
|
||||
),
|
||||
(
|
||||
vec![JoinValue::Str("world".to_string()), JoinValue::Int(0)],
|
||||
JoinValue::Str("world".to_string()),
|
||||
),
|
||||
])
|
||||
.expect("Test cases failed");
|
||||
}
|
||||
|
||||
// テストケース 2: v="world", at=0 → "world" (v そのまま)
|
||||
let result_original = run_joinir_via_vm(
|
||||
&join_module,
|
||||
join_module.entry.unwrap(),
|
||||
&[JoinValue::Str("world".to_string()), JoinValue::Int(0)],
|
||||
/// Phase 34-7: tiny while loop の A/B テスト
|
||||
///
|
||||
/// 入力: `fixtures/loop_frontend_simple.program.json`
|
||||
/// パターン: `local i = 0; local acc = 0; loop(i < n) { acc = acc + 1; i = i + 1; } return acc`
|
||||
/// 期待: Case-A な JoinIR (entry → loop_step → return) が生成され、正しく実行される
|
||||
#[test]
|
||||
fn joinir_frontend_loop_simple_ab_test() {
|
||||
JoinIrFrontendTestRunner::from_fixture(
|
||||
"docs/private/roadmap2/phases/phase-34-joinir-frontend/fixtures/loop_frontend_simple.program.json"
|
||||
)
|
||||
.expect("Failed to run JoinIR function (v=\"world\", at=0)");
|
||||
|
||||
// 検証: substring 呼び出し結果
|
||||
match result_substring {
|
||||
JoinValue::Str(s) => assert_eq!(s, "hel", "v.substring(0, 3) should return \"hel\""),
|
||||
_ => panic!("Expected Str, got {:?}", result_substring),
|
||||
.lower()
|
||||
.expect("Failed to lower fixture")
|
||||
.run_cases(&[
|
||||
(vec![JoinValue::Int(0)], JoinValue::Int(0)),
|
||||
(vec![JoinValue::Int(3)], JoinValue::Int(3)),
|
||||
(vec![JoinValue::Int(5)], JoinValue::Int(5)),
|
||||
])
|
||||
.expect("Test cases failed");
|
||||
}
|
||||
|
||||
// 検証: 元の文字列そのまま
|
||||
match result_original {
|
||||
JoinValue::Str(s) => assert_eq!(s, "world", "v should return \"world\""),
|
||||
_ => panic!("Expected Str, got {:?}", result_original),
|
||||
/// Phase 34-8: Break pattern の A/B テスト
|
||||
///
|
||||
/// 入力: `fixtures/loop_frontend_break.program.json`
|
||||
/// パターン: `loop { if i >= n { break }; acc = acc + i; i = i + 1 }`
|
||||
/// 期待: n=5 → acc=10 (0+1+2+3+4)
|
||||
#[test]
|
||||
fn joinir_frontend_loop_break_ab_test() {
|
||||
JoinIrFrontendTestRunner::from_fixture(
|
||||
"docs/private/roadmap2/phases/phase-34-joinir-frontend/fixtures/loop_frontend_break.program.json"
|
||||
)
|
||||
.lower()
|
||||
.expect("Failed to lower fixture")
|
||||
.run_cases(&[
|
||||
(vec![JoinValue::Int(0)], JoinValue::Int(0)), // n=0 → 0
|
||||
(vec![JoinValue::Int(5)], JoinValue::Int(10)), // n=5 → 10 (0+1+2+3+4)
|
||||
(vec![JoinValue::Int(3)], JoinValue::Int(3)), // n=3 → 3 (0+1+2)
|
||||
])
|
||||
.expect("Test cases failed");
|
||||
}
|
||||
|
||||
// Phase 34-6: 本物の Method 呼び出し意味論が JoinIR MethodCall → MIR BoxCall で実行されることを実証
|
||||
/// Phase 34-8: Continue pattern の A/B テスト
|
||||
///
|
||||
/// 入力: `fixtures/loop_frontend_continue.program.json`
|
||||
/// パターン: `loop { i = i + 1; if i == 3 { continue }; acc = acc + i }`
|
||||
/// 期待: n=5 → acc=12 (1+2+4+5, i==3 スキップ)
|
||||
#[test]
|
||||
fn joinir_frontend_loop_continue_ab_test() {
|
||||
JoinIrFrontendTestRunner::from_fixture(
|
||||
"docs/private/roadmap2/phases/phase-34-joinir-frontend/fixtures/loop_frontend_continue.program.json"
|
||||
)
|
||||
.lower()
|
||||
.expect("Failed to lower fixture")
|
||||
.run_cases(&[
|
||||
(vec![JoinValue::Int(0)], JoinValue::Int(0)), // n=0 → 0
|
||||
(vec![JoinValue::Int(5)], JoinValue::Int(12)), // n=5 → 12 (1+2+4+5)
|
||||
(vec![JoinValue::Int(2)], JoinValue::Int(3)), // n=2 → 3 (1+2)
|
||||
])
|
||||
.expect("Test cases failed");
|
||||
}
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
mod helpers;
|
||||
|
||||
#[cfg(feature = "aot-plan-import")]
|
||||
pub mod aot_plan_import;
|
||||
pub mod box_tests;
|
||||
|
||||
Reference in New Issue
Block a user