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}