simple_rsx_macros/
lib.rs

1use proc_macro::TokenStream;
2use proc_macro2::{Span, TokenStream as TokenStream2};
3use quote::quote;
4use syn::{
5    Expr, ExprLit, Ident, Lit, Result, Token,
6    parse::{Parse, ParseStream},
7    parse_macro_input,
8};
9
10/// A procedural macro that provides JSX-like syntax for creating HTML elements in Rust.
11///
12/// # Examples
13///
14/// ```rust
15/// // Fragment
16/// rsx!(<>Hello World</>);
17///
18/// // Self-closing tag
19/// rsx!(<div class="container" id="app" />);
20///
21/// // Tag with children
22/// rsx!(<div class="container">
23///     <h1>Title</h1>
24///     <p>Paragraph text</p>
25/// </div>);
26///
27/// // Expression
28/// let name = "World";
29/// rsx!(<div>Hello {name}</div>);
30/// ```
31#[proc_macro]
32pub fn rsx(input: TokenStream) -> TokenStream {
33    let input = parse_macro_input!(input as JsxNode);
34    let expanded = input.to_tokens();
35    expanded.into()
36}
37
38/// Represents the different types of JSX nodes
39enum JsxNode {
40    Fragment(Vec<JsxNode>),
41    Element {
42        tag: Ident,
43        attributes: Vec<(Ident, Lit)>,
44        children: Vec<JsxNode>,
45        close_tag: Option<Ident>, // Optional closing tag for elements
46    },
47    Text(Expr),
48    Empty,
49}
50
51/// Represents an attribute name-value pair
52struct Attribute {
53    name: Ident,
54    value: Lit,
55}
56
57impl Parse for Attribute {
58    fn parse(input: ParseStream) -> Result<Self> {
59        let name = input.parse()?;
60        input.parse::<Token![=]>()?;
61        let value = input.parse()?;
62        Ok(Attribute { name, value })
63    }
64}
65
66impl Parse for JsxNode {
67    fn parse(input: ParseStream) -> Result<Self> {
68        // Empty
69        if input.is_empty() {
70            return Ok(JsxNode::Empty);
71        }
72
73        // Look ahead to see if we start with a '<'
74        if input.peek(Token![<]) {
75            input.parse::<Token![<]>()?;
76
77            // Fragment: <>...</>
78            if input.peek(Token![>]) {
79                input.parse::<Token![>]>()?;
80
81                // Parse children until we find </> closing tag
82                let mut children = Vec::new();
83                while !input.is_empty()
84                    && !(input.peek(Token![<]) && input.peek2(Token![/]) && input.peek3(Token![>]))
85                {
86                    if let Ok(child) = input.parse::<JsxNode>() {
87                        children.push(child);
88                    } else {
89                        // Skip a token if we can't parse a node
90                        let _ = input.parse::<proc_macro2::TokenTree>();
91                    }
92                }
93
94                // Consume </> closing tag
95                input.parse::<Token![<]>()?;
96                input.parse::<Token![/]>()?;
97                input.parse::<Token![>]>()?;
98
99                return Ok(JsxNode::Fragment(children));
100            }
101
102            // Element: <tag ...>...</tag> or <tag ... />
103            let tag = input.parse::<Ident>()?;
104
105            // Parse attributes
106            let mut attributes = Vec::new();
107            while !input.peek(Token![>]) && !input.peek(Token![/]) {
108                let attr: Attribute = input.parse()?;
109                attributes.push((attr.name, attr.value));
110            }
111
112            // Self-closing tag: <tag ... />
113            if input.peek(Token![/]) {
114                input.parse::<Token![/]>()?;
115                input.parse::<Token![>]>()?;
116
117                return Ok(JsxNode::Element {
118                    tag,
119                    attributes,
120                    children: Vec::new(),
121                    close_tag: None,
122                });
123            }
124
125            // Opening tag ends: <tag ...>
126            input.parse::<Token![>]>()?;
127
128            // Parse children
129            let mut children = Vec::new();
130            while !input.is_empty() && !(input.peek(Token![<]) && input.peek2(Token![/])) {
131                let child = input.parse::<JsxNode>()?;
132                children.push(child);
133            }
134
135            // Closing tag: </tag>
136            input.parse::<Token![<]>()?;
137            input.parse::<Token![/]>()?;
138            let close_tag = input.parse::<Ident>()?;
139
140            // Validate matching tags
141            if tag != close_tag {
142                return Err(syn::Error::new(
143                    close_tag.span(),
144                    format!(
145                        "Closing tag </{}> doesn't match opening tag <{}>",
146                        close_tag, tag
147                    ),
148                ));
149            }
150
151            input.parse::<Token![>]>()?;
152
153            return Ok(JsxNode::Element {
154                tag,
155                attributes,
156                children,
157                close_tag: Some(close_tag),
158            });
159        }
160
161        // Text content or expression
162        if input.peek(Lit) {
163            let lit: Lit = input.parse()?;
164            let expr = Expr::Lit(ExprLit {
165                attrs: Vec::new(),
166                lit,
167            });
168            return Ok(JsxNode::Text(expr));
169        }
170
171        // Handle expressions in braces: {expr}
172        // if input.peek(Token!['{']) {
173        //     let content;
174        //     syn::braced!(content in input);
175        //     let expr: Expr = content.parse()?;
176        //     return Ok(JsxNode::Text(expr));
177        // }
178
179        // Try to parse as an expression
180        match input.parse::<Expr>() {
181            Ok(expr) => Ok(JsxNode::Text(expr)),
182            Err(_) => {
183                // If we reach here, likely we have multiple sibling nodes
184                // Since we don't have a way to directly detect this, we'll treat
185                // unrecognized patterns as an error
186                Err(syn::Error::new(
187                    Span::call_site(),
188                    "Expected a JSX element, fragment, text, or expression",
189                ))
190            }
191        }
192    }
193}
194
195impl JsxNode {
196    fn to_tokens(&self) -> TokenStream2 {
197        match self {
198            JsxNode::Fragment(children) => {
199                let children_tokens = children.iter().map(|child| child.to_tokens());
200
201                quote! {
202                    {
203                        let mut nodes = Vec::new();
204                        #(
205                            let result = #children_tokens;
206                            match result {
207                                simple_rsx::NodeList::Fragment(mut child_nodes) => nodes.append(&mut child_nodes),
208                                simple_rsx::NodeList::Single(node) => nodes.push(node),
209                            }
210                        )*
211                        simple_rsx::NodeList::Fragment(nodes)
212                    }
213                }
214            }
215            JsxNode::Element {
216                tag,
217                attributes,
218                children,
219                close_tag,
220            } => {
221                let tag_str = tag.to_string();
222                let attr_setters = attributes.iter().map(|(name, value)| {
223                    let name_str = name.to_string();
224                    quote! {
225                        if let Some(e) = #tag.as_element_mut() {
226                            let #name = #value;
227                            e.set_attribute(#name_str, #name);
228                        }
229                    }
230                });
231
232                let children_handlers = if children.is_empty() {
233                    quote! {}
234                } else {
235                    let children_tokens = children.iter().map(|child| child.to_tokens());
236
237                    quote! {
238                        #(
239                            let child_result = #children_tokens;
240                            match child_result {
241                                simple_rsx::NodeList::Fragment(nodes) => {
242                                    for child in nodes {
243                                        #tag.append_child(child);
244                                    }
245                                },
246                                simple_rsx::NodeList::Single(node) => {
247                                    #tag.append_child(node);
248                                }
249                            }
250                        )*
251                    }
252                };
253
254                let close_tag = if let Some(close_tag) = close_tag {
255                    quote! {
256                        #close_tag = #tag;
257                    }
258                } else {
259                    quote! {}
260                };
261
262                quote! {
263                    {
264                        #[allow(unused_mut)]
265                        let mut #tag = simple_rsx::Element::new(#tag_str);
266                        #(#attr_setters)*
267                        #children_handlers
268                        #close_tag
269                        simple_rsx::NodeList::Single(#tag)
270                    }
271                }
272            }
273            JsxNode::Text(expr) => {
274                quote! {
275                    simple_rsx::NodeList::Single(simple_rsx::TextNode::new(&(#expr).to_string()))
276                }
277            }
278            JsxNode::Empty => {
279                quote! {
280                    simple_rsx::NodeList::Fragment(Vec::new())
281                }
282            }
283        }
284    }
285}