css_lexer/
source_cursor.rs

1use crate::{
2	AssociatedWhitespaceRules, CommentStyle, Cursor, Kind, KindSet, QuoteStyle, SourceOffset, Span, ToSpan, Token,
3};
4use std::fmt::{Display, Formatter, Result};
5
6/// Wraps [Cursor] with a [str] that represents the underlying character data for this cursor.
7#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
8pub struct SourceCursor<'a> {
9	cursor: Cursor,
10	source: &'a str,
11}
12
13impl<'a> ToSpan for SourceCursor<'a> {
14	fn to_span(&self) -> Span {
15		self.cursor.to_span()
16	}
17}
18
19impl<'a> Display for SourceCursor<'a> {
20	fn fmt(&self, f: &mut Formatter<'_>) -> Result {
21		match self.token().kind() {
22			Kind::Eof => Ok(()),
23			// It is important to manually write out quotes for 2 reasons:
24			//  1. The quote style can be mutated from the source string (such as the case of normalising/switching quotes.
25			//  2. Some strings may not have the closing quote, which should be corrected.
26			Kind::String => match self.token().quote_style() {
27				QuoteStyle::Single => {
28					let inner =
29						&self.source[1..(self.token().len() as usize) - self.token().has_close_quote() as usize];
30					write!(f, "'{inner}'")
31				}
32				QuoteStyle::Double => {
33					let inner =
34						&self.source[1..(self.token().len() as usize) - self.token().has_close_quote() as usize];
35					write!(f, "\"{inner}\"")
36				}
37				// Strings must always be quoted!
38				QuoteStyle::None => unreachable!(),
39			},
40			Kind::Delim
41			| Kind::Colon
42			| Kind::Semicolon
43			| Kind::Comma
44			| Kind::LeftSquare
45			| Kind::LeftParen
46			| Kind::RightSquare
47			| Kind::RightParen
48			| Kind::LeftCurly
49			| Kind::RightCurly => self.token().char().unwrap().fmt(f),
50			_ => f.write_str(self.source),
51		}
52	}
53}
54
55impl<'a> SourceCursor<'a> {
56	pub const SPACE: SourceCursor<'static> = SourceCursor::from(Cursor::new(SourceOffset(0), Token::SPACE), " ");
57	pub const TAB: SourceCursor<'static> = SourceCursor::from(Cursor::new(SourceOffset(0), Token::TAB), "\t");
58	pub const NEWLINE: SourceCursor<'static> = SourceCursor::from(Cursor::new(SourceOffset(0), Token::NEWLINE), "\n");
59
60	#[inline(always)]
61	pub const fn from(cursor: Cursor, source: &'a str) -> Self {
62		debug_assert!(
63			(cursor.len() as usize) == source.len(),
64			"A SourceCursor should be constructed with a source that matches the length of the cursor!"
65		);
66		Self { cursor, source }
67	}
68
69	#[inline(always)]
70	pub const fn cursor(&self) -> Cursor {
71		self.cursor
72	}
73
74	#[inline(always)]
75	pub const fn token(&self) -> Token {
76		self.cursor.token()
77	}
78
79	#[inline(always)]
80	pub const fn source(&self) -> &'a str {
81		self.source
82	}
83
84	pub fn with_quotes(&self, quote_style: QuoteStyle) -> Self {
85		Self::from(self.cursor.with_quotes(quote_style), self.source)
86	}
87
88	pub fn with_associated_whitespace(&self, rules: AssociatedWhitespaceRules) -> Self {
89		Self::from(self.cursor.with_associated_whitespace(rules), self.source)
90	}
91}
92
93impl PartialEq<Kind> for SourceCursor<'_> {
94	fn eq(&self, other: &Kind) -> bool {
95		self.token() == *other
96	}
97}
98
99impl PartialEq<CommentStyle> for SourceCursor<'_> {
100	fn eq(&self, other: &CommentStyle) -> bool {
101		self.token() == *other
102	}
103}
104
105impl From<SourceCursor<'_>> for KindSet {
106	fn from(cursor: SourceCursor<'_>) -> Self {
107		cursor.token().into()
108	}
109}
110
111impl PartialEq<KindSet> for SourceCursor<'_> {
112	fn eq(&self, other: &KindSet) -> bool {
113		self.token() == *other
114	}
115}