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 str_slice<'a>(&self, str: &'a str) -> &'a str {
56		debug_assert!(
57			str.len() >= (self.end_offset().0 as usize),
58			"attempted to index out of bounds ({} < {})",
59			str.len(),
60			self.end_offset().0
61		);
62		&str[(self.offset().0 as usize)..(self.end_offset().0 as usize)]
63	}
64
65	pub fn with_quotes(&self, quote_style: QuoteStyle) -> Self {
66		if *self == quote_style || *self != Kind::String {
67			return *self;
68		}
69		Self::new(self.offset(), self.token().with_quotes(quote_style))
70	}
71
72	pub fn with_associated_whitespace(&self, rules: AssociatedWhitespaceRules) -> Self {
73		debug_assert!(self.1 == KindSet::DELIM_LIKE);
74		if self.1.associated_whitespace().to_bits() == rules.to_bits() {
75			return *self;
76		}
77		Self::new(self.offset(), self.token().with_associated_whitespace(rules))
78	}
79
80	/// Returns a new [Cursor] with the `sign_is_required` flag set on the token.
81	/// This indicates that the `+` sign should be preserved during minification.
82	///
83	/// Asserts: the token `kind()` is [Kind::Number].
84	pub fn with_sign_required(&self) -> Self {
85		debug_assert!(self.1 == Kind::Number);
86		Self::new(self.offset(), self.token().with_sign_required())
87	}
88
89	#[inline]
90	pub fn atom_bits(&self) -> u32 {
91		self.1.atom_bits()
92	}
93}
94
95impl From<Cursor> for Token {
96	fn from(cursor: Cursor) -> Self {
97		cursor.token()
98	}
99}
100
101impl PartialEq<Token> for Cursor {
102	fn eq(&self, other: &Token) -> bool {
103		self.1 == *other
104	}
105}
106
107impl ToSpan for Cursor {
108	fn to_span(&self) -> Span {
109		self.span()
110	}
111}
112
113impl From<Cursor> for Span {
114	fn from(cursor: Cursor) -> Self {
115		cursor.span()
116	}
117}
118
119impl PartialEq<Span> for Cursor {
120	fn eq(&self, other: &Span) -> bool {
121		self.span() == *other
122	}
123}
124
125impl From<Cursor> for Kind {
126	fn from(cursor: Cursor) -> Self {
127		cursor.token().kind()
128	}
129}
130
131impl PartialEq<Kind> for Cursor {
132	fn eq(&self, other: &Kind) -> bool {
133		self.1 == *other
134	}
135}
136
137impl PartialEq<CommentStyle> for Cursor {
138	fn eq(&self, other: &CommentStyle) -> bool {
139		self.1 == *other
140	}
141}
142
143impl From<Cursor> for KindSet {
144	fn from(cursor: Cursor) -> Self {
145		cursor.token().into()
146	}
147}
148
149impl PartialEq<KindSet> for Cursor {
150	fn eq(&self, other: &KindSet) -> bool {
151		self.1 == *other
152	}
153}
154
155impl From<Cursor> for QuoteStyle {
156	fn from(cursor: Cursor) -> Self {
157		cursor.token().into()
158	}
159}
160
161impl PartialEq<QuoteStyle> for Cursor {
162	fn eq(&self, other: &QuoteStyle) -> bool {
163		self.1 == *other
164	}
165}
166
167impl PartialEq<AssociatedWhitespaceRules> for Cursor {
168	fn eq(&self, other: &AssociatedWhitespaceRules) -> bool {
169		self.1 == *other
170	}
171}
172
173impl PartialEq<char> for Cursor {
174	fn eq(&self, other: &char) -> bool {
175		self.1 == *other
176	}
177}
178
179impl PartialEq<CommentStyle> for &Cursor {
180	fn eq(&self, other: &CommentStyle) -> bool {
181		self.1 == *other
182	}
183}
184
185impl PartialEq<Kind> for &Cursor {
186	fn eq(&self, other: &Kind) -> bool {
187		self.1 == *other
188	}
189}
190
191impl PartialEq<KindSet> for &Cursor {
192	fn eq(&self, other: &KindSet) -> bool {
193		self.1 == *other
194	}
195}
196
197impl PartialEq<QuoteStyle> for &Cursor {
198	fn eq(&self, other: &QuoteStyle) -> bool {
199		self.1 == *other
200	}
201}
202
203impl PartialEq<char> for &Cursor {
204	fn eq(&self, other: &char) -> bool {
205		self.1 == *other
206	}
207}
208
209#[cfg(feature = "miette")]
210impl From<Cursor> for miette::SourceSpan {
211	fn from(val: Cursor) -> Self {
212		let span = val.span();
213		span.into()
214	}
215}
216
217#[cfg(feature = "serde")]
218impl serde::ser::Serialize for Cursor {
219	fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
220	where
221		S: serde::ser::Serializer,
222	{
223		use serde::ser::SerializeStruct;
224		if self.token() == Token::EMPTY {
225			return serializer.serialize_none();
226		}
227		let mut state = serializer.serialize_struct("Cursor", 3)?;
228		state.serialize_field("kind", self.token().kind().as_str())?;
229		state.serialize_field("offset", &self.offset())?;
230		state.serialize_field("len", &self.token().len())?;
231		state.end()
232	}
233}
234
235#[test]
236fn size_test() {
237	assert_eq!(::std::mem::size_of::<Cursor>(), 12);
238}