use super::Pattern;
use super::pattern::Match;
use crate::engine::structure::{FusionChild, FusionNode};
use std::collections::HashMap;
use std::fmt;
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum Term {
Var(String),
Con(String, Vec<Term>),
Leaf(Pattern),
}
pub type Subst = HashMap<String, Term>;
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Evidence {
pub term: Term,
pub eqs: Vec<(String, Term)>,
}
impl Evidence {
#[must_use]
pub fn new(term: Term) -> Self {
Self {
term,
eqs: Vec::new(),
}
}
#[must_use]
pub fn top() -> Self {
Self::new(Term::top())
}
#[must_use]
pub fn bottom() -> Self {
Self::new(Term::bottom())
}
#[must_use]
pub fn is_top(&self) -> bool {
self.term.is_top()
}
}
impl From<Term> for Evidence {
fn from(term: Term) -> Self {
Self::new(term)
}
}
impl fmt::Display for Evidence {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.term)
}
}
impl fmt::Display for Term {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Var(n) => write!(f, "?{}", n.split('#').next().unwrap_or(n)),
Self::Leaf(p) => write!(f, "{p}"),
Self::Con(label, kids) => {
write!(f, "{label}(")?;
for (i, k) in kids.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{k}")?;
}
write!(f, ")")
}
}
}
}
impl Term {
#[must_use]
pub fn var(name: impl Into<String>) -> Self {
Self::Var(name.into())
}
#[must_use]
pub fn leaf(s: &str) -> Self {
Self::Leaf(Pattern::raw(s))
}
#[must_use]
pub fn raw(s: impl AsRef<str>) -> Self {
Self::Leaf(Pattern::raw(s.as_ref()))
}
#[must_use]
pub fn con(label: impl Into<String>, kids: Vec<Term>) -> Self {
Self::Con(label.into(), kids)
}
#[must_use]
pub fn top() -> Self {
Self::Var("⊤".into())
}
#[must_use]
pub fn bottom() -> Self {
Self::Leaf(Pattern::bottom())
}
#[must_use]
pub fn is_top(&self) -> bool {
matches!(self, Self::Var(n) if n == "⊤")
}
#[must_use]
pub fn has_vars(&self) -> bool {
match self {
Self::Var(_) => true,
Self::Con(_, kids) => kids.iter().any(Self::has_vars),
Self::Leaf(_) => false,
}
}
#[must_use]
pub fn vars(&self) -> Vec<&str> {
match self {
Self::Var(n) => vec![n],
Self::Con(_, kids) => kids.iter().flat_map(Self::vars).collect(),
Self::Leaf(_) => Vec::new(),
}
}
#[must_use]
pub fn is_ground(&self) -> bool {
!self.has_vars()
}
#[must_use]
pub fn freshen(&self, next: &mut u64) -> Self {
fn go(t: &Term, map: &mut HashMap<String, String>, next: &mut u64) -> Term {
match t {
Term::Var(x) => {
let fresh = map.entry(x.clone()).or_insert_with(|| {
let n = format!("{x}%{next}");
*next += 1;
n
});
Term::Var(fresh.clone())
}
Term::Con(l, ks) => {
Term::Con(l.clone(), ks.iter().map(|k| go(k, map, next)).collect())
}
Term::Leaf(p) => Term::Leaf(p.clone()),
}
}
go(self, &mut HashMap::new(), next)
}
#[must_use]
pub fn from_node(node: &FusionNode) -> Self {
let text = node.text();
if let Some(name) = text.trim().strip_prefix('?') {
return Self::var(name.trim());
}
let mut child_nodes = node.children().filter_map(|c| match c {
FusionChild::Node(n) => Some(n),
FusionChild::Terminal { .. } => None,
});
if node.is_transparent()
&& let Some(child) = child_nodes.next()
{
return Self::from_node(&child);
}
let kids: Vec<Term> = child_nodes.map(|n| Self::from_node(&n)).collect();
if kids.is_empty() {
return Self::leaf(text.trim());
}
Self::con(node.nt_name().unwrap_or("?"), kids)
}
}
#[must_use]
pub fn walk(t: &Term, s: &Subst) -> Term {
let mut t = t.clone();
while let Term::Var(x) = &t {
match s.get(x) {
Some(next) => t = next.clone(),
None => break,
}
}
t
}
#[must_use]
fn occurs(x: &str, t: &Term, s: &Subst) -> bool {
match walk(t, s) {
Term::Var(y) => x == y,
Term::Con(_, kids) => kids.iter().any(|k| occurs(x, k, s)),
Term::Leaf(_) => false,
}
}
#[must_use]
pub fn unify(a: &Term, b: &Term, s: &mut Subst, occurs_check: bool) -> bool {
let (a, b) = (walk(a, s), walk(b, s));
match (&a, &b) {
_ if a.is_top() || b.is_top() => true,
(Term::Var(x), Term::Var(y)) if x == y => true,
(Term::Var(x), t) | (t, Term::Var(x)) => {
if occurs_check && occurs(x, t, s) {
return false;
}
s.insert(x.clone(), t.clone());
true
}
(Term::Con(f, xs), Term::Con(g, ys)) => {
f == g
&& xs.len() == ys.len()
&& xs.iter().zip(ys).all(|(x, y)| unify(x, y, s, occurs_check))
}
(Term::Leaf(p), Term::Leaf(q)) => !matches!(p.unify(q), Match::Empty),
_ => false,
}
}
#[must_use]
pub fn apply(t: &Term, s: &Subst) -> Term {
match walk(t, s) {
Term::Con(label, kids) => Term::Con(label, kids.iter().map(|k| apply(k, s)).collect()),
other => other,
}
}