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
123impl From<TokensNode> for Node {
124    fn from(v: TokensNode) -> Self {
125        Self::Tokens(v)
126    }
127}
128
129impl From<InterpNode> for Node {
130    fn from(v: InterpNode) -> Self {
131        Self::Interp(v)
132    }
133}
134
135impl From<AtNode> for Node {
136    fn from(v: AtNode) -> Self {
137        Self::At(v)
138    }
139}
140
141impl From<GroupNode> for Node {
142    fn from(v: GroupNode) -> Self {
143        Self::Group(v)
144    }
145}
146
147impl Node {
148    pub fn parse_brace(input: ParseStream) -> syn::Result<Self> {
149        let content;
150        let brace = syn::braced!(content in input);
151        let span = brace.span.join();
152
153        let fork = content.fork();
154        let is_interp = if fork.peek(syn::token::Brace) {
155            let inner;
156            syn::braced!(inner in fork);
157            fork.is_empty()
158        } else {
159            false
160        };
161
162        if is_interp {
163            let inner;
164            syn::braced!(inner in content);
165
166            let mut expr = TokenStream::new();
167            let mut pipes = Vec::new();
168
169            while !inner.is_empty() {
170                if inner.peek(Token![|]) {
171                    inner.parse::<Token![|]>()?;
172                    pipes.push(inner.parse::<PipeNode>()?);
173                } else if pipes.is_empty() {
174                    let tt: TokenTree = inner.parse()?;
175                    tt.to_tokens(&mut expr);
176                } else {
177                    break;
178                }
179            }
180
181            if expr.is_empty() {
182                return Err(syn::Error::new(span, "empty interpolation"));
183            }
184
185            Ok(InterpNode { span, expr, pipes }.into())
186        } else {
187            let body = content.parse::<crate::template::Template>()?;
188            Ok(GroupNode {
189                span,
190                delimiter: proc_macro2::Delimiter::Brace,
191                body: Box::new(body),
192            }
193            .into())
194        }
195    }
196}
197
198impl Expand for Node {
199    fn expand(&self, output: &Ident, idents: &mut ident::Iter) -> TokenStream {
200        match self {
201            Self::Tokens(v) => v.expand(output, idents),
202            Self::Interp(v) => v.expand(output, idents),
203            Self::At(v) => v.expand(output, idents),
204            Self::Group(v) => v.expand(output, idents),
205        }
206    }
207}