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