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#[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
38enum JsxNode {
40 Fragment(Vec<JsxNode>),
41 Element {
42 tag: Ident,
43 attributes: Vec<(Ident, Lit)>,
44 children: Vec<JsxNode>,
45 close_tag: Option<Ident>, },
47 Text(Expr),
48 Empty,
49}
50
51struct 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 if input.is_empty() {
70 return Ok(JsxNode::Empty);
71 }
72
73 if input.peek(Token![<]) {
75 input.parse::<Token![<]>()?;
76
77 if input.peek(Token![>]) {
79 input.parse::<Token![>]>()?;
80
81 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 let _ = input.parse::<proc_macro2::TokenTree>();
91 }
92 }
93
94 input.parse::<Token![<]>()?;
96 input.parse::<Token![/]>()?;
97 input.parse::<Token![>]>()?;
98
99 return Ok(JsxNode::Fragment(children));
100 }
101
102 let tag = input.parse::<Ident>()?;
104
105 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 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 input.parse::<Token![>]>()?;
127
128 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 input.parse::<Token![<]>()?;
137 input.parse::<Token![/]>()?;
138 let close_tag = input.parse::<Ident>()?;
139
140 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 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 match input.parse::<Expr>() {
181 Ok(expr) => Ok(JsxNode::Text(expr)),
182 Err(_) => {
183 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}