use pasta_dsl::parse_str;
use pasta_lua::LuaTranspiler;
#[test]
fn test_set_spot_multiple_actors() {
let source = r#"
*シーン
%さくら、うにゅう=2
さくら:こんにちは
"#;
let file = parse_str(source, "test.pasta").unwrap();
let transpiler = LuaTranspiler::default();
let mut output = Vec::new();
transpiler.transpile(&file, &mut output).unwrap();
let lua_code = String::from_utf8(output).unwrap();
assert!(
lua_code.contains("act:clear_spot()"),
"Missing clear_spot call. Generated code:\n{lua_code}"
);
assert!(
lua_code.contains(r#"act:set_spot("さくら", 0)"#),
"Missing さくら set_spot call. Generated code:\n{lua_code}"
);
assert!(
lua_code.contains(r#"act:set_spot("うにゅう", 2)"#),
"Missing うにゅう set_spot call. Generated code:\n{lua_code}"
);
let init_scene_pos = lua_code
.find("act:init_scene")
.expect("init_scene not found");
let clear_spot_pos = lua_code
.find("act:clear_spot()")
.expect("clear_spot not found");
let sakura_pos = lua_code
.find(r#"act:set_spot("さくら""#)
.expect("さくら set_spot not found");
let unyu_pos = lua_code
.find(r#"act:set_spot("うにゅう""#)
.expect("うにゅう set_spot not found");
assert!(
init_scene_pos < clear_spot_pos,
"init_scene should come before clear_spot"
);
assert!(
clear_spot_pos < sakura_pos,
"clear_spot should come before さくら set_spot"
);
assert!(
sakura_pos < unyu_pos,
"Actor order not preserved: さくら should come before うにゅう"
);
}
#[test]
fn test_set_spot_single_actor() {
let source = r#"
*シーン
%さくら
さくら:こんにちは
"#;
let file = parse_str(source, "test.pasta").unwrap();
let transpiler = LuaTranspiler::default();
let mut output = Vec::new();
transpiler.transpile(&file, &mut output).unwrap();
let lua_code = String::from_utf8(output).unwrap();
assert!(
lua_code.contains("act:clear_spot()"),
"Missing clear_spot call. Generated code:\n{lua_code}"
);
assert!(
lua_code.contains(r#"act:set_spot("さくら", 0)"#),
"Missing single actor set_spot call. Generated code:\n{lua_code}"
);
let init_scene_pos = lua_code
.find("act:init_scene")
.expect("init_scene not found");
let clear_spot_pos = lua_code
.find("act:clear_spot()")
.expect("clear_spot not found");
let set_spot_pos = lua_code
.find(r#"act:set_spot("さくら""#)
.expect("set_spot not found");
assert!(
init_scene_pos < clear_spot_pos,
"init_scene should come before clear_spot"
);
assert!(
clear_spot_pos < set_spot_pos,
"clear_spot should come before set_spot"
);
}
#[test]
fn test_set_spot_empty_actors() {
let source = r#"
*シーン
さくら:こんにちは
"#;
let file = parse_str(source, "test.pasta").unwrap();
let transpiler = LuaTranspiler::default();
let mut output = Vec::new();
transpiler.transpile(&file, &mut output).unwrap();
let lua_code = String::from_utf8(output).unwrap();
assert!(
!lua_code.contains("set_spot"),
"set_spot should not be generated when no actors defined. Generated code:\n{lua_code}"
);
assert!(
!lua_code.contains("act:clear_spot()"),
"clear_spot should not be generated when no actors defined. Generated code:\n{lua_code}"
);
}
#[test]
fn test_set_spot_with_explicit_number() {
let source = r#"
*シーン
%さくら、うにゅう=2、まりか
さくら:こんにちは
"#;
let file = parse_str(source, "test.pasta").unwrap();
let transpiler = LuaTranspiler::default();
let mut output = Vec::new();
transpiler.transpile(&file, &mut output).unwrap();
let lua_code = String::from_utf8(output).unwrap();
assert!(
lua_code.contains("act:clear_spot()"),
"Missing clear_spot call. Generated code:\n{lua_code}"
);
assert!(
lua_code.contains(r#"act:set_spot("さくら", 0)"#),
"Missing さくら set_spot(0). Generated code:\n{lua_code}"
);
assert!(
lua_code.contains(r#"act:set_spot("うにゅう", 2)"#),
"Missing うにゅう set_spot(2). Generated code:\n{lua_code}"
);
assert!(
lua_code.contains(r#"act:set_spot("まりか", 3)"#),
"Missing まりか set_spot(3). Generated code:\n{lua_code}"
);
let init_scene_pos = lua_code
.find("act:init_scene")
.expect("init_scene not found");
let clear_spot_pos = lua_code
.find("act:clear_spot()")
.expect("clear_spot not found");
let marika_pos = lua_code
.find(r#"act:set_spot("まりか""#)
.expect("まりか set_spot not found");
assert!(
init_scene_pos < clear_spot_pos,
"init_scene should come before clear_spot"
);
assert!(
clear_spot_pos < marika_pos,
"clear_spot should come before last set_spot"
);
}
#[test]
fn test_single_call_scene_gets_return() {
let source = r#"
*メイン
>シーン2
"#;
let file = parse_str(source, "test.pasta").unwrap();
let transpiler = LuaTranspiler::default();
let mut output = Vec::new();
transpiler.transpile(&file, &mut output).unwrap();
let lua_code = String::from_utf8(output).unwrap();
assert!(
lua_code.contains(r#"return act:call(SCENE.__global_name__, "シーン2""#),
"Single call should have 'return' prefix for TCO. Generated code:\n{lua_code}"
);
}
#[test]
fn test_multiple_call_scenes_only_last_gets_return() {
let source = r#"
*メイン
>シーン1
>シーン2
>シーン3
"#;
let file = parse_str(source, "test.pasta").unwrap();
let transpiler = LuaTranspiler::default();
let mut output = Vec::new();
transpiler.transpile(&file, &mut output).unwrap();
let lua_code = String::from_utf8(output).unwrap();
assert!(
lua_code.contains(r#"act:call(SCENE.__global_name__, "シーン1""#),
"First call should NOT have 'return' prefix. Generated code:\n{lua_code}"
);
assert!(
lua_code.contains(r#"act:call(SCENE.__global_name__, "シーン2""#),
"Second call should NOT have 'return' prefix. Generated code:\n{lua_code}"
);
assert!(
lua_code.contains(r#"return act:call(SCENE.__global_name__, "シーン3""#),
"Last call should have 'return' prefix for TCO. Generated code:\n{lua_code}"
);
let return_calls: Vec<_> = lua_code.match_indices("return act:call").collect();
assert_eq!(
return_calls.len(),
1,
"Only the last call should have 'return'. Found {} return calls. Generated code:\n{lua_code}",
return_calls.len()
);
}
#[test]
fn test_call_scene_followed_by_action_no_return() {
let source = r#"
%さくら
@通常:\s[0]
*メイン
>シーン2
さくら:こんにちは
"#;
let file = parse_str(source, "test.pasta").unwrap();
let transpiler = LuaTranspiler::default();
let mut output = Vec::new();
transpiler.transpile(&file, &mut output).unwrap();
let lua_code = String::from_utf8(output).unwrap();
assert!(
lua_code.contains(r#"act:call(SCENE.__global_name__, "シーン2""#),
"Call should be present. Generated code:\n{lua_code}"
);
assert!(
!lua_code.contains(r#"return act:call(SCENE.__global_name__, "シーン2""#),
"Call followed by action should NOT have 'return' prefix. Generated code:\n{lua_code}"
);
let call_pos = lua_code
.find(r#"act:call(SCENE.__global_name__, "シーン2""#)
.expect("Call not found");
let talk_pos = lua_code
.find(r#"act.さくら:talk("#)
.expect("Talk not found");
assert!(call_pos < talk_pos, "Call should come before talk action");
}
#[test]
fn test_no_call_scene_no_return() {
let source = r#"
%さくら
@通常:\s[0]
*メイン
さくら:こんにちは
さくら:さようなら
"#;
let file = parse_str(source, "test.pasta").unwrap();
let transpiler = LuaTranspiler::default();
let mut output = Vec::new();
transpiler.transpile(&file, &mut output).unwrap();
let lua_code = String::from_utf8(output).unwrap();
assert!(
!lua_code.contains("return act:call"),
"No call scene means no 'return act:call' should be generated. Generated code:\n{lua_code}"
);
assert!(
lua_code.contains(r#"act.さくら:talk("こんにちは")"#),
"Talk action should be present. Generated code:\n{lua_code}"
);
assert!(
lua_code.contains(r#"act.さくら:talk("さようなら")"#),
"Second talk action should be present. Generated code:\n{lua_code}"
);
}
#[test]
fn test_tail_call_optimization_fixture() {
let source = include_str!("../fixtures/tail_call_optimization.pasta");
let file = parse_str(source, "tail_call_optimization.pasta").unwrap();
let transpiler = LuaTranspiler::default();
let mut output = Vec::new();
transpiler.transpile(&file, &mut output).unwrap();
let lua_code = String::from_utf8(output).unwrap();
assert!(
lua_code.contains(r#"return act:call(SCENE.__global_name__, "シーン2""#),
"単一呼び出し scene should have return. Generated code:\n{lua_code}"
);
let multiple_call_section = lua_code
.find("SCENE.複数呼び出し_")
.expect("複数呼び出し function not found");
let next_function = lua_code[multiple_call_section..]
.find("\n function SCENE.")
.map(|pos| multiple_call_section + pos)
.unwrap_or(lua_code.len());
let multiple_call_code = &lua_code[multiple_call_section..next_function];
assert!(
multiple_call_code.contains(r#"return act:call(SCENE.__global_name__, "シーン3""#),
"Last call in 複数呼び出し should have return. Section:\n{multiple_call_code}"
);
let return_count = multiple_call_code.matches("return act:call").count();
assert_eq!(
return_count, 1,
"Only シーン3 should have return in 複数呼び出し. Found {} return calls. Section:\n{multiple_call_code}",
return_count
);
let action_after_call_section = lua_code
.find("SCENE.呼び出し後にアクション_")
.expect("呼び出し後にアクション function not found");
let next_function2 = lua_code[action_after_call_section..]
.find("\n function SCENE.")
.map(|pos| action_after_call_section + pos)
.unwrap_or(lua_code.len());
let action_after_call_code = &lua_code[action_after_call_section..next_function2];
assert!(
!action_after_call_code.contains("return act:call"),
"Call followed by action should NOT have return. Section:\n{action_after_call_code}"
);
let no_call_section = lua_code
.find("SCENE.呼び出しなし_")
.expect("呼び出しなし function not found");
let next_function3 = lua_code[no_call_section..]
.find("\n function SCENE.")
.map(|pos| no_call_section + pos)
.unwrap_or(lua_code.len());
let no_call_code = &lua_code[no_call_section..next_function3];
assert!(
!no_call_code.contains("return act:call"),
"No call scene should have no return act:call. Section:\n{no_call_code}"
);
}