Skip to main content

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}