Skip to main content

rucc_rules/
emit.rs

1//! The matcher as Rust, for the compiler to link against.
2//!
3//! `spec/10-backend.md` section 10.2 asks for a generated automaton rather than a chain of
4//! conditionals, and this is the half of that which leaves this crate. A build script reads a
5//! rule file, builds the trie next door, and writes what this module produces into the build
6//! directory of the crate that matches with it. Nothing here is a copy of anything: the rule
7//! file is the only place the rules are written, and the table is regenerated whenever it
8//! changes.
9//!
10//! What comes out does not depend on whether the rules lower or simplify. The kind decides
11//! what a replacement is written in and therefore what the crate including the file does with
12//! it, and that crate already knows which file it asked for. A table of rewrite rules and a
13//! table of lowering rules are the same array of nodes and the same array of replacements.
14//!
15//! # What comes out
16//!
17//! One Rust source file, holding the trie as an array of nodes, the rules as an array of
18//! replacements, and one function per guard. It is data and not code, except for the guards,
19//! which are the one part of a rule that has to be evaluated rather than looked up. The walk
20//! over the table lives in the crate that includes the file, because the subject of a match
21//! there is the compiler's own IR rather than a term, and because a walk written once is a
22//! walk written once however many targets there are.
23//!
24//! The types the file names are the ones that crate defines, and it refers to them through
25//! `super`, which is what makes the file includable and nothing else. That is the whole of the
26//! contract between the two, and it is small on purpose.
27//!
28//! # Guards
29//!
30//! A guard is a condition on the constants a pattern matched, so it becomes a function of the
31//! values the bindings hold. A binding that is not a constant at all makes the guard false
32//! rather than an error, because a rule guarded by a claim about a number is a rule that does
33//! not fire when the operand is not one.
34//!
35//! The language a guard may be written in is small and this module is where it ends. A head it
36//! does not know is refused with the line it is on, rather than emitted and discovered as a
37//! compile error in generated code, which is the sort of message nobody can act on.
38//!
39//! # Computed numbers
40//!
41//! A replacement may work a number out of the numbers the pattern matched, and such a term
42//! becomes a function of the bindings in exactly the way a guard does. That is what lets a rule
43//! be written once per width rather than once per constant: multiplying by a power of two is a
44//! shift by the log of it, and the log is a number no rule can write down until it has seen which
45//! power it matched.
46//!
47//! What makes a term in a replacement a computation rather than something to build is its head
48//! being one of the arithmetic ones, which is the same closed list a guard is written in. So the
49//! two halves of a rule compute in one language, a head this module does not know is refused the
50//! same way in both, and the crate that runs the table gets a `Piece::Computed` holding a
51//! function rather than a term it would have to evaluate itself.
52
53use std::fmt::Write as _;
54
55use crate::ast::{Rule, Term, TermKind};
56use crate::error::Error;
57use crate::matcher::Matcher;
58
59/// The helpers a guard can call, and what each one needs emitted with it.
60const HELPERS: &[(&str, &str)] = &[
61    ("sign_extend", SIGN_EXTEND),
62    ("zero_extend", ZERO_EXTEND),
63    ("extract", EXTRACT),
64    ("power_of_two", POWER_OF_TWO),
65    ("trailing_zeros", TRAILING_ZEROS),
66    ("shifted", SHIFTED),
67    ("low", LOW),
68];
69
70/// Turn a rule set and the trie it compiles into into Rust.
71///
72/// `source` is the rule file as it should be named in the generated file and in anything the
73/// compiler says about a rule at run time, so it is the path a person could open rather than
74/// wherever the build script happened to find it.
75///
76/// # Errors
77///
78/// A guard this module cannot compile, reported with the position of the term that was not
79/// understood. Every other way a rule set can be wrong has been reported by the reader or by
80/// the trie before anything gets here.
81pub fn emit(source: &str, rules: &[Rule], matcher: &Matcher) -> Result<String, Vec<Error>> {
82    let mut out = String::new();
83    let mut errors = Vec::new();
84    let mut wanted: Vec<&'static str> = Vec::new();
85
86    let guards = compile_guards(source, rules, &mut wanted, &mut errors);
87    if !errors.is_empty() {
88        return Err(errors);
89    }
90
91    let mut computed = Vec::new();
92    let mut body = String::new();
93    replacements(&mut body, source, rules, &guards, &mut wanted, &mut computed, &mut errors);
94    if !errors.is_empty() {
95        return Err(errors);
96    }
97
98    header(&mut out, source, rules, matcher);
99    nodes(&mut out, matcher);
100    out.push_str(&body);
101    out.push_str(&guards.iter().flatten().map(String::as_str).collect::<String>());
102    out.push_str(&computed.concat());
103    helpers(&mut out, &wanted);
104    Ok(out)
105}
106
107/// The comment nobody reads until they have to, and the table itself.
108fn header(out: &mut String, source: &str, rules: &[Rule], matcher: &Matcher) {
109    let shape = matcher.shape();
110    let _ = write!(
111        out,
112        "\
113// Generated from {source} by rucc-rules. Do not edit this file: edit the
114// rule file and build again. It holds {} rules over {} trie nodes.
115//
116// The widest node has {} branches. Reading them in the order the rules are written would ask
117// that many questions to reach the last of them and to find that none of them matched, and the
118// search that is done instead asks {}. {} nodes ask more than one kind of question, which is how
119// many of them the order the kinds are tried in decides anything at.
120//
121// The types are the ones the module that includes this file defines, and the walk over the
122// table is there too. What is here is the table.
123
124use super::{{Node, Piece, Rule, Table}};
125
126/// The rule file this table was built from, so that anything said about a rule can name a file
127/// somebody can open.
128pub const SOURCE: &str = {source:?};
129
130/// The rules of this file, as an automaton over their patterns.
131pub static TABLE: Table = Table {{ source: SOURCE, nodes: NODES, rules: RULES }};
132",
133        rules.len(),
134        shape.nodes,
135        shape.widest,
136        shape.search,
137        shape.mixed,
138    );
139}
140
141/// The trie, one array entry per node, with node zero the root.
142fn nodes(out: &mut String, matcher: &Matcher) {
143    out.push_str(
144        "\n/// The trie over the patterns. A node holds the branches taken on the head of a\n\
145         /// term, the branches taken on the value of a constant, the branches taken on a\n\
146         /// repeat of an earlier binding, the branch that takes anything, and the rule that\n\
147         /// ends here if one does. The first two are sorted, which is what makes finding a\n\
148         /// branch a search.\nstatic NODES: &[Node] = &[\n",
149    );
150    for (index, node) in matcher.nodes.iter().enumerate() {
151        let _ = writeln!(out, "    // {index}");
152        out.push_str("    Node {\n        heads: &[");
153        for (head, arity, next) in &node.heads {
154            let _ = write!(out, "\n            ({head:?}, {arity}, {next}),");
155        }
156        if !node.heads.is_empty() {
157            out.push_str("\n        ");
158        }
159        out.push_str("],\n        ints: &[");
160        for (value, next) in &node.ints {
161            let _ = write!(out, "\n            ({value}, {next}),");
162        }
163        if !node.ints.is_empty() {
164            out.push_str("\n        ");
165        }
166        out.push_str("],\n        same: &[");
167        for (binding, next) in &node.same {
168            let _ = write!(out, "\n            ({binding}, {next}),");
169        }
170        if !node.same.is_empty() {
171            out.push_str("\n        ");
172        }
173        out.push_str("],\n");
174        match &node.wildcard {
175            Some((name, next)) => {
176                let _ = writeln!(out, "        wildcard: Some(({name:?}, {next})),");
177            }
178            None => out.push_str("        wildcard: None,\n"),
179        }
180        let accept: Vec<String> = node.accept.iter().map(ToString::to_string).collect();
181        let _ = writeln!(out, "        accept: &[{}],", accept.join(", "));
182        out.push_str("    },\n");
183    }
184    out.push_str("];\n");
185}
186
187/// The rules, one array entry each, in the order the file writes them.
188#[allow(clippy::too_many_arguments)]
189fn replacements(
190    out: &mut String,
191    source: &str,
192    rules: &[Rule],
193    guards: &[Option<String>],
194    wanted: &mut Vec<&'static str>,
195    computed: &mut Vec<String>,
196    errors: &mut Vec<Error>,
197) {
198    out.push_str(
199        "\n/// The rules, in the order the rule file writes them, which is the order the\n\
200         /// `accept` of a trie node names.\nstatic RULES: &[Rule] = &[\n",
201    );
202    for (index, rule) in rules.iter().enumerate() {
203        let pattern = rule.pattern.to_string();
204        let _ = writeln!(out, "    // {source}:{}", rule.line);
205        out.push_str("    Rule {\n");
206        let _ = writeln!(out, "        pattern: {pattern:?},");
207        out.push_str("        replacement: &[");
208        let bound = bound_names(&rule.pattern);
209        for piece in pieces(source, &rule.replacement, &bound, wanted, computed, errors) {
210            let _ = write!(out, "\n            {piece},");
211        }
212        out.push_str("\n        ],\n");
213        match guards[index] {
214            Some(_) => {
215                let _ = writeln!(out, "        guard: Some(guard_{index}),");
216            }
217            None => out.push_str("        guard: None,\n"),
218        }
219        let _ = writeln!(out, "        line: {},", rule.line);
220        out.push_str("    },\n");
221    }
222    out.push_str("];\n");
223}
224
225/// The names a pattern binds, in the order the matcher binds them, which is the pre-order it
226/// walks the subject in. A replacement names one of them and the table holds the position,
227/// because a position is what the match has and a name is what the reader has.
228///
229/// A name written twice binds once. The second occurrence is a test that the two places hold the
230/// same thing rather than a second hole, so it takes no position, and counting it here would put
231/// every later name one place along from where the match actually holds it.
232fn bound_names(pattern: &Term) -> Vec<String> {
233    let mut out: Vec<String> = Vec::new();
234    pattern.walk(&mut |term| {
235        if let TermKind::Var(name) = &term.kind {
236            if !out.iter().any(|have| have == name) {
237                out.push(name.clone());
238            }
239        }
240    });
241    out
242}
243
244/// One replacement term, flattened into the pieces that build it, in pre-order.
245fn pieces(
246    source: &str,
247    term: &Term,
248    bound: &[String],
249    wanted: &mut Vec<&'static str>,
250    computed: &mut Vec<String>,
251    errors: &mut Vec<Error>,
252) -> Vec<String> {
253    let mut out = Vec::new();
254    push_pieces(source, term, bound, wanted, computed, errors, &mut out);
255    out
256}
257
258#[allow(clippy::too_many_arguments)]
259fn push_pieces(
260    source: &str,
261    term: &Term,
262    bound: &[String],
263    wanted: &mut Vec<&'static str>,
264    computed: &mut Vec<String>,
265    errors: &mut Vec<Error>,
266    out: &mut Vec<String>,
267) {
268    match &term.kind {
269        TermKind::Var(name) => {
270            // The reader has already refused a replacement naming something the pattern never
271            // bound, so there is a position for every name that reaches here.
272            let index = bound.iter().position(|have| have == name).unwrap_or_default();
273            out.push(format!("Piece::Var {{ name: {name:?}, index: {index} }}"));
274        }
275        TermKind::Int(value) => out.push(format!("Piece::Int({value})")),
276        TermKind::App { head, args } if computes(head, args.len()) => {
277            // A number the rule works out rather than one it wrote down. What makes it one is its
278            // head being arithmetic, and that is the same closed list a guard is written in, so a
279            // rule that decides whether to fire and a rule that says what to fire compute in one
280            // language rather than in two.
281            let index = computed.len();
282            let mut used = Vec::new();
283            match value(source, term, bound, wanted, &mut used) {
284                Ok(text) => {
285                    computed.push(computation(index, term, &text, bound, &used));
286                    out.push(format!(
287                        "Piece::Computed {{ text: {:?}, work: computed_{index} }}",
288                        term.to_string()
289                    ));
290                }
291                Err(error) => errors.push(error),
292            }
293        }
294        TermKind::App { head, args } => {
295            out.push(format!("Piece::App {{ head: {head:?}, arity: {} }}", args.len()));
296            for arg in args {
297                push_pieces(source, arg, bound, wanted, computed, errors, out);
298            }
299        }
300    }
301}
302
303/// Whether a term in a replacement is arithmetic rather than something to build.
304///
305/// The heads are the ones [`value`] compiles and the arities are the ones it takes, so a head
306/// that is arithmetic at one arity and an instruction at another is read as what it was written
307/// as. Nothing in either vocabulary is named this way today and this is what keeps the day one is
308/// from turning a rule into a number quietly.
309fn computes(head: &str, arity: usize) -> bool {
310    matches!((head, arity), ("+" | "-", 2) | ("sign_extend" | "zero_extend" | "extract", 3))
311        || (arity == 1 && suffix(head, "ctz").is_some())
312}
313
314/// One function per computed piece, which is a guard in every way except what it gives back.
315fn computation(index: usize, term: &Term, text: &str, bound: &[String], used: &[usize]) -> String {
316    let mut out = format!(
317        "\n/// `{term}`, which is a number a replacement works out, written on line {}.\n\
318         fn computed_{index}(bound: &[Option<i128>]) -> Option<i128> {{\n",
319        term.line
320    );
321    let mut used = used.to_vec();
322    used.sort_unstable();
323    used.dedup();
324    for at in used {
325        let _ = writeln!(
326            out,
327            "    // {}\n    let Some(Some(v{at})) = {}.copied() else {{ return None }};",
328            bound[at],
329            reads(at)
330        );
331    }
332    let _ = writeln!(out, "    Some({text})\n}}");
333    out
334}
335
336/// How a compiled guard or computation reads one of the bindings.
337///
338/// The first is read by the name for it rather than by its index, because a generated file is
339/// linted along with everything else and clippy asks for the name.
340fn reads(at: usize) -> String {
341    if at == 0 { "bound.first()".to_owned() } else { format!("bound.get({at})") }
342}
343
344/// One function per guarded rule, or nothing for a rule with no guard.
345fn compile_guards(
346    source: &str,
347    rules: &[Rule],
348    wanted: &mut Vec<&'static str>,
349    errors: &mut Vec<Error>,
350) -> Vec<Option<String>> {
351    let mut out = Vec::with_capacity(rules.len());
352    for (index, rule) in rules.iter().enumerate() {
353        let Some(guard) = &rule.guard else {
354            out.push(None);
355            continue;
356        };
357        let bound = bound_names(&rule.pattern);
358        let mut used = Vec::new();
359        let condition = match condition(source, guard, &bound, wanted, &mut used) {
360            Ok(text) => text,
361            Err(error) => {
362                errors.push(error);
363                out.push(None);
364                continue;
365            }
366        };
367        // The condition comes out in the order the rule file writes it, so that a reader can hold
368        // the two side by side. That is what the lint is turned off for: `(>= k 0)` and `(< k 64)`
369        // are two conditions in the rule and `(0..64).contains(&k)` is not either of them.
370        let mut text = format!(
371            "\n/// `{guard}`, which is the guard of the rule on line {}.\n\
372             #[allow(clippy::manual_range_contains)]\nfn guard_{index}(bound: \
373             &[Option<i128>]) -> bool {{\n",
374            rule.line
375        );
376        used.sort_unstable();
377        used.dedup();
378        for at in used {
379            let _ = writeln!(
380                text,
381                "    // {}\n    let Some(Some(v{at})) = {}.copied() else {{ return false }};",
382                bound[at],
383                reads(at)
384            );
385        }
386        let _ = writeln!(text, "    {}\n}}", bare(&condition));
387        out.push(Some(text));
388    }
389    out
390}
391
392/// An expression without the parentheses that wrap the whole of it.
393///
394/// Every condition is emitted parenthesised, because an operand of one has to be. The outermost
395/// one is nobody's operand, and Rust warns about the parentheses around it, which in a generated
396/// file is a warning the reader of it can do nothing with.
397fn bare(text: &str) -> &str {
398    let Some(inner) = text.strip_prefix('(').and_then(|text| text.strip_suffix(')')) else {
399        return text;
400    };
401    let mut depth = 0i32;
402    for c in inner.chars() {
403        match c {
404            '(' => depth += 1,
405            ')' => depth -= 1,
406            _ => {}
407        }
408        // The pair that opened the string closed before the end of it, so the two ends are not
409        // a pair and taking them off would be taking off two different people's parentheses.
410        if depth < 0 {
411            return text;
412        }
413    }
414    inner
415}
416
417/// A guard as a Rust expression of type `bool`.
418fn condition(
419    source: &str,
420    term: &Term,
421    bound: &[String],
422    wanted: &mut Vec<&'static str>,
423    used: &mut Vec<usize>,
424) -> Result<String, Error> {
425    let TermKind::App { head, args } = &term.kind else {
426        return Err(refused(source, term, "a guard is a condition, and this is not one"));
427    };
428    let arity = args.len();
429    // A question about the bits of one number, which has to be asked at a width: whether a
430    // constant is a power of two is a different question at eight bits and at sixty four, and
431    // the rule that asks it is written at one of them.
432    if let Some(bits) = suffix(head, "power_of_two").filter(|_| arity == 1) {
433        let inner = value(source, &args[0], bound, wanted, used)?;
434        want(wanted, "power_of_two");
435        return Ok(format!("power_of_two({bits}, {inner})"));
436    }
437    match (head.as_str(), arity) {
438        ("and" | "or", 1..) => {
439            let joint = if head == "and" { " && " } else { " || " };
440            let mut parts = Vec::with_capacity(arity);
441            for arg in args {
442                parts.push(condition(source, arg, bound, wanted, used)?);
443            }
444            Ok(format!("({})", parts.join(joint)))
445        }
446        ("not", 1) => Ok(format!("!{}", condition(source, &args[0], bound, wanted, used)?)),
447        ("=" | "!=" | "<" | "<=" | ">" | ">=", 2) => {
448            let operator = if head == "=" { "==" } else { head.as_str() };
449            let left = value(source, &args[0], bound, wanted, used)?;
450            let right = value(source, &args[1], bound, wanted, used)?;
451            Ok(format!("({left} {operator} {right})"))
452        }
453        _ => Err(refused(
454            source,
455            term,
456            &format!(
457                "`{head}` of {arity} is not a condition a guard can be compiled to. A guard is \
458                 `and`, `or`, `not`, `power_of_two.iN`, or a comparison of two numbers"
459            ),
460        )),
461    }
462}
463
464/// A term inside a guard that stands for a number.
465fn value(
466    source: &str,
467    term: &Term,
468    bound: &[String],
469    wanted: &mut Vec<&'static str>,
470    used: &mut Vec<usize>,
471) -> Result<String, Error> {
472    match &term.kind {
473        TermKind::Int(number) => Ok(format!("{number}")),
474        TermKind::Var(name) => {
475            // The reader has already refused a guard naming something the pattern never bound.
476            let at = bound.iter().position(|have| have == name).unwrap_or_default();
477            used.push(at);
478            Ok(format!("v{at}"))
479        }
480        TermKind::App { head, args } => {
481            let arity = args.len();
482            // Counting the zero bits a number ends in, at a width, which is the log of it when it
483            // is a power of two. This is the one arithmetic here that a replacement needs and a
484            // guard does not, and it is what a shift standing in for a multiplication shifts by.
485            if let Some(bits) = suffix(head, "ctz").filter(|_| arity == 1) {
486                let inner = value(source, &args[0], bound, wanted, used)?;
487                want(wanted, "trailing_zeros");
488                return Ok(format!("trailing_zeros({bits}, {inner})"));
489            }
490            match (head.as_str(), arity) {
491                // Adding and subtracting, which is what a guard about two offsets into one object
492                // is written in.
493                //
494                // Saturating rather than plain, because a guard is a condition on whatever
495                // constants the match happened to hold and there is nothing to stop those being
496                // the ends of the type. Plain arithmetic there is a panic in a debug build and a
497                // wrap in a release one, and neither is an answer to a question about a rule.
498                //
499                // Saturating is not the solver's arithmetic either. The solver reads a guard in
500                // the width the rule runs at, where adding wraps, and this reads it in `i128`,
501                // where it does not. The two agree exactly while the operands stay small, so a
502                // rule that adds says how small in the same guard, and one that does not is a
503                // rule proved about arithmetic the compiler is not doing.
504                ("+" | "-", 2) => {
505                    let left = value(source, &args[0], bound, wanted, used)?;
506                    let right = value(source, &args[1], bound, wanted, used)?;
507                    let name = if head == "+" { "saturating_add" } else { "saturating_sub" };
508                    Ok(format!("({left}).{name}({right})"))
509                }
510                ("sign_extend" | "zero_extend" | "extract", 3) => {
511                    let first = width(source, &args[0])?;
512                    let second = width(source, &args[1])?;
513                    let inner = value(source, &args[2], bound, wanted, used)?;
514                    let name = match head.as_str() {
515                        "sign_extend" => "sign_extend",
516                        "zero_extend" => "zero_extend",
517                        _ => "extract",
518                    };
519                    want(wanted, name);
520                    Ok(format!("{name}({first}, {second}, {inner})"))
521                }
522                _ => Err(refused(
523                    source,
524                    term,
525                    &format!(
526                        "`{head}` of {arity} is not a number this can be compiled to. The ones \
527                         that are are `+`, `-`, `sign_extend`, `zero_extend`, `extract` and \
528                         `ctz.iN`"
529                    ),
530                )),
531            }
532        }
533    }
534}
535
536/// A width, which has to be written out rather than computed, because it is how many bits a
537/// machine instruction has room for and not something a program is allowed to vary.
538fn width(source: &str, term: &Term) -> Result<String, Error> {
539    match &term.kind {
540        TermKind::Int(number) if (0..=128).contains(number) => Ok(format!("{number}")),
541        _ => Err(refused(source, term, "a width has to be a number from 0 to 128")),
542    }
543}
544
545/// The width a head names, for the heads that are written once per width as `name.iN`.
546///
547/// Nothing if the head is some other name, so that a rule writing `ctz` with no width on it is
548/// refused with the message about what a number can be rather than compiled at a width nobody
549/// chose. The model file says what these mean once per width as well, which is the other half of
550/// the reason the width is written rather than inferred.
551fn suffix(head: &str, name: &str) -> Option<u32> {
552    head.strip_prefix(name)?.strip_prefix(".i")?.parse().ok().filter(|bits| *bits <= 128)
553}
554
555/// Remember a helper, and everything it is written in terms of.
556fn want(wanted: &mut Vec<&'static str>, name: &'static str) {
557    if wanted.contains(&name) {
558        return;
559    }
560    wanted.push(name);
561    match name {
562        "sign_extend" => want(wanted, "shifted"),
563        "zero_extend" | "extract" | "power_of_two" | "trailing_zeros" => want(wanted, "low"),
564        _ => {}
565    }
566}
567
568/// The helpers the guards used, in a fixed order so that the file does not move about between
569/// builds for no reason.
570fn helpers(out: &mut String, wanted: &[&str]) {
571    for (name, text) in HELPERS {
572        if wanted.contains(name) {
573            out.push_str(text);
574        }
575    }
576}
577
578fn refused(source: &str, term: &Term, message: &str) -> Error {
579    Error {
580        path: source.to_owned(),
581        line: term.line,
582        column: term.column,
583        message: message.to_owned(),
584    }
585}
586
587const SIGN_EXTEND: &str = "
588/// The low `from` bits of `value`, sign extended to `to` bits.
589fn sign_extend(from: u32, to: u32, value: i128) -> i128 {
590    shifted(to, shifted(from, value))
591}
592";
593
594const ZERO_EXTEND: &str = "
595/// The low `from` bits of `value`, read as a number and not sign extended.
596fn zero_extend(from: u32, to: u32, value: i128) -> i128 {
597    low(to, low(from, value))
598}
599";
600
601const EXTRACT: &str = "
602/// The bits from `hi` down to `lo` of `value`, read as a number.
603fn extract(hi: u32, lo: u32, value: i128) -> i128 {
604    if lo >= 128 || hi < lo {
605        return 0;
606    }
607    low(hi - lo + 1, value >> lo)
608}
609";
610
611const POWER_OF_TWO: &str = "
612/// Whether the low `bits` bits of `value` are one bit set and every other bit clear.
613fn power_of_two(bits: u32, value: i128) -> bool {
614    let masked = low(bits, value);
615    masked > 0 && masked & (masked - 1) == 0
616}
617";
618
619const TRAILING_ZEROS: &str = "
620/// How many zero bits the low `bits` bits of `value` end in, and `bits` when they are all zero.
621fn trailing_zeros(bits: u32, value: i128) -> i128 {
622    let masked = low(bits, value);
623    if masked == 0 { i128::from(bits) } else { i128::from(masked.trailing_zeros()) }
624}
625";
626
627const SHIFTED: &str = "
628/// `value` read as a signed number that many bits wide.
629fn shifted(bits: u32, value: i128) -> i128 {
630    match 128u32.checked_sub(bits) {
631        Some(room) if room > 0 => (value << room) >> room,
632        _ => value,
633    }
634}
635";
636
637const LOW: &str = "
638/// The low `bits` bits of `value`, read as a number.
639fn low(bits: u32, value: i128) -> i128 {
640    if bits >= 128 {
641        return value;
642    }
643    #[allow(clippy::cast_possible_wrap)]
644    let masked = (value as u128 & ((1u128 << bits) - 1)) as i128;
645    masked
646}
647";
648
649#[cfg(test)]
650mod tests {
651    use super::*;
652    use crate::parse;
653
654    fn built(text: &str) -> String {
655        let rules = parse("rules/test.rules", text).expect("the rules read");
656        let matcher = Matcher::build("rules/test.rules", &rules).expect("the matcher builds");
657        emit("rules/test.rules", &rules, &matcher).expect("the table is emitted")
658    }
659
660    /// The shape of the file, which is what the module that includes it is written against.
661    #[test]
662    fn a_rule_set_comes_out_as_a_table_of_nodes_and_a_table_of_rules() {
663        let out = built(
664            "(rule (lower (add.i64 (value.i64 x) (value.i64 y)))\n\
665             (x64.add_rr_64 x y)\n\
666             (spec (= (bvadd x y) (result))))\n",
667        );
668        assert!(out.contains("use super::{Node, Piece, Rule, Table};"), "{out}");
669        assert!(out.contains("pub const SOURCE: &str = \"rules/test.rules\";"), "{out}");
670        assert!(out.contains("(\"add.i64\", 2, 1),"), "{out}");
671        assert!(out.contains("wildcard: Some((\"x\", 3)),"), "{out}");
672        assert!(out.contains("accept: &[0],"), "{out}");
673        assert!(out.contains("Piece::App { head: \"x64.add_rr_64\", arity: 2 }"), "{out}");
674        assert!(out.contains("Piece::Var { name: \"x\", index: 0 }"), "{out}");
675        assert!(out.contains("Piece::Var { name: \"y\", index: 1 }"), "{out}");
676        assert!(out.contains("guard: None,"), "{out}");
677    }
678
679    /// A name written twice comes out as a test and not as a second hole, so the positions a
680    /// replacement and a guard are written against count it once. Here `k` is binding one, which
681    /// it would not be if the second `x` had taken a position of its own.
682    #[test]
683    fn a_name_written_twice_comes_out_as_a_test_and_takes_no_position() {
684        let out = built(
685            "(rule (simplify (and.i32 (value.i32 x) (value.i32 x)))\n\
686             (value.i32 x)\n\
687             (spec (= x (result))))\n\
688             (rule (simplify (shl.i32 (value.i32 x) (iconst.i32 k)))\n\
689             (if (>= k 0))\n\
690             (value.i32 x)\n\
691             (spec (= (bvshl x k) (result))))\n",
692        );
693        assert!(out.contains("same: &[\n            (0, "), "{out}");
694        assert!(out.contains("Piece::Var { name: \"x\", index: 0 }"), "{out}");
695        assert!(
696            out.contains("let Some(Some(v1)) = bound.get(1).copied() else { return false };"),
697            "{out}"
698        );
699    }
700
701    /// A guard becomes a function of the constants the pattern matched, and the helpers it
702    /// calls come with it. A binding it reads that is not a constant makes it false, which is
703    /// what the `let ... else` in it is for.
704    #[test]
705    fn a_guard_comes_out_as_a_function_of_the_bindings() {
706        let out = built(
707            "(rule (lower (shl.i64 (value.i64 x) (iconst.i64 k)))\n\
708             (if (and (>= k 0) (< k 64)))\n\
709             (x64.shl_ri_64 x k)\n\
710             (spec (= (bvshl x k) (result))))\n",
711        );
712        assert!(out.contains("guard: Some(guard_0),"), "{out}");
713        assert!(out.contains("fn guard_0(bound: &[Option<i128>]) -> bool {"), "{out}");
714        assert!(
715            out.contains("let Some(Some(v1)) = bound.get(1).copied() else { return false };"),
716            "{out}"
717        );
718        assert!(out.contains("(v1 >= 0) && (v1 < 64)"), "{out}");
719        // Nothing this guard does not use is emitted, because an unused function in a
720        // generated file is a warning in the crate that includes it.
721        assert!(!out.contains("fn sign_extend"), "{out}");
722        assert!(!out.contains("fn low"), "{out}");
723    }
724
725    /// The immediate guard, which is the one that needs the arithmetic helpers, and which is
726    /// what pulls `shifted` and `low` in behind them.
727    #[test]
728    fn a_guard_that_reads_bits_brings_the_helpers_it_needs() {
729        let out = built(
730            "(rule (lower (add.i64 (value.i64 x) (iconst.i64 k)))\n\
731             (if (= k (sign_extend 32 64 (extract 31 0 k))))\n\
732             (x64.add_ri_64 x k)\n\
733             (spec (= (bvadd x k) (result))))\n",
734        );
735        assert!(out.contains("v1 == sign_extend(32, 64, extract(31, 0, v1))"), "{out}");
736        assert!(out.contains("fn sign_extend(from: u32, to: u32, value: i128) -> i128 {"), "{out}");
737        assert!(out.contains("fn shifted(bits: u32, value: i128) -> i128 {"), "{out}");
738        assert!(out.contains("fn extract(hi: u32, lo: u32, value: i128) -> i128 {"), "{out}");
739        assert!(out.contains("fn low(bits: u32, value: i128) -> i128 {"), "{out}");
740        assert!(!out.contains("fn zero_extend"), "{out}");
741    }
742
743    /// A guard written in something this module does not compile is refused here, with the
744    /// position of the term, rather than emitted and found later as a compile error in a
745    /// generated file that nobody wrote.
746    #[test]
747    fn a_guard_nothing_can_be_made_of_is_refused_where_it_is_written() {
748        let rules = parse(
749            "rules/test.rules",
750            "(rule (lower (add.i64 (value.i64 x) (iconst.i64 k)))\n\
751             (if (fits_in_a_byte k))\n\
752             (x64.add_ri_64 x k)\n\
753             (spec (= (bvadd x k) (result))))\n",
754        )
755        .expect("the rules read");
756        let matcher = Matcher::build("rules/test.rules", &rules).expect("the matcher builds");
757        let errors = emit("rules/test.rules", &rules, &matcher).expect_err("the guard is refused");
758        assert_eq!(errors.len(), 1);
759        assert_eq!(errors[0].line, 2);
760        assert!(
761            errors[0].message.contains("`fits_in_a_byte` of 1 is not a condition"),
762            "{}",
763            errors[0]
764        );
765    }
766
767    /// The rule issue 523 was about, whole: a guard asking whether the matched constant is a
768    /// power of two and a replacement shifting by the log of it. Both halves come out as
769    /// functions of the bindings and both bring the helper they are written in.
770    #[test]
771    fn a_replacement_can_work_a_number_out_of_the_one_it_matched() {
772        let out = built(
773            "(rule (simplify (mul.i32 (value.i32 x) (iconst.i32 k)))\n\
774             (if (power_of_two.i32 k))\n\
775             (shl.i32 (value.i32 x) (iconst.i32 (ctz.i32 k)))\n\
776             (spec (= (bvmul x k) (result))))\n",
777        );
778        assert!(
779            out.contains("Piece::Computed { text: \"(ctz.i32 k)\", work: computed_0 }"),
780            "{out}"
781        );
782        assert!(out.contains("fn computed_0(bound: &[Option<i128>]) -> Option<i128> {"), "{out}");
783        assert!(
784            out.contains("let Some(Some(v1)) = bound.get(1).copied() else { return None };"),
785            "{out}"
786        );
787        assert!(out.contains("Some(trailing_zeros(32, v1))"), "{out}");
788        assert!(out.contains("power_of_two(32, v1)"), "{out}");
789        assert!(out.contains("fn power_of_two(bits: u32, value: i128) -> bool {"), "{out}");
790        assert!(out.contains("fn trailing_zeros(bits: u32, value: i128) -> i128 {"), "{out}");
791        assert!(out.contains("fn low(bits: u32, value: i128) -> i128 {"), "{out}");
792    }
793
794    /// The same arithmetic a guard is written in, in a replacement, and the mask a remainder
795    /// becomes is what wants it. Nothing about a computed piece is particular to counting bits.
796    #[test]
797    fn a_replacement_computes_in_the_language_a_guard_computes_in() {
798        let out = built(
799            "(rule (simplify (urem.i32 (value.i32 x) (iconst.i32 k)))\n\
800             (if (power_of_two.i32 k))\n\
801             (and.i32 (value.i32 x) (iconst.i32 (- k 1)))\n\
802             (spec (= (bvurem x k) (result))))\n",
803        );
804        assert!(out.contains("Piece::Computed { text: \"(- k 1)\", work: computed_0 }"), "{out}");
805        assert!(out.contains("Some((v1).saturating_sub(1))"), "{out}");
806        // A subtraction needs no helper, so the only one here is the guard's.
807        assert!(!out.contains("fn trailing_zeros"), "{out}");
808    }
809
810    /// A computation nothing can be made of is refused where it is written, the same as a guard
811    /// is, rather than emitted as a call to a function that does not exist.
812    #[test]
813    fn a_computed_piece_nothing_can_be_made_of_is_refused_where_it_is_written() {
814        let rules = parse(
815            "rules/test.rules",
816            "(rule (simplify (mul.i32 (value.i32 x) (iconst.i32 k)))\n\
817             (shl.i32 (value.i32 x) (iconst.i32 (extract 31 0 (log_of k))))\n\
818             (spec (= (bvmul x k) (result))))\n",
819        )
820        .expect("the rules read");
821        let matcher = Matcher::build("rules/test.rules", &rules).expect("the matcher builds");
822        let errors =
823            emit("rules/test.rules", &rules, &matcher).expect_err("the computation is refused");
824        assert_eq!(errors.len(), 1);
825        assert_eq!(errors[0].line, 2);
826        assert!(errors[0].message.contains("`log_of` of 1 is not a number"), "{}", errors[0]);
827    }
828
829    /// A head that only looks like one of the arithmetic ones is built rather than computed. The
830    /// width is what says which, so `ctz` with no width on it is a term and not a count.
831    #[test]
832    fn a_head_with_no_width_on_it_is_not_arithmetic() {
833        assert!(computes("ctz.i32", 1));
834        assert!(!computes("ctz", 1));
835        assert!(!computes("ctz.i32", 2));
836        assert!(!computes("ctz.f32", 1));
837    }
838
839    /// The first binding is read by the name for it rather than by an index of zero, which is
840    /// what the generated file being linted along with the rest of the tree comes to here.
841    #[test]
842    fn the_first_binding_is_read_by_the_name_for_it() {
843        let out = built(
844            "(rule (simplify (mul.i32 (iconst.i32 k) (value.i32 x)))\n\
845             (if (power_of_two.i32 k))\n\
846             (shl.i32 (value.i32 x) (iconst.i32 (ctz.i32 k)))\n\
847             (spec (= (bvmul k x) (result))))\n",
848        );
849        let guard = "let Some(Some(v0)) = bound.first().copied() else { return false };";
850        let computed = "let Some(Some(v0)) = bound.first().copied() else { return None };";
851        assert!(out.contains(guard), "{out}");
852        assert!(out.contains(computed), "{out}");
853        assert!(!out.contains("bound.get(0)"), "{out}");
854    }
855}