use logicaffeine_proof::tactic::combinators::{
all_goals, assumption, first, intro, repeat, seq, split, then_all, try_,
};
use logicaffeine_proof::tactic::ProofState;
use logicaffeine_proof::{ProofExpr, ProofTerm};
fn k(n: &str) -> ProofTerm {
ProofTerm::Constant(n.to_string())
}
fn pr(name: &str, who: &str) -> ProofExpr {
ProofExpr::Predicate { name: name.to_string(), args: vec![k(who)], world: None }
}
fn and(l: ProofExpr, r: ProofExpr) -> ProofExpr {
ProofExpr::And(Box::new(l), Box::new(r))
}
fn implies(l: ProofExpr, r: ProofExpr) -> ProofExpr {
ProofExpr::Implies(Box::new(l), Box::new(r))
}
#[test]
fn seq_runs_tactics_in_order() {
let premises = vec![pr("happy", "Bob"), pr("tall", "Bob")];
let goal = and(pr("happy", "Bob"), pr("tall", "Bob"));
let mut st = ProofState::start(premises, goal);
st.run(&seq(vec![split(), assumption(), assumption()])).unwrap();
let r = st.qed().unwrap();
assert!(r.verified, "seq: {:?}", r.verification_error);
}
#[test]
fn first_commits_the_first_applicable() {
let premises = vec![pr("happy", "Bob")];
let goal = pr("happy", "Bob");
let mut st = ProofState::start(premises, goal);
st.run(&first(vec![split(), assumption()])).unwrap();
let r = st.qed().unwrap();
assert!(r.verified, "first: {:?}", r.verification_error);
}
#[test]
fn first_fails_when_nothing_applies() {
let goal = pr("happy", "Bob");
let mut st = ProofState::start(vec![], goal);
assert!(st.run(&first(vec![split(), assumption()])).is_err());
}
#[test]
fn try_is_a_noop_on_failure() {
let premises = vec![pr("happy", "Bob")];
let goal = pr("happy", "Bob");
let mut st = ProofState::start(premises, goal);
st.run(&seq(vec![try_(split()), assumption()])).unwrap();
assert_eq!(st.open_goals(), 0);
let r = st.qed().unwrap();
assert!(r.verified, "try: {:?}", r.verification_error);
}
#[test]
fn repeat_decomposes_a_nested_conjunction() {
let premises = vec![pr("happy", "Bob"), pr("tall", "Bob"), pr("smart", "Bob")];
let goal = and(and(pr("happy", "Bob"), pr("tall", "Bob")), pr("smart", "Bob"));
let mut st = ProofState::start(premises, goal);
st.run(&repeat(first(vec![split(), assumption()]))).unwrap();
assert_eq!(st.open_goals(), 0, "repeat should close every goal");
let r = st.qed().unwrap();
assert!(r.verified, "repeat: {:?}", r.verification_error);
}
#[test]
fn all_goals_applies_to_every_subgoal() {
let premises = vec![pr("happy", "Bob"), pr("tall", "Bob")];
let goal = and(pr("happy", "Bob"), pr("tall", "Bob"));
let mut st = ProofState::start(premises, goal);
st.run(&seq(vec![split(), all_goals(assumption())])).unwrap();
let r = st.qed().unwrap();
assert!(r.verified, "all_goals: {:?}", r.verification_error);
}
#[test]
fn then_all_is_the_semicolon_combinator() {
let premises = vec![pr("happy", "Bob"), pr("tall", "Bob")];
let goal = and(pr("happy", "Bob"), pr("tall", "Bob"));
let mut st = ProofState::start(premises, goal);
st.run(&then_all(split(), assumption())).unwrap();
let r = st.qed().unwrap();
assert!(r.verified, "then_all: {:?}", r.verification_error);
}
#[test]
fn composed_script_intro_then_split() {
let premises = vec![pr("happy", "Bob"), pr("tall", "Bob")];
let goal = implies(pr("rains", "Day"), and(pr("happy", "Bob"), pr("tall", "Bob")));
let mut st = ProofState::start(premises, goal);
st.run(&seq(vec![intro("h"), then_all(split(), assumption())])).unwrap();
let r = st.qed().unwrap();
assert!(r.verified, "composed: {:?}", r.verification_error);
}