use logicaffeine_kernel::prelude::StandardLibrary;
use logicaffeine_kernel::{eval_bool, infer_type, is_subtype, native_decide, normalize, Context, 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 std_ctx() -> Context {
let mut ctx = Context::new();
StandardLibrary::register(&mut ctx);
ctx
}
fn nat_lit(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 {
app(app(app(g("Eq"), g("Nat")), a), b)
}
fn dec_eq_nat(a: Term, b: Term) -> Term {
app(app(g("decEqNat"), a), b)
}
#[test]
fn reduce_bool_and_of_reduce_bool_are_registered() {
let ctx = std_ctx();
assert!(infer_type(&ctx, &g("reduceBool")).is_ok(), "reduceBool must be typed");
assert!(infer_type(&ctx, &g("ofReduceBool")).is_ok(), "ofReduceBool must be typed");
}
#[test]
fn evaluator_agrees_with_normalize() {
let ctx = std_ctx();
for a in 0..5usize {
for b in 0..5usize {
let decide_app =
app(app(g("decide"), eqn(nat_lit(a), nat_lit(b))), dec_eq_nat(nat_lit(a), nat_lit(b)));
let eval = eval_bool(&ctx, &decide_app);
let norm = normalize(&ctx, &decide_app);
let expected = a == b;
assert_eq!(eval, Some(expected), "evaluator: {a} = {b} should be {expected}");
let norm_bool = match norm {
ref t if *t == g("true") => true,
ref t if *t == g("false") => false,
other => panic!("normalize gave a non-Bool: {other}"),
};
assert_eq!(norm_bool, expected, "normalize: {a} = {b} should be {expected}");
assert_eq!(eval, Some(norm_bool), "evaluator must AGREE with normalize on {a}={b}");
}
}
}
#[test]
fn native_decide_proves_arithmetic_and_typechecks_via_the_hook() {
let ctx = std_ctx();
let prop = eqn(nat_lit(7), nat_lit(7));
let proof = native_decide(&ctx, &prop, &dec_eq_nat(nat_lit(7), nat_lit(7)))
.expect("native_decide should prove 7 = 7");
let ty = infer_type(&ctx, &proof).expect("the native_decide proof must type-check");
assert!(is_subtype(&ctx, &ty, &prop), "the proof has type Eq Nat 7 7");
}
#[test]
fn native_decide_is_fail_closed() {
let ctx = std_ctx();
let prop = eqn(nat_lit(7), nat_lit(8));
assert!(
native_decide(&ctx, &prop, &dec_eq_nat(nat_lit(7), nat_lit(8))).is_none(),
"native_decide must decline a false goal"
);
}
#[test]
fn native_decide_scales_past_kernel_decide_range() {
let ctx = std_ctx();
let prop = eqn(nat_lit(60), nat_lit(60));
let proof = native_decide(&ctx, &prop, &dec_eq_nat(nat_lit(60), nat_lit(60)))
.expect("native_decide should prove 60 = 60");
let ty = infer_type(&ctx, &proof).expect("large native_decide proof must type-check");
assert!(is_subtype(&ctx, &ty, &prop), "the proof has type Eq Nat 60 60");
}
fn int_lit(n: i64) -> Term {
Term::Lit(logicaffeine_kernel::Literal::Int(n))
}
#[test]
fn evaluator_computes_int_primitives_like_normalize() {
let ctx = std_ctx();
let pairs: [(i64, i64); 7] =
[(0, 0), (2, 5), (5, 2), (-3, 3), (7, 7), (i64::MAX, i64::MIN), (-1, 0)];
for op in ["le", "lt", "ge", "gt"] {
for (a, b) in pairs {
let t = app(app(g(op), int_lit(a)), int_lit(b));
let via_eval = eval_bool(&ctx, &t);
let via_normalize = match normalize(&ctx, &t) {
Term::Global(n) if n == "true" => Some(true),
Term::Global(n) if n == "false" => Some(false),
_ => None,
};
assert_eq!(via_eval, via_normalize, "{op}({a},{b})");
assert!(via_eval.is_some(), "{op}({a},{b}) must decide");
}
}
}
#[test]
fn evaluator_int_arithmetic_agrees_with_normalize() {
let ctx = std_ctx();
let cases: Vec<(Term, Option<bool>)> = vec![
(app(app(g("le"), app(app(g("add"), int_lit(2)), int_lit(3))), int_lit(5)), Some(true)),
(app(app(g("lt"), app(app(g("mul"), int_lit(4)), int_lit(5))), int_lit(20)), Some(false)),
(app(app(g("ge"), app(app(g("sub"), int_lit(10)), int_lit(3))), int_lit(7)), Some(true)),
(app(app(g("le"), app(app(g("div"), int_lit(9)), int_lit(2))), int_lit(4)), Some(true)),
(app(app(g("le"), app(app(g("mod"), int_lit(9)), int_lit(2))), int_lit(1)), Some(true)),
(
app(app(g("le"), app(app(g("add"), int_lit(i64::MAX)), int_lit(1))), int_lit(0)),
Some(false),
),
(
app(app(g("le"), app(app(g("div"), int_lit(1)), int_lit(0))), int_lit(0)),
None,
),
];
for (t, expected) in cases {
let via_eval = eval_bool(&ctx, &t);
let via_normalize = match normalize(&ctx, &t) {
Term::Global(n) if n == "true" => Some(true),
Term::Global(n) if n == "false" => Some(false),
_ => None,
};
assert_eq!(via_eval, via_normalize, "differential: {t}");
assert_eq!(via_eval, expected, "expected verdict: {t}");
}
}
#[test]
fn native_decide_proves_ground_int_comparison() {
let ctx = std_ctx();
let le25 = app(app(g("le"), int_lit(2)), int_lit(5));
let prop = app(app(app(g("Eq"), g("Bool")), le25.clone()), g("true"));
let inst = app(app(g("decEqBool"), le25), g("true"));
let proof = native_decide(&ctx, &prop, &inst).expect("le(2,5)=true must decide");
let ty = infer_type(&ctx, &proof).expect("proof must type-check");
assert!(is_subtype(&ctx, &ty, &prop));
}
#[test]
fn native_decide_declines_false_int_comparison() {
let ctx = std_ctx();
let lt52 = app(app(g("lt"), int_lit(5)), int_lit(2));
let prop = app(app(app(g("Eq"), g("Bool")), lt52.clone()), g("true"));
let inst = app(app(g("decEqBool"), lt52), g("true"));
assert!(
native_decide(&ctx, &prop, &inst).is_none(),
"a false comparison must be declined"
);
}