text_span/
lib.rs

1#![no_std]
2
3use core::{cmp::Ordering, ops::Range};
4
5#[cfg(feature = "span-value-usize")]
6/// Type of span values
7pub type SpanValue = usize;
8#[cfg(feature = "span-value-u128")]
9/// Type of span values
10pub type SpanValue = u128;
11#[cfg(feature = "span-value-u64")]
12/// Type of span values
13pub type SpanValue = u64;
14#[cfg(feature = "span-value-u32")]
15/// Type of span values
16pub type SpanValue = u32;
17#[cfg(feature = "span-value-u16")]
18/// Type of span values
19pub type SpanValue = u16;
20#[cfg(feature = "span-value-u8")]
21/// Type of span values
22pub type SpanValue = u8;
23
24/// The `Span` type represents an area of a file.
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
26pub struct Span {
27    /// The start of the `Span`.
28    pub start: SpanValue,
29    /// The end of the `Span`.
30    pub end: SpanValue,
31}
32
33impl Span {
34    /// Creates a new `Span`. This span will start and end at the 0th character, making it have a length of zero.
35    #[inline(always)]
36    pub fn new() -> Self {
37        Self::new_from(0, 0)
38    }
39
40    /// Creates a new `Span` from a pair of start and end indexes. These indexes are indexes into a string by `char`s.
41    #[inline(always)]
42    pub fn new_from(start: SpanValue, end: SpanValue) -> Self {
43        Span { start, end }
44    }
45
46    /// Grows the span from the front. This moves the end value up by `amount`.
47    #[inline(always)]
48    pub fn grow_front(&mut self, amount: SpanValue) {
49        self.end += amount;
50    }
51
52    /// Grows the span from the back. This moves the start value back by `amount`.
53    ///
54    /// # Panics
55    /// Panics if the start of the span is less than `amount`, since spans can't have a negative start value,
56    #[inline(always)]
57    pub fn grow_back(&mut self, amount: SpanValue) {
58        assert!(
59            self.start >= amount,
60            "cannot create a span with a negative start value"
61        );
62        self.start -= amount;
63    }
64
65    /// Shrinks the span from the back. This moves the start value up by `amount`.
66    ///
67    /// # Panics
68    /// Panics if the size of the `Span` is less than `amount`, since a `Span`'s size can't be negative.
69    #[inline(always)]
70    #[allow(clippy::unnecessary_cast)]
71    pub fn shrink_back(&mut self, amount: SpanValue) {
72        assert!(self.len() >= amount, "cannot create negative-size span");
73        self.start += amount;
74    }
75
76    /// Shrinks the span from the front. This moves the end value back by `amount`.
77    ///
78    /// # Panics
79    /// This method will panic if the size of the `Span` is less than `amount`, since a `Span`'s size can't be negative.
80    #[inline(always)]
81    #[allow(clippy::unnecessary_cast)]
82    pub fn shrink_front(&mut self, amount: SpanValue) {
83        assert!(self.len() >= amount, "cannot create negative-size span");
84        self.end -= amount;
85    }
86
87    /// Checks if a `Span`'s size is `0`. Returns `true` if `0`, and false if anything else.
88    #[inline(always)]
89    pub fn is_empty(&self) -> bool {
90        self.len() == 0
91    }
92
93    /// Gets the length of a `Span`.
94    #[inline(always)]
95    pub fn len(&self) -> SpanValue {
96        self.end - self.start
97    }
98
99    /// Resets `self` by changing the start to be the end, plus 1, and changing the end to be the start.
100    /// The function also returns the old span.
101    #[inline(always)]
102    pub fn reset(&mut self) -> Self {
103        let span = *self;
104        self.start = self.end;
105        span
106    }
107
108    /// Applies the span to `string`.
109    ///
110    /// # Panics
111    /// Panics if `string` is shorter than the end of the span.
112    #[allow(clippy::unnecessary_cast)]
113    pub fn apply<'a>(&self, string: &'a str) -> &'a str {
114        assert!(
115            string.len() >= self.end as usize,
116            "string is too short to have the span applied"
117        );
118        let start = string.char_indices().nth(self.start as usize).unwrap().0;
119        let end = string.char_indices().nth(self.end as usize).unwrap().0;
120        &string[start..end]
121    }
122}
123
124impl From<Span> for Range<SpanValue> {
125    #[inline(always)]
126    fn from(val: Span) -> Self {
127        val.start..val.end
128    }
129}
130
131impl From<Range<SpanValue>> for Span {
132    #[inline(always)]
133    fn from(value: Range<SpanValue>) -> Self {
134        Self::new_from(value.start, value.end)
135    }
136}
137
138impl PartialOrd for Span {
139    #[inline(always)]
140    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
141        dual_order(self.start.cmp(&other.start), self.end.cmp(&other.end))
142    }
143}
144
145fn dual_order(x: Ordering, y: Ordering) -> Option<Ordering> {
146    match (x, y) {
147        (x, y) if x == y => Some(x),
148        (Ordering::Greater, Ordering::Less) | (Ordering::Less, Ordering::Greater) => None,
149        (x, Ordering::Equal) => Some(x),
150        (Ordering::Equal, x) => Some(x),
151        _ => unreachable!(),
152    }
153}