// 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
// <http://www.gnu.org/licenses/>.

#ifndef RUST_AST_MACRO_H
#define RUST_AST_MACRO_H

#include "rust-system.h"
#include "rust-ast.h"
#include "rust-ast-fragment.h"
#include "rust-location.h"
#include "rust-item.h"
#include "rust-make-unique.h"

namespace Rust {
namespace AST {
class MacroFragSpec
{
public:
  enum Kind
  {
    BLOCK,
    EXPR,
    IDENT,
    ITEM,
    LIFETIME,
    LITERAL,
    META,
    PAT,
    PATH,
    STMT,
    TT,
    TY,
    VIS,
    INVALID // not really a specifier, but used to mark invalid one passed in
  };

  MacroFragSpec (Kind kind) : kind (kind) {}

  static MacroFragSpec get_frag_spec_from_str (const std::string &str)
  {
    if (str == "block")
      return MacroFragSpec (BLOCK);
    else if (str == "expr")
      return MacroFragSpec (EXPR);
    else if (str == "ident")
      return MacroFragSpec (IDENT);
    else if (str == "item")
      return MacroFragSpec (ITEM);
    else if (str == "lifetime")
      return MacroFragSpec (LIFETIME);
    else if (str == "literal")
      return MacroFragSpec (LITERAL);
    else if (str == "meta")
      return MacroFragSpec (META);
    else if (str == "pat" || str == "pat_param")
      return MacroFragSpec (PAT);
    else if (str == "path")
      return MacroFragSpec (PATH);
    else if (str == "stmt")
      return MacroFragSpec (STMT);
    else if (str == "tt")
      return MacroFragSpec (TT);
    else if (str == "ty")
      return MacroFragSpec (TY);
    else if (str == "vis")
      return MacroFragSpec (VIS);
    else
      {
	// error_at("invalid string '%s' used as fragment specifier",
	// str->c_str()));
	return MacroFragSpec (INVALID);
      }
  }

  Kind get_kind () const { return kind; }
  bool is_error () const { return kind == Kind::INVALID; }

  // Converts a frag spec enum item to a string form.
  std::string as_string () const
  {
    switch (kind)
      {
      case BLOCK:
	return "block";
      case EXPR:
	return "expr";
      case IDENT:
	return "ident";
      case ITEM:
	return "item";
      case LIFETIME:
	return "lifetime";
      case LITERAL:
	return "literal";
      case META:
	return "meta";
      case PAT:
	return "pat";
      case PATH:
	return "path";
      case STMT:
	return "stmt";
      case TT:
	return "tt";
      case TY:
	return "ty";
      case VIS:
	return "vis";
      case INVALID:
	return "INVALID_FRAG_SPEC";
      default:
	return "ERROR_MARK_STRING - unknown frag spec";
      }
  }

  bool has_follow_set_restrictions () const
  {
    switch (kind)
      {
      case EXPR:
      case STMT:
      case PAT:
      case PATH:
      case TY:
      case VIS:
	return true;
      default:
	return false;
      }
  }

  bool has_follow_set_fragment_restrictions () const
  {
    switch (kind)
      {
      case PAT:
      case TY:
      case VIS:
	return true;
      default:
	return false;
      }
  }

private:
  Kind kind;
};

// A macro match that has an identifier and fragment spec
class MacroMatchFragment : public MacroMatch
{
  Identifier ident;
  MacroFragSpec frag_spec;
  Location locus;

public:
  MacroMatchFragment (Identifier ident, MacroFragSpec frag_spec, Location locus)
    : ident (std::move (ident)), frag_spec (frag_spec), locus (locus)
  {}

  // Returns whether macro match fragment is in an error state.
  bool is_error () const
  {
    return frag_spec.get_kind () == MacroFragSpec::INVALID;
  }

  // Creates an error state macro match fragment.
  static MacroMatchFragment create_error (Location locus)
  {
    return MacroMatchFragment (std::string (""),
			       MacroFragSpec (MacroFragSpec::Kind::INVALID),
			       locus);
  }

  std::string as_string () const override;
  Location get_match_locus () const override { return locus; };

  void accept_vis (ASTVisitor &vis) override;

  MacroMatchType get_macro_match_type () const override
  {
    return MacroMatchType::Fragment;
  }

  Identifier get_ident () const { return ident; }
  const MacroFragSpec &get_frag_spec () const { return frag_spec; }

protected:
  /* Use covariance to implement clone function as returning this object rather
   * than base */
  MacroMatchFragment *clone_macro_match_impl () const override
  {
    return new MacroMatchFragment (*this);
  }
};

// A repetition macro match
class MacroMatchRepetition : public MacroMatch
{
public:
  enum MacroRepOp
  {
    NONE,
    ANY,
    ONE_OR_MORE,
    ZERO_OR_ONE,
  };

private:
  std::vector<std::unique_ptr<MacroMatch>> matches;
  MacroRepOp op;

  // bool has_sep;
  typedef Token MacroRepSep;
  // any token except delimiters and repetition operators
  std::unique_ptr<MacroRepSep> sep;
  Location locus;

public:
  // Returns whether macro match repetition has separator token.
  bool has_sep () const { return sep != nullptr; }

  MacroMatchRepetition (std::vector<std::unique_ptr<MacroMatch>> matches,
			MacroRepOp op, std::unique_ptr<MacroRepSep> sep,
			Location locus)
    : matches (std::move (matches)), op (op), sep (std::move (sep)),
      locus (locus)
  {}

  // Copy constructor with clone
  MacroMatchRepetition (MacroMatchRepetition const &other)
    : op (other.op), locus (other.locus)
  {
    // guard to protect from null pointer dereference
    if (other.sep != nullptr)
      sep = other.sep->clone_token ();

    matches.reserve (other.matches.size ());
    for (const auto &e : other.matches)
      matches.push_back (e->clone_macro_match ());
  }

  // Overloaded assignment operator to clone
  MacroMatchRepetition &operator= (MacroMatchRepetition const &other)
  {
    op = other.op;
    locus = other.locus;

    // guard to protect from null pointer dereference
    if (other.sep != nullptr)
      sep = other.sep->clone_token ();
    else
      sep = nullptr;

    matches.reserve (other.matches.size ());
    for (const auto &e : other.matches)
      matches.push_back (e->clone_macro_match ());

    return *this;
  }

  // move constructors
  MacroMatchRepetition (MacroMatchRepetition &&other) = default;
  MacroMatchRepetition &operator= (MacroMatchRepetition &&other) = default;

  std::string as_string () const override;
  Location get_match_locus () const override { return locus; };

  void accept_vis (ASTVisitor &vis) override;

  MacroMatchType get_macro_match_type () const override
  {
    return MacroMatchType::Repetition;
  }

  MacroRepOp get_op () const { return op; }
  const std::unique_ptr<MacroRepSep> &get_sep () const { return sep; }
  std::vector<std::unique_ptr<MacroMatch>> &get_matches () { return matches; }
  const std::vector<std::unique_ptr<MacroMatch>> &get_matches () const
  {
    return matches;
  }

protected:
  /* Use covariance to implement clone function as returning this object rather
   * than base */
  MacroMatchRepetition *clone_macro_match_impl () const override
  {
    return new MacroMatchRepetition (*this);
  }
};

// can't inline due to polymorphism
class MacroMatcher : public MacroMatch
{
  DelimType delim_type;
  std::vector<std::unique_ptr<MacroMatch>> matches;
  Location locus;

  // TODO: think of way to mark invalid that doesn't take up more space
  bool is_invalid;

public:
  MacroMatcher (DelimType delim_type,
		std::vector<std::unique_ptr<MacroMatch>> matches,
		Location locus)
    : delim_type (delim_type), matches (std::move (matches)), locus (locus),
      is_invalid (false)
  {}

  // copy constructor with vector clone
  MacroMatcher (MacroMatcher const &other)
    : delim_type (other.delim_type), locus (other.locus)
  {
    matches.reserve (other.matches.size ());
    for (const auto &e : other.matches)
      matches.push_back (e->clone_macro_match ());
  }

  // overloaded assignment operator with vector clone
  MacroMatcher &operator= (MacroMatcher const &other)
  {
    delim_type = other.delim_type;
    locus = other.locus;

    matches.reserve (other.matches.size ());
    for (const auto &e : other.matches)
      matches.push_back (e->clone_macro_match ());

    return *this;
  }

  // move constructors
  MacroMatcher (MacroMatcher &&other) = default;
  MacroMatcher &operator= (MacroMatcher &&other) = default;

  // Creates an error state macro matcher.
  static MacroMatcher create_error (Location locus)
  {
    return MacroMatcher (true, locus);
  }

  // Returns whether MacroMatcher is in an error state.
  bool is_error () const { return is_invalid; }
  Location get_match_locus () const override { return locus; }

  std::string as_string () const override;

  void accept_vis (ASTVisitor &vis) override;

  MacroMatchType get_macro_match_type () const override
  {
    return MacroMatchType::Matcher;
  }

  DelimType get_delim_type () const { return delim_type; }
  std::vector<std::unique_ptr<MacroMatch>> &get_matches () { return matches; }
  const std::vector<std::unique_ptr<MacroMatch>> &get_matches () const
  {
    return matches;
  }

protected:
  /* Use covariance to implement clone function as returning this object rather
   * than base */
  MacroMatcher *clone_macro_match_impl () const override
  {
    return new MacroMatcher (*this);
  }

  // constructor only used to create error matcher
  MacroMatcher (bool is_invalid, Location locus)
    : delim_type (PARENS), locus (locus), is_invalid (is_invalid)
  {}
};

// TODO: inline?
struct MacroTranscriber
{
private:
  DelimTokenTree token_tree;
  Location locus;

public:
  MacroTranscriber (DelimTokenTree token_tree, Location locus)
    : token_tree (std::move (token_tree)), locus (locus)
  {}

  std::string as_string () const { return token_tree.as_string (); }

  Location get_locus () const { return locus; }

  DelimTokenTree &get_token_tree () { return token_tree; }
  const DelimTokenTree &get_token_tree () const { return token_tree; }
};

// A macro rule? Matcher and transcriber pair?
struct MacroRule
{
private:
  MacroMatcher matcher;
  MacroTranscriber transcriber;
  Location locus;

public:
  MacroRule (MacroMatcher matcher, MacroTranscriber transcriber, Location locus)
    : matcher (std::move (matcher)), transcriber (std::move (transcriber)),
      locus (locus)
  {}

  // Returns whether macro rule is in error state.
  bool is_error () const { return matcher.is_error (); }

  // Creates an error state macro rule.
  static MacroRule create_error (Location locus)
  {
    return MacroRule (MacroMatcher::create_error (locus),
		      MacroTranscriber (DelimTokenTree::create_empty (),
					Location ()),
		      locus);
  }

  Location get_locus () const { return locus; }

  std::string as_string () const;

  MacroMatcher &get_matcher () { return matcher; }
  MacroTranscriber &get_transcriber () { return transcriber; }
};

// A macro rules definition item AST node
class MacroRulesDefinition : public VisItem
{
public:
  enum MacroKind
  {
    // Macro by Example (legacy macro rules)
    MBE,
    // Declarative macros 2.0
    DeclMacro,
  };

private:
  std::vector<Attribute> outer_attrs;
  Identifier rule_name;
  // MacroRulesDef rules_def;
  // only curly without required semicolon at end
  DelimType delim_type;
  // MacroRules rules;
  std::vector<MacroRule> rules; // inlined form
  Location locus;

  std::function<Fragment (Location, MacroInvocData &)> associated_transcriber;
  // Since we can't compare std::functions, we need to use an extra boolean
  bool is_builtin_rule;
  MacroKind kind;

  /**
   * Default function to use as an associated transcriber. This function should
   * never be called, hence the gcc_unreachable().
   * If this function is used, then the macro is not builtin and the compiler
   * should make use of the actual rules. If the macro is builtin, then another
   * associated transcriber should be used
   */
  static Fragment dummy_builtin (Location, MacroInvocData &)
  {
    gcc_unreachable ();
    return Fragment::create_error ();
  }

  /* NOTE: in rustc, macro definitions are considered (and parsed as) a type
   * of macro, whereas here they are considered part of the language itself.
   * I am not aware of the implications of this decision. The rustc spec does
   * mention that using the same parser for macro definitions and invocations
   * is "extremely self-referential and non-intuitive". */
  MacroRulesDefinition (Identifier rule_name, DelimType delim_type,
			std::vector<MacroRule> rules,
			std::vector<Attribute> outer_attrs, Location locus,
			MacroKind kind, Visibility vis)
    : VisItem (std::move (vis), outer_attrs),
      outer_attrs (std::move (outer_attrs)), rule_name (std::move (rule_name)),
      delim_type (delim_type), rules (std::move (rules)), locus (locus),
      associated_transcriber (dummy_builtin), is_builtin_rule (false),
      kind (kind)
  {}

  MacroRulesDefinition (
    Identifier builtin_name, DelimType delim_type,
    std::function<Fragment (Location, MacroInvocData &)> associated_transcriber,
    MacroKind kind, Visibility vis)
    : VisItem (std::move (vis), std::vector<Attribute> ()),
      outer_attrs (std::vector<Attribute> ()), rule_name (builtin_name),
      delim_type (delim_type), rules (std::vector<MacroRule> ()),
      locus (Location ()), associated_transcriber (associated_transcriber),
      is_builtin_rule (true), kind (kind)
  {}

public:
  std::string as_string () const override;

  static std::unique_ptr<MacroRulesDefinition>
  mbe (Identifier rule_name, DelimType delim_type, std::vector<MacroRule> rules,
       std::vector<Attribute> outer_attrs, Location locus)
  {
    return Rust::make_unique<MacroRulesDefinition> (
      MacroRulesDefinition (rule_name, delim_type, rules, outer_attrs, locus,
			    AST::MacroRulesDefinition::MacroKind::MBE,
			    AST::Visibility::create_error ()));
  }

  static std::unique_ptr<MacroRulesDefinition>
  decl_macro (Identifier rule_name, std::vector<MacroRule> rules,
	      std::vector<Attribute> outer_attrs, Location locus,
	      Visibility vis)
  {
    return Rust::make_unique<MacroRulesDefinition> (MacroRulesDefinition (
      rule_name, AST::DelimType::CURLY, rules, outer_attrs, locus,
      AST::MacroRulesDefinition::MacroKind::DeclMacro, vis));
  }

  void accept_vis (ASTVisitor &vis) override;

  // Invalid if rule name is empty, so base stripping on that.
  void mark_for_strip () override { rule_name = ""; }
  bool is_marked_for_strip () const override { return rule_name.empty (); }

  // TODO: this mutable getter seems really dodgy. Think up better way.
  std::vector<Attribute> &get_outer_attrs () { return outer_attrs; }
  const std::vector<Attribute> &get_outer_attrs () const { return outer_attrs; }

  std::vector<MacroRule> &get_macro_rules () { return rules; }
  const std::vector<MacroRule> &get_macro_rules () const { return rules; }

  Location get_locus () const override final { return locus; }

  Identifier get_rule_name () const { return rule_name; }

  std::vector<MacroRule> &get_rules () { return rules; }
  const std::vector<MacroRule> &get_rules () const { return rules; }

  bool is_builtin () const { return is_builtin_rule; }
  const std::function<Fragment (Location, MacroInvocData &)> &
  get_builtin_transcriber () const
  {
    rust_assert (is_builtin ());
    return associated_transcriber;
  }
  void set_builtin_transcriber (
    std::function<Fragment (Location, MacroInvocData &)> transcriber)
  {
    associated_transcriber = transcriber;
    is_builtin_rule = true;
  }

  Kind get_ast_kind () const override { return Kind::MACRO_RULES_DEFINITION; }

protected:
  /* Use covariance to implement clone function as returning this object rather
   * than base */
  MacroRulesDefinition *clone_item_impl () const override
  {
    return new MacroRulesDefinition (*this);
  }
};

/**
 * All builtin macros possible
 */
enum class BuiltinMacro
{
  Assert,
  File,
  Line,
  Column,
  IncludeBytes,
  IncludeStr,
  CompileError,
  Concat,
  Env,
  Cfg,
  Include
};

BuiltinMacro
builtin_macro_from_string (const std::string &identifier);

/* AST node of a macro invocation, which is replaced by the macro result at
 * compile time. This is technically a sum-type/tagged-union, which represents
 * both classic macro invocations and builtin macro invocations. Regular macro
 * invocations are expanded lazily, but builtin macro invocations need to be
 * expanded eagerly, hence the differentiation.
 */
class MacroInvocation : public TypeNoBounds,
			public Pattern,
			public Item,
			public TraitItem,
			public TraitImplItem,
			public InherentImplItem,
			public ExternalItem,
			public ExprWithoutBlock
{
public:
  enum class InvocKind
  {
    Regular,
    Builtin,
  };

  std::string as_string () const override;

  /**
   * The default constructor you should use. Whenever we parse a macro call, we
   * cannot possibly know whether or not this call refers to a builtin macro or
   * a regular macro. With name resolution and scopes and nested macro calls,
   * this is literally impossible. Hence, always start by creating a `Regular`
   * MacroInvocation which will then (maybe!) become a `Builtin` macro
   * invocation in the expander.
   */
  static std::unique_ptr<MacroInvocation>
  Regular (MacroInvocData invoc_data, std::vector<Attribute> outer_attrs,
	   Location locus, bool is_semi_coloned = false)
  {
    return std::unique_ptr<MacroInvocation> (
      new MacroInvocation (InvocKind::Regular, Optional<BuiltinMacro>::none (),
			   invoc_data, outer_attrs, locus, is_semi_coloned,
			   {}));
  }

  /**
   * Create a builtin macro invocation. This can only be done after macro
   * name-resolution and within the macro expander, so unless you're modifying
   * these visitors, you probably do not want to use this function.
   */
  static std::unique_ptr<MacroInvocation> Builtin (
    BuiltinMacro kind, MacroInvocData invoc_data,
    std::vector<Attribute> outer_attrs, Location locus,
    std::vector<std::unique_ptr<MacroInvocation>> &&pending_eager_invocations
    = {},
    bool is_semi_coloned = false)
  {
    return std::unique_ptr<MacroInvocation> (
      new MacroInvocation (InvocKind::Builtin,
			   Optional<BuiltinMacro>::some (kind), invoc_data,
			   outer_attrs, locus, is_semi_coloned,
			   std::move (pending_eager_invocations)));
  }

  Location get_locus () const override final { return locus; }

  void accept_vis (ASTVisitor &vis) override;

  // Invalid if path is empty, so base stripping on that.
  void mark_for_strip () override { invoc_data.mark_for_strip (); }
  bool is_marked_for_strip () const override
  {
    return invoc_data.is_marked_for_strip ();
  }

  const std::vector<Attribute> &get_outer_attrs () const { return outer_attrs; }
  std::vector<Attribute> &get_outer_attrs () { return outer_attrs; }

  void set_outer_attrs (std::vector<Attribute> new_attrs) override
  {
    outer_attrs = std::move (new_attrs);
  }

  NodeId get_pattern_node_id () const override final
  {
    return ExprWithoutBlock::get_node_id ();
  }

  Kind get_ast_kind () const override { return Kind::MACRO_INVOCATION; }

  NodeId get_macro_node_id () const { return node_id; }

  MacroInvocData &get_invoc_data () { return invoc_data; }

  bool has_semicolon () const { return is_semi_coloned; }

  InvocKind get_kind () const { return kind; }
  Optional<BuiltinMacro> get_builtin_kind () const { return builtin_kind; }

  /**
   * Turn the current MacroInvocation into a builtin macro invocation
   */
  void map_to_builtin (BuiltinMacro macro)
  {
    kind = InvocKind::Builtin;
    builtin_kind = Optional<BuiltinMacro>::some (macro);
  }

  /**
   * Get the list of pending macro invcations within the builtin macro
   * invocation that should get expanded eagerly.
   */
  std::vector<std::unique_ptr<MacroInvocation>> &
  get_pending_eager_invocations ()
  {
    rust_assert (kind == InvocKind::Builtin);

    return pending_eager_invocs;
  }

private:
  /* Full constructor */
  MacroInvocation (
    InvocKind kind, Optional<BuiltinMacro> builtin_kind,
    MacroInvocData invoc_data, std::vector<Attribute> outer_attrs,
    Location locus, bool is_semi_coloned,
    std::vector<std::unique_ptr<MacroInvocation>> &&pending_eager_invocs)
    : TraitItem (locus), outer_attrs (std::move (outer_attrs)), locus (locus),
      node_id (Analysis::Mappings::get ()->get_next_node_id ()),
      invoc_data (std::move (invoc_data)), is_semi_coloned (is_semi_coloned),
      kind (kind), builtin_kind (builtin_kind),
      pending_eager_invocs (std::move (pending_eager_invocs))
  {}

  MacroInvocation (const MacroInvocation &other)
    : TraitItem (other.locus), outer_attrs (other.outer_attrs),
      locus (other.locus), node_id (other.node_id),
      invoc_data (other.invoc_data), is_semi_coloned (other.is_semi_coloned),
      kind (other.kind), builtin_kind (other.builtin_kind)
  {
    if (other.kind == InvocKind::Builtin)
      for (auto &pending : other.pending_eager_invocs)
	pending_eager_invocs.emplace_back (
	  pending->clone_macro_invocation_impl ());
  }

  std::vector<Attribute> outer_attrs;
  Location locus;
  NodeId node_id;

  /* The data given to the macro invocation */
  MacroInvocData invoc_data;

  /* Important for when we actually expand the macro */
  bool is_semi_coloned;

  /* Is this a builtin macro or a regular macro */
  InvocKind kind;

  /* If it is a builtin macro, which one */
  Optional<BuiltinMacro> builtin_kind = Optional<BuiltinMacro>::none ();

  /**
   * Pending invocations within a builtin macro invocation. This vector is empty
   * and should not be accessed for a regular macro invocation. The macro
   * invocations within should be name resolved and expanded before the builtin
   * macro invocation get expanded again. It is then the role of the expander to
   * insert these new tokens properly in the delimited token tree and try the
   * builtin transcriber once again.
   */
  std::vector<std::unique_ptr<MacroInvocation>> pending_eager_invocs;

protected:
  /* Use covariance to implement clone function as returning this object rather
   * than base */
  MacroInvocation *clone_pattern_impl () const final override
  {
    return clone_macro_invocation_impl ();
  }

  /* Use covariance to implement clone function as returning this object rather
   * than base */
  MacroInvocation *clone_expr_without_block_impl () const final override
  {
    return clone_macro_invocation_impl ();
  }

  /* Use covariance to implement clone function as returning this object rather
   * than base */
  MacroInvocation *clone_type_no_bounds_impl () const final override
  {
    return clone_macro_invocation_impl ();
  }

  MacroInvocation *clone_external_item_impl () const final override
  {
    return clone_macro_invocation_impl ();
  }

  /*virtual*/ MacroInvocation *clone_macro_invocation_impl () const
  {
    return new MacroInvocation (*this);
  }

  Item *clone_item_impl () const override
  {
    return clone_macro_invocation_impl ();
  }

  bool is_item () const override { return !has_semicolon (); }

  TraitItem *clone_trait_item_impl () const override
  {
    return clone_macro_invocation_impl ();
  };

  TraitImplItem *clone_trait_impl_item_impl () const override
  {
    return clone_macro_invocation_impl ();
  };

  InherentImplItem *clone_inherent_impl_item_impl () const override
  {
    return clone_macro_invocation_impl ();
  }

  ExprWithoutBlock *to_stmt () const override

  {
    auto new_impl = clone_macro_invocation_impl ();
    new_impl->is_semi_coloned = true;

    return new_impl;
  }
};

// more generic meta item path-only form
class MetaItemPath : public MetaItem
{
  SimplePath path;

public:
  MetaItemPath (SimplePath path) : path (std::move (path)) {}

  std::string as_string () const override { return path.as_string (); }

  void accept_vis (ASTVisitor &vis) override;

  // HACK: used to simplify parsing - returns non-empty only in this case
  SimplePath to_path_item () const override
  {
    // this should copy construct - TODO ensure it does
    return path;
  }

  Location get_locus () const override { return path.get_locus (); }

  bool check_cfg_predicate (const Session &session) const override;

  Attribute to_attribute () const override;

protected:
  // Use covariance to implement clone function as returning this type
  MetaItemPath *clone_meta_item_inner_impl () const override
  {
    return new MetaItemPath (*this);
  }
};

// more generic meta item sequence form
class MetaItemSeq : public MetaItem
{
  SimplePath path;
  std::vector<std::unique_ptr<MetaItemInner>> seq;

public:
  MetaItemSeq (SimplePath path, std::vector<std::unique_ptr<MetaItemInner>> seq)
    : path (std::move (path)), seq (std::move (seq))
  {}

  // copy constructor with vector clone
  MetaItemSeq (const MetaItemSeq &other) : path (other.path)
  {
    seq.reserve (other.seq.size ());
    for (const auto &e : other.seq)
      seq.push_back (e->clone_meta_item_inner ());
  }

  // overloaded assignment operator with vector clone
  MetaItemSeq &operator= (const MetaItemSeq &other)
  {
    MetaItem::operator= (other);
    path = other.path;

    seq.reserve (other.seq.size ());
    for (const auto &e : other.seq)
      seq.push_back (e->clone_meta_item_inner ());

    return *this;
  }

  // default move constructors
  MetaItemSeq (MetaItemSeq &&other) = default;
  MetaItemSeq &operator= (MetaItemSeq &&other) = default;

  std::string as_string () const override;

  void accept_vis (ASTVisitor &vis) override;

  Location get_locus () const override { return path.get_locus (); }

  bool check_cfg_predicate (const Session &session) const override;

  Attribute to_attribute () const override;

protected:
  // Use covariance to implement clone function as returning this type
  MetaItemSeq *clone_meta_item_inner_impl () const override
  {
    return new MetaItemSeq (*this);
  }
};

// Preferred specialisation for single-identifier meta items.
class MetaWord : public MetaItem
{
  Identifier ident;
  Location ident_locus;

public:
  MetaWord (Identifier ident, Location ident_locus)
    : ident (std::move (ident)), ident_locus (ident_locus)
  {}

  std::string as_string () const override { return ident; }

  void accept_vis (ASTVisitor &vis) override;

  Location get_locus () const override { return ident_locus; }

  bool check_cfg_predicate (const Session &session) const override;

  Attribute to_attribute () const override;

protected:
  // Use covariance to implement clone function as returning this type
  MetaWord *clone_meta_item_inner_impl () const override
  {
    return new MetaWord (*this);
  }
};

// Preferred specialisation for "identifier '=' string literal" meta items.
class MetaNameValueStr : public MetaItem
{
  Identifier ident;
  Location ident_locus;

  // NOTE: str stored without quotes
  std::string str;
  Location str_locus;

public:
  MetaNameValueStr (Identifier ident, Location ident_locus, std::string str,
		    Location str_locus)
    : ident (std::move (ident)), ident_locus (ident_locus),
      str (std::move (str)), str_locus (str_locus)
  {}

  std::string as_string () const override
  {
    return ident + " = \"" + str + "\"";
  }

  void accept_vis (ASTVisitor &vis) override;

  // HACK: used to simplify parsing - creates a copy of this
  std::unique_ptr<MetaNameValueStr> to_meta_name_value_str () const override
  {
    return std::unique_ptr<MetaNameValueStr> (clone_meta_item_inner_impl ());
  }

  Location get_locus () const override { return ident_locus; }

  bool check_cfg_predicate (const Session &session) const override;

  Attribute to_attribute () const override;

  inline std::pair<Identifier, std::string> get_name_value_pair () const
  {
    return std::pair<Identifier, std::string> (ident, str);
  }

  bool is_key_value_pair () const override { return true; }

protected:
  // Use covariance to implement clone function as returning this type
  MetaNameValueStr *clone_meta_item_inner_impl () const override
  {
    return new MetaNameValueStr (*this);
  }
};

// doubles up as MetaListIdents - determine via iterating through each path?
// Preferred specialisation for "identifier '(' SimplePath, SimplePath, ... ')'"
class MetaListPaths : public MetaItem
{
  Identifier ident;
  Location ident_locus;
  std::vector<SimplePath> paths;

public:
  MetaListPaths (Identifier ident, Location ident_locus,
		 std::vector<SimplePath> paths)
    : ident (std::move (ident)), ident_locus (ident_locus),
      paths (std::move (paths))
  {}

  std::string as_string () const override;

  void accept_vis (ASTVisitor &vis) override;

  Location get_locus () const override { return ident_locus; }

  bool check_cfg_predicate (const Session &session) const override;

  Attribute to_attribute () const override;

private:
  bool check_path_exists_in_cfg (const Session &session,
				 const SimplePath &path) const;

protected:
  // Use covariance to implement clone function as returning this type
  MetaListPaths *clone_meta_item_inner_impl () const override
  {
    return new MetaListPaths (*this);
  }
};

// Preferred specialisation for "identifier '(' MetaNameValueStr, ... ')'"
class MetaListNameValueStr : public MetaItem
{
  Identifier ident;
  Location ident_locus;
  std::vector<MetaNameValueStr> strs;

public:
  MetaListNameValueStr (Identifier ident, Location ident_locus,
			std::vector<MetaNameValueStr> strs)
    : ident (std::move (ident)), ident_locus (ident_locus),
      strs (std::move (strs))
  {}

  std::string as_string () const override;

  void accept_vis (ASTVisitor &vis) override;

  Location get_locus () const override { return ident_locus; }

  bool check_cfg_predicate (const Session &session) const override;

  Attribute to_attribute () const override;

protected:
  // Use covariance to implement clone function as returning this type
  MetaListNameValueStr *clone_meta_item_inner_impl () const override
  {
    return new MetaListNameValueStr (*this);
  }
};

// Object that parses macros from a token stream.
/* TODO: would "AttributeParser" be a better name? MetaItems are only for
 * attributes, I believe */
struct AttributeParser
{
private:
  // TODO: might as well rewrite to use lexer tokens
  std::vector<std::unique_ptr<Token>> token_stream;
  int stream_pos;

public:
  AttributeParser (std::vector<std::unique_ptr<Token>> token_stream,
		   int stream_start_pos = 0)
    : token_stream (std::move (token_stream)), stream_pos (stream_start_pos)
  {}

  ~AttributeParser () = default;

  std::vector<std::unique_ptr<MetaItemInner>> parse_meta_item_seq ();

private:
  // Parses a MetaItemInner.
  std::unique_ptr<MetaItemInner> parse_meta_item_inner ();
  // Returns whether token can end a meta item.
  bool is_end_meta_item_tok (TokenId id) const;
  // Parses a simple path.
  SimplePath parse_simple_path ();
  // Parses a segment of a simple path (but not scope resolution operator).
  SimplePathSegment parse_simple_path_segment ();
  // Parses a MetaItemLitExpr.
  std::unique_ptr<MetaItemLitExpr> parse_meta_item_lit ();
  // Parses a literal.
  Literal parse_literal ();
  // Parses a meta item that begins with a simple path.
  std::unique_ptr<MetaItem> parse_path_meta_item ();

  // TODO: should this be const?
  std::unique_ptr<Token> &peek_token (int i = 0)
  {
    return token_stream[stream_pos + i];
  }

  void skip_token (int i = 0) { stream_pos += 1 + i; }
};
} // namespace AST
} // namespace Rust

/* <https://stackoverflow.com/a/35304501> */
namespace std {
template <> struct hash<Rust::AST::MacroFragSpec::Kind>
{
  size_t operator() (const Rust::AST::MacroFragSpec::Kind &t) const noexcept
  {
    return size_t (t);
  }
};
} // namespace std

#endif