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 return self.scan_bad_url(start, close, content_start, flags);
1201 }
1202 if byte == 0 {
1203 flags.mark_null();
1204 } else if !byte.is_ascii() {
1205 flags.mark_non_ascii();
1206 }
1207 if byte == C_QUOTATION_MARK
1208 || byte == C_APOSTROPHE
1209 || byte == C_LEFT_PARENTHESIS
1210 || is_non_printable(byte)
1211 {
1212 return self.scan_bad_url(start, end, content_start, flags);
1213 }
1214 if byte == C_REVERSE_SOLIDUS {
1215 flags.mark_escape();
1216 if self.value.get(end + 1).is_some_and(|next| !next.is_ascii()) {
1217 flags.mark_non_ascii();
1218 }
1219 if self.is_valid_escape_at(end) {
1220 end = self.scan_escape(end);
1221 } else if self
1222 .value
1223 .get(end + 1)
1224 .is_some_and(|next| is_new_line(*next))
1225 {
1226 end += 2;
1227 if self.value.get(end - 1) == Some(&C_CARRIAGE_RETURN)
1228 && self.value.get(end) == Some(&C_LINE_FEED)
1229 {
1230 end += 1;
1231 }
1232 } else {
1233 return self.scan_bad_url(start, end, content_start, flags);
1234 }
1235 } else {
1236 end += self.utf8_width_at(end);
1237 }
1238 }
1239 if end == self.value.len() {
1240 return (
1241 TokenKind::BadUrl,
1242 self.value.len(),
1243 content_start,
1244 self.value.len(),
1245 flags,
1246 );
1247 }
1248 let rest = &self.value[end..];
1254 let close_rel = memchr::memchr(C_RIGHT_PARENTHESIS, rest).unwrap_or(rest.len());
1255 let segment = &rest[..close_rel];
1256 if !segment.is_ascii() {
1257 flags.mark_non_ascii();
1258 }
1259 let bad_rel = segment.iter().position(|byte| {
1260 *byte <= C_SPACE
1261 || *byte == C_QUOTATION_MARK
1262 || *byte == C_APOSTROPHE
1263 || *byte == C_LEFT_PARENTHESIS
1264 || *byte == C_REVERSE_SOLIDUS
1265 || *byte == 0x7F
1266 });
1267 if let Some(offset) = bad_rel {
1268 end += offset;
1269 continue 'scan;
1270 }
1271 end += close_rel;
1272 if close_rel == rest.len() {
1273 return (
1274 TokenKind::BadUrl,
1275 self.value.len(),
1276 content_start,
1277 self.value.len(),
1278 flags,
1279 );
1280 }
1281 return (TokenKind::Url, end + 1, content_start, end, flags);
1282 }
1283 }
1284
1285 fn scan_bad_url(
1286 &self,
1287 _start: usize,
1288 mut end: usize,
1289 content_start: usize,
1290 mut flags: TokenFlags,
1291 ) -> (TokenKind, usize, usize, usize, TokenFlags) {
1292 loop {
1293 let short_end = end.saturating_add(32).min(self.value.len());
1294 while end < short_end {
1295 let byte = self.value[end];
1296 if byte == 0 {
1297 flags.mark_null();
1298 } else if !byte.is_ascii() {
1299 flags.mark_non_ascii();
1300 }
1301 if byte == C_RIGHT_PARENTHESIS {
1302 end += 1;
1303 return (TokenKind::BadUrl, end, content_start, end, flags);
1304 }
1305 if byte == C_REVERSE_SOLIDUS {
1306 flags.mark_escape();
1307 if self.value.get(end + 1).is_some_and(|next| !next.is_ascii()) {
1308 flags.mark_non_ascii();
1309 }
1310 if self.is_valid_escape_at(end) {
1311 end = self.scan_escape(end);
1312 } else if self
1313 .value
1314 .get(end + 1)
1315 .is_some_and(|next| is_new_line(*next))
1316 {
1317 end += 2;
1318 if self.value.get(end - 1) == Some(&C_CARRIAGE_RETURN)
1319 && self.value.get(end) == Some(&C_LINE_FEED)
1320 {
1321 end += 1;
1322 }
1323 } else {
1324 end += 1;
1325 }
1326 } else {
1327 end += self.utf8_width_at(end);
1328 }
1329 }
1330 if end == self.value.len() {
1331 break;
1332 }
1333 let rest = &self.value[end..];
1336 let rel = memchr2(C_RIGHT_PARENTHESIS, C_REVERSE_SOLIDUS, rest).unwrap_or(rest.len());
1337 let segment = &rest[..rel];
1338 if memchr::memchr(b'\0', segment).is_some() {
1339 flags.mark_null();
1340 }
1341 if !segment.is_ascii() {
1342 flags.mark_non_ascii();
1343 }
1344 end += rel;
1345 }
1348 (TokenKind::BadUrl, end, content_start, end, flags)
1349 }
1350
1351 fn scan_numeric(&self, start: usize) -> (TokenKind, usize, usize, usize, TokenFlags) {
1352 let end = self.scan_number_end(start);
1353 if self.value.get(end) == Some(&C_PERCENTAGE) {
1354 return (
1355 TokenKind::Percentage,
1356 end + 1,
1357 start,
1358 end + 1,
1359 TokenFlags::ascii(),
1360 );
1361 }
1362 if self.starts_ident_at(end) {
1363 let (end, flags) = self.scan_name_with_flags(end);
1364 return (TokenKind::Dimension, end, start, end, flags);
1365 }
1366 (TokenKind::Number, end, start, end, TokenFlags::ascii())
1367 }
1368
1369 #[inline]
1370 fn scan_number_end(&self, start: usize) -> usize {
1371 let mut end = start;
1372 if matches!(self.value.get(end), Some(&C_PLUS_SIGN | &C_HYPHEN_MINUS)) {
1373 end += 1;
1374 }
1375 while self.value.get(end).is_some_and(|byte| is_digit(*byte)) {
1376 end += 1;
1377 }
1378 if self.value.get(end) == Some(&C_FULL_STOP)
1379 && self.value.get(end + 1).is_some_and(|byte| is_digit(*byte))
1380 {
1381 end += 1;
1382 while self.value.get(end).is_some_and(|byte| is_digit(*byte)) {
1383 end += 1;
1384 }
1385 }
1386 if matches!(self.value.get(end), Some(&C_LOWER_E | &C_UPPER_E)) {
1387 let exponent_start = end;
1388 let mut exponent_end = end + 1;
1389 if matches!(
1390 self.value.get(exponent_end),
1391 Some(&C_PLUS_SIGN | &C_HYPHEN_MINUS)
1392 ) {
1393 exponent_end += 1;
1394 }
1395 if self
1396 .value
1397 .get(exponent_end)
1398 .is_some_and(|byte| is_digit(*byte))
1399 {
1400 end = exponent_end + 1;
1401 while self.value.get(end).is_some_and(|byte| is_digit(*byte)) {
1402 end += 1;
1403 }
1404 } else {
1405 end = exponent_start;
1406 }
1407 }
1408 end
1409 }
1410
1411 #[inline]
1412 fn skip_white_space(&self, mut position: usize) -> usize {
1413 while self
1414 .value
1415 .get(position)
1416 .is_some_and(|byte| is_white_space(*byte))
1417 {
1418 position += 1;
1419 }
1420 position
1421 }
1422
1423 #[inline]
1424 fn scan_plain_ascii_name(&self, mut position: usize) -> usize {
1425 while self
1426 .value
1427 .get(position)
1428 .is_some_and(|byte| PLAIN_ASCII_NAME_BYTE[*byte as usize])
1429 {
1430 position += 1;
1431 }
1432 position
1433 }
1434
1435 #[inline]
1436 fn raw_name_needs_tokenizer(&self, position: usize) -> bool {
1437 self
1438 .value
1439 .get(position)
1440 .is_some_and(|byte| *byte == 0 || *byte == C_REVERSE_SOLIDUS || !byte.is_ascii())
1441 }
1442
1443 #[inline]
1444 fn scan_raw_numeric_end(&self, start: usize) -> Option<usize> {
1445 let end = self.scan_number_end(start);
1446 if self.value.get(end) == Some(&C_PERCENTAGE) {
1447 return Some(end + 1);
1448 }
1449 if self.starts_ident_at(end) {
1450 let name_end = self.scan_plain_ascii_name(end);
1451 if name_end == end || self.raw_name_needs_tokenizer(name_end) {
1452 return None;
1453 }
1454 return Some(name_end);
1455 }
1456 if self.raw_name_needs_tokenizer(end) {
1457 return None;
1458 }
1459 Some(end)
1460 }
1461
1462 #[inline]
1463 fn scan_name(&self, position: usize) -> usize {
1464 self.scan_name_impl(position, false).0
1465 }
1466
1467 #[inline]
1468 fn scan_name_with_flags(&self, position: usize) -> (usize, TokenFlags) {
1469 self.scan_name_impl(position, true)
1470 }
1471
1472 #[inline]
1473 fn scan_name_impl(&self, mut position: usize, track_flags: bool) -> (usize, TokenFlags) {
1474 let bytes = self.value;
1475 let mut flags = TokenFlags::ascii();
1476 while position < bytes.len() {
1477 while position < bytes.len() {
1478 let byte = bytes[position];
1479 if PLAIN_ASCII_NAME_BYTE[byte as usize] {
1480 position += 1;
1481 } else if byte == 0 {
1482 if track_flags {
1483 flags.mark_null();
1484 }
1485 position += 1;
1486 } else {
1487 break;
1488 }
1489 }
1490 if position == bytes.len() {
1491 break;
1492 }
1493
1494 let byte = bytes[position];
1495 if byte == C_REVERSE_SOLIDUS {
1496 if self.is_valid_escape_at(position) {
1497 if track_flags {
1498 flags.mark_escape();
1499 if bytes.get(position + 1).is_some_and(|next| !next.is_ascii()) {
1500 flags.mark_non_ascii();
1501 }
1502 }
1503 position = self.scan_escape(position);
1504 } else {
1505 break;
1506 }
1507 } else if byte.is_ascii() {
1508 break;
1509 } else {
1510 if track_flags {
1511 flags.mark_non_ascii();
1512 }
1513 position += self.utf8_width_at(position);
1514 }
1515 }
1516 (position, flags)
1517 }
1518
1519 #[inline]
1520 fn starts_ident_at(&self, position: usize) -> bool {
1521 let Some(byte) = self.value.get(position).copied() else {
1522 return false;
1523 };
1524 match byte {
1525 C_HYPHEN_MINUS => match self.value.get(position + 1).copied() {
1526 Some(next) => {
1527 is_name_start_byte(next)
1528 || next == C_HYPHEN_MINUS
1529 || self.is_valid_escape_at(position + 1)
1530 }
1531 None => false,
1532 },
1533 C_REVERSE_SOLIDUS => self.is_valid_escape_at(position),
1534 _ => is_name_start_byte(byte),
1535 }
1536 }
1537
1538 #[inline]
1539 fn starts_number_at(&self, position: usize) -> bool {
1540 let Some(first) = self.value.get(position).copied() else {
1541 return false;
1542 };
1543 let second = self.value.get(position + 1).copied();
1544 let third = self.value.get(position + 2).copied();
1545 match first {
1546 C_PLUS_SIGN | C_HYPHEN_MINUS => {
1547 second.is_some_and(is_digit) || (second == Some(C_FULL_STOP) && third.is_some_and(is_digit))
1548 }
1549 C_FULL_STOP => second.is_some_and(is_digit),
1550 _ => is_digit(first),
1551 }
1552 }
1553
1554 #[inline]
1555 fn is_valid_escape_at(&self, position: usize) -> bool {
1556 self.value.get(position) == Some(&C_REVERSE_SOLIDUS)
1557 && self
1558 .value
1559 .get(position + 1)
1560 .is_some_and(|byte| !is_new_line(*byte))
1561 }
1562
1563 #[inline]
1564 fn scan_escape(&self, position: usize) -> usize {
1565 debug_assert!(self.is_valid_escape_at(position));
1566 let mut end = position + 1;
1567 if self.value[end].is_ascii_hexdigit() {
1568 let mut digits = 0;
1569 while end < self.value.len() && digits < 6 && self.value[end].is_ascii_hexdigit() {
1570 end += 1;
1571 digits += 1;
1572 }
1573 if self
1574 .value
1575 .get(end)
1576 .is_some_and(|byte| is_white_space(*byte))
1577 {
1578 end += 1;
1579 if self.value.get(end - 1) == Some(&C_CARRIAGE_RETURN)
1580 && self.value.get(end) == Some(&C_LINE_FEED)
1581 {
1582 end += 1;
1583 }
1584 }
1585 return end;
1586 }
1587 end + self.utf8_width_at(end)
1588 }
1589
1590 #[inline]
1591 fn utf8_width_at(&self, position: usize) -> usize {
1592 let byte = self.value[position];
1593 if byte < 0x80 {
1594 1
1595 } else if byte < 0xE0 {
1596 2
1597 } else if byte < 0xF0 {
1598 3
1599 } else {
1600 4
1601 }
1602 }
1603}
1604
1605pub(crate) struct TokenStream<'a, 's, V: LexerVisitor = ()> {
1625 lexer: &'a mut Lexer<'s, V>,
1626 consumed: Pos,
1627 buffered: VecDeque<TokenWithTrivia>,
1628 generic_value_state: GenericValueScanState,
1629 at_rule_state: GenericValueScanState,
1630 special_value_state: GenericValueScanState,
1631}
1632
1633#[derive(Debug, Default)]
1634pub(crate) struct GenericValueScanState {
1635 parentheses: u32,
1636 squares: u32,
1637 curlies: u32,
1638}
1639
1640#[derive(Debug, Clone, Copy)]
1641struct GenericValueScanOptions {
1642 keep_comments: bool,
1643 preserve_strings: bool,
1644 preserve_delimiters: bool,
1645 property: Option<PropertyKind>,
1646}
1647
1648#[derive(Debug, Clone, Copy)]
1649struct PrescannedIdent {
1650 start: Pos,
1651 end: Pos,
1652 flags: TokenFlags,
1653}
1654
1655impl GenericValueScanState {
1656 #[inline]
1657 fn is_nested(&self) -> bool {
1658 self.parentheses != 0 || self.squares != 0 || self.curlies != 0
1659 }
1660}
1661
1662#[inline]
1663fn is_dependency_value_function(name: &str) -> bool {
1664 let mut lowercase = [0; MAX_CSS_KEYWORD_LEN];
1665 let Some(name) = lowercase_ascii_keyword(name, &mut lowercase) else {
1666 return name.starts_with("--");
1667 };
1668 matches!(name, "url" | "var" | "image-set")
1669 || strip_vendor_prefix(name) == Some("image-set")
1670 || name.starts_with("--")
1671}
1672
1673#[inline(always)]
1674fn never_fast_forward_candidate(_: &str) -> bool {
1675 false
1676}
1677
1678impl<'a, 's, V: LexerVisitor> TokenStream<'a, 's, V> {
1679 pub(crate) fn from_lexer(lexer: &'a mut Lexer<'s, V>) -> Self {
1680 Self {
1681 lexer,
1682 consumed: 0,
1683 buffered: VecDeque::new(),
1684 generic_value_state: GenericValueScanState::default(),
1685 at_rule_state: GenericValueScanState::default(),
1686 special_value_state: GenericValueScanState::default(),
1687 }
1688 }
1689
1690 #[inline(always)]
1691 pub(crate) fn next(&mut self, keep_comments: bool) -> TokenWithTrivia {
1692 let token = if let Some(token) = self.buffered.pop_front() {
1693 token
1694 } else {
1695 self.read_significant(keep_comments)
1696 };
1697 debug_assert!(
1698 token.token.range.start >= self.consumed,
1699 "token range starts before consumed_pos: {:?} < {}",
1700 token.token.range,
1701 self.consumed
1702 );
1703 self.consumed = token.token.range.end;
1704 if matches!(
1705 token.token.kind,
1706 TokenKind::Semicolon | TokenKind::RightCurlyBracket
1707 ) {
1708 self.generic_value_state = GenericValueScanState::default();
1709 }
1710 debug_assert!(
1711 self.consumed <= self.lexer.scan_pos(),
1712 "consumed_pos ({}) exceeded scan_pos ({})",
1713 self.consumed,
1714 self.lexer.scan_pos()
1715 );
1716 token
1717 }
1718
1719 #[inline]
1724 pub(crate) fn next_parser_token(&mut self) -> TokenWithTrivia {
1725 let mut item = self.next(true);
1726 if item.token.kind != TokenKind::Comment {
1727 return item;
1728 }
1729
1730 let start = item.leading.range.start;
1731 let first_comment_start = item
1732 .leading
1733 .first_comment_start
1734 .or(Some(item.token.range.start));
1735 let mut has_white_space = item.leading.has_white_space;
1736 loop {
1737 item = self.next(true);
1738 has_white_space |= item.leading.has_white_space;
1739 if item.token.kind == TokenKind::Comment {
1740 continue;
1741 }
1742 let end = item.leading.end;
1743 item.leading = Trivia {
1744 range: Range::new(start, end),
1745 end,
1746 first_comment_start,
1747 has_white_space,
1748 };
1749 return item;
1750 }
1751 }
1752
1753 #[inline]
1754 pub(crate) fn peek(&mut self, keep_comments: bool) -> TokenWithTrivia {
1755 if self.buffered.is_empty() {
1756 let item = self.read_significant(keep_comments);
1757 self.buffered.push_back(item);
1758 }
1759 *self
1760 .buffered
1761 .front()
1762 .expect("peek must buffer an item before reading the front")
1763 }
1764
1765 #[inline]
1769 pub(crate) fn peek_parser_token(&mut self) -> TokenWithTrivia {
1770 self.peek(true);
1771 let mut leading_start = None;
1772 let mut first_comment_start = None;
1773 let mut has_white_space = false;
1774 let mut index = 0usize;
1775 loop {
1776 if index == self.buffered.len() {
1777 let item = self.read_significant(true);
1778 self.buffered.push_back(item);
1779 }
1780 let item = self.buffered[index];
1781 leading_start.get_or_insert(item.leading.range.start);
1782 has_white_space |= item.leading.has_whitespace();
1783 if matches!(item.token.kind, TokenKind::Comment | TokenKind::BadComment) {
1784 first_comment_start.get_or_insert(item.token.range.start);
1785 index += 1;
1786 continue;
1787 }
1788
1789 let end = item.leading.end;
1790 let mut result = item;
1791 result.leading = Trivia {
1792 range: Range::new(leading_start.unwrap_or(end), end),
1793 end,
1794 first_comment_start: first_comment_start.or(item.leading.first_comment_start),
1795 has_white_space,
1796 };
1797 return result;
1798 }
1799 }
1800
1801 #[inline]
1804 pub(crate) fn peek_significant_skipping_comments(
1805 &mut self,
1806 keep_comments: bool,
1807 ) -> TokenWithTrivia {
1808 self.peek(keep_comments);
1809 for item in &self.buffered {
1810 if !matches!(item.token.kind, TokenKind::Comment | TokenKind::BadComment) {
1811 return *item;
1812 }
1813 }
1814 loop {
1815 let item = self.read_significant(keep_comments);
1816 self.buffered.push_back(item);
1817 if !matches!(item.token.kind, TokenKind::Comment | TokenKind::BadComment) {
1818 return item;
1819 }
1820 }
1821 }
1822
1823 #[inline]
1826 pub(crate) fn fast_forward_generic_value_if_buffer_empty<F, C>(
1827 &mut self,
1828 keep_comments: bool,
1829 preserve_strings: bool,
1830 preserve_delimiters: bool,
1831 mut is_candidate: F,
1832 mut is_comment_candidate: C,
1833 ) where
1834 F: FnMut(&str) -> bool,
1835 C: FnMut(&str) -> bool,
1836 {
1837 if !self.buffered.is_empty() {
1838 return;
1839 }
1840 let candidate = self.lexer.fast_forward_generic_value(
1841 &mut self.generic_value_state,
1842 GenericValueScanOptions {
1843 keep_comments,
1844 preserve_strings,
1845 preserve_delimiters,
1846 property: None,
1847 },
1848 false,
1849 &mut is_candidate,
1850 &mut is_comment_candidate,
1851 None,
1852 );
1853 self.finish_value_fast_forward(candidate);
1854 }
1855
1856 #[inline]
1857 pub(crate) fn fast_forward_generic_value_without_candidates_if_buffer_empty(
1858 &mut self,
1859 preserve_strings: bool,
1860 preserve_delimiters: bool,
1861 ) {
1862 if !self.buffered.is_empty() {
1863 return;
1864 }
1865 let candidate = self.lexer.fast_forward_generic_value(
1866 &mut self.generic_value_state,
1867 GenericValueScanOptions {
1868 keep_comments: false,
1869 preserve_strings,
1870 preserve_delimiters,
1871 property: None,
1872 },
1873 false,
1874 never_fast_forward_candidate,
1875 never_fast_forward_candidate,
1876 None,
1877 );
1878 self.finish_value_fast_forward(candidate);
1879 }
1880
1881 #[inline]
1882 pub(crate) fn fast_forward_at_rule_if_buffer_empty<F, C>(
1883 &mut self,
1884 keep_comments: bool,
1885 preserve_strings: bool,
1886 preserve_delimiters: bool,
1887 mut is_candidate: F,
1888 mut is_comment_candidate: C,
1889 ) where
1890 F: FnMut(&str) -> bool,
1891 C: FnMut(&str) -> bool,
1892 {
1893 if !self.buffered.is_empty() {
1894 return;
1895 }
1896 let candidate = self.lexer.fast_forward_generic_value(
1897 &mut self.at_rule_state,
1898 GenericValueScanOptions {
1899 keep_comments,
1900 preserve_strings,
1901 preserve_delimiters,
1902 property: None,
1903 },
1904 true,
1905 &mut is_candidate,
1906 &mut is_comment_candidate,
1907 None,
1908 );
1909 self.finish_value_fast_forward(candidate);
1910 }
1911
1912 #[inline]
1913 pub(crate) fn reset_at_rule_scan_state(&mut self) {
1914 self.at_rule_state = GenericValueScanState::default();
1915 }
1916
1917 #[inline]
1918 pub(crate) fn fast_forward_special_value_if_buffer_empty(
1919 &mut self,
1920 keep_comments: bool,
1921 preserve_strings: bool,
1922 preserve_delimiters: bool,
1923 property: PropertyKind,
1924 icss_symbols: Option<&FxHashSet<&str>>,
1925 ) {
1926 if !self.buffered.is_empty() {
1927 return;
1928 }
1929 let candidate = self.lexer.fast_forward_generic_value(
1930 &mut self.special_value_state,
1931 GenericValueScanOptions {
1932 keep_comments,
1933 preserve_strings,
1934 preserve_delimiters,
1935 property: Some(property),
1936 },
1937 false,
1938 never_fast_forward_candidate,
1939 is_css_modules_pure_magic_comment,
1940 icss_symbols,
1941 );
1942 self.finish_value_fast_forward(candidate);
1943 }
1944
1945 #[inline]
1946 fn finish_value_fast_forward(&mut self, candidate: Option<PrescannedIdent>) {
1947 let Some(candidate) = candidate else {
1948 self.consumed = self.lexer.scan_pos();
1949 return;
1950 };
1951 debug_assert_eq!(candidate.end, self.lexer.scan_pos());
1952 self.consumed = candidate.start;
1953 let range = Range::new(candidate.start, candidate.end);
1954 self.buffered.push_back(TokenWithTrivia {
1955 token: Token::with_flags(TokenKind::Ident, range, range, candidate.flags),
1956 leading: Trivia {
1957 range: Range::new(candidate.start, candidate.start),
1958 end: candidate.start,
1959 first_comment_start: None,
1960 has_white_space: false,
1961 },
1962 });
1963 }
1964
1965 #[inline]
1966 pub(crate) fn reset_special_value_scan_state(&mut self) {
1967 self.special_value_state = GenericValueScanState::default();
1968 }
1969
1970 #[inline]
1971 pub(crate) fn fast_forward_selector_if_buffer_empty<F, C>(
1972 &mut self,
1973 square_depth: &mut u32,
1974 keep_comments: bool,
1975 has_mode: bool,
1976 is_ident_candidate: F,
1977 is_comment_candidate: C,
1978 ) -> bool
1979 where
1980 F: FnMut(&str) -> bool,
1981 C: FnMut(&str) -> bool,
1982 {
1983 if !self.buffered.is_empty() {
1984 return false;
1985 }
1986 if *square_depth == 0 {
1987 let scan_pos = self.lexer.scan_pos();
1988 let Some(next) = self.lexer.byte_at(scan_pos) else {
1989 return false;
1990 };
1991 if matches!(
1992 next,
1993 b'"' | b'\'' | b'(' | b')' | b'{' | b'}' | b',' | b';' | b'@'
1994 ) || (has_mode && matches!(next, b'.' | b'#' | b':'))
1995 {
1996 return false;
1997 }
1998
1999 let mut probe = scan_pos;
2004 while probe - scan_pos < 4 {
2005 let Some(byte) = self.lexer.byte_at(probe) else {
2006 return false;
2007 };
2008 if matches!(byte, b'/' | b'[') {
2009 break;
2010 }
2011 if probe != scan_pos
2012 && (matches!(
2013 byte,
2014 b'"' | b'\'' | b'(' | b')' | b'{' | b'}' | b',' | b';' | b'@'
2015 ) || (has_mode && matches!(byte, b'.' | b'#' | b':')))
2016 {
2017 return false;
2018 }
2019 probe += 1;
2020 }
2021 }
2022 let invalidates_composes = self.lexer.fast_forward_selector(
2023 square_depth,
2024 keep_comments,
2025 has_mode,
2026 is_ident_candidate,
2027 is_comment_candidate,
2028 );
2029 self.consumed = self.lexer.scan_pos();
2030 invalidates_composes
2031 }
2032
2033 pub(crate) fn fast_forward(&mut self, end: TokenKind) -> Option<Range> {
2040 debug_assert!(
2041 self.buffered.is_empty(),
2042 "fast_forward requires an empty lookahead buffer"
2043 );
2044 let old_scan_pos = self.lexer.scan_pos();
2045 let old_consumed = self.consumed;
2046 let range = self.lexer.fast_forward(end)?;
2047 self.consumed = range.end;
2048 debug_assert!(old_scan_pos <= self.lexer.scan_pos());
2049 debug_assert!(old_consumed <= self.consumed);
2050 debug_assert!(self.consumed <= self.lexer.scan_pos());
2051 Some(range)
2052 }
2053
2054 #[inline]
2057 pub(crate) fn consumed_pos(&self) -> Pos {
2058 self.consumed
2059 }
2060
2061 #[inline]
2062 pub(crate) fn lexer(&self) -> &Lexer<'s, V> {
2063 self.lexer
2064 }
2065
2066 #[inline]
2067 pub(crate) fn lexer_mut(&mut self) -> &mut Lexer<'s, V> {
2068 self.lexer
2069 }
2070
2071 #[inline]
2072 pub(crate) fn source_end(&self) -> Pos {
2073 self.lexer.source_end()
2074 }
2075
2076 #[inline(always)]
2077 pub(crate) fn byte_at(&self, position: Pos) -> Option<u8> {
2078 self.lexer.byte_at(position)
2079 }
2080
2081 #[inline]
2082 pub(crate) fn slice(&self, start: Pos, end: Pos) -> Option<&'s str> {
2083 self.lexer.slice(start, end)
2084 }
2085
2086 #[inline(always)]
2087 pub(crate) fn slice_trusted(&self, start: Pos, end: Pos) -> &'s str {
2088 self.lexer.slice_trusted(start, end)
2089 }
2090
2091 #[inline(always)]
2092 fn read_significant(&mut self, keep_comments: bool) -> TokenWithTrivia {
2093 let start = self.lexer.scan_pos();
2094 let mut end = start;
2095 let mut first_comment_start = None;
2096 let mut has_white_space = false;
2097 loop {
2098 let token = self.lexer.next_token();
2099 debug_assert!(token.range.start >= end);
2100 if token.kind == TokenKind::WhiteSpace {
2101 has_white_space = true;
2102 end = token.range.end;
2103 continue;
2104 }
2105 if !keep_comments && matches!(token.kind, TokenKind::Comment | TokenKind::BadComment) {
2106 first_comment_start.get_or_insert(token.range.start);
2107 end = token.range.end;
2108 continue;
2109 }
2110 let leading = Trivia {
2111 range: Range::new(start, end),
2112 end,
2113 first_comment_start,
2114 has_white_space,
2115 };
2116 return TokenWithTrivia { token, leading };
2117 }
2118 }
2119}
2120
2121pub fn is_new_line(c: u8) -> bool {
2122 c == C_LINE_FEED || c == C_CARRIAGE_RETURN || c == C_FORM_FEED
2123}
2124
2125#[inline]
2126fn kind_to_right(byte: u8) -> TokenKind {
2127 match byte {
2128 C_RIGHT_PARENTHESIS => TokenKind::RightParenthesis,
2129 C_RIGHT_SQUARE => TokenKind::RightSquareBracket,
2130 C_RIGHT_CURLY => TokenKind::RightCurlyBracket,
2131 _ => unreachable!("fast_forward only sees closing bracket bytes"),
2132 }
2133}
2134
2135pub fn is_space(c: u8) -> bool {
2136 c == C_TAB || c == C_SPACE
2137}
2138
2139pub fn is_white_space(c: u8) -> bool {
2140 is_new_line(c) || is_space(c)
2141}
2142
2143pub fn is_digit(c: u8) -> bool {
2144 c.is_ascii_digit()
2145}
2146
2147#[inline]
2148fn is_name_start_byte(c: u8) -> bool {
2149 c == 0 || c == C_LOW_LINE || c.is_ascii_alphabetic() || !c.is_ascii()
2150}
2151
2152#[inline]
2153fn is_non_printable(c: u8) -> bool {
2154 matches!(c, 0x00..=0x08 | 0x0B | 0x0E..=0x1F | 0x7F)
2155}
2156
2157#[cfg(test)]
2158#[path = "../tests/lexer_tests.rs"]
2159mod tests;