]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_lexer/src/lib.rs
Rollup merge of #88782 - asquared31415:issue-79559, r=cjgillot
[rust.git] / compiler / rustc_lexer / src / lib.rs
1 //! Low-level Rust lexer.
2 //!
3 //! The idea with `rustc_lexer` is to make a reusable library,
4 //! by separating out pure lexing and rustc-specific concerns, like spans,
5 //! error reporting, and interning.  So, rustc_lexer operates directly on `&str`,
6 //! produces simple tokens which are a pair of type-tag and a bit of original text,
7 //! and does not report errors, instead storing them as flags on the token.
8 //!
9 //! Tokens produced by this lexer are not yet ready for parsing the Rust syntax.
10 //! For that see [`rustc_parse::lexer`], which converts this basic token stream
11 //! into wide tokens used by actual parser.
12 //!
13 //! The purpose of this crate is to convert raw sources into a labeled sequence
14 //! of well-known token types, so building an actual Rust token stream will
15 //! be easier.
16 //!
17 //! The main entity of this crate is the [`TokenKind`] enum which represents common
18 //! lexeme types.
19 //!
20 //! [`rustc_parse::lexer`]: ../rustc_parse/lexer/index.html
21 // We want to be able to build this crate with a stable compiler, so no
22 // `#![feature]` attributes should be added.
23
24 mod cursor;
25 pub mod unescape;
26
27 #[cfg(test)]
28 mod tests;
29
30 use self::LiteralKind::*;
31 use self::TokenKind::*;
32 use crate::cursor::{Cursor, EOF_CHAR};
33 use std::convert::TryFrom;
34
35 /// Parsed token.
36 /// It doesn't contain information about data that has been parsed,
37 /// only the type of the token and its size.
38 #[derive(Debug)]
39 pub struct Token {
40     pub kind: TokenKind,
41     pub len: usize,
42 }
43
44 impl Token {
45     fn new(kind: TokenKind, len: usize) -> Token {
46         Token { kind, len }
47     }
48 }
49
50 /// Enum representing common lexeme types.
51 // perf note: Changing all `usize` to `u32` doesn't change performance. See #77629
52 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
53 pub enum TokenKind {
54     // Multi-char tokens:
55     /// "// comment"
56     LineComment { doc_style: Option<DocStyle> },
57     /// `/* block comment */`
58     ///
59     /// Block comments can be recursive, so the sequence like `/* /* */`
60     /// will not be considered terminated and will result in a parsing error.
61     BlockComment { doc_style: Option<DocStyle>, terminated: bool },
62     /// Any whitespace characters sequence.
63     Whitespace,
64     /// "ident" or "continue"
65     /// At this step keywords are also considered identifiers.
66     Ident,
67     /// "r#ident"
68     RawIdent,
69     /// An unknown prefix like `foo#`, `foo'`, `foo"`. Note that only the
70     /// prefix (`foo`) is included in the token, not the separator (which is
71     /// lexed as its own distinct token). In Rust 2021 and later, reserved
72     /// prefixes are reported as errors; in earlier editions, they result in a
73     /// (allowed by default) lint, and are treated as regular identifier
74     /// tokens.
75     UnknownPrefix,
76     /// "12_u8", "1.0e-40", "b"123"". See `LiteralKind` for more details.
77     Literal { kind: LiteralKind, suffix_start: usize },
78     /// "'a"
79     Lifetime { starts_with_number: bool },
80
81     // One-char tokens:
82     /// ";"
83     Semi,
84     /// ","
85     Comma,
86     /// "."
87     Dot,
88     /// "("
89     OpenParen,
90     /// ")"
91     CloseParen,
92     /// "{"
93     OpenBrace,
94     /// "}"
95     CloseBrace,
96     /// "["
97     OpenBracket,
98     /// "]"
99     CloseBracket,
100     /// "@"
101     At,
102     /// "#"
103     Pound,
104     /// "~"
105     Tilde,
106     /// "?"
107     Question,
108     /// ":"
109     Colon,
110     /// "$"
111     Dollar,
112     /// "="
113     Eq,
114     /// "!"
115     Bang,
116     /// "<"
117     Lt,
118     /// ">"
119     Gt,
120     /// "-"
121     Minus,
122     /// "&"
123     And,
124     /// "|"
125     Or,
126     /// "+"
127     Plus,
128     /// "*"
129     Star,
130     /// "/"
131     Slash,
132     /// "^"
133     Caret,
134     /// "%"
135     Percent,
136
137     /// Unknown token, not expected by the lexer, e.g. "№"
138     Unknown,
139 }
140
141 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
142 pub enum DocStyle {
143     Outer,
144     Inner,
145 }
146
147 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
148 pub enum LiteralKind {
149     /// "12_u8", "0o100", "0b120i99"
150     Int { base: Base, empty_int: bool },
151     /// "12.34f32", "0b100.100"
152     Float { base: Base, empty_exponent: bool },
153     /// "'a'", "'\\'", "'''", "';"
154     Char { terminated: bool },
155     /// "b'a'", "b'\\'", "b'''", "b';"
156     Byte { terminated: bool },
157     /// ""abc"", ""abc"
158     Str { terminated: bool },
159     /// "b"abc"", "b"abc"
160     ByteStr { terminated: bool },
161     /// "r"abc"", "r#"abc"#", "r####"ab"###"c"####", "r#"a"
162     RawStr { n_hashes: u16, err: Option<RawStrError> },
163     /// "br"abc"", "br#"abc"#", "br####"ab"###"c"####", "br#"a"
164     RawByteStr { n_hashes: u16, err: Option<RawStrError> },
165 }
166
167 /// Error produced validating a raw string. Represents cases like:
168 /// - `r##~"abcde"##`: `InvalidStarter`
169 /// - `r###"abcde"##`: `NoTerminator { expected: 3, found: 2, possible_terminator_offset: Some(11)`
170 /// - Too many `#`s (>65535): `TooManyDelimiters`
171 // perf note: It doesn't matter that this makes `Token` 36 bytes bigger. See #77629
172 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
173 pub enum RawStrError {
174     /// Non `#` characters exist between `r` and `"` eg. `r#~"..`
175     InvalidStarter { bad_char: char },
176     /// The string was never terminated. `possible_terminator_offset` is the number of characters after `r` or `br` where they
177     /// may have intended to terminate it.
178     NoTerminator { expected: usize, found: usize, possible_terminator_offset: Option<usize> },
179     /// More than 65535 `#`s exist.
180     TooManyDelimiters { found: usize },
181 }
182
183 /// Base of numeric literal encoding according to its prefix.
184 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
185 pub enum Base {
186     /// Literal starts with "0b".
187     Binary,
188     /// Literal starts with "0o".
189     Octal,
190     /// Literal starts with "0x".
191     Hexadecimal,
192     /// Literal doesn't contain a prefix.
193     Decimal,
194 }
195
196 /// `rustc` allows files to have a shebang, e.g. "#!/usr/bin/rustrun",
197 /// but shebang isn't a part of rust syntax.
198 pub fn strip_shebang(input: &str) -> Option<usize> {
199     // Shebang must start with `#!` literally, without any preceding whitespace.
200     // For simplicity we consider any line starting with `#!` a shebang,
201     // regardless of restrictions put on shebangs by specific platforms.
202     if let Some(input_tail) = input.strip_prefix("#!") {
203         // Ok, this is a shebang but if the next non-whitespace token is `[`,
204         // then it may be valid Rust code, so consider it Rust code.
205         let next_non_whitespace_token = tokenize(input_tail).map(|tok| tok.kind).find(|tok| {
206             !matches!(
207                 tok,
208                 TokenKind::Whitespace
209                     | TokenKind::LineComment { doc_style: None }
210                     | TokenKind::BlockComment { doc_style: None, .. }
211             )
212         });
213         if next_non_whitespace_token != Some(TokenKind::OpenBracket) {
214             // No other choice than to consider this a shebang.
215             return Some(2 + input_tail.lines().next().unwrap_or_default().len());
216         }
217     }
218     None
219 }
220
221 /// Parses the first token from the provided input string.
222 pub fn first_token(input: &str) -> Token {
223     debug_assert!(!input.is_empty());
224     Cursor::new(input).advance_token()
225 }
226
227 /// Creates an iterator that produces tokens from the input string.
228 pub fn tokenize(mut input: &str) -> impl Iterator<Item = Token> + '_ {
229     std::iter::from_fn(move || {
230         if input.is_empty() {
231             return None;
232         }
233         let token = first_token(input);
234         input = &input[token.len..];
235         Some(token)
236     })
237 }
238
239 /// True if `c` is considered a whitespace according to Rust language definition.
240 /// See [Rust language reference](https://doc.rust-lang.org/reference/whitespace.html)
241 /// for definitions of these classes.
242 pub fn is_whitespace(c: char) -> bool {
243     // This is Pattern_White_Space.
244     //
245     // Note that this set is stable (ie, it doesn't change with different
246     // Unicode versions), so it's ok to just hard-code the values.
247
248     matches!(
249         c,
250         // Usual ASCII suspects
251         '\u{0009}'   // \t
252         | '\u{000A}' // \n
253         | '\u{000B}' // vertical tab
254         | '\u{000C}' // form feed
255         | '\u{000D}' // \r
256         | '\u{0020}' // space
257
258         // NEXT LINE from latin1
259         | '\u{0085}'
260
261         // Bidi markers
262         | '\u{200E}' // LEFT-TO-RIGHT MARK
263         | '\u{200F}' // RIGHT-TO-LEFT MARK
264
265         // Dedicated whitespace characters from Unicode
266         | '\u{2028}' // LINE SEPARATOR
267         | '\u{2029}' // PARAGRAPH SEPARATOR
268     )
269 }
270
271 /// True if `c` is valid as a first character of an identifier.
272 /// See [Rust language reference](https://doc.rust-lang.org/reference/identifiers.html) for
273 /// a formal definition of valid identifier name.
274 pub fn is_id_start(c: char) -> bool {
275     // This is XID_Start OR '_' (which formally is not a XID_Start).
276     c == '_' || unicode_xid::UnicodeXID::is_xid_start(c)
277 }
278
279 /// True if `c` is valid as a non-first character of an identifier.
280 /// See [Rust language reference](https://doc.rust-lang.org/reference/identifiers.html) for
281 /// a formal definition of valid identifier name.
282 pub fn is_id_continue(c: char) -> bool {
283     unicode_xid::UnicodeXID::is_xid_continue(c)
284 }
285
286 /// The passed string is lexically an identifier.
287 pub fn is_ident(string: &str) -> bool {
288     let mut chars = string.chars();
289     if let Some(start) = chars.next() {
290         is_id_start(start) && chars.all(is_id_continue)
291     } else {
292         false
293     }
294 }
295
296 impl Cursor<'_> {
297     /// Parses a token from the input string.
298     fn advance_token(&mut self) -> Token {
299         let first_char = self.bump().unwrap();
300         let token_kind = match first_char {
301             // Slash, comment or block comment.
302             '/' => match self.first() {
303                 '/' => self.line_comment(),
304                 '*' => self.block_comment(),
305                 _ => Slash,
306             },
307
308             // Whitespace sequence.
309             c if is_whitespace(c) => self.whitespace(),
310
311             // Raw identifier, raw string literal or identifier.
312             'r' => match (self.first(), self.second()) {
313                 ('#', c1) if is_id_start(c1) => self.raw_ident(),
314                 ('#', _) | ('"', _) => {
315                     let (n_hashes, err) = self.raw_double_quoted_string(1);
316                     let suffix_start = self.len_consumed();
317                     if err.is_none() {
318                         self.eat_literal_suffix();
319                     }
320                     let kind = RawStr { n_hashes, err };
321                     Literal { kind, suffix_start }
322                 }
323                 _ => self.ident_or_unknown_prefix(),
324             },
325
326             // Byte literal, byte string literal, raw byte string literal or identifier.
327             'b' => match (self.first(), self.second()) {
328                 ('\'', _) => {
329                     self.bump();
330                     let terminated = self.single_quoted_string();
331                     let suffix_start = self.len_consumed();
332                     if terminated {
333                         self.eat_literal_suffix();
334                     }
335                     let kind = Byte { terminated };
336                     Literal { kind, suffix_start }
337                 }
338                 ('"', _) => {
339                     self.bump();
340                     let terminated = self.double_quoted_string();
341                     let suffix_start = self.len_consumed();
342                     if terminated {
343                         self.eat_literal_suffix();
344                     }
345                     let kind = ByteStr { terminated };
346                     Literal { kind, suffix_start }
347                 }
348                 ('r', '"') | ('r', '#') => {
349                     self.bump();
350                     let (n_hashes, err) = self.raw_double_quoted_string(2);
351                     let suffix_start = self.len_consumed();
352                     if err.is_none() {
353                         self.eat_literal_suffix();
354                     }
355                     let kind = RawByteStr { n_hashes, err };
356                     Literal { kind, suffix_start }
357                 }
358                 _ => self.ident_or_unknown_prefix(),
359             },
360
361             // Identifier (this should be checked after other variant that can
362             // start as identifier).
363             c if is_id_start(c) => self.ident_or_unknown_prefix(),
364
365             // Numeric literal.
366             c @ '0'..='9' => {
367                 let literal_kind = self.number(c);
368                 let suffix_start = self.len_consumed();
369                 self.eat_literal_suffix();
370                 TokenKind::Literal { kind: literal_kind, suffix_start }
371             }
372
373             // One-symbol tokens.
374             ';' => Semi,
375             ',' => Comma,
376             '.' => Dot,
377             '(' => OpenParen,
378             ')' => CloseParen,
379             '{' => OpenBrace,
380             '}' => CloseBrace,
381             '[' => OpenBracket,
382             ']' => CloseBracket,
383             '@' => At,
384             '#' => Pound,
385             '~' => Tilde,
386             '?' => Question,
387             ':' => Colon,
388             '$' => Dollar,
389             '=' => Eq,
390             '!' => Bang,
391             '<' => Lt,
392             '>' => Gt,
393             '-' => Minus,
394             '&' => And,
395             '|' => Or,
396             '+' => Plus,
397             '*' => Star,
398             '^' => Caret,
399             '%' => Percent,
400
401             // Lifetime or character literal.
402             '\'' => self.lifetime_or_char(),
403
404             // String literal.
405             '"' => {
406                 let terminated = self.double_quoted_string();
407                 let suffix_start = self.len_consumed();
408                 if terminated {
409                     self.eat_literal_suffix();
410                 }
411                 let kind = Str { terminated };
412                 Literal { kind, suffix_start }
413             }
414             _ => Unknown,
415         };
416         Token::new(token_kind, self.len_consumed())
417     }
418
419     fn line_comment(&mut self) -> TokenKind {
420         debug_assert!(self.prev() == '/' && self.first() == '/');
421         self.bump();
422
423         let doc_style = match self.first() {
424             // `//!` is an inner line doc comment.
425             '!' => Some(DocStyle::Inner),
426             // `////` (more than 3 slashes) is not considered a doc comment.
427             '/' if self.second() != '/' => Some(DocStyle::Outer),
428             _ => None,
429         };
430
431         self.eat_while(|c| c != '\n');
432         LineComment { doc_style }
433     }
434
435     fn block_comment(&mut self) -> TokenKind {
436         debug_assert!(self.prev() == '/' && self.first() == '*');
437         self.bump();
438
439         let doc_style = match self.first() {
440             // `/*!` is an inner block doc comment.
441             '!' => Some(DocStyle::Inner),
442             // `/***` (more than 2 stars) is not considered a doc comment.
443             // `/**/` is not considered a doc comment.
444             '*' if !matches!(self.second(), '*' | '/') => Some(DocStyle::Outer),
445             _ => None,
446         };
447
448         let mut depth = 1usize;
449         while let Some(c) = self.bump() {
450             match c {
451                 '/' if self.first() == '*' => {
452                     self.bump();
453                     depth += 1;
454                 }
455                 '*' if self.first() == '/' => {
456                     self.bump();
457                     depth -= 1;
458                     if depth == 0 {
459                         // This block comment is closed, so for a construction like "/* */ */"
460                         // there will be a successfully parsed block comment "/* */"
461                         // and " */" will be processed separately.
462                         break;
463                     }
464                 }
465                 _ => (),
466             }
467         }
468
469         BlockComment { doc_style, terminated: depth == 0 }
470     }
471
472     fn whitespace(&mut self) -> TokenKind {
473         debug_assert!(is_whitespace(self.prev()));
474         self.eat_while(is_whitespace);
475         Whitespace
476     }
477
478     fn raw_ident(&mut self) -> TokenKind {
479         debug_assert!(self.prev() == 'r' && self.first() == '#' && is_id_start(self.second()));
480         // Eat "#" symbol.
481         self.bump();
482         // Eat the identifier part of RawIdent.
483         self.eat_identifier();
484         RawIdent
485     }
486
487     fn ident_or_unknown_prefix(&mut self) -> TokenKind {
488         debug_assert!(is_id_start(self.prev()));
489         // Start is already eaten, eat the rest of identifier.
490         self.eat_while(is_id_continue);
491         // Known prefixes must have been handled earlier. So if
492         // we see a prefix here, it is definitely an unknown prefix.
493         match self.first() {
494             '#' | '"' | '\'' => UnknownPrefix,
495             _ => Ident,
496         }
497     }
498
499     fn number(&mut self, first_digit: char) -> LiteralKind {
500         debug_assert!('0' <= self.prev() && self.prev() <= '9');
501         let mut base = Base::Decimal;
502         if first_digit == '0' {
503             // Attempt to parse encoding base.
504             let has_digits = match self.first() {
505                 'b' => {
506                     base = Base::Binary;
507                     self.bump();
508                     self.eat_decimal_digits()
509                 }
510                 'o' => {
511                     base = Base::Octal;
512                     self.bump();
513                     self.eat_decimal_digits()
514                 }
515                 'x' => {
516                     base = Base::Hexadecimal;
517                     self.bump();
518                     self.eat_hexadecimal_digits()
519                 }
520                 // Not a base prefix.
521                 '0'..='9' | '_' | '.' | 'e' | 'E' => {
522                     self.eat_decimal_digits();
523                     true
524                 }
525                 // Just a 0.
526                 _ => return Int { base, empty_int: false },
527             };
528             // Base prefix was provided, but there were no digits
529             // after it, e.g. "0x".
530             if !has_digits {
531                 return Int { base, empty_int: true };
532             }
533         } else {
534             // No base prefix, parse number in the usual way.
535             self.eat_decimal_digits();
536         };
537
538         match self.first() {
539             // Don't be greedy if this is actually an
540             // integer literal followed by field/method access or a range pattern
541             // (`0..2` and `12.foo()`)
542             '.' if self.second() != '.' && !is_id_start(self.second()) => {
543                 // might have stuff after the ., and if it does, it needs to start
544                 // with a number
545                 self.bump();
546                 let mut empty_exponent = false;
547                 if self.first().is_digit(10) {
548                     self.eat_decimal_digits();
549                     match self.first() {
550                         'e' | 'E' => {
551                             self.bump();
552                             empty_exponent = !self.eat_float_exponent();
553                         }
554                         _ => (),
555                     }
556                 }
557                 Float { base, empty_exponent }
558             }
559             'e' | 'E' => {
560                 self.bump();
561                 let empty_exponent = !self.eat_float_exponent();
562                 Float { base, empty_exponent }
563             }
564             _ => Int { base, empty_int: false },
565         }
566     }
567
568     fn lifetime_or_char(&mut self) -> TokenKind {
569         debug_assert!(self.prev() == '\'');
570
571         let can_be_a_lifetime = if self.second() == '\'' {
572             // It's surely not a lifetime.
573             false
574         } else {
575             // If the first symbol is valid for identifier, it can be a lifetime.
576             // Also check if it's a number for a better error reporting (so '0 will
577             // be reported as invalid lifetime and not as unterminated char literal).
578             is_id_start(self.first()) || self.first().is_digit(10)
579         };
580
581         if !can_be_a_lifetime {
582             let terminated = self.single_quoted_string();
583             let suffix_start = self.len_consumed();
584             if terminated {
585                 self.eat_literal_suffix();
586             }
587             let kind = Char { terminated };
588             return Literal { kind, suffix_start };
589         }
590
591         // Either a lifetime or a character literal with
592         // length greater than 1.
593
594         let starts_with_number = self.first().is_digit(10);
595
596         // Skip the literal contents.
597         // First symbol can be a number (which isn't a valid identifier start),
598         // so skip it without any checks.
599         self.bump();
600         self.eat_while(is_id_continue);
601
602         // Check if after skipping literal contents we've met a closing
603         // single quote (which means that user attempted to create a
604         // string with single quotes).
605         if self.first() == '\'' {
606             self.bump();
607             let kind = Char { terminated: true };
608             Literal { kind, suffix_start: self.len_consumed() }
609         } else {
610             Lifetime { starts_with_number }
611         }
612     }
613
614     fn single_quoted_string(&mut self) -> bool {
615         debug_assert!(self.prev() == '\'');
616         // Check if it's a one-symbol literal.
617         if self.second() == '\'' && self.first() != '\\' {
618             self.bump();
619             self.bump();
620             return true;
621         }
622
623         // Literal has more than one symbol.
624
625         // Parse until either quotes are terminated or error is detected.
626         loop {
627             match self.first() {
628                 // Quotes are terminated, finish parsing.
629                 '\'' => {
630                     self.bump();
631                     return true;
632                 }
633                 // Probably beginning of the comment, which we don't want to include
634                 // to the error report.
635                 '/' => break,
636                 // Newline without following '\'' means unclosed quote, stop parsing.
637                 '\n' if self.second() != '\'' => break,
638                 // End of file, stop parsing.
639                 EOF_CHAR if self.is_eof() => break,
640                 // Escaped slash is considered one character, so bump twice.
641                 '\\' => {
642                     self.bump();
643                     self.bump();
644                 }
645                 // Skip the character.
646                 _ => {
647                     self.bump();
648                 }
649             }
650         }
651         // String was not terminated.
652         false
653     }
654
655     /// Eats double-quoted string and returns true
656     /// if string is terminated.
657     fn double_quoted_string(&mut self) -> bool {
658         debug_assert!(self.prev() == '"');
659         while let Some(c) = self.bump() {
660             match c {
661                 '"' => {
662                     return true;
663                 }
664                 '\\' if self.first() == '\\' || self.first() == '"' => {
665                     // Bump again to skip escaped character.
666                     self.bump();
667                 }
668                 _ => (),
669             }
670         }
671         // End of file reached.
672         false
673     }
674
675     /// Eats the double-quoted string and returns `n_hashes` and an error if encountered.
676     fn raw_double_quoted_string(&mut self, prefix_len: usize) -> (u16, Option<RawStrError>) {
677         // Wrap the actual function to handle the error with too many hashes.
678         // This way, it eats the whole raw string.
679         let (n_hashes, err) = self.raw_string_unvalidated(prefix_len);
680         // Only up to 65535 `#`s are allowed in raw strings
681         match u16::try_from(n_hashes) {
682             Ok(num) => (num, err),
683             // We lie about the number of hashes here :P
684             Err(_) => (0, Some(RawStrError::TooManyDelimiters { found: n_hashes })),
685         }
686     }
687
688     fn raw_string_unvalidated(&mut self, prefix_len: usize) -> (usize, Option<RawStrError>) {
689         debug_assert!(self.prev() == 'r');
690         let start_pos = self.len_consumed();
691         let mut possible_terminator_offset = None;
692         let mut max_hashes = 0;
693
694         // Count opening '#' symbols.
695         let mut eaten = 0;
696         while self.first() == '#' {
697             eaten += 1;
698             self.bump();
699         }
700         let n_start_hashes = eaten;
701
702         // Check that string is started.
703         match self.bump() {
704             Some('"') => (),
705             c => {
706                 let c = c.unwrap_or(EOF_CHAR);
707                 return (n_start_hashes, Some(RawStrError::InvalidStarter { bad_char: c }));
708             }
709         }
710
711         // Skip the string contents and on each '#' character met, check if this is
712         // a raw string termination.
713         loop {
714             self.eat_while(|c| c != '"');
715
716             if self.is_eof() {
717                 return (
718                     n_start_hashes,
719                     Some(RawStrError::NoTerminator {
720                         expected: n_start_hashes,
721                         found: max_hashes,
722                         possible_terminator_offset,
723                     }),
724                 );
725             }
726
727             // Eat closing double quote.
728             self.bump();
729
730             // Check that amount of closing '#' symbols
731             // is equal to the amount of opening ones.
732             // Note that this will not consume extra trailing `#` characters:
733             // `r###"abcde"####` is lexed as a `RawStr { n_hashes: 3 }`
734             // followed by a `#` token.
735             let mut n_end_hashes = 0;
736             while self.first() == '#' && n_end_hashes < n_start_hashes {
737                 n_end_hashes += 1;
738                 self.bump();
739             }
740
741             if n_end_hashes == n_start_hashes {
742                 return (n_start_hashes, None);
743             } else if n_end_hashes > max_hashes {
744                 // Keep track of possible terminators to give a hint about
745                 // where there might be a missing terminator
746                 possible_terminator_offset =
747                     Some(self.len_consumed() - start_pos - n_end_hashes + prefix_len);
748                 max_hashes = n_end_hashes;
749             }
750         }
751     }
752
753     fn eat_decimal_digits(&mut self) -> bool {
754         let mut has_digits = false;
755         loop {
756             match self.first() {
757                 '_' => {
758                     self.bump();
759                 }
760                 '0'..='9' => {
761                     has_digits = true;
762                     self.bump();
763                 }
764                 _ => break,
765             }
766         }
767         has_digits
768     }
769
770     fn eat_hexadecimal_digits(&mut self) -> bool {
771         let mut has_digits = false;
772         loop {
773             match self.first() {
774                 '_' => {
775                     self.bump();
776                 }
777                 '0'..='9' | 'a'..='f' | 'A'..='F' => {
778                     has_digits = true;
779                     self.bump();
780                 }
781                 _ => break,
782             }
783         }
784         has_digits
785     }
786
787     /// Eats the float exponent. Returns true if at least one digit was met,
788     /// and returns false otherwise.
789     fn eat_float_exponent(&mut self) -> bool {
790         debug_assert!(self.prev() == 'e' || self.prev() == 'E');
791         if self.first() == '-' || self.first() == '+' {
792             self.bump();
793         }
794         self.eat_decimal_digits()
795     }
796
797     // Eats the suffix of the literal, e.g. "_u8".
798     fn eat_literal_suffix(&mut self) {
799         self.eat_identifier();
800     }
801
802     // Eats the identifier.
803     fn eat_identifier(&mut self) {
804         if !is_id_start(self.first()) {
805             return;
806         }
807         self.bump();
808
809         self.eat_while(is_id_continue);
810     }
811
812     /// Eats symbols while predicate returns true or until the end of file is reached.
813     fn eat_while(&mut self, mut predicate: impl FnMut(char) -> bool) {
814         while predicate(self.first()) && !self.is_eof() {
815             self.bump();
816         }
817     }
818 }