Lex a string literal.
(&mut self, quote: char)
| 884 | |
| 885 | /// Lex a string literal. |
| 886 | fn lex_string(&mut self, quote: char) -> TokenKind { |
| 887 | #[cfg(debug_assertions)] |
| 888 | debug_assert_eq!(self.cursor.previous(), quote); |
| 889 | |
| 890 | if quote == '"' { |
| 891 | self.current_flags |= TokenFlags::DOUBLE_QUOTES; |
| 892 | } |
| 893 | |
| 894 | // If the next two characters are also the quote character, then we have a triple-quoted |
| 895 | // string; consume those two characters and ensure that we require a triple-quote to close |
| 896 | if self.cursor.eat_char2(quote, quote) { |
| 897 | self.current_flags |= TokenFlags::TRIPLE_QUOTED_STRING; |
| 898 | } |
| 899 | |
| 900 | let value_start = self.offset(); |
| 901 | |
| 902 | let quote_byte = u8::try_from(quote).expect("char that fits in u8"); |
| 903 | let value_end = if self.current_flags.is_triple_quoted() { |
| 904 | // For triple-quoted strings, scan until we find the closing quote (ignoring escaped |
| 905 | // quotes) or the end of the file. |
| 906 | loop { |
| 907 | let Some(index) = memchr::memchr(quote_byte, self.cursor.rest().as_bytes()) else { |
| 908 | self.cursor.skip_to_end(); |
| 909 | |
| 910 | return self.push_error(LexicalError::new( |
| 911 | LexicalErrorType::UnclosedStringError, |
| 912 | self.token_range(), |
| 913 | )); |
| 914 | }; |
| 915 | |
| 916 | // Rare case: if there are an odd number of backslashes before the quote, then |
| 917 | // the quote is escaped and we should continue scanning. |
| 918 | let num_backslashes = self.cursor.rest().as_bytes()[..index] |
| 919 | .iter() |
| 920 | .rev() |
| 921 | .take_while(|&&c| c == b'\\') |
| 922 | .count(); |
| 923 | |
| 924 | // Advance the cursor past the quote and continue scanning. |
| 925 | self.cursor.skip_bytes(index + 1); |
| 926 | |
| 927 | // If the character is escaped, continue scanning. |
| 928 | if num_backslashes % 2 == 1 { |
| 929 | continue; |
| 930 | } |
| 931 | |
| 932 | // Otherwise, if it's followed by two more quotes, then we're done. |
| 933 | if self.cursor.eat_char2(quote, quote) { |
| 934 | break self.offset() - TextSize::new(3); |
| 935 | } |
| 936 | } |
| 937 | } else { |
| 938 | // For non-triple-quoted strings, scan until we find the closing quote, but end early |
| 939 | // if we encounter a newline or the end of the file. |
| 940 | loop { |
| 941 | let Some(index) = |
| 942 | memchr::memchr3(quote_byte, b'\r', b'\n', self.cursor.rest().as_bytes()) |
| 943 | else { |
no test coverage detected