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!(
252 "\n/// `{guard}`, which is the guard of the rule on line {}.\nfn guard_{index}(bound: \
253 &[Option<i128>]) -> bool {{\n",
254 rule.line
255 );
256 used.sort_unstable();
257 used.dedup();
258 for at in used {
259 let _ = writeln!(
260 text,
261 " // {}\n let Some(Some(v{at})) = bound.get({at}).copied() else {{ return \
262 false }};",
263 bound[at]
264 );
265 }
266 let _ = writeln!(text, " {}\n}}", bare(&condition));
267 out.push(Some(text));
268 }
269 out
270}
271
272fn bare(text: &str) -> &str {
278 let Some(inner) = text.strip_prefix('(').and_then(|text| text.strip_suffix(')')) else {
279 return text;
280 };
281 let mut depth = 0i32;
282 for c in inner.chars() {
283 match c {
284 '(' => depth += 1,
285 ')' => depth -= 1,
286 _ => {}
287 }
288 if depth < 0 {
291 return text;
292 }
293 }
294 inner
295}
296
297fn condition(
299 source: &str,
300 term: &Term,
301 bound: &[String],
302 wanted: &mut Vec<&'static str>,
303 used: &mut Vec<usize>,
304) -> Result<String, Error> {
305 let TermKind::App { head, args } = &term.kind else {
306 return Err(refused(source, term, "a guard is a condition, and this is not one"));
307 };
308 let arity = args.len();
309 match (head.as_str(), arity) {
310 ("and" | "or", 1..) => {
311 let joint = if head == "and" { " && " } else { " || " };
312 let mut parts = Vec::with_capacity(arity);
313 for arg in args {
314 parts.push(condition(source, arg, bound, wanted, used)?);
315 }
316 Ok(format!("({})", parts.join(joint)))
317 }
318 ("not", 1) => Ok(format!("!{}", condition(source, &args[0], bound, wanted, used)?)),
319 ("=" | "!=" | "<" | "<=" | ">" | ">=", 2) => {
320 let operator = if head == "=" { "==" } else { head.as_str() };
321 let left = value(source, &args[0], bound, wanted, used)?;
322 let right = value(source, &args[1], bound, wanted, used)?;
323 Ok(format!("({left} {operator} {right})"))
324 }
325 _ => Err(refused(
326 source,
327 term,
328 &format!(
329 "`{head}` of {arity} is not a condition a guard can be compiled to. A guard is \
330 `and`, `or`, `not`, or a comparison of two numbers"
331 ),
332 )),
333 }
334}
335
336fn value(
338 source: &str,
339 term: &Term,
340 bound: &[String],
341 wanted: &mut Vec<&'static str>,
342 used: &mut Vec<usize>,
343) -> Result<String, Error> {
344 match &term.kind {
345 TermKind::Int(number) => Ok(format!("{number}")),
346 TermKind::Var(name) => {
347 let at = bound.iter().position(|have| have == name).unwrap_or_default();
349 used.push(at);
350 Ok(format!("v{at}"))
351 }
352 TermKind::App { head, args } => {
353 let arity = args.len();
354 match (head.as_str(), arity) {
355 ("sign_extend" | "zero_extend" | "extract", 3) => {
356 let first = width(source, &args[0])?;
357 let second = width(source, &args[1])?;
358 let inner = value(source, &args[2], bound, wanted, used)?;
359 let name = match head.as_str() {
360 "sign_extend" => "sign_extend",
361 "zero_extend" => "zero_extend",
362 _ => "extract",
363 };
364 want(wanted, name);
365 Ok(format!("{name}({first}, {second}, {inner})"))
366 }
367 _ => Err(refused(
368 source,
369 term,
370 &format!(
371 "`{head}` of {arity} is not a number a guard can be compiled to. The \
372 ones that are are `sign_extend`, `zero_extend` and `extract`"
373 ),
374 )),
375 }
376 }
377 }
378}
379
380fn width(source: &str, term: &Term) -> Result<String, Error> {
383 match &term.kind {
384 TermKind::Int(number) if (0..=128).contains(number) => Ok(format!("{number}")),
385 _ => Err(refused(source, term, "a width has to be a number from 0 to 128")),
386 }
387}
388
389fn want(wanted: &mut Vec<&'static str>, name: &'static str) {
391 if wanted.contains(&name) {
392 return;
393 }
394 wanted.push(name);
395 match name {
396 "sign_extend" => want(wanted, "shifted"),
397 "zero_extend" | "extract" => want(wanted, "low"),
398 _ => {}
399 }
400}
401
402fn helpers(out: &mut String, wanted: &[&str]) {
405 for (name, text) in HELPERS {
406 if wanted.contains(name) {
407 out.push_str(text);
408 }
409 }
410}
411
412fn refused(source: &str, term: &Term, message: &str) -> Error {
413 Error {
414 path: source.to_owned(),
415 line: term.line,
416 column: term.column,
417 message: message.to_owned(),
418 }
419}
420
421const SIGN_EXTEND: &str = "
422/// The low `from` bits of `value`, sign extended to `to` bits.
423fn sign_extend(from: u32, to: u32, value: i128) -> i128 {
424 shifted(to, shifted(from, value))
425}
426";
427
428const ZERO_EXTEND: &str = "
429/// The low `from` bits of `value`, read as a number and not sign extended.
430fn zero_extend(from: u32, to: u32, value: i128) -> i128 {
431 low(to, low(from, value))
432}
433";
434
435const EXTRACT: &str = "
436/// The bits from `hi` down to `lo` of `value`, read as a number.
437fn extract(hi: u32, lo: u32, value: i128) -> i128 {
438 if lo >= 128 || hi < lo {
439 return 0;
440 }
441 low(hi - lo + 1, value >> lo)
442}
443";
444
445const SHIFTED: &str = "
446/// `value` read as a signed number that many bits wide.
447fn shifted(bits: u32, value: i128) -> i128 {
448 match 128u32.checked_sub(bits) {
449 Some(room) if room > 0 => (value << room) >> room,
450 _ => value,
451 }
452}
453";
454
455const LOW: &str = "
456/// The low `bits` bits of `value`, read as a number.
457fn low(bits: u32, value: i128) -> i128 {
458 if bits >= 128 {
459 return value;
460 }
461 #[allow(clippy::cast_possible_wrap)]
462 let masked = (value as u128 & ((1u128 << bits) - 1)) as i128;
463 masked
464}
465";
466
467#[cfg(test)]
468mod tests {
469 use super::*;
470 use crate::parse;
471
472 fn built(text: &str) -> String {
473 let rules = parse("rules/test.rules", text).expect("the rules read");
474 let matcher = Matcher::build("rules/test.rules", &rules).expect("the matcher builds");
475 emit("rules/test.rules", &rules, &matcher).expect("the table is emitted")
476 }
477
478 #[test]
480 fn a_rule_set_comes_out_as_a_table_of_nodes_and_a_table_of_rules() {
481 let out = built(
482 "(rule (lower (add.i64 (value.i64 x) (value.i64 y)))\n\
483 (x64.add_rr_64 x y)\n\
484 (spec (= (bvadd x y) (result))))\n",
485 );
486 assert!(out.contains("use super::{Node, Piece, Rule, Table, Test};"), "{out}");
487 assert!(out.contains("pub const SOURCE: &str = \"rules/test.rules\";"), "{out}");
488 assert!(out.contains("(Test::App { head: \"add.i64\", arity: 2 }, 1),"), "{out}");
489 assert!(out.contains("wildcard: Some((\"x\", 3)),"), "{out}");
490 assert!(out.contains("accept: Some(0),"), "{out}");
491 assert!(out.contains("Piece::App { head: \"x64.add_rr_64\", arity: 2 }"), "{out}");
492 assert!(out.contains("Piece::Var { name: \"x\", index: 0 }"), "{out}");
493 assert!(out.contains("Piece::Var { name: \"y\", index: 1 }"), "{out}");
494 assert!(out.contains("guard: None,"), "{out}");
495 }
496
497 #[test]
501 fn a_name_written_twice_comes_out_as_a_test_and_takes_no_position() {
502 let out = built(
503 "(rule (simplify (and.i32 (value.i32 x) (value.i32 x)))\n\
504 (value.i32 x)\n\
505 (spec (= x (result))))\n\
506 (rule (simplify (shl.i32 (value.i32 x) (iconst.i32 k)))\n\
507 (if (>= k 0))\n\
508 (value.i32 x)\n\
509 (spec (= (bvshl x k) (result))))\n",
510 );
511 assert!(out.contains("(Test::Same(0), "), "{out}");
512 assert!(out.contains("Piece::Var { name: \"x\", index: 0 }"), "{out}");
513 assert!(
514 out.contains("let Some(Some(v1)) = bound.get(1).copied() else { return false };"),
515 "{out}"
516 );
517 }
518
519 #[test]
523 fn a_guard_comes_out_as_a_function_of_the_bindings() {
524 let out = built(
525 "(rule (lower (shl.i64 (value.i64 x) (iconst.i64 k)))\n\
526 (if (and (>= k 0) (< k 64)))\n\
527 (x64.shl_ri_64 x k)\n\
528 (spec (= (bvshl x k) (result))))\n",
529 );
530 assert!(out.contains("guard: Some(guard_0),"), "{out}");
531 assert!(out.contains("fn guard_0(bound: &[Option<i128>]) -> bool {"), "{out}");
532 assert!(
533 out.contains("let Some(Some(v1)) = bound.get(1).copied() else { return false };"),
534 "{out}"
535 );
536 assert!(out.contains("(v1 >= 0) && (v1 < 64)"), "{out}");
537 assert!(!out.contains("fn sign_extend"), "{out}");
540 assert!(!out.contains("fn low"), "{out}");
541 }
542
543 #[test]
546 fn a_guard_that_reads_bits_brings_the_helpers_it_needs() {
547 let out = built(
548 "(rule (lower (add.i64 (value.i64 x) (iconst.i64 k)))\n\
549 (if (= k (sign_extend 32 64 (extract 31 0 k))))\n\
550 (x64.add_ri_64 x k)\n\
551 (spec (= (bvadd x k) (result))))\n",
552 );
553 assert!(out.contains("v1 == sign_extend(32, 64, extract(31, 0, v1))"), "{out}");
554 assert!(out.contains("fn sign_extend(from: u32, to: u32, value: i128) -> i128 {"), "{out}");
555 assert!(out.contains("fn shifted(bits: u32, value: i128) -> i128 {"), "{out}");
556 assert!(out.contains("fn extract(hi: u32, lo: u32, value: i128) -> i128 {"), "{out}");
557 assert!(out.contains("fn low(bits: u32, value: i128) -> i128 {"), "{out}");
558 assert!(!out.contains("fn zero_extend"), "{out}");
559 }
560
561 #[test]
565 fn a_guard_nothing_can_be_made_of_is_refused_where_it_is_written() {
566 let rules = parse(
567 "rules/test.rules",
568 "(rule (lower (add.i64 (value.i64 x) (iconst.i64 k)))\n\
569 (if (fits_in_a_byte k))\n\
570 (x64.add_ri_64 x k)\n\
571 (spec (= (bvadd x k) (result))))\n",
572 )
573 .expect("the rules read");
574 let matcher = Matcher::build("rules/test.rules", &rules).expect("the matcher builds");
575 let errors = emit("rules/test.rules", &rules, &matcher).expect_err("the guard is refused");
576 assert_eq!(errors.len(), 1);
577 assert_eq!(errors[0].line, 2);
578 assert!(
579 errors[0].message.contains("`fits_in_a_byte` of 1 is not a condition"),
580 "{}",
581 errors[0]
582 );
583 }
584}