use logicaffeine_proof::tactic::ProofState;
use logicaffeine_proof::{ProofExpr, ProofTerm};
fn k(n: &str) -> ProofTerm {
ProofTerm::Constant(n.to_string())
}
fn v(n: &str) -> ProofTerm {
ProofTerm::Variable(n.to_string())
}
fn p(name: &str, args: Vec<ProofTerm>) -> ProofExpr {
ProofExpr::Predicate { name: name.to_string(), args, world: None }
}
fn implies(l: ProofExpr, r: ProofExpr) -> ProofExpr {
ProofExpr::Implies(Box::new(l), Box::new(r))
}
fn and(l: ProofExpr, r: ProofExpr) -> ProofExpr {
ProofExpr::And(Box::new(l), Box::new(r))
}
fn or(l: ProofExpr, r: ProofExpr) -> ProofExpr {
ProofExpr::Or(Box::new(l), Box::new(r))
}
fn forall(var: &str, body: ProofExpr) -> ProofExpr {
ProofExpr::ForAll { variable: var.to_string(), body: Box::new(body) }
}
fn exists(var: &str, body: ProofExpr) -> ProofExpr {
ProofExpr::Exists { variable: var.to_string(), body: Box::new(body) }
}
#[test]
fn intro_then_assumption_proves_self_implication() {
let goal = implies(p("man", vec![k("Socrates")]), p("man", vec![k("Socrates")]));
let mut st = ProofState::start(vec![], goal);
st.intro("h").unwrap();
st.assumption().unwrap();
let r = st.qed().unwrap();
assert!(r.verified, "intro;assumption: {:?}", r.verification_error);
}
#[test]
fn nested_intro_proves_constant_implication() {
let goal = implies(
p("rains", vec![k("Tuesday")]),
implies(p("snows", vec![k("Tuesday")]), p("rains", vec![k("Tuesday")])),
);
let mut st = ProofState::start(vec![], goal);
st.intro("hp").unwrap();
st.intro("hq").unwrap();
st.exact("hp").unwrap();
let r = st.qed().unwrap();
assert!(r.verified, "K combinator: {:?}", r.verification_error);
}
#[test]
fn universal_intro_proves_reflexive_implication() {
let goal = forall("z", implies(p("mortal", vec![v("z")]), p("mortal", vec![v("z")])));
let mut st = ProofState::start(vec![], goal);
st.intro("z").unwrap();
st.intro("h").unwrap();
st.assumption().unwrap();
let r = st.qed().unwrap();
assert!(r.verified, "∀-intro reflexive: {:?}", r.verification_error);
}
#[test]
fn engine_proves_same_universal_via_prove_certify_check() {
use logicaffeine_proof::verify::prove_certify_check;
let goal = forall("z", implies(p("mortal", vec![v("z")]), p("mortal", vec![v("z")])));
let r = prove_certify_check(&[], &goal);
assert!(r.verified, "engine ∀-proof: {:?}", r.verification_error);
}
#[test]
fn check_derivation_on_engine_universal_tree() {
use logicaffeine_proof::verify::check_derivation;
use logicaffeine_proof::BackwardChainer;
let goal = forall("z", implies(p("mortal", vec![v("z")]), p("mortal", vec![v("z")])));
let mut eng = BackwardChainer::new();
let tree = eng.prove(goal.clone()).unwrap();
let r = check_derivation(&[], &goal, tree);
assert!(r.verified, "check_derivation on engine ∀-tree: {:?}", r.verification_error);
}
#[test]
fn split_proves_conjunction_from_premises() {
let premises = vec![p("happy", vec![k("Bob")]), p("tall", vec![k("Bob")])];
let goal = and(p("happy", vec![k("Bob")]), p("tall", vec![k("Bob")]));
let mut st = ProofState::start(premises, goal);
st.split().unwrap();
st.assumption().unwrap(); st.assumption().unwrap(); let r = st.qed().unwrap();
assert!(r.verified, "split: {:?}", r.verification_error);
}
#[test]
fn left_proves_disjunction() {
let premises = vec![p("happy", vec![k("Bob")])];
let goal = or(p("happy", vec![k("Bob")]), p("sad", vec![k("Bob")]));
let mut st = ProofState::start(premises, goal);
st.left().unwrap();
st.assumption().unwrap();
let r = st.qed().unwrap();
assert!(r.verified, "left: {:?}", r.verification_error);
}
#[test]
fn exists_intro_with_witness() {
let premises = vec![p("mortal", vec![k("Socrates")])];
let goal = exists("x", p("mortal", vec![v("x")]));
let mut st = ProofState::start(premises, goal);
st.exists(k("Socrates")).unwrap();
st.assumption().unwrap();
let r = st.qed().unwrap();
assert!(r.verified, "exists: {:?}", r.verification_error);
}
#[test]
fn apply_modus_ponens_then_assumption() {
let rule = implies(p("man", vec![k("Socrates")]), p("mortal", vec![k("Socrates")]));
let premises = vec![rule.clone(), p("man", vec![k("Socrates")])];
let goal = p("mortal", vec![k("Socrates")]);
let mut st = ProofState::start(premises, goal);
st.apply(&rule).unwrap();
st.assumption().unwrap(); let r = st.qed().unwrap();
assert!(r.verified, "apply MP: {:?}", r.verification_error);
}
#[test]
fn auto_closes_with_backward_chainer() {
let rule = forall(
"x",
implies(p("man", vec![v("x")]), p("mortal", vec![v("x")])),
);
let premises = vec![rule, p("man", vec![k("Socrates")])];
let goal = p("mortal", vec![k("Socrates")]);
let mut st = ProofState::start(premises, goal);
st.auto().unwrap();
let r = st.qed().unwrap();
assert!(r.verified, "auto: {:?}", r.verification_error);
}
#[test]
fn incomplete_proof_is_rejected() {
let goal = implies(p("P", vec![k("a")]), p("Q", vec![k("a")]));
let mut st = ProofState::start(vec![], goal);
st.intro("h").unwrap();
assert!(st.qed().is_err(), "qed with an open goal must fail");
assert_eq!(st.open_goals(), 1);
}