mod harness;
use harness::{Compiled, compile};
use std::sync::OnceLock;
fn prog() -> &'static Compiled {
static C: OnceLock<Compiled> = OnceLock::new();
C.get_or_init(|| compile("p.\n"))
}
#[track_caller]
fn check(goal: &str, expected: &str) {
let (out, code) = prog().query(goal, &[]);
assert_eq!(out.trim_end(), expected, "goal: {goal}");
assert_eq!(code, 1, "goal {goal} should succeed: {out}");
}
#[track_caller]
fn fails(goal: &str) {
let (out, code) = prog().query(goal, &[]);
assert_eq!(out, "false.\n", "goal {goal} should have no solutions");
assert_eq!(code, 0, "goal: {goal}");
}
#[track_caller]
fn errors(goal: &str, needle: &str) {
let (out, code) = prog().query(goal, &[]);
assert!(
out.contains(needle),
"goal {goal}: expected {needle}, got {out}"
);
assert_eq!(code, 3, "goal {goal} should error: {out}");
}
#[test]
fn forward_assembly() {
check("atom_concat(foo, bar, X)", "X = foobar");
check("atom_concat(foo, bar, foobar)", "true.");
fails("atom_concat(foo, bar, nope)");
}
#[test]
fn known_prefix_or_suffix_selects_the_split() {
check("atom_concat(X, bar, foobar)", "X = foo");
check("atom_concat(foo, Y, foobar)", "Y = bar");
fails("atom_concat(X, xyz, foobar)");
fails("atom_concat(zzz, Y, foobar)");
}
#[test]
fn both_unbound_enumerates_every_decomposition() {
check(
"atom_concat(A, B, abc)",
"A = \nB = abc\nA = a\nB = bc\nA = ab\nB = c\nA = abc\nB =",
);
check("atom_concat(A, B, '')", "A = \nB =");
}
#[test]
fn shared_variable_keeps_only_equal_halves() {
check("atom_concat(X, X, abab)", "X = ab");
fails("atom_concat(X, X, abc)");
}
#[test]
fn unicode_splits_on_char_boundaries() {
check(
"atom_concat(A, B, héllo)",
"A = \nB = héllo\nA = h\nB = éllo\nA = hé\nB = llo\nA = hél\nB = lo\nA = héll\nB = o\nA = héllo\nB =",
);
}
#[test]
fn unbound_with_unbound_c_is_instantiation_error() {
errors("atom_concat(X, Y, Z)", "instantiation_error");
errors("atom_concat(foo, Y, Z)", "instantiation_error");
errors("atom_concat(X, bar, Z)", "instantiation_error");
}
#[test]
fn bound_non_atom_is_type_error() {
errors("atom_concat(123, foo, _)", "type_error(atom, 123)");
errors("atom_concat(foo, 456, _)", "type_error(atom, 456)");
}
#[test]
fn split_mode_works_inside_a_compiled_clause_body() {
let c = compile("prefix(P, W) :- atom_concat(P, _, W).\n");
let (out, code) = c.query("prefix(P, abc)", &[]);
assert_eq!(code, 1, "{out}");
assert_eq!(out.trim_end(), "P = \nP = a\nP = ab\nP = abc",);
}
#[test]
fn split_mode_reachable_via_metacall() {
check(
"call(atom_concat(A, B, ab))",
"A = \nB = ab\nA = a\nB = b\nA = ab\nB =",
);
}