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    pub fn to_display_stream(&self, injections: &[(String, TokenStream)]) -> TokenStream {
116        match self {
117            Self::If(v) => v.to_display_stream(injections),
118            Self::For(v) => v.to_display_stream(injections),
119            Self::Match(v) => v.to_display_stream(injections),
120            Self::Element(v) => v.to_display_stream(injections),
121        }
122    }
123}
124
125impl From<IfNode> for AtNode {
126    fn from(v: IfNode) -> Self {
127        Self::If(v)
128    }
129}
130
131impl From<ForNode> for AtNode {
132    fn from(v: ForNode) -> Self {
133        Self::For(v)
134    }
135}
136
137impl From<MatchNode> for AtNode {
138    fn from(v: MatchNode) -> Self {
139        Self::Match(v)
140    }
141}
142
143impl From<ElementNode> for AtNode {
144    fn from(v: ElementNode) -> Self {
145        Self::Element(v)
146    }
147}
148
149impl Expand for AtNode {
150    fn expand(&self, output: &Ident, idents: &mut crate::ident::Iter) -> TokenStream {
151        match self {
152            Self::If(v) => v.expand(output, idents),
153            Self::For(v) => v.expand(output, idents),
154            Self::Match(v) => v.expand(output, idents),
155            Self::Element(v) => v.expand(output, idents),
156        }
157    }
158}
159
160impl Parse for AtNode {
161    fn parse(input: ParseStream) -> syn::Result<Self> {
162        let at_span = input.parse::<Token![@]>()?.span;
163        let ident: syn::Ident = input.call(syn::Ident::parse_any)?;
164        let name = ident.to_string();
165
166        match name.as_str() {
167            "if" => {
168                let mut v = input.parse::<IfNode>()?;
169                v.span = at_span;
170                Ok(v.into())
171            }
172            "for" => {
173                let mut v = input.parse::<ForNode>()?;
174                v.span = at_span;
175                Ok(v.into())
176            }
177            "match" => {
178                let mut v = input.parse::<MatchNode>()?;
179                v.span = at_span;
180                Ok(v.into())
181            }
182            "else" => Err(syn::Error::new(at_span, "unexpected @else without @if")),
183            _ => {
184                let mut v = ElementNode::parse_with_ident(input, ident)?;
185                v.span = at_span;
186                Ok(v.into())
187            }
188        }
189    }
190}