use std::fs;
use std::path::{Path, PathBuf};
fn compile_source(source: &str) -> Result<(tempfile::TempDir, PathBuf), String> {
use cobble::parser::parse;
use cobble::transpiler::Transpiler;
let temp_dir = tempfile::tempdir().map_err(|e| e.to_string())?;
let output_dir = temp_dir.path().join("output");
let program = parse(source).map_err(|errors| errors.join("\n"))?;
let mut transpiler = Transpiler::new("cobble".to_string(), output_dir.clone());
transpiler.transpile(&program)?;
transpiler.write_data_pack().map_err(|e| e.to_string())?;
Ok((temp_dir, output_dir))
}
fn read_function(output_dir: &Path, name: &str) -> String {
let path = output_dir.join(format!("data/cobble/function/{}.mcfunction", name));
fs::read_to_string(path).unwrap()
}
#[test]
fn test_for_loop_negative_step_minus_two() {
let source = r#"
def test():
for i in range(10) by -2:
/say Value: {i}
"#;
let (_temp, output_dir) = compile_source(source).unwrap();
let content = read_function(&output_dir, "test");
assert!(
content.contains("scoreboard players set i loop_counter 9"),
"range(10) by -2 should start at 9 (count - 1), not 8 (count + step)"
);
let loop_files: Vec<_> = fs::read_dir(output_dir.join("data/cobble/function"))
.unwrap()
.filter_map(|e| e.ok())
.filter(|e| e.file_name().to_string_lossy().starts_with("loop_temp_"))
.collect();
assert!(!loop_files.is_empty(), "Loop temp functions should exist");
let loop_content = fs::read_to_string(loop_files[0].path()).unwrap();
assert!(
loop_content.contains("scoreboard players remove i loop_counter 2"),
"Loop should decrement by 2"
);
assert!(
loop_content.contains("matches 0.."),
"Loop should continue while i >= 0"
);
}
#[test]
fn test_for_loop_negative_step_minus_three() {
let source = r#"
def test():
for i in range(10) by -3:
/say Value: {i}
"#;
let (_temp, output_dir) = compile_source(source).unwrap();
let content = read_function(&output_dir, "test");
assert!(
content.contains("scoreboard players set i loop_counter 9"),
"range(10) by -3 should start at 9 (count - 1), not 7 (count + step)"
);
let loop_files: Vec<_> = fs::read_dir(output_dir.join("data/cobble/function"))
.unwrap()
.filter_map(|e| e.ok())
.filter(|e| e.file_name().to_string_lossy().starts_with("loop_temp_"))
.collect();
let loop_content = fs::read_to_string(loop_files[0].path()).unwrap();
assert!(
loop_content.contains("scoreboard players remove i loop_counter 3"),
"Loop should decrement by 3"
);
}
#[test]
fn test_for_loop_negative_step_minus_five() {
let source = r#"
def test():
for i in range(20) by -5:
/say Value: {i}
"#;
let (_temp, output_dir) = compile_source(source).unwrap();
let content = read_function(&output_dir, "test");
assert!(
content.contains("scoreboard players set i loop_counter 19"),
"range(20) by -5 should start at 19 (count - 1), not 15 (count + step)"
);
}
#[test]
fn test_macro_dollar_syntax_direct() {
let source = r#"
def test(player):
/give $(player) diamond 1
"#;
let (_temp, output_dir) = compile_source(source).unwrap();
let content = read_function(&output_dir, "test");
assert!(
content.starts_with("$give"),
"Macro function with $(param) should have $ line prefix"
);
assert!(
content.contains("$(player)"),
"Parameter should remain as $(player)"
);
}
#[test]
fn test_macro_mixed_syntax() {
let source = r#"
def test(player, item):
/give {player} {item} 1
/tellraw $(player) {"text":"Given item"}
"#;
let (_temp, output_dir) = compile_source(source).unwrap();
let content = read_function(&output_dir, "test");
let lines: Vec<&str> = content.lines().collect();
assert_eq!(lines.len(), 2, "Should have exactly 2 lines");
assert!(
lines[0].starts_with("$give"),
"First line should have $ prefix"
);
assert!(
lines[1].starts_with("$tellraw"),
"Second line should have $ prefix"
);
}