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<C>(
480 &mut self,
481 square_depth: &mut u32,
482 keep_comments: bool,
483 has_mode: bool,
484 mut is_comment_candidate: C,
485 ) -> bool
486 where
487 C: FnMut(&str) -> bool,
488 {
489 let mut position = self.scan_pos as usize;
490 let scan_start = position;
491 let mut invalidates_composes = false;
492 let mut trivia_start = None;
493
494 while position < self.value.len() {
495 let byte = self.value[position];
496
497 if is_white_space(byte) {
498 trivia_start.get_or_insert(position);
499 position += 1;
500 continue;
501 }
502
503 if byte == C_SOLIDUS && self.value.get(position + 1) == Some(&C_ASTERISK) {
504 let (kind, end, _, _, _) = self.scan_comment(position);
505 if keep_comments && kind == TokenKind::Comment {
506 let content = &self.value[position + 2..end.saturating_sub(2)];
507 let content = unsafe { str::from_utf8_unchecked(content) };
509 if is_comment_candidate(content) {
510 break;
511 }
512 }
513 trivia_start.get_or_insert(position);
514 position = end;
515 continue;
516 }
517
518 if *square_depth > 0 {
519 if byte == C_REVERSE_SOLIDUS && self.is_valid_escape_at(position) {
520 position = self.scan_escape(position);
521 trivia_start = None;
522 continue;
523 }
524 match byte {
525 C_QUOTATION_MARK | C_APOSTROPHE => {
526 position = self.scan_string(position, byte).1;
527 }
528 C_LEFT_SQUARE => {
529 *square_depth += 1;
530 position += 1;
531 }
532 C_RIGHT_SQUARE => {
533 *square_depth -= 1;
534 position += 1;
535 }
536 _ => position += 1,
537 }
538 trivia_start = None;
539 continue;
540 }
541
542 let starts_ident = match byte {
543 C_HYPHEN_MINUS | C_REVERSE_SOLIDUS => self.starts_ident_at(position),
544 _ => is_name_start_byte(byte),
545 };
546 if starts_ident {
547 let end = self.scan_name(position);
548 if end == position {
549 break;
550 }
551 if self.value.get(end) == Some(&C_LEFT_PARENTHESIS) {
557 break;
558 }
559 let name = &self.value[position..end];
560 let name = unsafe { str::from_utf8_unchecked(name) };
562 self
563 .visitor
564 .visit_ident(name, Range::new(position as Pos, end as Pos));
565 invalidates_composes = true;
566 trivia_start = None;
567 position = end;
568 continue;
569 }
570
571 let starts_number = match byte {
572 b'0'..=b'9' => true,
573 C_PLUS_SIGN | C_HYPHEN_MINUS | C_FULL_STOP => self.starts_number_at(position),
574 _ => false,
575 };
576 if starts_number {
577 invalidates_composes = true;
578 trivia_start = None;
579 position = self.scan_numeric(position).1;
580 continue;
581 }
582
583 match byte {
584 C_LEFT_SQUARE => {
585 invalidates_composes = true;
586 *square_depth = 1;
587 trivia_start = None;
588 position += 1;
589 }
590 C_QUOTATION_MARK | C_APOSTROPHE => break,
594 C_FULL_STOP | C_NUMBER_SIGN | C_COLON if has_mode => break,
595 C_NUMBER_SIGN => {
596 let name_start = position + 1;
597 position = if self.starts_ident_at(name_start)
598 || self
599 .value
600 .get(name_start)
601 .is_some_and(|byte| is_digit(*byte) || *byte == C_HYPHEN_MINUS)
602 {
603 self.scan_name(name_start)
604 } else {
605 name_start
606 };
607 invalidates_composes = true;
608 trivia_start = None;
609 }
610 C_RIGHT_PARENTHESIS => {
611 position = trivia_start.unwrap_or(position);
612 break;
613 }
614 C_LEFT_PARENTHESIS | C_LEFT_CURLY | C_RIGHT_CURLY | C_COMMA | C_SEMICOLON | C_AT_SIGN => {
615 break;
616 }
617 _ => {
618 invalidates_composes = true;
619 trivia_start = None;
620 position += 1;
621 }
622 }
623 }
624
625 self.scan_pos = position as Pos;
626 debug_assert!(
627 self.scan_pos >= scan_start as Pos,
628 "selector fast-forward moved scan_pos backward from {scan_start} to {position}"
629 );
630 invalidates_composes
631 }
632
633 pub(crate) fn fast_forward(&mut self, end: TokenKind) -> Option<Range> {
649 match end {
650 TokenKind::RightParenthesis
651 | TokenKind::RightSquareBracket
652 | TokenKind::RightCurlyBracket => {}
653 _ => unreachable!("fast_forward accepts only closing bracket kinds"),
654 }
655 let open_start = self.scan_pos;
656 let mut stack: SmallVec<[TokenKind; 8]> = SmallVec::new();
657 stack.push(end);
658 let mut position = open_start as usize;
659 let bytes = self.value;
660 while position < bytes.len() {
661 let byte = bytes[position];
662 match byte {
663 C_QUOTATION_MARK | C_APOSTROPHE => {
664 let (kind, string_end, _, _, _) = self.scan_string(position, byte);
665 if kind == TokenKind::BadString {
666 return None;
667 }
668 position = string_end;
669 }
670 C_SOLIDUS if bytes.get(position + 1) == Some(&C_ASTERISK) => {
671 let (kind, comment_end, _, _, _) = self.scan_comment(position);
672 if kind == TokenKind::BadComment {
673 return None;
674 }
675 position = comment_end;
676 }
677 C_REVERSE_SOLIDUS if self.is_valid_escape_at(position) => {
678 position = self.scan_escape(position);
679 }
680 C_LEFT_PARENTHESIS => {
681 stack.push(TokenKind::RightParenthesis);
682 position += 1;
683 }
684 C_LEFT_SQUARE => {
685 stack.push(TokenKind::RightSquareBracket);
686 position += 1;
687 }
688 C_LEFT_CURLY => {
689 stack.push(TokenKind::RightCurlyBracket);
690 position += 1;
691 }
692 C_RIGHT_PARENTHESIS | C_RIGHT_SQUARE | C_RIGHT_CURLY => {
693 if *stack.last().expect("delimiter stack never empties") != kind_to_right(byte) {
694 return None;
695 }
696 stack.pop();
697 position += 1;
698 if stack.is_empty() {
699 self.scan_pos = position as Pos;
700 debug_assert!(
701 self.scan_pos >= open_start,
702 "fast_forward moved scan_pos backward from {open_start} to {position}"
703 );
704 return Some(Range::new(open_start, position as Pos));
705 }
706 }
707 C_SEMICOLON => return None,
711 _ if byte >= 0x80 => {
712 position += self.utf8_width_at(position);
713 }
714 _ => position += 1,
715 }
716 }
717 None
718 }
719
720 pub(crate) fn slice(&self, start: Pos, end: Pos) -> Option<&'s str> {
721 let range = Range::new(start, end);
722 let start = start as usize;
723 let end = end as usize;
724 if start > end || end > self.value.len() {
725 return None;
726 }
727 Some(unsafe { slice_unchecked(self.value, &range) })
730 }
731
732 #[inline(always)]
733 pub(crate) fn slice_trusted(&self, start: Pos, end: Pos) -> &'s str {
734 debug_assert!(start <= end && end <= self.value.len() as Pos);
735 unsafe { slice_unchecked(self.value, &Range::new(start, end)) }
738 }
739}
740
741impl Lexer<'_, ()> {
742 pub fn slice_range<'a>(input: &'a str, range: &Range) -> Option<&'a str> {
743 let start = range.start as usize;
744 let end = range.end as usize;
745 if start > end
746 || end > input.len()
747 || !input.is_char_boundary(start)
748 || !input.is_char_boundary(end)
749 {
750 return None;
751 }
752 Some(unsafe { slice_unchecked(input.as_bytes(), range) })
754 }
755}
756
757unsafe fn slice_unchecked<'a>(input: &'a [u8], range: &Range) -> &'a str {
758 unsafe {
759 let value = input.get_unchecked(range.start as usize..range.end as usize);
760 str::from_utf8_unchecked(value)
761 }
762}
763
764impl<V: LexerVisitor> Lexer<'_, V> {
765 #[inline]
766 pub(crate) fn scan_pos(&self) -> Pos {
767 self.scan_pos
768 }
769}
770
771impl<'s, V: LexerVisitor> Lexer<'s, V> {
772 #[inline]
778 pub fn next_token(&mut self) -> Token {
779 let start = self.scan_pos as usize;
780 let len = self.value.len();
781 if start >= len {
782 self.scan_pos = len as Pos;
783 return Token::new(
784 TokenKind::Eof,
785 Range::new(len as Pos, len as Pos),
786 Range::new(len as Pos, len as Pos),
787 );
788 }
789
790 let byte = self.value[start];
791 let (kind, end, value_start, value_end, flags) = match byte {
792 C_SPACE | C_TAB | C_LINE_FEED | C_CARRIAGE_RETURN | C_FORM_FEED => {
793 self.scan_whitespace(start)
794 }
795 C_QUOTATION_MARK => self.scan_string(start, C_QUOTATION_MARK),
796 C_APOSTROPHE => self.scan_string(start, C_APOSTROPHE),
797 C_NUMBER_SIGN => {
798 let value_start = start + 1;
799 if self.starts_ident_at(value_start) {
800 let (end, flags) = self.scan_name_with_flags(value_start);
801 (TokenKind::IdHash, end, value_start, end, flags)
802 } else if self
803 .value
804 .get(value_start)
805 .is_some_and(|byte| is_digit(*byte) || *byte == C_HYPHEN_MINUS)
806 {
807 let (end, flags) = self.scan_name_with_flags(value_start);
808 (TokenKind::Hash, end, value_start, end, flags)
809 } else {
810 (
811 TokenKind::Delim,
812 start + 1,
813 start,
814 start + 1,
815 TokenFlags::ascii(),
816 )
817 }
818 }
819 C_LEFT_PARENTHESIS => (
820 TokenKind::LeftParenthesis,
821 start + 1,
822 start,
823 start + 1,
824 TokenFlags::ascii(),
825 ),
826 C_RIGHT_PARENTHESIS => (
827 TokenKind::RightParenthesis,
828 start + 1,
829 start,
830 start + 1,
831 TokenFlags::ascii(),
832 ),
833 C_LEFT_SQUARE => (
834 TokenKind::LeftSquareBracket,
835 start + 1,
836 start,
837 start + 1,
838 TokenFlags::ascii(),
839 ),
840 C_RIGHT_SQUARE => (
841 TokenKind::RightSquareBracket,
842 start + 1,
843 start,
844 start + 1,
845 TokenFlags::ascii(),
846 ),
847 C_LEFT_CURLY => (
848 TokenKind::LeftCurlyBracket,
849 start + 1,
850 start,
851 start + 1,
852 TokenFlags::ascii(),
853 ),
854 C_RIGHT_CURLY => (
855 TokenKind::RightCurlyBracket,
856 start + 1,
857 start,
858 start + 1,
859 TokenFlags::ascii(),
860 ),
861 C_COLON => (
862 TokenKind::Colon,
863 start + 1,
864 start,
865 start + 1,
866 TokenFlags::ascii(),
867 ),
868 C_SEMICOLON => (
869 TokenKind::Semicolon,
870 start + 1,
871 start,
872 start + 1,
873 TokenFlags::ascii(),
874 ),
875 C_COMMA => (
876 TokenKind::Comma,
877 start + 1,
878 start,
879 start + 1,
880 TokenFlags::ascii(),
881 ),
882 C_PLUS_SIGN => {
883 if self.starts_number_at(start) {
884 self.scan_numeric(start)
885 } else {
886 (
887 TokenKind::Delim,
888 start + 1,
889 start,
890 start + 1,
891 TokenFlags::ascii(),
892 )
893 }
894 }
895 C_HYPHEN_MINUS => {
896 if self.starts_number_at(start) {
897 self.scan_numeric(start)
898 } else if self.starts_ident_at(start) {
899 self.scan_ident_like(start)
900 } else {
901 (
902 TokenKind::Delim,
903 start + 1,
904 start,
905 start + 1,
906 TokenFlags::ascii(),
907 )
908 }
909 }
910 C_FULL_STOP => {
911 if self.starts_number_at(start) {
912 self.scan_numeric(start)
913 } else {
914 (
915 TokenKind::Delim,
916 start + 1,
917 start,
918 start + 1,
919 TokenFlags::ascii(),
920 )
921 }
922 }
923 C_SOLIDUS => {
924 if self.value.get(start + 1) == Some(&C_ASTERISK) {
925 self.scan_comment(start)
926 } else {
927 (
928 TokenKind::Delim,
929 start + 1,
930 start,
931 start + 1,
932 TokenFlags::ascii(),
933 )
934 }
935 }
936 C_AT_SIGN => {
937 let value_start = start + 1;
938 if self.starts_ident_at(value_start) {
939 let (end, flags) = self.scan_name_with_flags(value_start);
940 (TokenKind::AtKeyword, end, value_start, end, flags)
941 } else {
942 (
943 TokenKind::Delim,
944 start + 1,
945 start,
946 start + 1,
947 TokenFlags::ascii(),
948 )
949 }
950 }
951 C_REVERSE_SOLIDUS => {
952 if self.is_valid_escape_at(start) {
953 self.scan_ident_like(start)
954 } else {
955 (
956 TokenKind::Delim,
957 start + 1,
958 start,
959 start + 1,
960 TokenFlags::ascii(),
961 )
962 }
963 }
964 b'0'..=b'9' => self.scan_numeric(start),
965 b'$' if self.value[start..].starts_with(b"$=") => (
966 TokenKind::SuffixMatch,
967 start + 2,
968 start,
969 start + 2,
970 TokenFlags::ascii(),
971 ),
972 b'^' if self.value[start..].starts_with(b"^=") => (
973 TokenKind::PrefixMatch,
974 start + 2,
975 start,
976 start + 2,
977 TokenFlags::ascii(),
978 ),
979 b'|' if self.value[start..].starts_with(b"|=") => (
980 TokenKind::DashMatch,
981 start + 2,
982 start,
983 start + 2,
984 TokenFlags::ascii(),
985 ),
986 b'~' if self.value[start..].starts_with(b"~=") => (
987 TokenKind::IncludeMatch,
988 start + 2,
989 start,
990 start + 2,
991 TokenFlags::ascii(),
992 ),
993 b'*' if self.value[start..].starts_with(b"*=") => (
994 TokenKind::SubstringMatch,
995 start + 2,
996 start,
997 start + 2,
998 TokenFlags::ascii(),
999 ),
1000 _ if self.starts_ident_at(start) => self.scan_ident_like(start),
1001 _ => (
1002 TokenKind::Delim,
1003 start + 1,
1004 start,
1005 start + 1,
1006 TokenFlags::ascii(),
1007 ),
1008 };
1009
1010 self.scan_pos = end as Pos;
1011 debug_assert!(
1012 self.scan_pos >= start as Pos,
1013 "scan_pos moved backward from {start} to {}",
1014 self.scan_pos
1015 );
1016 let token = Token::with_flags(
1017 kind,
1018 Range::new(start as Pos, end as Pos),
1019 Range::new(value_start as Pos, value_end as Pos),
1020 flags,
1021 );
1022 self.visit_ident(token.kind, token.value_range);
1023 token
1024 }
1025
1026 #[inline]
1027 fn scan_whitespace(&self, start: usize) -> (TokenKind, usize, usize, usize, TokenFlags) {
1028 let bytes = self.value;
1029 let mut end = start;
1030 while end < bytes.len() && is_white_space(bytes[end]) {
1031 end += 1;
1032 }
1033 (TokenKind::WhiteSpace, end, start, end, TokenFlags::ascii())
1034 }
1035
1036 fn scan_comment(&self, start: usize) -> (TokenKind, usize, usize, usize, TokenFlags) {
1037 let bytes = self.value;
1038 let mut end = start + 2;
1039 let mut flags = TokenFlags::ascii();
1040 while end < bytes.len() {
1041 if bytes[end] == C_ASTERISK && bytes.get(end + 1) == Some(&C_SOLIDUS) {
1042 return (TokenKind::Comment, end + 2, start + 2, end, flags);
1043 }
1044 if bytes[end] == 0 {
1045 flags.mark_null();
1046 } else if !bytes[end].is_ascii() {
1047 flags.mark_non_ascii();
1048 }
1049 end += if bytes[end] < 0x80 {
1050 1
1051 } else {
1052 self.utf8_width_at(end)
1053 };
1054 }
1055 (
1056 TokenKind::BadComment,
1057 bytes.len(),
1058 start + 2,
1059 bytes.len(),
1060 flags,
1061 )
1062 }
1063
1064 fn scan_string(&self, start: usize, quote: u8) -> (TokenKind, usize, usize, usize, TokenFlags) {
1065 let mut end = start + 1;
1066 let mut flags = TokenFlags::ascii();
1067 while end < self.value.len() {
1068 let byte = self.value[end];
1069 if byte == quote {
1070 return (TokenKind::QuotedString, end + 1, start + 1, end, flags);
1071 }
1072 if is_new_line(byte) {
1073 return (TokenKind::BadString, end, start + 1, end, flags);
1074 }
1075 if byte == 0 {
1076 flags.mark_null();
1077 } else if !byte.is_ascii() {
1078 flags.mark_non_ascii();
1079 }
1080 if byte == C_REVERSE_SOLIDUS {
1081 flags.mark_escape();
1082 if self.value.get(end + 1).is_some_and(|next| !next.is_ascii()) {
1083 flags.mark_non_ascii();
1084 }
1085 if self
1086 .value
1087 .get(end + 1)
1088 .is_some_and(|next| is_new_line(*next))
1089 {
1090 end += 1;
1091 if self.value.get(end) == Some(&C_CARRIAGE_RETURN)
1092 && self.value.get(end + 1) == Some(&C_LINE_FEED)
1093 {
1094 end += 1;
1095 }
1096 end += 1;
1097 } else if self.is_valid_escape_at(end) {
1098 end = self.scan_escape(end);
1099 } else {
1100 end += 1;
1101 }
1102 } else {
1103 end += self.utf8_width_at(end);
1104 }
1105 }
1106 (TokenKind::BadString, end, start + 1, end, flags)
1107 }
1108
1109 fn scan_ident_like(&self, start: usize) -> (TokenKind, usize, usize, usize, TokenFlags) {
1110 let (name_end, name_flags) = self.scan_name_with_flags(start);
1111 if self.value.get(name_end) != Some(&C_LEFT_PARENTHESIS) {
1112 return (TokenKind::Ident, name_end, start, name_end, name_flags);
1113 }
1114
1115 let open_end = name_end + 1;
1116 let name = &self.value[start..name_end];
1117 let name = unsafe { str::from_utf8_unchecked(name) };
1119 let mut normalized = [0; MAX_CSS_KEYWORD_LEN];
1120 let normalized = if name_flags.has_escape() {
1121 decode_css_keyword(name, &mut normalized)
1122 } else {
1123 lowercase_ascii_keyword(name, &mut normalized)
1124 };
1125 let is_url = normalized == Some("url");
1126 if is_url {
1127 let content_start = self.skip_white_space(open_end);
1128 if !matches!(
1129 self.value.get(content_start),
1130 Some(&C_QUOTATION_MARK | &C_APOSTROPHE)
1131 ) {
1132 let (kind, end, value_start, value_end, url_flags) = self.scan_url(start, content_start);
1133 let mut flags = name_flags;
1134 flags.merge(url_flags);
1135 return (kind, end, value_start, value_end, flags);
1136 }
1137 }
1138 (TokenKind::Function, open_end, start, name_end, name_flags)
1139 }
1140
1141 fn scan_url(
1142 &self,
1143 start: usize,
1144 content_start: usize,
1145 ) -> (TokenKind, usize, usize, usize, TokenFlags) {
1146 let mut end = content_start;
1147 let mut flags = TokenFlags::ascii();
1148 while end < self.value.len() {
1149 let byte = self.value[end];
1150 if byte == C_RIGHT_PARENTHESIS {
1151 return (TokenKind::Url, end + 1, content_start, end, flags);
1152 }
1153 if is_white_space(byte) {
1154 let content_end = end;
1155 let close = self.skip_white_space(end);
1156 if self.value.get(close) == Some(&C_RIGHT_PARENTHESIS) {
1157 return (TokenKind::Url, close + 1, content_start, content_end, flags);
1158 }
1159 return self.scan_bad_url(start, close, content_start, flags);
1160 }
1161 if byte == 0 {
1162 flags.mark_null();
1163 } else if !byte.is_ascii() {
1164 flags.mark_non_ascii();
1165 }
1166 if byte == C_QUOTATION_MARK
1167 || byte == C_APOSTROPHE
1168 || byte == C_LEFT_PARENTHESIS
1169 || is_non_printable(byte)
1170 {
1171 return self.scan_bad_url(start, end, content_start, flags);
1172 }
1173 if byte == C_REVERSE_SOLIDUS {
1174 flags.mark_escape();
1175 if self.value.get(end + 1).is_some_and(|next| !next.is_ascii()) {
1176 flags.mark_non_ascii();
1177 }
1178 if self.is_valid_escape_at(end) {
1179 end = self.scan_escape(end);
1180 } else if self
1181 .value
1182 .get(end + 1)
1183 .is_some_and(|next| is_new_line(*next))
1184 {
1185 end += 2;
1186 if self.value.get(end - 1) == Some(&C_CARRIAGE_RETURN)
1187 && self.value.get(end) == Some(&C_LINE_FEED)
1188 {
1189 end += 1;
1190 }
1191 } else {
1192 return self.scan_bad_url(start, end, content_start, flags);
1193 }
1194 } else {
1195 end += self.utf8_width_at(end);
1196 }
1197 }
1198 (
1199 TokenKind::BadUrl,
1200 self.value.len(),
1201 content_start,
1202 self.value.len(),
1203 flags,
1204 )
1205 }
1206
1207 fn scan_bad_url(
1208 &self,
1209 _start: usize,
1210 mut end: usize,
1211 content_start: usize,
1212 mut flags: TokenFlags,
1213 ) -> (TokenKind, usize, usize, usize, TokenFlags) {
1214 while end < self.value.len() {
1215 let byte = self.value[end];
1216 if byte == 0 {
1217 flags.mark_null();
1218 } else if !byte.is_ascii() {
1219 flags.mark_non_ascii();
1220 }
1221 if byte == C_RIGHT_PARENTHESIS {
1222 end += 1;
1223 break;
1224 }
1225 if byte == C_REVERSE_SOLIDUS {
1226 flags.mark_escape();
1227 if self.value.get(end + 1).is_some_and(|next| !next.is_ascii()) {
1228 flags.mark_non_ascii();
1229 }
1230 if self.is_valid_escape_at(end) {
1231 end = self.scan_escape(end);
1232 } else if self
1233 .value
1234 .get(end + 1)
1235 .is_some_and(|next| is_new_line(*next))
1236 {
1237 end += 2;
1238 if self.value.get(end - 1) == Some(&C_CARRIAGE_RETURN)
1239 && self.value.get(end) == Some(&C_LINE_FEED)
1240 {
1241 end += 1;
1242 }
1243 } else {
1244 end += 1;
1245 }
1246 } else {
1247 end += self.utf8_width_at(end);
1248 }
1249 }
1250 (TokenKind::BadUrl, end, content_start, end, flags)
1251 }
1252
1253 fn scan_numeric(&self, start: usize) -> (TokenKind, usize, usize, usize, TokenFlags) {
1254 let end = self.scan_number_end(start);
1255 if self.value.get(end) == Some(&C_PERCENTAGE) {
1256 return (
1257 TokenKind::Percentage,
1258 end + 1,
1259 start,
1260 end + 1,
1261 TokenFlags::ascii(),
1262 );
1263 }
1264 if self.starts_ident_at(end) {
1265 let (end, flags) = self.scan_name_with_flags(end);
1266 return (TokenKind::Dimension, end, start, end, flags);
1267 }
1268 (TokenKind::Number, end, start, end, TokenFlags::ascii())
1269 }
1270
1271 #[inline]
1272 fn scan_number_end(&self, start: usize) -> usize {
1273 let mut end = start;
1274 if matches!(self.value.get(end), Some(&C_PLUS_SIGN | &C_HYPHEN_MINUS)) {
1275 end += 1;
1276 }
1277 while self.value.get(end).is_some_and(|byte| is_digit(*byte)) {
1278 end += 1;
1279 }
1280 if self.value.get(end) == Some(&C_FULL_STOP)
1281 && self.value.get(end + 1).is_some_and(|byte| is_digit(*byte))
1282 {
1283 end += 1;
1284 while self.value.get(end).is_some_and(|byte| is_digit(*byte)) {
1285 end += 1;
1286 }
1287 }
1288 if matches!(self.value.get(end), Some(&C_LOWER_E | &C_UPPER_E)) {
1289 let exponent_start = end;
1290 let mut exponent_end = end + 1;
1291 if matches!(
1292 self.value.get(exponent_end),
1293 Some(&C_PLUS_SIGN | &C_HYPHEN_MINUS)
1294 ) {
1295 exponent_end += 1;
1296 }
1297 if self
1298 .value
1299 .get(exponent_end)
1300 .is_some_and(|byte| is_digit(*byte))
1301 {
1302 end = exponent_end + 1;
1303 while self.value.get(end).is_some_and(|byte| is_digit(*byte)) {
1304 end += 1;
1305 }
1306 } else {
1307 end = exponent_start;
1308 }
1309 }
1310 end
1311 }
1312
1313 #[inline]
1314 fn skip_white_space(&self, mut position: usize) -> usize {
1315 while self
1316 .value
1317 .get(position)
1318 .is_some_and(|byte| is_white_space(*byte))
1319 {
1320 position += 1;
1321 }
1322 position
1323 }
1324
1325 #[inline]
1326 fn scan_plain_ascii_name(&self, mut position: usize) -> usize {
1327 while self
1328 .value
1329 .get(position)
1330 .is_some_and(|byte| PLAIN_ASCII_NAME_BYTE[*byte as usize])
1331 {
1332 position += 1;
1333 }
1334 position
1335 }
1336
1337 #[inline]
1338 fn raw_name_needs_tokenizer(&self, position: usize) -> bool {
1339 self
1340 .value
1341 .get(position)
1342 .is_some_and(|byte| *byte == 0 || *byte == C_REVERSE_SOLIDUS || !byte.is_ascii())
1343 }
1344
1345 #[inline]
1346 fn scan_raw_numeric_end(&self, start: usize) -> Option<usize> {
1347 let end = self.scan_number_end(start);
1348 if self.value.get(end) == Some(&C_PERCENTAGE) {
1349 return Some(end + 1);
1350 }
1351 if self.starts_ident_at(end) {
1352 let name_end = self.scan_plain_ascii_name(end);
1353 if name_end == end || self.raw_name_needs_tokenizer(name_end) {
1354 return None;
1355 }
1356 return Some(name_end);
1357 }
1358 if self.raw_name_needs_tokenizer(end) {
1359 return None;
1360 }
1361 Some(end)
1362 }
1363
1364 #[inline]
1365 fn scan_name(&self, position: usize) -> usize {
1366 self.scan_name_impl(position, false).0
1367 }
1368
1369 #[inline]
1370 fn scan_name_with_flags(&self, position: usize) -> (usize, TokenFlags) {
1371 self.scan_name_impl(position, true)
1372 }
1373
1374 #[inline]
1375 fn scan_name_impl(&self, mut position: usize, track_flags: bool) -> (usize, TokenFlags) {
1376 let bytes = self.value;
1377 let mut flags = TokenFlags::ascii();
1378 while position < bytes.len() {
1379 while position < bytes.len() {
1380 let byte = bytes[position];
1381 if PLAIN_ASCII_NAME_BYTE[byte as usize] {
1382 position += 1;
1383 } else if byte == 0 {
1384 if track_flags {
1385 flags.mark_null();
1386 }
1387 position += 1;
1388 } else {
1389 break;
1390 }
1391 }
1392 if position == bytes.len() {
1393 break;
1394 }
1395
1396 let byte = bytes[position];
1397 if byte == C_REVERSE_SOLIDUS {
1398 if self.is_valid_escape_at(position) {
1399 if track_flags {
1400 flags.mark_escape();
1401 if bytes.get(position + 1).is_some_and(|next| !next.is_ascii()) {
1402 flags.mark_non_ascii();
1403 }
1404 }
1405 position = self.scan_escape(position);
1406 } else {
1407 break;
1408 }
1409 } else if byte.is_ascii() {
1410 break;
1411 } else {
1412 if track_flags {
1413 flags.mark_non_ascii();
1414 }
1415 position += self.utf8_width_at(position);
1416 }
1417 }
1418 (position, flags)
1419 }
1420
1421 #[inline]
1422 fn starts_ident_at(&self, position: usize) -> bool {
1423 let Some(byte) = self.value.get(position).copied() else {
1424 return false;
1425 };
1426 match byte {
1427 C_HYPHEN_MINUS => match self.value.get(position + 1).copied() {
1428 Some(next) => {
1429 is_name_start_byte(next)
1430 || next == C_HYPHEN_MINUS
1431 || self.is_valid_escape_at(position + 1)
1432 }
1433 None => false,
1434 },
1435 C_REVERSE_SOLIDUS => self.is_valid_escape_at(position),
1436 _ => is_name_start_byte(byte),
1437 }
1438 }
1439
1440 #[inline]
1441 fn starts_number_at(&self, position: usize) -> bool {
1442 let Some(first) = self.value.get(position).copied() else {
1443 return false;
1444 };
1445 let second = self.value.get(position + 1).copied();
1446 let third = self.value.get(position + 2).copied();
1447 match first {
1448 C_PLUS_SIGN | C_HYPHEN_MINUS => {
1449 second.is_some_and(is_digit) || (second == Some(C_FULL_STOP) && third.is_some_and(is_digit))
1450 }
1451 C_FULL_STOP => second.is_some_and(is_digit),
1452 _ => is_digit(first),
1453 }
1454 }
1455
1456 #[inline]
1457 fn is_valid_escape_at(&self, position: usize) -> bool {
1458 self.value.get(position) == Some(&C_REVERSE_SOLIDUS)
1459 && self
1460 .value
1461 .get(position + 1)
1462 .is_some_and(|byte| !is_new_line(*byte))
1463 }
1464
1465 #[inline]
1466 fn scan_escape(&self, position: usize) -> usize {
1467 debug_assert!(self.is_valid_escape_at(position));
1468 let mut end = position + 1;
1469 if self.value[end].is_ascii_hexdigit() {
1470 let mut digits = 0;
1471 while end < self.value.len() && digits < 6 && self.value[end].is_ascii_hexdigit() {
1472 end += 1;
1473 digits += 1;
1474 }
1475 if self
1476 .value
1477 .get(end)
1478 .is_some_and(|byte| is_white_space(*byte))
1479 {
1480 end += 1;
1481 if self.value.get(end - 1) == Some(&C_CARRIAGE_RETURN)
1482 && self.value.get(end) == Some(&C_LINE_FEED)
1483 {
1484 end += 1;
1485 }
1486 }
1487 return end;
1488 }
1489 end + self.utf8_width_at(end)
1490 }
1491
1492 #[inline]
1493 fn utf8_width_at(&self, position: usize) -> usize {
1494 let byte = self.value[position];
1495 if byte < 0x80 {
1496 1
1497 } else if byte < 0xE0 {
1498 2
1499 } else if byte < 0xF0 {
1500 3
1501 } else {
1502 4
1503 }
1504 }
1505}
1506
1507pub(crate) struct TokenStream<'a, 's, V: LexerVisitor = ()> {
1527 lexer: &'a mut Lexer<'s, V>,
1528 consumed: Pos,
1529 buffered: VecDeque<TokenWithTrivia>,
1530 generic_value_state: GenericValueScanState,
1531 at_rule_state: GenericValueScanState,
1532 special_value_state: GenericValueScanState,
1533}
1534
1535#[derive(Debug, Default)]
1536pub(crate) struct GenericValueScanState {
1537 parentheses: u32,
1538 squares: u32,
1539 curlies: u32,
1540}
1541
1542#[derive(Debug, Clone, Copy)]
1543struct GenericValueScanOptions {
1544 keep_comments: bool,
1545 preserve_strings: bool,
1546 preserve_delimiters: bool,
1547 property: Option<PropertyKind>,
1548}
1549
1550#[derive(Debug, Clone, Copy)]
1551struct PrescannedIdent {
1552 start: Pos,
1553 end: Pos,
1554 flags: TokenFlags,
1555}
1556
1557impl GenericValueScanState {
1558 #[inline]
1559 fn is_nested(&self) -> bool {
1560 self.parentheses != 0 || self.squares != 0 || self.curlies != 0
1561 }
1562}
1563
1564#[inline]
1565fn is_dependency_value_function(name: &str) -> bool {
1566 let mut lowercase = [0; MAX_CSS_KEYWORD_LEN];
1567 let Some(name) = lowercase_ascii_keyword(name, &mut lowercase) else {
1568 return name.starts_with("--");
1569 };
1570 matches!(name, "url" | "var" | "image-set")
1571 || strip_vendor_prefix(name) == Some("image-set")
1572 || name.starts_with("--")
1573}
1574
1575#[inline(always)]
1576fn never_fast_forward_candidate(_: &str) -> bool {
1577 false
1578}
1579
1580impl<'a, 's, V: LexerVisitor> TokenStream<'a, 's, V> {
1581 pub(crate) fn from_lexer(lexer: &'a mut Lexer<'s, V>) -> Self {
1582 Self {
1583 lexer,
1584 consumed: 0,
1585 buffered: VecDeque::new(),
1586 generic_value_state: GenericValueScanState::default(),
1587 at_rule_state: GenericValueScanState::default(),
1588 special_value_state: GenericValueScanState::default(),
1589 }
1590 }
1591
1592 #[inline(always)]
1593 pub(crate) fn next(&mut self, keep_comments: bool) -> TokenWithTrivia {
1594 let token = if let Some(token) = self.buffered.pop_front() {
1595 token
1596 } else {
1597 self.read_significant(keep_comments)
1598 };
1599 debug_assert!(
1600 token.token.range.start >= self.consumed,
1601 "token range starts before consumed_pos: {:?} < {}",
1602 token.token.range,
1603 self.consumed
1604 );
1605 self.consumed = token.token.range.end;
1606 if matches!(
1607 token.token.kind,
1608 TokenKind::Semicolon | TokenKind::RightCurlyBracket
1609 ) {
1610 self.generic_value_state = GenericValueScanState::default();
1611 }
1612 debug_assert!(
1613 self.consumed <= self.lexer.scan_pos(),
1614 "consumed_pos ({}) exceeded scan_pos ({})",
1615 self.consumed,
1616 self.lexer.scan_pos()
1617 );
1618 token
1619 }
1620
1621 #[inline]
1626 pub(crate) fn next_parser_token(&mut self) -> TokenWithTrivia {
1627 let mut item = self.next(true);
1628 if item.token.kind != TokenKind::Comment {
1629 return item;
1630 }
1631
1632 let start = item.leading.range.start;
1633 let first_comment_start = item
1634 .leading
1635 .first_comment_start
1636 .or(Some(item.token.range.start));
1637 let mut has_white_space = item.leading.has_white_space;
1638 loop {
1639 item = self.next(true);
1640 has_white_space |= item.leading.has_white_space;
1641 if item.token.kind == TokenKind::Comment {
1642 continue;
1643 }
1644 let end = item.leading.end;
1645 item.leading = Trivia {
1646 range: Range::new(start, end),
1647 end,
1648 first_comment_start,
1649 has_white_space,
1650 };
1651 return item;
1652 }
1653 }
1654
1655 #[inline]
1656 pub(crate) fn peek(&mut self, keep_comments: bool) -> TokenWithTrivia {
1657 if self.buffered.is_empty() {
1658 let item = self.read_significant(keep_comments);
1659 self.buffered.push_back(item);
1660 }
1661 *self
1662 .buffered
1663 .front()
1664 .expect("peek must buffer an item before reading the front")
1665 }
1666
1667 #[inline]
1671 pub(crate) fn peek_parser_token(&mut self) -> TokenWithTrivia {
1672 self.peek(true);
1673 let mut leading_start = None;
1674 let mut first_comment_start = None;
1675 let mut has_white_space = false;
1676 let mut index = 0usize;
1677 loop {
1678 if index == self.buffered.len() {
1679 let item = self.read_significant(true);
1680 self.buffered.push_back(item);
1681 }
1682 let item = self.buffered[index];
1683 leading_start.get_or_insert(item.leading.range.start);
1684 has_white_space |= item.leading.has_whitespace();
1685 if matches!(item.token.kind, TokenKind::Comment | TokenKind::BadComment) {
1686 first_comment_start.get_or_insert(item.token.range.start);
1687 index += 1;
1688 continue;
1689 }
1690
1691 let end = item.leading.end;
1692 let mut result = item;
1693 result.leading = Trivia {
1694 range: Range::new(leading_start.unwrap_or(end), end),
1695 end,
1696 first_comment_start: first_comment_start.or(item.leading.first_comment_start),
1697 has_white_space,
1698 };
1699 return result;
1700 }
1701 }
1702
1703 #[inline]
1706 pub(crate) fn peek_significant_skipping_comments(
1707 &mut self,
1708 keep_comments: bool,
1709 ) -> TokenWithTrivia {
1710 self.peek(keep_comments);
1711 for item in &self.buffered {
1712 if !matches!(item.token.kind, TokenKind::Comment | TokenKind::BadComment) {
1713 return *item;
1714 }
1715 }
1716 loop {
1717 let item = self.read_significant(keep_comments);
1718 self.buffered.push_back(item);
1719 if !matches!(item.token.kind, TokenKind::Comment | TokenKind::BadComment) {
1720 return item;
1721 }
1722 }
1723 }
1724
1725 #[inline]
1728 pub(crate) fn fast_forward_generic_value_if_buffer_empty<F, C>(
1729 &mut self,
1730 keep_comments: bool,
1731 preserve_strings: bool,
1732 preserve_delimiters: bool,
1733 mut is_candidate: F,
1734 mut is_comment_candidate: C,
1735 ) where
1736 F: FnMut(&str) -> bool,
1737 C: FnMut(&str) -> bool,
1738 {
1739 if !self.buffered.is_empty() {
1740 return;
1741 }
1742 let candidate = self.lexer.fast_forward_generic_value(
1743 &mut self.generic_value_state,
1744 GenericValueScanOptions {
1745 keep_comments,
1746 preserve_strings,
1747 preserve_delimiters,
1748 property: None,
1749 },
1750 false,
1751 &mut is_candidate,
1752 &mut is_comment_candidate,
1753 None,
1754 );
1755 self.finish_value_fast_forward(candidate);
1756 }
1757
1758 #[inline]
1759 pub(crate) fn fast_forward_generic_value_without_candidates_if_buffer_empty(
1760 &mut self,
1761 preserve_strings: bool,
1762 preserve_delimiters: bool,
1763 ) {
1764 if !self.buffered.is_empty() {
1765 return;
1766 }
1767 let candidate = self.lexer.fast_forward_generic_value(
1768 &mut self.generic_value_state,
1769 GenericValueScanOptions {
1770 keep_comments: false,
1771 preserve_strings,
1772 preserve_delimiters,
1773 property: None,
1774 },
1775 false,
1776 never_fast_forward_candidate,
1777 never_fast_forward_candidate,
1778 None,
1779 );
1780 self.finish_value_fast_forward(candidate);
1781 }
1782
1783 #[inline]
1784 pub(crate) fn fast_forward_at_rule_if_buffer_empty<F, C>(
1785 &mut self,
1786 keep_comments: bool,
1787 preserve_strings: bool,
1788 preserve_delimiters: bool,
1789 mut is_candidate: F,
1790 mut is_comment_candidate: C,
1791 ) where
1792 F: FnMut(&str) -> bool,
1793 C: FnMut(&str) -> bool,
1794 {
1795 if !self.buffered.is_empty() {
1796 return;
1797 }
1798 let candidate = self.lexer.fast_forward_generic_value(
1799 &mut self.at_rule_state,
1800 GenericValueScanOptions {
1801 keep_comments,
1802 preserve_strings,
1803 preserve_delimiters,
1804 property: None,
1805 },
1806 true,
1807 &mut is_candidate,
1808 &mut is_comment_candidate,
1809 None,
1810 );
1811 self.finish_value_fast_forward(candidate);
1812 }
1813
1814 #[inline]
1815 pub(crate) fn reset_at_rule_scan_state(&mut self) {
1816 self.at_rule_state = GenericValueScanState::default();
1817 }
1818
1819 #[inline]
1820 pub(crate) fn fast_forward_special_value_if_buffer_empty(
1821 &mut self,
1822 keep_comments: bool,
1823 preserve_strings: bool,
1824 preserve_delimiters: bool,
1825 property: PropertyKind,
1826 icss_symbols: Option<&FxHashSet<&str>>,
1827 ) {
1828 if !self.buffered.is_empty() {
1829 return;
1830 }
1831 let candidate = self.lexer.fast_forward_generic_value(
1832 &mut self.special_value_state,
1833 GenericValueScanOptions {
1834 keep_comments,
1835 preserve_strings,
1836 preserve_delimiters,
1837 property: Some(property),
1838 },
1839 false,
1840 never_fast_forward_candidate,
1841 is_css_modules_pure_magic_comment,
1842 icss_symbols,
1843 );
1844 self.finish_value_fast_forward(candidate);
1845 }
1846
1847 #[inline]
1848 fn finish_value_fast_forward(&mut self, candidate: Option<PrescannedIdent>) {
1849 let Some(candidate) = candidate else {
1850 self.consumed = self.lexer.scan_pos();
1851 return;
1852 };
1853 debug_assert_eq!(candidate.end, self.lexer.scan_pos());
1854 self.consumed = candidate.start;
1855 let range = Range::new(candidate.start, candidate.end);
1856 self.buffered.push_back(TokenWithTrivia {
1857 token: Token::with_flags(TokenKind::Ident, range, range, candidate.flags),
1858 leading: Trivia {
1859 range: Range::new(candidate.start, candidate.start),
1860 end: candidate.start,
1861 first_comment_start: None,
1862 has_white_space: false,
1863 },
1864 });
1865 }
1866
1867 #[inline]
1868 pub(crate) fn reset_special_value_scan_state(&mut self) {
1869 self.special_value_state = GenericValueScanState::default();
1870 }
1871
1872 #[inline]
1873 pub(crate) fn fast_forward_selector_if_buffer_empty<C>(
1874 &mut self,
1875 square_depth: &mut u32,
1876 keep_comments: bool,
1877 has_mode: bool,
1878 is_comment_candidate: C,
1879 ) -> bool
1880 where
1881 C: FnMut(&str) -> bool,
1882 {
1883 if !self.buffered.is_empty() {
1884 return false;
1885 }
1886 if *square_depth == 0 {
1887 let scan_pos = self.lexer.scan_pos();
1888 let Some(next) = self.lexer.byte_at(scan_pos) else {
1889 return false;
1890 };
1891 if matches!(
1892 next,
1893 b'"' | b'\'' | b'(' | b')' | b'{' | b'}' | b',' | b';' | b'@'
1894 ) || (has_mode && matches!(next, b'.' | b'#' | b':'))
1895 {
1896 return false;
1897 }
1898
1899 let mut probe = scan_pos;
1904 while probe - scan_pos < 4 {
1905 let Some(byte) = self.lexer.byte_at(probe) else {
1906 return false;
1907 };
1908 if matches!(byte, b'/' | b'[') {
1909 break;
1910 }
1911 if probe != scan_pos
1912 && (matches!(
1913 byte,
1914 b'"' | b'\'' | b'(' | b')' | b'{' | b'}' | b',' | b';' | b'@'
1915 ) || (has_mode && matches!(byte, b'.' | b'#' | b':')))
1916 {
1917 return false;
1918 }
1919 probe += 1;
1920 }
1921 }
1922 let invalidates_composes =
1923 self
1924 .lexer
1925 .fast_forward_selector(square_depth, keep_comments, has_mode, is_comment_candidate);
1926 self.consumed = self.lexer.scan_pos();
1927 invalidates_composes
1928 }
1929
1930 pub(crate) fn fast_forward(&mut self, end: TokenKind) -> Option<Range> {
1937 debug_assert!(
1938 self.buffered.is_empty(),
1939 "fast_forward requires an empty lookahead buffer"
1940 );
1941 let old_scan_pos = self.lexer.scan_pos();
1942 let old_consumed = self.consumed;
1943 let range = self.lexer.fast_forward(end)?;
1944 self.consumed = range.end;
1945 debug_assert!(old_scan_pos <= self.lexer.scan_pos());
1946 debug_assert!(old_consumed <= self.consumed);
1947 debug_assert!(self.consumed <= self.lexer.scan_pos());
1948 Some(range)
1949 }
1950
1951 #[inline]
1954 pub(crate) fn consumed_pos(&self) -> Pos {
1955 self.consumed
1956 }
1957
1958 #[inline]
1959 pub(crate) fn lexer(&self) -> &Lexer<'s, V> {
1960 self.lexer
1961 }
1962
1963 #[inline]
1964 pub(crate) fn lexer_mut(&mut self) -> &mut Lexer<'s, V> {
1965 self.lexer
1966 }
1967
1968 #[inline]
1969 pub(crate) fn source_end(&self) -> Pos {
1970 self.lexer.source_end()
1971 }
1972
1973 #[inline(always)]
1974 pub(crate) fn byte_at(&self, position: Pos) -> Option<u8> {
1975 self.lexer.byte_at(position)
1976 }
1977
1978 #[inline]
1979 pub(crate) fn slice(&self, start: Pos, end: Pos) -> Option<&'s str> {
1980 self.lexer.slice(start, end)
1981 }
1982
1983 #[inline(always)]
1984 pub(crate) fn slice_trusted(&self, start: Pos, end: Pos) -> &'s str {
1985 self.lexer.slice_trusted(start, end)
1986 }
1987
1988 #[inline(always)]
1989 fn read_significant(&mut self, keep_comments: bool) -> TokenWithTrivia {
1990 let start = self.lexer.scan_pos();
1991 let mut end = start;
1992 let mut first_comment_start = None;
1993 let mut has_white_space = false;
1994 loop {
1995 let token = self.lexer.next_token();
1996 debug_assert!(token.range.start >= end);
1997 if token.kind == TokenKind::WhiteSpace {
1998 has_white_space = true;
1999 end = token.range.end;
2000 continue;
2001 }
2002 if !keep_comments && matches!(token.kind, TokenKind::Comment | TokenKind::BadComment) {
2003 first_comment_start.get_or_insert(token.range.start);
2004 end = token.range.end;
2005 continue;
2006 }
2007 let leading = Trivia {
2008 range: Range::new(start, end),
2009 end,
2010 first_comment_start,
2011 has_white_space,
2012 };
2013 return TokenWithTrivia { token, leading };
2014 }
2015 }
2016}
2017
2018pub fn is_new_line(c: u8) -> bool {
2019 c == C_LINE_FEED || c == C_CARRIAGE_RETURN || c == C_FORM_FEED
2020}
2021
2022#[inline]
2023fn kind_to_right(byte: u8) -> TokenKind {
2024 match byte {
2025 C_RIGHT_PARENTHESIS => TokenKind::RightParenthesis,
2026 C_RIGHT_SQUARE => TokenKind::RightSquareBracket,
2027 C_RIGHT_CURLY => TokenKind::RightCurlyBracket,
2028 _ => unreachable!("fast_forward only sees closing bracket bytes"),
2029 }
2030}
2031
2032pub fn is_space(c: u8) -> bool {
2033 c == C_TAB || c == C_SPACE
2034}
2035
2036pub fn is_white_space(c: u8) -> bool {
2037 is_new_line(c) || is_space(c)
2038}
2039
2040pub fn is_digit(c: u8) -> bool {
2041 c.is_ascii_digit()
2042}
2043
2044#[inline]
2045fn is_name_start_byte(c: u8) -> bool {
2046 c == 0 || c == C_LOW_LINE || c.is_ascii_alphabetic() || !c.is_ascii()
2047}
2048
2049#[inline]
2050fn is_non_printable(c: u8) -> bool {
2051 matches!(c, 0x00..=0x08 | 0x0B | 0x0E..=0x1F | 0x7F)
2052}
2053
2054#[cfg(test)]
2055#[path = "../tests/lexer_tests.rs"]
2056mod tests;