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