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