roam_codegen/code_writer.rs
1//! Code writer with automatic indentation tracking for code generation.
2//!
3//! This module provides a `CodeWriter` that simplifies generating properly
4//! indented code in C-like languages (Swift, TypeScript, Go, Java, etc.).
5//!
6//! # Features
7//!
8//! - **RAII-based indentation**: Use `indent()` to get a guard that automatically
9//! manages indentation levels
10//! - **No borrow checker fights**: Uses `Rc<Cell<usize>>` internally so indent
11//! guards don't conflict with mutable writes
12//! - **C-like syntax helpers**: Built-in support for blocks, comments, parentheses
13//! - **Format macros**: `cw_write!` and `cw_writeln!` for formatted output
14//!
15//! # Basic Example
16//!
17//! ```
18//! use roam_codegen::code_writer::CodeWriter;
19//! use roam_codegen::{cw_write, cw_writeln};
20//!
21//! let mut output = String::new();
22//! let mut w = CodeWriter::with_indent_spaces(&mut output, 4);
23//!
24//! w.writeln("class Example {").unwrap();
25//! {
26//! let _indent = w.indent();
27//! w.writeln("private let value: Int").unwrap();
28//! w.blank_line().unwrap();
29//!
30//! cw_writeln!(w, "func compute() -> Int {{").unwrap();
31//! {
32//! let _indent = w.indent();
33//! cw_writeln!(w, "return value * 2").unwrap();
34//! }
35//! w.writeln("}").unwrap();
36//! }
37//! w.writeln("}").unwrap();
38//! ```
39//!
40//! # Using the `block` Helper
41//!
42//! For common brace-delimited blocks, use the `block` helper:
43//!
44//! ```
45//! use roam_codegen::code_writer::CodeWriter;
46//!
47//! let mut output = String::new();
48//! let mut w = CodeWriter::with_indent_spaces(&mut output, 2);
49//!
50//! w.block("class Foo", |w| {
51//! w.writeln("let x = 42")?;
52//! w.block("func bar()", |w| {
53//! w.writeln("return x")
54//! })
55//! }).unwrap();
56//! ```
57//!
58//! # Comma-Separated Lists
59//!
60//! ```
61//! use roam_codegen::code_writer::CodeWriter;
62//!
63//! let mut output = String::new();
64//! let mut w = CodeWriter::with_indent_spaces(&mut output, 2);
65//!
66//! w.write("func(").unwrap();
67//! w.write_separated(vec!["a: Int", "b: String"], ", ", |w, item| {
68//! w.write(item)
69//! }).unwrap();
70//! w.write(")").unwrap();
71//! // Output: "func(a: Int, b: String)"
72//! ```
73
74use std::cell::Cell;
75use std::fmt;
76use std::rc::Rc;
77
78/// A code writer that tracks indentation and provides helpers for generating
79/// C-like syntax (used by Swift, TypeScript, Go, etc.)
80pub struct CodeWriter<W> {
81 writer: W,
82 indent_level: Rc<Cell<usize>>,
83 indent_string: String,
84 at_line_start: Cell<bool>,
85}
86
87impl<W: fmt::Write> CodeWriter<W> {
88 /// Create a new CodeWriter with the given writer and indent string (e.g., " " or "\t")
89 pub fn new(writer: W, indent_string: String) -> Self {
90 Self {
91 writer,
92 indent_level: Rc::new(Cell::new(0)),
93 indent_string,
94 at_line_start: Cell::new(true),
95 }
96 }
97
98 /// Create a new CodeWriter with 4-space indentation
99 pub fn with_indent_spaces(writer: W, spaces: usize) -> Self {
100 Self::new(writer, " ".repeat(spaces))
101 }
102
103 /// Write text without a newline. Adds indentation if at line start.
104 pub fn write(&mut self, text: &str) -> fmt::Result {
105 if text.is_empty() {
106 return Ok(());
107 }
108
109 if self.at_line_start.get() && !text.trim().is_empty() {
110 for _ in 0..self.indent_level.get() {
111 self.writer.write_str(&self.indent_string)?;
112 }
113 self.at_line_start.set(false);
114 }
115
116 self.writer.write_str(text)
117 }
118
119 /// Write text followed by a newline. Adds indentation if needed.
120 pub fn writeln(&mut self, text: &str) -> fmt::Result {
121 self.write(text)?;
122 self.writer.write_char('\n')?;
123 self.at_line_start.set(true);
124 Ok(())
125 }
126
127 /// Write an empty line
128 pub fn blank_line(&mut self) -> fmt::Result {
129 self.writer.write_char('\n')?;
130 self.at_line_start.set(true);
131 Ok(())
132 }
133
134 /// Create an indentation guard. Indentation increases while the guard is alive.
135 pub fn indent(&mut self) -> IndentGuard {
136 self.indent_level.set(self.indent_level.get() + 1);
137 IndentGuard {
138 indent_level: Rc::clone(&self.indent_level),
139 }
140 }
141
142 /// Write a single-line comment (e.g., "// comment")
143 pub fn comment(&mut self, comment_prefix: &str, text: &str) -> fmt::Result {
144 self.writeln(&format!("{} {}", comment_prefix, text))
145 }
146
147 /// Write a doc comment block. Each line is prefixed with the comment marker.
148 pub fn doc_comment(&mut self, comment_prefix: &str, text: &str) -> fmt::Result {
149 for line in text.lines() {
150 self.writeln(&format!("{} {}", comment_prefix, line))?;
151 }
152 Ok(())
153 }
154
155 /// Begin a block with opening brace: writes "header {" and returns indent guard
156 pub fn begin_block(&mut self, header: &str) -> Result<IndentGuard, fmt::Error> {
157 self.writeln(&format!("{} {{", header))?;
158 Ok(self.indent())
159 }
160
161 /// End a block with closing brace
162 pub fn end_block(&mut self) -> fmt::Result {
163 self.writeln("}")
164 }
165
166 /// Write a complete block with a closure for the body
167 pub fn block<F>(&mut self, header: &str, body: F) -> fmt::Result
168 where
169 F: FnOnce(&mut Self) -> fmt::Result,
170 {
171 self.writeln(&format!("{} {{", header))?;
172 {
173 let _indent = self.indent();
174 body(self)?;
175 }
176 self.writeln("}")
177 }
178
179 /// Get the current indentation level
180 pub fn indent_level(&self) -> usize {
181 self.indent_level.get()
182 }
183
184 /// Consume the writer and return the inner writer
185 pub fn into_inner(self) -> W {
186 self.writer
187 }
188
189 /// Get a reference to the inner writer
190 pub fn inner(&self) -> &W {
191 &self.writer
192 }
193
194 /// Get a mutable reference to the inner writer
195 pub fn inner_mut(&mut self) -> &mut W {
196 &mut self.writer
197 }
198
199 /// Write formatted text (like write! macro)
200 ///
201 /// Use the `write!` and `writeln!` macros instead of calling this directly.
202 #[doc(hidden)]
203 pub fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> fmt::Result {
204 let formatted = format!("{}", args);
205 self.write(&formatted)
206 }
207
208 /// Write formatted text with newline (like writeln! macro)
209 ///
210 /// Use the `write!` and `writeln!` macros instead of calling this directly.
211 #[doc(hidden)]
212 pub fn writeln_fmt(&mut self, args: fmt::Arguments<'_>) -> fmt::Result {
213 let formatted = format!("{}", args);
214 self.writeln(&formatted)
215 }
216
217 /// Write items separated by a delimiter (e.g., comma-separated list)
218 pub fn write_separated<I, F>(
219 &mut self,
220 items: I,
221 separator: &str,
222 mut write_item: F,
223 ) -> fmt::Result
224 where
225 I: IntoIterator,
226 F: FnMut(&mut Self, I::Item) -> fmt::Result,
227 {
228 let mut first = true;
229 for item in items {
230 if !first {
231 self.write(separator)?;
232 }
233 write_item(self, item)?;
234 first = false;
235 }
236 Ok(())
237 }
238
239 /// Write items separated by delimiter with newlines (one item per line)
240 pub fn write_separated_lines<I, F>(
241 &mut self,
242 items: I,
243 separator: &str,
244 mut write_item: F,
245 ) -> fmt::Result
246 where
247 I: IntoIterator,
248 F: FnMut(&mut Self, I::Item) -> fmt::Result,
249 {
250 let mut first = true;
251 for item in items {
252 if !first {
253 self.writeln(separator)?;
254 }
255 write_item(self, item)?;
256 first = false;
257 }
258 Ok(())
259 }
260
261 /// Conditionally write content
262 pub fn write_if<F>(&mut self, condition: bool, f: F) -> fmt::Result
263 where
264 F: FnOnce(&mut Self) -> fmt::Result,
265 {
266 if condition { f(self) } else { Ok(()) }
267 }
268
269 /// Write a parenthesized list (e.g., "(a, b, c)")
270 pub fn write_parens<F>(&mut self, f: F) -> fmt::Result
271 where
272 F: FnOnce(&mut Self) -> fmt::Result,
273 {
274 self.write("(")?;
275 f(self)?;
276 self.write(")")
277 }
278
279 /// Write a bracketed list (e.g., "[a, b, c]")
280 pub fn write_brackets<F>(&mut self, f: F) -> fmt::Result
281 where
282 F: FnOnce(&mut Self) -> fmt::Result,
283 {
284 self.write("[")?;
285 f(self)?;
286 self.write("]")
287 }
288
289 /// Write an angle-bracketed list (e.g., "<T, U>")
290 pub fn write_angles<F>(&mut self, f: F) -> fmt::Result
291 where
292 F: FnOnce(&mut Self) -> fmt::Result,
293 {
294 self.write("<")?;
295 f(self)?;
296 self.write(">")
297 }
298}
299
300/// RAII guard that maintains indentation level
301///
302/// Uses `Rc<Cell<usize>>` to independently manage indent level without any borrows.
303pub struct IndentGuard {
304 indent_level: Rc<Cell<usize>>,
305}
306
307impl Drop for IndentGuard {
308 fn drop(&mut self) {
309 let current = self.indent_level.get();
310 self.indent_level.set(current.saturating_sub(1));
311 }
312}
313
314/// Write formatted text to a CodeWriter (like std::write!)
315///
316/// # Example
317/// ```ignore
318/// write!(w, "let x = {}", 42)?;
319/// ```
320#[macro_export]
321macro_rules! cw_write {
322 ($writer:expr, $($arg:tt)*) => {
323 $writer.write_fmt(format_args!($($arg)*))
324 };
325}
326
327/// Write formatted text with newline to a CodeWriter (like std::writeln!)
328///
329/// # Example
330/// ```ignore
331/// writeln!(w, "let x = {}", 42)?;
332/// ```
333#[macro_export]
334macro_rules! cw_writeln {
335 ($writer:expr, $($arg:tt)*) => {
336 $writer.writeln_fmt(format_args!($($arg)*))
337 };
338}