Skip to main content

css_module_lexer/
lexer.rs

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