1use std::fmt;
9
10#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
15pub struct BytePos(pub u32);
16
17impl BytePos {
18 pub const ZERO: BytePos = BytePos(0);
20
21 #[inline]
22 pub const fn to_u32(self) -> u32 {
23 self.0
24 }
25
26 #[inline]
27 pub const fn to_usize(self) -> usize {
28 self.0 as usize
29 }
30
31 #[inline]
33 pub const fn saturating_add(self, bytes: u32) -> BytePos {
34 BytePos(self.0.saturating_add(bytes))
35 }
36
37 #[inline]
40 pub const fn saturating_sub(self, other: BytePos) -> u32 {
41 self.0.saturating_sub(other.0)
42 }
43}
44
45impl fmt::Debug for BytePos {
46 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
47 write!(f, "BytePos({})", self.0)
48 }
49}
50
51impl From<u32> for BytePos {
52 fn from(value: u32) -> Self {
53 BytePos(value)
54 }
55}
56
57#[derive(Clone, Copy, PartialEq, Eq, Hash)]
64pub struct Span {
65 start: BytePos,
66 len: u32,
67}
68
69impl Span {
70 #[track_caller]
76 pub fn new(start: impl Into<BytePos>, end: impl Into<BytePos>) -> Span {
77 let start = start.into();
78 let end = end.into();
79 debug_assert!(
80 start <= end,
81 "Span::new called with inverted range {start:?}..{end:?}"
82 );
83 let len = end.saturating_sub(start);
87 Span { start, len }
88 }
89
90 #[inline]
92 pub fn at(pos: impl Into<BytePos>) -> Span {
93 let pos = pos.into();
94 Span { start: pos, len: 0 }
95 }
96
97 pub const EMPTY: Span = Span {
99 start: BytePos::ZERO,
100 len: 0,
101 };
102
103 #[inline]
104 pub const fn start(self) -> BytePos {
105 self.start
106 }
107
108 #[inline]
109 pub const fn end(self) -> BytePos {
110 BytePos(self.start.0.saturating_add(self.len))
111 }
112
113 #[inline]
114 pub const fn len(self) -> u32 {
115 self.len
116 }
117
118 #[inline]
119 pub const fn is_empty(self) -> bool {
120 self.len == 0
121 }
122
123 #[inline]
131 #[must_use]
132 pub const fn shifted(self, delta: u32) -> Span {
133 Span {
134 start: BytePos(self.start.0.saturating_add(delta)),
135 len: self.len,
136 }
137 }
138
139 #[inline]
141 pub fn contains(self, pos: BytePos) -> bool {
142 pos >= self.start && pos < self.end()
143 }
144
145 pub fn cover(self, other: Span) -> Span {
151 if self.is_empty() {
152 return other;
153 }
154 if other.is_empty() {
155 return self;
156 }
157 let start = self.start.min(other.start);
158 let end = self.end().max(other.end());
159 Span::new(start, end)
160 }
161}
162
163impl fmt::Debug for Span {
164 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
165 write!(
166 f,
167 "{}..{} (len {})",
168 self.start.to_u32(),
169 self.end().to_u32(),
170 self.len
171 )
172 }
173}
174
175#[derive(Clone, Copy, PartialEq, Eq, Hash)]
181pub struct FileSpan {
182 pub file: FileId,
183 pub span: Span,
184}
185
186impl FileSpan {
187 #[inline]
188 pub fn new(file: FileId, span: Span) -> FileSpan {
189 FileSpan { file, span }
190 }
191
192 pub fn union(self, other: FileSpan) -> Option<FileSpan> {
198 if self.file != other.file {
199 return None;
200 }
201 Some(FileSpan::new(self.file, self.span.cover(other.span)))
202 }
203}
204
205impl fmt::Debug for FileSpan {
206 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
207 write!(f, "{:?}@{:?}", self.span, self.file)
208 }
209}
210
211use crate::file::FileId;
212
213#[cfg(test)]
214mod tests {
215 use super::*;
216
217 #[test]
218 fn span_round_trip_and_endpoints() {
219 let s = Span::new(10, 25);
220 assert_eq!(s.start(), BytePos(10));
221 assert_eq!(s.end(), BytePos(25));
222 assert_eq!(s.len(), 15);
223 assert!(!s.is_empty());
224
225 let empty = Span::new(7, 7);
226 assert!(empty.is_empty());
227 assert_eq!(empty.len(), 0);
228 }
229
230 #[test]
231 fn at_and_empty_constants() {
232 assert_eq!(Span::at(5), Span::new(5, 5));
233 assert!(Span::EMPTY.is_empty());
234 assert_eq!(Span::EMPTY.start(), BytePos::ZERO);
235 }
236
237 #[test]
238 fn contains_respects_half_open_semantics() {
239 let s = Span::new(10, 20);
240 assert!(!s.contains(9.into()));
241 assert!(s.contains(10.into()));
242 assert!(s.contains(19.into()));
243 assert!(!s.contains(20.into())); }
245
246 #[test]
247 fn cover_smallest_enclosing() {
248 let a = Span::new(10, 20);
249 let b = Span::new(15, 30);
250 assert_eq!(a.cover(b), Span::new(10, 30));
251
252 let c = Span::new(100, 110);
253 assert_eq!(a.cover(c), Span::new(10, 110));
254 }
255
256 #[test]
257 fn cover_empty_is_neutral() {
258 let a = Span::new(10, 20);
259 assert_eq!(a.cover(Span::EMPTY), a);
260 assert_eq!(Span::EMPTY.cover(a), a);
261 assert_eq!(Span::EMPTY.cover(Span::EMPTY), Span::EMPTY);
262 }
263
264 #[test]
265 fn bytepos_saturating_arithmetic() {
266 assert_eq!(BytePos(5).saturating_add(10), BytePos(15));
267 assert_eq!(BytePos(u32::MAX).saturating_add(1), BytePos(u32::MAX));
268 assert_eq!(BytePos(10).saturating_sub(BytePos(3)), 7);
269 assert_eq!(BytePos(3).saturating_sub(BytePos(10)), 0); }
271
272 #[cfg(debug_assertions)]
276 #[test]
277 #[should_panic(expected = "inverted range")]
278 fn inverted_span_is_rejected_in_debug() {
279 let _ = Span::new(25, 10);
285 }
286}