LCOV - code coverage report
Current view: top level - gcc/rust/lex - rust-lex.cc (source / functions) Coverage Total Hit
Test: gcc.info Lines: 91.7 % 1363 1250
Test Date: 2026-07-11 15:47:05 Functions: 94.4 % 54 51
Legend: Lines:     hit not hit

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

Generated by: LCOV version 2.4-beta

LCOV profile is generated on x86_64 machine using following configure options: configure --disable-bootstrap --enable-coverage=opt --enable-languages=c,c++,fortran,go,jit,lto,rust,m2 --enable-host-shared. GCC test suite is run with the built compiler.