use crate::discrimination::DiscTree;
use crate::unify::{apply_subst_to_expr, match_expr_pattern, match_term_pattern, Substitution};
use crate::{DerivationTree, InferenceRule, ProofExpr, ProofTerm};
enum RuleCore {
TermEq { lhs: ProofTerm, rhs: ProofTerm },
PropIff { lhs: ProofExpr, rhs: ProofExpr },
}
struct CompiledRule {
#[allow(dead_code)]
name: String,
params: Vec<String>,
conds: Vec<ProofExpr>,
core: RuleCore,
lemma: ProofExpr,
base: DerivationTree,
}
#[derive(Default)]
pub struct SimpSet {
rules: Vec<CompiledRule>,
term_index: DiscTree<usize>,
iff_index: DiscTree<usize>,
}
pub(crate) struct RewriteStep {
pub eq: DerivationTree,
pub from: ProofTerm,
pub to: ProofTerm,
}
pub(crate) struct IffStep {
pub imp: DerivationTree,
pub rhs: ProofExpr,
}
impl SimpSet {
pub fn new() -> Self {
Self::default()
}
pub fn len(&self) -> usize {
self.rules.len()
}
pub fn is_empty(&self) -> bool {
self.rules.is_empty()
}
pub fn register_lemma(&mut self, name: &str, lemma: &ProofExpr) -> bool {
let mut params: Vec<String> = Vec::new();
let mut body = lemma;
while let ProofExpr::ForAll { variable, body: inner } = body {
if params.contains(variable) {
return false; }
params.push(variable.clone());
body = inner;
}
let mut conds: Vec<ProofExpr> = Vec::new();
while let ProofExpr::Implies(c, rest) = body {
conds.push((**c).clone());
body = rest;
}
let core = match body {
ProofExpr::Identity(l, r) => {
if matches!(l, ProofTerm::Variable(_)) {
return false; }
RuleCore::TermEq { lhs: l.clone(), rhs: r.clone() }
}
ProofExpr::Iff(l, r) => {
RuleCore::PropIff { lhs: (**l).clone(), rhs: (**r).clone() }
}
_ => return false,
};
let lhs_vars = match &core {
RuleCore::TermEq { lhs, .. } => term_vars(lhs),
RuleCore::PropIff { lhs, .. } => expr_vars(lhs),
};
let rhs_vars = match &core {
RuleCore::TermEq { rhs, .. } => term_vars(rhs),
RuleCore::PropIff { rhs, .. } => expr_vars(rhs),
};
if !rhs_vars.iter().all(|v| lhs_vars.contains(v)) {
return false;
}
for c in &conds {
if !expr_vars(c).iter().all(|v| lhs_vars.contains(v)) {
return false;
}
}
if !params.iter().all(|p| lhs_vars.contains(p)) {
return false;
}
let idx = self.rules.len();
match &core {
RuleCore::TermEq { lhs, .. } => self.term_index.insert_term(lhs, idx),
RuleCore::PropIff { lhs, .. } => self.iff_index.insert_expr(lhs, idx),
}
self.rules.push(CompiledRule {
name: name.to_string(),
params,
conds,
core,
lemma: lemma.clone(),
base: DerivationTree::leaf(lemma.clone(), InferenceRule::PremiseMatch),
});
true
}
pub(crate) fn find_term_step(
&self,
goal: &ProofExpr,
hyps: &[(&ProofExpr, &DerivationTree)],
) -> Option<RewriteStep> {
let mut subterms = Vec::new();
collect_goal_terms(goal, &mut subterms);
for t in subterms {
let mut cand: Vec<usize> = self.term_index.candidates_term(t).into_iter().copied().collect();
cand.sort_unstable();
cand.dedup();
for idx in cand {
if let Some(step) = self.try_term_rule(idx, t, hyps) {
return Some(step);
}
}
}
None
}
pub(crate) fn find_iff_step(
&self,
goal: &ProofExpr,
hyps: &[(&ProofExpr, &DerivationTree)],
) -> Option<IffStep> {
let mut cand: Vec<usize> = self.iff_index.candidates_expr(goal).into_iter().copied().collect();
cand.sort_unstable();
cand.dedup();
for idx in cand {
let rule = &self.rules[idx];
let RuleCore::PropIff { lhs, .. } = &rule.core else { continue };
let Some(subst) = match_expr_pattern(lhs, goal) else { continue };
let Some((tree, concl)) = instantiate_rule(rule, &subst, hyps) else { continue };
let ProofExpr::Iff(l, r) = &concl else { continue };
if l.as_ref() != goal {
continue;
}
let imp = DerivationTree::new(
ProofExpr::Implies(r.clone(), l.clone()),
InferenceRule::ConjunctionElim,
vec![tree],
);
return Some(IffStep { imp, rhs: (**r).clone() });
}
None
}
fn try_term_rule(
&self,
idx: usize,
t: &ProofTerm,
hyps: &[(&ProofExpr, &DerivationTree)],
) -> Option<RewriteStep> {
let rule = &self.rules[idx];
let RuleCore::TermEq { lhs, rhs } = &rule.core else { return None };
let subst = match_term_pattern(lhs, t)?;
let (tree, concl) = instantiate_rule(rule, &subst, hyps)?;
let ProofExpr::Identity(from, to) = concl else { return None };
if &from != t {
return None;
}
if from == to {
return None;
}
Some(RewriteStep { eq: tree, from, to })
}
}
fn instantiate_rule(
rule: &CompiledRule,
subst: &Substitution,
hyps: &[(&ProofExpr, &DerivationTree)],
) -> Option<(DerivationTree, ProofExpr)> {
let mut tree = rule.base.clone();
let mut expr = rule.lemma.clone();
for param in &rule.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;
}
for _ in 0..rule.conds.len() {
let ProofExpr::Implies(cond, rest) = expr else { return None };
let cond_proof = discharge_condition(&cond, hyps)?;
tree = DerivationTree::new(
(*rest).clone(),
InferenceRule::ModusPonens,
vec![tree, cond_proof],
);
expr = *rest;
}
Some((tree, expr))
}
fn discharge_condition(
cond: &ProofExpr,
hyps: &[(&ProofExpr, &DerivationTree)],
) -> Option<DerivationTree> {
if let Some((_, proof)) = hyps.iter().find(|(prop, _)| *prop == cond) {
return Some((*proof).clone());
}
if let ProofExpr::Identity(l, r) = cond {
if ground_int(l).is_some() && ground_int(r).is_some() {
return Some(DerivationTree::leaf(cond.clone(), InferenceRule::ArithDecision));
}
}
None
}
pub(crate) fn find_ground_fold(goal: &ProofExpr) -> Option<RewriteStep> {
let mut subterms = Vec::new();
collect_goal_terms(goal, &mut subterms);
for t in subterms {
let ProofTerm::Function(op, args) = t else { continue };
if args.len() != 2 {
continue;
}
let (Some(a), Some(b)) = (ground_int(&args[0]), ground_int(&args[1])) else {
continue;
};
let result = match op.as_str() {
"add" => a.checked_add(b),
"sub" => a.checked_sub(b),
"mul" => a.checked_mul(b),
_ => None,
}?;
let to = ProofTerm::Constant(result.to_string());
if &to == t {
continue;
}
let eq = DerivationTree::leaf(
ProofExpr::Identity(t.clone(), to.clone()),
InferenceRule::ArithDecision,
);
return Some(RewriteStep { eq, from: t.clone(), to });
}
None
}
fn ground_int(t: &ProofTerm) -> Option<i64> {
match t {
ProofTerm::Constant(s) => s.parse::<i64>().ok(),
_ => None,
}
}
fn collect_goal_terms<'a>(e: &'a ProofExpr, out: &mut Vec<&'a ProofTerm>) {
fn walk_term<'a>(t: &'a ProofTerm, out: &mut Vec<&'a ProofTerm>) {
match t {
ProofTerm::Function(_, args) | ProofTerm::Group(args) => {
for a in args {
walk_term(a, out);
}
}
_ => {}
}
if !out.contains(&t) {
out.push(t);
}
}
match e {
ProofExpr::Predicate { args, .. } => {
for a in args {
walk_term(a, out);
}
}
ProofExpr::Identity(l, r) => {
walk_term(l, out);
walk_term(r, out);
}
ProofExpr::And(l, r)
| ProofExpr::Or(l, r)
| ProofExpr::Implies(l, r)
| ProofExpr::Iff(l, r) => {
collect_goal_terms(l, out);
collect_goal_terms(r, out);
}
ProofExpr::Not(p) => collect_goal_terms(p, out),
ProofExpr::ForAll { body, .. } | ProofExpr::Exists { body, .. } => {
collect_goal_terms(body, out)
}
_ => {}
}
}
fn term_vars(t: &ProofTerm) -> Vec<String> {
let mut out = Vec::new();
collect_term_vars(t, &mut out);
out
}
fn collect_term_vars(t: &ProofTerm, out: &mut Vec<String>) {
match t {
ProofTerm::Variable(n) => {
if !out.contains(n) {
out.push(n.clone());
}
}
ProofTerm::Function(_, args) | ProofTerm::Group(args) => {
for a in args {
collect_term_vars(a, out);
}
}
_ => {}
}
}
fn expr_vars(e: &ProofExpr) -> Vec<String> {
let mut out = Vec::new();
collect_expr_vars(e, &mut out);
out
}
fn collect_expr_vars(e: &ProofExpr, out: &mut Vec<String>) {
match e {
ProofExpr::Predicate { args, .. } => {
for a in args {
collect_term_vars(a, out);
}
}
ProofExpr::Identity(l, r) => {
collect_term_vars(l, out);
collect_term_vars(r, out);
}
ProofExpr::And(l, r)
| ProofExpr::Or(l, r)
| ProofExpr::Implies(l, r)
| ProofExpr::Iff(l, r) => {
collect_expr_vars(l, out);
collect_expr_vars(r, out);
}
ProofExpr::Not(p) => collect_expr_vars(p, out),
ProofExpr::ForAll { variable, body } | ProofExpr::Exists { variable, body } => {
let mut inner = Vec::new();
collect_expr_vars(body, &mut inner);
for v in inner {
if v != *variable && !out.contains(&v) {
out.push(v);
}
}
}
_ => {}
}
}