css_lexer/
span.rs

1use crate::SourceOffset;
2use std::{fmt::Display, hash::Hash, marker::PhantomData, ops::Add};
3
4/// Represents a range of text within a document, as a Start and End offset.
5///
6/// Effectively two [SourceOffsets][SourceOffset] in one struct.
7#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
8#[cfg_attr(feature = "serde", derive(serde::Serialize))]
9pub struct Span {
10	start: SourceOffset,
11	end: SourceOffset,
12}
13
14impl Span {
15	/// Represents a fake Span with [SourceOffset::DUMMY] as the start/end offsets.
16	pub const DUMMY: Span = Span::new(SourceOffset::DUMMY, SourceOffset::DUMMY);
17	pub const ZERO: Span = Span::new(SourceOffset::ZERO, SourceOffset::ZERO);
18
19	/// Creates a new [Span] given a starting [SourceOffset] and an ending [SourceOffset].
20	///
21	/// Asserts: start <= end
22	#[inline]
23	pub const fn new(start: SourceOffset, end: SourceOffset) -> Self {
24		debug_assert!(start.0 <= end.0);
25		Self { start, end }
26	}
27
28	/// Gets the starting [SourceOffset].
29	#[inline]
30	pub const fn start(&self) -> SourceOffset {
31		self.start
32	}
33
34	/// Gets the ending [SourceOffset].
35	#[inline]
36	pub const fn end(&self) -> SourceOffset {
37		self.end
38	}
39
40	/// Extends this [Span] into a new one with the end altered to be [SourceOffset].
41	///
42	/// Asserts: start <= end
43	#[inline]
44	pub fn with_end(self, end: SourceOffset) -> Self {
45		debug_assert!(self.start <= end);
46		Self { start: self.start, end }
47	}
48
49	/// Checks if the given [Span] would fit entirely within this [Span].
50	pub fn contains(&self, span: Span) -> bool {
51		self.start <= span.start && span.end <= self.end
52	}
53
54	/// Checks if the [Span] has no length.
55	pub const fn is_empty(&self) -> bool {
56		self.start.0 == self.end.0
57	}
58
59	/// Returns the length of the [Span].
60	pub fn len(&self) -> u32 {
61		debug_assert!(self.start <= self.end);
62		self.end.0 - self.start.0
63	}
64
65	/// Given a string `source`, establish the line number and column number that this span would reside in.
66	pub fn line_and_column(self, source: &'_ str) -> (u32, u32) {
67		let mut line = 0;
68		let mut column = 0;
69		let mut offset = self.start.0;
70		for char in source.chars() {
71			if offset == 0 {
72				break;
73			}
74			if char == '\n' {
75				column = 0;
76				line += 1;
77			} else {
78				column += 1;
79			}
80			offset -= char.len_utf8() as u32;
81		}
82		(line, column)
83	}
84}
85
86/// Extends this [Span], ensuring that the resulting new [Span] is broader than both this and the given [Span].
87/// In other words the resulting span will always [Span::contains()] both [Spans][Span].
88impl Add for Span {
89	type Output = Self;
90	fn add(self, rhs: Self) -> Self::Output {
91		if rhs == Span::DUMMY {
92			return self;
93		}
94		if self == Span::DUMMY {
95			return rhs;
96		}
97		let start = if self.start < rhs.start { self.start } else { rhs.start };
98		let end = if self.end > rhs.end { self.end } else { rhs.end };
99		Self { start, end }
100	}
101}
102
103impl Display for Span {
104	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
105		write!(f, "[{}..{})", self.start.0, self.end.0)
106	}
107}
108
109#[cfg(feature = "miette")]
110impl From<Span> for miette::SourceSpan {
111	fn from(val: Span) -> Self {
112		Self::new(miette::SourceOffset::from(val.start.0 as usize), val.len() as usize)
113	}
114}
115
116impl<T: ToSpan> ToSpan for Vec<T> {
117	fn to_span(&self) -> Span {
118		let mut span = Span::ZERO;
119		for item in self {
120			if span == Span::ZERO {
121				span = item.to_span();
122			} else {
123				span = span + item.to_span()
124			}
125		}
126		span
127	}
128}
129
130impl<T: ToSpan, A: allocator_api2::alloc::Allocator> ToSpan for allocator_api2::vec::Vec<T, A> {
131	fn to_span(&self) -> Span {
132		let mut span = Span::ZERO;
133		for item in self {
134			if span == Span::ZERO {
135				span = item.to_span();
136			} else {
137				span = span + item.to_span()
138			}
139		}
140		span
141	}
142}
143
144#[cfg(feature = "bumpalo")]
145impl<'a, T: ToSpan> ToSpan for bumpalo::collections::Vec<'a, T> {
146	fn to_span(&self) -> Span {
147		let mut span = Span::ZERO;
148		for item in self {
149			if span == Span::ZERO {
150				span = item.to_span();
151			} else {
152				span = span + item.to_span()
153			}
154		}
155		span
156	}
157}
158
159macro_rules! impl_tuple {
160    ($len:tt: $($name:ident),+) => {
161        impl<$($name: ToSpan),+> ToSpan for ($($name),+) {
162            fn to_span(&self) -> Span {
163                self.0.to_span() + self.$len.to_span()
164            }
165        }
166    };
167}
168impl_tuple!(1: A, B);
169impl_tuple!(2: A, B, C);
170impl_tuple!(3: A, B, C, D);
171impl_tuple!(4: A, B, C, D, E);
172impl_tuple!(5: A, B, C, D, E, F);
173impl_tuple!(6: A, B, C, D, E, F, G);
174impl_tuple!(7: A, B, C, D, E, F, G, H);
175impl_tuple!(8: A, B, C, D, E, F, G, H, I);
176impl_tuple!(9: A, B, C, D, E, F, G, H, I, J);
177impl_tuple!(10: A, B, C, D, E, F, G, H, I, J, K);
178impl_tuple!(11: A, B, C, D, E, F, G, H, I, J, K, L);
179
180impl<T: ToSpan> ToSpan for Option<T> {
181	fn to_span(&self) -> Span {
182		self.as_ref().map_or(Span::DUMMY, |t| t.to_span())
183	}
184}
185
186impl<T> ToSpan for PhantomData<T> {
187	fn to_span(&self) -> Span {
188		Span::DUMMY
189	}
190}
191
192/// A trait representing an object that can derive its own [Span]. This is very similar to `From<MyStuct> for Span`,
193/// however `From<MyStruct> for Span` requires `Sized`, meaning it is not `dyn` compatible.
194pub trait ToSpan {
195	fn to_span(&self) -> Span;
196}
197
198impl ToSpan for Span {
199	fn to_span(&self) -> Span {
200		*self
201	}
202}
203
204impl<T: ToSpan> ToSpan for &T {
205	fn to_span(&self) -> Span {
206		(**self).to_span()
207	}
208}
209
210impl<T: ToSpan> ToSpan for &mut T {
211	fn to_span(&self) -> Span {
212		(**self).to_span()
213	}
214}
215
216#[cfg(test)]
217mod test {
218	use super::*;
219
220	#[test]
221	fn test_span_vec() {
222		let mut vec = vec![];
223		vec.push(Span::new(SourceOffset(3), SourceOffset(10)));
224		vec.push(Span::new(SourceOffset(13), SourceOffset(15)));
225		assert_eq!(vec.to_span(), Span::new(SourceOffset(3), SourceOffset(15)));
226	}
227}