use crate::typing::term::{self, Subst, Term};
use std::fmt;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RewriteRule {
pub lhs: Term,
pub rhs: Term,
}
impl fmt::Display for RewriteRule {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} ⇝ {}", self.lhs, self.rhs)
}
}
#[derive(Clone, Debug, Default)]
pub struct Normalizer {
rules: Vec<RewriteRule>,
}
const MAX_STEPS: usize = 1024;
impl Normalizer {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn from_rules(rules: Vec<RewriteRule>) -> Self {
Self { rules }
}
#[must_use]
pub fn rules(&self) -> &[RewriteRule] {
&self.rules
}
#[must_use]
pub fn normalize(&self, t: &Term) -> Term {
if self.rules.is_empty() {
return t.clone();
}
let normalized = match t {
Term::Con(label, kids) => {
Term::Con(label.clone(), kids.iter().map(|k| self.normalize(k)).collect())
}
other => other.clone(),
};
let mut t = normalized;
for _ in 0..MAX_STEPS {
match self.reduce_once(&t) {
Some(next) => t = self.normalize(&next),
None => return t,
}
}
t
}
fn reduce_once(&self, t: &Term) -> Option<Term> {
for rule in &self.rules {
if let Some(subst) = matches(&rule.lhs, t) {
return Some(term::apply(&rule.rhs, &subst));
}
}
None
}
}
#[must_use]
pub fn matches(pat: &Term, t: &Term) -> Option<Subst> {
let mut subst = Subst::new();
match_into(pat, t, &mut subst).then_some(subst)
}
fn match_into(pat: &Term, t: &Term, subst: &mut Subst) -> bool {
match (pat, t) {
(Term::Var(x), _) => match subst.get(x) {
Some(bound) => bound == t,
None => {
subst.insert(x.clone(), t.clone());
true
}
},
(Term::Con(f, ps), Term::Con(g, ts)) => {
f == g && ps.len() == ts.len() && ps.iter().zip(ts).all(|(p, t)| match_into(p, t, subst))
}
(Term::Leaf(p), Term::Leaf(q)) => p == q,
_ => false,
}
}
#[must_use]
pub fn failure_is_stable(norm: &Normalizer, a: &Term, b: &Term) -> bool {
norm.rules().is_empty() || (a.is_ground() && b.is_ground())
}
#[must_use]
pub fn unify_modulo(
norm: &Normalizer,
a: &Term,
b: &Term,
subst: &mut Subst,
occurs_check: bool,
) -> bool {
if norm.rules().is_empty() {
return term::unify(a, b, subst, occurs_check);
}
let a = norm.normalize(a);
let b = norm.normalize(b);
term::unify(&a, &b, subst, occurs_check)
}
#[cfg(test)]
mod tests {
use super::*;
fn con(label: &str, kids: Vec<Term>) -> Term {
Term::con(label, kids)
}
#[test]
fn empty_theory_is_identity() {
let norm = Normalizer::new();
let t = con("Fun", vec![Term::leaf("A"), Term::var("B")]);
assert_eq!(norm.normalize(&t), t);
}
#[test]
fn matches_binds_subtree() {
let pat = con("Fun", vec![Term::var("A"), Term::var("B")]);
let inner = con("Fun", vec![Term::leaf("b"), Term::leaf("c")]);
let t = con("Fun", vec![Term::leaf("a"), inner.clone()]);
let subst = matches(&pat, &t).expect("should match");
assert_eq!(subst.get("A"), Some(&Term::leaf("a")));
assert_eq!(subst.get("B"), Some(&inner));
}
#[test]
fn matches_rejects_label_clash() {
let pat = con("Fun", vec![Term::var("A")]);
let t = con("Prod", vec![Term::leaf("a")]);
assert!(matches(&pat, &t).is_none());
}
#[test]
fn repeated_metavar_must_agree() {
let pat = con("Pair", vec![Term::var("A"), Term::var("A")]);
let same = con("Pair", vec![Term::leaf("x"), Term::leaf("x")]);
let diff = con("Pair", vec![Term::leaf("x"), Term::leaf("y")]);
assert!(matches(&pat, &same).is_some());
assert!(matches(&pat, &diff).is_none());
}
#[test]
fn rewrite_unfolds_synonym() {
let rule = RewriteRule {
lhs: Term::leaf("Bool"),
rhs: con("Sum", vec![Term::leaf("Unit"), Term::leaf("Unit")]),
};
let norm = Normalizer::from_rules(vec![rule]);
assert_eq!(
norm.normalize(&Term::leaf("Bool")),
con("Sum", vec![Term::leaf("Unit"), Term::leaf("Unit")])
);
assert_eq!(
norm.normalize(&con("Fun", vec![Term::leaf("Bool"), Term::leaf("A")])),
con(
"Fun",
vec![
con("Sum", vec![Term::leaf("Unit"), Term::leaf("Unit")]),
Term::leaf("A")
]
)
);
}
#[test]
fn rewrite_projection_with_metavars() {
let rule = RewriteRule {
lhs: con("fst", vec![con("Pair", vec![Term::var("A"), Term::var("B")])]),
rhs: Term::var("A"),
};
let norm = Normalizer::from_rules(vec![rule]);
let t = con("fst", vec![con("Pair", vec![Term::leaf("X"), Term::leaf("Y")])]);
assert_eq!(norm.normalize(&t), Term::leaf("X"));
}
#[test]
fn unify_modulo_sees_through_synonym() {
let rule = RewriteRule {
lhs: Term::leaf("Bool"),
rhs: con("Sum", vec![Term::leaf("Unit"), Term::leaf("Unit")]),
};
let norm = Normalizer::from_rules(vec![rule]);
let expanded = con("Sum", vec![Term::leaf("Unit"), Term::leaf("Unit")]);
let mut s = Subst::new();
assert!(unify_modulo(&norm, &Term::leaf("Bool"), &expanded, &mut s, true));
let mut s2 = Subst::new();
assert!(!term::unify(&Term::leaf("Bool"), &expanded, &mut s2, true));
}
#[test]
fn empty_theory_modulo_equals_unify() {
let norm = Normalizer::new();
let a = con("Fun", vec![Term::var("A"), Term::leaf("X")]);
let b = con("Fun", vec![Term::leaf("Y"), Term::var("B")]);
let (mut s1, mut s2) = (Subst::new(), Subst::new());
assert_eq!(
unify_modulo(&norm, &a, &b, &mut s1, true),
term::unify(&a, &b, &mut s2, true)
);
}
}