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#[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
39enum JsxNode {
41 Fragment(Vec<JsxNode>),
42 Element {
43 tag: Ident,
44 attributes: Vec<(Ident, Block)>,
45 children: Vec<JsxNode>,
46 close_tag: Option<Ident>, },
48 Text(Expr),
49 Block(Block),
50 Empty,
51}
52
53struct NodeBlock {
54 value: Block,
55}
56
57impl Parse for NodeBlock {
58 fn parse(input: ParseStream) -> Result<Self> {
59 if input.peek(LitStr) {
60 let parsed: LitStr = input.parse()?;
61 let value = Block {
62 brace_token: Brace::default(),
63 stmts: vec![syn::Stmt::Expr(
64 syn::Expr::Macro(syn::ExprMacro {
65 attrs: Vec::new(),
66 mac: Macro {
67 path: parse_quote!(format),
68 bang_token: Not::default(),
69 delimiter: syn::MacroDelimiter::Paren(syn::token::Paren::default()),
70 tokens: {
71 let string_lit = syn::Lit::Str(parsed);
72 quote::quote!(#string_lit)
73 },
74 },
75 }),
76 None,
77 )],
78 };
79 return Ok(NodeBlock { value });
80 }
81 let is_block = input.to_string().trim().starts_with('{');
82
83 if is_block {
84 let value = input.parse()?;
85 Ok(NodeBlock { value })
86 } else {
87 let mut str = String::new();
88 let mut in_string = false;
89 let mut last_end = 0;
90
91 loop {
92 if input.lookahead1().peek(Token![<]) && !in_string {
93 break;
95 }
96
97 match input.parse::<proc_macro2::TokenTree>() {
98 Ok(token) => {
99 match &token {
100 proc_macro2::TokenTree::Literal(lit) => {
101 let lit_str = lit.to_string();
102 in_string = lit_str.starts_with('"') || lit_str.starts_with('\'');
103 }
104 _ => in_string = false,
105 }
106
107 let span_info = format!("{:?}", token.span());
108 let (start, end) = parse_range(&span_info).unwrap_or((0, 0));
109
110 let mut value = token.to_string();
111
112 if value.starts_with('{') && value.ends_with('}') {
113 value = value.replace("{ ", "{");
114 value = value.replace(" }", "}");
115 }
116
117 if start > last_end {
118 str.push(' ');
119 last_end = end;
120 }
121 str.push_str(&value);
122 }
123 Err(_) => break, }
125 }
126
127 let lit = LitStr::new(&str, Span::call_site());
128 let value = Block {
129 brace_token: Brace::default(),
130 stmts: vec![syn::Stmt::Expr(
131 syn::Expr::Macro(syn::ExprMacro {
132 attrs: Vec::new(),
133 mac: Macro {
134 path: parse_quote!(format),
135 bang_token: Not::default(),
136 delimiter: syn::MacroDelimiter::Paren(syn::token::Paren::default()),
137 tokens: {
138 let string_lit = syn::Lit::Str(lit);
139 quote::quote!(#string_lit)
140 },
141 },
142 }),
143 None,
144 )],
145 };
146 Ok(NodeBlock { value })
147 }
148 }
149}
150
151struct NodeValue {
153 name: Ident,
154 value: Block,
155}
156
157impl Parse for NodeValue {
158 fn parse(input: ParseStream) -> Result<Self> {
159 let name = input.parse()?;
160 input.parse::<Token![=]>()?;
161 let NodeBlock { value } = input.parse()?;
162 Ok(NodeValue { name, value })
163 }
164}
165
166impl Parse for JsxNode {
167 fn parse(input: ParseStream) -> Result<Self> {
168 if input.is_empty() {
170 return Ok(JsxNode::Empty);
171 }
172
173 if input.peek(Token![<]) {
175 input.parse::<Token![<]>()?;
176
177 if input.peek(Token![>]) {
179 input.parse::<Token![>]>()?;
180
181 let mut children = Vec::new();
182 while !input.is_empty()
183 && !(input.peek(Token![<]) && input.peek2(Token![/]) && input.peek3(Token![>]))
184 {
185 if let Ok(child) = input.parse::<JsxNode>() {
186 children.push(child);
187 } else {
188 let _ = input.parse::<proc_macro2::TokenTree>();
189 }
190 }
191
192 input.parse::<Token![<]>()?;
193 input.parse::<Token![/]>()?;
194 input.parse::<Token![>]>()?;
195
196 return Ok(JsxNode::Fragment(children));
197 }
198
199 let tag = input.parse::<Ident>()?;
201
202 let mut attributes = Vec::new();
204 while !input.peek(Token![>]) && !input.peek(Token![/]) {
205 let attr: NodeValue = input.parse()?;
206 attributes.push((attr.name, attr.value));
207 }
208
209 if input.peek(Token![/]) {
211 input.parse::<Token![/]>()?;
212 input.parse::<Token![>]>()?;
213
214 return Ok(JsxNode::Element {
215 tag,
216 attributes,
217 children: Vec::new(),
218 close_tag: None,
219 });
220 }
221
222 input.parse::<Token![>]>()?;
224
225 let mut children = Vec::new();
227 while !input.is_empty() && !(input.peek(Token![<]) && input.peek2(Token![/])) {
228 let child = input.parse::<JsxNode>()?;
229 children.push(child);
230 }
231
232 input.parse::<Token![<]>()?;
234 input.parse::<Token![/]>()?;
235 let close_tag = input.parse::<Ident>()?;
236
237 if tag != close_tag {
239 return Err(syn::Error::new(
240 close_tag.span(),
241 format!(
242 "Closing tag </{}> doesn't match opening tag <{}>",
243 close_tag, tag
244 ),
245 ));
246 }
247
248 input.parse::<Token![>]>()?;
249
250 return Ok(JsxNode::Element {
251 tag,
252 attributes,
253 children,
254 close_tag: Some(close_tag),
255 });
256 }
257
258 if input.peek(Lit) {
260 let lit: Lit = input.parse()?;
261 let expr = Expr::Lit(ExprLit {
262 attrs: Vec::new(),
263 lit,
264 });
265 return Ok(JsxNode::Text(expr));
266 }
267 match input.parse::<Block>() {
268 Ok(block) => Ok(JsxNode::Block(block)),
269 Err(_) => match input.parse::<NodeBlock>() {
270 Ok(block) => Ok(JsxNode::Block(block.value)),
271 Err(_) => match input.parse::<Expr>() {
272 Ok(expr) => Ok(JsxNode::Text(expr)),
273 Err(_) => Err(syn::Error::new(
274 Span::call_site(),
275 "Invalid JSX node, expected text or expression",
276 )),
277 },
278 },
279 }
280 }
281}
282
283impl JsxNode {
284 fn to_tokens(&self) -> TokenStream2 {
285 match self {
286 JsxNode::Fragment(children) => {
287 let children_tokens = children.iter().map(|child| child.to_tokens());
288
289 quote! {
290 {
291 let mut nodes = Vec::new();
292 #(
293 let result = #children_tokens;
294 match result {
295 simple_rsx::NodeList::Fragment(mut child_nodes) => nodes.append(&mut child_nodes),
296 simple_rsx::NodeList::Single(node) => nodes.push(node),
297 }
298 )*
299 simple_rsx::NodeList::Fragment(nodes)
300 }
301 }
302 }
303 JsxNode::Element {
304 tag,
305 attributes,
306 children,
307 close_tag,
308 } => {
309 let tag_str = tag.to_string();
310 let attr_setters = attributes.iter().map(|(name, value)| {
311 let name_str = name.to_string().replace("r#", "");
312 quote! {
313 if let Some(e) = #tag.as_element_mut() {
314 let #name = #value;
315 e.set_attribute(#name_str, #name);
316 }
317 }
318 });
319
320 let children_handlers = if children.is_empty() {
321 quote! {}
322 } else {
323 let children_tokens = children.iter().map(|child| child.to_tokens());
324
325 quote! {
326 #(
327 let child_result = #children_tokens;
328 match child_result {
329 simple_rsx::NodeList::Fragment(nodes) => {
330 for child in nodes {
331 #tag.append_child(child);
332 }
333 },
334 simple_rsx::NodeList::Single(node) => {
335 #tag.append_child(node);
336 }
337 }
338 )*
339 }
340 };
341
342 let close_tag = if let Some(close_tag) = close_tag {
343 quote! {
344 #close_tag = #tag;
345 }
346 } else {
347 quote! {}
348 };
349
350 quote! {
351 {
352 #[allow(unused_mut)]
353 let mut #tag = simple_rsx::Element::new(#tag_str);
354 #(#attr_setters)*
355 #children_handlers
356 #close_tag
357 simple_rsx::NodeList::Single(#tag)
358 }
359 }
360 }
361 JsxNode::Text(expr) => {
362 quote! {
363 simple_rsx::NodeList::Single(simple_rsx::TextNode::new(&(#expr).to_string()))
364 }
365 }
366 JsxNode::Empty => {
367 quote! {
368 simple_rsx::NodeList::Fragment(Vec::new())
369 }
370 }
371 JsxNode::Block(block) => {
372 quote! {
373 simple_rsx::NodeList::from(#block)
374 }
375 }
376 }
377 }
378}
379
380fn parse_range(input: &str) -> Option<(usize, usize)> {
381 use regex::Regex;
382 let re = Regex::new(r"(\d+)\.\.(\d+)").ok()?;
383 let captures = re.captures(input)?;
384 let start = captures.get(1)?.as_str().parse::<usize>().ok()?;
385 let end = captures.get(2)?.as_str().parse::<usize>().ok()?;
386
387 Some((start, end))
388}