pub trait Subject {
type Node: Copy;
fn head(&self, node: Self::Node) -> Option<(&str, usize)>;
fn arg(&self, node: Self::Node, index: usize) -> Self::Node;
fn int(&self, node: Self::Node) -> Option<i128>;
fn same(&self, a: Self::Node, b: Self::Node) -> bool;
}
#[derive(Debug, Clone, Copy)]
pub struct Node {
pub heads: &'static [(&'static str, usize, u32)],
pub ints: &'static [(i128, u32)],
pub same: &'static [(usize, u32)],
pub wildcard: Option<(&'static str, u32)>,
pub accept: Option<u32>,
}
impl Node {
#[must_use]
pub fn branch(&self, head: &str, arity: usize) -> Option<u32> {
let found = self
.heads
.binary_search_by(|(have, count, _)| have.cmp(&head).then(count.cmp(&arity)))
.ok()?;
Some(self.heads[found].2)
}
#[must_use]
pub fn literal(&self, value: i128) -> Option<u32> {
let found = self.ints.binary_search_by(|(have, _)| have.cmp(&value)).ok()?;
Some(self.ints[found].1)
}
}
#[derive(Debug)]
pub enum Piece {
Var {
name: &'static str,
index: usize,
},
Int(i128),
App {
head: &'static str,
arity: usize,
},
}
pub type Guard = fn(&[Option<i128>]) -> bool;
#[derive(Debug)]
pub struct Rule {
pub pattern: &'static str,
pub replacement: &'static [Piece],
pub guard: Option<Guard>,
pub line: u32,
}
impl Rule {
#[must_use]
pub fn head(&self) -> Option<&'static str> {
match self.replacement.first() {
Some(Piece::App { head, .. }) => Some(head),
_ => None,
}
}
}
#[derive(Debug)]
pub struct Table {
pub source: &'static str,
pub nodes: &'static [Node],
pub rules: &'static [Rule],
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Match<N> {
pub rule: usize,
pub bindings: Vec<N>,
}
impl Table {
#[must_use]
pub fn find<S: Subject>(&self, subject: &S, term: S::Node) -> Option<Match<S::Node>> {
let mut bindings = Vec::new();
let rule = self.run(subject, 0, vec![term], &mut bindings)?;
Some(Match { rule, bindings })
}
#[must_use]
pub fn rule<N>(&self, found: &Match<N>) -> &Rule {
&self.rules[found.rule]
}
fn run<S: Subject>(
&self,
subject: &S,
at: usize,
mut left: Vec<S::Node>,
bindings: &mut Vec<S::Node>,
) -> Option<usize> {
let Some(term) = left.pop() else {
return self.accept(subject, at, bindings);
};
let node = &self.nodes[at];
let head = subject.head(term);
if let Some(next) = head.and_then(|(name, arity)| node.branch(name, arity)) {
if let Some(rule) = self.take(subject, next, (term, head), &left, bindings) {
return Some(rule);
}
}
if !node.ints.is_empty() {
if let Some(next) = subject.int(term).and_then(|value| node.literal(value)) {
if let Some(rule) = self.take(subject, next, (term, head), &left, bindings) {
return Some(rule);
}
}
}
for &(index, next) in node.same {
if bindings.get(index).is_some_and(|&bound| subject.same(bound, term)) {
if let Some(rule) = self.take(subject, next, (term, head), &left, bindings) {
return Some(rule);
}
}
}
let (_, next) = node.wildcard.as_ref()?;
let depth = bindings.len();
bindings.push(term);
if let Some(rule) = self.run(subject, *next as usize, left, bindings) {
return Some(rule);
}
bindings.truncate(depth);
None
}
fn take<S: Subject>(
&self,
subject: &S,
next: u32,
term: (S::Node, Option<(&str, usize)>),
left: &[S::Node],
bindings: &mut Vec<S::Node>,
) -> Option<usize> {
let (term, head) = term;
let mut deeper = left.to_vec();
if let Some((_, arity)) = head {
for index in (0..arity).rev() {
deeper.push(subject.arg(term, index));
}
}
let depth = bindings.len();
if let Some(rule) = self.run(subject, next as usize, deeper, bindings) {
return Some(rule);
}
bindings.truncate(depth);
None
}
fn accept<S: Subject>(&self, subject: &S, at: usize, bindings: &[S::Node]) -> Option<usize> {
let rule = self.nodes[at].accept? as usize;
if let Some(guard) = self.rules[rule].guard {
let values: Vec<Option<i128>> =
bindings.iter().map(|&node| subject.int(node)).collect();
if !guard(&values) {
return None;
}
}
Some(rule)
}
}
#[cfg(test)]
mod tests {
use super::{Match, Node, Piece, Rule, Subject, Table};
#[derive(Debug)]
enum Held {
Int(i128),
App(String, Vec<usize>),
}
#[derive(Debug, Default)]
struct Terms {
nodes: Vec<Held>,
}
impl Terms {
fn constant(&mut self, value: i128) -> usize {
self.nodes.push(Held::Int(value));
self.nodes.len() - 1
}
fn app(&mut self, head: &str, args: &[usize]) -> usize {
self.nodes.push(Held::App(head.to_owned(), args.to_vec()));
self.nodes.len() - 1
}
}
impl Subject for Terms {
type Node = usize;
fn head(&self, node: usize) -> Option<(&str, usize)> {
match &self.nodes[node] {
Held::App(head, args) => Some((head.as_str(), args.len())),
Held::Int(_) => None,
}
}
fn arg(&self, node: usize, index: usize) -> usize {
match &self.nodes[node] {
Held::App(_, args) => args[index],
Held::Int(_) => unreachable!("a constant has no arguments"),
}
}
fn int(&self, node: usize) -> Option<i128> {
match self.nodes[node] {
Held::Int(value) => Some(value),
Held::App(..) => None,
}
}
fn same(&self, a: usize, b: usize) -> bool {
a == b
}
}
const NOTHING: Node = Node { heads: &[], ints: &[], same: &[], wildcard: None, accept: None };
static NODES: &[Node] = &[
Node { heads: &[("add", 2, 1), ("and", 2, 5)], ..NOTHING },
Node { wildcard: Some(("x", 2)), ..NOTHING },
Node { ints: &[(0, 3)], wildcard: Some(("k", 4)), ..NOTHING },
Node { accept: Some(0), ..NOTHING },
Node { accept: Some(1), ..NOTHING },
Node { wildcard: Some(("x", 6)), ..NOTHING },
Node { same: &[(0, 7)], ..NOTHING },
Node { accept: Some(2), ..NOTHING },
];
fn not_negative(bound: &[Option<i128>]) -> bool {
let Some(Some(k)) = bound.get(1).copied() else { return false };
k >= 0
}
static RULES: &[Rule] = &[
Rule {
pattern: "(add x 0)",
replacement: &[Piece::Var { name: "x", index: 0 }],
guard: None,
line: 1,
},
Rule {
pattern: "(add x k)",
replacement: &[
Piece::App { head: "add_immediate", arity: 2 },
Piece::Var { name: "x", index: 0 },
Piece::Var { name: "k", index: 1 },
],
guard: Some(not_negative),
line: 2,
},
Rule {
pattern: "(and x x)",
replacement: &[Piece::Var { name: "x", index: 0 }],
guard: None,
line: 3,
},
];
static TABLE: Table = Table { source: "rules/test.rules", nodes: NODES, rules: RULES };
fn add(terms: &mut Terms, second: usize) -> usize {
let first = terms.app("v0", &[]);
terms.app("add", &[first, second])
}
#[test]
fn the_rule_that_names_the_operand_beats_the_rule_that_takes_anything() {
let mut terms = Terms::default();
let zero = terms.constant(0);
let term = add(&mut terms, zero);
let found = TABLE.find(&terms, term).expect("a rule fires");
assert_eq!(TABLE.rule(&found).pattern, "(add x 0)");
}
#[test]
fn a_match_gives_back_what_the_pattern_bound_in_the_order_it_bound_it() {
let mut terms = Terms::default();
let seven = terms.constant(7);
let term = add(&mut terms, seven);
let found = TABLE.find(&terms, term).expect("a rule fires");
let rule = TABLE.rule(&found);
assert_eq!(rule.pattern, "(add x k)");
assert_eq!(rule.head(), Some("add_immediate"));
assert_eq!(found.bindings.len(), 2);
assert_eq!(found.bindings[1], seven);
assert_eq!(terms.int(found.bindings[1]), Some(7));
}
#[test]
fn a_guard_that_refuses_takes_its_rule_out_of_the_running() {
let mut terms = Terms::default();
let negative = terms.constant(-1);
let term = add(&mut terms, negative);
assert_eq!(TABLE.find(&terms, term), None);
}
#[test]
fn a_guard_about_a_number_refuses_an_operand_that_is_not_one() {
let mut terms = Terms::default();
let other = terms.app("v1", &[]);
let term = add(&mut terms, other);
assert_eq!(TABLE.find(&terms, term), None);
}
#[test]
fn a_term_no_rule_covers_finds_no_rule() {
let mut terms = Terms::default();
let x = terms.app("v0", &[]);
let y = terms.app("v1", &[]);
let term = terms.app("no.such.head", &[x, y]);
assert_eq!(TABLE.find(&terms, term), None);
}
#[test]
fn a_pattern_that_names_one_hole_twice_matches_a_term_that_has_one_thing_in_both() {
let mut terms = Terms::default();
let x = terms.app("v0", &[]);
let term = terms.app("and", &[x, x]);
let found = TABLE.find(&terms, term).expect("a rule fires");
assert_eq!(TABLE.rule(&found).pattern, "(and x x)");
assert_eq!(found.bindings, vec![x]);
}
#[test]
fn a_pattern_that_names_one_hole_twice_refuses_a_term_that_has_two_things_in_it() {
let mut terms = Terms::default();
let x = terms.app("v0", &[]);
let y = terms.app("v1", &[]);
let term = terms.app("and", &[x, y]);
assert_eq!(TABLE.find(&terms, term), None);
}
#[test]
fn a_branch_is_found_by_searching_the_node_and_not_by_reading_it() {
static WIDE: &[(&str, usize, u32)] = &[
("add.i16", 2, 1),
("add.i32", 2, 2),
("add.i64", 2, 3),
("add.i64", 3, 4),
("sub.i32", 2, 5),
("sub.i64", 2, 6),
("xor.i8", 2, 7),
];
let node = Node { heads: WIDE, ..NOTHING };
assert!(WIDE.is_sorted(), "the search is only a search if the node is in order");
assert_eq!(node.branch("add.i64", 2), Some(3));
assert_eq!(node.branch("add.i16", 2), Some(1));
assert_eq!(node.branch("xor.i8", 2), Some(7));
assert_eq!(node.branch("add.i64", 3), Some(4));
assert_eq!(node.branch("mul.i64", 2), None);
assert_eq!(node.branch("sub.i32", 3), None);
}
#[test]
fn a_literal_is_found_by_searching_too() {
let node = Node { ints: &[(-8, 1), (0, 2), (1, 3), (4096, 4)], ..NOTHING };
assert_eq!(node.literal(-8), Some(1));
assert_eq!(node.literal(0), Some(2));
assert_eq!(node.literal(4096), Some(4));
assert_eq!(node.literal(7), None);
}
#[test]
fn the_head_is_asked_about_before_the_value_and_the_value_before_a_repeat() {
#[derive(Debug)]
struct Both;
impl Subject for Both {
type Node = u8;
fn head(&self, node: u8) -> Option<(&str, usize)> {
if node == 0 { Some(("f", 2)) } else { Some(("k", 0)) }
}
fn int(&self, node: u8) -> Option<i128> {
if node == 0 { None } else { Some(7) }
}
fn arg(&self, _: u8, _: usize) -> u8 {
1
}
fn same(&self, _: u8, _: u8) -> bool {
true
}
}
static FOUR: &[Rule] = &[
Rule { pattern: "the head", replacement: &[], guard: None, line: 1 },
Rule { pattern: "the value", replacement: &[], guard: None, line: 2 },
Rule { pattern: "the repeat", replacement: &[], guard: None, line: 3 },
Rule { pattern: "the hole", replacement: &[], guard: None, line: 4 },
];
fn table(second: &'static Node) -> Table {
let nodes: &'static [Node] = Box::leak(Box::new([
Node { heads: &[("f", 2, 1)], ..NOTHING },
Node { wildcard: Some(("x", 2)), ..NOTHING },
*second,
Node { accept: Some(0), ..NOTHING },
Node { accept: Some(1), ..NOTHING },
Node { accept: Some(2), ..NOTHING },
Node { accept: Some(3), ..NOTHING },
]));
Table { source: "rules/test.rules", nodes, rules: FOUR }
}
static MIXED: Node = Node {
heads: &[("k", 0, 3)],
ints: &[(7, 4)],
same: &[(0, 5)],
wildcard: Some(("y", 6)),
accept: None,
};
assert_eq!(table(&MIXED).find(&Both, 0).map(|found| found.rule), Some(0));
static WITHOUT_HEAD: Node = Node { heads: &[], ..MIXED };
assert_eq!(table(&WITHOUT_HEAD).find(&Both, 0).map(|found| found.rule), Some(1));
static REPEAT: Node = Node { ints: &[], ..WITHOUT_HEAD };
assert_eq!(table(&REPEAT).find(&Both, 0).map(|found| found.rule), Some(2));
static HOLE: Node = Node { same: &[], ..REPEAT };
assert_eq!(table(&HOLE).find(&Both, 0).map(|found| found.rule), Some(3));
}
#[test]
fn a_match_names_the_rule_it_found() {
let mut terms = Terms::default();
let zero = terms.constant(0);
let term = add(&mut terms, zero);
assert_eq!(TABLE.find(&terms, term), Some(Match { rule: 0, bindings: vec![term - 1] }));
}
}