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