1use proc_macro::TokenStream;
2use quote::{quote, ToTokens};
3use syn::parse::{Parse, ParseStream};
4use syn::{braced, Expr, Ident, LitStr, Token};
5
6struct HtmlTemplate {
8 nodes: Vec<HtmlNode>,
9}
10
11enum HtmlNode {
12 Element {
13 tag: String,
14 attrs: Vec<HtmlAttr>,
15 children: Vec<HtmlNode>,
16 self_closing: bool,
17 },
18 Text(String),
19 Fragment(Vec<HtmlNode>),
20 Expr(Expr),
21 VNodeExpr(Expr),
23}
24
25struct HtmlAttr {
26 name: String,
27 value: HtmlAttrValue,
28}
29
30enum HtmlAttrValue {
31 Static(String),
32 Dynamic(Expr),
33 Event(Expr),
34 Bool,
35}
36
37impl Parse for HtmlTemplate {
38 fn parse(input: ParseStream) -> syn::Result<Self> {
39 let nodes = parse_nodes(input, false)?;
40 Ok(HtmlTemplate { nodes })
41 }
42}
43
44fn parse_nodes(input: ParseStream, inside_tag: bool) -> syn::Result<Vec<HtmlNode>> {
46 let mut nodes = Vec::new();
47
48 while !input.is_empty() {
49 if inside_tag {
50 if input.peek(Token![<]) && input.peek2(Token![/]) {
51 break;
52 }
53 }
54
55 if input.peek(Token![<]) {
56 let fork = input.fork();
57 let _ = fork.parse::<Token![<]>();
58 if fork.peek(Token![>]) {
59 let _ = input.parse::<Token![<]>();
61 let _ = input.parse::<Token![>]>();
62 let children = parse_nodes(input, false)?;
63 let _ = input.parse::<Token![<]>();
64 let _ = input.parse::<Token![/]>();
65 let _ = input.parse::<Token![>]>();
66 nodes.push(HtmlNode::Fragment(children));
67 continue;
68 }
69
70 nodes.push(parse_element(input)?);
71 } else if input.peek(syn::token::Brace) {
72 let content;
73 let _ = braced!(content in input);
74
75 if content.peek(Ident) && content.fork().parse::<Ident>().ok().map_or(false, |id| id == "vnode") {
78 let _: Ident = content.parse()?;
80 if content.peek(Token![:]) {
82 let _: Token![:] = content.parse()?;
83 }
84 let expr: Expr = content.parse()?;
85 nodes.push(HtmlNode::VNodeExpr(expr));
86 } else {
87 let expr: Expr = content.parse()?;
89 nodes.push(HtmlNode::Expr(expr));
90 }
91 } else {
92 let mut text = String::new();
94 while !input.is_empty() && !input.peek(Token![<]) && !input.peek(syn::token::Brace) {
95 if let Ok(lit) = input.parse::<LitStr>() {
96 text.push_str(&lit.value());
97 } else {
98 let fork = input.fork();
99 if let Ok(ident) = fork.parse::<Ident>() {
100 text.push_str(&ident.to_string());
101 text.push(' ');
102 input.parse::<Ident>().ok();
103 } else if let Ok(remaining) = input.fork().parse::<proc_macro2::TokenStream>()
104 {
105 let s = remaining.to_string();
106 if !s.is_empty() {
107 text.push_str(&s);
108 input.parse::<proc_macro2::TokenStream>().ok();
109 } else {
110 break;
111 }
112 } else {
113 break;
114 }
115 }
116 }
117 if !text.is_empty() {
118 let text = text.trim_end().to_string();
119 nodes.push(HtmlNode::Text(text));
120 } else {
121 break;
122 }
123 }
124 }
125
126 Ok(nodes)
127}
128
129fn parse_element(input: ParseStream) -> syn::Result<HtmlNode> {
130 let _ = input.parse::<Token![<]>();
131 let tag: Ident = input.parse()?;
132 let tag_str = tag.to_string();
133
134 let mut attrs = Vec::new();
135 let mut self_closing = false;
136
137 while !input.peek(Token![>]) && !input.peek(Token![/]) {
139 if input.peek(syn::token::Brace) {
140 let content;
141 let _ = braced!(content in input);
142 let _: Expr = content.parse()?;
143 continue;
144 }
145
146 let first_ident: Ident = input.parse()?;
148 let mut name_str = first_ident.to_string();
149 while input.peek(Token![-]) {
151 let _ = input.parse::<Token![-]>();
152 let part: Ident = input.parse()?;
153 name_str.push('-');
154 name_str.push_str(&part.to_string());
155 }
156
157 if name_str == "on" && input.peek(Token![:]) {
159 let _ = input.parse::<Token![:]>();
160 let event_ident: Ident = input.parse()?;
161 let full_name = format!("on:{}", event_ident);
162
163 if input.peek(Token![=]) {
164 let _ = input.parse::<Token![=]>();
165 }
166 if input.peek(syn::token::Brace) {
167 let content;
168 let _ = braced!(content in input);
169 let handler: Expr = content.parse()?;
170 attrs.push(HtmlAttr {
171 name: full_name,
172 value: HtmlAttrValue::Event(handler),
173 });
174 }
175 continue;
176 }
177
178 if input.peek(Token![=]) {
179 let _ = input.parse::<Token![=]>();
180
181 if input.peek(LitStr) {
182 let lit: LitStr = input.parse()?;
183 attrs.push(HtmlAttr {
184 name: name_str,
185 value: HtmlAttrValue::Static(lit.value()),
186 });
187 } else if input.peek(syn::token::Brace) {
188 let content;
189 let _ = braced!(content in input);
190 let expr: Expr = content.parse()?;
191 attrs.push(HtmlAttr {
192 name: name_str,
193 value: HtmlAttrValue::Dynamic(expr),
194 });
195 }
196 } else {
197 attrs.push(HtmlAttr {
198 name: name_str,
199 value: HtmlAttrValue::Bool,
200 });
201 }
202 }
203
204 if input.peek(Token![/]) {
205 let _ = input.parse::<Token![/]>();
206 self_closing = true;
207 }
208
209 let _ = input.parse::<Token![>]>();
210
211 let children = if self_closing {
212 vec![]
213 } else {
214 let children = parse_nodes(input, true)?;
215 let _ = input.parse::<Token![<]>();
216 let _ = input.parse::<Token![/]>();
217 let _: Ident = input.parse()?;
218 let _ = input.parse::<Token![>]>();
219 children
220 };
221
222 Ok(HtmlNode::Element {
223 tag: tag_str,
224 attrs,
225 children,
226 self_closing,
227 })
228}
229
230impl ToTokens for HtmlTemplate {
231 fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
232 let node_tokens = self.nodes.iter().map(|node| node.as_statement());
233 let expanded = quote! {
234 {
235 let mut __rue_children: ::std::vec::Vec<rue_core::node::VNode> = ::std::vec::Vec::new();
236 #(#node_tokens)*
237 if __rue_children.len() == 1 {
238 __rue_children.into_iter().next().unwrap()
239 } else {
240 rue_core::node::VNode::fragment(__rue_children)
241 }
242 }
243 };
244 tokens.extend(expanded);
245 }
246}
247
248impl HtmlNode {
249 fn as_statement(&self) -> proc_macro2::TokenStream {
251 match self {
252 HtmlNode::Text(text) => {
253 let text = text.clone();
254 quote! {
255 __rue_children.push(rue_core::node::VNode::text(#text));
256 }
257 }
258 HtmlNode::Expr(expr) => {
259 quote! {
260 __rue_children.push({
261 let __val = (#expr);
262 rue_core::node::VNode::text(&__val.to_string())
263 });
264 }
265 }
266 HtmlNode::VNodeExpr(expr) => {
267 quote! {
269 __rue_children.push({
270 let __vnode: rue_core::node::VNode = (#expr);
271 __vnode
272 });
273 }
274 }
275 HtmlNode::Fragment(children) => {
276 let child_stmts: Vec<_> = children.iter().map(|c| c.as_statement()).collect();
277 quote! {
278 {
279 let mut __frag: ::std::vec::Vec<rue_core::node::VNode> = ::std::vec::Vec::new();
280 #(#child_stmts)*
281 __rue_children.push(rue_core::node::VNode::fragment(__frag));
282 }
283 }
284 }
285 HtmlNode::Element { .. } => {
286 let expr = self.as_expression();
287 quote! {
288 __rue_children.push(#expr);
289 }
290 }
291 }
292 }
293
294 fn as_expression(&self) -> proc_macro2::TokenStream {
296 match self {
297 HtmlNode::Element { tag, attrs, children, self_closing } => {
298 let tag_str = tag.as_str();
299 let mut builder = quote! {
300 rue_core::node::VNode::element(#tag_str)
301 };
302
303 for attr in attrs {
304 match &attr.value {
305 HtmlAttrValue::Static(val) => {
306 let name = attr.name.as_str();
307 let val = val.clone();
308 builder = quote! {
309 #builder.attr(#name, #val)
310 };
311 }
312 HtmlAttrValue::Dynamic(expr) => {
313 let name = attr.name.as_str();
314 builder = quote! {
315 #builder.attr(#name, &(#expr).to_string())
316 };
317 }
318 HtmlAttrValue::Event(expr) => {
319 let event_type = &attr.name;
320 let event_type = event_type.strip_prefix("on:").unwrap_or(event_type);
321 builder = quote! {
322 #builder.on(#event_type, #expr)
323 };
324 }
325 HtmlAttrValue::Bool => {
326 let name = attr.name.as_str();
327 builder = quote! {
328 #builder.attr(#name, "")
329 };
330 }
331 }
332 }
333
334 if !children.is_empty() && !*self_closing {
335 let child_exprs: Vec<_> = children.iter().map(|c| c.as_expression()).collect();
336 builder = quote! {
337 #builder.children(::std::vec![#(#child_exprs),*])
338 };
339 }
340
341 quote! { #builder.build() }
342 }
343 HtmlNode::Text(text) => {
344 let text = text.clone();
345 quote! { rue_core::node::VNode::text(#text) }
346 }
347 HtmlNode::Expr(expr) => {
348 quote! {{
349 let __val = (#expr);
350 rue_core::node::VNode::text(&__val.to_string())
351 }}
352 }
353 HtmlNode::VNodeExpr(expr) => {
354 quote! {{
355 let __vnode: rue_core::node::VNode = (#expr);
356 __vnode
357 }}
358 }
359 HtmlNode::Fragment(children) => {
360 let child_exprs: Vec<_> = children.iter().map(|c| c.as_expression()).collect();
361 quote! { rue_core::node::VNode::fragment(::std::vec![#(#child_exprs),*]) }
362 }
363 }
364 }
365}
366
367#[proc_macro]
369pub fn html(input: TokenStream) -> TokenStream {
370 let template = syn::parse_macro_input!(input as HtmlTemplate);
371 let expanded = template.to_token_stream();
372 TokenStream::from(expanded)
373}
374
375#[proc_macro_attribute]
377pub fn component(_attr: TokenStream, item: TokenStream) -> TokenStream {
378 item
379}