1use std::fmt::Write as _;
40
41use crate::ast::{Rule, Term, TermKind};
42use crate::error::Error;
43use crate::matcher::{Matcher, Test};
44
45const HELPERS: &[(&str, &str)] = &[
47 ("sign_extend", SIGN_EXTEND),
48 ("zero_extend", ZERO_EXTEND),
49 ("extract", EXTRACT),
50 ("shifted", SHIFTED),
51 ("low", LOW),
52];
53
54pub fn emit(source: &str, rules: &[Rule], matcher: &Matcher) -> Result<String, Vec<Error>> {
66 let mut out = String::new();
67 let mut errors = Vec::new();
68 let mut wanted: Vec<&'static str> = Vec::new();
69
70 let guards = compile_guards(source, rules, &mut wanted, &mut errors);
71 if !errors.is_empty() {
72 return Err(errors);
73 }
74
75 header(&mut out, source, rules, matcher);
76 nodes(&mut out, matcher);
77 replacements(&mut out, source, rules, &guards);
78 out.push_str(&guards.iter().flatten().map(String::as_str).collect::<String>());
79 helpers(&mut out, &wanted);
80 Ok(out)
81}
82
83fn header(out: &mut String, source: &str, rules: &[Rule], matcher: &Matcher) {
85 let _ = write!(
86 out,
87 "\
88// Generated from {source} by rucc-rules. Do not edit this file: edit the
89// rule file and build again. It holds {} rules over {} trie nodes.
90//
91// The types are the ones the module that includes this file defines, and the walk over the
92// table is there too. What is here is the table.
93
94use super::{{Node, Piece, Rule, Table, Test}};
95
96/// The rule file this table was built from, so that anything said about a rule can name a file
97/// somebody can open.
98pub const SOURCE: &str = {source:?};
99
100/// The rules of this file, as an automaton over their patterns.
101pub static TABLE: Table = Table {{ source: SOURCE, nodes: NODES, rules: RULES }};
102",
103 rules.len(),
104 matcher.nodes.len()
105 );
106}
107
108fn nodes(out: &mut String, matcher: &Matcher) {
110 out.push_str(
111 "\n/// The trie over the patterns. A node holds the tests to try in order, the branch\n\
112 /// that takes anything, and the rule that ends here if one does.\nstatic NODES: \
113 &[Node] = &[\n",
114 );
115 for (index, node) in matcher.nodes.iter().enumerate() {
116 let _ = writeln!(out, " // {index}");
117 out.push_str(" Node {\n tests: &[");
118 for (test, next) in &node.tests {
119 match test {
120 Test::App { head, arity } => {
121 let _ = write!(
122 out,
123 "\n (Test::App {{ head: {head:?}, arity: {arity} }}, {next}),"
124 );
125 }
126 Test::Int(value) => {
127 let _ = write!(out, "\n (Test::Int({value}), {next}),");
128 }
129 Test::Same(index) => {
130 let _ = write!(out, "\n (Test::Same({index}), {next}),");
131 }
132 }
133 }
134 if !node.tests.is_empty() {
135 out.push_str("\n ");
136 }
137 out.push_str("],\n");
138 match &node.wildcard {
139 Some((name, next)) => {
140 let _ = writeln!(out, " wildcard: Some(({name:?}, {next})),");
141 }
142 None => out.push_str(" wildcard: None,\n"),
143 }
144 match node.accept {
145 Some(rule) => {
146 let _ = writeln!(out, " accept: Some({rule}),");
147 }
148 None => out.push_str(" accept: None,\n"),
149 }
150 out.push_str(" },\n");
151 }
152 out.push_str("];\n");
153}
154
155fn replacements(out: &mut String, source: &str, rules: &[Rule], guards: &[Option<String>]) {
157 out.push_str(
158 "\n/// The rules, in the order the rule file writes them, which is the order the\n\
159 /// `accept` of a trie node names.\nstatic RULES: &[Rule] = &[\n",
160 );
161 for (index, rule) in rules.iter().enumerate() {
162 let pattern = rule.pattern.to_string();
163 let _ = writeln!(out, " // {source}:{}", rule.line);
164 out.push_str(" Rule {\n");
165 let _ = writeln!(out, " pattern: {pattern:?},");
166 out.push_str(" replacement: &[");
167 let bound = bound_names(&rule.pattern);
168 for piece in pieces(&rule.replacement, &bound) {
169 let _ = write!(out, "\n {piece},");
170 }
171 out.push_str("\n ],\n");
172 match guards[index] {
173 Some(_) => {
174 let _ = writeln!(out, " guard: Some(guard_{index}),");
175 }
176 None => out.push_str(" guard: None,\n"),
177 }
178 let _ = writeln!(out, " line: {},", rule.line);
179 out.push_str(" },\n");
180 }
181 out.push_str("];\n");
182}
183
184fn bound_names(pattern: &Term) -> Vec<String> {
192 let mut out: Vec<String> = Vec::new();
193 pattern.walk(&mut |term| {
194 if let TermKind::Var(name) = &term.kind {
195 if !out.iter().any(|have| have == name) {
196 out.push(name.clone());
197 }
198 }
199 });
200 out
201}
202
203fn pieces(term: &Term, bound: &[String]) -> Vec<String> {
205 let mut out = Vec::new();
206 push_pieces(term, bound, &mut out);
207 out
208}
209
210fn push_pieces(term: &Term, bound: &[String], out: &mut Vec<String>) {
211 match &term.kind {
212 TermKind::Var(name) => {
213 let index = bound.iter().position(|have| have == name).unwrap_or_default();
216 out.push(format!("Piece::Var {{ name: {name:?}, index: {index} }}"));
217 }
218 TermKind::Int(value) => out.push(format!("Piece::Int({value})")),
219 TermKind::App { head, args } => {
220 out.push(format!("Piece::App {{ head: {head:?}, arity: {} }}", args.len()));
221 for arg in args {
222 push_pieces(arg, bound, out);
223 }
224 }
225 }
226}
227
228fn compile_guards(
230 source: &str,
231 rules: &[Rule],
232 wanted: &mut Vec<&'static str>,
233 errors: &mut Vec<Error>,
234) -> Vec<Option<String>> {
235 let mut out = Vec::with_capacity(rules.len());
236 for (index, rule) in rules.iter().enumerate() {
237 let Some(guard) = &rule.guard else {
238 out.push(None);
239 continue;
240 };
241 let bound = bound_names(&rule.pattern);
242 let mut used = Vec::new();
243 let condition = match condition(source, guard, &bound, wanted, &mut used) {
244 Ok(text) => text,
245 Err(error) => {
246 errors.push(error);
247 out.push(None);
248 continue;
249 }
250 };
251 let mut text = format!(
255 "\n/// `{guard}`, which is the guard of the rule on line {}.\n\
256 #[allow(clippy::manual_range_contains)]\nfn guard_{index}(bound: \
257 &[Option<i128>]) -> bool {{\n",
258 rule.line
259 );
260 used.sort_unstable();
261 used.dedup();
262 for at in used {
263 let _ = writeln!(
264 text,
265 " // {}\n let Some(Some(v{at})) = bound.get({at}).copied() else {{ return \
266 false }};",
267 bound[at]
268 );
269 }
270 let _ = writeln!(text, " {}\n}}", bare(&condition));
271 out.push(Some(text));
272 }
273 out
274}
275
276fn bare(text: &str) -> &str {
282 let Some(inner) = text.strip_prefix('(').and_then(|text| text.strip_suffix(')')) else {
283 return text;
284 };
285 let mut depth = 0i32;
286 for c in inner.chars() {
287 match c {
288 '(' => depth += 1,
289 ')' => depth -= 1,
290 _ => {}
291 }
292 if depth < 0 {
295 return text;
296 }
297 }
298 inner
299}
300
301fn condition(
303 source: &str,
304 term: &Term,
305 bound: &[String],
306 wanted: &mut Vec<&'static str>,
307 used: &mut Vec<usize>,
308) -> Result<String, Error> {
309 let TermKind::App { head, args } = &term.kind else {
310 return Err(refused(source, term, "a guard is a condition, and this is not one"));
311 };
312 let arity = args.len();
313 match (head.as_str(), arity) {
314 ("and" | "or", 1..) => {
315 let joint = if head == "and" { " && " } else { " || " };
316 let mut parts = Vec::with_capacity(arity);
317 for arg in args {
318 parts.push(condition(source, arg, bound, wanted, used)?);
319 }
320 Ok(format!("({})", parts.join(joint)))
321 }
322 ("not", 1) => Ok(format!("!{}", condition(source, &args[0], bound, wanted, used)?)),
323 ("=" | "!=" | "<" | "<=" | ">" | ">=", 2) => {
324 let operator = if head == "=" { "==" } else { head.as_str() };
325 let left = value(source, &args[0], bound, wanted, used)?;
326 let right = value(source, &args[1], bound, wanted, used)?;
327 Ok(format!("({left} {operator} {right})"))
328 }
329 _ => Err(refused(
330 source,
331 term,
332 &format!(
333 "`{head}` of {arity} is not a condition a guard can be compiled to. A guard is \
334 `and`, `or`, `not`, or a comparison of two numbers"
335 ),
336 )),
337 }
338}
339
340fn value(
342 source: &str,
343 term: &Term,
344 bound: &[String],
345 wanted: &mut Vec<&'static str>,
346 used: &mut Vec<usize>,
347) -> Result<String, Error> {
348 match &term.kind {
349 TermKind::Int(number) => Ok(format!("{number}")),
350 TermKind::Var(name) => {
351 let at = bound.iter().position(|have| have == name).unwrap_or_default();
353 used.push(at);
354 Ok(format!("v{at}"))
355 }
356 TermKind::App { head, args } => {
357 let arity = args.len();
358 match (head.as_str(), arity) {
359 ("+" | "-", 2) => {
373 let left = value(source, &args[0], bound, wanted, used)?;
374 let right = value(source, &args[1], bound, wanted, used)?;
375 let name = if head == "+" { "saturating_add" } else { "saturating_sub" };
376 Ok(format!("({left}).{name}({right})"))
377 }
378 ("sign_extend" | "zero_extend" | "extract", 3) => {
379 let first = width(source, &args[0])?;
380 let second = width(source, &args[1])?;
381 let inner = value(source, &args[2], bound, wanted, used)?;
382 let name = match head.as_str() {
383 "sign_extend" => "sign_extend",
384 "zero_extend" => "zero_extend",
385 _ => "extract",
386 };
387 want(wanted, name);
388 Ok(format!("{name}({first}, {second}, {inner})"))
389 }
390 _ => Err(refused(
391 source,
392 term,
393 &format!(
394 "`{head}` of {arity} is not a number a guard can be compiled to. The \
395 ones that are are `+`, `-`, `sign_extend`, `zero_extend` and `extract`"
396 ),
397 )),
398 }
399 }
400 }
401}
402
403fn width(source: &str, term: &Term) -> Result<String, Error> {
406 match &term.kind {
407 TermKind::Int(number) if (0..=128).contains(number) => Ok(format!("{number}")),
408 _ => Err(refused(source, term, "a width has to be a number from 0 to 128")),
409 }
410}
411
412fn want(wanted: &mut Vec<&'static str>, name: &'static str) {
414 if wanted.contains(&name) {
415 return;
416 }
417 wanted.push(name);
418 match name {
419 "sign_extend" => want(wanted, "shifted"),
420 "zero_extend" | "extract" => want(wanted, "low"),
421 _ => {}
422 }
423}
424
425fn helpers(out: &mut String, wanted: &[&str]) {
428 for (name, text) in HELPERS {
429 if wanted.contains(name) {
430 out.push_str(text);
431 }
432 }
433}
434
435fn refused(source: &str, term: &Term, message: &str) -> Error {
436 Error {
437 path: source.to_owned(),
438 line: term.line,
439 column: term.column,
440 message: message.to_owned(),
441 }
442}
443
444const SIGN_EXTEND: &str = "
445/// The low `from` bits of `value`, sign extended to `to` bits.
446fn sign_extend(from: u32, to: u32, value: i128) -> i128 {
447 shifted(to, shifted(from, value))
448}
449";
450
451const ZERO_EXTEND: &str = "
452/// The low `from` bits of `value`, read as a number and not sign extended.
453fn zero_extend(from: u32, to: u32, value: i128) -> i128 {
454 low(to, low(from, value))
455}
456";
457
458const EXTRACT: &str = "
459/// The bits from `hi` down to `lo` of `value`, read as a number.
460fn extract(hi: u32, lo: u32, value: i128) -> i128 {
461 if lo >= 128 || hi < lo {
462 return 0;
463 }
464 low(hi - lo + 1, value >> lo)
465}
466";
467
468const SHIFTED: &str = "
469/// `value` read as a signed number that many bits wide.
470fn shifted(bits: u32, value: i128) -> i128 {
471 match 128u32.checked_sub(bits) {
472 Some(room) if room > 0 => (value << room) >> room,
473 _ => value,
474 }
475}
476";
477
478const LOW: &str = "
479/// The low `bits` bits of `value`, read as a number.
480fn low(bits: u32, value: i128) -> i128 {
481 if bits >= 128 {
482 return value;
483 }
484 #[allow(clippy::cast_possible_wrap)]
485 let masked = (value as u128 & ((1u128 << bits) - 1)) as i128;
486 masked
487}
488";
489
490#[cfg(test)]
491mod tests {
492 use super::*;
493 use crate::parse;
494
495 fn built(text: &str) -> String {
496 let rules = parse("rules/test.rules", text).expect("the rules read");
497 let matcher = Matcher::build("rules/test.rules", &rules).expect("the matcher builds");
498 emit("rules/test.rules", &rules, &matcher).expect("the table is emitted")
499 }
500
501 #[test]
503 fn a_rule_set_comes_out_as_a_table_of_nodes_and_a_table_of_rules() {
504 let out = built(
505 "(rule (lower (add.i64 (value.i64 x) (value.i64 y)))\n\
506 (x64.add_rr_64 x y)\n\
507 (spec (= (bvadd x y) (result))))\n",
508 );
509 assert!(out.contains("use super::{Node, Piece, Rule, Table, Test};"), "{out}");
510 assert!(out.contains("pub const SOURCE: &str = \"rules/test.rules\";"), "{out}");
511 assert!(out.contains("(Test::App { head: \"add.i64\", arity: 2 }, 1),"), "{out}");
512 assert!(out.contains("wildcard: Some((\"x\", 3)),"), "{out}");
513 assert!(out.contains("accept: Some(0),"), "{out}");
514 assert!(out.contains("Piece::App { head: \"x64.add_rr_64\", arity: 2 }"), "{out}");
515 assert!(out.contains("Piece::Var { name: \"x\", index: 0 }"), "{out}");
516 assert!(out.contains("Piece::Var { name: \"y\", index: 1 }"), "{out}");
517 assert!(out.contains("guard: None,"), "{out}");
518 }
519
520 #[test]
524 fn a_name_written_twice_comes_out_as_a_test_and_takes_no_position() {
525 let out = built(
526 "(rule (simplify (and.i32 (value.i32 x) (value.i32 x)))\n\
527 (value.i32 x)\n\
528 (spec (= x (result))))\n\
529 (rule (simplify (shl.i32 (value.i32 x) (iconst.i32 k)))\n\
530 (if (>= k 0))\n\
531 (value.i32 x)\n\
532 (spec (= (bvshl x k) (result))))\n",
533 );
534 assert!(out.contains("(Test::Same(0), "), "{out}");
535 assert!(out.contains("Piece::Var { name: \"x\", index: 0 }"), "{out}");
536 assert!(
537 out.contains("let Some(Some(v1)) = bound.get(1).copied() else { return false };"),
538 "{out}"
539 );
540 }
541
542 #[test]
546 fn a_guard_comes_out_as_a_function_of_the_bindings() {
547 let out = built(
548 "(rule (lower (shl.i64 (value.i64 x) (iconst.i64 k)))\n\
549 (if (and (>= k 0) (< k 64)))\n\
550 (x64.shl_ri_64 x k)\n\
551 (spec (= (bvshl x k) (result))))\n",
552 );
553 assert!(out.contains("guard: Some(guard_0),"), "{out}");
554 assert!(out.contains("fn guard_0(bound: &[Option<i128>]) -> bool {"), "{out}");
555 assert!(
556 out.contains("let Some(Some(v1)) = bound.get(1).copied() else { return false };"),
557 "{out}"
558 );
559 assert!(out.contains("(v1 >= 0) && (v1 < 64)"), "{out}");
560 assert!(!out.contains("fn sign_extend"), "{out}");
563 assert!(!out.contains("fn low"), "{out}");
564 }
565
566 #[test]
569 fn a_guard_that_reads_bits_brings_the_helpers_it_needs() {
570 let out = built(
571 "(rule (lower (add.i64 (value.i64 x) (iconst.i64 k)))\n\
572 (if (= k (sign_extend 32 64 (extract 31 0 k))))\n\
573 (x64.add_ri_64 x k)\n\
574 (spec (= (bvadd x k) (result))))\n",
575 );
576 assert!(out.contains("v1 == sign_extend(32, 64, extract(31, 0, v1))"), "{out}");
577 assert!(out.contains("fn sign_extend(from: u32, to: u32, value: i128) -> i128 {"), "{out}");
578 assert!(out.contains("fn shifted(bits: u32, value: i128) -> i128 {"), "{out}");
579 assert!(out.contains("fn extract(hi: u32, lo: u32, value: i128) -> i128 {"), "{out}");
580 assert!(out.contains("fn low(bits: u32, value: i128) -> i128 {"), "{out}");
581 assert!(!out.contains("fn zero_extend"), "{out}");
582 }
583
584 #[test]
588 fn a_guard_nothing_can_be_made_of_is_refused_where_it_is_written() {
589 let rules = parse(
590 "rules/test.rules",
591 "(rule (lower (add.i64 (value.i64 x) (iconst.i64 k)))\n\
592 (if (fits_in_a_byte k))\n\
593 (x64.add_ri_64 x k)\n\
594 (spec (= (bvadd x k) (result))))\n",
595 )
596 .expect("the rules read");
597 let matcher = Matcher::build("rules/test.rules", &rules).expect("the matcher builds");
598 let errors = emit("rules/test.rules", &rules, &matcher).expect_err("the guard is refused");
599 assert_eq!(errors.len(), 1);
600 assert_eq!(errors[0].line, 2);
601 assert!(
602 errors[0].message.contains("`fits_in_a_byte` of 1 is not a condition"),
603 "{}",
604 errors[0]
605 );
606 }
607}