use logicaffeine_kernel::prelude::StandardLibrary;
use logicaffeine_kernel::{
derive_recursor, double_check, infer_type, normalize, Context, DoubleCheck, Term, Universe,
};
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 lam(p: &str, ty: Term, body: Term) -> Term {
Term::Lambda { param: p.to_string(), param_type: Box::new(ty), body: Box::new(body) }
}
fn pi(p: &str, ty: Term, body: Term) -> Term {
Term::Pi { param: p.to_string(), param_type: Box::new(ty), body_type: Box::new(body) }
}
fn var(n: &str) -> Term {
Term::Var(n.to_string())
}
fn std_ctx() -> Context {
let mut ctx = Context::new();
StandardLibrary::register(&mut ctx);
ctx
}
fn assert_recursor_two_kernel_verified(ctx: &Context, ind: &str) -> (Term, Term) {
let (ty, term) = derive_recursor(ctx, ind).expect("recursor derives");
assert!(
infer_type(ctx, &term).is_ok(),
"{ind}_rec must type-check in the main kernel: {:?}",
infer_type(ctx, &term)
);
assert!(matches!(ty, Term::Pi { .. }), "{ind}_rec's type must be a Π, got {ty}");
assert_eq!(
double_check(ctx, &term),
DoubleCheck::Agreed,
"{ind}_rec must be independently re-checked (two-kernel-verified), got {:?}",
double_check(ctx, &term)
);
(ty, term)
}
#[test]
fn derives_nat_recursor_two_kernel_verified() {
let ctx = std_ctx();
assert_recursor_two_kernel_verified(&ctx, "Nat");
}
#[test]
fn derives_bool_recursor_two_kernel_verified() {
let ctx = std_ctx();
assert_recursor_two_kernel_verified(&ctx, "Bool");
}
#[test]
fn derives_monomorphic_list_recursor_two_kernel_verified() {
let ctx = std_ctx();
assert_recursor_two_kernel_verified(&ctx, "EList");
}
#[test]
fn derives_false_recursor_is_ex_falso() {
let ctx = std_ctx();
let (ty, _term) = assert_recursor_two_kernel_verified(&ctx, "False");
if let Term::Pi { body_type, .. } = &ty {
assert!(
matches!(body_type.as_ref(), Term::Pi { .. }),
"False_rec = Π(P). Π(x:False). P x, got {ty}"
);
} else {
panic!("expected a Π, got {ty}");
}
}
#[test]
fn derives_three_constructor_enum_recursor() {
let mut ctx = std_ctx();
ctx.add_inductive("Light", Term::Sort(Universe::Type(0)));
for c in ["Red", "Yellow", "Green"] {
ctx.add_constructor(c, "Light", g("Light"));
}
assert_recursor_two_kernel_verified(&ctx, "Light");
}
#[test]
fn derives_binary_tree_recursor_threads_both_hypotheses() {
let mut ctx = std_ctx();
ctx.add_inductive("Tree", Term::Sort(Universe::Type(0)));
ctx.add_constructor("Leaf", "Tree", g("Tree"));
ctx.add_constructor("Node", "Tree", pi("l", g("Tree"), pi("r", g("Tree"), g("Tree"))));
let (_ty, term) = assert_recursor_two_kernel_verified(&ctx, "Tree");
let rec_calls = format!("{term}").matches("(rec ").count();
assert!(rec_calls >= 2, "Node's two IHs need two rec-calls, found {rec_calls}: {term}");
}
#[test]
fn derives_parametric_list_recursor_two_kernel_verified() {
let ctx = std_ctx();
let (ty, term) = assert_recursor_two_kernel_verified(&ctx, "TList");
assert!(matches!(ty, Term::Pi { .. }), "TList_rec opens with Π(A:Type). …, got {ty}");
assert!(format!("{term}").contains("(rec "), "the Cons tail needs a recursive call: {term}");
}
#[test]
fn parametric_list_recursor_computes_length() {
let ctx = std_ctx();
let (_ty, rec_term) = derive_recursor(&ctx, "TList").expect("derive");
let nat = g("Nat");
let tlist_nat = app(g("TList"), nat.clone());
let list = app(
app(app(g("TCons"), nat.clone()), g("Zero")),
app(g("TNil"), nat.clone()),
);
let motive = lam("_", tlist_nat.clone(), nat.clone());
let f_nil = g("Zero");
let f_cons = lam(
"h",
nat.clone(),
lam("t", tlist_nat.clone(), lam("ih", nat.clone(), app(g("Succ"), var("ih")))),
);
let applied = app(
app(app(app(app(rec_term, nat.clone()), motive), f_nil), f_cons),
list,
);
let result = normalize(&ctx, &applied);
let one = app(g("Succ"), g("Zero"));
assert_eq!(result, one, "length [Zero] must reduce to 1, got {result}");
}
#[test]
fn nat_recursor_computes_by_reduction() {
let ctx = std_ctx();
let (_ty, rec_term) = derive_recursor(&ctx, "Nat").expect("derive");
let nat = g("Nat");
let two = app(g("Succ"), app(g("Succ"), g("Zero")));
let const_nat_motive = lam("_", nat.clone(), nat.clone());
let base = g("Zero");
let step = lam("a", nat.clone(), lam("ih", nat.clone(), app(g("Succ"), var("ih"))));
let applied = app(app(app(app(rec_term, const_nat_motive), base), step), two.clone());
let result = normalize(&ctx, &applied);
assert_eq!(
result, two,
"the identity-shaped Nat recursor must reconstruct its argument, got {result}"
);
}
#[test]
fn nat_recursor_computes_a_real_function_predecessor() {
let ctx = std_ctx();
let (_ty, rec_term) = derive_recursor(&ctx, "Nat").expect("derive");
let nat = g("Nat");
let three = app(g("Succ"), app(g("Succ"), app(g("Succ"), g("Zero"))));
let two = app(g("Succ"), app(g("Succ"), g("Zero")));
let const_nat_motive = lam("_", nat.clone(), nat.clone());
let base = g("Zero");
let step = lam("a", nat.clone(), lam("ih", nat.clone(), var("a")));
let applied = app(app(app(app(rec_term, const_nat_motive), base), step), three);
let result = normalize(&ctx, &applied);
assert_eq!(result, two, "pred(3) must reduce to 2, got {result}");
}