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