baobao_codegen/builder/
renderable.rs

1//! Renderable trait and CodeFragment for decoupled code generation.
2//!
3//! This module provides abstractions that allow AST nodes to be composed
4//! and rendered without direct coupling to CodeBuilder.
5
6/// Represents a fragment of generated code.
7///
8/// CodeFragments form an intermediate representation between AST nodes
9/// and the final string output, enabling composition and transformation.
10#[derive(Debug, Clone, PartialEq)]
11pub enum CodeFragment {
12    /// A single line of code (will have newline appended).
13    Line(String),
14    /// A blank line.
15    Blank,
16    /// Raw text without newline.
17    Raw(String),
18    /// A block with header, body fragments, and optional closing line.
19    Block {
20        header: String,
21        body: Vec<CodeFragment>,
22        close: Option<String>,
23    },
24    /// Indent the contained fragments.
25    Indent(Vec<CodeFragment>),
26    /// A sequence of fragments.
27    Sequence(Vec<CodeFragment>),
28    /// A JSDoc comment.
29    JsDoc(String),
30    /// A Rust doc comment.
31    RustDoc(String),
32}
33
34impl CodeFragment {
35    /// Create a line fragment.
36    pub fn line(s: impl Into<String>) -> Self {
37        Self::Line(s.into())
38    }
39
40    /// Create a blank line fragment.
41    pub fn blank() -> Self {
42        Self::Blank
43    }
44
45    /// Create a raw text fragment.
46    pub fn raw(s: impl Into<String>) -> Self {
47        Self::Raw(s.into())
48    }
49
50    /// Create a block fragment.
51    pub fn block(
52        header: impl Into<String>,
53        body: Vec<CodeFragment>,
54        close: Option<String>,
55    ) -> Self {
56        Self::Block {
57            header: header.into(),
58            body,
59            close,
60        }
61    }
62
63    /// Create an indented fragment sequence.
64    pub fn indent(fragments: Vec<CodeFragment>) -> Self {
65        Self::Indent(fragments)
66    }
67
68    /// Create a sequence of fragments.
69    pub fn sequence(fragments: Vec<CodeFragment>) -> Self {
70        Self::Sequence(fragments)
71    }
72
73    /// Create a JSDoc comment fragment.
74    pub fn jsdoc(s: impl Into<String>) -> Self {
75        Self::JsDoc(s.into())
76    }
77
78    /// Create a Rust doc comment fragment.
79    pub fn rust_doc(s: impl Into<String>) -> Self {
80        Self::RustDoc(s.into())
81    }
82}
83
84/// Trait for types that can be rendered to code fragments.
85///
86/// Implement this trait for AST nodes to enable them to be rendered
87/// through CodeBuilder without direct coupling.
88pub trait Renderable {
89    /// Convert this node to a sequence of code fragments.
90    fn to_fragments(&self) -> Vec<CodeFragment>;
91}
92
93/// Blanket implementation for references.
94impl<T: Renderable + ?Sized> Renderable for &T {
95    fn to_fragments(&self) -> Vec<CodeFragment> {
96        (*self).to_fragments()
97    }
98}
99
100/// Blanket implementation for Box.
101impl<T: Renderable + ?Sized> Renderable for Box<T> {
102    fn to_fragments(&self) -> Vec<CodeFragment> {
103        self.as_ref().to_fragments()
104    }
105}
106
107#[cfg(test)]
108mod tests {
109    use super::*;
110
111    #[test]
112    fn test_code_fragment_constructors() {
113        assert_eq!(
114            CodeFragment::line("test"),
115            CodeFragment::Line("test".to_string())
116        );
117        assert_eq!(CodeFragment::blank(), CodeFragment::Blank);
118        assert_eq!(
119            CodeFragment::raw("raw"),
120            CodeFragment::Raw("raw".to_string())
121        );
122    }
123
124    #[test]
125    fn test_block_fragment() {
126        let block = CodeFragment::block(
127            "if (true) {",
128            vec![CodeFragment::line("return 1;")],
129            Some("}".to_string()),
130        );
131        match block {
132            CodeFragment::Block {
133                header,
134                body,
135                close,
136            } => {
137                assert_eq!(header, "if (true) {");
138                assert_eq!(body.len(), 1);
139                assert_eq!(close, Some("}".to_string()));
140            }
141            _ => panic!("Expected Block variant"),
142        }
143    }
144}