// Copyright (C) 2020-2024 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 "optional.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"
#include "rust-proc-macro-invoc-lexer.h"
#include "rust-token-converter.h"
#include "rust-ast-collector.h"
#include "rust-system.h"
#include "libproc_macro_internal/proc_macro.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,
};
virtual ~MatchedFragmentContainer () = default;
virtual Kind get_kind () const = 0;
virtual std::string as_string () const = 0;
/**
* Create a valid fragment matched zero times. This is useful for repetitions
* which allow the absence of a fragment, such as * and ?
*/
static std::unique_ptr zero ();
/**
* Create a valid fragment matched one time
*/
static std::unique_ptr
metavar (MatchedFragment fragment);
/**
* Add a matched fragment to the container
*/
void add_fragment (MatchedFragment fragment);
/**
* Add a matched fragment to the container
*/
void add_fragment (std::unique_ptr fragment);
// const std::string &get_fragment_name () const { return fragment_name; }
bool is_single_fragment () const { return get_kind () == Kind::MetaVar; }
MatchedFragment &get_single_fragment ();
std::vector> &get_fragments ();
};
class MatchedFragmentContainerMetaVar : public MatchedFragmentContainer
{
MatchedFragment fragment;
public:
MatchedFragmentContainerMetaVar (const MatchedFragment &fragment)
: fragment (fragment)
{}
MatchedFragment &get_fragment () { return fragment; }
virtual Kind get_kind () const { return Kind::MetaVar; }
virtual std::string as_string () const { return fragment.as_string (); }
};
class MatchedFragmentContainerRepetition : public MatchedFragmentContainer
{
std::vector> fragments;
public:
MatchedFragmentContainerRepetition () {}
size_t get_match_amount () const { return fragments.size (); }
std::vector> &get_fragments ()
{
return fragments;
}
/**
* Add a matched fragment to the container
*/
void add_fragment (MatchedFragment fragment)
{
add_fragment (metavar (fragment));
}
/**
* Add a matched fragment to the container
*/
void add_fragment (std::unique_ptr fragment)
{
fragments.emplace_back (std::move (fragment));
}
virtual Kind get_kind () const { return Kind::Repetition; }
virtual std::string as_string () const
{
std::string acc = "[";
for (size_t i = 0; i < fragments.size (); i++)
{
if (i)
acc += " ";
acc += fragments[i]->as_string ();
}
acc += "]";
return acc;
}
};
class SubstitutionScope
{
public:
SubstitutionScope () : stack () {}
void push () { stack.push_back ({}); }
std::map> pop ()
{
auto top = std::move (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.emplace (fragment.fragment_ident,
MatchedFragmentContainer::metavar (fragment));
else
rust_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 ())
it = current_map
.emplace (fragment.fragment_ident,
std::unique_ptr (
new MatchedFragmentContainerRepetition ()))
.first;
it->second->add_fragment (fragment);
}
/**
* Append a new matched fragment to a repetition into the current substitution
* map
*/
void append_fragment (std::string ident,
std::unique_ptr fragment)
{
auto ¤t_map = stack.back ();
auto it = current_map.find (ident);
if (it == current_map.end ())
it = current_map
.emplace (ident, std::unique_ptr (
new MatchedFragmentContainerRepetition ()))
.first;
it->second->add_fragment (std::move (fragment));
}
void insert_matches (std::string key,
std::unique_ptr matches)
{
auto ¤t_map = stack.back ();
auto it = current_map.find (key);
rust_assert (it == current_map.end ());
current_map.emplace (std::move (key), std::move (matches));
}
private:
std::vector>>
stack;
};
// Object used to store shared data (between functions) for macro expansion.
struct MacroExpander
{
enum class ContextType
{
ITEM,
STMT,
EXPR,
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_t locus, AST::MacroInvocData &invoc,
AST::MacroRulesDefinition &rules_def,
bool semicolon);
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);
void match_repetition_skipped_metavars (AST::MacroMatch &);
void match_repetition_skipped_metavars (AST::MacroMatchFragment &);
void match_repetition_skipped_metavars (AST::MacroMatchRepetition &);
void match_repetition_skipped_metavars (AST::MacroMatcher &);
bool match_repetition (Parser &parser,
AST::MacroMatchRepetition &rep);
bool match_matcher (Parser &parser,
AST::MacroMatcher &matcher, bool in_repetition = false,
bool match_delim = true);
/**
* 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;
}
void import_proc_macros (std::string extern_crate);
template
AST::Fragment expand_derive_proc_macro (T &item, AST::SimplePath &path)
{
tl::optional macro
= mappings->lookup_derive_proc_macro_invocation (path);
if (!macro.has_value ())
{
rust_error_at (path.get_locus (), "macro not found");
return AST::Fragment::create_error ();
}
AST::TokenCollector collector;
collector.visit (item);
auto c = collector.collect_tokens ();
std::vector vec (c.cbegin (), c.cend ());
return parse_proc_macro_output (
macro.value ().get_handle () (convert (vec)));
}
template
AST::Fragment expand_bang_proc_macro (T &item,
AST::MacroInvocation &invocation)
{
tl::optional macro
= mappings->lookup_bang_proc_macro_invocation (invocation);
if (!macro.has_value ())
{
rust_error_at (invocation.get_locus (), "macro not found");
return AST::Fragment::create_error ();
}
AST::TokenCollector collector;
collector.visit (item);
auto c = collector.collect_tokens ();
std::vector vec (c.cbegin (), c.cend ());
return parse_proc_macro_output (
macro.value ().get_handle () (convert (vec)));
}
template
AST::Fragment expand_attribute_proc_macro (T &item, AST::SimplePath &path)
{
tl::optional macro
= mappings->lookup_attribute_proc_macro_invocation (path);
if (!macro.has_value ())
{
rust_error_at (path.get_locus (), "macro not found");
return AST::Fragment::create_error ();
}
AST::TokenCollector collector;
collector.visit (item);
auto c = collector.collect_tokens ();
std::vector vec (c.cbegin (), c.cend ());
// FIXME: Handle attributes
return parse_proc_macro_output (
macro.value ().get_handle () (ProcMacro::TokenStream::make_tokenstream (),
convert (vec)));
}
/**
* 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; }
tl::optional &get_last_definition ()
{
return last_def;
}
tl::optional &get_last_invocation ()
{
return last_invoc;
}
private:
AST::Fragment parse_proc_macro_output (ProcMacro::TokenStream ts);
AST::Crate &crate;
Session &session;
SubstitutionScope sub_stack;
std::vector context;
AST::Fragment expanded_fragment;
bool has_changed_flag;
tl::optional last_def;
tl::optional last_invoc;
public:
Resolver::Resolver *resolver;
Analysis::Mappings *mappings;
};
} // namespace Rust
#endif