1use std::fmt::Write as _;
40
41use crate::ast::{Rule, Term, TermKind};
42use crate::error::Error;
43use crate::matcher::Matcher;
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 shape = matcher.shape();
86 let _ = write!(
87 out,
88 "\
89// Generated from {source} by rucc-rules. Do not edit this file: edit the
90// rule file and build again. It holds {} rules over {} trie nodes.
91//
92// The widest node has {} branches. Reading them in the order the rules are written would ask
93// that many questions to reach the last of them and to find that none of them matched, and the
94// search that is done instead asks {}. {} nodes ask more than one kind of question, which is how
95// many of them the order the kinds are tried in decides anything at.
96//
97// The types are the ones the module that includes this file defines, and the walk over the
98// table is there too. What is here is the table.
99
100use super::{{Node, Piece, Rule, Table}};
101
102/// The rule file this table was built from, so that anything said about a rule can name a file
103/// somebody can open.
104pub const SOURCE: &str = {source:?};
105
106/// The rules of this file, as an automaton over their patterns.
107pub static TABLE: Table = Table {{ source: SOURCE, nodes: NODES, rules: RULES }};
108",
109 rules.len(),
110 shape.nodes,
111 shape.widest,
112 shape.search,
113 shape.mixed,
114 );
115}
116
117fn nodes(out: &mut String, matcher: &Matcher) {
119 out.push_str(
120 "\n/// The trie over the patterns. A node holds the branches taken on the head of a\n\
121 /// term, the branches taken on the value of a constant, the branches taken on a\n\
122 /// repeat of an earlier binding, the branch that takes anything, and the rule that\n\
123 /// ends here if one does. The first two are sorted, which is what makes finding a\n\
124 /// branch a search.\nstatic NODES: &[Node] = &[\n",
125 );
126 for (index, node) in matcher.nodes.iter().enumerate() {
127 let _ = writeln!(out, " // {index}");
128 out.push_str(" Node {\n heads: &[");
129 for (head, arity, next) in &node.heads {
130 let _ = write!(out, "\n ({head:?}, {arity}, {next}),");
131 }
132 if !node.heads.is_empty() {
133 out.push_str("\n ");
134 }
135 out.push_str("],\n ints: &[");
136 for (value, next) in &node.ints {
137 let _ = write!(out, "\n ({value}, {next}),");
138 }
139 if !node.ints.is_empty() {
140 out.push_str("\n ");
141 }
142 out.push_str("],\n same: &[");
143 for (binding, next) in &node.same {
144 let _ = write!(out, "\n ({binding}, {next}),");
145 }
146 if !node.same.is_empty() {
147 out.push_str("\n ");
148 }
149 out.push_str("],\n");
150 match &node.wildcard {
151 Some((name, next)) => {
152 let _ = writeln!(out, " wildcard: Some(({name:?}, {next})),");
153 }
154 None => out.push_str(" wildcard: None,\n"),
155 }
156 match node.accept {
157 Some(rule) => {
158 let _ = writeln!(out, " accept: Some({rule}),");
159 }
160 None => out.push_str(" accept: None,\n"),
161 }
162 out.push_str(" },\n");
163 }
164 out.push_str("];\n");
165}
166
167fn replacements(out: &mut String, source: &str, rules: &[Rule], guards: &[Option<String>]) {
169 out.push_str(
170 "\n/// The rules, in the order the rule file writes them, which is the order the\n\
171 /// `accept` of a trie node names.\nstatic RULES: &[Rule] = &[\n",
172 );
173 for (index, rule) in rules.iter().enumerate() {
174 let pattern = rule.pattern.to_string();
175 let _ = writeln!(out, " // {source}:{}", rule.line);
176 out.push_str(" Rule {\n");
177 let _ = writeln!(out, " pattern: {pattern:?},");
178 out.push_str(" replacement: &[");
179 let bound = bound_names(&rule.pattern);
180 for piece in pieces(&rule.replacement, &bound) {
181 let _ = write!(out, "\n {piece},");
182 }
183 out.push_str("\n ],\n");
184 match guards[index] {
185 Some(_) => {
186 let _ = writeln!(out, " guard: Some(guard_{index}),");
187 }
188 None => out.push_str(" guard: None,\n"),
189 }
190 let _ = writeln!(out, " line: {},", rule.line);
191 out.push_str(" },\n");
192 }
193 out.push_str("];\n");
194}
195
196fn bound_names(pattern: &Term) -> Vec<String> {
204 let mut out: Vec<String> = Vec::new();
205 pattern.walk(&mut |term| {
206 if let TermKind::Var(name) = &term.kind {
207 if !out.iter().any(|have| have == name) {
208 out.push(name.clone());
209 }
210 }
211 });
212 out
213}
214
215fn pieces(term: &Term, bound: &[String]) -> Vec<String> {
217 let mut out = Vec::new();
218 push_pieces(term, bound, &mut out);
219 out
220}
221
222fn push_pieces(term: &Term, bound: &[String], out: &mut Vec<String>) {
223 match &term.kind {
224 TermKind::Var(name) => {
225 let index = bound.iter().position(|have| have == name).unwrap_or_default();
228 out.push(format!("Piece::Var {{ name: {name:?}, index: {index} }}"));
229 }
230 TermKind::Int(value) => out.push(format!("Piece::Int({value})")),
231 TermKind::App { head, args } => {
232 out.push(format!("Piece::App {{ head: {head:?}, arity: {} }}", args.len()));
233 for arg in args {
234 push_pieces(arg, bound, out);
235 }
236 }
237 }
238}
239
240fn compile_guards(
242 source: &str,
243 rules: &[Rule],
244 wanted: &mut Vec<&'static str>,
245 errors: &mut Vec<Error>,
246) -> Vec<Option<String>> {
247 let mut out = Vec::with_capacity(rules.len());
248 for (index, rule) in rules.iter().enumerate() {
249 let Some(guard) = &rule.guard else {
250 out.push(None);
251 continue;
252 };
253 let bound = bound_names(&rule.pattern);
254 let mut used = Vec::new();
255 let condition = match condition(source, guard, &bound, wanted, &mut used) {
256 Ok(text) => text,
257 Err(error) => {
258 errors.push(error);
259 out.push(None);
260 continue;
261 }
262 };
263 let mut text = format!(
267 "\n/// `{guard}`, which is the guard of the rule on line {}.\n\
268 #[allow(clippy::manual_range_contains)]\nfn guard_{index}(bound: \
269 &[Option<i128>]) -> bool {{\n",
270 rule.line
271 );
272 used.sort_unstable();
273 used.dedup();
274 for at in used {
275 let _ = writeln!(
276 text,
277 " // {}\n let Some(Some(v{at})) = bound.get({at}).copied() else {{ return \
278 false }};",
279 bound[at]
280 );
281 }
282 let _ = writeln!(text, " {}\n}}", bare(&condition));
283 out.push(Some(text));
284 }
285 out
286}
287
288fn bare(text: &str) -> &str {
294 let Some(inner) = text.strip_prefix('(').and_then(|text| text.strip_suffix(')')) else {
295 return text;
296 };
297 let mut depth = 0i32;
298 for c in inner.chars() {
299 match c {
300 '(' => depth += 1,
301 ')' => depth -= 1,
302 _ => {}
303 }
304 if depth < 0 {
307 return text;
308 }
309 }
310 inner
311}
312
313fn condition(
315 source: &str,
316 term: &Term,
317 bound: &[String],
318 wanted: &mut Vec<&'static str>,
319 used: &mut Vec<usize>,
320) -> Result<String, Error> {
321 let TermKind::App { head, args } = &term.kind else {
322 return Err(refused(source, term, "a guard is a condition, and this is not one"));
323 };
324 let arity = args.len();
325 match (head.as_str(), arity) {
326 ("and" | "or", 1..) => {
327 let joint = if head == "and" { " && " } else { " || " };
328 let mut parts = Vec::with_capacity(arity);
329 for arg in args {
330 parts.push(condition(source, arg, bound, wanted, used)?);
331 }
332 Ok(format!("({})", parts.join(joint)))
333 }
334 ("not", 1) => Ok(format!("!{}", condition(source, &args[0], bound, wanted, used)?)),
335 ("=" | "!=" | "<" | "<=" | ">" | ">=", 2) => {
336 let operator = if head == "=" { "==" } else { head.as_str() };
337 let left = value(source, &args[0], bound, wanted, used)?;
338 let right = value(source, &args[1], bound, wanted, used)?;
339 Ok(format!("({left} {operator} {right})"))
340 }
341 _ => Err(refused(
342 source,
343 term,
344 &format!(
345 "`{head}` of {arity} is not a condition a guard can be compiled to. A guard is \
346 `and`, `or`, `not`, or a comparison of two numbers"
347 ),
348 )),
349 }
350}
351
352fn value(
354 source: &str,
355 term: &Term,
356 bound: &[String],
357 wanted: &mut Vec<&'static str>,
358 used: &mut Vec<usize>,
359) -> Result<String, Error> {
360 match &term.kind {
361 TermKind::Int(number) => Ok(format!("{number}")),
362 TermKind::Var(name) => {
363 let at = bound.iter().position(|have| have == name).unwrap_or_default();
365 used.push(at);
366 Ok(format!("v{at}"))
367 }
368 TermKind::App { head, args } => {
369 let arity = args.len();
370 match (head.as_str(), arity) {
371 ("+" | "-", 2) => {
385 let left = value(source, &args[0], bound, wanted, used)?;
386 let right = value(source, &args[1], bound, wanted, used)?;
387 let name = if head == "+" { "saturating_add" } else { "saturating_sub" };
388 Ok(format!("({left}).{name}({right})"))
389 }
390 ("sign_extend" | "zero_extend" | "extract", 3) => {
391 let first = width(source, &args[0])?;
392 let second = width(source, &args[1])?;
393 let inner = value(source, &args[2], bound, wanted, used)?;
394 let name = match head.as_str() {
395 "sign_extend" => "sign_extend",
396 "zero_extend" => "zero_extend",
397 _ => "extract",
398 };
399 want(wanted, name);
400 Ok(format!("{name}({first}, {second}, {inner})"))
401 }
402 _ => Err(refused(
403 source,
404 term,
405 &format!(
406 "`{head}` of {arity} is not a number a guard can be compiled to. The \
407 ones that are are `+`, `-`, `sign_extend`, `zero_extend` and `extract`"
408 ),
409 )),
410 }
411 }
412 }
413}
414
415fn width(source: &str, term: &Term) -> Result<String, Error> {
418 match &term.kind {
419 TermKind::Int(number) if (0..=128).contains(number) => Ok(format!("{number}")),
420 _ => Err(refused(source, term, "a width has to be a number from 0 to 128")),
421 }
422}
423
424fn want(wanted: &mut Vec<&'static str>, name: &'static str) {
426 if wanted.contains(&name) {
427 return;
428 }
429 wanted.push(name);
430 match name {
431 "sign_extend" => want(wanted, "shifted"),
432 "zero_extend" | "extract" => want(wanted, "low"),
433 _ => {}
434 }
435}
436
437fn helpers(out: &mut String, wanted: &[&str]) {
440 for (name, text) in HELPERS {
441 if wanted.contains(name) {
442 out.push_str(text);
443 }
444 }
445}
446
447fn refused(source: &str, term: &Term, message: &str) -> Error {
448 Error {
449 path: source.to_owned(),
450 line: term.line,
451 column: term.column,
452 message: message.to_owned(),
453 }
454}
455
456const SIGN_EXTEND: &str = "
457/// The low `from` bits of `value`, sign extended to `to` bits.
458fn sign_extend(from: u32, to: u32, value: i128) -> i128 {
459 shifted(to, shifted(from, value))
460}
461";
462
463const ZERO_EXTEND: &str = "
464/// The low `from` bits of `value`, read as a number and not sign extended.
465fn zero_extend(from: u32, to: u32, value: i128) -> i128 {
466 low(to, low(from, value))
467}
468";
469
470const EXTRACT: &str = "
471/// The bits from `hi` down to `lo` of `value`, read as a number.
472fn extract(hi: u32, lo: u32, value: i128) -> i128 {
473 if lo >= 128 || hi < lo {
474 return 0;
475 }
476 low(hi - lo + 1, value >> lo)
477}
478";
479
480const SHIFTED: &str = "
481/// `value` read as a signed number that many bits wide.
482fn shifted(bits: u32, value: i128) -> i128 {
483 match 128u32.checked_sub(bits) {
484 Some(room) if room > 0 => (value << room) >> room,
485 _ => value,
486 }
487}
488";
489
490const LOW: &str = "
491/// The low `bits` bits of `value`, read as a number.
492fn low(bits: u32, value: i128) -> i128 {
493 if bits >= 128 {
494 return value;
495 }
496 #[allow(clippy::cast_possible_wrap)]
497 let masked = (value as u128 & ((1u128 << bits) - 1)) as i128;
498 masked
499}
500";
501
502#[cfg(test)]
503mod tests {
504 use super::*;
505 use crate::parse;
506
507 fn built(text: &str) -> String {
508 let rules = parse("rules/test.rules", text).expect("the rules read");
509 let matcher = Matcher::build("rules/test.rules", &rules).expect("the matcher builds");
510 emit("rules/test.rules", &rules, &matcher).expect("the table is emitted")
511 }
512
513 #[test]
515 fn a_rule_set_comes_out_as_a_table_of_nodes_and_a_table_of_rules() {
516 let out = built(
517 "(rule (lower (add.i64 (value.i64 x) (value.i64 y)))\n\
518 (x64.add_rr_64 x y)\n\
519 (spec (= (bvadd x y) (result))))\n",
520 );
521 assert!(out.contains("use super::{Node, Piece, Rule, Table};"), "{out}");
522 assert!(out.contains("pub const SOURCE: &str = \"rules/test.rules\";"), "{out}");
523 assert!(out.contains("(\"add.i64\", 2, 1),"), "{out}");
524 assert!(out.contains("wildcard: Some((\"x\", 3)),"), "{out}");
525 assert!(out.contains("accept: Some(0),"), "{out}");
526 assert!(out.contains("Piece::App { head: \"x64.add_rr_64\", arity: 2 }"), "{out}");
527 assert!(out.contains("Piece::Var { name: \"x\", index: 0 }"), "{out}");
528 assert!(out.contains("Piece::Var { name: \"y\", index: 1 }"), "{out}");
529 assert!(out.contains("guard: None,"), "{out}");
530 }
531
532 #[test]
536 fn a_name_written_twice_comes_out_as_a_test_and_takes_no_position() {
537 let out = built(
538 "(rule (simplify (and.i32 (value.i32 x) (value.i32 x)))\n\
539 (value.i32 x)\n\
540 (spec (= x (result))))\n\
541 (rule (simplify (shl.i32 (value.i32 x) (iconst.i32 k)))\n\
542 (if (>= k 0))\n\
543 (value.i32 x)\n\
544 (spec (= (bvshl x k) (result))))\n",
545 );
546 assert!(out.contains("same: &[\n (0, "), "{out}");
547 assert!(out.contains("Piece::Var { name: \"x\", index: 0 }"), "{out}");
548 assert!(
549 out.contains("let Some(Some(v1)) = bound.get(1).copied() else { return false };"),
550 "{out}"
551 );
552 }
553
554 #[test]
558 fn a_guard_comes_out_as_a_function_of_the_bindings() {
559 let out = built(
560 "(rule (lower (shl.i64 (value.i64 x) (iconst.i64 k)))\n\
561 (if (and (>= k 0) (< k 64)))\n\
562 (x64.shl_ri_64 x k)\n\
563 (spec (= (bvshl x k) (result))))\n",
564 );
565 assert!(out.contains("guard: Some(guard_0),"), "{out}");
566 assert!(out.contains("fn guard_0(bound: &[Option<i128>]) -> bool {"), "{out}");
567 assert!(
568 out.contains("let Some(Some(v1)) = bound.get(1).copied() else { return false };"),
569 "{out}"
570 );
571 assert!(out.contains("(v1 >= 0) && (v1 < 64)"), "{out}");
572 assert!(!out.contains("fn sign_extend"), "{out}");
575 assert!(!out.contains("fn low"), "{out}");
576 }
577
578 #[test]
581 fn a_guard_that_reads_bits_brings_the_helpers_it_needs() {
582 let out = built(
583 "(rule (lower (add.i64 (value.i64 x) (iconst.i64 k)))\n\
584 (if (= k (sign_extend 32 64 (extract 31 0 k))))\n\
585 (x64.add_ri_64 x k)\n\
586 (spec (= (bvadd x k) (result))))\n",
587 );
588 assert!(out.contains("v1 == sign_extend(32, 64, extract(31, 0, v1))"), "{out}");
589 assert!(out.contains("fn sign_extend(from: u32, to: u32, value: i128) -> i128 {"), "{out}");
590 assert!(out.contains("fn shifted(bits: u32, value: i128) -> i128 {"), "{out}");
591 assert!(out.contains("fn extract(hi: u32, lo: u32, value: i128) -> i128 {"), "{out}");
592 assert!(out.contains("fn low(bits: u32, value: i128) -> i128 {"), "{out}");
593 assert!(!out.contains("fn zero_extend"), "{out}");
594 }
595
596 #[test]
600 fn a_guard_nothing_can_be_made_of_is_refused_where_it_is_written() {
601 let rules = parse(
602 "rules/test.rules",
603 "(rule (lower (add.i64 (value.i64 x) (iconst.i64 k)))\n\
604 (if (fits_in_a_byte k))\n\
605 (x64.add_ri_64 x k)\n\
606 (spec (= (bvadd x k) (result))))\n",
607 )
608 .expect("the rules read");
609 let matcher = Matcher::build("rules/test.rules", &rules).expect("the matcher builds");
610 let errors = emit("rules/test.rules", &rules, &matcher).expect_err("the guard is refused");
611 assert_eq!(errors.len(), 1);
612 assert_eq!(errors[0].line, 2);
613 assert!(
614 errors[0].message.contains("`fits_in_a_byte` of 1 is not a condition"),
615 "{}",
616 errors[0]
617 );
618 }
619}