compose_syntax/ast/
control_flow.rs

1use crate::ast::macros::node;
2use crate::ast::{CodeBlock, Expr, Pattern};
3use crate::kind::SyntaxKind;
4use crate::SyntaxNode;
5
6node! {
7    struct Conditional
8}
9
10impl<'a> Conditional<'a> {
11    /// the condition
12    pub fn condition(self) -> Condition<'a> {
13        self.0.cast_first()
14    }
15
16    /// the consequent for the condition
17    pub fn consequent(self) -> CodeBlock<'a> {
18        self.0.cast_first()
19    }
20
21    /// the else if branches
22    pub fn cond_alternates(self) -> impl DoubleEndedIterator<Item = ConditionalAlternate<'a>> {
23        self.0.children().filter_map(SyntaxNode::cast)
24    }
25
26    /// the else node if any
27    pub fn cond_else(self) -> Option<ConditionalElse<'a>> {
28        self.0.try_cast_last()
29    }
30}
31
32node! {
33    struct WhileLoop
34}
35
36impl<'a> WhileLoop<'a> {
37    pub fn condition(self) -> Condition<'a> {
38        self.0.cast_first()
39    }
40
41    pub fn body(self) -> CodeBlock<'a> {
42        self.0.cast_first()
43    }
44}
45
46node! {
47    struct ForLoop
48}
49
50impl<'a> ForLoop<'a> {
51    pub fn body(self) -> CodeBlock<'a> {
52        self.0.cast_last()
53    }
54
55    pub fn binding(self) -> Pattern<'a> {
56        self.0.cast_first()
57    }
58
59    pub fn iterable(self) -> Expr<'a> {
60        self.0
61            .children()
62            .skip_while(|n| n.kind() != SyntaxKind::In)
63            .find_map(SyntaxNode::cast)
64            .unwrap_or_default()
65    }
66}
67
68node! {
69    struct ConditionalAlternate
70}
71impl<'a> ConditionalAlternate<'a> {
72    pub fn condition(self) -> Condition<'a> {
73        self.0.cast_first()
74    }
75
76    pub fn consequent(self) -> CodeBlock<'a> {
77        self.0.cast_first()
78    }
79}
80
81node! {
82    struct ConditionalElse
83}
84
85impl<'a> ConditionalElse<'a> {
86    pub fn consequent(self) -> CodeBlock<'a> {
87        self.0.cast_first()
88    }
89}
90
91node! {
92    struct Condition
93}
94
95impl<'a> Condition<'a> {
96    pub fn expr(self) -> Expr<'a> {
97        self.0.cast_first()
98    }
99}
100
101#[cfg(test)]
102mod tests {
103    use super::*;
104    use crate::assert_ast;
105    use crate::ast::{FuncCall, Ident};
106    use crate::ast::Str;
107
108    #[test]
109    fn test_for_loop() {
110        assert_ast!(
111            r#"
112                for (c in "abc") {
113                    println(c);
114                }
115            "#,
116            for_loop as ForLoop {
117                with binding: Ident = for_loop.binding() => {
118                    assert_eq!(binding.get(), "c");
119                }
120                with iterable: Str = for_loop.iterable() => {
121                    assert_eq!(iterable.get(), "abc");
122                }
123                with body: CodeBlock = for_loop.body() => {
124                    body.statements() => [
125                        call as FuncCall {
126                            assert_eq!(call.to_text(), "println(c)");
127                        }
128                    ]
129                }
130            }
131        );
132    }
133
134    #[test]
135    fn test_while_loop() {
136        assert_ast!(
137            r#"
138                while (true) {
139                    println("hello");
140                }
141            "#,
142            while_loop as WhileLoop {
143                with condition: Condition = while_loop.condition() => {
144                    assert_eq!(condition.expr().to_text(), "true");
145                }
146                with body: CodeBlock = while_loop.body() => {
147                    body.statements() => [
148                        call as FuncCall {
149                            assert_eq!(call.to_text(), "println(\"hello\")");
150                        }
151                    ]
152                }
153            }
154        )
155    }
156
157    #[test]
158    fn test_conditionals() {
159        assert_ast!(
160            r#"
161                if (true) {
162                    println("hello");
163                } else if (false) {
164                    println("bob");
165                } else {
166                    println("world");
167                }
168            "#,
169            conditional as Conditional {
170                with condition: Condition = conditional.condition() => {
171                    assert_eq!(condition.expr().to_text(), "true");
172                }
173                with consequent: CodeBlock = conditional.consequent() => {
174                    consequent.statements() => [
175                        call as FuncCall {
176                            assert_eq!(call.to_text(), "println(\"hello\")");
177                        }
178                    ]
179                }
180                conditional.cond_alternates() => [
181                    alternate as ConditionalAlternate {
182                        with condition: Condition = alternate.condition() => {
183                            assert_eq!(condition.expr().to_text(), "false");
184                        }
185                        with consequent: CodeBlock = alternate.consequent() => {
186                            consequent.statements() => [
187                                call as FuncCall {
188                                    assert_eq!(call.to_text(), "println(\"bob\")");
189                                }
190                            ]
191                        }
192                    }
193                ]
194                with alternate: ConditionalElse = conditional.cond_else().unwrap() => {
195                    with consequent: CodeBlock = alternate.consequent() => {
196                        consequent.statements() => [
197                            call as FuncCall {
198                                assert_eq!(call.to_text(), "println(\"world\")");
199                            }
200                        ]
201                    }
202                }
203            }
204        )
205    }
206}