SpECTRE Documentation Coverage Report
Current view: top level - Options - ParseOptions.hpp Hit Total Coverage
Commit: ebec864322c50bab8dca0a90baf8d01875114261 Lines: 18 24 75.0 %
Date: 2020-11-25 20:28:50
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 <cstring>
      10             : #include <exception>
      11             : #include <ios>
      12             : #include <iterator>
      13             : #include <map>
      14             : #include <ostream>
      15             : #include <sstream>
      16             : #include <string>
      17             : #include <unordered_map>
      18             : #include <unordered_set>
      19             : #include <utility>
      20             : #include <vector>
      21             : #include <yaml-cpp/yaml.h>
      22             : 
      23             : #include "ErrorHandling/Assert.hpp"
      24             : #include "ErrorHandling/Error.hpp"
      25             : #include "Options/Options.hpp"
      26             : #include "Options/OptionsDetails.hpp"
      27             : #include "Utilities/MakeString.hpp"
      28             : #include "Utilities/NoSuchType.hpp"
      29             : #include "Utilities/PrettyType.hpp"
      30             : #include "Utilities/StdHelpers.hpp"
      31             : #include "Utilities/TypeTraits.hpp"
      32             : #include "Utilities/TypeTraits/IsA.hpp"
      33             : #include "Utilities/TypeTraits/IsMaplike.hpp"
      34             : #include "Utilities/TypeTraits/IsStdArray.hpp"
      35             : #include "Utilities/TypeTraits/IsStdArrayOfSize.hpp"
      36             : 
      37             : namespace Options {
      38             : // Defining methods as inline in a different header from the class
      39             : // definition is somewhat strange.  It is done here to minimize the
      40             : // amount of code in the frequently-included Options.hpp file.  The
      41             : // only external consumers of Option should be create_from_yaml
      42             : // specializations, and they should only be instantiated by code in
      43             : // this file.  (Or explicitly instantiated in cpp files, which can
      44             : // include this file.)
      45             : 
      46             : // clang-tidy: YAML::Node not movable (as of yaml-cpp-0.5.3)
      47             : // NOLINTNEXTLINE(performance-unnecessary-value-param)
      48             : inline Option::Option(YAML::Node node, Context context) noexcept
      49             :     : node_(std::make_unique<YAML::Node>(std::move(node))),
      50             :       context_(std::move(context)) {  // NOLINT
      51             :   context_.line = node.Mark().line;
      52             :   context_.column = node.Mark().column;
      53             : }
      54             : 
      55             : inline Option::Option(Context context) noexcept
      56             :     : node_(std::make_unique<YAML::Node>()), context_(std::move(context)) {}
      57             : 
      58             : inline const YAML::Node& Option::node() const noexcept { return *node_; }
      59             : inline const Context& Option::context() const noexcept { return context_; }
      60             : 
      61             : /// Append a line to the contained context.
      62             : inline void Option::append_context(const std::string& context) noexcept {
      63             :   context_.append(context);
      64             : }
      65             : 
      66             : // NOLINTNEXTLINE(performance-unnecessary-value-param)
      67             : inline void Option::set_node(YAML::Node node) noexcept {
      68             :   // clang-tidy: YAML::Node not movable (as of yaml-cpp-0.5.3)
      69             :   *node_ = std::move(node);  // NOLINT
      70             :   context_.line = node_->Mark().line;
      71             :   context_.column = node_->Mark().column;
      72             : }
      73             : 
      74             : template <typename T, typename Metavariables>
      75             : T Option::parse_as() const {
      76             :   try {
      77             :     // yaml-cpp's `as` method won't parse empty nodes, so we need to
      78             :     // inline a bit of its logic.
      79             :     Options_detail::wrap_create_types<T, Metavariables> result{};
      80             :     if (YAML::convert<decltype(result)>::decode(node(), result)) {
      81             :       return Options_detail::unwrap_create_types(std::move(result));
      82             :     }
      83             :     // clang-tidy: thrown exception is not nothrow copy constructible
      84             :     throw YAML::BadConversion(node().Mark());  // NOLINT
      85             :   } catch (const YAML::BadConversion& e) {
      86             :     // This happens when trying to parse an empty value as a container
      87             :     // with no entries.
      88             :     if ((tt::is_a_v<std::vector, T> or tt::is_std_array_of_size_v<0, T> or
      89             :          tt::is_maplike_v<T>) and node().IsNull()) {
      90             :       return T{};
      91             :     }
      92             :     Context error_context = context();
      93             :     error_context.line = e.mark.line;
      94             :     error_context.column = e.mark.column;
      95             :     std::ostringstream ss;
      96             :     ss << "Failed to convert value to type "
      97             :        << Options_detail::yaml_type<T>::value() << ":";
      98             : 
      99             :     const std::string value_text = YAML::Dump(node());
     100             :     if (value_text.find('\n') == std::string::npos) {
     101             :       ss << " " << value_text;
     102             :     } else {
     103             :       // Indent each line of the value by two spaces and start on a new line
     104             :       ss << "\n  ";
     105             :       for (char c : value_text) {
     106             :         ss << c;
     107             :         if (c == '\n') {
     108             :           ss << "  ";
     109             :         }
     110             :       }
     111             :     }
     112             : 
     113             :     if (tt::is_a_v<std::vector, T> or tt::is_std_array_v<T>) {
     114             :       ss << "\n\nNote: For sequences this can happen because the length of the "
     115             :             "sequence specified\nin the input file is not equal to the length "
     116             :             "expected by the code. Sequences in\nfiles can be denoted either "
     117             :             "as a bracket enclosed list ([foo, bar]) or with each\nentry on a "
     118             :             "separate line, indented and preceeded by a dash (  - foo).";
     119             :     }
     120             :     PARSE_ERROR(error_context, ss.str());
     121             :   } catch (const Options_detail::propagate_context& e) {
     122             :     Context error_context = context();
     123             :     // Avoid line numbers in the middle of the trace
     124             :     error_context.line = -1;
     125             :     error_context.column = -1;
     126             :     PARSE_ERROR(error_context, e.message());
     127             :   } catch (std::exception& e) {
     128             :     ERROR("Unexpected exception: " << e.what());
     129             :   }
     130             : }
     131             : 
     132             : namespace Options_detail {
     133             : template <typename T, typename Metavariables, typename Subgroup>
     134             : struct get_impl;
     135             : }  // namespace Options_detail
     136             : 
     137             : /// \ingroup OptionParsingGroup
     138             : /// \brief Class that handles parsing an input file
     139             : ///
     140             : /// Options must be given YAML data to parse before output can be
     141             : /// extracted.  This can be done either from a file (parse_file
     142             : /// method), from a string (parse method), or, in the case of
     143             : /// recursive parsing, from an Option (parse method).  The options
     144             : /// can then be extracted using the get method.
     145             : ///
     146             : /// \example
     147             : /// \snippet Test_Options.cpp options_example_scalar_struct
     148             : /// \snippet Test_Options.cpp options_example_scalar_parse
     149             : ///
     150             : /// \see the \ref dev_guide_option_parsing tutorial
     151             : ///
     152             : /// \tparam OptionList the list of option structs to parse
     153             : /// \tparam Group the option group with a group hierarchy
     154             : template <typename OptionList, typename Group = NoSuchType>
     155           1 : class Parser {
     156             :  public:
     157             :   /// \param help_text an overall description of the options
     158           1 :   explicit Parser(std::string help_text) noexcept;
     159             : 
     160             :   /// Parse a string to obtain options and their values.
     161             :   ///
     162             :   /// \param options the string holding the YAML formatted options
     163           1 :   void parse(const std::string& options) noexcept;
     164             : 
     165             :   /// Parse an Option to obtain options and their values.
     166           1 :   void parse(const Option& options);
     167             : 
     168             :   /// Parse a file containing options
     169             :   ///
     170             :   /// \param file_name the path to the file to parse
     171           1 :   void parse_file(const std::string& file_name) noexcept;
     172             : 
     173             :   /// Parse a YAML node containing options
     174           1 :   void parse(const YAML::Node& node);
     175             : 
     176             :   /// Get the value of the specified option
     177             :   ///
     178             :   /// \tparam T the option to retrieve
     179             :   /// \return the value of the option
     180             :   template <typename T, typename Metavariables = NoSuchType>
     181           1 :   typename T::type get() const;
     182             : 
     183             :   /// Call a function with the specified options as arguments.
     184             :   ///
     185             :   /// \tparam TagList a typelist of options to pass
     186             :   /// \return the result of the function call
     187             :   template <typename TagList, typename Metavariables = NoSuchType, typename F>
     188           1 :   decltype(auto) apply(F&& func) const;
     189             : 
     190             :   /// Get the help string
     191           1 :   std::string help() const noexcept;
     192             : 
     193             :  private:
     194             :   template <typename, typename, typename>
     195             :   friend struct Options_detail::get_impl;
     196             : 
     197             :   static_assert(tt::is_a<tmpl::list, OptionList>::value,
     198             :                 "The OptionList template parameter to Options must be a "
     199             :                 "tmpl::list<...>.");
     200             : 
     201             :   /// All top-level options and top-level groups of options. Every option in
     202             :   /// `OptionList` is either in this list or in the hierarchy of one of the
     203             :   /// groups in this list.
     204           1 :   using tags_and_subgroups_list = tmpl::remove_duplicates<tmpl::transform<
     205             :       OptionList, Options_detail::find_subgroup<tmpl::_1, Group>>>;
     206             : 
     207             :   // The maximum length of an option label.
     208           0 :   static constexpr int max_label_size_ = 70;
     209             : 
     210             :   /// Check that the size is not smaller than the lower bound
     211             :   ///
     212             :   /// \tparam T the option struct
     213             :   /// \param t the value of the read in option
     214             :   template <typename T>
     215           1 :   void check_lower_bound_on_size(const typename T::type& t,
     216             :                                  const Context& context) const;
     217             : 
     218             :   /// Check that the size is not larger than the upper bound
     219             :   ///
     220             :   /// \tparam T the option struct
     221             :   /// \param t the value of the read in option
     222             :   template <typename T>
     223           1 :   void check_upper_bound_on_size(const typename T::type& t,
     224             :                                  const Context& context) const;
     225             : 
     226             :   /// Returns the default value or errors if there is no default.
     227             :   ///
     228             :   /// \tparam T the option struct
     229             :   template <typename T>
     230           1 :   typename T::type get_default() const;
     231             : 
     232             :   /// If the options has a lower bound, check it is satisfied.
     233             :   ///
     234             :   /// Note: Lower bounds are >=, not just >.
     235             :   /// \tparam T the option struct
     236             :   /// \param t the value of the read in option
     237             :   template <typename T>
     238           1 :   void check_lower_bound(const typename T::type& t,
     239             :                          const Context& context) const;
     240             : 
     241             :   /// If the options has a upper bound, check it is satisfied.
     242             :   ///
     243             :   /// Note: Upper bounds are <=, not just <.
     244             :   /// \tparam T the option struct
     245             :   /// \param t the value of the read in option
     246             :   template <typename T>
     247           1 :   void check_upper_bound(const typename T::type& t,
     248             :                          const Context& context) const;
     249             : 
     250             :   /// Get the help string for parsing errors
     251           1 :   std::string parsing_help(const YAML::Node& options) const noexcept;
     252             : 
     253             :   /// Error message when failed to parse an input file.
     254           1 :   [[noreturn]] void parser_error(const YAML::Exception& e) const noexcept;
     255             : 
     256           0 :   std::string help_text_{};
     257           0 :   Context context_{};
     258           0 :   std::unordered_set<std::string> valid_names_{};
     259           0 :   std::unordered_map<std::string, YAML::Node> parsed_options_{};
     260             : };
     261             : 
     262             : template <typename OptionList, typename Group>
     263             : Parser<OptionList, Group>::Parser(std::string help_text) noexcept
     264             :     : help_text_(std::move(help_text)),
     265             :       valid_names_(tmpl::for_each<tags_and_subgroups_list>(
     266             :                        Options_detail::create_valid_names{})
     267             :                        .value) {
     268             :   tmpl::for_each<tags_and_subgroups_list>([](auto t) noexcept {
     269             :     using T = typename decltype(t)::type;
     270             :     const std::string label = name<T>();
     271             :     ASSERT(label.size() <= max_label_size_,
     272             :            "The option name " << label
     273             :                               << " is too long for nice formatting, "
     274             :                                  "please shorten the name to "
     275             :                               << max_label_size_ << " characters or fewer");
     276             :     ASSERT(std::strlen(T::help) > 0,
     277             :            "You must supply a help string of non-zero length for " << label);
     278             :   });
     279             : }
     280             : 
     281             : template <typename OptionList, typename Group>
     282             : void Parser<OptionList, Group>::parse_file(
     283             :     const std::string& file_name) noexcept {
     284             :   context_.append("In " + file_name);
     285             :   try {
     286             :     parse(YAML::LoadFile(file_name));
     287             :   } catch (const YAML::BadFile& /*e*/) {
     288             :     ERROR("Could not open the input file " << file_name);
     289             :   } catch (const YAML::Exception& e) {
     290             :     parser_error(e);
     291             :   } catch (const std::ios_base::failure& e) {
     292             :     ERROR("I/O error reading " << file_name << ": " << e.what());
     293             :   }
     294             : }
     295             : 
     296             : template <typename OptionList, typename Group>
     297             : void Parser<OptionList, Group>::parse(const std::string& options) noexcept {
     298             :   context_.append("In string");
     299             :   try {
     300             :     parse(YAML::Load(options));
     301             :   } catch (YAML::Exception& e) {
     302             :     parser_error(e);
     303             :   }
     304             : }
     305             : 
     306             : template <typename OptionList, typename Group>
     307             : void Parser<OptionList, Group>::parse(const Option& options) {
     308             :   context_ = options.context();
     309             :   parse(options.node());
     310             : }
     311             : 
     312             : template <typename OptionList, typename Group>
     313             : void Parser<OptionList, Group>::parse(const YAML::Node& node) {
     314             :   if (not(node.IsMap() or node.IsNull())) {
     315             :     PARSE_ERROR(context_, "'" << node << "' does not look like options.\n"
     316             :                               << help());
     317             :   }
     318             :   for (const auto& name_and_value : node) {
     319             :     const auto& name = name_and_value.first.as<std::string>();
     320             :     const auto& value = name_and_value.second;
     321             :     auto context = context_;
     322             :     context.line = name_and_value.first.Mark().line;
     323             :     context.column = name_and_value.first.Mark().column;
     324             : 
     325             :     // Check for invalid key
     326             :     if (1 != valid_names_.count(name)) {
     327             :       PARSE_ERROR(context, "Option '" << name << "' is not a valid option.\n"
     328             :                                       << parsing_help(node));
     329             :     }
     330             : 
     331             :     // Check for duplicate key
     332             :     if (0 != parsed_options_.count(name)) {
     333             :       PARSE_ERROR(context, "Option '" << name << "' specified twice.\n"
     334             :                                       << parsing_help(node));
     335             :     }
     336             :     parsed_options_.emplace(name, value);
     337             :   }
     338             : }
     339             : 
     340             : namespace Options_detail {
     341             : template <typename Tag, typename Metavariables, typename Subgroup>
     342             : struct get_impl {
     343             :   template <typename OptionList, typename Group>
     344             :   static typename Tag::type apply(const Parser<OptionList, Group>& opts) {
     345             :     static_assert(
     346             :         tmpl::list_contains_v<OptionList, Tag>,
     347             :         "Could not find requested option in the list of options provided. Did "
     348             :         "you forget to add the option tag to the OptionList?");
     349             :     const std::string subgroup_label = name<Subgroup>();
     350             :     if (0 == opts.parsed_options_.count(subgroup_label)) {
     351             :       PARSE_ERROR(opts.context_, "You did not specify the group '"
     352             :                                      << subgroup_label << "'.\n"
     353             :                                      << opts.help());
     354             :     }
     355             :     Parser<options_in_group<OptionList, Subgroup>, Subgroup> subgroup_options(
     356             :         Subgroup::help);
     357             :     subgroup_options.context_ = opts.context_;
     358             :     subgroup_options.context_.append("In group " + subgroup_label);
     359             :     subgroup_options.parse(opts.parsed_options_.find(subgroup_label)->second);
     360             :     return subgroup_options.template get<Tag, Metavariables>();
     361             :   }
     362             : };
     363             : 
     364             : template <typename Tag, typename Metavariables>
     365             : struct get_impl<Tag, Metavariables, Tag> {
     366             :   template <typename OptionList, typename Group>
     367             :   static typename Tag::type apply(const Parser<OptionList, Group>& opts) {
     368             :     static_assert(
     369             :         tmpl::list_contains_v<OptionList, Tag>,
     370             :         "Could not find requested option in the list of options provided. Did "
     371             :         "you forget to add the option tag to the OptionList?");
     372             :     const std::string label = name<Tag>();
     373             : 
     374             :     if constexpr (Options_detail::has_default<Tag>::value) {
     375             :       Context context;
     376             :       context.append("Checking DEFAULT value for " + name<Tag>());
     377             :       const auto default_value = Tag::default_value();
     378             :       opts.template check_lower_bound_on_size<Tag>(default_value, context);
     379             :       opts.template check_upper_bound_on_size<Tag>(default_value, context);
     380             :       opts.template check_lower_bound<Tag>(default_value, context);
     381             :       opts.template check_upper_bound<Tag>(default_value, context);
     382             :     }
     383             :     if (0 == opts.parsed_options_.count(label)) {
     384             :       return opts.template get_default<Tag>();
     385             :     }
     386             : 
     387             :     Option option(opts.parsed_options_.find(label)->second, opts.context_);
     388             :     option.append_context("While parsing option " + label);
     389             : 
     390             :     auto t = option.parse_as<typename Tag::type, Metavariables>();
     391             : 
     392             :     opts.template check_lower_bound_on_size<Tag>(t, option.context());
     393             :     opts.template check_upper_bound_on_size<Tag>(t, option.context());
     394             :     opts.template check_lower_bound<Tag>(t, option.context());
     395             :     opts.template check_upper_bound<Tag>(t, option.context());
     396             :     return t;
     397             :   }
     398             : };
     399             : }  // namespace Options_detail
     400             : 
     401             : template <typename OptionList, typename Group>
     402             : template <typename Tag, typename Metavariables>
     403           0 : typename Tag::type Parser<OptionList, Group>::get() const {
     404             :   return Options_detail::get_impl<
     405             :       Tag, Metavariables,
     406             :       typename Options_detail::find_subgroup<Tag, Group>::type>::apply(*this);
     407             : }
     408             : 
     409             : namespace Options_detail {
     410             : template <typename>
     411             : struct apply_helper;
     412             : 
     413             : template <typename... Tags>
     414             : struct apply_helper<tmpl::list<Tags...>> {
     415             :   template <typename Metavariables, typename Options, typename F>
     416             :   static decltype(auto) apply(const Options& opts, F&& func) {
     417             :     return func(opts.template get<Tags, Metavariables>()...);
     418             :   }
     419             : };
     420             : }  // namespace Options_detail
     421             : 
     422             : /// \cond
     423             : // Doxygen is confused by decltype(auto)
     424             : template <typename OptionList, typename Group>
     425             : template <typename TagList, typename Metavariables, typename F>
     426             : decltype(auto) Parser<OptionList, Group>::apply(F&& func) const {
     427             :   return Options_detail::apply_helper<TagList>::template apply<Metavariables>(
     428             :       *this, std::forward<F>(func));
     429             : }
     430             : /// \endcond
     431             : 
     432             : template <typename OptionList, typename Group>
     433             : std::string Parser<OptionList, Group>::help() const noexcept {
     434             :   std::ostringstream ss;
     435             :   ss << "\n==== Description of expected options:\n" << help_text_;
     436             :   if (tmpl::size<tags_and_subgroups_list>::value > 0) {
     437             :     ss << "\n\nOptions:\n"
     438             :        << tmpl::for_each<tags_and_subgroups_list>(
     439             :               Options_detail::print<OptionList>{})
     440             :               .value;
     441             :   } else {
     442             :     ss << "\n\n<No options>\n";
     443             :   }
     444             :   return ss.str();
     445             : }
     446             : 
     447             : template <typename OptionList, typename Group>
     448             : template <typename T>
     449             : void Parser<OptionList, Group>::check_lower_bound_on_size(
     450             :     const typename T::type& t, const Context& context) const {
     451             :   if constexpr (Options_detail::has_lower_bound_on_size<T>::value) {
     452             :     static_assert(std::is_same_v<decltype(T::lower_bound_on_size()), size_t>,
     453             :                   "lower_bound_on_size() is not a size_t.");
     454             :     if (t.size() < T::lower_bound_on_size()) {
     455             :       PARSE_ERROR(context, "Value must have at least "
     456             :                                << T::lower_bound_on_size() << " entries, but "
     457             :                                << t.size() << " were given.\n"
     458             :                                << help());
     459             :     }
     460             :   }
     461             : }
     462             : 
     463             : template <typename OptionList, typename Group>
     464             : template <typename T>
     465             : void Parser<OptionList, Group>::check_upper_bound_on_size(
     466             :     const typename T::type& t, const Context& context) const {
     467             :   if constexpr (Options_detail::has_upper_bound_on_size<T>::value) {
     468             :     static_assert(std::is_same_v<decltype(T::upper_bound_on_size()), size_t>,
     469             :                   "upper_bound_on_size() is not a size_t.");
     470             :     if (t.size() > T::upper_bound_on_size()) {
     471             :       PARSE_ERROR(context, "Value must have at most "
     472             :                                << T::upper_bound_on_size() << " entries, but "
     473             :                                << t.size() << " were given.\n"
     474             :                                << help());
     475             :     }
     476             :   }
     477             : }
     478             : 
     479             : #if defined(__GNUC__) && !defined(__clang__) && __GNUC__ < 8
     480             : #pragma GCC diagnostic push
     481             : #pragma GCC diagnostic ignored "-Wsuggest-attribute=noreturn"
     482             : #endif  // defined(__GNUC__) && !defined(__clang__) && __GNUC__ < 8
     483             : template <typename OptionList, typename Group>
     484             : template <typename T>
     485             : typename T::type Parser<OptionList, Group>::get_default() const {
     486             :   if constexpr (Options_detail::has_default<T>::value) {
     487             :     static_assert(
     488             :         std::is_same_v<decltype(T::default_value()), typename T::type>,
     489             :         "Default value is not of the same type as the option.");
     490             :     return T::default_value();
     491             :   } else {
     492             :     PARSE_ERROR(context_, "You did not specify the option '" << name<T>()
     493             :                                                              << "'.\n"
     494             :                                                              << help());
     495             :   }
     496             : }
     497             : #if defined(__GNUC__) && !defined(__clang__) && __GNUC__ < 8
     498             : #pragma GCC diagnostic pop
     499             : #endif  // defined(__GNUC__) && !defined(__clang__) && __GNUC__ < 8
     500             : 
     501             : template <typename OptionList, typename Group>
     502             : template <typename T>
     503             : inline void Parser<OptionList, Group>::check_lower_bound(
     504             :     const typename T::type& t, const Context& context) const {
     505             :   if constexpr (Options_detail::has_lower_bound<T>::value) {
     506             :     static_assert(std::is_same_v<decltype(T::lower_bound()), typename T::type>,
     507             :                   "Lower bound is not of the same type as the option.");
     508             :     static_assert(not std::is_same_v<typename T::type, bool>,
     509             :                   "Cannot set a lower bound for a bool.");
     510             :     if (t < T::lower_bound()) {
     511             :       PARSE_ERROR(context, "Value " << (MakeString{} << t)
     512             :                                     << " is below the lower bound of "
     513             :                                     << (MakeString{} << T::lower_bound())
     514             :                                     << ".\n" << help());
     515             :     }
     516             :   }
     517             : }
     518             : 
     519             : template <typename OptionList, typename Group>
     520             : template <typename T>
     521             : inline void Parser<OptionList, Group>::check_upper_bound(
     522             :     const typename T::type& t, const Context& context) const {
     523             :   if constexpr (Options_detail::has_upper_bound<T>::value) {
     524             :     static_assert(std::is_same_v<decltype(T::upper_bound()), typename T::type>,
     525             :                   "Upper bound is not of the same type as the option.");
     526             :     static_assert(not std::is_same_v<typename T::type, bool>,
     527             :                   "Cannot set an upper bound for a bool.");
     528             :     if (t > T::upper_bound()) {
     529             :       PARSE_ERROR(context, "Value " << (MakeString{} << t)
     530             :                                     << " is above the upper bound of "
     531             :                                     << (MakeString{} << T::upper_bound())
     532             :                                     << ".\n" << help());
     533             :     }
     534             :   }
     535             : }
     536             : 
     537             : template <typename OptionList, typename Group>
     538             : std::string Parser<OptionList, Group>::parsing_help(
     539             :     const YAML::Node& options) const noexcept {
     540             :   std::ostringstream os;
     541             :   // At top level this would dump the entire input file, which is very
     542             :   // verbose and not very informative.  At lower levels the result
     543             :   // should be much shorter and may actually give useful context for
     544             :   // what part of the file is being parsed.
     545             :   if (not context_.top_level) {
     546             :     os << "\n==== Parsing the option string:\n" << options << "\n";
     547             :   }
     548             :   os << help();
     549             :   return os.str();
     550             : }
     551             : 
     552             : template <typename OptionList, typename Group>
     553             : [[noreturn]] void Parser<OptionList, Group>::parser_error(
     554             :     const YAML::Exception& e) const noexcept {
     555             :   auto context = context_;
     556             :   context.line = e.mark.line;
     557             :   context.column = e.mark.column;
     558             :   // Inline the top_level branch of PARSE_ERROR to avoid warning that
     559             :   // the other branch would call terminate.  (Parser errors can only
     560             :   // be generated at top level.)
     561             :   ERROR(
     562             :       "\n"
     563             :       << context
     564             :       << "Unable to correctly parse the input file because of a syntax error.\n"
     565             :          "This is often due to placing a suboption on the same line as an "
     566             :          "option, e.g.:\nDomainCreator: CreateInterval:\n  IsPeriodicIn: "
     567             :          "[false]\n\nShould be:\nDomainCreator:\n  CreateInterval:\n    "
     568             :          "IsPeriodicIn: [true]\n\nSee an example input file for help.");
     569             : }
     570             : 
     571             : namespace Options_detail {
     572             : template <typename T, typename Metavariables, typename = std::void_t<>>
     573             : struct get_options_list {
     574             :   using type = typename T::template options<Metavariables>;
     575             : };
     576             : 
     577             : template <typename T, typename Metavariables>
     578             : struct get_options_list<T, Metavariables, std::void_t<typename T::options>> {
     579             :   using type = typename T::options;
     580             : };
     581             : }  // namespace Options_detail
     582             : 
     583             : template <typename T>
     584             : template <typename Metavariables>
     585             : T create_from_yaml<T>::create(const Option& options) {
     586             :   Parser<typename Options_detail::get_options_list<T, Metavariables>::type>
     587             :       parser(T::help);
     588             :   parser.parse(options);
     589             :   return parser.template apply<typename Options_detail::get_options_list<
     590             :       T, Metavariables>::type>([&options](auto&&... args) {
     591             :     if constexpr (std::is_constructible<T, decltype(std::move(args))...,
     592             :                                         const Context&, Metavariables>{}) {
     593             :       return T(std::move(args)..., options.context(), Metavariables{});
     594             :     } else if constexpr (std::is_constructible<T, decltype(std::move(args))...,
     595             :                                                const Context&>{}) {
     596             :       return T(std::move(args)..., options.context());
     597             :     } else {
     598             :       return T(std::move(args)...);
     599             :     }
     600             :   });
     601             : }
     602             : 
     603             : // yaml-cpp doesn't handle C++11 types yet
     604             : template <typename K, typename V, typename H, typename P>
     605             : struct create_from_yaml<std::unordered_map<K, V, H, P>> {
     606             :   template <typename Metavariables>
     607             :   static std::unordered_map<K, V, H, P> create(const Option& options) {
     608             :     auto ordered = options.parse_as<std::map<K, V>, Metavariables>();
     609             :     std::unordered_map<K, V, H, P> result;
     610             :     for (auto it = ordered.begin(); it != ordered.end();) {
     611             :       auto node = ordered.extract(it++);
     612             :       result.emplace(std::move(node.key()), std::move(node.mapped()));
     613             :     }
     614             :     return result;
     615             :   }
     616             : };
     617             : }  // namespace Options
     618             : 
     619             : template <typename T, typename Metavariables>
     620             : struct YAML::convert<Options::Options_detail::CreateWrapper<T, Metavariables>> {
     621             :   static bool decode(
     622             :       const Node& node,
     623             :       Options::Options_detail::CreateWrapper<T, Metavariables>& rhs) {
     624             :     Options::Context context;
     625             :     context.top_level = false;
     626             :     context.append("While creating a " + pretty_type::short_name<T>());
     627             :     Options::Option options(node, std::move(context));
     628             :     rhs = Options::Options_detail::CreateWrapper<T, Metavariables>{
     629             :         Options::create_from_yaml<T>::template create<Metavariables>(options)};
     630             :     return true;
     631             :   }
     632             : };
     633             : 
     634             : #include "Options/Factory.hpp"

Generated by: LCOV version 1.14