| // Copyright (C) 2025-2026 Free Software Foundation, Inc. |
| |
| // 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/>. |
| |
| /* DO NOT INCLUDE ANYWHERE - this is automatically included |
| * by rust-parse-impl.h |
| * This is also the reason why there are no include guards. */ |
| |
| #include "rust-parse.h" |
| |
| namespace Rust { |
| |
| // Parses a semi-coloned (except for full block) macro invocation item. |
| template <typename ManagedTokenSource> |
| std::unique_ptr<AST::MacroInvocation> |
| Parser<ManagedTokenSource>::parse_macro_invocation_semi ( |
| AST::AttrVec outer_attrs) |
| { |
| location_t macro_locus = lexer.peek_token ()->get_locus (); |
| auto path = parse_simple_path (); |
| if (!path) |
| return nullptr; |
| |
| if (!skip_token (EXCLAM)) |
| { |
| // skip after somewhere? |
| return nullptr; |
| } |
| |
| // save delim type to ensure it is reused later |
| AST::DelimType delim_type = AST::PARENS; |
| |
| // Map tokens to DelimType |
| const_TokenPtr t = lexer.peek_token (); |
| switch (t->get_id ()) |
| { |
| case LEFT_PAREN: |
| delim_type = AST::PARENS; |
| break; |
| case LEFT_SQUARE: |
| delim_type = AST::SQUARE; |
| break; |
| case LEFT_CURLY: |
| delim_type = AST::CURLY; |
| break; |
| default: |
| add_error (Error (t->get_locus (), |
| "unexpected token %qs - expecting delimiters (for a " |
| "macro invocation semi body)", |
| t->get_token_description ())); |
| |
| return nullptr; |
| } |
| location_t tok_tree_locus = t->get_locus (); |
| lexer.skip_token (); |
| |
| // parse actual token trees |
| std::vector<std::unique_ptr<AST::TokenTree>> token_trees; |
| auto delim_open |
| = std::unique_ptr<AST::Token> (new AST::Token (std::move (t))); |
| token_trees.push_back (std::move (delim_open)); |
| |
| t = lexer.peek_token (); |
| // parse token trees until the initial delimiter token is found again |
| while (!Parse::Utils::token_id_matches_delims (t->get_id (), delim_type) |
| && t->get_id () != END_OF_FILE) |
| { |
| auto tree = parse_token_tree (); |
| if (!tree) |
| return nullptr; |
| |
| token_trees.push_back (std::move (tree.value ())); |
| |
| t = lexer.peek_token (); |
| } |
| auto delim_close |
| = std::unique_ptr<AST::Token> (new AST::Token (std::move (t))); |
| token_trees.push_back (std::move (delim_close)); |
| |
| AST::DelimTokenTree delim_tok_tree (delim_type, std::move (token_trees), |
| tok_tree_locus); |
| AST::MacroInvocData invoc_data (std::move (path.value ()), |
| std::move (delim_tok_tree)); |
| |
| // parse end delimiters |
| t = lexer.peek_token (); |
| if (Parse::Utils::token_id_matches_delims (t->get_id (), delim_type)) |
| { |
| // tokens match opening delimiter, so skip. |
| lexer.skip_token (); |
| |
| if (delim_type != AST::CURLY) |
| { |
| // skip semicolon at end of non-curly macro invocation semis |
| if (!skip_token (SEMICOLON)) |
| { |
| // as this is the end, allow recovery (probably) - may change |
| |
| return AST::MacroInvocation::Regular (std::move (invoc_data), |
| std::move (outer_attrs), |
| macro_locus, true); |
| } |
| } |
| |
| // DEBUG: |
| rust_debug ("skipped token is '%s', next token (current peek) is '%s'", |
| t->get_token_description (), |
| lexer.peek_token ()->get_token_description ()); |
| |
| return AST::MacroInvocation::Regular (std::move (invoc_data), |
| std::move (outer_attrs), |
| macro_locus, true); |
| } |
| else |
| { |
| // tokens don't match opening delimiters, so produce error |
| Error error (t->get_locus (), |
| "unexpected token %qs - expecting closing delimiter %qs " |
| "(for a macro invocation semi)", |
| t->get_token_description (), |
| (delim_type == AST::PARENS |
| ? ")" |
| : (delim_type == AST::SQUARE ? "]" : "}"))); |
| add_error (std::move (error)); |
| |
| /* return empty macro invocation despite possibly parsing mostly valid one |
| * - TODO is this a good idea? */ |
| return nullptr; |
| } |
| } |
| |
| // Parses a non-semicoloned macro invocation (i.e. as pattern or expression). |
| template <typename ManagedTokenSource> |
| std::unique_ptr<AST::MacroInvocation> |
| Parser<ManagedTokenSource>::parse_macro_invocation (AST::AttrVec outer_attrs) |
| { |
| // parse macro path |
| auto macro_path = parse_simple_path (); |
| if (!macro_path) |
| { |
| Error error (lexer.peek_token ()->get_locus (), |
| "failed to parse macro invocation path"); |
| add_error (std::move (error)); |
| |
| // skip? |
| return nullptr; |
| } |
| |
| if (!skip_token (EXCLAM)) |
| { |
| // skip after somewhere? |
| return nullptr; |
| } |
| |
| // parse internal delim token tree |
| auto delim_tok_tree = parse_delim_token_tree (); |
| if (!delim_tok_tree) |
| return nullptr; |
| |
| location_t macro_locus = macro_path->get_locus (); |
| |
| return AST::MacroInvocation::Regular ( |
| AST::MacroInvocData (std::move (macro_path.value ()), |
| std::move (delim_tok_tree.value ())), |
| std::move (outer_attrs), macro_locus); |
| } |
| |
| // Parses a macro rule definition - does not parse semicolons. |
| template <typename ManagedTokenSource> |
| AST::MacroRule |
| Parser<ManagedTokenSource>::parse_macro_rule () |
| { |
| location_t locus = lexer.peek_token ()->get_locus (); |
| |
| // parse macro matcher |
| AST::MacroMatcher matcher = parse_macro_matcher (); |
| |
| if (matcher.is_error ()) |
| return AST::MacroRule::create_error (locus); |
| |
| if (!skip_token (MATCH_ARROW)) |
| { |
| // skip after somewhere? |
| return AST::MacroRule::create_error (locus); |
| } |
| |
| // parse transcriber (this is just a delim token tree) |
| location_t token_tree_loc = lexer.peek_token ()->get_locus (); |
| auto delim_token_tree = parse_delim_token_tree (); |
| if (!delim_token_tree) |
| return AST::MacroRule::create_error (token_tree_loc); |
| |
| AST::MacroTranscriber transcriber (delim_token_tree.value (), token_tree_loc); |
| |
| return AST::MacroRule (std::move (matcher), std::move (transcriber), locus); |
| } |
| |
| // Parses a macro matcher (part of a macro rule definition). |
| template <typename ManagedTokenSource> |
| AST::MacroMatcher |
| Parser<ManagedTokenSource>::parse_macro_matcher () |
| { |
| // save delim type to ensure it is reused later |
| AST::DelimType delim_type = AST::PARENS; |
| |
| // DEBUG |
| rust_debug ("begun parsing macro matcher"); |
| |
| // Map tokens to DelimType |
| const_TokenPtr t = lexer.peek_token (); |
| location_t locus = t->get_locus (); |
| switch (t->get_id ()) |
| { |
| case LEFT_PAREN: |
| delim_type = AST::PARENS; |
| break; |
| case LEFT_SQUARE: |
| delim_type = AST::SQUARE; |
| break; |
| case LEFT_CURLY: |
| delim_type = AST::CURLY; |
| break; |
| default: |
| add_error (Error ( |
| t->get_locus (), |
| "unexpected token %qs - expecting delimiters (for a macro matcher)", |
| t->get_token_description ())); |
| |
| return AST::MacroMatcher::create_error (t->get_locus ()); |
| } |
| lexer.skip_token (); |
| |
| // parse actual macro matches |
| std::vector<std::unique_ptr<AST::MacroMatch>> matches; |
| // Set of possible preceding macro matches to make sure follow-set |
| // restrictions are respected. |
| // TODO: Consider using std::reference_wrapper instead of raw pointers? |
| std::vector<const AST::MacroMatch *> last_matches; |
| |
| t = lexer.peek_token (); |
| // parse token trees until the initial delimiter token is found again |
| while (!Parse::Utils::token_id_matches_delims (t->get_id (), delim_type)) |
| { |
| std::unique_ptr<AST::MacroMatch> match = parse_macro_match (); |
| |
| if (match == nullptr) |
| { |
| Error error ( |
| t->get_locus (), |
| "failed to parse macro match for macro matcher - found %qs", |
| t->get_token_description ()); |
| add_error (std::move (error)); |
| |
| return AST::MacroMatcher::create_error (t->get_locus ()); |
| } |
| |
| if (matches.size () > 0) |
| { |
| const auto *last_match = matches.back ().get (); |
| |
| // We want to check if we are dealing with a zeroable repetition |
| bool zeroable = false; |
| if (last_match->get_macro_match_type () |
| == AST::MacroMatch::MacroMatchType::Repetition) |
| { |
| auto repetition |
| = static_cast<const AST::MacroMatchRepetition *> (last_match); |
| |
| if (repetition->get_op () |
| != AST::MacroMatchRepetition::MacroRepOp::ONE_OR_MORE) |
| zeroable = true; |
| } |
| |
| if (!zeroable) |
| last_matches.clear (); |
| |
| last_matches.emplace_back (last_match); |
| |
| for (auto last : last_matches) |
| if (!is_match_compatible (*last, *match)) |
| return AST::MacroMatcher::create_error ( |
| match->get_match_locus ()); |
| } |
| |
| matches.push_back (std::move (match)); |
| |
| // DEBUG |
| rust_debug ("pushed back a match in macro matcher"); |
| |
| t = lexer.peek_token (); |
| } |
| |
| // parse end delimiters |
| t = lexer.peek_token (); |
| if (Parse::Utils::token_id_matches_delims (t->get_id (), delim_type)) |
| { |
| // tokens match opening delimiter, so skip. |
| lexer.skip_token (); |
| |
| return AST::MacroMatcher (delim_type, std::move (matches), locus); |
| } |
| else |
| { |
| // tokens don't match opening delimiters, so produce error |
| Error error (t->get_locus (), |
| "unexpected token %qs - expecting closing delimiter %qs " |
| "(for a macro matcher)", |
| t->get_token_description (), |
| (delim_type == AST::PARENS |
| ? ")" |
| : (delim_type == AST::SQUARE ? "]" : "}"))); |
| add_error (std::move (error)); |
| |
| /* return error macro matcher despite possibly parsing mostly correct one? |
| * TODO is this the best idea? */ |
| return AST::MacroMatcher::create_error (t->get_locus ()); |
| } |
| } |
| |
| // Parses a macro match (syntax match inside a matcher in a macro rule). |
| template <typename ManagedTokenSource> |
| std::unique_ptr<AST::MacroMatch> |
| Parser<ManagedTokenSource>::parse_macro_match () |
| { |
| // branch based on token available |
| const_TokenPtr t = lexer.peek_token (); |
| switch (t->get_id ()) |
| { |
| case LEFT_PAREN: |
| case LEFT_SQUARE: |
| case LEFT_CURLY: |
| { |
| // must be macro matcher as delimited |
| AST::MacroMatcher matcher = parse_macro_matcher (); |
| if (matcher.is_error ()) |
| { |
| Error error (lexer.peek_token ()->get_locus (), |
| "failed to parse macro matcher in macro match"); |
| add_error (std::move (error)); |
| |
| return nullptr; |
| } |
| return std::unique_ptr<AST::MacroMatcher> ( |
| new AST::MacroMatcher (std::move (matcher))); |
| } |
| case DOLLAR_SIGN: |
| { |
| // have to do more lookahead to determine if fragment or repetition |
| const_TokenPtr t2 = lexer.peek_token (1); |
| switch (t2->get_id ()) |
| { |
| case IDENTIFIER: |
| case UNDERSCORE: |
| // macro fragment |
| return parse_macro_match_fragment (); |
| case LEFT_PAREN: |
| // macro repetition |
| return parse_macro_match_repetition (); |
| default: |
| if (token_id_is_keyword (t2->get_id ()) && t2->get_id () != CRATE) |
| { |
| // keyword as macro fragment |
| return parse_macro_match_fragment (); |
| } |
| else |
| { |
| // error: unrecognised |
| add_error (Error ( |
| t2->get_locus (), |
| "unrecognised token combination %<$%s%> at start of " |
| "macro match - did you mean %<$identifier%> or %<$(%>?", |
| t2->get_token_description ())); |
| |
| // skip somewhere? |
| return nullptr; |
| } |
| } |
| } |
| case RIGHT_PAREN: |
| case RIGHT_SQUARE: |
| case RIGHT_CURLY: |
| // not allowed |
| add_error (Error ( |
| t->get_locus (), |
| "closing delimiters like %qs are not allowed at the start of a macro " |
| "match", |
| t->get_token_description ())); |
| |
| // skip somewhere? |
| return nullptr; |
| default: |
| // just the token |
| lexer.skip_token (); |
| return std::unique_ptr<AST::Token> (new AST::Token (std::move (t))); |
| } |
| } |
| |
| // Parses a fragment macro match. |
| template <typename ManagedTokenSource> |
| std::unique_ptr<AST::MacroMatchFragment> |
| Parser<ManagedTokenSource>::parse_macro_match_fragment () |
| { |
| location_t fragment_locus = lexer.peek_token ()->get_locus (); |
| skip_token (DOLLAR_SIGN); |
| |
| Identifier ident; |
| auto identifier = lexer.peek_token (); |
| if (identifier->get_id () == UNDERSCORE) |
| ident = {Values::Keywords::UNDERSCORE, identifier->get_locus ()}; |
| else |
| ident = {identifier}; |
| |
| if (ident.empty ()) |
| { |
| Error error (lexer.peek_token ()->get_locus (), |
| "missing identifier in macro match fragment"); |
| add_error (std::move (error)); |
| |
| return nullptr; |
| } |
| skip_token (identifier->get_id ()); |
| |
| if (!skip_token (COLON)) |
| { |
| // skip after somewhere? |
| return nullptr; |
| } |
| |
| // get MacroFragSpec for macro |
| const_TokenPtr t = expect_token (IDENTIFIER); |
| if (t == nullptr) |
| return nullptr; |
| |
| AST::MacroFragSpec frag |
| = AST::MacroFragSpec::get_frag_spec_from_str (t->get_str ()); |
| if (frag.is_error ()) |
| { |
| Error error (t->get_locus (), |
| "invalid fragment specifier %qs in fragment macro match", |
| t->get_str ().c_str ()); |
| add_error (std::move (error)); |
| |
| return nullptr; |
| } |
| |
| return std::unique_ptr<AST::MacroMatchFragment> ( |
| new AST::MacroMatchFragment (std::move (ident), frag, fragment_locus)); |
| } |
| |
| // Parses a repetition macro match. |
| template <typename ManagedTokenSource> |
| std::unique_ptr<AST::MacroMatchRepetition> |
| Parser<ManagedTokenSource>::parse_macro_match_repetition () |
| { |
| skip_token (DOLLAR_SIGN); |
| skip_token (LEFT_PAREN); |
| |
| std::vector<std::unique_ptr<AST::MacroMatch>> matches; |
| |
| // parse required first macro match |
| std::unique_ptr<AST::MacroMatch> initial_match = parse_macro_match (); |
| if (initial_match == nullptr) |
| { |
| Error error ( |
| lexer.peek_token ()->get_locus (), |
| "could not parse required first macro match in macro match repetition"); |
| add_error (std::move (error)); |
| |
| // skip after somewhere? |
| return nullptr; |
| } |
| matches.push_back (std::move (initial_match)); |
| |
| // parse optional later macro matches |
| const_TokenPtr t = lexer.peek_token (); |
| while (t->get_id () != RIGHT_PAREN) |
| { |
| std::unique_ptr<AST::MacroMatch> match = parse_macro_match (); |
| |
| if (match == nullptr) |
| { |
| Error error (lexer.peek_token ()->get_locus (), |
| "failed to parse macro match in macro match repetition"); |
| add_error (std::move (error)); |
| |
| return nullptr; |
| } |
| |
| matches.push_back (std::move (match)); |
| |
| t = lexer.peek_token (); |
| } |
| |
| if (!skip_token (RIGHT_PAREN)) |
| { |
| // skip after somewhere? |
| return nullptr; |
| } |
| |
| t = lexer.peek_token (); |
| // see if separator token exists |
| std::unique_ptr<AST::Token> separator = nullptr; |
| switch (t->get_id ()) |
| { |
| // repetition operators |
| case ASTERISK: |
| case PLUS: |
| case QUESTION_MARK: |
| // delimiters |
| case LEFT_PAREN: |
| case LEFT_CURLY: |
| case LEFT_SQUARE: |
| case RIGHT_PAREN: |
| case RIGHT_CURLY: |
| case RIGHT_SQUARE: |
| // separator does not exist, so still null and don't skip token |
| break; |
| default: |
| // separator does exist |
| separator = std::unique_ptr<AST::Token> (new AST::Token (std::move (t))); |
| lexer.skip_token (); |
| break; |
| } |
| |
| // parse repetition operator |
| t = lexer.peek_token (); |
| AST::MacroMatchRepetition::MacroRepOp op = AST::MacroMatchRepetition::NONE; |
| switch (t->get_id ()) |
| { |
| case ASTERISK: |
| op = AST::MacroMatchRepetition::ANY; |
| lexer.skip_token (); |
| break; |
| case PLUS: |
| op = AST::MacroMatchRepetition::ONE_OR_MORE; |
| lexer.skip_token (); |
| break; |
| case QUESTION_MARK: |
| op = AST::MacroMatchRepetition::ZERO_OR_ONE; |
| lexer.skip_token (); |
| |
| if (separator != nullptr) |
| { |
| add_error ( |
| Error (separator->get_locus (), |
| "the %<?%> macro repetition operator does not take a " |
| "separator")); |
| separator = nullptr; |
| } |
| |
| break; |
| default: |
| add_error ( |
| Error (t->get_locus (), |
| "expected macro repetition operator (%<*%>, %<+%>, or %<?%>) in " |
| "macro match - found %qs", |
| t->get_token_description ())); |
| |
| // skip after somewhere? |
| return nullptr; |
| } |
| |
| return std::unique_ptr<AST::MacroMatchRepetition> ( |
| new AST::MacroMatchRepetition (std::move (matches), op, |
| std::move (separator), t->get_locus ())); |
| } |
| |
| /* Parses a macro invocation with a path in expression already parsed (but not |
| * '!' token). */ |
| template <typename ManagedTokenSource> |
| std::unique_ptr<AST::MacroInvocation> |
| Parser<ManagedTokenSource>::parse_macro_invocation_partial ( |
| AST::PathInExpression path, AST::AttrVec outer_attrs, |
| ParseRestrictions restrictions) |
| { |
| // macro invocation |
| if (!skip_token (EXCLAM)) |
| { |
| return nullptr; |
| } |
| |
| // convert PathInExpression to SimplePath - if this isn't possible, error |
| AST::SimplePath converted_path = path.as_simple_path (); |
| if (converted_path.is_empty ()) |
| { |
| Error error (lexer.peek_token ()->get_locus (), |
| "failed to parse simple path in macro invocation"); |
| add_error (std::move (error)); |
| |
| return nullptr; |
| } |
| |
| auto tok_tree = parse_delim_token_tree (); |
| if (!tok_tree) |
| return nullptr; |
| |
| rust_debug ("successfully parsed macro invocation (via partial)"); |
| |
| location_t macro_locus = converted_path.get_locus (); |
| |
| return AST::MacroInvocation::Regular ( |
| AST::MacroInvocData (std::move (converted_path), |
| std::move (tok_tree.value ())), |
| std::move (outer_attrs), macro_locus); |
| } |
| |
| } // namespace Rust |