use logicaffeine_kernel::prelude::StandardLibrary;
use logicaffeine_kernel::{infer_type, is_subtype, Context, Term, Universe};
fn g(n: &str) -> Term {
Term::Global(n.to_string())
}
fn kvar(n: &str) -> Term {
Term::Var(n.to_string())
}
fn app(f: Term, x: Term) -> Term {
Term::App(Box::new(f), Box::new(x))
}
fn app2(f: Term, x: Term, y: Term) -> Term {
app(app(f, x), y)
}
fn lam(param: &str, ty: Term, body: Term) -> Term {
Term::Lambda { param: param.to_string(), param_type: Box::new(ty), body: Box::new(body) }
}
fn pi(param: &str, ty: Term, body: Term) -> Term {
Term::Pi { param: param.to_string(), param_type: Box::new(ty), body_type: Box::new(body) }
}
fn mtch(disc: Term, motive: Term, cases: Vec<Term>) -> Term {
Term::Match { discriminant: Box::new(disc), motive: Box::new(motive), cases }
}
fn boolt() -> Term {
g("Bool")
}
fn tt() -> Term {
g("true")
}
fn ff() -> Term {
g("false")
}
fn xor(a: Term, b: Term) -> Term {
app2(g("xor"), a, b)
}
fn and(a: Term, b: Term) -> Term {
app2(g("and2"), a, b)
}
fn poly1() -> Term {
g("Poly1")
}
fn mk(a: Term, b: Term) -> Term {
app2(g("mk"), a, b)
}
fn eqp(a: Term, b: Term) -> Term {
app(app2(g("Eq"), poly1(), a), b)
}
fn refl_p(x: Term) -> Term {
app2(g("refl"), poly1(), x)
}
fn padd(a: Term, b: Term) -> Term {
app2(g("padd"), a, b)
}
fn pmul(a: Term, b: Term) -> Term {
app2(g("pmul"), a, b)
}
fn pone() -> Term {
mk(tt(), ff())
}
fn px() -> Term {
mk(ff(), tt())
}
fn poly_ret() -> Term {
lam("_", poly1(), poly1())
}
fn gf2_poly_context() -> Context {
let mut ctx = Context::new();
StandardLibrary::register(&mut ctx);
let not_b = mtch(kvar("b"), lam("_", boolt(), boolt()), vec![ff(), tt()]);
let xor_body = lam(
"a",
boolt(),
lam("b", boolt(), mtch(kvar("a"), lam("_", boolt(), boolt()), vec![not_b, kvar("b")])),
);
ctx.add_definition("xor".to_string(), pi("a", boolt(), pi("b", boolt(), boolt())), xor_body);
let and_body = lam(
"a",
boolt(),
lam("b", boolt(), mtch(kvar("a"), lam("_", boolt(), boolt()), vec![kvar("b"), ff()])),
);
ctx.add_definition("and2".to_string(), pi("a", boolt(), pi("b", boolt(), boolt())), and_body);
ctx.add_inductive("Poly1", Term::Sort(Universe::Type(0)));
ctx.add_constructor("mk", "Poly1", pi("a", boolt(), pi("b", boolt(), poly1())));
let padd_inner = mtch(
kvar("q"),
poly_ret(),
vec![lam(
"a2",
boolt(),
lam("b2", boolt(), mk(xor(kvar("a1"), kvar("a2")), xor(kvar("b1"), kvar("b2")))),
)],
);
let padd_body = lam(
"p",
poly1(),
lam(
"q",
poly1(),
mtch(kvar("p"), poly_ret(), vec![lam("a1", boolt(), lam("b1", boolt(), padd_inner))]),
),
);
ctx.add_definition("padd".to_string(), pi("p", poly1(), pi("q", poly1(), poly1())), padd_body);
let hi = xor(
xor(and(kvar("a1"), kvar("b2")), and(kvar("b1"), kvar("a2"))),
and(kvar("b1"), kvar("b2")),
);
let pmul_inner = mtch(
kvar("q"),
poly_ret(),
vec![lam("a2", boolt(), lam("b2", boolt(), mk(and(kvar("a1"), kvar("a2")), hi)))],
);
let pmul_body = lam(
"p",
poly1(),
lam(
"q",
poly1(),
mtch(kvar("p"), poly_ret(), vec![lam("a1", boolt(), lam("b1", boolt(), pmul_inner))]),
),
);
ctx.add_definition("pmul".to_string(), pi("p", poly1(), pi("q", poly1(), poly1())), pmul_body);
ctx
}
fn proves(ctx: &Context, proof: &Term, law: &Term) -> bool {
match infer_type(ctx, proof) {
Ok(ty) => is_subtype(ctx, &ty, law) && is_subtype(ctx, law, &ty),
Err(_) => false,
}
}
#[test]
fn the_polynomial_ring_is_well_formed() {
let ctx = gf2_poly_context();
assert!(matches!(infer_type(&ctx, &g("padd")), Ok(Term::Pi { .. })), "padd : Poly1 → Poly1 → Poly1");
assert!(matches!(infer_type(&ctx, &g("pmul")), Ok(Term::Pi { .. })), "pmul : Poly1 → Poly1 → Poly1");
assert!(matches!(infer_type(&ctx, &pone()), Ok(_)), "one = mk true false : Poly1");
assert!(matches!(infer_type(&ctx, &px()), Ok(_)), "X = mk false true : Poly1");
}
#[test]
fn atom_is_one_as_a_genuine_polynomial_identity() {
let ctx = gf2_poly_context();
let atom = padd(padd(pone(), px()), px());
let law = eqp(atom.clone(), pone());
let proof = refl_p(pone());
assert!(proves(&ctx, &proof, &law), "(1+X)+X = 1 as a polynomial identity — kernel-proven");
}
#[test]
fn multiplicative_identity_for_all_polynomials() {
let ctx = gf2_poly_context();
let law = pi("p", poly1(), eqp(pmul(kvar("p"), pone()), kvar("p")));
let inner = |af: Term| {
mtch(
kvar("b"),
lam("b", boolt(), eqp(pmul(mk(af.clone(), kvar("b")), pone()), mk(af.clone(), kvar("b")))),
vec![refl_p(mk(af.clone(), tt())), refl_p(mk(af, ff()))],
)
};
let proof = lam(
"p",
poly1(),
mtch(
kvar("p"),
lam("p", poly1(), eqp(pmul(kvar("p"), pone()), kvar("p"))),
vec![lam(
"a",
boolt(),
lam(
"b",
boolt(),
mtch(
kvar("a"),
lam(
"a",
boolt(),
eqp(pmul(mk(kvar("a"), kvar("b")), pone()), mk(kvar("a"), kvar("b"))),
),
vec![inner(tt()), inner(ff())],
),
),
)],
),
);
assert!(proves(&ctx, &proof, &law), "∀p. p · one = p — kernel-proven for all polynomials");
}
#[test]
fn a_false_polynomial_law_is_rejected() {
let ctx = gf2_poly_context();
let law = pi("p", poly1(), eqp(pmul(kvar("p"), pone()), pone()));
let inner = |af: Term| {
mtch(
kvar("b"),
lam("b", boolt(), eqp(pmul(mk(af.clone(), kvar("b")), pone()), pone())),
vec![refl_p(mk(af.clone(), tt())), refl_p(mk(af, ff()))],
)
};
let proof = lam(
"p",
poly1(),
mtch(
kvar("p"),
lam("p", poly1(), eqp(pmul(kvar("p"), pone()), pone())),
vec![lam(
"a",
boolt(),
lam(
"b",
boolt(),
mtch(
kvar("a"),
lam("a", boolt(), eqp(pmul(mk(kvar("a"), kvar("b")), pone()), pone())),
vec![inner(tt()), inner(ff())],
),
),
)],
),
);
assert!(!proves(&ctx, &proof, &law), "a false polynomial law must be rejected by the kernel");
}