1use std::{collections::HashMap, str::FromStr};
3
4use quote::{ToTokens, format_ident, quote};
5use syn::{Expr, Ident, Token, parse::Parse, spanned::Spanned};
6
7fn under_to_dash(s: impl AsRef<str>) -> String {
8 s.as_ref().trim_matches('_').replace('_', "-")
9}
10
11#[derive(Clone, Debug)]
16pub struct ProxyUpdate {
17 proxy_ident: syn::Ident,
18 update_ident: Option<syn::Ident>,
19 pattern: syn::Pat,
20 expr: syn::Expr,
21}
22
23impl Parse for ProxyUpdate {
24 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
25 let proxy_ident: syn::Ident = input.parse()?;
26 let content;
27 let _ = syn::parenthesized!(content in input);
28 let pattern = syn::Pat::parse_single(&content)?;
29 let _ = content.parse::<Token![=]>()?;
30 let _ = content.parse::<Token![>]>()?;
31 let expr: syn::Expr = content.parse()?;
32 Ok(ProxyUpdate {
33 proxy_ident,
34 update_ident: None,
35 pattern,
36 expr,
37 })
38 }
39}
40
41#[derive(Clone, PartialEq, Eq, Hash)]
42pub struct ProxyAttribute {
43 element_ident: syn::Ident,
44 fn_ident: syn::Ident,
45 param: String,
46 expr: syn::Expr,
47}
48
49impl ProxyAttribute {
50 fn new(
51 element_ident: syn::Ident,
52 keys: &[String],
53 proxy_update: &ProxyUpdate,
54 ) -> Result<Self, syn::Error> {
55 let ks = keys.iter().map(|s| s.as_str()).collect::<Vec<_>>();
56 let (fn_ident, param) = match ks.as_slice() {
57 ["style", style] => (format_ident!("set_style"), style.to_string()),
58 [prop] => (format_ident!("set_property"), prop.to_string()),
59 _ => {
60 return Err(syn::Error::new(
61 proxy_update.pattern.span(),
62 "unsupported proxy update attribute",
63 ));
64 }
65 };
66 Ok(ProxyAttribute {
67 element_ident,
68 fn_ident,
69 param,
70 expr: proxy_update.expr.clone(),
71 })
72 }
73}
74
75#[derive(Clone, PartialEq, Eq, Hash)]
76pub enum ProxyUpdateKey {
77 Attrib(Box<ProxyAttribute>),
78 Block {
79 parent: syn::Ident,
80 block: syn::Ident,
81 },
82}
83
84pub struct ProxyOnUpdate {
86 updated_idents: HashMap<syn::Ident, bool>,
88 updates: HashMap<ProxyUpdateKey, ProxyUpdate>,
89}
90
91impl ToTokens for ProxyOnUpdate {
92 fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
93 let clones = self
94 .updated_idents
95 .iter()
96 .map(|(ident, is_mut)| {
97 if *is_mut {
98 quote! { let mut #ident = #ident.clone();}
99 } else {
100 quote! { let #ident = #ident.clone();}
101 }
102 })
103 .collect::<Vec<_>>();
104 let updates = self
105 .updates
106 .iter()
107 .map(|(key, update)| match key {
108 ProxyUpdateKey::Attrib(inner) => {
109 let ProxyAttribute {
110 element_ident,
111 fn_ident,
112 param,
113 expr,
114 } = inner.as_ref();
115 let pat = &update.pattern;
116 quote! {{
117 let #pat = model;
118 #element_ident.#fn_ident(#param, #expr);
119 }}
120 }
121 ProxyUpdateKey::Block { parent, block } => {
122 let pat = &update.pattern;
123 let expr = &update.expr;
124 quote! {{
125 let #pat = model;
126 #block.replace(&#parent, #expr);
127 }}
128 }
129 })
130 .collect::<Vec<_>>();
131 quote! {{
132 #(#clones)*
133 move |model| {
134 #(#updates)*
135 }
136 }}
137 .to_tokens(tokens);
138 }
139}
140
141fn insert_proxy(
142 proxies: &mut HashMap<syn::Ident, ProxyOnUpdate>,
143 ident: &syn::Ident,
144 should_clone: bool,
145 is_mut: bool,
146 parent: Option<&syn::Ident>,
147 key: ProxyUpdateKey,
148 proxy_update: &ProxyUpdate,
149) {
150 let mut proxy_update = proxy_update.clone();
151 proxy_update.update_ident = Some(ident.clone());
152 let proxy_on_update = proxies
153 .entry(proxy_update.proxy_ident.clone())
154 .or_insert_with(|| ProxyOnUpdate {
155 updated_idents: Default::default(),
156 updates: HashMap::default(),
157 });
158 if should_clone {
159 let updated_ident_is_mut = proxy_on_update
160 .updated_idents
161 .entry(ident.clone())
162 .or_insert(is_mut);
163 *updated_ident_is_mut = *updated_ident_is_mut || is_mut;
164 }
165 if let Some(parent) = parent {
166 proxy_on_update.updated_idents.insert(parent.clone(), false);
167 let _updated_ident_is_mut = proxy_on_update
168 .updated_idents
169 .entry(parent.clone())
170 .or_default();
171 }
172 proxy_on_update.updates.insert(key, proxy_update);
173}
174
175#[derive(Debug, Clone)]
177pub struct LetIdent {
178 ident: Ident,
179 cast: Option<syn::Type>,
180}
181
182impl Parse for LetIdent {
183 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
184 input.parse::<syn::token::Let>()?;
185 let ident = input.parse::<Ident>()?;
186 let cast = {
187 let lookahead = input.lookahead1();
188 if lookahead.peek(syn::Token![:]) {
189 input.parse::<syn::Token![:]>()?;
190 let ty = input.parse::<syn::Type>()?;
191 Some(ty)
192 } else {
193 None
194 }
195 };
196 let _ = input.parse::<Token![=]>()?;
197
198 Ok(Self { ident, cast })
199 }
200}
201
202#[derive(Debug)]
203pub enum ViewToken {
205 Element {
206 name: String,
207 ident: Option<LetIdent>,
208 attributes: Vec<AttributeToken>,
209 children: Vec<ViewToken>,
210 },
211 Text {
212 ident: Option<LetIdent>,
213 expr: syn::Expr,
214 },
215 BlockExpr {
216 ident: Option<LetIdent>,
217 expr: syn::Expr,
218 },
219 BlockProxy {
220 ident: Option<LetIdent>,
221 proxy: Box<ProxyUpdate>,
222 },
223}
224
225impl Parse for ViewToken {
226 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
227 let ident = if input.lookahead1().peek(syn::token::Let) {
228 Some(input.parse::<LetIdent>()?)
229 } else {
230 None
231 };
232
233 let lookahead = input.lookahead1();
234 if lookahead.peek(syn::token::Brace) {
235 let braced_content;
236 let _ = syn::braced!(braced_content in input);
237
238 if braced_content.fork().parse::<ProxyUpdate>().is_ok() {
239 let mut proxy = braced_content.parse::<ProxyUpdate>()?;
240 proxy.update_ident = ident.as_ref().map(|lid| lid.ident.clone());
241 Ok(ViewToken::BlockProxy {
242 ident,
243 proxy: Box::new(proxy),
244 })
245 } else {
246 let expr: syn::Expr = braced_content.parse()?;
247 Ok(ViewToken::BlockExpr { ident, expr })
248 }
249 } else if lookahead.peek(syn::LitStr) {
250 Ok(ViewToken::Text {
251 ident,
252 expr: input.parse::<syn::Expr>()?,
253 })
254 } else {
255 let tag: Ident = input.parse()?;
256 let attributes = if input.lookahead1().peek(syn::token::Paren) {
257 let paren_content;
258 let _paren_token: syn::token::Paren = syn::parenthesized!(paren_content in input);
259 let attrs: syn::punctuated::Punctuated<AttributeToken, Token![,]> =
260 paren_content.parse_terminated(AttributeToken::parse, syn::Token![,])?;
261 attrs.into_iter().collect::<Vec<_>>()
262 } else {
263 vec![]
264 };
265
266 let brace_content;
267 let _brace: syn::token::Brace = syn::braced!(brace_content in input);
268 let mut children: Vec<ViewToken> = vec![];
269 while !brace_content.is_empty() {
270 children.push(brace_content.parse::<ViewToken>()?);
271 }
272
273 Ok(ViewToken::Element {
274 name: format!("{}", tag),
275 ident,
276 attributes,
277 children,
278 })
279 }
280 }
281}
282
283pub struct WebFlavor;
284
285impl WebFlavor {
286 fn create_text(ident: &syn::Ident, expr: &syn::Expr) -> proc_macro2::TokenStream {
287 quote! { let #ident = V::Text::new(#expr); }
288 }
289 fn create_element_ns(el: &str, ns: &syn::Expr) -> proc_macro2::TokenStream {
290 quote! { V::Element::new_namespace(#el, #ns) }
291 }
292
293 fn create_element(el: &str) -> proc_macro2::TokenStream {
294 quote! { V::Element::new(#el) }
295 }
296
297 fn append_child(ident: &syn::Ident, child_id: &syn::Ident) -> proc_macro2::TokenStream {
298 quote! { #ident.append_child(&#child_id); }
299 }
300
301 fn set_style_property(
302 ident: &syn::Ident,
303 key: &str,
304 expr: &syn::Expr,
305 ) -> proc_macro2::TokenStream {
306 quote! { #ident.set_style(#key, #expr); }
307 }
308
309 fn set_attribute(ident: &syn::Ident, key: &str, expr: &syn::Expr) -> proc_macro2::TokenStream {
310 quote! { #ident.set_property(#key, #expr); }
311 }
312
313 fn set_attribute_proxy(
314 ProxyAttribute {
315 element_ident,
316 fn_ident,
317 param,
318 expr,
319 }: &ProxyAttribute,
320 proxy: &ProxyUpdate,
321 ) -> proc_macro2::TokenStream {
322 let proxy_ident = &proxy.proxy_ident;
323 let pattern = &proxy.pattern;
324 quote! {{
325 let #pattern = #proxy_ident.as_ref();
326 #element_ident.#fn_ident(#param, #expr);
327 }}
328 }
329
330 fn create_listener(
331 ident: &syn::Ident,
332 listener: &syn::Expr,
333 event: &str,
334 ) -> proc_macro2::TokenStream {
335 quote! { let #listener = #ident.listen(#event); }
336 }
337
338 fn create_window_listener(listener: &syn::Expr, event: &str) -> proc_macro2::TokenStream {
339 quote! { let #listener = V::EventListener::on_window( #event ); }
340 }
341
342 fn create_document_listener(listener: &syn::Expr, event: &str) -> proc_macro2::TokenStream {
343 quote! { let #listener = V::EventListener::on_document( #event ); }
344 }
345
346 fn proxy_child(ident: &syn::Ident, proxy: &ProxyUpdate) -> proc_macro2::TokenStream {
347 let proxy_ident = &proxy.proxy_ident;
348 let pattern = &proxy.pattern;
349 let expr = &proxy.expr;
350 quote! { let mut #ident = {
351 let #pattern = (std::ops::Deref::deref(&#proxy_ident));
352 mogwai::proxy::ProxyChild::new(#expr)
353 };}
354 }
355}
356
357impl quote::ToTokens for ViewToken {
358 fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
359 let mut proxies = HashMap::default();
360 self.to_named_tokens(None, 0, tokens, &mut proxies);
361 for (proxy, updates) in proxies.into_iter() {
362 quote! {
363 #proxy.on_update(#updates);
364 }
365 .to_tokens(tokens);
366 }
367 }
368}
369
370impl ViewToken {
371 fn leaf_name(&self) -> &str {
372 match self {
373 ViewToken::Element { name, .. } => name,
374 ViewToken::Text { .. } => "text",
375 ViewToken::BlockExpr { .. } => "block_expr",
376 ViewToken::BlockProxy { .. } => "block_proxy",
377 }
378 }
379
380 fn to_named_tokens(
381 &self,
382 parent_name: Option<syn::Ident>,
383 index: usize,
384 tokens: &mut proc_macro2::TokenStream,
385 proxies: &mut HashMap<syn::Ident, ProxyOnUpdate>,
386 ) -> LetIdent {
387 let n = if index == 0 {
388 String::new()
389 } else {
390 format!("{index}")
391 };
392
393 let spaced_parent_name = parent_name
394 .as_ref()
395 .map(|name| format!("{}_", name))
396 .unwrap_or_default();
397 let name = format!("{spaced_parent_name}{}{n}", self.leaf_name());
398 let generic_id = LetIdent {
399 ident: quote::format_ident!("_{name}"),
400 cast: None,
401 };
402
403 match self {
404 ViewToken::Element {
405 name: el,
406 ident,
407 attributes,
408 children,
409 } => {
410 let (ident, cast) = match ident {
411 None => (
412 quote::format_ident!("_{name}"),
413 Some(syn::parse_str("web_sys::Element").unwrap()),
414 ),
415 Some(LetIdent { ident, cast }) => (ident.clone(), cast.clone()),
416 };
417
418 let creation = attributes
419 .iter()
420 .find_map(|att| {
421 if let AttributeToken::Xmlns(ns) = att {
422 Some(WebFlavor::create_element_ns(el, ns))
423 } else {
424 None
425 }
426 })
427 .unwrap_or_else(|| WebFlavor::create_element(el));
428 quote! {
429 let #ident = #creation;
430 }
431 .to_tokens(tokens);
432
433 let mut indices = HashMap::<&str, usize>::new();
434 for child in children.iter() {
435 let index = indices
436 .entry(child.leaf_name())
437 .and_modify(|i| {
438 *i += 1;
439 })
440 .or_insert(0);
441 let child_id = child
442 .to_named_tokens(Some(ident.clone()), *index, tokens, proxies)
443 .ident;
444 WebFlavor::append_child(&ident, &child_id).to_tokens(tokens);
445 }
446 for att in attributes.iter() {
447 match att {
448 AttributeToken::Let(outside_id) => {
449 quote! { #outside_id = #ident; }.to_tokens(tokens);
450 }
451 AttributeToken::StyleSingle(key, expr) => {
452 WebFlavor::set_style_property(&ident, key, expr).to_tokens(tokens);
453 }
454 AttributeToken::Attrib(key, expr) => {
455 WebFlavor::set_attribute(&ident, key, expr).to_tokens(tokens);
456 }
457 AttributeToken::On(event, listener) => {
458 WebFlavor::create_listener(&ident, listener, event).to_tokens(tokens);
459 }
460 AttributeToken::Xmlns(_) => {
461 }
463 AttributeToken::Window(event, listener) => {
464 WebFlavor::create_window_listener(listener, event).to_tokens(tokens);
465 }
466 AttributeToken::Document(event, listener) => {
467 WebFlavor::create_document_listener(listener, event).to_tokens(tokens);
468 }
469 AttributeToken::AttribProxy(keys, proxy_update) => {
470 match ProxyAttribute::new(ident.clone(), keys, proxy_update) {
471 Err(e) => e.to_compile_error().to_tokens(tokens),
472 Ok(att) => {
473 WebFlavor::set_attribute_proxy(&att, proxy_update)
474 .to_tokens(tokens);
475 insert_proxy(
476 proxies,
477 &ident,
478 true,
479 false,
480 None,
481 ProxyUpdateKey::Attrib(Box::new(att)),
482 proxy_update,
483 );
484 }
485 }
486 }
487 }
488 }
489 LetIdent { ident, cast }
490 }
491 ViewToken::Text { ident, expr } => {
492 let let_ident = ident.clone().unwrap_or(generic_id);
493 let id = let_ident.ident.clone();
494 WebFlavor::create_text(&id, expr).to_tokens(tokens);
495 let_ident
496 }
497 ViewToken::BlockExpr { ident, expr } => {
498 let let_ident = ident.clone().unwrap_or(generic_id);
499 let id = let_ident.ident.clone();
500 quote! { let #id = #expr; }.to_tokens(tokens);
501 let_ident
502 }
503 ViewToken::BlockProxy { ident, proxy } => {
504 let mut should_clone = true;
505 let let_ident = ident.clone().unwrap_or_else(|| {
506 should_clone = false;
507 generic_id
508 });
509 let id = let_ident.ident.clone();
510 if let Some(parent) = parent_name.as_ref() {
511 insert_proxy(
512 proxies,
513 &id,
514 should_clone,
515 true,
516 Some(parent),
517 ProxyUpdateKey::Block {
518 parent: if let Some(parent) = parent_name.as_ref() {
519 parent.clone()
520 } else {
521 syn::Error::new(
522 proxy.update_ident.span(),
523 "Cannot use child block pattern for the outer-most block",
524 )
525 .into_compile_error()
526 .to_tokens(tokens);
527 quote::format_ident!("unknown")
528 },
529 block: id.clone(),
530 },
531 proxy,
532 );
533 WebFlavor::proxy_child(&id, proxy).to_tokens(tokens);
534 } else {
535 syn::Error::new(
536 proxy.update_ident.span(),
537 "Cannot use child block pattern for the outer-most block",
538 )
539 .into_compile_error()
540 .to_tokens(tokens);
541 }
542 let_ident
543 }
544 }
545 }
546}
547
548#[derive(Clone, Debug)]
549pub enum AttributeToken {
551 Let(Ident),
552 Xmlns(syn::Expr),
553 StyleSingle(String, syn::Expr),
555 On(String, syn::Expr),
556 Window(String, syn::Expr),
557 Document(String, syn::Expr),
558 Attrib(String, syn::Expr),
559 AttribProxy(Vec<String>, Box<ProxyUpdate>),
560}
561
562impl Parse for AttributeToken {
563 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
564 let mut keys: Vec<String> = vec![];
565 while !input.lookahead1().peek(Token![=])
566 && !input.lookahead1().peek(Token![,])
567 && !input.is_empty()
568 {
569 let key_segment = match input.parse::<Ident>() {
570 Ok(ident) => Ok(format!("{}", ident)),
571 Err(e1) => {
572 if input.parse::<Token![type]>().is_ok() {
573 Ok("type".to_string())
574 } else {
575 Err(e1)
576 }
577 }
578 }?;
579 let _ = input.parse::<Option<Token![:]>>()?;
580 keys.push(key_segment);
581 }
582 if input.parse::<Token![=]>().is_ok() {
583 if input.fork().parse::<ProxyUpdate>().is_ok() {
584 let update = input.parse::<ProxyUpdate>()?;
585 Ok(AttributeToken::AttribProxy(keys.clone(), Box::new(update)))
586 } else {
587 let expr = input.parse::<Expr>()?;
588 Ok(AttributeToken::from_keys_expr_pair(&keys, expr))
589 }
590 } else if keys.len() == 1 {
591 let ident = quote::format_ident!("{}", keys[0]);
592 Ok(AttributeToken::Let(ident))
593 } else {
594 let key = under_to_dash(keys.join(":"));
595 let none: syn::Expr =
596 syn::parse2(proc_macro2::TokenStream::from_str("None").unwrap()).unwrap();
597 Ok(AttributeToken::Attrib(key, none))
598 }
599 }
600}
601
602impl AttributeToken {
603 pub fn from_keys_expr_pair(keys: &[impl AsRef<str>], expr: Expr) -> Self {
604 let ks = keys.iter().map(|s| s.as_ref()).collect::<Vec<_>>();
605 match ks.as_slice() {
606 ["xmlns"] => AttributeToken::Xmlns(expr),
607 ["style", name] => {
608 let name = under_to_dash(name);
609 AttributeToken::StyleSingle(name, expr)
610 }
611 ["on", event] => AttributeToken::On(event.to_string(), expr),
612 ["window", event] => AttributeToken::Window(event.to_string(), expr),
613 ["document", event] => AttributeToken::Document(event.to_string(), expr),
614 [attribute_name] => {
615 let name = under_to_dash(attribute_name);
616 AttributeToken::Attrib(name, expr)
617 }
618 keys => {
619 let name = under_to_dash(keys.join(":"));
620 AttributeToken::Attrib(name, expr)
621 }
622 }
623 }
624}