simple_rsx_macros/
lib.rs

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