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