use std::collections::HashSet;
use crate::engine::cert_congruence_path;
use crate::unify::{apply_subst_to_expr, match_term_pattern, Substitution};
use crate::{DerivationTree, InferenceRule, ProofExpr, ProofTerm};
pub(crate) fn crush_prove(premises: &[ProofExpr], goal: &ProofExpr) -> Option<DerivationTree> {
let mut known: Vec<(ProofExpr, DerivationTree)> = Vec::new();
let mut eq_lemmas: Vec<&ProofExpr> = Vec::new();
for prem in premises {
if is_forall_eq_lemma(prem) {
eq_lemmas.push(prem);
} else {
known.push((prem.clone(), DerivationTree::leaf(prem.clone(), InferenceRule::PremiseMatch)));
}
}
let mut universe: Vec<ProofTerm> = Vec::new();
for (prop, _) in &known {
collect_expr_terms(prop, &mut universe);
}
collect_expr_terms(goal, &mut universe);
dedup_terms(&mut universe);
for lemma in &eq_lemmas {
let (params, lhs, _rhs) = peel_eq_lemma(lemma).expect("checked by is_forall_eq_lemma");
let mut fired: HashSet<String> = HashSet::new();
for g in &universe {
let Some(subst) = match_term_pattern(&lhs, g) else { continue };
if !params.iter().all(|p| subst.contains_key(p)) {
continue; }
let key = format!("{g}");
if !fired.insert(key) {
continue;
}
if let Some(inst) = instantiate_eq_lemma(lemma, ¶ms, &subst) {
known.push(inst);
}
}
}
match goal {
ProofExpr::Identity(a, b) => cert_congruence_path(a, b, &known),
ProofExpr::Predicate { name, args, world } => {
close_predicate(name, args, world.as_deref(), &known)
}
_ => None,
}
}
fn close_predicate(
name: &str,
goal_args: &[ProofTerm],
world: Option<&str>,
known: &[(ProofExpr, DerivationTree)],
) -> Option<DerivationTree> {
let pred = |args: &[ProofTerm]| ProofExpr::Predicate {
name: name.to_string(),
args: args.to_vec(),
world: world.map(str::to_string),
};
for (prop, atom_proof) in known {
let ProofExpr::Predicate { name: kn, args: kargs, world: kw } = prop else { continue };
if kn != name || kargs.len() != goal_args.len() || kw.as_deref() != world {
continue;
}
let mut cur = kargs.clone();
let mut proof = atom_proof.clone();
let mut ok = true;
for i in 0..goal_args.len() {
if cur[i] == goal_args[i] {
continue;
}
let Some(eq) = cert_congruence_path(&cur[i], &goal_args[i], known) else {
ok = false;
break;
};
let mut next = cur.clone();
next[i] = goal_args[i].clone();
proof = DerivationTree::new(
pred(&next),
InferenceRule::Rewrite { from: cur[i].clone(), to: goal_args[i].clone() },
vec![eq, proof],
);
cur = next;
}
if ok && cur == goal_args {
return Some(proof);
}
}
None
}
fn is_forall_eq_lemma(e: &ProofExpr) -> bool {
peel_eq_lemma(e).is_some()
}
fn peel_eq_lemma(e: &ProofExpr) -> Option<(Vec<String>, ProofTerm, ProofTerm)> {
let mut params = Vec::new();
let mut body = e;
while let ProofExpr::ForAll { variable, body: inner } = body {
params.push(variable.clone());
body = inner;
}
if params.is_empty() {
return None;
}
match body {
ProofExpr::Identity(l, r) => Some((params, l.clone(), r.clone())),
_ => None,
}
}
fn instantiate_eq_lemma(
lemma: &ProofExpr,
params: &[String],
subst: &Substitution,
) -> Option<(ProofExpr, DerivationTree)> {
let mut tree = DerivationTree::leaf(lemma.clone(), InferenceRule::PremiseMatch);
let mut expr = lemma.clone();
for param in params {
let witness = subst.get(param)?.clone();
let ProofExpr::ForAll { variable, body } = expr else { return None };
debug_assert_eq!(&variable, param);
let mut single = Substitution::new();
single.insert(param.clone(), witness.clone());
let inst = apply_subst_to_expr(&body, &single);
tree = DerivationTree::new(
inst.clone(),
InferenceRule::UniversalInstTerm(witness),
vec![tree],
);
expr = inst;
}
Some((expr, tree))
}
fn collect_expr_terms(e: &ProofExpr, out: &mut Vec<ProofTerm>) {
match e {
ProofExpr::Predicate { args, .. } => {
for a in args {
collect_term(a, out);
}
}
ProofExpr::Identity(l, r) => {
collect_term(l, out);
collect_term(r, out);
}
ProofExpr::And(l, r)
| ProofExpr::Or(l, r)
| ProofExpr::Implies(l, r)
| ProofExpr::Iff(l, r) => {
collect_expr_terms(l, out);
collect_expr_terms(r, out);
}
ProofExpr::Not(p) => collect_expr_terms(p, out),
ProofExpr::ForAll { body, .. } | ProofExpr::Exists { body, .. } => {
collect_expr_terms(body, out)
}
_ => {}
}
}
fn collect_term(t: &ProofTerm, out: &mut Vec<ProofTerm>) {
out.push(t.clone());
if let ProofTerm::Function(_, args) | ProofTerm::Group(args) = t {
for a in args {
collect_term(a, out);
}
}
}
fn dedup_terms(v: &mut Vec<ProofTerm>) {
let mut seen = HashSet::new();
v.retain(|t| seen.insert(format!("{t}")));
}