1 //! Lexer analyzes raw input string and produces lexemes (tokens).
2 //! It is just a bridge to `rustc_lexer`.
4 use std::convert::TryInto;
6 use rustc_lexer::RawStrError;
10 SyntaxKind::{self, *},
11 TextRange, TextSize, T,
14 /// A token of Rust source.
15 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
17 /// The kind of token.
19 /// The length of the token.
23 /// Break a string up into its component tokens.
24 /// Beware that it checks for shebang first and its length contributes to resulting
26 pub fn tokenize(text: &str) -> (Vec<Token>, Vec<SyntaxError>) {
27 // non-empty string is a precondition of `rustc_lexer::strip_shebang()`.
29 return Default::default();
32 let mut tokens = Vec::new();
33 let mut errors = Vec::new();
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() });
43 let text_without_shebang = &text[offset..];
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);
49 let (syntax_kind, err_message) =
50 rustc_token_kind_to_syntax_kind(&rustc_token.kind, &text[token_range]);
52 tokens.push(Token { kind: syntax_kind, len: token_len });
54 if let Some(err_message) = err_message {
55 errors.push(SyntaxError::new(err_message, token_range));
58 offset += rustc_token.len;
64 /// Returns `SyntaxKind` and `Option<SyntaxError>` if `text` parses as a single token.
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`.
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) {
75 Some((first_token.kind, err))
78 /// The same as `lex_single_syntax_kind()` but returns only `SyntaxKind` and
79 /// returns `None` if any tokenization error occurred.
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)?;
90 /// Returns `SyntaxKind` and `Option<SyntaxError>` of the first token
91 /// encountered at the beginning of the string.
93 /// Returns `None` if the string contains zero tokens or if the token was parsed
95 /// The token is malformed if the returned error is not `None`.
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()`.
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);
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))));
111 Some((token, optional_error))
114 /// Returns `SyntaxKind` and an optional tokenize error message.
115 fn rustc_token_kind_to_syntax_kind(
116 rustc_token_kind: &rustc_lexer::TokenKind,
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`.
125 match rustc_token_kind {
126 rustc_lexer::TokenKind::LineComment { doc_style: _ } => COMMENT,
128 rustc_lexer::TokenKind::BlockComment { doc_style: _, terminated: true } => COMMENT,
129 rustc_lexer::TokenKind::BlockComment { doc_style: _, terminated: false } => {
132 Some("Missing trailing `*/` symbols to terminate the block comment"),
136 rustc_lexer::TokenKind::Whitespace => WHITESPACE,
138 rustc_lexer::TokenKind::Ident => {
139 if token_text == "_" {
142 SyntaxKind::from_keyword(token_text).unwrap_or(IDENT)
146 rustc_lexer::TokenKind::RawIdent => IDENT,
147 rustc_lexer::TokenKind::Literal { kind, .. } => return match_literal_kind(&kind),
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"))
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,
185 return (syntax_kind, None);
187 fn match_literal_kind(kind: &rustc_lexer::LiteralKind) -> (SyntaxKind, Option<&'static str>) {
189 let syntax_kind = match *kind {
190 rustc_lexer::LiteralKind::Int { empty_int, base: _ } => {
192 err = "Missing digits after the integer base prefix";
196 rustc_lexer::LiteralKind::Float { empty_exponent, base: _ } => {
198 err = "Missing digits after the exponent symbol";
202 rustc_lexer::LiteralKind::Char { terminated } => {
204 err = "Missing trailing `'` symbol to terminate the character literal";
208 rustc_lexer::LiteralKind::Byte { terminated } => {
210 err = "Missing trailing `'` symbol to terminate the byte literal";
214 rustc_lexer::LiteralKind::Str { terminated } => {
216 err = "Missing trailing `\"` symbol to terminate the string literal";
220 rustc_lexer::LiteralKind::ByteStr { terminated } => {
222 err = "Missing trailing `\"` symbol to terminate the byte string literal";
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"
233 "Missing trailing `\"` with `#` symbols to terminate the raw string literal"
235 RawStrError::TooManyDelimiters { .. } => "Too many `#` symbols: raw strings may be delimited by up to 65535 `#` symbols",
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"
247 "Missing trailing `\"` with `#` symbols to terminate the raw byte string literal"
249 RawStrError::TooManyDelimiters { .. } => "Too many `#` symbols: raw byte strings may be delimited by up to 65535 `#` symbols",
257 let err = if err.is_empty() { None } else { Some(err) };