| /* This file is part of GCC. | |
| GCC is free software; you can redistribute it and/or modify | |
| it under the terms of the GNU General Public License as published by | |
| the Free Software Foundation; either version 3, or (at your option) | |
| any later version. | |
| GCC is distributed in the hope that it will be useful, | |
| but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| GNU General Public License for more details. | |
| You should have received a copy of the GNU General Public License | |
| along with GCC; see the file COPYING3. If not see | |
| <http://www.gnu.org/licenses/>. */ | |
| #include "rust-parse.h" | |
| #include "rust-linemap.h" | |
| #include "rust-diagnostics.h" | |
| #include "rust-token.h" | |
| #include "rust-attribute-values.h" | |
| namespace Rust { | |
| std::string | |
| extract_module_path (const AST::AttrVec &inner_attrs, | |
| const AST::AttrVec &outer_attrs, const std::string &name) | |
| { | |
| AST::Attribute path_attr = AST::Attribute::create_empty (); | |
| for (const auto &attr : inner_attrs) | |
| { | |
| if (attr.get_path ().as_string () == Values::Attributes::PATH) | |
| { | |
| path_attr = attr; | |
| break; | |
| } | |
| } | |
| // Here, we found a path attribute, but it has no associated string. This is | |
| // invalid | |
| if (!path_attr.is_empty () && !path_attr.has_attr_input ()) | |
| { | |
| rust_error_at ( | |
| path_attr.get_locus (), | |
| "path attributes must contain a filename: %<#[path = \"file\"]%>"); | |
| return name; | |
| } | |
| for (const auto &attr : outer_attrs) | |
| { | |
| if (attr.get_path ().as_string () == Values::Attributes::PATH) | |
| { | |
| path_attr = attr; | |
| break; | |
| } | |
| } | |
| // We didn't find a path attribute. This is not an error, there simply isn't | |
| // one present | |
| if (path_attr.is_empty ()) | |
| return name; | |
| // Here, we found a path attribute, but it has no associated string. This is | |
| // invalid | |
| if (!path_attr.has_attr_input ()) | |
| { | |
| rust_error_at ( | |
| path_attr.get_locus (), | |
| "path attributes must contain a filename: %<#[path = \"file\"]%>"); | |
| return name; | |
| } | |
| auto path_value = path_attr.get_attr_input ().as_string (); | |
| // At this point, the 'path' is of the following format: '= "<file.rs>"' | |
| // We need to remove the equal sign and only keep the actual filename. | |
| // In order to do this, we can simply go through the string until we find | |
| // a character that is not an equal sign or whitespace | |
| auto filename_begin = path_value.find_first_not_of ("=\t "); | |
| // If the path consists of only whitespace, then we have an error | |
| if (filename_begin == std::string::npos) | |
| { | |
| rust_error_at ( | |
| path_attr.get_locus (), | |
| "path attributes must contain a filename: %<#[path = \"file\"]%>"); | |
| return name; | |
| } | |
| auto path = path_value.substr (filename_begin); | |
| // On windows, the path might mix '/' and '\' separators. Replace the | |
| // UNIX-like separators by MSDOS separators to make sure the path will resolve | |
| // properly. | |
| // | |
| // Source: rustc compiler | |
| // (https://github.com/rust-lang/rust/blob/9863bf51a52b8e61bcad312f81b5193d53099f9f/compiler/rustc_expand/src/module.rs#L174) | |
| #if defined(HAVE_DOS_BASED_FILE_SYSTEM) | |
| std::replace (path.begin (), path.end (), '/', '\\'); | |
| #endif /* HAVE_DOS_BASED_FILE_SYSTEM */ | |
| return path; | |
| } | |
| template <typename T> | |
| static bool | |
| contains (std::vector<T> &vec, T elm) | |
| { | |
| return std::find (vec.begin (), vec.end (), elm) != vec.end (); | |
| } | |
| /** | |
| * Avoid UB by calling .front() and .back() on empty containers... | |
| */ | |
| template <typename T> | |
| static const T * | |
| get_back_ptr (const std::vector<std::unique_ptr<T>> &values) | |
| { | |
| if (values.empty ()) | |
| return nullptr; | |
| return values.back ().get (); | |
| } | |
| template <typename T> | |
| static const T * | |
| get_front_ptr (const std::vector<std::unique_ptr<T>> &values) | |
| { | |
| if (values.empty ()) | |
| return nullptr; | |
| return values.front ().get (); | |
| } | |
| static bool | |
| peculiar_fragment_match_compatible_fragment ( | |
| const AST::MacroFragSpec &last_spec, const AST::MacroFragSpec &spec, | |
| location_t match_locus) | |
| { | |
| static std::unordered_map<AST::MacroFragSpec::Kind, | |
| std::vector<AST::MacroFragSpec::Kind>> | |
| fragment_follow_set | |
| = {{AST::MacroFragSpec::PATH, {AST::MacroFragSpec::BLOCK}}, | |
| {AST::MacroFragSpec::TY, {AST::MacroFragSpec::BLOCK}}, | |
| {AST::MacroFragSpec::VIS, | |
| {AST::MacroFragSpec::IDENT, AST::MacroFragSpec::TY, | |
| AST::MacroFragSpec::PATH}}}; | |
| auto is_valid | |
| = contains (fragment_follow_set[last_spec.get_kind ()], spec.get_kind ()); | |
| if (!is_valid) | |
| rust_error_at (match_locus, | |
| "fragment specifier %qs is not allowed after %qs fragments", | |
| spec.as_string ().c_str (), last_spec.as_string ().c_str ()); | |
| return is_valid; | |
| } | |
| static bool | |
| peculiar_fragment_match_compatible (const AST::MacroMatchFragment &last_match, | |
| const AST::MacroMatch &match) | |
| { | |
| static std::unordered_map<AST::MacroFragSpec::Kind, std::vector<TokenId>> | |
| follow_set | |
| = {{AST::MacroFragSpec::EXPR, {MATCH_ARROW, COMMA, SEMICOLON}}, | |
| {AST::MacroFragSpec::STMT, {MATCH_ARROW, COMMA, SEMICOLON}}, | |
| {AST::MacroFragSpec::PAT, {MATCH_ARROW, COMMA, EQUAL, PIPE, IF, IN}}, | |
| {AST::MacroFragSpec::PATH, | |
| {MATCH_ARROW, COMMA, EQUAL, PIPE, SEMICOLON, COLON, RIGHT_ANGLE, | |
| RIGHT_SHIFT, LEFT_SQUARE, LEFT_CURLY, AS, WHERE}}, | |
| {AST::MacroFragSpec::TY, | |
| {MATCH_ARROW, COMMA, EQUAL, PIPE, SEMICOLON, COLON, RIGHT_ANGLE, | |
| RIGHT_SHIFT, LEFT_SQUARE, LEFT_CURLY, AS, WHERE}}, | |
| {AST::MacroFragSpec::VIS, | |
| {COMMA, | |
| IDENTIFIER, | |
| LEFT_PAREN, | |
| LEFT_SQUARE, | |
| EXCLAM, | |
| ASTERISK, | |
| AMP, | |
| LOGICAL_AND, | |
| QUESTION_MARK, | |
| LIFETIME, | |
| LEFT_ANGLE, | |
| LEFT_SHIFT, | |
| UNDERSCORE, | |
| ABSTRACT, | |
| AS, | |
| ASYNC, | |
| AUTO, | |
| BECOME, | |
| BOX, | |
| BREAK, | |
| CONST, | |
| CONTINUE, | |
| CRATE, | |
| DO, | |
| DYN, | |
| ELSE, | |
| ENUM_KW, | |
| EXTERN_KW, | |
| FALSE_LITERAL, | |
| FINAL_KW, | |
| FN_KW, | |
| FOR, | |
| IF, | |
| IMPL, | |
| IN, | |
| LET, | |
| LOOP, | |
| MACRO, | |
| MATCH_KW, | |
| MOD, | |
| MOVE, | |
| MUT, | |
| OVERRIDE_KW, | |
| PUB, | |
| REF, | |
| RETURN_KW, | |
| SELF_ALIAS, | |
| SELF, | |
| STATIC_KW, | |
| STRUCT_KW, | |
| SUPER, | |
| TRAIT, | |
| TRUE_LITERAL, | |
| TRY, | |
| TYPE, | |
| TYPEOF, | |
| UNSAFE, | |
| UNSIZED, | |
| USE, | |
| VIRTUAL, | |
| WHERE, | |
| WHILE, | |
| YIELD}}}; | |
| location_t error_locus = match.get_match_locus (); | |
| std::string kind_str = "fragment"; | |
| auto &allowed_toks = follow_set[last_match.get_frag_spec ().get_kind ()]; | |
| // There are two behaviors to handle here: If the follow-up match is a token, | |
| // we want to check if it is allowed. | |
| // If it is a fragment, repetition or matcher then we know that it will be | |
| // an error. | |
| // For repetitions and matchers we want to extract a proper location to report | |
| // the error. | |
| switch (match.get_macro_match_type ()) | |
| { | |
| case AST::MacroMatch::Tok: | |
| { | |
| auto tok = static_cast<const AST::Token *> (&match); | |
| if (contains (allowed_toks, tok->get_id ())) | |
| return true; | |
| kind_str = "token `" | |
| + std::string (get_token_description (tok->get_id ())) + "`"; | |
| error_locus = tok->get_match_locus (); | |
| break; | |
| } | |
| break; | |
| case AST::MacroMatch::Repetition: | |
| { | |
| auto repetition | |
| = static_cast<const AST::MacroMatchRepetition *> (&match); | |
| auto &matches = repetition->get_matches (); | |
| auto first_frag = get_front_ptr (matches); | |
| if (first_frag) | |
| return peculiar_fragment_match_compatible (last_match, *first_frag); | |
| break; | |
| } | |
| case AST::MacroMatch::Matcher: | |
| { | |
| auto matcher = static_cast<const AST::MacroMatcher *> (&match); | |
| auto first_token = matcher->get_delim_type (); | |
| TokenId delim_id; | |
| switch (first_token) | |
| { | |
| case AST::PARENS: | |
| delim_id = LEFT_PAREN; | |
| break; | |
| case AST::SQUARE: | |
| delim_id = LEFT_SQUARE; | |
| break; | |
| case AST::CURLY: | |
| delim_id = LEFT_CURLY; | |
| break; | |
| default: | |
| rust_unreachable (); | |
| break; | |
| } | |
| if (contains (allowed_toks, delim_id)) | |
| return true; | |
| kind_str = "token `" + std::string (get_token_description (delim_id)) | |
| + "` at start of matcher"; | |
| error_locus = matcher->get_match_locus (); | |
| break; | |
| } | |
| case AST::MacroMatch::Fragment: | |
| { | |
| auto last_spec = last_match.get_frag_spec (); | |
| auto fragment = static_cast<const AST::MacroMatchFragment *> (&match); | |
| if (last_spec.has_follow_set_fragment_restrictions ()) | |
| return peculiar_fragment_match_compatible_fragment ( | |
| last_spec, fragment->get_frag_spec (), match.get_match_locus ()); | |
| } | |
| break; | |
| } | |
| rust_error_at (error_locus, "%s is not allowed after %qs fragment", | |
| kind_str.c_str (), | |
| last_match.get_frag_spec ().as_string ().c_str ()); | |
| auto allowed_toks_str | |
| = "`" + std::string (get_token_description (allowed_toks[0])) + "`"; | |
| for (size_t i = 1; i < allowed_toks.size (); i++) | |
| allowed_toks_str | |
| += ", `" + std::string (get_token_description (allowed_toks[i])) + "`"; | |
| rust_inform (error_locus, "allowed tokens are %s", allowed_toks_str.c_str ()); | |
| return false; | |
| } | |
| bool | |
| is_match_compatible (const AST::MacroMatch &last_match, | |
| const AST::MacroMatch &match) | |
| { | |
| const AST::MacroMatch *new_last = nullptr; | |
| // We want to "extract" the concerning matches. In cases such as matchers and | |
| // repetitions, we actually store multiple matchers, but are only concerned | |
| // about the follow-set ambiguities of certain elements. | |
| // There are some cases where we can short-circuit the algorithm: There will | |
| // never be restrictions on token literals, or on certain fragments which do | |
| // not have a set of follow-restrictions. | |
| switch (last_match.get_macro_match_type ()) | |
| { | |
| // This is our main stop condition: When we are finally looking at the | |
| // last match (or its actual last component), and it is a fragment, it | |
| // may contain some follow up restrictions. | |
| case AST::MacroMatch::Fragment: | |
| { | |
| auto fragment | |
| = static_cast<const AST::MacroMatchFragment *> (&last_match); | |
| if (fragment->get_frag_spec ().has_follow_set_restrictions ()) | |
| return peculiar_fragment_match_compatible (*fragment, match); | |
| else | |
| return true; | |
| } | |
| case AST::MacroMatch::Repetition: | |
| { | |
| // A repetition on the left hand side means we want to make sure the | |
| // last match of the repetition is compatible with the new match | |
| auto repetition | |
| = static_cast<const AST::MacroMatchRepetition *> (&last_match); | |
| new_last = get_back_ptr (repetition->get_matches ()); | |
| // If there are no matches in the matcher, then it can be followed by | |
| // anything | |
| if (!new_last) | |
| return true; | |
| break; | |
| } | |
| case AST::MacroMatch::Matcher: | |
| case AST::MacroMatch::Tok: | |
| return true; | |
| } | |
| rust_assert (new_last); | |
| // We check recursively until we find a terminating condition | |
| // FIXME: Does expansion depth/limit matter here? | |
| return is_match_compatible (*new_last, match); | |
| } | |
| namespace LiteralResolve { | |
| PrimitiveCoreType | |
| resolve_literal_suffix (const_TokenPtr token) | |
| { | |
| const std::string &raw_str = token->get_str (); | |
| uint16_t start = token->get_suffix_start (); | |
| if (start >= raw_str.length ()) | |
| { | |
| return token->is_pure_decimal () ? CORETYPE_PURE_DECIMAL | |
| : CORETYPE_UNKNOWN; | |
| } | |
| std::string suffix = raw_str.substr (start); | |
| if (suffix == "f32" || suffix == "f64") | |
| { | |
| auto base = token->get_literal_base (); | |
| if (base == IntegerLiteralBase::Hex || base == IntegerLiteralBase::Octal | |
| || base == IntegerLiteralBase::Binary) | |
| { | |
| rust_error_at (token->get_locus (), | |
| "invalid type suffix %qs for integer (%s) literal", | |
| suffix.c_str (), | |
| base == IntegerLiteralBase::Hex | |
| ? "hex" | |
| : (base == IntegerLiteralBase::Octal | |
| ? "octal" | |
| : (base == IntegerLiteralBase::Binary | |
| ? "binary" | |
| : "<insert unknown base>"))); | |
| return CORETYPE_UNKNOWN; | |
| } | |
| return suffix == "f32" ? CORETYPE_F32 : CORETYPE_F64; | |
| } | |
| else if (suffix == "i8") | |
| { | |
| return CORETYPE_I8; | |
| } | |
| else if (suffix == "i16") | |
| { | |
| return CORETYPE_I16; | |
| } | |
| else if (suffix == "i32") | |
| { | |
| return CORETYPE_I32; | |
| } | |
| else if (suffix == "i64") | |
| { | |
| return CORETYPE_I64; | |
| } | |
| else if (suffix == "i128") | |
| { | |
| return CORETYPE_I128; | |
| } | |
| else if (suffix == "isize") | |
| { | |
| return CORETYPE_ISIZE; | |
| } | |
| else if (suffix == "u8") | |
| { | |
| return CORETYPE_U8; | |
| } | |
| else if (suffix == "u16") | |
| { | |
| return CORETYPE_U16; | |
| } | |
| else if (suffix == "u32") | |
| { | |
| return CORETYPE_U32; | |
| } | |
| else if (suffix == "u64") | |
| { | |
| return CORETYPE_U64; | |
| } | |
| else if (suffix == "u128") | |
| { | |
| return CORETYPE_U128; | |
| } | |
| else if (suffix == "usize") | |
| { | |
| return CORETYPE_USIZE; | |
| } | |
| else | |
| rust_error_at (token->get_locus (), "invalid suffix %qs for number literal", | |
| suffix.c_str ()); | |
| return CORETYPE_UNKNOWN; | |
| } | |
| std::string | |
| evaluate_integer_literal (const_TokenPtr token) | |
| { | |
| const std::string &raw_str = token->get_str (); | |
| uint16_t suffix_start = token->get_suffix_start (); | |
| std::string num_str = raw_str.substr (0, suffix_start); | |
| num_str.erase (std::remove (num_str.begin (), num_str.end (), '_'), | |
| num_str.end ()); | |
| auto base = token->get_literal_base (); | |
| if (base == IntegerLiteralBase::Decimal || base == IntegerLiteralBase::None) | |
| return num_str; | |
| num_str = num_str.substr (2); | |
| int base_int = 10; | |
| if (base == IntegerLiteralBase::Hex) | |
| base_int = 16; | |
| else if (base == IntegerLiteralBase::Octal) | |
| base_int = 8; | |
| else if (base == IntegerLiteralBase::Binary) | |
| base_int = 2; | |
| mpz_t dec_num; | |
| mpz_init (dec_num); | |
| mpz_set_str (dec_num, num_str.c_str (), base_int); | |
| char *s = mpz_get_str (NULL, 10, dec_num); | |
| std::string dec_str = s; | |
| free (s); | |
| mpz_clear (dec_num); | |
| return dec_str; | |
| } | |
| std::string | |
| evaluate_float_literal (const_TokenPtr token) | |
| { | |
| std::string raw_str | |
| = token->get_str ().substr (0, token->get_suffix_start ()); | |
| raw_str.erase (std::remove (raw_str.begin (), raw_str.end (), '_'), | |
| raw_str.end ()); | |
| return raw_str; | |
| } | |
| } // namespace LiteralResolve | |
| } // namespace Rust |