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