1#![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
17macro_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 pub fn new() -> Self {
46 Self {
47 indent: 0,
48 lines: Vec::new(),
49 }
50 }
51
52 pub fn indent_push(&mut self) {
54 self.indent += 1;
55 }
56
57 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 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 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 pub fn empty_line(&mut self) {
87 self.lines.push("\n".to_string());
88 }
89
90 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 pub fn multi_line(&mut self, s: &str) {
109 parse_multiline(s).into_iter().for_each(|l| self.line(&l));
110 }
111
112 pub fn comment(&mut self, s: impl AsRef<str>) {
114 fmtln!(self, "// {}", s.as_ref());
115 }
116
117 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 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 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 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
170fn _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
180fn parse_multiline(s: &str) -> Vec<String> {
184 let expanded_tab = format!("{:-1$}", " ", SHIFTWIDTH);
186 let lines: Vec<String> = s.lines().map(|l| l.replace('\t', &expanded_tab)).collect();
187
188 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 let mut lines_iter = lines.iter().skip_while(|l| l.is_empty());
198 let mut trimmed = Vec::with_capacity(lines.len());
199
200 if let Some(s) = lines_iter.next().map(|l| l.trim()).map(|l| l.to_string()) {
202 trimmed.push(s);
203 }
204
205 let mut other_lines = if let Some(indent) = indent {
207 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 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
235pub(crate) struct Match {
244 expr: String,
245 arms: BTreeMap<(Vec<String>, String), BTreeSet<String>>,
246 catch_all: Option<String>,
248}
249
250impl Match {
251 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 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 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 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); 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}