]> git.lizzy.rs Git - rust.git/blob - crates/syntax/src/parsing/lexer.rs
Fixed typos in code comments
[rust.git] / crates / syntax / src / parsing / lexer.rs
1 //! Lexer analyzes raw input string and produces lexemes (tokens).
2 //! It is just a bridge to `rustc_lexer`.
3
4 use std::convert::TryInto;
5
6 use rustc_lexer::RawStrError;
7
8 use crate::{
9     SyntaxError,
10     SyntaxKind::{self, *},
11     TextRange, TextSize, T,
12 };
13
14 /// A token of Rust source.
15 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
16 pub struct Token {
17     /// The kind of token.
18     pub kind: SyntaxKind,
19     /// The length of the token.
20     pub len: TextSize,
21 }
22
23 /// Break a string up into its component tokens.
24 /// Beware that it checks for shebang first and its length contributes to resulting
25 /// tokens offsets.
26 pub fn tokenize(text: &str) -> (Vec<Token>, Vec<SyntaxError>) {
27     // non-empty string is a precondition of `rustc_lexer::strip_shebang()`.
28     if text.is_empty() {
29         return Default::default();
30     }
31
32     let mut tokens = Vec::new();
33     let mut errors = Vec::new();
34
35     let mut offset = match rustc_lexer::strip_shebang(text) {
36         Some(shebang_len) => {
37             tokens.push(Token { kind: SHEBANG, len: shebang_len.try_into().unwrap() });
38             shebang_len
39         }
40         None => 0,
41     };
42
43     let text_without_shebang = &text[offset..];
44
45     for rustc_token in rustc_lexer::tokenize(text_without_shebang) {
46         let token_len: TextSize = rustc_token.len.try_into().unwrap();
47         let token_range = TextRange::at(offset.try_into().unwrap(), token_len);
48
49         let (syntax_kind, err_message) =
50             rustc_token_kind_to_syntax_kind(&rustc_token.kind, &text[token_range]);
51
52         tokens.push(Token { kind: syntax_kind, len: token_len });
53
54         if let Some(err_message) = err_message {
55             errors.push(SyntaxError::new(err_message, token_range));
56         }
57
58         offset += rustc_token.len;
59     }
60
61     (tokens, errors)
62 }
63
64 /// Returns `SyntaxKind` and `Option<SyntaxError>` if `text` parses as a single token.
65 ///
66 /// Returns `None` if the string contains zero *or two or more* tokens.
67 /// The token is malformed if the returned error is not `None`.
68 ///
69 /// Beware that unescape errors are not checked at tokenization time.
70 pub fn lex_single_syntax_kind(text: &str) -> Option<(SyntaxKind, Option<SyntaxError>)> {
71     let (first_token, err) = lex_first_token(text)?;
72     if first_token.len != TextSize::of(text) {
73         return None;
74     }
75     Some((first_token.kind, err))
76 }
77
78 /// The same as `lex_single_syntax_kind()` but returns only `SyntaxKind` and
79 /// returns `None` if any tokenization error occurred.
80 ///
81 /// Beware that unescape errors are not checked at tokenization time.
82 pub fn lex_single_valid_syntax_kind(text: &str) -> Option<SyntaxKind> {
83     let (single_token, err) = lex_single_syntax_kind(text)?;
84     if err.is_some() {
85         return None;
86     }
87     Some(single_token)
88 }
89
90 /// Returns `SyntaxKind` and `Option<SyntaxError>` of the first token
91 /// encountered at the beginning of the string.
92 ///
93 /// Returns `None` if the string contains zero tokens or if the token was parsed
94 /// with an error.
95 /// The token is malformed if the returned error is not `None`.
96 ///
97 /// Beware that unescape errors are not checked at tokenization time.
98 fn lex_first_token(text: &str) -> Option<(Token, Option<SyntaxError>)> {
99     // non-empty string is a precondition of `rustc_lexer::first_token()`.
100     if text.is_empty() {
101         return None;
102     }
103
104     let rustc_token = rustc_lexer::first_token(text);
105     let (syntax_kind, err_message) = rustc_token_kind_to_syntax_kind(&rustc_token.kind, text);
106
107     let token = Token { kind: syntax_kind, len: rustc_token.len.try_into().unwrap() };
108     let optional_error = err_message
109         .map(|err_message| SyntaxError::new(err_message, TextRange::up_to(TextSize::of(text))));
110
111     Some((token, optional_error))
112 }
113
114 /// Returns `SyntaxKind` and an optional tokenize error message.
115 fn rustc_token_kind_to_syntax_kind(
116     rustc_token_kind: &rustc_lexer::TokenKind,
117     token_text: &str,
118 ) -> (SyntaxKind, Option<&'static str>) {
119     // A note on an intended tradeoff:
120     // We drop some useful information here (see patterns with double dots `..`)
121     // Storing that info in `SyntaxKind` is not possible due to its layout requirements of
122     // being `u16` that come from `rowan::SyntaxKind`.
123
124     let syntax_kind = {
125         match rustc_token_kind {
126             rustc_lexer::TokenKind::LineComment { doc_style: _ } => COMMENT,
127
128             rustc_lexer::TokenKind::BlockComment { doc_style: _, terminated: true } => COMMENT,
129             rustc_lexer::TokenKind::BlockComment { doc_style: _, terminated: false } => {
130                 return (
131                     COMMENT,
132                     Some("Missing trailing `*/` symbols to terminate the block comment"),
133                 );
134             }
135
136             rustc_lexer::TokenKind::Whitespace => WHITESPACE,
137
138             rustc_lexer::TokenKind::Ident => {
139                 if token_text == "_" {
140                     UNDERSCORE
141                 } else {
142                     SyntaxKind::from_keyword(token_text).unwrap_or(IDENT)
143                 }
144             }
145
146             rustc_lexer::TokenKind::RawIdent => IDENT,
147             rustc_lexer::TokenKind::Literal { kind, .. } => return match_literal_kind(&kind),
148
149             rustc_lexer::TokenKind::Lifetime { starts_with_number: false } => LIFETIME_IDENT,
150             rustc_lexer::TokenKind::Lifetime { starts_with_number: true } => {
151                 return (LIFETIME_IDENT, Some("Lifetime name cannot start with a number"))
152             }
153
154             rustc_lexer::TokenKind::Semi => T![;],
155             rustc_lexer::TokenKind::Comma => T![,],
156             rustc_lexer::TokenKind::Dot => T![.],
157             rustc_lexer::TokenKind::OpenParen => T!['('],
158             rustc_lexer::TokenKind::CloseParen => T![')'],
159             rustc_lexer::TokenKind::OpenBrace => T!['{'],
160             rustc_lexer::TokenKind::CloseBrace => T!['}'],
161             rustc_lexer::TokenKind::OpenBracket => T!['['],
162             rustc_lexer::TokenKind::CloseBracket => T![']'],
163             rustc_lexer::TokenKind::At => T![@],
164             rustc_lexer::TokenKind::Pound => T![#],
165             rustc_lexer::TokenKind::Tilde => T![~],
166             rustc_lexer::TokenKind::Question => T![?],
167             rustc_lexer::TokenKind::Colon => T![:],
168             rustc_lexer::TokenKind::Dollar => T![$],
169             rustc_lexer::TokenKind::Eq => T![=],
170             rustc_lexer::TokenKind::Bang => T![!],
171             rustc_lexer::TokenKind::Lt => T![<],
172             rustc_lexer::TokenKind::Gt => T![>],
173             rustc_lexer::TokenKind::Minus => T![-],
174             rustc_lexer::TokenKind::And => T![&],
175             rustc_lexer::TokenKind::Or => T![|],
176             rustc_lexer::TokenKind::Plus => T![+],
177             rustc_lexer::TokenKind::Star => T![*],
178             rustc_lexer::TokenKind::Slash => T![/],
179             rustc_lexer::TokenKind::Caret => T![^],
180             rustc_lexer::TokenKind::Percent => T![%],
181             rustc_lexer::TokenKind::Unknown => ERROR,
182         }
183     };
184
185     return (syntax_kind, None);
186
187     fn match_literal_kind(kind: &rustc_lexer::LiteralKind) -> (SyntaxKind, Option<&'static str>) {
188         let mut err = "";
189         let syntax_kind = match *kind {
190             rustc_lexer::LiteralKind::Int { empty_int, base: _ } => {
191                 if empty_int {
192                     err = "Missing digits after the integer base prefix";
193                 }
194                 INT_NUMBER
195             }
196             rustc_lexer::LiteralKind::Float { empty_exponent, base: _ } => {
197                 if empty_exponent {
198                     err = "Missing digits after the exponent symbol";
199                 }
200                 FLOAT_NUMBER
201             }
202             rustc_lexer::LiteralKind::Char { terminated } => {
203                 if !terminated {
204                     err = "Missing trailing `'` symbol to terminate the character literal";
205                 }
206                 CHAR
207             }
208             rustc_lexer::LiteralKind::Byte { terminated } => {
209                 if !terminated {
210                     err = "Missing trailing `'` symbol to terminate the byte literal";
211                 }
212                 BYTE
213             }
214             rustc_lexer::LiteralKind::Str { terminated } => {
215                 if !terminated {
216                     err = "Missing trailing `\"` symbol to terminate the string literal";
217                 }
218                 STRING
219             }
220             rustc_lexer::LiteralKind::ByteStr { terminated } => {
221                 if !terminated {
222                     err = "Missing trailing `\"` symbol to terminate the byte string literal";
223                 }
224                 BYTE_STRING
225             }
226             rustc_lexer::LiteralKind::RawStr { err: raw_str_err, .. } => {
227                 if let Some(raw_str_err) = raw_str_err {
228                     err = match raw_str_err {
229                         RawStrError::InvalidStarter { .. } => "Missing `\"` symbol after `#` symbols to begin the raw string literal",
230                         RawStrError::NoTerminator { expected, found, .. } => if expected == found {
231                             "Missing trailing `\"` to terminate the raw string literal"
232                         } else {
233                             "Missing trailing `\"` with `#` symbols to terminate the raw string literal"
234                         },
235                         RawStrError::TooManyDelimiters { .. } => "Too many `#` symbols: raw strings may be delimited by up to 65535 `#` symbols",
236                     };
237                 };
238                 STRING
239             }
240             rustc_lexer::LiteralKind::RawByteStr { err: raw_str_err, .. } => {
241                 if let Some(raw_str_err) = raw_str_err {
242                     err = match raw_str_err {
243                         RawStrError::InvalidStarter { .. } => "Missing `\"` symbol after `#` symbols to begin the raw byte string literal",
244                         RawStrError::NoTerminator { expected, found, .. } => if expected == found {
245                             "Missing trailing `\"` to terminate the raw byte string literal"
246                         } else {
247                             "Missing trailing `\"` with `#` symbols to terminate the raw byte string literal"
248                         },
249                         RawStrError::TooManyDelimiters { .. } => "Too many `#` symbols: raw byte strings may be delimited by up to 65535 `#` symbols",
250                     };
251                 };
252
253                 BYTE_STRING
254             }
255         };
256
257         let err = if err.is_empty() { None } else { Some(err) };
258
259         (syntax_kind, err)
260     }
261 }