/src/solidity/liblangutil/Scanner.cpp
Line | Count | Source |
1 | | /* |
2 | | * This file is part of solidity. |
3 | | * |
4 | | * solidity is free software: you can redistribute it and/or modify |
5 | | * it under the terms of the GNU General Public License as published by |
6 | | * the Free Software Foundation, either version 3 of the License, or |
7 | | * (at your option) any later version. |
8 | | * |
9 | | * solidity is distributed in the hope that it will be useful, |
10 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 | | * GNU General Public License for more details. |
13 | | * |
14 | | * You should have received a copy of the GNU General Public License |
15 | | * along with solidity. If not, see <http://www.gnu.org/licenses/>. |
16 | | * |
17 | | * This file is derived from the file "scanner.cc", which was part of the |
18 | | * V8 project. The original copyright header follows: |
19 | | * |
20 | | * Copyright 2006-2012, the V8 project authors. All rights reserved. |
21 | | * Redistribution and use in source and binary forms, with or without |
22 | | * modification, are permitted provided that the following conditions are |
23 | | * met: |
24 | | * |
25 | | * * Redistributions of source code must retain the above copyright |
26 | | * notice, this list of conditions and the following disclaimer. |
27 | | * * Redistributions in binary form must reproduce the above |
28 | | * copyright notice, this list of conditions and the following |
29 | | * disclaimer in the documentation and/or other materials provided |
30 | | * with the distribution. |
31 | | * * Neither the name of Google Inc. nor the names of its |
32 | | * contributors may be used to endorse or promote products derived |
33 | | * from this software without specific prior written permission. |
34 | | * |
35 | | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
36 | | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
37 | | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
38 | | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
39 | | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
40 | | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
41 | | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
42 | | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
43 | | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
44 | | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
45 | | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
46 | | */ |
47 | | /** |
48 | | * @author Christian <c@ethdev.com> |
49 | | * @date 2014 |
50 | | * Solidity scanner. |
51 | | */ |
52 | | |
53 | | #include <liblangutil/Common.h> |
54 | | #include <liblangutil/Exceptions.h> |
55 | | #include <liblangutil/Scanner.h> |
56 | | |
57 | | #include <boost/algorithm/string/classification.hpp> |
58 | | |
59 | | #include <optional> |
60 | | #include <string_view> |
61 | | #include <tuple> |
62 | | #include <array> |
63 | | |
64 | | |
65 | | namespace solidity::langutil |
66 | | { |
67 | | |
68 | | std::string to_string(ScannerError _errorCode) |
69 | 2.49k | { |
70 | 2.49k | switch (_errorCode) |
71 | 2.49k | { |
72 | 0 | case ScannerError::NoError: return "No error."; |
73 | 555 | case ScannerError::IllegalToken: return "Invalid token."; |
74 | 109 | case ScannerError::IllegalHexString: return "Expected even number of hex-nibbles."; |
75 | 44 | case ScannerError::IllegalHexDigit: return "Hexadecimal digit missing or invalid."; |
76 | 505 | case ScannerError::IllegalCommentTerminator: return "Expected multi-line comment-terminator."; |
77 | 317 | case ScannerError::IllegalEscapeSequence: return "Invalid escape sequence."; |
78 | 5 | case ScannerError::UnicodeCharacterInNonUnicodeString: return "Invalid character in string. If you are trying to use Unicode characters, use a unicode\"...\" string literal."; |
79 | 82 | case ScannerError::IllegalCharacterInString: return "Invalid character in string."; |
80 | 457 | case ScannerError::IllegalStringEndQuote: return "Expected string end-quote."; |
81 | 23 | case ScannerError::IllegalNumberSeparator: return "Invalid use of number separator '_'."; |
82 | 95 | case ScannerError::IllegalExponent: return "Invalid exponent."; |
83 | 173 | case ScannerError::IllegalNumberEnd: return "Identifier-start is not allowed at end of a number."; |
84 | 26 | case ScannerError::OctalNotAllowed: return "Octal numbers not allowed."; |
85 | 13 | case ScannerError::DirectionalOverrideUnderflow: return "Unicode direction override underflow in comment or string literal."; |
86 | 92 | case ScannerError::DirectionalOverrideMismatch: return "Mismatching directional override markers in comment or string literal."; |
87 | 0 | default: |
88 | 0 | solAssert(false, "Unhandled case in to_string(ScannerError)"); |
89 | 0 | return ""; |
90 | 2.49k | } |
91 | 2.49k | } |
92 | | |
93 | | |
94 | | std::ostream& operator<<(std::ostream& os, ScannerError _errorCode) |
95 | 0 | { |
96 | 0 | return os << to_string(_errorCode); |
97 | 0 | } |
98 | | |
99 | | /// Scoped helper for literal recording. Automatically drops the literal |
100 | | /// if aborting the scanning before it's complete. |
101 | | enum LiteralType |
102 | | { |
103 | | LITERAL_TYPE_STRING, |
104 | | LITERAL_TYPE_NUMBER, // not really different from string type in behaviour |
105 | | LITERAL_TYPE_COMMENT |
106 | | }; |
107 | | |
108 | | class LiteralScope |
109 | | { |
110 | | public: |
111 | | explicit LiteralScope(Scanner* _self, enum LiteralType _type): |
112 | 35.6M | m_type(_type), |
113 | 35.6M | m_scanner(_self), |
114 | 35.6M | m_complete(false) |
115 | 35.6M | { |
116 | 35.6M | if (_type == LITERAL_TYPE_COMMENT) |
117 | 392k | m_scanner->m_skippedComments[Scanner::NextNext].literal.clear(); |
118 | 35.2M | else |
119 | 35.2M | m_scanner->m_tokens[Scanner::NextNext].literal.clear(); |
120 | 35.6M | } |
121 | | ~LiteralScope() |
122 | 35.6M | { |
123 | 35.6M | if (!m_complete) |
124 | 13.8k | { |
125 | 13.8k | if (m_type == LITERAL_TYPE_COMMENT) |
126 | 0 | m_scanner->m_skippedComments[Scanner::NextNext].literal.clear(); |
127 | 13.8k | else |
128 | 13.8k | m_scanner->m_tokens[Scanner::NextNext].literal.clear(); |
129 | 13.8k | } |
130 | 35.6M | } |
131 | 35.6M | void complete() { m_complete = true; } |
132 | | |
133 | | private: |
134 | | enum LiteralType m_type; |
135 | | Scanner* m_scanner; |
136 | | bool m_complete; |
137 | | }; |
138 | | |
139 | | void Scanner::reset() |
140 | 628k | { |
141 | 628k | m_source.reset(); |
142 | 628k | m_kind = ScannerKind::Solidity; |
143 | 628k | m_char = m_source.get(); |
144 | 628k | skipWhitespace(); |
145 | 628k | next(); |
146 | 628k | next(); |
147 | 628k | next(); |
148 | 628k | } |
149 | | |
150 | | void Scanner::setPosition(size_t _offset) |
151 | 0 | { |
152 | 0 | m_char = m_source.setPosition(_offset); |
153 | 0 | scanToken(); |
154 | 0 | next(); |
155 | 0 | next(); |
156 | 0 | } |
157 | | |
158 | | bool Scanner::scanHexByte(char& o_scannedByte) |
159 | 5.33M | { |
160 | 5.33M | char x = 0; |
161 | 16.0M | for (size_t i = 0; i < 2; i++) |
162 | 10.6M | { |
163 | 10.6M | int d = hexValue(m_char); |
164 | 10.6M | if (d < 0) |
165 | 2.60k | { |
166 | 2.60k | rollback(i); |
167 | 2.60k | return false; |
168 | 2.60k | } |
169 | 10.6M | x = static_cast<char>(x * 16 + d); |
170 | 10.6M | advance(); |
171 | 10.6M | } |
172 | 5.33M | o_scannedByte = x; |
173 | 5.33M | return true; |
174 | 5.33M | } |
175 | | |
176 | | std::optional<unsigned> Scanner::scanUnicode() |
177 | 14.0k | { |
178 | 14.0k | unsigned x = 0; |
179 | 64.2k | for (size_t i = 0; i < 4; i++) |
180 | 52.4k | { |
181 | 52.4k | int d = hexValue(m_char); |
182 | 52.4k | if (d < 0) |
183 | 2.20k | { |
184 | 2.20k | rollback(i); |
185 | 2.20k | return {}; |
186 | 2.20k | } |
187 | 50.2k | x = x * 16 + static_cast<unsigned>(d); |
188 | 50.2k | advance(); |
189 | 50.2k | } |
190 | 11.8k | return x; |
191 | 14.0k | } |
192 | | |
193 | | // This supports codepoints between 0000 and FFFF. |
194 | | void Scanner::addUnicodeAsUTF8(unsigned codepoint) |
195 | 11.8k | { |
196 | 11.8k | if (codepoint <= 0x7f) |
197 | 2.47k | addLiteralChar(char(codepoint)); |
198 | 9.36k | else if (codepoint <= 0x7ff) |
199 | 3.08k | { |
200 | 3.08k | addLiteralChar(char(0xc0u | (codepoint >> 6u))); |
201 | 3.08k | addLiteralChar(char(0x80u | (codepoint & 0x3fu))); |
202 | 3.08k | } |
203 | 6.28k | else |
204 | 6.28k | { |
205 | 6.28k | addLiteralChar(char(0xe0u | (codepoint >> 12u))); |
206 | 6.28k | addLiteralChar(char(0x80u | ((codepoint >> 6u) & 0x3fu))); |
207 | 6.28k | addLiteralChar(char(0x80u | (codepoint & 0x3fu))); |
208 | 6.28k | } |
209 | 11.8k | } |
210 | | |
211 | | void Scanner::rescan() |
212 | 1.04M | { |
213 | 1.04M | size_t rollbackTo = 0; |
214 | 1.04M | if (m_skippedComments[Current].literal.empty()) |
215 | 1.03M | rollbackTo = static_cast<size_t>(m_tokens[Current].location.start); |
216 | 16.7k | else |
217 | 16.7k | rollbackTo = static_cast<size_t>(m_skippedComments[Current].location.start); |
218 | 1.04M | m_char = m_source.rollback(m_source.position() - rollbackTo); |
219 | 1.04M | next(); |
220 | 1.04M | next(); |
221 | 1.04M | next(); |
222 | 1.04M | } |
223 | | |
224 | | // Ensure that tokens can be stored in a byte. |
225 | | BOOST_STATIC_ASSERT(TokenTraits::count() <= 0x100); |
226 | | |
227 | | Token Scanner::next() |
228 | 78.1M | { |
229 | 78.1M | m_tokens[Current] = std::move(m_tokens[Next]); |
230 | 78.1M | m_tokens[Next] = std::move(m_tokens[NextNext]); |
231 | 78.1M | m_skippedComments[Current] = std::move(m_skippedComments[Next]); |
232 | 78.1M | m_skippedComments[Next] = std::move(m_skippedComments[NextNext]); |
233 | | |
234 | 78.1M | scanToken(); |
235 | | |
236 | 78.1M | return m_tokens[Current].token; |
237 | 78.1M | } |
238 | | |
239 | | Token Scanner::selectToken(char _next, Token _then, Token _else) |
240 | 14.3k | { |
241 | 14.3k | advance(); |
242 | 14.3k | if (m_char == _next) |
243 | 1.00k | return selectToken(_then); |
244 | 13.3k | else |
245 | 13.3k | return _else; |
246 | 14.3k | } |
247 | | |
248 | | bool Scanner::skipWhitespace() |
249 | 36.8M | { |
250 | 36.8M | size_t const startPosition = sourcePos(); |
251 | 156M | while (isWhiteSpace(m_char)) |
252 | 119M | advance(); |
253 | | // Return whether or not we skipped any characters. |
254 | 36.8M | return sourcePos() != startPosition; |
255 | 36.8M | } |
256 | | |
257 | | bool Scanner::skipWhitespaceExceptUnicodeLinebreak() |
258 | 676k | { |
259 | 676k | size_t const startPosition = sourcePos(); |
260 | 5.08M | while (isWhiteSpace(m_char) && !isUnicodeLinebreak()) |
261 | 4.40M | advance(); |
262 | | // Return whether or not we skipped any characters. |
263 | 676k | return sourcePos() != startPosition; |
264 | 676k | } |
265 | | |
266 | | |
267 | | namespace |
268 | | { |
269 | | |
270 | | /// Tries to scan for an RLO/LRO/RLE/LRE/PDF and keeps track of script writing direction override depth. |
271 | | /// |
272 | | /// @returns ScannerError::NoError in case of successful parsing and directional encodings are paired |
273 | | /// and error code in case the input's lexical parser state is invalid and this error should be reported |
274 | | /// to the user. |
275 | | static ScannerError validateBiDiMarkup(CharStream& _stream, size_t _startPosition) |
276 | 224k | { |
277 | 224k | static std::array<std::pair<std::string_view, int>, 5> constexpr directionalSequences{ |
278 | 224k | std::pair<std::string_view, int>{"\xE2\x80\xAD", 1}, // U+202D (LRO - Left-to-Right Override) |
279 | 224k | std::pair<std::string_view, int>{"\xE2\x80\xAE", 1}, // U+202E (RLO - Right-to-Left Override) |
280 | 224k | std::pair<std::string_view, int>{"\xE2\x80\xAA", 1}, // U+202A (LRE - Left-to-Right Embedding) |
281 | 224k | std::pair<std::string_view, int>{"\xE2\x80\xAB", 1}, // U+202B (RLE - Right-to-Left Embedding) |
282 | 224k | std::pair<std::string_view, int>{"\xE2\x80\xAC", -1} // U+202C (PDF - Pop Directional Formatting |
283 | 224k | }; |
284 | | |
285 | 224k | size_t endPosition = _stream.position(); |
286 | 224k | _stream.setPosition(_startPosition); |
287 | | |
288 | 224k | int directionOverrideDepth = 0; |
289 | | |
290 | 4.13M | for (size_t currentPos = _startPosition; currentPos < endPosition; ++currentPos) |
291 | 3.91M | { |
292 | 3.91M | _stream.setPosition(currentPos); |
293 | | |
294 | 3.91M | for (auto const& [sequence, depthChange]: directionalSequences) |
295 | 19.5M | if (_stream.prefixMatch(sequence)) |
296 | 3.55k | directionOverrideDepth += depthChange; |
297 | | |
298 | 3.91M | if (directionOverrideDepth < 0) |
299 | 89 | return ScannerError::DirectionalOverrideUnderflow; |
300 | 3.91M | } |
301 | | |
302 | 224k | _stream.setPosition(endPosition); |
303 | | |
304 | 224k | return directionOverrideDepth > 0 ? ScannerError::DirectionalOverrideMismatch : ScannerError::NoError; |
305 | 224k | } |
306 | | |
307 | | } |
308 | | |
309 | | Token Scanner::skipSingleLineComment() |
310 | 221k | { |
311 | | // Line terminator is not part of the comment. If it is a |
312 | | // non-ascii line terminator, it will result in a parser error. |
313 | 221k | size_t startPosition = m_source.position(); |
314 | 4.06M | while (!isUnicodeLinebreak()) |
315 | 3.84M | if (!advance()) |
316 | 1.93k | break; |
317 | | |
318 | 221k | ScannerError unicodeDirectionError = validateBiDiMarkup(m_source, startPosition); |
319 | 221k | if (unicodeDirectionError != ScannerError::NoError) |
320 | 394 | return setError(unicodeDirectionError); |
321 | | |
322 | 221k | return Token::Whitespace; |
323 | 221k | } |
324 | | |
325 | | bool Scanner::atEndOfLine() const |
326 | 1.73M | { |
327 | 1.73M | return m_char == '\n' || m_char == '\r'; |
328 | 1.73M | } |
329 | | |
330 | | bool Scanner::tryScanEndOfLine() |
331 | 11.0M | { |
332 | 11.0M | if (m_char == '\n') |
333 | 342k | { |
334 | 342k | advance(); |
335 | 342k | return true; |
336 | 342k | } |
337 | | |
338 | 10.6M | if (m_char == '\r') |
339 | 4.43k | { |
340 | 4.43k | if (advance() && m_char == '\n') |
341 | 991 | advance(); |
342 | 4.43k | return true; |
343 | 4.43k | } |
344 | | |
345 | 10.6M | return false; |
346 | 10.6M | } |
347 | | |
348 | | size_t Scanner::scanSingleLineDocComment() |
349 | 331k | { |
350 | 331k | LiteralScope literal(this, LITERAL_TYPE_COMMENT); |
351 | 331k | size_t endPosition = m_source.position(); |
352 | | |
353 | 331k | skipWhitespaceExceptUnicodeLinebreak(); |
354 | | |
355 | 10.9M | while (!isSourcePastEndOfInput()) |
356 | 10.9M | { |
357 | 10.9M | endPosition = m_source.position(); |
358 | 10.9M | if (tryScanEndOfLine()) |
359 | 345k | { |
360 | | // Check if next line is also a single-line comment. |
361 | | // If any whitespaces were skipped, use source position before. |
362 | 345k | if (!skipWhitespaceExceptUnicodeLinebreak()) |
363 | 58.8k | endPosition = m_source.position(); |
364 | | |
365 | 345k | if (!m_source.isPastEndOfInput(3) && |
366 | 343k | m_source.get(0) == '/' && |
367 | 19.2k | m_source.get(1) == '/' && |
368 | 18.2k | m_source.get(2) == '/') |
369 | 16.8k | { |
370 | 16.8k | if (!m_source.isPastEndOfInput(4) && m_source.get(3) == '/') |
371 | 738 | break; // "////" is not a documentation comment |
372 | 16.1k | m_char = m_source.advanceAndGet(3); |
373 | 16.1k | if (atEndOfLine()) |
374 | 2.67k | continue; |
375 | 13.4k | addCommentLiteralChar('\n'); |
376 | 13.4k | } |
377 | 328k | else |
378 | 328k | break; // next line is not a documentation comment, we are done |
379 | 345k | } |
380 | 10.6M | else if (isUnicodeLinebreak()) |
381 | | // Any line terminator that is not '\n' is considered to end the |
382 | | // comment. |
383 | 153 | break; |
384 | 10.6M | addCommentLiteralChar(m_char); |
385 | 10.6M | advance(); |
386 | 10.6M | } |
387 | 331k | literal.complete(); |
388 | 331k | return endPosition; |
389 | 331k | } |
390 | | |
391 | | Token Scanner::skipMultiLineComment() |
392 | 2.69k | { |
393 | 2.69k | size_t startPosition = m_source.position(); |
394 | 22.6k | while (!isSourcePastEndOfInput()) |
395 | 21.9k | { |
396 | 21.9k | char prevChar = m_char; |
397 | 21.9k | advance(); |
398 | | |
399 | | // If we have reached the end of the multi-line comment, we |
400 | | // consume the '/' and insert a whitespace. This way all |
401 | | // multi-line comments are treated as whitespace. |
402 | 21.9k | if (prevChar == '*' && m_char == '/') |
403 | 2.01k | { |
404 | 2.01k | ScannerError unicodeDirectionError = validateBiDiMarkup(m_source, startPosition); |
405 | 2.01k | if (unicodeDirectionError != ScannerError::NoError) |
406 | 148 | return setError(unicodeDirectionError); |
407 | | |
408 | 1.86k | m_char = ' '; |
409 | 1.86k | return Token::Whitespace; |
410 | 2.01k | } |
411 | 21.9k | } |
412 | | // Unterminated multi-line comment. |
413 | 685 | return setError(ScannerError::IllegalCommentTerminator); |
414 | 2.69k | } |
415 | | |
416 | | Token Scanner::scanMultiLineDocComment() |
417 | 61.1k | { |
418 | 61.1k | LiteralScope literal(this, LITERAL_TYPE_COMMENT); |
419 | 61.1k | bool endFound = false; |
420 | 61.1k | bool charsAdded = false; |
421 | | |
422 | 116k | while (isWhiteSpace(m_char) && !atEndOfLine()) |
423 | 55.3k | advance(); |
424 | | |
425 | 1.65M | while (!isSourcePastEndOfInput()) |
426 | 1.65M | { |
427 | | // handle newlines in multiline comments |
428 | 1.65M | if (atEndOfLine()) |
429 | 26.4k | { |
430 | 26.4k | skipWhitespace(); |
431 | 26.4k | if (!m_source.isPastEndOfInput(1) && m_source.get(0) == '*' && m_source.get(1) == '*') |
432 | 3.76k | { // it is unknown if this leads to the end of the comment |
433 | 3.76k | addCommentLiteralChar('*'); |
434 | 3.76k | advance(); |
435 | 3.76k | } |
436 | 22.6k | else if (!m_source.isPastEndOfInput(1) && m_source.get(0) == '*' && m_source.get(1) != '/') |
437 | 7.38k | { // skip first '*' in subsequent lines |
438 | 7.38k | m_char = m_source.advanceAndGet(1); |
439 | 7.38k | if (atEndOfLine()) // ignores empty lines |
440 | 2.65k | continue; |
441 | 4.72k | if (charsAdded) |
442 | 3.00k | addCommentLiteralChar('\n'); // corresponds to the end of previous line |
443 | 4.72k | } |
444 | 15.2k | else if (!m_source.isPastEndOfInput(1) && m_source.get(0) == '*' && m_source.get(1) == '/') |
445 | 1.37k | { // if after newline the comment ends, don't insert the newline |
446 | 1.37k | m_char = m_source.advanceAndGet(2); |
447 | 1.37k | endFound = true; |
448 | 1.37k | break; |
449 | 1.37k | } |
450 | 13.8k | else if (charsAdded) |
451 | 12.2k | addCommentLiteralChar('\n'); |
452 | 26.4k | } |
453 | | |
454 | 1.64M | if (!m_source.isPastEndOfInput(1) && m_source.get(0) == '*' && m_source.get(1) == '/') |
455 | 57.0k | { |
456 | 57.0k | m_char = m_source.advanceAndGet(2); |
457 | 57.0k | endFound = true; |
458 | 57.0k | break; |
459 | 57.0k | } |
460 | 1.58M | addCommentLiteralChar(m_char); |
461 | 1.58M | charsAdded = true; |
462 | 1.58M | advance(); |
463 | 1.58M | } |
464 | 61.1k | literal.complete(); |
465 | 61.1k | if (!endFound) |
466 | 2.64k | return setError(ScannerError::IllegalCommentTerminator); |
467 | 58.4k | else |
468 | 58.4k | return Token::CommentLiteral; |
469 | 61.1k | } |
470 | | |
471 | | Token Scanner::scanSlash() |
472 | 627k | { |
473 | 627k | int firstSlashPosition = static_cast<int>(sourcePos()); |
474 | 627k | advance(); |
475 | 627k | if (m_char == '/') |
476 | 553k | { |
477 | 553k | if (!advance()) /* double slash comment directly before EOS */ |
478 | 368 | return Token::Whitespace; |
479 | 553k | else if (m_char == '/') |
480 | 333k | { |
481 | 333k | advance(); //consume the last '/' at /// |
482 | | |
483 | | // "////" |
484 | 333k | if (m_char == '/') |
485 | 1.78k | return skipSingleLineComment(); |
486 | | // doxygen style /// comment |
487 | 331k | m_skippedComments[NextNext].location.start = firstSlashPosition; |
488 | 331k | m_skippedComments[NextNext].location.sourceName = m_sourceName; |
489 | 331k | m_skippedComments[NextNext].token = Token::CommentLiteral; |
490 | 331k | m_skippedComments[NextNext].location.end = static_cast<int>(scanSingleLineDocComment()); |
491 | 331k | return Token::Whitespace; |
492 | 333k | } |
493 | 219k | else |
494 | 219k | return skipSingleLineComment(); |
495 | 553k | } |
496 | 73.7k | else if (m_char == '*') |
497 | 65.5k | { |
498 | | // doxygen style /** natspec comment |
499 | 65.5k | if (!advance()) /* slash star comment before EOS */ |
500 | 101 | return setError(ScannerError::IllegalCommentTerminator); |
501 | 65.4k | else if (m_char == '*') |
502 | 63.9k | { |
503 | 63.9k | advance(); //consume the last '*' at /** |
504 | | |
505 | | // "/**/" |
506 | 63.9k | if (m_char == '/') |
507 | 1.66k | { |
508 | 1.66k | advance(); //skip the closing slash |
509 | 1.66k | return Token::Whitespace; |
510 | 1.66k | } |
511 | | // "/***" |
512 | 62.2k | if (m_char == '*') |
513 | | // "/***/" may be interpreted as empty natspec or skipped; skipping is simpler |
514 | 1.13k | return skipMultiLineComment(); |
515 | | // we actually have a multiline documentation comment |
516 | 61.1k | m_skippedComments[NextNext].location.start = firstSlashPosition; |
517 | 61.1k | m_skippedComments[NextNext].location.sourceName = m_sourceName; |
518 | 61.1k | Token comment = scanMultiLineDocComment(); |
519 | 61.1k | m_skippedComments[NextNext].location.end = static_cast<int>(sourcePos()); |
520 | 61.1k | m_skippedComments[NextNext].token = comment; |
521 | 61.1k | if (comment == Token::Illegal) |
522 | 2.64k | return Token::Illegal; // error already set |
523 | 58.4k | else |
524 | 58.4k | return Token::Whitespace; |
525 | 61.1k | } |
526 | 1.55k | else |
527 | 1.55k | return skipMultiLineComment(); |
528 | 65.5k | } |
529 | 8.15k | else if (m_char == '=') |
530 | 276 | return selectToken(Token::AssignDiv); |
531 | 7.88k | else |
532 | 7.88k | return Token::Div; |
533 | 627k | } |
534 | | |
535 | | void Scanner::scanToken() |
536 | 78.1M | { |
537 | 78.1M | m_tokens[NextNext] = {}; |
538 | 78.1M | m_skippedComments[NextNext] = {}; |
539 | | |
540 | 78.1M | Token token; |
541 | | // M and N are for the purposes of grabbing different type sizes |
542 | 78.1M | unsigned m = 0; |
543 | 78.1M | unsigned n = 0; |
544 | 78.1M | do |
545 | 112M | { |
546 | | // Remember the position of the next token |
547 | 112M | m_tokens[NextNext].location.start = static_cast<int>(sourcePos()); |
548 | 112M | switch (m_char) |
549 | 112M | { |
550 | 783k | case '"': |
551 | 794k | case '\'': |
552 | 794k | token = scanString(false); |
553 | 794k | break; |
554 | 10.6k | case '<': |
555 | | // < <= << <<= |
556 | 10.6k | advance(); |
557 | 10.6k | if (m_char == '=') |
558 | 333 | token = selectToken(Token::LessThanOrEqual); |
559 | 10.2k | else if (m_char == '<') |
560 | 2.05k | token = selectToken('=', Token::AssignShl, Token::SHL); |
561 | 8.24k | else |
562 | 8.24k | token = Token::LessThan; |
563 | 10.6k | break; |
564 | 6.98k | case '>': |
565 | | // > >= >> >>= >>> >>>= |
566 | 6.98k | advance(); |
567 | 6.98k | if (m_char == '=') |
568 | 2.37k | token = selectToken(Token::GreaterThanOrEqual); |
569 | 4.61k | else if (m_char == '>') |
570 | 1.57k | { |
571 | | // >> >>= >>> >>>= |
572 | 1.57k | advance(); |
573 | 1.57k | if (m_char == '=') |
574 | 151 | token = selectToken(Token::AssignSar); |
575 | 1.42k | else if (m_char == '>') |
576 | 434 | token = selectToken('=', Token::AssignShr, Token::SHR); |
577 | 990 | else |
578 | 990 | token = Token::SAR; |
579 | 1.57k | } |
580 | 3.04k | else |
581 | 3.04k | token = Token::GreaterThan; |
582 | 6.98k | break; |
583 | 86.4k | case '=': |
584 | | // = == => |
585 | 86.4k | advance(); |
586 | 86.4k | if (m_char == '=') |
587 | 6.31k | token = selectToken(Token::Equal); |
588 | 80.1k | else if (m_char == '>') |
589 | 1.55k | token = selectToken(Token::DoubleArrow); |
590 | 78.5k | else |
591 | 78.5k | token = Token::Assign; |
592 | 86.4k | break; |
593 | 83.9k | case '!': |
594 | | // ! != |
595 | 83.9k | advance(); |
596 | 83.9k | if (m_char == '=') |
597 | 67.3k | token = selectToken(Token::NotEqual); |
598 | 16.6k | else |
599 | 16.6k | token = Token::Not; |
600 | 83.9k | break; |
601 | 12.3k | case '+': |
602 | | // + ++ += |
603 | 12.3k | advance(); |
604 | 12.3k | if (m_char == '+') |
605 | 5.50k | token = selectToken(Token::Inc); |
606 | 6.87k | else if (m_char == '=') |
607 | 444 | token = selectToken(Token::AssignAdd); |
608 | 6.43k | else |
609 | 6.43k | token = Token::Add; |
610 | 12.3k | break; |
611 | 710k | case '-': |
612 | | // - -- -= -> |
613 | 710k | advance(); |
614 | 710k | if (m_char == '-') |
615 | 3.31k | token = selectToken(Token::Dec); |
616 | 706k | else if (m_char == '=') |
617 | 156 | token = selectToken(Token::AssignSub); |
618 | 706k | else if (m_char == '>') |
619 | 609k | token = selectToken(Token::RightArrow); |
620 | 96.8k | else |
621 | 96.8k | token = Token::Sub; |
622 | 710k | break; |
623 | 20.2k | case '*': |
624 | | // * ** *= |
625 | 20.2k | advance(); |
626 | 20.2k | if (m_char == '*') |
627 | 9.69k | token = selectToken(Token::Exp); |
628 | 10.5k | else if (m_char == '=') |
629 | 180 | token = selectToken(Token::AssignMul); |
630 | 10.3k | else |
631 | 10.3k | token = Token::Mul; |
632 | 20.2k | break; |
633 | 6.70k | case '%': |
634 | | // % %= |
635 | 6.70k | token = selectToken('=', Token::AssignMod, Token::Mod); |
636 | 6.70k | break; |
637 | 627k | case '/': |
638 | | // / // /* /= |
639 | 627k | token = scanSlash(); |
640 | 627k | break; |
641 | 6.82k | case '&': |
642 | | // & && &= |
643 | 6.82k | advance(); |
644 | 6.82k | if (m_char == '&') |
645 | 2.51k | token = selectToken(Token::And); |
646 | 4.30k | else if (m_char == '=') |
647 | 870 | token = selectToken(Token::AssignBitAnd); |
648 | 3.43k | else |
649 | 3.43k | token = Token::BitAnd; |
650 | 6.82k | break; |
651 | 5.27k | case '|': |
652 | | // | || |= |
653 | 5.27k | advance(); |
654 | 5.27k | if (m_char == '|') |
655 | 1.98k | token = selectToken(Token::Or); |
656 | 3.29k | else if (m_char == '=') |
657 | 118 | token = selectToken(Token::AssignBitOr); |
658 | 3.17k | else |
659 | 3.17k | token = Token::BitOr; |
660 | 5.27k | break; |
661 | 5.19k | case '^': |
662 | | // ^ ^= |
663 | 5.19k | token = selectToken('=', Token::AssignBitXor, Token::BitXor); |
664 | 5.19k | break; |
665 | 320k | case '.': |
666 | | // . Number |
667 | 320k | advance(); |
668 | 320k | if (isDecimalDigit(m_char)) |
669 | 3.91k | token = scanNumber('.'); |
670 | 316k | else |
671 | 316k | token = Token::Period; |
672 | 320k | break; |
673 | 3.29M | case ':': |
674 | | // : := |
675 | 3.29M | advance(); |
676 | 3.29M | if (m_char == '=') |
677 | 2.39M | token = selectToken(Token::AssemblyAssign); |
678 | 902k | else |
679 | 902k | token = Token::Colon; |
680 | 3.29M | break; |
681 | 300k | case ';': |
682 | 300k | token = selectToken(Token::Semicolon); |
683 | 300k | break; |
684 | 7.79M | case ',': |
685 | 7.79M | token = selectToken(Token::Comma); |
686 | 7.79M | break; |
687 | 9.40M | case '(': |
688 | 9.40M | token = selectToken(Token::LParen); |
689 | 9.40M | break; |
690 | 9.27M | case ')': |
691 | 9.27M | token = selectToken(Token::RParen); |
692 | 9.27M | break; |
693 | 295k | case '[': |
694 | 295k | token = selectToken(Token::LBrack); |
695 | 295k | break; |
696 | 290k | case ']': |
697 | 290k | token = selectToken(Token::RBrack); |
698 | 290k | break; |
699 | 4.52M | case '{': |
700 | 4.52M | token = selectToken(Token::LBrace); |
701 | 4.52M | break; |
702 | 4.01M | case '}': |
703 | 4.01M | token = selectToken(Token::RBrace); |
704 | 4.01M | break; |
705 | 3.23k | case '?': |
706 | 3.23k | token = selectToken(Token::Conditional); |
707 | 3.23k | break; |
708 | 12.4k | case '~': |
709 | 12.4k | token = selectToken(Token::BitNot); |
710 | 12.4k | break; |
711 | 70.4M | default: |
712 | 70.4M | if (isIdentifierStart(m_char)) |
713 | 24.5M | { |
714 | 24.5M | std::tie(token, m, n) = scanIdentifierOrKeyword(); |
715 | | |
716 | | // Special case for hexadecimal literals |
717 | 24.5M | if (token == Token::Hex) |
718 | 102k | { |
719 | | // reset |
720 | 102k | m = 0; |
721 | 102k | n = 0; |
722 | | |
723 | | // Special quoted hex string must follow |
724 | 102k | if (m_char == '"' || m_char == '\'') |
725 | 102k | token = scanHexString(); |
726 | 294 | else |
727 | 294 | token = setError(ScannerError::IllegalToken); |
728 | 102k | } |
729 | 24.4M | else if (token == Token::Unicode && m_kind != ScannerKind::Yul) |
730 | 1.12k | { |
731 | | // reset |
732 | 1.12k | m = 0; |
733 | 1.12k | n = 0; |
734 | | |
735 | | // Special quoted hex string must follow |
736 | 1.12k | if (m_char == '"' || m_char == '\'') |
737 | 1.06k | token = scanString(true); |
738 | 60 | else |
739 | 60 | token = setError(ScannerError::IllegalToken); |
740 | 1.12k | } |
741 | 24.5M | } |
742 | 45.8M | else if (isDecimalDigit(m_char)) |
743 | 9.74M | token = scanNumber(); |
744 | 36.1M | else if (skipWhitespace()) |
745 | 33.5M | token = Token::Whitespace; |
746 | 2.55M | else if (isSourcePastEndOfInput()) |
747 | 2.53M | token = Token::EOS; |
748 | 22.2k | else |
749 | 22.2k | token = selectErrorToken(ScannerError::IllegalToken); |
750 | 70.4M | break; |
751 | 112M | } |
752 | | // Continue scanning for tokens as long as we're just skipping |
753 | | // whitespace. |
754 | 112M | } |
755 | 112M | while (token == Token::Whitespace); |
756 | 78.1M | m_tokens[NextNext].location.end = static_cast<int>(sourcePos()); |
757 | 78.1M | m_tokens[NextNext].location.sourceName = m_sourceName; |
758 | 78.1M | m_tokens[NextNext].token = token; |
759 | 78.1M | m_tokens[NextNext].extendedTokenInfo = std::make_tuple(m, n); |
760 | 78.1M | } |
761 | | |
762 | | bool Scanner::scanEscape() |
763 | 37.2k | { |
764 | 37.2k | char c = m_char; |
765 | | |
766 | | // Skip escaped newlines. |
767 | 37.2k | if (tryScanEndOfLine()) |
768 | 1.74k | return true; |
769 | 35.4k | advance(); |
770 | | |
771 | 35.4k | switch (c) |
772 | 35.4k | { |
773 | 1.85k | case '\'': // fall through |
774 | 4.03k | case '"': // fall through |
775 | 9.89k | case '\\': |
776 | 9.89k | break; |
777 | 1.85k | case 'n': |
778 | 1.85k | c = '\n'; |
779 | 1.85k | break; |
780 | 1.27k | case 'r': |
781 | 1.27k | c = '\r'; |
782 | 1.27k | break; |
783 | 2.72k | case 't': |
784 | 2.72k | c = '\t'; |
785 | 2.72k | break; |
786 | 14.0k | case 'u': |
787 | 14.0k | { |
788 | 14.0k | if (auto const codepoint = scanUnicode(); codepoint.has_value()) |
789 | 11.8k | addUnicodeAsUTF8(*codepoint); |
790 | 2.20k | else |
791 | 2.20k | return false; |
792 | 11.8k | return true; |
793 | 14.0k | } |
794 | 5.24k | case 'x': |
795 | 5.24k | if (!scanHexByte(c)) |
796 | 359 | return false; |
797 | 4.88k | break; |
798 | 4.88k | default: |
799 | 467 | return false; |
800 | 35.4k | } |
801 | | |
802 | 20.6k | addLiteralChar(c); |
803 | 20.6k | return true; |
804 | 35.4k | } |
805 | | |
806 | | bool Scanner::isUnicodeLinebreak() |
807 | 27.8M | { |
808 | 27.8M | if (0x0a <= m_char && m_char <= 0x0d) |
809 | | // line feed, vertical tab, form feed, carriage return |
810 | 242k | return true; |
811 | 27.6M | if (!m_source.isPastEndOfInput(1) && uint8_t(m_source.get(0)) == 0xc2 && uint8_t(m_source.get(1)) == 0x85) |
812 | | // NEL - U+0085, C2 85 in utf8 |
813 | 61 | return true; |
814 | 27.6M | if (!m_source.isPastEndOfInput(2) && uint8_t(m_source.get(0)) == 0xe2 && uint8_t(m_source.get(1)) == 0x80 && ( |
815 | 6.16k | uint8_t(m_source.get(2)) == 0xa8 || uint8_t(m_source.get(2)) == 0xa9 |
816 | 6.16k | )) |
817 | | // LS - U+2028, E2 80 A8 in utf8 |
818 | | // PS - U+2029, E2 80 A9 in utf8 |
819 | 103 | return true; |
820 | 27.6M | return false; |
821 | 27.6M | } |
822 | | |
823 | | Token Scanner::scanString(bool const _isUnicode) |
824 | 795k | { |
825 | 795k | size_t startPosition = m_source.position(); |
826 | 795k | char const quote = m_char; |
827 | 795k | advance(); // consume quote |
828 | 795k | LiteralScope literal(this, LITERAL_TYPE_STRING); |
829 | | // for source location comments we allow multiline string literals |
830 | 9.51M | while (m_char != quote && !isSourcePastEndOfInput() && (!isUnicodeLinebreak() || m_kind == ScannerKind::SpecialComment)) |
831 | 8.72M | { |
832 | 8.72M | char c = m_char; |
833 | 8.72M | advance(); |
834 | | |
835 | 8.72M | if (m_kind == ScannerKind::SpecialComment) |
836 | 4.52M | { |
837 | 4.52M | if (c == '\\') |
838 | 81.1k | { |
839 | 81.1k | if (isSourcePastEndOfInput()) |
840 | 121 | return setError(ScannerError::IllegalEscapeSequence); |
841 | 81.0k | advance(); |
842 | 81.0k | } |
843 | 4.44M | else |
844 | 4.44M | addLiteralChar(c); |
845 | 4.52M | } |
846 | 4.19M | else |
847 | 4.19M | { |
848 | 4.19M | if (c == '\\') |
849 | 37.4k | { |
850 | 37.4k | if (isSourcePastEndOfInput() || !scanEscape()) |
851 | 3.27k | return setError(ScannerError::IllegalEscapeSequence); |
852 | 37.4k | } |
853 | 4.15M | else |
854 | 4.15M | { |
855 | | // Report error on non-printable characters in string literals, however |
856 | | // allow anything for unicode string literals, because their validity will |
857 | | // be verified later (in the syntax checker). |
858 | | // |
859 | | // We are using a manual range and not isprint() to avoid |
860 | | // any potential complications with locale. |
861 | 4.15M | if (!_isUnicode && (static_cast<unsigned>(c) <= 0x1f || static_cast<unsigned>(c) >= 0x7f)) |
862 | 1.17k | { |
863 | 1.17k | if (m_kind == ScannerKind::Yul) |
864 | 224 | return setError(ScannerError::IllegalCharacterInString); |
865 | 955 | return setError(ScannerError::UnicodeCharacterInNonUnicodeString); |
866 | 1.17k | } |
867 | 4.15M | addLiteralChar(c); |
868 | 4.15M | } |
869 | 4.19M | } |
870 | | |
871 | 8.72M | } |
872 | 791k | if (m_char != quote) |
873 | 2.98k | return setError(ScannerError::IllegalStringEndQuote); |
874 | | |
875 | 788k | if (_isUnicode) |
876 | 935 | { |
877 | 935 | ScannerError unicodeDirectionError = validateBiDiMarkup(m_source, startPosition); |
878 | 935 | if (unicodeDirectionError != ScannerError::NoError) |
879 | 21 | return setError(unicodeDirectionError); |
880 | 935 | } |
881 | | |
882 | 788k | literal.complete(); |
883 | 788k | advance(); // consume quote |
884 | 788k | return _isUnicode ? Token::UnicodeStringLiteral : Token::StringLiteral; |
885 | 788k | } |
886 | | |
887 | | Token Scanner::scanHexString() |
888 | 102k | { |
889 | 102k | char const quote = m_char; |
890 | 102k | advance(); // consume quote |
891 | 102k | LiteralScope literal(this, LITERAL_TYPE_STRING); |
892 | 102k | bool allowUnderscore = false; |
893 | 5.43M | while (m_char != quote && !isSourcePastEndOfInput()) |
894 | 5.33M | { |
895 | 5.33M | char c = m_char; |
896 | | |
897 | 5.33M | if (scanHexByte(c)) |
898 | 5.32M | { |
899 | 5.32M | addLiteralChar(c); |
900 | 5.32M | allowUnderscore = true; |
901 | 5.32M | } |
902 | 2.24k | else if (c == '_') |
903 | 1.21k | { |
904 | 1.21k | advance(); |
905 | 1.21k | if (!allowUnderscore || m_char == quote) |
906 | 268 | return setError(ScannerError::IllegalNumberSeparator); |
907 | 949 | allowUnderscore = false; |
908 | 949 | } |
909 | 1.02k | else |
910 | 1.02k | return setError(ScannerError::IllegalHexString); |
911 | 5.33M | } |
912 | | |
913 | 100k | if (m_char != quote) |
914 | 307 | return setError(ScannerError::IllegalStringEndQuote); |
915 | | |
916 | 100k | literal.complete(); |
917 | 100k | advance(); // consume quote |
918 | 100k | return Token::HexStringLiteral; |
919 | 100k | } |
920 | | |
921 | | // Parse for regex [:digit:]+(_[:digit:]+)* |
922 | | void Scanner::scanDecimalDigits() |
923 | 6.99M | { |
924 | | // MUST begin with a decimal digit. |
925 | 6.99M | if (!isDecimalDigit(m_char)) |
926 | 2.17M | return; |
927 | | |
928 | | // May continue with decimal digit or underscore for grouping. |
929 | 4.81M | do |
930 | 15.7M | addLiteralCharAndAdvance(); |
931 | 15.7M | while (!m_source.isPastEndOfInput() && (isDecimalDigit(m_char) || m_char == '_')); |
932 | | |
933 | | // Defer further validation of underscore to SyntaxChecker. |
934 | 4.81M | } |
935 | | |
936 | | Token Scanner::scanNumber(char _charSeen) |
937 | 9.74M | { |
938 | 9.74M | enum { DECIMAL, HEX, BINARY } kind = DECIMAL; |
939 | 9.74M | LiteralScope literal(this, LITERAL_TYPE_NUMBER); |
940 | 9.74M | if (_charSeen == '.') |
941 | 3.91k | { |
942 | | // we have already seen a decimal point of the float |
943 | 3.91k | addLiteralChar('.'); |
944 | 3.91k | if (m_char == '_') |
945 | 0 | return setError(ScannerError::IllegalToken); |
946 | 3.91k | scanDecimalDigits(); // we know we have at least one digit |
947 | 3.91k | } |
948 | 9.74M | else |
949 | 9.74M | { |
950 | 9.74M | solAssert(_charSeen == 0, ""); |
951 | | // if the first character is '0' we must check for octals and hex |
952 | 9.74M | if (m_char == '0') |
953 | 4.94M | { |
954 | 4.94M | addLiteralCharAndAdvance(); |
955 | | // either 0, 0exxx, 0Exxx, 0.xxx or a hex number |
956 | 4.94M | if (m_char == 'x') |
957 | 2.76M | { |
958 | | // hex number |
959 | 2.76M | kind = HEX; |
960 | 2.76M | addLiteralCharAndAdvance(); |
961 | 2.76M | if (!isHexDigit(m_char)) |
962 | 604 | return setError(ScannerError::IllegalHexDigit); // we must have at least one hex digit after 'x' |
963 | | |
964 | 65.4M | while (isHexDigit(m_char) || m_char == '_') // We keep the underscores for later validation |
965 | 62.7M | addLiteralCharAndAdvance(); |
966 | 2.76M | } |
967 | 2.17M | else if (isDecimalDigit(m_char)) |
968 | | // We do not allow octal numbers |
969 | 692 | return setError(ScannerError::OctalNotAllowed); |
970 | 4.94M | } |
971 | | // Parse decimal digits and allow trailing fractional part. |
972 | 9.74M | if (kind == DECIMAL) |
973 | 6.97M | { |
974 | 6.97M | scanDecimalDigits(); // optional |
975 | 6.97M | if (m_char == '.') |
976 | 7.50k | { |
977 | 7.50k | if (!m_source.isPastEndOfInput(1) && m_source.get(1) == '_') |
978 | 1.08k | { |
979 | | // Assume the input may be a floating point number with leading '_' in fraction part. |
980 | | // Recover by consuming it all but returning `Illegal` right away. |
981 | 1.08k | addLiteralCharAndAdvance(); // '.' |
982 | 1.08k | addLiteralCharAndAdvance(); // '_' |
983 | 1.08k | scanDecimalDigits(); |
984 | 1.08k | } |
985 | 7.50k | if (m_source.isPastEndOfInput() || !isDecimalDigit(m_source.get(1))) |
986 | 990 | { |
987 | | // A '.' has to be followed by a number. |
988 | 990 | literal.complete(); |
989 | 990 | return Token::Number; |
990 | 990 | } |
991 | 6.51k | addLiteralCharAndAdvance(); |
992 | 6.51k | scanDecimalDigits(); |
993 | 6.51k | } |
994 | 6.97M | } |
995 | 9.74M | } |
996 | | // scan exponent, if any |
997 | 9.74M | if (m_char == 'e' || m_char == 'E') |
998 | 10.6k | { |
999 | 10.6k | solAssert(kind != HEX, "'e'/'E' must be scanned as part of the hex number"); |
1000 | 10.6k | if (kind != DECIMAL) |
1001 | 0 | return setError(ScannerError::IllegalExponent); |
1002 | 10.6k | else if (!m_source.isPastEndOfInput(1) && m_source.get(1) == '_') |
1003 | 684 | { |
1004 | | // Recover from wrongly placed underscore as delimiter in literal with scientific |
1005 | | // notation by consuming until the end. |
1006 | 684 | addLiteralCharAndAdvance(); // 'e' |
1007 | 684 | addLiteralCharAndAdvance(); // '_' |
1008 | 684 | scanDecimalDigits(); |
1009 | 684 | literal.complete(); |
1010 | 684 | return Token::Number; |
1011 | 684 | } |
1012 | | // scan exponent |
1013 | 9.92k | addLiteralCharAndAdvance(); // 'e' | 'E' |
1014 | 9.92k | if (m_char == '+' || m_char == '-') |
1015 | 2.67k | addLiteralCharAndAdvance(); |
1016 | 9.92k | if (!isDecimalDigit(m_char)) // we must have at least one decimal digit after 'e'/'E' |
1017 | 928 | return setError(ScannerError::IllegalExponent); |
1018 | 8.99k | scanDecimalDigits(); |
1019 | 8.99k | } |
1020 | | // The source character immediately following a numeric literal must |
1021 | | // not be an identifier start or a decimal digit; see ECMA-262 |
1022 | | // section 7.8.3, page 17 (note that we read only one decimal digit |
1023 | | // if the value is 0). |
1024 | 9.74M | if (isDecimalDigit(m_char) || isIdentifierStart(m_char)) |
1025 | 2.39k | return setError(ScannerError::IllegalNumberEnd); |
1026 | 9.74M | literal.complete(); |
1027 | 9.74M | return Token::Number; |
1028 | 9.74M | } |
1029 | | |
1030 | | std::tuple<Token, unsigned, unsigned> Scanner::scanIdentifierOrKeyword() |
1031 | 24.5M | { |
1032 | 24.5M | solAssert(isIdentifierStart(m_char), ""); |
1033 | 24.5M | LiteralScope literal(this, LITERAL_TYPE_STRING); |
1034 | 24.5M | addLiteralCharAndAdvance(); |
1035 | | // Scan the rest of the identifier characters. |
1036 | 174M | while (isIdentifierPart(m_char) || (m_char == '.' && m_kind == ScannerKind::Yul)) |
1037 | 149M | addLiteralCharAndAdvance(); |
1038 | 24.5M | literal.complete(); |
1039 | | |
1040 | 24.5M | auto const token = TokenTraits::fromIdentifierOrKeyword(m_tokens[NextNext].literal); |
1041 | 24.5M | switch (m_kind) |
1042 | 24.5M | { |
1043 | 1.37k | case ScannerKind::SpecialComment: |
1044 | | // there are no keywords in special comments |
1045 | 1.37k | return std::make_tuple(Token::Identifier, 0, 0); |
1046 | 2.28M | case ScannerKind::Solidity: |
1047 | 2.28M | break; |
1048 | 22.3M | case ScannerKind::Yul: |
1049 | | // Turn Solidity identifier into a Yul keyword |
1050 | 22.3M | if (m_tokens[NextNext].literal == "leave") |
1051 | 103k | return std::make_tuple(Token::Leave, 0, 0); |
1052 | | // Turn non-Yul keywords into identifiers. |
1053 | 22.2M | if (!TokenTraits::isYulKeyword(std::get<0>(token))) |
1054 | 18.4M | return std::make_tuple(Token::Identifier, 0, 0); |
1055 | 3.77M | break; |
1056 | 24.5M | } |
1057 | 6.06M | return token; |
1058 | 24.5M | } |
1059 | | |
1060 | | } // namespace solidity::langutil |