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