use std::process::Command;
fn ilo() -> Command {
Command::new(env!("CARGO_BIN_EXE_ilo"))
}
fn run(engine: &str, src: &str, entry: &str, args: &[&str]) -> String {
let mut cmd = ilo();
cmd.arg(src).arg(engine).arg(entry);
for a in args {
cmd.arg(a);
}
let out = cmd.output().expect("failed to run ilo");
assert!(
out.status.success(),
"ilo {engine} failed for `{src}`: stderr={}",
String::from_utf8_lossy(&out.stderr)
);
String::from_utf8_lossy(&out.stdout).trim().to_string()
}
fn run_err(src: &str) -> String {
let out = ilo()
.args([src, "--vm", "f"])
.output()
.expect("failed to run ilo");
assert!(!out.status.success(), "expected failure for `{src}`");
String::from_utf8_lossy(&out.stderr).to_string()
}
fn prog(field: &str) -> String {
format!("f j:t>R n t;r=jpar! j;r.{field}")
}
fn check_keyword(engine: &str, field: &str, value: i32) {
let src = prog(field);
let json = format!("{{\"{field}\":{value}}}");
assert_eq!(
run(engine, &src, "f", &[&json]),
value.to_string(),
"engine={engine} field={field}"
);
}
const KEYWORDS: &[&str] = &[
"type", "tool", "use", "with", "timeout", "retry", "if", "return", "let", "fn", "def", "var",
"const", "true", "false", "nil", "R", "L", "F", "O", "M", "S",
];
fn check_all_keywords(engine: &str) {
for (i, kw) in KEYWORDS.iter().enumerate() {
check_keyword(engine, kw, i as i32 + 1);
}
}
#[test]
fn dot_keywords_all_tree() {
check_all_keywords("--vm");
}
#[test]
fn dot_keywords_all_vm() {
check_all_keywords("--vm");
}
#[test]
#[cfg(feature = "cranelift")]
fn dot_keywords_all_cranelift() {
check_all_keywords("--jit");
}
const SAFE: &str = "f j:t>R n t;r=jpar! j;r.?type";
fn check_safe(engine: &str) {
assert_eq!(run(engine, SAFE, "f", &[r#"{"type":17}"#]), "17");
}
#[test]
fn dot_keywords_safe_tree() {
check_safe("--vm");
}
#[test]
fn dot_keywords_safe_vm() {
check_safe("--vm");
}
#[test]
#[cfg(feature = "cranelift")]
fn dot_keywords_safe_cranelift() {
check_safe("--jit");
}
const SNAKE_AFTER_KW: &str = "f j:t>R n t;r=jpar! j;r.type_id";
fn check_snake_after_kw(engine: &str) {
assert_eq!(
run(engine, SNAKE_AFTER_KW, "f", &[r#"{"type_id":1234}"#]),
"1234",
"engine={engine}"
);
}
#[test]
fn dot_keywords_snake_after_kw_tree() {
check_snake_after_kw("--vm");
}
#[test]
fn dot_keywords_snake_after_kw_vm() {
check_snake_after_kw("--vm");
}
#[test]
#[cfg(feature = "cranelift")]
fn dot_keywords_snake_after_kw_cranelift() {
check_snake_after_kw("--jit");
}
const SNAKE_LONG: &str = "f j:t>R n t;r=jpar! j;r.type_kind_id";
fn check_snake_long(engine: &str) {
assert_eq!(
run(engine, SNAKE_LONG, "f", &[r#"{"type_kind_id":42}"#]),
"42",
"engine={engine}"
);
}
#[test]
fn dot_keywords_snake_long_tree() {
check_snake_long("--vm");
}
#[test]
fn dot_keywords_snake_long_vm() {
check_snake_long("--vm");
}
#[test]
#[cfg(feature = "cranelift")]
fn dot_keywords_snake_long_cranelift() {
check_snake_long("--jit");
}
#[test]
fn type_as_binding_still_errors() {
let err = run_err("f j:t>n;type=5;type");
assert!(
err.contains("reserved")
|| err.contains("Type")
|| err.contains("got Type")
|| err.contains("`type`"),
"expected reserved-word error, got: {err}"
);
}
#[test]
fn if_as_binding_still_errors() {
let err = run_err("f j:t>n;if=5;if");
assert!(
err.contains("reserved")
|| err.contains("KwIf")
|| err.contains("got KwIf")
|| err.contains("`if`"),
"expected reserved-word error, got: {err}"
);
}
#[test]
fn dot_with_whitespace_still_errors() {
let err = run_err("f j:t>R n t;r=jpar! j;r. type");
assert!(
err.contains("ILO-P") || err.contains("expected"),
"expected parse error, got: {err}"
);
}