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    pub fn to_display_stream(&self, injections: &[(String, TokenStream)]) -> TokenStream {
36        let key = self.expr.to_string();
37
38        if let Some((_, val)) = injections.iter().find(|(k, _)| *k == key) {
39            if self.pipes.is_empty() {
40                return val.clone();
41            }
42
43            let mut s = val.to_string();
44
45            for pipe in &self.pipes {
46                s = pipe.apply_display(s);
47            }
48
49            return syn::parse_str::<TokenStream>(&s).unwrap_or_else(|_| val.clone());
50        }
51
52        // fallback: render as {{ expr }} or {{ expr | pipe }} placeholder
53        let expr = &self.expr;
54
55        if self.pipes.is_empty() {
56            quote::quote! { {{ #expr }} }
57        } else {
58            let pipe_tokens: TokenStream = self
59                .pipes
60                .iter()
61                .flat_map(|p| {
62                    let name = &p.name;
63                    std::iter::once(quote::quote! { | #name })
64                })
65                .collect();
66
67            quote::quote! { {{ #expr #pipe_tokens }} }
68        }
69    }
70}
71
72impl Parse for InterpNode {
73    fn parse(input: ParseStream) -> syn::Result<Self> {
74        let content;
75        let brace = syn::braced!(content in input);
76        let span = brace.span.join();
77
78        let inner;
79        syn::braced!(inner in content);
80
81        let mut expr = TokenStream::new();
82        let mut pipes = Vec::new();
83
84        while !inner.is_empty() {
85            if inner.peek(Token![|]) {
86                inner.parse::<Token![|]>()?;
87                pipes.push(inner.parse::<PipeNode>()?);
88            } else if pipes.is_empty() {
89                let tt: TokenTree = inner.parse()?;
90                tt.to_tokens(&mut expr);
91            } else {
92                break;
93            }
94        }
95
96        if expr.is_empty() {
97            return Err(syn::Error::new(span, "empty interpolation"));
98        }
99
100        Ok(Self { span, expr, pipes })
101    }
102}
103
104impl Expand for InterpNode {
105    fn expand(&self, output: &Ident, idents: &mut crate::ident::Iter) -> TokenStream {
106        let expr = &self.expr;
107
108        if self.pipes.is_empty() {
109            return quote! {
110                ::zyn::quote::ToTokens::to_tokens(&(#expr), &mut #output);
111            };
112        }
113
114        let mut steps = vec![quote! { let __zyn_val = (#expr).to_string(); }];
115
116        for (i, pipe) in self.pipes.iter().enumerate() {
117            if i > 0 {
118                steps.push(quote! { let __zyn_val = __zyn_val.to_string(); });
119            }
120
121            steps.push(pipe.expand(output, idents));
122        }
123
124        quote! {
125            {
126                #(#steps)*
127                ::zyn::quote::ToTokens::to_tokens(&__zyn_val, &mut #output);
128            }
129        }
130    }
131}