compose_syntax/ast/
range.rs1use 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 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}