use logicaffeine_kernel::prelude::StandardLibrary;
use logicaffeine_kernel::{
double_check, infer_type, is_subtype, normalize, BigInt, 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 arrow(a: Term, b: Term) -> Term {
Term::Pi { param: "_".to_string(), param_type: Box::new(a), body_type: Box::new(b) }
}
fn big(lit: BigInt) -> Term {
Term::Lit(Literal::BigInt(lit))
}
fn int(n: i64) -> Term {
Term::Lit(Literal::Int(n))
}
fn ten_pow(k: usize) -> BigInt {
BigInt::parse_decimal(&format!("1{}", "0".repeat(k))).unwrap()
}
fn ctx_with_ops() -> Context {
let mut ctx = Context::new();
StandardLibrary::register(&mut ctx);
let int = g("Int");
let binop = arrow(int.clone(), arrow(int.clone(), int.clone()));
for op in ["mul", "add", "sub"] {
ctx.add_declaration(op, binop.clone());
}
ctx.add_declaration("le", arrow(int.clone(), arrow(int, g("Bool"))));
ctx
}
#[test]
fn refl_on_huge_arithmetic() {
let ctx = ctx_with_ops();
let huge = big(ten_pow(30));
let prod = apps(g("mul"), &[huge.clone(), huge]);
let ten60 = big(ten_pow(60));
assert_eq!(normalize(&ctx, &prod), ten60.clone(), "10^30 * 10^30 = 10^60 by reduction");
let refl_proof = apps(g("refl"), &[g("Int"), ten60.clone()]);
let claim = apps(g("Eq"), &[g("Int"), prod, ten60]);
let proof_ty = infer_type(&ctx, &refl_proof).expect("refl type-checks");
assert!(
is_subtype(&ctx, &proof_ty, &claim),
"refl must prove 10^30*10^30 = 10^60 by computation.\n proof_ty = {proof_ty}\n claim = {claim}"
);
match double_check(&ctx, &refl_proof) {
DoubleCheck::Agreed => {}
other => panic!("both kernels must agree on the bignum refl proof, got {other:?}"),
}
}
#[test]
fn i64_overflow_promotes_to_bigint() {
let ctx = ctx_with_ops();
let e10 = int(10_000_000_000); let prod = apps(g("mul"), &[e10.clone(), e10]);
assert_eq!(normalize(&ctx, &prod), big(ten_pow(20)), "10^10 * 10^10 promotes to BigInt 10^20");
}
#[test]
fn results_are_canonicalized_no_bigint_that_fits_i64() {
let ctx = ctx_with_ops();
let a = big(ten_pow(20));
let diff = apps(g("sub"), &[a.clone(), a]);
assert_eq!(normalize(&ctx, &diff), int(0), "10^20 - 10^20 = Int(0), canonicalized");
let inner = apps(g("sub"), &[big(ten_pow(20)), int(5)]); let outer = apps(g("sub"), &[big(ten_pow(20)), inner]);
assert_eq!(normalize(&ctx, &outer), int(5), "mixed Int/BigInt arithmetic canonicalizes to Int(5)");
}
#[test]
fn bignum_comparison_decides_by_computation() {
let ctx = ctx_with_ops();
let lo = big(ten_pow(30));
let hi = big(ten_pow(60));
assert_eq!(normalize(&ctx, &apps(g("le"), &[lo.clone(), hi.clone()])), g("true"), "10^30 ≤ 10^60");
assert_eq!(normalize(&ctx, &apps(g("le"), &[hi, lo])), g("false"), "¬(10^60 ≤ 10^30)");
}
#[test]
fn small_arithmetic_stays_on_the_fast_i64_path() {
let ctx = ctx_with_ops();
assert_eq!(normalize(&ctx, &apps(g("mul"), &[int(2), int(3)])), int(6), "2 * 3 = Int(6)");
assert_eq!(normalize(&ctx, &apps(g("add"), &[int(40), int(2)])), int(42), "40 + 2 = Int(42)");
}
#[cfg(feature = "serde")]
mod serde_format {
use super::*;
use logicaffeine_kernel::certificate::Certificate;
#[test]
fn existing_literal_encoding_is_byte_unchanged() {
assert_eq!(serde_json::to_string(&Literal::Int(5)).unwrap(), r#"{"Int":5}"#);
assert_eq!(serde_json::to_string(&Literal::Text("x".into())).unwrap(), r#"{"Text":"x"}"#);
}
#[test]
fn old_format_certificate_still_deserializes() {
let json = r#"{
"proof_term": {"Lit":{"Int":42}},
"claimed_type": {"Global":"Int"},
"prelude_version": "logos-coc-1"
}"#;
let cert: Certificate = serde_json::from_str(json).expect("old-format certificate loads");
assert_eq!(cert.proof_term, Term::Lit(Literal::Int(42)));
assert_eq!(cert.claimed_type, Term::Global("Int".to_string()));
}
#[test]
fn bigint_literal_round_trips_as_decimal_string() {
let lit = Literal::BigInt(ten_pow(60));
let json = serde_json::to_string(&lit).unwrap();
assert_eq!(json, format!(r#"{{"BigInt":"1{}"}}"#, "0".repeat(60)));
let back: Literal = serde_json::from_str(&json).unwrap();
assert_eq!(back, lit, "BigInt literal round-trips through serde");
}
#[test]
fn nat_literal_round_trips_as_decimal_string() {
let lit = Literal::Nat(BigInt::from_i64(1_000_000));
let json = serde_json::to_string(&lit).unwrap();
assert_eq!(json, r#"{"Nat":"1000000"}"#);
let back: Literal = serde_json::from_str(&json).unwrap();
assert_eq!(back, lit, "Nat literal round-trips through serde");
}
}