cranelift_codegen_meta/
srcgen.rs

1//! Source code generator.
2//!
3//! The `srcgen` module contains generic helper routines and classes for
4//! generating source code.
5
6#![macro_use]
7
8use std::cmp;
9use std::collections::{BTreeMap, BTreeSet};
10use std::fs;
11use std::io::Write;
12
13use crate::error;
14
15static SHIFTWIDTH: usize = 4;
16
17/// A macro that simplifies the usage of the Formatter by allowing format
18/// strings.
19macro_rules! fmtln {
20    ($fmt:ident, $fmtstring:expr, $($fmtargs:expr),*) => {
21        $fmt.line(format!($fmtstring, $($fmtargs),*))
22    };
23
24    ($fmt:ident, $arg:expr) => {
25        $fmt.line($arg)
26    };
27
28    ($_:tt, $($args:expr),+) => {
29        compile_error!("This macro requires at least two arguments: the Formatter instance and a format string.")
30    };
31
32    ($_:tt) => {
33        compile_error!("This macro requires at least two arguments: the Formatter instance and a format string.")
34    };
35}
36
37pub(crate) struct Formatter {
38    indent: usize,
39    lines: Vec<String>,
40}
41
42impl Formatter {
43    /// Source code formatter class. Used to collect source code to be written
44    /// to a file, and keep track of indentation.
45    pub fn new() -> Self {
46        Self {
47            indent: 0,
48            lines: Vec::new(),
49        }
50    }
51
52    /// Increase current indentation level by one.
53    pub fn indent_push(&mut self) {
54        self.indent += 1;
55    }
56
57    /// Decrease indentation by one level.
58    pub fn indent_pop(&mut self) {
59        assert!(self.indent > 0, "Already at top level indentation");
60        self.indent -= 1;
61    }
62
63    pub fn indent<T, F: FnOnce(&mut Formatter) -> T>(&mut self, f: F) -> T {
64        self.indent_push();
65        let ret = f(self);
66        self.indent_pop();
67        ret
68    }
69
70    /// Get the current whitespace indentation in the form of a String.
71    fn get_indent(&self) -> String {
72        if self.indent == 0 {
73            String::new()
74        } else {
75            format!("{:-1$}", " ", self.indent * SHIFTWIDTH)
76        }
77    }
78
79    /// Add an indented line.
80    pub fn line(&mut self, contents: impl AsRef<str>) {
81        let indented_line = format!("{}{}\n", self.get_indent(), contents.as_ref());
82        self.lines.push(indented_line);
83    }
84
85    /// Pushes an empty line.
86    pub fn empty_line(&mut self) {
87        self.lines.push("\n".to_string());
88    }
89
90    /// Write `self.lines` to a file.
91    pub fn update_file(
92        &self,
93        filename: impl AsRef<std::path::Path>,
94        directory: &std::path::Path,
95    ) -> Result<(), error::Error> {
96        let path = directory.join(&filename);
97        eprintln!("Writing generated file: {}", path.display());
98        let mut f = fs::File::create(path)?;
99
100        for l in self.lines.iter().map(|l| l.as_bytes()) {
101            f.write_all(l)?;
102        }
103
104        Ok(())
105    }
106
107    /// Add one or more lines after stripping common indentation.
108    pub fn multi_line(&mut self, s: &str) {
109        parse_multiline(s).into_iter().for_each(|l| self.line(&l));
110    }
111
112    /// Add a comment line.
113    pub fn comment(&mut self, s: impl AsRef<str>) {
114        fmtln!(self, "// {}", s.as_ref());
115    }
116
117    /// Add a (multi-line) documentation comment.
118    pub fn doc_comment(&mut self, contents: impl AsRef<str>) {
119        parse_multiline(contents.as_ref())
120            .iter()
121            .map(|l| {
122                if l.is_empty() {
123                    "///".into()
124                } else {
125                    format!("/// {l}")
126                }
127            })
128            .for_each(|s| self.line(s.as_str()));
129    }
130
131    /// Add a match expression.
132    pub fn add_match(&mut self, m: Match) {
133        fmtln!(self, "match {} {{", m.expr);
134        self.indent(|fmt| {
135            for (&(ref fields, ref body), ref names) in m.arms.iter() {
136                // name { fields } | name { fields } => { body }
137                let conditions = names
138                    .iter()
139                    .map(|name| {
140                        if !fields.is_empty() {
141                            format!("{} {{ {} }}", name, fields.join(", "))
142                        } else {
143                            name.clone()
144                        }
145                    })
146                    .collect::<Vec<_>>()
147                    .join(" |\n")
148                    + " => {";
149
150                fmt.multi_line(&conditions);
151                fmt.indent(|fmt| {
152                    fmt.line(body);
153                });
154                fmt.line("}");
155            }
156
157            // Make sure to include the catch all clause last.
158            if let Some(body) = m.catch_all {
159                fmt.line("_ => {");
160                fmt.indent(|fmt| {
161                    fmt.line(body);
162                });
163                fmt.line("}");
164            }
165        });
166        self.line("}");
167    }
168}
169
170/// Compute the indentation of s, or None of an empty line.
171fn _indent(s: &str) -> Option<usize> {
172    if s.is_empty() {
173        None
174    } else {
175        let t = s.trim_start();
176        Some(s.len() - t.len())
177    }
178}
179
180/// Given a multi-line string, split it into a sequence of lines after
181/// stripping a common indentation. This is useful for strings defined with
182/// doc strings.
183fn parse_multiline(s: &str) -> Vec<String> {
184    // Convert tabs into spaces.
185    let expanded_tab = format!("{:-1$}", " ", SHIFTWIDTH);
186    let lines: Vec<String> = s.lines().map(|l| l.replace('\t', &expanded_tab)).collect();
187
188    // Determine minimum indentation, ignoring the first line and empty lines.
189    let indent = lines
190        .iter()
191        .skip(1)
192        .filter(|l| !l.trim().is_empty())
193        .map(|l| l.len() - l.trim_start().len())
194        .min();
195
196    // Strip off leading blank lines.
197    let mut lines_iter = lines.iter().skip_while(|l| l.is_empty());
198    let mut trimmed = Vec::with_capacity(lines.len());
199
200    // Remove indentation (first line is special)
201    if let Some(s) = lines_iter.next().map(|l| l.trim()).map(|l| l.to_string()) {
202        trimmed.push(s);
203    }
204
205    // Remove trailing whitespace from other lines.
206    let mut other_lines = if let Some(indent) = indent {
207        // Note that empty lines may have fewer than `indent` chars.
208        lines_iter
209            .map(|l| &l[cmp::min(indent, l.len())..])
210            .map(|l| l.trim_end())
211            .map(|l| l.to_string())
212            .collect::<Vec<_>>()
213    } else {
214        lines_iter
215            .map(|l| l.trim_end())
216            .map(|l| l.to_string())
217            .collect::<Vec<_>>()
218    };
219
220    trimmed.append(&mut other_lines);
221
222    // Strip off trailing blank lines.
223    while let Some(s) = trimmed.pop() {
224        if s.is_empty() {
225            continue;
226        } else {
227            trimmed.push(s);
228            break;
229        }
230    }
231
232    trimmed
233}
234
235/// Match formatting class.
236///
237/// Match objects collect all the information needed to emit a Rust `match`
238/// expression, automatically deduplicating overlapping identical arms.
239///
240/// Note that this class is ignorant of Rust types, and considers two fields
241/// with the same name to be equivalent. BTreeMap/BTreeSet are used to
242/// represent the arms in order to make the order deterministic.
243pub(crate) struct Match {
244    expr: String,
245    arms: BTreeMap<(Vec<String>, String), BTreeSet<String>>,
246    /// The clause for the placeholder pattern _.
247    catch_all: Option<String>,
248}
249
250impl Match {
251    /// Create a new match statement on `expr`.
252    pub fn new(expr: impl Into<String>) -> Self {
253        Self {
254            expr: expr.into(),
255            arms: BTreeMap::new(),
256            catch_all: None,
257        }
258    }
259
260    fn set_catch_all(&mut self, clause: String) {
261        assert!(self.catch_all.is_none());
262        self.catch_all = Some(clause);
263    }
264
265    /// Add an arm that reads fields to the Match statement.
266    pub fn arm<T: Into<String>, S: Into<String>>(&mut self, name: T, fields: Vec<S>, body: T) {
267        let name = name.into();
268        assert!(
269            name != "_",
270            "catch all clause can't extract fields, use arm_no_fields instead."
271        );
272
273        let body = body.into();
274        let fields = fields.into_iter().map(|x| x.into()).collect();
275        let match_arm = self
276            .arms
277            .entry((fields, body))
278            .or_insert_with(BTreeSet::new);
279        match_arm.insert(name);
280    }
281
282    /// Adds an arm that doesn't read anythings from the fields to the Match statement.
283    pub fn arm_no_fields(&mut self, name: impl Into<String>, body: impl Into<String>) {
284        let body = body.into();
285
286        let name = name.into();
287        if name == "_" {
288            self.set_catch_all(body);
289            return;
290        }
291
292        let match_arm = self
293            .arms
294            .entry((Vec::new(), body))
295            .or_insert_with(BTreeSet::new);
296        match_arm.insert(name);
297    }
298}
299
300#[cfg(test)]
301mod srcgen_tests {
302    use super::parse_multiline;
303    use super::Formatter;
304    use super::Match;
305
306    fn from_raw_string<S: Into<String>>(s: S) -> Vec<String> {
307        s.into()
308            .trim()
309            .split("\n")
310            .into_iter()
311            .map(|x| format!("{x}\n"))
312            .collect()
313    }
314
315    #[test]
316    fn adding_arms_works() {
317        let mut m = Match::new("x");
318        m.arm("Orange", vec!["a", "b"], "some body");
319        m.arm("Yellow", vec!["a", "b"], "some body");
320        m.arm("Green", vec!["a", "b"], "different body");
321        m.arm("Blue", vec!["x", "y"], "some body");
322        assert_eq!(m.arms.len(), 3);
323
324        let mut fmt = Formatter::new();
325        fmt.add_match(m);
326
327        let expected_lines = from_raw_string(
328            r#"
329match x {
330    Green { a, b } => {
331        different body
332    }
333    Orange { a, b } |
334    Yellow { a, b } => {
335        some body
336    }
337    Blue { x, y } => {
338        some body
339    }
340}
341        "#,
342        );
343        assert_eq!(fmt.lines, expected_lines);
344    }
345
346    #[test]
347    fn match_with_catchall_order() {
348        // The catchall placeholder must be placed after other clauses.
349        let mut m = Match::new("x");
350        m.arm("Orange", vec!["a", "b"], "some body");
351        m.arm("Green", vec!["a", "b"], "different body");
352        m.arm_no_fields("_", "unreachable!()");
353        assert_eq!(m.arms.len(), 2); // catchall is not counted
354
355        let mut fmt = Formatter::new();
356        fmt.add_match(m);
357
358        let expected_lines = from_raw_string(
359            r#"
360match x {
361    Green { a, b } => {
362        different body
363    }
364    Orange { a, b } => {
365        some body
366    }
367    _ => {
368        unreachable!()
369    }
370}
371        "#,
372        );
373        assert_eq!(fmt.lines, expected_lines);
374    }
375
376    #[test]
377    fn parse_multiline_works() {
378        let input = "\n    hello\n    world\n";
379        let expected = vec!["hello", "world"];
380        let output = parse_multiline(input);
381        assert_eq!(output, expected);
382    }
383
384    #[test]
385    fn formatter_basic_example_works() {
386        let mut fmt = Formatter::new();
387        fmt.line("Hello line 1");
388        fmt.indent_push();
389        fmt.comment("Nested comment");
390        fmt.indent_pop();
391        fmt.line("Back home again");
392        let expected_lines = vec![
393            "Hello line 1\n",
394            "    // Nested comment\n",
395            "Back home again\n",
396        ];
397        assert_eq!(fmt.lines, expected_lines);
398    }
399
400    #[test]
401    fn get_indent_works() {
402        let mut fmt = Formatter::new();
403        let expected_results = vec!["", "    ", "        ", ""];
404
405        let actual_results = Vec::with_capacity(4);
406        (0..3).for_each(|_| {
407            fmt.get_indent();
408            fmt.indent_push();
409        });
410        (0..3).for_each(|_| fmt.indent_pop());
411        fmt.get_indent();
412
413        actual_results
414            .into_iter()
415            .zip(expected_results.into_iter())
416            .for_each(|(actual, expected): (String, &str)| assert_eq!(&actual, expected));
417    }
418
419    #[test]
420    fn fmt_can_add_type_to_lines() {
421        let mut fmt = Formatter::new();
422        fmt.line(format!("pub const {}: Type = Type({:#x});", "example", 0,));
423        let expected_lines = vec!["pub const example: Type = Type(0x0);\n"];
424        assert_eq!(fmt.lines, expected_lines);
425    }
426
427    #[test]
428    fn fmt_can_add_indented_line() {
429        let mut fmt = Formatter::new();
430        fmt.line("hello");
431        fmt.indent_push();
432        fmt.line("world");
433        let expected_lines = vec!["hello\n", "    world\n"];
434        assert_eq!(fmt.lines, expected_lines);
435    }
436
437    #[test]
438    fn fmt_can_add_doc_comments() {
439        let mut fmt = Formatter::new();
440        fmt.doc_comment("documentation\nis\ngood");
441        let expected_lines = vec!["/// documentation\n", "/// is\n", "/// good\n"];
442        assert_eq!(fmt.lines, expected_lines);
443    }
444
445    #[test]
446    fn fmt_can_add_doc_comments_with_empty_lines() {
447        let mut fmt = Formatter::new();
448        fmt.doc_comment(
449            r#"documentation
450        can be really good.
451
452        If you stick to writing it.
453"#,
454        );
455        let expected_lines = from_raw_string(
456            r#"
457/// documentation
458/// can be really good.
459///
460/// If you stick to writing it."#,
461        );
462        assert_eq!(fmt.lines, expected_lines);
463    }
464}