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                    let (__zyn_tokens, __zyn_diag) = __zyn_rendered.into_parts();
79                    #output.extend(__zyn_tokens);
80                    __zyn_diagnostic = __zyn_diagnostic.add(__zyn_diag);
81                }
82            }
83        } else {
84            quote! {
85                {
86                    let __zyn_rendered = ::zyn::Render::render(&#name {
87                        #(#prop_names: #prop_values,)*
88                    }, &::zyn::Input::from(input.clone()));
89                    let (__zyn_tokens, __zyn_diag) = __zyn_rendered.into_parts();
90                    #output.extend(__zyn_tokens);
91                    __zyn_diagnostic = __zyn_diagnostic.add(__zyn_diag);
92                }
93            }
94        }
95    }
96}
97
98impl Parse for ElementNode {
99    fn parse(input: ParseStream) -> syn::Result<Self> {
100        let first_ident: syn::Ident = input.parse()?;
101        let span = first_ident.span();
102
103        let mut name = TokenStream::new();
104        first_ident.to_tokens(&mut name);
105
106        while input.peek(Token![::]) {
107            let colons: Token![::] = input.parse()?;
108            colons.to_tokens(&mut name);
109            let segment: syn::Ident = input.parse()?;
110            segment.to_tokens(&mut name);
111        }
112
113        parse_props_and_children(input, span, name)
114    }
115}
116
117fn parse_props_and_children(
118    input: ParseStream,
119    span: Span,
120    name: TokenStream,
121) -> syn::Result<ElementNode> {
122    let mut props = Vec::new();
123
124    if input.peek(syn::token::Paren) {
125        let props_content;
126        syn::parenthesized!(props_content in input);
127
128        while !props_content.is_empty() {
129            let prop_name: syn::Ident = props_content.parse()?;
130            props_content.parse::<Token![=]>()?;
131
132            let mut value = TokenStream::new();
133
134            while !props_content.is_empty() && !props_content.peek(Token![,]) {
135                let tt: TokenTree = props_content.parse()?;
136                tt.to_tokens(&mut value);
137            }
138
139            props.push((prop_name, value));
140
141            if props_content.peek(Token![,]) {
142                props_content.parse::<Token![,]>()?;
143            }
144        }
145    }
146
147    let children = if input.peek(syn::token::Brace) {
148        let children_content;
149        syn::braced!(children_content in input);
150        Some(Box::new(children_content.parse::<Template>()?))
151    } else {
152        None
153    };
154
155    Ok(ElementNode {
156        span,
157        name,
158        props,
159        children,
160    })
161}