use std::fmt;
use crate::ast::{Rule, Term, TermKind};
use crate::error::Error;
#[derive(Debug, Clone, PartialEq, Eq)]
enum Step {
App { head: String, arity: usize },
Int(i128),
Bind(String),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum Test {
App { head: String, arity: usize },
Int(i128),
}
#[derive(Debug, Default)]
pub(crate) struct Node {
pub(crate) tests: Vec<(Test, usize)>,
pub(crate) wildcard: Option<(String, usize)>,
pub(crate) accept: Option<usize>,
}
#[derive(Debug)]
pub struct Matcher {
pub(crate) nodes: Vec<Node>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Match<'t> {
pub rule: usize,
pub bindings: Vec<(String, &'t Term)>,
}
impl<'t> Match<'t> {
#[must_use]
pub fn get(&self, name: &str) -> Option<&'t Term> {
self.bindings.iter().find(|(bound, _)| bound == name).map(|(_, term)| *term)
}
}
impl Matcher {
pub fn build(path: &str, rules: &[Rule]) -> Result<Matcher, Vec<Error>> {
let mut matcher = Matcher { nodes: vec![Node::default()] };
let mut errors = Vec::new();
for (index, rule) in rules.iter().enumerate() {
let mut at = 0;
for step in flatten(&rule.pattern) {
at = matcher.follow(at, step);
}
match matcher.nodes[at].accept {
Some(first) => errors.push(Error {
path: path.to_owned(),
line: rule.line,
column: rule.column,
message: format!(
"this rule can never fire, because the rule on line {} matches everything it does",
rules[first].line
),
}),
None => matcher.nodes[at].accept = Some(index),
}
}
if errors.is_empty() { Ok(matcher) } else { Err(errors) }
}
fn follow(&mut self, at: usize, step: Step) -> usize {
let test = match step {
Step::App { head, arity } => Test::App { head, arity },
Step::Int(value) => Test::Int(value),
Step::Bind(name) => {
if let Some((_, next)) = &self.nodes[at].wildcard {
return *next;
}
let next = self.push();
self.nodes[at].wildcard = Some((name, next));
return next;
}
};
if let Some((_, next)) = self.nodes[at].tests.iter().find(|(have, _)| *have == test) {
return *next;
}
let next = self.push();
self.nodes[at].tests.push((test, next));
next
}
fn push(&mut self) -> usize {
self.nodes.push(Node::default());
self.nodes.len() - 1
}
#[must_use]
pub fn find<'t>(&self, term: &'t Term) -> Option<Match<'t>> {
let mut bindings = Vec::new();
let rule = self.run(0, vec![term], &mut bindings)?;
Some(Match { rule, bindings })
}
fn run<'t>(
&self,
at: usize,
mut left: Vec<&'t Term>,
bindings: &mut Vec<(String, &'t Term)>,
) -> Option<usize> {
let Some(subject) = left.pop() else {
return self.nodes[at].accept;
};
let node = &self.nodes[at];
for (test, next) in &node.tests {
let matched = match (test, &subject.kind) {
(Test::Int(want), TermKind::Int(have)) => want == have,
(Test::App { head, arity }, TermKind::App { head: name, args }) => {
head == name && *arity == args.len()
}
_ => false,
};
if !matched {
continue;
}
let mut deeper = left.clone();
if let TermKind::App { args, .. } = &subject.kind {
deeper.extend(args.iter().rev());
}
let depth = bindings.len();
if let Some(rule) = self.run(*next, deeper, bindings) {
return Some(rule);
}
bindings.truncate(depth);
}
let (name, next) = node.wildcard.as_ref()?;
let depth = bindings.len();
bindings.push((name.clone(), subject));
if let Some(rule) = self.run(*next, left, bindings) {
return Some(rule);
}
bindings.truncate(depth);
None
}
#[must_use]
pub fn len(&self) -> usize {
self.nodes.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.nodes.len() <= 1
}
}
fn flatten(pattern: &Term) -> Vec<Step> {
let mut out = Vec::new();
push_steps(pattern, &mut out);
out
}
fn push_steps(term: &Term, out: &mut Vec<Step>) {
match &term.kind {
TermKind::Var(name) => out.push(Step::Bind(name.clone())),
TermKind::Int(value) => out.push(Step::Int(*value)),
TermKind::App { head, args } => {
out.push(Step::App { head: head.clone(), arity: args.len() });
for arg in args {
push_steps(arg, out);
}
}
}
}
impl fmt::Display for Matcher {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.show(f, 0, 0)
}
}
impl Matcher {
fn show(&self, f: &mut fmt::Formatter<'_>, at: usize, depth: usize) -> fmt::Result {
let pad = " ".repeat(depth);
let node = &self.nodes[at];
if let Some(rule) = node.accept {
writeln!(f, "{pad}=> rule {rule}")?;
}
for (test, next) in &node.tests {
match test {
Test::App { head, arity } => writeln!(f, "{pad}{head}/{arity}")?,
Test::Int(value) => writeln!(f, "{pad}{value}")?,
}
self.show(f, *next, depth + 1)?;
}
if let Some((name, next)) = &node.wildcard {
writeln!(f, "{pad}bind {name}")?;
self.show(f, *next, depth + 1)?;
}
Ok(())
}
}