Skip to main content

ruff_python_parser/
lexer.rs

1//! This module takes care of lexing Python source text.
2//!
3//! This means source code is scanned and translated into separate tokens. The rules
4//! governing what is and is not a valid token are defined in the Python reference
5//! guide section on [Lexical analysis].
6//!
7//! [Lexical analysis]: https://docs.python.org/3/reference/lexical_analysis.html
8
9use 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::{InterpolatedStringErrorType, LexicalError, LexicalErrorType};
21use crate::lexer::cursor::{Cursor, EOF_CHAR};
22use crate::lexer::indentation::{Indentation, Indentations, IndentationsCheckpoint};
23use crate::lexer::interpolated_string::{
24    InterpolatedStringContext, InterpolatedStrings, InterpolatedStringsCheckpoint,
25};
26use crate::string::InterpolatedStringKind;
27
28mod cursor;
29mod indentation;
30mod interpolated_string;
31
32const BOM: char = '\u{feff}';
33
34/// A lexer for Python source code.
35#[derive(Debug)]
36pub struct Lexer<'src> {
37    /// Source code to be lexed.
38    source: &'src str,
39
40    /// A pointer to the current character of the source code which is being lexed.
41    cursor: Cursor<'src>,
42
43    /// The kind of the current token.
44    current_kind: TokenKind,
45
46    /// The range of the current token.
47    current_range: TextRange,
48
49    /// Flags for the current token.
50    current_flags: TokenFlags,
51
52    /// Lexer state.
53    state: State,
54
55    /// Represents the current level of nesting in the lexer, indicating the depth of parentheses.
56    /// The lexer is within a parenthesized context if the value is greater than 0.
57    nesting: u32,
58
59    /// A stack of indentation representing the current indentation level.
60    indentations: Indentations,
61    pending_indentation: Option<Indentation>,
62
63    /// Lexer mode.
64    mode: Mode,
65
66    /// F-string and t-string contexts.
67    interpolated_strings: InterpolatedStrings,
68
69    /// Errors encountered while lexing.
70    errors: Vec<LexicalError>,
71}
72
73impl<'src> Lexer<'src> {
74    /// Create a new lexer for the given input source which starts at the given offset.
75    ///
76    /// If the start offset is greater than 0, the cursor is moved ahead that many bytes.
77    /// This means that the input source should be the complete source code and not the
78    /// sliced version.
79    pub(crate) fn new(source: &'src str, mode: Mode, start_offset: TextSize) -> Self {
80        assert!(
81            u32::try_from(source.len()).is_ok(),
82            "Lexer only supports files with a size up to 4GB"
83        );
84
85        let (state, nesting) = if mode == Mode::ParenthesizedExpression {
86            (State::Other, 1)
87        } else {
88            (State::AfterNewline, 0)
89        };
90
91        let mut lexer = Lexer {
92            source,
93            cursor: Cursor::new(source),
94            state,
95            current_kind: TokenKind::EndOfFile,
96            current_range: TextRange::empty(start_offset),
97            current_flags: TokenFlags::empty(),
98            nesting,
99            indentations: Indentations::default(),
100            pending_indentation: None,
101            mode,
102            interpolated_strings: InterpolatedStrings::default(),
103            errors: Vec::new(),
104        };
105
106        if start_offset == TextSize::new(0) {
107            // TODO: Handle possible mismatch between BOM and explicit encoding declaration.
108            lexer.cursor.eat_char(BOM);
109        } else {
110            lexer.cursor.skip_bytes(start_offset.to_usize());
111        }
112
113        lexer
114    }
115
116    /// Returns the kind of the current token.
117    pub(crate) const fn current_kind(&self) -> TokenKind {
118        self.current_kind
119    }
120
121    /// Returns the range of the current token.
122    pub(crate) const fn current_range(&self) -> TextRange {
123        self.current_range
124    }
125
126    /// Returns the flags for the current token.
127    pub(crate) const fn current_flags(&self) -> TokenFlags {
128        self.current_flags
129    }
130
131    /// Helper function to push the given error, updating the current range with the error location
132    /// and return the [`TokenKind::Unknown`] token.
133    fn push_error(&mut self, error: LexicalError) -> TokenKind {
134        self.current_range = error.location();
135        self.errors.push(error);
136        TokenKind::Unknown
137    }
138
139    /// Lex the next token.
140    pub fn next_token(&mut self) -> TokenKind {
141        // `lex_token` marks the start on the path that lexes each token.
142        self.current_flags = TokenFlags::empty();
143        self.current_kind = self.lex_token();
144        // For `Unknown` token, the `push_error` method updates the current range.
145        if !matches!(self.current_kind, TokenKind::Unknown) {
146            self.current_range = self.token_range();
147        }
148        self.current_kind
149    }
150
151    fn lex_token(&mut self) -> TokenKind {
152        if let Some(interpolated_string) = self.interpolated_strings.current() {
153            if !interpolated_string.is_in_interpolation(self.nesting) {
154                self.cursor.start_token();
155                if let Some(token) = self.lex_interpolated_string_middle_or_end() {
156                    if token.is_interpolated_string_end() {
157                        self.interpolated_strings.pop();
158                    }
159                    return token;
160                }
161            }
162        }
163        // Return dedent tokens until the current indentation level matches the indentation of the next token.
164        // Avoid `Option::take` here: this check runs for every token, and `take` writes `None`
165        // even when there is no pending indentation.
166        else if let Some(indentation) = self.pending_indentation {
167            self.pending_indentation = None;
168            self.cursor.start_token();
169            match self.indentations.current().try_compare(indentation) {
170                Ok(Ordering::Greater) => {
171                    self.pending_indentation = Some(indentation);
172                    if self.indentations.dedent_one(indentation).is_err() {
173                        return self.push_error(LexicalError::new(
174                            LexicalErrorType::IndentationError,
175                            self.token_range(),
176                        ));
177                    }
178                    return TokenKind::Dedent;
179                }
180                Ok(_) => {}
181                Err(_) => {
182                    return self.push_error(LexicalError::new(
183                        LexicalErrorType::IndentationError,
184                        self.token_range(),
185                    ));
186                }
187            }
188        }
189
190        if self.state.is_after_newline() {
191            // Indent and dedent tokens include leading whitespace in their ranges.
192            self.cursor.start_token();
193            if let Some(indentation) = self.eat_indentation() {
194                return indentation;
195            }
196        } else {
197            if let Err(error) = self.skip_whitespace() {
198                return self.push_error(error);
199            }
200        }
201
202        // Whitespace between tokens is not part of the next token's range.
203        self.cursor.start_token();
204
205        if let Some(c) = self.cursor.bump() {
206            if c.is_ascii() {
207                self.consume_ascii_character(c)
208            } else if is_unicode_identifier_start(c) {
209                let identifier = self.lex_identifier(c);
210                self.state = State::Other;
211
212                identifier
213            } else {
214                self.push_error(LexicalError::new(
215                    LexicalErrorType::UnrecognizedToken { tok: c },
216                    self.token_range(),
217                ))
218            }
219        } else {
220            // Reached the end of the file. Emit a trailing newline token if not at the beginning of a logical line,
221            // empty the dedent stack, and finally, return the EndOfFile token.
222            self.consume_end()
223        }
224    }
225
226    fn eat_indentation(&mut self) -> Option<TokenKind> {
227        let mut indentation = Indentation::root();
228
229        loop {
230            match self.cursor.first() {
231                ' ' => {
232                    self.cursor.bump();
233                    indentation = indentation.add_space();
234                }
235                '\t' => {
236                    self.cursor.bump();
237                    indentation = indentation.add_tab();
238                }
239                '\\' => {
240                    self.cursor.bump();
241                    if self.cursor.eat_char('\r') {
242                        self.cursor.eat_char('\n');
243                    } else if !self.cursor.eat_char('\n') {
244                        return Some(self.push_error(LexicalError::new(
245                            LexicalErrorType::LineContinuationError,
246                            TextRange::at(self.offset() - '\\'.text_len(), '\\'.text_len()),
247                        )));
248                    }
249                    if self.cursor.is_eof() {
250                        return Some(self.push_error(LexicalError::new(
251                            LexicalErrorType::Eof,
252                            self.token_range(),
253                        )));
254                    }
255                    // test_ok backslash_continuation_indentation
256                    // if True:
257                    //     \
258                    //         1
259                    //     \
260                    // 2
261                    // else:\
262                    //     3
263
264                    // test_err backslash_continuation_indentation_error
265                    // if True:
266                    //     1
267                    //       \
268                    //     2
269
270                    // > Indentation cannot be split over multiple physical lines using backslashes;
271                    // > the whitespace up to the first backslash determines the indentation.
272                    // >
273                    // > https://docs.python.org/3/reference/lexical_analysis.html#indentation
274                    //
275                    // Skip whitespace after the continuation-line without accumulating it into
276                    // `indentation`. However, if the backslash is at column 0 (no prior
277                    // indentation), let the loop continue so the next line's whitespace is
278                    // accumulated normally.
279                    //
280                    // See also: https://github.com/python/cpython/issues/90249
281                    if indentation != Indentation::root() {
282                        self.cursor.eat_while(is_python_whitespace);
283                    }
284                }
285                // Form feed
286                '\x0C' => {
287                    self.cursor.bump();
288                    indentation = Indentation::root();
289                }
290                _ => break,
291            }
292        }
293
294        // Handle indentation if this is a new, not all empty, logical line
295        if !matches!(self.cursor.first(), '\n' | '\r' | '#' | EOF_CHAR) {
296            self.state = State::NonEmptyLogicalLine;
297
298            // Set to false so that we don't handle indentation on the next call.
299            return self.handle_indentation(indentation);
300        }
301
302        None
303    }
304
305    fn handle_indentation(&mut self, indentation: Indentation) -> Option<TokenKind> {
306        match self.indentations.current().try_compare(indentation) {
307            // Dedent
308            Ok(Ordering::Greater) => {
309                self.pending_indentation = Some(indentation);
310
311                if self.indentations.dedent_one(indentation).is_err() {
312                    return Some(self.push_error(LexicalError::new(
313                        LexicalErrorType::IndentationError,
314                        self.token_range(),
315                    )));
316                }
317
318                // The lexer might've eaten some whitespaces to calculate the `indentation`. For
319                // example:
320                //
321                // ```py
322                // if first:
323                //     if second:
324                //         pass
325                //     foo
326                // #   ^
327                // ```
328                //
329                // Here, the cursor is at `^` and the `indentation` contains the whitespaces before
330                // the `pass` token.
331                self.cursor.start_token();
332
333                Some(TokenKind::Dedent)
334            }
335
336            Ok(Ordering::Equal) => None,
337
338            // Indent
339            Ok(Ordering::Less) => {
340                self.indentations.indent(indentation);
341                Some(TokenKind::Indent)
342            }
343            Err(_) => Some(self.push_error(LexicalError::new(
344                LexicalErrorType::IndentationError,
345                self.token_range(),
346            ))),
347        }
348    }
349
350    fn skip_whitespace(&mut self) -> Result<(), LexicalError> {
351        let whitespace_start = if matches!(self.cursor.first(), ' ' | '\t' | '\\' | '\x0C') {
352            self.offset()
353        } else {
354            return Ok(());
355        };
356
357        loop {
358            match self.cursor.first() {
359                ' ' => {
360                    self.cursor.bump();
361                }
362                '\t' => {
363                    self.cursor.bump();
364                }
365                '\\' => {
366                    self.cursor.bump();
367                    if self.cursor.eat_char('\r') {
368                        self.cursor.eat_char('\n');
369                    } else if !self.cursor.eat_char('\n') {
370                        return Err(LexicalError::new(
371                            LexicalErrorType::LineContinuationError,
372                            TextRange::at(self.offset() - '\\'.text_len(), '\\'.text_len()),
373                        ));
374                    }
375                    if self.cursor.is_eof() {
376                        return Err(LexicalError::new(
377                            LexicalErrorType::Eof,
378                            TextRange::new(whitespace_start, self.offset()),
379                        ));
380                    }
381                }
382                // Form feed
383                '\x0C' => {
384                    self.cursor.bump();
385                }
386                _ => break,
387            }
388        }
389
390        Ok(())
391    }
392
393    // Dispatch based on the given character.
394    fn consume_ascii_character(&mut self, c: char) -> TokenKind {
395        let token = match c {
396            c if is_ascii_identifier_start(c) => self.lex_identifier(c),
397            '0'..='9' => self.lex_number(c),
398            '#' => return self.lex_comment(),
399            '\'' | '"' => self.lex_string(c),
400            '=' => {
401                if self.cursor.eat_char('=') {
402                    TokenKind::EqEqual
403                } else {
404                    self.state = State::AfterEqual;
405                    return TokenKind::Equal;
406                }
407            }
408            '+' => {
409                if self.cursor.eat_char('=') {
410                    TokenKind::PlusEqual
411                } else {
412                    TokenKind::Plus
413                }
414            }
415            '*' => {
416                if self.cursor.eat_char('=') {
417                    TokenKind::StarEqual
418                } else if self.cursor.eat_char('*') {
419                    if self.cursor.eat_char('=') {
420                        TokenKind::DoubleStarEqual
421                    } else {
422                        TokenKind::DoubleStar
423                    }
424                } else {
425                    TokenKind::Star
426                }
427            }
428
429            '%' | '!'
430                if self.mode == Mode::Ipython
431                    && self.state.is_after_equal()
432                    && self.nesting == 0 =>
433            {
434                self.lex_ipython_escape_command()
435            }
436
437            '%' | '!' | '?' | '/' | ';' | ','
438                if self.mode == Mode::Ipython && self.state.is_new_logical_line() =>
439            {
440                self.lex_ipython_escape_command()
441            }
442
443            '?' if self.mode == Mode::Ipython => TokenKind::Question,
444
445            '/' => {
446                if self.cursor.eat_char('=') {
447                    TokenKind::SlashEqual
448                } else if self.cursor.eat_char('/') {
449                    if self.cursor.eat_char('=') {
450                        TokenKind::DoubleSlashEqual
451                    } else {
452                        TokenKind::DoubleSlash
453                    }
454                } else {
455                    TokenKind::Slash
456                }
457            }
458            '%' => {
459                if self.cursor.eat_char('=') {
460                    TokenKind::PercentEqual
461                } else {
462                    TokenKind::Percent
463                }
464            }
465            '|' => {
466                if self.cursor.eat_char('=') {
467                    TokenKind::VbarEqual
468                } else {
469                    TokenKind::Vbar
470                }
471            }
472            '^' => {
473                if self.cursor.eat_char('=') {
474                    TokenKind::CircumflexEqual
475                } else {
476                    TokenKind::CircumFlex
477                }
478            }
479            '&' => {
480                if self.cursor.eat_char('=') {
481                    TokenKind::AmperEqual
482                } else {
483                    TokenKind::Amper
484                }
485            }
486            '-' => {
487                if self.cursor.eat_char('=') {
488                    TokenKind::MinusEqual
489                } else if self.cursor.eat_char('>') {
490                    TokenKind::Rarrow
491                } else {
492                    TokenKind::Minus
493                }
494            }
495            '@' => {
496                if self.cursor.eat_char('=') {
497                    TokenKind::AtEqual
498                } else {
499                    TokenKind::At
500                }
501            }
502            '!' => {
503                if self.cursor.eat_char('=') {
504                    TokenKind::NotEqual
505                } else {
506                    TokenKind::Exclamation
507                }
508            }
509            '~' => TokenKind::Tilde,
510            '(' => {
511                self.nesting += 1;
512                TokenKind::Lpar
513            }
514            ')' => {
515                self.nesting = self.nesting.saturating_sub(1);
516                TokenKind::Rpar
517            }
518            '[' => {
519                self.nesting += 1;
520                TokenKind::Lsqb
521            }
522            ']' => {
523                self.nesting = self.nesting.saturating_sub(1);
524                TokenKind::Rsqb
525            }
526            '{' => {
527                self.nesting += 1;
528                TokenKind::Lbrace
529            }
530            '}' => {
531                if let Some(interpolated_string) = self.interpolated_strings.current_mut() {
532                    if interpolated_string.nesting() == self.nesting {
533                        let error_type = LexicalErrorType::from_interpolated_string_error(
534                            InterpolatedStringErrorType::SingleRbrace,
535                            interpolated_string.kind(),
536                        );
537                        return self.push_error(LexicalError::new(error_type, self.token_range()));
538                    }
539                    interpolated_string.try_end_format_spec(self.nesting);
540                }
541                self.nesting = self.nesting.saturating_sub(1);
542                TokenKind::Rbrace
543            }
544            ':' => {
545                if self
546                    .interpolated_strings
547                    .current_mut()
548                    .is_some_and(|interpolated_string| {
549                        interpolated_string.try_start_format_spec(self.nesting)
550                    })
551                {
552                    TokenKind::Colon
553                } else if self.cursor.eat_char('=') {
554                    TokenKind::ColonEqual
555                } else {
556                    TokenKind::Colon
557                }
558            }
559            ';' => TokenKind::Semi,
560            '<' => {
561                if self.cursor.eat_char('<') {
562                    if self.cursor.eat_char('=') {
563                        TokenKind::LeftShiftEqual
564                    } else {
565                        TokenKind::LeftShift
566                    }
567                } else if self.cursor.eat_char('=') {
568                    TokenKind::LessEqual
569                } else {
570                    TokenKind::Less
571                }
572            }
573            '>' => {
574                if self.cursor.eat_char('>') {
575                    if self.cursor.eat_char('=') {
576                        TokenKind::RightShiftEqual
577                    } else {
578                        TokenKind::RightShift
579                    }
580                } else if self.cursor.eat_char('=') {
581                    TokenKind::GreaterEqual
582                } else {
583                    TokenKind::Greater
584                }
585            }
586            ',' => TokenKind::Comma,
587            '.' => {
588                if self.cursor.first().is_ascii_digit() {
589                    self.lex_decimal_number('.')
590                } else if self.cursor.eat_char2('.', '.') {
591                    TokenKind::Ellipsis
592                } else {
593                    TokenKind::Dot
594                }
595            }
596            '\n' => {
597                return if self.nesting == 0 && !self.state.is_new_logical_line() {
598                    self.state = State::AfterNewline;
599                    TokenKind::Newline
600                } else {
601                    if let Some(interpolated_string) = self.interpolated_strings.current_mut() {
602                        interpolated_string.try_end_format_spec(self.nesting);
603                    }
604                    TokenKind::NonLogicalNewline
605                };
606            }
607            '\r' => {
608                self.cursor.eat_char('\n');
609
610                return if self.nesting == 0 && !self.state.is_new_logical_line() {
611                    self.state = State::AfterNewline;
612                    TokenKind::Newline
613                } else {
614                    if let Some(interpolated_string) = self.interpolated_strings.current_mut() {
615                        interpolated_string.try_end_format_spec(self.nesting);
616                    }
617                    TokenKind::NonLogicalNewline
618                };
619            }
620
621            _ => {
622                self.state = State::Other;
623
624                return self.push_error(LexicalError::new(
625                    LexicalErrorType::UnrecognizedToken { tok: c },
626                    self.token_range(),
627                ));
628            }
629        };
630
631        self.state = State::Other;
632
633        token
634    }
635
636    /// Lex an identifier. Also used for keywords and string/bytes literals with a prefix.
637    fn lex_identifier(&mut self, first: char) -> TokenKind {
638        // Detect potential string like rb'' b'' f'' t'' u'' r''
639        let quote = if let Some(prefix) = single_char_prefix(first) {
640            match self.cursor.first() {
641                quote @ ('\'' | '"') => {
642                    self.current_flags |= prefix;
643                    self.cursor.bump();
644                    Some(quote)
645                }
646                second
647                    if let quote = self.cursor.second()
648                        && is_quote(quote) =>
649                {
650                    self.try_double_char_prefix([first, second]).then(|| {
651                        self.cursor.bump();
652                        self.cursor.bump();
653                        quote
654                    })
655                }
656                _ => None,
657            }
658        } else {
659            None
660        };
661
662        if let Some(quote) = quote {
663            if self.current_flags.is_interpolated_string() {
664                if let Some(kind) = self.lex_interpolated_string_start(quote) {
665                    return kind;
666                }
667            }
668
669            return self.lex_string(quote);
670        }
671
672        // Keep track of whether the identifier is ASCII-only or not.
673        //
674        // This is important because Python applies NFKC normalization to
675        // identifiers: https://docs.python.org/3/reference/lexical_analysis.html#identifiers.
676        // The parser needs to do the same when cooking the name, but applying
677        // NFKC normalization unconditionally is extremely expensive. If we know
678        // an identifier is ASCII-only (by far the most common case), the parser
679        // can skip NFKC normalization.
680        let mut is_ascii = first.is_ascii();
681        self.cursor
682            .eat_while(|c| is_identifier_continuation(c, &mut is_ascii));
683
684        if !is_ascii {
685            self.current_flags |= TokenFlags::NON_ASCII_NAME;
686            return TokenKind::Name;
687        }
688
689        let text = self.token_text();
690
691        // No Python keyword is longer than eight bytes.
692        if text.len() > 8 {
693            return TokenKind::Name;
694        }
695
696        match text.as_bytes() {
697            b"False" => TokenKind::False,
698            b"None" => TokenKind::None,
699            b"True" => TokenKind::True,
700            b"and" => TokenKind::And,
701            b"as" => TokenKind::As,
702            b"assert" => TokenKind::Assert,
703            b"async" => TokenKind::Async,
704            b"await" => TokenKind::Await,
705            b"break" => TokenKind::Break,
706            b"case" => TokenKind::Case,
707            b"class" => TokenKind::Class,
708            b"continue" => TokenKind::Continue,
709            b"def" => TokenKind::Def,
710            b"del" => TokenKind::Del,
711            b"elif" => TokenKind::Elif,
712            b"else" => TokenKind::Else,
713            b"except" => TokenKind::Except,
714            b"finally" => TokenKind::Finally,
715            b"for" => TokenKind::For,
716            b"from" => TokenKind::From,
717            b"global" => TokenKind::Global,
718            b"if" => TokenKind::If,
719            b"import" => TokenKind::Import,
720            b"in" => TokenKind::In,
721            b"is" => TokenKind::Is,
722            b"lazy" => TokenKind::Lazy,
723            b"lambda" => TokenKind::Lambda,
724            b"match" => TokenKind::Match,
725            b"nonlocal" => TokenKind::Nonlocal,
726            b"not" => TokenKind::Not,
727            b"or" => TokenKind::Or,
728            b"pass" => TokenKind::Pass,
729            b"raise" => TokenKind::Raise,
730            b"return" => TokenKind::Return,
731            b"try" => TokenKind::Try,
732            b"type" => TokenKind::Type,
733            b"while" => TokenKind::While,
734            b"with" => TokenKind::With,
735            b"yield" => TokenKind::Yield,
736            _ => TokenKind::Name,
737        }
738    }
739
740    /// Try lexing the double character string prefix, updating the token flags accordingly.
741    /// Returns `true` if it matches.
742    fn try_double_char_prefix(&mut self, value: [char; 2]) -> bool {
743        match value {
744            ['r', 'f' | 'F'] | ['f' | 'F', 'r'] => {
745                self.current_flags |= TokenFlags::F_STRING | TokenFlags::RAW_STRING_LOWERCASE;
746            }
747            ['R', 'f' | 'F'] | ['f' | 'F', 'R'] => {
748                self.current_flags |= TokenFlags::F_STRING | TokenFlags::RAW_STRING_UPPERCASE;
749            }
750            ['r', 't' | 'T'] | ['t' | 'T', 'r'] => {
751                self.current_flags |= TokenFlags::T_STRING | TokenFlags::RAW_STRING_LOWERCASE;
752            }
753            ['R', 't' | 'T'] | ['t' | 'T', 'R'] => {
754                self.current_flags |= TokenFlags::T_STRING | TokenFlags::RAW_STRING_UPPERCASE;
755            }
756            ['r', 'b' | 'B'] | ['b' | 'B', 'r'] => {
757                self.current_flags |= TokenFlags::BYTE_STRING | TokenFlags::RAW_STRING_LOWERCASE;
758            }
759            ['R', 'b' | 'B'] | ['b' | 'B', 'R'] => {
760                self.current_flags |= TokenFlags::BYTE_STRING | TokenFlags::RAW_STRING_UPPERCASE;
761            }
762            _ => return false,
763        }
764        true
765    }
766
767    /// Lex a f-string or t-string start token if positioned at the start of an f-string or t-string.
768    fn lex_interpolated_string_start(&mut self, quote: char) -> Option<TokenKind> {
769        #[cfg(debug_assertions)]
770        debug_assert_eq!(self.cursor.previous(), quote);
771
772        if quote == '"' {
773            self.current_flags |= TokenFlags::DOUBLE_QUOTES;
774        }
775
776        if self.cursor.eat_char2(quote, quote) {
777            self.current_flags |= TokenFlags::TRIPLE_QUOTED_STRING;
778        }
779
780        let ftcontext = InterpolatedStringContext::new(self.current_flags, self.nesting)?;
781
782        let kind = ftcontext.kind();
783
784        self.interpolated_strings.push(ftcontext);
785
786        Some(kind.start_token())
787    }
788
789    /// Lex an f-string or t-string middle or end token.
790    fn lex_interpolated_string_middle_or_end(&mut self) -> Option<TokenKind> {
791        // SAFETY: Safe because the function is only called when `self.fstrings` is not empty.
792        let interpolated_string = self.interpolated_strings.current().unwrap();
793        let string_kind = interpolated_string.kind();
794        let interpolated_flags = interpolated_string.flags();
795
796        // Check if we're at the end of the f-string.
797        if interpolated_string.is_triple_quoted() {
798            let quote_char = interpolated_string.quote_char();
799            if self.cursor.eat_char3(quote_char, quote_char, quote_char) {
800                self.current_flags = interpolated_string.flags();
801                return Some(string_kind.end_token());
802            }
803        } else if self.cursor.eat_char(interpolated_string.quote_char()) {
804            self.current_flags = interpolated_string.flags();
805            return Some(string_kind.end_token());
806        }
807
808        // This isn't going to change for the duration of the loop.
809        let in_format_spec = interpolated_string.is_in_format_spec(self.nesting);
810
811        let mut in_named_unicode = false;
812
813        loop {
814            match self.cursor.first() {
815                // The condition is to differentiate between the `NUL` (`\0`) character
816                // in the source code and the one returned by `self.cursor.first()` when
817                // we reach the end of the source code.
818                EOF_CHAR if self.cursor.is_eof() => {
819                    let error = if interpolated_string.is_triple_quoted() {
820                        InterpolatedStringErrorType::UnterminatedTripleQuotedString
821                    } else {
822                        InterpolatedStringErrorType::UnterminatedString
823                    };
824
825                    self.nesting = interpolated_string.nesting();
826                    self.interpolated_strings.pop();
827                    self.current_flags |= TokenFlags::UNCLOSED_STRING;
828                    self.push_error(LexicalError::new(
829                        LexicalErrorType::from_interpolated_string_error(error, string_kind),
830                        self.token_range(),
831                    ));
832
833                    break;
834                }
835                '\n' | '\r' if !interpolated_string.is_triple_quoted() => {
836                    // https://github.com/astral-sh/ruff/issues/18632
837
838                    let error_type = if in_format_spec {
839                        InterpolatedStringErrorType::NewlineInFormatSpec
840                    } else {
841                        InterpolatedStringErrorType::UnterminatedString
842                    };
843
844                    self.nesting = interpolated_string.nesting();
845                    self.interpolated_strings.pop();
846                    self.current_flags |= TokenFlags::UNCLOSED_STRING;
847
848                    self.push_error(LexicalError::new(
849                        LexicalErrorType::from_interpolated_string_error(error_type, string_kind),
850                        self.token_range(),
851                    ));
852
853                    break;
854                }
855                '\\' => {
856                    self.cursor.bump(); // '\'
857                    if matches!(self.cursor.first(), '{' | '}') {
858                        // Don't consume `{` or `}` as we want them to be emitted as tokens.
859                        // They will be handled in the next iteration.
860                        continue;
861                    } else if !interpolated_string.is_raw_string() {
862                        if self.cursor.eat_char2('N', '{') {
863                            in_named_unicode = true;
864                            continue;
865                        }
866                    }
867                    // Consume the escaped character.
868                    if self.cursor.eat_char('\r') {
869                        self.cursor.eat_char('\n');
870                    } else {
871                        self.cursor.bump();
872                    }
873                }
874                quote @ ('\'' | '"') if quote == interpolated_string.quote_char() => {
875                    if let Some(triple_quotes) = interpolated_string.triple_quotes() {
876                        if self.cursor.rest().starts_with(triple_quotes) {
877                            break;
878                        }
879                        self.cursor.bump();
880                    } else {
881                        break;
882                    }
883                }
884                '{' => {
885                    if self.cursor.second() == '{' && !in_format_spec {
886                        self.cursor.bump();
887                        self.cursor.bump(); // Skip the second `{`
888                    } else {
889                        break;
890                    }
891                }
892                '}' => {
893                    if in_named_unicode {
894                        in_named_unicode = false;
895                        self.cursor.bump();
896                    } else if self.cursor.second() == '}' && !in_format_spec {
897                        self.cursor.bump();
898                        self.cursor.bump(); // Skip the second `}`
899                    } else {
900                        break;
901                    }
902                }
903                _ => {
904                    self.cursor.bump();
905                }
906            }
907        }
908        let range = self.token_range();
909        if range.is_empty() {
910            return None;
911        }
912
913        self.current_flags = interpolated_flags;
914        Some(string_kind.middle_token())
915    }
916
917    /// Lex a string literal.
918    fn lex_string(&mut self, quote: char) -> TokenKind {
919        #[cfg(debug_assertions)]
920        debug_assert_eq!(self.cursor.previous(), quote);
921
922        if quote == '"' {
923            self.current_flags |= TokenFlags::DOUBLE_QUOTES;
924        }
925
926        // If the next two characters are also the quote character, then we have a triple-quoted
927        // string; consume those two characters and ensure that we require a triple-quote to close
928        if self.cursor.eat_char2(quote, quote) {
929            self.current_flags |= TokenFlags::TRIPLE_QUOTED_STRING;
930        }
931
932        let quote_byte = u8::try_from(quote).expect("char that fits in u8");
933        if self.current_flags.is_triple_quoted() {
934            // For triple-quoted strings, scan until we find the closing quote (ignoring escaped
935            // quotes) or the end of the file.
936            loop {
937                let Some(index) = memchr::memchr(quote_byte, self.cursor.rest().as_bytes()) else {
938                    self.cursor.skip_to_end();
939
940                    self.current_flags |= TokenFlags::UNCLOSED_STRING;
941                    self.push_error(LexicalError::new(
942                        LexicalErrorType::UnclosedStringError,
943                        self.token_range(),
944                    ));
945                    break;
946                };
947
948                // Rare case: if there are an odd number of backslashes before the quote, then
949                // the quote is escaped and we should continue scanning.
950                let num_backslashes = self.cursor.rest().as_bytes()[..index]
951                    .iter()
952                    .rev()
953                    .take_while(|&&c| c == b'\\')
954                    .count();
955
956                // Advance the cursor past the quote and continue scanning.
957                self.cursor.skip_bytes(index + 1);
958
959                // If the character is escaped, continue scanning.
960                if num_backslashes % 2 == 1 {
961                    continue;
962                }
963
964                // Otherwise, if it's followed by two more quotes, then we're done.
965                if self.cursor.eat_char2(quote, quote) {
966                    break;
967                }
968            }
969        } else {
970            // For non-triple-quoted strings, scan until we find the closing quote, but end early
971            // if we encounter a newline or the end of the file.
972            loop {
973                let Some(index) =
974                    memchr::memchr3(quote_byte, b'\r', b'\n', self.cursor.rest().as_bytes())
975                else {
976                    self.cursor.skip_to_end();
977                    self.current_flags |= TokenFlags::UNCLOSED_STRING;
978
979                    self.push_error(LexicalError::new(
980                        LexicalErrorType::UnclosedStringError,
981                        self.token_range(),
982                    ));
983
984                    break;
985                };
986
987                // Rare case: if there are an odd number of backslashes before the quote, then
988                // the quote is escaped and we should continue scanning.
989                let num_backslashes = self.cursor.rest().as_bytes()[..index]
990                    .iter()
991                    .rev()
992                    .take_while(|&&c| c == b'\\')
993                    .count();
994
995                // Skip up to the current character.
996                self.cursor.skip_bytes(index);
997
998                // Lookahead because we want to bump only if it's a quote or being escaped.
999                let quote_or_newline = self.cursor.first();
1000
1001                // If the character is escaped, continue scanning.
1002                if num_backslashes % 2 == 1 {
1003                    self.cursor.bump();
1004                    if quote_or_newline == '\r' {
1005                        self.cursor.eat_char('\n');
1006                    }
1007                    continue;
1008                }
1009
1010                match quote_or_newline {
1011                    '\r' | '\n' => {
1012                        self.current_flags |= TokenFlags::UNCLOSED_STRING;
1013                        self.push_error(LexicalError::new(
1014                            LexicalErrorType::UnclosedStringError,
1015                            self.token_range(),
1016                        ));
1017                        break;
1018                    }
1019                    ch if ch == quote => {
1020                        self.cursor.bump();
1021                        break;
1022                    }
1023                    _ => unreachable!("memchr2 returned an index that is not a quote or a newline"),
1024                }
1025            }
1026        }
1027
1028        TokenKind::String
1029    }
1030
1031    /// Numeric lexing. The feast can start!
1032    fn lex_number(&mut self, first: char) -> TokenKind {
1033        if first == '0' {
1034            if self.cursor.eat_if(|c| matches!(c, 'x' | 'X')).is_some() {
1035                self.lex_number_radix(Radix::Hex)
1036            } else if self.cursor.eat_if(|c| matches!(c, 'o' | 'O')).is_some() {
1037                self.lex_number_radix(Radix::Octal)
1038            } else if self.cursor.eat_if(|c| matches!(c, 'b' | 'B')).is_some() {
1039                self.lex_number_radix(Radix::Binary)
1040            } else {
1041                self.lex_decimal_number(first)
1042            }
1043        } else {
1044            self.lex_decimal_number(first)
1045        }
1046    }
1047
1048    /// Lex a hex/octal/decimal/binary number without a decimal point.
1049    fn lex_number_radix(&mut self, radix: Radix) -> TokenKind {
1050        #[cfg(debug_assertions)]
1051        debug_assert!(matches!(
1052            self.cursor.previous().to_ascii_lowercase(),
1053            'x' | 'o' | 'b'
1054        ));
1055
1056        let number = self.radix_run(radix);
1057        if !number.has_digit {
1058            let err = u64::from_str_radix("", radix.as_u32()).unwrap_err();
1059            return self.push_error(LexicalError::new(
1060                LexicalErrorType::OtherError(format!("{err:?}").into_boxed_str()),
1061                self.token_range(),
1062            ));
1063        }
1064        TokenKind::Int
1065    }
1066
1067    /// Lex a normal number, that is, no octal, hex or binary number.
1068    fn lex_decimal_number(&mut self, first_digit_or_dot: char) -> TokenKind {
1069        #[cfg(debug_assertions)]
1070        debug_assert!(self.cursor.previous().is_ascii_digit() || self.cursor.previous() == '.');
1071        let start_is_zero = first_digit_or_dot == '0';
1072
1073        let mut integer_part = RadixRun {
1074            has_digit: first_digit_or_dot != '.',
1075            has_nonzero_digit: first_digit_or_dot != '.' && first_digit_or_dot != '0',
1076        };
1077        if first_digit_or_dot != '.' {
1078            integer_part.has_nonzero_digit |= self.radix_run(Radix::Decimal).has_nonzero_digit;
1079        }
1080
1081        let is_float = if first_digit_or_dot == '.' || self.cursor.eat_char('.') {
1082            if self.cursor.eat_char('_') {
1083                return self.push_error(LexicalError::new(
1084                    LexicalErrorType::OtherError("Invalid Syntax".to_string().into_boxed_str()),
1085                    TextRange::new(self.offset() - TextSize::new(1), self.offset()),
1086                ));
1087            }
1088
1089            self.radix_run(Radix::Decimal);
1090            true
1091        } else {
1092            // Normal number:
1093            false
1094        };
1095
1096        let is_float = match self.cursor.rest().as_bytes() {
1097            [b'e' | b'E', b'0'..=b'9', ..] | [b'e' | b'E', b'-' | b'+', b'0'..=b'9', ..] => {
1098                // 'e' | 'E'
1099                self.cursor.bump();
1100
1101                self.cursor.eat_if(|c| matches!(c, '+' | '-'));
1102
1103                self.radix_run(Radix::Decimal);
1104
1105                true
1106            }
1107            _ => is_float,
1108        };
1109
1110        if self.cursor.eat_if(|c| matches!(c, 'j' | 'J')).is_some() {
1111            TokenKind::Complex
1112        } else if is_float {
1113            TokenKind::Float
1114        } else if start_is_zero && integer_part.has_nonzero_digit {
1115            // Leading zeros in decimal integer literals are not permitted.
1116            self.push_error(LexicalError::new(
1117                LexicalErrorType::OtherError(
1118                    "Invalid decimal integer literal"
1119                        .to_string()
1120                        .into_boxed_str(),
1121                ),
1122                self.token_range(),
1123            ))
1124        } else {
1125            TokenKind::Int
1126        }
1127    }
1128
1129    /// Consume a sequence of numbers with the given radix,
1130    /// the digits can be decorated with underscores
1131    /// like this: '`1_2_3_4`' == '1234'
1132    fn radix_run(&mut self, radix: Radix) -> RadixRun {
1133        let mut run = RadixRun::default();
1134        loop {
1135            if let Some(c) = self.cursor.eat_if(|c| radix.is_digit(c)) {
1136                run.has_digit = true;
1137                run.has_nonzero_digit |= c != '0';
1138            }
1139            // Number that contains `_` separators.
1140            else if self.cursor.first() == '_' && radix.is_digit(self.cursor.second()) {
1141                // Skip over `_`
1142                self.cursor.bump();
1143            } else {
1144                break;
1145            }
1146        }
1147        run
1148    }
1149
1150    /// Lex a single comment.
1151    fn lex_comment(&mut self) -> TokenKind {
1152        #[cfg(debug_assertions)]
1153        debug_assert_eq!(self.cursor.previous(), '#');
1154
1155        let bytes = self.cursor.rest().as_bytes();
1156        let offset = memchr::memchr2(b'\n', b'\r', bytes).unwrap_or(bytes.len());
1157        self.cursor.skip_bytes(offset);
1158
1159        TokenKind::Comment
1160    }
1161
1162    /// Lex a single IPython escape command.
1163    fn lex_ipython_escape_command(&mut self) -> TokenKind {
1164        loop {
1165            match self.cursor.first() {
1166                '\\' => {
1167                    // Consume an escaped newline so it doesn't terminate the command. A normal
1168                    // backslash is consumed by itself and remains part of the token range.
1169                    self.cursor.bump();
1170                    self.cursor.eat_char('\r');
1171                    self.cursor.eat_char('\n');
1172                }
1173                '\n' | '\r' | EOF_CHAR => return TokenKind::IpyEscapeCommand,
1174                _ => {
1175                    self.cursor.bump();
1176                }
1177            }
1178        }
1179    }
1180
1181    fn consume_end(&mut self) -> TokenKind {
1182        // We reached end of file.
1183
1184        // First, finish any unterminated interpolated-strings.
1185        while let Some(interpolated_string) = self.interpolated_strings.pop() {
1186            self.nesting = interpolated_string.nesting();
1187            self.push_error(LexicalError::new(
1188                LexicalErrorType::from_interpolated_string_error(
1189                    InterpolatedStringErrorType::UnterminatedString,
1190                    interpolated_string.kind(),
1191                ),
1192                self.token_range(),
1193            ));
1194        }
1195
1196        // Second, finish all nestings.
1197        // For Mode::ParenthesizedExpression we start with nesting level 1.
1198        // So we check if we end with that level.
1199        let init_nesting = u32::from(self.mode == Mode::ParenthesizedExpression);
1200
1201        if self.nesting > init_nesting {
1202            // Reset the nesting to avoid going into infinite loop.
1203            self.nesting = 0;
1204            return self.push_error(LexicalError::new(LexicalErrorType::Eof, self.token_range()));
1205        }
1206
1207        // Next, insert a trailing newline, if required.
1208        if !self.state.is_new_logical_line() {
1209            self.state = State::AfterNewline;
1210            TokenKind::Newline
1211        }
1212        // Next, flush the indentation stack to zero.
1213        else if self.indentations.dedent().is_some() {
1214            TokenKind::Dedent
1215        } else {
1216            TokenKind::EndOfFile
1217        }
1218    }
1219
1220    /// Re-lex the [`NonLogicalNewline`] token at the given position in the context of a logical
1221    /// line.
1222    ///
1223    /// Returns a boolean indicating whether the lexer's position has changed. This could result
1224    /// into the new current token being different than the previous current token but is not
1225    /// necessarily true. If the return value is `true` then the caller is responsible for updating
1226    /// it's state accordingly.
1227    ///
1228    /// This method is a no-op if the lexer isn't in a parenthesized context.
1229    ///
1230    /// ## Explanation
1231    ///
1232    /// The lexer emits two different kinds of newline token based on the context. If it's in a
1233    /// parenthesized context, it'll emit a [`NonLogicalNewline`] token otherwise it'll emit a
1234    /// regular [`Newline`] token. Based on the type of newline token, the lexer will consume and
1235    /// emit the indentation tokens appropriately which affects the structure of the code.
1236    ///
1237    /// For example:
1238    /// ```py
1239    /// if call(foo
1240    ///     def bar():
1241    ///         pass
1242    /// ```
1243    ///
1244    /// Here, the lexer emits a [`NonLogicalNewline`] token after `foo` which means that the lexer
1245    /// doesn't emit an `Indent` token before the `def` keyword. This leads to an AST which
1246    /// considers the function `bar` as part of the module block and the `if` block remains empty.
1247    ///
1248    /// This method is to facilitate the parser if it recovers from these kind of scenarios so that
1249    /// the lexer can then re-lex a [`NonLogicalNewline`] token to a [`Newline`] token which in
1250    /// turn helps the parser to build the correct AST.
1251    ///
1252    /// In the above snippet, it would mean that this method would move the lexer back to the
1253    /// newline character after the `foo` token and emit it as a [`Newline`] token instead of
1254    /// [`NonLogicalNewline`]. This means that the next token emitted by the lexer would be an
1255    /// `Indent` token.
1256    ///
1257    /// There are cases where the lexer's position will change but the re-lexed token will remain
1258    /// the same. This is to help the parser to add the error message at an appropriate location.
1259    /// Consider the following example:
1260    ///
1261    /// ```py
1262    /// if call(foo, [a, b
1263    ///     def bar():
1264    ///         pass
1265    /// ```
1266    ///
1267    /// Here, the parser recovers from two unclosed parenthesis. The inner unclosed `[` will call
1268    /// into the re-lexing logic and reduce the nesting level from 2 to 1. And, the re-lexing logic
1269    /// will move the lexer at the newline after `b` but still emit a [`NonLogicalNewline`] token.
1270    /// Only after the parser recovers from the outer unclosed `(` does the re-lexing logic emit
1271    /// the [`Newline`] token.
1272    ///
1273    /// [`Newline`]: TokenKind::Newline
1274    /// [`NonLogicalNewline`]: TokenKind::NonLogicalNewline
1275    pub(crate) fn re_lex_logical_token(
1276        &mut self,
1277        non_logical_newline_start: Option<TextSize>,
1278    ) -> bool {
1279        if self.nesting == 0 {
1280            return false;
1281        }
1282
1283        // Reduce the nesting level because the parser recovered from an error inside list parsing
1284        // i.e., it recovered from an unclosed parenthesis (`(`, `[`, or `{`).
1285        self.nesting -= 1;
1286
1287        // The lexer can't be moved back for a triple-quoted f/t-string because the newlines are
1288        // part of the f/t-string itself, so there is no newline token to be emitted.
1289        if self.current_flags.is_triple_quoted_interpolated_string() {
1290            return false;
1291        }
1292
1293        let Some(new_position) = non_logical_newline_start else {
1294            return false;
1295        };
1296
1297        // Earlier we reduced the nesting level unconditionally. Now that we know the lexer's
1298        // position is going to be moved back, the lexer needs to be put back into a
1299        // parenthesized context if the current token is a closing parenthesis.
1300        //
1301        // ```py
1302        // (a, [b,
1303        //     c
1304        // )
1305        // ```
1306        //
1307        // Here, the parser would request to re-lex the token when it's at `)` and can recover
1308        // from an unclosed `[`. This method will move the lexer back to the newline character
1309        // after `c` which means it goes back into parenthesized context.
1310        if matches!(
1311            self.current_kind,
1312            TokenKind::Rpar | TokenKind::Rsqb | TokenKind::Rbrace
1313        ) {
1314            self.nesting += 1;
1315        }
1316
1317        self.cursor = Cursor::new(self.source);
1318        self.cursor.skip_bytes(new_position.to_usize());
1319        self.state = State::Other;
1320        self.next_token();
1321        true
1322    }
1323
1324    /// Re-lexes an unclosed string token in the context of an interpolated string element.
1325    ///
1326    /// ```py
1327    /// f'{a'
1328    /// ```
1329    ///
1330    /// This method re-lexes the trailing `'` as the end of the f-string rather than the
1331    /// start of a new string token for better error recovery.
1332    pub(crate) fn re_lex_string_token_in_interpolation_element(
1333        &mut self,
1334        kind: InterpolatedStringKind,
1335    ) {
1336        let Some(interpolated_string) = self.interpolated_strings.current() else {
1337            return;
1338        };
1339
1340        let current_string_flags = self.current_flags().as_any_string_flags();
1341
1342        // Only unclosed strings, that have the same quote character
1343        if !matches!(self.current_kind, TokenKind::String)
1344            || !self.current_flags.is_unclosed()
1345            || current_string_flags.prefix() != AnyStringPrefix::Regular(StringLiteralPrefix::Empty)
1346            || current_string_flags.quote_style().as_char() != interpolated_string.quote_char()
1347            || current_string_flags.is_triple_quoted() != interpolated_string.is_triple_quoted()
1348        {
1349            return;
1350        }
1351
1352        // Only if the string's first line only contains whitespace,
1353        // or ends in a comment (not `f"{"abc`)
1354        let first_line = &self.source
1355            [(self.current_range.start() + current_string_flags.quote_len()).to_usize()..];
1356
1357        for c in first_line.chars() {
1358            if matches!(c, '\n' | '\r' | '#') {
1359                break;
1360            }
1361
1362            // `f'{'ab`, we want to parse `ab` as a normal string and not the closing element of the f-string
1363            if !is_python_whitespace(c) {
1364                return;
1365            }
1366        }
1367
1368        if self.errors.last().is_some_and(|error| {
1369            error.location() == self.current_range
1370                && matches!(error.error(), LexicalErrorType::UnclosedStringError)
1371        }) {
1372            self.errors.pop();
1373        }
1374
1375        self.current_range =
1376            TextRange::at(self.current_range.start(), self.current_flags.quote_len());
1377        self.current_kind = kind.end_token();
1378        self.current_flags = TokenFlags::empty();
1379
1380        self.nesting = interpolated_string.nesting();
1381        self.interpolated_strings.pop();
1382
1383        self.cursor = Cursor::new(self.source);
1384        self.cursor.skip_bytes(self.current_range.end().to_usize());
1385    }
1386
1387    /// Re-lex `r"` in a format specifier position.
1388    ///
1389    /// `r"` in a format specifier position is unlikely to be the start of a raw string.
1390    /// Instead, it's the format specifier `!r` followed by the closing quote of the f-string,
1391    /// when the `}` is missing.
1392    ///
1393    /// ```py
1394    /// f"{test!r"
1395    /// ```
1396    ///
1397    /// This function re-lexes the `r"` as `r` (a name token). The next `next_token` call will
1398    /// return a unclosed string token for `"`, which [`Self::re_lex_string_token_in_interpolation_element`]
1399    /// can then re-lex as the end of the f-string.
1400    pub(crate) fn re_lex_raw_string_in_format_spec(&mut self) {
1401        // Re-lex `r"` as `NAME r` followed by an unclosed string
1402        // `f"{test!r"` -> `f"{test!`, `r`, `"`
1403        if matches!(self.current_kind, TokenKind::String)
1404            && self.current_flags.is_unclosed()
1405            && self.current_flags.prefix()
1406                == AnyStringPrefix::Regular(StringLiteralPrefix::Raw { uppercase: false })
1407        {
1408            if self.errors.last().is_some_and(|error| {
1409                error.location() == self.current_range
1410                    && matches!(error.error(), LexicalErrorType::UnclosedStringError)
1411            }) {
1412                self.errors.pop();
1413            }
1414
1415            self.current_range = TextRange::at(self.current_range.start(), 'r'.text_len());
1416            self.current_kind = TokenKind::Name;
1417            self.current_flags = TokenFlags::empty();
1418            self.cursor = Cursor::new(self.source);
1419            self.cursor.skip_bytes(self.current_range.end().to_usize());
1420        }
1421    }
1422
1423    #[inline]
1424    fn token_range(&self) -> TextRange {
1425        let end = self.offset();
1426        let len = self.cursor.token_len();
1427
1428        TextRange::at(end - len, len)
1429    }
1430
1431    #[inline]
1432    fn token_text(&self) -> &'src str {
1433        &self.source[self.token_range()]
1434    }
1435
1436    /// Retrieves the current offset of the cursor within the source code.
1437    // SAFETY: Lexer doesn't allow files larger than 4GB
1438    #[expect(clippy::cast_possible_truncation)]
1439    #[inline]
1440    fn offset(&self) -> TextSize {
1441        TextSize::new(self.source.len() as u32) - self.cursor.text_len()
1442    }
1443
1444    /// Creates a checkpoint to which the lexer can later return to using [`Self::rewind`].
1445    pub(crate) fn checkpoint(&self) -> LexerCheckpoint {
1446        LexerCheckpoint {
1447            current_kind: self.current_kind,
1448            current_range: self.current_range,
1449            current_flags: self.current_flags,
1450            cursor_offset: self.offset(),
1451            state: self.state,
1452            nesting: self.nesting,
1453            indentations_checkpoint: self.indentations.checkpoint(),
1454            pending_indentation: self.pending_indentation,
1455            interpolated_strings_checkpoint: self.interpolated_strings.checkpoint(),
1456            errors_position: self.errors.len(),
1457        }
1458    }
1459
1460    /// Restore the lexer to the given checkpoint.
1461    pub(crate) fn rewind(&mut self, checkpoint: LexerCheckpoint) {
1462        let LexerCheckpoint {
1463            current_kind,
1464            current_range,
1465            current_flags,
1466            cursor_offset,
1467            state,
1468            nesting,
1469            indentations_checkpoint,
1470            pending_indentation,
1471            interpolated_strings_checkpoint,
1472            errors_position,
1473        } = checkpoint;
1474
1475        let mut cursor = Cursor::new(self.source);
1476        // We preserve the previous char using this method.
1477        cursor.skip_bytes(cursor_offset.to_usize());
1478
1479        self.current_kind = current_kind;
1480        self.current_range = current_range;
1481        self.current_flags = current_flags;
1482        self.cursor = cursor;
1483        self.state = state;
1484        self.nesting = nesting;
1485        self.indentations.rewind(indentations_checkpoint);
1486        self.pending_indentation = pending_indentation;
1487        self.interpolated_strings
1488            .rewind(interpolated_strings_checkpoint);
1489        self.errors.truncate(errors_position);
1490    }
1491
1492    pub(crate) fn finish(self) -> Vec<LexicalError> {
1493        self.errors
1494    }
1495}
1496
1497pub(crate) struct LexerCheckpoint {
1498    current_kind: TokenKind,
1499    current_range: TextRange,
1500    current_flags: TokenFlags,
1501    cursor_offset: TextSize,
1502    state: State,
1503    nesting: u32,
1504    indentations_checkpoint: IndentationsCheckpoint,
1505    pending_indentation: Option<Indentation>,
1506    interpolated_strings_checkpoint: InterpolatedStringsCheckpoint,
1507    errors_position: usize,
1508}
1509
1510#[derive(Copy, Clone, Debug)]
1511enum State {
1512    /// Lexer is right at the beginning of the file or after a `Newline` token.
1513    AfterNewline,
1514
1515    /// The lexer is at the start of a new logical line but **after** the indentation
1516    NonEmptyLogicalLine,
1517
1518    /// Lexer is right after an equal token
1519    AfterEqual,
1520
1521    /// Inside of a logical line
1522    Other,
1523}
1524
1525impl State {
1526    const fn is_after_newline(self) -> bool {
1527        matches!(self, State::AfterNewline)
1528    }
1529
1530    const fn is_new_logical_line(self) -> bool {
1531        matches!(self, State::AfterNewline | State::NonEmptyLogicalLine)
1532    }
1533
1534    const fn is_after_equal(self) -> bool {
1535        matches!(self, State::AfterEqual)
1536    }
1537}
1538
1539#[derive(Copy, Clone, Debug)]
1540enum Radix {
1541    Binary,
1542    Octal,
1543    Decimal,
1544    Hex,
1545}
1546
1547impl Radix {
1548    const fn as_u32(self) -> u32 {
1549        match self {
1550            Radix::Binary => 2,
1551            Radix::Octal => 8,
1552            Radix::Decimal => 10,
1553            Radix::Hex => 16,
1554        }
1555    }
1556
1557    const fn is_digit(self, c: char) -> bool {
1558        match self {
1559            Radix::Binary => matches!(c, '0'..='1'),
1560            Radix::Octal => matches!(c, '0'..='7'),
1561            Radix::Decimal => c.is_ascii_digit(),
1562            Radix::Hex => c.is_ascii_hexdigit(),
1563        }
1564    }
1565}
1566
1567#[derive(Default)]
1568struct RadixRun {
1569    has_digit: bool,
1570    has_nonzero_digit: bool,
1571}
1572
1573const fn is_quote(c: char) -> bool {
1574    matches!(c, '\'' | '"')
1575}
1576
1577fn single_char_prefix(c: char) -> Option<TokenFlags> {
1578    Some(match c {
1579        'f' | 'F' => TokenFlags::F_STRING,
1580        't' | 'T' => TokenFlags::T_STRING,
1581        'u' | 'U' => TokenFlags::UNICODE_STRING,
1582        'b' | 'B' => TokenFlags::BYTE_STRING,
1583        'r' => TokenFlags::RAW_STRING_LOWERCASE,
1584        'R' => TokenFlags::RAW_STRING_UPPERCASE,
1585        _ => return None,
1586    })
1587}
1588
1589const fn is_ascii_identifier_start(c: char) -> bool {
1590    matches!(c, 'a'..='z' | 'A'..='Z' | '_')
1591}
1592
1593// Checks if the character c is a valid starting character as described
1594// in https://docs.python.org/3/reference/lexical_analysis.html#identifiers
1595fn is_unicode_identifier_start(c: char) -> bool {
1596    is_xid_start(c)
1597}
1598
1599/// Checks if the character c is a valid continuation character as described
1600/// in <https://docs.python.org/3/reference/lexical_analysis.html#identifiers>.
1601///
1602/// Additionally, this function also keeps track of whether or not the total
1603/// identifier is ASCII-only or not by mutably altering a reference to a
1604/// boolean value passed in.
1605fn is_identifier_continuation(c: char, identifier_is_ascii_only: &mut bool) -> bool {
1606    // Arrange things such that ASCII codepoints never
1607    // result in the slower `is_xid_continue` getting called.
1608    if c.is_ascii() {
1609        matches!(c, 'a'..='z' | 'A'..='Z' | '_' | '0'..='9')
1610    } else {
1611        let is_continuation = is_xid_continue(c);
1612        if is_continuation {
1613            *identifier_is_ascii_only = false;
1614        }
1615        is_continuation
1616    }
1617}
1618
1619/// Create a new [`Lexer`] for the given source code and [`Mode`].
1620pub fn lex(source: &str, mode: Mode) -> Lexer<'_> {
1621    Lexer::new(source, mode, TextSize::default())
1622}
1623
1624#[cfg(test)]
1625mod tests {
1626    use std::fmt::Write;
1627
1628    use insta::assert_snapshot;
1629    use ruff_python_ast::token::Token;
1630    use ruff_text_size::Ranged;
1631
1632    use super::*;
1633
1634    const WINDOWS_EOL: &str = "\r\n";
1635    const MAC_EOL: &str = "\r";
1636    const UNIX_EOL: &str = "\n";
1637
1638    struct LexerOutput {
1639        tokens: Vec<Token>,
1640        errors: Vec<LexicalError>,
1641    }
1642
1643    impl std::fmt::Display for LexerOutput {
1644        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1645            writeln!(f, "## Tokens")?;
1646            writeln!(f, "```\n{:#?}\n```", self.tokens)?;
1647            if !self.errors.is_empty() {
1648                writeln!(f, "## Errors")?;
1649                writeln!(f, "```\n{:#?}\n```", self.errors)?;
1650            }
1651            Ok(())
1652        }
1653    }
1654
1655    fn lex(source: &str, mode: Mode, start_offset: TextSize) -> LexerOutput {
1656        let mut lexer = Lexer::new(source, mode, start_offset);
1657        let mut tokens = Vec::new();
1658        loop {
1659            let kind = lexer.next_token();
1660            if kind.is_eof() {
1661                break;
1662            }
1663            tokens.push(Token::new(
1664                kind,
1665                lexer.current_range(),
1666                lexer.current_flags(),
1667            ));
1668        }
1669        LexerOutput {
1670            tokens,
1671            errors: lexer.finish(),
1672        }
1673    }
1674
1675    #[track_caller]
1676    fn lex_valid(source: &str, mode: Mode, start_offset: TextSize) -> LexerOutput {
1677        let output = lex(source, mode, start_offset);
1678
1679        if !output.errors.is_empty() {
1680            let mut message = "Unexpected lexical errors for a valid source:\n".to_string();
1681            for error in &output.errors {
1682                writeln!(&mut message, "{error:?}").unwrap();
1683            }
1684            writeln!(&mut message, "Source:\n{source}").unwrap();
1685            panic!("{message}");
1686        }
1687
1688        output
1689    }
1690
1691    #[track_caller]
1692    fn lex_invalid(source: &str, mode: Mode) -> LexerOutput {
1693        let output = lex(source, mode, TextSize::default());
1694
1695        assert!(
1696            !output.errors.is_empty(),
1697            "Expected lexer to generate at least one error for the following source:\n{source}"
1698        );
1699
1700        output
1701    }
1702
1703    #[track_caller]
1704    fn lex_source(source: &str) -> LexerOutput {
1705        lex_valid(source, Mode::Module, TextSize::default())
1706    }
1707
1708    #[track_caller]
1709    fn lex_source_with_offset(source: &str, start_offset: TextSize) -> LexerOutput {
1710        lex_valid(source, Mode::Module, start_offset)
1711    }
1712
1713    #[track_caller]
1714    fn lex_jupyter_source(source: &str) -> LexerOutput {
1715        lex_valid(source, Mode::Ipython, TextSize::default())
1716    }
1717
1718    #[test]
1719    fn bom() {
1720        let source = "\u{feff}x = 1";
1721        assert_snapshot!(lex_source(source));
1722    }
1723
1724    #[test]
1725    fn bom_with_offset() {
1726        let source = "\u{feff}x + y + z";
1727        assert_snapshot!(lex_source_with_offset(source, TextSize::new(7)));
1728    }
1729
1730    #[test]
1731    fn bom_with_offset_edge() {
1732        // BOM offsets the first token by 3, so make sure that lexing from offset 11 (variable z)
1733        // doesn't panic. Refer https://github.com/astral-sh/ruff/issues/11731
1734        let source = "\u{feff}x + y + z";
1735        assert_snapshot!(lex_source_with_offset(source, TextSize::new(11)));
1736    }
1737
1738    fn ipython_escape_command_line_continuation_eol(eol: &str) -> LexerOutput {
1739        let source = format!("%matplotlib \\{eol}  --inline");
1740        lex_jupyter_source(&source)
1741    }
1742
1743    #[test]
1744    fn test_ipython_escape_command_line_continuation_unix_eol() {
1745        assert_snapshot!(ipython_escape_command_line_continuation_eol(UNIX_EOL));
1746    }
1747
1748    #[test]
1749    fn test_ipython_escape_command_line_continuation_mac_eol() {
1750        assert_snapshot!(ipython_escape_command_line_continuation_eol(MAC_EOL));
1751    }
1752
1753    #[test]
1754    fn test_ipython_escape_command_line_continuation_windows_eol() {
1755        assert_snapshot!(ipython_escape_command_line_continuation_eol(WINDOWS_EOL));
1756    }
1757
1758    fn ipython_escape_command_line_continuation_with_eol_and_eof(eol: &str) -> LexerOutput {
1759        let source = format!("%matplotlib \\{eol}");
1760        lex_jupyter_source(&source)
1761    }
1762
1763    #[test]
1764    fn test_ipython_escape_command_line_continuation_with_unix_eol_and_eof() {
1765        assert_snapshot!(ipython_escape_command_line_continuation_with_eol_and_eof(
1766            UNIX_EOL
1767        ));
1768    }
1769
1770    #[test]
1771    fn test_ipython_escape_command_line_continuation_with_mac_eol_and_eof() {
1772        assert_snapshot!(ipython_escape_command_line_continuation_with_eol_and_eof(
1773            MAC_EOL
1774        ));
1775    }
1776
1777    #[test]
1778    fn test_ipython_escape_command_line_continuation_with_windows_eol_and_eof() {
1779        assert_snapshot!(ipython_escape_command_line_continuation_with_eol_and_eof(
1780            WINDOWS_EOL
1781        ));
1782    }
1783
1784    #[test]
1785    fn test_empty_ipython_escape_command() {
1786        let source = "%\n%%\n!\n!!\n?\n??\n/\n,\n;";
1787        assert_snapshot!(lex_jupyter_source(source));
1788    }
1789
1790    #[test]
1791    fn test_ipython_escape_command() {
1792        let source = r"
1793?foo
1794??foo
1795%timeit a = b
1796%timeit a % 3
1797%matplotlib \
1798    --inline
1799!pwd \
1800  && ls -a | sed 's/^/\\    /'
1801!!cd /Users/foo/Library/Application\ Support/
1802/foo 1 2
1803,foo 1 2
1804;foo 1 2
1805!ls
1806"
1807        .trim();
1808        assert_snapshot!(lex_jupyter_source(source));
1809    }
1810
1811    #[test]
1812    fn test_ipython_help_end_escape_command() {
1813        let source = r"
1814?foo?
1815??   foo?
1816??   foo  ?
1817?foo??
1818??foo??
1819???foo?
1820???foo??
1821??foo???
1822???foo???
1823?? \
1824    foo?
1825?? \
1826?
1827????
1828%foo?
1829%foo??
1830%%foo???
1831!pwd?"
1832            .trim();
1833        assert_snapshot!(lex_jupyter_source(source));
1834    }
1835
1836    #[test]
1837    fn test_ipython_escape_command_indentation() {
1838        let source = r"
1839if True:
1840    %matplotlib \
1841        --inline"
1842            .trim();
1843        assert_snapshot!(lex_jupyter_source(source));
1844    }
1845
1846    #[test]
1847    fn test_ipython_escape_command_assignment() {
1848        let source = r"
1849pwd = !pwd
1850foo = %timeit a = b
1851bar = %timeit a % 3
1852baz = %matplotlib \
1853        inline
1854qux = %foo?
1855quux = !pwd?"
1856            .trim();
1857        assert_snapshot!(lex_jupyter_source(source));
1858    }
1859
1860    fn assert_no_ipython_escape_command(tokens: &[Token]) {
1861        for token in tokens {
1862            if matches!(token.kind(), TokenKind::IpyEscapeCommand) {
1863                panic!("Unexpected escape command token at {:?}", token.range())
1864            }
1865        }
1866    }
1867
1868    #[test]
1869    fn test_ipython_escape_command_not_an_assignment() {
1870        let source = r"
1871# Other escape kinds are not valid here (can't test `foo = ?str` because '?' is not a valid token)
1872foo = /func
1873foo = ;func
1874foo = ,func
1875
1876(foo == %timeit a = b)
1877(foo := %timeit a = b)
1878def f(arg=%timeit a = b):
1879    pass"
1880            .trim();
1881        let output = lex(source, Mode::Ipython, TextSize::default());
1882        assert!(output.errors.is_empty());
1883        assert_no_ipython_escape_command(&output.tokens);
1884    }
1885
1886    #[test]
1887    fn test_numbers() {
1888        let source = "0x2f 0o12 0b1101 0 123 123_45_67_890 0.2 1e+2 2.1e3 2j 2.2j 000 0x995DC9BBDF1939FA 0x995DC9BBDF1939FA995DC9BBDF1939FA";
1889        assert_snapshot!(lex_source(source));
1890    }
1891
1892    #[test]
1893    fn test_invalid_leading_zero_small() {
1894        let source = "025";
1895        assert_snapshot!(lex_invalid(source, Mode::Module));
1896    }
1897
1898    #[test]
1899    fn test_invalid_leading_zero_big() {
1900        let source =
1901            "0252222222222222522222222222225222222222222252222222222222522222222222225222222222222";
1902        assert_snapshot!(lex_invalid(source, Mode::Module));
1903    }
1904
1905    #[test]
1906    fn test_line_comment_long() {
1907        let source = "99232  # foo".to_string();
1908        assert_snapshot!(lex_source(&source));
1909    }
1910
1911    #[test]
1912    fn test_line_comment_whitespace() {
1913        let source = "99232  #  ".to_string();
1914        assert_snapshot!(lex_source(&source));
1915    }
1916
1917    #[test]
1918    fn test_line_comment_single_whitespace() {
1919        let source = "99232  # ".to_string();
1920        assert_snapshot!(lex_source(&source));
1921    }
1922
1923    #[test]
1924    fn test_line_comment_empty() {
1925        let source = "99232  #".to_string();
1926        assert_snapshot!(lex_source(&source));
1927    }
1928
1929    fn comment_until_eol(eol: &str) -> LexerOutput {
1930        let source = format!("123  # Foo{eol}456");
1931        lex_source(&source)
1932    }
1933
1934    #[test]
1935    fn test_comment_until_unix_eol() {
1936        assert_snapshot!(comment_until_eol(UNIX_EOL));
1937    }
1938
1939    #[test]
1940    fn test_comment_until_mac_eol() {
1941        assert_snapshot!(comment_until_eol(MAC_EOL));
1942    }
1943
1944    #[test]
1945    fn test_comment_until_windows_eol() {
1946        assert_snapshot!(comment_until_eol(WINDOWS_EOL));
1947    }
1948
1949    #[test]
1950    fn test_assignment() {
1951        let source = r"a_variable = 99 + 2-0";
1952        assert_snapshot!(lex_source(source));
1953    }
1954
1955    fn indentation_with_eol(eol: &str) -> LexerOutput {
1956        let source = format!("def foo():{eol}    return 99{eol}{eol}");
1957        lex_source(&source)
1958    }
1959
1960    #[test]
1961    fn test_indentation_with_unix_eol() {
1962        assert_snapshot!(indentation_with_eol(UNIX_EOL));
1963    }
1964
1965    #[test]
1966    fn test_indentation_with_mac_eol() {
1967        assert_snapshot!(indentation_with_eol(MAC_EOL));
1968    }
1969
1970    #[test]
1971    fn test_indentation_with_windows_eol() {
1972        assert_snapshot!(indentation_with_eol(WINDOWS_EOL));
1973    }
1974
1975    fn double_dedent_with_eol(eol: &str) -> LexerOutput {
1976        let source = format!("def foo():{eol} if x:{eol}{eol}  return 99{eol}{eol}");
1977        lex_source(&source)
1978    }
1979
1980    #[test]
1981    fn test_double_dedent_with_unix_eol() {
1982        assert_snapshot!(double_dedent_with_eol(UNIX_EOL));
1983    }
1984
1985    #[test]
1986    fn test_double_dedent_with_mac_eol() {
1987        assert_snapshot!(double_dedent_with_eol(MAC_EOL));
1988    }
1989
1990    #[test]
1991    fn test_double_dedent_with_windows_eol() {
1992        assert_snapshot!(double_dedent_with_eol(WINDOWS_EOL));
1993    }
1994
1995    fn double_dedent_with_tabs_eol(eol: &str) -> LexerOutput {
1996        let source = format!("def foo():{eol}\tif x:{eol}{eol}\t\t return 99{eol}{eol}");
1997        lex_source(&source)
1998    }
1999
2000    #[test]
2001    fn test_double_dedent_with_tabs_unix_eol() {
2002        assert_snapshot!(double_dedent_with_tabs_eol(UNIX_EOL));
2003    }
2004
2005    #[test]
2006    fn test_double_dedent_with_tabs_mac_eol() {
2007        assert_snapshot!(double_dedent_with_tabs_eol(MAC_EOL));
2008    }
2009
2010    #[test]
2011    fn test_double_dedent_with_tabs_windows_eol() {
2012        assert_snapshot!(double_dedent_with_tabs_eol(WINDOWS_EOL));
2013    }
2014
2015    #[test]
2016    fn dedent_after_whitespace() {
2017        let source = "\
2018if first:
2019    if second:
2020        pass
2021    foo
2022";
2023        assert_snapshot!(lex_source(source));
2024    }
2025
2026    fn newline_in_brackets_eol(eol: &str) -> LexerOutput {
2027        let source = r"x = [
2028
2029    1,2
2030,(3,
20314,
2032), {
20335,
20346,\
20357}]
2036"
2037        .replace('\n', eol);
2038        lex_source(&source)
2039    }
2040
2041    #[test]
2042    fn test_newline_in_brackets_unix_eol() {
2043        assert_snapshot!(newline_in_brackets_eol(UNIX_EOL));
2044    }
2045
2046    #[test]
2047    fn test_newline_in_brackets_mac_eol() {
2048        assert_snapshot!(newline_in_brackets_eol(MAC_EOL));
2049    }
2050
2051    #[test]
2052    fn test_newline_in_brackets_windows_eol() {
2053        assert_snapshot!(newline_in_brackets_eol(WINDOWS_EOL));
2054    }
2055
2056    #[test]
2057    fn test_non_logical_newline_in_string_continuation() {
2058        let source = r"(
2059    'a'
2060    'b'
2061
2062    'c' \
2063    'd'
2064)";
2065        assert_snapshot!(lex_source(source));
2066    }
2067
2068    #[test]
2069    fn test_logical_newline_line_comment() {
2070        let source = "#Hello\n#World\n";
2071        assert_snapshot!(lex_source(source));
2072    }
2073
2074    #[test]
2075    fn test_operators() {
2076        let source = "//////=/ /";
2077        assert_snapshot!(lex_source(source));
2078    }
2079
2080    #[test]
2081    fn test_string() {
2082        let source = r#""double" 'single' 'can\'t' "\\\"" '\t\r\n' '\g' r'raw\'' '\420' '\200\0a'"#;
2083        assert_snapshot!(lex_source(source));
2084    }
2085
2086    fn string_continuation_with_eol(eol: &str) -> LexerOutput {
2087        let source = format!("\"abc\\{eol}def\"");
2088        lex_source(&source)
2089    }
2090
2091    #[test]
2092    fn test_string_continuation_with_unix_eol() {
2093        assert_snapshot!(string_continuation_with_eol(UNIX_EOL));
2094    }
2095
2096    #[test]
2097    fn test_string_continuation_with_mac_eol() {
2098        assert_snapshot!(string_continuation_with_eol(MAC_EOL));
2099    }
2100
2101    #[test]
2102    fn test_string_continuation_with_windows_eol() {
2103        assert_snapshot!(string_continuation_with_eol(WINDOWS_EOL));
2104    }
2105
2106    #[test]
2107    fn test_escape_unicode_name() {
2108        let source = r#""\N{EN SPACE}""#;
2109        assert_snapshot!(lex_source(source));
2110    }
2111
2112    #[test]
2113    fn test_non_ascii_name_flag() {
2114        let mut lexer = Lexer::new("a€\naβ = β\nascii", Mode::Module, TextSize::default());
2115        let mut flags = Vec::new();
2116        loop {
2117            let kind = lexer.next_token();
2118            if kind.is_eof() {
2119                break;
2120            }
2121            if kind == TokenKind::Name {
2122                flags.push(lexer.current_flags().is_non_ascii_name());
2123            }
2124        }
2125
2126        assert_eq!(lexer.finish().len(), 1);
2127        assert_eq!(flags, [false, true, true, false]);
2128    }
2129
2130    fn triple_quoted_eol(eol: &str) -> LexerOutput {
2131        let source = format!("\"\"\"{eol} test string{eol} \"\"\"");
2132        lex_source(&source)
2133    }
2134
2135    #[test]
2136    fn test_triple_quoted_unix_eol() {
2137        assert_snapshot!(triple_quoted_eol(UNIX_EOL));
2138    }
2139
2140    #[test]
2141    fn test_triple_quoted_mac_eol() {
2142        assert_snapshot!(triple_quoted_eol(MAC_EOL));
2143    }
2144
2145    #[test]
2146    fn test_triple_quoted_windows_eol() {
2147        assert_snapshot!(triple_quoted_eol(WINDOWS_EOL));
2148    }
2149
2150    fn line_continuation_at_eof_after_newline(eol: &str) -> LexerOutput {
2151        let source = format!(r"\{eol}");
2152        lex_invalid(&source, Mode::Module)
2153    }
2154
2155    #[test]
2156    fn test_line_continuation_at_eof_after_newline_unix_eol() {
2157        assert_snapshot!(line_continuation_at_eof_after_newline(UNIX_EOL));
2158    }
2159
2160    #[test]
2161    fn test_line_continuation_at_eof_after_newline_mac_eol() {
2162        assert_snapshot!(line_continuation_at_eof_after_newline(MAC_EOL));
2163    }
2164
2165    #[test]
2166    fn test_line_continuation_at_eof_after_newline_windows_eol() {
2167        assert_snapshot!(line_continuation_at_eof_after_newline(WINDOWS_EOL));
2168    }
2169
2170    fn line_continuation_at_eof(eol: &str) -> LexerOutput {
2171        let source = format!(r"1, \{eol}");
2172        lex_invalid(&source, Mode::Module)
2173    }
2174
2175    #[test]
2176    fn test_line_continuation_at_eof_unix_eol() {
2177        assert_snapshot!(line_continuation_at_eof(UNIX_EOL));
2178    }
2179
2180    #[test]
2181    fn test_line_continuation_at_eof_mac_eol() {
2182        assert_snapshot!(line_continuation_at_eof(MAC_EOL));
2183    }
2184
2185    #[test]
2186    fn test_line_continuation_at_eof_windows_eol() {
2187        assert_snapshot!(line_continuation_at_eof(WINDOWS_EOL));
2188    }
2189
2190    // This test case is to just make sure that the lexer doesn't go into
2191    // infinite loop on invalid input.
2192    #[test]
2193    fn test_infinite_loop() {
2194        let source = "[1";
2195        lex_invalid(source, Mode::Module);
2196    }
2197
2198    /// Emoji identifiers are a non-standard python feature and are not supported by our lexer.
2199    #[test]
2200    fn test_emoji_identifier() {
2201        let source = "🐦";
2202        assert_snapshot!(lex_invalid(source, Mode::Module));
2203    }
2204
2205    #[test]
2206    fn tet_too_low_dedent() {
2207        let source = "if True:
2208    pass
2209  pass";
2210        assert_snapshot!(lex_invalid(source, Mode::Module));
2211    }
2212
2213    #[test]
2214    fn test_empty_fstrings() {
2215        let source = r#"f"" "" F"" f'' '' f"""""" f''''''"#;
2216        assert_snapshot!(lex_source(source));
2217    }
2218
2219    #[test]
2220    fn test_fstring_prefix() {
2221        let source = r#"f"" F"" rf"" rF"" Rf"" RF"" fr"" Fr"" fR"" FR"""#;
2222        assert_snapshot!(lex_source(source));
2223    }
2224
2225    #[test]
2226    fn test_fstring() {
2227        let source = r#"f"normal {foo} {{another}} {bar} {{{three}}}""#;
2228        assert_snapshot!(lex_source(source));
2229    }
2230
2231    #[test]
2232    fn test_fstring_parentheses() {
2233        let source = r#"f"{}" f"{{}}" f" {}" f"{{{}}}" f"{{{{}}}}" f" {} {{}} {{{}}} {{{{}}}}  ""#;
2234        assert_snapshot!(lex_source(source));
2235    }
2236
2237    fn fstring_single_quote_escape_eol(eol: &str) -> LexerOutput {
2238        let source = format!(r"f'text \{eol} more text'");
2239        lex_source(&source)
2240    }
2241
2242    #[test]
2243    fn test_fstring_single_quote_escape_unix_eol() {
2244        assert_snapshot!(fstring_single_quote_escape_eol(UNIX_EOL));
2245    }
2246
2247    #[test]
2248    fn test_fstring_single_quote_escape_mac_eol() {
2249        assert_snapshot!(fstring_single_quote_escape_eol(MAC_EOL));
2250    }
2251
2252    #[test]
2253    fn test_fstring_single_quote_escape_windows_eol() {
2254        assert_snapshot!(fstring_single_quote_escape_eol(WINDOWS_EOL));
2255    }
2256
2257    #[test]
2258    fn test_fstring_escape() {
2259        let source = r#"f"\{x:\"\{x}} \"\"\
2260 end""#;
2261        assert_snapshot!(lex_source(source));
2262    }
2263
2264    #[test]
2265    fn test_fstring_escape_braces() {
2266        let source = r"f'\{foo}' f'\\{foo}' f'\{{foo}}' f'\\{{foo}}'";
2267        assert_snapshot!(lex_source(source));
2268    }
2269
2270    #[test]
2271    fn test_fstring_escape_raw() {
2272        let source = r#"rf"\{x:\"\{x}} \"\"\
2273 end""#;
2274        assert_snapshot!(lex_source(source));
2275    }
2276
2277    #[test]
2278    fn test_fstring_named_unicode() {
2279        let source = r#"f"\N{BULLET} normal \Nope \N""#;
2280        assert_snapshot!(lex_source(source));
2281    }
2282
2283    #[test]
2284    fn test_fstring_named_unicode_raw() {
2285        let source = r#"rf"\N{BULLET} normal""#;
2286        assert_snapshot!(lex_source(source));
2287    }
2288
2289    #[test]
2290    fn test_fstring_with_named_expression() {
2291        let source = r#"f"{x:=10} {(x:=10)} {x,{y:=10}} {[x:=10]}""#;
2292        assert_snapshot!(lex_source(source));
2293    }
2294
2295    #[test]
2296    fn test_fstring_with_format_spec() {
2297        let source = r#"f"{foo:} {x=!s:.3f} {x:.{y}f} {'':*^{1:{1}}} {x:{{1}.pop()}}""#;
2298        assert_snapshot!(lex_source(source));
2299    }
2300
2301    #[test]
2302    fn test_fstring_with_multiline_format_spec() {
2303        // The last f-string is invalid syntactically but we should still lex it.
2304        // Note that the `b` is a `Name` token and not a `FStringMiddle` token.
2305        let source = r"f'''__{
2306    x:d
2307}__'''
2308f'''__{
2309    x:a
2310        b
2311          c
2312}__'''
2313";
2314        assert_snapshot!(lex_source(source));
2315    }
2316
2317    #[test]
2318    fn test_fstring_newline_format_spec() {
2319        let source = r"
2320f'__{
2321    x:d
2322}__'
2323f'__{
2324    x:a
2325        b
2326}__'
2327";
2328        assert_snapshot!(lex_invalid(source, Mode::Module));
2329    }
2330
2331    #[test]
2332    fn test_fstring_conversion() {
2333        let source = r#"f"{x!s} {x=!r} {x:.3f!r} {{x!r}}""#;
2334        assert_snapshot!(lex_source(source));
2335    }
2336
2337    #[test]
2338    fn test_fstring_nested() {
2339        let source = r#"f"foo {f"bar {x + f"{wow}"}"} baz" f'foo {f'bar'} some {f"another"}'"#;
2340        assert_snapshot!(lex_source(source));
2341    }
2342
2343    #[test]
2344    fn test_fstring_expression_multiline() {
2345        let source = r#"f"first {
2346    x
2347        *
2348            y
2349} second""#;
2350        assert_snapshot!(lex_source(source));
2351    }
2352
2353    #[test]
2354    fn test_fstring_multiline() {
2355        let source = r#"f"""
2356hello
2357    world
2358""" f'''
2359    world
2360hello
2361''' f"some {f"""multiline
2362allowed {x}"""} string""#;
2363        assert_snapshot!(lex_source(source));
2364    }
2365
2366    #[test]
2367    fn test_fstring_comments() {
2368        let source = r#"f"""
2369# not a comment { # comment {
2370    x
2371} # not a comment
2372""""#;
2373        assert_snapshot!(lex_source(source));
2374    }
2375
2376    #[test]
2377    fn test_fstring_with_ipy_escape_command() {
2378        let source = r#"f"foo {!pwd} bar""#;
2379        assert_snapshot!(lex_source(source));
2380    }
2381
2382    #[test]
2383    fn test_fstring_with_lambda_expression() {
2384        let source = r#"
2385f"{lambda x:{x}}"
2386f"{(lambda x:{x})}"
2387"#
2388        .trim();
2389        assert_snapshot!(lex_source(source));
2390    }
2391
2392    #[test]
2393    fn test_fstring_with_nul_char() {
2394        let source = r"f'\0'";
2395        assert_snapshot!(lex_source(source));
2396    }
2397
2398    #[test]
2399    fn test_empty_tstrings() {
2400        let source = r#"t"" "" t"" t'' '' t"""""" t''''''"#;
2401        assert_snapshot!(lex_source(source));
2402    }
2403
2404    #[test]
2405    fn test_tstring_prefix() {
2406        let source = r#"t"" t"" rt"" rt"" Rt"" Rt"" tr"" Tr"" tR"" TR"""#;
2407        assert_snapshot!(lex_source(source));
2408    }
2409
2410    #[test]
2411    fn test_tstring() {
2412        let source = r#"t"normal {foo} {{another}} {bar} {{{three}}}""#;
2413        assert_snapshot!(lex_source(source));
2414    }
2415
2416    #[test]
2417    fn test_tstring_parentheses() {
2418        let source = r#"t"{}" t"{{}}" t" {}" t"{{{}}}" t"{{{{}}}}" t" {} {{}} {{{}}} {{{{}}}}  ""#;
2419        assert_snapshot!(lex_source(source));
2420    }
2421
2422    fn tstring_single_quote_escape_eol(eol: &str) -> LexerOutput {
2423        let source = format!(r"t'text \{eol} more text'");
2424        lex_source(&source)
2425    }
2426
2427    #[test]
2428    fn test_tstring_single_quote_escape_unix_eol() {
2429        assert_snapshot!(tstring_single_quote_escape_eol(UNIX_EOL));
2430    }
2431
2432    #[test]
2433    fn test_tstring_single_quote_escape_mac_eol() {
2434        assert_snapshot!(tstring_single_quote_escape_eol(MAC_EOL));
2435    }
2436
2437    #[test]
2438    fn test_tstring_single_quote_escape_windows_eol() {
2439        assert_snapshot!(tstring_single_quote_escape_eol(WINDOWS_EOL));
2440    }
2441
2442    #[test]
2443    fn test_tstring_escape() {
2444        let source = r#"t"\{x:\"\{x}} \"\"\
2445 end""#;
2446        assert_snapshot!(lex_source(source));
2447    }
2448
2449    #[test]
2450    fn test_tstring_escape_braces() {
2451        let source = r"t'\{foo}' t'\\{foo}' t'\{{foo}}' t'\\{{foo}}'";
2452        assert_snapshot!(lex_source(source));
2453    }
2454
2455    #[test]
2456    fn test_tstring_escape_raw() {
2457        let source = r#"rt"\{x:\"\{x}} \"\"\
2458 end""#;
2459        assert_snapshot!(lex_source(source));
2460    }
2461
2462    #[test]
2463    fn test_tstring_named_unicode() {
2464        let source = r#"t"\N{BULLET} normal \Nope \N""#;
2465        assert_snapshot!(lex_source(source));
2466    }
2467
2468    #[test]
2469    fn test_tstring_named_unicode_raw() {
2470        let source = r#"rt"\N{BULLET} normal""#;
2471        assert_snapshot!(lex_source(source));
2472    }
2473
2474    #[test]
2475    fn test_tstring_with_named_expression() {
2476        let source = r#"t"{x:=10} {(x:=10)} {x,{y:=10}} {[x:=10]}""#;
2477        assert_snapshot!(lex_source(source));
2478    }
2479
2480    #[test]
2481    fn test_tstring_with_format_spec() {
2482        let source = r#"t"{foo:} {x=!s:.3f} {x:.{y}f} {'':*^{1:{1}}} {x:{{1}.pop()}}""#;
2483        assert_snapshot!(lex_source(source));
2484    }
2485
2486    #[test]
2487    fn test_tstring_with_multiline_format_spec() {
2488        // The last t-string is invalid syntactically but we should still lex it.
2489        // Note that the `b` is a `Name` token and not a `TStringMiddle` token.
2490        let source = r"t'''__{
2491    x:d
2492}__'''
2493t'''__{
2494    x:a
2495        b
2496          c
2497}__'''
2498";
2499        assert_snapshot!(lex_source(source));
2500    }
2501
2502    #[test]
2503    fn test_tstring_newline_format_spec() {
2504        let source = r"
2505t'__{
2506    x:d
2507}__'
2508t'__{
2509    x:a
2510        b
2511}__'
2512";
2513        assert_snapshot!(lex_invalid(source, Mode::Module));
2514    }
2515
2516    #[test]
2517    fn test_tstring_conversion() {
2518        let source = r#"t"{x!s} {x=!r} {x:.3f!r} {{x!r}}""#;
2519        assert_snapshot!(lex_source(source));
2520    }
2521
2522    #[test]
2523    fn test_tstring_nested() {
2524        let source = r#"t"foo {t"bar {x + t"{wow}"}"} baz" t'foo {t'bar'} some {t"another"}'"#;
2525        assert_snapshot!(lex_source(source));
2526    }
2527
2528    #[test]
2529    fn test_tstring_expression_multiline() {
2530        let source = r#"t"first {
2531    x
2532        *
2533            y
2534} second""#;
2535        assert_snapshot!(lex_source(source));
2536    }
2537
2538    #[test]
2539    fn test_tstring_multiline() {
2540        let source = r#"t"""
2541hello
2542    world
2543""" t'''
2544    world
2545hello
2546''' t"some {t"""multiline
2547allowed {x}"""} string""#;
2548        assert_snapshot!(lex_source(source));
2549    }
2550
2551    #[test]
2552    fn test_tstring_comments() {
2553        let source = r#"t"""
2554# not a comment { # comment {
2555    x
2556} # not a comment
2557""""#;
2558        assert_snapshot!(lex_source(source));
2559    }
2560
2561    #[test]
2562    fn test_tstring_with_ipy_escape_command() {
2563        let source = r#"t"foo {!pwd} bar""#;
2564        assert_snapshot!(lex_source(source));
2565    }
2566
2567    #[test]
2568    fn test_tstring_with_lambda_expression() {
2569        let source = r#"
2570t"{lambda x:{x}}"
2571t"{(lambda x:{x})}"
2572"#
2573        .trim();
2574        assert_snapshot!(lex_source(source));
2575    }
2576
2577    #[test]
2578    fn test_tstring_with_nul_char() {
2579        let source = r"t'\0'";
2580        assert_snapshot!(lex_source(source));
2581    }
2582
2583    #[test]
2584    fn test_nested_t_and_fstring() {
2585        let source = r#"t"foo {f"bar {x + t"{wow}"}"} baz" f'foo {t'bar'!r} some {f"another"}'"#;
2586        assert_snapshot!(lex_source(source));
2587    }
2588
2589    #[test]
2590    fn test_match_softkeyword_in_notebook() {
2591        let source = r"match foo:
2592    case bar:
2593        pass";
2594        assert_snapshot!(lex_jupyter_source(source));
2595    }
2596
2597    fn lex_fstring_error(source: &str) -> InterpolatedStringErrorType {
2598        let output = lex(source, Mode::Module, TextSize::default());
2599        match output
2600            .errors
2601            .into_iter()
2602            .next()
2603            .expect("lexer should give at least one error")
2604            .into_error()
2605        {
2606            LexicalErrorType::FStringError(error) => error,
2607            err => panic!("Expected FStringError: {err:?}"),
2608        }
2609    }
2610
2611    #[test]
2612    fn test_fstring_error() {
2613        use InterpolatedStringErrorType::{
2614            SingleRbrace, UnterminatedString, UnterminatedTripleQuotedString,
2615        };
2616
2617        assert_eq!(lex_fstring_error("f'}'"), SingleRbrace);
2618        assert_eq!(lex_fstring_error("f'{{}'"), SingleRbrace);
2619        assert_eq!(lex_fstring_error("f'{{}}}'"), SingleRbrace);
2620        assert_eq!(lex_fstring_error("f'foo}'"), SingleRbrace);
2621        assert_eq!(lex_fstring_error(r"f'\u007b}'"), SingleRbrace);
2622        assert_eq!(lex_fstring_error("f'{a:b}}'"), SingleRbrace);
2623        assert_eq!(lex_fstring_error("f'{3:}}>10}'"), SingleRbrace);
2624        assert_eq!(lex_fstring_error(r"f'\{foo}\}'"), SingleRbrace);
2625
2626        assert_eq!(lex_fstring_error(r#"f""#), UnterminatedString);
2627        assert_eq!(lex_fstring_error(r"f'"), UnterminatedString);
2628
2629        assert_eq!(lex_fstring_error(r#"f""""#), UnterminatedTripleQuotedString);
2630        assert_eq!(lex_fstring_error(r"f'''"), UnterminatedTripleQuotedString);
2631        assert_eq!(
2632            lex_fstring_error(r#"f"""""#),
2633            UnterminatedTripleQuotedString
2634        );
2635        assert_eq!(
2636            lex_fstring_error(r#"f""""""#),
2637            UnterminatedTripleQuotedString
2638        );
2639    }
2640
2641    fn lex_tstring_error(source: &str) -> InterpolatedStringErrorType {
2642        let output = lex(source, Mode::Module, TextSize::default());
2643        match output
2644            .errors
2645            .into_iter()
2646            .next()
2647            .expect("lexer should give at least one error")
2648            .into_error()
2649        {
2650            LexicalErrorType::TStringError(error) => error,
2651            err => panic!("Expected TStringError: {err:?}"),
2652        }
2653    }
2654
2655    #[test]
2656    fn lex_fstring_unclosed() {
2657        let source = r#"f"hello"#;
2658
2659        assert_snapshot!(lex_invalid(source, Mode::Module), @"
2660        ## Tokens
2661        ```
2662        [
2663            FStringStart 0..2 (flags = DOUBLE_QUOTES | F_STRING),
2664            FStringMiddle 2..7 (flags = DOUBLE_QUOTES | F_STRING),
2665            Newline 7..7,
2666        ]
2667        ```
2668        ## Errors
2669        ```
2670        [
2671            LexicalError {
2672                error: FStringError(
2673                    UnterminatedString,
2674                ),
2675                location: 2..7,
2676            },
2677        ]
2678        ```
2679        ");
2680    }
2681
2682    #[test]
2683    fn lex_fstring_missing_brace() {
2684        let source = "f'{'";
2685
2686        assert_snapshot!(lex_invalid(source, Mode::Module), @"
2687        ## Tokens
2688        ```
2689        [
2690            FStringStart 0..2 (flags = F_STRING),
2691            Lbrace 2..3,
2692            String 3..4 (flags = UNCLOSED_STRING),
2693            Newline 4..4,
2694        ]
2695        ```
2696        ## Errors
2697        ```
2698        [
2699            LexicalError {
2700                error: UnclosedStringError,
2701                location: 3..4,
2702            },
2703            LexicalError {
2704                error: FStringError(
2705                    UnterminatedString,
2706                ),
2707                location: 4..4,
2708            },
2709        ]
2710        ```
2711        ");
2712    }
2713
2714    #[test]
2715    fn lex_fstring_missing_brace_after_format_spec() {
2716        let source = r#"f"{foo!r""#;
2717
2718        assert_snapshot!(lex_invalid(source, Mode::Module), @"
2719        ## Tokens
2720        ```
2721        [
2722            FStringStart 0..2 (flags = DOUBLE_QUOTES | F_STRING),
2723            Lbrace 2..3,
2724            Name 3..6,
2725            Exclamation 6..7,
2726            String 7..9 (flags = DOUBLE_QUOTES | RAW_STRING_LOWERCASE | UNCLOSED_STRING),
2727            Newline 9..9,
2728        ]
2729        ```
2730        ## Errors
2731        ```
2732        [
2733            LexicalError {
2734                error: UnclosedStringError,
2735                location: 7..9,
2736            },
2737            LexicalError {
2738                error: FStringError(
2739                    UnterminatedString,
2740                ),
2741                location: 9..9,
2742            },
2743        ]
2744        ```
2745        ");
2746    }
2747
2748    #[test]
2749    fn test_tstring_error() {
2750        use InterpolatedStringErrorType::{
2751            SingleRbrace, UnterminatedString, UnterminatedTripleQuotedString,
2752        };
2753
2754        assert_eq!(lex_tstring_error("t'}'"), SingleRbrace);
2755        assert_eq!(lex_tstring_error("t'{{}'"), SingleRbrace);
2756        assert_eq!(lex_tstring_error("t'{{}}}'"), SingleRbrace);
2757        assert_eq!(lex_tstring_error("t'foo}'"), SingleRbrace);
2758        assert_eq!(lex_tstring_error(r"t'\u007b}'"), SingleRbrace);
2759        assert_eq!(lex_tstring_error("t'{a:b}}'"), SingleRbrace);
2760        assert_eq!(lex_tstring_error("t'{3:}}>10}'"), SingleRbrace);
2761        assert_eq!(lex_tstring_error(r"t'\{foo}\}'"), SingleRbrace);
2762
2763        assert_eq!(lex_tstring_error(r#"t""#), UnterminatedString);
2764        assert_eq!(lex_tstring_error(r"t'"), UnterminatedString);
2765
2766        assert_eq!(lex_tstring_error(r#"t""""#), UnterminatedTripleQuotedString);
2767        assert_eq!(lex_tstring_error(r"t'''"), UnterminatedTripleQuotedString);
2768        assert_eq!(
2769            lex_tstring_error(r#"t"""""#),
2770            UnterminatedTripleQuotedString
2771        );
2772        assert_eq!(
2773            lex_tstring_error(r#"t""""""#),
2774            UnterminatedTripleQuotedString
2775        );
2776    }
2777
2778    #[test]
2779    fn backslash_continuation_indentation() {
2780        // The first `\` has 4 spaces before it which matches the indentation level at that point,
2781        // so the whitespace before `2` is irrelevant and shouldn't produce an indentation error.
2782        // Similarly, the second `\` is also at the same indentation level, so the `3` line is also
2783        // valid.
2784        let source = r"if True:
2785    1
2786    \
2787        2
2788    \
27893
2790else:
2791    pass
2792"
2793        .to_string();
2794        assert_snapshot!(lex_source(&source));
2795    }
2796
2797    #[test]
2798    fn backslash_continuation_at_root() {
2799        // But, it's a different when the backslash character itself is at the root indentation
2800        // level. Then, the whitespaces following it determines the indentation level of the next
2801        // line, so `1` is indented with 4 spaces and `2` is indented with 8 spaces, and `3` is
2802        // indented with 4 spaces, all of which are valid.
2803        let source = r"if True:
2804\
2805    1
2806    if True:
2807\
2808        2
2809else:\
2810    3
2811"
2812        .to_string();
2813        assert_snapshot!(lex_source(&source));
2814    }
2815
2816    #[test]
2817    fn multiple_backslash_continuation() {
2818        // It's only the first backslash character that determines the indentation level of the next
2819        // line, so all the lines after the first `\` are indented with 4 spaces, and the remaining
2820        // backslashes are just ignored and don't affect the indentation level.
2821        let source = r"if True:
2822    1
2823    \
2824            \
2825        \
2826    \
2827    2
2828"
2829        .to_string();
2830        assert_snapshot!(lex_source(&source));
2831    }
2832
2833    #[test]
2834    fn backslash_continuation_mismatch_indentation() {
2835        // Indentation doesn't match any previous indentation level
2836        let source = r"if True:
2837    1
2838  \
2839    2
2840"
2841        .to_string();
2842        assert_snapshot!(lex_invalid(&source, Mode::Module));
2843    }
2844}