Skip to main content

zyn_core/ast/at/
element_node.rs

1use proc_macro2::Ident;
2use proc_macro2::Span;
3use proc_macro2::TokenStream;
4use proc_macro2::TokenTree;
5
6use quote::ToTokens;
7use quote::quote;
8
9use syn::Token;
10use syn::parse::Parse;
11use syn::parse::ParseStream;
12
13use crate::template::Template;
14
15use crate::Expand;
16use crate::pascal;
17
18/// A `#[zyn::element]` component invocation.
19///
20/// ```text
21/// @my_getter(name = field.ident.clone(), ty = field.ty.clone())
22/// @wrapper(title = "hello") { nested content }
23/// ```
24///
25/// At expand time the name is PascalCase-converted, a struct literal is constructed
26/// with the given props, and [`crate::Render::render`] is called on it.
27pub struct ElementNode {
28    /// Source span of the `@` token.
29    pub span: Span,
30    /// The element name (may include `::` path separators).
31    pub name: TokenStream,
32    /// Named props as `(name, value)` pairs from `(prop = val, ...)`.
33    pub props: Vec<(syn::Ident, TokenStream)>,
34    /// Optional children block passed to the element's `children` field.
35    pub children: Option<Box<Template>>,
36}
37
38impl ElementNode {
39    pub fn span(&self) -> Span {
40        self.span
41    }
42
43    pub fn parse_with_ident(input: ParseStream, first_ident: syn::Ident) -> syn::Result<Self> {
44        let span = first_ident.span();
45
46        let mut name = TokenStream::new();
47        first_ident.to_tokens(&mut name);
48
49        while input.peek(Token![::]) {
50            let colons: Token![::] = input.parse()?;
51            colons.to_tokens(&mut name);
52            let segment: syn::Ident = input.parse()?;
53            segment.to_tokens(&mut name);
54        }
55
56        parse_props_and_children(input, span, name)
57    }
58}
59
60impl Expand for ElementNode {
61    fn expand(&self, output: &Ident, idents: &mut crate::ident::Iter) -> TokenStream {
62        let name = pascal!(self.name => token_stream);
63        let prop_names: Vec<&syn::Ident> = self.props.iter().map(|(n, _)| n).collect();
64        let prop_values: Vec<&TokenStream> = self.props.iter().map(|(_, v)| v).collect();
65
66        if let Some(children) = &self.children {
67            let inner = idents.next().unwrap();
68            let children_expanded = children.expand(&inner, idents);
69
70            quote! {
71                {
72                    let mut #inner = ::zyn::proc_macro2::TokenStream::new();
73                    #children_expanded
74                    let __zyn_rendered = ::zyn::Render::render(&#name {
75                        #(#prop_names: #prop_values,)*
76                        children: #inner,
77                    }, &::zyn::Input::from(input.clone()));
78                    ::zyn::quote::ToTokens::to_tokens(&__zyn_rendered, &mut #output);
79                }
80            }
81        } else {
82            quote! {
83                {
84                    let __zyn_rendered = ::zyn::Render::render(&#name {
85                        #(#prop_names: #prop_values,)*
86                    }, &::zyn::Input::from(input.clone()));
87                    ::zyn::quote::ToTokens::to_tokens(&__zyn_rendered, &mut #output);
88                }
89            }
90        }
91    }
92}
93
94impl Parse for ElementNode {
95    fn parse(input: ParseStream) -> syn::Result<Self> {
96        let first_ident: syn::Ident = input.parse()?;
97        let span = first_ident.span();
98
99        let mut name = TokenStream::new();
100        first_ident.to_tokens(&mut name);
101
102        while input.peek(Token![::]) {
103            let colons: Token![::] = input.parse()?;
104            colons.to_tokens(&mut name);
105            let segment: syn::Ident = input.parse()?;
106            segment.to_tokens(&mut name);
107        }
108
109        parse_props_and_children(input, span, name)
110    }
111}
112
113fn parse_props_and_children(
114    input: ParseStream,
115    span: Span,
116    name: TokenStream,
117) -> syn::Result<ElementNode> {
118    let mut props = Vec::new();
119
120    if input.peek(syn::token::Paren) {
121        let props_content;
122        syn::parenthesized!(props_content in input);
123
124        while !props_content.is_empty() {
125            let prop_name: syn::Ident = props_content.parse()?;
126            props_content.parse::<Token![=]>()?;
127
128            let mut value = TokenStream::new();
129
130            while !props_content.is_empty() && !props_content.peek(Token![,]) {
131                let tt: TokenTree = props_content.parse()?;
132                tt.to_tokens(&mut value);
133            }
134
135            props.push((prop_name, value));
136
137            if props_content.peek(Token![,]) {
138                props_content.parse::<Token![,]>()?;
139            }
140        }
141    }
142
143    let children = if input.peek(syn::token::Brace) {
144        let children_content;
145        syn::braced!(children_content in input);
146        Some(Box::new(children_content.parse::<Template>()?))
147    } else {
148        None
149    };
150
151    Ok(ElementNode {
152        span,
153        name,
154        props,
155        children,
156    })
157}