mogwai_macros/
tokens.rs

1//! Contains parsing an RSX node into various data types.
2use 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/// Matches `proxy (model) => model.id.to_string()` where
12/// * `proxy_ident`: `proxy`
13/// * `pattern`: `model`
14/// * `expr`: `model.id.to_string()`
15#[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
84/// Used to create a proxy "on_update" call at the end of an rsx macro.
85pub struct ProxyOnUpdate {
86    /// Map of ident to mutability
87    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/// Parses `let my_ident: MyType =`
176#[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)]
203/// An enumeration of all supported nodes types.
204pub 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                            // handled elsewhere
462                        }
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)]
549/// An enumeration of all supported attribute types.
550pub enum AttributeToken {
551    Let(Ident),
552    Xmlns(syn::Expr),
553    // TODO: allow the name to be syn::Expr
554    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}