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}