1use std::cmp::Ordering;
10
11use unicode_ident::{is_xid_continue, is_xid_start};
12
13use ruff_python_ast::StringFlags;
14use ruff_python_ast::str_prefix::{AnyStringPrefix, StringLiteralPrefix};
15use ruff_python_ast::token::{TokenFlags, TokenKind};
16use ruff_python_trivia::is_python_whitespace;
17use ruff_text_size::{TextLen, TextRange, TextSize};
18
19use crate::Mode;
20use crate::error::{
21 InterpolatedStringErrorType, LexicalError, LexicalErrorType, NumberLiteralKind,
22};
23use crate::lexer::cursor::{Cursor, EOF_CHAR};
24use crate::lexer::indentation::{
25 Indentation, IndentationError, Indentations, IndentationsCheckpoint,
26};
27use crate::lexer::interpolated_string::{
28 InterpolatedStringContext, InterpolatedStrings, InterpolatedStringsCheckpoint,
29};
30use crate::string::InterpolatedStringKind;
31
32mod cursor;
33mod indentation;
34mod interpolated_string;
35
36const BOM: char = '\u{feff}';
37
38#[derive(Debug)]
40pub struct Lexer<'src> {
41 source: &'src str,
43
44 cursor: Cursor<'src>,
46
47 current_kind: TokenKind,
49
50 current_range: TextRange,
52
53 current_flags: TokenFlags,
55
56 state: State,
58
59 nesting: u32,
62
63 brackets: Vec<OpenBracket>,
67
68 indentations: Indentations,
70 pending_indentation: Option<Indentation>,
71
72 mode: Mode,
74
75 interpolated_strings: InterpolatedStrings,
77
78 errors: Vec<LexicalError>,
80}
81
82impl<'src> Lexer<'src> {
83 pub(crate) fn new(source: &'src str, mode: Mode, start_offset: TextSize) -> Self {
89 assert!(
90 u32::try_from(source.len()).is_ok(),
91 "Lexer only supports files with a size up to 4GB"
92 );
93
94 let (state, nesting) = if mode == Mode::ParenthesizedExpression {
95 (State::Other, 1)
96 } else {
97 (State::AfterNewline, 0)
98 };
99
100 let mut lexer = Lexer {
101 source,
102 cursor: Cursor::new(source),
103 state,
104 current_kind: TokenKind::EndOfFile,
105 current_range: TextRange::empty(start_offset),
106 current_flags: TokenFlags::empty(),
107 nesting,
108 brackets: Vec::new(),
109 indentations: Indentations::default(),
110 pending_indentation: None,
111 mode,
112 interpolated_strings: InterpolatedStrings::default(),
113 errors: Vec::new(),
114 };
115
116 if start_offset == TextSize::new(0) {
117 lexer.cursor.eat_char(BOM);
119 } else {
120 lexer.cursor.skip_bytes(start_offset.to_usize());
121 }
122
123 lexer
124 }
125
126 pub(crate) const fn current_kind(&self) -> TokenKind {
128 self.current_kind
129 }
130
131 #[doc(hidden)]
133 pub const fn current_range(&self) -> TextRange {
134 self.current_range
135 }
136
137 #[doc(hidden)]
139 pub const fn current_flags(&self) -> TokenFlags {
140 self.current_flags
141 }
142
143 fn push_error(&mut self, error: LexicalError) -> TokenKind {
146 self.current_range = error.location();
147 self.errors.push(error);
148 TokenKind::Unknown
149 }
150
151 fn push_indentation_error(&mut self, error: IndentationError) -> TokenKind {
155 let offset = self.offset().to_usize();
156 let line_start = self.source[..offset]
157 .rfind(['\n', '\r'])
158 .map_or(0, |index| index + 1);
159 let line_end = self.source[offset..]
160 .find(['\n', '\r'])
161 .map_or(self.source.len(), |index| offset + index);
162 let (error, position) = match error {
163 IndentationError::UnmatchedDedent => (LexicalErrorType::IndentationError, line_end),
164 IndentationError::InconsistentTabs => (LexicalErrorType::TabError, line_start),
165 IndentationError::TooDeep => (LexicalErrorType::TooDeepIndentation, line_start),
166 };
167 let range = TextRange::empty(TextSize::try_from(position).unwrap());
168 self.push_error(LexicalError::new(error, range));
169 self.current_range = self.token_range();
171 TokenKind::Unknown
172 }
173
174 pub fn next_token(&mut self) -> TokenKind {
176 self.current_flags = TokenFlags::empty();
178 self.current_kind = self.lex_token();
179 if !matches!(self.current_kind, TokenKind::Unknown) {
181 self.current_range = self.token_range();
182 }
183 self.current_kind
184 }
185
186 fn lex_token(&mut self) -> TokenKind {
187 if let Some(interpolated_string) = self.interpolated_strings.current() {
188 if !interpolated_string.is_in_interpolation(self.nesting) {
189 let nesting = interpolated_string.nesting();
190 self.cursor.start_token();
191 if let Some(token) = self.lex_interpolated_string_middle_or_end() {
192 if token.is_interpolated_string_end() {
193 self.reset_nesting(nesting);
195 self.interpolated_strings.pop();
196 }
197 return token;
198 }
199 }
200 }
201 else if let Some(indentation) = self.pending_indentation {
205 self.pending_indentation = None;
206 self.cursor.start_token();
207 match self.indentations.current().try_compare(indentation) {
208 Ok(Ordering::Greater) => {
209 self.pending_indentation = Some(indentation);
210 if let Err(error) = self.indentations.dedent_one(indentation) {
211 return self.push_indentation_error(error);
212 }
213 return TokenKind::Dedent;
214 }
215 Ok(_) => {}
216 Err(error) => return self.push_indentation_error(error),
217 }
218 }
219
220 if self.state.is_after_newline() {
221 self.cursor.start_token();
223 if let Some(indentation) = self.eat_indentation() {
224 return indentation;
225 }
226 } else {
227 if let Err(error) = self.skip_whitespace() {
228 return self.push_error(error);
229 }
230 }
231
232 self.cursor.start_token();
234
235 if let Some(c) = self.cursor.bump() {
236 if c.is_ascii() {
237 self.consume_ascii_character(c)
238 } else if is_unicode_identifier_start(c) {
239 let identifier = self.lex_identifier(c);
240 self.state = State::Other;
241
242 identifier
243 } else {
244 self.push_error(LexicalError::new(
245 LexicalErrorType::UnrecognizedToken { tok: c },
246 self.token_range(),
247 ))
248 }
249 } else {
250 self.consume_end()
253 }
254 }
255
256 fn eat_indentation(&mut self) -> Option<TokenKind> {
257 let mut indentation = Indentation::root();
258
259 loop {
260 match self.cursor.first() {
261 ' ' => {
262 self.cursor.bump();
263 indentation = indentation.add_space();
264 }
265 '\t' => {
266 self.cursor.bump();
267 indentation = indentation.add_tab();
268 }
269 '\\' => {
270 self.cursor.bump();
271 if self.cursor.eat_char('\r') {
272 self.cursor.eat_char('\n');
273 } else if !self.cursor.eat_char('\n') {
274 return Some(self.push_error(LexicalError::new(
275 LexicalErrorType::LineContinuationError,
276 TextRange::empty(self.offset()),
277 )));
278 }
279 if self.cursor.is_eof() {
280 let error = self.eof_error(self.token_range());
281 return Some(self.push_error(error));
282 }
283 if indentation != Indentation::root() {
310 self.cursor.eat_while(is_python_whitespace);
311 }
312 }
313 '\x0C' => {
315 self.cursor.bump();
316 indentation = Indentation::root();
317 }
318 _ => break,
319 }
320 }
321
322 if !matches!(self.cursor.first(), '\n' | '\r' | '#' | EOF_CHAR) {
324 self.state = State::NonEmptyLogicalLine;
325
326 return self.handle_indentation(indentation);
328 }
329
330 None
331 }
332
333 fn handle_indentation(&mut self, indentation: Indentation) -> Option<TokenKind> {
334 match self.indentations.current().try_compare(indentation) {
335 Ok(Ordering::Greater) => {
337 self.pending_indentation = Some(indentation);
338
339 if let Err(error) = self.indentations.dedent_one(indentation) {
340 return Some(self.push_indentation_error(error));
341 }
342
343 self.cursor.start_token();
357
358 Some(TokenKind::Dedent)
359 }
360
361 Ok(Ordering::Equal) => None,
362
363 Ok(Ordering::Less) => {
365 if let Err(error) = self.indentations.indent(indentation) {
366 return Some(self.push_indentation_error(error));
367 }
368 Some(TokenKind::Indent)
369 }
370 Err(error) => Some(self.push_indentation_error(error)),
371 }
372 }
373
374 fn skip_whitespace(&mut self) -> Result<(), LexicalError> {
375 let whitespace_start = if matches!(self.cursor.first(), ' ' | '\t' | '\\' | '\x0C') {
376 self.offset()
377 } else {
378 return Ok(());
379 };
380
381 loop {
382 match self.cursor.first() {
383 ' ' => {
384 self.cursor.bump();
385 }
386 '\t' => {
387 self.cursor.bump();
388 }
389 '\\' => {
390 self.cursor.bump();
391 if self.cursor.eat_char('\r') {
392 self.cursor.eat_char('\n');
393 } else if !self.cursor.eat_char('\n') {
394 return Err(LexicalError::new(
395 LexicalErrorType::LineContinuationError,
396 TextRange::empty(self.offset()),
397 ));
398 }
399 if self.cursor.is_eof() {
400 return Err(self.eof_error(TextRange::new(whitespace_start, self.offset())));
401 }
402 }
403 '\x0C' => {
405 self.cursor.bump();
406 }
407 _ => break,
408 }
409 }
410
411 Ok(())
412 }
413
414 fn consume_ascii_character(&mut self, c: char) -> TokenKind {
416 let token = match c {
417 c if is_ascii_identifier_start(c) => self.lex_identifier(c),
418 '0'..='9' => self.lex_number(c),
419 '#' => return self.lex_comment(),
420 '\'' | '"' => self.lex_string(c),
421 '=' => {
422 if self.cursor.eat_char('=') {
423 TokenKind::EqEqual
424 } else {
425 self.state = State::AfterEqual;
426 return TokenKind::Equal;
427 }
428 }
429 '+' => {
430 if self.cursor.eat_char('=') {
431 TokenKind::PlusEqual
432 } else {
433 TokenKind::Plus
434 }
435 }
436 '*' => {
437 if self.cursor.eat_char('=') {
438 TokenKind::StarEqual
439 } else if self.cursor.eat_char('*') {
440 if self.cursor.eat_char('=') {
441 TokenKind::DoubleStarEqual
442 } else {
443 TokenKind::DoubleStar
444 }
445 } else {
446 TokenKind::Star
447 }
448 }
449
450 '%' | '!'
451 if self.mode == Mode::Ipython
452 && self.state.is_after_equal()
453 && self.nesting == 0 =>
454 {
455 self.lex_ipython_escape_command()
456 }
457
458 '%' | '!' | '?' | '/' | ';' | ','
459 if self.mode == Mode::Ipython && self.state.is_new_logical_line() =>
460 {
461 self.lex_ipython_escape_command()
462 }
463
464 '?' if self.mode == Mode::Ipython => TokenKind::Question,
465
466 '/' => {
467 if self.cursor.eat_char('=') {
468 TokenKind::SlashEqual
469 } else if self.cursor.eat_char('/') {
470 if self.cursor.eat_char('=') {
471 TokenKind::DoubleSlashEqual
472 } else {
473 TokenKind::DoubleSlash
474 }
475 } else {
476 TokenKind::Slash
477 }
478 }
479 '%' => {
480 if self.cursor.eat_char('=') {
481 TokenKind::PercentEqual
482 } else {
483 TokenKind::Percent
484 }
485 }
486 '|' => {
487 if self.cursor.eat_char('=') {
488 TokenKind::VbarEqual
489 } else {
490 TokenKind::Vbar
491 }
492 }
493 '^' => {
494 if self.cursor.eat_char('=') {
495 TokenKind::CircumflexEqual
496 } else {
497 TokenKind::CircumFlex
498 }
499 }
500 '&' => {
501 if self.cursor.eat_char('=') {
502 TokenKind::AmperEqual
503 } else {
504 TokenKind::Amper
505 }
506 }
507 '-' => {
508 if self.cursor.eat_char('=') {
509 TokenKind::MinusEqual
510 } else if self.cursor.eat_char('>') {
511 TokenKind::Rarrow
512 } else {
513 TokenKind::Minus
514 }
515 }
516 '@' => {
517 if self.cursor.eat_char('=') {
518 TokenKind::AtEqual
519 } else {
520 TokenKind::At
521 }
522 }
523 '!' => {
524 if self.cursor.eat_char('=') {
525 TokenKind::NotEqual
526 } else {
527 TokenKind::Exclamation
528 }
529 }
530 '~' => TokenKind::Tilde,
531 '(' => {
532 self.open_bracket('(');
533 TokenKind::Lpar
534 }
535 ')' => {
536 self.close_bracket(')');
537 TokenKind::Rpar
538 }
539 '[' => {
540 self.open_bracket('[');
541 TokenKind::Lsqb
542 }
543 ']' => {
544 self.close_bracket(']');
545 TokenKind::Rsqb
546 }
547 '{' => {
548 self.open_bracket('{');
549 TokenKind::Lbrace
550 }
551 '}' => {
552 if let Some(interpolated_string) = self.interpolated_strings.current_mut() {
553 if interpolated_string.nesting() == self.nesting {
554 let error_type = LexicalErrorType::from_interpolated_string_error(
555 InterpolatedStringErrorType::SingleRbrace,
556 interpolated_string.kind(),
557 );
558 return self.push_error(LexicalError::new(error_type, self.token_range()));
559 }
560 interpolated_string.try_end_format_spec(self.nesting);
561 }
562 self.close_bracket('}');
563 TokenKind::Rbrace
564 }
565 ':' => {
566 if self
567 .interpolated_strings
568 .current_mut()
569 .is_some_and(|interpolated_string| {
570 interpolated_string.try_start_format_spec(self.nesting)
571 })
572 {
573 TokenKind::Colon
574 } else if self.cursor.eat_char('=') {
575 TokenKind::ColonEqual
576 } else {
577 TokenKind::Colon
578 }
579 }
580 ';' => TokenKind::Semi,
581 '<' => {
582 if self.cursor.eat_char('<') {
583 if self.cursor.eat_char('=') {
584 TokenKind::LeftShiftEqual
585 } else {
586 TokenKind::LeftShift
587 }
588 } else if self.cursor.eat_char('=') {
589 TokenKind::LessEqual
590 } else {
591 TokenKind::Less
592 }
593 }
594 '>' => {
595 if self.cursor.eat_char('>') {
596 if self.cursor.eat_char('=') {
597 TokenKind::RightShiftEqual
598 } else {
599 TokenKind::RightShift
600 }
601 } else if self.cursor.eat_char('=') {
602 TokenKind::GreaterEqual
603 } else {
604 TokenKind::Greater
605 }
606 }
607 ',' => TokenKind::Comma,
608 '.' => {
609 if self.cursor.first().is_ascii_digit() {
610 self.lex_decimal_number('.')
611 } else if self.cursor.eat_char2('.', '.') {
612 TokenKind::Ellipsis
613 } else {
614 TokenKind::Dot
615 }
616 }
617 '\n' => {
618 return if self.nesting == 0 && !self.state.is_new_logical_line() {
619 self.state = State::AfterNewline;
620 TokenKind::Newline
621 } else {
622 if let Some(interpolated_string) = self.interpolated_strings.current_mut() {
623 interpolated_string.try_end_format_spec(self.nesting);
624 }
625 TokenKind::NonLogicalNewline
626 };
627 }
628 '\r' => {
629 self.cursor.eat_char('\n');
630
631 return if self.nesting == 0 && !self.state.is_new_logical_line() {
632 self.state = State::AfterNewline;
633 TokenKind::Newline
634 } else {
635 if let Some(interpolated_string) = self.interpolated_strings.current_mut() {
636 interpolated_string.try_end_format_spec(self.nesting);
637 }
638 TokenKind::NonLogicalNewline
639 };
640 }
641
642 _ => {
643 self.state = State::Other;
644
645 return self.push_error(LexicalError::new(
646 LexicalErrorType::UnrecognizedToken { tok: c },
647 self.token_range(),
648 ));
649 }
650 };
651
652 self.state = State::Other;
653
654 token
655 }
656
657 fn lex_identifier(&mut self, first: char) -> TokenKind {
659 if let Some(error) = self.incompatible_string_prefixes(first) {
660 self.cursor
661 .skip_bytes(error.location().len().to_usize() - first.len_utf8());
662 self.errors.push(error);
663 self.current_range = self.token_range();
664 return TokenKind::Unknown;
665 }
666
667 let quote = if let Some(prefix) = single_char_prefix(first) {
669 match self.cursor.first() {
670 quote @ ('\'' | '"') => {
671 self.current_flags |= prefix;
672 self.cursor.bump();
673 Some(quote)
674 }
675 second
676 if let quote = self.cursor.second()
677 && is_quote(quote) =>
678 {
679 self.try_double_char_prefix([first, second]).then(|| {
680 self.cursor.bump();
681 self.cursor.bump();
682 quote
683 })
684 }
685 _ => None,
686 }
687 } else {
688 None
689 };
690
691 if let Some(quote) = quote {
692 if self.current_flags.is_interpolated_string() {
693 if let Some(kind) = self.lex_interpolated_string_start(quote) {
694 return kind;
695 }
696 }
697
698 return self.lex_string(quote);
699 }
700
701 let mut is_ascii = first.is_ascii();
710 self.cursor
711 .eat_while(|c| is_identifier_continuation(c, &mut is_ascii));
712
713 if !is_ascii {
714 self.current_flags |= TokenFlags::NON_ASCII_IDENTIFIER;
715 return TokenKind::Identifier;
716 }
717
718 let text = self.token_text();
719
720 if text.len() > 8 {
722 return TokenKind::Identifier;
723 }
724
725 match text.as_bytes() {
726 b"False" => TokenKind::False,
727 b"None" => TokenKind::None,
728 b"True" => TokenKind::True,
729 b"and" => TokenKind::And,
730 b"as" => TokenKind::As,
731 b"assert" => TokenKind::Assert,
732 b"async" => TokenKind::Async,
733 b"await" => TokenKind::Await,
734 b"break" => TokenKind::Break,
735 b"case" => TokenKind::Case,
736 b"class" => TokenKind::Class,
737 b"continue" => TokenKind::Continue,
738 b"def" => TokenKind::Def,
739 b"del" => TokenKind::Del,
740 b"elif" => TokenKind::Elif,
741 b"else" => TokenKind::Else,
742 b"except" => TokenKind::Except,
743 b"finally" => TokenKind::Finally,
744 b"for" => TokenKind::For,
745 b"from" => TokenKind::From,
746 b"global" => TokenKind::Global,
747 b"if" => TokenKind::If,
748 b"import" => TokenKind::Import,
749 b"in" => TokenKind::In,
750 b"is" => TokenKind::Is,
751 b"lazy" => TokenKind::Lazy,
752 b"lambda" => TokenKind::Lambda,
753 b"match" => TokenKind::Match,
754 b"nonlocal" => TokenKind::Nonlocal,
755 b"not" => TokenKind::Not,
756 b"or" => TokenKind::Or,
757 b"pass" => TokenKind::Pass,
758 b"raise" => TokenKind::Raise,
759 b"return" => TokenKind::Return,
760 b"try" => TokenKind::Try,
761 b"type" => TokenKind::Type,
762 b"while" => TokenKind::While,
763 b"with" => TokenKind::With,
764 b"yield" => TokenKind::Yield,
765 _ => TokenKind::Identifier,
766 }
767 }
768
769 fn incompatible_string_prefixes(&self, first: char) -> Option<LexicalError> {
772 const PREFIXES: [char; 5] = ['u', 'b', 'r', 'f', 't'];
773 const INCOMPATIBLE: [(char, char); 7] = [
774 ('u', 'b'),
775 ('u', 'r'),
776 ('u', 'f'),
777 ('u', 't'),
778 ('b', 'f'),
779 ('b', 't'),
780 ('f', 't'),
781 ];
782
783 let bit = |prefix: char| PREFIXES.iter().position(|&c| c == prefix).map(|i| 1u8 << i);
784 let mut seen = 0u8;
785 let mut len = TextSize::new(0);
786 let mut chars = std::iter::once(first).chain(self.cursor.rest().chars());
787 loop {
788 let c = chars.next()?;
789 if is_quote(c) {
790 break;
791 }
792 let prefix = bit(c.to_ascii_lowercase())?;
793 if seen & prefix != 0 {
794 return None;
795 }
796 seen |= prefix;
797 len += c.text_len();
798 }
799 let has = |prefix: char| bit(prefix).is_some_and(|prefix| seen & prefix != 0);
800 let (first, second) = INCOMPATIBLE
801 .into_iter()
802 .find(|(first, second)| has(*first) && has(*second))?;
803 Some(LexicalError::new(
804 LexicalErrorType::IncompatibleStringPrefixes { first, second },
805 TextRange::at(self.token_range().start(), len),
806 ))
807 }
808
809 fn try_double_char_prefix(&mut self, value: [char; 2]) -> bool {
812 match value {
813 ['r', 'f' | 'F'] | ['f' | 'F', 'r'] => {
814 self.current_flags |= TokenFlags::F_STRING | TokenFlags::RAW_STRING_LOWERCASE;
815 }
816 ['R', 'f' | 'F'] | ['f' | 'F', 'R'] => {
817 self.current_flags |= TokenFlags::F_STRING | TokenFlags::RAW_STRING_UPPERCASE;
818 }
819 ['r', 't' | 'T'] | ['t' | 'T', 'r'] => {
820 self.current_flags |= TokenFlags::T_STRING | TokenFlags::RAW_STRING_LOWERCASE;
821 }
822 ['R', 't' | 'T'] | ['t' | 'T', 'R'] => {
823 self.current_flags |= TokenFlags::T_STRING | TokenFlags::RAW_STRING_UPPERCASE;
824 }
825 ['r', 'b' | 'B'] | ['b' | 'B', 'r'] => {
826 self.current_flags |= TokenFlags::BYTE_STRING | TokenFlags::RAW_STRING_LOWERCASE;
827 }
828 ['R', 'b' | 'B'] | ['b' | 'B', 'R'] => {
829 self.current_flags |= TokenFlags::BYTE_STRING | TokenFlags::RAW_STRING_UPPERCASE;
830 }
831 _ => return false,
832 }
833 true
834 }
835
836 fn lex_interpolated_string_start(&mut self, quote: char) -> Option<TokenKind> {
838 #[cfg(debug_assertions)]
839 debug_assert_eq!(self.cursor.previous(), quote);
840
841 if quote == '"' {
842 self.current_flags |= TokenFlags::DOUBLE_QUOTES;
843 }
844
845 if self.cursor.eat_char2(quote, quote) {
846 self.current_flags |= TokenFlags::TRIPLE_QUOTED_STRING;
847 }
848
849 let ftcontext = InterpolatedStringContext::new(
850 self.current_flags,
851 self.nesting,
852 self.token_range().start(),
853 )?;
854
855 let kind = ftcontext.kind();
856
857 self.interpolated_strings.push(ftcontext);
858
859 Some(kind.start_token())
860 }
861
862 fn lex_interpolated_string_middle_or_end(&mut self) -> Option<TokenKind> {
864 let interpolated_string = self.interpolated_strings.current().unwrap();
866 let string_kind = interpolated_string.kind();
867 let interpolated_flags = interpolated_string.flags();
868
869 if interpolated_string.is_triple_quoted() {
871 let quote_char = interpolated_string.quote_char();
872 if self.cursor.eat_char3(quote_char, quote_char, quote_char) {
873 self.current_flags = interpolated_string.flags();
874 return Some(string_kind.end_token());
875 }
876 } else if self.cursor.eat_char(interpolated_string.quote_char()) {
877 self.current_flags = interpolated_string.flags();
878 return Some(string_kind.end_token());
879 }
880
881 let in_format_spec = interpolated_string.is_in_format_spec(self.nesting);
883
884 let mut in_named_unicode = false;
885
886 loop {
887 match self.cursor.first() {
888 EOF_CHAR if self.cursor.is_eof() => {
892 let detected_line = self.detected_line();
893 let range = TextRange::empty(interpolated_string.start());
894 let error = if interpolated_string.is_triple_quoted() {
895 InterpolatedStringErrorType::UnterminatedTripleQuotedString {
896 detected_line,
897 }
898 } else {
899 InterpolatedStringErrorType::UnterminatedString { detected_line }
900 };
901
902 self.reset_nesting(interpolated_string.nesting());
903 self.interpolated_strings.pop();
904 self.current_flags |= TokenFlags::UNCLOSED_STRING;
905 self.push_error(LexicalError::new(
906 LexicalErrorType::from_interpolated_string_error(error, string_kind),
907 range,
908 ));
909
910 break;
911 }
912 '\n' | '\r' if !interpolated_string.is_triple_quoted() => {
913 let (error_type, range) = if in_format_spec {
916 (
917 InterpolatedStringErrorType::NewlineInFormatSpec,
918 self.token_range(),
919 )
920 } else {
921 (
922 InterpolatedStringErrorType::UnterminatedString {
923 detected_line: self.detected_line(),
924 },
925 TextRange::empty(interpolated_string.start()),
926 )
927 };
928
929 self.reset_nesting(interpolated_string.nesting());
930 self.interpolated_strings.pop();
931 self.current_flags |= TokenFlags::UNCLOSED_STRING;
932
933 self.push_error(LexicalError::new(
934 LexicalErrorType::from_interpolated_string_error(error_type, string_kind),
935 range,
936 ));
937
938 break;
939 }
940 '\\' => {
941 self.cursor.bump(); if matches!(self.cursor.first(), '{' | '}') {
943 continue;
946 } else if !interpolated_string.is_raw_string() {
947 if self.cursor.eat_char2('N', '{') {
948 in_named_unicode = true;
949 continue;
950 }
951 }
952 if self.cursor.eat_char('\r') {
954 self.cursor.eat_char('\n');
955 } else {
956 self.cursor.bump();
957 }
958 }
959 quote @ ('\'' | '"') if quote == interpolated_string.quote_char() => {
960 if let Some(triple_quotes) = interpolated_string.triple_quotes() {
961 if self.cursor.rest().starts_with(triple_quotes) {
962 break;
963 }
964 self.cursor.bump();
965 } else {
966 break;
967 }
968 }
969 '{' => {
970 if self.cursor.second() == '{' && !in_format_spec {
971 self.cursor.bump();
972 self.cursor.bump(); } else {
974 break;
975 }
976 }
977 '}' => {
978 if in_named_unicode {
979 in_named_unicode = false;
980 self.cursor.bump();
981 } else if self.cursor.second() == '}' && !in_format_spec {
982 self.cursor.bump();
983 self.cursor.bump(); } else {
985 break;
986 }
987 }
988 _ => {
989 self.cursor.bump();
990 }
991 }
992 }
993 let range = self.token_range();
994 if range.is_empty() {
995 return None;
996 }
997
998 self.current_flags = interpolated_flags;
999 Some(string_kind.middle_token())
1000 }
1001
1002 fn lex_string(&mut self, quote: char) -> TokenKind {
1004 #[cfg(debug_assertions)]
1005 debug_assert_eq!(self.cursor.previous(), quote);
1006
1007 if quote == '"' {
1008 self.current_flags |= TokenFlags::DOUBLE_QUOTES;
1009 }
1010
1011 if self.cursor.eat_char2(quote, quote) {
1014 self.current_flags |= TokenFlags::TRIPLE_QUOTED_STRING;
1015 }
1016
1017 let quote_byte = u8::try_from(quote).expect("char that fits in u8");
1018 if self.current_flags.is_triple_quoted() {
1019 loop {
1022 let Some(index) = memchr::memchr(quote_byte, self.cursor.rest().as_bytes()) else {
1023 self.cursor.skip_to_end();
1024
1025 self.current_flags |= TokenFlags::UNCLOSED_STRING;
1026 self.push_unclosed_string_error(quote);
1027 break;
1028 };
1029
1030 let num_backslashes = self.cursor.rest().as_bytes()[..index]
1033 .iter()
1034 .rev()
1035 .take_while(|&&c| c == b'\\')
1036 .count();
1037
1038 self.cursor.skip_bytes(index + 1);
1040
1041 if num_backslashes % 2 == 1 {
1043 continue;
1044 }
1045
1046 if self.cursor.eat_char2(quote, quote) {
1048 break;
1049 }
1050 }
1051 } else {
1052 loop {
1055 let Some(index) =
1056 memchr::memchr3(quote_byte, b'\r', b'\n', self.cursor.rest().as_bytes())
1057 else {
1058 self.cursor.skip_to_end();
1059 self.current_flags |= TokenFlags::UNCLOSED_STRING;
1060 self.push_unclosed_string_error(quote);
1061
1062 break;
1063 };
1064
1065 let num_backslashes = self.cursor.rest().as_bytes()[..index]
1068 .iter()
1069 .rev()
1070 .take_while(|&&c| c == b'\\')
1071 .count();
1072
1073 self.cursor.skip_bytes(index);
1075
1076 let quote_or_newline = self.cursor.first();
1078
1079 if num_backslashes % 2 == 1 {
1081 self.cursor.bump();
1082 if quote_or_newline == '\r' {
1083 self.cursor.eat_char('\n');
1084 }
1085 continue;
1086 }
1087
1088 match quote_or_newline {
1089 '\r' | '\n' => {
1090 self.current_flags |= TokenFlags::UNCLOSED_STRING;
1091 self.push_unclosed_string_error(quote);
1092 break;
1093 }
1094 ch if ch == quote => {
1095 self.cursor.bump();
1096 break;
1097 }
1098 _ => unreachable!("memchr2 returned an index that is not a quote or a newline"),
1099 }
1100 }
1101 }
1102
1103 TokenKind::String
1104 }
1105
1106 fn push_unclosed_string_error(&mut self, quote: char) {
1109 let range = self.token_range();
1110 let body = &self.source[range];
1111 let body = &body[body.find(quote).unwrap_or(0)..];
1112 let mut escaped_end_quote = false;
1113 let mut chars = body.chars();
1114 while let Some(c) = chars.next() {
1115 if c == '\\' && chars.next() == Some(quote) {
1116 escaped_end_quote = true;
1117 break;
1118 }
1119 }
1120 let error = match self.interpolated_strings.current() {
1121 Some(interpolated_string)
1124 if interpolated_string.quote_char() == quote
1125 && interpolated_string.is_triple_quoted()
1126 == self.current_flags.is_triple_quoted() =>
1127 {
1128 LexicalErrorType::from_interpolated_string_error(
1129 InterpolatedStringErrorType::UnclosedLbrace,
1130 interpolated_string.kind(),
1131 )
1132 }
1133 _ => LexicalErrorType::UnclosedStringError {
1134 triple_quoted: self.current_flags.is_triple_quoted(),
1135 escaped_end_quote,
1136 detected_line: self.detected_line(),
1137 },
1138 };
1139 self.push_error(LexicalError::new(error, TextRange::empty(range.start())));
1140 }
1141
1142 fn detected_line(&self) -> u32 {
1145 let mut offset = self.offset();
1146 if offset.to_usize() == self.source.len() && self.source.ends_with(['\n', '\r']) {
1147 offset -= TextSize::new(1);
1148 }
1149 self.line_number(offset)
1150 }
1151
1152 fn line_number(&self, offset: TextSize) -> u32 {
1154 let source = self.source.as_bytes();
1155 let newlines = source[..offset.to_usize()]
1156 .iter()
1157 .enumerate()
1158 .filter(|&(index, &byte)| {
1159 byte == b'\n' || (byte == b'\r' && source.get(index + 1) != Some(&b'\n'))
1160 })
1161 .count();
1162 u32::try_from(newlines + 1).unwrap()
1163 }
1164
1165 fn eof_error(&self, range: TextRange) -> LexicalError {
1168 match self.brackets.last() {
1169 Some(bracket) => LexicalError::new(
1170 LexicalErrorType::UnclosedBracket {
1171 opening: bracket.kind,
1172 incomplete: true,
1173 },
1174 TextRange::empty(bracket.start),
1175 ),
1176 None => LexicalError::new(LexicalErrorType::Eof, range),
1177 }
1178 }
1179
1180 fn open_bracket(&mut self, kind: char) {
1182 let start = self.token_range().start();
1183 if self.nesting >= MAX_NESTING {
1184 self.errors.push(LexicalError::new(
1185 LexicalErrorType::TooDeeplyNestedBrackets,
1186 TextRange::empty(start),
1187 ));
1188 }
1189 self.nesting += 1;
1190 self.brackets.push(OpenBracket { kind, start });
1191 }
1192
1193 fn close_bracket(&mut self, closing: char) {
1196 let start = self.token_range().start();
1197 let at_interpolation_brace =
1198 self.interpolated_strings
1199 .current()
1200 .filter(|interpolated_string| {
1201 interpolated_string.is_at_interpolation_brace(self.nesting)
1202 });
1203 let error = match self.brackets.pop() {
1204 Some(open) if open.kind == opening_bracket(closing) => None,
1205 Some(open) if open.kind == '{' && at_interpolation_brace.is_some() => {
1206 Some(LexicalErrorType::from_interpolated_string_error(
1207 InterpolatedStringErrorType::UnmatchedBracket(closing),
1208 at_interpolation_brace.unwrap().kind(),
1209 ))
1210 }
1211 Some(open) => {
1212 let opening_line = self.line_number(open.start);
1213 Some(LexicalErrorType::MismatchedBracket {
1214 closing,
1215 opening: open.kind,
1216 opening_line: (opening_line != self.line_number(start)).then_some(opening_line),
1217 })
1218 }
1219 None if self.nesting == 0 => Some(LexicalErrorType::UnmatchedBracket { closing }),
1220 None => None,
1221 };
1222 if let Some(error) = error {
1223 self.errors
1224 .push(LexicalError::new(error, TextRange::empty(start)));
1225 }
1226 self.nesting = self.nesting.saturating_sub(1);
1227 }
1228
1229 fn reset_nesting(&mut self, nesting: u32) {
1231 let implicit = self.nesting as usize - self.brackets.len();
1232 self.brackets
1233 .truncate((nesting as usize).saturating_sub(implicit));
1234 self.nesting = nesting;
1235 }
1236
1237 fn lex_number(&mut self, first: char) -> TokenKind {
1239 if first == '0' {
1240 if self.cursor.eat_if(|c| matches!(c, 'x' | 'X')).is_some() {
1241 self.lex_number_radix(Radix::Hex)
1242 } else if self.cursor.eat_if(|c| matches!(c, 'o' | 'O')).is_some() {
1243 self.lex_number_radix(Radix::Octal)
1244 } else if self.cursor.eat_if(|c| matches!(c, 'b' | 'B')).is_some() {
1245 self.lex_number_radix(Radix::Binary)
1246 } else {
1247 self.lex_decimal_number(first)
1248 }
1249 } else {
1250 self.lex_decimal_number(first)
1251 }
1252 }
1253
1254 fn lex_number_radix(&mut self, radix: Radix) -> TokenKind {
1256 #[cfg(debug_assertions)]
1257 {
1258 use std::debug_assert_matches;
1259 debug_assert_matches!(self.cursor.previous().to_ascii_lowercase(), 'x' | 'o' | 'b');
1260 }
1261
1262 loop {
1263 self.cursor.eat_char('_');
1264 if !radix.is_digit(self.cursor.first()) {
1265 return self.push_invalid_radix_digit(radix);
1266 }
1267 self.cursor.eat_while(|c| radix.is_digit(c));
1268 if self.cursor.first() != '_' {
1269 break;
1270 }
1271 }
1272 if self.cursor.first().is_ascii_digit() && radix != Radix::Hex {
1273 return self.push_invalid_radix_digit(radix);
1274 }
1275 self.verify_end_of_number(radix.kind());
1276 TokenKind::Int
1277 }
1278
1279 fn push_invalid_radix_digit(&mut self, radix: Radix) -> TokenKind {
1282 let kind = radix.kind();
1283 let digit = self.cursor.first();
1284 let error = if digit.is_ascii_digit() && radix != Radix::Hex {
1285 self.cursor.bump();
1286 LexicalErrorType::InvalidDigit { digit, kind }
1287 } else {
1288 LexicalErrorType::InvalidNumberLiteral { kind }
1289 };
1290 self.push_number_error(error)
1291 }
1292
1293 fn lex_decimal_number(&mut self, first_digit_or_dot: char) -> TokenKind {
1295 #[cfg(debug_assertions)]
1296 debug_assert!(self.cursor.previous().is_ascii_digit() || self.cursor.previous() == '.');
1297
1298 let mut fraction = first_digit_or_dot == '.';
1299 if first_digit_or_dot == '0' {
1300 loop {
1302 if self.cursor.eat_char('_') && !self.cursor.first().is_ascii_digit() {
1303 return self.push_number_error(LexicalErrorType::InvalidNumberLiteral {
1304 kind: NumberLiteralKind::Decimal,
1305 });
1306 }
1307 if !self.cursor.eat_char('0') {
1308 break;
1309 }
1310 }
1311 let nonzero_start = self.offset();
1312 let nonzero = self.cursor.first().is_ascii_digit();
1313 if nonzero && !self.decimal_tail() {
1314 return TokenKind::Unknown;
1315 }
1316 match self.cursor.first() {
1317 '.' => {
1318 self.cursor.bump();
1319 fraction = true;
1320 }
1321 'e' | 'E' | 'j' | 'J' => {}
1322 _ if nonzero => {
1323 self.errors.push(LexicalError::new(
1324 LexicalErrorType::LeadingZerosInDecimalInteger,
1325 TextRange::new(self.token_range().start(), nonzero_start),
1326 ));
1327 self.current_range = self.token_range();
1328 return TokenKind::Unknown;
1329 }
1330 _ => {}
1331 }
1332 } else if first_digit_or_dot != '.' {
1333 if !self.decimal_tail() {
1334 return TokenKind::Unknown;
1335 }
1336 fraction = self.cursor.eat_char('.');
1337 }
1338
1339 let mut is_float = fraction;
1340 if fraction && self.cursor.first().is_ascii_digit() && !self.decimal_tail() {
1341 return TokenKind::Unknown;
1342 }
1343
1344 if matches!(self.cursor.first(), 'e' | 'E') {
1345 match self.cursor.second() {
1346 '+' | '-' => {
1347 self.cursor.bump();
1348 self.cursor.bump();
1349 if !self.cursor.first().is_ascii_digit() {
1350 return self.push_number_error(LexicalErrorType::InvalidNumberLiteral {
1351 kind: NumberLiteralKind::Decimal,
1352 });
1353 }
1354 }
1355 second if !second.is_ascii_digit() => {
1356 self.verify_end_of_number(NumberLiteralKind::Decimal);
1358 return if is_float {
1359 TokenKind::Float
1360 } else {
1361 TokenKind::Int
1362 };
1363 }
1364 _ => {
1365 self.cursor.bump();
1366 }
1367 }
1368 if !self.decimal_tail() {
1369 return TokenKind::Unknown;
1370 }
1371 is_float = true;
1372 }
1373
1374 if self.cursor.eat_if(|c| matches!(c, 'j' | 'J')).is_some() {
1375 self.verify_end_of_number(NumberLiteralKind::Imaginary);
1376 TokenKind::Complex
1377 } else {
1378 self.verify_end_of_number(NumberLiteralKind::Decimal);
1379 if is_float {
1380 TokenKind::Float
1381 } else {
1382 TokenKind::Int
1383 }
1384 }
1385 }
1386
1387 fn decimal_tail(&mut self) -> bool {
1390 loop {
1391 self.cursor.eat_while(|c| c.is_ascii_digit());
1392 if !self.cursor.eat_char('_') {
1393 return true;
1394 }
1395 if !self.cursor.first().is_ascii_digit() {
1396 self.push_number_error(LexicalErrorType::InvalidNumberLiteral {
1397 kind: NumberLiteralKind::Decimal,
1398 });
1399 return false;
1400 }
1401 }
1402 }
1403
1404 fn push_number_error(&mut self, error: LexicalErrorType) -> TokenKind {
1406 self.errors.push(LexicalError::new(
1407 error,
1408 TextRange::empty(self.offset() - TextSize::new(1)),
1409 ));
1410 self.current_range = self.token_range();
1411 TokenKind::Unknown
1412 }
1413
1414 fn verify_end_of_number(&mut self, kind: NumberLiteralKind) {
1417 let rest = self.cursor.rest();
1418 let before_keyword = ["and", "else", "for", "if", "in", "is", "not", "or"]
1419 .iter()
1420 .any(|keyword| rest.starts_with(keyword));
1421 if !before_keyword
1422 && rest
1423 .chars()
1424 .next()
1425 .is_some_and(|c| c.is_ascii_alphanumeric() || c == '_')
1426 {
1427 self.errors.push(LexicalError::new(
1428 LexicalErrorType::InvalidNumberLiteral { kind },
1429 TextRange::empty(self.offset() - TextSize::new(1)),
1430 ));
1431 }
1432 }
1433
1434 fn lex_comment(&mut self) -> TokenKind {
1436 #[cfg(debug_assertions)]
1437 debug_assert_eq!(self.cursor.previous(), '#');
1438
1439 let bytes = self.cursor.rest().as_bytes();
1440 let offset = memchr::memchr2(b'\n', b'\r', bytes).unwrap_or(bytes.len());
1441 self.cursor.skip_bytes(offset);
1442
1443 TokenKind::Comment
1444 }
1445
1446 fn lex_ipython_escape_command(&mut self) -> TokenKind {
1448 loop {
1449 match self.cursor.first() {
1450 '\\' => {
1451 self.cursor.bump();
1454 self.cursor.eat_char('\r');
1455 self.cursor.eat_char('\n');
1456 }
1457 '\n' | '\r' | EOF_CHAR => return TokenKind::IpyEscapeCommand,
1458 _ => {
1459 self.cursor.bump();
1460 }
1461 }
1462 }
1463 }
1464
1465 fn consume_end(&mut self) -> TokenKind {
1466 while let Some(interpolated_string) = self.interpolated_strings.pop() {
1470 let error = match self.brackets.last() {
1472 Some(bracket) if self.nesting > interpolated_string.nesting() => LexicalError::new(
1473 LexicalErrorType::UnclosedBracket {
1474 opening: bracket.kind,
1475 incomplete: true,
1476 },
1477 TextRange::empty(bracket.start),
1478 ),
1479 _ => LexicalError::new(
1480 LexicalErrorType::from_interpolated_string_error(
1481 InterpolatedStringErrorType::UnterminatedString {
1482 detected_line: self.detected_line(),
1483 },
1484 interpolated_string.kind(),
1485 ),
1486 TextRange::empty(interpolated_string.start()),
1487 ),
1488 };
1489 self.reset_nesting(interpolated_string.nesting());
1490 self.push_error(error);
1491 }
1492
1493 let init_nesting = u32::from(self.mode == Mode::ParenthesizedExpression);
1497
1498 if self.nesting > init_nesting {
1499 let error = self.eof_error(self.token_range());
1500 self.nesting = 0;
1502 self.brackets.clear();
1503 self.push_error(error);
1504 self.current_range = self.token_range();
1505 return TokenKind::Unknown;
1506 }
1507
1508 if !self.state.is_new_logical_line() {
1510 self.state = State::AfterNewline;
1511 TokenKind::Newline
1512 }
1513 else if self.indentations.dedent().is_some() {
1515 TokenKind::Dedent
1516 } else {
1517 TokenKind::EndOfFile
1518 }
1519 }
1520
1521 pub(crate) fn re_lex_logical_token(
1577 &mut self,
1578 non_logical_newline_start: Option<TextSize>,
1579 ) -> bool {
1580 if self.nesting == 0 {
1581 return false;
1582 }
1583
1584 self.nesting -= 1;
1587 let unclosed = self.brackets.pop();
1588
1589 if self.current_flags.is_triple_quoted_interpolated_string() {
1592 return false;
1593 }
1594
1595 let Some(new_position) = non_logical_newline_start else {
1596 return false;
1597 };
1598
1599 if matches!(
1613 self.current_kind,
1614 TokenKind::Rpar | TokenKind::Rsqb | TokenKind::Rbrace
1615 ) {
1616 self.nesting += 1;
1617 self.brackets.extend(unclosed);
1618 }
1619
1620 self.cursor = Cursor::new(self.source);
1621 self.cursor.skip_bytes(new_position.to_usize());
1622 self.state = State::Other;
1623 self.next_token();
1624 true
1625 }
1626
1627 pub(crate) fn re_lex_string_token_in_interpolation_element(
1636 &mut self,
1637 kind: InterpolatedStringKind,
1638 ) {
1639 let Some(interpolated_string) = self.interpolated_strings.current() else {
1640 return;
1641 };
1642
1643 let current_string_flags = self.current_flags().as_any_string_flags();
1644
1645 if !matches!(self.current_kind, TokenKind::String)
1647 || !self.current_flags.is_unclosed()
1648 || current_string_flags.prefix() != AnyStringPrefix::Regular(StringLiteralPrefix::Empty)
1649 || current_string_flags.quote_style().as_char() != interpolated_string.quote_char()
1650 || current_string_flags.is_triple_quoted() != interpolated_string.is_triple_quoted()
1651 {
1652 return;
1653 }
1654
1655 let first_line = &self.source
1658 [(self.current_range.start() + current_string_flags.quote_len()).to_usize()..];
1659
1660 for c in first_line.chars() {
1661 if matches!(c, '\n' | '\r' | '#') {
1662 break;
1663 }
1664
1665 if !is_python_whitespace(c) {
1667 return;
1668 }
1669 }
1670
1671 if self.errors.last().is_some_and(|error| {
1672 error.location().start() == self.current_range.start()
1673 && error.error().is_unclosed_string_error()
1674 }) {
1675 self.errors.pop();
1676 }
1677
1678 self.current_range =
1679 TextRange::at(self.current_range.start(), self.current_flags.quote_len());
1680 self.current_kind = kind.end_token();
1681 self.current_flags = TokenFlags::empty();
1682
1683 self.reset_nesting(interpolated_string.nesting());
1684 self.interpolated_strings.pop();
1685
1686 self.cursor = Cursor::new(self.source);
1687 self.cursor.skip_bytes(self.current_range.end().to_usize());
1688 }
1689
1690 pub(crate) fn re_lex_raw_string_in_format_spec(&mut self) {
1704 if matches!(self.current_kind, TokenKind::String)
1707 && self.current_flags.is_unclosed()
1708 && self.current_flags.prefix()
1709 == AnyStringPrefix::Regular(StringLiteralPrefix::Raw { uppercase: false })
1710 {
1711 if self.errors.last().is_some_and(|error| {
1712 error.location().start() == self.current_range.start()
1713 && error.error().is_unclosed_string_error()
1714 }) {
1715 self.errors.pop();
1716 }
1717
1718 self.current_range = TextRange::at(self.current_range.start(), 'r'.text_len());
1719 self.current_kind = TokenKind::Identifier;
1720 self.current_flags = TokenFlags::empty();
1721 self.cursor = Cursor::new(self.source);
1722 self.cursor.skip_bytes(self.current_range.end().to_usize());
1723 }
1724 }
1725
1726 #[inline]
1727 fn token_range(&self) -> TextRange {
1728 let end = self.offset();
1729 let len = self.cursor.token_len();
1730
1731 TextRange::at(end - len, len)
1732 }
1733
1734 #[inline]
1735 fn token_text(&self) -> &'src str {
1736 &self.source[self.token_range()]
1737 }
1738
1739 #[expect(clippy::cast_possible_truncation)]
1742 #[inline]
1743 pub(crate) fn offset(&self) -> TextSize {
1744 TextSize::new(self.source.len() as u32) - self.cursor.text_len()
1745 }
1746
1747 pub(crate) fn checkpoint(&self) -> LexerCheckpoint {
1749 LexerCheckpoint {
1750 current_kind: self.current_kind,
1751 current_range: self.current_range,
1752 current_flags: self.current_flags,
1753 cursor_offset: self.offset(),
1754 state: self.state,
1755 nesting: self.nesting,
1756 brackets: self.brackets.clone(),
1757 indentations_checkpoint: self.indentations.checkpoint(),
1758 pending_indentation: self.pending_indentation,
1759 interpolated_strings_checkpoint: self.interpolated_strings.checkpoint(),
1760 errors_position: self.errors.len(),
1761 }
1762 }
1763
1764 pub(crate) fn rewind(&mut self, checkpoint: LexerCheckpoint) {
1766 let LexerCheckpoint {
1767 current_kind,
1768 current_range,
1769 current_flags,
1770 cursor_offset,
1771 state,
1772 nesting,
1773 brackets,
1774 indentations_checkpoint,
1775 pending_indentation,
1776 interpolated_strings_checkpoint,
1777 errors_position,
1778 } = checkpoint;
1779
1780 let mut cursor = Cursor::new(self.source);
1781 cursor.skip_bytes(cursor_offset.to_usize());
1783
1784 self.current_kind = current_kind;
1785 self.current_range = current_range;
1786 self.current_flags = current_flags;
1787 self.cursor = cursor;
1788 self.state = state;
1789 self.nesting = nesting;
1790 self.brackets = brackets;
1791 self.indentations.rewind(indentations_checkpoint);
1792 self.pending_indentation = pending_indentation;
1793 self.interpolated_strings
1794 .rewind(interpolated_strings_checkpoint);
1795 self.errors.truncate(errors_position);
1796 }
1797
1798 pub(crate) fn finish(self) -> Vec<LexicalError> {
1799 self.errors
1800 }
1801
1802 pub(crate) fn errors(&self) -> &[LexicalError] {
1804 &self.errors
1805 }
1806
1807 pub(crate) fn in_interpolated_string(&self) -> bool {
1809 self.interpolated_strings.current().is_some()
1810 }
1811}
1812
1813const MAX_NESTING: u32 = 200;
1815
1816#[derive(Copy, Clone, Debug)]
1818struct OpenBracket {
1819 kind: char,
1820 start: TextSize,
1821}
1822
1823const fn opening_bracket(closing: char) -> char {
1825 match closing {
1826 ')' => '(',
1827 ']' => '[',
1828 _ => '{',
1829 }
1830}
1831
1832pub(crate) struct LexerCheckpoint {
1833 current_kind: TokenKind,
1834 current_range: TextRange,
1835 current_flags: TokenFlags,
1836 cursor_offset: TextSize,
1837 state: State,
1838 nesting: u32,
1839 brackets: Vec<OpenBracket>,
1840 indentations_checkpoint: IndentationsCheckpoint,
1841 pending_indentation: Option<Indentation>,
1842 interpolated_strings_checkpoint: InterpolatedStringsCheckpoint,
1843 errors_position: usize,
1844}
1845
1846#[derive(Copy, Clone, Debug)]
1847enum State {
1848 AfterNewline,
1850
1851 NonEmptyLogicalLine,
1853
1854 AfterEqual,
1856
1857 Other,
1859}
1860
1861impl State {
1862 const fn is_after_newline(self) -> bool {
1863 matches!(self, State::AfterNewline)
1864 }
1865
1866 const fn is_new_logical_line(self) -> bool {
1867 matches!(self, State::AfterNewline | State::NonEmptyLogicalLine)
1868 }
1869
1870 const fn is_after_equal(self) -> bool {
1871 matches!(self, State::AfterEqual)
1872 }
1873}
1874
1875#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1876enum Radix {
1877 Binary,
1878 Octal,
1879 Hex,
1880}
1881
1882impl Radix {
1883 const fn kind(self) -> NumberLiteralKind {
1884 match self {
1885 Radix::Binary => NumberLiteralKind::Binary,
1886 Radix::Octal => NumberLiteralKind::Octal,
1887 Radix::Hex => NumberLiteralKind::Hexadecimal,
1888 }
1889 }
1890
1891 const fn is_digit(self, c: char) -> bool {
1892 match self {
1893 Radix::Binary => matches!(c, '0'..='1'),
1894 Radix::Octal => matches!(c, '0'..='7'),
1895 Radix::Hex => c.is_ascii_hexdigit(),
1896 }
1897 }
1898}
1899
1900const fn is_quote(c: char) -> bool {
1901 matches!(c, '\'' | '"')
1902}
1903
1904fn single_char_prefix(c: char) -> Option<TokenFlags> {
1905 Some(match c {
1906 'f' | 'F' => TokenFlags::F_STRING,
1907 't' | 'T' => TokenFlags::T_STRING,
1908 'u' | 'U' => TokenFlags::UNICODE_STRING,
1909 'b' | 'B' => TokenFlags::BYTE_STRING,
1910 'r' => TokenFlags::RAW_STRING_LOWERCASE,
1911 'R' => TokenFlags::RAW_STRING_UPPERCASE,
1912 _ => return None,
1913 })
1914}
1915
1916const fn is_ascii_identifier_start(c: char) -> bool {
1917 matches!(c, 'a'..='z' | 'A'..='Z' | '_')
1918}
1919
1920fn is_unicode_identifier_start(c: char) -> bool {
1923 is_xid_start(c)
1924}
1925
1926fn is_identifier_continuation(c: char, identifier_is_ascii_only: &mut bool) -> bool {
1933 if c.is_ascii() {
1936 matches!(c, 'a'..='z' | 'A'..='Z' | '_' | '0'..='9')
1937 } else {
1938 let is_continuation = is_xid_continue(c);
1939 if is_continuation {
1940 *identifier_is_ascii_only = false;
1941 }
1942 is_continuation
1943 }
1944}
1945
1946#[doc(hidden)]
1948pub fn lex(source: &str, mode: Mode) -> Lexer<'_> {
1949 Lexer::new(source, mode, TextSize::default())
1950}
1951
1952#[cfg(test)]
1953mod tests {
1954 use std::fmt::Write;
1955
1956 use insta::assert_snapshot;
1957 use ruff_python_ast::token::Token;
1958 use ruff_text_size::Ranged;
1959
1960 use super::*;
1961
1962 const WINDOWS_EOL: &str = "\r\n";
1963 const MAC_EOL: &str = "\r";
1964 const UNIX_EOL: &str = "\n";
1965
1966 struct LexerOutput {
1967 tokens: Vec<Token>,
1968 errors: Vec<LexicalError>,
1969 }
1970
1971 impl std::fmt::Display for LexerOutput {
1972 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1973 writeln!(f, "## Tokens")?;
1974 writeln!(f, "```\n{:#?}\n```", self.tokens)?;
1975 if !self.errors.is_empty() {
1976 writeln!(f, "## Errors")?;
1977 writeln!(f, "```\n{:#?}\n```", self.errors)?;
1978 }
1979 Ok(())
1980 }
1981 }
1982
1983 fn lex(source: &str, mode: Mode, start_offset: TextSize) -> LexerOutput {
1984 let mut lexer = Lexer::new(source, mode, start_offset);
1985 let mut tokens = Vec::new();
1986 loop {
1987 let kind = lexer.next_token();
1988 if kind.is_eof() {
1989 break;
1990 }
1991 tokens.push(Token::new(
1992 kind,
1993 lexer.current_range(),
1994 lexer.current_flags(),
1995 ));
1996 }
1997 LexerOutput {
1998 tokens,
1999 errors: lexer.finish(),
2000 }
2001 }
2002
2003 #[track_caller]
2004 fn lex_valid(source: &str, mode: Mode, start_offset: TextSize) -> LexerOutput {
2005 let output = lex(source, mode, start_offset);
2006
2007 if !output.errors.is_empty() {
2008 let mut message = "Unexpected lexical errors for a valid source:\n".to_string();
2009 for error in &output.errors {
2010 writeln!(&mut message, "{error:?}").unwrap();
2011 }
2012 writeln!(&mut message, "Source:\n{source}").unwrap();
2013 panic!("{message}");
2014 }
2015
2016 output
2017 }
2018
2019 #[track_caller]
2020 fn lex_invalid(source: &str, mode: Mode) -> LexerOutput {
2021 let output = lex(source, mode, TextSize::default());
2022
2023 assert!(
2024 !output.errors.is_empty(),
2025 "Expected lexer to generate at least one error for the following source:\n{source}"
2026 );
2027
2028 output
2029 }
2030
2031 #[track_caller]
2032 fn lex_source(source: &str) -> LexerOutput {
2033 lex_valid(source, Mode::Module, TextSize::default())
2034 }
2035
2036 #[track_caller]
2037 fn lex_source_with_offset(source: &str, start_offset: TextSize) -> LexerOutput {
2038 lex_valid(source, Mode::Module, start_offset)
2039 }
2040
2041 #[track_caller]
2042 fn lex_jupyter_source(source: &str) -> LexerOutput {
2043 lex_valid(source, Mode::Ipython, TextSize::default())
2044 }
2045
2046 #[test]
2047 fn bom() {
2048 let source = "\u{feff}x = 1";
2049 assert_snapshot!(lex_source(source));
2050 }
2051
2052 #[test]
2053 fn bom_with_offset() {
2054 let source = "\u{feff}x + y + z";
2055 assert_snapshot!(lex_source_with_offset(source, TextSize::new(7)));
2056 }
2057
2058 #[test]
2059 fn bom_with_offset_edge() {
2060 let source = "\u{feff}x + y + z";
2063 assert_snapshot!(lex_source_with_offset(source, TextSize::new(11)));
2064 }
2065
2066 fn ipython_escape_command_line_continuation_eol(eol: &str) -> LexerOutput {
2067 let source = format!("%matplotlib \\{eol} --inline");
2068 lex_jupyter_source(&source)
2069 }
2070
2071 #[test]
2072 fn test_ipython_escape_command_line_continuation_unix_eol() {
2073 assert_snapshot!(ipython_escape_command_line_continuation_eol(UNIX_EOL));
2074 }
2075
2076 #[test]
2077 fn test_ipython_escape_command_line_continuation_mac_eol() {
2078 assert_snapshot!(ipython_escape_command_line_continuation_eol(MAC_EOL));
2079 }
2080
2081 #[test]
2082 fn test_ipython_escape_command_line_continuation_windows_eol() {
2083 assert_snapshot!(ipython_escape_command_line_continuation_eol(WINDOWS_EOL));
2084 }
2085
2086 fn ipython_escape_command_line_continuation_with_eol_and_eof(eol: &str) -> LexerOutput {
2087 let source = format!("%matplotlib \\{eol}");
2088 lex_jupyter_source(&source)
2089 }
2090
2091 #[test]
2092 fn test_ipython_escape_command_line_continuation_with_unix_eol_and_eof() {
2093 assert_snapshot!(ipython_escape_command_line_continuation_with_eol_and_eof(
2094 UNIX_EOL
2095 ));
2096 }
2097
2098 #[test]
2099 fn test_ipython_escape_command_line_continuation_with_mac_eol_and_eof() {
2100 assert_snapshot!(ipython_escape_command_line_continuation_with_eol_and_eof(
2101 MAC_EOL
2102 ));
2103 }
2104
2105 #[test]
2106 fn test_ipython_escape_command_line_continuation_with_windows_eol_and_eof() {
2107 assert_snapshot!(ipython_escape_command_line_continuation_with_eol_and_eof(
2108 WINDOWS_EOL
2109 ));
2110 }
2111
2112 #[test]
2113 fn test_empty_ipython_escape_command() {
2114 let source = "%\n%%\n!\n!!\n?\n??\n/\n,\n;";
2115 assert_snapshot!(lex_jupyter_source(source));
2116 }
2117
2118 #[test]
2119 fn test_ipython_escape_command() {
2120 let source = r"
2121?foo
2122??foo
2123%timeit a = b
2124%timeit a % 3
2125%matplotlib \
2126 --inline
2127!pwd \
2128 && ls -a | sed 's/^/\\ /'
2129!!cd /Users/foo/Library/Application\ Support/
2130/foo 1 2
2131,foo 1 2
2132;foo 1 2
2133!ls
2134"
2135 .trim();
2136 assert_snapshot!(lex_jupyter_source(source));
2137 }
2138
2139 #[test]
2140 fn test_ipython_help_end_escape_command() {
2141 let source = r"
2142?foo?
2143?? foo?
2144?? foo ?
2145?foo??
2146??foo??
2147???foo?
2148???foo??
2149??foo???
2150???foo???
2151?? \
2152 foo?
2153?? \
2154?
2155????
2156%foo?
2157%foo??
2158%%foo???
2159!pwd?"
2160 .trim();
2161 assert_snapshot!(lex_jupyter_source(source));
2162 }
2163
2164 #[test]
2165 fn test_ipython_escape_command_indentation() {
2166 let source = r"
2167if True:
2168 %matplotlib \
2169 --inline"
2170 .trim();
2171 assert_snapshot!(lex_jupyter_source(source));
2172 }
2173
2174 #[test]
2175 fn test_ipython_escape_command_assignment() {
2176 let source = r"
2177pwd = !pwd
2178foo = %timeit a = b
2179bar = %timeit a % 3
2180baz = %matplotlib \
2181 inline
2182qux = %foo?
2183quux = !pwd?"
2184 .trim();
2185 assert_snapshot!(lex_jupyter_source(source));
2186 }
2187
2188 fn assert_no_ipython_escape_command(tokens: &[Token]) {
2189 for token in tokens {
2190 if matches!(token.kind(), TokenKind::IpyEscapeCommand) {
2191 panic!("Unexpected escape command token at {:?}", token.range())
2192 }
2193 }
2194 }
2195
2196 #[test]
2197 fn test_ipython_escape_command_not_an_assignment() {
2198 let source = r"
2199# Other escape kinds are not valid here (can't test `foo = ?str` because '?' is not a valid token)
2200foo = /func
2201foo = ;func
2202foo = ,func
2203
2204(foo == %timeit a = b)
2205(foo := %timeit a = b)
2206def f(arg=%timeit a = b):
2207 pass"
2208 .trim();
2209 let output = lex(source, Mode::Ipython, TextSize::default());
2210 assert!(output.errors.is_empty());
2211 assert_no_ipython_escape_command(&output.tokens);
2212 }
2213
2214 #[test]
2215 fn test_numbers() {
2216 let source = "0x2f 0o12 0b1101 0 123 123_45_67_890 0.2 1e+2 2.1e3 2j 2.2j 000 0x995DC9BBDF1939FA 0x995DC9BBDF1939FA995DC9BBDF1939FA";
2217 assert_snapshot!(lex_source(source));
2218 }
2219
2220 #[test]
2221 fn test_invalid_leading_zero_small() {
2222 let source = "025";
2223 assert_snapshot!(lex_invalid(source, Mode::Module));
2224 }
2225
2226 #[test]
2227 fn test_invalid_leading_zero_big() {
2228 let source =
2229 "0252222222222222522222222222225222222222222252222222222222522222222222225222222222222";
2230 assert_snapshot!(lex_invalid(source, Mode::Module));
2231 }
2232
2233 #[test]
2234 fn test_line_comment_long() {
2235 let source = "99232 # foo".to_string();
2236 assert_snapshot!(lex_source(&source));
2237 }
2238
2239 #[test]
2240 fn test_line_comment_whitespace() {
2241 let source = "99232 # ".to_string();
2242 assert_snapshot!(lex_source(&source));
2243 }
2244
2245 #[test]
2246 fn test_line_comment_single_whitespace() {
2247 let source = "99232 # ".to_string();
2248 assert_snapshot!(lex_source(&source));
2249 }
2250
2251 #[test]
2252 fn test_line_comment_empty() {
2253 let source = "99232 #".to_string();
2254 assert_snapshot!(lex_source(&source));
2255 }
2256
2257 fn comment_until_eol(eol: &str) -> LexerOutput {
2258 let source = format!("123 # Foo{eol}456");
2259 lex_source(&source)
2260 }
2261
2262 #[test]
2263 fn test_comment_until_unix_eol() {
2264 assert_snapshot!(comment_until_eol(UNIX_EOL));
2265 }
2266
2267 #[test]
2268 fn test_comment_until_mac_eol() {
2269 assert_snapshot!(comment_until_eol(MAC_EOL));
2270 }
2271
2272 #[test]
2273 fn test_comment_until_windows_eol() {
2274 assert_snapshot!(comment_until_eol(WINDOWS_EOL));
2275 }
2276
2277 #[test]
2278 fn test_assignment() {
2279 let source = r"a_variable = 99 + 2-0";
2280 assert_snapshot!(lex_source(source));
2281 }
2282
2283 fn indentation_with_eol(eol: &str) -> LexerOutput {
2284 let source = format!("def foo():{eol} return 99{eol}{eol}");
2285 lex_source(&source)
2286 }
2287
2288 #[test]
2289 fn test_indentation_with_unix_eol() {
2290 assert_snapshot!(indentation_with_eol(UNIX_EOL));
2291 }
2292
2293 #[test]
2294 fn test_indentation_with_mac_eol() {
2295 assert_snapshot!(indentation_with_eol(MAC_EOL));
2296 }
2297
2298 #[test]
2299 fn test_indentation_with_windows_eol() {
2300 assert_snapshot!(indentation_with_eol(WINDOWS_EOL));
2301 }
2302
2303 fn double_dedent_with_eol(eol: &str) -> LexerOutput {
2304 let source = format!("def foo():{eol} if x:{eol}{eol} return 99{eol}{eol}");
2305 lex_source(&source)
2306 }
2307
2308 #[test]
2309 fn test_double_dedent_with_unix_eol() {
2310 assert_snapshot!(double_dedent_with_eol(UNIX_EOL));
2311 }
2312
2313 #[test]
2314 fn test_double_dedent_with_mac_eol() {
2315 assert_snapshot!(double_dedent_with_eol(MAC_EOL));
2316 }
2317
2318 #[test]
2319 fn test_double_dedent_with_windows_eol() {
2320 assert_snapshot!(double_dedent_with_eol(WINDOWS_EOL));
2321 }
2322
2323 fn double_dedent_with_tabs_eol(eol: &str) -> LexerOutput {
2324 let source = format!("def foo():{eol}\tif x:{eol}{eol}\t\t return 99{eol}{eol}");
2325 lex_source(&source)
2326 }
2327
2328 #[test]
2329 fn test_double_dedent_with_tabs_unix_eol() {
2330 assert_snapshot!(double_dedent_with_tabs_eol(UNIX_EOL));
2331 }
2332
2333 #[test]
2334 fn test_double_dedent_with_tabs_mac_eol() {
2335 assert_snapshot!(double_dedent_with_tabs_eol(MAC_EOL));
2336 }
2337
2338 #[test]
2339 fn test_double_dedent_with_tabs_windows_eol() {
2340 assert_snapshot!(double_dedent_with_tabs_eol(WINDOWS_EOL));
2341 }
2342
2343 #[test]
2344 fn dedent_after_whitespace() {
2345 let source = "\
2346if first:
2347 if second:
2348 pass
2349 foo
2350";
2351 assert_snapshot!(lex_source(source));
2352 }
2353
2354 fn newline_in_brackets_eol(eol: &str) -> LexerOutput {
2355 let source = r"x = [
2356
2357 1,2
2358,(3,
23594,
2360), {
23615,
23626,\
23637}]
2364"
2365 .replace('\n', eol);
2366 lex_source(&source)
2367 }
2368
2369 #[test]
2370 fn test_newline_in_brackets_unix_eol() {
2371 assert_snapshot!(newline_in_brackets_eol(UNIX_EOL));
2372 }
2373
2374 #[test]
2375 fn test_newline_in_brackets_mac_eol() {
2376 assert_snapshot!(newline_in_brackets_eol(MAC_EOL));
2377 }
2378
2379 #[test]
2380 fn test_newline_in_brackets_windows_eol() {
2381 assert_snapshot!(newline_in_brackets_eol(WINDOWS_EOL));
2382 }
2383
2384 #[test]
2385 fn test_non_logical_newline_in_string_continuation() {
2386 let source = r"(
2387 'a'
2388 'b'
2389
2390 'c' \
2391 'd'
2392)";
2393 assert_snapshot!(lex_source(source));
2394 }
2395
2396 #[test]
2397 fn test_logical_newline_line_comment() {
2398 let source = "#Hello\n#World\n";
2399 assert_snapshot!(lex_source(source));
2400 }
2401
2402 #[test]
2403 fn test_operators() {
2404 let source = "//////=/ /";
2405 assert_snapshot!(lex_source(source));
2406 }
2407
2408 #[test]
2409 fn test_string() {
2410 let source = r#""double" 'single' 'can\'t' "\\\"" '\t\r\n' '\g' r'raw\'' '\420' '\200\0a'"#;
2411 assert_snapshot!(lex_source(source));
2412 }
2413
2414 fn string_continuation_with_eol(eol: &str) -> LexerOutput {
2415 let source = format!("\"abc\\{eol}def\"");
2416 lex_source(&source)
2417 }
2418
2419 #[test]
2420 fn test_string_continuation_with_unix_eol() {
2421 assert_snapshot!(string_continuation_with_eol(UNIX_EOL));
2422 }
2423
2424 #[test]
2425 fn test_string_continuation_with_mac_eol() {
2426 assert_snapshot!(string_continuation_with_eol(MAC_EOL));
2427 }
2428
2429 #[test]
2430 fn test_string_continuation_with_windows_eol() {
2431 assert_snapshot!(string_continuation_with_eol(WINDOWS_EOL));
2432 }
2433
2434 #[test]
2435 fn test_escape_unicode_name() {
2436 let source = r#""\N{EN SPACE}""#;
2437 assert_snapshot!(lex_source(source));
2438 }
2439
2440 #[test]
2441 fn non_ascii_identifier_flag() {
2442 let mut lexer = Lexer::new("a€\naβ = β\nascii", Mode::Module, TextSize::default());
2443 let mut flags = Vec::new();
2444 loop {
2445 let kind = lexer.next_token();
2446 if kind.is_eof() {
2447 break;
2448 }
2449 if kind == TokenKind::Identifier {
2450 flags.push(lexer.current_flags().is_non_ascii_identifier());
2451 }
2452 }
2453
2454 assert_eq!(lexer.finish().len(), 1);
2455 assert_eq!(flags, [false, true, true, false]);
2456 }
2457
2458 fn triple_quoted_eol(eol: &str) -> LexerOutput {
2459 let source = format!("\"\"\"{eol} test string{eol} \"\"\"");
2460 lex_source(&source)
2461 }
2462
2463 #[test]
2464 fn test_triple_quoted_unix_eol() {
2465 assert_snapshot!(triple_quoted_eol(UNIX_EOL));
2466 }
2467
2468 #[test]
2469 fn test_triple_quoted_mac_eol() {
2470 assert_snapshot!(triple_quoted_eol(MAC_EOL));
2471 }
2472
2473 #[test]
2474 fn test_triple_quoted_windows_eol() {
2475 assert_snapshot!(triple_quoted_eol(WINDOWS_EOL));
2476 }
2477
2478 fn line_continuation_at_eof_after_newline(eol: &str) -> LexerOutput {
2479 let source = format!(r"\{eol}");
2480 lex_invalid(&source, Mode::Module)
2481 }
2482
2483 #[test]
2484 fn test_line_continuation_at_eof_after_newline_unix_eol() {
2485 assert_snapshot!(line_continuation_at_eof_after_newline(UNIX_EOL));
2486 }
2487
2488 #[test]
2489 fn test_line_continuation_at_eof_after_newline_mac_eol() {
2490 assert_snapshot!(line_continuation_at_eof_after_newline(MAC_EOL));
2491 }
2492
2493 #[test]
2494 fn test_line_continuation_at_eof_after_newline_windows_eol() {
2495 assert_snapshot!(line_continuation_at_eof_after_newline(WINDOWS_EOL));
2496 }
2497
2498 fn line_continuation_at_eof(eol: &str) -> LexerOutput {
2499 let source = format!(r"1, \{eol}");
2500 lex_invalid(&source, Mode::Module)
2501 }
2502
2503 #[test]
2504 fn test_line_continuation_at_eof_unix_eol() {
2505 assert_snapshot!(line_continuation_at_eof(UNIX_EOL));
2506 }
2507
2508 #[test]
2509 fn test_line_continuation_at_eof_mac_eol() {
2510 assert_snapshot!(line_continuation_at_eof(MAC_EOL));
2511 }
2512
2513 #[test]
2514 fn test_line_continuation_at_eof_windows_eol() {
2515 assert_snapshot!(line_continuation_at_eof(WINDOWS_EOL));
2516 }
2517
2518 #[test]
2521 fn test_infinite_loop() {
2522 let source = "[1";
2523 lex_invalid(source, Mode::Module);
2524 }
2525
2526 #[test]
2528 fn test_emoji_identifier() {
2529 let source = "🐦";
2530 assert_snapshot!(lex_invalid(source, Mode::Module));
2531 }
2532
2533 #[test]
2534 fn tet_too_low_dedent() {
2535 let source = "if True:
2536 pass
2537 pass";
2538 assert_snapshot!(lex_invalid(source, Mode::Module));
2539 }
2540
2541 #[test]
2542 fn test_empty_fstrings() {
2543 let source = r#"f"" "" F"" f'' '' f"""""" f''''''"#;
2544 assert_snapshot!(lex_source(source));
2545 }
2546
2547 #[test]
2548 fn test_fstring_prefix() {
2549 let source = r#"f"" F"" rf"" rF"" Rf"" RF"" fr"" Fr"" fR"" FR"""#;
2550 assert_snapshot!(lex_source(source));
2551 }
2552
2553 #[test]
2554 fn test_fstring() {
2555 let source = r#"f"normal {foo} {{another}} {bar} {{{three}}}""#;
2556 assert_snapshot!(lex_source(source));
2557 }
2558
2559 #[test]
2560 fn test_fstring_parentheses() {
2561 let source = r#"f"{}" f"{{}}" f" {}" f"{{{}}}" f"{{{{}}}}" f" {} {{}} {{{}}} {{{{}}}} ""#;
2562 assert_snapshot!(lex_source(source));
2563 }
2564
2565 fn fstring_single_quote_escape_eol(eol: &str) -> LexerOutput {
2566 let source = format!(r"f'text \{eol} more text'");
2567 lex_source(&source)
2568 }
2569
2570 #[test]
2571 fn test_fstring_single_quote_escape_unix_eol() {
2572 assert_snapshot!(fstring_single_quote_escape_eol(UNIX_EOL));
2573 }
2574
2575 #[test]
2576 fn test_fstring_single_quote_escape_mac_eol() {
2577 assert_snapshot!(fstring_single_quote_escape_eol(MAC_EOL));
2578 }
2579
2580 #[test]
2581 fn test_fstring_single_quote_escape_windows_eol() {
2582 assert_snapshot!(fstring_single_quote_escape_eol(WINDOWS_EOL));
2583 }
2584
2585 #[test]
2586 fn test_fstring_escape() {
2587 let source = r#"f"\{x:\"\{x}} \"\"\
2588 end""#;
2589 assert_snapshot!(lex_source(source));
2590 }
2591
2592 #[test]
2593 fn test_fstring_escape_braces() {
2594 let source = r"f'\{foo}' f'\\{foo}' f'\{{foo}}' f'\\{{foo}}'";
2595 assert_snapshot!(lex_source(source));
2596 }
2597
2598 #[test]
2599 fn test_fstring_escape_raw() {
2600 let source = r#"rf"\{x:\"\{x}} \"\"\
2601 end""#;
2602 assert_snapshot!(lex_source(source));
2603 }
2604
2605 #[test]
2606 fn test_fstring_named_unicode() {
2607 let source = r#"f"\N{BULLET} normal \Nope \N""#;
2608 assert_snapshot!(lex_source(source));
2609 }
2610
2611 #[test]
2612 fn test_fstring_named_unicode_raw() {
2613 let source = r#"rf"\N{BULLET} normal""#;
2614 assert_snapshot!(lex_source(source));
2615 }
2616
2617 #[test]
2618 fn test_fstring_with_named_expression() {
2619 let source = r#"f"{x:=10} {(x:=10)} {x,{y:=10}} {[x:=10]}""#;
2620 assert_snapshot!(lex_source(source));
2621 }
2622
2623 #[test]
2624 fn test_fstring_with_format_spec() {
2625 let source = r#"f"{foo:} {x=!s:.3f} {x:.{y}f} {'':*^{1:{1}}} {x:{{1}.pop()}}""#;
2626 assert_snapshot!(lex_source(source));
2627 }
2628
2629 #[test]
2630 fn test_fstring_with_multiline_format_spec() {
2631 let source = r"f'''__{
2634 x:d
2635}__'''
2636f'''__{
2637 x:a
2638 b
2639 c
2640}__'''
2641";
2642 assert_snapshot!(lex_source(source));
2643 }
2644
2645 #[test]
2646 fn test_fstring_newline_format_spec() {
2647 let source = r"
2648f'__{
2649 x:d
2650}__'
2651f'__{
2652 x:a
2653 b
2654}__'
2655";
2656 assert_snapshot!(lex_invalid(source, Mode::Module));
2657 }
2658
2659 #[test]
2660 fn test_fstring_conversion() {
2661 let source = r#"f"{x!s} {x=!r} {x:.3f!r} {{x!r}}""#;
2662 assert_snapshot!(lex_source(source));
2663 }
2664
2665 #[test]
2666 fn test_fstring_nested() {
2667 let source = r#"f"foo {f"bar {x + f"{wow}"}"} baz" f'foo {f'bar'} some {f"another"}'"#;
2668 assert_snapshot!(lex_source(source));
2669 }
2670
2671 #[test]
2672 fn test_fstring_expression_multiline() {
2673 let source = r#"f"first {
2674 x
2675 *
2676 y
2677} second""#;
2678 assert_snapshot!(lex_source(source));
2679 }
2680
2681 #[test]
2682 fn test_fstring_multiline() {
2683 let source = r#"f"""
2684hello
2685 world
2686""" f'''
2687 world
2688hello
2689''' f"some {f"""multiline
2690allowed {x}"""} string""#;
2691 assert_snapshot!(lex_source(source));
2692 }
2693
2694 #[test]
2695 fn test_fstring_comments() {
2696 let source = r#"f"""
2697# not a comment { # comment {
2698 x
2699} # not a comment
2700""""#;
2701 assert_snapshot!(lex_source(source));
2702 }
2703
2704 #[test]
2705 fn test_fstring_with_ipy_escape_command() {
2706 let source = r#"f"foo {!pwd} bar""#;
2707 assert_snapshot!(lex_source(source));
2708 }
2709
2710 #[test]
2711 fn test_fstring_with_lambda_expression() {
2712 let source = r#"
2713f"{lambda x:{x}}"
2714f"{(lambda x:{x})}"
2715"#
2716 .trim();
2717 assert_snapshot!(lex_source(source));
2718 }
2719
2720 #[test]
2721 fn test_fstring_with_nul_char() {
2722 let source = r"f'\0'";
2723 assert_snapshot!(lex_source(source));
2724 }
2725
2726 #[test]
2727 fn test_empty_tstrings() {
2728 let source = r#"t"" "" t"" t'' '' t"""""" t''''''"#;
2729 assert_snapshot!(lex_source(source));
2730 }
2731
2732 #[test]
2733 fn test_tstring_prefix() {
2734 let source = r#"t"" t"" rt"" rt"" Rt"" Rt"" tr"" Tr"" tR"" TR"""#;
2735 assert_snapshot!(lex_source(source));
2736 }
2737
2738 #[test]
2739 fn test_tstring() {
2740 let source = r#"t"normal {foo} {{another}} {bar} {{{three}}}""#;
2741 assert_snapshot!(lex_source(source));
2742 }
2743
2744 #[test]
2745 fn test_tstring_parentheses() {
2746 let source = r#"t"{}" t"{{}}" t" {}" t"{{{}}}" t"{{{{}}}}" t" {} {{}} {{{}}} {{{{}}}} ""#;
2747 assert_snapshot!(lex_source(source));
2748 }
2749
2750 fn tstring_single_quote_escape_eol(eol: &str) -> LexerOutput {
2751 let source = format!(r"t'text \{eol} more text'");
2752 lex_source(&source)
2753 }
2754
2755 #[test]
2756 fn test_tstring_single_quote_escape_unix_eol() {
2757 assert_snapshot!(tstring_single_quote_escape_eol(UNIX_EOL));
2758 }
2759
2760 #[test]
2761 fn test_tstring_single_quote_escape_mac_eol() {
2762 assert_snapshot!(tstring_single_quote_escape_eol(MAC_EOL));
2763 }
2764
2765 #[test]
2766 fn test_tstring_single_quote_escape_windows_eol() {
2767 assert_snapshot!(tstring_single_quote_escape_eol(WINDOWS_EOL));
2768 }
2769
2770 #[test]
2771 fn test_tstring_escape() {
2772 let source = r#"t"\{x:\"\{x}} \"\"\
2773 end""#;
2774 assert_snapshot!(lex_source(source));
2775 }
2776
2777 #[test]
2778 fn test_tstring_escape_braces() {
2779 let source = r"t'\{foo}' t'\\{foo}' t'\{{foo}}' t'\\{{foo}}'";
2780 assert_snapshot!(lex_source(source));
2781 }
2782
2783 #[test]
2784 fn test_tstring_escape_raw() {
2785 let source = r#"rt"\{x:\"\{x}} \"\"\
2786 end""#;
2787 assert_snapshot!(lex_source(source));
2788 }
2789
2790 #[test]
2791 fn test_tstring_named_unicode() {
2792 let source = r#"t"\N{BULLET} normal \Nope \N""#;
2793 assert_snapshot!(lex_source(source));
2794 }
2795
2796 #[test]
2797 fn test_tstring_named_unicode_raw() {
2798 let source = r#"rt"\N{BULLET} normal""#;
2799 assert_snapshot!(lex_source(source));
2800 }
2801
2802 #[test]
2803 fn test_tstring_with_named_expression() {
2804 let source = r#"t"{x:=10} {(x:=10)} {x,{y:=10}} {[x:=10]}""#;
2805 assert_snapshot!(lex_source(source));
2806 }
2807
2808 #[test]
2809 fn test_tstring_with_format_spec() {
2810 let source = r#"t"{foo:} {x=!s:.3f} {x:.{y}f} {'':*^{1:{1}}} {x:{{1}.pop()}}""#;
2811 assert_snapshot!(lex_source(source));
2812 }
2813
2814 #[test]
2815 fn test_tstring_with_multiline_format_spec() {
2816 let source = r"t'''__{
2819 x:d
2820}__'''
2821t'''__{
2822 x:a
2823 b
2824 c
2825}__'''
2826";
2827 assert_snapshot!(lex_source(source));
2828 }
2829
2830 #[test]
2831 fn test_tstring_newline_format_spec() {
2832 let source = r"
2833t'__{
2834 x:d
2835}__'
2836t'__{
2837 x:a
2838 b
2839}__'
2840";
2841 assert_snapshot!(lex_invalid(source, Mode::Module));
2842 }
2843
2844 #[test]
2845 fn test_tstring_conversion() {
2846 let source = r#"t"{x!s} {x=!r} {x:.3f!r} {{x!r}}""#;
2847 assert_snapshot!(lex_source(source));
2848 }
2849
2850 #[test]
2851 fn test_tstring_nested() {
2852 let source = r#"t"foo {t"bar {x + t"{wow}"}"} baz" t'foo {t'bar'} some {t"another"}'"#;
2853 assert_snapshot!(lex_source(source));
2854 }
2855
2856 #[test]
2857 fn test_tstring_expression_multiline() {
2858 let source = r#"t"first {
2859 x
2860 *
2861 y
2862} second""#;
2863 assert_snapshot!(lex_source(source));
2864 }
2865
2866 #[test]
2867 fn test_tstring_multiline() {
2868 let source = r#"t"""
2869hello
2870 world
2871""" t'''
2872 world
2873hello
2874''' t"some {t"""multiline
2875allowed {x}"""} string""#;
2876 assert_snapshot!(lex_source(source));
2877 }
2878
2879 #[test]
2880 fn test_tstring_comments() {
2881 let source = r#"t"""
2882# not a comment { # comment {
2883 x
2884} # not a comment
2885""""#;
2886 assert_snapshot!(lex_source(source));
2887 }
2888
2889 #[test]
2890 fn test_tstring_with_ipy_escape_command() {
2891 let source = r#"t"foo {!pwd} bar""#;
2892 assert_snapshot!(lex_source(source));
2893 }
2894
2895 #[test]
2896 fn test_tstring_with_lambda_expression() {
2897 let source = r#"
2898t"{lambda x:{x}}"
2899t"{(lambda x:{x})}"
2900"#
2901 .trim();
2902 assert_snapshot!(lex_source(source));
2903 }
2904
2905 #[test]
2906 fn test_tstring_with_nul_char() {
2907 let source = r"t'\0'";
2908 assert_snapshot!(lex_source(source));
2909 }
2910
2911 #[test]
2912 fn test_nested_t_and_fstring() {
2913 let source = r#"t"foo {f"bar {x + t"{wow}"}"} baz" f'foo {t'bar'!r} some {f"another"}'"#;
2914 assert_snapshot!(lex_source(source));
2915 }
2916
2917 #[test]
2918 fn test_match_softkeyword_in_notebook() {
2919 let source = r"match foo:
2920 case bar:
2921 pass";
2922 assert_snapshot!(lex_jupyter_source(source));
2923 }
2924
2925 fn lex_fstring_error(source: &str) -> InterpolatedStringErrorType {
2926 let output = lex(source, Mode::Module, TextSize::default());
2927 match output
2928 .errors
2929 .into_iter()
2930 .next()
2931 .expect("lexer should give at least one error")
2932 .into_error()
2933 {
2934 LexicalErrorType::FStringError(error) => error,
2935 err => panic!("Expected FStringError: {err:?}"),
2936 }
2937 }
2938
2939 #[test]
2940 fn test_fstring_error() {
2941 use InterpolatedStringErrorType::{
2942 SingleRbrace, UnterminatedString, UnterminatedTripleQuotedString,
2943 };
2944
2945 assert_eq!(lex_fstring_error("f'}'"), SingleRbrace);
2946 assert_eq!(lex_fstring_error("f'{{}'"), SingleRbrace);
2947 assert_eq!(lex_fstring_error("f'{{}}}'"), SingleRbrace);
2948 assert_eq!(lex_fstring_error("f'foo}'"), SingleRbrace);
2949 assert_eq!(lex_fstring_error(r"f'\u007b}'"), SingleRbrace);
2950 assert_eq!(lex_fstring_error("f'{a:b}}'"), SingleRbrace);
2951 assert_eq!(lex_fstring_error("f'{3:}}>10}'"), SingleRbrace);
2952 assert_eq!(lex_fstring_error(r"f'\{foo}\}'"), SingleRbrace);
2953
2954 assert!(matches!(
2955 lex_fstring_error(r#"f""#),
2956 UnterminatedString { .. }
2957 ));
2958 assert!(matches!(
2959 lex_fstring_error(r"f'"),
2960 UnterminatedString { .. }
2961 ));
2962
2963 assert!(matches!(
2964 lex_fstring_error(r#"f""""#),
2965 UnterminatedTripleQuotedString { .. }
2966 ));
2967 assert!(matches!(
2968 lex_fstring_error(r"f'''"),
2969 UnterminatedTripleQuotedString { .. }
2970 ));
2971 assert!(matches!(
2972 lex_fstring_error(r#"f"""""#),
2973 UnterminatedTripleQuotedString { .. }
2974 ));
2975 assert!(matches!(
2976 lex_fstring_error(r#"f""""""#),
2977 UnterminatedTripleQuotedString { .. }
2978 ));
2979 }
2980
2981 fn lex_tstring_error(source: &str) -> InterpolatedStringErrorType {
2982 let output = lex(source, Mode::Module, TextSize::default());
2983 match output
2984 .errors
2985 .into_iter()
2986 .next()
2987 .expect("lexer should give at least one error")
2988 .into_error()
2989 {
2990 LexicalErrorType::TStringError(error) => error,
2991 err => panic!("Expected TStringError: {err:?}"),
2992 }
2993 }
2994
2995 #[test]
2996 fn lex_fstring_unclosed() {
2997 let source = r#"f"hello"#;
2998
2999 assert_snapshot!(lex_invalid(source, Mode::Module), @"
3000 ## Tokens
3001 ```
3002 [
3003 FStringStart 0..2 (flags = DOUBLE_QUOTES | F_STRING),
3004 FStringMiddle 2..7 (flags = DOUBLE_QUOTES | F_STRING),
3005 Newline 7..7,
3006 ]
3007 ```
3008 ## Errors
3009 ```
3010 [
3011 LexicalError {
3012 error: FStringError(
3013 UnterminatedString {
3014 detected_line: 1,
3015 },
3016 ),
3017 location: 0..0,
3018 },
3019 ]
3020 ```
3021 ");
3022 }
3023
3024 #[test]
3025 fn lex_fstring_missing_brace() {
3026 let source = "f'{'";
3027
3028 assert_snapshot!(lex_invalid(source, Mode::Module), @"
3029 ## Tokens
3030 ```
3031 [
3032 FStringStart 0..2 (flags = F_STRING),
3033 Lbrace 2..3,
3034 String 3..4 (flags = UNCLOSED_STRING),
3035 Newline 4..4,
3036 ]
3037 ```
3038 ## Errors
3039 ```
3040 [
3041 LexicalError {
3042 error: FStringError(
3043 UnclosedLbrace,
3044 ),
3045 location: 3..3,
3046 },
3047 LexicalError {
3048 error: UnclosedBracket {
3049 opening: '{',
3050 incomplete: true,
3051 },
3052 location: 2..2,
3053 },
3054 ]
3055 ```
3056 ");
3057 }
3058
3059 #[test]
3060 fn lex_fstring_missing_brace_after_format_spec() {
3061 let source = r#"f"{foo!r""#;
3062
3063 assert_snapshot!(lex_invalid(source, Mode::Module), @"
3064 ## Tokens
3065 ```
3066 [
3067 FStringStart 0..2 (flags = DOUBLE_QUOTES | F_STRING),
3068 Lbrace 2..3,
3069 Identifier 3..6,
3070 Exclamation 6..7,
3071 String 7..9 (flags = DOUBLE_QUOTES | RAW_STRING_LOWERCASE | UNCLOSED_STRING),
3072 Newline 9..9,
3073 ]
3074 ```
3075 ## Errors
3076 ```
3077 [
3078 LexicalError {
3079 error: FStringError(
3080 UnclosedLbrace,
3081 ),
3082 location: 7..7,
3083 },
3084 LexicalError {
3085 error: UnclosedBracket {
3086 opening: '{',
3087 incomplete: true,
3088 },
3089 location: 2..2,
3090 },
3091 ]
3092 ```
3093 ");
3094 }
3095
3096 #[test]
3097 fn test_tstring_error() {
3098 use InterpolatedStringErrorType::{
3099 SingleRbrace, UnterminatedString, UnterminatedTripleQuotedString,
3100 };
3101
3102 assert_eq!(lex_tstring_error("t'}'"), SingleRbrace);
3103 assert_eq!(lex_tstring_error("t'{{}'"), SingleRbrace);
3104 assert_eq!(lex_tstring_error("t'{{}}}'"), SingleRbrace);
3105 assert_eq!(lex_tstring_error("t'foo}'"), SingleRbrace);
3106 assert_eq!(lex_tstring_error(r"t'\u007b}'"), SingleRbrace);
3107 assert_eq!(lex_tstring_error("t'{a:b}}'"), SingleRbrace);
3108 assert_eq!(lex_tstring_error("t'{3:}}>10}'"), SingleRbrace);
3109 assert_eq!(lex_tstring_error(r"t'\{foo}\}'"), SingleRbrace);
3110
3111 assert!(matches!(
3112 lex_tstring_error(r#"t""#),
3113 UnterminatedString { .. }
3114 ));
3115 assert!(matches!(
3116 lex_tstring_error(r"t'"),
3117 UnterminatedString { .. }
3118 ));
3119
3120 assert!(matches!(
3121 lex_tstring_error(r#"t""""#),
3122 UnterminatedTripleQuotedString { .. }
3123 ));
3124 assert!(matches!(
3125 lex_tstring_error(r"t'''"),
3126 UnterminatedTripleQuotedString { .. }
3127 ));
3128 assert!(matches!(
3129 lex_tstring_error(r#"t"""""#),
3130 UnterminatedTripleQuotedString { .. }
3131 ));
3132 assert!(matches!(
3133 lex_tstring_error(r#"t""""""#),
3134 UnterminatedTripleQuotedString { .. }
3135 ));
3136 }
3137
3138 #[test]
3139 fn backslash_continuation_indentation() {
3140 let source = r"if True:
3145 1
3146 \
3147 2
3148 \
31493
3150else:
3151 pass
3152"
3153 .to_string();
3154 assert_snapshot!(lex_source(&source));
3155 }
3156
3157 #[test]
3158 fn backslash_continuation_at_root() {
3159 let source = r"if True:
3164\
3165 1
3166 if True:
3167\
3168 2
3169else:\
3170 3
3171"
3172 .to_string();
3173 assert_snapshot!(lex_source(&source));
3174 }
3175
3176 #[test]
3177 fn multiple_backslash_continuation() {
3178 let source = r"if True:
3182 1
3183 \
3184 \
3185 \
3186 \
3187 2
3188"
3189 .to_string();
3190 assert_snapshot!(lex_source(&source));
3191 }
3192
3193 #[test]
3194 fn backslash_continuation_mismatch_indentation() {
3195 let source = r"if True:
3197 1
3198 \
3199 2
3200"
3201 .to_string();
3202 assert_snapshot!(lex_invalid(&source, Mode::Module));
3203 }
3204}