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