Skip to main content

rucc_base/
rules.rs

1//! Matching a set of rules against a term.
2//!
3//! Design: `spec/10-backend.md` section 10.2 and `spec/optimizer/13-rewrite-rules.md`. The rules
4//! themselves are rule files, one per rule set, and the automaton they compile into is generated
5//! by `rucc-rules` when the crate that owns the file is built. What is here is the walk over
6//! that automaton, which is the same walk for every rule set and is written once.
7//!
8//! # Why this is at the bottom of the stack
9//!
10//! Two crates match with a generated table and neither can see the other. `rucc-codegen` lowers
11//! IR to machine terms and `rucc-opt` rewrites IR to IR, and a lowering and a rewrite are the
12//! same claim about two terms, so they are the same trie and the same walk. Putting the walk
13//! here rather than in either of them is what keeps that true rather than merely intended, and
14//! it costs nothing: none of this knows what an instruction is, what a value is, or what C is.
15//!
16//! # What a subject is
17//!
18//! A rule matches a term, and the compiler does not have terms: it has a function full of
19//! instructions, and what a pattern is about is one of them and whatever it was computed from.
20//! So the walk is written against [`Subject`], which is the three questions the automaton asks
21//! of whatever it is matching, and a caller answers them out of the IR without building a term
22//! to be thrown away. A test can answer them out of anything at all, which is what the tests at
23//! the bottom of this file do.
24//!
25//! # What a match gives back
26//!
27//! The rule that fired and what its pattern bound, in the order the pattern binds it. The
28//! bindings are positions rather than names because that is what the walk has, and the rule
29//! carries the names for anything that has to say what it did. Building the replacement out of
30//! [`Piece`] belongs to the caller rather than to this file, because what a replacement becomes
31//! is a machine instruction in one crate and an IR instruction in the other, and this module is
32//! about matching.
33//!
34//! # A name written twice
35//!
36//! A pattern may write one name in two places, which is how `x & x` is said. The second place
37//! becomes a branch in [`Node::same`] rather than a hole, and it asks the subject whether the two
38//! are the same thing rather than comparing nodes, because a node is a place and two places can
39//! hold one value. It is a concrete test, so it is tried before the wildcard for the same reason
40//! every other test is: a rule about one value in both operands is more specific than a rule
41//! about any two.
42//!
43//! # Order
44//!
45//! At every node the concrete tests are tried before the branch that takes anything, so a rule
46//! naming an operand is tried before a rule taking whatever is there. That is the maximal munch
47//! `spec/10-backend.md` asks for, and it falls out of the shape of the trie rather than being
48//! sorted for. Among rules that are equally specific the first one written wins.
49//!
50//! The concrete tests are three kinds of question and they are asked in this order: the head of
51//! the term, then its value as a constant, then whether it is what an earlier binding took.
52//! `spec/optimizer/36-lowering-and-isel.md` section 36.5 asks that the order be stated rather
53//! than left to be read out of what the matcher does, so it is stated here, next to the walk that
54//! applies it. It decides nothing in any rule set shipped today, because deciding something would
55//! need one node to ask two kinds of question about one place and none does, which is a number
56//! `rucc-rules` prints in the header of every table it generates.
57//!
58//! # Finding a branch
59//!
60//! A term has one head and a constant has one value, so at most one head branch and at most one
61//! value branch can match, and the two lists are sorted by the thing they are asked about. That
62//! makes finding the branch a binary search rather than a walk over the node, which is the
63//! difference section 36.5 is about: the widest node of the x86-64 rule set has a hundred and
64//! sixty seven heads on it, and the selector reaches that node once for every instruction in the
65//! program. A repeat of an earlier binding is not searchable, because two of them can hold the
66//! same value, so those stay in the order the rules were written and there are never many.
67//!
68//! A guard is part of deciding whether a rule fires, so a rule whose guard is false is a rule
69//! that did not match, and the walk carries on looking rather than giving up. What that costs is
70//! the search from where the guard failed, which is the price of a guard being allowed to be
71//! about the values rather than only about the shape.
72
73/// The bits of a term the automaton asks about.
74///
75/// A node is whatever the thing doing the matching calls one of its terms: an IR value, an index
76/// into an arena, a pointer. It has to be cheap to copy because the walk keeps a stack of them.
77pub trait Subject {
78    /// What this subject calls one of its terms.
79    type Node: Copy;
80
81    /// The head of a term and how many arguments it has, or nothing if the term is not an
82    /// application. An IR instruction answers with its opcode and its width, spelled the way the
83    /// rule file spells it.
84    fn head(&self, node: Self::Node) -> Option<(&str, usize)>;
85
86    /// One argument of a term, counted from zero. Only ever asked for an argument the answer to
87    /// [`Subject::head`] said was there.
88    fn arg(&self, node: Self::Node, index: usize) -> Self::Node;
89
90    /// The value of a term that is a constant, or nothing if it is not one. This is what a
91    /// pattern matching a literal is asking, and what a guard reads.
92    fn int(&self, node: Self::Node) -> Option<i128>;
93
94    /// Whether two terms are the same thing, which is what a pattern that writes one name in two
95    /// places is asking.
96    ///
97    /// This is a question for the subject rather than something the walk can answer by comparing
98    /// nodes, because a node is a place and two places can hold one value. In
99    /// `(and.i32 (value.i32 x) (value.i32 x))` the two operands are operand zero and operand
100    /// one, which are different places, and what the rule wants to know is whether the same
101    /// value is in both. A subject that cannot tell may answer `false`, which costs the rule a
102    /// match it could have had and never gives it one it should not.
103    fn same(&self, a: Self::Node, b: Self::Node) -> bool;
104}
105
106/// One node of the trie over the patterns.
107///
108/// The branches are held by the kind of question they ask rather than in one list, which is what
109/// lets the two that can be searched be searched.
110#[derive(Debug, Clone, Copy)]
111pub struct Node {
112    /// The branches taken on the head of the subterm, as the name, how many arguments it takes,
113    /// and where to go. Sorted by the first two, which is what [`Node::branch`] needs.
114    pub heads: &'static [(&'static str, usize, u32)],
115    /// The branches taken on the value of a subterm that is a constant, sorted by the value.
116    pub ints: &'static [(i128, u32)],
117    /// The branches taken when the subterm is the same thing as a binding this pattern already
118    /// made, named by which binding it is. A pattern writes one where it writes a name for the
119    /// second time, so this is how `x & x` is told apart from `x & y`. In the order the rules
120    /// were written, because two of them can match one subterm.
121    pub same: &'static [(usize, u32)],
122    /// The branch that takes anything, and the name the first rule to reach it gave that hole.
123    pub wildcard: Option<(&'static str, u32)>,
124    /// The rule that ends here, if one does.
125    pub accept: Option<u32>,
126}
127
128impl Node {
129    /// The branch for a term with this head and this many arguments, if the node has one.
130    ///
131    /// A binary search, which is the whole point of the list being sorted. At most one branch can
132    /// answer, so nothing about which rule fires depends on the list being in this order rather
133    /// than in the order the rules were written.
134    #[must_use]
135    pub fn branch(&self, head: &str, arity: usize) -> Option<u32> {
136        let found = self
137            .heads
138            .binary_search_by(|(have, count, _)| have.cmp(&head).then(count.cmp(&arity)))
139            .ok()?;
140        Some(self.heads[found].2)
141    }
142
143    /// The branch for a constant of this value, if the node has one.
144    #[must_use]
145    pub fn literal(&self, value: i128) -> Option<u32> {
146        let found = self.ints.binary_search_by(|(have, _)| have.cmp(&value)).ok()?;
147        Some(self.ints[found].1)
148    }
149}
150
151/// One piece of a replacement, in the pre-order that builds it.
152#[derive(Debug)]
153pub enum Piece {
154    /// Whatever the pattern bound at this position.
155    Var {
156        /// The name the rule gave it, for anything that has to say what it did.
157        name: &'static str,
158        /// Which binding of the match it is.
159        index: usize,
160    },
161    /// A constant written in the rule.
162    Int(i128),
163    /// A constant the rule works out from the ones the pattern matched.
164    ///
165    /// This is what lets a rule be written once per width rather than once per constant. A shift
166    /// that stands in for a multiplication by a power of two shifts by the log of that power, and
167    /// the log is a number no rule can write down until it has seen which power it matched.
168    Computed {
169        /// The computation as the rule file writes it, for anything that has to say what it did.
170        text: &'static str,
171        /// What it works out.
172        work: Computation,
173    },
174    /// A term the rule writes, which is an instruction once the caller has built it.
175    App {
176        /// The name in head position.
177        head: &'static str,
178        /// How many arguments it takes.
179        arity: usize,
180    },
181}
182
183/// A condition on the constants a pattern matched.
184///
185/// It is handed one entry per binding, holding the value of that binding when it has one. A
186/// guard about a binding that is not a constant is false, which is how a rule about a number
187/// declines an operand that is a register.
188pub type Guard = fn(&[Option<i128>]) -> bool;
189
190/// A number worked out from the constants a pattern matched.
191///
192/// Handed one entry per binding, the same as a [`Guard`] is, and for the same reason: the
193/// computation is written in the names the pattern bound and those are positions by the time it
194/// runs. It gives nothing back when a binding it reads is not a constant, which is the answer a
195/// guard gives as false, and the rule does not fire.
196pub type Computation = fn(&[Option<i128>]) -> Option<i128>;
197
198/// One rule, as much of it as matching needs.
199#[derive(Debug)]
200pub struct Rule {
201    /// The pattern as it is written in the rule file, for diagnostics and for tests.
202    pub pattern: &'static str,
203    /// What to put in the matched term's place, flattened into pre-order.
204    pub replacement: &'static [Piece],
205    /// The condition on the match, if the rule has one.
206    pub guard: Option<Guard>,
207    /// The line of the rule file this rule starts on.
208    pub line: u32,
209}
210
211impl Rule {
212    /// The head of the replacement, which is what this rule writes.
213    #[must_use]
214    pub fn head(&self) -> Option<&'static str> {
215        match self.replacement.first() {
216            Some(Piece::App { head, .. }) => Some(head),
217            _ => None,
218        }
219    }
220}
221
222/// A set of rules, as an automaton over their patterns.
223#[derive(Debug)]
224pub struct Table {
225    /// The rule file this was built from, so that anything said about a rule can name a file
226    /// somebody can open.
227    pub source: &'static str,
228    /// The trie. Node zero is the root.
229    pub nodes: &'static [Node],
230    /// The rules, in the order the file writes them.
231    pub rules: &'static [Rule],
232}
233
234/// What a successful match found.
235#[derive(Debug, Clone, PartialEq, Eq)]
236pub struct Match<N> {
237    /// Which rule of the table fired.
238    pub rule: usize,
239    /// What the pattern bound, in the order it binds it.
240    pub bindings: Vec<N>,
241}
242
243impl Table {
244    /// The rule that fires on this term, and what it bound.
245    ///
246    /// The term is matched as a whole. Finding the terms in a function worth matching is the
247    /// caller's job and not this one's.
248    #[must_use]
249    pub fn find<S: Subject>(&self, subject: &S, term: S::Node) -> Option<Match<S::Node>> {
250        let mut bindings = Vec::new();
251        let rule = self.run(subject, 0, vec![term], &mut bindings)?;
252        Some(Match { rule, bindings })
253    }
254
255    /// The rule a match found, which is the one thing every caller wants out of it.
256    #[must_use]
257    pub fn rule<N>(&self, found: &Match<N>) -> &Rule {
258        &self.rules[found.rule]
259    }
260
261    /// Walk the trie and the subject together.
262    ///
263    /// `left` is the subterms still to be matched, innermost last, so that popping gives the
264    /// pre-order the patterns were flattened in.
265    fn run<S: Subject>(
266        &self,
267        subject: &S,
268        at: usize,
269        mut left: Vec<S::Node>,
270        bindings: &mut Vec<S::Node>,
271    ) -> Option<usize> {
272        let Some(term) = left.pop() else {
273            return self.accept(subject, at, bindings);
274        };
275        let node = &self.nodes[at];
276        let head = subject.head(term);
277
278        // The head of the term, which is the question nearly every branch of nearly every node
279        // is about and the one that has to be found rather than looked for.
280        if let Some(next) = head.and_then(|(name, arity)| node.branch(name, arity)) {
281            if let Some(rule) = self.take(subject, next, (term, head), &left, bindings) {
282                return Some(rule);
283            }
284        }
285
286        // Its value, if it is a constant and if this node asks about one. The emptiness is
287        // checked first because asking the subject for a value costs something and most nodes
288        // have nothing to compare it against.
289        if !node.ints.is_empty() {
290            if let Some(next) = subject.int(term).and_then(|value| node.literal(value)) {
291                if let Some(rule) = self.take(subject, next, (term, head), &left, bindings) {
292                    return Some(rule);
293                }
294            }
295        }
296
297        // A repeat of an earlier binding. The binding is always there, because a pattern only
298        // writes a name for the second time after it has written it once and the trie keeps that
299        // order.
300        for &(index, next) in node.same {
301            if bindings.get(index).is_some_and(|&bound| subject.same(bound, term)) {
302                if let Some(rule) = self.take(subject, next, (term, head), &left, bindings) {
303                    return Some(rule);
304                }
305            }
306        }
307
308        // The wildcard is last, which is the whole of what specificity order means here.
309        let (_, next) = node.wildcard.as_ref()?;
310        let depth = bindings.len();
311        bindings.push(term);
312        if let Some(rule) = self.run(subject, *next as usize, left, bindings) {
313            return Some(rule);
314        }
315        bindings.truncate(depth);
316        None
317    }
318
319    /// Follow one branch, and give the bindings back as they were if it led nowhere.
320    ///
321    /// What goes on the stack is the arguments of the term, innermost last, whenever the term has
322    /// any. That is the same for every kind of branch, because what a branch decided is that this
323    /// subterm is matched and the walk carries on into what is under it.
324    fn take<S: Subject>(
325        &self,
326        subject: &S,
327        next: u32,
328        term: (S::Node, Option<(&str, usize)>),
329        left: &[S::Node],
330        bindings: &mut Vec<S::Node>,
331    ) -> Option<usize> {
332        let (term, head) = term;
333        let mut deeper = left.to_vec();
334        if let Some((_, arity)) = head {
335            for index in (0..arity).rev() {
336                deeper.push(subject.arg(term, index));
337            }
338        }
339        let depth = bindings.len();
340        if let Some(rule) = self.run(subject, next as usize, deeper, bindings) {
341            return Some(rule);
342        }
343        bindings.truncate(depth);
344        None
345    }
346
347    /// The rule that ends at this node, if one does and if its guard holds.
348    fn accept<S: Subject>(&self, subject: &S, at: usize, bindings: &[S::Node]) -> Option<usize> {
349        let rule = self.nodes[at].accept? as usize;
350        if let Some(guard) = self.rules[rule].guard {
351            // The values are collected here rather than as the bindings are made, because most
352            // rules have no guard and would pay for it every time.
353            let values: Vec<Option<i128>> =
354                bindings.iter().map(|&node| subject.int(node)).collect();
355            if !guard(&values) {
356                return None;
357            }
358        }
359        Some(rule)
360    }
361}
362
363#[cfg(test)]
364mod tests {
365    use super::{Match, Node, Piece, Rule, Subject, Table};
366
367    /// A term, in the only shape a test needs: a flat arena, because that is the shape the IR
368    /// has and answering the questions out of one is what the callers will be doing.
369    #[derive(Debug)]
370    enum Held {
371        Int(i128),
372        App(String, Vec<usize>),
373    }
374
375    #[derive(Debug, Default)]
376    struct Terms {
377        nodes: Vec<Held>,
378    }
379
380    impl Terms {
381        fn constant(&mut self, value: i128) -> usize {
382            self.nodes.push(Held::Int(value));
383            self.nodes.len() - 1
384        }
385
386        fn app(&mut self, head: &str, args: &[usize]) -> usize {
387            self.nodes.push(Held::App(head.to_owned(), args.to_vec()));
388            self.nodes.len() - 1
389        }
390    }
391
392    impl Subject for Terms {
393        type Node = usize;
394
395        fn head(&self, node: usize) -> Option<(&str, usize)> {
396            match &self.nodes[node] {
397                Held::App(head, args) => Some((head.as_str(), args.len())),
398                Held::Int(_) => None,
399            }
400        }
401
402        fn arg(&self, node: usize, index: usize) -> usize {
403            match &self.nodes[node] {
404                Held::App(_, args) => args[index],
405                Held::Int(_) => unreachable!("a constant has no arguments"),
406            }
407        }
408
409        fn int(&self, node: usize) -> Option<i128> {
410            match self.nodes[node] {
411                Held::Int(value) => Some(value),
412                Held::App(..) => None,
413            }
414        }
415
416        // An index into the arena is the identity of a term here, so two places are the same
417        // thing when they point at the same entry. A subject over the IR answers this out of the
418        // value each place holds instead, which is the same question asked of a different shape.
419        fn same(&self, a: usize, b: usize) -> bool {
420            a == b
421        }
422    }
423
424    /// A table written by hand, in the shape `rucc-rules` emits.
425    ///
426    /// Two rules over `(add x k)`: the first wants the constant to be zero and the second takes
427    /// any constant that is not negative. That is enough to exercise everything the walk does,
428    /// which is a concrete test before a wildcard, a guard that can refuse, and the search
429    /// carrying on after it does. A third rule, `(and x x)`, is the one that writes a name
430    /// twice.
431    /// A node with nothing on it, so that the ones below say only what they are about.
432    const NOTHING: Node = Node { heads: &[], ints: &[], same: &[], wildcard: None, accept: None };
433
434    static NODES: &[Node] = &[
435        // 0, the root.
436        Node { heads: &[("add", 2, 1), ("and", 2, 5)], ..NOTHING },
437        // 1, the first operand.
438        Node { wildcard: Some(("x", 2)), ..NOTHING },
439        // 2, the second operand.
440        Node { ints: &[(0, 3)], wildcard: Some(("k", 4)), ..NOTHING },
441        // 3, an addition of zero.
442        Node { accept: Some(0), ..NOTHING },
443        // 4, an addition of anything, if the guard holds.
444        Node { accept: Some(1), ..NOTHING },
445        // 5, the first operand of the conjunction, which is the one that binds.
446        Node { wildcard: Some(("x", 6)), ..NOTHING },
447        // 6, the second operand, which has to be what the first one bound.
448        Node { same: &[(0, 7)], ..NOTHING },
449        // 7, a conjunction of one thing with itself.
450        Node { accept: Some(2), ..NOTHING },
451    ];
452
453    fn not_negative(bound: &[Option<i128>]) -> bool {
454        let Some(Some(k)) = bound.get(1).copied() else { return false };
455        k >= 0
456    }
457
458    static RULES: &[Rule] = &[
459        Rule {
460            pattern: "(add x 0)",
461            replacement: &[Piece::Var { name: "x", index: 0 }],
462            guard: None,
463            line: 1,
464        },
465        Rule {
466            pattern: "(add x k)",
467            replacement: &[
468                Piece::App { head: "add_immediate", arity: 2 },
469                Piece::Var { name: "x", index: 0 },
470                Piece::Var { name: "k", index: 1 },
471            ],
472            guard: Some(not_negative),
473            line: 2,
474        },
475        Rule {
476            pattern: "(and x x)",
477            replacement: &[Piece::Var { name: "x", index: 0 }],
478            guard: None,
479            line: 3,
480        },
481    ];
482
483    static TABLE: Table = Table { source: "rules/test.rules", nodes: NODES, rules: RULES };
484
485    fn add(terms: &mut Terms, second: usize) -> usize {
486        let first = terms.app("v0", &[]);
487        terms.app("add", &[first, second])
488    }
489
490    /// The concrete test is tried before the wildcard, so the rule about zero wins over the rule
491    /// about any constant even though both of them match. That is the whole of what specificity
492    /// order means here, and it falls out of the shape of the trie.
493    #[test]
494    fn the_rule_that_names_the_operand_beats_the_rule_that_takes_anything() {
495        let mut terms = Terms::default();
496        let zero = terms.constant(0);
497        let term = add(&mut terms, zero);
498        let found = TABLE.find(&terms, term).expect("a rule fires");
499        assert_eq!(TABLE.rule(&found).pattern, "(add x 0)");
500    }
501
502    /// The bindings come back in the order the pattern binds them, which is the pre-order the
503    /// replacement was flattened in, so a `Piece::Var` can be read as an index into them.
504    #[test]
505    fn a_match_gives_back_what_the_pattern_bound_in_the_order_it_bound_it() {
506        let mut terms = Terms::default();
507        let seven = terms.constant(7);
508        let term = add(&mut terms, seven);
509        let found = TABLE.find(&terms, term).expect("a rule fires");
510        let rule = TABLE.rule(&found);
511        assert_eq!(rule.pattern, "(add x k)");
512        assert_eq!(rule.head(), Some("add_immediate"));
513        assert_eq!(found.bindings.len(), 2);
514        assert_eq!(found.bindings[1], seven);
515        assert_eq!(terms.int(found.bindings[1]), Some(7));
516    }
517
518    /// A guard that does not hold is a rule that did not match, and there is nothing else to
519    /// try, so the answer is nothing rather than the wrong rule.
520    #[test]
521    fn a_guard_that_refuses_takes_its_rule_out_of_the_running() {
522        let mut terms = Terms::default();
523        let negative = terms.constant(-1);
524        let term = add(&mut terms, negative);
525        assert_eq!(TABLE.find(&terms, term), None);
526    }
527
528    /// The same guard against an operand that is not a constant at all. A guard is a claim about
529    /// a number, so a register makes it false rather than an error.
530    #[test]
531    fn a_guard_about_a_number_refuses_an_operand_that_is_not_one() {
532        let mut terms = Terms::default();
533        let other = terms.app("v1", &[]);
534        let term = add(&mut terms, other);
535        assert_eq!(TABLE.find(&terms, term), None);
536    }
537
538    #[test]
539    fn a_term_no_rule_covers_finds_no_rule() {
540        let mut terms = Terms::default();
541        let x = terms.app("v0", &[]);
542        let y = terms.app("v1", &[]);
543        let term = terms.app("no.such.head", &[x, y]);
544        assert_eq!(TABLE.find(&terms, term), None);
545    }
546
547    /// The rule that writes one name twice. Both operands are the same term, so the test that
548    /// they are holds and the rule fires, and what comes back is the one binding the pattern
549    /// made rather than two.
550    #[test]
551    fn a_pattern_that_names_one_hole_twice_matches_a_term_that_has_one_thing_in_both() {
552        let mut terms = Terms::default();
553        let x = terms.app("v0", &[]);
554        let term = terms.app("and", &[x, x]);
555        let found = TABLE.find(&terms, term).expect("a rule fires");
556        assert_eq!(TABLE.rule(&found).pattern, "(and x x)");
557        assert_eq!(found.bindings, vec![x]);
558    }
559
560    /// The same rule against two different terms. There is no wildcard beside the test, so a
561    /// conjunction of two things is a conjunction no rule covers rather than one this rule
562    /// wrongly claims.
563    #[test]
564    fn a_pattern_that_names_one_hole_twice_refuses_a_term_that_has_two_things_in_it() {
565        let mut terms = Terms::default();
566        let x = terms.app("v0", &[]);
567        let y = terms.app("v1", &[]);
568        let term = terms.app("and", &[x, y]);
569        assert_eq!(TABLE.find(&terms, term), None);
570    }
571
572    /// The branch is found rather than looked for, which is the thing a node being sorted buys.
573    /// A node as wide as the root of a real rule set answers in the same number of comparisons a
574    /// node with eight branches does, and it answers about the head it was never given by not
575    /// finding one rather than by reading to the end.
576    #[test]
577    fn a_branch_is_found_by_searching_the_node_and_not_by_reading_it() {
578        static WIDE: &[(&str, usize, u32)] = &[
579            ("add.i16", 2, 1),
580            ("add.i32", 2, 2),
581            ("add.i64", 2, 3),
582            ("add.i64", 3, 4),
583            ("sub.i32", 2, 5),
584            ("sub.i64", 2, 6),
585            ("xor.i8", 2, 7),
586        ];
587        let node = Node { heads: WIDE, ..NOTHING };
588        assert!(WIDE.is_sorted(), "the search is only a search if the node is in order");
589        assert_eq!(node.branch("add.i64", 2), Some(3));
590        assert_eq!(node.branch("add.i16", 2), Some(1));
591        assert_eq!(node.branch("xor.i8", 2), Some(7));
592        // The same name at two arities is two branches, and they are told apart.
593        assert_eq!(node.branch("add.i64", 3), Some(4));
594        // A head no branch is about, and one the node has at another arity, are both nothing.
595        assert_eq!(node.branch("mul.i64", 2), None);
596        assert_eq!(node.branch("sub.i32", 3), None);
597    }
598
599    /// The same for a constant, which is the other kind of branch that can be searched.
600    #[test]
601    fn a_literal_is_found_by_searching_too() {
602        let node = Node { ints: &[(-8, 1), (0, 2), (1, 3), (4096, 4)], ..NOTHING };
603        assert_eq!(node.literal(-8), Some(1));
604        assert_eq!(node.literal(0), Some(2));
605        assert_eq!(node.literal(4096), Some(4));
606        assert_eq!(node.literal(7), None);
607    }
608
609    /// The order the kinds of question are asked in, which is the heuristic the module doc
610    /// states. It only decides anything when one node asks two kinds about one place and the
611    /// subject answers both, which is why this needs a subject of its own: the one above answers
612    /// either what a term is called or what number it is, never both, and so does the IR. What is
613    /// asserted is the order that is written down, so that a rule set which starts to depend on
614    /// it gets the answer somebody chose rather than the one that fell out.
615    #[test]
616    fn the_head_is_asked_about_before_the_value_and_the_value_before_a_repeat() {
617        /// `(f a a)`, where each operand is an application and a number at the same time and the
618        /// two of them are one thing. Every question a node can ask is true of them, so which
619        /// one is asked first is the only thing that decides the answer.
620        #[derive(Debug)]
621        struct Both;
622
623        impl Subject for Both {
624            type Node = u8;
625
626            fn head(&self, node: u8) -> Option<(&str, usize)> {
627                if node == 0 { Some(("f", 2)) } else { Some(("k", 0)) }
628            }
629
630            fn int(&self, node: u8) -> Option<i128> {
631                if node == 0 { None } else { Some(7) }
632            }
633
634            fn arg(&self, _: u8, _: usize) -> u8 {
635                1
636            }
637
638            fn same(&self, _: u8, _: u8) -> bool {
639                true
640            }
641        }
642
643        static FOUR: &[Rule] = &[
644            Rule { pattern: "the head", replacement: &[], guard: None, line: 1 },
645            Rule { pattern: "the value", replacement: &[], guard: None, line: 2 },
646            Rule { pattern: "the repeat", replacement: &[], guard: None, line: 3 },
647            Rule { pattern: "the hole", replacement: &[], guard: None, line: 4 },
648        ];
649
650        /// The four ends, and in front of them the node that binds the first operand so that
651        /// there is something for a repeat to be a repeat of.
652        fn table(second: &'static Node) -> Table {
653            let nodes: &'static [Node] = Box::leak(Box::new([
654                Node { heads: &[("f", 2, 1)], ..NOTHING },
655                Node { wildcard: Some(("x", 2)), ..NOTHING },
656                *second,
657                Node { accept: Some(0), ..NOTHING },
658                Node { accept: Some(1), ..NOTHING },
659                Node { accept: Some(2), ..NOTHING },
660                Node { accept: Some(3), ..NOTHING },
661            ]));
662            Table { source: "rules/test.rules", nodes, rules: FOUR }
663        }
664
665        // All three kinds on one node, with a hole behind them.
666        static MIXED: Node = Node {
667            heads: &[("k", 0, 3)],
668            ints: &[(7, 4)],
669            same: &[(0, 5)],
670            wildcard: Some(("y", 6)),
671            accept: None,
672        };
673        assert_eq!(table(&MIXED).find(&Both, 0).map(|found| found.rule), Some(0));
674
675        // The same node without the head, which is what puts the value in front.
676        static WITHOUT_HEAD: Node = Node { heads: &[], ..MIXED };
677        assert_eq!(table(&WITHOUT_HEAD).find(&Both, 0).map(|found| found.rule), Some(1));
678
679        // And without either, which leaves the repeat in front of the hole. That last pair is
680        // the one that is not a heuristic: a concrete question always comes before the hole.
681        static REPEAT: Node = Node { ints: &[], ..WITHOUT_HEAD };
682        assert_eq!(table(&REPEAT).find(&Both, 0).map(|found| found.rule), Some(2));
683
684        // And with nothing concrete left, the hole.
685        static HOLE: Node = Node { same: &[], ..REPEAT };
686        assert_eq!(table(&HOLE).find(&Both, 0).map(|found| found.rule), Some(3));
687    }
688
689    /// A match is what a caller keeps, so it says what it is when a test prints it.
690    #[test]
691    fn a_match_names_the_rule_it_found() {
692        let mut terms = Terms::default();
693        let zero = terms.constant(0);
694        let term = add(&mut terms, zero);
695        assert_eq!(TABLE.find(&terms, term), Some(Match { rule: 0, bindings: vec![term - 1] }));
696    }
697}