feat(joinir): Phase 30.x jsonir v0 - JoinIR JSON serialization

Implement JSON serialization for JoinIR module.

Implementation:
- src/mir/join_ir/json.rs: JSON serializer (~250 lines, no external deps)
- src/tests/joinir_json_min.rs: Integration tests (8 unit + 2 integration)
- 10 tests total, all passing

Features:
- JoinModule → JSON serialization
- All instruction types: Call, Jump, Ret, Compute
- All MirLikeInst types: Const, BinOp, Compare, BoxCall
- Full ConstValue support: Integer, Bool, String, Null
- Full operator coverage: Add/Sub/Mul/Div/Or/And, Lt/Le/Gt/Ge/Eq/Ne
- JSON string escaping for special characters

Usage:
  use crate::mir::join_ir::json::join_module_to_json_string;
  let json = join_module_to_json_string(&module);

Non-goals (this phase):
- CLI flag (--emit-joinir-json)
- JSON → JoinIR reverse conversion

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
nyash-codex
2025-11-25 09:53:26 +09:00
parent daf4f9af57
commit e9c7d27a7f
5 changed files with 635 additions and 1 deletions

View File

@ -0,0 +1,232 @@
//! JoinIR JSON シリアライズテスト (Phase 30.x)
//!
//! 手動構築した JoinIR を JSON に変換し、構造の妥当性を検証する。
use crate::mir::join_ir::json::join_module_to_json_string;
use crate::mir::join_ir::{
BinOpKind, CompareOp, ConstValue, JoinContId, JoinFuncId, JoinFunction, JoinInst, JoinModule,
MirLikeInst,
};
use crate::mir::ValueId;
/// 手動構築した JoinModule の JSON 出力テストNYASH_JOINIR_EXPERIMENT 不要)
#[test]
fn test_manual_joinir_json() {
// skip_ws 相当の JoinIR を手動で構築
let mut module = JoinModule::new();
// skip 関数: skip(s) -> i
let mut skip_func = JoinFunction::new(
JoinFuncId::new(0),
"skip".to_string(),
vec![ValueId(3000)], // s
);
// i_init = 0
skip_func.body.push(JoinInst::Compute(MirLikeInst::Const {
dst: ValueId(3001),
value: ConstValue::Integer(0),
}));
// n = s.length()
skip_func.body.push(JoinInst::Compute(MirLikeInst::BoxCall {
dst: Some(ValueId(3002)),
box_name: "StringBox".to_string(),
method: "length".to_string(),
args: vec![ValueId(3000)],
}));
// loop_step(s, i_init, n) - 末尾呼び出し
skip_func.body.push(JoinInst::Call {
func: JoinFuncId::new(1),
args: vec![ValueId(3000), ValueId(3001), ValueId(3002)],
k_next: None,
dst: None,
});
module.add_function(skip_func);
// loop_step 関数: loop_step(s, i, n) -> i
let mut loop_step = JoinFunction::new(
JoinFuncId::new(1),
"loop_step".to_string(),
vec![ValueId(3100), ValueId(3101), ValueId(3102)], // s, i, n
);
// cond = i < n
loop_step.body.push(JoinInst::Compute(MirLikeInst::Compare {
dst: ValueId(3103),
op: CompareOp::Lt,
lhs: ValueId(3101),
rhs: ValueId(3102),
}));
// 条件分岐(簡略化: 条件付き ret
loop_step.body.push(JoinInst::Ret {
value: Some(ValueId(3101)),
});
module.add_function(loop_step);
module.entry = Some(JoinFuncId::new(0));
// JSON に変換
let json = join_module_to_json_string(&module);
// 構造チェック
assert!(json.contains("\"version\":0"));
assert!(json.contains("\"entry\":0"));
assert!(json.contains("\"name\":\"skip\""));
assert!(json.contains("\"name\":\"loop_step\""));
// 命令チェック
assert!(json.contains("\"kind\":\"const\""));
assert!(json.contains("\"kind\":\"boxcall\""));
assert!(json.contains("\"kind\":\"compare\""));
assert!(json.contains("\"type\":\"call\""));
assert!(json.contains("\"type\":\"ret\""));
// 値チェック
assert!(json.contains("\"value_type\":\"integer\""));
assert!(json.contains("\"value\":0"));
assert!(json.contains("\"box\":\"StringBox\""));
assert!(json.contains("\"method\":\"length\""));
assert!(json.contains("\"op\":\"lt\""));
eprintln!("[joinir/json] manual JoinIR JSON output:");
eprintln!("{}", json);
}
/// 全命令タイプの JSON 出力をカバーするテスト
#[test]
fn test_all_instruction_types_json() {
let mut module = JoinModule::new();
let mut func = JoinFunction::new(JoinFuncId::new(0), "all_types".to_string(), vec![]);
// Const - Integer
func.body.push(JoinInst::Compute(MirLikeInst::Const {
dst: ValueId(1),
value: ConstValue::Integer(42),
}));
// Const - Bool
func.body.push(JoinInst::Compute(MirLikeInst::Const {
dst: ValueId(2),
value: ConstValue::Bool(true),
}));
// Const - String
func.body.push(JoinInst::Compute(MirLikeInst::Const {
dst: ValueId(3),
value: ConstValue::String("hello".to_string()),
}));
// Const - Null
func.body.push(JoinInst::Compute(MirLikeInst::Const {
dst: ValueId(4),
value: ConstValue::Null,
}));
// BinOp - all types
for (i, op) in [
BinOpKind::Add,
BinOpKind::Sub,
BinOpKind::Mul,
BinOpKind::Div,
BinOpKind::Or,
BinOpKind::And,
]
.iter()
.enumerate()
{
func.body.push(JoinInst::Compute(MirLikeInst::BinOp {
dst: ValueId(10 + i as u32),
op: *op,
lhs: ValueId(1),
rhs: ValueId(1),
}));
}
// Compare - all types
for (i, op) in [
CompareOp::Lt,
CompareOp::Le,
CompareOp::Gt,
CompareOp::Ge,
CompareOp::Eq,
CompareOp::Ne,
]
.iter()
.enumerate()
{
func.body.push(JoinInst::Compute(MirLikeInst::Compare {
dst: ValueId(20 + i as u32),
op: *op,
lhs: ValueId(1),
rhs: ValueId(1),
}));
}
// BoxCall
func.body.push(JoinInst::Compute(MirLikeInst::BoxCall {
dst: Some(ValueId(30)),
box_name: "TestBox".to_string(),
method: "test_method".to_string(),
args: vec![ValueId(1), ValueId(2)],
}));
// Call
func.body.push(JoinInst::Call {
func: JoinFuncId::new(1),
args: vec![ValueId(1)],
k_next: Some(JoinContId::new(2)),
dst: Some(ValueId(40)),
});
// Jump (conditional)
func.body.push(JoinInst::Jump {
cont: JoinContId::new(3),
args: vec![ValueId(1)],
cond: Some(ValueId(2)),
});
// Jump (unconditional)
func.body.push(JoinInst::Jump {
cont: JoinContId::new(4),
args: vec![],
cond: None,
});
// Ret with value
func.body.push(JoinInst::Ret {
value: Some(ValueId(1)),
});
module.add_function(func);
// JSON に変換
let json = join_module_to_json_string(&module);
// 全 BinOp タイプをチェック
assert!(json.contains("\"op\":\"add\""));
assert!(json.contains("\"op\":\"sub\""));
assert!(json.contains("\"op\":\"mul\""));
assert!(json.contains("\"op\":\"div\""));
assert!(json.contains("\"op\":\"or\""));
assert!(json.contains("\"op\":\"and\""));
// 全 Compare タイプをチェック
assert!(json.contains("\"op\":\"lt\""));
assert!(json.contains("\"op\":\"le\""));
assert!(json.contains("\"op\":\"gt\""));
assert!(json.contains("\"op\":\"ge\""));
assert!(json.contains("\"op\":\"eq\""));
assert!(json.contains("\"op\":\"ne\""));
// 全 ConstValue タイプをチェック
assert!(json.contains("\"value_type\":\"integer\""));
assert!(json.contains("\"value_type\":\"bool\""));
assert!(json.contains("\"value_type\":\"string\""));
assert!(json.contains("\"value_type\":\"null\""));
eprintln!("[joinir/json] all_instruction_types test passed");
}

View File

@ -6,6 +6,7 @@ pub mod identical_exec;
pub mod identical_exec_collections;
pub mod identical_exec_instance;
pub mod identical_exec_string;
pub mod joinir_json_min; // Phase 30.x: JoinIR JSON シリアライズテスト
pub mod joinir_runner_min; // Phase 27.2: JoinIR 実行器 A/B 比較テスト
pub mod joinir_runner_standalone; // Phase 27-shortterm S-3.2: JoinIR Runner 単体テスト
pub mod joinir_vm_bridge_skip_ws; // Phase 27-shortterm S-4.4: JoinIR → Rust VM Bridge A/B Test