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