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 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 if input.is_empty() {
94 return Ok(JsxNode::Empty);
95 }
96
97 if input.peek(Token![<]) {
99 input.parse::<Token![<]>()?;
100
101 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 let tag = input.parse::<Ident>()?;
125
126 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 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 input.parse::<Token![>]>()?;
148
149 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 input.parse::<Token![<]>()?;
158 input.parse::<Token![/]>()?;
159 let close_tag = input.parse::<Ident>()?;
160
161 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 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 match input.parse::<Expr>() {
196 Ok(expr) => Ok(JsxNode::Text(expr)),
197 Err(_) => {
198 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}