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

Generated by: LCOV version 1.14