Skip to main content

zyn_core/ast/at/
mod.rs

1//! `@`-prefixed control-flow and component nodes.
2//!
3//! An [`AtNode`] is produced whenever the parser encounters `@` in a template.
4//! The keyword or identifier following `@` determines the variant:
5//!
6//! | Syntax | Variant |
7//! |--------|---------|
8//! | `@if (cond) { ... }` | [`AtNode::If`] |
9//! | `@for (x in iter) { ... }` | [`AtNode::For`] |
10//! | `@match (expr) { ... }` | [`AtNode::Match`] |
11//! | `@my_element(...)` | [`AtNode::Element`] |
12
13mod element_node;
14mod for_node;
15mod if_node;
16mod match_node;
17
18pub use element_node::ElementNode;
19pub use for_node::ForNode;
20pub use if_node::IfNode;
21pub use match_node::MatchNode;
22
23use proc_macro2::Ident;
24use proc_macro2::Span;
25use proc_macro2::TokenStream;
26
27use syn::Token;
28use syn::ext::IdentExt;
29use syn::parse::Parse;
30use syn::parse::ParseStream;
31
32use crate::Expand;
33
34/// An `@`-prefixed statement in a zyn template.
35///
36/// See the [`at`](crate::ast::at) module for the syntax of each variant.
37pub enum AtNode {
38    /// `@if (cond) { ... } @else if (...) { ... } @else { ... }`
39    If(IfNode),
40    /// `@for (item in expr) { ... }` or `@for (count) { ... }`
41    For(ForNode),
42    /// `@match (expr) { pattern => { ... }, ... }`
43    Match(MatchNode),
44    /// `@component_name(prop = val, ...) { children }`
45    Element(ElementNode),
46}
47
48impl AtNode {
49    /// Returns `true` if this is an [`AtNode::If`] variant.
50    pub fn is_if(&self) -> bool {
51        matches!(self, Self::If(_))
52    }
53
54    /// Returns `true` if this is an [`AtNode::For`] variant.
55    pub fn is_for(&self) -> bool {
56        matches!(self, Self::For(_))
57    }
58
59    /// Returns `true` if this is an [`AtNode::Match`] variant.
60    pub fn is_match(&self) -> bool {
61        matches!(self, Self::Match(_))
62    }
63
64    /// Returns `true` if this is an [`AtNode::Element`] variant.
65    pub fn is_element(&self) -> bool {
66        matches!(self, Self::Element(_))
67    }
68}
69
70impl AtNode {
71    /// Returns the inner [`IfNode`]. Panics if not an `If` variant.
72    pub fn as_if(&self) -> &IfNode {
73        match self {
74            Self::If(v) => v,
75            _ => panic!("called as_if on non-If node"),
76        }
77    }
78
79    /// Returns the inner [`ForNode`]. Panics if not a `For` variant.
80    pub fn as_for(&self) -> &ForNode {
81        match self {
82            Self::For(v) => v,
83            _ => panic!("called as_for on non-For node"),
84        }
85    }
86
87    /// Returns the inner [`MatchNode`]. Panics if not a `Match` variant.
88    pub fn as_match(&self) -> &MatchNode {
89        match self {
90            Self::Match(v) => v,
91            _ => panic!("called as_match on non-Match node"),
92        }
93    }
94
95    /// Returns the inner [`ElementNode`]. Panics if not an `Element` variant.
96    pub fn as_element(&self) -> &ElementNode {
97        match self {
98            Self::Element(v) => v,
99            _ => panic!("called as_element on non-Element node"),
100        }
101    }
102}
103
104impl AtNode {
105    /// Returns the source span of this node.
106    pub fn span(&self) -> Span {
107        match self {
108            Self::If(v) => v.span(),
109            Self::For(v) => v.span(),
110            Self::Match(v) => v.span(),
111            Self::Element(v) => v.span(),
112        }
113    }
114}
115
116impl From<IfNode> for AtNode {
117    fn from(v: IfNode) -> Self {
118        Self::If(v)
119    }
120}
121
122impl From<ForNode> for AtNode {
123    fn from(v: ForNode) -> Self {
124        Self::For(v)
125    }
126}
127
128impl From<MatchNode> for AtNode {
129    fn from(v: MatchNode) -> Self {
130        Self::Match(v)
131    }
132}
133
134impl From<ElementNode> for AtNode {
135    fn from(v: ElementNode) -> Self {
136        Self::Element(v)
137    }
138}
139
140impl Expand for AtNode {
141    fn expand(&self, output: &Ident, idents: &mut crate::ident::Iter) -> TokenStream {
142        match self {
143            Self::If(v) => v.expand(output, idents),
144            Self::For(v) => v.expand(output, idents),
145            Self::Match(v) => v.expand(output, idents),
146            Self::Element(v) => v.expand(output, idents),
147        }
148    }
149}
150
151impl Parse for AtNode {
152    fn parse(input: ParseStream) -> syn::Result<Self> {
153        let at_span = input.parse::<Token![@]>()?.span;
154        let ident: syn::Ident = input.call(syn::Ident::parse_any)?;
155        let name = ident.to_string();
156
157        match name.as_str() {
158            "if" => {
159                let mut v = input.parse::<IfNode>()?;
160                v.span = at_span;
161                Ok(v.into())
162            }
163            "for" => {
164                let mut v = input.parse::<ForNode>()?;
165                v.span = at_span;
166                Ok(v.into())
167            }
168            "match" => {
169                let mut v = input.parse::<MatchNode>()?;
170                v.span = at_span;
171                Ok(v.into())
172            }
173            "else" => Err(syn::Error::new(at_span, "unexpected @else without @if")),
174            _ => {
175                let mut v = ElementNode::parse_with_ident(input, ident)?;
176                v.span = at_span;
177                Ok(v.into())
178            }
179        }
180    }
181}