use logicaffeine_kernel::interface::TermParser;
use logicaffeine_kernel::prelude::StandardLibrary;
use logicaffeine_kernel::{
double_check, infer_type, surface_elaborate, surface_elaborate_against, Context, DoubleCheck,
Term, Universe, ANON_CTOR_MARKER, DOT_MARKER,
};
fn g(n: &str) -> Term {
Term::Global(n.to_string())
}
fn v(n: &str) -> Term {
Term::Var(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 ty0() -> Term {
Term::Sort(Universe::Type(0))
}
fn prod_ctx() -> Context {
let mut ctx = Context::new();
StandardLibrary::register(&mut ctx);
ctx.add_structure("Prod", &[("A", ty0()), ("B", ty0())], &[("fst", v("A")), ("snd", v("B"))]);
let prod_nat_bool = apps(g("Prod"), &[g("Nat"), g("Bool")]);
let p_val = apps(g("Prod_mk"), &[g("Nat"), g("Bool"), g("Zero"), g("true")]);
ctx.add_definition("p".to_string(), prod_nat_bool, p_val);
ctx
}
#[test]
fn parses_anonymous_constructor_to_the_marker() {
let t = TermParser::parse("⟨Zero, true⟩").expect("parses");
assert_eq!(t, apps(g(ANON_CTOR_MARKER), &[g("Zero"), g("true")]));
}
#[test]
fn parses_empty_and_singleton_anonymous_constructors() {
assert_eq!(TermParser::parse("⟨⟩").expect("parses"), g(ANON_CTOR_MARKER));
assert_eq!(TermParser::parse("⟨Zero⟩").expect("parses"), app(g(ANON_CTOR_MARKER), g("Zero")));
}
#[test]
fn parses_dot_projection_to_the_marker() {
let t = TermParser::parse("p.fst").expect("parses");
assert_eq!(t, apps(g(DOT_MARKER), &[g("p"), g("fst")]));
}
#[test]
fn dot_binds_tighter_than_application() {
let t = TermParser::parse("f x.fst").expect("parses");
assert_eq!(t, app(g("f"), apps(g(DOT_MARKER), &[g("x"), g("fst")])));
}
#[test]
fn dot_chains_left_associatively() {
let t = TermParser::parse("p.fst.snd").expect("parses");
let inner = apps(g(DOT_MARKER), &[g("p"), g("fst")]);
assert_eq!(t, apps(g(DOT_MARKER), &[inner, g("snd")]));
}
#[test]
fn a_bare_trailing_dot_is_not_a_projection() {
let t = TermParser::parse("Zero.").expect("parses");
assert_eq!(t, g("Zero"), "a trailing `.` is a terminator, projected term must be just Zero");
}
#[test]
fn anonymous_constructor_elaborates_against_expected_type() {
let ctx = prod_ctx();
let expected = apps(g("Prod"), &[g("Nat"), g("Bool")]);
let parsed = TermParser::parse("⟨Zero, true⟩").expect("parses");
let elab = surface_elaborate_against(&ctx, &parsed, Some(&expected)).expect("elaborates");
assert_eq!(elab, apps(g("Prod_mk"), &[g("Nat"), g("Bool"), g("Zero"), g("true")]));
assert_eq!(infer_type(&ctx, &elab).unwrap(), expected, "⟨Zero,true⟩ : Prod Nat Bool");
match double_check(&ctx, &elab) {
DoubleCheck::Agreed => {}
other => panic!("both kernels must certify the lowered ⟨…⟩, got {other:?}"),
}
}
#[test]
fn dot_notation_elaborates_to_projections() {
let ctx = prod_ctx();
let fst = surface_elaborate(&ctx, &TermParser::parse("p.fst").unwrap()).expect("p.fst");
assert_eq!(fst, apps(g("Prod_fst"), &[g("Nat"), g("Bool"), g("p")]));
assert_eq!(infer_type(&ctx, &fst).unwrap(), g("Nat"), "p.fst : Nat");
let snd = surface_elaborate(&ctx, &TermParser::parse("p.snd").unwrap()).expect("p.snd");
assert_eq!(snd, apps(g("Prod_snd"), &[g("Nat"), g("Bool"), g("p")]));
assert_eq!(infer_type(&ctx, &snd).unwrap(), g("Bool"), "p.snd : Bool");
}
#[test]
fn nested_anonymous_constructor_resolves_with_flowed_expected_type() {
let ctx = prod_ctx();
let inner_ty = apps(g("Prod"), &[g("Nat"), g("Bool")]);
let expected = apps(g("Prod"), &[inner_ty.clone(), g("Bool")]);
let parsed = TermParser::parse("⟨⟨Zero, true⟩, false⟩").expect("parses");
let elab = surface_elaborate_against(&ctx, &parsed, Some(&expected)).expect("elaborates");
let inner_val = apps(g("Prod_mk"), &[g("Nat"), g("Bool"), g("Zero"), g("true")]);
assert_eq!(
elab,
apps(g("Prod_mk"), &[inner_ty, g("Bool"), inner_val, g("false")]),
"the nested ⟨…⟩ lowered against its flowed-in field type"
);
assert_eq!(infer_type(&ctx, &elab).unwrap(), expected);
}
#[test]
fn dot_on_an_anonymous_constructor_projects_it() {
let ctx = prod_ctx();
let fst = surface_elaborate(&ctx, &TermParser::parse("p.fst").unwrap()).unwrap();
assert_eq!(
logicaffeine_kernel::normalize(&ctx, &fst),
g("Zero"),
"p.fst reduces to the first component"
);
}
#[test]
fn anonymous_constructor_without_expected_type_is_rejected() {
let ctx = prod_ctx();
let parsed = TermParser::parse("⟨Zero, true⟩").expect("parses");
assert!(
surface_elaborate(&ctx, &parsed).is_err(),
"an ambiguous ⟨…⟩ with no expected type must be rejected, not guessed"
);
}
#[test]
fn dot_on_an_unknown_field_is_rejected() {
let ctx = prod_ctx();
let parsed = TermParser::parse("p.nope").expect("parses");
assert!(
surface_elaborate(&ctx, &parsed).is_err(),
"projecting an unknown field must fail, not silently produce a bad term"
);
}