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	/// The full source text the parser is reading from.
147	#[inline]
148	pub fn source_text(&self) -> &'a str {
149		self.source_text
150	}
151
152	#[inline]
153	pub fn is(&self, state: State) -> bool {
154		self.state.contains(state)
155	}
156
157	#[inline]
158	pub fn set_state(&mut self, state: State) -> State {
159		let old = self.state;
160		self.state = state;
161		old
162	}
163
164	#[inline]
165	pub fn set_skip(&mut self, skip: KindSet) -> KindSet {
166		let old = self.skip;
167		self.skip = skip;
168		old
169	}
170
171	#[inline]
172	pub fn set_stop(&mut self, stop: KindSet) -> KindSet {
173		let old = self.stop;
174		self.stop = stop;
175		old
176	}
177
178	pub fn parse_entirely<T: Parse<'a> + ToCursors>(&mut self) -> ParserReturn<'a, T> {
179		let output = match T::parse(self) {
180			Ok(output) => Some(output),
181			Err(error) => {
182				self.errors.push(error);
183				None
184			}
185		};
186		let remaining_non_trivia = !self.at_end() && self.peek_n(1) != Kind::Eof;
187		let at_end = self.peek_n_with_skip(1, KindSet::NONE) == Kind::Eof;
188
189		if !at_end {
190			let start = self.peek_n_with_skip(1, KindSet::NONE);
191			let mut end;
192			loop {
193				end = self.next();
194				if end == Kind::Eof {
195					break;
196				}
197			}
198			if remaining_non_trivia {
199				self.errors.push(Diagnostic::new(start, Diagnostic::expected_end).with_end_cursor(end));
200			}
201		}
202		let errors = mem::replace(&mut self.errors, Vec::new_in(self.alloc));
203		let trivia = mem::replace(&mut self.trivia, Vec::new_in(self.alloc));
204		ParserReturn::new(output, self.source_text, errors, trivia)
205	}
206
207	pub fn parse<T: Parse<'a>>(&mut self) -> Result<T> {
208		T::parse(self)
209	}
210
211	pub fn peek<T: Peek<'a>>(&self) -> bool {
212		T::peek(self, self.peek_n(1))
213	}
214
215	pub fn parse_if_peek<T: Peek<'a> + Parse<'a>>(&mut self) -> Result<Option<T>> {
216		if T::peek(self, self.peek_n(1)) { T::parse(self).map(Some) } else { Ok(None) }
217	}
218
219	pub fn try_parse<T: Parse<'a>>(&mut self) -> Result<T> {
220		T::try_parse(self)
221	}
222
223	pub fn try_parse_if_peek<T: Peek<'a> + Parse<'a>>(&mut self) -> Result<Option<T>> {
224		if T::peek(self, self.peek_n(1)) { T::try_parse(self).map(Some) } else { Ok(None) }
225	}
226
227	pub fn equals_atom(&self, c: Cursor, atom: &'static dyn DynAtomSet) -> bool {
228		let mut cursor_bits = c.token().atom_bits();
229		if cursor_bits == 0 {
230			if c != KindSet::ATOM_LIKE {
231				return false;
232			}
233			let source_cursor = self.to_source_cursor(c);
234			cursor_bits = atom.str_to_bits(&source_cursor.parse(self.alloc));
235		}
236		cursor_bits == atom.bits()
237	}
238
239	pub fn to_atom<A: AtomSet + PartialEq>(&self, c: Cursor) -> A {
240		let bits = c.token().atom_bits();
241		if bits == 0 {
242			if c != KindSet::ATOM_LIKE {
243				return A::from_bits(0);
244			}
245			let source_cursor = self.to_source_cursor(c);
246			return A::from_str(&source_cursor.parse(self.alloc));
247		}
248		#[cfg(debug_assertions)]
249		if c == KindSet::ATOM_LIKE && c != Kind::Dimension {
250			let is_dashed = c.token().is_dashed_ident();
251			let source_cursor = self.to_source_cursor(c);
252			let text = source_cursor.parse(self.alloc);
253			let comparable = if is_dashed { &text[2..] } else { &text[..] };
254			debug_assert!(
255				A::from_bits(bits) == A::from_str(comparable),
256				"{:?} -> {:?} != {:?} ({:?})",
257				c,
258				A::from_bits(bits),
259				A::from_str(comparable),
260				comparable
261			);
262		}
263		A::from_bits(bits)
264	}
265
266	#[inline(always)]
267	pub fn offset(&self) -> SourceOffset {
268		self.buffer[self.buffer_index].offset()
269	}
270
271	#[inline(always)]
272	pub fn at_end(&self) -> bool {
273		self.buffer[self.buffer_index] == Kind::Eof
274	}
275
276	pub fn rewind(&mut self, checkpoint: ParserCheckpoint<I>) {
277		let ParserCheckpoint { iter, errors_pos, trivia_pos, buffer, buffer_index, skip, stop, state, .. } = checkpoint;
278
279		self.cursor_iter = iter;
280
281		self.errors.truncate(errors_pos as usize);
282		self.trivia.truncate(trivia_pos as usize);
283
284		self.buffer = buffer;
285		self.buffer_index = buffer_index;
286
287		self.skip = skip;
288		self.stop = stop;
289		self.state = state;
290
291		#[cfg(debug_assertions)]
292		{
293			self.last_cursor = None;
294		}
295	}
296
297	#[inline]
298	pub fn checkpoint(&self) -> ParserCheckpoint<I> {
299		ParserCheckpoint {
300			cursor: self.buffer[self.buffer_index],
301			errors_pos: self.errors.len() as u8,
302			trivia_pos: self.trivia.len() as u16,
303			iter: self.cursor_iter.clone(),
304			buffer: self.buffer,
305			buffer_index: self.buffer_index,
306			skip: self.skip,
307			stop: self.stop,
308			state: self.state,
309		}
310	}
311
312	#[inline]
313	pub fn next_is_stop(&self) -> bool {
314		for c in &self.buffer[self.buffer_index..BUFFER_LEN] {
315			if c != self.skip {
316				return c == self.stop;
317			}
318		}
319
320		let mut iter = self.cursor_iter.clone();
321		loop {
322			let Some(cursor) = iter.next() else {
323				return false;
324			};
325			if cursor != self.skip {
326				return cursor == self.stop;
327			}
328		}
329	}
330
331	#[inline]
332	pub(crate) fn peek_n_with_skip(&self, n: u8, skip: KindSet) -> Cursor {
333		let mut remaining = n;
334
335		for c in &self.buffer[self.buffer_index..BUFFER_LEN] {
336			if c == Kind::Eof {
337				return *c;
338			}
339			if c != skip {
340				remaining -= 1;
341				if remaining == 0 {
342					return *c;
343				}
344			}
345		}
346
347		let mut iter = self.cursor_iter.clone();
348		loop {
349			let Some(cursor) = iter.next() else {
350				return eof_cursor(self.source_text.len());
351			};
352			if cursor == Kind::Eof {
353				return cursor;
354			}
355			if cursor != skip {
356				remaining -= 1;
357				if remaining == 0 {
358					return cursor;
359				}
360			}
361		}
362	}
363
364	#[inline]
365	pub fn peek_n(&self, n: u8) -> Cursor {
366		self.peek_n_with_skip(n, self.skip)
367	}
368
369	#[inline]
370	pub fn peek_n_including_whitespace(&self, n: u8) -> Cursor {
371		self.peek_n_with_skip(n, self.skip.remove(Kind::Whitespace))
372	}
373
374	pub fn to_source_cursor(&self, cursor: Cursor) -> SourceCursor<'a> {
375		SourceCursor::from(cursor, cursor.str_slice(self.source_text))
376	}
377
378	pub fn consume_trivia(&mut self) -> Vec<'a, Cursor> {
379		let mut trivia = Vec::new_in(self.alloc);
380		for i in self.buffer_index..BUFFER_LEN {
381			let c = self.buffer[i];
382			if c == Kind::Eof {
383				self.buffer_index = i;
384				return trivia;
385			} else if c == self.skip {
386				trivia.push(c)
387			} else {
388				self.buffer_index = i;
389				self.fill_buffer(i);
390				return trivia;
391			}
392		}
393
394		let eof = eof_cursor(self.source_text.len());
395		loop {
396			let Some(c) = self.cursor_iter.next() else {
397				self.buffer = [eof; BUFFER_LEN];
398				self.buffer_index = 0;
399				return trivia;
400			};
401			if c == Kind::Eof {
402				self.buffer = [eof; BUFFER_LEN];
403				self.buffer_index = 0;
404				return trivia;
405			} else if c == self.skip {
406				trivia.push(c)
407			} else {
408				self.buffer[0] = c;
409				for i in 1..BUFFER_LEN {
410					self.buffer[i] = self.cursor_iter.next().unwrap_or(eof);
411				}
412				self.buffer_index = 0;
413				return trivia;
414			}
415		}
416	}
417
418	/// Consume trivia and attach it to the next content token for output preservation.
419	/// This should be called when you want to consume whitespace/comments but preserve
420	/// them for round-trip output fidelity.
421	pub fn consume_trivia_as_leading(&mut self) {
422		let trivia = self.consume_trivia();
423		if !trivia.is_empty() {
424			// Peek the next content token to attach trivia to it
425			let next = self.peek_n(1);
426			self.trivia.push((trivia, next));
427		}
428	}
429
430	#[allow(clippy::should_implement_trait)]
431	pub fn next(&mut self) -> Cursor {
432		// Collect trivia that should be associated with the next content token
433		let mut pending_trivia = Vec::new_in(self.alloc);
434
435		loop {
436			if self.buffer_index >= BUFFER_REFILL_INDEX {
437				self.fill_buffer(self.buffer_index);
438			}
439
440			for i in self.buffer_index..BUFFER_LEN {
441				let c = self.buffer[i];
442				if c == Kind::Eof {
443					self.buffer_index = i;
444					// Associate pending trivia with EOF if any
445					if !pending_trivia.is_empty() {
446						self.trivia.push((pending_trivia.clone(), c));
447					}
448					#[cfg(debug_assertions)]
449					{
450						self.last_cursor = None;
451					}
452					return c;
453				} else if c == self.skip {
454					pending_trivia.push(c);
455				} else {
456					self.buffer_index = i + 1;
457					if self.buffer_index >= BUFFER_REFILL_INDEX {
458						self.fill_buffer(self.buffer_index);
459					}
460					// Associate all pending trivia with this content token
461					if !pending_trivia.is_empty() {
462						self.trivia.push((pending_trivia.clone(), c));
463					}
464					#[cfg(debug_assertions)]
465					{
466						if let Some(last_cursor) = self.last_cursor {
467							debug_assert!(last_cursor != c, "Detected a next loop, {c:?} was fetched twice");
468						}
469						self.last_cursor = Some(c);
470					}
471					return c;
472				}
473			}
474
475			// Buffer exhausted with only skip tokens. Refill so buffer_index stays valid.
476			self.fill_buffer(BUFFER_LEN);
477		}
478	}
479}
480
481#[test]
482fn test_filling_buffer_with_skip_tokens() {
483	let str = "/*x*//*x*//*x*//*x*//*x*//*x*//*x*//*x*//*x*//*x*//*x*/a";
484	let alloc = crate::Arena::default();
485	let lexer = css_lexer::Lexer::new(&css_lexer::EmptyAtomSet::ATOMS, str);
486	let mut p = Parser::new(&alloc, str, lexer);
487	let c = p.next();
488	assert_eq!(c.token(), Kind::Ident);
489	// Must not panic:
490	let _ = p.at_end();
491	let _ = p.offset();
492}
493
494#[test]
495fn peek_and_next() {
496	let str = "0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21";
497	let alloc = crate::Arena::default();
498	let lexer = css_lexer::Lexer::new(&css_lexer::EmptyAtomSet::ATOMS, str);
499	let mut p = Parser::new(&alloc, str, lexer);
500	assert!(!p.at_end());
501	assert_eq!(p.offset(), 0);
502	for n in 0..=1 {
503		let c = p.checkpoint();
504		for i in 0..=19 {
505			let c = p.peek_n(1);
506			assert_eq!(c.token(), Kind::Number);
507			assert_eq!(c.token().value(), i as f32);
508			let c = p.peek_n(2);
509			assert_eq!(c.token(), Kind::Number);
510			assert_eq!(c.token().value(), (i + 1) as f32);
511			let c = p.peek_n(3);
512			assert_eq!(c.token(), Kind::Number);
513			assert_eq!(c.token().value(), (i + 2) as f32);
514			let c = p.next();
515			assert_eq!(c.token().value(), i as f32);
516			let c = p.peek_n(1);
517			assert_eq!(c.token(), Kind::Number);
518			assert_eq!(c.token().value(), (i + 1) as f32);
519		}
520		if n == 0 {
521			p.rewind(c)
522		}
523	}
524	let c = p.next();
525	assert_eq!(c.token(), Kind::Number);
526	assert_eq!(c.token().value(), 20.0);
527	let c = p.next();
528	assert_eq!(c.token(), Kind::Number);
529	assert_eq!(c.token().value(), 21.0);
530	let c = p.next();
531	assert_eq!(c.token(), Kind::Eof);
532}
533
534#[test]
535fn peek_and_next_with_whitsespace() {
536	let str = "0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21";
537	let alloc = crate::Arena::default();
538	let lexer = css_lexer::Lexer::new(&css_lexer::EmptyAtomSet::ATOMS, str);
539	let mut p = Parser::new(&alloc, str, lexer);
540	p.set_skip(KindSet::COMMENTS);
541	assert!(!p.at_end());
542	assert_eq!(p.offset(), 0);
543	for n in 0..=1 {
544		let c = p.checkpoint();
545		for i in 0..=19 {
546			let c = p.peek_n(1);
547			assert_eq!(c.token(), Kind::Number);
548			assert_eq!(c.token().value(), i as f32);
549			let c = p.peek_n(2);
550			assert_eq!(c.token(), Kind::Whitespace);
551			let c = p.peek_n(3);
552			assert_eq!(c.token(), Kind::Number);
553			assert_eq!(c.token().value(), (i + 1) as f32);
554			let c = p.peek_n(4);
555			assert_eq!(c.token(), Kind::Whitespace);
556			let c = p.peek_n(5);
557			assert_eq!(c.token(), Kind::Number);
558			assert_eq!(c.token().value(), (i + 2) as f32);
559			let c = p.next();
560			assert_eq!(c.token().value(), i as f32);
561			let c = p.peek_n(1);
562			assert_eq!(c.token(), Kind::Whitespace);
563			let c = p.peek_n(2);
564			assert_eq!(c.token(), Kind::Number);
565			assert_eq!(c.token().value(), (i + 1) as f32);
566			p.next();
567		}
568		if n == 0 {
569			p.rewind(c);
570		}
571	}
572	let c = p.next();
573	assert_eq!(c.token(), Kind::Number);
574	assert_eq!(c.token().value(), 20.0);
575	let c = p.next();
576	assert_eq!(c.token(), Kind::Whitespace);
577	let c = p.next();
578	assert_eq!(c.token(), Kind::Number);
579	assert_eq!(c.token().value(), 21.0);
580	let c = p.next();
581	assert_eq!(c.token(), Kind::Eof);
582}