1use std::{collections::VecDeque, str};
2
3use bitflags::bitflags;
4use rustc_hash::FxHashSet;
5use smallvec::SmallVec;
6
7use crate::{
8 Range,
9 css_syntax::{
10 MAX_CSS_KEYWORD_LEN, decode_css_keyword, is_css_modules_pure_magic_comment,
11 lowercase_ascii_keyword, strip_vendor_prefix,
12 },
13 dependencies::{PropertyKind, special_value_is_candidate},
14};
15
16const fn build_plain_ascii_name_byte_table() -> [bool; 256] {
17 let mut table = [false; 256];
18 let mut byte = 0usize;
19 while byte < table.len() {
20 table[byte] = (byte >= b'0' as usize && byte <= b'9' as usize)
21 || (byte >= b'A' as usize && byte <= b'Z' as usize)
22 || (byte >= b'a' as usize && byte <= b'z' as usize)
23 || byte == b'_' as usize
24 || byte == b'-' as usize;
25 byte += 1;
26 }
27 table
28}
29
30const PLAIN_ASCII_NAME_BYTE: [bool; 256] = build_plain_ascii_name_byte_table();
31
32pub const C_LINE_FEED: u8 = b'\n';
33pub const C_CARRIAGE_RETURN: u8 = b'\r';
34pub const C_FORM_FEED: u8 = b'\x0c';
35
36pub const C_TAB: u8 = b'\t';
37pub const C_SPACE: u8 = b' ';
38
39pub const C_SOLIDUS: u8 = b'/';
40pub const C_REVERSE_SOLIDUS: u8 = b'\\';
41pub const C_ASTERISK: u8 = b'*';
42
43pub const C_LEFT_PARENTHESIS: u8 = b'(';
44pub const C_RIGHT_PARENTHESIS: u8 = b')';
45pub const C_LEFT_CURLY: u8 = b'{';
46pub const C_RIGHT_CURLY: u8 = b'}';
47pub const C_LEFT_SQUARE: u8 = b'[';
48pub const C_RIGHT_SQUARE: u8 = b']';
49
50pub const C_QUOTATION_MARK: u8 = b'"';
51pub const C_APOSTROPHE: u8 = b'\'';
52
53pub const C_FULL_STOP: u8 = b'.';
54pub const C_COLON: u8 = b':';
55pub const C_SEMICOLON: u8 = b';';
56pub const C_COMMA: u8 = b',';
57pub const C_PERCENTAGE: u8 = b'%';
58pub const C_AT_SIGN: u8 = b'@';
59
60pub const C_LOW_LINE: u8 = b'_';
61pub const C_LOWER_E: u8 = b'e';
62pub const C_UPPER_E: u8 = b'E';
63
64pub const C_NUMBER_SIGN: u8 = b'#';
65pub const C_PLUS_SIGN: u8 = b'+';
66pub const C_HYPHEN_MINUS: u8 = b'-';
67
68pub type Pos = u32;
69
70#[derive(Debug, Clone, Copy, PartialEq, Eq)]
76pub enum TokenKind {
77 Eof,
78 Ident,
79 AtKeyword,
80 Hash,
81 IdHash,
82 QuotedString,
83 Url,
84 Function,
85 Number,
86 Percentage,
87 Dimension,
88 WhiteSpace,
89 Comment,
90 BadComment,
91 BadString,
92 BadUrl,
93 Delim,
94 Colon,
95 Semicolon,
96 Comma,
97 LeftParenthesis,
98 RightParenthesis,
99 LeftSquareBracket,
100 RightSquareBracket,
101 LeftCurlyBracket,
102 RightCurlyBracket,
103 IncludeMatch,
104 DashMatch,
105 PrefixMatch,
106 SuffixMatch,
107 SubstringMatch,
108}
109
110impl TokenKind {
111 #[inline]
112 pub fn is_trivia(self) -> bool {
113 matches!(self, Self::WhiteSpace | Self::Comment)
114 }
115
116 #[inline]
117 pub fn is_scan_error(self) -> bool {
118 matches!(self, Self::BadComment | Self::BadString | Self::BadUrl)
119 }
120}
121
122#[derive(Debug, Clone, Copy, PartialEq, Eq)]
124pub struct Token {
125 pub kind: TokenKind,
126 pub range: Range,
127 pub value_range: Range,
128 pub flags: TokenFlags,
129}
130
131impl Token {
132 #[inline]
133 pub const fn new(kind: TokenKind, range: Range, value_range: Range) -> Self {
134 Self::with_flags(kind, range, value_range, TokenFlags::ascii())
135 }
136
137 #[inline]
138 pub const fn with_flags(
139 kind: TokenKind,
140 range: Range,
141 value_range: Range,
142 flags: TokenFlags,
143 ) -> Self {
144 Self {
145 kind,
146 range,
147 value_range,
148 flags,
149 }
150 }
151}
152
153bitflags! {
154 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
157 pub struct TokenFlags: u8 {
158 const HAS_ESCAPE = 1 << 0;
159 const HAS_NULL = 1 << 1;
160 const IS_ASCII = 1 << 2;
161 }
162}
163
164impl TokenFlags {
165 #[inline]
166 pub const fn ascii() -> Self {
167 Self::IS_ASCII
168 }
169
170 #[inline]
171 pub const fn has_escape(self) -> bool {
172 self.contains(Self::HAS_ESCAPE)
173 }
174
175 #[inline]
176 pub const fn has_null(self) -> bool {
177 self.contains(Self::HAS_NULL)
178 }
179
180 #[inline]
181 pub const fn is_ascii(self) -> bool {
182 self.contains(Self::IS_ASCII)
183 }
184
185 #[inline]
186 fn mark_escape(&mut self) {
187 self.insert(Self::HAS_ESCAPE);
188 }
189
190 #[inline]
191 fn mark_null(&mut self) {
192 self.insert(Self::HAS_NULL);
193 }
194
195 #[inline]
196 fn mark_non_ascii(&mut self) {
197 self.remove(Self::IS_ASCII);
198 }
199
200 #[inline]
201 fn merge(&mut self, other: Self) {
202 let is_ascii = self.is_ascii() && other.is_ascii();
203 self.insert(other);
204 if !is_ascii {
205 self.mark_non_ascii();
206 }
207 }
208}
209
210#[derive(Debug, Clone, Copy, PartialEq, Eq)]
211pub struct Trivia {
212 pub range: Range,
213 pub end: Pos,
214 pub first_comment_start: Option<Pos>,
215 pub has_white_space: bool,
216}
217
218impl Trivia {
219 #[inline]
220 pub fn has_whitespace(self) -> bool {
221 self.has_white_space
222 }
223}
224
225#[derive(Debug, Clone, Copy, PartialEq, Eq)]
228pub struct TokenWithTrivia {
229 pub token: Token,
230 pub leading: Trivia,
231}
232
233pub trait LexerVisitor {
234 fn visit_ident(&mut self, name: &str, range: Range);
235}
236
237impl LexerVisitor for () {
238 #[inline(always)]
239 fn visit_ident(&mut self, _name: &str, _range: Range) {}
240}
241
242#[derive(Debug)]
243pub struct Lexer<'s, V: LexerVisitor = ()> {
244 value: &'s [u8],
245 scan_pos: Pos,
246 visitor: V,
247}
248
249impl<'s, V: LexerVisitor> Lexer<'s, V> {
250 pub fn new(value: &'s str, visitor: V) -> Self {
251 assert!(value.len() <= Pos::MAX as usize, "CSS input is too large");
252 Self {
253 value: value.as_bytes(),
254 scan_pos: 0,
255 visitor,
256 }
257 }
258
259 #[inline]
260 pub(crate) fn visitor_mut(&mut self) -> &mut V {
261 &mut self.visitor
262 }
263
264 #[inline]
265 fn visit_ident(&mut self, kind: TokenKind, range: Range) {
266 if !matches!(kind, TokenKind::Ident | TokenKind::Function) {
267 return;
268 }
269 let value = &self.value[range.start as usize..range.end as usize];
270 let value = unsafe { str::from_utf8_unchecked(value) };
272 self.visitor.visit_ident(value, range);
273 }
274
275 #[inline]
276 pub(crate) fn source_end(&self) -> Pos {
277 self.value.len() as Pos
278 }
279
280 #[inline(always)]
281 pub(crate) fn byte_at(&self, position: Pos) -> Option<u8> {
282 self.value.get(position as usize).copied()
283 }
284
285 #[inline(always)]
286 pub(crate) fn could_start_ident_at(&self, position: Pos) -> bool {
287 self.byte_at(position).is_some_and(|byte| {
288 matches!(byte, C_HYPHEN_MINUS | C_REVERSE_SOLIDUS) || is_name_start_byte(byte)
289 })
290 }
291
292 fn fast_forward_generic_value<F, C>(
298 &mut self,
299 state: &mut GenericValueScanState,
300 options: GenericValueScanOptions,
301 stop_at_top_level_left_curly: bool,
302 mut is_candidate: F,
303 mut is_comment_candidate: C,
304 icss_symbols: Option<&FxHashSet<&str>>,
305 ) -> Option<PrescannedIdent>
306 where
307 F: FnMut(&str) -> bool,
308 C: FnMut(&str) -> bool,
309 {
310 let mut position = self.scan_pos as usize;
311 let scan_start = position;
312 let mut candidate_token = None;
313 while position < self.value.len() {
314 let byte = self.value[position];
315 if is_white_space(byte) {
316 position += 1;
317 continue;
318 }
319
320 if byte == C_SOLIDUS && self.value.get(position + 1) == Some(&C_ASTERISK) {
321 let (kind, end, _, _, _) = self.scan_comment(position);
322 if options.keep_comments && kind == TokenKind::Comment {
323 let content = &self.value[position + 2..end.saturating_sub(2)];
324 let mut first = 0usize;
325 while content.get(first).is_some_and(|byte| is_white_space(*byte)) {
326 first += 1;
327 }
328 if content.get(first) == Some(&b'c') {
329 let content = unsafe { str::from_utf8_unchecked(content) };
330 if is_comment_candidate(content) {
331 break;
332 }
333 }
334 }
335 position = end;
336 continue;
337 }
338
339 if matches!(byte, C_QUOTATION_MARK | C_APOSTROPHE) {
340 if options.preserve_strings {
341 break;
342 }
343 position = self.scan_string(position, byte).1;
344 continue;
345 }
346
347 let starts_ident = match byte {
348 C_HYPHEN_MINUS | C_REVERSE_SOLIDUS => self.starts_ident_at(position),
349 _ => is_name_start_byte(byte),
350 };
351 if starts_ident {
352 let end = self.scan_plain_ascii_name(position);
353 if end == position || self.raw_name_needs_tokenizer(end) {
354 break;
355 }
356 let flags = TokenFlags::ascii();
357 let name = unsafe { str::from_utf8_unchecked(&self.value[position..end]) };
358 let is_function = self.value.get(end) == Some(&C_LEFT_PARENTHESIS);
359 let is_candidate = !flags.has_escape()
360 && (is_candidate(name)
361 || options
362 .property
363 .is_some_and(|property| special_value_is_candidate(property, name, icss_symbols)));
364 if flags.has_escape()
365 || is_candidate
366 || (is_function && is_dependency_value_function(name))
367 || (is_function && options.preserve_delimiters)
368 {
369 if is_candidate && !is_function && flags.is_ascii() && !flags.has_null() {
370 candidate_token = Some(PrescannedIdent {
371 start: position as Pos,
372 end: end as Pos,
373 flags,
374 });
375 position = end;
376 }
377 break;
378 }
379 self
380 .visitor
381 .visit_ident(name, Range::new(position as Pos, end as Pos));
382 if is_function {
383 state.parentheses += 1;
384 position = end + 1;
385 } else {
386 position = end;
387 }
388 continue;
389 }
390
391 let starts_number = match byte {
392 b'0'..=b'9' => true,
393 C_PLUS_SIGN | C_HYPHEN_MINUS | C_FULL_STOP => self.starts_number_at(position),
394 _ => false,
395 };
396 if starts_number {
397 let Some(end) = self.scan_raw_numeric_end(position) else {
398 break;
399 };
400 position = end;
401 continue;
402 }
403
404 match byte {
405 C_NUMBER_SIGN => {
406 let name_start = position + 1;
407 if self.starts_ident_at(name_start)
408 || self
409 .value
410 .get(name_start)
411 .is_some_and(|byte| is_digit(*byte) || *byte == C_HYPHEN_MINUS)
412 {
413 let end = self.scan_plain_ascii_name(name_start);
414 if end == name_start || self.raw_name_needs_tokenizer(end) {
415 break;
416 }
417 position = end;
418 } else {
419 position = name_start;
420 }
421 }
422 C_LEFT_PARENTHESIS => {
423 if options.preserve_delimiters {
424 break;
425 }
426 state.parentheses += 1;
427 position += 1;
428 }
429 C_RIGHT_PARENTHESIS => {
430 if options.preserve_delimiters {
431 break;
432 }
433 state.parentheses = state.parentheses.saturating_sub(1);
434 position += 1;
435 }
436 C_LEFT_SQUARE => {
437 if options.preserve_delimiters {
438 break;
439 }
440 state.squares += 1;
441 position += 1;
442 }
443 C_RIGHT_SQUARE => {
444 if options.preserve_delimiters {
445 break;
446 }
447 state.squares = state.squares.saturating_sub(1);
448 position += 1;
449 }
450 C_LEFT_CURLY if stop_at_top_level_left_curly && !state.is_nested() => break,
451 C_LEFT_CURLY => {
452 if options.preserve_delimiters {
453 break;
454 }
455 state.curlies += 1;
456 position += 1;
457 }
458 C_RIGHT_CURLY if state.curlies > 0 && !options.preserve_delimiters => {
459 state.curlies -= 1;
460 position += 1;
461 }
462 C_RIGHT_CURLY => break,
463 C_SEMICOLON if !state.is_nested() => break,
464 _ => position += 1,
465 }
466 }
467 self.scan_pos = position as Pos;
468 debug_assert!(
469 self.scan_pos >= scan_start as Pos,
470 "fast_forward moved scan_pos backward from {scan_start} to {position}"
471 );
472 candidate_token
473 }
474
475 pub(crate) fn fast_forward_selector<F, C>(
480 &mut self,
481 square_depth: &mut u32,
482 keep_comments: bool,
483 has_mode: bool,
484 mut is_ident_candidate: F,
485 mut is_comment_candidate: C,
486 ) -> bool
487 where
488 F: FnMut(&str) -> bool,
489 C: FnMut(&str) -> bool,
490 {
491 let mut position = self.scan_pos as usize;
492 let scan_start = position;
493 let mut invalidates_composes = false;
494 let mut trivia_start = None;
495
496 while position < self.value.len() {
497 let byte = self.value[position];
498
499 if is_white_space(byte) {
500 trivia_start.get_or_insert(position);
501 position += 1;
502 continue;
503 }
504
505 if byte == C_SOLIDUS && self.value.get(position + 1) == Some(&C_ASTERISK) {
506 let (kind, end, _, _, _) = self.scan_comment(position);
507 if keep_comments && kind == TokenKind::Comment {
508 let content = &self.value[position + 2..end.saturating_sub(2)];
509 let content = unsafe { str::from_utf8_unchecked(content) };
511 if is_comment_candidate(content) {
512 break;
513 }
514 }
515 trivia_start.get_or_insert(position);
516 position = end;
517 continue;
518 }
519
520 if *square_depth > 0 {
521 if byte == C_REVERSE_SOLIDUS && self.is_valid_escape_at(position) {
522 position = self.scan_escape(position);
523 trivia_start = None;
524 continue;
525 }
526 match byte {
527 C_QUOTATION_MARK | C_APOSTROPHE => {
528 position = self.scan_string(position, byte).1;
529 }
530 C_LEFT_SQUARE => {
531 *square_depth += 1;
532 position += 1;
533 }
534 C_RIGHT_SQUARE => {
535 *square_depth -= 1;
536 position += 1;
537 }
538 _ => position += 1,
539 }
540 trivia_start = None;
541 continue;
542 }
543
544 let starts_ident = match byte {
545 C_HYPHEN_MINUS | C_REVERSE_SOLIDUS => self.starts_ident_at(position),
546 _ => is_name_start_byte(byte),
547 };
548 if starts_ident {
549 let end = self.scan_name(position);
550 if end == position {
551 break;
552 }
553 if self.value.get(end) == Some(&C_LEFT_PARENTHESIS) {
559 break;
560 }
561 let name = &self.value[position..end];
562 let name = unsafe { str::from_utf8_unchecked(name) };
564 if is_ident_candidate(name) {
565 break;
566 }
567 self
568 .visitor
569 .visit_ident(name, Range::new(position as Pos, end as Pos));
570 invalidates_composes = true;
571 trivia_start = None;
572 position = end;
573 continue;
574 }
575
576 let starts_number = match byte {
577 b'0'..=b'9' => true,
578 C_PLUS_SIGN | C_HYPHEN_MINUS | C_FULL_STOP => self.starts_number_at(position),
579 _ => false,
580 };
581 if starts_number {
582 invalidates_composes = true;
583 trivia_start = None;
584 position = self.scan_numeric(position).1;
585 continue;
586 }
587
588 match byte {
589 C_LEFT_SQUARE => {
590 invalidates_composes = true;
591 *square_depth = 1;
592 trivia_start = None;
593 position += 1;
594 }
595 C_QUOTATION_MARK | C_APOSTROPHE => break,
599 C_FULL_STOP | C_NUMBER_SIGN | C_COLON if has_mode => break,
600 C_NUMBER_SIGN => {
601 let name_start = position + 1;
602 position = if self.starts_ident_at(name_start)
603 || self
604 .value
605 .get(name_start)
606 .is_some_and(|byte| is_digit(*byte) || *byte == C_HYPHEN_MINUS)
607 {
608 self.scan_name(name_start)
609 } else {
610 name_start
611 };
612 invalidates_composes = true;
613 trivia_start = None;
614 }
615 C_RIGHT_PARENTHESIS => {
616 position = trivia_start.unwrap_or(position);
617 break;
618 }
619 C_LEFT_PARENTHESIS | C_LEFT_CURLY | C_RIGHT_CURLY | C_COMMA | C_SEMICOLON | C_AT_SIGN => {
620 break;
621 }
622 _ => {
623 invalidates_composes = true;
624 trivia_start = None;
625 position += 1;
626 }
627 }
628 }
629
630 self.scan_pos = position as Pos;
631 debug_assert!(
632 self.scan_pos >= scan_start as Pos,
633 "selector fast-forward moved scan_pos backward from {scan_start} to {position}"
634 );
635 invalidates_composes
636 }
637
638 pub(crate) fn fast_forward(&mut self, end: TokenKind) -> Option<Range> {
654 match end {
655 TokenKind::RightParenthesis
656 | TokenKind::RightSquareBracket
657 | TokenKind::RightCurlyBracket => {}
658 _ => unreachable!("fast_forward accepts only closing bracket kinds"),
659 }
660 let open_start = self.scan_pos;
661 let mut stack: SmallVec<[TokenKind; 8]> = SmallVec::new();
662 stack.push(end);
663 let mut position = open_start as usize;
664 let bytes = self.value;
665 while position < bytes.len() {
666 let byte = bytes[position];
667 match byte {
668 C_QUOTATION_MARK | C_APOSTROPHE => {
669 let (kind, string_end, _, _, _) = self.scan_string(position, byte);
670 if kind == TokenKind::BadString {
671 return None;
672 }
673 position = string_end;
674 }
675 C_SOLIDUS if bytes.get(position + 1) == Some(&C_ASTERISK) => {
676 let (kind, comment_end, _, _, _) = self.scan_comment(position);
677 if kind == TokenKind::BadComment {
678 return None;
679 }
680 position = comment_end;
681 }
682 C_REVERSE_SOLIDUS if self.is_valid_escape_at(position) => {
683 position = self.scan_escape(position);
684 }
685 C_LEFT_PARENTHESIS => {
686 stack.push(TokenKind::RightParenthesis);
687 position += 1;
688 }
689 C_LEFT_SQUARE => {
690 stack.push(TokenKind::RightSquareBracket);
691 position += 1;
692 }
693 C_LEFT_CURLY => {
694 stack.push(TokenKind::RightCurlyBracket);
695 position += 1;
696 }
697 C_RIGHT_PARENTHESIS | C_RIGHT_SQUARE | C_RIGHT_CURLY => {
698 if *stack.last().expect("delimiter stack never empties") != kind_to_right(byte) {
699 return None;
700 }
701 stack.pop();
702 position += 1;
703 if stack.is_empty() {
704 self.scan_pos = position as Pos;
705 debug_assert!(
706 self.scan_pos >= open_start,
707 "fast_forward moved scan_pos backward from {open_start} to {position}"
708 );
709 return Some(Range::new(open_start, position as Pos));
710 }
711 }
712 C_SEMICOLON => return None,
716 _ if byte >= 0x80 => {
717 position += self.utf8_width_at(position);
718 }
719 _ => position += 1,
720 }
721 }
722 None
723 }
724
725 pub(crate) fn slice(&self, start: Pos, end: Pos) -> Option<&'s str> {
726 let range = Range::new(start, end);
727 let start = start as usize;
728 let end = end as usize;
729 if start > end || end > self.value.len() {
730 return None;
731 }
732 Some(unsafe { slice_unchecked(self.value, &range) })
735 }
736
737 #[inline(always)]
738 pub(crate) fn slice_trusted(&self, start: Pos, end: Pos) -> &'s str {
739 debug_assert!(start <= end && end <= self.value.len() as Pos);
740 unsafe { slice_unchecked(self.value, &Range::new(start, end)) }
743 }
744}
745
746impl Lexer<'_, ()> {
747 pub fn slice_range<'a>(input: &'a str, range: &Range) -> Option<&'a str> {
748 let start = range.start as usize;
749 let end = range.end as usize;
750 if start > end
751 || end > input.len()
752 || !input.is_char_boundary(start)
753 || !input.is_char_boundary(end)
754 {
755 return None;
756 }
757 Some(unsafe { slice_unchecked(input.as_bytes(), range) })
759 }
760}
761
762unsafe fn slice_unchecked<'a>(input: &'a [u8], range: &Range) -> &'a str {
763 unsafe {
764 let value = input.get_unchecked(range.start as usize..range.end as usize);
765 str::from_utf8_unchecked(value)
766 }
767}
768
769impl<V: LexerVisitor> Lexer<'_, V> {
770 #[inline]
771 pub(crate) fn scan_pos(&self) -> Pos {
772 self.scan_pos
773 }
774}
775
776impl<'s, V: LexerVisitor> Lexer<'s, V> {
777 #[inline]
783 pub fn next_token(&mut self) -> Token {
784 let start = self.scan_pos as usize;
785 let len = self.value.len();
786 if start >= len {
787 self.scan_pos = len as Pos;
788 return Token::new(
789 TokenKind::Eof,
790 Range::new(len as Pos, len as Pos),
791 Range::new(len as Pos, len as Pos),
792 );
793 }
794
795 let byte = self.value[start];
796 let (kind, end, value_start, value_end, flags) = match byte {
797 C_SPACE | C_TAB | C_LINE_FEED | C_CARRIAGE_RETURN | C_FORM_FEED => {
798 self.scan_whitespace(start)
799 }
800 C_QUOTATION_MARK => self.scan_string(start, C_QUOTATION_MARK),
801 C_APOSTROPHE => self.scan_string(start, C_APOSTROPHE),
802 C_NUMBER_SIGN => {
803 let value_start = start + 1;
804 if self.starts_ident_at(value_start) {
805 let (end, flags) = self.scan_name_with_flags(value_start);
806 (TokenKind::IdHash, end, value_start, end, flags)
807 } else if self
808 .value
809 .get(value_start)
810 .is_some_and(|byte| is_digit(*byte) || *byte == C_HYPHEN_MINUS)
811 {
812 let (end, flags) = self.scan_name_with_flags(value_start);
813 (TokenKind::Hash, end, value_start, end, flags)
814 } else {
815 (
816 TokenKind::Delim,
817 start + 1,
818 start,
819 start + 1,
820 TokenFlags::ascii(),
821 )
822 }
823 }
824 C_LEFT_PARENTHESIS => (
825 TokenKind::LeftParenthesis,
826 start + 1,
827 start,
828 start + 1,
829 TokenFlags::ascii(),
830 ),
831 C_RIGHT_PARENTHESIS => (
832 TokenKind::RightParenthesis,
833 start + 1,
834 start,
835 start + 1,
836 TokenFlags::ascii(),
837 ),
838 C_LEFT_SQUARE => (
839 TokenKind::LeftSquareBracket,
840 start + 1,
841 start,
842 start + 1,
843 TokenFlags::ascii(),
844 ),
845 C_RIGHT_SQUARE => (
846 TokenKind::RightSquareBracket,
847 start + 1,
848 start,
849 start + 1,
850 TokenFlags::ascii(),
851 ),
852 C_LEFT_CURLY => (
853 TokenKind::LeftCurlyBracket,
854 start + 1,
855 start,
856 start + 1,
857 TokenFlags::ascii(),
858 ),
859 C_RIGHT_CURLY => (
860 TokenKind::RightCurlyBracket,
861 start + 1,
862 start,
863 start + 1,
864 TokenFlags::ascii(),
865 ),
866 C_COLON => (
867 TokenKind::Colon,
868 start + 1,
869 start,
870 start + 1,
871 TokenFlags::ascii(),
872 ),
873 C_SEMICOLON => (
874 TokenKind::Semicolon,
875 start + 1,
876 start,
877 start + 1,
878 TokenFlags::ascii(),
879 ),
880 C_COMMA => (
881 TokenKind::Comma,
882 start + 1,
883 start,
884 start + 1,
885 TokenFlags::ascii(),
886 ),
887 C_PLUS_SIGN => {
888 if self.starts_number_at(start) {
889 self.scan_numeric(start)
890 } else {
891 (
892 TokenKind::Delim,
893 start + 1,
894 start,
895 start + 1,
896 TokenFlags::ascii(),
897 )
898 }
899 }
900 C_HYPHEN_MINUS => {
901 if self.starts_number_at(start) {
902 self.scan_numeric(start)
903 } else if self.starts_ident_at(start) {
904 self.scan_ident_like(start)
905 } else {
906 (
907 TokenKind::Delim,
908 start + 1,
909 start,
910 start + 1,
911 TokenFlags::ascii(),
912 )
913 }
914 }
915 C_FULL_STOP => {
916 if self.starts_number_at(start) {
917 self.scan_numeric(start)
918 } else {
919 (
920 TokenKind::Delim,
921 start + 1,
922 start,
923 start + 1,
924 TokenFlags::ascii(),
925 )
926 }
927 }
928 C_SOLIDUS => {
929 if self.value.get(start + 1) == Some(&C_ASTERISK) {
930 self.scan_comment(start)
931 } else {
932 (
933 TokenKind::Delim,
934 start + 1,
935 start,
936 start + 1,
937 TokenFlags::ascii(),
938 )
939 }
940 }
941 C_AT_SIGN => {
942 let value_start = start + 1;
943 if self.starts_ident_at(value_start) {
944 let (end, flags) = self.scan_name_with_flags(value_start);
945 (TokenKind::AtKeyword, end, value_start, end, flags)
946 } else {
947 (
948 TokenKind::Delim,
949 start + 1,
950 start,
951 start + 1,
952 TokenFlags::ascii(),
953 )
954 }
955 }
956 C_REVERSE_SOLIDUS => {
957 if self.is_valid_escape_at(start) {
958 self.scan_ident_like(start)
959 } else {
960 (
961 TokenKind::Delim,
962 start + 1,
963 start,
964 start + 1,
965 TokenFlags::ascii(),
966 )
967 }
968 }
969 b'0'..=b'9' => self.scan_numeric(start),
970 b'$' if self.value[start..].starts_with(b"$=") => (
971 TokenKind::SuffixMatch,
972 start + 2,
973 start,
974 start + 2,
975 TokenFlags::ascii(),
976 ),
977 b'^' if self.value[start..].starts_with(b"^=") => (
978 TokenKind::PrefixMatch,
979 start + 2,
980 start,
981 start + 2,
982 TokenFlags::ascii(),
983 ),
984 b'|' if self.value[start..].starts_with(b"|=") => (
985 TokenKind::DashMatch,
986 start + 2,
987 start,
988 start + 2,
989 TokenFlags::ascii(),
990 ),
991 b'~' if self.value[start..].starts_with(b"~=") => (
992 TokenKind::IncludeMatch,
993 start + 2,
994 start,
995 start + 2,
996 TokenFlags::ascii(),
997 ),
998 b'*' if self.value[start..].starts_with(b"*=") => (
999 TokenKind::SubstringMatch,
1000 start + 2,
1001 start,
1002 start + 2,
1003 TokenFlags::ascii(),
1004 ),
1005 _ if self.starts_ident_at(start) => self.scan_ident_like(start),
1006 _ => (
1007 TokenKind::Delim,
1008 start + 1,
1009 start,
1010 start + 1,
1011 TokenFlags::ascii(),
1012 ),
1013 };
1014
1015 self.scan_pos = end as Pos;
1016 debug_assert!(
1017 self.scan_pos >= start as Pos,
1018 "scan_pos moved backward from {start} to {}",
1019 self.scan_pos
1020 );
1021 let token = Token::with_flags(
1022 kind,
1023 Range::new(start as Pos, end as Pos),
1024 Range::new(value_start as Pos, value_end as Pos),
1025 flags,
1026 );
1027 self.visit_ident(token.kind, token.value_range);
1028 token
1029 }
1030
1031 #[inline]
1032 fn scan_whitespace(&self, start: usize) -> (TokenKind, usize, usize, usize, TokenFlags) {
1033 let bytes = self.value;
1034 let mut end = start;
1035 while end < bytes.len() && is_white_space(bytes[end]) {
1036 end += 1;
1037 }
1038 (TokenKind::WhiteSpace, end, start, end, TokenFlags::ascii())
1039 }
1040
1041 fn scan_comment(&self, start: usize) -> (TokenKind, usize, usize, usize, TokenFlags) {
1042 let bytes = self.value;
1043 let mut end = start + 2;
1044 let mut flags = TokenFlags::ascii();
1045 while end < bytes.len() {
1046 if bytes[end] == C_ASTERISK && bytes.get(end + 1) == Some(&C_SOLIDUS) {
1047 return (TokenKind::Comment, end + 2, start + 2, end, flags);
1048 }
1049 if bytes[end] == 0 {
1050 flags.mark_null();
1051 } else if !bytes[end].is_ascii() {
1052 flags.mark_non_ascii();
1053 }
1054 end += if bytes[end] < 0x80 {
1055 1
1056 } else {
1057 self.utf8_width_at(end)
1058 };
1059 }
1060 (
1061 TokenKind::BadComment,
1062 bytes.len(),
1063 start + 2,
1064 bytes.len(),
1065 flags,
1066 )
1067 }
1068
1069 fn scan_string(&self, start: usize, quote: u8) -> (TokenKind, usize, usize, usize, TokenFlags) {
1070 let mut end = start + 1;
1071 let mut flags = TokenFlags::ascii();
1072 while end < self.value.len() {
1073 let byte = self.value[end];
1074 if byte == quote {
1075 return (TokenKind::QuotedString, end + 1, start + 1, end, flags);
1076 }
1077 if is_new_line(byte) {
1078 return (TokenKind::BadString, end, start + 1, end, flags);
1079 }
1080 if byte == 0 {
1081 flags.mark_null();
1082 } else if !byte.is_ascii() {
1083 flags.mark_non_ascii();
1084 }
1085 if byte == C_REVERSE_SOLIDUS {
1086 flags.mark_escape();
1087 if self.value.get(end + 1).is_some_and(|next| !next.is_ascii()) {
1088 flags.mark_non_ascii();
1089 }
1090 if self
1091 .value
1092 .get(end + 1)
1093 .is_some_and(|next| is_new_line(*next))
1094 {
1095 end += 1;
1096 if self.value.get(end) == Some(&C_CARRIAGE_RETURN)
1097 && self.value.get(end + 1) == Some(&C_LINE_FEED)
1098 {
1099 end += 1;
1100 }
1101 end += 1;
1102 } else if self.is_valid_escape_at(end) {
1103 end = self.scan_escape(end);
1104 } else {
1105 end += 1;
1106 }
1107 } else {
1108 end += self.utf8_width_at(end);
1109 }
1110 }
1111 (TokenKind::BadString, end, start + 1, end, flags)
1112 }
1113
1114 fn scan_ident_like(&self, start: usize) -> (TokenKind, usize, usize, usize, TokenFlags) {
1115 let (name_end, name_flags) = self.scan_name_with_flags(start);
1116 if self.value.get(name_end) != Some(&C_LEFT_PARENTHESIS) {
1117 return (TokenKind::Ident, name_end, start, name_end, name_flags);
1118 }
1119
1120 let open_end = name_end + 1;
1121 let name = &self.value[start..name_end];
1122 let name = unsafe { str::from_utf8_unchecked(name) };
1124 let mut normalized = [0; MAX_CSS_KEYWORD_LEN];
1125 let normalized = if name_flags.has_escape() {
1126 decode_css_keyword(name, &mut normalized)
1127 } else {
1128 lowercase_ascii_keyword(name, &mut normalized)
1129 };
1130 let is_url = normalized == Some("url");
1131 if is_url {
1132 let content_start = self.skip_white_space(open_end);
1133 if !matches!(
1134 self.value.get(content_start),
1135 Some(&C_QUOTATION_MARK | &C_APOSTROPHE)
1136 ) {
1137 let (kind, end, value_start, value_end, url_flags) = self.scan_url(start, content_start);
1138 let mut flags = name_flags;
1139 flags.merge(url_flags);
1140 return (kind, end, value_start, value_end, flags);
1141 }
1142 }
1143 (TokenKind::Function, open_end, start, name_end, name_flags)
1144 }
1145
1146 fn scan_url(
1147 &self,
1148 start: usize,
1149 content_start: usize,
1150 ) -> (TokenKind, usize, usize, usize, TokenFlags) {
1151 let mut end = content_start;
1152 let mut flags = TokenFlags::ascii();
1153 while end < self.value.len() {
1154 let byte = self.value[end];
1155 if byte == C_RIGHT_PARENTHESIS {
1156 return (TokenKind::Url, end + 1, content_start, end, flags);
1157 }
1158 if is_white_space(byte) {
1159 let content_end = end;
1160 let close = self.skip_white_space(end);
1161 if self.value.get(close) == Some(&C_RIGHT_PARENTHESIS) {
1162 return (TokenKind::Url, close + 1, content_start, content_end, flags);
1163 }
1164 return self.scan_bad_url(start, close, content_start, flags);
1165 }
1166 if byte == 0 {
1167 flags.mark_null();
1168 } else if !byte.is_ascii() {
1169 flags.mark_non_ascii();
1170 }
1171 if byte == C_QUOTATION_MARK
1172 || byte == C_APOSTROPHE
1173 || byte == C_LEFT_PARENTHESIS
1174 || is_non_printable(byte)
1175 {
1176 return self.scan_bad_url(start, end, content_start, flags);
1177 }
1178 if byte == C_REVERSE_SOLIDUS {
1179 flags.mark_escape();
1180 if self.value.get(end + 1).is_some_and(|next| !next.is_ascii()) {
1181 flags.mark_non_ascii();
1182 }
1183 if self.is_valid_escape_at(end) {
1184 end = self.scan_escape(end);
1185 } else if self
1186 .value
1187 .get(end + 1)
1188 .is_some_and(|next| is_new_line(*next))
1189 {
1190 end += 2;
1191 if self.value.get(end - 1) == Some(&C_CARRIAGE_RETURN)
1192 && self.value.get(end) == Some(&C_LINE_FEED)
1193 {
1194 end += 1;
1195 }
1196 } else {
1197 return self.scan_bad_url(start, end, content_start, flags);
1198 }
1199 } else {
1200 end += self.utf8_width_at(end);
1201 }
1202 }
1203 (
1204 TokenKind::BadUrl,
1205 self.value.len(),
1206 content_start,
1207 self.value.len(),
1208 flags,
1209 )
1210 }
1211
1212 fn scan_bad_url(
1213 &self,
1214 _start: usize,
1215 mut end: usize,
1216 content_start: usize,
1217 mut flags: TokenFlags,
1218 ) -> (TokenKind, usize, usize, usize, TokenFlags) {
1219 while end < self.value.len() {
1220 let byte = self.value[end];
1221 if byte == 0 {
1222 flags.mark_null();
1223 } else if !byte.is_ascii() {
1224 flags.mark_non_ascii();
1225 }
1226 if byte == C_RIGHT_PARENTHESIS {
1227 end += 1;
1228 break;
1229 }
1230 if byte == C_REVERSE_SOLIDUS {
1231 flags.mark_escape();
1232 if self.value.get(end + 1).is_some_and(|next| !next.is_ascii()) {
1233 flags.mark_non_ascii();
1234 }
1235 if self.is_valid_escape_at(end) {
1236 end = self.scan_escape(end);
1237 } else if self
1238 .value
1239 .get(end + 1)
1240 .is_some_and(|next| is_new_line(*next))
1241 {
1242 end += 2;
1243 if self.value.get(end - 1) == Some(&C_CARRIAGE_RETURN)
1244 && self.value.get(end) == Some(&C_LINE_FEED)
1245 {
1246 end += 1;
1247 }
1248 } else {
1249 end += 1;
1250 }
1251 } else {
1252 end += self.utf8_width_at(end);
1253 }
1254 }
1255 (TokenKind::BadUrl, end, content_start, end, flags)
1256 }
1257
1258 fn scan_numeric(&self, start: usize) -> (TokenKind, usize, usize, usize, TokenFlags) {
1259 let end = self.scan_number_end(start);
1260 if self.value.get(end) == Some(&C_PERCENTAGE) {
1261 return (
1262 TokenKind::Percentage,
1263 end + 1,
1264 start,
1265 end + 1,
1266 TokenFlags::ascii(),
1267 );
1268 }
1269 if self.starts_ident_at(end) {
1270 let (end, flags) = self.scan_name_with_flags(end);
1271 return (TokenKind::Dimension, end, start, end, flags);
1272 }
1273 (TokenKind::Number, end, start, end, TokenFlags::ascii())
1274 }
1275
1276 #[inline]
1277 fn scan_number_end(&self, start: usize) -> usize {
1278 let mut end = start;
1279 if matches!(self.value.get(end), Some(&C_PLUS_SIGN | &C_HYPHEN_MINUS)) {
1280 end += 1;
1281 }
1282 while self.value.get(end).is_some_and(|byte| is_digit(*byte)) {
1283 end += 1;
1284 }
1285 if self.value.get(end) == Some(&C_FULL_STOP)
1286 && self.value.get(end + 1).is_some_and(|byte| is_digit(*byte))
1287 {
1288 end += 1;
1289 while self.value.get(end).is_some_and(|byte| is_digit(*byte)) {
1290 end += 1;
1291 }
1292 }
1293 if matches!(self.value.get(end), Some(&C_LOWER_E | &C_UPPER_E)) {
1294 let exponent_start = end;
1295 let mut exponent_end = end + 1;
1296 if matches!(
1297 self.value.get(exponent_end),
1298 Some(&C_PLUS_SIGN | &C_HYPHEN_MINUS)
1299 ) {
1300 exponent_end += 1;
1301 }
1302 if self
1303 .value
1304 .get(exponent_end)
1305 .is_some_and(|byte| is_digit(*byte))
1306 {
1307 end = exponent_end + 1;
1308 while self.value.get(end).is_some_and(|byte| is_digit(*byte)) {
1309 end += 1;
1310 }
1311 } else {
1312 end = exponent_start;
1313 }
1314 }
1315 end
1316 }
1317
1318 #[inline]
1319 fn skip_white_space(&self, mut position: usize) -> usize {
1320 while self
1321 .value
1322 .get(position)
1323 .is_some_and(|byte| is_white_space(*byte))
1324 {
1325 position += 1;
1326 }
1327 position
1328 }
1329
1330 #[inline]
1331 fn scan_plain_ascii_name(&self, mut position: usize) -> usize {
1332 while self
1333 .value
1334 .get(position)
1335 .is_some_and(|byte| PLAIN_ASCII_NAME_BYTE[*byte as usize])
1336 {
1337 position += 1;
1338 }
1339 position
1340 }
1341
1342 #[inline]
1343 fn raw_name_needs_tokenizer(&self, position: usize) -> bool {
1344 self
1345 .value
1346 .get(position)
1347 .is_some_and(|byte| *byte == 0 || *byte == C_REVERSE_SOLIDUS || !byte.is_ascii())
1348 }
1349
1350 #[inline]
1351 fn scan_raw_numeric_end(&self, start: usize) -> Option<usize> {
1352 let end = self.scan_number_end(start);
1353 if self.value.get(end) == Some(&C_PERCENTAGE) {
1354 return Some(end + 1);
1355 }
1356 if self.starts_ident_at(end) {
1357 let name_end = self.scan_plain_ascii_name(end);
1358 if name_end == end || self.raw_name_needs_tokenizer(name_end) {
1359 return None;
1360 }
1361 return Some(name_end);
1362 }
1363 if self.raw_name_needs_tokenizer(end) {
1364 return None;
1365 }
1366 Some(end)
1367 }
1368
1369 #[inline]
1370 fn scan_name(&self, position: usize) -> usize {
1371 self.scan_name_impl(position, false).0
1372 }
1373
1374 #[inline]
1375 fn scan_name_with_flags(&self, position: usize) -> (usize, TokenFlags) {
1376 self.scan_name_impl(position, true)
1377 }
1378
1379 #[inline]
1380 fn scan_name_impl(&self, mut position: usize, track_flags: bool) -> (usize, TokenFlags) {
1381 let bytes = self.value;
1382 let mut flags = TokenFlags::ascii();
1383 while position < bytes.len() {
1384 while position < bytes.len() {
1385 let byte = bytes[position];
1386 if PLAIN_ASCII_NAME_BYTE[byte as usize] {
1387 position += 1;
1388 } else if byte == 0 {
1389 if track_flags {
1390 flags.mark_null();
1391 }
1392 position += 1;
1393 } else {
1394 break;
1395 }
1396 }
1397 if position == bytes.len() {
1398 break;
1399 }
1400
1401 let byte = bytes[position];
1402 if byte == C_REVERSE_SOLIDUS {
1403 if self.is_valid_escape_at(position) {
1404 if track_flags {
1405 flags.mark_escape();
1406 if bytes.get(position + 1).is_some_and(|next| !next.is_ascii()) {
1407 flags.mark_non_ascii();
1408 }
1409 }
1410 position = self.scan_escape(position);
1411 } else {
1412 break;
1413 }
1414 } else if byte.is_ascii() {
1415 break;
1416 } else {
1417 if track_flags {
1418 flags.mark_non_ascii();
1419 }
1420 position += self.utf8_width_at(position);
1421 }
1422 }
1423 (position, flags)
1424 }
1425
1426 #[inline]
1427 fn starts_ident_at(&self, position: usize) -> bool {
1428 let Some(byte) = self.value.get(position).copied() else {
1429 return false;
1430 };
1431 match byte {
1432 C_HYPHEN_MINUS => match self.value.get(position + 1).copied() {
1433 Some(next) => {
1434 is_name_start_byte(next)
1435 || next == C_HYPHEN_MINUS
1436 || self.is_valid_escape_at(position + 1)
1437 }
1438 None => false,
1439 },
1440 C_REVERSE_SOLIDUS => self.is_valid_escape_at(position),
1441 _ => is_name_start_byte(byte),
1442 }
1443 }
1444
1445 #[inline]
1446 fn starts_number_at(&self, position: usize) -> bool {
1447 let Some(first) = self.value.get(position).copied() else {
1448 return false;
1449 };
1450 let second = self.value.get(position + 1).copied();
1451 let third = self.value.get(position + 2).copied();
1452 match first {
1453 C_PLUS_SIGN | C_HYPHEN_MINUS => {
1454 second.is_some_and(is_digit) || (second == Some(C_FULL_STOP) && third.is_some_and(is_digit))
1455 }
1456 C_FULL_STOP => second.is_some_and(is_digit),
1457 _ => is_digit(first),
1458 }
1459 }
1460
1461 #[inline]
1462 fn is_valid_escape_at(&self, position: usize) -> bool {
1463 self.value.get(position) == Some(&C_REVERSE_SOLIDUS)
1464 && self
1465 .value
1466 .get(position + 1)
1467 .is_some_and(|byte| !is_new_line(*byte))
1468 }
1469
1470 #[inline]
1471 fn scan_escape(&self, position: usize) -> usize {
1472 debug_assert!(self.is_valid_escape_at(position));
1473 let mut end = position + 1;
1474 if self.value[end].is_ascii_hexdigit() {
1475 let mut digits = 0;
1476 while end < self.value.len() && digits < 6 && self.value[end].is_ascii_hexdigit() {
1477 end += 1;
1478 digits += 1;
1479 }
1480 if self
1481 .value
1482 .get(end)
1483 .is_some_and(|byte| is_white_space(*byte))
1484 {
1485 end += 1;
1486 if self.value.get(end - 1) == Some(&C_CARRIAGE_RETURN)
1487 && self.value.get(end) == Some(&C_LINE_FEED)
1488 {
1489 end += 1;
1490 }
1491 }
1492 return end;
1493 }
1494 end + self.utf8_width_at(end)
1495 }
1496
1497 #[inline]
1498 fn utf8_width_at(&self, position: usize) -> usize {
1499 let byte = self.value[position];
1500 if byte < 0x80 {
1501 1
1502 } else if byte < 0xE0 {
1503 2
1504 } else if byte < 0xF0 {
1505 3
1506 } else {
1507 4
1508 }
1509 }
1510}
1511
1512pub(crate) struct TokenStream<'a, 's, V: LexerVisitor = ()> {
1532 lexer: &'a mut Lexer<'s, V>,
1533 consumed: Pos,
1534 buffered: VecDeque<TokenWithTrivia>,
1535 generic_value_state: GenericValueScanState,
1536 at_rule_state: GenericValueScanState,
1537 special_value_state: GenericValueScanState,
1538}
1539
1540#[derive(Debug, Default)]
1541pub(crate) struct GenericValueScanState {
1542 parentheses: u32,
1543 squares: u32,
1544 curlies: u32,
1545}
1546
1547#[derive(Debug, Clone, Copy)]
1548struct GenericValueScanOptions {
1549 keep_comments: bool,
1550 preserve_strings: bool,
1551 preserve_delimiters: bool,
1552 property: Option<PropertyKind>,
1553}
1554
1555#[derive(Debug, Clone, Copy)]
1556struct PrescannedIdent {
1557 start: Pos,
1558 end: Pos,
1559 flags: TokenFlags,
1560}
1561
1562impl GenericValueScanState {
1563 #[inline]
1564 fn is_nested(&self) -> bool {
1565 self.parentheses != 0 || self.squares != 0 || self.curlies != 0
1566 }
1567}
1568
1569#[inline]
1570fn is_dependency_value_function(name: &str) -> bool {
1571 let mut lowercase = [0; MAX_CSS_KEYWORD_LEN];
1572 let Some(name) = lowercase_ascii_keyword(name, &mut lowercase) else {
1573 return name.starts_with("--");
1574 };
1575 matches!(name, "url" | "var" | "image-set")
1576 || strip_vendor_prefix(name) == Some("image-set")
1577 || name.starts_with("--")
1578}
1579
1580#[inline(always)]
1581fn never_fast_forward_candidate(_: &str) -> bool {
1582 false
1583}
1584
1585impl<'a, 's, V: LexerVisitor> TokenStream<'a, 's, V> {
1586 pub(crate) fn from_lexer(lexer: &'a mut Lexer<'s, V>) -> Self {
1587 Self {
1588 lexer,
1589 consumed: 0,
1590 buffered: VecDeque::new(),
1591 generic_value_state: GenericValueScanState::default(),
1592 at_rule_state: GenericValueScanState::default(),
1593 special_value_state: GenericValueScanState::default(),
1594 }
1595 }
1596
1597 #[inline(always)]
1598 pub(crate) fn next(&mut self, keep_comments: bool) -> TokenWithTrivia {
1599 let token = if let Some(token) = self.buffered.pop_front() {
1600 token
1601 } else {
1602 self.read_significant(keep_comments)
1603 };
1604 debug_assert!(
1605 token.token.range.start >= self.consumed,
1606 "token range starts before consumed_pos: {:?} < {}",
1607 token.token.range,
1608 self.consumed
1609 );
1610 self.consumed = token.token.range.end;
1611 if matches!(
1612 token.token.kind,
1613 TokenKind::Semicolon | TokenKind::RightCurlyBracket
1614 ) {
1615 self.generic_value_state = GenericValueScanState::default();
1616 }
1617 debug_assert!(
1618 self.consumed <= self.lexer.scan_pos(),
1619 "consumed_pos ({}) exceeded scan_pos ({})",
1620 self.consumed,
1621 self.lexer.scan_pos()
1622 );
1623 token
1624 }
1625
1626 #[inline]
1631 pub(crate) fn next_parser_token(&mut self) -> TokenWithTrivia {
1632 let mut item = self.next(true);
1633 if item.token.kind != TokenKind::Comment {
1634 return item;
1635 }
1636
1637 let start = item.leading.range.start;
1638 let first_comment_start = item
1639 .leading
1640 .first_comment_start
1641 .or(Some(item.token.range.start));
1642 let mut has_white_space = item.leading.has_white_space;
1643 loop {
1644 item = self.next(true);
1645 has_white_space |= item.leading.has_white_space;
1646 if item.token.kind == TokenKind::Comment {
1647 continue;
1648 }
1649 let end = item.leading.end;
1650 item.leading = Trivia {
1651 range: Range::new(start, end),
1652 end,
1653 first_comment_start,
1654 has_white_space,
1655 };
1656 return item;
1657 }
1658 }
1659
1660 #[inline]
1661 pub(crate) fn peek(&mut self, keep_comments: bool) -> TokenWithTrivia {
1662 if self.buffered.is_empty() {
1663 let item = self.read_significant(keep_comments);
1664 self.buffered.push_back(item);
1665 }
1666 *self
1667 .buffered
1668 .front()
1669 .expect("peek must buffer an item before reading the front")
1670 }
1671
1672 #[inline]
1676 pub(crate) fn peek_parser_token(&mut self) -> TokenWithTrivia {
1677 self.peek(true);
1678 let mut leading_start = None;
1679 let mut first_comment_start = None;
1680 let mut has_white_space = false;
1681 let mut index = 0usize;
1682 loop {
1683 if index == self.buffered.len() {
1684 let item = self.read_significant(true);
1685 self.buffered.push_back(item);
1686 }
1687 let item = self.buffered[index];
1688 leading_start.get_or_insert(item.leading.range.start);
1689 has_white_space |= item.leading.has_whitespace();
1690 if matches!(item.token.kind, TokenKind::Comment | TokenKind::BadComment) {
1691 first_comment_start.get_or_insert(item.token.range.start);
1692 index += 1;
1693 continue;
1694 }
1695
1696 let end = item.leading.end;
1697 let mut result = item;
1698 result.leading = Trivia {
1699 range: Range::new(leading_start.unwrap_or(end), end),
1700 end,
1701 first_comment_start: first_comment_start.or(item.leading.first_comment_start),
1702 has_white_space,
1703 };
1704 return result;
1705 }
1706 }
1707
1708 #[inline]
1711 pub(crate) fn peek_significant_skipping_comments(
1712 &mut self,
1713 keep_comments: bool,
1714 ) -> TokenWithTrivia {
1715 self.peek(keep_comments);
1716 for item in &self.buffered {
1717 if !matches!(item.token.kind, TokenKind::Comment | TokenKind::BadComment) {
1718 return *item;
1719 }
1720 }
1721 loop {
1722 let item = self.read_significant(keep_comments);
1723 self.buffered.push_back(item);
1724 if !matches!(item.token.kind, TokenKind::Comment | TokenKind::BadComment) {
1725 return item;
1726 }
1727 }
1728 }
1729
1730 #[inline]
1733 pub(crate) fn fast_forward_generic_value_if_buffer_empty<F, C>(
1734 &mut self,
1735 keep_comments: bool,
1736 preserve_strings: bool,
1737 preserve_delimiters: bool,
1738 mut is_candidate: F,
1739 mut is_comment_candidate: C,
1740 ) where
1741 F: FnMut(&str) -> bool,
1742 C: FnMut(&str) -> bool,
1743 {
1744 if !self.buffered.is_empty() {
1745 return;
1746 }
1747 let candidate = self.lexer.fast_forward_generic_value(
1748 &mut self.generic_value_state,
1749 GenericValueScanOptions {
1750 keep_comments,
1751 preserve_strings,
1752 preserve_delimiters,
1753 property: None,
1754 },
1755 false,
1756 &mut is_candidate,
1757 &mut is_comment_candidate,
1758 None,
1759 );
1760 self.finish_value_fast_forward(candidate);
1761 }
1762
1763 #[inline]
1764 pub(crate) fn fast_forward_generic_value_without_candidates_if_buffer_empty(
1765 &mut self,
1766 preserve_strings: bool,
1767 preserve_delimiters: bool,
1768 ) {
1769 if !self.buffered.is_empty() {
1770 return;
1771 }
1772 let candidate = self.lexer.fast_forward_generic_value(
1773 &mut self.generic_value_state,
1774 GenericValueScanOptions {
1775 keep_comments: false,
1776 preserve_strings,
1777 preserve_delimiters,
1778 property: None,
1779 },
1780 false,
1781 never_fast_forward_candidate,
1782 never_fast_forward_candidate,
1783 None,
1784 );
1785 self.finish_value_fast_forward(candidate);
1786 }
1787
1788 #[inline]
1789 pub(crate) fn fast_forward_at_rule_if_buffer_empty<F, C>(
1790 &mut self,
1791 keep_comments: bool,
1792 preserve_strings: bool,
1793 preserve_delimiters: bool,
1794 mut is_candidate: F,
1795 mut is_comment_candidate: C,
1796 ) where
1797 F: FnMut(&str) -> bool,
1798 C: FnMut(&str) -> bool,
1799 {
1800 if !self.buffered.is_empty() {
1801 return;
1802 }
1803 let candidate = self.lexer.fast_forward_generic_value(
1804 &mut self.at_rule_state,
1805 GenericValueScanOptions {
1806 keep_comments,
1807 preserve_strings,
1808 preserve_delimiters,
1809 property: None,
1810 },
1811 true,
1812 &mut is_candidate,
1813 &mut is_comment_candidate,
1814 None,
1815 );
1816 self.finish_value_fast_forward(candidate);
1817 }
1818
1819 #[inline]
1820 pub(crate) fn reset_at_rule_scan_state(&mut self) {
1821 self.at_rule_state = GenericValueScanState::default();
1822 }
1823
1824 #[inline]
1825 pub(crate) fn fast_forward_special_value_if_buffer_empty(
1826 &mut self,
1827 keep_comments: bool,
1828 preserve_strings: bool,
1829 preserve_delimiters: bool,
1830 property: PropertyKind,
1831 icss_symbols: Option<&FxHashSet<&str>>,
1832 ) {
1833 if !self.buffered.is_empty() {
1834 return;
1835 }
1836 let candidate = self.lexer.fast_forward_generic_value(
1837 &mut self.special_value_state,
1838 GenericValueScanOptions {
1839 keep_comments,
1840 preserve_strings,
1841 preserve_delimiters,
1842 property: Some(property),
1843 },
1844 false,
1845 never_fast_forward_candidate,
1846 is_css_modules_pure_magic_comment,
1847 icss_symbols,
1848 );
1849 self.finish_value_fast_forward(candidate);
1850 }
1851
1852 #[inline]
1853 fn finish_value_fast_forward(&mut self, candidate: Option<PrescannedIdent>) {
1854 let Some(candidate) = candidate else {
1855 self.consumed = self.lexer.scan_pos();
1856 return;
1857 };
1858 debug_assert_eq!(candidate.end, self.lexer.scan_pos());
1859 self.consumed = candidate.start;
1860 let range = Range::new(candidate.start, candidate.end);
1861 self.buffered.push_back(TokenWithTrivia {
1862 token: Token::with_flags(TokenKind::Ident, range, range, candidate.flags),
1863 leading: Trivia {
1864 range: Range::new(candidate.start, candidate.start),
1865 end: candidate.start,
1866 first_comment_start: None,
1867 has_white_space: false,
1868 },
1869 });
1870 }
1871
1872 #[inline]
1873 pub(crate) fn reset_special_value_scan_state(&mut self) {
1874 self.special_value_state = GenericValueScanState::default();
1875 }
1876
1877 #[inline]
1878 pub(crate) fn fast_forward_selector_if_buffer_empty<F, C>(
1879 &mut self,
1880 square_depth: &mut u32,
1881 keep_comments: bool,
1882 has_mode: bool,
1883 is_ident_candidate: F,
1884 is_comment_candidate: C,
1885 ) -> bool
1886 where
1887 F: FnMut(&str) -> bool,
1888 C: FnMut(&str) -> bool,
1889 {
1890 if !self.buffered.is_empty() {
1891 return false;
1892 }
1893 if *square_depth == 0 {
1894 let scan_pos = self.lexer.scan_pos();
1895 let Some(next) = self.lexer.byte_at(scan_pos) else {
1896 return false;
1897 };
1898 if matches!(
1899 next,
1900 b'"' | b'\'' | b'(' | b')' | b'{' | b'}' | b',' | b';' | b'@'
1901 ) || (has_mode && matches!(next, b'.' | b'#' | b':'))
1902 {
1903 return false;
1904 }
1905
1906 let mut probe = scan_pos;
1911 while probe - scan_pos < 4 {
1912 let Some(byte) = self.lexer.byte_at(probe) else {
1913 return false;
1914 };
1915 if matches!(byte, b'/' | b'[') {
1916 break;
1917 }
1918 if probe != scan_pos
1919 && (matches!(
1920 byte,
1921 b'"' | b'\'' | b'(' | b')' | b'{' | b'}' | b',' | b';' | b'@'
1922 ) || (has_mode && matches!(byte, b'.' | b'#' | b':')))
1923 {
1924 return false;
1925 }
1926 probe += 1;
1927 }
1928 }
1929 let invalidates_composes = self.lexer.fast_forward_selector(
1930 square_depth,
1931 keep_comments,
1932 has_mode,
1933 is_ident_candidate,
1934 is_comment_candidate,
1935 );
1936 self.consumed = self.lexer.scan_pos();
1937 invalidates_composes
1938 }
1939
1940 pub(crate) fn fast_forward(&mut self, end: TokenKind) -> Option<Range> {
1947 debug_assert!(
1948 self.buffered.is_empty(),
1949 "fast_forward requires an empty lookahead buffer"
1950 );
1951 let old_scan_pos = self.lexer.scan_pos();
1952 let old_consumed = self.consumed;
1953 let range = self.lexer.fast_forward(end)?;
1954 self.consumed = range.end;
1955 debug_assert!(old_scan_pos <= self.lexer.scan_pos());
1956 debug_assert!(old_consumed <= self.consumed);
1957 debug_assert!(self.consumed <= self.lexer.scan_pos());
1958 Some(range)
1959 }
1960
1961 #[inline]
1964 pub(crate) fn consumed_pos(&self) -> Pos {
1965 self.consumed
1966 }
1967
1968 #[inline]
1969 pub(crate) fn lexer(&self) -> &Lexer<'s, V> {
1970 self.lexer
1971 }
1972
1973 #[inline]
1974 pub(crate) fn lexer_mut(&mut self) -> &mut Lexer<'s, V> {
1975 self.lexer
1976 }
1977
1978 #[inline]
1979 pub(crate) fn source_end(&self) -> Pos {
1980 self.lexer.source_end()
1981 }
1982
1983 #[inline(always)]
1984 pub(crate) fn byte_at(&self, position: Pos) -> Option<u8> {
1985 self.lexer.byte_at(position)
1986 }
1987
1988 #[inline]
1989 pub(crate) fn slice(&self, start: Pos, end: Pos) -> Option<&'s str> {
1990 self.lexer.slice(start, end)
1991 }
1992
1993 #[inline(always)]
1994 pub(crate) fn slice_trusted(&self, start: Pos, end: Pos) -> &'s str {
1995 self.lexer.slice_trusted(start, end)
1996 }
1997
1998 #[inline(always)]
1999 fn read_significant(&mut self, keep_comments: bool) -> TokenWithTrivia {
2000 let start = self.lexer.scan_pos();
2001 let mut end = start;
2002 let mut first_comment_start = None;
2003 let mut has_white_space = false;
2004 loop {
2005 let token = self.lexer.next_token();
2006 debug_assert!(token.range.start >= end);
2007 if token.kind == TokenKind::WhiteSpace {
2008 has_white_space = true;
2009 end = token.range.end;
2010 continue;
2011 }
2012 if !keep_comments && matches!(token.kind, TokenKind::Comment | TokenKind::BadComment) {
2013 first_comment_start.get_or_insert(token.range.start);
2014 end = token.range.end;
2015 continue;
2016 }
2017 let leading = Trivia {
2018 range: Range::new(start, end),
2019 end,
2020 first_comment_start,
2021 has_white_space,
2022 };
2023 return TokenWithTrivia { token, leading };
2024 }
2025 }
2026}
2027
2028pub fn is_new_line(c: u8) -> bool {
2029 c == C_LINE_FEED || c == C_CARRIAGE_RETURN || c == C_FORM_FEED
2030}
2031
2032#[inline]
2033fn kind_to_right(byte: u8) -> TokenKind {
2034 match byte {
2035 C_RIGHT_PARENTHESIS => TokenKind::RightParenthesis,
2036 C_RIGHT_SQUARE => TokenKind::RightSquareBracket,
2037 C_RIGHT_CURLY => TokenKind::RightCurlyBracket,
2038 _ => unreachable!("fast_forward only sees closing bracket bytes"),
2039 }
2040}
2041
2042pub fn is_space(c: u8) -> bool {
2043 c == C_TAB || c == C_SPACE
2044}
2045
2046pub fn is_white_space(c: u8) -> bool {
2047 is_new_line(c) || is_space(c)
2048}
2049
2050pub fn is_digit(c: u8) -> bool {
2051 c.is_ascii_digit()
2052}
2053
2054#[inline]
2055fn is_name_start_byte(c: u8) -> bool {
2056 c == 0 || c == C_LOW_LINE || c.is_ascii_alphabetic() || !c.is_ascii()
2057}
2058
2059#[inline]
2060fn is_non_printable(c: u8) -> bool {
2061 matches!(c, 0x00..=0x08 | 0x0B | 0x0E..=0x1F | 0x7F)
2062}
2063
2064#[cfg(test)]
2065#[path = "../tests/lexer_tests.rs"]
2066mod tests;