tan_formatting/pretty.rs
1use std::collections::{BTreeMap, HashMap};
2
3use tan::expr::Expr;
4
5use crate::{
6 layout::{Arranger, Layout},
7 types::Dialect,
8 util::{ensure_ends_with_empty_line, trim_separators},
9};
10
11// #insight The formatter cannot err.
12
13// #todo align inline/side comments
14// #todo align vertical pairs (e.g. let)
15
16// #todo preprocess to handle inline comments?
17
18// #todo add pragmas to define sections with different formatting options or even disabled formatting.
19// #todo try to use annotations to define the above-mentioned sections.
20// #todo rename to `formatter.rs`
21// #todo optimize formatter to minimize diffs.
22// #todo try to maintain some empty separator lines.
23// #todo consider using tabs to indent?
24// #todo consider allowing absolutely no parameters for the formatter.
25// #todo idea: pre-process the input, add artificial separator-line annotations to maintain some of the user's separators?
26
27/// The default indentation size (char count)
28const DEFAULT_INDENT_SIZE: usize = 4;
29
30/// The default (target) line size (char count)
31const DEFAULT_LINE_SIZE: usize = 80;
32
33// const DEFAULT_DIALECT: &str = "code";
34
35pub struct Formatter<'a> {
36 arranger: Arranger<'a>,
37 indent_size: usize,
38 #[allow(dead_code)]
39 line_size: usize,
40 // #todo consider different names, e.g. `flavor`?
41 // #todo use a builder pattern.
42 pub dialect: Dialect,
43 indent: usize,
44 #[allow(dead_code)]
45 col: usize,
46}
47
48// #todo introduce default constructor.
49// #todo introduce 'builder' api?
50
51impl<'a> Formatter<'a> {
52 pub fn new(exprs: &'a [Expr]) -> Self {
53 Self::for_dialect(exprs, Dialect::default())
54 }
55
56 // #todo find a better name.
57 pub fn for_dialect(exprs: &'a [Expr], dialect: Dialect) -> Self {
58 // #todo lazy-initialize the Arranger.
59 Self {
60 arranger: Arranger::new(exprs, dialect),
61 indent: 0,
62 indent_size: DEFAULT_INDENT_SIZE,
63 line_size: DEFAULT_LINE_SIZE,
64 dialect,
65 col: 0,
66 }
67 }
68
69 fn apply_indent(&self, s: String, indent: usize) -> String {
70 format!("{ }{s}", " ".repeat(indent))
71 }
72
73 fn format_annotations(&self, ann: &HashMap<String, Expr>) -> String {
74 if ann.is_empty() {
75 return "".to_string();
76 }
77
78 // #todo temp solution (sorts annotations by key), ideally we want insertion order? or not?
79
80 // Sort the annotations map, for stable formatting.
81 let ann = BTreeMap::from_iter(ann);
82
83 let mut output = String::new();
84
85 for (key, value) in ann {
86 if key == "range" {
87 continue;
88 } else if let Expr::Bool(true) = value {
89 // Abbreviation for true booleans.
90 output.push_str(&format!("#{key} "));
91 } else {
92 // This case handles both (type X) and (key value) annotations.
93 // The value is the whole expression.
94 output.push_str(&format!("#{value} "));
95 }
96 }
97
98 output
99 }
100
101 // #todo automatically put `_` separators to numbers.
102
103 fn format_layout(&mut self, layout: &Layout) -> String {
104 match layout {
105 Layout::Item(s) => s.clone(),
106 Layout::Row(v, separator) => v
107 .iter()
108 .map(|l| self.format_layout(l))
109 .collect::<Vec<String>>()
110 .join(separator),
111 Layout::Stack(v) => v
112 .iter()
113 .map(|l| self.format_layout(l))
114 .collect::<Vec<String>>()
115 .join("\n"),
116 Layout::Indent(v, indent_size) => {
117 let indent_size = indent_size.unwrap_or(self.indent_size);
118 self.indent += indent_size;
119 let string = v
120 .iter()
121 .map(|l| {
122 let string = self.format_layout(l);
123 self.apply_indent(string, self.indent)
124 })
125 .collect::<Vec<String>>()
126 .join("\n");
127 self.indent -= indent_size;
128 string
129 }
130 Layout::Apply(l) => {
131 let string = self.format_layout(l);
132 self.apply_indent(string, self.indent)
133 }
134 Layout::Ann(ann, l) => {
135 let ann = self.format_annotations(ann);
136 let string = self.format_layout(l);
137 format!("{ann}{string}")
138 }
139 Layout::Separator => "".to_owned(),
140 }
141 }
142
143 /// Formats expressions into an aestheticall pleasing form.
144 /// This is the standard textual representation of expressions.
145 pub fn format(mut self) -> String {
146 let layout = self.arranger.arrange();
147 // eprintln!("{:?}", &layout);
148 // dbg!(&layout);
149 let output = self.format_layout(&layout);
150 let output = trim_separators(&output);
151 ensure_ends_with_empty_line(&output)
152 }
153}