sipha_core/
span.rs

1//! Default span implementations for common use cases.
2
3use std::ops::Range;
4
5/// Span type that tracks a byte range.
6///
7/// # Example
8///
9/// ```rust
10/// use sipha_core::span::Span;
11///
12/// let span = Span::new(10, 20);
13/// assert_eq!(span.start(), 10);
14/// assert_eq!(span.end(), 20);
15/// ```
16#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
17pub struct Span {
18    /// Start byte offset (inclusive).
19    pub start: usize,
20    /// End byte offset (exclusive).
21    pub end: usize,
22}
23
24impl Default for Span {
25    #[inline]
26    fn default() -> Self {
27        Self { start: 0, end: 0 }
28    }
29}
30
31impl Span {
32    /// Create a new span with start/end positions.
33    ///
34    /// # Panics
35    ///
36    /// Panics if `end < start` in debug mode.
37    #[inline]
38    pub fn new(start: usize, end: usize) -> Self {
39        debug_assert!(end >= start, "span end must be >= start");
40        Self { start, end }
41    }
42
43    /// Create a span from a range.
44    #[inline]
45    pub fn from_range(range: Range<usize>) -> Self {
46        Self::new(range.start, range.end)
47    }
48
49    /// Create an empty span at the given position.
50    #[inline]
51    pub fn empty(pos: usize) -> Self {
52        Self {
53            start: pos,
54            end: pos,
55        }
56    }
57
58    /// Get the range of this span.
59    #[inline]
60    pub fn range(&self) -> Range<usize> {
61        self.start..self.end
62    }
63
64    /// Get the start byte offset.
65    #[inline]
66    pub fn start(&self) -> usize {
67        self.start
68    }
69
70    /// Get the end byte offset.
71    #[inline]
72    pub fn end(&self) -> usize {
73        self.end
74    }
75
76    /// Get the length of the span in bytes.
77    #[inline]
78    pub fn len(&self) -> usize {
79        self.end - self.start
80    }
81
82    /// Check if the span is empty (start == end).
83    #[inline]
84    pub fn is_empty(&self) -> bool {
85        self.start == self.end
86    }
87
88    /// Check if this span contains the given position.
89    ///
90    /// A position is contained if it's >= start and < end.
91    #[inline]
92    pub fn contains(&self, pos: usize) -> bool {
93        self.start <= pos && pos < self.end
94    }
95
96    /// Check if this span contains another span entirely.
97    #[inline]
98    pub fn contains_span(&self, other: &Self) -> bool {
99        self.start <= other.start && self.end >= other.end
100    }
101
102    /// Check if this span overlaps with another span.
103    #[inline]
104    pub fn overlaps(&self, other: &Self) -> bool {
105        self.start < other.end && other.start < self.end
106    }
107
108    /// Check if this span is adjacent to another span (touching but not overlapping).
109    #[inline]
110    pub fn is_adjacent_to(&self, other: &Self) -> bool {
111        self.end == other.start || other.end == self.start
112    }
113
114    /// Join two adjacent or overlapping spans into one.
115    ///
116    /// Returns `None` if the spans are not adjacent/overlapping.
117    #[inline]
118    pub fn join(self, other: Self) -> Option<Self> {
119        if self.overlaps(&other) || self.is_adjacent_to(&other) {
120            Some(Self::new(
121                self.start.min(other.start),
122                self.end.max(other.end),
123            ))
124        } else {
125            None
126        }
127    }
128
129    /// Merge two spans into one, creating the smallest span that contains both.
130    #[inline]
131    pub fn merge(self, other: Self) -> Self {
132        Self::new(self.start.min(other.start), self.end.max(other.end))
133    }
134
135    /// Get the intersection of two spans, if they overlap.
136    ///
137    /// Returns `None` if the spans don't overlap.
138    #[inline]
139    pub fn intersection(self, other: Self) -> Option<Self> {
140        let start = self.start.max(other.start);
141        let end = self.end.min(other.end);
142        if start < end {
143            Some(Self::new(start, end))
144        } else {
145            None
146        }
147    }
148
149    /// Get the span that comes before this span, from `start_pos` to this span's start.
150    ///
151    /// Returns `None` if `start_pos >= self.start()`.
152    ///
153    /// # Example
154    ///
155    /// ```rust
156    /// use sipha_core::span::Span;
157    ///
158    /// let span = Span::new(10, 20);
159    /// let before = span.before(5);
160    /// assert_eq!(before, Some(Span::new(5, 10)));
161    /// ```
162    #[inline]
163    pub fn before(&self, start_pos: usize) -> Option<Self> {
164        if start_pos < self.start {
165            Some(Self::new(start_pos, self.start))
166        } else {
167            None
168        }
169    }
170
171    /// Get the span that comes after this span, from this span's end to `end_pos`.
172    ///
173    /// Returns `None` if `end_pos <= self.end()`.
174    ///
175    /// # Example
176    ///
177    /// ```rust
178    /// use sipha_core::span::Span;
179    ///
180    /// let span = Span::new(10, 20);
181    /// let after = span.after(25);
182    /// assert_eq!(after, Some(Span::new(20, 25)));
183    /// ```
184    #[inline]
185    pub fn after(&self, end_pos: usize) -> Option<Self> {
186        if end_pos > self.end {
187            Some(Self::new(self.end, end_pos))
188        } else {
189            None
190        }
191    }
192
193    /// Expand this span to include another span.
194    ///
195    /// This is equivalent to `self.merge(other)` but modifies `self` in place.
196    ///
197    /// # Example
198    ///
199    /// ```rust
200    /// use sipha_core::span::Span;
201    ///
202    /// let mut span = Span::new(10, 20);
203    /// span.expand_to_include(&Span::new(25, 30));
204    /// assert_eq!(span, Span::new(10, 30));
205    /// ```
206    #[inline]
207    pub fn expand_to_include(&mut self, other: &Self) {
208        self.start = self.start.min(other.start);
209        self.end = self.end.max(other.end);
210    }
211
212    /// Split this span at the given position, returning two spans.
213    ///
214    /// Returns `(before, after)` where:
215    /// - `before` is the span from `self.start()` to `pos` (exclusive)
216    /// - `after` is the span from `pos` to `self.end()`
217    ///
218    /// Returns `None` if `pos` is not within this span.
219    ///
220    /// # Example
221    ///
222    /// ```rust
223    /// use sipha_core::span::Span;
224    ///
225    /// let span = Span::new(10, 20);
226    /// let (before, after) = span.split_at(15).unwrap();
227    /// assert_eq!(before, Span::new(10, 15));
228    /// assert_eq!(after, Span::new(15, 20));
229    /// ```
230    #[inline]
231    pub fn split_at(&self, pos: usize) -> Option<(Self, Self)> {
232        if self.contains(pos) || pos == self.end {
233            Some((Self::new(self.start, pos), Self::new(pos, self.end)))
234        } else {
235            None
236        }
237    }
238}
239
240impl From<Range<usize>> for Span {
241    #[inline]
242    fn from(range: Range<usize>) -> Self {
243        Self::from_range(range)
244    }
245}
246
247impl From<Span> for Range<usize> {
248    #[inline]
249    fn from(span: Span) -> Self {
250        span.start..span.end
251    }
252}