text_span/
lib.rs

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