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