diff options
author | Arthur Cohen <arthur.cohen@embecosm.com> | 2022-03-18 16:20:47 +0100 |
---|---|---|
committer | Arthur Cohen <arthur.cohen@embecosm.com> | 2022-03-23 09:56:23 +0100 |
commit | 35ca685200830626e5abd623f65a850649beace2 (patch) | |
tree | aed3eeb63995c6cced4bdeed0f327ceaf3d618e8 /gcc/rust/parse/rust-parse.cc | |
parent | cc6e405912c83aee41efd3015d9157cdbe9134fe (diff) | |
download | gcc-35ca685200830626e5abd623f65a850649beace2.zip gcc-35ca685200830626e5abd623f65a850649beace2.tar.gz gcc-35ca685200830626e5abd623f65a850649beace2.tar.bz2 |
macros: Add base functions to check for follow-set ambiguities
Rust does not allow for all macro fragments to be followed by any kind
of tokens: We must check tokens following those fragments that might
contain restrictions and make sure that they are allowed, conforming to
the Macro Follow-Set Ambiguity specification
Co-authored-by: philberty <philip.herron@embecosm.com>
macro-frag-spec: Transform enum into a class
This allows us to add methods on the fragment specifier, which are
needed to make sure that follow-set ambiguities are respected
tests: Add tests for forbidden follow-up tokens
This also fix a test that was previously accepted but invalid: rustc
also rejected it
Diffstat (limited to 'gcc/rust/parse/rust-parse.cc')
-rw-r--r-- | gcc/rust/parse/rust-parse.cc | 141 |
1 files changed, 141 insertions, 0 deletions
diff --git a/gcc/rust/parse/rust-parse.cc b/gcc/rust/parse/rust-parse.cc index f995e4b..16ed4a0 100644 --- a/gcc/rust/parse/rust-parse.cc +++ b/gcc/rust/parse/rust-parse.cc @@ -92,4 +92,145 @@ extract_module_path (const AST::AttrVec &inner_attrs, return path; } + +static bool +peculiar_fragment_match_compatible (AST::MacroMatchFragment &last_match, + 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}}, + }; + + Location error_locus = match.get_match_locus (); + + // 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<AST::Token *> (&match); + auto &allowed_toks + = follow_set[last_match.get_frag_spec ().get_kind ()]; + auto is_valid = std::find (allowed_toks.begin (), allowed_toks.end (), + tok->get_id ()) + != allowed_toks.end (); + if (!is_valid) + // FIXME: Add hint about allowed fragments + rust_error_at (tok->get_match_locus (), + "token %<%s%> is not allowed after %<%s%> fragment", + tok->get_str ().c_str (), + last_match.get_frag_spec ().as_string ().c_str ()); + return is_valid; + } + break; + case AST::MacroMatch::Repetition: { + auto repetition = static_cast<AST::MacroMatchRepetition *> (&match); + auto &matches = repetition->get_matches (); + if (!matches.empty ()) + error_locus = matches.front ()->get_match_locus (); + break; + } + case AST::MacroMatch::Matcher: { + auto matcher = static_cast<AST::MacroMatcher *> (&match); + auto &matches = matcher->get_matches (); + if (!matches.empty ()) + error_locus = matches.front ()->get_match_locus (); + break; + } + default: + break; + } + + rust_error_at (error_locus, "fragment not allowed after %<%s%> fragment", + last_match.get_frag_spec ().as_string ().c_str ()); + + return false; +} + +/** + * Avoid UB by calling .front() and .back() on empty containers... + */ + +template <typename T> +static T * +get_back_ptr (std::vector<std::unique_ptr<T>> &values) +{ + if (values.empty ()) + return nullptr; + + return values.back ().get (); +} + +template <typename T> +static T * +get_front_ptr (std::vector<std::unique_ptr<T>> &values) +{ + if (values.empty ()) + return nullptr; + + return values.front ().get (); +} + +bool +is_match_compatible (AST::MacroMatch &last_match, AST::MacroMatch &match) +{ + 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<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<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: { + // Likewise for another matcher + auto matcher = static_cast<AST::MacroMatcher *> (&last_match); + new_last = get_back_ptr (matcher->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::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 Rust |