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 span = pending
90            .clone()
91            .into_iter()
92            .next()
93            .map(|tt| tt.span())
94            .unwrap_or_else(Span::call_site);
95
96        nodes.push(
97            TokensNode {
98                span,
99                stream: pending.clone(),
100            }
101            .into(),
102        );
103
104        *pending = TokenStream::new();
105    }
106}
107
108impl Expand for Template {
109    fn expand(&self, output: &Ident, idents: &mut ident::Iter) -> TokenStream {
110        let mut result = TokenStream::new();
111
112        for node in &self.nodes {
113            result.extend(node.expand(output, idents));
114        }
115
116        result
117    }
118}
119
120impl Parse for Template {
121    fn parse(input: ParseStream) -> syn::Result<Self> {
122        let mut nodes = Vec::new();
123        let mut pending = TokenStream::new();
124
125        while !input.is_empty() {
126            if input.peek(Token![@]) {
127                Self::flush(&mut pending, &mut nodes);
128                nodes.push(input.parse::<AtNode>()?.into());
129            } else if input.peek(syn::token::Brace) {
130                Self::flush(&mut pending, &mut nodes);
131                nodes.push(Node::parse_brace(input)?);
132            } else if input.peek(syn::token::Paren) || input.peek(syn::token::Bracket) {
133                Self::flush(&mut pending, &mut nodes);
134                nodes.push(input.parse::<GroupNode>()?.into());
135            } else {
136                let tt: TokenTree = input.parse()?;
137                tt.to_tokens(&mut pending);
138            }
139        }
140
141        Self::flush(&mut pending, &mut nodes);
142        Ok(Self { nodes })
143    }
144}