Skip to main content

css_parse/
parser.rs

1use crate::{
2	Arena, Cursor, Diagnostic, Feature, Kind, KindSet, ParserCheckpoint, ParserReturn, Result, SourceOffset, ToCursors,
3	Vec,
4	traits::{Parse, Peek},
5};
6use bitmask_enum::bitmask;
7use css_lexer::{AtomSet, DynAtomSet, SourceCursor};
8use std::mem;
9
10// This is chosen rather arbitrarily, but:
11// - It needs to be a number larger than BUFFER_REFILL_INDEX (the largest `peek_n` distance we currently peek).
12// - It would be nice to keep Parser aligned to 64. It's not moved/copied... ever, so struct size doesn't really matter
13//   but making it, say, 1000, doesn't really improve performance. Always benchmark when changing!
14const BUFFER_LEN: usize = 12;
15// This number is chosen specifically because we peek_n(5) at most. Ensuring the buffer is always full enough that
16// peeks only use the buffer and don't end up cloning the lexer. While cloning the lexer is quite cheap, it's definitely
17// cheaper to simply look into the buffer. If we ever peek more than 5 tokens, we should change this number.
18const BUFFER_REFILL_INDEX: usize = BUFFER_LEN - 5;
19
20#[derive(Debug)]
21pub struct Parser<'a, I: Iterator<Item = Cursor> + Clone> {
22	pub(crate) source_text: &'a str,
23
24	pub(crate) cursor_iter: I,
25
26	#[allow(dead_code)]
27	pub(crate) features: Feature,
28
29	pub(crate) errors: Vec<'a, Diagnostic>,
30
31	pub(crate) trivia: Vec<'a, (Vec<'a, Cursor>, Cursor)>,
32
33	pub(crate) state: State,
34
35	pub(crate) alloc: &'a Arena,
36
37	skip: KindSet,
38
39	stop: KindSet,
40
41	buffer: [Cursor; BUFFER_LEN],
42	buffer_index: usize,
43
44	/// Nesting depth of substitution functions (`var()`, `env()`, etc.) currently being parsed.
45	/// Guards against stack overflow from deeply-nested fallbacks like `var(--a, var(--a, ...))`.
46	substitution_depth: u8,
47
48	#[cfg(debug_assertions)]
49	pub(crate) last_cursor: Option<Cursor>,
50}
51
52#[bitmask(u8)]
53#[bitmask_config(vec_debug)]
54#[derive(Default)]
55pub enum State {
56	Nested = 0b0000_0001,
57	/// Disallow relative selectors (:has). Set when inside :has() since nested :has() is invalid.
58	DisallowRelativeSelector = 0b0000_0010,
59}
60
61#[inline]
62fn eof_cursor(len: usize) -> Cursor {
63	let eof_offset = css_lexer::SourceOffset(len as u32);
64	Cursor::new(eof_offset, css_lexer::Token::EOF)
65}
66
67impl<'a, I> Parser<'a, I>
68where
69	I: Iterator<Item = Cursor> + Clone,
70{
71	/// Create a new parser with an iterator over cursors
72	pub fn new(alloc: &'a Arena, source_text: &'a str, mut cursor_iter: I) -> Self {
73		let eof_cursor = eof_cursor(source_text.len());
74		let mut buffer = [eof_cursor; BUFFER_LEN];
75		buffer.fill_with(|| cursor_iter.next().unwrap_or(eof_cursor));
76
77		Self {
78			source_text,
79			cursor_iter,
80			features: Feature::none(),
81			errors: Vec::new_in(alloc),
82			trivia: Vec::new_in(alloc),
83			state: State::none(),
84			skip: KindSet::TRIVIA,
85			stop: KindSet::NONE,
86			buffer,
87			buffer_index: 0,
88			substitution_depth: 0,
89			alloc,
90			#[cfg(debug_assertions)]
91			last_cursor: None,
92		}
93	}
94
95	pub fn with_features(mut self, features: Feature) -> Self {
96		self.features = features;
97		self
98	}
99
100	fn fill_buffer(&mut self, from: usize) {
101		// Shift remaining buffer cursors left to the start of the slice.
102		self.buffer.copy_within(from..BUFFER_LEN, 0);
103		// Re-fill the buffer with new cursors.
104		let eof = eof_cursor(self.source_text.len());
105		for i in BUFFER_LEN - from..BUFFER_LEN {
106			self.buffer[i] = self.cursor_iter.next().unwrap_or(eof);
107		}
108		self.buffer_index = 0;
109	}
110
111	#[inline]
112	pub fn alloc(&self) -> &'a Arena {
113		self.alloc
114	}
115
116	/// Maximum nesting depth of substitution functions before parsing bails to `Unresolved`.
117	pub const MAX_SUBSTITUTION_DEPTH: u8 = 32;
118
119	/// Enters a substitution-function parse scope, incrementing the depth counter.
120	///
121	/// Returns `false` if the depth limit ([`Self::MAX_SUBSTITUTION_DEPTH`]) would be exceeded;
122	/// callers should then consume the tokens as an unresolved token sequence instead of recursing.
123	/// On success, callers MUST call [`Self::exit_substitution`] once parsing of the scope ends.
124	#[inline]
125	#[must_use]
126	pub fn enter_substitution(&mut self) -> bool {
127		if self.substitution_depth >= Self::MAX_SUBSTITUTION_DEPTH {
128			return false;
129		}
130		self.substitution_depth += 1;
131		true
132	}
133
134	/// Exits a substitution-function parse scope, decrementing the depth counter.
135	#[inline]
136	pub fn exit_substitution(&mut self) {
137		debug_assert!(self.substitution_depth > 0);
138		self.substitution_depth = self.substitution_depth.saturating_sub(1);
139	}
140
141	#[inline]
142	pub fn enabled(&self, other: Feature) -> bool {
143		self.features.contains(other)
144	}
145
146	#[inline]
147	pub fn is(&self, state: State) -> bool {
148		self.state.contains(state)
149	}
150
151	#[inline]
152	pub fn set_state(&mut self, state: State) -> State {
153		let old = self.state;
154		self.state = state;
155		old
156	}
157
158	#[inline]
159	pub fn set_skip(&mut self, skip: KindSet) -> KindSet {
160		let old = self.skip;
161		self.skip = skip;
162		old
163	}
164
165	#[inline]
166	pub fn set_stop(&mut self, stop: KindSet) -> KindSet {
167		let old = self.stop;
168		self.stop = stop;
169		old
170	}
171
172	pub fn parse_entirely<T: Parse<'a> + ToCursors>(&mut self) -> ParserReturn<'a, T> {
173		let output = match T::parse(self) {
174			Ok(output) => Some(output),
175			Err(error) => {
176				self.errors.push(error);
177				None
178			}
179		};
180		let remaining_non_trivia = !self.at_end() && self.peek_n(1) != Kind::Eof;
181		let at_end = self.peek_n_with_skip(1, KindSet::NONE) == Kind::Eof;
182
183		if !at_end {
184			let start = self.peek_n_with_skip(1, KindSet::NONE);
185			let mut end;
186			loop {
187				end = self.next();
188				if end == Kind::Eof {
189					break;
190				}
191			}
192			if remaining_non_trivia {
193				self.errors.push(Diagnostic::new(start, Diagnostic::expected_end).with_end_cursor(end));
194			}
195		}
196		let errors = mem::replace(&mut self.errors, Vec::new_in(self.alloc));
197		let trivia = mem::replace(&mut self.trivia, Vec::new_in(self.alloc));
198		ParserReturn::new(output, self.source_text, errors, trivia)
199	}
200
201	pub fn parse<T: Parse<'a>>(&mut self) -> Result<T> {
202		T::parse(self)
203	}
204
205	pub fn peek<T: Peek<'a>>(&self) -> bool {
206		T::peek(self, self.peek_n(1))
207	}
208
209	pub fn parse_if_peek<T: Peek<'a> + Parse<'a>>(&mut self) -> Result<Option<T>> {
210		if T::peek(self, self.peek_n(1)) { T::parse(self).map(Some) } else { Ok(None) }
211	}
212
213	pub fn try_parse<T: Parse<'a>>(&mut self) -> Result<T> {
214		T::try_parse(self)
215	}
216
217	pub fn try_parse_if_peek<T: Peek<'a> + Parse<'a>>(&mut self) -> Result<Option<T>> {
218		if T::peek(self, self.peek_n(1)) { T::try_parse(self).map(Some) } else { Ok(None) }
219	}
220
221	pub fn equals_atom(&self, c: Cursor, atom: &'static dyn DynAtomSet) -> bool {
222		let mut cursor_bits = c.atom_bits();
223		if cursor_bits == 0 {
224			if c != KindSet::ATOM_LIKE {
225				return false;
226			}
227			let source_cursor = self.to_source_cursor(c);
228			cursor_bits = atom.str_to_bits(&source_cursor.parse(self.alloc));
229		}
230		cursor_bits == atom.bits()
231	}
232
233	pub fn to_atom<A: AtomSet + PartialEq>(&self, c: Cursor) -> A {
234		let bits = c.atom_bits();
235		if bits == 0 {
236			if c != KindSet::ATOM_LIKE {
237				return A::from_bits(0);
238			}
239			let source_cursor = self.to_source_cursor(c);
240			return A::from_str(&source_cursor.parse(self.alloc));
241		}
242		#[cfg(debug_assertions)]
243		if c == KindSet::ATOM_LIKE && c != Kind::Dimension {
244			let is_dashed = c.token().is_dashed_ident();
245			let source_cursor = self.to_source_cursor(c);
246			let text = source_cursor.parse(self.alloc);
247			let comparable = if is_dashed { &text[2..] } else { &text[..] };
248			debug_assert!(
249				A::from_bits(bits) == A::from_str(comparable),
250				"{:?} -> {:?} != {:?} ({:?})",
251				c,
252				A::from_bits(bits),
253				A::from_str(comparable),
254				comparable
255			);
256		}
257		A::from_bits(bits)
258	}
259
260	#[inline(always)]
261	pub fn offset(&self) -> SourceOffset {
262		self.buffer[self.buffer_index].offset()
263	}
264
265	#[inline(always)]
266	pub fn at_end(&self) -> bool {
267		self.buffer[self.buffer_index] == Kind::Eof
268	}
269
270	pub fn rewind(&mut self, checkpoint: ParserCheckpoint<I>) {
271		let ParserCheckpoint { iter, errors_pos, trivia_pos, buffer, buffer_index, skip, stop, state, .. } = checkpoint;
272
273		self.cursor_iter = iter;
274
275		self.errors.truncate(errors_pos as usize);
276		self.trivia.truncate(trivia_pos as usize);
277
278		self.buffer = buffer;
279		self.buffer_index = buffer_index;
280
281		self.skip = skip;
282		self.stop = stop;
283		self.state = state;
284
285		#[cfg(debug_assertions)]
286		{
287			self.last_cursor = None;
288		}
289	}
290
291	#[inline]
292	pub fn checkpoint(&self) -> ParserCheckpoint<I> {
293		ParserCheckpoint {
294			cursor: self.buffer[self.buffer_index],
295			errors_pos: self.errors.len() as u8,
296			trivia_pos: self.trivia.len() as u16,
297			iter: self.cursor_iter.clone(),
298			buffer: self.buffer,
299			buffer_index: self.buffer_index,
300			skip: self.skip,
301			stop: self.stop,
302			state: self.state,
303		}
304	}
305
306	#[inline]
307	pub fn next_is_stop(&self) -> bool {
308		for c in &self.buffer[self.buffer_index..BUFFER_LEN] {
309			if c != self.skip {
310				return c == self.stop;
311			}
312		}
313
314		let mut iter = self.cursor_iter.clone();
315		loop {
316			let Some(cursor) = iter.next() else {
317				return false;
318			};
319			if cursor != self.skip {
320				return cursor == self.stop;
321			}
322		}
323	}
324
325	#[inline]
326	pub(crate) fn peek_n_with_skip(&self, n: u8, skip: KindSet) -> Cursor {
327		let mut remaining = n;
328
329		for c in &self.buffer[self.buffer_index..BUFFER_LEN] {
330			if c == Kind::Eof {
331				return *c;
332			}
333			if c != skip {
334				remaining -= 1;
335				if remaining == 0 {
336					return *c;
337				}
338			}
339		}
340
341		let mut iter = self.cursor_iter.clone();
342		loop {
343			let Some(cursor) = iter.next() else {
344				return eof_cursor(self.source_text.len());
345			};
346			if cursor == Kind::Eof {
347				return cursor;
348			}
349			if cursor != skip {
350				remaining -= 1;
351				if remaining == 0 {
352					return cursor;
353				}
354			}
355		}
356	}
357
358	#[inline]
359	pub fn peek_n(&self, n: u8) -> Cursor {
360		self.peek_n_with_skip(n, self.skip)
361	}
362
363	#[inline]
364	pub fn peek_n_including_whitespace(&self, n: u8) -> Cursor {
365		self.peek_n_with_skip(n, self.skip.remove(Kind::Whitespace))
366	}
367
368	pub fn to_source_cursor(&self, cursor: Cursor) -> SourceCursor<'a> {
369		SourceCursor::from(cursor, cursor.str_slice(self.source_text))
370	}
371
372	pub fn consume_trivia(&mut self) -> Vec<'a, Cursor> {
373		let mut trivia = Vec::new_in(self.alloc);
374		for i in self.buffer_index..BUFFER_LEN {
375			let c = self.buffer[i];
376			if c == Kind::Eof {
377				self.buffer_index = i;
378				return trivia;
379			} else if c == self.skip {
380				trivia.push(c)
381			} else {
382				self.buffer_index = i;
383				self.fill_buffer(i);
384				return trivia;
385			}
386		}
387
388		let eof = eof_cursor(self.source_text.len());
389		loop {
390			let Some(c) = self.cursor_iter.next() else {
391				self.buffer = [eof; BUFFER_LEN];
392				self.buffer_index = 0;
393				return trivia;
394			};
395			if c == Kind::Eof {
396				self.buffer = [eof; BUFFER_LEN];
397				self.buffer_index = 0;
398				return trivia;
399			} else if c == self.skip {
400				trivia.push(c)
401			} else {
402				self.buffer[0] = c;
403				for i in 1..BUFFER_LEN {
404					self.buffer[i] = self.cursor_iter.next().unwrap_or(eof);
405				}
406				self.buffer_index = 0;
407				return trivia;
408			}
409		}
410	}
411
412	/// Consume trivia and attach it to the next content token for output preservation.
413	/// This should be called when you want to consume whitespace/comments but preserve
414	/// them for round-trip output fidelity.
415	pub fn consume_trivia_as_leading(&mut self) {
416		let trivia = self.consume_trivia();
417		if !trivia.is_empty() {
418			// Peek the next content token to attach trivia to it
419			let next = self.peek_n(1);
420			self.trivia.push((trivia, next));
421		}
422	}
423
424	#[allow(clippy::should_implement_trait)]
425	pub fn next(&mut self) -> Cursor {
426		// Collect trivia that should be associated with the next content token
427		let mut pending_trivia = Vec::new_in(self.alloc);
428
429		loop {
430			if self.buffer_index >= BUFFER_REFILL_INDEX {
431				self.fill_buffer(self.buffer_index);
432			}
433
434			for i in self.buffer_index..BUFFER_LEN {
435				let c = self.buffer[i];
436				if c == Kind::Eof {
437					self.buffer_index = i;
438					// Associate pending trivia with EOF if any
439					if !pending_trivia.is_empty() {
440						self.trivia.push((pending_trivia.clone(), c));
441					}
442					#[cfg(debug_assertions)]
443					{
444						self.last_cursor = None;
445					}
446					return c;
447				} else if c == self.skip {
448					pending_trivia.push(c);
449				} else {
450					self.buffer_index = i + 1;
451					if self.buffer_index >= BUFFER_REFILL_INDEX {
452						self.fill_buffer(self.buffer_index);
453					}
454					// Associate all pending trivia with this content token
455					if !pending_trivia.is_empty() {
456						self.trivia.push((pending_trivia.clone(), c));
457					}
458					#[cfg(debug_assertions)]
459					{
460						if let Some(last_cursor) = self.last_cursor {
461							debug_assert!(last_cursor != c, "Detected a next loop, {c:?} was fetched twice");
462						}
463						self.last_cursor = Some(c);
464					}
465					return c;
466				}
467			}
468
469			// Buffer exhausted with only skip tokens. Refill so buffer_index stays valid.
470			self.fill_buffer(BUFFER_LEN);
471		}
472	}
473}
474
475#[test]
476fn test_filling_buffer_with_skip_tokens() {
477	let str = "/*x*//*x*//*x*//*x*//*x*//*x*//*x*//*x*//*x*//*x*//*x*/a";
478	let alloc = crate::Arena::default();
479	let lexer = css_lexer::Lexer::new(&css_lexer::EmptyAtomSet::ATOMS, str);
480	let mut p = Parser::new(&alloc, str, lexer);
481	let c = p.next();
482	assert_eq!(c.token(), Kind::Ident);
483	// Must not panic:
484	let _ = p.at_end();
485	let _ = p.offset();
486}
487
488#[test]
489fn peek_and_next() {
490	let str = "0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21";
491	let alloc = crate::Arena::default();
492	let lexer = css_lexer::Lexer::new(&css_lexer::EmptyAtomSet::ATOMS, str);
493	let mut p = Parser::new(&alloc, str, lexer);
494	assert!(!p.at_end());
495	assert_eq!(p.offset(), 0);
496	for n in 0..=1 {
497		let c = p.checkpoint();
498		for i in 0..=19 {
499			let c = p.peek_n(1);
500			assert_eq!(c.token(), Kind::Number);
501			assert_eq!(c.token().value(), i as f32);
502			let c = p.peek_n(2);
503			assert_eq!(c.token(), Kind::Number);
504			assert_eq!(c.token().value(), (i + 1) as f32);
505			let c = p.peek_n(3);
506			assert_eq!(c.token(), Kind::Number);
507			assert_eq!(c.token().value(), (i + 2) as f32);
508			let c = p.next();
509			assert_eq!(c.token().value(), i as f32);
510			let c = p.peek_n(1);
511			assert_eq!(c.token(), Kind::Number);
512			assert_eq!(c.token().value(), (i + 1) as f32);
513		}
514		if n == 0 {
515			p.rewind(c)
516		}
517	}
518	let c = p.next();
519	assert_eq!(c.token(), Kind::Number);
520	assert_eq!(c.token().value(), 20.0);
521	let c = p.next();
522	assert_eq!(c.token(), Kind::Number);
523	assert_eq!(c.token().value(), 21.0);
524	let c = p.next();
525	assert_eq!(c.token(), Kind::Eof);
526}
527
528#[test]
529fn peek_and_next_with_whitsespace() {
530	let str = "0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21";
531	let alloc = crate::Arena::default();
532	let lexer = css_lexer::Lexer::new(&css_lexer::EmptyAtomSet::ATOMS, str);
533	let mut p = Parser::new(&alloc, str, lexer);
534	p.set_skip(KindSet::COMMENTS);
535	assert!(!p.at_end());
536	assert_eq!(p.offset(), 0);
537	for n in 0..=1 {
538		let c = p.checkpoint();
539		for i in 0..=19 {
540			let c = p.peek_n(1);
541			assert_eq!(c.token(), Kind::Number);
542			assert_eq!(c.token().value(), i as f32);
543			let c = p.peek_n(2);
544			assert_eq!(c.token(), Kind::Whitespace);
545			let c = p.peek_n(3);
546			assert_eq!(c.token(), Kind::Number);
547			assert_eq!(c.token().value(), (i + 1) as f32);
548			let c = p.peek_n(4);
549			assert_eq!(c.token(), Kind::Whitespace);
550			let c = p.peek_n(5);
551			assert_eq!(c.token(), Kind::Number);
552			assert_eq!(c.token().value(), (i + 2) as f32);
553			let c = p.next();
554			assert_eq!(c.token().value(), i as f32);
555			let c = p.peek_n(1);
556			assert_eq!(c.token(), Kind::Whitespace);
557			let c = p.peek_n(2);
558			assert_eq!(c.token(), Kind::Number);
559			assert_eq!(c.token().value(), (i + 1) as f32);
560			p.next();
561		}
562		if n == 0 {
563			p.rewind(c);
564		}
565	}
566	let c = p.next();
567	assert_eq!(c.token(), Kind::Number);
568	assert_eq!(c.token().value(), 20.0);
569	let c = p.next();
570	assert_eq!(c.token(), Kind::Whitespace);
571	let c = p.next();
572	assert_eq!(c.token(), Kind::Number);
573	assert_eq!(c.token().value(), 21.0);
574	let c = p.next();
575	assert_eq!(c.token(), Kind::Eof);
576}