use std::collections::HashMap;
use std::sync::atomic::{AtomicUsize, Ordering};
use crate::error::{ProofError, ProofResult};
use crate::{MatchArm, ProofExpr, ProofTerm};
pub type Substitution = HashMap<String, ProofTerm>;
pub type ExprSubstitution = HashMap<String, ProofExpr>;
static ALPHA_COUNTER: AtomicUsize = AtomicUsize::new(0);
fn fresh_alpha_constant() -> ProofTerm {
let id = ALPHA_COUNTER.fetch_add(1, Ordering::SeqCst);
ProofTerm::Constant(format!("#α{}", id))
}
fn is_constructor_form(expr: &ProofExpr) -> bool {
matches!(expr, ProofExpr::Ctor { .. })
}
pub fn beta_reduce(expr: &ProofExpr) -> ProofExpr {
match expr {
ProofExpr::App(func, arg) => {
let func_reduced = beta_reduce(func);
let arg_reduced = beta_reduce(arg);
match func_reduced {
ProofExpr::Lambda { variable, body } => {
let result = substitute_expr_for_var(&body, &variable, &arg_reduced);
beta_reduce(&result)
}
ProofExpr::Fixpoint { ref name, ref body } if is_constructor_form(&arg_reduced) => {
let fix_expr = ProofExpr::Fixpoint {
name: name.clone(),
body: body.clone(),
};
let unfolded = substitute_expr_for_var(body, name, &fix_expr);
let applied = ProofExpr::App(Box::new(unfolded), Box::new(arg_reduced));
beta_reduce(&applied)
}
_ => {
ProofExpr::App(Box::new(func_reduced), Box::new(arg_reduced))
}
}
}
ProofExpr::And(l, r) => ProofExpr::And(
Box::new(beta_reduce(l)),
Box::new(beta_reduce(r)),
),
ProofExpr::Or(l, r) => ProofExpr::Or(
Box::new(beta_reduce(l)),
Box::new(beta_reduce(r)),
),
ProofExpr::Implies(l, r) => ProofExpr::Implies(
Box::new(beta_reduce(l)),
Box::new(beta_reduce(r)),
),
ProofExpr::Iff(l, r) => ProofExpr::Iff(
Box::new(beta_reduce(l)),
Box::new(beta_reduce(r)),
),
ProofExpr::Not(inner) => ProofExpr::Not(Box::new(beta_reduce(inner))),
ProofExpr::ForAll { variable, body } => ProofExpr::ForAll {
variable: variable.clone(),
body: Box::new(beta_reduce(body)),
},
ProofExpr::Exists { variable, body } => ProofExpr::Exists {
variable: variable.clone(),
body: Box::new(beta_reduce(body)),
},
ProofExpr::Lambda { variable, body } => ProofExpr::Lambda {
variable: variable.clone(),
body: Box::new(beta_reduce(body)),
},
ProofExpr::Modal { domain, force, flavor, body } => ProofExpr::Modal {
domain: domain.clone(),
force: *force,
flavor: flavor.clone(),
body: Box::new(beta_reduce(body)),
},
ProofExpr::Temporal { operator, body } => ProofExpr::Temporal {
operator: operator.clone(),
body: Box::new(beta_reduce(body)),
},
ProofExpr::TemporalBinary { operator, left, right } => ProofExpr::TemporalBinary {
operator: operator.clone(),
left: Box::new(beta_reduce(left)),
right: Box::new(beta_reduce(right)),
},
ProofExpr::Ctor { name, args } => ProofExpr::Ctor {
name: name.clone(),
args: args.iter().map(beta_reduce).collect(),
},
ProofExpr::Match { scrutinee, arms } => {
let reduced_scrutinee = beta_reduce(scrutinee);
if let ProofExpr::Ctor { name: ctor_name, args: ctor_args } = &reduced_scrutinee {
for arm in arms {
if &arm.ctor == ctor_name {
let mut result = arm.body.clone();
for (binding, arg) in arm.bindings.iter().zip(ctor_args.iter()) {
result = substitute_expr_for_var(&result, binding, arg);
}
return beta_reduce(&result);
}
}
}
ProofExpr::Match {
scrutinee: Box::new(reduced_scrutinee),
arms: arms.iter().map(|arm| MatchArm {
ctor: arm.ctor.clone(),
bindings: arm.bindings.clone(),
body: beta_reduce(&arm.body),
}).collect(),
}
}
ProofExpr::Fixpoint { name, body } => ProofExpr::Fixpoint {
name: name.clone(),
body: Box::new(beta_reduce(body)),
},
ProofExpr::Predicate { .. }
| ProofExpr::Identity(_, _)
| ProofExpr::Atom(_)
| ProofExpr::NeoEvent { .. }
| ProofExpr::TypedVar { .. }
| ProofExpr::Unsupported(_)
| ProofExpr::Hole(_)
| ProofExpr::Term(_) => expr.clone(),
}
}
fn substitute_expr_for_var(body: &ProofExpr, var: &str, replacement: &ProofExpr) -> ProofExpr {
match body {
ProofExpr::Predicate { name, args, world } => ProofExpr::Predicate {
name: name.clone(),
args: args.iter().map(|t| substitute_term_for_var(t, var, replacement)).collect(),
world: world.clone(),
},
ProofExpr::Identity(l, r) => ProofExpr::Identity(
substitute_term_for_var(l, var, replacement),
substitute_term_for_var(r, var, replacement),
),
ProofExpr::Atom(a) => {
if a == var {
replacement.clone()
} else {
ProofExpr::Atom(a.clone())
}
}
ProofExpr::And(l, r) => ProofExpr::And(
Box::new(substitute_expr_for_var(l, var, replacement)),
Box::new(substitute_expr_for_var(r, var, replacement)),
),
ProofExpr::Or(l, r) => ProofExpr::Or(
Box::new(substitute_expr_for_var(l, var, replacement)),
Box::new(substitute_expr_for_var(r, var, replacement)),
),
ProofExpr::Implies(l, r) => ProofExpr::Implies(
Box::new(substitute_expr_for_var(l, var, replacement)),
Box::new(substitute_expr_for_var(r, var, replacement)),
),
ProofExpr::Iff(l, r) => ProofExpr::Iff(
Box::new(substitute_expr_for_var(l, var, replacement)),
Box::new(substitute_expr_for_var(r, var, replacement)),
),
ProofExpr::Not(inner) => ProofExpr::Not(
Box::new(substitute_expr_for_var(inner, var, replacement))
),
ProofExpr::ForAll { variable, body: inner } => {
if variable == var {
body.clone()
} else {
ProofExpr::ForAll {
variable: variable.clone(),
body: Box::new(substitute_expr_for_var(inner, var, replacement)),
}
}
}
ProofExpr::Exists { variable, body: inner } => {
if variable == var {
body.clone()
} else {
ProofExpr::Exists {
variable: variable.clone(),
body: Box::new(substitute_expr_for_var(inner, var, replacement)),
}
}
}
ProofExpr::Lambda { variable, body: inner } => {
if variable == var {
body.clone()
} else {
ProofExpr::Lambda {
variable: variable.clone(),
body: Box::new(substitute_expr_for_var(inner, var, replacement)),
}
}
}
ProofExpr::App(f, a) => ProofExpr::App(
Box::new(substitute_expr_for_var(f, var, replacement)),
Box::new(substitute_expr_for_var(a, var, replacement)),
),
ProofExpr::Modal { domain, force, flavor, body: inner } => ProofExpr::Modal {
domain: domain.clone(),
force: *force,
flavor: flavor.clone(),
body: Box::new(substitute_expr_for_var(inner, var, replacement)),
},
ProofExpr::Temporal { operator, body: inner } => ProofExpr::Temporal {
operator: operator.clone(),
body: Box::new(substitute_expr_for_var(inner, var, replacement)),
},
ProofExpr::TemporalBinary { operator, left, right } => ProofExpr::TemporalBinary {
operator: operator.clone(),
left: Box::new(substitute_expr_for_var(left, var, replacement)),
right: Box::new(substitute_expr_for_var(right, var, replacement)),
},
ProofExpr::NeoEvent { event_var, verb, roles } => {
ProofExpr::NeoEvent {
event_var: event_var.clone(),
verb: verb.clone(),
roles: roles.iter().map(|(r, t)| {
(r.clone(), substitute_term_for_var(t, var, replacement))
}).collect(),
}
}
ProofExpr::Ctor { name, args } => ProofExpr::Ctor {
name: name.clone(),
args: args.iter().map(|a| substitute_expr_for_var(a, var, replacement)).collect(),
},
ProofExpr::Match { scrutinee, arms } => ProofExpr::Match {
scrutinee: Box::new(substitute_expr_for_var(scrutinee, var, replacement)),
arms: arms.iter().map(|arm| {
if arm.bindings.contains(&var.to_string()) {
arm.clone()
} else {
MatchArm {
ctor: arm.ctor.clone(),
bindings: arm.bindings.clone(),
body: substitute_expr_for_var(&arm.body, var, replacement),
}
}
}).collect(),
},
ProofExpr::Fixpoint { name, body: inner } => {
if name == var {
body.clone()
} else {
ProofExpr::Fixpoint {
name: name.clone(),
body: Box::new(substitute_expr_for_var(inner, var, replacement)),
}
}
}
ProofExpr::TypedVar { .. } | ProofExpr::Unsupported(_) => body.clone(),
ProofExpr::Hole(_) => body.clone(),
ProofExpr::Term(t) => ProofExpr::Term(substitute_term_for_var(t, var, replacement)),
}
}
fn substitute_term_for_var(term: &ProofTerm, var: &str, replacement: &ProofExpr) -> ProofTerm {
match term {
ProofTerm::Variable(v) if v == var => {
expr_to_term(replacement)
}
ProofTerm::BoundVarRef(v) if v == var => {
expr_to_term(replacement)
}
ProofTerm::Variable(_) | ProofTerm::Constant(_) | ProofTerm::BoundVarRef(_) => term.clone(),
ProofTerm::Function(name, args) => ProofTerm::Function(
name.clone(),
args.iter().map(|a| substitute_term_for_var(a, var, replacement)).collect(),
),
ProofTerm::Group(terms) => ProofTerm::Group(
terms.iter().map(|t| substitute_term_for_var(t, var, replacement)).collect(),
),
}
}
fn expr_to_term(expr: &ProofExpr) -> ProofTerm {
match expr {
ProofExpr::Atom(s) => ProofTerm::Constant(s.clone()),
ProofExpr::Predicate { name, args, .. } if args.is_empty() => {
ProofTerm::Constant(name.clone())
}
ProofExpr::Predicate { name, args, .. } => {
ProofTerm::Function(name.clone(), args.clone())
}
ProofExpr::Ctor { name, args } => {
ProofTerm::Function(name.clone(), args.iter().map(expr_to_term).collect())
}
ProofExpr::TypedVar { name, .. } => ProofTerm::Variable(name.clone()),
ProofExpr::Term(t) => t.clone(),
_ => ProofTerm::Constant(format!("{}", expr)),
}
}
pub fn unify_terms(t1: &ProofTerm, t2: &ProofTerm) -> ProofResult<Substitution> {
let mut subst = Substitution::new();
unify_terms_with_subst(t1, t2, &mut subst)?;
Ok(subst)
}
fn unify_terms_with_subst(
t1: &ProofTerm,
t2: &ProofTerm,
subst: &mut Substitution,
) -> ProofResult<()> {
let t1 = apply_subst_to_term(t1, subst);
let t2 = apply_subst_to_term(t2, subst);
match (&t1, &t2) {
(ProofTerm::Constant(c1), ProofTerm::Constant(c2)) if c1 == c2 => Ok(()),
(ProofTerm::Constant(c1), ProofTerm::Constant(c2)) => {
Err(ProofError::SymbolMismatch {
left: c1.clone(),
right: c2.clone(),
})
}
(ProofTerm::Variable(v), t) => {
if let ProofTerm::Variable(v2) = t {
if v == v2 {
return Ok(());
}
}
if occurs(v, t) {
return Err(ProofError::OccursCheck {
variable: v.clone(),
term: t.clone(),
});
}
subst.insert(v.clone(), t.clone());
Ok(())
}
(t, ProofTerm::Variable(v)) => {
if occurs(v, t) {
return Err(ProofError::OccursCheck {
variable: v.clone(),
term: t.clone(),
});
}
subst.insert(v.clone(), t.clone());
Ok(())
}
(ProofTerm::BoundVarRef(v), t) => {
if let ProofTerm::BoundVarRef(v2) = t {
if v == v2 {
return Ok(());
}
}
if occurs(v, t) {
return Err(ProofError::OccursCheck {
variable: v.clone(),
term: t.clone(),
});
}
subst.insert(v.clone(), t.clone());
Ok(())
}
(t, ProofTerm::BoundVarRef(v)) => {
if occurs(v, t) {
return Err(ProofError::OccursCheck {
variable: v.clone(),
term: t.clone(),
});
}
subst.insert(v.clone(), t.clone());
Ok(())
}
(ProofTerm::Function(f1, args1), ProofTerm::Function(f2, args2)) => {
if f1 != f2 {
return Err(ProofError::SymbolMismatch {
left: f1.clone(),
right: f2.clone(),
});
}
if args1.len() != args2.len() {
return Err(ProofError::ArityMismatch {
expected: args1.len(),
found: args2.len(),
});
}
for (a1, a2) in args1.iter().zip(args2.iter()) {
unify_terms_with_subst(a1, a2, subst)?;
}
Ok(())
}
(ProofTerm::Group(g1), ProofTerm::Group(g2)) => {
if g1.len() != g2.len() {
return Err(ProofError::ArityMismatch {
expected: g1.len(),
found: g2.len(),
});
}
for (t1, t2) in g1.iter().zip(g2.iter()) {
unify_terms_with_subst(t1, t2, subst)?;
}
Ok(())
}
_ => Err(ProofError::UnificationFailed {
left: t1,
right: t2,
}),
}
}
fn occurs(var: &str, term: &ProofTerm) -> bool {
match term {
ProofTerm::Variable(v) => v == var,
ProofTerm::BoundVarRef(v) => v == var, ProofTerm::Constant(_) => false,
ProofTerm::Function(_, args) => args.iter().any(|a| occurs(var, a)),
ProofTerm::Group(terms) => terms.iter().any(|t| occurs(var, t)),
}
}
pub fn apply_subst_to_term(term: &ProofTerm, subst: &Substitution) -> ProofTerm {
match term {
ProofTerm::Variable(v) => {
if let Some(replacement) = subst.get(v) {
apply_subst_to_term(replacement, subst)
} else {
term.clone()
}
}
ProofTerm::BoundVarRef(v) => {
if let Some(replacement) = subst.get(v) {
apply_subst_to_term(replacement, subst)
} else {
term.clone()
}
}
ProofTerm::Constant(_) => term.clone(),
ProofTerm::Function(name, args) => {
let new_args = args.iter().map(|a| apply_subst_to_term(a, subst)).collect();
ProofTerm::Function(name.clone(), new_args)
}
ProofTerm::Group(terms) => {
let new_terms = terms.iter().map(|t| apply_subst_to_term(t, subst)).collect();
ProofTerm::Group(new_terms)
}
}
}
pub fn unify_exprs(e1: &ProofExpr, e2: &ProofExpr) -> ProofResult<Substitution> {
let mut subst = Substitution::new();
unify_exprs_with_subst(e1, e2, &mut subst)?;
Ok(subst)
}
fn unify_exprs_with_subst(
e1: &ProofExpr,
e2: &ProofExpr,
subst: &mut Substitution,
) -> ProofResult<()> {
let e1 = beta_reduce(e1);
let e2 = beta_reduce(e2);
match (&e1, &e2) {
(ProofExpr::Atom(a1), ProofExpr::Atom(a2)) if a1 == a2 => Ok(()),
(
ProofExpr::Predicate { name: n1, args: a1, world: w1 },
ProofExpr::Predicate { name: n2, args: a2, world: w2 },
) => {
if n1 != n2 {
return Err(ProofError::SymbolMismatch {
left: n1.clone(),
right: n2.clone(),
});
}
if a1.len() != a2.len() {
return Err(ProofError::ArityMismatch {
expected: a1.len(),
found: a2.len(),
});
}
match (w1, w2) {
(Some(w1), Some(w2)) if w1 != w2 => {
return Err(ProofError::SymbolMismatch {
left: w1.clone(),
right: w2.clone(),
});
}
_ => {}
}
for (t1, t2) in a1.iter().zip(a2.iter()) {
unify_terms_with_subst(t1, t2, subst)?;
}
Ok(())
}
(ProofExpr::Identity(l1, r1), ProofExpr::Identity(l2, r2)) => {
unify_terms_with_subst(l1, l2, subst)?;
unify_terms_with_subst(r1, r2, subst)?;
Ok(())
}
(ProofExpr::And(l1, r1), ProofExpr::And(l2, r2))
| (ProofExpr::Or(l1, r1), ProofExpr::Or(l2, r2))
| (ProofExpr::Implies(l1, r1), ProofExpr::Implies(l2, r2))
| (ProofExpr::Iff(l1, r1), ProofExpr::Iff(l2, r2)) => {
unify_exprs_with_subst(l1, l2, subst)?;
unify_exprs_with_subst(r1, r2, subst)?;
Ok(())
}
(ProofExpr::Not(inner1), ProofExpr::Not(inner2)) => {
unify_exprs_with_subst(inner1, inner2, subst)
}
(
ProofExpr::ForAll { variable: v1, body: b1 },
ProofExpr::ForAll { variable: v2, body: b2 },
)
| (
ProofExpr::Exists { variable: v1, body: b1 },
ProofExpr::Exists { variable: v2, body: b2 },
) => {
let fresh = fresh_alpha_constant();
let subst1: Substitution = [(v1.clone(), fresh.clone())].into_iter().collect();
let subst2: Substitution = [(v2.clone(), fresh)].into_iter().collect();
let body1_renamed = apply_subst_to_expr(b1, &subst1);
let body2_renamed = apply_subst_to_expr(b2, &subst2);
unify_exprs_with_subst(&body1_renamed, &body2_renamed, subst)
}
(
ProofExpr::Lambda { variable: v1, body: b1 },
ProofExpr::Lambda { variable: v2, body: b2 },
) => {
let fresh = fresh_alpha_constant();
let subst1: Substitution = [(v1.clone(), fresh.clone())].into_iter().collect();
let subst2: Substitution = [(v2.clone(), fresh)].into_iter().collect();
let body1_renamed = apply_subst_to_expr(b1, &subst1);
let body2_renamed = apply_subst_to_expr(b2, &subst2);
unify_exprs_with_subst(&body1_renamed, &body2_renamed, subst)
}
(ProofExpr::App(f1, a1), ProofExpr::App(f2, a2)) => {
unify_exprs_with_subst(f1, f2, subst)?;
unify_exprs_with_subst(a1, a2, subst)?;
Ok(())
}
(
ProofExpr::NeoEvent {
event_var: e1,
verb: v1,
roles: r1,
},
ProofExpr::NeoEvent {
event_var: e2,
verb: v2,
roles: r2,
},
) => {
if v1.to_lowercase() != v2.to_lowercase() {
return Err(ProofError::SymbolMismatch {
left: v1.clone(),
right: v2.clone(),
});
}
if r1.len() != r2.len() {
return Err(ProofError::ArityMismatch {
expected: r1.len(),
found: r2.len(),
});
}
let fresh = fresh_alpha_constant();
let subst1: Substitution = [(e1.clone(), fresh.clone())].into_iter().collect();
let subst2: Substitution = [(e2.clone(), fresh)].into_iter().collect();
for ((role1, term1), (role2, term2)) in r1.iter().zip(r2.iter()) {
if role1 != role2 {
return Err(ProofError::SymbolMismatch {
left: role1.clone(),
right: role2.clone(),
});
}
let t1_renamed = apply_subst_to_term(term1, &subst1);
let t2_renamed = apply_subst_to_term(term2, &subst2);
unify_terms_with_subst(&t1_renamed, &t2_renamed, subst)?;
}
Ok(())
}
(
ProofExpr::Temporal { operator: op1, body: b1 },
ProofExpr::Temporal { operator: op2, body: b2 },
) => {
if op1 != op2 {
return Err(ProofError::ExprUnificationFailed {
left: e1.clone(),
right: e2.clone(),
});
}
unify_exprs_with_subst(b1, b2, subst)
}
(
ProofExpr::TemporalBinary { operator: op1, left: l1, right: r1 },
ProofExpr::TemporalBinary { operator: op2, left: l2, right: r2 },
) => {
if op1 != op2 {
return Err(ProofError::ExprUnificationFailed {
left: e1.clone(),
right: e2.clone(),
});
}
unify_exprs_with_subst(l1, l2, subst)?;
unify_exprs_with_subst(r1, r2, subst)
}
_ => Err(ProofError::ExprUnificationFailed {
left: e1.clone(),
right: e2.clone(),
}),
}
}
pub fn apply_subst_to_expr(expr: &ProofExpr, subst: &Substitution) -> ProofExpr {
match expr {
ProofExpr::Predicate { name, args, world } => ProofExpr::Predicate {
name: name.clone(),
args: args.iter().map(|a| apply_subst_to_term(a, subst)).collect(),
world: world.clone(),
},
ProofExpr::Identity(l, r) => ProofExpr::Identity(
apply_subst_to_term(l, subst),
apply_subst_to_term(r, subst),
),
ProofExpr::Atom(a) => ProofExpr::Atom(a.clone()),
ProofExpr::And(l, r) => ProofExpr::And(
Box::new(apply_subst_to_expr(l, subst)),
Box::new(apply_subst_to_expr(r, subst)),
),
ProofExpr::Or(l, r) => ProofExpr::Or(
Box::new(apply_subst_to_expr(l, subst)),
Box::new(apply_subst_to_expr(r, subst)),
),
ProofExpr::Implies(l, r) => ProofExpr::Implies(
Box::new(apply_subst_to_expr(l, subst)),
Box::new(apply_subst_to_expr(r, subst)),
),
ProofExpr::Iff(l, r) => ProofExpr::Iff(
Box::new(apply_subst_to_expr(l, subst)),
Box::new(apply_subst_to_expr(r, subst)),
),
ProofExpr::Not(inner) => ProofExpr::Not(Box::new(apply_subst_to_expr(inner, subst))),
ProofExpr::ForAll { variable, body } => {
let new_variable = match subst.get(variable) {
Some(ProofTerm::Variable(new_name)) => new_name.clone(),
_ => variable.clone(),
};
ProofExpr::ForAll {
variable: new_variable,
body: Box::new(apply_subst_to_expr(body, subst)),
}
}
ProofExpr::Exists { variable, body } => {
let new_variable = match subst.get(variable) {
Some(ProofTerm::Variable(new_name)) => new_name.clone(),
_ => variable.clone(),
};
ProofExpr::Exists {
variable: new_variable,
body: Box::new(apply_subst_to_expr(body, subst)),
}
}
ProofExpr::Modal { domain, force, flavor, body } => ProofExpr::Modal {
domain: domain.clone(),
force: *force,
flavor: flavor.clone(),
body: Box::new(apply_subst_to_expr(body, subst)),
},
ProofExpr::Temporal { operator, body } => ProofExpr::Temporal {
operator: operator.clone(),
body: Box::new(apply_subst_to_expr(body, subst)),
},
ProofExpr::TemporalBinary { operator, left, right } => ProofExpr::TemporalBinary {
operator: operator.clone(),
left: Box::new(apply_subst_to_expr(left, subst)),
right: Box::new(apply_subst_to_expr(right, subst)),
},
ProofExpr::Lambda { variable, body } => {
let new_variable = match subst.get(variable) {
Some(ProofTerm::Variable(new_name)) => new_name.clone(),
_ => variable.clone(),
};
ProofExpr::Lambda {
variable: new_variable,
body: Box::new(apply_subst_to_expr(body, subst)),
}
}
ProofExpr::App(f, a) => ProofExpr::App(
Box::new(apply_subst_to_expr(f, subst)),
Box::new(apply_subst_to_expr(a, subst)),
),
ProofExpr::NeoEvent { event_var, verb, roles } => ProofExpr::NeoEvent {
event_var: event_var.clone(),
verb: verb.clone(),
roles: roles
.iter()
.map(|(r, t)| (r.clone(), apply_subst_to_term(t, subst)))
.collect(),
},
ProofExpr::Ctor { name, args } => ProofExpr::Ctor {
name: name.clone(),
args: args.iter().map(|a| apply_subst_to_expr(a, subst)).collect(),
},
ProofExpr::Match { scrutinee, arms } => ProofExpr::Match {
scrutinee: Box::new(apply_subst_to_expr(scrutinee, subst)),
arms: arms
.iter()
.map(|arm| MatchArm {
ctor: arm.ctor.clone(),
bindings: arm.bindings.clone(),
body: apply_subst_to_expr(&arm.body, subst),
})
.collect(),
},
ProofExpr::Fixpoint { name, body } => ProofExpr::Fixpoint {
name: name.clone(),
body: Box::new(apply_subst_to_expr(body, subst)),
},
ProofExpr::TypedVar { name, typename } => ProofExpr::TypedVar {
name: name.clone(),
typename: typename.clone(),
},
ProofExpr::Unsupported(s) => ProofExpr::Unsupported(s.clone()),
ProofExpr::Hole(name) => ProofExpr::Hole(name.clone()),
ProofExpr::Term(t) => ProofExpr::Term(apply_subst_to_term(t, subst)),
}
}
pub fn compose_substitutions(s1: Substitution, s2: Substitution) -> Substitution {
let mut result: Substitution = s1
.into_iter()
.map(|(k, v)| (k, apply_subst_to_term(&v, &s2)))
.collect();
for (k, v) in s2 {
result.entry(k).or_insert(v);
}
result
}
pub fn unify_pattern(lhs: &ProofExpr, rhs: &ProofExpr) -> ProofResult<ExprSubstitution> {
let mut solution = ExprSubstitution::new();
unify_pattern_internal(lhs, rhs, &mut solution)?;
Ok(solution)
}
fn unify_pattern_internal(
lhs: &ProofExpr,
rhs: &ProofExpr,
solution: &mut ExprSubstitution,
) -> ProofResult<()> {
let lhs = beta_reduce(lhs);
let rhs = beta_reduce(rhs);
match &lhs {
ProofExpr::Hole(h) => {
solution.insert(h.clone(), rhs.clone());
Ok(())
}
ProofExpr::App(_, _) => {
let (head, args) = collect_app_args(&lhs);
if let ProofExpr::Hole(h) = head {
let var_args = extract_distinct_vars(&args)?;
check_scope(&rhs, &var_args)?;
let renamed_rhs = rename_vars_to_bound(&rhs, &var_args);
let lambda = build_lambda(var_args, renamed_rhs);
solution.insert(h.clone(), lambda);
Ok(())
} else {
if lhs == rhs {
Ok(())
} else {
Err(ProofError::ExprUnificationFailed {
left: lhs.clone(),
right: rhs.clone(),
})
}
}
}
_ => {
if lhs == rhs {
Ok(())
} else {
Err(ProofError::ExprUnificationFailed {
left: lhs.clone(),
right: rhs.clone(),
})
}
}
}
}
fn collect_app_args(expr: &ProofExpr) -> (ProofExpr, Vec<ProofExpr>) {
let mut args = Vec::new();
let mut current = expr.clone();
while let ProofExpr::App(func, arg) = current {
args.push(*arg);
current = *func;
}
args.reverse();
(current, args)
}
fn extract_distinct_vars(args: &[ProofExpr]) -> ProofResult<Vec<String>> {
let mut vars = Vec::new();
for arg in args {
match arg {
ProofExpr::Term(ProofTerm::BoundVarRef(v)) => {
if vars.contains(v) {
return Err(ProofError::PatternNotDistinct(v.clone()));
}
vars.push(v.clone());
}
_ => return Err(ProofError::NotAPattern(arg.clone())),
}
}
Ok(vars)
}
fn check_scope(expr: &ProofExpr, allowed: &[String]) -> ProofResult<()> {
let free_vars = collect_free_vars(expr);
for var in free_vars {
if !allowed.contains(&var) {
return Err(ProofError::ScopeViolation {
var,
allowed: allowed.to_vec(),
});
}
}
Ok(())
}
fn collect_free_vars(expr: &ProofExpr) -> Vec<String> {
let mut vars = Vec::new();
collect_free_vars_impl(expr, &mut vars, &mut Vec::new());
vars
}
fn collect_free_vars_impl(expr: &ProofExpr, vars: &mut Vec<String>, bound: &mut Vec<String>) {
match expr {
ProofExpr::Predicate { args, .. } => {
for arg in args {
collect_free_vars_term(arg, vars, bound);
}
}
ProofExpr::Identity(l, r) => {
collect_free_vars_term(l, vars, bound);
collect_free_vars_term(r, vars, bound);
}
ProofExpr::Atom(s) => {
if !bound.contains(s) && !vars.contains(s) {
vars.push(s.clone());
}
}
ProofExpr::And(l, r)
| ProofExpr::Or(l, r)
| ProofExpr::Implies(l, r)
| ProofExpr::Iff(l, r) => {
collect_free_vars_impl(l, vars, bound);
collect_free_vars_impl(r, vars, bound);
}
ProofExpr::Not(inner) => collect_free_vars_impl(inner, vars, bound),
ProofExpr::ForAll { variable, body }
| ProofExpr::Exists { variable, body }
| ProofExpr::Lambda { variable, body } => {
bound.push(variable.clone());
collect_free_vars_impl(body, vars, bound);
bound.pop();
}
ProofExpr::App(f, a) => {
collect_free_vars_impl(f, vars, bound);
collect_free_vars_impl(a, vars, bound);
}
ProofExpr::Term(t) => collect_free_vars_term(t, vars, bound),
ProofExpr::Hole(_) => {} _ => {} }
}
fn collect_free_vars_term(term: &ProofTerm, vars: &mut Vec<String>, bound: &[String]) {
match term {
ProofTerm::Variable(v) => {
if !bound.contains(v) && !vars.contains(v) {
vars.push(v.clone());
}
}
ProofTerm::Function(_, args) => {
for arg in args {
collect_free_vars_term(arg, vars, bound);
}
}
ProofTerm::Group(terms) => {
for t in terms {
collect_free_vars_term(t, vars, bound);
}
}
ProofTerm::Constant(_) | ProofTerm::BoundVarRef(_) => {}
}
}
fn rename_vars_to_bound(expr: &ProofExpr, bound_vars: &[String]) -> ProofExpr {
expr.clone()
}
fn build_lambda(vars: Vec<String>, body: ProofExpr) -> ProofExpr {
vars.into_iter().rev().fold(body, |acc, var| {
ProofExpr::Lambda {
variable: var,
body: Box::new(acc),
}
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_unify_same_constant() {
let t1 = ProofTerm::Constant("a".into());
let t2 = ProofTerm::Constant("a".into());
let result = unify_terms(&t1, &t2);
assert!(result.is_ok());
assert!(result.unwrap().is_empty());
}
#[test]
fn test_unify_different_constants() {
let t1 = ProofTerm::Constant("a".into());
let t2 = ProofTerm::Constant("b".into());
let result = unify_terms(&t1, &t2);
assert!(result.is_err());
}
#[test]
fn test_unify_var_constant() {
let t1 = ProofTerm::Variable("x".into());
let t2 = ProofTerm::Constant("a".into());
let result = unify_terms(&t1, &t2);
assert!(result.is_ok());
let subst = result.unwrap();
assert_eq!(subst.get("x"), Some(&ProofTerm::Constant("a".into())));
}
#[test]
fn test_occurs_check() {
let t1 = ProofTerm::Variable("x".into());
let t2 = ProofTerm::Function("f".into(), vec![ProofTerm::Variable("x".into())]);
let result = unify_terms(&t1, &t2);
assert!(matches!(result, Err(ProofError::OccursCheck { .. })));
}
#[test]
fn test_compose_substitutions() {
let mut s1 = Substitution::new();
s1.insert("x".into(), ProofTerm::Variable("y".into()));
let mut s2 = Substitution::new();
s2.insert("y".into(), ProofTerm::Constant("a".into()));
let composed = compose_substitutions(s1, s2);
assert_eq!(composed.get("x"), Some(&ProofTerm::Constant("a".into())));
assert_eq!(composed.get("y"), Some(&ProofTerm::Constant("a".into())));
}
#[test]
fn test_alpha_equivalence_exists() {
let e1 = ProofExpr::Exists {
variable: "e".to_string(),
body: Box::new(ProofExpr::Predicate {
name: "run".to_string(),
args: vec![ProofTerm::Variable("e".to_string())],
world: None,
}),
};
let e2 = ProofExpr::Exists {
variable: "x".to_string(),
body: Box::new(ProofExpr::Predicate {
name: "run".to_string(),
args: vec![ProofTerm::Variable("x".to_string())],
world: None,
}),
};
let result = unify_exprs(&e1, &e2);
assert!(
result.is_ok(),
"Alpha-equivalent expressions should unify: {:?}",
result
);
}
#[test]
fn test_alpha_equivalence_forall() {
let e1 = ProofExpr::ForAll {
variable: "x".to_string(),
body: Box::new(ProofExpr::Predicate {
name: "mortal".to_string(),
args: vec![ProofTerm::Variable("x".to_string())],
world: None,
}),
};
let e2 = ProofExpr::ForAll {
variable: "y".to_string(),
body: Box::new(ProofExpr::Predicate {
name: "mortal".to_string(),
args: vec![ProofTerm::Variable("y".to_string())],
world: None,
}),
};
let result = unify_exprs(&e1, &e2);
assert!(
result.is_ok(),
"Alpha-equivalent universals should unify: {:?}",
result
);
}
#[test]
fn test_alpha_equivalence_nested() {
let e1 = ProofExpr::Exists {
variable: "e".to_string(),
body: Box::new(ProofExpr::And(
Box::new(ProofExpr::Predicate {
name: "run".to_string(),
args: vec![ProofTerm::Variable("e".to_string())],
world: None,
}),
Box::new(ProofExpr::Predicate {
name: "agent".to_string(),
args: vec![
ProofTerm::Variable("e".to_string()),
ProofTerm::Constant("John".to_string()),
],
world: None,
}),
)),
};
let e2 = ProofExpr::Exists {
variable: "x".to_string(),
body: Box::new(ProofExpr::And(
Box::new(ProofExpr::Predicate {
name: "run".to_string(),
args: vec![ProofTerm::Variable("x".to_string())],
world: None,
}),
Box::new(ProofExpr::Predicate {
name: "agent".to_string(),
args: vec![
ProofTerm::Variable("x".to_string()),
ProofTerm::Constant("John".to_string()),
],
world: None,
}),
)),
};
let result = unify_exprs(&e1, &e2);
assert!(
result.is_ok(),
"Nested alpha-equivalent expressions should unify: {:?}",
result
);
}
}