dioxus_rsx/
ifchain.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
use crate::location::DynIdx;
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use quote::{ToTokens, TokenStreamExt};
use syn::{
    parse::{Parse, ParseStream},
    token::Brace,
    Expr, Result, Token,
};

use crate::TemplateBody;

#[non_exhaustive]
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct IfChain {
    pub if_token: Token![if],
    pub cond: Box<Expr>,
    pub then_brace: Brace,
    pub then_branch: TemplateBody,
    pub else_if_branch: Option<Box<IfChain>>,
    pub else_brace: Option<Brace>,
    pub else_branch: Option<TemplateBody>,
    pub dyn_idx: DynIdx,
}

impl IfChain {
    pub fn for_each_branch(&self, f: &mut impl FnMut(&TemplateBody)) {
        f(&self.then_branch);

        if let Some(else_if) = &self.else_if_branch {
            else_if.for_each_branch(f);
        }

        if let Some(else_branch) = &self.else_branch {
            f(else_branch);
        }
    }
}

impl Parse for IfChain {
    fn parse(input: ParseStream) -> Result<Self> {
        let if_token: Token![if] = input.parse()?;

        // stolen from ExprIf
        let cond = Box::new(input.call(Expr::parse_without_eager_brace)?);

        let content;
        let then_brace = syn::braced!(content in input);

        let then_branch = content.parse()?;

        let mut else_brace = None;
        let mut else_branch = None;
        let mut else_if_branch = None;

        // if the next token is `else`, set the else branch as the next if chain
        if input.peek(Token![else]) {
            input.parse::<Token![else]>()?;
            if input.peek(Token![if]) {
                else_if_branch = Some(Box::new(input.parse::<IfChain>()?));
            } else {
                let content;
                else_brace = Some(syn::braced!(content in input));
                else_branch = Some(content.parse()?);
            }
        }

        Ok(Self {
            cond,
            if_token,
            then_branch,
            else_if_branch,
            else_branch,
            then_brace,
            else_brace,
            dyn_idx: DynIdx::default(),
        })
    }
}

impl ToTokens for IfChain {
    fn to_tokens(&self, tokens: &mut TokenStream2) {
        let mut body = TokenStream2::new();
        let mut terminated = false;

        let mut elif = Some(self);

        while let Some(chain) = elif {
            let IfChain {
                if_token,
                cond,
                then_branch,
                else_if_branch,
                else_branch,
                ..
            } = chain;

            body.append_all(quote! {
                #if_token #cond {
                    { #then_branch }
                }
            });

            if let Some(next) = else_if_branch {
                body.append_all(quote! {
                    else
                });
                elif = Some(next);
            } else if let Some(else_branch) = else_branch {
                body.append_all(quote! {
                    else {
                        {#else_branch}
                    }
                });
                terminated = true;
                break;
            } else {
                elif = None;
            }
        }

        if !terminated {
            body.append_all(quote! {
                else { dioxus_core::VNode::empty() }
            });
        }

        tokens.append_all(quote! {
            {
                let ___nodes = (#body).into_dyn_node();
                ___nodes
            }
        })
    }
}

#[test]
fn parses_if_chain() {
    let input = quote! {
        if true {
            "one"
        } else if false {
            "two"
        } else {
            "three"
        }
    };

    let _chain: IfChain = syn::parse2(input).unwrap();
}