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 to_display_stream(&self, injections: &[(String, TokenStream)]) -> TokenStream {
44        let name = pascal!(self.name => token_stream);
45        let prop_names: Vec<&syn::Ident> = self.props.iter().map(|(n, _)| n).collect();
46        let prop_values: Vec<&TokenStream> = self.props.iter().map(|(_, v)| v).collect();
47
48        if let Some(children) = &self.children {
49            let children_display = children.to_display_stream(injections);
50            quote! { #name { #(#prop_names: #prop_values,)* children: { #children_display } } }
51        } else {
52            quote! { #name { #(#prop_names: #prop_values,)* } }
53        }
54    }
55
56    pub fn parse_with_ident(input: ParseStream, first_ident: syn::Ident) -> syn::Result<Self> {
57        let span = first_ident.span();
58
59        let mut name = TokenStream::new();
60        first_ident.to_tokens(&mut name);
61
62        while input.peek(Token![::]) {
63            let colons: Token![::] = input.parse()?;
64            colons.to_tokens(&mut name);
65            let segment: syn::Ident = input.parse()?;
66            segment.to_tokens(&mut name);
67        }
68
69        parse_props_and_children(input, span, name)
70    }
71}
72
73impl Expand for ElementNode {
74    fn expand(&self, output: &Ident, idents: &mut crate::ident::Iter) -> TokenStream {
75        let name = pascal!(self.name => token_stream);
76        let prop_names: Vec<&syn::Ident> = self.props.iter().map(|(n, _)| n).collect();
77        let prop_values: Vec<&TokenStream> = self.props.iter().map(|(_, v)| v).collect();
78
79        if let Some(children) = &self.children {
80            let inner = idents.next().unwrap();
81            let children_expanded = children.expand(&inner, idents);
82
83            quote! {
84                {
85                    let mut #inner = ::zyn::proc_macro2::TokenStream::new();
86                    #children_expanded
87                    let __zyn_rendered = ::zyn::Render::render(&#name {
88                        #(#prop_names: #prop_values,)*
89                        children: #inner,
90                    }, &::zyn::Input::from(input.clone()));
91                    let (__zyn_tokens, __zyn_diag) = __zyn_rendered.into_parts();
92                    #output.extend(__zyn_tokens);
93                    __zyn_diagnostic = __zyn_diagnostic.add(__zyn_diag);
94                }
95            }
96        } else {
97            quote! {
98                {
99                    let __zyn_rendered = ::zyn::Render::render(&#name {
100                        #(#prop_names: #prop_values,)*
101                    }, &::zyn::Input::from(input.clone()));
102                    let (__zyn_tokens, __zyn_diag) = __zyn_rendered.into_parts();
103                    #output.extend(__zyn_tokens);
104                    __zyn_diagnostic = __zyn_diagnostic.add(__zyn_diag);
105                }
106            }
107        }
108    }
109}
110
111impl Parse for ElementNode {
112    fn parse(input: ParseStream) -> syn::Result<Self> {
113        let first_ident: syn::Ident = input.parse()?;
114        let span = first_ident.span();
115
116        let mut name = TokenStream::new();
117        first_ident.to_tokens(&mut name);
118
119        while input.peek(Token![::]) {
120            let colons: Token![::] = input.parse()?;
121            colons.to_tokens(&mut name);
122            let segment: syn::Ident = input.parse()?;
123            segment.to_tokens(&mut name);
124        }
125
126        parse_props_and_children(input, span, name)
127    }
128}
129
130fn parse_props_and_children(
131    input: ParseStream,
132    span: Span,
133    name: TokenStream,
134) -> syn::Result<ElementNode> {
135    let mut props = Vec::new();
136
137    if input.peek(syn::token::Paren) {
138        let props_content;
139        syn::parenthesized!(props_content in input);
140
141        while !props_content.is_empty() {
142            let prop_name: syn::Ident = props_content.parse()?;
143            props_content.parse::<Token![=]>()?;
144
145            let mut value = TokenStream::new();
146
147            while !props_content.is_empty() && !props_content.peek(Token![,]) {
148                let tt: TokenTree = props_content.parse()?;
149                tt.to_tokens(&mut value);
150            }
151
152            props.push((prop_name, value));
153
154            if props_content.peek(Token![,]) {
155                props_content.parse::<Token![,]>()?;
156            }
157        }
158    }
159
160    let children = if input.peek(syn::token::Brace) {
161        let children_content;
162        syn::braced!(children_content in input);
163        Some(Box::new(children_content.parse::<Template>()?))
164    } else {
165        None
166    };
167
168    Ok(ElementNode {
169        span,
170        name,
171        props,
172        children,
173    })
174}