rustpython_parser_vendored/text_size/range.rs
1use cmp::Ordering;
2
3use {
4 super::TextSize,
5 std::{
6 cmp, fmt,
7 ops::{Add, AddAssign, Bound, Index, IndexMut, Range, RangeBounds, Sub, SubAssign},
8 },
9};
10
11/// A range in text, represented as a pair of [`TextSize`][struct@TextSize].
12///
13/// It is a logic error for `start` to be greater than `end`.
14#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)]
15pub struct TextRange {
16 // Invariant: start <= end
17 start: TextSize,
18 end: TextSize,
19}
20
21impl fmt::Debug for TextRange {
22 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
23 write!(f, "{}..{}", self.start().raw, self.end().raw)
24 }
25}
26
27impl TextRange {
28 /// Creates a new `TextRange` with the given `start` and `end` (`start..end`).
29 ///
30 /// # Panics
31 ///
32 /// Panics if `end < start`.
33 ///
34 /// # Examples
35 ///
36 /// ```rust
37 /// # use rustpython_parser_vendored::text_size::*;
38 /// let start = TextSize::from(5);
39 /// let end = TextSize::from(10);
40 /// let range = TextRange::new(start, end);
41 ///
42 /// assert_eq!(range.start(), start);
43 /// assert_eq!(range.end(), end);
44 /// assert_eq!(range.len(), end - start);
45 /// ```
46 #[inline]
47 pub const fn new(start: TextSize, end: TextSize) -> TextRange {
48 assert!(start.raw <= end.raw);
49 TextRange { start, end }
50 }
51
52 /// Create a new `TextRange` with the given `offset` and `len` (`offset..offset + len`).
53 ///
54 /// # Examples
55 ///
56 /// ```rust
57 /// # use rustpython_parser_vendored::text_size::*;
58 /// let text = "0123456789";
59 ///
60 /// let offset = TextSize::from(2);
61 /// let length = TextSize::from(5);
62 /// let range = TextRange::at(offset, length);
63 ///
64 /// assert_eq!(range, TextRange::new(offset, offset + length));
65 /// assert_eq!(&text[range], "23456")
66 /// ```
67 #[inline]
68 pub fn at(offset: TextSize, len: TextSize) -> TextRange {
69 TextRange::new(offset, offset + len)
70 }
71
72 /// Create a zero-length range at the specified offset (`offset..offset`).
73 ///
74 /// # Examples
75 ///
76 /// ```rust
77 /// # use rustpython_parser_vendored::text_size::*;
78 /// let point: TextSize;
79 /// # point = TextSize::from(3);
80 /// let range = TextRange::empty(point);
81 /// assert!(range.is_empty());
82 /// assert_eq!(range, TextRange::new(point, point));
83 /// ```
84 #[inline]
85 pub fn empty(offset: TextSize) -> TextRange {
86 TextRange {
87 start: offset,
88 end: offset,
89 }
90 }
91
92 /// Create a range up to the given end (`..end`).
93 ///
94 /// # Examples
95 ///
96 /// ```rust
97 /// # use rustpython_parser_vendored::text_size::*;
98 /// let point: TextSize;
99 /// # point = TextSize::from(12);
100 /// let range = TextRange::up_to(point);
101 ///
102 /// assert_eq!(range.len(), point);
103 /// assert_eq!(range, TextRange::new(0.into(), point));
104 /// assert_eq!(range, TextRange::at(0.into(), point));
105 /// ```
106 #[inline]
107 pub fn up_to(end: TextSize) -> TextRange {
108 TextRange {
109 start: 0.into(),
110 end,
111 }
112 }
113}
114
115/// Identity methods.
116impl TextRange {
117 /// The start point of this range.
118 #[inline]
119 pub const fn start(self) -> TextSize {
120 self.start
121 }
122
123 /// The end point of this range.
124 #[inline]
125 pub const fn end(self) -> TextSize {
126 self.end
127 }
128
129 /// The size of this range.
130 #[inline]
131 pub const fn len(self) -> TextSize {
132 // HACK for const fn: math on primitives only
133 TextSize {
134 raw: self.end().raw - self.start().raw,
135 }
136 }
137
138 /// Check if this range is empty.
139 #[inline]
140 pub const fn is_empty(self) -> bool {
141 // HACK for const fn: math on primitives only
142 self.start().raw == self.end().raw
143 }
144}
145
146/// Manipulation methods.
147impl TextRange {
148 /// Check if this range contains an offset.
149 ///
150 /// The end index is considered excluded.
151 ///
152 /// # Examples
153 ///
154 /// ```rust
155 /// # use rustpython_parser_vendored::text_size::*;
156 /// let (start, end): (TextSize, TextSize);
157 /// # start = 10.into(); end = 20.into();
158 /// let range = TextRange::new(start, end);
159 /// assert!(range.contains(start));
160 /// assert!(!range.contains(end));
161 /// ```
162 #[inline]
163 pub fn contains(self, offset: TextSize) -> bool {
164 self.start() <= offset && offset < self.end()
165 }
166
167 /// Check if this range contains an offset.
168 ///
169 /// The end index is considered included.
170 ///
171 /// # Examples
172 ///
173 /// ```rust
174 /// # use rustpython_parser_vendored::text_size::*;
175 /// let (start, end): (TextSize, TextSize);
176 /// # start = 10.into(); end = 20.into();
177 /// let range = TextRange::new(start, end);
178 /// assert!(range.contains_inclusive(start));
179 /// assert!(range.contains_inclusive(end));
180 /// ```
181 #[inline]
182 pub fn contains_inclusive(self, offset: TextSize) -> bool {
183 self.start() <= offset && offset <= self.end()
184 }
185
186 /// Check if this range completely contains another range.
187 ///
188 /// # Examples
189 ///
190 /// ```rust
191 /// # use rustpython_parser_vendored::text_size::*;
192 /// let larger = TextRange::new(0.into(), 20.into());
193 /// let smaller = TextRange::new(5.into(), 15.into());
194 /// assert!(larger.contains_range(smaller));
195 /// assert!(!smaller.contains_range(larger));
196 ///
197 /// // a range always contains itself
198 /// assert!(larger.contains_range(larger));
199 /// assert!(smaller.contains_range(smaller));
200 /// ```
201 #[inline]
202 pub fn contains_range(self, other: TextRange) -> bool {
203 self.start() <= other.start() && other.end() <= self.end()
204 }
205
206 /// The range covered by both ranges, if it exists.
207 /// If the ranges touch but do not overlap, the output range is empty.
208 ///
209 /// # Examples
210 ///
211 /// ```rust
212 /// # use rustpython_parser_vendored::text_size::*;
213 /// assert_eq!(
214 /// TextRange::intersect(
215 /// TextRange::new(0.into(), 10.into()),
216 /// TextRange::new(5.into(), 15.into()),
217 /// ),
218 /// Some(TextRange::new(5.into(), 10.into())),
219 /// );
220 /// ```
221 #[inline]
222 pub fn intersect(self, other: TextRange) -> Option<TextRange> {
223 let start = cmp::max(self.start(), other.start());
224 let end = cmp::min(self.end(), other.end());
225 if end < start {
226 return None;
227 }
228 Some(TextRange::new(start, end))
229 }
230
231 /// Extends the range to cover `other` as well.
232 ///
233 /// # Examples
234 ///
235 /// ```rust
236 /// # use rustpython_parser_vendored::text_size::*;
237 /// assert_eq!(
238 /// TextRange::cover(
239 /// TextRange::new(0.into(), 5.into()),
240 /// TextRange::new(15.into(), 20.into()),
241 /// ),
242 /// TextRange::new(0.into(), 20.into()),
243 /// );
244 /// ```
245 #[inline]
246 #[must_use]
247 pub fn cover(self, other: TextRange) -> TextRange {
248 let start = cmp::min(self.start(), other.start());
249 let end = cmp::max(self.end(), other.end());
250 TextRange::new(start, end)
251 }
252
253 /// Extends the range to cover `other` offsets as well.
254 ///
255 /// # Examples
256 ///
257 /// ```rust
258 /// # use rustpython_parser_vendored::text_size::*;
259 /// assert_eq!(
260 /// TextRange::empty(0.into()).cover_offset(20.into()),
261 /// TextRange::new(0.into(), 20.into()),
262 /// )
263 /// ```
264 #[inline]
265 #[must_use]
266 pub fn cover_offset(self, offset: TextSize) -> TextRange {
267 self.cover(TextRange::empty(offset))
268 }
269
270 /// Add an offset to this range.
271 ///
272 /// Note that this is not appropriate for changing where a `TextRange` is
273 /// within some string; rather, it is for changing the reference anchor
274 /// that the `TextRange` is measured against.
275 ///
276 /// The unchecked version (`Add::add`) will _always_ panic on overflow,
277 /// in contrast to primitive integers, which check in debug mode only.
278 #[inline]
279 pub fn checked_add(self, offset: TextSize) -> Option<TextRange> {
280 Some(TextRange {
281 start: self.start.checked_add(offset)?,
282 end: self.end.checked_add(offset)?,
283 })
284 }
285
286 /// Subtract an offset from this range.
287 ///
288 /// Note that this is not appropriate for changing where a `TextRange` is
289 /// within some string; rather, it is for changing the reference anchor
290 /// that the `TextRange` is measured against.
291 ///
292 /// The unchecked version (`Sub::sub`) will _always_ panic on overflow,
293 /// in contrast to primitive integers, which check in debug mode only.
294 #[inline]
295 pub fn checked_sub(self, offset: TextSize) -> Option<TextRange> {
296 Some(TextRange {
297 start: self.start.checked_sub(offset)?,
298 end: self.end.checked_sub(offset)?,
299 })
300 }
301
302 /// Relative order of the two ranges (overlapping ranges are considered
303 /// equal).
304 ///
305 ///
306 /// This is useful when, for example, binary searching an array of disjoint
307 /// ranges.
308 ///
309 /// # Examples
310 ///
311 /// ```
312 /// # use rustpython_parser_vendored::text_size::*;
313 /// # use std::cmp::Ordering;
314 ///
315 /// let a = TextRange::new(0.into(), 3.into());
316 /// let b = TextRange::new(4.into(), 5.into());
317 /// assert_eq!(a.ordering(b), Ordering::Less);
318 ///
319 /// let a = TextRange::new(0.into(), 3.into());
320 /// let b = TextRange::new(3.into(), 5.into());
321 /// assert_eq!(a.ordering(b), Ordering::Less);
322 ///
323 /// let a = TextRange::new(0.into(), 3.into());
324 /// let b = TextRange::new(2.into(), 5.into());
325 /// assert_eq!(a.ordering(b), Ordering::Equal);
326 ///
327 /// let a = TextRange::new(0.into(), 3.into());
328 /// let b = TextRange::new(2.into(), 2.into());
329 /// assert_eq!(a.ordering(b), Ordering::Equal);
330 ///
331 /// let a = TextRange::new(2.into(), 3.into());
332 /// let b = TextRange::new(2.into(), 2.into());
333 /// assert_eq!(a.ordering(b), Ordering::Greater);
334 /// ```
335 #[inline]
336 pub fn ordering(self, other: TextRange) -> Ordering {
337 if self.end() <= other.start() {
338 Ordering::Less
339 } else if other.end() <= self.start() {
340 Ordering::Greater
341 } else {
342 Ordering::Equal
343 }
344 }
345
346 /// Subtracts an offset from the start position.
347 ///
348 ///
349 /// ## Panics
350 /// If `start - amount` is less than zero.
351 ///
352 /// ## Examples
353 ///
354 /// ```
355 /// use rustpython_parser_vendored::text_size::{TextRange, TextSize};
356 ///
357 /// let range = TextRange::new(TextSize::from(5), TextSize::from(10));
358 /// assert_eq!(range.sub_start(TextSize::from(2)), TextRange::new(TextSize::from(3), TextSize::from(10)));
359 /// ```
360 #[inline]
361 #[must_use]
362 pub fn sub_start(&self, amount: TextSize) -> TextRange {
363 TextRange::new(self.start() - amount, self.end())
364 }
365
366 /// Adds an offset to the start position.
367 ///
368 /// ## Panics
369 /// If `start + amount > end`
370 ///
371 /// ## Examples
372 ///
373 /// ```
374 /// use rustpython_parser_vendored::text_size::{TextRange, TextSize};
375 ///
376 /// let range = TextRange::new(TextSize::from(5), TextSize::from(10));
377 /// assert_eq!(range.add_start(TextSize::from(3)), TextRange::new(TextSize::from(8), TextSize::from(10)));
378 /// ```
379 #[inline]
380 #[must_use]
381 pub fn add_start(&self, amount: TextSize) -> TextRange {
382 TextRange::new(self.start() + amount, self.end())
383 }
384
385 /// Subtracts an offset from the end position.
386 ///
387 ///
388 /// ## Panics
389 /// If `end - amount < 0` or `end - amount < start`
390 ///
391 /// ## Examples
392 ///
393 /// ```
394 /// use rustpython_parser_vendored::text_size::{TextRange, TextSize};
395 ///
396 /// let range = TextRange::new(TextSize::from(5), TextSize::from(10));
397 /// assert_eq!(range.sub_end(TextSize::from(2)), TextRange::new(TextSize::from(5), TextSize::from(8)));
398 /// ```
399 #[inline]
400 #[must_use]
401 pub fn sub_end(&self, amount: TextSize) -> TextRange {
402 TextRange::new(self.start(), self.end() - amount)
403 }
404
405 /// Adds an offset to the end position.
406 ///
407 ///
408 /// ## Panics
409 /// If `end + amount > u32::MAX`
410 ///
411 /// ## Examples
412 ///
413 /// ```
414 /// use rustpython_parser_vendored::text_size::{TextRange, TextSize};
415 ///
416 /// let range = TextRange::new(TextSize::from(5), TextSize::from(10));
417 /// assert_eq!(range.add_end(TextSize::from(2)), TextRange::new(TextSize::from(5), TextSize::from(12)));
418 /// ```
419 #[inline]
420 #[must_use]
421 pub fn add_end(&self, amount: TextSize) -> TextRange {
422 TextRange::new(self.start(), self.end() + amount)
423 }
424}
425
426impl Index<TextRange> for str {
427 type Output = str;
428 #[inline]
429 fn index(&self, index: TextRange) -> &str {
430 &self[Range::<usize>::from(index)]
431 }
432}
433
434impl Index<TextRange> for String {
435 type Output = str;
436 #[inline]
437 fn index(&self, index: TextRange) -> &str {
438 &self[Range::<usize>::from(index)]
439 }
440}
441
442impl IndexMut<TextRange> for str {
443 #[inline]
444 fn index_mut(&mut self, index: TextRange) -> &mut str {
445 &mut self[Range::<usize>::from(index)]
446 }
447}
448
449impl IndexMut<TextRange> for String {
450 #[inline]
451 fn index_mut(&mut self, index: TextRange) -> &mut str {
452 &mut self[Range::<usize>::from(index)]
453 }
454}
455
456impl RangeBounds<TextSize> for TextRange {
457 fn start_bound(&self) -> Bound<&TextSize> {
458 Bound::Included(&self.start)
459 }
460
461 fn end_bound(&self) -> Bound<&TextSize> {
462 Bound::Excluded(&self.end)
463 }
464}
465
466impl From<Range<TextSize>> for TextRange {
467 #[inline]
468 fn from(r: Range<TextSize>) -> Self {
469 TextRange::new(r.start, r.end)
470 }
471}
472
473impl<T> From<TextRange> for Range<T>
474where
475 T: From<TextSize>,
476{
477 #[inline]
478 fn from(r: TextRange) -> Self {
479 r.start().into()..r.end().into()
480 }
481}
482
483macro_rules! ops {
484 (impl $Op:ident for TextRange by fn $f:ident = $op:tt) => {
485 impl $Op<&TextSize> for TextRange {
486 type Output = TextRange;
487 #[inline]
488 fn $f(self, other: &TextSize) -> TextRange {
489 self $op *other
490 }
491 }
492 impl<T> $Op<T> for &TextRange
493 where
494 TextRange: $Op<T, Output=TextRange>,
495 {
496 type Output = TextRange;
497 #[inline]
498 fn $f(self, other: T) -> TextRange {
499 *self $op other
500 }
501 }
502 };
503}
504
505impl Add<TextSize> for TextRange {
506 type Output = TextRange;
507 #[inline]
508 fn add(self, offset: TextSize) -> TextRange {
509 self.checked_add(offset)
510 .expect("TextRange +offset overflowed")
511 }
512}
513
514impl Sub<TextSize> for TextRange {
515 type Output = TextRange;
516 #[inline]
517 fn sub(self, offset: TextSize) -> TextRange {
518 self.checked_sub(offset)
519 .expect("TextRange -offset overflowed")
520 }
521}
522
523ops!(impl Add for TextRange by fn add = +);
524ops!(impl Sub for TextRange by fn sub = -);
525
526impl<A> AddAssign<A> for TextRange
527where
528 TextRange: Add<A, Output = TextRange>,
529{
530 #[inline]
531 fn add_assign(&mut self, rhs: A) {
532 *self = *self + rhs;
533 }
534}
535
536impl<S> SubAssign<S> for TextRange
537where
538 TextRange: Sub<S, Output = TextRange>,
539{
540 #[inline]
541 fn sub_assign(&mut self, rhs: S) {
542 *self = *self - rhs;
543 }
544}