SpECTRE Documentation Coverage Report
Current view: top level - Options - ParseOptions.hpp Coverage Total Hit
Commit: 0d1d717af84a81fb1284a20c4013708fe85cc2f0 Lines: 57.9 % 38 22
Test Date: 2026-10-22 17:29:42
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 "DataStructures/TaggedTuple.hpp"
      29              : #include "Options/Context.hpp"
      30              : #include "Options/Options.hpp"
      31              : #include "Options/OptionsDetails.hpp"
      32              : #include "Options/ParseError.hpp"
      33              : #include "Options/Tags.hpp"
      34              : #include "Parallel/Printf/Printf.hpp"
      35              : #include "Utilities/Algorithm.hpp"
      36              : #include "Utilities/ErrorHandling/Assert.hpp"
      37              : #include "Utilities/ErrorHandling/Error.hpp"
      38              : #include "Utilities/Gsl.hpp"
      39              : #include "Utilities/MakeString.hpp"
      40              : #include "Utilities/NoSuchType.hpp"
      41              : #include "Utilities/PrettyType.hpp"
      42              : #include "Utilities/StdHelpers.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              : 
     994              :   std::string hint =
     995              :       "This is often due to placing a suboption on the same line as an "
     996              :       "option, e.g.:\nDomainCreator: CreateInterval:\n  IsPeriodicIn: "
     997              :       "[false]\n\nShould be:\nDomainCreator:\n  CreateInterval:\n    "
     998              :       "IsPeriodicIn: [true]\n\nSee an example input file for help.";
     999              :   if (input_source_.back().find('\t') != std::string::npos) {
    1000              :     hint = "This may be due to indenting with tabs.";
    1001              :   }
    1002              :   // Inline the top_level branch of PARSE_ERROR to avoid warning that
    1003              :   // the other branch would call terminate.  (Parser errors can only
    1004              :   // be generated at top level.)
    1005              :   ERROR_NO_TRACE(
    1006              :       "\n"
    1007              :       << context
    1008              :       << "Unable to correctly parse the input file because of a syntax error:\n"
    1009              :       << "  " << e.msg << "\n"
    1010              :       << hint);
    1011              : }
    1012              : 
    1013              : template <typename OptionList, typename Group>
    1014              : template <typename ChosenOptions, typename RemainingOptions, typename F>
    1015              : auto Parser<OptionList, Group>::call_with_chosen_alternatives_impl(
    1016              :     F&& func, std::vector<size_t> choices) const {
    1017              :   if constexpr (std::is_same_v<RemainingOptions, tmpl::list<>>) {
    1018              :     return std::forward<F>(func)(ChosenOptions{});
    1019              :   } else {
    1020              :     using next_option = tmpl::front<RemainingOptions>;
    1021              :     using remaining_options = tmpl::pop_front<RemainingOptions>;
    1022              : 
    1023              :     if constexpr (not tt::is_a_v<Options::Alternatives, next_option>) {
    1024              :       return call_with_chosen_alternatives_impl<
    1025              :           tmpl::push_back<ChosenOptions, next_option>, remaining_options>(
    1026              :           std::forward<F>(func), std::move(choices));
    1027              :     } else {
    1028              :       using Result =
    1029              :           decltype(call_with_chosen_alternatives_impl<
    1030              :                    ChosenOptions,
    1031              :                    tmpl::append<tmpl::front<next_option>, remaining_options>>(
    1032              :               std::forward<F>(func), std::move(choices)));
    1033              : 
    1034              :       const size_t choice = choices.back();
    1035              :       choices.pop_back();
    1036              : 
    1037              :       Result result{};
    1038              :       size_t alternative_number = 0;
    1039              :       tmpl::for_each<next_option>([this, &alternative_number, &choice, &choices,
    1040              :                                    &func, &result](auto alternative) {
    1041              :         using Alternative = tmpl::type_from<decltype(alternative)>;
    1042              :         if (choice == alternative_number++) {
    1043              :           result = this->call_with_chosen_alternatives_impl<
    1044              :               ChosenOptions, tmpl::append<Alternative, remaining_options>>(
    1045              :               std::forward<F>(func), std::move(choices));
    1046              :         }
    1047              :       });
    1048              :       return result;
    1049              :     }
    1050              :   }
    1051              : }
    1052              : 
    1053              : namespace Options_detail {
    1054              : // Work around Clang bug: https://github.com/llvm/llvm-project/issues/33002
    1055              : template <typename... T>
    1056              : struct my_void_t_impl {
    1057              :   using type = void;
    1058              : };
    1059              : template <typename... T>
    1060              : using my_void_t = typename my_void_t_impl<T...>::type;
    1061              : 
    1062              : template <typename T, typename Metavariables, typename = my_void_t<>>
    1063              : struct has_options_list : std::false_type {};
    1064              : 
    1065              : template <typename T, typename Metavariables>
    1066              : struct has_options_list<T, Metavariables,
    1067              :                         my_void_t<typename T::template options<Metavariables>>>
    1068              :     : std::true_type {};
    1069              : 
    1070              : template <typename T, typename Metavariables>
    1071              : struct has_options_list<T, Metavariables, my_void_t<typename T::options>>
    1072              :     : std::true_type {};
    1073              : 
    1074              : template <typename T, typename Metavariables, typename = std::void_t<>>
    1075              : struct get_options_list {
    1076              :   using type = typename T::template options<Metavariables>;
    1077              : };
    1078              : 
    1079              : template <typename T, typename Metavariables>
    1080              : struct get_options_list<T, Metavariables, std::void_t<typename T::options>> {
    1081              :   using type = typename T::options;
    1082              : };
    1083              : 
    1084              : template <typename T, typename Metavariables>
    1085              : struct ClassConstructor {
    1086              :   const Options::Context& context;
    1087              : 
    1088              :   template <typename ParsedOptions, typename... Args>
    1089              :   T operator()(ParsedOptions /*meta*/, Args&&... args) const {
    1090              :     if constexpr (std::is_constructible<T, ParsedOptions,
    1091              :                                         decltype(std::move(args))...,
    1092              :                                         const Context&, Metavariables>{}) {
    1093              :       return T(ParsedOptions{}, std::move(args)..., context, Metavariables{});
    1094              :     } else if constexpr (std::is_constructible<T, ParsedOptions,
    1095              :                                                decltype(std::move(args))...,
    1096              :                                                const Context&>{}) {
    1097              :       return T(ParsedOptions{}, std::move(args)..., context);
    1098              :     } else if constexpr (std::is_constructible<T, ParsedOptions,
    1099              :                                                decltype(std::move(
    1100              :                                                    args))...>{}) {
    1101              :       return T(ParsedOptions{}, std::move(args)...);
    1102              :     } else if constexpr (std::is_constructible<T, decltype(std::move(args))...,
    1103              :                                                const Context&,
    1104              :                                                Metavariables>{}) {
    1105              :       return T(std::move(args)..., context, Metavariables{});
    1106              :     } else if constexpr (std::is_constructible<T, decltype(std::move(args))...,
    1107              :                                                const Context&>{}) {
    1108              :       return T(std::move(args)..., context);
    1109              :     } else {
    1110              :       return T{std::move(args)...};
    1111              :     }
    1112              :   }
    1113              : };
    1114              : }  // namespace Options_detail
    1115              : 
    1116              : template <typename T>
    1117              : template <typename Metavariables>
    1118              : T create_from_yaml<T>::create(const Option& options) {
    1119              :   Parser<typename Options_detail::get_options_list<T, Metavariables>::type>
    1120              :       parser(T::help);
    1121              :   parser.parse(options);
    1122              :   return parser.template apply_all<Metavariables>(
    1123              :       Options_detail::ClassConstructor<T, Metavariables>{options.context()});
    1124              : }
    1125              : 
    1126              : // yaml-cpp doesn't handle C++11 types yet
    1127              : template <typename K, typename V, typename H, typename P>
    1128              : struct create_from_yaml<std::unordered_map<K, V, H, P>> {
    1129              :   template <typename Metavariables>
    1130              :   static std::unordered_map<K, V, H, P> create(const Option& options) {
    1131              :     auto ordered = options.parse_as<std::map<K, V>, Metavariables>();
    1132              :     std::unordered_map<K, V, H, P> result;
    1133              :     for (auto it = ordered.begin(); it != ordered.end();) {
    1134              :       auto node = ordered.extract(it++);
    1135              :       result.emplace(std::move(node.key()), std::move(node.mapped()));
    1136              :     }
    1137              :     return result;
    1138              :   }
    1139              : };
    1140              : 
    1141              : namespace Options_detail {
    1142              : // To get the full parse backtrace for a variant parse error the
    1143              : // failure should occur inside a nested call to parse_as.  This is a
    1144              : // type that will produce the correct error by failing to parse.
    1145              : template <typename... T>
    1146              : struct variant_parse_error {};
    1147              : 
    1148              : template <typename... T>
    1149              : struct yaml_type<variant_parse_error<T...>> : yaml_type<std::variant<T...>> {};
    1150              : }  // namespace Options_detail
    1151              : 
    1152              : template <typename... T>
    1153              : struct create_from_yaml<Options::Options_detail::variant_parse_error<T...>> {
    1154              :   template <typename Metavariables>
    1155              :   [[noreturn]] static Options::Options_detail::variant_parse_error<T...> create(
    1156              :       const Option& options) {
    1157              :     throw YAML::BadConversion(options.node().Mark());
    1158              :   }
    1159              : };
    1160              : 
    1161              : namespace Options_variant_detail {
    1162              : template <typename T, typename Metavariables,
    1163              :           typename =
    1164              :               typename Options_detail::has_options_list<T, Metavariables>::type>
    1165              : struct is_alternative_parsable_impl : std::false_type {};
    1166              : 
    1167              : template <typename T, typename Metavariables>
    1168              : struct is_alternative_parsable_impl<T, Metavariables, std::true_type>
    1169              :     : tmpl::all<
    1170              :           typename Options_detail::get_options_list<T, Metavariables>::type,
    1171              :           std::is_same<tmpl::_1,
    1172              :                        Options_detail::find_subgroup<tmpl::_1, void>>> {};
    1173              : 
    1174              : template <typename T, typename Metavariables>
    1175              : struct is_alternative_parsable
    1176              :     : is_alternative_parsable_impl<T, Metavariables> {};
    1177              : 
    1178              : template <typename Result, typename Metavariables, typename... Alternatives>
    1179              : Result parse_as_alternatives(const Options::Option& options,
    1180              :                              tmpl::list<Alternatives...> /*meta*/) {
    1181              :   using options_list = tmpl::list<
    1182              :       Options::Alternatives<typename Options_detail::get_options_list<
    1183              :           Alternatives, Metavariables>::type...>>;
    1184              :   std::string help = ("" + ... +
    1185              :                       (Options_detail::yaml_type<Alternatives>::value() + "\n" +
    1186              :                        wrap_text(Alternatives::help, 77, "  ") + "\n\n"));
    1187              :   help.resize(help.size() - 2);
    1188              :   Options::Parser<options_list> parser(std::move(help));
    1189              :   parser.parse(options);
    1190              :   return parser.template apply_all<Metavariables>([&](auto parsed_options,
    1191              :                                                       auto... args) -> Result {
    1192              :     // Actually matching against the whole option list is hard in the
    1193              :     // presence of possible nested alternatives, so we just check if
    1194              :     // all the options are individually valid.
    1195              :     using possible_alternatives = tmpl::filter<
    1196              :         tmpl::list<Alternatives...>,
    1197              :         std::is_same<tmpl::bind<tmpl::list_difference,
    1198              :                                 tmpl::pin<decltype(parsed_options)>,
    1199              :                                 Options_detail::flatten_alternatives<
    1200              :                                     Options_detail::get_options_list<
    1201              :                                         tmpl::_1, tmpl::pin<Metavariables>>>>,
    1202              :                      tmpl::pin<tmpl::list<>>>>;
    1203              :     static_assert(tmpl::size<possible_alternatives>::value == 1,
    1204              :                   "Option lists for variant alternatives are too similar.");
    1205              :     return Options_detail::ClassConstructor<tmpl::front<possible_alternatives>,
    1206              :                                             Metavariables>{options.context()}(
    1207              :         parsed_options, std::move(args)...);
    1208              :   });
    1209              : }
    1210              : }  // namespace Options_variant_detail
    1211              : 
    1212              : template <typename... T>
    1213              : struct create_from_yaml<std::variant<T...>> {
    1214              :   using Result = std::variant<T...>;
    1215              :   static_assert(std::is_same_v<tmpl::list<T...>,
    1216              :                                tmpl::remove_duplicates<tmpl::list<T...>>>,
    1217              :                 "Cannot parse variants with duplicate types.");
    1218              : 
    1219              :   template <typename Metavariables>
    1220              :   static Result create(const Option& options) {
    1221              :     using alternative_parsable_types =
    1222              :         tmpl::filter<tmpl::list<T...>,
    1223              :                      Options_variant_detail::is_alternative_parsable<
    1224              :                          tmpl::_1, tmpl::pin<Metavariables>>>;
    1225              : 
    1226              :     static constexpr bool use_alternative_parsing =
    1227              :         std::is_same_v<alternative_parsable_types, tmpl::list<T...>>;
    1228              :     static constexpr bool use_hybrid_parsing =
    1229              :         not use_alternative_parsing and
    1230              :         tmpl::size<alternative_parsable_types>::value > 1;
    1231              : 
    1232              :     if constexpr (use_alternative_parsing) {
    1233              :       return Options_variant_detail::parse_as_alternatives<Result,
    1234              :                                                            Metavariables>(
    1235              :           options, alternative_parsable_types{});
    1236              :     } else {
    1237              :       const std::string error_separator =
    1238              :           "\n--------------------------------------------------\n";
    1239              :       Result result{};
    1240              :       std::string errors{};
    1241              :       bool constructed = false;
    1242              : 
    1243              :       if constexpr (use_hybrid_parsing) {
    1244              :         try {
    1245              :           result = Options_variant_detail::parse_as_alternatives<Result,
    1246              :                                                                  Metavariables>(
    1247              :               options, alternative_parsable_types{});
    1248              :           constructed = true;
    1249              :         } catch (const Options::detail::propagate_context& e) {
    1250              :           // This alternative failed, but a later one may succeed.
    1251              :           errors += error_separator + e.message();
    1252              :         }
    1253              :       }
    1254              : 
    1255              :       const auto try_parse = [&constructed, &error_separator, &errors, &options,
    1256              :                               &result](auto alternative_v) {
    1257              :         using Alternative = tmpl::type_from<decltype(alternative_v)>;
    1258              :         if (use_hybrid_parsing and
    1259              :             tmpl::any<alternative_parsable_types,
    1260              :                       std::is_same<tmpl::_1, tmpl::pin<Alternative>>>::value) {
    1261              :           return;
    1262              :         }
    1263              :         if (constructed) {
    1264              :           return;
    1265              :         }
    1266              :         try {
    1267              :           result = options.parse_as<Alternative, Metavariables>();
    1268              :           constructed = true;
    1269              :         } catch (const Options::detail::propagate_context& e) {
    1270              :           // This alternative failed, but a later one may succeed.
    1271              :           errors += error_separator + e.message();
    1272              :         }
    1273              :       };
    1274              :       EXPAND_PACK_LEFT_TO_RIGHT(try_parse(tmpl::type_<T>{}));
    1275              :       if (not constructed) {
    1276              :         try {
    1277              :           options.parse_as<Options_detail::variant_parse_error<T...>,
    1278              :                            Metavariables>();
    1279              :         } catch (const Options::detail::propagate_context& e) {
    1280              :           throw Options::detail::propagate_context(
    1281              :               e.message() + "\n\nPossible errors:" + errors);
    1282              :         }
    1283              :       }
    1284              :       return result;
    1285              :     }
    1286              :   }
    1287              : };
    1288              : }  // namespace Options
    1289              : 
    1290              : /// \cond
    1291              : template <typename T, typename Metavariables>
    1292              : struct YAML::convert<Options::Options_detail::CreateWrapper<T, Metavariables>> {
    1293              :   static bool decode(
    1294              :       const Node& node,
    1295              :       Options::Options_detail::CreateWrapper<T, Metavariables>& rhs) {
    1296              :     if constexpr (tt::is_integer_v<T>) {
    1297              :       // Try parsing integers as floating point to allow scientific
    1298              :       // notation for large values.
    1299              :       T result{};
    1300              :       if (not YAML::convert<T>::decode(node, result)) {
    1301              :         double float_result{};
    1302              :         if (not YAML::convert<double>::decode(node, float_result) or
    1303              :             float_result > static_cast<double>(std::numeric_limits<T>::max()) or
    1304              :             float_result < static_cast<double>(std::numeric_limits<T>::min())) {
    1305              :           return false;
    1306              :         }
    1307              :         result = static_cast<T>(float_result);
    1308              :         if (static_cast<double>(result) != float_result) {
    1309              :           return false;
    1310              :         }
    1311              :       }
    1312              :       rhs = Options::Options_detail::CreateWrapper<T, Metavariables>{result};
    1313              :       return true;
    1314              :     } else {
    1315              :       Options::Context context;
    1316              :       context.top_level = false;
    1317              :       context.append("While creating a " + pretty_type::name<T>());
    1318              :       Options::Option options(node, std::move(context));
    1319              :       rhs = Options::Options_detail::CreateWrapper<T, Metavariables>{
    1320              :           Options::create_from_yaml<T>::template create<Metavariables>(
    1321              :               options)};
    1322              :       return true;
    1323              :     }
    1324              :   }
    1325              : };
    1326              : /// \endcond
    1327              : 
    1328              : #include "Options/Factory.hpp"
        

Generated by: LCOV version 2.0-1