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:
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)");
|
||||
|
||||
// テストケース 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)],
|
||||
)
|
||||
.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),
|
||||
}
|
||||
|
||||
// 検証: 元の文字列そのまま
|
||||
match result_original {
|
||||
JoinValue::Str(s) => assert_eq!(s, "world", "v should return \"world\""),
|
||||
_ => panic!("Expected Str, got {:?}", result_original),
|
||||
}
|
||||
|
||||
// Phase 34-6: 本物の Method 呼び出し意味論が JoinIR MethodCall → MIR BoxCall で実行されることを実証
|
||||
.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");
|
||||
}
|
||||
|
||||
/// 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"
|
||||
)
|
||||
.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");
|
||||
}
|
||||
|
||||
/// 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-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