Skip to main content

zyn_core/
template.rs

1//! Template parsing and expansion.
2//!
3//! In normal use, the `zyn!` proc macro compiles templates at macro-expansion time.
4//! [`Template`] is primarily useful for inspecting the parsed AST or building
5//! templates dynamically.
6//!
7//! # Examples
8//!
9//! Parsing a template and inspecting its nodes:
10//!
11//! ```ignore
12//! use zyn_core::template::Template;
13//!
14//! let tmpl: Template = syn::parse_str("fn {{ name }}() {}").unwrap();
15//! // tmpl.nodes → [Tokens("fn"), Interp(name), Tokens("() {}")]
16//! assert_eq!(tmpl.nodes.len(), 3);
17//! ```
18//!
19//! `to_token_stream()` produces proc macro output code that evaluates to an
20//! [`Output`](crate::Output) carrying both the generated tokens and any diagnostics
21//! from nested element renders. `render(&input)` additionally binds `input` in scope
22//! so templates can reference it directly.
23
24use proc_macro2::Ident;
25use proc_macro2::Span;
26use proc_macro2::TokenStream;
27use proc_macro2::TokenTree;
28
29use quote::ToTokens;
30use quote::quote;
31
32use syn::Token;
33use syn::parse::Parse;
34use syn::parse::ParseStream;
35
36use crate::Expand;
37use crate::ast::AtNode;
38use crate::ast::GroupNode;
39use crate::ast::Node;
40use crate::ast::TokensNode;
41use crate::ident;
42use crate::types::Input;
43
44/// A parsed template AST. Created by parsing template syntax via `syn::parse2::<Template>(tokens)`.
45pub struct Template {
46    pub nodes: Vec<Node>,
47}
48
49impl Template {
50    /// Returns the source span of the first node, or `Span::call_site()` if empty.
51    pub fn span(&self) -> Span {
52        self.nodes
53            .first()
54            .map(|n| n.span())
55            .unwrap_or_else(Span::call_site)
56    }
57
58    /// Expands the template into a `TokenStream` without an `Input` binding.
59    pub fn to_token_stream(&self) -> TokenStream {
60        let mut idents = ident::Iter::new();
61        let output = idents.next().unwrap();
62        let expanded = self.expand(&output, &mut idents);
63
64        quote! {
65            {
66                let mut #output = ::zyn::proc_macro2::TokenStream::new();
67                let mut __zyn_diagnostic = ::zyn::mark::new();
68                #expanded
69                ::zyn::Output::new()
70                    .tokens(#output)
71                    .diagnostic(__zyn_diagnostic)
72                    .build()
73            }
74        }
75    }
76
77    /// Expands the template with the given `Input` bound as `input` in the generated code.
78    pub fn render(&self, input: &Input) -> TokenStream {
79        let expanded = self.to_token_stream();
80        quote! {
81            {
82                let input: ::zyn::Input = ::zyn::parse!(#input).unwrap();
83                #expanded
84            }
85        }
86    }
87
88    fn flush(pending: &mut TokenStream, nodes: &mut Vec<Node>) {
89        if pending.is_empty() {
90            return;
91        }
92
93        let stream = std::mem::take(pending);
94        let span = stream
95            .clone()
96            .into_iter()
97            .next()
98            .map(|tt| tt.span())
99            .unwrap_or_else(Span::call_site);
100
101        nodes.push(TokensNode { span, stream }.into());
102    }
103}
104
105impl Expand for Template {
106    fn expand(&self, output: &Ident, idents: &mut ident::Iter) -> TokenStream {
107        let mut result = TokenStream::new();
108
109        for node in &self.nodes {
110            result.extend(node.expand(output, idents));
111        }
112
113        result
114    }
115}
116
117impl Parse for Template {
118    fn parse(input: ParseStream) -> syn::Result<Self> {
119        let mut nodes = Vec::new();
120        let mut pending = TokenStream::new();
121
122        while !input.is_empty() {
123            if input.peek(Token![@]) {
124                Self::flush(&mut pending, &mut nodes);
125                nodes.push(input.parse::<AtNode>()?.into());
126            } else if input.peek(syn::token::Brace) {
127                Self::flush(&mut pending, &mut nodes);
128                nodes.push(Node::parse_brace(input)?);
129            } else if input.peek(syn::token::Paren) || input.peek(syn::token::Bracket) {
130                Self::flush(&mut pending, &mut nodes);
131                nodes.push(input.parse::<GroupNode>()?.into());
132            } else {
133                let tt: TokenTree = input.parse()?;
134                tt.to_tokens(&mut pending);
135            }
136        }
137
138        Self::flush(&mut pending, &mut nodes);
139        Ok(Self { nodes })
140    }
141}