dioxus_rsx/
ifchain.rs

1use crate::location::DynIdx;
2use proc_macro2::TokenStream as TokenStream2;
3use quote::quote;
4use quote::{ToTokens, TokenStreamExt};
5use syn::{
6    parse::{Parse, ParseStream},
7    token::Brace,
8    Expr, Result, Token,
9};
10
11use crate::TemplateBody;
12
13#[non_exhaustive]
14#[derive(PartialEq, Eq, Clone, Debug)]
15pub struct IfChain {
16    pub if_token: Token![if],
17    pub cond: Box<Expr>,
18    pub then_brace: Brace,
19    pub then_branch: TemplateBody,
20    pub else_if_branch: Option<Box<IfChain>>,
21    pub else_brace: Option<Brace>,
22    pub else_branch: Option<TemplateBody>,
23    pub dyn_idx: DynIdx,
24}
25
26impl IfChain {
27    pub fn for_each_branch(&self, f: &mut impl FnMut(&TemplateBody)) {
28        f(&self.then_branch);
29
30        if let Some(else_if) = &self.else_if_branch {
31            else_if.for_each_branch(f);
32        }
33
34        if let Some(else_branch) = &self.else_branch {
35            f(else_branch);
36        }
37    }
38}
39
40impl Parse for IfChain {
41    fn parse(input: ParseStream) -> Result<Self> {
42        let if_token: Token![if] = input.parse()?;
43
44        // stolen from ExprIf
45        let cond = Box::new(input.call(Expr::parse_without_eager_brace)?);
46
47        let content;
48        let then_brace = syn::braced!(content in input);
49
50        let then_branch = content.parse()?;
51
52        let mut else_brace = None;
53        let mut else_branch = None;
54        let mut else_if_branch = None;
55
56        // if the next token is `else`, set the else branch as the next if chain
57        if input.peek(Token![else]) {
58            input.parse::<Token![else]>()?;
59            if input.peek(Token![if]) {
60                else_if_branch = Some(Box::new(input.parse::<IfChain>()?));
61            } else {
62                let content;
63                else_brace = Some(syn::braced!(content in input));
64                else_branch = Some(content.parse()?);
65            }
66        }
67
68        Ok(Self {
69            cond,
70            if_token,
71            then_branch,
72            else_if_branch,
73            else_branch,
74            then_brace,
75            else_brace,
76            dyn_idx: DynIdx::default(),
77        })
78    }
79}
80
81impl ToTokens for IfChain {
82    fn to_tokens(&self, tokens: &mut TokenStream2) {
83        let mut body = TokenStream2::new();
84        let mut terminated = false;
85
86        let mut elif = Some(self);
87
88        while let Some(chain) = elif {
89            let IfChain {
90                if_token,
91                cond,
92                then_branch,
93                else_if_branch,
94                else_branch,
95                ..
96            } = chain;
97
98            body.append_all(quote! {
99                #if_token #cond {
100                    { #then_branch }
101                }
102            });
103
104            if let Some(next) = else_if_branch {
105                body.append_all(quote! {
106                    else
107                });
108                elif = Some(next);
109            } else if let Some(else_branch) = else_branch {
110                body.append_all(quote! {
111                    else {
112                        {#else_branch}
113                    }
114                });
115                terminated = true;
116                break;
117            } else {
118                elif = None;
119            }
120        }
121
122        if !terminated {
123            body.append_all(quote! {
124                else { dioxus_core::VNode::empty() }
125            });
126        }
127
128        tokens.append_all(quote! {
129            {
130                let ___nodes = (#body).into_dyn_node();
131                ___nodes
132            }
133        })
134    }
135}
136
137#[test]
138fn parses_if_chain() {
139    let input = quote! {
140        if true {
141            "one"
142        } else if false {
143            "two"
144        } else {
145            "three"
146        }
147    };
148
149    let _chain: IfChain = syn::parse2(input).unwrap();
150}