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