// Copyright (C) 2020-2023 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
// .
#ifndef RUST_MACRO_EXPAND_H
#define RUST_MACRO_EXPAND_H
#include "rust-buffered-queue.h"
#include "rust-parse.h"
#include "rust-token.h"
#include "rust-ast.h"
#include "rust-macro.h"
#include "rust-hir-map.h"
#include "rust-early-name-resolver.h"
#include "rust-name-resolver.h"
#include "rust-macro-invoc-lexer.h"
// Provides objects and method prototypes for macro expansion
namespace Rust {
// forward decls for AST
namespace AST {
class MacroInvocation;
}
// Object used to store configuration data for macro expansion.
// NOTE: Keep all these items complying with the latest rustc.
struct ExpansionCfg
{
// features?
// TODO: Add `features' when we have it.
unsigned int recursion_limit = 1024;
bool trace_mac = false; // trace macro
bool should_test = false; // strip #[test] nodes if false
bool keep_macs = false; // keep macro definitions
std::string crate_name = "";
};
struct MatchedFragment
{
std::string fragment_ident;
size_t token_offset_begin;
size_t token_offset_end;
MatchedFragment (std::string identifier, size_t token_offset_begin,
size_t token_offset_end)
: fragment_ident (identifier), token_offset_begin (token_offset_begin),
token_offset_end (token_offset_end)
{}
/**
* Empty constructor for uninitialized fragments
*/
MatchedFragment () : MatchedFragment ("", 0, 0) {}
std::string as_string () const
{
return fragment_ident + "=" + std::to_string (token_offset_begin) + ":"
+ std::to_string (token_offset_end);
}
};
class MatchedFragmentContainer
{
public:
// Does the container refer to a simple metavariable, different from a
// repetition repeated once
enum class Kind
{
MetaVar,
Repetition,
};
MatchedFragmentContainer (std::vector fragments,
Kind kind = Kind::Repetition)
: fragments (fragments), kind (kind)
{}
/**
* Create a valid fragment matched zero times. This is useful for repetitions
* which allow the absence of a fragment, such as * and ?
*/
static MatchedFragmentContainer zero ()
{
return MatchedFragmentContainer ({});
}
/**
* Create a valid fragment matched one time
*/
static MatchedFragmentContainer metavar (MatchedFragment fragment)
{
return MatchedFragmentContainer ({fragment}, Kind::MetaVar);
}
/**
* Add a matched fragment to the container
*/
void add_fragment (MatchedFragment fragment)
{
rust_assert (!is_single_fragment ());
fragments.emplace_back (fragment);
}
size_t get_match_amount () const { return fragments.size (); }
const std::vector &get_fragments () const
{
return fragments;
}
// const std::string &get_fragment_name () const { return fragment_name; }
bool is_single_fragment () const
{
return get_match_amount () == 1 && kind == Kind::MetaVar;
}
const MatchedFragment get_single_fragment () const
{
rust_assert (is_single_fragment ());
return fragments[0];
}
const Kind &get_kind () const { return kind; }
private:
/**
* Fragments matched `match_amount` times. This can be an empty vector
* in case having zero matches is allowed (i.e ? or * operators)
*/
std::vector fragments;
Kind kind;
};
class SubstitutionScope
{
public:
SubstitutionScope () : stack () {}
void push () { stack.push_back ({}); }
std::map pop ()
{
auto top = stack.back ();
stack.pop_back ();
return top;
}
std::map &peek ()
{
return stack.back ();
}
/**
* Insert a new matched metavar into the current substitution map
*/
void insert_metavar (MatchedFragment fragment)
{
auto ¤t_map = stack.back ();
auto it = current_map.find (fragment.fragment_ident);
if (it == current_map.end ())
current_map.insert ({fragment.fragment_ident,
MatchedFragmentContainer::metavar (fragment)});
else
gcc_unreachable ();
}
/**
* Append a new matched fragment to a repetition into the current substitution
* map
*/
void append_fragment (MatchedFragment fragment)
{
auto ¤t_map = stack.back ();
auto it = current_map.find (fragment.fragment_ident);
if (it == current_map.end ())
current_map.insert (
{fragment.fragment_ident, MatchedFragmentContainer ({fragment})});
else
it->second.add_fragment (fragment);
}
void insert_matches (std::string key, MatchedFragmentContainer matches)
{
auto ¤t_map = stack.back ();
auto it = current_map.find (key);
rust_assert (it == current_map.end ());
current_map.insert ({key, matches});
}
private:
std::vector> stack;
};
// Object used to store shared data (between functions) for macro expansion.
struct MacroExpander
{
enum ContextType
{
ITEM,
BLOCK,
EXTERN,
TYPE,
TRAIT,
IMPL,
TRAIT_IMPL,
};
ExpansionCfg cfg;
unsigned int expansion_depth = 0;
MacroExpander (AST::Crate &crate, ExpansionCfg cfg, Session &session)
: cfg (cfg), crate (crate), session (session),
sub_stack (SubstitutionScope ()),
expanded_fragment (AST::Fragment::create_error ()),
has_changed_flag (false), resolver (Resolver::Resolver::get ()),
mappings (Analysis::Mappings::get ())
{}
~MacroExpander () = default;
// Expands all macros in the crate passed in.
void expand_crate ();
/**
* Expand the eager invocations contained within a builtin macro invocation.
* Called by `expand_invoc` when expanding builtin invocations.
*/
void expand_eager_invocations (AST::MacroInvocation &invoc);
/* Expands a macro invocation - possibly make both
* have similar duck-typed interface and use templates?*/
// should this be public or private?
void expand_invoc (AST::MacroInvocation &invoc, bool has_semicolon);
// Expands a single declarative macro.
AST::Fragment expand_decl_macro (Location locus, AST::MacroInvocData &invoc,
AST::MacroRulesDefinition &rules_def,
bool semicolon);
void expand_cfg_attrs (AST::AttrVec &attrs);
bool fails_cfg (const AST::AttrVec &attr) const;
bool fails_cfg_with_expand (AST::AttrVec &attrs) const;
bool depth_exceeds_recursion_limit () const;
bool try_match_rule (AST::MacroRule &match_rule,
AST::DelimTokenTree &invoc_token_tree);
AST::Fragment transcribe_rule (
AST::MacroRule &match_rule, AST::DelimTokenTree &invoc_token_tree,
std::map &matched_fragments,
bool semicolon, ContextType ctx);
bool match_fragment (Parser &parser,
AST::MacroMatchFragment &fragment);
bool match_token (Parser &parser, AST::Token &token);
bool match_repetition (Parser &parser,
AST::MacroMatchRepetition &rep);
bool match_matcher (Parser &parser,
AST::MacroMatcher &matcher, bool in_repetition = false);
/**
* Match any amount of matches
*
* @param parser Parser to use for matching
* @param rep Repetition to try and match
* @param match_amount Reference in which to store the ammount of succesful
* and valid matches
*
* @param lo_bound Lower bound of the matcher. When specified, the matcher
* will only succeed if it parses at *least* `lo_bound` fragments. If
* unspecified, the matcher could succeed when parsing 0 fragments.
*
* @param hi_bound Higher bound of the matcher. When specified, the matcher
* will only succeed if it parses *less than* `hi_bound` fragments. If
* unspecified, the matcher could succeed when parsing an infinity of
* fragments.
*
* @return true if matching was successful and within the given limits, false
* otherwise
*/
bool match_n_matches (Parser &parser,
AST::MacroMatchRepetition &rep, size_t &match_amount,
size_t lo_bound = 0, size_t hi_bound = 0);
void push_context (ContextType t) { context.push_back (t); }
ContextType pop_context ()
{
rust_assert (!context.empty ());
ContextType t = context.back ();
context.pop_back ();
return t;
}
ContextType peek_context () { return context.back (); }
void set_expanded_fragment (AST::Fragment &&fragment)
{
if (!fragment.is_error ())
has_changed_flag = true;
expanded_fragment = std::move (fragment);
}
AST::Fragment take_expanded_fragment ()
{
auto fragment = std::move (expanded_fragment);
expanded_fragment = AST::Fragment::create_error ();
return fragment;
}
/**
* Has the MacroExpander expanded a macro since its state was last reset?
*/
bool has_changed () const { return has_changed_flag; }
/**
* Reset the expander's "changed" state. This function should be executed at
* each iteration in a fixed point loop
*/
void reset_changed_state () { has_changed_flag = false; }
AST::MacroRulesDefinition *get_last_definition () { return last_def; }
AST::MacroInvocation *get_last_invocation () { return last_invoc; }
private:
AST::Crate &crate;
Session &session;
SubstitutionScope sub_stack;
std::vector context;
AST::Fragment expanded_fragment;
bool has_changed_flag;
AST::MacroRulesDefinition *last_def;
AST::MacroInvocation *last_invoc;
public:
Resolver::Resolver *resolver;
Analysis::Mappings *mappings;
};
} // namespace Rust
#endif