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