1use proc_macro::TokenStream;
2use proc_macro2::TokenStream as TokenStream2;
3use quote::{quote, ToTokens};
4use syn::{
5 braced,
6 parse::{Parse, ParseStream},
7 parse_macro_input,
8 token::Brace,
9 Expr, Ident, LitStr, Result, Token,
10};
11
12enum RefDeref {
13 Ref(Token![&]),
14 Deref(Token![*]),
15 Mut(Token![mut]),
16}
17
18struct RefsDerefs {
19 refs: Vec<RefDeref>,
20}
21
22impl Parse for RefsDerefs {
23 fn parse(input: ParseStream) -> Result<Self> {
24 let mut refs = Vec::new();
25 while !input.is_empty() {
26 if let Some(r) = input.parse()? {
27 refs.push(RefDeref::Ref(r));
28 } else if let Some(r) = input.parse()? {
29 refs.push(RefDeref::Deref(r));
30 } else if let Some(r) = input.parse()? {
31 refs.push(RefDeref::Mut(r));
32 } else {
33 break;
34 }
35 }
36 Ok(RefsDerefs { refs })
37 }
38}
39
40impl ToTokens for RefsDerefs {
41 fn to_tokens(&self, tokens: &mut TokenStream2) {
42 for ref_deref in &self.refs {
43 match ref_deref {
44 RefDeref::Ref(r) => r.to_tokens(tokens),
45 RefDeref::Deref(d) => d.to_tokens(tokens),
46 RefDeref::Mut(m) => m.to_tokens(tokens),
47 }
48 }
49 }
50}
51
52enum NxmlAttr {
53 Literal(Ident, LitStr),
54 Expr(Ident, Expr),
55 Shortcut(RefsDerefs, Ident),
56}
57
58impl Parse for NxmlAttr {
59 fn parse(input: ParseStream) -> Result<Self> {
60 let Some(ident) = input.parse()? else {
61 let content = (|| {
63 let content;
64 braced!(content in input);
65 Ok(content)
66 })()
67 .map_err(|_| input.error("expected attribute name or identifier in curly braces"))?;
68
69 return Ok(NxmlAttr::Shortcut(content.parse()?, content.parse()?));
70 };
71
72 input.parse::<Token![=]>()?;
73
74 if let Some(lit) = input.parse()? {
75 return Ok(NxmlAttr::Literal(ident, lit));
76 }
77
78 let content = (|| {
80 let content;
81 braced!(content in input);
82 Ok(content)
83 })()
84 .map_err(|_| input.error("expected a string literal or an expression in curly braces"))?;
85 Ok(NxmlAttr::Expr(ident, content.parse()?))
86 }
87}
88
89enum TextPart {
90 Static(String),
91 Expr(Expr),
92}
93
94enum NxmlFinish {
95 SelfClosing,
96 Closing {
97 text_content: Vec<TextPart>,
98 children: Vec<NxmlInput>,
99 name: Ident,
100 },
101}
102
103impl Parse for NxmlFinish {
104 fn parse(input: ParseStream) -> Result<Self> {
105 if input.peek(Token![/]) && input.peek2(Token![>]) {
106 input.parse::<Token![/]>()?;
107 input.parse::<Token![>]>()?;
108 return Ok(NxmlFinish::SelfClosing);
109 }
110
111 input.parse::<Token![>]>()?;
112
113 let mut children = Vec::new();
114 let mut text_content = Vec::new();
115
116 while !(input.peek(Token![<]) && input.peek2(Token![/])) {
117 if let Some(lit) = input.parse::<Option<LitStr>>()? {
118 text_content.push(TextPart::Static(lit.value()));
119 continue;
120 }
121 if let Some(ident) = input.parse::<Option<Ident>>()? {
122 text_content.push(TextPart::Static(ident.to_string()));
123 continue;
124 }
125 if input.peek(Brace) {
126 let content;
127 braced!(content in input);
128 text_content.push(TextPart::Expr(content.parse()?));
129 continue;
130 }
131 if input.peek(Token![<]) {
132 children.push(input.parse()?);
133 continue;
134 }
135 return Err(input.error(
136 "expected a string literal, an expression in curly braces or a child element",
137 ));
138 }
139
140 input.parse::<Token![<]>()?;
141 input.parse::<Token![/]>()?;
142
143 let name = input.parse()?;
144
145 input.parse::<Token![>]>()?;
146
147 Ok(Self::Closing {
148 text_content,
149 children,
150 name,
151 })
152 }
153}
154
155struct NxmlInput {
156 name: Ident,
157 attrs: Vec<NxmlAttr>,
158 finish: NxmlFinish,
159}
160
161impl Parse for NxmlInput {
162 fn parse(input: ParseStream) -> Result<Self> {
163 input.parse::<Token![<]>()?;
164
165 let name = input.parse()?;
166
167 Ok(NxmlInput {
168 attrs: {
169 let mut attrs = Vec::new();
170 while !(input.peek(Token![>]) || input.peek(Token![/]) && input.peek2(Token![>])) {
171 attrs.push(input.parse()?);
172 }
173 attrs
174 },
175 finish: {
176 let finish = input.parse()?;
177 if let NxmlFinish::Closing { name: end_name, .. } = &finish {
178 if *end_name != name && end_name != "intellijRulezz" {
181 let err = syn::Error::new_spanned(
182 end_name,
183 "expected closing tag to match opening tag",
184 );
185 return Err(err);
190 }
191 }
192 finish
193 },
194 name,
195 })
196 }
197}
198
199fn codegen(input: &NxmlInput, element: TokenStream2) -> TokenStream2 {
200 let name = &input.name;
201
202 let (text_parts, children, end_name) = match &input.finish {
203 NxmlFinish::Closing {
204 text_content,
205 children,
206 name,
207 } => (&text_content[..], &children[..], name),
208 _ => (&[][..], &[][..], name),
209 };
210
211 let mut static_text = String::new();
212 let mut text_exprs = Vec::new();
213 for part in text_parts {
214 if !static_text.is_empty() {
215 static_text.push(' ');
216 }
217 match part {
218 TextPart::Static(str) => static_text.push_str(str),
219 TextPart::Expr(expr) => {
220 static_text.push_str("{}");
221 text_exprs.push(expr);
222 }
223 }
224 }
225
226 let attrs = input.attrs.iter().map(|attr| match attr {
227 NxmlAttr::Literal(ident, value) => quote!(.with_attr(stringify!(#ident), #value)),
228 NxmlAttr::Expr(ident, expr) => quote!(.with_attr(stringify!(#ident), #expr)),
229 NxmlAttr::Shortcut(r, ident) => quote!(.with_attr(stringify!(#ident), #r #ident)),
230 });
231
232 let text_content = if text_exprs.is_empty() {
233 if !static_text.is_empty() {
234 quote!(.with_text(#static_text))
235 } else {
236 quote!()
237 }
238 } else {
239 quote!(.with_text(format!(#static_text, #(#text_exprs),*)))
240 };
241
242 let children = children.iter().map(|child| {
243 let tokens = codegen(child, element.clone());
244 quote!(.with_child(#tokens))
245 });
246
247 quote!({
248 #[allow(non_camel_case_types)]
249 struct #name;
250 let _: #name = #end_name;
254
255 ::nxml_rs::#element::new(stringify!(#name))
256 #text_content
257 #(#attrs)*
258 #(#children)*
259 })
260}
261
262#[proc_macro]
286pub fn nxml(input: TokenStream) -> TokenStream {
287 let input = parse_macro_input!(input as NxmlInput);
288 codegen(&input, quote!(Element)).into()
289}
290
291#[proc_macro]
320pub fn nxml_ref(input: TokenStream) -> TokenStream {
321 let input = parse_macro_input!(input as NxmlInput);
322 codegen(&input, quote!(ElementRef)).into()
323}
324
325struct NxmlMultiInput {
326 children: Vec<NxmlInput>,
327}
328
329impl Parse for NxmlMultiInput {
330 fn parse(input: ParseStream) -> Result<Self> {
331 let mut children = Vec::new();
332 while !(input.peek(Token![<]) && input.peek2(Token![/]) || input.is_empty()) {
333 children.push(input.parse()?);
334 }
335 Ok(NxmlMultiInput { children })
336 }
337}
338
339#[proc_macro]
352pub fn nxmls(input: TokenStream) -> TokenStream {
353 let input = parse_macro_input!(input as NxmlMultiInput);
354 let items = input
355 .children
356 .iter()
357 .map(|child| codegen(child, quote!(Element)));
358 quote!(vec![#(#items),*]).into()
359}
360
361#[proc_macro]
375pub fn nxml_refs(input: TokenStream) -> TokenStream {
376 let input = parse_macro_input!(input as NxmlMultiInput);
377 let items = input
378 .children
379 .iter()
380 .map(|child| codegen(child, quote!(ElementRef)));
381 quote!(vec![#(#items),*]).into()
382}