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 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#[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
225fn 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
244fn 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 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 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
303fn 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
314fn 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
336fn reads(at: usize) -> String {
341 if at == 0 { "bound.first()".to_owned() } else { format!("bound.get({at})") }
342}
343
344fn 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 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
392fn 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 if depth < 0 {
411 return text;
412 }
413 }
414 inner
415}
416
417fn 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 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
464fn 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 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 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 ("+" | "-", 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
536fn 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
545fn suffix(head: &str, name: &str) -> Option<u32> {
552 head.strip_prefix(name)?.strip_prefix(".i")?.parse().ok().filter(|bits| *bits <= 128)
553}
554
555fn 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
568fn 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 #[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 #[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 #[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 assert!(!out.contains("fn sign_extend"), "{out}");
722 assert!(!out.contains("fn low"), "{out}");
723 }
724
725 #[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 #[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 #[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 #[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 assert!(!out.contains("fn trailing_zeros"), "{out}");
808 }
809
810 #[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 #[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 #[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}