use logicaffeine_kernel::interface::Repl;
use logicaffeine_kernel::prelude::StandardLibrary;
use logicaffeine_kernel::{
double_check, eval_bool_tree, infer_type, is_subtype, native_compile_decide, native_decide,
normalize, Context, DoubleCheck, Literal, Term,
};
fn g(n: &str) -> Term {
Term::Global(n.to_string())
}
fn app(f: Term, x: Term) -> Term {
Term::App(Box::new(f), Box::new(x))
}
fn apps(f: Term, xs: &[Term]) -> Term {
xs.iter().fold(f, |a, x| app(a, x.clone()))
}
fn std_ctx() -> Context {
let mut ctx = Context::new();
StandardLibrary::register(&mut ctx);
ctx
}
fn nat(n: usize) -> Term {
let mut t = g("Zero");
for _ in 0..n {
t = app(g("Succ"), t);
}
t
}
fn eqn(a: Term, b: Term) -> Term {
apps(g("Eq"), &[g("Nat"), a, b])
}
fn normalize_bool(ctx: &Context, t: &Term) -> Option<bool> {
match normalize(ctx, t) {
Term::Global(n) if n == "true" => Some(true),
Term::Global(n) if n == "false" => Some(false),
_ => None,
}
}
#[track_caller]
fn assert_all_agree(ctx: &Context, t: &Term, expected: Option<bool>) {
let compiled = native_compile_decide(ctx, t);
let tree = eval_bool_tree(ctx, t);
let norm = normalize_bool(ctx, t);
assert_eq!(compiled, tree, "compiled must equal the tree-walker for {t}");
assert_eq!(compiled, norm, "compiled must equal normalize for {t}");
assert_eq!(compiled, expected, "compiled verdict for {t}");
}
#[test]
fn compiled_dec_eq_nat_matches_tree_and_normalize() {
let ctx = std_ctx();
for a in 0..8usize {
for b in 0..8usize {
let dec = apps(g("decide"), &[eqn(nat(a), nat(b)), apps(g("decEqNat"), &[nat(a), nat(b)])]);
assert_all_agree(&ctx, &dec, Some(a == b));
}
}
}
fn even_ctx() -> Context {
let mut repl = Repl::new();
repl.execute(
"Definition boolNot : Bool -> Bool := fun b : Bool => \
match b with | true => false | false => true end.",
)
.expect("define boolNot");
repl.execute(
"Definition even : Nat -> Bool := fun n : Nat => \
match n with | Zero => true | Succ m => boolNot (even m) end.",
)
.expect("define even");
repl.context().clone()
}
#[test]
fn compiled_even_matches_tree_and_normalize() {
let ctx = even_ctx();
for n in 0..14usize {
assert_all_agree(&ctx, &app(g("even"), nat(n)), Some(n % 2 == 0));
}
}
fn le_nat_ctx() -> Context {
let mut repl = Repl::new();
repl.execute(
"Definition le_nat : Nat -> Nat -> Bool := fun a : Nat => fun b : Nat => \
match a with | Zero => true | Succ a2 => \
match b with | Zero => false | Succ b2 => le_nat a2 b2 end end.",
)
.expect("define le_nat");
repl.context().clone()
}
#[test]
fn compiled_le_nat_matches_tree_and_normalize() {
let ctx = le_nat_ctx();
for a in 0..7usize {
for b in 0..7usize {
assert_all_agree(&ctx, &apps(g("le_nat"), &[nat(a), nat(b)]), Some(a <= b));
}
}
}
#[test]
fn compiled_decision_delegating_across_definitions_agrees() {
let mut repl = Repl::new();
repl.execute(
"Definition boolNot : Bool -> Bool := fun b : Bool => \
match b with | true => false | false => true end.",
)
.expect("define boolNot");
repl.execute(
"Definition even : Nat -> Bool := fun n : Nat => \
match n with | Zero => true | Succ m => boolNot (even m) end.",
)
.expect("define even");
repl.execute("Definition evenSucc : Nat -> Bool := fun n : Nat => even (Succ n).")
.expect("define evenSucc");
let ctx = repl.context().clone();
for n in 0..10usize {
assert_all_agree(&ctx, &app(g("evenSucc"), nat(n)), Some((n + 1) % 2 == 0));
}
}
#[test]
fn native_decide_proves_a_recursive_decision_two_kernel() {
let ctx = std_ctx();
let prop = eqn(nat(6), nat(6));
let inst = apps(g("decEqNat"), &[nat(6), nat(6)]);
let proof = native_decide(&ctx, &prop, &inst).expect("native_decide proves 6 = 6");
let ty = infer_type(&ctx, &proof).expect("proof type-checks via the reduceBool hook");
assert!(is_subtype(&ctx, &ty, &prop), "proof : Eq Nat 6 6");
match double_check(&ctx, &proof) {
DoubleCheck::Agreed => {}
other => panic!("both kernels must certify the native_decide proof, got {other:?}"),
}
}
#[test]
fn native_decide_declines_a_false_recursive_decision() {
let ctx = std_ctx();
let prop = eqn(nat(6), nat(7));
let inst = apps(g("decEqNat"), &[nat(6), nat(7)]);
assert!(native_decide(&ctx, &prop, &inst).is_none(), "must decline 6 = 7");
}
#[test]
fn compiler_declines_non_bool_and_open_terms() {
let ctx = even_ctx();
assert_eq!(native_compile_decide(&ctx, &g("Zero")), None, "Zero is not a Bool decision");
assert_eq!(
native_compile_decide(&ctx, &app(g("even"), Term::Var("n".to_string()))),
None,
"a free variable is not decidable"
);
assert_eq!(native_compile_decide(&ctx, &g("Bool")), None, "an inductive type is not a decision");
}
#[test]
fn compiled_and_tree_agree_on_a_large_recursive_decision() {
let ctx = even_ctx();
assert_all_agree(&ctx, &app(g("even"), nat(40)), Some(true));
assert_all_agree(&ctx, &app(g("even"), nat(41)), Some(false));
}
#[test]
fn compiled_decide_handles_ground_arithmetic_too() {
let ctx = std_ctx();
let i = |n: i64| Term::Lit(Literal::Int(n));
let cases: &[(Term, Option<bool>)] = &[
(apps(g("le"), &[apps(g("add"), &[i(2), i(3)]), i(5)]), Some(true)),
(apps(g("lt"), &[apps(g("mul"), &[i(4), i(5)]), i(20)]), Some(false)),
(apps(g("ge"), &[apps(g("sub"), &[i(10), i(3)]), i(7)]), Some(true)),
];
for (t, expected) in cases {
assert_all_agree(&ctx, t, *expected);
}
}