Skip to main content

zyn_core/ast/
mod.rs

1//! Template AST node types.
2//!
3//! A [`crate::template::Template`] is a flat sequence of [`Node`]s. The node type
4//! is determined by the template syntax:
5//!
6//! | Template syntax | Node variant |
7//! |----------------|-------------|
8//! | `fn foo() {}` (literal tokens) | [`Node::Tokens`] |
9//! | `{{ name \| snake }}` | [`Node::Interp`] |
10//! | `@if`, `@for`, `@match`, `@element` | [`Node::At`] |
11//! | `(...)`, `[...]`, `{...}` groups | [`Node::Group`] |
12//!
13//! These types are public for inspection but are not typically used directly —
14//! the `zyn!` macro handles parsing and expansion internally.
15
16pub mod at;
17mod group_node;
18mod interp_node;
19mod pipe_node;
20mod tokens_node;
21
22pub use at::*;
23pub use group_node::GroupNode;
24pub use interp_node::InterpNode;
25pub use pipe_node::PipeNode;
26pub use tokens_node::TokensNode;
27
28use proc_macro2::Ident;
29use proc_macro2::Span;
30use proc_macro2::TokenStream;
31use proc_macro2::TokenTree;
32
33use quote::ToTokens;
34
35use syn::Token;
36use syn::parse::ParseStream;
37
38use crate::Expand;
39use crate::ident;
40
41/// A single node in a parsed zyn template.
42///
43/// See the [`ast`](crate::ast) module for the full node taxonomy.
44pub enum Node {
45    /// Literal Rust tokens passed through unchanged.
46    Tokens(TokensNode),
47    /// An interpolation expression: `{{ expr }}` or `{{ expr | pipe | ... }}`.
48    Interp(InterpNode),
49    /// An `@`-prefixed control-flow or element statement.
50    At(AtNode),
51    /// A delimited group: `(...)`, `[...]`, or `{...}`.
52    Group(GroupNode),
53}
54
55impl Node {
56    /// Returns `true` if this is a [`Node::Tokens`] variant.
57    pub fn is_tokens(&self) -> bool {
58        matches!(self, Self::Tokens(_))
59    }
60
61    /// Returns `true` if this is a [`Node::Interp`] variant.
62    pub fn is_interp(&self) -> bool {
63        matches!(self, Self::Interp(_))
64    }
65
66    /// Returns `true` if this is a [`Node::At`] variant.
67    pub fn is_at(&self) -> bool {
68        matches!(self, Self::At(_))
69    }
70
71    /// Returns `true` if this is a [`Node::Group`] variant.
72    pub fn is_group(&self) -> bool {
73        matches!(self, Self::Group(_))
74    }
75}
76
77impl Node {
78    /// Returns the inner [`TokensNode`]. Panics if not a `Tokens` variant.
79    pub fn as_tokens(&self) -> &TokensNode {
80        match self {
81            Self::Tokens(v) => v,
82            _ => panic!("called as_tokens on non-Tokens node"),
83        }
84    }
85
86    /// Returns the inner [`InterpNode`]. Panics if not an `Interp` variant.
87    pub fn as_interp(&self) -> &InterpNode {
88        match self {
89            Self::Interp(v) => v,
90            _ => panic!("called as_interp on non-Interp node"),
91        }
92    }
93
94    /// Returns the inner [`AtNode`]. Panics if not an `At` variant.
95    pub fn as_at(&self) -> &AtNode {
96        match self {
97            Self::At(v) => v,
98            _ => panic!("called as_at on non-At node"),
99        }
100    }
101
102    /// Returns the inner [`GroupNode`]. Panics if not a `Group` variant.
103    pub fn as_group(&self) -> &GroupNode {
104        match self {
105            Self::Group(v) => v,
106            _ => panic!("called as_group on non-Group node"),
107        }
108    }
109}
110
111impl Node {
112    /// Returns the source span of this node.
113    pub fn span(&self) -> Span {
114        match self {
115            Self::Tokens(v) => v.span(),
116            Self::Interp(v) => v.span(),
117            Self::At(v) => v.span(),
118            Self::Group(v) => v.span(),
119        }
120    }
121
122    pub fn to_display_stream(&self, injections: &[(String, TokenStream)]) -> TokenStream {
123        match self {
124            Self::Tokens(v) => v.to_display_stream(injections),
125            Self::Interp(v) => v.to_display_stream(injections),
126            Self::At(v) => v.to_display_stream(injections),
127            Self::Group(v) => v.to_display_stream(injections),
128        }
129    }
130}
131
132impl From<TokensNode> for Node {
133    fn from(v: TokensNode) -> Self {
134        Self::Tokens(v)
135    }
136}
137
138impl From<InterpNode> for Node {
139    fn from(v: InterpNode) -> Self {
140        Self::Interp(v)
141    }
142}
143
144impl From<AtNode> for Node {
145    fn from(v: AtNode) -> Self {
146        Self::At(v)
147    }
148}
149
150impl From<GroupNode> for Node {
151    fn from(v: GroupNode) -> Self {
152        Self::Group(v)
153    }
154}
155
156impl Node {
157    pub fn parse_brace(input: ParseStream) -> syn::Result<Self> {
158        let content;
159        let brace = syn::braced!(content in input);
160        let span = brace.span.join();
161
162        let fork = content.fork();
163        let is_interp = if fork.peek(syn::token::Brace) {
164            let inner;
165            syn::braced!(inner in fork);
166            fork.is_empty()
167        } else {
168            false
169        };
170
171        if is_interp {
172            let inner;
173            syn::braced!(inner in content);
174
175            let mut expr = TokenStream::new();
176            let mut pipes = Vec::new();
177
178            while !inner.is_empty() {
179                if inner.peek(Token![|]) {
180                    inner.parse::<Token![|]>()?;
181                    pipes.push(inner.parse::<PipeNode>()?);
182                } else if pipes.is_empty() {
183                    let tt: TokenTree = inner.parse()?;
184                    tt.to_tokens(&mut expr);
185                } else {
186                    break;
187                }
188            }
189
190            if expr.is_empty() {
191                return Err(syn::Error::new(span, "empty interpolation"));
192            }
193
194            Ok(InterpNode { span, expr, pipes }.into())
195        } else {
196            let body = content.parse::<crate::template::Template>()?;
197            Ok(GroupNode {
198                span,
199                delimiter: proc_macro2::Delimiter::Brace,
200                body: Box::new(body),
201            }
202            .into())
203        }
204    }
205}
206
207impl Expand for Node {
208    fn expand(&self, output: &Ident, idents: &mut ident::Iter) -> TokenStream {
209        match self {
210            Self::Tokens(v) => v.expand(output, idents),
211            Self::Interp(v) => v.expand(output, idents),
212            Self::At(v) => v.expand(output, idents),
213            Self::Group(v) => v.expand(output, idents),
214        }
215    }
216}