use logicaffeine_kernel::prelude::StandardLibrary;
use logicaffeine_kernel::{instantiate, normalize, unify, unify_in, Context, MetaCtx, Term};
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 std_ctx() -> Context {
let mut ctx = Context::new();
StandardLibrary::register(&mut ctx);
ctx
}
fn lctx(pairs: &[(&str, Term)]) -> Vec<(String, Term)> {
pairs.iter().map(|(n, t)| (n.to_string(), t.clone())).collect()
}
fn solved(ctx: &Context, mctx: &MetaCtx, t: &Term) -> Term {
normalize(ctx, &instantiate(t, mctx))
}
#[test]
fn pattern_solves_a_constant_function() {
let ctx = std_ctx();
let mut m = MetaCtx::new();
let mv = m.fresh();
let l = lctx(&[("x", g("Nat"))]);
assert!(unify_in(&ctx, &mut m, &l, &app(mv.clone(), v("x")), &g("Nat")));
assert_eq!(solved(&ctx, &m, &app(mv, g("Zero"))), g("Nat"));
}
#[test]
fn pattern_solves_the_identity() {
let ctx = std_ctx();
let mut m = MetaCtx::new();
let mv = m.fresh();
let l = lctx(&[("x", g("Nat"))]);
assert!(unify_in(&ctx, &mut m, &l, &app(mv.clone(), v("x")), &v("x")));
assert_eq!(solved(&ctx, &m, &app(mv, g("Zero"))), g("Zero"));
}
#[test]
fn pattern_solves_an_argument_permutation() {
let ctx = std_ctx();
let mut m = MetaCtx::new();
let mv = m.fresh();
let l = lctx(&[("x", g("Nat")), ("y", g("Bool"))]);
let lhs = app(app(mv.clone(), v("x")), v("y"));
let rhs = app(app(g("f"), v("y")), v("x"));
assert!(unify_in(&ctx, &mut m, &l, &lhs, &rhs));
let applied = app(app(mv, g("Zero")), g("true"));
assert_eq!(solved(&ctx, &m, &applied), app(app(g("f"), g("true")), g("Zero")));
}
#[test]
fn pattern_respects_the_occurs_check() {
let ctx = std_ctx();
let mut m = MetaCtx::new();
let mv = m.fresh();
let l = lctx(&[("x", g("Nat"))]);
let lhs = app(mv.clone(), v("x"));
let rhs = app(g("Succ"), app(mv, v("x")));
assert!(!unify_in(&ctx, &mut m, &l, &lhs, &rhs), "?M x := Succ (?M x) is cyclic");
}
#[test]
fn pattern_rejects_an_out_of_scope_right_hand_side() {
let ctx = std_ctx();
let mut m = MetaCtx::new();
let mv = m.fresh();
let l = lctx(&[("x", g("Nat")), ("y", g("Nat"))]);
assert!(
!unify_in(&ctx, &mut m, &l, &app(mv, v("x")), &v("y")),
"cannot abstract `x` and keep a free `y`"
);
}
#[test]
fn repeated_arguments_are_not_a_pattern() {
let ctx = std_ctx();
let mut m = MetaCtx::new();
let mv = m.fresh();
let l = lctx(&[("x", g("Nat"))]);
assert!(!unify_in(&ctx, &mut m, &l, &app(app(mv, v("x")), v("x")), &v("x")));
}
#[test]
fn a_non_variable_argument_is_not_a_pattern() {
let ctx = std_ctx();
let mut m = MetaCtx::new();
let mv = m.fresh();
let l = lctx(&[("x", g("Nat"))]);
assert!(!unify_in(&ctx, &mut m, &l, &app(mv, g("Zero")), &g("Nat")));
}
#[test]
fn pattern_unification_solves_what_first_order_cannot() {
let ctx = std_ctx();
let mut first_order = MetaCtx::new();
let mv1 = first_order.fresh();
assert!(
!unify(&ctx, &mut first_order, &app(mv1, v("x")), &g("Nat")),
"first-order unification cannot solve a metavariable application"
);
let mut higher_order = MetaCtx::new();
let mv2 = higher_order.fresh();
let l = lctx(&[("x", g("Nat"))]);
assert!(
unify_in(&ctx, &mut higher_order, &l, &app(mv2, v("x")), &g("Nat")),
"pattern unification solves it"
);
}