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    pub fn to_display_stream(&self, injections: &[(String, TokenStream)]) -> TokenStream {
43        let mut result = TokenStream::new();
44
45        for (i, (cond, body)) in self.branches.iter().enumerate() {
46            let body_display = body.to_display_stream(injections);
47
48            if i == 0 {
49                result = quote! { if #cond { #body_display } };
50            } else {
51                result = quote! { #result else if #cond { #body_display } };
52            }
53        }
54
55        if let Some(else_body) = &self.else_body {
56            let else_display = else_body.to_display_stream(injections);
57            result = quote! { #result else { #else_display } };
58        }
59
60        result
61    }
62}
63
64impl Parse for IfNode {
65    fn parse(input: ParseStream) -> syn::Result<Self> {
66        let mut branches = Vec::new();
67        let mut else_body = None;
68
69        let cond_content;
70        syn::parenthesized!(cond_content in input);
71        let condition: TokenStream = cond_content.parse()?;
72
73        let body_content;
74        syn::braced!(body_content in input);
75        let body = body_content.parse::<Template>()?;
76
77        branches.push((condition, body));
78
79        while is_at_else(input) {
80            input.parse::<Token![@]>()?;
81            input.call(syn::Ident::parse_any)?;
82
83            if is_keyword_if(input) {
84                input.call(syn::Ident::parse_any)?;
85
86                let cond_content;
87                syn::parenthesized!(cond_content in input);
88                let condition: TokenStream = cond_content.parse()?;
89
90                let body_content;
91                syn::braced!(body_content in input);
92                let body = body_content.parse::<Template>()?;
93
94                branches.push((condition, body));
95            } else {
96                let body_content;
97                syn::braced!(body_content in input);
98                else_body = Some(Box::new(body_content.parse::<Template>()?));
99                break;
100            }
101        }
102
103        Ok(Self {
104            span: Span::call_site(),
105            branches,
106            else_body,
107        })
108    }
109}
110
111impl Expand for IfNode {
112    fn expand(&self, output: &Ident, idents: &mut crate::ident::Iter) -> TokenStream {
113        let mut result = TokenStream::new();
114
115        for (i, (cond, body)) in self.branches.iter().enumerate() {
116            let body_expanded = body.expand(output, idents);
117
118            if i == 0 {
119                result = quote! { if #cond { #body_expanded }};
120            } else {
121                result = quote! { #result else if #cond { #body_expanded }};
122            }
123        }
124
125        if let Some(else_body) = &self.else_body {
126            let else_expanded = else_body.expand(output, idents);
127            result = quote! { #result else { #else_expanded }};
128        }
129
130        result
131    }
132}
133
134fn is_at_else(input: ParseStream) -> bool {
135    if !input.peek(Token![@]) {
136        return false;
137    }
138
139    let fork = input.fork();
140    let _ = fork.parse::<Token![@]>();
141
142    match fork.call(syn::Ident::parse_any) {
143        Ok(ident) => ident == "else",
144        Err(_) => false,
145    }
146}
147
148fn is_keyword_if(input: ParseStream) -> bool {
149    let fork = input.fork();
150
151    match fork.call(syn::Ident::parse_any) {
152        Ok(ident) => ident == "if",
153        Err(_) => false,
154    }
155}