SpECTRE Documentation Coverage Report
Current view: top level - Options - ParseOptions.hpp Hit Total Coverage
Commit: 9a905b0737f373631c1b8e8389b8f26e67fa5313 Lines: 22 38 57.9 %
Date: 2024-03-28 09:03:18
Legend: Lines: hit not hit

          Line data    Source code
       1           1 : // Distributed under the MIT License.
       2             : // See LICENSE.txt for details.
       3             : 
       4             : /// \file
       5             : /// Defines classes and functions that handle parsing of input parameters.
       6             : 
       7             : #pragma once
       8             : 
       9             : #include <cerrno>
      10             : #include <cstring>
      11             : #include <exception>
      12             : #include <fstream>
      13             : #include <ios>
      14             : #include <iterator>
      15             : #include <limits>
      16             : #include <map>
      17             : #include <ostream>
      18             : #include <pup.h>
      19             : #include <sstream>
      20             : #include <string>
      21             : #include <unordered_map>
      22             : #include <unordered_set>
      23             : #include <utility>
      24             : #include <variant>
      25             : #include <vector>
      26             : #include <yaml-cpp/yaml.h>
      27             : 
      28             : #include "Options/Context.hpp"
      29             : #include "Options/Options.hpp"
      30             : #include "Options/OptionsDetails.hpp"
      31             : #include "Options/ParseError.hpp"
      32             : #include "Options/Tags.hpp"
      33             : #include "Parallel/Printf.hpp"
      34             : #include "Utilities/Algorithm.hpp"
      35             : #include "Utilities/ErrorHandling/Assert.hpp"
      36             : #include "Utilities/ErrorHandling/Error.hpp"
      37             : #include "Utilities/Gsl.hpp"
      38             : #include "Utilities/MakeString.hpp"
      39             : #include "Utilities/NoSuchType.hpp"
      40             : #include "Utilities/PrettyType.hpp"
      41             : #include "Utilities/StdHelpers.hpp"
      42             : #include "Utilities/TaggedTuple.hpp"
      43             : #include "Utilities/TypeTraits.hpp"
      44             : #include "Utilities/TypeTraits/IsA.hpp"
      45             : #include "Utilities/TypeTraits/IsMaplike.hpp"
      46             : #include "Utilities/TypeTraits/IsStdArray.hpp"
      47             : #include "Utilities/TypeTraits/IsStdArrayOfSize.hpp"
      48             : 
      49             : namespace Options {
      50             : // Defining methods as inline in a different header from the class
      51             : // definition is somewhat strange.  It is done here to minimize the
      52             : // amount of code in the frequently-included Options.hpp file.  The
      53             : // only external consumers of Option should be create_from_yaml
      54             : // specializations, and they should only be instantiated by code in
      55             : // this file.  (Or explicitly instantiated in cpp files, which can
      56             : // include this file.)
      57             : 
      58             : // clang-tidy: YAML::Node not movable (as of yaml-cpp-0.5.3)
      59             : // NOLINTNEXTLINE(performance-unnecessary-value-param)
      60             : inline Option::Option(YAML::Node node, Context context)
      61             :     : node_(std::make_unique<YAML::Node>(std::move(node))),
      62             :       context_(std::move(context)) {  // NOLINT
      63             :   context_.line = node.Mark().line;
      64             :   context_.column = node.Mark().column;
      65             : }
      66             : 
      67             : inline Option::Option(Context context)
      68             :     : node_(std::make_unique<YAML::Node>()), context_(std::move(context)) {}
      69             : 
      70             : inline const YAML::Node& Option::node() const { return *node_; }
      71             : inline const Context& Option::context() const { return context_; }
      72             : 
      73             : /// Append a line to the contained context.
      74             : inline void Option::append_context(const std::string& context) {
      75             :   context_.append(context);
      76             : }
      77             : 
      78             : // NOLINTNEXTLINE(performance-unnecessary-value-param)
      79             : inline void Option::set_node(YAML::Node node) {
      80             :   // clang-tidy: YAML::Node not movable (as of yaml-cpp-0.5.3)
      81             :   *node_ = std::move(node);  // NOLINT
      82             :   context_.line = node_->Mark().line;
      83             :   context_.column = node_->Mark().column;
      84             : }
      85             : 
      86             : template <typename T, typename Metavariables>
      87             : T Option::parse_as() const {
      88             :   try {
      89             :     // yaml-cpp's `as` method won't parse empty nodes, so we need to
      90             :     // inline a bit of its logic.
      91             :     Options_detail::wrap_create_types<T, Metavariables> result{};
      92             : #if defined(__GNUC__) and not defined(__clang__) and __GNUC__ == 13
      93             : #pragma GCC diagnostic push
      94             : #pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
      95             : #endif
      96             :     if (YAML::convert<decltype(result)>::decode(node(), result)) {
      97             : #if defined(__GNUC__) and not defined(__clang__) and __GNUC__ == 13
      98             : #pragma GCC diagnostic pop
      99             : #endif
     100             :       return Options_detail::unwrap_create_types(std::move(result));
     101             :     }
     102             :     // clang-tidy: thrown exception is not nothrow copy constructible
     103             :     throw YAML::BadConversion(node().Mark());  // NOLINT
     104             :   } catch (const YAML::BadConversion& e) {
     105             :     // This happens when trying to parse an empty value as a container
     106             :     // with no entries.
     107             :     if ((tt::is_a_v<std::vector, T> or tt::is_std_array_of_size_v<0, T> or
     108             :          tt::is_maplike_v<T>) and node().IsNull()) {
     109             :       return T{};
     110             :     }
     111             :     Context error_context = context();
     112             :     error_context.line = e.mark.line;
     113             :     error_context.column = e.mark.column;
     114             :     std::ostringstream ss;
     115             :     ss << "Failed to convert value to type "
     116             :        << Options_detail::yaml_type<T>::value() << ":";
     117             : 
     118             :     const std::string value_text = YAML::Dump(node());
     119             :     if (value_text.find('\n') == std::string::npos) {
     120             :       ss << " " << value_text;
     121             :     } else {
     122             :       // Indent each line of the value by two spaces and start on a new line
     123             :       ss << "\n  ";
     124             :       for (char c : value_text) {
     125             :         ss << c;
     126             :         if (c == '\n') {
     127             :           ss << "  ";
     128             :         }
     129             :       }
     130             :     }
     131             : 
     132             :     if (tt::is_a_v<std::vector, T> or tt::is_std_array_v<T>) {
     133             :       ss << "\n\nNote: For sequences this can happen because the length of the "
     134             :             "sequence specified\nin the input file is not equal to the length "
     135             :             "expected by the code. Sequences in\nfiles can be denoted either "
     136             :             "as a bracket enclosed list ([foo, bar]) or with each\nentry on a "
     137             :             "separate line, indented and preceeded by a dash (  - foo).";
     138             :     }
     139             :     PARSE_ERROR(error_context, ss.str());
     140             :   } catch (const Options::detail::propagate_context& e) {
     141             :     Context error_context = context();
     142             :     // Avoid line numbers in the middle of the trace
     143             :     error_context.line = -1;
     144             :     error_context.column = -1;
     145             :     PARSE_ERROR(error_context, e.message());
     146             :   } catch (std::exception& e) {
     147             :     ERROR("Unexpected exception: " << e.what());
     148             :   }
     149             : }
     150             : 
     151             : namespace Options_detail {
     152             : template <typename T, typename Metavariables, typename Subgroup>
     153             : struct get_impl;
     154             : }  // namespace Options_detail
     155             : 
     156             : /// \ingroup OptionParsingGroup
     157             : /// \brief Class that handles parsing an input file
     158             : ///
     159             : /// Options must be given YAML data to parse before output can be
     160             : /// extracted.  This can be done either from a file (parse_file
     161             : /// method), from a string (parse method), or, in the case of
     162             : /// recursive parsing, from an Option (parse method).  The options
     163             : /// can then be extracted using the get method.
     164             : ///
     165             : /// \example
     166             : /// \snippet Test_Options.cpp options_example_scalar_struct
     167             : /// \snippet Test_Options.cpp options_example_scalar_parse
     168             : ///
     169             : /// \see the \ref dev_guide_option_parsing tutorial
     170             : ///
     171             : /// \tparam OptionList the list of option structs to parse
     172             : /// \tparam Group the option group with a group hierarchy
     173             : template <typename OptionList, typename Group = NoSuchType>
     174           1 : class Parser {
     175             :  private:
     176             :   /// All top-level options and top-level groups of options. Every option in
     177             :   /// `OptionList` is either in this list or in the hierarchy of one of the
     178             :   /// groups in this list.
     179           1 :   using tags_and_subgroups_list = tmpl::remove_duplicates<tmpl::transform<
     180             :       OptionList, Options_detail::find_subgroup<tmpl::_1, tmpl::pin<Group>>>>;
     181             : 
     182             :  public:
     183           0 :   Parser() = default;
     184             : 
     185             :   /// \param help_text an overall description of the options
     186           1 :   explicit Parser(std::string help_text);
     187             : 
     188             :   /// Parse a string to obtain options and their values.
     189             :   ///
     190             :   /// \param options the string holding the YAML formatted options
     191           1 :   void parse(std::string options);
     192             : 
     193             :   /// Parse an Option to obtain options and their values.
     194           1 :   void parse(const Option& options);
     195             : 
     196             :   /// Parse a file containing options
     197             :   ///
     198             :   /// \param file_name the path to the file to parse
     199           1 :   void parse_file(const std::string& file_name);
     200             : 
     201             :   /// Overlay the options from a string or file on top of the
     202             :   /// currently parsed options.
     203             :   ///
     204             :   /// Any tag included in the list passed as the template parameter
     205             :   /// can be overridden by a new parsed value.  Newly parsed options
     206             :   /// replace the previous values.  Any tags not appearing in the new
     207             :   /// input are left unchanged.
     208             :   /// @{
     209             :   template <typename OverlayOptions>
     210           1 :   void overlay(std::string options);
     211             : 
     212             :   template <typename OverlayOptions>
     213           1 :   void overlay_file(const std::string& file_name);
     214             :   /// @}
     215             : 
     216             :   /// Get the value of the specified option
     217             :   ///
     218             :   /// \tparam T the option to retrieve
     219             :   /// \return the value of the option
     220             :   template <typename T, typename Metavariables = NoSuchType>
     221           1 :   typename T::type get() const;
     222             : 
     223             :   /// Call a function with the specified options as arguments.
     224             :   ///
     225             :   /// \tparam TagList a typelist of options to pass
     226             :   /// \return the result of the function call
     227             :   template <typename TagList, typename Metavariables = NoSuchType, typename F>
     228           1 :   decltype(auto) apply(F&& func) const;
     229             : 
     230             :   /// Call a function with the typelist of parsed options (i.e., the
     231             :   /// supplied option list with the chosen branches of any
     232             :   /// Alternatives inlined) and the option values as arguments.
     233             :   ///
     234             :   /// \return the result of the function call.  This must have the
     235             :   /// same type for all valid sets of parsed arguments.
     236             :   template <typename Metavariables = NoSuchType, typename F>
     237           1 :   decltype(auto) apply_all(F&& func) const;
     238             : 
     239             :   /// Get the help string
     240             :   template <typename TagsAndSubgroups = tags_and_subgroups_list>
     241           1 :   std::string help() const;
     242             : 
     243             :   // NOLINTNEXTLINE(google-runtime-references)
     244           0 :   void pup(PUP::er& p);
     245             : 
     246             :  private:
     247             :   template <typename, typename>
     248           0 :   friend class Parser;
     249             :   template <typename, typename, typename>
     250             :   friend struct Options_detail::get_impl;
     251             : 
     252             :   static_assert(tt::is_a<tmpl::list, OptionList>::value,
     253             :                 "The OptionList template parameter to Options must be a "
     254             :                 "tmpl::list<...>.");
     255             : 
     256             :   // All options that could be specified, including those that have
     257             :   // alternatives and are therefore not required.
     258           0 :   using all_possible_options = tmpl::remove_duplicates<
     259             :       typename Options_detail::flatten_alternatives<OptionList>::type>;
     260             : 
     261             :   static_assert(
     262             :       std::is_same_v<
     263             :           typename Options_detail::flatten_alternatives<OptionList>::type,
     264             :           OptionList> or
     265             :           tmpl::all<
     266             :               all_possible_options,
     267             :               std::is_same<tmpl::_1, Options_detail::find_subgroup<
     268             :                                          tmpl::_1, tmpl::pin<Group>>>>::value,
     269             :       "Option parser cannot handle Alternatives and options with groups "
     270             :       "simultaneously.");
     271             : 
     272             :   /// All top-level subgroups
     273           1 :   using subgroups = tmpl::list_difference<tags_and_subgroups_list, OptionList>;
     274             : 
     275             :   // The maximum length of an option label.
     276           0 :   static constexpr int max_label_size_ = 70;
     277             : 
     278             :   /// Parse a YAML node containing options
     279           1 :   void parse(const YAML::Node& node);
     280             : 
     281             :   /// Overlay data from a YAML node
     282             :   template <typename OverlayOptions>
     283           1 :   void overlay(const YAML::Node& node);
     284             : 
     285             :   /// Check that the size is not smaller than the lower bound
     286             :   ///
     287             :   /// \tparam T the option struct
     288             :   /// \param t the value of the read in option
     289             :   template <typename T>
     290           1 :   void check_lower_bound_on_size(const typename T::type& t,
     291             :                                  const Context& context) const;
     292             : 
     293             :   /// Check that the size is not larger than the upper bound
     294             :   ///
     295             :   /// \tparam T the option struct
     296             :   /// \param t the value of the read in option
     297             :   template <typename T>
     298           1 :   void check_upper_bound_on_size(const typename T::type& t,
     299             :                                  const Context& context) const;
     300             : 
     301             :   /// If the options has a lower bound, check it is satisfied.
     302             :   ///
     303             :   /// Note: Lower bounds are >=, not just >.
     304             :   /// \tparam T the option struct
     305             :   /// \param t the value of the read in option
     306             :   template <typename T>
     307           1 :   void check_lower_bound(const typename T::type& t,
     308             :                          const Context& context) const;
     309             : 
     310             :   /// If the options has a upper bound, check it is satisfied.
     311             :   ///
     312             :   /// Note: Upper bounds are <=, not just <.
     313             :   /// \tparam T the option struct
     314             :   /// \param t the value of the read in option
     315             :   template <typename T>
     316           1 :   void check_upper_bound(const typename T::type& t,
     317             :                          const Context& context) const;
     318             : 
     319             :   /// Get the help string for parsing errors
     320             :   template <typename TagsAndSubgroups = tags_and_subgroups_list>
     321           1 :   std::string parsing_help(const YAML::Node& options) const;
     322             : 
     323             :   /// Error message when failed to parse an input file.
     324           1 :   [[noreturn]] void parser_error(const YAML::Exception& e) const;
     325             : 
     326             :   template <typename ChosenOptions, typename RemainingOptions, typename F>
     327           0 :   auto call_with_chosen_alternatives_impl(F&& func,
     328             :                                           std::vector<size_t> choices) const;
     329             : 
     330             :   template <typename F>
     331           0 :   auto call_with_chosen_alternatives(F&& func) const {
     332             :     return call_with_chosen_alternatives_impl<tmpl::list<>, OptionList>(
     333             :         std::forward<F>(func), alternative_choices_);
     334             :   }
     335             : 
     336           0 :   std::string help_text_{};
     337           0 :   Context context_{};
     338           0 :   std::vector<std::string> input_source_{};
     339           0 :   std::unordered_map<std::string, YAML::Node> parsed_options_{};
     340             : 
     341             :   template <typename Subgroup>
     342           0 :   struct SubgroupParser {
     343           0 :     using type = Parser<Options_detail::options_in_group<OptionList, Subgroup>,
     344             :                         Subgroup>;
     345             :   };
     346             : 
     347             :   tuples::tagged_tuple_from_typelist<
     348             :       tmpl::transform<subgroups, tmpl::bind<SubgroupParser, tmpl::_1>>>
     349           0 :       subgroup_parsers_ =
     350             :           tmpl::as_pack<subgroups>([this](auto... subgroup_tags) {
     351             :             (void)this;  // gcc wants this for subgroup_parsers_
     352             :             return decltype(subgroup_parsers_)(
     353             :                 tmpl::type_from<decltype(subgroup_tags)>::help...);
     354             :           });
     355             : 
     356             :   // The choices made for option alternatives in a depth-first order.
     357             :   // Starting from the front of the option list, when reaching the
     358             :   // first Alternatives object, replace it with the options in the nth
     359             :   // choice, where n is the *last* element of this vector.  Continue
     360             :   // processing from the start of those options, using the second to
     361             :   // last value here for the next choice, and so on.
     362           0 :   std::vector<size_t> alternative_choices_{};
     363             : };
     364             : 
     365             : template <typename OptionList, typename Group>
     366             : Parser<OptionList, Group>::Parser(std::string help_text)
     367             :     : help_text_(std::move(help_text)) {
     368             :   tmpl::for_each<all_possible_options>([](auto t) {
     369             :     using T = typename decltype(t)::type;
     370             :     const std::string label = pretty_type::name<T>();
     371             :     ASSERT(label.size() <= max_label_size_,
     372             :            "The option name " << label
     373             :                               << " is too long for nice formatting, "
     374             :                                  "please shorten the name to "
     375             :                               << max_label_size_ << " characters or fewer");
     376             :     ASSERT(std::strlen(T::help) > 0,
     377             :            "You must supply a help string of non-zero length for " << label);
     378             :   });
     379             : }
     380             : 
     381             : namespace detail {
     382             : YAML::Node load_and_check_yaml(const std::string& options,
     383             :                                bool require_metadata);
     384             : }
     385             : 
     386             : template <typename OptionList, typename Group>
     387             : void Parser<OptionList, Group>::parse(std::string options) {
     388             :   context_.append("In string");
     389             :   input_source_.push_back(std::move(options));
     390             :   try {
     391             :     parse(detail::load_and_check_yaml(input_source_.back(), false));
     392             :   } catch (const YAML::Exception& e) {
     393             :     parser_error(e);
     394             :   }
     395             : }
     396             : 
     397             : template <typename OptionList, typename Group>
     398             : void Parser<OptionList, Group>::parse(const Option& options) {
     399             :   context_ = options.context();
     400             :   parse(options.node());
     401             : }
     402             : 
     403             : namespace Options_detail {
     404             : inline std::ifstream open_file(const std::string& file_name) {
     405             :   errno = 0;
     406             :   std::ifstream input(file_name);
     407             :   if (not input) {
     408             :     // There is no standard way to get an error message from an
     409             :     // fstream, but this works on many implementations.
     410             :     ERROR("Could not open " << file_name << ": " << strerror(errno));
     411             :   }
     412             :   return input;
     413             : }
     414             : }  // namespace Options_detail
     415             : 
     416             : template <typename OptionList, typename Group>
     417             : void Parser<OptionList, Group>::parse_file(const std::string& file_name) {
     418             :   context_.append("In " + file_name);
     419             :   auto input = Options_detail::open_file(file_name);
     420             :   input_source_.push_back(std::string(std::istreambuf_iterator(input), {}));
     421             :   try {
     422             :     parse(detail::load_and_check_yaml(input_source_.back(), true));
     423             :   } catch (const YAML::Exception& e) {
     424             :     parser_error(e);
     425             :   }
     426             : }
     427             : 
     428             : template <typename OptionList, typename Group>
     429             : template <typename OverlayOptions>
     430             : void Parser<OptionList, Group>::overlay(std::string options) {
     431             :   context_ = Context{};
     432             :   context_.append("In string");
     433             :   input_source_.push_back(std::move(options));
     434             :   try {
     435             :     overlay<OverlayOptions>(
     436             :         detail::load_and_check_yaml(input_source_.back(), false));
     437             :   } catch (const YAML::Exception& e) {
     438             :     parser_error(e);
     439             :   }
     440             : }
     441             : 
     442             : template <typename OptionList, typename Group>
     443             : template <typename OverlayOptions>
     444             : void Parser<OptionList, Group>::overlay_file(const std::string& file_name) {
     445             :   context_ = Context{};
     446             :   context_.append("In " + file_name);
     447             :   auto input = Options_detail::open_file(file_name);
     448             :   input_source_.push_back(std::string(std::istreambuf_iterator(input), {}));
     449             :   try {
     450             :     overlay<OverlayOptions>(
     451             :         detail::load_and_check_yaml(input_source_.back(), false));
     452             :   } catch (const YAML::Exception& e) {
     453             :     parser_error(e);
     454             :   }
     455             : }
     456             : 
     457             : namespace Options_detail {
     458             : // Attempts to match the given_options against OptionList, choosing
     459             : // between any alternatives to maximize the number of matches.
     460             : // Returns the highest number of matches and the choices required to
     461             : // obtain that number.  (See the description of
     462             : // Parser::alternative_choices_ for the format of the choices.)
     463             : //
     464             : // In the case of multiple equally good matches, the choice between
     465             : // them will be given as std::numeric_limits<size_t>::max().  This may
     466             : // cause a failure later or may be ignored if a better choice is
     467             : // found.
     468             : //
     469             : // This does not handle groups correctly, but we disallow alternatives
     470             : // when groups are present so there is only one possible choice that
     471             : // this function could make.
     472             : template <typename OptionList>
     473             : std::pair<int, std::vector<size_t>> choose_alternatives(
     474             :     const std::unordered_set<std::string>& given_options) {
     475             :   int num_matched = 0;
     476             :   std::vector<size_t> alternative_choices{};
     477             :   tmpl::for_each<OptionList>([&alternative_choices, &num_matched,
     478             :                               &given_options](auto opt) {
     479             :     using Opt = tmpl::type_from<decltype(opt)>;
     480             :     if constexpr (not tt::is_a_v<Options::Alternatives, Opt>) {
     481             :       if (given_options.count(pretty_type::name<Opt>()) == 1) {
     482             :         ++num_matched;
     483             :       }
     484             :     } else {
     485             :       int most_matches = 0;
     486             :       std::vector<size_t> best_alternatives{std::numeric_limits<size_t>::max()};
     487             : 
     488             :       size_t alternative_number = 0;
     489             :       tmpl::for_each<Opt>([&alternative_number, &best_alternatives,
     490             :                            &most_matches, &given_options](auto alternative) {
     491             :         using Alternative = tmpl::type_from<decltype(alternative)>;
     492             :         auto alternative_match =
     493             :             choose_alternatives<Alternative>(given_options);
     494             :         if (alternative_match.first > most_matches) {
     495             :           most_matches = alternative_match.first;
     496             :           alternative_match.second.push_back(alternative_number);
     497             :           best_alternatives = std::move(alternative_match.second);
     498             :         } else if (alternative_match.first == most_matches) {
     499             :           // Two equally good matches
     500             :           best_alternatives.clear();
     501             :           best_alternatives.push_back(std::numeric_limits<size_t>::max());
     502             :         }
     503             :         ++alternative_number;
     504             :       });
     505             :       num_matched += most_matches;
     506             :       alternative_choices.insert(alternative_choices.begin(),
     507             :                                  best_alternatives.begin(),
     508             :                                  best_alternatives.end());
     509             :     }
     510             :   });
     511             :   return {num_matched, std::move(alternative_choices)};
     512             : }
     513             : }  // namespace Options_detail
     514             : 
     515             : namespace Options_detail {
     516             : template <typename Tag, typename Metavariables, typename Subgroup>
     517             : struct get_impl {
     518             :   template <typename OptionList, typename Group>
     519             :   static typename Tag::type apply(const Parser<OptionList, Group>& opts) {
     520             :     static_assert(
     521             :         tmpl::list_contains_v<OptionList, Tag>,
     522             :         "Could not find requested option in the list of options provided. Did "
     523             :         "you forget to add the option tag to the OptionList?");
     524             :     return tuples::get<typename Parser<
     525             :         OptionList, Group>::template SubgroupParser<Subgroup>>(
     526             :                opts.subgroup_parsers_)
     527             :         .template get<Tag, Metavariables>();
     528             :   }
     529             : };
     530             : 
     531             : template <typename Tag, typename Metavariables>
     532             : struct get_impl<Tag, Metavariables, Tag> {
     533             :   template <typename OptionList, typename Group>
     534             :   static typename Tag::type apply(const Parser<OptionList, Group>& opts) {
     535             :     static_assert(
     536             :         tmpl::list_contains_v<
     537             :             typename Parser<OptionList, Group>::all_possible_options, Tag>,
     538             :         "Could not find requested option in the list of options provided. Did "
     539             :         "you forget to add the option tag to the OptionList?");
     540             :     const std::string label = pretty_type::name<Tag>();
     541             : 
     542             :     const auto supplied_option = opts.parsed_options_.find(label);
     543             :     ASSERT(supplied_option != opts.parsed_options_.end(),
     544             :            "Requested option from alternative that was not supplied.");
     545             :     Option option(supplied_option->second, opts.context_);
     546             :     option.append_context("While parsing option " + label);
     547             : 
     548             :     auto t = option.parse_as<typename Tag::type, Metavariables>();
     549             : 
     550             :     if constexpr (Options_detail::has_suggested<Tag>::value) {
     551             :       static_assert(
     552             :           std::is_same_v<decltype(Tag::suggested_value()), typename Tag::type>,
     553             :           "Suggested value is not of the same type as the option.");
     554             : 
     555             :       // This can be easily relaxed, but using it would require
     556             :       // writing comparison operators for abstract base classes.  If
     557             :       // someone wants this enough to go though the effort of doing
     558             :       // that, it would just require comparing the dereferenced
     559             :       // pointers below to decide whether the suggestion was followed.
     560             :       static_assert(not tt::is_a_v<std::unique_ptr, typename Tag::type>,
     561             :                     "Suggestions are not supported for pointer types.");
     562             : 
     563             :       const auto suggested_value = Tag::suggested_value();
     564             :       {
     565             :         Context context;
     566             :         context.append("Checking SUGGESTED value for " +
     567             :                        pretty_type::name<Tag>());
     568             :         opts.template check_lower_bound_on_size<Tag>(suggested_value, context);
     569             :         opts.template check_upper_bound_on_size<Tag>(suggested_value, context);
     570             :         opts.template check_lower_bound<Tag>(suggested_value, context);
     571             :         opts.template check_upper_bound<Tag>(suggested_value, context);
     572             :       }
     573             : 
     574             :       if (t != suggested_value) {
     575             :         Parallel::printf_error(
     576             :             "%s, line %d:\n  Specified: %s\n  Suggested: %s\n",
     577             :             label, option.context().line + 1,
     578             :             (MakeString{} << std::boolalpha << t),
     579             :             (MakeString{} << std::boolalpha << suggested_value));
     580             :       }
     581             :     }
     582             : 
     583             :     opts.template check_lower_bound_on_size<Tag>(t, option.context());
     584             :     opts.template check_upper_bound_on_size<Tag>(t, option.context());
     585             :     opts.template check_lower_bound<Tag>(t, option.context());
     586             :     opts.template check_upper_bound<Tag>(t, option.context());
     587             :     return t;
     588             :   }
     589             : };
     590             : 
     591             : template <typename Metavariables>
     592             : struct get_impl<Tags::InputSource, Metavariables, Tags::InputSource> {
     593             :   template <typename OptionList, typename Group>
     594             :   static Tags::InputSource::type apply(const Parser<OptionList, Group>& opts) {
     595             :     return opts.input_source_;
     596             :   }
     597             : };
     598             : }  // namespace Options_detail
     599             : 
     600             : template <typename OptionList, typename Group>
     601             : template <typename Tag, typename Metavariables>
     602           0 : typename Tag::type Parser<OptionList, Group>::get() const {
     603             :   return Options_detail::get_impl<
     604             :       Tag, Metavariables,
     605             :       typename Options_detail::find_subgroup<Tag, Group>::type>::apply(*this);
     606             : }
     607             : 
     608             : namespace Options_detail {
     609             : template <typename>
     610             : struct apply_helper;
     611             : 
     612             : template <typename... Tags>
     613             : struct apply_helper<tmpl::list<Tags...>> {
     614             :   template <typename Metavariables, typename Options, typename F>
     615             :   static decltype(auto) apply(const Options& opts, F&& func) {
     616             :     return func(opts.template get<Tags, Metavariables>()...);
     617             :   }
     618             : };
     619             : }  // namespace Options_detail
     620             : 
     621             : /// \cond
     622             : // Doxygen is confused by decltype(auto)
     623             : template <typename OptionList, typename Group>
     624             : template <typename TagList, typename Metavariables, typename F>
     625             : decltype(auto) Parser<OptionList, Group>::apply(F&& func) const {
     626             :   return Options_detail::apply_helper<TagList>::template apply<Metavariables>(
     627             :       *this, std::forward<F>(func));
     628             : }
     629             : 
     630             : template <typename OptionList, typename Group>
     631             : template <typename Metavariables, typename F>
     632             : decltype(auto) Parser<OptionList, Group>::apply_all(F&& func) const {
     633             :   return call_with_chosen_alternatives([this, &func](
     634             :                                            auto chosen_alternatives /*meta*/) {
     635             :     using ChosenAlternatives = decltype(chosen_alternatives);
     636             :     return this->apply<ChosenAlternatives, Metavariables>([&func](
     637             :                                                               auto&&... args) {
     638             :       return std::forward<F>(func)(ChosenAlternatives{}, std::move(args)...);
     639             :     });
     640             :   });
     641             : }
     642             : /// \endcond
     643             : 
     644             : template <typename OptionList, typename Group>
     645             : template <typename TagsAndSubgroups>
     646             : std::string Parser<OptionList, Group>::help() const {
     647             :   std::ostringstream ss;
     648             :   ss << "\n==== Description of expected options:\n" << help_text_;
     649             :   if (tmpl::size<TagsAndSubgroups>::value > 0) {
     650             :     ss << "\n\nOptions:\n"
     651             :        << Options_detail::print<OptionList, TagsAndSubgroups>::apply("  ");
     652             :   } else {
     653             :     ss << "\n\n<No options>\n";
     654             :   }
     655             :   return ss.str();
     656             : }
     657             : 
     658             : template <typename OptionList, typename Group>
     659             : void Parser<OptionList, Group>::pup(PUP::er& p) {
     660             :   static_assert(std::is_same_v<Group, NoSuchType>,
     661             :                 "Inner parsers should be recreated by the root parser, not "
     662             :                 "serialized.");
     663             :   // We reconstruct most of the state when deserializing, rather than
     664             :   // trying to package it.
     665             :   p | help_text_;
     666             :   p | input_source_;
     667             :   if (p.isUnpacking() and not input_source_.empty()) {
     668             :     // input_source_ is populated by the `parse` and `overlay` calls
     669             :     // below, so we have to clear out the old values before calling
     670             :     // them.
     671             :     auto received_source = std::move(input_source_);
     672             :     input_source_.clear();
     673             :     parse(std::move(received_source[0]));
     674             :     for (size_t i = 1; i < received_source.size(); ++i) {
     675             :       overlay<OptionList>(std::move(received_source[i]));
     676             :     }
     677             :   }
     678             : }
     679             : 
     680             : // implementation of Options::Parser::parse() factored out to save on compile
     681             : // time and compile memory
     682             : namespace parse_detail {
     683             : std::unordered_set<std::string> get_given_options(
     684             :     const Options::Context& context, const YAML::Node& node,
     685             :     const std::string& help);
     686             : 
     687             : void check_for_unique_choice(const std::vector<size_t>& alternative_choices,
     688             :                              const Options::Context& context,
     689             :                              const std::string& parsing_help);
     690             : 
     691             : void add_name_to_valid_option_names(
     692             :     gsl::not_null<std::vector<std::string>*> valid_option_names,
     693             :     const std::string& label);
     694             : 
     695             : template <typename TopLevelOptionsAndGroups>
     696             : struct get_valid_option_names;
     697             : 
     698             : template <typename... TopLevelOptionsAndGroups>
     699             : struct get_valid_option_names<tmpl::list<TopLevelOptionsAndGroups...>> {
     700             :   static std::vector<std::string> apply() {
     701             :     // Use an ordered container so the missing options are reported in
     702             :     // the order they are given in the help string.
     703             :     std::vector<std::string> valid_option_names;
     704             :     valid_option_names.reserve(
     705             :         tmpl::size<tmpl::list<TopLevelOptionsAndGroups...>>{});
     706             :     (add_name_to_valid_option_names(
     707             :          make_not_null(&valid_option_names),
     708             :          pretty_type::name<TopLevelOptionsAndGroups>()),
     709             :      ...);
     710             :     return valid_option_names;
     711             :   }
     712             : };
     713             : 
     714             : [[noreturn]] void option_specified_twice_error(const Options::Context& context,
     715             :                                                const std::string& name,
     716             :                                                const std::string& parsing_help);
     717             : 
     718             : [[noreturn]] void unused_key_error(const Context& context,
     719             :                                    const std::string& name,
     720             :                                    const std::string& parsing_help);
     721             : 
     722             : template <typename Tag>
     723             : void check_for_unused_key(const Context& context, const std::string& name,
     724             :                           const std::string& parsing_help) {
     725             :   if (name == pretty_type::name<Tag>()) {
     726             :     unused_key_error(context, name, parsing_help);
     727             :   }
     728             : }
     729             : 
     730             : template <typename AllPossibleOptions>
     731             : struct check_for_unused_key_helper;
     732             : 
     733             : template <typename... AllPossibleOptions>
     734             : struct check_for_unused_key_helper<tmpl::list<AllPossibleOptions...>> {
     735             :   static void apply(const Context& context, const std::string& name,
     736             :                     const std::string& parsing_help) {
     737             :     (check_for_unused_key<AllPossibleOptions>(context, name, parsing_help),
     738             :      ...);
     739             :   }
     740             : };
     741             : 
     742             : [[noreturn]] void option_invalid_error(const Options::Context& context,
     743             :                                        const std::string& name,
     744             :                                        const std::string& parsing_help);
     745             : 
     746             : void check_for_missing_option(const std::vector<std::string>& valid_names,
     747             :                               const Options::Context& context,
     748             :                               const std::string& parsing_help);
     749             : 
     750             : std::string add_group_prefix_to_name(const std::string& name);
     751             : 
     752             : void print_top_level_error_message();
     753             : }  // namespace parse_detail
     754             : 
     755             : template <typename OptionList, typename Group>
     756             : void Parser<OptionList, Group>::parse(const YAML::Node& node) {
     757             :   std::unordered_set<std::string> given_options =
     758             :       parse_detail::get_given_options(context_, node, help());
     759             : 
     760             :   alternative_choices_ =
     761             :       Options_detail::choose_alternatives<OptionList>(given_options).second;
     762             :   const std::string parsing_help_message = parsing_help(node);
     763             :   parse_detail::check_for_unique_choice(alternative_choices_, context_,
     764             :                                         parsing_help_message);
     765             : 
     766             :   auto valid_names = call_with_chosen_alternatives([](auto option_list_v) {
     767             :     using option_list = decltype(option_list_v);
     768             :     using top_level_options_and_groups =
     769             :         tmpl::remove_duplicates<tmpl::transform<
     770             :             option_list,
     771             :             Options_detail::find_subgroup<tmpl::_1, tmpl::pin<Group>>>>;
     772             :     return parse_detail::get_valid_option_names<
     773             :         top_level_options_and_groups>::apply();
     774             :   });
     775             : 
     776             :   for (const auto& name_and_value : node) {
     777             :     const auto& name = name_and_value.first.as<std::string>();
     778             :     const auto& value = name_and_value.second;
     779             :     auto context = context_;
     780             :     context.line = name_and_value.first.Mark().line;
     781             :     context.column = name_and_value.first.Mark().column;
     782             : 
     783             :     // Check for duplicate key
     784             :     if (0 != parsed_options_.count(name)) {
     785             :       parse_detail::option_specified_twice_error(context, name,
     786             :                                                  parsing_help_message);
     787             :     }
     788             : 
     789             :     // Check for invalid key
     790             :     const auto name_it = alg::find(valid_names, name);
     791             :     if (name_it == valid_names.end()) {
     792             :       parse_detail::check_for_unused_key_helper<all_possible_options>::apply(
     793             :           context, name, parsing_help_message);
     794             :       parse_detail::option_invalid_error(context, name, parsing_help_message);
     795             :     }
     796             : 
     797             :     parsed_options_.emplace(name, value);
     798             :     valid_names.erase(name_it);
     799             :   }
     800             : 
     801             :   parse_detail::check_for_missing_option(valid_names, context_,
     802             :                                          parsing_help_message);
     803             : 
     804             :   tmpl::for_each<subgroups>([this](auto subgroup_v) {
     805             :     using subgroup = tmpl::type_from<decltype(subgroup_v)>;
     806             :     auto& subgroup_parser =
     807             :         tuples::get<SubgroupParser<subgroup>>(subgroup_parsers_);
     808             :     subgroup_parser.context_ = context_;
     809             :     subgroup_parser.context_.append(
     810             :         parse_detail::add_group_prefix_to_name(pretty_type::name<subgroup>()));
     811             :     subgroup_parser.parse(
     812             :         parsed_options_.find(pretty_type::name<subgroup>())->second);
     813             :   });
     814             : 
     815             :   // Any actual warnings will be printed by later calls to get or
     816             :   // apply, but it is not clear how to determine in those functions
     817             :   // whether this message should be printed.
     818             :   if constexpr (std::is_same_v<Group, NoSuchType>) {
     819             :     if (context_.top_level) {
     820             :       parse_detail::print_top_level_error_message();
     821             :     }
     822             :   }
     823             : }
     824             : 
     825             : template <typename OptionList, typename Group>
     826             : template <typename OverlayOptions>
     827             : void Parser<OptionList, Group>::overlay(const YAML::Node& node) {
     828             :   // This could be relaxed to allow mandatory options in a list with
     829             :   // alternatives to be overlaid (or even any options in the chosen
     830             :   // alternative), but overlaying is only done at top level and we
     831             :   // don't use alternatives there (because they conflict with groups,
     832             :   // which we do use).
     833             :   static_assert(
     834             :       std::is_same_v<
     835             :           typename Options_detail::flatten_alternatives<OptionList>::type,
     836             :           OptionList>,
     837             :       "Cannot overlay options when using alternatives.");
     838             :   static_assert(
     839             :       std::is_same_v<tmpl::list_difference<OverlayOptions, OptionList>,
     840             :                      tmpl::list<>>,
     841             :       "Can only overlay options that were originally parsed.");
     842             : 
     843             :   using overlayable_tags_and_subgroups_list =
     844             :       tmpl::remove_duplicates<tmpl::transform<
     845             :           OverlayOptions,
     846             :           Options_detail::find_subgroup<tmpl::_1, tmpl::pin<Group>>>>;
     847             : 
     848             :   if (not(node.IsMap() or node.IsNull())) {
     849             :     PARSE_ERROR(context_, "'" << node << "' does not look like options.\n"
     850             :                               << help<overlayable_tags_and_subgroups_list>());
     851             :   }
     852             : 
     853             :   std::unordered_set<std::string> overlaid_options{};
     854             :   overlaid_options.reserve(node.size());
     855             : 
     856             :   for (const auto& name_and_value : node) {
     857             :     const auto& name = name_and_value.first.as<std::string>();
     858             :     const auto& value = name_and_value.second;
     859             :     auto context = context_;
     860             :     context.line = name_and_value.first.Mark().line;
     861             :     context.column = name_and_value.first.Mark().column;
     862             : 
     863             :     if (tmpl::as_pack<tags_and_subgroups_list>([&name](auto... opts) {
     864             :           return (
     865             :               (name != pretty_type::name<tmpl::type_from<decltype(opts)>>()) and
     866             :               ...);
     867             :         })) {
     868             :       PARSE_ERROR(context,
     869             :                   "Option '" << name << "' is not a valid option.\n"
     870             :                   << parsing_help<overlayable_tags_and_subgroups_list>(node));
     871             :     }
     872             : 
     873             :     if (tmpl::as_pack<overlayable_tags_and_subgroups_list>(
     874             :             [&name](auto... opts) {
     875             :               return ((name !=
     876             :                        pretty_type::name<tmpl::type_from<decltype(opts)>>()) and
     877             :                       ...);
     878             :             })) {
     879             :       PARSE_ERROR(context,
     880             :                   "Option '" << name << "' is not overlayable.\n"
     881             :                   << parsing_help<overlayable_tags_and_subgroups_list>(node));
     882             :     }
     883             : 
     884             :     // Check for duplicate key
     885             :     if (0 != overlaid_options.count(name)) {
     886             :       PARSE_ERROR(context,
     887             :                   "Option '" << name << "' specified twice.\n"
     888             :                   << parsing_help<overlayable_tags_and_subgroups_list>(node));
     889             :     }
     890             : 
     891             :     overlaid_options.insert(name);
     892             :     parsed_options_.at(name) = value;
     893             :   }
     894             : 
     895             :   tmpl::for_each<subgroups>([this, &overlaid_options](auto subgroup_v) {
     896             :     using subgroup = tmpl::type_from<decltype(subgroup_v)>;
     897             :     if (overlaid_options.count(pretty_type::name<subgroup>()) == 1) {
     898             :       auto& subgroup_parser =
     899             :           tuples::get<SubgroupParser<subgroup>>(subgroup_parsers_);
     900             :       subgroup_parser.template overlay<
     901             :           Options_detail::options_in_group<OverlayOptions, subgroup>>(
     902             :           parsed_options_.find(pretty_type::name<subgroup>())->second);
     903             :     }
     904             :   });
     905             : }
     906             : 
     907             : template <typename OptionList, typename Group>
     908             : template <typename T>
     909             : void Parser<OptionList, Group>::check_lower_bound_on_size(
     910             :     const typename T::type& t, const Context& context) const {
     911             :   if constexpr (Options_detail::has_lower_bound_on_size<T>::value) {
     912             :     static_assert(std::is_same_v<decltype(T::lower_bound_on_size()), size_t>,
     913             :                   "lower_bound_on_size() is not a size_t.");
     914             :     if (t.size() < T::lower_bound_on_size()) {
     915             :       PARSE_ERROR(context, "Value must have at least "
     916             :                                << T::lower_bound_on_size() << " entries, but "
     917             :                                << t.size() << " were given.\n"
     918             :                                << help());
     919             :     }
     920             :   }
     921             : }
     922             : 
     923             : template <typename OptionList, typename Group>
     924             : template <typename T>
     925             : void Parser<OptionList, Group>::check_upper_bound_on_size(
     926             :     const typename T::type& t, const Context& context) const {
     927             :   if constexpr (Options_detail::has_upper_bound_on_size<T>::value) {
     928             :     static_assert(std::is_same_v<decltype(T::upper_bound_on_size()), size_t>,
     929             :                   "upper_bound_on_size() is not a size_t.");
     930             :     if (t.size() > T::upper_bound_on_size()) {
     931             :       PARSE_ERROR(context, "Value must have at most "
     932             :                                << T::upper_bound_on_size() << " entries, but "
     933             :                                << t.size() << " were given.\n"
     934             :                                << help());
     935             :     }
     936             :   }
     937             : }
     938             : 
     939             : template <typename OptionList, typename Group>
     940             : template <typename T>
     941             : inline void Parser<OptionList, Group>::check_lower_bound(
     942             :     const typename T::type& t, const Context& context) const {
     943             :   if constexpr (Options_detail::has_lower_bound<T>::value) {
     944             :     static_assert(std::is_same_v<decltype(T::lower_bound()), typename T::type>,
     945             :                   "Lower bound is not of the same type as the option.");
     946             :     static_assert(not std::is_same_v<typename T::type, bool>,
     947             :                   "Cannot set a lower bound for a bool.");
     948             :     if (t < T::lower_bound()) {
     949             :       PARSE_ERROR(context, "Value " << (MakeString{} << t)
     950             :                                     << " is below the lower bound of "
     951             :                                     << (MakeString{} << T::lower_bound())
     952             :                                     << ".\n" << help());
     953             :     }
     954             :   }
     955             : }
     956             : 
     957             : template <typename OptionList, typename Group>
     958             : template <typename T>
     959             : inline void Parser<OptionList, Group>::check_upper_bound(
     960             :     const typename T::type& t, const Context& context) const {
     961             :   if constexpr (Options_detail::has_upper_bound<T>::value) {
     962             :     static_assert(std::is_same_v<decltype(T::upper_bound()), typename T::type>,
     963             :                   "Upper bound is not of the same type as the option.");
     964             :     static_assert(not std::is_same_v<typename T::type, bool>,
     965             :                   "Cannot set an upper bound for a bool.");
     966             :     if (t > T::upper_bound()) {
     967             :       PARSE_ERROR(context, "Value " << (MakeString{} << t)
     968             :                                     << " is above the upper bound of "
     969             :                                     << (MakeString{} << T::upper_bound())
     970             :                                     << ".\n" << help());
     971             :     }
     972             :   }
     973             : }
     974             : 
     975             : template <typename OptionList, typename Group>
     976             : template <typename TagsAndSubgroups>
     977             : std::string Parser<OptionList, Group>::parsing_help(
     978             :     const YAML::Node& options) const {
     979             :   std::ostringstream os;
     980             :   // At top level this would dump the entire input file, which is very
     981             :   // verbose and not very informative.  At lower levels the result
     982             :   // should be much shorter and may actually give useful context for
     983             :   // what part of the file is being parsed.
     984             :   if (not context_.top_level) {
     985             :     os << "\n==== Parsing the option string:\n" << options << "\n";
     986             :   }
     987             :   os << help<TagsAndSubgroups>();
     988             :   return os.str();
     989             : }
     990             : 
     991             : template <typename OptionList, typename Group>
     992             : [[noreturn]] void Parser<OptionList, Group>::parser_error(
     993             :     const YAML::Exception& e) const {
     994             :   auto context = context_;
     995             :   context.line = e.mark.line;
     996             :   context.column = e.mark.column;
     997             :   // Inline the top_level branch of PARSE_ERROR to avoid warning that
     998             :   // the other branch would call terminate.  (Parser errors can only
     999             :   // be generated at top level.)
    1000             :   ERROR(
    1001             :       "\n"
    1002             :       << context
    1003             :       << "Unable to correctly parse the input file because of a syntax error.\n"
    1004             :          "This is often due to placing a suboption on the same line as an "
    1005             :          "option, e.g.:\nDomainCreator: CreateInterval:\n  IsPeriodicIn: "
    1006             :          "[false]\n\nShould be:\nDomainCreator:\n  CreateInterval:\n    "
    1007             :          "IsPeriodicIn: [true]\n\nSee an example input file for help.");
    1008             : }
    1009             : 
    1010             : template <typename OptionList, typename Group>
    1011             : template <typename ChosenOptions, typename RemainingOptions, typename F>
    1012             : auto Parser<OptionList, Group>::call_with_chosen_alternatives_impl(
    1013             :     F&& func, std::vector<size_t> choices) const {
    1014             :   if constexpr (std::is_same_v<RemainingOptions, tmpl::list<>>) {
    1015             :     return std::forward<F>(func)(ChosenOptions{});
    1016             :   } else {
    1017             :     using next_option = tmpl::front<RemainingOptions>;
    1018             :     using remaining_options = tmpl::pop_front<RemainingOptions>;
    1019             : 
    1020             :     if constexpr (not tt::is_a_v<Options::Alternatives, next_option>) {
    1021             :       return call_with_chosen_alternatives_impl<
    1022             :           tmpl::push_back<ChosenOptions, next_option>, remaining_options>(
    1023             :           std::forward<F>(func), std::move(choices));
    1024             :     } else {
    1025             :       using Result =
    1026             :           decltype(call_with_chosen_alternatives_impl<
    1027             :                    ChosenOptions,
    1028             :                    tmpl::append<tmpl::front<next_option>, remaining_options>>(
    1029             :               std::forward<F>(func), std::move(choices)));
    1030             : 
    1031             :       const size_t choice = choices.back();
    1032             :       choices.pop_back();
    1033             : 
    1034             :       Result result{};
    1035             :       size_t alternative_number = 0;
    1036             :       tmpl::for_each<next_option>([this, &alternative_number, &choice, &choices,
    1037             :                                    &func, &result](auto alternative) {
    1038             :         using Alternative = tmpl::type_from<decltype(alternative)>;
    1039             :         if (choice == alternative_number++) {
    1040             :           result = this->call_with_chosen_alternatives_impl<
    1041             :               ChosenOptions, tmpl::append<Alternative, remaining_options>>(
    1042             :               std::forward<F>(func), std::move(choices));
    1043             :         }
    1044             :       });
    1045             :       return result;
    1046             :     }
    1047             :   }
    1048             : }
    1049             : 
    1050             : namespace Options_detail {
    1051             : // Work around Clang bug: https://github.com/llvm/llvm-project/issues/33002
    1052             : template <typename... T>
    1053             : struct my_void_t_impl {
    1054             :   using type = void;
    1055             : };
    1056             : template <typename... T>
    1057             : using my_void_t = typename my_void_t_impl<T...>::type;
    1058             : 
    1059             : template <typename T, typename Metavariables, typename = my_void_t<>>
    1060             : struct has_options_list : std::false_type {};
    1061             : 
    1062             : template <typename T, typename Metavariables>
    1063             : struct has_options_list<T, Metavariables,
    1064             :                         my_void_t<typename T::template options<Metavariables>>>
    1065             :     : std::true_type {};
    1066             : 
    1067             : template <typename T, typename Metavariables>
    1068             : struct has_options_list<T, Metavariables, my_void_t<typename T::options>>
    1069             :     : std::true_type {};
    1070             : 
    1071             : template <typename T, typename Metavariables, typename = std::void_t<>>
    1072             : struct get_options_list {
    1073             :   using type = typename T::template options<Metavariables>;
    1074             : };
    1075             : 
    1076             : template <typename T, typename Metavariables>
    1077             : struct get_options_list<T, Metavariables, std::void_t<typename T::options>> {
    1078             :   using type = typename T::options;
    1079             : };
    1080             : 
    1081             : template <typename T, typename Metavariables>
    1082             : struct ClassConstructor {
    1083             :   const Options::Context& context;
    1084             : 
    1085             :   template <typename ParsedOptions, typename... Args>
    1086             :   T operator()(ParsedOptions /*meta*/, Args&&... args) const {
    1087             :     if constexpr (std::is_constructible<T, ParsedOptions,
    1088             :                                         decltype(std::move(args))...,
    1089             :                                         const Context&, Metavariables>{}) {
    1090             :       return T(ParsedOptions{}, std::move(args)..., context, Metavariables{});
    1091             :     } else if constexpr (std::is_constructible<T, ParsedOptions,
    1092             :                                                decltype(std::move(args))...,
    1093             :                                                const Context&>{}) {
    1094             :       return T(ParsedOptions{}, std::move(args)..., context);
    1095             :     } else if constexpr (std::is_constructible<T, ParsedOptions,
    1096             :                                                decltype(std::move(
    1097             :                                                    args))...>{}) {
    1098             :       return T(ParsedOptions{}, std::move(args)...);
    1099             :     } else if constexpr (std::is_constructible<T, decltype(std::move(args))...,
    1100             :                                                const Context&,
    1101             :                                                Metavariables>{}) {
    1102             :       return T(std::move(args)..., context, Metavariables{});
    1103             :     } else if constexpr (std::is_constructible<T, decltype(std::move(args))...,
    1104             :                                                const Context&>{}) {
    1105             :       return T(std::move(args)..., context);
    1106             :     } else {
    1107             :       return T{std::move(args)...};
    1108             :     }
    1109             :   }
    1110             : };
    1111             : }  // namespace Options_detail
    1112             : 
    1113             : template <typename T>
    1114             : template <typename Metavariables>
    1115             : T create_from_yaml<T>::create(const Option& options) {
    1116             :   Parser<typename Options_detail::get_options_list<T, Metavariables>::type>
    1117             :       parser(T::help);
    1118             :   parser.parse(options);
    1119             :   return parser.template apply_all<Metavariables>(
    1120             :       Options_detail::ClassConstructor<T, Metavariables>{options.context()});
    1121             : }
    1122             : 
    1123             : // yaml-cpp doesn't handle C++11 types yet
    1124             : template <typename K, typename V, typename H, typename P>
    1125             : struct create_from_yaml<std::unordered_map<K, V, H, P>> {
    1126             :   template <typename Metavariables>
    1127             :   static std::unordered_map<K, V, H, P> create(const Option& options) {
    1128             :     auto ordered = options.parse_as<std::map<K, V>, Metavariables>();
    1129             :     std::unordered_map<K, V, H, P> result;
    1130             :     for (auto it = ordered.begin(); it != ordered.end();) {
    1131             :       auto node = ordered.extract(it++);
    1132             :       result.emplace(std::move(node.key()), std::move(node.mapped()));
    1133             :     }
    1134             :     return result;
    1135             :   }
    1136             : };
    1137             : 
    1138             : namespace Options_detail {
    1139             : // To get the full parse backtrace for a variant parse error the
    1140             : // failure should occur inside a nested call to parse_as.  This is a
    1141             : // type that will produce the correct error by failing to parse.
    1142             : template <typename... T>
    1143             : struct variant_parse_error {};
    1144             : 
    1145             : template <typename... T>
    1146             : struct yaml_type<variant_parse_error<T...>> : yaml_type<std::variant<T...>> {};
    1147             : }  // namespace Options_detail
    1148             : 
    1149             : template <typename... T>
    1150             : struct create_from_yaml<Options::Options_detail::variant_parse_error<T...>> {
    1151             :   template <typename Metavariables>
    1152             :   [[noreturn]] static Options::Options_detail::variant_parse_error<T...> create(
    1153             :       const Option& options) {
    1154             :     throw YAML::BadConversion(options.node().Mark());
    1155             :   }
    1156             : };
    1157             : 
    1158             : namespace Options_variant_detail {
    1159             : template <typename T, typename Metavariables,
    1160             :           typename =
    1161             :               typename Options_detail::has_options_list<T, Metavariables>::type>
    1162             : struct is_alternative_parsable_impl : std::false_type {};
    1163             : 
    1164             : template <typename T, typename Metavariables>
    1165             : struct is_alternative_parsable_impl<T, Metavariables, std::true_type>
    1166             :     : tmpl::all<
    1167             :           typename Options_detail::get_options_list<T, Metavariables>::type,
    1168             :           std::is_same<tmpl::_1,
    1169             :                        Options_detail::find_subgroup<tmpl::_1, void>>> {};
    1170             : 
    1171             : template <typename T, typename Metavariables>
    1172             : struct is_alternative_parsable
    1173             :     : is_alternative_parsable_impl<T, Metavariables> {};
    1174             : 
    1175             : template <typename Result, typename Metavariables, typename... Alternatives>
    1176             : Result parse_as_alternatives(const Options::Option& options,
    1177             :                              tmpl::list<Alternatives...> /*meta*/) {
    1178             :   using options_list = tmpl::list<
    1179             :       Options::Alternatives<typename Options_detail::get_options_list<
    1180             :           Alternatives, Metavariables>::type...>>;
    1181             :   std::string help = ("" + ... +
    1182             :                       (Options_detail::yaml_type<Alternatives>::value() + "\n" +
    1183             :                        wrap_text(Alternatives::help, 77, "  ") + "\n\n"));
    1184             :   help.resize(help.size() - 2);
    1185             :   Options::Parser<options_list> parser(std::move(help));
    1186             :   parser.parse(options);
    1187             :   return parser.template apply_all<Metavariables>([&](auto parsed_options,
    1188             :                                                       auto... args) -> Result {
    1189             :     // Actually matching against the whole option list is hard in the
    1190             :     // presence of possible nested alternatives, so we just check if
    1191             :     // all the options are individually valid.
    1192             :     using possible_alternatives = tmpl::filter<
    1193             :         tmpl::list<Alternatives...>,
    1194             :         std::is_same<tmpl::bind<tmpl::list_difference,
    1195             :                                 tmpl::pin<decltype(parsed_options)>,
    1196             :                                 Options_detail::flatten_alternatives<
    1197             :                                     Options_detail::get_options_list<
    1198             :                                         tmpl::_1, tmpl::pin<Metavariables>>>>,
    1199             :                      tmpl::pin<tmpl::list<>>>>;
    1200             :     static_assert(tmpl::size<possible_alternatives>::value == 1,
    1201             :                   "Option lists for variant alternatives are too similar.");
    1202             :     return Options_detail::ClassConstructor<tmpl::front<possible_alternatives>,
    1203             :                                             Metavariables>{options.context()}(
    1204             :         parsed_options, std::move(args)...);
    1205             :   });
    1206             : }
    1207             : }  // namespace Options_variant_detail
    1208             : 
    1209             : template <typename... T>
    1210             : struct create_from_yaml<std::variant<T...>> {
    1211             :   using Result = std::variant<T...>;
    1212             :   static_assert(std::is_same_v<tmpl::list<T...>,
    1213             :                                tmpl::remove_duplicates<tmpl::list<T...>>>,
    1214             :                 "Cannot parse variants with duplicate types.");
    1215             : 
    1216             :   template <typename Metavariables>
    1217             :   static Result create(const Option& options) {
    1218             :     using alternative_parsable_types =
    1219             :         tmpl::filter<tmpl::list<T...>,
    1220             :                      Options_variant_detail::is_alternative_parsable<
    1221             :                          tmpl::_1, tmpl::pin<Metavariables>>>;
    1222             : 
    1223             :     static constexpr bool use_alternative_parsing =
    1224             :         std::is_same_v<alternative_parsable_types, tmpl::list<T...>>;
    1225             :     static constexpr bool use_hybrid_parsing =
    1226             :         not use_alternative_parsing and
    1227             :         tmpl::size<alternative_parsable_types>::value > 1;
    1228             : 
    1229             :     if constexpr (use_alternative_parsing) {
    1230             :       return Options_variant_detail::parse_as_alternatives<Result,
    1231             :                                                            Metavariables>(
    1232             :           options, alternative_parsable_types{});
    1233             :     } else {
    1234             :       Result result{};
    1235             :       std::string errors{};
    1236             :       bool constructed = false;
    1237             : 
    1238             :       if constexpr (use_hybrid_parsing) {
    1239             :         try {
    1240             :           result = Options_variant_detail::parse_as_alternatives<Result,
    1241             :                                                                  Metavariables>(
    1242             :               options, alternative_parsable_types{});
    1243             :           constructed = true;
    1244             :         } catch (const Options::detail::propagate_context& e) {
    1245             :           // This alternative failed, but a later one may succeed.
    1246             :           errors += "\n\n" + e.message();
    1247             :         }
    1248             :       }
    1249             : 
    1250             :       const auto try_parse = [&constructed, &errors, &options,
    1251             :                               &result](auto alternative_v) {
    1252             :         using Alternative = tmpl::type_from<decltype(alternative_v)>;
    1253             :         if (use_hybrid_parsing and
    1254             :             tmpl::any<alternative_parsable_types,
    1255             :                       std::is_same<tmpl::_1, tmpl::pin<Alternative>>>::value) {
    1256             :           return;
    1257             :         }
    1258             :         if (constructed) {
    1259             :           return;
    1260             :         }
    1261             :         try {
    1262             :           result = options.parse_as<Alternative, Metavariables>();
    1263             :           constructed = true;
    1264             :         } catch (const Options::detail::propagate_context& e) {
    1265             :           // This alternative failed, but a later one may succeed.
    1266             :           errors += "\n\n" + e.message();
    1267             :         }
    1268             :       };
    1269             :       EXPAND_PACK_LEFT_TO_RIGHT(try_parse(tmpl::type_<T>{}));
    1270             :       if (not constructed) {
    1271             :         try {
    1272             :           options.parse_as<Options_detail::variant_parse_error<T...>,
    1273             :                            Metavariables>();
    1274             :         } catch (const Options::detail::propagate_context& e) {
    1275             :           throw Options::detail::propagate_context(
    1276             :               e.message() + "\n\nPossible errors:" + errors);
    1277             :         }
    1278             :       }
    1279             :       return result;
    1280             :     }
    1281             :   }
    1282             : };
    1283             : }  // namespace Options
    1284             : 
    1285             : /// \cond
    1286             : template <typename T, typename Metavariables>
    1287             : struct YAML::convert<Options::Options_detail::CreateWrapper<T, Metavariables>> {
    1288             :   static bool decode(
    1289             :       const Node& node,
    1290             :       Options::Options_detail::CreateWrapper<T, Metavariables>& rhs) {
    1291             :     Options::Context context;
    1292             :     context.top_level = false;
    1293             :     context.append("While creating a " + pretty_type::name<T>());
    1294             :     Options::Option options(node, std::move(context));
    1295             :     rhs = Options::Options_detail::CreateWrapper<T, Metavariables>{
    1296             :         Options::create_from_yaml<T>::template create<Metavariables>(options)};
    1297             :     return true;
    1298             :   }
    1299             : };
    1300             : /// \endcond
    1301             : 
    1302             : #include "Options/Factory.hpp"

Generated by: LCOV version 1.14