use std::process::Command;
fn ilo() -> Command {
Command::new(env!("CARGO_BIN_EXE_ilo"))
}
fn write_src(src: &str, tag: &str) -> std::path::PathBuf {
use std::sync::atomic::{AtomicU64, Ordering};
static COUNTER: AtomicU64 = AtomicU64::new(0);
let seq = COUNTER.fetch_add(1, Ordering::SeqCst);
let path = std::env::temp_dir().join(format!(
"ilo_loop_print_{}_{}_{}.ilo",
std::process::id(),
seq,
tag,
));
std::fs::write(&path, src).unwrap();
path
}
fn run_file(engine: &str, src: &str, fn_name: &str, args: &[&str]) -> String {
let path = write_src(
src,
&format!("{}_{}", fn_name, engine.trim_start_matches("--")),
);
let mut cmd = ilo();
cmd.arg(path.to_str().unwrap()).arg(engine).arg(fn_name);
for a in args {
cmd.arg(a);
}
let out = cmd.output().expect("failed to run ilo");
assert!(
out.status.success(),
"ilo {engine} failed for fn={fn_name} src=`{src}`: stderr={}",
String::from_utf8_lossy(&out.stderr)
);
String::from_utf8_lossy(&out.stdout).to_string()
}
fn run_file_json(engine: &str, src: &str, fn_name: &str) -> String {
let path = write_src(
src,
&format!("{}_{}_json", fn_name, engine.trim_start_matches("--")),
);
let out = ilo()
.arg(path.to_str().unwrap())
.arg(engine)
.arg("--json")
.arg(fn_name)
.output()
.expect("failed to run ilo");
assert!(
out.status.success(),
"ilo {engine} --json failed for fn={fn_name}: stderr={}",
String::from_utf8_lossy(&out.stderr)
);
String::from_utf8_lossy(&out.stdout).to_string()
}
const PRINT_LOOP: &str = "f>n;xs=[1 2 3];@x xs{prnt x}";
const NESTED_PRINT_LOOP: &str = "inner>n;xs=[1 2 3];@x xs{prnt x}\nouter>n;v=inner();+v 0";
const EMPTY_LOOP: &str = "f>n;xs=[];@x xs{prnt x}";
const BRK_LOOP: &str = "f>n;s=0;@i 0..10{>=i 3{brk};s=+s i;prnt s}";
const WHILE_LOOP: &str = "f>n;i=0;wh <i 3{i=+i 1;prnt i}";
const TRAILING_SENTINEL: &str = "f>n;xs=[1 2 3];@x xs{prnt x};+42 0";
const EARLY_RETURN_THEN_LOOP: &str = "f n:n>n;>n 0{ret 99};xs=[1 2 3];@x xs{prnt x}";
const EXPLICIT_NIL: &str = "f>O n;nil";
fn check_all(engine: &str) {
let out = run_file(engine, PRINT_LOOP, "f", &[]);
assert_eq!(
out, "1\n2\n3\n",
"print-loop double-output regression engine={engine}: got `{out}`"
);
let out = run_file(engine, NESTED_PRINT_LOOP, "outer", &[]);
assert_eq!(
out, "1\n2\n3\n3\n",
"nested print-loop must still propagate inner's return engine={engine}: got `{out}`"
);
let out = run_file(engine, EMPTY_LOOP, "f", &[]);
assert_eq!(
out, "",
"empty-loop nil should be suppressed engine={engine}: got `{out}`"
);
let out = run_file(engine, BRK_LOOP, "f", &[]);
assert_eq!(
out, "0\n1\n3\n",
"brk-loop tail suppressed engine={engine}: got `{out}`"
);
let out = run_file(engine, WHILE_LOOP, "f", &[]);
assert_eq!(
out, "1\n2\n3\n",
"while-loop double-output engine={engine}: got `{out}`"
);
let out = run_file(engine, TRAILING_SENTINEL, "f", &[]);
assert_eq!(
out, "1\n2\n3\n42\n",
"trailing-expression sentinel auto-print engine={engine}: got `{out}`"
);
let out = run_file(engine, EARLY_RETURN_THEN_LOOP, "f", &["1"]);
assert_eq!(
out, "99\n",
"early-return value must still print engine={engine}: got `{out}`"
);
let out = run_file(engine, EARLY_RETURN_THEN_LOOP, "f", &["0"]);
assert_eq!(
out, "1\n2\n3\n3\n",
"early-return + loop-tail conservative rule engine={engine}: got `{out}`"
);
let out = run_file(engine, EXPLICIT_NIL, "f", &[]);
assert_eq!(
out, "nil\n",
"explicit nil return must auto-print engine={engine}: got `{out}`"
);
let out = run_file_json(engine, PRINT_LOOP, "f");
assert!(
out.contains("\"ok\":3") || out.contains("\"ok\": 3"),
"JSON mode must still emit structured result engine={engine}: got `{out}`"
);
let out = run_file_json(engine, EMPTY_LOOP, "f");
assert!(
out.contains("\"ok\":null") || out.contains("\"ok\": null"),
"JSON mode must still emit nil engine={engine}: got `{out}`"
);
}
#[test]
fn loop_print_tree() {
check_all("--run-tree");
}
#[test]
fn loop_print_vm() {
check_all("--run-vm");
}
#[test]
#[cfg(feature = "cranelift")]
fn loop_print_cranelift() {
check_all("--run-cranelift");
}