Branch data Line data Source code
1 : : // Copyright (C) 2020-2025 Free Software Foundation, Inc.
2 : :
3 : : // This file is part of GCC.
4 : :
5 : : // GCC is free software; you can redistribute it and/or modify it under
6 : : // the terms of the GNU General Public License as published by the Free
7 : : // Software Foundation; either version 3, or (at your option) any later
8 : : // version.
9 : :
10 : : // GCC is distributed in the hope that it will be useful, but WITHOUT ANY
11 : : // WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 : : // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13 : : // for more details.
14 : :
15 : : // You should have received a copy of the GNU General Public License
16 : : // along with GCC; see the file COPYING3. If not see
17 : : // <http://www.gnu.org/licenses/>.
18 : :
19 : : #include "rust-codepoint.h"
20 : : #include "rust-system.h"
21 : : #include "rust-lex.h"
22 : : #include "rust-diagnostics.h"
23 : : #include "rust-linemap.h"
24 : : #include "rust-edition.h"
25 : : #include "safe-ctype.h"
26 : : #include "cpplib.h"
27 : : #include "rust-keyword-values.h"
28 : :
29 : : namespace Rust {
30 : : // TODO: move to separate compilation unit?
31 : : // overload += for uint32_t to allow 32-bit encoded utf-8 to be added
32 : : std::string &
33 : 2156487 : operator+= (std::string &str, Codepoint char32)
34 : : {
35 : 2156487 : if (char32.value < 0x80)
36 : : {
37 : 2155277 : str += static_cast<char> (char32.value);
38 : : }
39 : 1210 : else if (char32.value < (0x1F + 1) << (1 * 6))
40 : : {
41 : 796 : str += static_cast<char> (0xC0 | ((char32.value >> 6) & 0x1F));
42 : 796 : str += static_cast<char> (0x80 | ((char32.value >> 0) & 0x3F));
43 : : }
44 : 414 : else if (char32.value < (0x0F + 1) << (2 * 6))
45 : : {
46 : 395 : str += static_cast<char> (0xE0 | ((char32.value >> 12) & 0x0F));
47 : 395 : str += static_cast<char> (0x80 | ((char32.value >> 6) & 0x3F));
48 : 395 : str += static_cast<char> (0x80 | ((char32.value >> 0) & 0x3F));
49 : : }
50 : 19 : else if (char32.value < (0x07 + 1) << (3 * 6))
51 : : {
52 : 11 : str += static_cast<char> (0xF0 | ((char32.value >> 18) & 0x07));
53 : 11 : str += static_cast<char> (0x80 | ((char32.value >> 12) & 0x3F));
54 : 11 : str += static_cast<char> (0x80 | ((char32.value >> 6) & 0x3F));
55 : 11 : str += static_cast<char> (0x80 | ((char32.value >> 0) & 0x3F));
56 : : }
57 : : else
58 : : {
59 : 8 : rust_debug ("Invalid unicode codepoint found: '%u' ", char32.value);
60 : : }
61 : 2156487 : return str;
62 : : }
63 : :
64 : : std::string
65 : 2053040 : Codepoint::as_string ()
66 : : {
67 : 2053040 : std::string str;
68 : :
69 : : // str += Codepoint (value);
70 : 2053040 : str += *this;
71 : :
72 : 2053040 : return str;
73 : : }
74 : :
75 : : /* Includes all allowable float digits EXCEPT _ and . as that needs lookahead
76 : : * for handling. */
77 : : bool
78 : 0 : is_float_digit (uint32_t number)
79 : : {
80 : 0 : return ISDIGIT (number) || number == 'E' || number == 'e';
81 : : }
82 : :
83 : : /* Basically ISXDIGIT from safe-ctype but may change if Rust's encoding or
84 : : * whatever is different */
85 : : bool
86 : 1525 : is_x_digit (uint32_t number)
87 : : {
88 : 1525 : return ISXDIGIT (number);
89 : : }
90 : :
91 : : bool
92 : 64 : is_octal_digit (uint32_t number)
93 : : {
94 : 64 : return number >= '0' && number <= '7';
95 : : }
96 : :
97 : : bool
98 : 224 : is_bin_digit (uint32_t number)
99 : : {
100 : 224 : return number == '0' || number == '1';
101 : : }
102 : :
103 : : bool
104 : 112 : check_valid_float_dot_end (uint32_t character)
105 : : {
106 : 112 : return character != '.' && character != '_' && !ISALPHA (character);
107 : : }
108 : :
109 : : bool
110 : 974 : is_whitespace (uint32_t character)
111 : : {
112 : : // https://doc.rust-lang.org/reference/whitespace.html
113 : 974 : return character == '\t' || character == '\n' || character == '\v'
114 : 974 : || character == '\f' || character == '\r' || character == ' '
115 : 534 : || character == 0x0085 // next line
116 : 534 : || character == 0x200e // left-to-right mark
117 : 534 : || character == 0x200f // right-to-left mark
118 : 534 : || character == 0x2028 // line separator
119 : 1508 : || character == 0x2029; // pragraph separator
120 : : }
121 : :
122 : : bool
123 : 3059 : is_non_decimal_int_literal_separator (uint32_t character)
124 : : {
125 : 3059 : return character == 'x' || character == 'o' || character == 'b';
126 : : }
127 : :
128 : : bool
129 : 249717 : is_identifier_start (uint32_t codepoint)
130 : : {
131 : 249717 : return (cpp_check_xid_property (codepoint) & CPP_XID_START)
132 : 249717 : || codepoint == '_';
133 : : }
134 : :
135 : : bool
136 : 831528 : is_identifier_continue (uint32_t codepoint)
137 : : {
138 : 831528 : return cpp_check_xid_property (codepoint) & CPP_XID_CONTINUE;
139 : : }
140 : :
141 : 115 : Lexer::Lexer (const std::string &input, Linemap *linemap)
142 : 115 : : input (RAIIFile::create_error ()), current_line (1), current_column (1),
143 : 115 : line_map (linemap), dump_lex_out ({}),
144 : 115 : raw_input_source (new BufferInputSource (input, 0)),
145 : 115 : input_queue{*raw_input_source}, token_queue (TokenSource (this))
146 : 115 : {}
147 : :
148 : 5191 : Lexer::Lexer (const char *filename, RAIIFile file_input, Linemap *linemap,
149 : 5191 : tl::optional<std::ofstream &> dump_lex_opt)
150 : 5191 : : input (std::move (file_input)), current_line (1), current_column (1),
151 : 5191 : line_map (linemap), dump_lex_out (dump_lex_opt),
152 : 10382 : raw_input_source (new FileInputSource (input.get_raw ())),
153 : 10382 : input_queue{*raw_input_source}, token_queue (TokenSource (this))
154 : : {
155 : : // inform line_table that file is being entered and is in line 1
156 : 5191 : if (linemap)
157 : 5191 : line_map->start_file (filename, current_line);
158 : 5191 : }
159 : :
160 : 5299 : Lexer::~Lexer ()
161 : : {
162 : : /* ok apparently stop (which is equivalent of original code in destructor) is
163 : : * meant to be called after all files have finished parsing, for cleanup. On
164 : : * the other hand, actual code that it calls to leave a certain line map is
165 : : * mentioned in GCC docs as being useful for "just leaving an included header"
166 : : * and stuff like that, so this line mapping functionality may need fixing.
167 : : * FIXME: find out whether this occurs. */
168 : :
169 : : // line_map->stop();
170 : 5299 : }
171 : :
172 : : bool
173 : 5120 : Lexer::input_source_is_valid_utf8 ()
174 : : {
175 : 5120 : return raw_input_source->is_valid ();
176 : : }
177 : :
178 : : location_t
179 : 1331142 : Lexer::get_current_location ()
180 : : {
181 : 1331142 : if (line_map)
182 : 1330945 : return linemap_position_for_column (line_table, current_column);
183 : : else
184 : : // If we have no linemap, we're lexing something without proper locations
185 : : return UNDEF_LOCATION;
186 : : }
187 : :
188 : : Codepoint
189 : 2981740 : Lexer::peek_input (int n)
190 : : {
191 : 2981740 : return input_queue.peek (n);
192 : : }
193 : :
194 : : Codepoint
195 : 2935180 : Lexer::peek_input ()
196 : : {
197 : 2935180 : return peek_input (0);
198 : : }
199 : :
200 : : void
201 : 2503567 : Lexer::skip_input (int n)
202 : : {
203 : 2503567 : input_queue.skip (n);
204 : 2503567 : }
205 : :
206 : : void
207 : 2500505 : Lexer::skip_input ()
208 : : {
209 : 2500505 : skip_input (0);
210 : 2500505 : }
211 : :
212 : : void
213 : 542024 : Lexer::skip_token (int n)
214 : : {
215 : : // dump tokens if dump-lex option is enabled
216 : 542024 : if (dump_lex_out.has_value ())
217 : 0 : dump_and_skip (n);
218 : : else
219 : 542024 : token_queue.skip (n);
220 : 542024 : }
221 : :
222 : : void
223 : 0 : Lexer::dump_and_skip (int n)
224 : : {
225 : 0 : std::ofstream &out = dump_lex_out.value ();
226 : 0 : bool found_eof = false;
227 : 0 : const_TokenPtr tok;
228 : 0 : for (int i = 0; i < n + 1; i++)
229 : : {
230 : 0 : if (!found_eof)
231 : : {
232 : 0 : tok = peek_token ();
233 : 0 : found_eof |= tok->get_id () == Rust::END_OF_FILE;
234 : :
235 : 0 : location_t loc = tok->get_locus ();
236 : :
237 : 0 : out << "<id=";
238 : 0 : out << tok->token_id_to_str ();
239 : 0 : out << (tok->has_str () ? (std::string (", text=") + tok->get_str ()
240 : 0 : + std::string (", typehint=")
241 : 0 : + std::string (tok->get_type_hint_str ()))
242 : 0 : : "")
243 : 0 : << " ";
244 : 0 : out << Linemap::location_to_string (loc) << '\n';
245 : : }
246 : :
247 : 0 : token_queue.skip (0);
248 : : }
249 : 0 : }
250 : :
251 : : void
252 : 0 : Lexer::replace_current_token (TokenPtr replacement)
253 : : {
254 : 0 : token_queue.replace_current_value (replacement);
255 : :
256 : 0 : rust_debug ("called 'replace_current_token' - this is deprecated");
257 : 0 : }
258 : :
259 : : /* Determines whether the string passed in is a keyword or not. If it is, it
260 : : * returns the keyword name. */
261 : : TokenId
262 : 224937 : Lexer::classify_keyword (const std::string &str)
263 : : {
264 : 224937 : auto &keywords = Rust::Values::Keywords::keywords_tokens;
265 : 224937 : auto keyword = keywords.find (str);
266 : :
267 : 224937 : if (keyword == keywords.end ())
268 : : return IDENTIFIER;
269 : :
270 : 80857 : auto id = keyword->second;
271 : :
272 : : // We now have the expected token ID of the reserved keyword. However, some
273 : : // keywords are reserved starting in certain editions. For example, `try` is
274 : : // only a reserved keyword in editions >=2018. The language might gain new
275 : : // reserved keywords in the future.
276 : : //
277 : : // https://doc.rust-lang.org/reference/keywords.html#reserved-keywords
278 : :
279 : : // `try` is not a reserved keyword before 2018
280 : 80857 : if (get_rust_edition () == Edition::E2015 && id == TRY)
281 : : return IDENTIFIER;
282 : :
283 : : return id;
284 : : }
285 : :
286 : : TokenPtr
287 : 549313 : Lexer::build_token ()
288 : : {
289 : : // loop to go through multiple characters to build a single token
290 : 1323649 : while (true)
291 : : {
292 : 1323649 : location_t loc = get_current_location ();
293 : :
294 : 1323649 : current_char = peek_input ();
295 : 1323649 : skip_input ();
296 : :
297 : : // detect shebang
298 : : // Must be the first thing on the first line, starting with #!
299 : : // But since an attribute can also start with an #! we don't count it as a
300 : : // shebang line when after any whitespace or comments there is a [. If it
301 : : // is a shebang line we simple drop the line. Otherwise we don't consume
302 : : // any characters and fall through to the real tokenizer.
303 : 36695 : if (current_line == 1 && current_column == 1 && current_char == '#'
304 : 1360344 : && peek_input () == '!')
305 : : {
306 : : int n = 1;
307 : 594 : while (true)
308 : : {
309 : 594 : Codepoint next_char = peek_input (n);
310 : 594 : if (is_whitespace (next_char.value))
311 : 104 : n++;
312 : 490 : else if ((next_char == '/' && peek_input (n + 1) == '/'
313 : 24 : && peek_input (n + 2) != '!'
314 : 24 : && peek_input (n + 2) != '/')
315 : 530 : || (next_char == '/' && peek_input (n + 1) == '/'
316 : 0 : && peek_input (n + 2) == '/'
317 : 0 : && peek_input (n + 3) == '/'))
318 : : {
319 : : // two // or four ////
320 : : // A single line comment
321 : : // (but not an inner or outer doc comment)
322 : 24 : n += 2;
323 : 24 : next_char = peek_input (n);
324 : 424 : while (next_char != '\n' && !next_char.is_eof ())
325 : : {
326 : 400 : n++;
327 : 400 : next_char = peek_input (n);
328 : : }
329 : 24 : if (next_char == '\n')
330 : 24 : n++;
331 : : }
332 : 466 : else if (next_char == '/' && peek_input (n + 1) == '*'
333 : 16 : && peek_input (n + 2) == '*'
334 : 466 : && peek_input (n + 3) == '/')
335 : : {
336 : : /**/
337 : 0 : n += 4;
338 : : }
339 : 466 : else if (next_char == '/' && peek_input (n + 1) == '*'
340 : 16 : && peek_input (n + 2) == '*' && peek_input (n + 3) == '*'
341 : 466 : && peek_input (n + 4) == '/')
342 : : {
343 : : /***/
344 : 0 : n += 5;
345 : : }
346 : 466 : else if ((next_char == '/' && peek_input (n + 1) == '*'
347 : 16 : && peek_input (n + 2) != '*'
348 : 16 : && peek_input (n + 2) != '!')
349 : 490 : || (next_char == '/' && peek_input (n + 1) == '*'
350 : 0 : && peek_input (n + 2) == '*'
351 : 0 : && peek_input (n + 3) == '*'))
352 : : {
353 : : // one /* or three /***
354 : : // Start of a block comment
355 : : // (but not an inner or outer doc comment)
356 : 16 : n += 2;
357 : 16 : int level = 1;
358 : 1176 : while (level > 0)
359 : : {
360 : 1160 : if (peek_input (n).is_eof ())
361 : : break;
362 : 1160 : else if (peek_input (n) == '/'
363 : 1160 : && peek_input (n + 1) == '*')
364 : : {
365 : 8 : n += 2;
366 : 8 : level += 1;
367 : : }
368 : 1152 : else if (peek_input (n) == '*'
369 : 1152 : && peek_input (n + 1) == '/')
370 : : {
371 : 24 : n += 2;
372 : 24 : level -= 1;
373 : : }
374 : : else
375 : 1128 : n++;
376 : : }
377 : : }
378 : 450 : else if (next_char != '[')
379 : : {
380 : : // definitely shebang, ignore the first line
381 : 608 : while (current_char != '\n' && !current_char.is_eof ())
382 : : {
383 : 576 : current_char = peek_input ();
384 : 576 : skip_input ();
385 : : }
386 : :
387 : : // newline
388 : 32 : current_line++;
389 : 32 : current_column = 1;
390 : : // tell line_table that new line starts
391 : 32 : start_line (current_line, max_column_hint);
392 : 32 : break;
393 : : }
394 : : else
395 : : break; /* Definitely not a shebang line. */
396 : : }
397 : : }
398 : :
399 : : // return end of file token if end of file
400 : 1323649 : if (current_char.is_eof ())
401 : 5659 : return Token::make (END_OF_FILE, loc);
402 : :
403 : : // if not end of file, start tokenising
404 : 1317990 : switch (current_char.value)
405 : : {
406 : : /* ignore whitespace characters for tokens but continue updating
407 : : * location */
408 : 125656 : case '\n': // newline
409 : 125656 : case 0x0085: // next line
410 : 125656 : case 0x2028: // line separator
411 : 125656 : case 0x2029: // paragraph separator
412 : 125656 : current_line++;
413 : 125656 : current_column = 1;
414 : : // tell line_table that new line starts
415 : 125656 : start_line (current_line, max_column_hint);
416 : 125656 : continue;
417 : 288 : case '\r': // cr
418 : : // Ignore, we expect a newline (lf) soon.
419 : 288 : continue;
420 : 639450 : case ' ': // space
421 : 639450 : current_column++;
422 : 639450 : continue;
423 : 134 : case '\t': // horizontal tab
424 : : // width of a tab is not well-defined, assume 8 spaces
425 : 134 : current_column += 8;
426 : 134 : continue;
427 : 32 : case '\v': // vertical tab
428 : 32 : case 0x000c: // form feed
429 : 32 : case 0x200e: // left-to-right mark
430 : 32 : case 0x200f: // right-to-left mark
431 : : // Ignored.
432 : 32 : continue;
433 : :
434 : : // punctuation - actual tokens
435 : 22331 : case '=':
436 : 22331 : if (peek_input () == '>')
437 : : {
438 : : // match arm arrow
439 : 2257 : skip_input ();
440 : 2257 : current_column += 2;
441 : 2257 : loc += 1;
442 : :
443 : 2257 : return Token::make (MATCH_ARROW, loc);
444 : : }
445 : 20074 : else if (peek_input () == '=')
446 : : {
447 : : // equality operator
448 : 419 : skip_input ();
449 : 419 : current_column += 2;
450 : 419 : loc += 1;
451 : :
452 : 419 : return Token::make (EQUAL_EQUAL, loc);
453 : : }
454 : : else
455 : : {
456 : : // assignment operator
457 : 19655 : current_column++;
458 : 19655 : return Token::make (EQUAL, loc);
459 : : }
460 : 33757 : case '(':
461 : 33757 : current_column++;
462 : 33757 : return Token::make (LEFT_PAREN, loc);
463 : 9414 : case '-':
464 : 9414 : if (peek_input () == '>')
465 : : {
466 : : // return type specifier
467 : 8373 : skip_input ();
468 : 8373 : current_column += 2;
469 : 8373 : loc += 1;
470 : :
471 : 8373 : return Token::make (RETURN_TYPE, loc);
472 : : }
473 : 1041 : else if (peek_input () == '=')
474 : : {
475 : : // minus-assign
476 : 44 : skip_input ();
477 : 44 : current_column += 2;
478 : 44 : loc += 1;
479 : :
480 : 44 : return Token::make (MINUS_EQ, loc);
481 : : }
482 : : else
483 : : {
484 : : // minus
485 : 997 : current_column++;
486 : 997 : return Token::make (MINUS, loc);
487 : : }
488 : 1725 : case '+':
489 : 1725 : if (peek_input () == '=')
490 : : {
491 : : // add-assign
492 : 119 : skip_input ();
493 : 119 : current_column += 2;
494 : 119 : loc += 1;
495 : :
496 : 119 : return Token::make (PLUS_EQ, loc);
497 : : }
498 : : else
499 : : {
500 : : // add
501 : 1606 : current_column++;
502 : 1606 : return Token::make (PLUS, loc);
503 : : }
504 : 33731 : case ')':
505 : 33731 : current_column++;
506 : 33731 : return Token::make (RIGHT_PAREN, loc);
507 : 29473 : case ';':
508 : 29473 : current_column++;
509 : 29473 : return Token::make (SEMICOLON, loc);
510 : 8275 : case '*':
511 : 8275 : if (peek_input () == '=')
512 : : {
513 : : // multiplication-assign
514 : 8 : skip_input ();
515 : 8 : current_column += 2;
516 : 8 : loc += 1;
517 : :
518 : 8 : return Token::make (ASTERISK_EQ, loc);
519 : : }
520 : : else
521 : : {
522 : : // multiplication
523 : 8267 : current_column++;
524 : 8267 : return Token::make (ASTERISK, loc);
525 : : }
526 : 17201 : case ',':
527 : 17201 : current_column++;
528 : 17201 : return Token::make (COMMA, loc);
529 : 9770 : case '/':
530 : 9770 : if (peek_input () == '=')
531 : : {
532 : : // division-assign
533 : 8 : skip_input ();
534 : 8 : current_column += 2;
535 : 8 : loc += 1;
536 : :
537 : 8 : return Token::make (DIV_EQ, loc);
538 : : }
539 : 9762 : else if ((peek_input () == '/' && peek_input (1) != '!'
540 : 8827 : && peek_input (1) != '/')
541 : 10587 : || (peek_input () == '/' && peek_input (1) == '/'
542 : 749 : && peek_input (2) == '/'))
543 : : {
544 : : // two // or four ////
545 : : // single line comment
546 : : // (but not an inner or outer doc comment)
547 : 8096 : skip_input ();
548 : 8096 : current_column += 2;
549 : 8096 : current_char = peek_input ();
550 : :
551 : : // basically ignore until line finishes
552 : 426460 : while (current_char != '\n' && !current_char.is_eof ())
553 : : {
554 : 410268 : skip_input ();
555 : 410268 : current_column++; // not used
556 : 410268 : current_char = peek_input ();
557 : : }
558 : 8096 : continue;
559 : : }
560 : 1666 : else if (peek_input () == '/'
561 : 1666 : && (peek_input (1) == '!' || peek_input (1) == '/'))
562 : : {
563 : : /* single line doc comment, inner or outer. */
564 : 807 : bool is_inner = peek_input (1) == '!';
565 : 807 : skip_input (1);
566 : 807 : current_column += 3;
567 : :
568 : 807 : std::string str;
569 : 807 : str.reserve (32);
570 : 807 : current_char = peek_input ();
571 : 29927 : while (current_char != '\n')
572 : : {
573 : 28369 : skip_input ();
574 : 28369 : if (current_char == '\r')
575 : : {
576 : 60 : Codepoint next_char = peek_input ();
577 : 60 : if (next_char == '\n')
578 : : {
579 : 56 : current_char = '\n';
580 : 56 : break;
581 : : }
582 : 4 : rust_error_at (
583 : : loc, "Isolated CR %<\\r%> not allowed in doc comment");
584 : 4 : current_char = next_char;
585 : 4 : continue;
586 : 4 : }
587 : 28309 : if (current_char.is_eof ())
588 : : {
589 : 0 : rust_error_at (
590 : : loc, ErrorCode::E0758,
591 : : "unexpected EOF while looking for end of comment");
592 : 0 : break;
593 : : }
594 : 28309 : str += current_char;
595 : 28309 : current_char = peek_input ();
596 : : }
597 : 807 : skip_input ();
598 : 807 : current_line++;
599 : 807 : current_column = 1;
600 : : // tell line_table that new line starts
601 : 807 : start_line (current_line, max_column_hint);
602 : :
603 : 807 : str.shrink_to_fit ();
604 : :
605 : 807 : loc += str.size () - 1;
606 : 807 : if (is_inner)
607 : 76 : return Token::make_inner_doc_comment (loc, std::move (str));
608 : : else
609 : 731 : return Token::make_outer_doc_comment (loc, std::move (str));
610 : 807 : }
611 : 859 : else if (peek_input () == '*' && peek_input (1) == '*'
612 : 957 : && peek_input (2) == '/')
613 : : {
614 : : /**/
615 : 16 : skip_input (2);
616 : 16 : current_column += 4;
617 : 16 : continue;
618 : : }
619 : 843 : else if (peek_input () == '*' && peek_input (1) == '*'
620 : 925 : && peek_input (2) == '*' && peek_input (3) == '/')
621 : : {
622 : : /***/
623 : 16 : skip_input (3);
624 : 16 : current_column += 5;
625 : 16 : continue;
626 : : }
627 : 827 : else if ((peek_input () == '*' && peek_input (1) != '!'
628 : 696 : && peek_input (1) != '*')
629 : 979 : || (peek_input () == '*' && peek_input (1) == '*'
630 : 66 : && peek_input (2) == '*'))
631 : : {
632 : : // one /* or three /***
633 : : // block comment
634 : : // (but not an inner or outer doc comment)
635 : 646 : skip_input ();
636 : 646 : current_column += 2;
637 : :
638 : 646 : int level = 1;
639 : 29807 : while (level > 0)
640 : : {
641 : 29163 : current_char = peek_input ();
642 : :
643 : 29163 : if (current_char.is_eof ())
644 : : {
645 : 2 : rust_error_at (
646 : : loc, ErrorCode::E0758,
647 : : "unexpected EOF while looking for end of comment");
648 : 2 : break;
649 : : }
650 : :
651 : : // if /* found
652 : 29161 : if (current_char == '/' && peek_input (1) == '*')
653 : : {
654 : : // skip /* characters
655 : 56 : skip_input (1);
656 : :
657 : 56 : current_column += 2;
658 : :
659 : 56 : level += 1;
660 : 56 : continue;
661 : : }
662 : :
663 : : // ignore until */ is found
664 : 29105 : if (current_char == '*' && peek_input (1) == '/')
665 : : {
666 : : // skip */ characters
667 : 700 : skip_input (1);
668 : :
669 : 700 : current_column += 2;
670 : :
671 : 700 : level -= 1;
672 : 700 : continue;
673 : : }
674 : :
675 : 28405 : if (current_char == '\n')
676 : : {
677 : 423 : skip_input ();
678 : 423 : current_line++;
679 : 423 : current_column = 1;
680 : : // tell line_table that new line starts
681 : 423 : start_line (current_line, max_column_hint);
682 : 423 : continue;
683 : : }
684 : :
685 : 27982 : skip_input ();
686 : 27982 : current_column++;
687 : : }
688 : :
689 : : // refresh new token
690 : 646 : continue;
691 : 646 : }
692 : 181 : else if (peek_input () == '*'
693 : 181 : && (peek_input (1) == '!' || peek_input (1) == '*'))
694 : : {
695 : : // block doc comment, inner /*! or outer /**
696 : 136 : bool is_inner = peek_input (1) == '!';
697 : 136 : skip_input (1);
698 : 136 : current_column += 3;
699 : :
700 : 136 : std::string str;
701 : 136 : str.reserve (96);
702 : :
703 : 136 : int level = 1;
704 : 136 : while (level > 0)
705 : : {
706 : 3138 : current_char = peek_input ();
707 : :
708 : 3138 : if (current_char.is_eof ())
709 : : {
710 : 0 : rust_error_at (
711 : : loc, ErrorCode::E0758,
712 : : "unexpected EOF while looking for end of comment");
713 : 0 : break;
714 : : }
715 : :
716 : : // if /* found
717 : 3138 : if (current_char == '/' && peek_input (1) == '*')
718 : : {
719 : : // skip /* characters
720 : 96 : skip_input (1);
721 : 96 : current_column += 2;
722 : :
723 : 96 : level += 1;
724 : 96 : str += "/*";
725 : 96 : continue;
726 : : }
727 : :
728 : : // ignore until */ is found
729 : 3042 : if (current_char == '*' && peek_input (1) == '/')
730 : : {
731 : : // skip */ characters
732 : 232 : skip_input (1);
733 : 232 : current_column += 2;
734 : :
735 : 232 : level -= 1;
736 : 232 : if (level > 0)
737 : 96 : str += "*/";
738 : 232 : continue;
739 : : }
740 : :
741 : 2810 : if (current_char == '\r' && peek_input (1) != '\n')
742 : 4 : rust_error_at (
743 : : loc, "Isolated CR %<\\r%> not allowed in doc comment");
744 : :
745 : 2810 : if (current_char == '\n')
746 : : {
747 : 0 : skip_input ();
748 : 0 : current_line++;
749 : 0 : current_column = 1;
750 : : // tell line_table that new line starts
751 : 0 : start_line (current_line, max_column_hint);
752 : 0 : str += '\n';
753 : 0 : continue;
754 : : }
755 : :
756 : 2810 : str += current_char;
757 : 2810 : skip_input ();
758 : 2810 : current_column++;
759 : : }
760 : :
761 : 136 : str.shrink_to_fit ();
762 : :
763 : 136 : loc += str.size () - 1;
764 : 136 : if (is_inner)
765 : 86 : return Token::make_inner_doc_comment (loc, std::move (str));
766 : : else
767 : 50 : return Token::make_outer_doc_comment (loc, std::move (str));
768 : 136 : }
769 : : else
770 : : {
771 : : // division
772 : 45 : current_column++;
773 : 45 : return Token::make (DIV, loc);
774 : : }
775 : 49 : case '%':
776 : 49 : if (peek_input () == '=')
777 : : {
778 : : // modulo-assign
779 : 8 : skip_input ();
780 : 8 : current_column += 2;
781 : 8 : loc += 1;
782 : :
783 : 8 : return Token::make (PERCENT_EQ, loc);
784 : : }
785 : : else
786 : : {
787 : : // modulo
788 : 41 : current_column++;
789 : 41 : return Token::make (PERCENT, loc);
790 : : }
791 : 24 : case '^':
792 : 24 : if (peek_input () == '=')
793 : : {
794 : : // xor-assign?
795 : 8 : skip_input ();
796 : 8 : current_column += 2;
797 : 8 : loc += 1;
798 : :
799 : 8 : return Token::make (CARET_EQ, loc);
800 : : }
801 : : else
802 : : {
803 : : // xor?
804 : 16 : current_column++;
805 : 16 : return Token::make (CARET, loc);
806 : : }
807 : 7486 : case '<':
808 : 7486 : if (peek_input () == '<')
809 : : {
810 : 56 : if (peek_input (1) == '=')
811 : : {
812 : : // left-shift assign
813 : 8 : skip_input (1);
814 : 8 : current_column += 3;
815 : 8 : loc += 2;
816 : :
817 : 8 : return Token::make (LEFT_SHIFT_EQ, loc);
818 : : }
819 : : else
820 : : {
821 : : // left-shift
822 : 48 : skip_input ();
823 : 48 : current_column += 2;
824 : 48 : loc += 1;
825 : :
826 : 48 : return Token::make (LEFT_SHIFT, loc);
827 : : }
828 : : }
829 : 7430 : else if (peek_input () == '=')
830 : : {
831 : : // smaller than or equal to
832 : 87 : skip_input ();
833 : 87 : current_column += 2;
834 : 87 : loc += 1;
835 : :
836 : 87 : return Token::make (LESS_OR_EQUAL, loc);
837 : : }
838 : : else
839 : : {
840 : : // smaller than
841 : 7343 : current_column++;
842 : 7343 : return Token::make (LEFT_ANGLE, loc);
843 : : }
844 : 7370 : break;
845 : 7370 : case '>':
846 : 7370 : if (peek_input () == '>')
847 : : {
848 : 160 : if (peek_input (1) == '=')
849 : : {
850 : : // right-shift-assign
851 : 8 : skip_input (1);
852 : 8 : current_column += 3;
853 : 8 : loc += 2;
854 : :
855 : 8 : return Token::make (RIGHT_SHIFT_EQ, loc);
856 : : }
857 : : else
858 : : {
859 : : // right-shift
860 : 152 : skip_input ();
861 : 152 : current_column += 2;
862 : 152 : loc += 1;
863 : :
864 : 152 : return Token::make (RIGHT_SHIFT, loc);
865 : : }
866 : : }
867 : 7210 : else if (peek_input () == '=')
868 : : {
869 : : // larger than or equal to
870 : 47 : skip_input ();
871 : 47 : current_column += 2;
872 : 47 : loc += 1;
873 : :
874 : 47 : return Token::make (GREATER_OR_EQUAL, loc);
875 : : }
876 : : else
877 : : {
878 : : // larger than
879 : 7163 : current_column++;
880 : 7163 : return Token::make (RIGHT_ANGLE, loc);
881 : : }
882 : 23528 : case ':':
883 : 23528 : if (peek_input () == ':')
884 : : {
885 : : // scope resolution ::
886 : 6950 : skip_input ();
887 : 6950 : current_column += 2;
888 : 6950 : loc += 1;
889 : :
890 : 6950 : return Token::make (SCOPE_RESOLUTION, loc);
891 : : }
892 : : else
893 : : {
894 : : // single colon :
895 : 16578 : current_column++;
896 : 16578 : return Token::make (COLON, loc);
897 : : }
898 : 3914 : case '!':
899 : : // no special handling for macros in lexer?
900 : 3914 : if (peek_input () == '=')
901 : : {
902 : : // not equal boolean operator
903 : 154 : skip_input ();
904 : 154 : current_column += 2;
905 : 154 : loc += 1;
906 : :
907 : 154 : return Token::make (NOT_EQUAL, loc);
908 : : }
909 : : else
910 : : {
911 : : // not equal unary operator
912 : 3760 : current_column++;
913 : :
914 : 3760 : return Token::make (EXCLAM, loc);
915 : : }
916 : 128 : case '?':
917 : 128 : current_column++;
918 : 128 : return Token::make (QUESTION_MARK, loc);
919 : 5235 : case '#':
920 : 5235 : current_column++;
921 : 5235 : return Token::make (HASH, loc);
922 : 7094 : case '[':
923 : 7094 : current_column++;
924 : 7094 : return Token::make (LEFT_SQUARE, loc);
925 : 7086 : case ']':
926 : 7086 : current_column++;
927 : 7086 : return Token::make (RIGHT_SQUARE, loc);
928 : 30000 : case '{':
929 : 30000 : current_column++;
930 : 30000 : return Token::make (LEFT_CURLY, loc);
931 : 29932 : case '}':
932 : 29932 : current_column++;
933 : 29932 : return Token::make (RIGHT_CURLY, loc);
934 : 0 : case '@':
935 : 0 : current_column++;
936 : 0 : return Token::make (PATTERN_BIND, loc);
937 : 3736 : case '$':
938 : 3736 : current_column++;
939 : 3736 : return Token::make (DOLLAR_SIGN, loc);
940 : 0 : case '~':
941 : 0 : current_column++;
942 : 0 : return Token::make (TILDE, loc);
943 : 0 : case '\\':
944 : 0 : current_column++;
945 : 0 : return Token::make (BACKSLASH, loc);
946 : 0 : case '`':
947 : 0 : current_column++;
948 : 0 : return Token::make (BACKTICK, loc);
949 : 341 : case '|':
950 : 341 : if (peek_input () == '=')
951 : : {
952 : : // bitwise or-assign?
953 : 8 : skip_input ();
954 : 8 : current_column += 2;
955 : 8 : loc += 1;
956 : :
957 : 8 : return Token::make (PIPE_EQ, loc);
958 : : }
959 : 333 : else if (peek_input () == '|')
960 : : {
961 : : // logical or
962 : 77 : skip_input ();
963 : 77 : current_column += 2;
964 : 77 : loc += 1;
965 : :
966 : 77 : return Token::make (OR, loc);
967 : : }
968 : : else
969 : : {
970 : : // bitwise or
971 : 256 : current_column++;
972 : :
973 : 256 : return Token::make (PIPE, loc);
974 : : }
975 : 6302 : case '&':
976 : 6302 : if (peek_input () == '=')
977 : : {
978 : : // bitwise and-assign?
979 : 22 : skip_input ();
980 : 22 : current_column += 2;
981 : 22 : loc += 1;
982 : :
983 : 22 : return Token::make (AMP_EQ, loc);
984 : : }
985 : 6280 : else if (peek_input () == '&')
986 : : {
987 : : // logical and
988 : 320 : skip_input ();
989 : 320 : current_column += 2;
990 : 320 : loc += 1;
991 : :
992 : 320 : return Token::make (LOGICAL_AND, loc);
993 : : }
994 : : else
995 : : {
996 : : // bitwise and/reference
997 : 5960 : current_column++;
998 : :
999 : 5960 : return Token::make (AMP, loc);
1000 : : }
1001 : 5277 : case '.':
1002 : 5277 : if (peek_input () == '.')
1003 : : {
1004 : 1059 : if (peek_input (1) == '.')
1005 : : {
1006 : : // ellipsis
1007 : 844 : skip_input (1);
1008 : 844 : current_column += 3;
1009 : 844 : loc += 2;
1010 : :
1011 : 844 : return Token::make (ELLIPSIS, loc);
1012 : : }
1013 : 215 : else if (peek_input (1) == '=')
1014 : : {
1015 : : // ..=
1016 : 29 : skip_input (1);
1017 : 29 : current_column += 3;
1018 : 29 : loc += 2;
1019 : :
1020 : 29 : return Token::make (DOT_DOT_EQ, loc);
1021 : : }
1022 : : else
1023 : : {
1024 : : // ..
1025 : 186 : skip_input ();
1026 : 186 : current_column += 2;
1027 : 186 : loc += 1;
1028 : :
1029 : 186 : return Token::make (DOT_DOT, loc);
1030 : : }
1031 : : }
1032 : : else /*if (!ISDIGIT (peek_input ()))*/
1033 : : {
1034 : : // single dot .
1035 : : // Only if followed by a non-number - otherwise is float
1036 : : // nope, float cannot start with '.'.
1037 : 4218 : current_column++;
1038 : 4218 : return Token::make (DOT, loc);
1039 : : }
1040 : 765560 : }
1041 : : // TODO: special handling of _ in the lexer? instead of being identifier
1042 : :
1043 : : // byte character, byte string and raw byte string literals
1044 : 249251 : if (current_char == 'b')
1045 : : {
1046 : 9583 : if (peek_input () == '\'')
1047 : 87 : return parse_byte_char (loc);
1048 : 9496 : else if (peek_input () == '"')
1049 : 79 : return parse_byte_string (loc);
1050 : 9417 : else if (peek_input () == 'r'
1051 : 9417 : && (peek_input (1) == '#' || peek_input (1) == '"'))
1052 : 50 : return parse_raw_byte_string (loc);
1053 : : }
1054 : :
1055 : : // raw identifiers and raw strings
1056 : 249035 : if (current_char == 'r')
1057 : : {
1058 : 3989 : Codepoint peek = peek_input ();
1059 : 3989 : Codepoint peek1 = peek_input (1);
1060 : :
1061 : : // TODO (tamaron) parse Unicode ident
1062 : 3989 : if (peek == '#' && is_identifier_start (peek1.value))
1063 : : {
1064 : 96 : TokenPtr raw_ident_ptr = parse_raw_identifier (loc);
1065 : 96 : if (raw_ident_ptr != nullptr)
1066 : 94 : return raw_ident_ptr;
1067 : : else
1068 : 2 : continue; /* input got parsed, it just wasn't valid. An error
1069 : : was produced. */
1070 : 96 : }
1071 : : else
1072 : : {
1073 : 3893 : TokenPtr maybe_raw_string_ptr = maybe_parse_raw_string (loc);
1074 : 3893 : if (maybe_raw_string_ptr != nullptr)
1075 : 50 : return maybe_raw_string_ptr;
1076 : 3893 : }
1077 : : }
1078 : :
1079 : : // find identifiers and keywords.
1080 : 248889 : if (is_identifier_start (current_char.value))
1081 : 225914 : return parse_identifier_or_keyword (loc);
1082 : :
1083 : : // int and float literals
1084 : 22975 : if (ISDIGIT (current_char.value))
1085 : : { // _ not allowed as first char
1086 : 14777 : if (current_char == '0'
1087 : 14777 : && is_non_decimal_int_literal_separator (peek_input ().value))
1088 : : {
1089 : : // handle binary, octal, hex literals
1090 : 158 : TokenPtr non_dec_int_lit_ptr
1091 : 158 : = parse_non_decimal_int_literals (loc);
1092 : 158 : if (non_dec_int_lit_ptr != nullptr)
1093 : 158 : return non_dec_int_lit_ptr;
1094 : 158 : }
1095 : : else
1096 : : {
1097 : : // handle decimals (integer or float)
1098 : 14619 : TokenPtr decimal_or_float_ptr = parse_decimal_int_or_float (loc);
1099 : 14619 : if (decimal_or_float_ptr != nullptr)
1100 : 14619 : return decimal_or_float_ptr;
1101 : 14619 : }
1102 : : }
1103 : :
1104 : : // string literals
1105 : 8198 : if (current_char == '"')
1106 : 7216 : return parse_string (loc);
1107 : :
1108 : : // char literals and lifetime names
1109 : 982 : if (current_char == '\'')
1110 : : {
1111 : 982 : TokenPtr char_or_lifetime_ptr = parse_char_or_lifetime (loc);
1112 : 982 : if (char_or_lifetime_ptr != nullptr)
1113 : 982 : return char_or_lifetime_ptr;
1114 : 982 : }
1115 : :
1116 : : // DEBUG: check for specific character problems:
1117 : 0 : if (current_char == '0')
1118 : 0 : rust_debug ("'0' uncaught before unexpected character");
1119 : 0 : else if (current_char == ']')
1120 : 0 : rust_debug ("']' uncaught before unexpected character");
1121 : : else if (current_char == 0x5d)
1122 : : rust_debug ("whatever 0x5d is (not '0' or ']') uncaught before "
1123 : : "unexpected character");
1124 : :
1125 : : // didn't match anything so error
1126 : 0 : rust_error_at (loc, "unexpected character %<%x%>", current_char.value);
1127 : 0 : current_column++;
1128 : : }
1129 : : }
1130 : :
1131 : : // Parses in a type suffix.
1132 : : std::pair<PrimitiveCoreType, int>
1133 : 14767 : Lexer::parse_in_type_suffix ()
1134 : : {
1135 : 14767 : std::string suffix;
1136 : 14767 : suffix.reserve (5);
1137 : :
1138 : 14767 : int additional_length_offset = 0;
1139 : :
1140 : : // get suffix
1141 : 31917 : while (ISALPHA (current_char.value) || ISDIGIT (current_char.value)
1142 : 33384 : || current_char == '_')
1143 : : {
1144 : 3850 : if (current_char == '_')
1145 : : {
1146 : : // don't add _ to suffix
1147 : 0 : skip_input ();
1148 : 0 : current_char = peek_input ();
1149 : :
1150 : 0 : additional_length_offset++;
1151 : :
1152 : 0 : continue;
1153 : : }
1154 : :
1155 : 3850 : additional_length_offset++;
1156 : :
1157 : 3850 : suffix += current_char;
1158 : 3850 : skip_input ();
1159 : 3850 : current_char = peek_input ();
1160 : : }
1161 : :
1162 : 14767 : if (suffix.empty ())
1163 : : {
1164 : : // no type suffix: do nothing but also no error
1165 : 13508 : return std::make_pair (CORETYPE_UNKNOWN, additional_length_offset);
1166 : : }
1167 : 1259 : else if (suffix == "f32")
1168 : : {
1169 : 583 : return std::make_pair (CORETYPE_F32, additional_length_offset);
1170 : : }
1171 : 676 : else if (suffix == "f64")
1172 : : {
1173 : 256 : return std::make_pair (CORETYPE_F64, additional_length_offset);
1174 : : }
1175 : 420 : else if (suffix == "i8")
1176 : : {
1177 : 27 : return std::make_pair (CORETYPE_I8, additional_length_offset);
1178 : : }
1179 : 393 : else if (suffix == "i16")
1180 : : {
1181 : 18 : return std::make_pair (CORETYPE_I16, additional_length_offset);
1182 : : }
1183 : 375 : else if (suffix == "i32")
1184 : : {
1185 : 98 : return std::make_pair (CORETYPE_I32, additional_length_offset);
1186 : : }
1187 : 277 : else if (suffix == "i64")
1188 : : {
1189 : 18 : return std::make_pair (CORETYPE_I64, additional_length_offset);
1190 : : }
1191 : 259 : else if (suffix == "i128")
1192 : : {
1193 : 18 : return std::make_pair (CORETYPE_I128, additional_length_offset);
1194 : : }
1195 : 241 : else if (suffix == "isize")
1196 : : {
1197 : 6 : return std::make_pair (CORETYPE_ISIZE, additional_length_offset);
1198 : : }
1199 : 235 : else if (suffix == "u8")
1200 : : {
1201 : 40 : return std::make_pair (CORETYPE_U8, additional_length_offset);
1202 : : }
1203 : 195 : else if (suffix == "u16")
1204 : : {
1205 : 32 : return std::make_pair (CORETYPE_U16, additional_length_offset);
1206 : : }
1207 : 163 : else if (suffix == "u32")
1208 : : {
1209 : 70 : return std::make_pair (CORETYPE_U32, additional_length_offset);
1210 : : }
1211 : 93 : else if (suffix == "u64")
1212 : : {
1213 : 29 : return std::make_pair (CORETYPE_U64, additional_length_offset);
1214 : : }
1215 : 64 : else if (suffix == "u128")
1216 : : {
1217 : 18 : return std::make_pair (CORETYPE_U128, additional_length_offset);
1218 : : }
1219 : 46 : else if (suffix == "usize")
1220 : : {
1221 : 46 : return std::make_pair (CORETYPE_USIZE, additional_length_offset);
1222 : : }
1223 : : else
1224 : : {
1225 : 0 : rust_error_at (get_current_location (), "unknown number suffix %qs",
1226 : : suffix.c_str ());
1227 : :
1228 : 0 : return std::make_pair (CORETYPE_UNKNOWN, additional_length_offset);
1229 : : }
1230 : 14767 : }
1231 : :
1232 : : // Parses in the exponent part (if any) of a float literal.
1233 : : std::pair<std::string, int>
1234 : 402 : Lexer::parse_in_exponent_part ()
1235 : : {
1236 : 402 : int additional_length_offset = 0;
1237 : 402 : std::string str;
1238 : 402 : if (current_char == 'E' || current_char == 'e')
1239 : : {
1240 : : // add exponent to string as strtod works with it
1241 : 14 : str += current_char;
1242 : 14 : skip_input ();
1243 : 14 : current_char = peek_input ();
1244 : :
1245 : 14 : additional_length_offset++;
1246 : :
1247 : : // special - and + handling
1248 : 14 : if (current_char == '-')
1249 : : {
1250 : 0 : str += '-';
1251 : :
1252 : 0 : skip_input ();
1253 : 0 : current_char = peek_input ();
1254 : :
1255 : 0 : additional_length_offset++;
1256 : : }
1257 : 14 : else if (current_char == '+')
1258 : : {
1259 : : // don't add + but still skip input
1260 : 14 : skip_input ();
1261 : 14 : current_char = peek_input ();
1262 : :
1263 : 14 : additional_length_offset++;
1264 : : }
1265 : :
1266 : : // parse another decimal number for exponent
1267 : 14 : auto str_length = parse_in_decimal ();
1268 : 14 : str += std::get<0> (str_length);
1269 : 14 : additional_length_offset += std::get<1> (str_length);
1270 : 14 : }
1271 : 804 : return std::make_pair (str, additional_length_offset);
1272 : 402 : }
1273 : :
1274 : : // Parses a decimal integer.
1275 : : std::tuple<std::string, int, bool>
1276 : 15035 : Lexer::parse_in_decimal ()
1277 : : {
1278 : : /* A pure decimal contains only digits. */
1279 : 15035 : bool pure_decimal = true;
1280 : 15035 : int additional_length_offset = 0;
1281 : 15035 : std::string str;
1282 : 22963 : while (ISDIGIT (current_char.value) || current_char.value == '_')
1283 : : {
1284 : 7928 : if (current_char == '_')
1285 : : {
1286 : 18 : pure_decimal = false;
1287 : : // don't add _ to number
1288 : 18 : skip_input ();
1289 : 18 : current_char = peek_input ();
1290 : :
1291 : 18 : additional_length_offset++;
1292 : :
1293 : 18 : continue;
1294 : : }
1295 : :
1296 : 7910 : additional_length_offset++;
1297 : :
1298 : 7910 : str += current_char;
1299 : 7910 : skip_input ();
1300 : 7910 : current_char = peek_input ();
1301 : : }
1302 : 30070 : return std::make_tuple (str, additional_length_offset, pure_decimal);
1303 : 15035 : }
1304 : :
1305 : : /* Parses escapes (and string continues) in "byte" strings and characters. Does
1306 : : * not support unicode. */
1307 : : std::tuple<char, int, bool>
1308 : 78 : Lexer::parse_escape (char opening_char)
1309 : : {
1310 : 78 : int additional_length_offset = 0;
1311 : 78 : char output_char = 0;
1312 : :
1313 : : // skip to actual letter
1314 : 78 : skip_input ();
1315 : 78 : current_char = peek_input ();
1316 : 78 : additional_length_offset++;
1317 : :
1318 : 78 : switch (current_char.value)
1319 : : {
1320 : 27 : case 'x': {
1321 : 27 : auto hex_escape_pair = parse_partial_hex_escape ();
1322 : 27 : long hexLong = hex_escape_pair.first;
1323 : 27 : additional_length_offset += hex_escape_pair.second;
1324 : :
1325 : 27 : if (hexLong > 255 || hexLong < 0)
1326 : 0 : rust_error_at (
1327 : : get_current_location (),
1328 : : "byte \\x escape %<\\x%x%> out of range - allows up to %<\\xFF%>",
1329 : : static_cast<unsigned int> (hexLong));
1330 : : /* TODO: restore capital for escape output - gcc pretty-printer doesn't
1331 : : * support %X directly */
1332 : 27 : char hexChar = static_cast<char> (hexLong);
1333 : :
1334 : 27 : output_char = hexChar;
1335 : : }
1336 : 27 : break;
1337 : : case 'n':
1338 : : output_char = '\n';
1339 : : break;
1340 : 0 : case 'r':
1341 : 0 : output_char = '\r';
1342 : 0 : break;
1343 : 2 : case 't':
1344 : 2 : output_char = '\t';
1345 : 2 : break;
1346 : 9 : case '\\':
1347 : 9 : output_char = '\\';
1348 : 9 : break;
1349 : 9 : case '0':
1350 : 9 : output_char = '\0';
1351 : 9 : break;
1352 : 16 : case '\'':
1353 : 16 : output_char = '\'';
1354 : 16 : break;
1355 : 2 : case '"':
1356 : 2 : output_char = '"';
1357 : 2 : break;
1358 : 4 : case 'u':
1359 : 6 : rust_error_at (get_current_location (),
1360 : : "cannot have a unicode escape \\u in a byte %s",
1361 : : opening_char == '\'' ? "character" : "string");
1362 : : // Try to parse it anyway, just to skip it
1363 : 4 : parse_partial_unicode_escape ();
1364 : 4 : return std::make_tuple (output_char, additional_length_offset, false);
1365 : 0 : case '\r':
1366 : 0 : case '\n':
1367 : : // string continue
1368 : 0 : return std::make_tuple (0, parse_partial_string_continue (), true);
1369 : 2 : default:
1370 : 2 : rust_error_at (get_current_location (),
1371 : : "unknown escape sequence %<\\%s%>",
1372 : 2 : current_char.as_string ().c_str ());
1373 : : // returns false if no parsing could be done
1374 : : // return false;
1375 : 2 : return std::make_tuple (output_char, additional_length_offset, false);
1376 : 72 : break;
1377 : : }
1378 : : // all non-special cases (string continue) should skip their used char
1379 : 72 : skip_input ();
1380 : 72 : current_char = peek_input ();
1381 : 72 : additional_length_offset++;
1382 : :
1383 : : // returns true if parsing was successful
1384 : : // return true;
1385 : 72 : return std::make_tuple (output_char, additional_length_offset, false);
1386 : : }
1387 : :
1388 : : /* Parses an escape (or string continue) in a string or character. Supports
1389 : : * unicode escapes. */
1390 : : std::tuple<Codepoint, int, bool>
1391 : 2698 : Lexer::parse_utf8_escape ()
1392 : : {
1393 : 2698 : Codepoint output_char;
1394 : 2698 : int additional_length_offset = 0;
1395 : :
1396 : : // skip to actual letter
1397 : 2698 : skip_input ();
1398 : 2698 : current_char = peek_input ();
1399 : 2698 : additional_length_offset++;
1400 : :
1401 : 2698 : switch (current_char.value)
1402 : : {
1403 : 27 : case 'x': {
1404 : 27 : auto hex_escape_pair = parse_partial_hex_escape ();
1405 : 27 : long hexLong = hex_escape_pair.first;
1406 : 27 : additional_length_offset += hex_escape_pair.second;
1407 : :
1408 : 27 : if (hexLong > 127 || hexLong < 0)
1409 : 8 : rust_error_at (
1410 : : get_current_location (),
1411 : : "ascii \\x escape %<\\x%x%> out of range - allows up to %<\\x7F%>",
1412 : : static_cast<unsigned int> (hexLong));
1413 : : /* TODO: restore capital for escape output - gcc pretty-printer doesn't
1414 : : * support %X directly */
1415 : 27 : char hexChar = static_cast<char> (hexLong);
1416 : :
1417 : 27 : output_char = hexChar;
1418 : : }
1419 : 27 : break;
1420 : : case 'n':
1421 : : output_char = '\n';
1422 : : break;
1423 : 0 : case 'r':
1424 : 0 : output_char = '\r';
1425 : 0 : break;
1426 : 2 : case 't':
1427 : 2 : output_char = '\t';
1428 : 2 : break;
1429 : 2 : case '\\':
1430 : 2 : output_char = '\\';
1431 : 2 : break;
1432 : 1253 : case '0':
1433 : 1253 : output_char = '\0';
1434 : 1253 : break;
1435 : 2 : case '\'':
1436 : 2 : output_char = '\'';
1437 : 2 : break;
1438 : 2 : case '"':
1439 : 2 : output_char = '"';
1440 : 2 : break;
1441 : 85 : case 'u': {
1442 : 85 : auto unicode_escape_pair = parse_partial_unicode_escape ();
1443 : 85 : output_char = unicode_escape_pair.first;
1444 : 85 : additional_length_offset += unicode_escape_pair.second;
1445 : :
1446 : 85 : return std::make_tuple (output_char, additional_length_offset, false);
1447 : : }
1448 : 28 : break;
1449 : 28 : case '\r':
1450 : 28 : case '\n':
1451 : : // string continue
1452 : 28 : return std::make_tuple (0, parse_partial_string_continue (), true);
1453 : 2 : default:
1454 : 2 : rust_error_at (get_current_location (),
1455 : : "unknown escape sequence %<\\%s%>",
1456 : 2 : current_char.as_string ().c_str ());
1457 : : // returns false if no parsing could be done
1458 : : // return false;
1459 : 2 : return std::make_tuple (output_char, additional_length_offset, false);
1460 : 2583 : break;
1461 : : }
1462 : : /* all non-special cases (unicode, string continue) should skip their used
1463 : : * char */
1464 : 2583 : skip_input ();
1465 : 2583 : current_char = peek_input ();
1466 : 2583 : additional_length_offset++;
1467 : :
1468 : : // returns true if parsing was successful
1469 : : // return true;
1470 : 2583 : return std::make_tuple (output_char, additional_length_offset, false);
1471 : : }
1472 : :
1473 : : // Parses the body of a string continue that has been found in an escape.
1474 : : int
1475 : 28 : Lexer::parse_partial_string_continue ()
1476 : : {
1477 : 28 : int additional_length_offset = 1;
1478 : :
1479 : : // string continue
1480 : : // TODO use utf-8 codepoint to skip whitespaces
1481 : 364 : while (is_whitespace (current_char.value))
1482 : : {
1483 : 336 : if (current_char == '\n')
1484 : : {
1485 : 28 : current_line++;
1486 : 28 : current_column = 1;
1487 : : // tell line_table that new line starts
1488 : 28 : start_line (current_line, max_column_hint);
1489 : :
1490 : : // reset "length"
1491 : 28 : additional_length_offset = 1;
1492 : :
1493 : : // get next char
1494 : 28 : skip_input ();
1495 : 28 : current_char = peek_input ();
1496 : :
1497 : 28 : continue;
1498 : : }
1499 : :
1500 : 308 : skip_input ();
1501 : 308 : current_char = peek_input ();
1502 : 308 : additional_length_offset++;
1503 : : }
1504 : :
1505 : 28 : return additional_length_offset;
1506 : : }
1507 : :
1508 : : /* Parses the body of a '\x' escape. Note that it does not check that the number
1509 : : * is valid and smaller than 255. */
1510 : : std::pair<long, int>
1511 : 54 : Lexer::parse_partial_hex_escape ()
1512 : : {
1513 : : // hex char string (null-terminated)
1514 : 54 : char hexNum[3] = {0, 0, 0};
1515 : :
1516 : : // first hex char
1517 : 54 : current_char = peek_input (1);
1518 : 54 : int additional_length_offset = 1;
1519 : :
1520 : 54 : if (!is_x_digit (current_char.value))
1521 : : {
1522 : 8 : rust_error_at (get_current_location (),
1523 : : "invalid character %<\\x%s%> in \\x sequence",
1524 : 8 : current_char.as_string ().c_str ());
1525 : 8 : return std::make_pair (0, 0);
1526 : : }
1527 : 46 : hexNum[0] = current_char.value;
1528 : :
1529 : : // second hex char
1530 : 46 : skip_input ();
1531 : 46 : current_char = peek_input (1);
1532 : 46 : additional_length_offset++;
1533 : :
1534 : 46 : if (!is_x_digit (current_char.value))
1535 : : {
1536 : 4 : rust_error_at (get_current_location (),
1537 : 4 : "invalid character %<\\x%c%s%> in \\x sequence", hexNum[0],
1538 : 4 : current_char.as_string ().c_str ());
1539 : 4 : return std::make_pair (0, 1);
1540 : : }
1541 : 42 : skip_input ();
1542 : 42 : hexNum[1] = current_char.value;
1543 : :
1544 : 42 : long hexLong = std::strtol (hexNum, nullptr, 16);
1545 : :
1546 : 42 : return std::make_pair (hexLong, additional_length_offset);
1547 : : }
1548 : :
1549 : : // Parses the body of a unicode escape.
1550 : : std::pair<Codepoint, int>
1551 : 89 : Lexer::parse_partial_unicode_escape ()
1552 : : {
1553 : 89 : skip_input ();
1554 : 89 : current_char = peek_input ();
1555 : 89 : int additional_length_offset = 0;
1556 : :
1557 : 89 : if (current_char != '{')
1558 : : {
1559 : 4 : rust_error_at (get_current_location (),
1560 : : "unicode escape should start with %<{%>");
1561 : : /* Skip what should probaby have been between brackets. */
1562 : 20 : while (is_x_digit (current_char.value) || current_char == '_')
1563 : : {
1564 : 12 : skip_input ();
1565 : 12 : current_char = peek_input ();
1566 : 12 : additional_length_offset++;
1567 : : }
1568 : 4 : return std::make_pair (Codepoint (0), additional_length_offset);
1569 : : }
1570 : :
1571 : 85 : skip_input ();
1572 : 85 : current_char = peek_input ();
1573 : 85 : additional_length_offset++;
1574 : :
1575 : 85 : if (current_char == '_')
1576 : : {
1577 : 4 : rust_error_at (get_current_location (),
1578 : : "unicode escape cannot start with %<_%>");
1579 : 4 : skip_input ();
1580 : 4 : current_char = peek_input ();
1581 : 4 : additional_length_offset++;
1582 : : // fallthrough and try to parse the rest anyway
1583 : : }
1584 : :
1585 : : // parse unicode escape - 1-6 hex digits
1586 : 85 : std::string num_str;
1587 : 85 : num_str.reserve (6);
1588 : :
1589 : : // loop through to add entire hex number to string
1590 : 566 : while (is_x_digit (current_char.value) || current_char.value == '_')
1591 : : {
1592 : 396 : if (current_char == '_')
1593 : : {
1594 : : // don't add _ to number
1595 : 48 : skip_input ();
1596 : 48 : current_char = peek_input ();
1597 : :
1598 : 48 : additional_length_offset++;
1599 : :
1600 : 48 : continue;
1601 : : }
1602 : :
1603 : 348 : additional_length_offset++;
1604 : :
1605 : : // add raw hex numbers
1606 : 348 : num_str += current_char;
1607 : :
1608 : 348 : skip_input ();
1609 : 348 : current_char = peek_input ();
1610 : : }
1611 : :
1612 : 85 : if (current_char == '}')
1613 : : {
1614 : 81 : skip_input ();
1615 : 81 : current_char = peek_input ();
1616 : 81 : additional_length_offset++;
1617 : : }
1618 : : else
1619 : : {
1620 : : // actually an error, but allow propagation anyway Assume that
1621 : : // wrong bracketm whitespace or single/double quotes are wrong
1622 : : // termination, otherwise it is a wrong character, then skip to the actual
1623 : : // terminator.
1624 : : // TODO use utf-8 codepoint to skip whitespaces
1625 : 4 : if (current_char == '{' || is_whitespace (current_char.value)
1626 : 8 : || current_char == '\'' || current_char == '"')
1627 : : {
1628 : 0 : rust_error_at (get_current_location (),
1629 : : "expected terminating %<}%> in unicode escape");
1630 : 0 : return std::make_pair (Codepoint (0), additional_length_offset);
1631 : : }
1632 : : else
1633 : : {
1634 : 4 : rust_error_at (get_current_location (),
1635 : : "invalid character %qs in unicode escape",
1636 : 4 : current_char.as_string ().c_str ());
1637 : : // TODO use utf-8 codepoint to skip whitespaces
1638 : 16 : while (current_char != '}' && current_char != '{'
1639 : 12 : && !is_whitespace (current_char.value) && current_char != '\''
1640 : 28 : && current_char != '"')
1641 : : {
1642 : 12 : skip_input ();
1643 : 12 : current_char = peek_input ();
1644 : 12 : additional_length_offset++;
1645 : : }
1646 : : // Consume the actual closing bracket if found
1647 : 4 : if (current_char == '}')
1648 : : {
1649 : 4 : skip_input ();
1650 : 4 : current_char = peek_input ();
1651 : 4 : additional_length_offset++;
1652 : : }
1653 : 4 : return std::make_pair (Codepoint (0), additional_length_offset);
1654 : : }
1655 : : }
1656 : :
1657 : : // ensure 1-6 hex characters
1658 : 81 : if (num_str.length () > 6 || num_str.length () < 1)
1659 : : {
1660 : 8 : rust_error_at (get_current_location (),
1661 : : "unicode escape should be between 1 and 6 hex "
1662 : : "characters; it is %lu",
1663 : 8 : (unsigned long) num_str.length ());
1664 : : // return false;
1665 : 8 : return std::make_pair (Codepoint (0), additional_length_offset);
1666 : : }
1667 : :
1668 : 73 : unsigned long hex_num = std::strtoul (num_str.c_str (), nullptr, 16);
1669 : :
1670 : 73 : if (hex_num > 0xd7ff && hex_num < 0xe000)
1671 : : {
1672 : 8 : rust_error_at (
1673 : : get_current_location (),
1674 : : "unicode escape cannot be a surrogate value (D800 to DFFF)");
1675 : 8 : return std::make_pair (Codepoint (0), additional_length_offset);
1676 : : }
1677 : :
1678 : 65 : if (hex_num > 0x10ffff)
1679 : : {
1680 : 8 : rust_error_at (get_current_location (),
1681 : : "unicode escape cannot be larger than 10FFFF");
1682 : 8 : return std::make_pair (Codepoint (0), additional_length_offset);
1683 : : }
1684 : :
1685 : : // return true;
1686 : 57 : return std::make_pair (Codepoint (static_cast<uint32_t> (hex_num)),
1687 : : additional_length_offset);
1688 : 85 : }
1689 : :
1690 : : // Parses a byte character.
1691 : : TokenPtr
1692 : 87 : Lexer::parse_byte_char (location_t loc)
1693 : : {
1694 : 87 : skip_input ();
1695 : 87 : current_column++;
1696 : : // make current char the next character
1697 : 87 : current_char = peek_input ();
1698 : :
1699 : 87 : int length = 1;
1700 : :
1701 : : // char to save
1702 : 87 : Codepoint byte_char = 0;
1703 : :
1704 : : // detect escapes
1705 : 87 : if (current_char == '\\')
1706 : : {
1707 : 32 : auto escape_length_pair = parse_escape ('\'');
1708 : 32 : byte_char = std::get<0> (escape_length_pair);
1709 : 32 : length += std::get<1> (escape_length_pair);
1710 : :
1711 : 32 : current_char = peek_input ();
1712 : :
1713 : 32 : if (current_char != '\'')
1714 : : {
1715 : 0 : rust_error_at (get_current_location (), "unclosed %<byte char%>");
1716 : : }
1717 : :
1718 : 32 : skip_input ();
1719 : 32 : current_char = peek_input ();
1720 : 32 : length++; // go to next char
1721 : : }
1722 : 55 : else if (current_char != '\'')
1723 : : {
1724 : : // otherwise, get character from direct input character
1725 : 55 : byte_char = current_char;
1726 : :
1727 : 55 : if (!byte_char.is_ascii ())
1728 : : {
1729 : 4 : rust_error_at (get_current_location (),
1730 : : "non-ASCII character in %<byte char%>");
1731 : : }
1732 : :
1733 : 55 : skip_input ();
1734 : 55 : current_char = peek_input ();
1735 : 55 : length++;
1736 : :
1737 : 55 : if (current_char != '\'')
1738 : : {
1739 : 0 : rust_error_at (get_current_location (), "unclosed %<byte char%>");
1740 : : }
1741 : :
1742 : 55 : skip_input ();
1743 : 55 : current_char = peek_input ();
1744 : 55 : length++; // go to next char
1745 : : }
1746 : : else
1747 : : {
1748 : 0 : rust_error_at (get_current_location (),
1749 : : "no character inside %<%> for %<byte char%>");
1750 : : }
1751 : :
1752 : 87 : current_column += length;
1753 : :
1754 : 87 : loc += length - 1;
1755 : 87 : return Token::make_byte_char (loc, byte_char.value);
1756 : : }
1757 : :
1758 : : // Parses a byte string.
1759 : : TokenPtr
1760 : 79 : Lexer::parse_byte_string (location_t loc)
1761 : : {
1762 : : // byte string
1763 : :
1764 : : // skip quote character
1765 : 79 : skip_input ();
1766 : 79 : current_column++;
1767 : :
1768 : 79 : std::string str;
1769 : 79 : str.reserve (16); // some sensible default
1770 : :
1771 : 79 : current_char = peek_input ();
1772 : :
1773 : 79 : const location_t string_begin_locus = get_current_location ();
1774 : :
1775 : 473 : while (current_char != '"' && !current_char.is_eof ())
1776 : : {
1777 : 315 : if (current_char == '\\')
1778 : : {
1779 : 46 : int length = 1;
1780 : 46 : auto escape_length_pair = parse_escape ('"');
1781 : 46 : char output_char = std::get<0> (escape_length_pair);
1782 : :
1783 : 46 : if (output_char == 0 && std::get<2> (escape_length_pair))
1784 : 0 : length = std::get<1> (escape_length_pair) - 1;
1785 : : else
1786 : 46 : length += std::get<1> (escape_length_pair);
1787 : :
1788 : 46 : if (output_char != 0 || !std::get<2> (escape_length_pair))
1789 : 46 : str += output_char;
1790 : :
1791 : 46 : current_column += length;
1792 : :
1793 : 46 : continue;
1794 : 46 : }
1795 : :
1796 : 269 : current_column++;
1797 : 269 : if (current_char.value == '\n')
1798 : : {
1799 : 27 : current_line++;
1800 : 27 : current_column = 1;
1801 : : // tell line_table that new line starts
1802 : 27 : start_line (current_line, max_column_hint);
1803 : : }
1804 : :
1805 : 269 : str += current_char;
1806 : 269 : skip_input ();
1807 : 269 : current_char = peek_input ();
1808 : : }
1809 : :
1810 : 79 : if (current_char == '"')
1811 : : {
1812 : 71 : current_column++;
1813 : :
1814 : 71 : skip_input ();
1815 : 71 : current_char = peek_input ();
1816 : : }
1817 : 8 : else if (current_char.is_eof ())
1818 : : {
1819 : 8 : rust_error_at (string_begin_locus, "unended byte string literal");
1820 : 8 : return Token::make (END_OF_FILE, get_current_location ());
1821 : : }
1822 : : else
1823 : : {
1824 : : rust_unreachable ();
1825 : : }
1826 : :
1827 : 71 : str.shrink_to_fit ();
1828 : 71 : loc += str.size () - 1;
1829 : :
1830 : 71 : return Token::make_byte_string (loc, std::move (str));
1831 : 79 : }
1832 : :
1833 : : // Parses a raw byte string.
1834 : : TokenPtr
1835 : 50 : Lexer::parse_raw_byte_string (location_t loc)
1836 : : {
1837 : : // raw byte string literals
1838 : 50 : std::string str;
1839 : 50 : str.reserve (16); // some sensible default
1840 : :
1841 : 50 : int length = 1;
1842 : 50 : int hash_count = 0;
1843 : :
1844 : 50 : const location_t string_begin_locus = get_current_location ();
1845 : :
1846 : : // get hash count at beginnning
1847 : 50 : skip_input ();
1848 : 50 : current_char = peek_input ();
1849 : 50 : length++;
1850 : 50 : current_column++;
1851 : 66 : while (current_char == '#')
1852 : : {
1853 : 16 : hash_count++;
1854 : 16 : length++;
1855 : 16 : current_column++;
1856 : :
1857 : 16 : skip_input ();
1858 : 16 : current_char = peek_input ();
1859 : : }
1860 : :
1861 : 50 : if (current_char != '"')
1862 : : {
1863 : 0 : rust_error_at (get_current_location (),
1864 : : "raw byte string has no opening %<\"%>");
1865 : : }
1866 : :
1867 : 50 : skip_input ();
1868 : 50 : current_char = peek_input ();
1869 : 50 : length++;
1870 : 50 : current_column++;
1871 : :
1872 : 394 : while (true)
1873 : : {
1874 : 222 : if (current_char == '"')
1875 : : {
1876 : 74 : bool enough_hashes = true;
1877 : :
1878 : 74 : for (int i = 0; i < hash_count; i++)
1879 : : {
1880 : 24 : if (peek_input (i + 1) != '#')
1881 : : {
1882 : : enough_hashes = false;
1883 : : break;
1884 : : }
1885 : : }
1886 : :
1887 : 56 : if (enough_hashes)
1888 : : {
1889 : : // skip enough input and peek enough input
1890 : 50 : skip_input (hash_count);
1891 : 50 : current_char = peek_input ();
1892 : 50 : length += hash_count + 1;
1893 : 50 : current_column += hash_count + 1;
1894 : 50 : break;
1895 : : }
1896 : : }
1897 : 166 : else if (current_char.value > 127)
1898 : : {
1899 : 2 : rust_error_at (get_current_location (),
1900 : : "character %qs in raw byte string out of range",
1901 : 2 : current_char.as_string ().c_str ());
1902 : 2 : current_char = 0;
1903 : : }
1904 : : else if (current_char.is_eof ())
1905 : : {
1906 : : rust_error_at (string_begin_locus, "unended raw byte string literal");
1907 : : return Token::make (END_OF_FILE, get_current_location ());
1908 : : }
1909 : :
1910 : 172 : length++;
1911 : 172 : current_column++;
1912 : 172 : if (current_char == '\n')
1913 : : {
1914 : 2 : current_line++;
1915 : 2 : current_column = 1;
1916 : 2 : start_line (current_line, max_column_hint);
1917 : : }
1918 : :
1919 : 172 : str += current_char;
1920 : 172 : skip_input ();
1921 : 172 : current_char = peek_input ();
1922 : 172 : }
1923 : :
1924 : 50 : loc += length - 1;
1925 : :
1926 : 50 : str.shrink_to_fit ();
1927 : :
1928 : 50 : return Token::make_byte_string (loc, std::move (str));
1929 : 50 : }
1930 : :
1931 : : // Parses a raw identifier.
1932 : : TokenPtr
1933 : 96 : Lexer::parse_raw_identifier (location_t loc)
1934 : : {
1935 : : // raw identifier
1936 : 96 : std::string str;
1937 : 96 : str.reserve (16); // default
1938 : :
1939 : 96 : skip_input ();
1940 : 96 : current_char = peek_input ();
1941 : :
1942 : 96 : current_column += 2;
1943 : :
1944 : 96 : bool first_is_underscore = current_char == '_';
1945 : :
1946 : 96 : int length = 0;
1947 : 96 : current_char = peek_input ();
1948 : : // loop through entire name
1949 : 560 : while (is_identifier_continue (current_char.value))
1950 : : {
1951 : 368 : length++;
1952 : :
1953 : 368 : str += current_char;
1954 : 368 : skip_input ();
1955 : 368 : current_char = peek_input ();
1956 : : }
1957 : :
1958 : 96 : current_column += length;
1959 : :
1960 : 96 : rust_debug ("raw ident: %s", str.c_str ());
1961 : :
1962 : : // if just a single underscore, not an identifier
1963 : 96 : if (first_is_underscore && length == 1)
1964 : 2 : rust_error_at (get_current_location (),
1965 : : "%<_%> is not a valid raw identifier");
1966 : :
1967 : 96 : using namespace Rust::Values;
1968 : 96 : std::set<std::string> invalid{
1969 : 96 : Keywords::CRATE, Keywords::EXTERN_KW, Keywords::SELF,
1970 : 96 : Keywords::SUPER, Keywords::SELF_ALIAS,
1971 : 576 : };
1972 : :
1973 : 96 : if (invalid.find (str) != invalid.end ())
1974 : : {
1975 : 2 : rust_error_at (get_current_location (),
1976 : : "%qs is a forbidden raw identifier", str.c_str ());
1977 : :
1978 : 2 : return nullptr;
1979 : : }
1980 : : else
1981 : : {
1982 : 94 : str.shrink_to_fit ();
1983 : 94 : loc += length - 1;
1984 : :
1985 : 94 : return Token::make_identifier (loc, std::move (str));
1986 : : }
1987 : 96 : }
1988 : :
1989 : : // skip broken string input (unterminated strings)
1990 : : void
1991 : 0 : Lexer::skip_broken_string_input (Codepoint current_char)
1992 : : {
1993 : 0 : while (current_char != '"' && !current_char.is_eof ())
1994 : : {
1995 : 0 : if (current_char == '\n')
1996 : : {
1997 : 0 : current_line++;
1998 : 0 : current_column = 1;
1999 : : }
2000 : : else
2001 : : {
2002 : 0 : current_column++;
2003 : : }
2004 : 0 : skip_input ();
2005 : 0 : current_char = peek_input ();
2006 : : }
2007 : 0 : if (current_char == '"')
2008 : : {
2009 : 0 : current_column++;
2010 : :
2011 : 0 : skip_input ();
2012 : 0 : current_char = peek_input ();
2013 : : }
2014 : 0 : rust_debug ("skipped to %d:%d due to bad quotes", current_line,
2015 : : current_column);
2016 : 0 : }
2017 : :
2018 : : // Parses a string.
2019 : : TokenPtr
2020 : 7216 : Lexer::parse_string (location_t loc)
2021 : : {
2022 : 7216 : std::string str;
2023 : 7216 : str.reserve (16); // some sensible default
2024 : :
2025 : 7216 : current_char = peek_input ();
2026 : :
2027 : 7216 : const location_t string_begin_locus = get_current_location ();
2028 : :
2029 : : // FIXME: This fails if the input ends. How do we check for EOF?
2030 : 60258 : while (current_char.value != '"' && !current_char.is_eof ())
2031 : : {
2032 : 45826 : if (current_char.value == '\\')
2033 : : {
2034 : 2652 : int length = 1;
2035 : :
2036 : : // parse escape
2037 : 2652 : auto utf8_escape_pair = parse_utf8_escape ();
2038 : 2652 : current_char = std::get<0> (utf8_escape_pair);
2039 : :
2040 : 2652 : if (current_char == Codepoint (0) && std::get<2> (utf8_escape_pair))
2041 : 28 : length = std::get<1> (utf8_escape_pair) - 1;
2042 : : else
2043 : 2624 : length += std::get<1> (utf8_escape_pair);
2044 : :
2045 : 2652 : if (current_char != Codepoint (0) || !std::get<2> (utf8_escape_pair))
2046 : 5248 : str += current_char.as_string ();
2047 : :
2048 : 2652 : current_column += length;
2049 : :
2050 : : // FIXME: should remove this but can't.
2051 : : // `parse_utf8_escape` does not update `current_char` correctly.
2052 : 2652 : current_char = peek_input ();
2053 : 2652 : continue;
2054 : 2652 : }
2055 : :
2056 : 43174 : current_column++;
2057 : 43174 : if (current_char.value == '\n')
2058 : : {
2059 : 83 : current_line++;
2060 : 83 : current_column = 1;
2061 : : // tell line_table that new line starts
2062 : 83 : start_line (current_line, max_column_hint);
2063 : : }
2064 : :
2065 : 43174 : str += current_char;
2066 : 43174 : skip_input ();
2067 : 43174 : current_char = peek_input ();
2068 : : }
2069 : :
2070 : 7216 : if (current_char.value == '"')
2071 : : {
2072 : 7200 : current_column++;
2073 : :
2074 : 7200 : skip_input ();
2075 : 7200 : current_char = peek_input ();
2076 : : }
2077 : 16 : else if (current_char.is_eof ())
2078 : : {
2079 : 16 : rust_error_at (string_begin_locus, "unended string literal");
2080 : 16 : return Token::make (END_OF_FILE, get_current_location ());
2081 : : }
2082 : : else
2083 : : {
2084 : : rust_unreachable ();
2085 : : }
2086 : :
2087 : 7200 : str.shrink_to_fit ();
2088 : :
2089 : 7200 : return Token::make_string (loc, std::move (str));
2090 : 7216 : }
2091 : :
2092 : : // Parses an identifier or keyword.
2093 : : TokenPtr
2094 : 225914 : Lexer::parse_identifier_or_keyword (location_t loc)
2095 : : {
2096 : 225914 : std::string str;
2097 : 225914 : str.reserve (16); // default
2098 : 451828 : str += current_char.as_string ();
2099 : :
2100 : 225914 : bool first_is_underscore = current_char == '_';
2101 : :
2102 : 225914 : int length = 1;
2103 : 225914 : current_char = peek_input ();
2104 : :
2105 : : // loop through entire name
2106 : 1055566 : while (is_identifier_continue (current_char.value))
2107 : : {
2108 : 603738 : auto s = current_char.as_string ();
2109 : 603738 : length++;
2110 : :
2111 : 1207476 : str += current_char.as_string ();
2112 : 603738 : skip_input ();
2113 : 603738 : current_char = peek_input ();
2114 : 603738 : }
2115 : :
2116 : 225914 : current_column += length;
2117 : :
2118 : : // if just a single underscore, not an identifier
2119 : 225914 : if (first_is_underscore && length == 1)
2120 : 977 : return Token::make (UNDERSCORE, loc);
2121 : :
2122 : 224937 : str.shrink_to_fit ();
2123 : :
2124 : 224937 : loc += length - 1;
2125 : :
2126 : 224937 : TokenId keyword = classify_keyword (str);
2127 : 224937 : if (keyword == IDENTIFIER)
2128 : 144082 : return Token::make_identifier (loc, std::move (str));
2129 : : else
2130 : 80855 : return Token::make (keyword, loc);
2131 : 225914 : }
2132 : :
2133 : : // Possibly returns a raw string token if it exists - otherwise returns null.
2134 : : TokenPtr
2135 : 3893 : Lexer::maybe_parse_raw_string (location_t loc)
2136 : : {
2137 : 3893 : int peek_index = 0;
2138 : 3911 : while (peek_input (peek_index) == '#')
2139 : 18 : peek_index++;
2140 : :
2141 : 3893 : if (peek_input (peek_index) == '"')
2142 : 50 : return parse_raw_string (loc, peek_index);
2143 : : else
2144 : 3843 : return nullptr;
2145 : : }
2146 : :
2147 : : // Returns a raw string token.
2148 : : TokenPtr
2149 : 50 : Lexer::parse_raw_string (location_t loc, int initial_hash_count)
2150 : : {
2151 : : // raw string literals
2152 : 50 : std::string str;
2153 : 50 : str.reserve (16); // some sensible default
2154 : :
2155 : 50 : int length = 1 + initial_hash_count;
2156 : 50 : current_column += length;
2157 : :
2158 : 50 : const location_t string_begin_locus = get_current_location ();
2159 : :
2160 : 50 : if (initial_hash_count > 0)
2161 : 14 : skip_input (initial_hash_count - 1);
2162 : :
2163 : 50 : current_char = peek_input ();
2164 : :
2165 : 50 : if (current_char != '"')
2166 : 0 : rust_error_at (get_current_location (), "raw string has no opening %<\"%>");
2167 : :
2168 : 50 : length++;
2169 : 50 : current_column++;
2170 : 50 : skip_input ();
2171 : 50 : current_char = peek_input ();
2172 : :
2173 : 362 : while (true)
2174 : : {
2175 : 206 : if (current_char.value == '"')
2176 : : {
2177 : 76 : bool enough_hashes = true;
2178 : :
2179 : 76 : for (int i = 0; i < initial_hash_count; i++)
2180 : : {
2181 : 26 : if (peek_input (i + 1) != '#')
2182 : : {
2183 : : enough_hashes = false;
2184 : : break;
2185 : : }
2186 : : }
2187 : :
2188 : 56 : if (enough_hashes)
2189 : : {
2190 : : // skip enough input and peek enough input
2191 : 50 : skip_input (initial_hash_count);
2192 : 50 : current_char = peek_input ();
2193 : 50 : length += initial_hash_count + 1;
2194 : 50 : current_column += initial_hash_count + 1;
2195 : 50 : break;
2196 : : }
2197 : : }
2198 : 150 : else if (current_char.is_eof ())
2199 : : {
2200 : 0 : rust_error_at (string_begin_locus, "unended raw string literal");
2201 : 0 : return Token::make (END_OF_FILE, get_current_location ());
2202 : : }
2203 : :
2204 : 156 : length++;
2205 : 156 : current_column++;
2206 : 156 : if (current_char == '\n')
2207 : : {
2208 : 2 : current_line++;
2209 : 2 : current_column = 1;
2210 : 2 : start_line (current_line, max_column_hint);
2211 : : }
2212 : :
2213 : 312 : str += current_char.as_string ();
2214 : 156 : skip_input ();
2215 : 156 : current_char = peek_input ();
2216 : 156 : }
2217 : :
2218 : 50 : loc += length - 1;
2219 : :
2220 : 50 : str.shrink_to_fit ();
2221 : :
2222 : 50 : return Token::make_raw_string (loc, std::move (str));
2223 : 50 : }
2224 : :
2225 : : template <typename IsDigitFunc>
2226 : : TokenPtr
2227 : 158 : Lexer::parse_non_decimal_int_literal (location_t loc, IsDigitFunc is_digit_func,
2228 : : std::string existent_str, int base)
2229 : : {
2230 : 158 : int length = 1;
2231 : :
2232 : 158 : skip_input ();
2233 : 158 : current_char = peek_input ();
2234 : :
2235 : 158 : length++;
2236 : :
2237 : : // loop through to add entire number to string
2238 : 1216 : while (is_digit_func (current_char.value) || current_char == '_')
2239 : : {
2240 : 1058 : if (current_char == '_')
2241 : : {
2242 : : // don't add _ to number
2243 : 24 : skip_input ();
2244 : 24 : current_char = peek_input ();
2245 : :
2246 : 24 : length++;
2247 : :
2248 : 24 : continue;
2249 : : }
2250 : :
2251 : 1034 : length++;
2252 : :
2253 : : // add raw numbers
2254 : 1034 : existent_str += current_char;
2255 : 1034 : skip_input ();
2256 : 1034 : current_char = peek_input ();
2257 : : }
2258 : :
2259 : : // convert value to decimal representation
2260 : 158 : long dec_num = std::strtol (existent_str.c_str (), nullptr, base);
2261 : :
2262 : 158 : existent_str = std::to_string (dec_num);
2263 : :
2264 : : // parse in type suffix if it exists
2265 : 158 : auto type_suffix_pair = parse_in_type_suffix ();
2266 : 158 : PrimitiveCoreType type_hint = type_suffix_pair.first;
2267 : 158 : length += type_suffix_pair.second;
2268 : :
2269 : 158 : current_column += length;
2270 : :
2271 : 158 : if (type_hint == CORETYPE_F32 || type_hint == CORETYPE_F64)
2272 : : {
2273 : 0 : rust_error_at (get_current_location (),
2274 : : "invalid type suffix %qs for integer (%s) literal",
2275 : : get_type_hint_string (type_hint),
2276 : : base == 16
2277 : : ? "hex"
2278 : : : (base == 8 ? "octal"
2279 : : : (base == 2 ? "binary"
2280 : : : "<insert unknown base>")));
2281 : 0 : return nullptr;
2282 : : }
2283 : :
2284 : 158 : loc += length - 1;
2285 : :
2286 : 158 : return Token::make_int (loc, std::move (existent_str), type_hint);
2287 : : }
2288 : :
2289 : : // Parses a hex, binary or octal int literal.
2290 : : TokenPtr
2291 : 158 : Lexer::parse_non_decimal_int_literals (location_t loc)
2292 : : {
2293 : 158 : std::string str;
2294 : 158 : str.reserve (16); // some sensible default
2295 : 158 : str += current_char;
2296 : :
2297 : 158 : current_char = peek_input ();
2298 : :
2299 : 158 : if (current_char == 'x')
2300 : : {
2301 : : // hex (integer only)
2302 : 118 : return parse_non_decimal_int_literal (loc, is_x_digit, str + "x", 16);
2303 : : }
2304 : 40 : else if (current_char == 'o')
2305 : : {
2306 : : // octal (integer only)
2307 : 40 : return parse_non_decimal_int_literal (loc, is_octal_digit,
2308 : 20 : std::move (str), 8);
2309 : : }
2310 : 20 : else if (current_char == 'b')
2311 : : {
2312 : : // binary (integer only)
2313 : 40 : return parse_non_decimal_int_literal (loc, is_bin_digit, std::move (str),
2314 : 20 : 2);
2315 : : }
2316 : : else
2317 : : {
2318 : 0 : return nullptr;
2319 : : }
2320 : 158 : }
2321 : :
2322 : : // Parses a decimal-based int literal or float literal.
2323 : : TokenPtr
2324 : 14619 : Lexer::parse_decimal_int_or_float (location_t loc)
2325 : : {
2326 : 14619 : std::string str;
2327 : 14619 : str.reserve (16); // some sensible default
2328 : 14619 : str += current_char;
2329 : :
2330 : 14619 : int length = 1;
2331 : 14619 : bool first_zero = current_char == '0';
2332 : :
2333 : 14619 : current_char = peek_input ();
2334 : :
2335 : : // parse initial decimal integer (or first integer part of float) literal
2336 : 14619 : auto initial_decimal = parse_in_decimal ();
2337 : 14619 : str += std::get<0> (initial_decimal);
2338 : 14619 : length += std::get<1> (initial_decimal);
2339 : :
2340 : : // detect float literal
2341 : : //
2342 : : // Note:
2343 : : //
2344 : : // We should not use is_float_digit () for this verification but instead
2345 : : // directly ISDIGIT because rust does not support non digit values right after
2346 : : // a dot.
2347 : : // The following value is not legal in rust:
2348 : : // let a = 3.e1;
2349 : : // A `0` should be put between the dot and the exponent to be valid
2350 : : // (eg. 3.0e1).
2351 : 14619 : if (current_char == '.' && ISDIGIT (peek_input (1).value))
2352 : : {
2353 : : // float with a '.', parse another decimal into it
2354 : :
2355 : : // add . to str
2356 : 402 : str += current_char;
2357 : 402 : skip_input ();
2358 : 402 : current_char = peek_input ();
2359 : 402 : length++;
2360 : :
2361 : : // parse another decimal number for float
2362 : 402 : auto second_decimal = parse_in_decimal ();
2363 : 402 : str += std::get<0> (second_decimal);
2364 : 402 : length += std::get<1> (second_decimal);
2365 : :
2366 : : // parse in exponent part if it exists
2367 : 402 : auto exponent_pair = parse_in_exponent_part ();
2368 : 402 : str += exponent_pair.first;
2369 : 402 : length += exponent_pair.second;
2370 : :
2371 : : // parse in type suffix if it exists
2372 : 402 : auto type_suffix_pair = parse_in_type_suffix ();
2373 : 402 : PrimitiveCoreType type_hint = type_suffix_pair.first;
2374 : 402 : length += type_suffix_pair.second;
2375 : :
2376 : 402 : if (type_hint != CORETYPE_F32 && type_hint != CORETYPE_F64
2377 : 402 : && type_hint != CORETYPE_UNKNOWN)
2378 : : {
2379 : 0 : rust_error_at (get_current_location (),
2380 : : "invalid type suffix %qs for floating-point literal",
2381 : : get_type_hint_string (type_hint));
2382 : : // ignore invalid type suffix as everything else seems fine
2383 : 0 : type_hint = CORETYPE_UNKNOWN;
2384 : : }
2385 : :
2386 : 402 : current_column += length;
2387 : :
2388 : 402 : loc += length - 1;
2389 : :
2390 : 402 : str.shrink_to_fit ();
2391 : 402 : return Token::make_float (loc, std::move (str), type_hint);
2392 : 402 : }
2393 : 14217 : else if (current_char == '.'
2394 : 14217 : && check_valid_float_dot_end (peek_input (1).value))
2395 : : {
2396 : : // float that is just an integer with a terminating '.' character
2397 : :
2398 : : // add . to str
2399 : 10 : str += current_char;
2400 : 10 : skip_input ();
2401 : 10 : current_char = peek_input ();
2402 : 10 : length++;
2403 : :
2404 : : // type hint not allowed
2405 : :
2406 : 10 : current_column += length;
2407 : :
2408 : 10 : loc += length - 1;
2409 : :
2410 : 10 : str.shrink_to_fit ();
2411 : 10 : return Token::make_float (loc, std::move (str), CORETYPE_UNKNOWN);
2412 : : }
2413 : 14207 : else if (current_char == 'E' || current_char == 'e')
2414 : : {
2415 : : // exponent float with no '.' character
2416 : :
2417 : : // parse exponent part
2418 : 0 : auto exponent_pair = parse_in_exponent_part ();
2419 : 0 : str += exponent_pair.first;
2420 : 0 : length += exponent_pair.second;
2421 : :
2422 : : // parse in type suffix if it exists
2423 : 0 : auto type_suffix_pair = parse_in_type_suffix ();
2424 : 0 : PrimitiveCoreType type_hint = type_suffix_pair.first;
2425 : 0 : length += type_suffix_pair.second;
2426 : :
2427 : 0 : if (type_hint != CORETYPE_F32 && type_hint != CORETYPE_F64
2428 : 0 : && type_hint != CORETYPE_UNKNOWN)
2429 : : {
2430 : 0 : rust_error_at (get_current_location (),
2431 : : "invalid type suffix %qs for floating-point literal",
2432 : : get_type_hint_string (type_hint));
2433 : : // ignore invalid type suffix as everything else seems fine
2434 : 0 : type_hint = CORETYPE_UNKNOWN;
2435 : : }
2436 : :
2437 : 0 : current_column += length;
2438 : :
2439 : 0 : loc += length - 1;
2440 : :
2441 : 0 : str.shrink_to_fit ();
2442 : 0 : return Token::make_float (loc, std::move (str), type_hint);
2443 : 0 : }
2444 : : else
2445 : : {
2446 : : // is an integer
2447 : :
2448 : : // parse in type suffix if it exists
2449 : 14207 : auto type_suffix_pair = parse_in_type_suffix ();
2450 : 14207 : PrimitiveCoreType type_hint = type_suffix_pair.first;
2451 : : /* A "real" pure decimal doesn't have a suffix and no zero prefix. */
2452 : 14207 : if (type_hint == CORETYPE_UNKNOWN)
2453 : : {
2454 : 13027 : bool pure_decimal = std::get<2> (initial_decimal);
2455 : 15748 : if (pure_decimal && (!first_zero || str.size () == 1))
2456 : : type_hint = CORETYPE_PURE_DECIMAL;
2457 : : }
2458 : 14207 : length += type_suffix_pair.second;
2459 : :
2460 : 14207 : current_column += length;
2461 : :
2462 : 14207 : loc += length - 1;
2463 : :
2464 : 14207 : str.shrink_to_fit ();
2465 : 14207 : return Token::make_int (loc, std::move (str), type_hint);
2466 : : }
2467 : 14619 : }
2468 : :
2469 : : TokenPtr
2470 : 982 : Lexer::parse_char_or_lifetime (location_t loc)
2471 : : {
2472 : 982 : int length = 1;
2473 : :
2474 : 982 : current_char = peek_input ();
2475 : 982 : if (current_char.is_eof ())
2476 : 0 : return nullptr;
2477 : :
2478 : : // parse escaped char literal
2479 : 982 : if (current_char.value == '\\')
2480 : : {
2481 : : // parse escape
2482 : 46 : auto utf8_escape_pair = parse_utf8_escape ();
2483 : 46 : Codepoint escaped_char = std::get<0> (utf8_escape_pair);
2484 : 46 : length += std::get<1> (utf8_escape_pair);
2485 : :
2486 : 46 : if (peek_input ().value != '\'')
2487 : : {
2488 : 0 : rust_error_at (get_current_location (), "unended character literal");
2489 : : }
2490 : : else
2491 : : {
2492 : 46 : skip_input ();
2493 : 46 : current_char = peek_input ();
2494 : 46 : length++;
2495 : : }
2496 : :
2497 : 46 : current_column += length;
2498 : :
2499 : 46 : loc += length - 1;
2500 : :
2501 : 46 : return Token::make_char (loc, escaped_char);
2502 : : }
2503 : : else
2504 : : {
2505 : 936 : skip_input ();
2506 : :
2507 : 936 : if (peek_input ().value == '\'')
2508 : : {
2509 : : // parse non-escaped char literal
2510 : 218 : Codepoint non_escaped_char = current_char;
2511 : :
2512 : : // skip the ' character
2513 : 218 : skip_input ();
2514 : 218 : current_char = peek_input ();
2515 : :
2516 : : // TODO fix due to different widths of utf-8 chars?
2517 : 218 : current_column += 3;
2518 : :
2519 : 218 : loc += 2;
2520 : :
2521 : 218 : return Token::make_char (loc, non_escaped_char);
2522 : : }
2523 : 718 : else if (is_identifier_start (current_char.value))
2524 : : {
2525 : : // parse lifetime name
2526 : 718 : std::string str;
2527 : 1436 : str += current_char.as_string ();
2528 : 718 : length++;
2529 : :
2530 : 718 : current_char = peek_input ();
2531 : 2130 : while (is_identifier_continue (current_char.value))
2532 : : {
2533 : 1388 : str += current_char.as_string ();
2534 : 694 : skip_input ();
2535 : 694 : current_char = peek_input ();
2536 : 694 : length++;
2537 : : }
2538 : :
2539 : 718 : current_column += length;
2540 : :
2541 : 718 : loc += length - 1;
2542 : :
2543 : : // TODO some keywords cannot be used for a lifetime label #2306
2544 : : // https://doc.rust-lang.org/reference/tokens.html
2545 : :
2546 : 718 : str.shrink_to_fit ();
2547 : 718 : return Token::make_lifetime (loc, std::move (str));
2548 : 718 : }
2549 : : else
2550 : : {
2551 : 0 : rust_error_at (
2552 : : get_current_location (),
2553 : : "expected %' after character constant in character literal");
2554 : 0 : return nullptr;
2555 : : }
2556 : : }
2557 : : }
2558 : :
2559 : : void
2560 : 126 : Lexer::split_current_token (TokenId new_left, TokenId new_right)
2561 : : {
2562 : : /* TODO: assert that this TokenId is a "simple token" like punctuation and not
2563 : : * like "IDENTIFIER"? */
2564 : 126 : location_t current_loc = peek_token ()->get_locus ();
2565 : 126 : TokenPtr new_left_tok = Token::make (new_left, current_loc);
2566 : 126 : TokenPtr new_right_tok = Token::make (new_right, current_loc + 1);
2567 : :
2568 : 126 : token_queue.replace_current_value (std::move (new_left_tok));
2569 : 126 : token_queue.insert (1, std::move (new_right_tok));
2570 : 126 : }
2571 : :
2572 : : void
2573 : 4 : Lexer::split_current_token (std::vector<TokenPtr> new_tokens)
2574 : : {
2575 : 4 : rust_assert (new_tokens.size () > 0);
2576 : 8 : token_queue.replace_current_value (new_tokens[0]);
2577 : :
2578 : 10 : for (size_t i = 1; i < new_tokens.size (); i++)
2579 : : {
2580 : 12 : token_queue.insert (i, new_tokens[i]);
2581 : : }
2582 : 4 : }
2583 : :
2584 : : void
2585 : 127060 : Lexer::start_line (int current_line, int current_column)
2586 : : {
2587 : 127060 : if (line_map)
2588 : 127060 : linemap_line_start (line_table, current_line, current_column);
2589 : 127060 : }
2590 : :
2591 : : } // namespace Rust
2592 : :
2593 : : #if CHECKING_P
2594 : :
2595 : : namespace selftest {
2596 : :
2597 : : // Checks if `src` has the same contents as the given characters
2598 : : static void
2599 : 6 : assert_source_content (Rust::InputSource &src,
2600 : : const std::vector<uint32_t> &expected)
2601 : : {
2602 : 6 : Rust::Codepoint src_char = src.next ();
2603 : 41 : for (auto expected_char : expected)
2604 : : {
2605 : : // Make sure that `src` is not shorter than `expected`
2606 : 35 : ASSERT_FALSE (src_char.is_eof ());
2607 : : // Checks skipped character is expeceted one.
2608 : 35 : ASSERT_EQ (src_char.value, expected_char);
2609 : 35 : src_char = src.next ();
2610 : : }
2611 : : // Checks if `src` and `chars` has the same length.
2612 : 6 : ASSERT_TRUE (src_char.is_eof ());
2613 : 6 : }
2614 : :
2615 : : static void
2616 : 4 : test_buffer_input_source (std::string str,
2617 : : const std::vector<uint32_t> &expected)
2618 : : {
2619 : 4 : Rust::BufferInputSource source (str, 0);
2620 : 4 : assert_source_content (source, expected);
2621 : 4 : }
2622 : :
2623 : : static void
2624 : 2 : test_file_input_source (std::string str, const std::vector<uint32_t> &expected)
2625 : : {
2626 : 2 : FILE *tmpf = tmpfile ();
2627 : : // Moves to the first character
2628 : 2 : fputs (str.c_str (), tmpf);
2629 : 2 : std::rewind (tmpf);
2630 : 2 : Rust::FileInputSource source (tmpf);
2631 : 2 : assert_source_content (source, expected);
2632 : 2 : }
2633 : :
2634 : : void
2635 : 1 : rust_input_source_test ()
2636 : : {
2637 : : // ASCII
2638 : 1 : std::string src = u8"_abcde\tXYZ\v\f";
2639 : 1 : std::vector<uint32_t> expected
2640 : 1 : = {'_', 'a', 'b', 'c', 'd', 'e', '\t', 'X', 'Y', 'Z', '\v', '\f'};
2641 : 2 : test_buffer_input_source (src, expected);
2642 : :
2643 : : // BOM
2644 : 1 : src = u8"\xef\xbb\xbfOK";
2645 : 1 : expected = {'O', 'K'};
2646 : 2 : test_buffer_input_source (src, expected);
2647 : :
2648 : : // Russian
2649 : 1 : src = u8"приве́т";
2650 : 1 : expected = {L'п',
2651 : : L'р',
2652 : : L'и',
2653 : : L'в',
2654 : : 0x0435 /* CYRILLIC SMALL LETTER IE е */,
2655 : : 0x301 /* COMBINING ACUTE ACCENT ́ */,
2656 : 1 : L'т'};
2657 : 2 : test_buffer_input_source (src, expected);
2658 : :
2659 : 1 : src = u8"❤️🦀";
2660 : 1 : expected = {0x2764 /* HEAVY BLACK HEART */,
2661 : 1 : 0xfe0f /* VARIATION SELECTOR-16 */, L'🦀'};
2662 : 2 : test_buffer_input_source (src, expected);
2663 : :
2664 : 1 : src = u8"こんにちは";
2665 : 1 : expected = {L'こ', L'ん', L'に', L'ち', L'は'};
2666 : 2 : test_file_input_source (src, expected);
2667 : :
2668 : 1 : src = u8"👮♂👩⚕";
2669 : 1 : expected
2670 : : = {0x1f46e /* POLICE OFFICER */, 0x200d /* ZERO WIDTH JOINER */,
2671 : : 0x2642 /* MALE SIGN */, 0x1f469 /* WOMAN */,
2672 : 1 : 0x200d /* ZERO WIDTH JOINER */, 0x2695 /* STAFF OF AESCULAPIUS */};
2673 : 2 : test_file_input_source (src, expected);
2674 : 1 : }
2675 : :
2676 : : } // namespace selftest
2677 : :
2678 : : #endif // CHECKING_P
|