Skip to main content

zyn_core/ast/
interp_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 super::PipeNode;
14
15use crate::Expand;
16
17/// An interpolation expression: `{{ expr }}` or `{{ expr | pipe | ... }}`.
18///
19/// The expression is evaluated at expand time and emitted into the output token
20/// stream, optionally transformed through a chain of [`PipeNode`]s.
21pub struct InterpNode {
22    /// Source span of the `{{ ... }}` delimiters.
23    pub span: Span,
24    /// The expression to interpolate, e.g. `name` or `field.ty`.
25    pub expr: TokenStream,
26    /// Pipe transforms applied in order, e.g. `[snake, ident:"get_{}"]`.
27    pub pipes: Vec<PipeNode>,
28}
29
30impl InterpNode {
31    pub fn span(&self) -> Span {
32        self.span
33    }
34}
35
36impl Parse for InterpNode {
37    fn parse(input: ParseStream) -> syn::Result<Self> {
38        let content;
39        let brace = syn::braced!(content in input);
40        let span = brace.span.join();
41
42        let inner;
43        syn::braced!(inner in content);
44
45        let mut expr = TokenStream::new();
46        let mut pipes = Vec::new();
47
48        while !inner.is_empty() {
49            if inner.peek(Token![|]) {
50                inner.parse::<Token![|]>()?;
51                pipes.push(inner.parse::<PipeNode>()?);
52            } else if pipes.is_empty() {
53                let tt: TokenTree = inner.parse()?;
54                tt.to_tokens(&mut expr);
55            } else {
56                break;
57            }
58        }
59
60        if expr.is_empty() {
61            return Err(syn::Error::new(span, "empty interpolation"));
62        }
63
64        Ok(Self { span, expr, pipes })
65    }
66}
67
68impl Expand for InterpNode {
69    fn expand(&self, output: &Ident, idents: &mut crate::ident::Iter) -> TokenStream {
70        let expr = &self.expr;
71
72        if self.pipes.is_empty() {
73            return quote! {
74                ::zyn::quote::ToTokens::to_tokens(&(#expr), &mut #output);
75            };
76        }
77
78        let mut steps = vec![quote! { let __zyn_val = (#expr).to_string(); }];
79
80        for (i, pipe) in self.pipes.iter().enumerate() {
81            if i > 0 {
82                steps.push(quote! { let __zyn_val = __zyn_val.to_string(); });
83            }
84
85            steps.push(pipe.expand(output, idents));
86        }
87
88        quote! {
89            {
90                #(#steps)*
91                ::zyn::quote::ToTokens::to_tokens(&__zyn_val, &mut #output);
92            }
93        }
94    }
95}