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()` and `render(&input)` produce proc macro output code —
20//! `TokenStream` that, when returned from a proc macro, generates the expanded
21//! template in the user's crate. `render` 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                #expanded
68                #output
69            }
70        }
71    }
72
73    /// Expands the template with the given `Input` bound as `input` in the generated code.
74    pub fn render(&self, input: &Input) -> TokenStream {
75        let expanded = self.to_token_stream();
76        quote! {
77            {
78                let input: ::zyn::Input = ::zyn::parse!(#input).unwrap();
79                #expanded
80            }
81        }
82    }
83
84    fn flush(pending: &mut TokenStream, nodes: &mut Vec<Node>) {
85        if pending.is_empty() {
86            return;
87        }
88
89        let stream = std::mem::take(pending);
90        let span = stream
91            .clone()
92            .into_iter()
93            .next()
94            .map(|tt| tt.span())
95            .unwrap_or_else(Span::call_site);
96
97        nodes.push(TokensNode { span, stream }.into());
98    }
99}
100
101impl Expand for Template {
102    fn expand(&self, output: &Ident, idents: &mut ident::Iter) -> TokenStream {
103        let mut result = TokenStream::new();
104
105        for node in &self.nodes {
106            result.extend(node.expand(output, idents));
107        }
108
109        result
110    }
111}
112
113impl Parse for Template {
114    fn parse(input: ParseStream) -> syn::Result<Self> {
115        let mut nodes = Vec::new();
116        let mut pending = TokenStream::new();
117
118        while !input.is_empty() {
119            if input.peek(Token![@]) {
120                Self::flush(&mut pending, &mut nodes);
121                nodes.push(input.parse::<AtNode>()?.into());
122            } else if input.peek(syn::token::Brace) {
123                Self::flush(&mut pending, &mut nodes);
124                nodes.push(Node::parse_brace(input)?);
125            } else if input.peek(syn::token::Paren) || input.peek(syn::token::Bracket) {
126                Self::flush(&mut pending, &mut nodes);
127                nodes.push(input.parse::<GroupNode>()?.into());
128            } else {
129                let tt: TokenTree = input.parse()?;
130                tt.to_tokens(&mut pending);
131            }
132        }
133
134        Self::flush(&mut pending, &mut nodes);
135        Ok(Self { nodes })
136    }
137}