Skip to main content

zyn_core/ast/at/
if_node.rs

1use proc_macro2::Ident;
2use proc_macro2::Span;
3use proc_macro2::TokenStream;
4
5use quote::quote;
6
7use syn::Token;
8use syn::ext::IdentExt;
9use syn::parse::Parse;
10use syn::parse::ParseStream;
11
12use crate::template::Template;
13
14use crate::Expand;
15
16/// An `@if` / `@else if` / `@else` conditional block.
17///
18/// ```text
19/// @if (condition) {
20///     // then branch
21/// } @else if (other) {
22///     // else-if branch
23/// } @else {
24///     // else branch
25/// }
26/// ```
27pub struct IfNode {
28    /// Source span of the `@` token.
29    pub span: Span,
30    /// One or more `(condition, body)` pairs. The first is the `@if` branch;
31    /// subsequent pairs are `@else if` branches.
32    pub branches: Vec<(TokenStream, Template)>,
33    /// Body of the trailing `@else` block, if present.
34    pub else_body: Option<Box<Template>>,
35}
36
37impl IfNode {
38    pub fn span(&self) -> Span {
39        self.span
40    }
41}
42
43impl Parse for IfNode {
44    fn parse(input: ParseStream) -> syn::Result<Self> {
45        let mut branches = Vec::new();
46        let mut else_body = None;
47
48        let cond_content;
49        syn::parenthesized!(cond_content in input);
50        let condition: TokenStream = cond_content.parse()?;
51
52        let body_content;
53        syn::braced!(body_content in input);
54        let body = body_content.parse::<Template>()?;
55
56        branches.push((condition, body));
57
58        while is_at_else(input) {
59            input.parse::<Token![@]>()?;
60            input.call(syn::Ident::parse_any)?;
61
62            if is_keyword_if(input) {
63                input.call(syn::Ident::parse_any)?;
64
65                let cond_content;
66                syn::parenthesized!(cond_content in input);
67                let condition: TokenStream = cond_content.parse()?;
68
69                let body_content;
70                syn::braced!(body_content in input);
71                let body = body_content.parse::<Template>()?;
72
73                branches.push((condition, body));
74            } else {
75                let body_content;
76                syn::braced!(body_content in input);
77                else_body = Some(Box::new(body_content.parse::<Template>()?));
78                break;
79            }
80        }
81
82        Ok(Self {
83            span: Span::call_site(),
84            branches,
85            else_body,
86        })
87    }
88}
89
90impl Expand for IfNode {
91    fn expand(&self, output: &Ident, idents: &mut crate::ident::Iter) -> TokenStream {
92        let mut result = TokenStream::new();
93
94        for (i, (cond, body)) in self.branches.iter().enumerate() {
95            let body_expanded = body.expand(output, idents);
96
97            if i == 0 {
98                result = quote! { if #cond { #body_expanded }};
99            } else {
100                result = quote! { #result else if #cond { #body_expanded }};
101            }
102        }
103
104        if let Some(else_body) = &self.else_body {
105            let else_expanded = else_body.expand(output, idents);
106            result = quote! { #result else { #else_expanded }};
107        }
108
109        result
110    }
111}
112
113fn is_at_else(input: ParseStream) -> bool {
114    if !input.peek(Token![@]) {
115        return false;
116    }
117
118    let fork = input.fork();
119    let _ = fork.parse::<Token![@]>();
120
121    match fork.call(syn::Ident::parse_any) {
122        Ok(ident) => ident == "else",
123        Err(_) => false,
124    }
125}
126
127fn is_keyword_if(input: ParseStream) -> bool {
128    let fork = input.fork();
129
130    match fork.call(syn::Ident::parse_any) {
131        Ok(ident) => ident == "if",
132        Err(_) => false,
133    }
134}