use crate::common;
use common::e2e_helpers::transpile;
use insta::assert_snapshot;
use pasta_dsl::parser::parse_str;
use pasta_lua::LuaTranspiler;
use pasta_lua::code_gen::source_map::SourceMapSink;
use std::path::PathBuf;
#[derive(Default)]
struct DiscardingSink {
records: Vec<(u32, u32)>,
}
impl SourceMapSink for DiscardingSink {
fn record_line(&mut self, lua_line: u32, pasta_line: u32) {
self.records.push((lua_line, pasta_line));
}
}
fn transpile_off_path(source: &str) -> Vec<u8> {
let file = parse_str(source, "final_regression.pasta").expect("parse ok");
let transpiler = LuaTranspiler::default();
let mut output = Vec::new();
transpiler
.transpile(&file, &mut output)
.expect("transpile (off path) ok");
output
}
fn transpile_with_discarding_sink(source: &str) -> (Vec<u8>, usize) {
let file = parse_str(source, "final_regression.pasta").expect("parse ok");
let transpiler = LuaTranspiler::default();
let mut sink = DiscardingSink::default();
let mut output = Vec::new();
transpiler
.transpile_with_sink(&file, &mut output, Some(&mut sink))
.expect("transpile (sink path) ok");
(output, sink.records.len())
}
fn fixtures_path() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("tests")
.join("fixtures")
}
fn read_fixture(name: &str) -> String {
std::fs::read_to_string(fixtures_path().join(name))
.unwrap_or_else(|e| panic!("Failed to read fixture {name}: {e}"))
}
fn broad_fixtures() -> Vec<(&'static str, String)> {
vec![
("fixture_sample", read_fixture("sample.pasta")),
(
"fixture_tail_call_optimization",
read_fixture("tail_call_optimization.pasta"),
),
(
"fixture_zero_cost_all_syntax",
read_fixture("zero_cost_all_syntax.pasta"),
),
(
"kind_simple_talk",
"*メイン\n さくら:「こんにちは」\n".to_string(),
),
(
"kind_word_reference",
"@挨拶:おはよう、こんにちは、こんばんは\n\n*メイン\n さくら:「@挨拶!」\n".to_string(),
),
(
"kind_variable_assignment",
"*メイン\n $カウンタ=「10」\n $*グローバル=「永続値」\n さくら:「カウンタは$カウンタ、グローバルは$*グローバル」\n".to_string(),
),
(
"kind_scene_call",
"*メイン\n さくら:「サブルーチンを呼びます」\n >サブ\n\n ・サブ\n うにゅう:「サブルーチンです」\n".to_string(),
),
(
"kind_scene_attributes",
"&天気:晴れ\n&場所:東京\n\n*メイン\n &時間帯:朝\n さくら:「今日の天気は晴れです」\n".to_string(),
),
(
"kind_multiple_scenes",
"*挨拶\n さくら:「おはようございます」\n\n*挨拶\n さくら:「こんにちは」\n\n*メイン\n >挨拶\n".to_string(),
),
(
"kind_actor_word_definition",
"%さくら\n @一人称:私、わたし、あたし\n\n%うにゅう\n @一人称:僕、ぼく\n\n*メイン\n さくら:「@一人称は元気です」\n".to_string(),
),
(
"kind_dynamic_call",
"*メイン\n $target=「挨拶」\n >$target\n".to_string(),
),
(
"kind_choice_branch",
"*選択シーン\n @?挨拶「あいさつする」\n".to_string(),
),
(
"kind_code_block",
"%さくら\n @一人称:私\n```lua\nfunction ACTOR.時刻(act)\n return \"朝\"\nend\n```\n\n*メイン\n さくら:「テスト」\n".to_string(),
),
]
}
#[test]
fn r7_1_off_path_byte_invariant_across_broad_fixtures() {
let mut total_records = 0usize;
for (id, source) in broad_fixtures() {
let off = transpile_off_path(&source);
let (with_sink, records) = transpile_with_discarding_sink(&source);
assert!(
!off.is_empty(),
"7.1: fixture '{id}' must transpile to real Lua output bytes (non-vacuous)"
);
assert_eq!(
off, with_sink,
"7.1: fixture '{id}' OFF-path bytes (no sink) must be byte-identical to the \
sink-attached transpile (source-map wiring is observe-only / zero-cost)"
);
total_records += records;
}
assert!(
total_records > 0,
"7.1: the source-map sink path must actually record mappings across the broad set \
(otherwise byte-equality would be vacuous)"
);
}
#[test]
fn r7_1_off_path_is_deterministic_across_broad_fixtures() {
for (id, source) in broad_fixtures() {
let run1 = transpile_off_path(&source);
let run2 = transpile_off_path(&source);
assert_eq!(
run1, run2,
"7.1: fixture '{id}' OFF-path transpile must be byte-for-byte deterministic"
);
assert!(!run1.is_empty(), "fixture '{id}' must produce real output bytes");
}
}
#[test]
fn r7_1_off_path_matches_committed_snapshots() {
for (id, source) in broad_fixtures() {
let lua = transpile(&source);
assert!(
!lua.is_empty(),
"7.1: fixture '{id}' must transpile to real Lua output bytes"
);
assert_snapshot!(format!("r7_1_off_path__{id}"), lua);
}
}
#[test]
fn r7_2_existing_lua_level_debug_surface_is_present() {
use pasta_lua::debug::{DebugConfig, enable};
let cfg = DebugConfig {
enabled: false,
..Default::default()
};
let lua = unsafe { mlua::Lua::unsafe_new_with(mlua::StdLib::ALL_SAFE, mlua::LuaOptions::default()) };
let handle = enable(&lua, &cfg, None).expect("enable must not error for disabled config");
assert!(
handle.is_none(),
"7.2: with debug disabled (production default) enable() must return None \
(no line hook, full JIT — zero-cost); the existing Lua-level debug machinery \
is still present and exercised by the wiring.rs E2E tests"
);
}
#[test]
fn r7_3_slice_harness_removed_production_paths_compile() {
use pasta_lua::debug::source_map::SourceMap;
let empty = SourceMap::default();
assert!(
empty.resolve_lua_to_pasta("any_chunk", 1).is_none(),
"7.3: production source-map面 (SourceMap::resolve_lua_to_pasta) must be always-compiled \
(no feature gate); empty aggregate map resolves to None"
);
use pasta_lua::debug::{DebugConfig, SourceMode};
let cfg = DebugConfig::default();
let _mode: SourceMode = cfg.source_mode;
assert_eq!(
cfg.source_mode,
SourceMode::Pasta,
"7.3: DebugConfig must expose `source_mode` (SourceMode) replacing the removed dead \
`source_map_slice: bool`; default presentation mode is .pasta (6.1)"
);
}