1use proc_macro::TokenStream;
2use proc_macro2::{Span, TokenStream as TokenStream2};
3use quote::quote;
4use syn::spanned::Spanned;
5use syn::token::Colon;
6use syn::{
7 Block, Expr, ExprLit, Ident, ItemFn, Lit, LitStr, Macro, Result, Token,
8 parse::{Parse, ParseStream},
9 parse_macro_input, parse_quote,
10 token::{Brace, Not},
11};
12use syn::{FnArg, PatType, Signature};
13
14#[proc_macro]
37pub fn rsx(input: TokenStream) -> TokenStream {
38 let input = parse_macro_input!(input as RsxNode);
39 let expanded = input.to_tokens();
40 expanded.into()
41}
42#[proc_macro]
51pub fn either(input: TokenStream) -> TokenStream {
52 let input = parse_macro_input!(input as Either);
53 let expanded = input.to_tokens();
54 expanded.into()
55}
56
57struct Either {
58 condition: Expr,
59 true_value: RsxNode,
60 false_value: Option<RsxNode>,
61}
62
63impl Parse for Either {
64 fn parse(input: ParseStream) -> Result<Self> {
65 let condition = input.parse()?;
66 input.parse::<Token![=>]>()?;
67 let true_value = input.parse()?;
68 let false_value = if input.peek(Token![else]) {
69 input.parse::<Token![else]>()?;
70 Some(input.parse()?)
71 } else {
72 None
73 };
74 Ok(Either {
75 condition,
76 true_value,
77 false_value,
78 })
79 }
80}
81
82impl Either {
83 fn to_tokens(&self) -> TokenStream2 {
84 let condition = &self.condition;
85 let false_value = &self
86 .false_value
87 .as_ref()
88 .and_then(|v| Some(v.to_tokens()))
89 .or_else(|| Some(quote! {simple_rsx::Node::Fragment(vec![])}));
90 let true_value = self.true_value.to_tokens();
91
92 quote! {
93 if #condition {
94 #true_value.into()
95 } else {
96 #false_value
97 }
98 }
99 }
100}
101
102#[proc_macro_attribute]
115pub fn component(_attr: TokenStream, input: TokenStream) -> TokenStream {
116 let ItemFn {
117 vis,
118 attrs,
119 sig,
120 block,
121 } = parse_macro_input!(input as ItemFn);
122 let Signature {
123 ident,
124 asyncness,
125 constness,
126 unsafety,
127 mut inputs,
128 output,
129 fn_token,
130 ..
131 } = sig;
132
133 if asyncness.is_some() || constness.is_some() || unsafety.is_some() {
134 panic!("async, const, and unsafe functions are not supported");
135 }
136
137 if inputs.len() > 1 {
138 panic!("Components can only take a single prop as input");
139 }
140
141 let prop_type = inputs
142 .iter()
143 .find_map(|input| match input {
144 FnArg::Typed(PatType { ty, .. }) => Some(quote! {type Props = #ty;}),
145 _ => panic!("Only typed inputs are supported"),
146 })
147 .unwrap_or_else(|| quote! {type Props = ();});
148
149 if inputs.is_empty() {
150 inputs.push(FnArg::Typed(PatType {
151 attrs: Vec::new(),
152 pat: parse_quote!(_),
153 colon_token: Colon::default(),
154 ty: parse_quote!(Self::Props),
155 }));
156 }
157
158 let expanded = quote! {
159 #vis #(#attrs)* struct #ident;
160
161 impl simple_rsx::Component for #ident {
162 #prop_type
163 #fn_token render(#inputs) #output #block
164 }
165 };
166
167 expanded.into()
168}
169
170enum RsxNode {
172 Fragment(Vec<RsxNode>),
173 Component {
174 name: Ident,
175 props: Vec<(Ident, Option<Block>)>,
176 children: Vec<RsxNode>,
177 close_tag: Option<Ident>,
178 },
179 Text(Expr),
180 Block(Block),
181 Empty,
182 Comment(String), }
184
185struct NodeBlock {
186 expr: Option<Expr>,
187 value: Option<Block>,
188}
189
190impl Parse for NodeBlock {
191 fn parse(input: ParseStream) -> Result<Self> {
192 if input.peek(LitStr) {
193 let parsed: LitStr = input.parse()?;
194 return Ok(NodeBlock {
195 value: None,
196 expr: Some(syn::Expr::Macro(syn::ExprMacro {
197 attrs: Vec::new(),
198 mac: Macro {
199 path: parse_quote!(format),
200 bang_token: Not::default(),
201 delimiter: syn::MacroDelimiter::Paren(syn::token::Paren::default()),
202 tokens: quote::quote!(#parsed),
203 },
204 })),
205 });
206 }
207
208 let is_block = input.to_string().trim().starts_with('{');
209
210 if is_block {
211 let value: Block = input.parse()?;
212 return Ok(NodeBlock {
213 value: Some(value),
214 expr: None,
215 });
216 }
217
218 let mut str = String::new();
219 let mut in_string = false;
220 let mut last_end = 0;
221
222 while !input.is_empty() {
223 if input.lookahead1().peek(Token![<]) && !in_string {
224 break;
226 }
227
228 match input.parse::<proc_macro2::TokenTree>() {
229 Ok(token) => {
230 match &token {
231 proc_macro2::TokenTree::Literal(lit) => {
232 let lit_str = lit.to_string();
233 in_string = lit_str.starts_with('"') || lit_str.starts_with('\'');
234 }
235 _ => in_string = false,
236 }
237
238 let span_info = format!("{:?}", token.span());
239 let (start, end) = parse_range(&span_info).unwrap_or((0, 0));
240
241 let mut value = token.to_string();
242
243 if value.starts_with('{') && value.ends_with('}') {
244 value = value.replace("{ ", "{");
245 value = value.replace(" }", "}");
246 }
247
248 if start > last_end {
249 str.push(' ');
250 last_end = end;
251 }
252 str.push_str(&value);
253 }
254 Err(_) => break, }
256 }
257
258 let lit = LitStr::new(&str.trim(), Span::call_site());
259
260 Ok(NodeBlock {
261 value: None,
262 expr: Some(syn::Expr::Macro(syn::ExprMacro {
263 attrs: Vec::new(),
264 mac: Macro {
265 path: parse_quote!(format),
266 bang_token: Not::default(),
267 delimiter: syn::MacroDelimiter::Paren(syn::token::Paren::default()),
268 tokens: quote::quote!(#lit),
269 },
270 })),
271 })
272 }
273}
274
275struct NodeValue {
277 name: Ident,
278 value: Option<Block>,
279}
280
281impl Parse for NodeValue {
282 fn parse(input: ParseStream) -> Result<Self> {
283 let name = input.parse()?;
284 if !input.peek(Token![=]) {
285 return Ok(NodeValue { name, value: None });
286 }
287 input.parse::<Token![=]>()?;
288 let NodeBlock { value, expr } = input.parse()?;
289 Ok(NodeValue {
290 name,
291 value: value.or_else(|| match expr {
292 Some(expr) => Some(Block {
293 brace_token: Brace::default(),
294 stmts: vec![syn::Stmt::Expr(expr, None)],
295 }),
296 None => None,
297 }),
298 })
299 }
300}
301
302impl Parse for RsxNode {
303 fn parse(input: ParseStream) -> Result<Self> {
304 if input.is_empty() {
305 return Ok(RsxNode::Empty);
306 }
307
308 if input.peek(Token![<]) {
310 input.parse::<Token![<]>()?;
311
312 if input.peek(Token![!]) && input.peek2(Token![-]) && input.peek3(Token![-]) {
314 input.parse::<Token![!]>()?;
315 input.parse::<Token![-]>()?;
316 input.parse::<Token![-]>()?;
317
318 let mut comment = String::new();
319 let mut last_end = 0;
320 while !input.is_empty()
321 && !(input.peek(Token![-]) && input.peek2(Token![-]) && input.peek3(Token![>]))
322 {
323 let token = input.parse::<proc_macro2::TokenTree>()?;
324 let span_info = format!("{:?}", token.span());
325 let (start, end) = parse_range(&span_info).unwrap_or((0, 0));
326 if start > last_end {
327 comment.push(' ');
328 last_end = end;
329 }
330 comment.push_str(&token.to_string());
331 }
332
333 let token = input.parse::<Token![-]>()?;
334 let span_info = format!("{:?}", token.span());
335 let (start, _) = parse_range(&span_info).unwrap_or((0, 0));
336 if start > last_end {
337 comment.push(' ');
338 }
339 input.parse::<Token![-]>()?;
340 input.parse::<Token![>]>()?;
341
342 return Ok(RsxNode::Comment(comment.to_string()));
343 }
344
345 if input.peek(Token![>]) {
347 input.parse::<Token![>]>()?;
348
349 let mut children = Vec::with_capacity(4); while !input.is_empty()
351 && !(input.peek(Token![<]) && input.peek2(Token![/]) && input.peek3(Token![>]))
352 {
353 match input.parse::<RsxNode>() {
354 Ok(child) => children.push(child),
355 Err(_) => {
356 input.parse::<proc_macro2::TokenTree>()?;
357 }
358 }
359 }
360
361 input.parse::<Token![<]>()?;
362 input.parse::<Token![/]>()?;
363 input.parse::<Token![>]>()?;
364
365 return Ok(RsxNode::Fragment(children));
366 }
367
368 let tag = input.parse::<Ident>()?;
370
371 let mut attributes = Vec::with_capacity(4);
372 while !input.peek(Token![>]) && !input.peek(Token![/]) {
373 match input.parse::<NodeValue>() {
374 Ok(attr) => attributes.push((attr.name, attr.value)),
375 Err(e) => return Err(e),
376 }
377 }
378
379 if input.peek(Token![/]) {
381 input.parse::<Token![/]>()?;
382 input.parse::<Token![>]>()?;
383
384 return Ok(RsxNode::Component {
385 name: tag.clone(),
386 props: attributes,
387 children: Vec::new(),
388 close_tag: None,
389 });
390 }
391
392 input.parse::<Token![>]>()?;
394
395 let mut children = Vec::with_capacity(4);
396 while !input.is_empty() && !(input.peek(Token![<]) && input.peek2(Token![/])) {
397 match input.parse::<RsxNode>() {
398 Ok(child) => children.push(child),
399 Err(e) => return Err(e),
400 }
401 }
402
403 input.parse::<Token![<]>()?;
405 input.parse::<Token![/]>()?;
406 let close_tag = input.parse::<Ident>()?;
407
408 if tag != close_tag {
410 return Err(syn::Error::new(
411 close_tag.span(),
412 format!(
413 "Closing tag </{}> doesn't match opening tag <{}>",
414 close_tag, tag
415 ),
416 ));
417 }
418
419 input.parse::<Token![>]>()?;
420
421 return Ok(RsxNode::Component {
422 name: tag,
423 props: attributes,
424 children,
425 close_tag: Some(close_tag),
426 });
427 }
428
429 if input.peek(Lit) {
431 let lit: Lit = input.parse()?;
432 let expr = Expr::Lit(ExprLit {
433 attrs: Vec::new(),
434 lit,
435 });
436 return Ok(RsxNode::Text(expr));
437 }
438 match input.parse::<Block>() {
439 Ok(block) => Ok(RsxNode::Block(block)),
440 Err(_) => match input.parse::<NodeBlock>() {
441 Ok(block) => match block.value {
442 Some(value) => Ok(RsxNode::Block(value)),
443 _ => match block.expr {
444 Some(expr) => Ok(RsxNode::Text(expr)),
445 _ => Ok(RsxNode::Empty),
446 },
447 },
448 Err(_) => match input.parse::<Expr>() {
449 Ok(expr) => Ok(RsxNode::Text(expr)),
450 Err(_) => Err(syn::Error::new(
451 Span::call_site(),
452 "Invalid JSX node, expected a valid rsx block, an expression or plain text",
453 )),
454 },
455 },
456 }
457 }
458}
459
460impl RsxNode {
461 fn to_tokens(&self) -> TokenStream2 {
462 match self {
463 RsxNode::Component {
464 name,
465 props,
466 children,
467 close_tag,
468 } => {
469 let props_tokens = props.iter().map(|(name, value)| {
470 if value.is_none() {
471 quote! {
472 #name: true,
473 }
474 } else {
475 quote! {
476 #name: #value.into(),
477 }
478 }
479 });
480
481 let children_tokens = if !children.is_empty() {
482 let child_tokens = children.iter().map(|child| child.to_tokens());
483 Some(quote! {
484 children: vec![#(#child_tokens),*],
485 })
486 } else {
487 Some(quote! {
488 children: vec![],
489 })
490 };
491
492 let close_tag = close_tag.as_ref().and_then(|close_tag| {
493 Some(quote! {
494 let #close_tag = #name;
495 })
496 });
497 let is_component = name.to_string().starts_with(|c: char| c.is_uppercase());
498
499 let use_element = if !is_component {
500 Some(quote! {use simple_rsx::elements::#name;})
501 } else {
502 None
503 };
504
505 let default_props = if !is_component {
506 Some(quote! {
507 ..Default::default()
508 })
509 } else {
510 None
511 };
512
513 quote! {
514 {
515 #use_element
516 type Props = <#name as simple_rsx::Component>::Props;
517 #close_tag
518 <#name as simple_rsx::Component>::render(
519 Props {
520 #(#props_tokens)*
521 #children_tokens
522 #default_props
523 },
524 )
525 }
526 }
527 }
528 RsxNode::Fragment(children) => {
529 let children_tokens = children.iter().map(|child| child.to_tokens());
530
531 quote! {
532 {
533 simple_rsx::Node::Fragment(vec![#(#children_tokens)*])
534 }
535 }
536 }
537 RsxNode::Text(expr) => {
538 quote! {
539 simple_rsx::Node::Text(#expr.to_string())
540 }
541 }
542 RsxNode::Empty => {
543 quote! {
544 simple_rsx::Node::Fragment(Vec::new())
545 }
546 }
547 RsxNode::Comment(text) => {
548 quote! {
549 simple_rsx::Node::Comment(#text.to_string())
550 }
551 }
552 RsxNode::Block(block) => {
553 quote! {
554 simple_rsx::Node::from(#block)
555 }
556 }
557 }
558 }
559}
560
561fn parse_range(input: &str) -> Option<(usize, usize)> {
562 use regex::Regex;
563 let re = Regex::new(r"(\d+)\.\.(\d+)").ok()?;
564 let captures = re.captures(input)?;
565 let start = captures.get(1)?.as_str().parse::<usize>().ok()?;
566 let end = captures.get(2)?.as_str().parse::<usize>().ok()?;
567
568 Some((start, end))
569}