diff --git a/docs/private b/docs/private index 1840731c..847c0875 160000 --- a/docs/private +++ b/docs/private @@ -1 +1 @@ -Subproject commit 1840731c685509f4874650c1454ba621b9987d76 +Subproject commit 847c0875595c984c74b5e6c4c5ca34747cbcb6f9 diff --git a/src/tests/joinir_json_min.rs b/src/tests/joinir_json_min.rs index 0d3d84a3..6fea1fb6 100644 --- a/src/tests/joinir_json_min.rs +++ b/src/tests/joinir_json_min.rs @@ -230,3 +230,217 @@ fn test_all_instruction_types_json() { eprintln!("[joinir/json] all_instruction_types test passed"); } + +// ============================================================================ +// Phase 30.x: jsonir v0 スナップショットテスト +// ============================================================================ +// +// 目的: +// - 現状の JoinIR 形(pre-generic な generic_case_a / case-specific lowering)を +// v0_ フィクスチャとして固定する +// - JoinIR/LoopScopeShape の汎用化中に意図しない退行を検知する +// +// 実行方法: +// NYASH_JOINIR_SNAPSHOT_TEST=1 cargo test --release joinir_json_v0_ -- --nocapture +// +// フィクスチャ初期生成: +// NYASH_JOINIR_SNAPSHOT_GENERATE=1 cargo test --release joinir_json_v0_ -- --nocapture + +use crate::ast::ASTNode; +use crate::mir::join_ir::lowering::{ + lower_funcscanner_append_defs_to_joinir, lower_funcscanner_trim_to_joinir, + lower_skip_ws_to_joinir, lower_stage1_usingresolver_to_joinir, lower_stageb_body_to_joinir, + lower_stageb_funcscanner_to_joinir, +}; +use crate::mir::MirCompiler; +use crate::parser::NyashParser; + +/// フィクスチャファイルのベースディレクトリ +const FIXTURE_DIR: &str = "tests/fixtures/joinir"; + +/// v0 スナップショットのケース定義 +#[derive(Debug, Clone, Copy)] +enum SnapshotCase { + SkipWsMin, + FuncscannerTrimMin, + FuncscannerAppendDefsMin, + Stage1UsingresolverMin, + StagebBodyMin, + StagebFuncscannerMin, +} + +impl SnapshotCase { + fn fixture_filename(&self) -> &'static str { + match self { + Self::SkipWsMin => "v0_skip_ws_min.jsonir", + Self::FuncscannerTrimMin => "v0_funcscanner_trim_min.jsonir", + Self::FuncscannerAppendDefsMin => "v0_funcscanner_append_defs_min.jsonir", + Self::Stage1UsingresolverMin => "v0_stage1_usingresolver_min.jsonir", + Self::StagebBodyMin => "v0_stageb_body_min.jsonir", + Self::StagebFuncscannerMin => "v0_stageb_funcscanner_min.jsonir", + } + } + + fn source_file(&self) -> &'static str { + match self { + Self::SkipWsMin => "apps/tests/minimal_ssa_skip_ws.hako", + Self::FuncscannerTrimMin => "lang/src/compiler/tests/funcscanner_trim_min.hako", + Self::FuncscannerAppendDefsMin => "apps/tests/funcscanner_append_defs_minimal.hako", + Self::Stage1UsingresolverMin => "apps/tests/stage1_usingresolver_minimal.hako", + Self::StagebBodyMin => "apps/tests/stageb_body_extract_minimal.hako", + Self::StagebFuncscannerMin => "apps/tests/stageb_funcscanner_scan_boxes_minimal.hako", + } + } + + fn name(&self) -> &'static str { + match self { + Self::SkipWsMin => "skip_ws_min", + Self::FuncscannerTrimMin => "funcscanner_trim_min", + Self::FuncscannerAppendDefsMin => "funcscanner_append_defs_min", + Self::Stage1UsingresolverMin => "stage1_usingresolver_min", + Self::StagebBodyMin => "stageb_body_min", + Self::StagebFuncscannerMin => "stageb_funcscanner_min", + } + } +} + +/// ケースに対応する JoinIR JSON を生成 +fn generate_joinir_json(case: SnapshotCase) -> Option { + // Stage-3 parser を有効化 + std::env::set_var("NYASH_PARSER_STAGE3", "1"); + std::env::set_var("HAKO_PARSER_STAGE3", "1"); + + let src = std::fs::read_to_string(case.source_file()).ok()?; + + // FuncScanner.trim は FuncScannerBox の定義が必要 + let full_src = if matches!(case, SnapshotCase::FuncscannerTrimMin) { + let func_scanner_src = + std::fs::read_to_string("lang/src/compiler/entry/func_scanner.hako").ok()?; + format!("{func_scanner_src}\n\n{src}") + } else { + src + }; + + let ast: ASTNode = NyashParser::parse_from_string(&full_src).ok()?; + let mut mc = MirCompiler::with_options(false); + let compiled = mc.compile(ast).ok()?; + + let join_module = match case { + SnapshotCase::SkipWsMin => lower_skip_ws_to_joinir(&compiled.module), + SnapshotCase::FuncscannerTrimMin => lower_funcscanner_trim_to_joinir(&compiled.module), + SnapshotCase::FuncscannerAppendDefsMin => { + lower_funcscanner_append_defs_to_joinir(&compiled.module) + } + SnapshotCase::Stage1UsingresolverMin => { + lower_stage1_usingresolver_to_joinir(&compiled.module) + } + SnapshotCase::StagebBodyMin => lower_stageb_body_to_joinir(&compiled.module), + SnapshotCase::StagebFuncscannerMin => lower_stageb_funcscanner_to_joinir(&compiled.module), + }?; + + Some(join_module_to_json_string(&join_module)) +} + +/// スナップショット比較を実行(共通ロジック) +fn run_snapshot_test(case: SnapshotCase) { + // トグルチェック + if std::env::var("NYASH_JOINIR_SNAPSHOT_TEST").ok().as_deref() != Some("1") { + eprintln!( + "[joinir/snapshot] NYASH_JOINIR_SNAPSHOT_TEST=1 not set, skipping {}", + case.name() + ); + return; + } + + let fixture_path = format!("{}/{}", FIXTURE_DIR, case.fixture_filename()); + + // JoinIR JSON 生成 + let json = match generate_joinir_json(case) { + Some(j) => j, + None => { + eprintln!( + "[joinir/snapshot] Failed to generate JoinIR for {}, skipping", + case.name() + ); + return; + } + }; + + // フィクスチャ生成モード + if std::env::var("NYASH_JOINIR_SNAPSHOT_GENERATE").ok().as_deref() == Some("1") { + std::fs::write(&fixture_path, &json).expect("Failed to write fixture"); + eprintln!( + "[joinir/snapshot] Generated fixture: {} ({} bytes)", + fixture_path, + json.len() + ); + return; + } + + // フィクスチャ読み込み + let fixture = match std::fs::read_to_string(&fixture_path) { + Ok(f) => f, + Err(_) => { + eprintln!( + "[joinir/snapshot] Fixture not found: {}\n\ + Run with NYASH_JOINIR_SNAPSHOT_GENERATE=1 to create it.", + fixture_path + ); + panic!("Fixture not found: {}", fixture_path); + } + }; + + // 比較 + if json != fixture { + eprintln!( + "[joinir/snapshot] MISMATCH for {}\n\ + --- actual ({} bytes) ---\n{}\n\ + --- fixture ({} bytes) ---\n{}", + case.name(), + json.len(), + json, + fixture.len(), + fixture + ); + panic!("jsonir v0 snapshot mismatch for {}", case.name()); + } + + eprintln!( + "[joinir/snapshot] {} matches fixture ✓", + case.name() + ); +} + +// ============================================================================ +// 個別スナップショットテスト +// ============================================================================ + +#[test] +fn joinir_json_v0_skip_ws_min_matches_fixture() { + run_snapshot_test(SnapshotCase::SkipWsMin); +} + +#[test] +fn joinir_json_v0_funcscanner_trim_min_matches_fixture() { + run_snapshot_test(SnapshotCase::FuncscannerTrimMin); +} + +#[test] +fn joinir_json_v0_funcscanner_append_defs_min_matches_fixture() { + run_snapshot_test(SnapshotCase::FuncscannerAppendDefsMin); +} + +#[test] +fn joinir_json_v0_stage1_usingresolver_min_matches_fixture() { + run_snapshot_test(SnapshotCase::Stage1UsingresolverMin); +} + +#[test] +fn joinir_json_v0_stageb_body_min_matches_fixture() { + run_snapshot_test(SnapshotCase::StagebBodyMin); +} + +#[test] +fn joinir_json_v0_stageb_funcscanner_min_matches_fixture() { + run_snapshot_test(SnapshotCase::StagebFuncscannerMin); +} diff --git a/tests/fixtures/joinir/v0_funcscanner_append_defs_min.jsonir b/tests/fixtures/joinir/v0_funcscanner_append_defs_min.jsonir new file mode 100644 index 00000000..9e8243f4 --- /dev/null +++ b/tests/fixtures/joinir/v0_funcscanner_append_defs_min.jsonir @@ -0,0 +1 @@ +{"version":0,"entry":0,"functions":[{"id":0,"name":"append_defs_entry","params":[9000,9001,9002],"exit_cont":null,"body":[{"type":"compute","op":{"kind":"const","dst":9010,"value_type":"integer","value":0}},{"type":"call","func":1,"args":[9000,9001,9002,9010],"k_next":null,"dst":null}]},{"id":1,"name":"loop_step","params":[10000,10001,10002,10003],"exit_cont":null,"body":[{"type":"compute","op":{"kind":"compare","dst":10010,"op":"ge","lhs":10003,"rhs":10002}},{"type":"jump","cont":0,"args":[],"cond":10010},{"type":"compute","op":{"kind":"boxcall","dst":10011,"box":"ArrayBox","method":"get","args":[10001,10003]}},{"type":"compute","op":{"kind":"boxcall","dst":null,"box":"ArrayBox","method":"push","args":[10000,10011]}},{"type":"compute","op":{"kind":"const","dst":10013,"value_type":"integer","value":1}},{"type":"compute","op":{"kind":"binop","dst":10012,"op":"add","lhs":10003,"rhs":10013}},{"type":"call","func":1,"args":[10000,10001,10002,10012],"k_next":null,"dst":null}]}]} \ No newline at end of file diff --git a/tests/fixtures/joinir/v0_funcscanner_trim_min.jsonir b/tests/fixtures/joinir/v0_funcscanner_trim_min.jsonir new file mode 100644 index 00000000..34b1479e --- /dev/null +++ b/tests/fixtures/joinir/v0_funcscanner_trim_min.jsonir @@ -0,0 +1 @@ +{"version":0,"entry":0,"functions":[{"id":0,"name":"trim_main","params":[5000],"exit_cont":null,"body":[{"type":"compute","op":{"kind":"const","dst":5005,"value_type":"string","value":""}},{"type":"compute","op":{"kind":"binop","dst":5001,"op":"add","lhs":5005,"rhs":5000}},{"type":"compute","op":{"kind":"boxcall","dst":5002,"box":"StringBox","method":"length","args":[5001]}},{"type":"compute","op":{"kind":"const","dst":5006,"value_type":"integer","value":0}},{"type":"call","func":2,"args":[5001,5006,5002],"k_next":null,"dst":5003},{"type":"compute","op":{"kind":"binop","dst":5004,"op":"add","lhs":5002,"rhs":5006}},{"type":"call","func":1,"args":[5001,5003,5004],"k_next":null,"dst":null}]},{"id":1,"name":"loop_step","params":[6000,6001,6002],"exit_cont":null,"body":[{"type":"compute","op":{"kind":"compare","dst":6003,"op":"gt","lhs":6002,"rhs":6001}},{"type":"compute","op":{"kind":"const","dst":6019,"value_type":"bool","value":false}},{"type":"compute","op":{"kind":"boxcall","dst":6004,"box":"StringBox","method":"substring","args":[6000,6001,6002]}},{"type":"compute","op":{"kind":"compare","dst":6020,"op":"eq","lhs":6003,"rhs":6019}},{"type":"jump","cont":0,"args":[6004],"cond":6020},{"type":"compute","op":{"kind":"const","dst":6005,"value_type":"integer","value":1}},{"type":"compute","op":{"kind":"binop","dst":6006,"op":"sub","lhs":6002,"rhs":6005}},{"type":"compute","op":{"kind":"boxcall","dst":6007,"box":"StringBox","method":"substring","args":[6000,6006,6002]}},{"type":"compute","op":{"kind":"const","dst":6012,"value_type":"string","value":" "}},{"type":"compute","op":{"kind":"compare","dst":6008,"op":"eq","lhs":6007,"rhs":6012}},{"type":"compute","op":{"kind":"const","dst":6013,"value_type":"string","value":"\\t"}},{"type":"compute","op":{"kind":"compare","dst":6009,"op":"eq","lhs":6007,"rhs":6013}},{"type":"compute","op":{"kind":"const","dst":6014,"value_type":"string","value":"\\n"}},{"type":"compute","op":{"kind":"compare","dst":6010,"op":"eq","lhs":6007,"rhs":6014}},{"type":"compute","op":{"kind":"const","dst":6015,"value_type":"string","value":"\\r"}},{"type":"compute","op":{"kind":"compare","dst":6011,"op":"eq","lhs":6007,"rhs":6015}},{"type":"compute","op":{"kind":"binop","dst":6016,"op":"or","lhs":6008,"rhs":6009}},{"type":"compute","op":{"kind":"binop","dst":6017,"op":"or","lhs":6016,"rhs":6010}},{"type":"compute","op":{"kind":"binop","dst":6018,"op":"or","lhs":6017,"rhs":6011}},{"type":"compute","op":{"kind":"compare","dst":6021,"op":"eq","lhs":6018,"rhs":6019}},{"type":"jump","cont":1,"args":[6004],"cond":6021},{"type":"compute","op":{"kind":"binop","dst":6022,"op":"sub","lhs":6002,"rhs":6005}},{"type":"call","func":1,"args":[6000,6001,6022],"k_next":null,"dst":null}]},{"id":2,"name":"skip_leading","params":[7000,7001,7002],"exit_cont":null,"body":[{"type":"compute","op":{"kind":"compare","dst":7003,"op":"ge","lhs":7001,"rhs":7002}},{"type":"jump","cont":2,"args":[7001],"cond":7003},{"type":"compute","op":{"kind":"const","dst":7004,"value_type":"integer","value":1}},{"type":"compute","op":{"kind":"binop","dst":7005,"op":"add","lhs":7001,"rhs":7004}},{"type":"compute","op":{"kind":"boxcall","dst":7006,"box":"StringBox","method":"substring","args":[7000,7001,7005]}},{"type":"compute","op":{"kind":"const","dst":7011,"value_type":"string","value":" "}},{"type":"compute","op":{"kind":"compare","dst":7007,"op":"eq","lhs":7006,"rhs":7011}},{"type":"compute","op":{"kind":"const","dst":7012,"value_type":"string","value":"\\t"}},{"type":"compute","op":{"kind":"compare","dst":7008,"op":"eq","lhs":7006,"rhs":7012}},{"type":"compute","op":{"kind":"const","dst":7013,"value_type":"string","value":"\\n"}},{"type":"compute","op":{"kind":"compare","dst":7009,"op":"eq","lhs":7006,"rhs":7013}},{"type":"compute","op":{"kind":"const","dst":7014,"value_type":"string","value":"\\r"}},{"type":"compute","op":{"kind":"compare","dst":7010,"op":"eq","lhs":7006,"rhs":7014}},{"type":"compute","op":{"kind":"binop","dst":7015,"op":"or","lhs":7007,"rhs":7008}},{"type":"compute","op":{"kind":"binop","dst":7016,"op":"or","lhs":7015,"rhs":7009}},{"type":"compute","op":{"kind":"binop","dst":7017,"op":"or","lhs":7016,"rhs":7010}},{"type":"compute","op":{"kind":"const","dst":7018,"value_type":"bool","value":false}},{"type":"compute","op":{"kind":"compare","dst":7019,"op":"eq","lhs":7017,"rhs":7018}},{"type":"jump","cont":3,"args":[7001],"cond":7019},{"type":"call","func":2,"args":[7000,7005,7002],"k_next":null,"dst":null}]}]} \ No newline at end of file diff --git a/tests/fixtures/joinir/v0_skip_ws_min.jsonir b/tests/fixtures/joinir/v0_skip_ws_min.jsonir new file mode 100644 index 00000000..2195c9a4 --- /dev/null +++ b/tests/fixtures/joinir/v0_skip_ws_min.jsonir @@ -0,0 +1 @@ +{"version":0,"entry":0,"functions":[{"id":0,"name":"skip","params":[3000],"exit_cont":null,"body":[{"type":"compute","op":{"kind":"const","dst":3001,"value_type":"integer","value":0}},{"type":"compute","op":{"kind":"boxcall","dst":3002,"box":"StringBox","method":"length","args":[3000]}},{"type":"call","func":1,"args":[3000,3001,3002],"k_next":null,"dst":null}]},{"id":1,"name":"loop_step","params":[4000,4001,4002],"exit_cont":null,"body":[{"type":"compute","op":{"kind":"compare","dst":4003,"op":"ge","lhs":4001,"rhs":4002}},{"type":"jump","cont":0,"args":[4001],"cond":4003},{"type":"compute","op":{"kind":"const","dst":4007,"value_type":"integer","value":1}},{"type":"compute","op":{"kind":"binop","dst":4006,"op":"add","lhs":4001,"rhs":4007}},{"type":"compute","op":{"kind":"boxcall","dst":4004,"box":"StringBox","method":"substring","args":[4000,4001,4006]}},{"type":"compute","op":{"kind":"const","dst":4010,"value_type":"string","value":" "}},{"type":"compute","op":{"kind":"compare","dst":4005,"op":"eq","lhs":4004,"rhs":4010}},{"type":"compute","op":{"kind":"const","dst":4011,"value_type":"bool","value":false}},{"type":"compute","op":{"kind":"compare","dst":4012,"op":"eq","lhs":4005,"rhs":4011}},{"type":"jump","cont":1,"args":[4001],"cond":4012},{"type":"call","func":1,"args":[4000,4006,4002],"k_next":null,"dst":null}]}]} \ No newline at end of file diff --git a/tests/fixtures/joinir/v0_stage1_usingresolver_min.jsonir b/tests/fixtures/joinir/v0_stage1_usingresolver_min.jsonir new file mode 100644 index 00000000..7f23b4c5 --- /dev/null +++ b/tests/fixtures/joinir/v0_stage1_usingresolver_min.jsonir @@ -0,0 +1 @@ +{"version":0,"entry":0,"functions":[{"id":0,"name":"resolve_entries","params":[7000,7001,7002,7003,7004],"exit_cont":null,"body":[{"type":"compute","op":{"kind":"const","dst":7010,"value_type":"integer","value":0}},{"type":"call","func":1,"args":[7000,7001,7002,7003,7004,7010],"k_next":null,"dst":null}]},{"id":1,"name":"loop_step","params":[8000,8001,8002,8003,8004,8005],"exit_cont":null,"body":[{"type":"compute","op":{"kind":"compare","dst":8010,"op":"ge","lhs":8005,"rhs":8001}},{"type":"jump","cont":0,"args":[8004],"cond":8010},{"type":"compute","op":{"kind":"boxcall","dst":8011,"box":"ArrayBox","method":"get","args":[8000,8005]}},{"type":"compute","op":{"kind":"const","dst":8013,"value_type":"integer","value":1}},{"type":"compute","op":{"kind":"binop","dst":8012,"op":"add","lhs":8005,"rhs":8013}},{"type":"compute","op":{"kind":"binop","dst":8014,"op":"or","lhs":8004,"rhs":8011}},{"type":"call","func":1,"args":[8000,8001,8002,8003,8014,8012],"k_next":null,"dst":null}]}]} \ No newline at end of file diff --git a/tests/fixtures/joinir/v0_stageb_body_min.jsonir b/tests/fixtures/joinir/v0_stageb_body_min.jsonir new file mode 100644 index 00000000..ecc67a01 --- /dev/null +++ b/tests/fixtures/joinir/v0_stageb_body_min.jsonir @@ -0,0 +1 @@ +{"version":0,"entry":0,"functions":[{"id":0,"name":"build_body_src","params":[11000,11001],"exit_cont":null,"body":[{"type":"compute","op":{"kind":"boxcall","dst":11002,"box":"StringBox","method":"length","args":[11000]}},{"type":"compute","op":{"kind":"const","dst":11003,"value_type":"integer","value":0}},{"type":"compute","op":{"kind":"const","dst":11004,"value_type":"integer","value":0}},{"type":"call","func":1,"args":[11000,11001,11002,11004,11003],"k_next":null,"dst":null}]},{"id":1,"name":"loop_step","params":[12000,12001,12002,12003,12004],"exit_cont":null,"body":[{"type":"compute","op":{"kind":"compare","dst":12010,"op":"ge","lhs":12004,"rhs":12002}},{"type":"jump","cont":0,"args":[12003],"cond":12010},{"type":"compute","op":{"kind":"const","dst":12011,"value_type":"integer","value":1}},{"type":"compute","op":{"kind":"binop","dst":12012,"op":"add","lhs":12004,"rhs":12011}},{"type":"compute","op":{"kind":"binop","dst":12013,"op":"add","lhs":12003,"rhs":12011}},{"type":"call","func":1,"args":[12000,12001,12002,12013,12012],"k_next":null,"dst":null}]}]} \ No newline at end of file diff --git a/tests/fixtures/joinir/v0_stageb_funcscanner_min.jsonir b/tests/fixtures/joinir/v0_stageb_funcscanner_min.jsonir new file mode 100644 index 00000000..7b35ccce --- /dev/null +++ b/tests/fixtures/joinir/v0_stageb_funcscanner_min.jsonir @@ -0,0 +1 @@ +{"version":0,"entry":0,"functions":[{"id":0,"name":"scan_all_boxes","params":[13000],"exit_cont":null,"body":[{"type":"compute","op":{"kind":"boxcall","dst":13001,"box":"StringBox","method":"length","args":[13000]}},{"type":"compute","op":{"kind":"const","dst":13002,"value_type":"integer","value":0}},{"type":"compute","op":{"kind":"const","dst":13003,"value_type":"integer","value":0}},{"type":"call","func":1,"args":[13000,13001,13002,13003],"k_next":null,"dst":null}]},{"id":1,"name":"loop_step","params":[14000,14001,14002,14003],"exit_cont":null,"body":[{"type":"compute","op":{"kind":"compare","dst":14010,"op":"ge","lhs":14003,"rhs":14001}},{"type":"jump","cont":0,"args":[14002],"cond":14010},{"type":"compute","op":{"kind":"const","dst":14011,"value_type":"integer","value":1}},{"type":"compute","op":{"kind":"binop","dst":14012,"op":"add","lhs":14003,"rhs":14011}},{"type":"call","func":1,"args":[14000,14001,14002,14012],"k_next":null,"dst":null}]}]} \ No newline at end of file