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}