weaveffi_core/codegen/
writer.rs1#[derive(Debug, Clone)]
26pub struct CodeWriter {
27 buf: String,
28 indent_unit: String,
29 level: usize,
30}
31
32impl CodeWriter {
33 pub fn new(indent_unit: impl Into<String>) -> Self {
36 Self {
37 buf: String::new(),
38 indent_unit: indent_unit.into(),
39 level: 0,
40 }
41 }
42
43 pub fn with_capacity(indent_unit: impl Into<String>, capacity: usize) -> Self {
45 Self {
46 buf: String::with_capacity(capacity),
47 indent_unit: indent_unit.into(),
48 level: 0,
49 }
50 }
51
52 pub fn push_raw(&mut self, text: impl AsRef<str>) {
55 self.buf.push_str(text.as_ref());
56 }
57
58 pub fn level(&self) -> usize {
60 self.level
61 }
62
63 pub fn indent(&mut self) {
65 self.level += 1;
66 }
67
68 pub fn dedent(&mut self) {
71 self.level = self.level.saturating_sub(1);
72 }
73
74 pub fn scope(&mut self, f: impl FnOnce(&mut Self)) {
77 let saved = self.level;
78 self.level = saved + 1;
79 f(self);
80 self.level = saved;
81 }
82
83 pub fn line(&mut self, line: impl AsRef<str>) {
90 let line = line.as_ref();
91 if line.is_empty() {
92 self.buf.push('\n');
93 return;
94 }
95 for segment in line.split('\n') {
96 if !segment.is_empty() {
97 self.write_indent();
98 self.buf.push_str(segment);
99 }
100 self.buf.push('\n');
101 }
102 }
103
104 pub fn top(&mut self, line: impl AsRef<str>) {
107 debug_assert_eq!(self.level, 0, "top() called inside an indented scope");
108 self.line(line);
109 }
110
111 pub fn blank(&mut self) {
113 self.buf.push('\n');
114 }
115
116 pub fn raw(&mut self, text: impl AsRef<str>) {
120 self.buf.push_str(text.as_ref());
121 }
122
123 pub fn as_str(&self) -> &str {
125 &self.buf
126 }
127
128 pub fn is_empty(&self) -> bool {
130 self.buf.is_empty()
131 }
132
133 pub fn finish(self) -> String {
135 self.buf
136 }
137
138 fn write_indent(&mut self) {
139 for _ in 0..self.level {
140 self.buf.push_str(&self.indent_unit);
141 }
142 }
143}
144
145#[cfg(test)]
146mod tests {
147 use super::*;
148
149 #[test]
150 fn empty_writer_is_empty() {
151 let w = CodeWriter::new(" ");
152 assert!(w.is_empty());
153 assert_eq!(w.finish(), "");
154 }
155
156 #[test]
157 fn line_appends_newline() {
158 let mut w = CodeWriter::new(" ");
159 w.line("hello");
160 assert_eq!(w.finish(), "hello\n");
161 }
162
163 #[test]
164 fn scope_indents_with_unit() {
165 let mut w = CodeWriter::new(" ");
166 w.line("a");
167 w.scope(|w| w.line("b"));
168 w.line("c");
169 assert_eq!(w.finish(), "a\n b\nc\n");
170 }
171
172 #[test]
173 fn nested_scopes_stack() {
174 let mut w = CodeWriter::new(" ");
175 w.line("a");
176 w.scope(|w| {
177 w.line("b");
178 w.scope(|w| w.line("c"));
179 });
180 assert_eq!(w.finish(), "a\n b\n c\n");
181 }
182
183 #[test]
184 fn tab_indent_unit() {
185 let mut w = CodeWriter::new("\t");
186 w.scope(|w| w.line("x"));
187 assert_eq!(w.finish(), "\tx\n");
188 }
189
190 #[test]
191 fn blank_has_no_indent() {
192 let mut w = CodeWriter::new(" ");
193 w.scope(|w| {
194 w.line("x");
195 w.blank();
196 w.line("y");
197 });
198 assert_eq!(w.finish(), " x\n\n y\n");
199 }
200
201 #[test]
202 fn multiline_line_reindents_each_segment() {
203 let mut w = CodeWriter::new(" ");
204 w.scope(|w| w.line("a\nb\nc"));
205 assert_eq!(w.finish(), " a\n b\n c\n");
206 }
207
208 #[test]
209 fn multiline_keeps_interior_blanks_unindented() {
210 let mut w = CodeWriter::new(" ");
211 w.scope(|w| w.line("a\n\nb"));
212 assert_eq!(w.finish(), " a\n\n b\n");
213 }
214
215 #[test]
216 fn manual_indent_dedent_saturates() {
217 let mut w = CodeWriter::new(" ");
218 w.dedent(); w.line("a");
220 w.indent();
221 w.line("b");
222 assert_eq!(w.finish(), "a\n b\n");
223 }
224
225 #[test]
226 fn push_raw_and_raw_bypass_indentation() {
227 let mut w = CodeWriter::new(" ");
228 w.push_raw("// prelude\n");
229 w.scope(|w| w.raw("verbatim"));
230 assert_eq!(w.finish(), "// prelude\nverbatim");
231 }
232}