css_lexer/
cursor.rs

1use crate::{AssociatedWhitespaceRules, CommentStyle, Kind, KindSet, QuoteStyle, SourceOffset, Span, ToSpan, Token};
2
3/// Wraps [Token] with a [SourceOffset], allows it to reason about the character data of the source text.
4#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
5pub struct Cursor(SourceOffset, Token);
6
7impl Cursor {
8	pub const DUMMY_SITE_NUMBER_ZERO: Self = Self(SourceOffset::DUMMY, Token::NUMBER_ZERO);
9	pub const EMPTY: Self = Self(SourceOffset::ZERO, Token::EMPTY);
10
11	#[inline(always)]
12	pub const fn new(offset: SourceOffset, token: Token) -> Self {
13		Self(offset, token)
14	}
15
16	#[inline(always)]
17	pub const fn dummy(token: Token) -> Self {
18		Self(SourceOffset::DUMMY, token)
19	}
20
21	#[inline(always)]
22	pub const fn token(&self) -> Token {
23		self.1
24	}
25
26	#[inline(always)]
27	pub const fn offset(&self) -> SourceOffset {
28		self.0
29	}
30
31	#[inline(always)]
32	pub fn end_offset(&self) -> SourceOffset {
33		if self.offset() == SourceOffset::DUMMY {
34			return self.offset();
35		}
36		SourceOffset(self.offset().0 + self.len())
37	}
38
39	#[inline(always)]
40	pub const fn is_empty(&self) -> bool {
41		self.token().is_empty()
42	}
43
44	#[inline(always)]
45	pub const fn len(&self) -> u32 {
46		self.token().len()
47	}
48
49	#[inline(always)]
50	pub fn span(&self) -> Span {
51		Span::new(self.offset(), self.end_offset())
52	}
53
54	#[inline(always)]
55	pub fn with_bad_flag(&self) -> Self {
56		Self(self.0, self.1.with_bad_flag())
57	}
58
59	#[inline(always)]
60	pub fn is_bad(&self) -> bool {
61		self.1.is_bad()
62	}
63
64	#[inline(always)]
65	pub fn str_slice<'a>(&self, str: &'a str) -> &'a str {
66		debug_assert!(
67			str.len() >= (self.end_offset().0 as usize),
68			"attempted to index out of bounds ({} < {})",
69			str.len(),
70			self.end_offset().0
71		);
72		&str[(self.offset().0 as usize)..(self.end_offset().0 as usize)]
73	}
74
75	pub fn with_quotes(&self, quote_style: QuoteStyle) -> Self {
76		if *self == quote_style || *self != Kind::String {
77			return *self;
78		}
79		Self::new(self.offset(), self.token().with_quotes(quote_style))
80	}
81
82	pub fn with_associated_whitespace(&self, rules: AssociatedWhitespaceRules) -> Self {
83		debug_assert!(self.1 == KindSet::DELIM_LIKE);
84		if self.1.associated_whitespace().to_bits() == rules.to_bits() {
85			return *self;
86		}
87		Self::new(self.offset(), self.token().with_associated_whitespace(rules))
88	}
89
90	/// Returns a new [Cursor] with the `sign_is_required` flag set on the token.
91	/// This indicates that the `+` sign should be preserved during minification.
92	///
93	/// Asserts: the token `kind()` is [Kind::Number].
94	pub fn with_sign_required(&self) -> Self {
95		debug_assert!(self.1 == Kind::Number);
96		Self::new(self.offset(), self.token().with_sign_required())
97	}
98
99	#[inline]
100	pub fn atom_bits(&self) -> u32 {
101		self.1.atom_bits()
102	}
103}
104
105impl From<Cursor> for Token {
106	fn from(cursor: Cursor) -> Self {
107		cursor.token()
108	}
109}
110
111impl PartialEq<Token> for Cursor {
112	fn eq(&self, other: &Token) -> bool {
113		self.1 == *other
114	}
115}
116
117impl ToSpan for Cursor {
118	fn to_span(&self) -> Span {
119		self.span()
120	}
121}
122
123impl From<Cursor> for Span {
124	fn from(cursor: Cursor) -> Self {
125		cursor.span()
126	}
127}
128
129impl PartialEq<Span> for Cursor {
130	fn eq(&self, other: &Span) -> bool {
131		self.span() == *other
132	}
133}
134
135impl From<Cursor> for Kind {
136	fn from(cursor: Cursor) -> Self {
137		cursor.token().kind()
138	}
139}
140
141impl PartialEq<Kind> for Cursor {
142	fn eq(&self, other: &Kind) -> bool {
143		self.1 == *other
144	}
145}
146
147impl PartialEq<CommentStyle> for Cursor {
148	fn eq(&self, other: &CommentStyle) -> bool {
149		self.1 == *other
150	}
151}
152
153impl From<Cursor> for KindSet {
154	fn from(cursor: Cursor) -> Self {
155		cursor.token().into()
156	}
157}
158
159impl PartialEq<KindSet> for Cursor {
160	fn eq(&self, other: &KindSet) -> bool {
161		self.1 == *other
162	}
163}
164
165impl From<Cursor> for QuoteStyle {
166	fn from(cursor: Cursor) -> Self {
167		cursor.token().into()
168	}
169}
170
171impl PartialEq<QuoteStyle> for Cursor {
172	fn eq(&self, other: &QuoteStyle) -> bool {
173		self.1 == *other
174	}
175}
176
177impl PartialEq<AssociatedWhitespaceRules> for Cursor {
178	fn eq(&self, other: &AssociatedWhitespaceRules) -> bool {
179		self.1 == *other
180	}
181}
182
183impl PartialEq<char> for Cursor {
184	fn eq(&self, other: &char) -> bool {
185		self.1 == *other
186	}
187}
188
189impl PartialEq<CommentStyle> for &Cursor {
190	fn eq(&self, other: &CommentStyle) -> bool {
191		self.1 == *other
192	}
193}
194
195impl PartialEq<Kind> for &Cursor {
196	fn eq(&self, other: &Kind) -> bool {
197		self.1 == *other
198	}
199}
200
201impl PartialEq<KindSet> for &Cursor {
202	fn eq(&self, other: &KindSet) -> bool {
203		self.1 == *other
204	}
205}
206
207impl PartialEq<QuoteStyle> for &Cursor {
208	fn eq(&self, other: &QuoteStyle) -> bool {
209		self.1 == *other
210	}
211}
212
213impl PartialEq<char> for &Cursor {
214	fn eq(&self, other: &char) -> bool {
215		self.1 == *other
216	}
217}
218
219#[cfg(feature = "miette")]
220impl From<Cursor> for miette::SourceSpan {
221	fn from(val: Cursor) -> Self {
222		let span = val.span();
223		span.into()
224	}
225}
226
227#[cfg(feature = "serde")]
228impl serde::ser::Serialize for Cursor {
229	fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
230	where
231		S: serde::ser::Serializer,
232	{
233		use serde::ser::SerializeStruct;
234		if self.token() == Token::EMPTY {
235			return serializer.serialize_none();
236		}
237		let mut state = serializer.serialize_struct("Cursor", 3)?;
238		state.serialize_field("kind", self.token().kind().as_str())?;
239		state.serialize_field("offset", &self.offset())?;
240		state.serialize_field("len", &self.token().len())?;
241		state.end()
242	}
243}
244
245#[test]
246fn size_test() {
247	assert_eq!(::std::mem::size_of::<Cursor>(), 12);
248}