compose_syntax/ast/
range.rs

1use crate::ast::macros::node;
2use crate::ast::Expr;
3use crate::{SyntaxKind, SyntaxNode};
4
5node! {
6    struct Range
7}
8
9impl<'a> Range<'a> {
10    pub fn start(self) -> Option<Expr<'a>> {
11        self.0
12            .children()
13            .take_while(|p| !matches!(p.kind(), SyntaxKind::Dots | SyntaxKind::DotsEq))
14            .find_map(SyntaxNode::cast)
15    }
16
17    pub fn end(self) -> Option<Expr<'a>> {
18        let mut children = self.0.children();
19        // move past the range operator
20        while let Some(p) = children.next() {
21            if matches!(p.kind(), SyntaxKind::Dots | SyntaxKind::DotsEq) {
22                break;
23            }
24        }
25        
26        children.find_map(SyntaxNode::cast)
27    }
28
29    pub fn is_inclusive(self) -> bool {
30        self.0.children().any(|p| p.kind() == SyntaxKind::DotsEq)
31    }
32}
33
34#[cfg(test)]
35mod tests {
36    use super::*;
37    use crate::assert_ast;
38    use crate::ast::{AstNode, FieldAccess, FuncCall, Int, Parenthesized};
39    use crate::test_utils::test_parse;
40
41    #[test]
42    fn test_range_exclusive() {
43        assert_ast!(
44            "1..5",
45            range as Range {
46                with start: Int = range.start().unwrap() => {
47                    assert_eq!(start.get(), 1);
48                }
49                with end: Int = range.end().unwrap() => {
50                    assert_eq!(end.get(), 5);
51                }
52                assert_eq!(range.is_inclusive(), false);
53            }
54        )
55    }
56
57    #[test]
58    fn test_range_inclusive() {
59        let nodes = test_parse("1..=5");
60        let range: Range = nodes[0].cast().unwrap();
61
62        assert_eq!(range.start().unwrap().to_text(), "1");
63        assert_eq!(range.end().unwrap().to_text(), "5");
64        assert!(range.is_inclusive());
65
66        assert_ast!(
67            "1..=5",
68            range as Range {
69                with start: Int = range.start().unwrap() => {
70                    assert_eq!(start.get(), 1);
71                }
72                with end: Int = range.end().unwrap() => {
73                    assert_eq!(end.get(), 5);
74                }
75                assert_eq!(range.is_inclusive(), true);
76            }
77        )
78    }
79
80    #[test]
81    fn test_range_from() {
82        assert_ast!(
83            "1..",
84            range as Range {
85                with start: Int = range.start().unwrap() => {
86                    assert_eq!(start.get(), 1);
87                }
88                assert_eq!(range.end().is_none(), true);
89                assert_eq!(range.is_inclusive(), false);
90            }
91        )
92    }
93
94    #[test]
95    fn test_range_from_inclusive() {
96        assert_ast!(
97            "1..=",
98            range as Range {
99                with start: Int = range.start().unwrap() => {
100                    assert_eq!(start.get(), 1);
101                }
102                assert_eq!(range.end().is_none(), true);
103                assert_eq!(range.is_inclusive(), true);
104            }
105        )
106    }
107
108    #[test]
109    fn test_range_to_exclusive() {
110        assert_ast!(
111            "..5",
112            range as Range {
113                assert_eq!(range.start().is_none(), true);
114                with end: Int = range.end().unwrap() => {
115                    assert_eq!(end.get(), 5);
116                }
117                assert_eq!(range.is_inclusive(), false);
118            }
119        )
120    }
121
122    #[test]
123    fn test_range_to_inclusive() {
124        assert_ast!(
125            "..=5",
126            range as Range {
127                assert_eq!(range.start().is_none(), true);
128                with end: Int = range.end().unwrap() => {
129                    assert_eq!(end.get(), 5);
130                }
131                assert_eq!(range.is_inclusive(), true);
132            }
133        )
134    }
135
136    #[test]
137    fn test_range_full() {
138        assert_ast!(
139            "..",
140            range as Range {
141                assert_eq!(range.start().is_none(), true);
142                assert_eq!(range.end().is_none(), true);
143                assert_eq!(range.is_inclusive(), false);
144            }
145        )
146    }
147
148    #[test]
149    fn test_range_with_expressions() {
150        assert_ast!(
151            "(1 + 2)..(3 * 4)",
152            range as Range {
153                with start: Parenthesized = range.start().unwrap() => {
154                    assert_eq!(start.to_text(), "(1+2)");
155                }
156                with end: Parenthesized = range.end().unwrap() => {
157                    assert_eq!(end.to_text(), "(3*4)");
158                }
159                assert_eq!(range.is_inclusive(), false);
160            }
161        )
162    }
163
164    #[test]
165    fn test_range_with_method_calls() {
166        assert_ast!(
167            "x.min()..=y.max()",
168            range as Range {
169                with start: FuncCall = range.start().unwrap() => {
170                    assert_eq!(start.to_text(), "x.min()");
171                }
172                with end: FuncCall = range.end().unwrap() => {
173                    assert_eq!(end.to_text(), "y.max()");
174                }
175            }
176        )
177    }
178
179    #[test]
180    fn test_range_with_field_access() {
181        assert_ast!(
182            "point.x..point.y",
183            range as Range {
184                with start: FieldAccess = range.start().unwrap() => {
185                    assert_eq!(start.to_text(), "point.x");
186                }
187                with end: FieldAccess = range.end().unwrap() => {
188                    assert_eq!(end.to_text(), "point.y");
189                }
190            }
191        )
192    }
193
194    #[test]
195    fn test_nested_ranges() {
196        let nodes = test_parse("(0..10)..20");
197        let range: Range = nodes[0].cast().unwrap();
198
199        assert_eq!(range.start().unwrap().to_text(), "(0..10)");
200        assert_eq!(range.end().unwrap().to_text(), "20");
201        assert!(!range.is_inclusive());
202
203        assert_ast!(
204            "(0..10)..20",
205            range as Range {
206                with start: Parenthesized = range.start().unwrap() => {
207                    with inner_range: Range = start.expr() => {
208                        with inner_start: Int = inner_range.start().unwrap() => {
209                            assert_eq!(inner_start.get(), 0);
210                        }
211                        with inner_end: Int = inner_range.end().unwrap() => {
212                            assert_eq!(inner_end.get(), 10);
213                        }
214                        assert_eq!(inner_range.is_inclusive(), false);
215                    }
216                }
217                with end: Int = range.end().unwrap() => {
218                    assert_eq!(end.get(), 20);
219                }
220                assert_eq!(range.is_inclusive(), false);
221            }
222        )
223    }
224}