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    /// Returns a flat `TokenStream` representing the template's output pattern.
59    /// Unlike [`to_token_stream`](Self::to_token_stream), which produces builder code,
60    /// this produces the structural output: literal tokens pass through, interpolations
61    /// emit their expression or a substituted value, and control flow emits its body structure.
62    ///
63    /// `injections` is a slice of `(key, tokens)` pairs. When an interpolation expression
64    /// matches a key, the corresponding tokens are substituted. Unmatched interpolations
65    /// render as `{{ expr }}` placeholders.
66    pub fn to_display_stream(&self, injections: &[(String, TokenStream)]) -> TokenStream {
67        let mut result = TokenStream::new();
68        for node in &self.nodes {
69            result.extend(node.to_display_stream(injections));
70        }
71        result
72    }
73
74    /// Expands the template into a `TokenStream` without an `Input` binding.
75    pub fn to_token_stream(&self) -> TokenStream {
76        let mut idents = ident::Iter::new();
77        let output = idents.next().unwrap();
78        let expanded = self.expand(&output, &mut idents);
79
80        quote! {
81            {
82                let mut #output = ::zyn::proc_macro2::TokenStream::new();
83                let mut __zyn_diagnostic = ::zyn::mark::new();
84                #expanded
85                ::zyn::Output::new()
86                    .tokens(#output)
87                    .diagnostic(__zyn_diagnostic)
88                    .build()
89            }
90        }
91    }
92
93    /// Expands the template with the given `Input` bound as `input` in the generated code.
94    pub fn render(&self, input: &Input) -> TokenStream {
95        let expanded = self.to_token_stream();
96        quote! {
97            {
98                let input: ::zyn::Input = ::zyn::parse!(#input).unwrap();
99                #expanded
100            }
101        }
102    }
103
104    fn flush(pending: &mut TokenStream, nodes: &mut Vec<Node>) {
105        if pending.is_empty() {
106            return;
107        }
108
109        let stream = std::mem::take(pending);
110        let span = stream
111            .clone()
112            .into_iter()
113            .next()
114            .map(|tt| tt.span())
115            .unwrap_or_else(Span::call_site);
116
117        nodes.push(TokensNode { span, stream }.into());
118    }
119}
120
121impl Expand for Template {
122    fn expand(&self, output: &Ident, idents: &mut ident::Iter) -> TokenStream {
123        let mut result = TokenStream::new();
124
125        for node in &self.nodes {
126            result.extend(node.expand(output, idents));
127        }
128
129        result
130    }
131}
132
133impl Parse for Template {
134    fn parse(input: ParseStream) -> syn::Result<Self> {
135        let mut nodes = Vec::new();
136        let mut pending = TokenStream::new();
137
138        while !input.is_empty() {
139            if input.peek(Token![@]) {
140                Self::flush(&mut pending, &mut nodes);
141                nodes.push(input.parse::<AtNode>()?.into());
142            } else if input.peek(syn::token::Brace) {
143                Self::flush(&mut pending, &mut nodes);
144                nodes.push(Node::parse_brace(input)?);
145            } else if input.peek(syn::token::Paren) || input.peek(syn::token::Bracket) {
146                Self::flush(&mut pending, &mut nodes);
147                nodes.push(input.parse::<GroupNode>()?.into());
148            } else {
149                let tt: TokenTree = input.parse()?;
150                tt.to_tokens(&mut pending);
151            }
152        }
153
154        Self::flush(&mut pending, &mut nodes);
155        Ok(Self { nodes })
156    }
157}