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