1use self::Token::*;
8use crate::cow_rc_str::CowRcStr;
9use crate::parser::{ArbitrarySubstitutionFunctions, ParserState};
10use std::char;
11use std::ops::Range;
12
13#[cfg(feature = "fast_match_byte")]
14pub use crate::match_byte;
15
16#[derive(PartialEq, Debug, Clone)]
21pub enum Token<'a> {
22 Ident(CowRcStr<'a>),
24
25 AtKeyword(CowRcStr<'a>),
29
30 Hash(CowRcStr<'a>),
34
35 IDHash(CowRcStr<'a>), QuotedString(CowRcStr<'a>),
44
45 UnquotedUrl(CowRcStr<'a>),
50
51 Delim(char),
53
54 Number {
56 has_sign: bool,
60
61 value: f32,
63
64 int_value: Option<i32>,
66 },
67
68 Percentage {
70 has_sign: bool,
72
73 unit_value: f32,
75
76 int_value: Option<i32>,
79 },
80
81 Dimension {
83 has_sign: bool,
87
88 value: f32,
90
91 int_value: Option<i32>,
93
94 unit: CowRcStr<'a>,
96 },
97
98 WhiteSpace(&'a str),
100
101 Comment(&'a str),
108
109 Colon, Semicolon, Comma, IncludeMatch,
120
121 DashMatch,
123
124 PrefixMatch,
126
127 SuffixMatch,
129
130 SubstringMatch,
132
133 CDO,
135
136 CDC,
138
139 Function(CowRcStr<'a>),
143
144 ParenthesisBlock,
146
147 SquareBracketBlock,
149
150 CurlyBracketBlock,
152
153 BadUrl(CowRcStr<'a>),
157
158 BadString(CowRcStr<'a>),
162
163 CloseParenthesis,
168
169 CloseSquareBracket,
174
175 CloseCurlyBracket,
180}
181
182impl Token<'_> {
183 pub fn is_parse_error(&self) -> bool {
190 matches!(
191 *self,
192 BadUrl(_) | BadString(_) | CloseParenthesis | CloseSquareBracket | CloseCurlyBracket
193 )
194 }
195}
196
197#[derive(Clone)]
198pub struct Tokenizer<'a> {
199 input: &'a str,
200 position: usize,
202 current_line_start_position: usize,
206 current_line_number: u32,
207 arbitrary_substitution_functions: SeenStatus<'a>,
208 source_map_url: Option<&'a str>,
209 source_url: Option<&'a str>,
210}
211
212#[derive(Copy, Clone, PartialEq, Eq)]
213enum SeenStatus<'a> {
214 DontCare,
215 LookingForThem(ArbitrarySubstitutionFunctions<'a>),
216 SeenAtLeastOne,
217}
218
219impl<'a> Tokenizer<'a> {
220 #[inline]
221 pub fn new(input: &'a str) -> Self {
222 Tokenizer {
223 input,
224 position: 0,
225 current_line_start_position: 0,
226 current_line_number: 0,
227 arbitrary_substitution_functions: SeenStatus::DontCare,
228 source_map_url: None,
229 source_url: None,
230 }
231 }
232
233 #[inline]
234 pub fn look_for_arbitrary_substitution_functions(
235 &mut self,
236 fns: ArbitrarySubstitutionFunctions<'a>,
237 ) {
238 self.arbitrary_substitution_functions = SeenStatus::LookingForThem(fns);
239 }
240
241 #[inline]
242 pub fn seen_arbitrary_substitution_functions(&mut self) -> bool {
243 let seen = self.arbitrary_substitution_functions == SeenStatus::SeenAtLeastOne;
244 self.arbitrary_substitution_functions = SeenStatus::DontCare;
245 seen
246 }
247
248 #[inline]
249 pub fn see_function(&mut self, name: &str) {
250 if let SeenStatus::LookingForThem(fns) = self.arbitrary_substitution_functions {
251 if fns.iter().any(|a| name.eq_ignore_ascii_case(a)) {
252 self.arbitrary_substitution_functions = SeenStatus::SeenAtLeastOne;
253 }
254 }
255 }
256
257 #[inline]
258 pub fn next(&mut self) -> Result<Token<'a>, ()> {
259 if self.is_eof() {
260 return Err(());
261 }
262 Ok(self.next_unchecked())
263 }
264
265 #[inline]
266 pub fn next_unchecked(&mut self) -> Token<'a> {
267 next_token_unchecked(self)
268 }
269
270 #[inline]
271 pub fn position(&self) -> SourcePosition {
272 debug_assert!(self.input.is_char_boundary(self.position));
273 SourcePosition(self.position)
274 }
275
276 #[inline]
277 pub fn current_source_location(&self) -> SourceLocation {
278 SourceLocation {
279 line: self.current_line_number,
280 column: (self.position - self.current_line_start_position + 1) as u32,
281 }
282 }
283
284 #[inline]
285 pub fn current_source_map_url(&self) -> Option<&'a str> {
286 self.source_map_url
287 }
288
289 #[inline]
290 pub fn current_source_url(&self) -> Option<&'a str> {
291 self.source_url
292 }
293
294 #[inline]
295 pub fn state(&self) -> ParserState {
296 ParserState {
297 position: self.position,
298 current_line_start_position: self.current_line_start_position,
299 current_line_number: self.current_line_number,
300 at_start_of: None,
301 }
302 }
303
304 #[inline]
305 pub fn reset(&mut self, state: &ParserState) {
306 self.position = state.position;
307 self.current_line_start_position = state.current_line_start_position;
308 self.current_line_number = state.current_line_number;
309 }
310
311 #[inline]
312 pub(crate) fn slice_from(&self, start_pos: SourcePosition) -> &'a str {
313 self.slice(start_pos..self.position())
314 }
315
316 #[inline]
317 pub(crate) fn slice(&self, range: Range<SourcePosition>) -> &'a str {
318 debug_assert!(self.input.is_char_boundary(range.start.0));
319 debug_assert!(self.input.is_char_boundary(range.end.0));
320 unsafe { self.input.get_unchecked(range.start.0..range.end.0) }
321 }
322
323 #[inline]
324 pub(crate) fn byte_slice(&self, range: Range<usize>) -> &'a [u8] {
325 &self.input.as_bytes()[range]
326 }
327
328 #[inline]
329 pub(crate) fn byte_slice_from(&self, start: usize) -> &'a [u8] {
330 self.byte_slice(start..self.position)
331 }
332
333 pub fn current_source_line(&self) -> &'a str {
334 let current = self.position();
335 let start = self
336 .slice(SourcePosition(0)..current)
337 .rfind(['\r', '\n', '\x0C'])
338 .map_or(0, |start| start + 1);
339 let end = self
340 .slice(current..SourcePosition(self.input.len()))
341 .find(['\r', '\n', '\x0C'])
342 .map_or(self.input.len(), |end| current.0 + end);
343 self.slice(SourcePosition(start)..SourcePosition(end))
344 }
345
346 #[inline]
347 pub fn next_byte(&self) -> Option<u8> {
348 if self.is_eof() {
349 None
350 } else {
351 Some(self.input.as_bytes()[self.position])
352 }
353 }
354
355 #[inline]
357 fn is_eof(&self) -> bool {
358 !self.has_at_least(0)
359 }
360
361 #[inline]
364 fn has_at_least(&self, n: usize) -> bool {
365 self.position + n < self.input.len()
366 }
367
368 #[inline]
372 pub fn advance(&mut self, n: usize) {
373 if cfg!(debug_assertions) {
374 for i in 0..n {
378 let b = self.byte_at(i);
379 debug_assert!(b.is_ascii() || (b & 0xF0 != 0xF0 && b & 0xC0 != 0x80));
380 debug_assert!(b != b'\r' && b != b'\n' && b != b'\x0C');
381 }
382 }
383 self.position += n
384 }
385
386 fn advance_while(&mut self, mut matches: impl FnMut(u8) -> bool) -> &[u8] {
389 let start = self.position;
390 let mut position = start;
391
392 let bytes = &self.input.as_bytes()[start..];
393 for b in bytes {
394 if !matches(*b) {
395 break;
396 }
397 position += 1;
398 }
399
400 self.advance(position - self.position);
402
403 self.byte_slice_from(start)
404 }
405
406 #[inline]
408 fn next_byte_unchecked(&self) -> u8 {
409 self.byte_at(0)
410 }
411
412 #[inline]
413 fn byte_at(&self, offset: usize) -> u8 {
414 self.input.as_bytes()[self.position + offset]
415 }
416
417 #[inline]
420 fn consume_4byte_intro(&mut self) {
421 debug_assert!(self.next_byte_unchecked() & 0xF0 == 0xF0);
422 self.current_line_start_position = self.current_line_start_position.wrapping_sub(1);
425 self.position += 1;
426 }
427
428 #[inline]
431 fn consume_continuation_byte(&mut self) {
432 debug_assert!(self.next_byte_unchecked() & 0xC0 == 0x80);
433 self.current_line_start_position = self.current_line_start_position.wrapping_add(1);
437 self.position += 1;
438 }
439
440 #[inline(never)]
442 fn consume_known_byte(&mut self, byte: u8) {
443 debug_assert!(byte != b'\r' && byte != b'\n' && byte != b'\x0C');
444 self.position += 1;
445 if byte & 0xF0 == 0xF0 {
447 self.current_line_start_position = self.current_line_start_position.wrapping_sub(1);
450 } else if byte & 0xC0 == 0x80 {
451 self.current_line_start_position = self.current_line_start_position.wrapping_add(1);
454 }
455 }
456
457 #[inline]
458 fn next_char(&self) -> char {
459 unsafe { self.input.get_unchecked(self.position().0..) }
460 .chars()
461 .next()
462 .unwrap()
463 }
464
465 #[inline]
468 fn consume_newline(&mut self) {
469 let byte = self.next_byte_unchecked();
470 debug_assert!(byte == b'\r' || byte == b'\n' || byte == b'\x0C');
471 self.position += 1;
472 if byte == b'\r' && self.next_byte() == Some(b'\n') {
473 self.position += 1;
474 }
475 self.current_line_start_position = self.position;
476 self.current_line_number += 1;
477 }
478
479 #[inline]
480 fn has_newline_at(&self, offset: usize) -> bool {
481 self.position + offset < self.input.len()
482 && matches!(self.byte_at(offset), b'\n' | b'\r' | b'\x0C')
483 }
484
485 #[inline]
486 fn consume_char(&mut self) -> char {
487 let c = self.next_char();
488 let len_utf8 = c.len_utf8();
489 self.position += len_utf8;
490 self.current_line_start_position = self
493 .current_line_start_position
494 .wrapping_add(len_utf8 - c.len_utf16());
495 c
496 }
497
498 #[inline]
499 fn starts_with(&self, needle: &[u8]) -> bool {
500 self.input.as_bytes()[self.position..].starts_with(needle)
501 }
502
503 pub fn skip_whitespace(&mut self) {
504 while !self.is_eof() {
505 match_byte! { self.next_byte_unchecked(),
506 b' ' | b'\t' => {
507 self.advance(1)
508 },
509 b'\n' | b'\x0C' | b'\r' => {
510 self.consume_newline();
511 },
512 b'/' => {
513 if self.starts_with(b"/*") {
514 consume_comment(self);
515 } else {
516 return
517 }
518 }
519 _ => return,
520 }
521 }
522 }
523
524 pub fn skip_cdc_and_cdo(&mut self) {
525 while !self.is_eof() {
526 match_byte! { self.next_byte_unchecked(),
527 b' ' | b'\t' => {
528 self.advance(1)
529 },
530 b'\n' | b'\x0C' | b'\r' => {
531 self.consume_newline();
532 },
533 b'/' => {
534 if self.starts_with(b"/*") {
535 consume_comment(self);
536 } else {
537 return
538 }
539 }
540 b'<' => {
541 if self.starts_with(b"<!--") {
542 self.advance(4)
543 } else {
544 return
545 }
546 }
547 b'-' => {
548 if self.starts_with(b"-->") {
549 self.advance(3)
550 } else {
551 return
552 }
553 }
554 _ => {
555 return
556 }
557 }
558 }
559 }
560}
561
562#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)]
564pub struct SourcePosition(pub(crate) usize);
565
566#[cfg(feature = "malloc_size_of")]
567malloc_size_of::malloc_size_of_is_0!(SourcePosition);
568
569impl SourcePosition {
570 #[inline]
572 pub fn byte_index(&self) -> usize {
573 self.0
574 }
575}
576
577#[derive(PartialEq, Eq, Debug, Clone, Copy, Default)]
579pub struct SourceLocation {
580 pub line: u32,
582
583 pub column: u32,
586}
587
588#[cfg(feature = "malloc_size_of")]
589malloc_size_of::malloc_size_of_is_0!(SourceLocation);
590
591fn next_token_unchecked<'a>(tokenizer: &mut Tokenizer<'a>) -> Token<'a> {
592 debug_assert!(!tokenizer.is_eof());
593 let b = tokenizer.next_byte_unchecked();
594 let token = match_byte! { b,
595 b' ' | b'\t' => {
596 consume_whitespace(tokenizer, false)
597 },
598 b'\n' | b'\x0C' | b'\r' => consume_whitespace(tokenizer, true),
599 b'"' => consume_string(tokenizer, false),
600 b'#' => {
601 tokenizer.advance(1);
602 if is_ident_start(tokenizer) { IDHash(consume_name(tokenizer)) }
603 else if !tokenizer.is_eof() &&
604 matches!(tokenizer.next_byte_unchecked(), b'0'..=b'9' | b'-') {
605 Hash(consume_name(tokenizer))
607 }
608 else { Delim('#') }
609 },
610 b'$' => {
611 if tokenizer.starts_with(b"$=") { tokenizer.advance(2); SuffixMatch }
612 else { tokenizer.advance(1); Delim('$') }
613 },
614 b'\'' => consume_string(tokenizer, true),
615 b'(' => { tokenizer.advance(1); ParenthesisBlock },
616 b')' => { tokenizer.advance(1); CloseParenthesis },
617 b'*' => {
618 if tokenizer.starts_with(b"*=") { tokenizer.advance(2); SubstringMatch }
619 else { tokenizer.advance(1); Delim('*') }
620 },
621 b'+' => {
622 if (
623 tokenizer.has_at_least(1)
624 && tokenizer.byte_at(1).is_ascii_digit()
625 ) || (
626 tokenizer.has_at_least(2)
627 && tokenizer.byte_at(1) == b'.'
628 && tokenizer.byte_at(2).is_ascii_digit()
629 ) {
630 consume_numeric(tokenizer)
631 } else {
632 tokenizer.advance(1);
633 Delim('+')
634 }
635 },
636 b',' => { tokenizer.advance(1); Comma },
637 b'-' => {
638 if (
639 tokenizer.has_at_least(1)
640 && tokenizer.byte_at(1).is_ascii_digit()
641 ) || (
642 tokenizer.has_at_least(2)
643 && tokenizer.byte_at(1) == b'.'
644 && tokenizer.byte_at(2).is_ascii_digit()
645 ) {
646 consume_numeric(tokenizer)
647 } else if tokenizer.starts_with(b"-->") {
648 tokenizer.advance(3);
649 CDC
650 } else if is_ident_start(tokenizer) {
651 consume_ident_like(tokenizer)
652 } else {
653 tokenizer.advance(1);
654 Delim('-')
655 }
656 },
657 b'.' => {
658 if tokenizer.has_at_least(1)
659 && tokenizer.byte_at(1).is_ascii_digit() {
660 consume_numeric(tokenizer)
661 } else {
662 tokenizer.advance(1);
663 Delim('.')
664 }
665 }
666 b'/' => {
667 if tokenizer.starts_with(b"/*") {
668 Comment(consume_comment(tokenizer))
669 } else {
670 tokenizer.advance(1);
671 Delim('/')
672 }
673 }
674 b'0'..=b'9' => consume_numeric(tokenizer),
675 b':' => { tokenizer.advance(1); Colon },
676 b';' => { tokenizer.advance(1); Semicolon },
677 b'<' => {
678 if tokenizer.starts_with(b"<!--") {
679 tokenizer.advance(4);
680 CDO
681 } else {
682 tokenizer.advance(1);
683 Delim('<')
684 }
685 },
686 b'@' => {
687 tokenizer.advance(1);
688 if is_ident_start(tokenizer) { AtKeyword(consume_name(tokenizer)) }
689 else { Delim('@') }
690 },
691 b'a'..=b'z' | b'A'..=b'Z' | b'_' | b'\0' => consume_ident_like(tokenizer),
692 b'[' => { tokenizer.advance(1); SquareBracketBlock },
693 b'\\' => {
694 if !tokenizer.has_newline_at(1) { consume_ident_like(tokenizer) }
695 else { tokenizer.advance(1); Delim('\\') }
696 },
697 b']' => { tokenizer.advance(1); CloseSquareBracket },
698 b'^' => {
699 if tokenizer.starts_with(b"^=") { tokenizer.advance(2); PrefixMatch }
700 else { tokenizer.advance(1); Delim('^') }
701 },
702 b'{' => { tokenizer.advance(1); CurlyBracketBlock },
703 b'|' => {
704 if tokenizer.starts_with(b"|=") { tokenizer.advance(2); DashMatch }
705 else { tokenizer.advance(1); Delim('|') }
706 },
707 b'}' => { tokenizer.advance(1); CloseCurlyBracket },
708 b'~' => {
709 if tokenizer.starts_with(b"~=") { tokenizer.advance(2); IncludeMatch }
710 else { tokenizer.advance(1); Delim('~') }
711 },
712 _ => {
713 if !b.is_ascii() {
714 consume_ident_like(tokenizer)
715 } else {
716 tokenizer.advance(1);
717 Delim(b as char)
718 }
719 },
720 };
721 token
722}
723
724fn consume_whitespace<'a>(tokenizer: &mut Tokenizer<'a>, newline: bool) -> Token<'a> {
725 let start_position = tokenizer.position();
726 if newline {
727 tokenizer.consume_newline();
728 } else {
729 tokenizer.advance(1);
730 }
731 while !tokenizer.is_eof() {
732 let b = tokenizer.next_byte_unchecked();
733 match_byte! { b,
734 b' ' | b'\t' => {
735 tokenizer.advance(1);
736 }
737 b'\n' | b'\x0C' | b'\r' => {
738 tokenizer.consume_newline();
739 }
740 _ => {
741 break
742 }
743 }
744 }
745 WhiteSpace(tokenizer.slice_from(start_position))
746}
747
748fn check_for_source_map<'a>(tokenizer: &mut Tokenizer<'a>, contents: &'a str) {
751 let directive = "# sourceMappingURL=";
752 let directive_old = "@ sourceMappingURL=";
753
754 if contents.starts_with(directive) || contents.starts_with(directive_old) {
756 let contents = &contents[directive.len()..];
757 tokenizer.source_map_url = contents.split([' ', '\t', '\x0C', '\r', '\n']).next();
758 }
759
760 let directive = "# sourceURL=";
761 let directive_old = "@ sourceURL=";
762
763 if contents.starts_with(directive) || contents.starts_with(directive_old) {
765 let contents = &contents[directive.len()..];
766 tokenizer.source_url = contents.split([' ', '\t', '\x0C', '\r', '\n']).next()
767 }
768}
769
770fn consume_comment<'a>(tokenizer: &mut Tokenizer<'a>) -> &'a str {
771 tokenizer.advance(2); let start_position = tokenizer.position();
773 while !tokenizer.is_eof() {
774 match_byte! { tokenizer.next_byte_unchecked(),
775 b'*' => {
776 let end_position = tokenizer.position();
777 tokenizer.advance(1);
778 if tokenizer.next_byte() == Some(b'/') {
779 tokenizer.advance(1);
780 let contents = tokenizer.slice(start_position..end_position);
781 check_for_source_map(tokenizer, contents);
782 return contents
783 }
784 }
785 b'\n' | b'\x0C' | b'\r' => {
786 tokenizer.consume_newline();
787 }
788 b'\x80'..=b'\xBF' => { tokenizer.consume_continuation_byte(); }
789 b'\xF0'..=b'\xFF' => { tokenizer.consume_4byte_intro(); }
790 _ => {
791 tokenizer.advance(1);
793 }
794 }
795 }
796 let contents = tokenizer.slice_from(start_position);
797 check_for_source_map(tokenizer, contents);
798 contents
799}
800
801fn consume_string<'a>(tokenizer: &mut Tokenizer<'a>, single_quote: bool) -> Token<'a> {
802 match consume_quoted_string(tokenizer, single_quote) {
803 Ok(value) => QuotedString(value),
804 Err(value) => BadString(value),
805 }
806}
807
808fn consume_quoted_string<'a>(
810 tokenizer: &mut Tokenizer<'a>,
811 single_quote: bool,
812) -> Result<CowRcStr<'a>, CowRcStr<'a>> {
813 tokenizer.advance(1); let start_pos = tokenizer.position();
816 let mut string_bytes;
817 loop {
818 if tokenizer.is_eof() {
819 return Ok(tokenizer.slice_from(start_pos).into());
820 }
821 match_byte! { tokenizer.next_byte_unchecked(),
822 b'"' => {
823 if !single_quote {
824 let value = tokenizer.slice_from(start_pos);
825 tokenizer.advance(1);
826 return Ok(value.into())
827 }
828 tokenizer.advance(1);
829 }
830 b'\'' => {
831 if single_quote {
832 let value = tokenizer.slice_from(start_pos);
833 tokenizer.advance(1);
834 return Ok(value.into())
835 }
836 tokenizer.advance(1);
837 }
838 b'\\' | b'\0' => {
839 string_bytes = tokenizer.slice_from(start_pos).as_bytes().to_owned();
845 break
846 }
847 b'\n' | b'\r' | b'\x0C' => {
848 return Err(tokenizer.slice_from(start_pos).into())
849 },
850 b'\x80'..=b'\xBF' => { tokenizer.consume_continuation_byte(); }
851 b'\xF0'..=b'\xFF' => { tokenizer.consume_4byte_intro(); }
852 _ => {
853 tokenizer.advance(1);
855 }
856 }
857 }
858
859 while !tokenizer.is_eof() {
860 let b = tokenizer.next_byte_unchecked();
861 match_byte! { b,
862 b'\n' | b'\r' | b'\x0C' => {
863 return Err(
864 unsafe {
866 from_utf8_release_unchecked(string_bytes)
867 }.into()
868 );
869 }
870 b'"' => {
871 tokenizer.advance(1);
872 if !single_quote {
873 break;
874 }
875 }
876 b'\'' => {
877 tokenizer.advance(1);
878 if single_quote {
879 break;
880 }
881 }
882 b'\\' => {
883 tokenizer.advance(1);
884 if !tokenizer.is_eof() {
885 match tokenizer.next_byte_unchecked() {
886 b'\n' | b'\x0C' | b'\r' => {
888 tokenizer.consume_newline();
889 }
890 _ => consume_escape_and_write(tokenizer, &mut string_bytes)
892 }
893 }
894 continue;
896 }
897 b'\0' => {
898 tokenizer.advance(1);
899 string_bytes.extend("\u{FFFD}".as_bytes());
900 continue;
901 }
902 b'\x80'..=b'\xBF' => { tokenizer.consume_continuation_byte(); }
903 b'\xF0'..=b'\xFF' => { tokenizer.consume_4byte_intro(); }
904 _ => {
905 tokenizer.advance(1);
907 },
908 }
909
910 string_bytes.push(b);
913 }
914
915 Ok(
916 unsafe { from_utf8_release_unchecked(string_bytes) }.into(),
918 )
919}
920
921#[inline]
922fn is_ident_start(tokenizer: &Tokenizer) -> bool {
923 !tokenizer.is_eof()
924 && match_byte! { tokenizer.next_byte_unchecked(),
925 b'a'..=b'z' | b'A'..=b'Z' | b'_' | b'\0' => true,
926 b'-' => {
927 tokenizer.has_at_least(1) && match_byte! { tokenizer.byte_at(1),
928 b'a'..=b'z' | b'A'..=b'Z' | b'-' | b'_' | b'\0' => {
929 true
930 }
931 b'\\' => !tokenizer.has_newline_at(1),
932 b => !b.is_ascii(),
933 }
934 },
935 b'\\' => !tokenizer.has_newline_at(1),
936 b => !b.is_ascii(),
937 }
938}
939
940fn consume_ident_like<'a>(tokenizer: &mut Tokenizer<'a>) -> Token<'a> {
941 let value = consume_name(tokenizer);
942 if !tokenizer.is_eof() && tokenizer.next_byte_unchecked() == b'(' {
943 tokenizer.advance(1);
944 if value.eq_ignore_ascii_case("url") {
945 consume_unquoted_url(tokenizer).unwrap_or(Function(value))
946 } else {
947 tokenizer.see_function(&value);
948 Function(value)
949 }
950 } else {
951 Ident(value)
952 }
953}
954
955fn consume_name<'a>(tokenizer: &mut Tokenizer<'a>) -> CowRcStr<'a> {
956 static IS_SIMPLE_NAME_BYTE: [bool; 256] = {
958 let mut table = [false; 256];
959 let mut i = 0;
960 while i < 256 {
961 table[i as usize] = matches!(i as u8, b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'_' | b'-' | b'\xC0'..=b'\xEF');
962 i += 1;
963 }
964 table
965 };
966
967 let start_pos = tokenizer.position();
969 let mut value_bytes;
970 loop {
971 tokenizer.advance_while(|b| IS_SIMPLE_NAME_BYTE[b as usize]);
972 if tokenizer.is_eof() {
973 return tokenizer.slice_from(start_pos).into();
974 }
975 match_byte! { tokenizer.next_byte_unchecked(),
976 b'\\' | b'\0' => {
977 value_bytes = tokenizer.slice_from(start_pos).as_bytes().to_owned();
983 break
984 }
985 b'\x80'..=b'\xBF' => { tokenizer.consume_continuation_byte(); }
986 b'\xF0'..=b'\xFF' => { tokenizer.consume_4byte_intro(); }
987 _b => {
988 return tokenizer.slice_from(start_pos).into();
989 }
990 }
991 }
992
993 loop {
994 value_bytes.extend(tokenizer.advance_while(|b| IS_SIMPLE_NAME_BYTE[b as usize]));
995 if tokenizer.is_eof() {
996 break;
997 }
998 let b = tokenizer.next_byte_unchecked();
999 match_byte! { b,
1000 b'\\' => {
1001 if tokenizer.has_newline_at(1) { break }
1002 tokenizer.advance(1);
1003 consume_escape_and_write(tokenizer, &mut value_bytes)
1005 }
1006 b'\0' => {
1007 tokenizer.advance(1);
1008 value_bytes.extend("\u{FFFD}".as_bytes());
1009 },
1010 b'\x80'..=b'\xBF' => {
1011 tokenizer.consume_continuation_byte();
1012 value_bytes.push(b)
1013 }
1014 b'\xF0'..=b'\xFF' => {
1015 tokenizer.consume_4byte_intro();
1016 value_bytes.push(b)
1017 }
1018 _ => {
1019 break;
1021 }
1022 }
1023 }
1024 unsafe { from_utf8_release_unchecked(value_bytes) }.into()
1026}
1027
1028fn byte_to_hex_digit(b: u8) -> Option<u32> {
1029 Some(match_byte! { b,
1030 b'0' ..= b'9' => b - b'0',
1031 b'a' ..= b'f' => b - b'a' + 10,
1032 b'A' ..= b'F' => b - b'A' + 10,
1033 _ => {
1034 return None
1035 }
1036 } as u32)
1037}
1038
1039fn byte_to_decimal_digit(b: u8) -> Option<u32> {
1040 if b.is_ascii_digit() {
1041 Some((b - b'0') as u32)
1042 } else {
1043 None
1044 }
1045}
1046
1047fn consume_numeric<'a>(tokenizer: &mut Tokenizer<'a>) -> Token<'a> {
1048 let (has_sign, sign) = match tokenizer.next_byte_unchecked() {
1055 b'-' => (true, -1.),
1056 b'+' => (true, 1.),
1057 _ => (false, 1.),
1058 };
1059 if has_sign {
1060 tokenizer.advance(1);
1061 }
1062
1063 let mut integral_part: f64 = 0.;
1064 while let Some(digit) = byte_to_decimal_digit(tokenizer.next_byte_unchecked()) {
1065 integral_part = integral_part * 10. + digit as f64;
1066 tokenizer.advance(1);
1067 if tokenizer.is_eof() {
1068 break;
1069 }
1070 }
1071
1072 let mut is_integer = true;
1073
1074 let mut fractional_part: f64 = 0.;
1075 if tokenizer.has_at_least(1)
1076 && tokenizer.next_byte_unchecked() == b'.'
1077 && tokenizer.byte_at(1).is_ascii_digit()
1078 {
1079 is_integer = false;
1080 tokenizer.advance(1); let mut factor = 0.1;
1082 while let Some(digit) = byte_to_decimal_digit(tokenizer.next_byte_unchecked()) {
1083 fractional_part += digit as f64 * factor;
1084 factor *= 0.1;
1085 tokenizer.advance(1);
1086 if tokenizer.is_eof() {
1087 break;
1088 }
1089 }
1090 }
1091
1092 let mut value = sign * (integral_part + fractional_part);
1093
1094 if tokenizer.has_at_least(1)
1095 && matches!(tokenizer.next_byte_unchecked(), b'e' | b'E')
1096 && (tokenizer.byte_at(1).is_ascii_digit()
1097 || (tokenizer.has_at_least(2)
1098 && matches!(tokenizer.byte_at(1), b'+' | b'-')
1099 && tokenizer.byte_at(2).is_ascii_digit()))
1100 {
1101 is_integer = false;
1102 tokenizer.advance(1);
1103 let (has_sign, sign) = match tokenizer.next_byte_unchecked() {
1104 b'-' => (true, -1.),
1105 b'+' => (true, 1.),
1106 _ => (false, 1.),
1107 };
1108 if has_sign {
1109 tokenizer.advance(1);
1110 }
1111 let mut exponent: f64 = 0.;
1112 while let Some(digit) = byte_to_decimal_digit(tokenizer.next_byte_unchecked()) {
1113 exponent = exponent * 10. + digit as f64;
1114 tokenizer.advance(1);
1115 if tokenizer.is_eof() {
1116 break;
1117 }
1118 }
1119 value *= f64::powf(10., sign * exponent);
1120 }
1121
1122 let int_value = if is_integer {
1123 Some(if value >= i32::MAX as f64 {
1124 i32::MAX
1125 } else if value <= i32::MIN as f64 {
1126 i32::MIN
1127 } else {
1128 value as i32
1129 })
1130 } else {
1131 None
1132 };
1133
1134 if !tokenizer.is_eof() && tokenizer.next_byte_unchecked() == b'%' {
1135 tokenizer.advance(1);
1136 return Percentage {
1137 unit_value: (value / 100.) as f32,
1138 int_value,
1139 has_sign,
1140 };
1141 }
1142 let value = value as f32;
1143 if is_ident_start(tokenizer) {
1144 let unit = consume_name(tokenizer);
1145 Dimension {
1146 value,
1147 int_value,
1148 has_sign,
1149 unit,
1150 }
1151 } else {
1152 Number {
1153 value,
1154 int_value,
1155 has_sign,
1156 }
1157 }
1158}
1159
1160#[inline]
1161unsafe fn from_utf8_release_unchecked(string_bytes: Vec<u8>) -> String {
1162 unsafe {
1163 if cfg!(debug_assertions) {
1164 String::from_utf8(string_bytes).unwrap()
1165 } else {
1166 String::from_utf8_unchecked(string_bytes)
1167 }
1168 }
1169}
1170
1171fn consume_unquoted_url<'a>(tokenizer: &mut Tokenizer<'a>) -> Result<Token<'a>, ()> {
1172 let start_position = tokenizer.position;
1174 let from_start = &tokenizer.input[tokenizer.position..];
1175 let mut newlines = 0;
1176 let mut last_newline = 0;
1177 let mut found_printable_char = false;
1178 let mut iter = from_start.bytes().enumerate();
1179 loop {
1180 let (offset, b) = match iter.next() {
1181 Some(item) => item,
1182 None => {
1183 tokenizer.position = tokenizer.input.len();
1184 break;
1185 }
1186 };
1187 match_byte! { b,
1188 b' ' | b'\t' => {},
1189 b'\n' | b'\x0C' => {
1190 newlines += 1;
1191 last_newline = offset;
1192 }
1193 b'\r' => {
1194 if from_start.as_bytes().get(offset + 1) != Some(&b'\n') {
1195 newlines += 1;
1196 last_newline = offset;
1197 }
1198 }
1199 b'"' | b'\'' => return Err(()), b')' => {
1201 tokenizer.position += offset + 1;
1204 break
1205 }
1206 _ => {
1207 tokenizer.position += offset;
1210 found_printable_char = true;
1211 break
1212 }
1213 }
1214 }
1215
1216 if newlines > 0 {
1217 tokenizer.current_line_number += newlines;
1218 tokenizer.current_line_start_position = start_position + last_newline + 1;
1221 }
1222
1223 if found_printable_char {
1224 return Ok(consume_unquoted_url_internal(tokenizer));
1227 } else {
1228 return Ok(UnquotedUrl("".into()));
1229 }
1230
1231 fn consume_unquoted_url_internal<'a>(tokenizer: &mut Tokenizer<'a>) -> Token<'a> {
1232 let start_pos = tokenizer.position();
1234 let mut string_bytes: Vec<u8>;
1235 loop {
1236 if tokenizer.is_eof() {
1237 return UnquotedUrl(tokenizer.slice_from(start_pos).into());
1238 }
1239 match_byte! { tokenizer.next_byte_unchecked(),
1240 b' ' | b'\t' | b'\n' | b'\r' | b'\x0C' => {
1241 let value = tokenizer.slice_from(start_pos);
1242 return consume_url_end(tokenizer, start_pos, value.into())
1243 }
1244 b')' => {
1245 let value = tokenizer.slice_from(start_pos);
1246 tokenizer.advance(1);
1247 return UnquotedUrl(value.into())
1248 }
1249 b'\x01'..=b'\x08' | b'\x0B' | b'\x0E'..=b'\x1F' | b'\x7F' | b'"' | b'\'' | b'(' => {
1251 tokenizer.advance(1);
1252 return consume_bad_url(tokenizer, start_pos)
1253 },
1254 b'\\' | b'\0' => {
1255 string_bytes = tokenizer.slice_from(start_pos).as_bytes().to_owned();
1261 break
1262 }
1263 b'\x80'..=b'\xBF' => { tokenizer.consume_continuation_byte(); }
1264 b'\xF0'..=b'\xFF' => { tokenizer.consume_4byte_intro(); }
1265 _ => {
1266 tokenizer.advance(1);
1268 }
1269 }
1270 }
1271 while !tokenizer.is_eof() {
1272 let b = tokenizer.next_byte_unchecked();
1273 match_byte! { b,
1274 b' ' | b'\t' | b'\n' | b'\r' | b'\x0C' => {
1275 let string = unsafe { from_utf8_release_unchecked(string_bytes) }.into();
1277 return consume_url_end(tokenizer, start_pos, string)
1278 }
1279 b')' => {
1280 tokenizer.advance(1);
1281 break;
1282 }
1283 b'\x01'..=b'\x08' | b'\x0B' | b'\x0E'..=b'\x1F' | b'\x7F' | b'"' | b'\'' | b'(' => {
1285 tokenizer.advance(1);
1286 return consume_bad_url(tokenizer, start_pos);
1287 }
1288 b'\\' => {
1289 tokenizer.advance(1);
1290 if tokenizer.has_newline_at(0) {
1291 return consume_bad_url(tokenizer, start_pos)
1292 }
1293
1294 consume_escape_and_write(tokenizer, &mut string_bytes)
1296 },
1297 b'\0' => {
1298 tokenizer.advance(1);
1299 string_bytes.extend("\u{FFFD}".as_bytes());
1300 }
1301 b'\x80'..=b'\xBF' => {
1302 tokenizer.consume_continuation_byte();
1305 string_bytes.push(b);
1306 }
1307 b'\xF0'..=b'\xFF' => {
1308 tokenizer.consume_4byte_intro();
1311 string_bytes.push(b);
1312 }
1313 b => {
1316 tokenizer.advance(1);
1318 string_bytes.push(b)
1319 }
1320 }
1321 }
1322 UnquotedUrl(
1323 unsafe { from_utf8_release_unchecked(string_bytes) }.into(),
1325 )
1326 }
1327
1328 fn consume_url_end<'a>(
1329 tokenizer: &mut Tokenizer<'a>,
1330 start_pos: SourcePosition,
1331 string: CowRcStr<'a>,
1332 ) -> Token<'a> {
1333 while !tokenizer.is_eof() {
1334 match_byte! { tokenizer.next_byte_unchecked(),
1335 b')' => {
1336 tokenizer.advance(1);
1337 break
1338 }
1339 b' ' | b'\t' => { tokenizer.advance(1); }
1340 b'\n' | b'\x0C' | b'\r' => {
1341 tokenizer.consume_newline();
1342 }
1343 b => {
1344 tokenizer.consume_known_byte(b);
1345 return consume_bad_url(tokenizer, start_pos);
1346 }
1347 }
1348 }
1349 UnquotedUrl(string)
1350 }
1351
1352 fn consume_bad_url<'a>(tokenizer: &mut Tokenizer<'a>, start_pos: SourcePosition) -> Token<'a> {
1353 while !tokenizer.is_eof() {
1355 match_byte! { tokenizer.next_byte_unchecked(),
1356 b')' => {
1357 let contents = tokenizer.slice_from(start_pos).into();
1358 tokenizer.advance(1);
1359 return BadUrl(contents)
1360 }
1361 b'\\' => {
1362 tokenizer.advance(1);
1363 if matches!(tokenizer.next_byte(), Some(b')') | Some(b'\\')) {
1364 tokenizer.advance(1); }
1366 }
1367 b'\n' | b'\x0C' | b'\r' => {
1368 tokenizer.consume_newline();
1369 }
1370 b => {
1371 tokenizer.consume_known_byte(b);
1372 }
1373 }
1374 }
1375 BadUrl(tokenizer.slice_from(start_pos).into())
1376 }
1377}
1378
1379fn consume_hex_digits(tokenizer: &mut Tokenizer<'_>) -> (u32, u32) {
1381 let mut value = 0;
1382 let mut digits = 0;
1383 while digits < 6 && !tokenizer.is_eof() {
1384 match byte_to_hex_digit(tokenizer.next_byte_unchecked()) {
1385 Some(digit) => {
1386 value = value * 16 + digit;
1387 digits += 1;
1388 tokenizer.advance(1);
1389 }
1390 None => break,
1391 }
1392 }
1393 (value, digits)
1394}
1395
1396fn consume_escape_and_write(tokenizer: &mut Tokenizer, bytes: &mut Vec<u8>) {
1399 bytes.extend(
1400 consume_escape(tokenizer)
1401 .encode_utf8(&mut [0; 4])
1402 .as_bytes(),
1403 )
1404}
1405
1406fn consume_escape(tokenizer: &mut Tokenizer) -> char {
1410 if tokenizer.is_eof() {
1411 return '\u{FFFD}';
1412 } match_byte! { tokenizer.next_byte_unchecked(),
1414 b'0'..=b'9' | b'A'..=b'F' | b'a'..=b'f' => {
1415 let (c, _) = consume_hex_digits(tokenizer);
1416 if !tokenizer.is_eof() {
1417 match_byte! { tokenizer.next_byte_unchecked(),
1418 b' ' | b'\t' => {
1419 tokenizer.advance(1)
1420 }
1421 b'\n' | b'\x0C' | b'\r' => {
1422 tokenizer.consume_newline();
1423 }
1424 _ => {}
1425 }
1426 }
1427 static REPLACEMENT_CHAR: char = '\u{FFFD}';
1428 if c != 0 {
1429 let c = char::from_u32(c);
1430 c.unwrap_or(REPLACEMENT_CHAR)
1431 } else {
1432 REPLACEMENT_CHAR
1433 }
1434 },
1435 b'\0' => {
1436 tokenizer.advance(1);
1437 '\u{FFFD}'
1438 }
1439 _ => tokenizer.consume_char(),
1440 }
1441}