css_lexer/
span.rs

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