synkit_core/traits/
printer.rs

1use super::to_tokens::ToTokens;
2
3/// Trait for building formatted text output.
4///
5/// `Printer` provides a structured way to generate formatted text with
6/// support for indentation, whitespace control, and token formatting.
7/// It's used by [`ToTokens`] implementations to produce output.
8///
9/// # Associated Types
10///
11/// - `Token`: The token type for grammar-specific formatting
12///
13/// # Required Methods
14///
15/// - `buf()`: Get current buffer contents
16/// - `buf_mut()`: Get mutable buffer for appending
17/// - `indent_level()`: Current indentation depth
18/// - `set_indent(level)`: Set indentation depth
19/// - `into_string()`: Consume and return final output
20/// - `token(t)`: Format a token (grammar-specific)
21///
22/// # Provided Methods
23///
24/// Basic output:
25/// - `word(s)`, `char(c)`: Append text
26/// - `space()`, `spaces(n)`, `tab()`, `tabs(n)`: Whitespace
27/// - `newline()`: Newline with auto-indent
28///
29/// Indentation:
30/// - `indent()`, `dedent()`: Change indent level
31/// - `open_block(token)`, `close_block(token)`: Block delimiters
32///
33/// Structured output:
34/// - `write(value)`: Write a `ToTokens` value
35/// - `write_separated(items, sep, ...)`: Write items with separators
36///
37/// # Example
38///
39/// ```ignore
40/// use synkit::Printer;
41///
42/// #[derive(Default)]
43/// struct MyPrinter {
44///     buf: String,
45///     indent: usize,
46/// }
47///
48/// impl Printer for MyPrinter {
49///     type Token = MyTok;
50///
51///     fn buf(&self) -> &str { &self.buf }
52///     fn buf_mut(&mut self) -> &mut String { &mut self.buf }
53///     fn indent_level(&self) -> usize { self.indent }
54///     fn set_indent(&mut self, level: usize) { self.indent = level; }
55///     fn into_string(self) -> String { self.buf }
56///
57///     fn token(&mut self, t: &Self::Token) {
58///         match t {
59///             MyTok::Plus => self.word("+"),
60///             MyTok::Minus => self.word("-"),
61///             // ...
62///         }
63///     }
64/// }
65/// ```
66pub trait Printer: Sized {
67    /// The token type for grammar-specific formatting.
68    type Token;
69
70    /// Get the current buffer contents.
71    fn buf(&self) -> &str;
72    /// Get a mutable reference to the buffer for appending.
73    fn buf_mut(&mut self) -> &mut String;
74    /// Get the current indentation level.
75    fn indent_level(&self) -> usize;
76    /// Set the indentation level.
77    fn set_indent(&mut self, level: usize);
78    /// Consume the printer and return the final string.
79    fn into_string(self) -> String;
80
81    /// Format a token to text.
82    ///
83    /// This is grammar-specific and should convert tokens to their
84    /// textual representation (e.g., `Plus` → `"+"`).
85    fn token(&mut self, t: &Self::Token);
86
87    /// Append a string to the buffer.
88    fn word(&mut self, s: &str) {
89        self.buf_mut().push_str(s);
90    }
91
92    /// Append a single character to the buffer.
93    fn char(&mut self, c: char) {
94        self.buf_mut().push(c);
95    }
96
97    /// Append a single space.
98    fn space(&mut self) {
99        self.char(' ');
100    }
101
102    /// Append multiple spaces.
103    fn spaces(&mut self, n: usize) {
104        self.buf_mut().extend(std::iter::repeat_n(' ', n));
105    }
106
107    /// Append a single tab.
108    fn tab(&mut self) {
109        self.char('\t');
110    }
111
112    /// Append multiple tabs.
113    fn tabs(&mut self, n: usize) {
114        self.buf_mut().extend(std::iter::repeat_n('\t', n));
115    }
116
117    /// Append a newline and auto-indent.
118    fn newline(&mut self) {
119        self.char('\n');
120        self.add_indent();
121    }
122
123    /// Add indentation at the current level.
124    fn add_indent(&mut self) {
125        if self.use_tabs() {
126            self.tabs(self.indent_level());
127        } else {
128            self.spaces(self.spaces_width());
129        }
130    }
131
132    /// Get the number of spaces per indent level.
133    ///
134    /// Default: 4 spaces
135    fn indent_width(&self) -> usize {
136        4
137    }
138
139    /// Calculate total spaces for current indent level.
140    fn spaces_width(&self) -> usize {
141        self.indent_level() * self.indent_width()
142    }
143
144    /// Whether to use tabs for indentation.
145    ///
146    /// Default: `true` (tabs)
147    fn use_tabs(&self) -> bool {
148        true
149    }
150
151    /// Increase indentation level by 1.
152    fn indent(&mut self) {
153        self.set_indent(self.indent_level() + 1);
154    }
155
156    /// Decrease indentation level by 1.
157    ///
158    /// Saturates at 0 (won't go negative).
159    fn dedent(&mut self) {
160        let level = self.indent_level();
161        if level > 0 {
162            self.set_indent(level - 1);
163        }
164    }
165
166    /// Open a block: write token, indent, newline.
167    fn open_block(&mut self, open: &Self::Token) {
168        self.token(open);
169        self.indent();
170        self.newline();
171    }
172
173    /// Close a block: dedent, newline, write token.
174    fn close_block(&mut self, close: &Self::Token) {
175        self.dedent();
176        self.newline();
177        self.token(close);
178    }
179
180    /// Write a value implementing `ToTokens`.
181    fn write<T: ToTokens<Printer = Self>>(&mut self, value: &T) {
182        value.write(self);
183    }
184
185    /// Write items separated by a delimiter token.
186    ///
187    /// # Arguments
188    ///
189    /// * `items` - Iterator of items to write
190    /// * `sep` - Separator token between items
191    /// * `trailing` - Whether to add separator after last item
192    /// * `newline_after_sep` - Whether to add newline after each separator
193    fn write_separated<T, I>(
194        &mut self,
195        items: I,
196        sep: &Self::Token,
197        trailing: bool,
198        newline_after_sep: bool,
199    ) where
200        T: ToTokens<Printer = Self>,
201        I: IntoIterator<Item = T>,
202        I::IntoIter: ExactSizeIterator,
203    {
204        let iter = items.into_iter();
205        let len = iter.len();
206        for (idx, item) in iter.enumerate() {
207            self.write(&item);
208            let is_last = idx == len - 1;
209            if !is_last || trailing {
210                self.token(sep);
211                if newline_after_sep && !is_last {
212                    self.newline();
213                }
214            }
215        }
216    }
217
218    /// Write items with inline spacing (space after separator, no newlines).
219    fn write_separated_inline<T, I>(&mut self, items: I, sep: &Self::Token)
220    where
221        T: ToTokens<Printer = Self>,
222        I: IntoIterator<Item = T>,
223        I::IntoIter: ExactSizeIterator,
224    {
225        let iter = items.into_iter();
226        let len = iter.len();
227        for (idx, item) in iter.enumerate() {
228            self.write(&item);
229            if idx < len - 1 {
230                self.token(sep);
231                self.space();
232            }
233        }
234    }
235}