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}