1use std::fmt::Write as _;
54
55use crate::ast::{Rule, Term, TermKind};
56use crate::error::Error;
57use crate::matcher::Matcher;
58
59const 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
70pub 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
107fn 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
141fn 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#[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
229fn 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
248fn 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 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 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
307fn 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
318fn 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
340fn reads(at: usize) -> String {
345 if at == 0 { "bound.first()".to_owned() } else { format!("bound.get({at})") }
346}
347
348fn 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 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
396fn 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 if depth < 0 {
415 return text;
416 }
417 }
418 inner
419}
420
421fn 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 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
468fn 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 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 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 ("+" | "-", 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
540fn 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
549fn suffix(head: &str, name: &str) -> Option<u32> {
556 head.strip_prefix(name)?.strip_prefix(".i")?.parse().ok().filter(|bits| *bits <= 128)
557}
558
559fn 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
572fn 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 #[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 #[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 #[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 assert!(!out.contains("fn sign_extend"), "{out}");
726 assert!(!out.contains("fn low"), "{out}");
727 }
728
729 #[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 #[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 #[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 #[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 assert!(!out.contains("fn trailing_zeros"), "{out}");
812 }
813
814 #[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 #[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 #[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}