arg_router  1.4.0
C++ command line argument parsing and routing
tree_node.hpp
1 // Copyright (C) 2022-2023 by Camden Mannett.
2 // Distributed under the Boost Software License, Version 1.0.
3 // (See accompanying file LICENSE or copy at https://www.boost.org/LICENSE_1_0.txt)
4 
5 #pragma once
6 
7 #include "arg_router/algorithm.hpp"
8 #include "arg_router/config.hpp"
9 #include "arg_router/list.hpp"
10 #include "arg_router/parsing/global_parser.hpp"
11 #include "arg_router/parsing/parsing.hpp"
12 #include "arg_router/parsing/pre_parse_data.hpp"
13 #include "arg_router/policy/min_max_count.hpp"
14 #include "arg_router/policy/policy.hpp"
16 #include "arg_router/utility/tuple_iterator.hpp"
17 
18 namespace arg_router
19 {
24 template <typename... Params>
25 class tree_node :
26  public traits::unpack_and_derive< //
27  boost::mp11::mp_filter<policy::is_policy, std::tuple<std::decay_t<Params>...>>>
28 {
29  template <typename LHS, typename RHS>
30  struct priority_order {
31  template <typename T>
32  struct get_priority_or_default {
33  constexpr static std::size_t value = []() {
34  if constexpr (policy::has_priority_v<T>) {
35  return T::priority;
36  } else {
37  return 0;
38  }
39  }();
40  };
41 
42  constexpr static auto value =
43  get_priority_or_default<LHS>::value > get_priority_or_default<RHS>::value;
44  };
45 
46 public:
48  using parameters_type = std::tuple<std::decay_t<Params>...>;
49 
51  using policies_type = boost::mp11::mp_filter<policy::is_policy, parameters_type>;
52 
54  using priority_ordered_policies_type = boost::mp11::mp_sort<policies_type, priority_order>;
55 
57  using children_type = boost::mp11::mp_filter<
59  std::decay_t<decltype(list_expander(std::declval<std::decay_t<Params>>()...))>>;
60 
61  static_assert(
62  (std::tuple_size_v<children_type> + std::tuple_size_v<policies_type>) ==
63  std::tuple_size_v<
64  std::decay_t<decltype(list_expander(std::declval<std::decay_t<Params>>()...))>>,
65  "tree_node constructor can only accept other tree_nodes and policies");
66 
68  constexpr static auto is_named = traits::has_short_name_method_v<tree_node> ||
69  traits::has_long_name_method_v<tree_node> ||
70  traits::has_none_name_method_v<tree_node>;
71 
77  template <template <typename...> typename PolicyChecker, typename... Args>
78  struct phase_finder {
79  using finder = boost::mp11::mp_bind<PolicyChecker, boost::mp11::_1, Args...>;
80 
82  using type =
83  boost::mp11::mp_eval_if_c<boost::mp11::mp_find_if_q<policies_type, finder>::value ==
84  std::tuple_size_v<policies_type>,
85  void,
86  boost::mp11::mp_at,
88  boost::mp11::mp_find_if_q<policies_type, finder>>;
89  };
90 
96  template <template <typename...> typename PolicyChecker, typename... Args>
97  using phase_finder_t = typename phase_finder<PolicyChecker, Args...>::type;
98 
110  template <typename ValueType, template <typename...> typename... PolicyCheckers>
112  {
113  template <template <typename...> typename PolicyChecker>
114  struct checker {
115  constexpr static bool value = []() {
116  // This is shonky but I can't think of an easy arity test for a class template
117  if constexpr (std::tuple_size_v<policies_type> == 0) {
118  return false;
119  } else if constexpr (boost::mp11::mp_valid<PolicyChecker,
120  boost::mp11::mp_first<policies_type>,
121  ValueType>::value) {
122  return !std::is_void_v<phase_finder_t<PolicyChecker, ValueType>>;
123  } else {
124  return !std::is_void_v<phase_finder_t<PolicyChecker>>;
125  }
126  }();
127  };
128 
129  public:
130  constexpr static bool value = boost::mp11::mp_any_of<std::tuple<checker<PolicyCheckers>...>,
131  boost::mp11::mp_to_bool>::value;
132  };
133 
141  template <typename ValueType, template <typename...> typename... PolicyCheckers>
142  constexpr static bool any_phases_v = any_phases<ValueType, PolicyCheckers...>::value;
143 
148  [[nodiscard]] children_type& children() noexcept { return children_; }
149 
154  [[nodiscard]] constexpr const children_type& children() const noexcept { return children_; }
155 
156 protected:
162  template <bool Flatten>
164  {
165  template <typename Child>
166  using child_help_getter = typename Child::template help_data_type<Flatten>;
167 
168  template <typename Child>
169  using child_help = boost::mp11::
170  mp_eval_if_c<!traits::has_help_data_type_v<Child>, void, child_help_getter, Child>;
171 
172  constexpr static auto num_policies = std::tuple_size_v<policies_type>;
173 
174  public:
177  [[nodiscard]] constexpr static auto value_suffix() noexcept
178  {
179  constexpr auto has_min_value = traits::has_minimum_value_method_v<tree_node>;
180  constexpr auto has_max_value = traits::has_maximum_value_method_v<tree_node>;
181 
182  if constexpr (has_min_value || has_max_value) {
183  // This pointless-looking way of invoking a lambda and providing a null tree_node
184  // type is all to get round an MSVC bug whereby the false constexpr if branch was
185  // being evaluated
186  constexpr auto min_value = [](auto* type_ptr) {
187  using node_type = std::remove_pointer_t<decltype(type_ptr)>;
188 
189  if constexpr (has_min_value) {
190  constexpr auto value = node_type::minimum_value();
191  return utility::convert_integral_to_cts_t<static_cast<
192  traits::underlying_type_t<std::decay_t<decltype(value)>>>(value)>{};
193  } else {
194  // If we have got this far, then we must have a maximum value, we can use
195  // that type to determine if the lowest bound is 0 or -N depending on
196  // whether or not the type is signed
197  using max_value_type = traits::underlying_type_t<
198  std::decay_t<decltype(node_type::maximum_value())>>;
199  if constexpr (std::is_unsigned_v<max_value_type>) {
200  return AR_STRING("0"){};
201  } else {
202  return AR_STRING("-N"){};
203  }
204  }
205  }(static_cast<tree_node*>(nullptr));
206 
207  constexpr auto max_value = [](auto* type_ptr) {
208  using node_type = std::remove_pointer_t<decltype(type_ptr)>;
209 
210  if constexpr (has_max_value) {
211  constexpr auto value = node_type::maximum_value();
212  return utility::convert_integral_to_cts_t<static_cast<
213  traits::underlying_type_t<std::decay_t<decltype(value)>>>(value)>{};
214  } else {
215  return AR_STRING("N"){};
216  }
217  }(static_cast<tree_node*>(nullptr));
218 
219  return AR_STRING("<"){} + min_value + AR_STRING("-"){} + max_value +
220  AR_STRING(">"){};
221  } else {
222  return AR_STRING(""){};
223  }
224  }
225 
229  [[nodiscard]] constexpr static auto value_separator_suffix()
230  {
231  [[maybe_unused]] constexpr bool fixed_count_of_one = []() {
232  if constexpr (traits::has_minimum_count_method_v<tree_node> &&
233  traits::has_maximum_count_method_v<tree_node>) {
234  return (tree_node::minimum_count() == tree_node::maximum_count()) &&
235  (tree_node::minimum_count() == 1);
236  }
237 
238  return false;
239  }();
240 
241  [[maybe_unused]] constexpr auto value_str = []() {
242  constexpr auto min_max_str = value_suffix();
243  if constexpr (std::is_same_v<std::decay_t<decltype(min_max_str)>, AR_STRING("")>) {
244  return AR_STRING("<Value>"){};
245  } else {
246  return min_max_str;
247  }
248  }();
249 
250  [[maybe_unused]] constexpr auto has_separator =
251  boost::mp11::mp_find_if<policies_type, traits::has_value_separator_method>::value !=
252  num_policies;
253 
254  if constexpr (has_separator) {
255  return AR_STRING_SV(tree_node::value_separator()){} + value_str;
256  } else if constexpr (fixed_count_of_one) {
257  return AR_STRING(" "){} + value_str;
258  } else {
259  return AR_STRING(""){};
260  }
261  }
262 
266  [[nodiscard]] constexpr static auto label_generator() noexcept
267  {
268  [[maybe_unused]] constexpr auto has_long_name =
269  boost::mp11::mp_find_if<policies_type, traits::has_long_name_method>::value !=
270  num_policies;
271  [[maybe_unused]] constexpr auto has_short_name =
272  boost::mp11::mp_find_if<policies_type, traits::has_short_name_method>::value !=
273  num_policies;
274  [[maybe_unused]] constexpr auto has_none_name =
275  boost::mp11::mp_find_if<policies_type, traits::has_none_name_method>::value !=
276  num_policies;
277 
278  if constexpr (has_long_name && has_short_name) {
279  return AR_STRING_SV(config::long_prefix){} +
280  AR_STRING_SV(tree_node::long_name()){} + AR_STRING(","){} +
281  AR_STRING_SV(config::short_prefix){} +
282  AR_STRING_SV(tree_node::short_name()){} + value_separator_suffix();
283  } else if constexpr (has_long_name) {
284  return AR_STRING_SV(config::long_prefix){} +
285  AR_STRING_SV(tree_node::long_name()){} + value_separator_suffix();
286  } else if constexpr (has_short_name) {
287  return AR_STRING_SV(config::short_prefix){} +
288  AR_STRING_SV(tree_node::short_name()){} + value_separator_suffix();
289  } else if constexpr (has_none_name) {
290  return AR_STRING_SV(tree_node::none_name()){} + value_separator_suffix();
291  } else {
292  return AR_STRING(""){};
293  }
294  }
295 
298  [[nodiscard]] constexpr static auto description_generator() noexcept
299  {
300  [[maybe_unused]] constexpr auto has_description =
301  boost::mp11::mp_find_if<policies_type, traits::has_description_method>::value !=
302  num_policies;
303 
304  if constexpr (has_description) {
305  return AR_STRING_SV(tree_node::description()){};
306  } else {
307  return AR_STRING(""){};
308  }
309  }
310 
313  [[nodiscard]] constexpr static auto count_suffix() noexcept
314  {
315  [[maybe_unused]] constexpr bool fixed_count = []() {
316  if constexpr (traits::has_minimum_count_method_v<tree_node> &&
317  traits::has_maximum_count_method_v<tree_node>) {
318  return tree_node::minimum_count() == tree_node::maximum_count();
319  }
320 
321  return false;
322  }();
323 
324  constexpr auto prefix = AR_STRING("["){};
325 
326  if constexpr (fixed_count) {
327  return prefix + utility::convert_integral_to_cts_t<tree_node::minimum_count()>{} +
328  AR_STRING("]"){};
329  } else {
330  constexpr auto min_count = []() {
331  if constexpr (traits::has_minimum_count_method_v<tree_node>) {
332  return utility::convert_integral_to_cts_t<tree_node::minimum_count()>{};
333  } else {
334  return AR_STRING("0"){};
335  }
336  }();
337 
338  constexpr auto max_count = []() {
339  if constexpr (traits::has_maximum_count_method_v<tree_node>) {
340  if constexpr (tree_node::maximum_count() ==
341  decltype(policy::min_count<0>)::maximum_count()) {
342  return AR_STRING("N"){};
343  } else {
344  return utility::convert_integral_to_cts_t<tree_node::maximum_count()>{};
345  }
346  } else {
347  return AR_STRING("N"){};
348  }
349  }();
350 
351  return prefix + min_count + AR_STRING(","){} + max_count + AR_STRING("]"){};
352  }
353  }
354 
370  template <typename OwnerNode, typename FilterFn>
371  [[nodiscard]] static vector<runtime_help_data> runtime_children(const OwnerNode& owner,
372  FilterFn&& f)
373  {
374  auto result = vector<runtime_help_data>{};
376  [&](auto, auto& child) {
377  using child_type = std::decay_t<decltype(child)>;
378  if constexpr (traits::has_help_data_type_v<child_type>) {
379  using child_help_data_type =
380  typename child_type::template help_data_type<Flatten>;
381 
382  static_assert(child_help_data_type::description::get().find('\t') ==
383  std::string_view::npos,
384  "Help descriptions cannot contain tabs");
385 
386  if (f(child)) {
387  auto child_result = vector<runtime_help_data>{};
389  child_help_data_type>) {
390  child_result = child_help_data_type::runtime_children(child, f);
391  } else {
392  child_result = runtime_children(child, f);
393  }
394 
395  result.push_back(
396  runtime_help_data{child_help_data_type::label::get(),
397  child_help_data_type::description::get(),
398  std::move(child_result)});
399  }
400  }
401  },
402  owner.children());
403  return result;
404  }
405 
408  boost::mp11::mp_remove_if<boost::mp11::mp_transform<child_help, children_type>,
409  std::is_void>;
410 
416  using label = std::decay_t<decltype(label_generator())>;
417 
422  using description = std::decay_t<decltype(description_generator())>;
423 
428  using children = std::tuple<>;
429  };
430 
435  constexpr explicit tree_node(Params... params) noexcept :
436  traits::unpack_and_derive<policies_type>{common_filter<policy::is_policy>(params...)},
437  children_(common_filter_tuple<is_tree_node>(list_expander(std::move(params)...)))
438  {
439  }
440 
464  template <typename Validator, bool HasTarget, typename Node, typename... Parents>
465  [[nodiscard]] std::optional<parsing::parse_target> pre_parse(
467  const Node& node,
468  const Parents&... parents) const
469  {
470  return std::apply(
471  [&](auto&&... ancestors) { return pre_parse_impl(pre_parse_data, ancestors.get()...); },
472  parsing::clean_node_ancestry_list(node, parents...));
473  }
474 
487  template <typename ValueType, typename Node, typename... Parents>
488  [[nodiscard]] auto parse(std::string_view token,
489  const Node& node,
490  const Parents&... parents) const
491  {
492  return std::apply(
493  [&](auto&&... ancestors) { return parse_impl<ValueType>(token, ancestors.get()...); },
494  parsing::clean_node_ancestry_list(node, parents...));
495  }
496 
497 private:
498  static_assert((boost::mp11::mp_count_if_q<
500  typename phase_finder<policy::has_routing_phase_method>::finder>::value <=
501  1),
502  "Only zero or one policies supporting a routing phase is supported");
503 
504  template <template <typename...> typename Fn, typename... ExpandedParams>
505  [[nodiscard]] constexpr static auto common_filter(ExpandedParams&... expanded_params) noexcept
506  {
507  // Send references to the filter method so we don't copy anything unnecessarily
508  using ref_tuple = std::tuple<std::reference_wrapper<ExpandedParams>...>;
509 
510  // We have to wrap Fn because we're now giving it a std::reference_wrapper
511  using ref_fn =
512  boost::mp11::mp_bind<Fn, boost::mp11::mp_bind<traits::get_type, boost::mp11::_1>>;
513 
514  // We have our result tuple, but it only contains references so we now have to move
515  // construct them into the 'real' types
516  auto ref_result = algorithm::tuple_filter_and_construct<ref_fn::template fn>(
517  ref_tuple{expanded_params...});
518 
519  using ref_result_t = std::decay_t<decltype(ref_result)>;
520  using result_t = boost::mp11::mp_transform<traits::get_type, ref_result_t>;
521 
522  // Converting move-constructor of std::tuple
523  return result_t{std::move(ref_result)};
524  }
525 
526  template <template <typename...> typename Fn, typename Tuple>
527  [[nodiscard]] constexpr static auto common_filter_tuple(Tuple&& tuple_params) noexcept
528  {
529  // std::apply does not like templates, so we have to wrap in a lambda
530  return std::apply(
531  [](auto&... args) { return common_filter<Fn>(std::forward<decltype(args)>(args)...); },
532  tuple_params);
533  }
534 
535  template <typename Validator, bool HasTarget>
536  [[nodiscard]] constexpr auto extract_parent_target(
537  [[maybe_unused]] parsing::pre_parse_data<Validator, HasTarget> pre_parse_data)
538  const noexcept
539  {
540  if constexpr (HasTarget) {
541  return utility::compile_time_optional{std::cref(pre_parse_data.target())};
542  } else {
543  return utility::compile_time_optional{};
544  }
545  }
546 
547  template <typename Validator, bool HasTarget, typename Node, typename... Parents>
548  [[nodiscard]] std::optional<parsing::parse_target> pre_parse_impl(
549  parsing::pre_parse_data<Validator, HasTarget> pre_parse_data,
550  const Node& node,
551  const Parents&... parents) const
552  {
553  auto result = vector<parsing::token_type>{};
554  auto tmp_args = pre_parse_data.args();
555  auto adapter = parsing::dynamic_token_adapter{result, tmp_args};
556 
557  // At this stage, the target is only for collecting sub-targets
558  auto target = parsing::parse_target{node, parents...};
559 
561  utility::tuple_type_iterator<priority_ordered_policies_type>([&](auto i) {
562  using policy_type = std::tuple_element_t<i, priority_ordered_policies_type>;
563  if constexpr (policy::has_pre_parse_phase_method_v<policy_type>) {
564  if (!match || (match == parsing::pre_parse_action::skip_node)) {
565  return;
566  }
567 
568  // Keep the skip_node_but_use_sub_targets state if later policies return valid_node
569  const auto use_subs =
571  match = this->policy_type::pre_parse_phase(adapter,
572  extract_parent_target(pre_parse_data),
573  target,
574  node,
575  parents...);
576  if (use_subs && (match == parsing::pre_parse_action::valid_node)) {
578  }
579  }
580  });
581 
582  // If we have a result and it is false, then exit early. We need to wait until after name
583  // checking is (possibly) performed to throw an exception, otherwise a parse-stopping
584  // exception could be thrown when the target of the token isn't even this node
586  return {};
587  }
588 
589  if constexpr (is_named) {
590  // Check the first token against the names (if the node is named of course)
592  // If the node is named but there are no tokens, then take the first from args.
593  // If args is empty, then return false
594  if (result.empty()) {
595  if (tmp_args.empty()) {
596  return {};
597  }
598  result.push_back(tmp_args.front());
599  tmp_args.erase(tmp_args.begin());
600  }
601 
602  // The first token may not have been processed, so convert
603  auto& first_token = result.front();
604  if (first_token.prefix == parsing::prefix_type::none) {
605  first_token = parsing::get_token_type(*this, first_token.name);
606  }
607 
608  // And then test it is correct unless we are skipping
609  if (!parsing::match<tree_node>(first_token)) {
610  return {};
611  }
612  }
613  }
614 
615  // Exit early if the caller doesn't want this node
616  if (!pre_parse_data.validator()(node, parents...)) {
617  return {};
618  }
619 
620  // If the policy checking returned an exception, now is the time to throw it
621  match.throw_exception();
622 
623  // Update the unprocessed args
624  pre_parse_data.args() = tmp_args;
625 
626  // Update the target with the pre-parsed tokens. Remove the label token if present
627  if constexpr (is_named) {
628  if (!result.empty()) {
629  result.erase(result.begin());
630  }
631  }
632  target.tokens(std::move(result));
633 
634  return target;
635  }
636 
637  template <typename ValueType, typename... Parents>
638  [[nodiscard]] auto parse_impl(std::string_view token, const Parents&... parents) const
639  {
640  using finder_type = phase_finder<policy::has_parse_phase_method, ValueType>;
641  using policy_type = typename finder_type::type;
642 
643 #ifdef MSVC_1936_WORKAROUND
644  static_assert(
645  (boost::mp11::mp_count_if<policies_type, finder_type::finder::fn>::value <= 1),
646  "Only zero or one policies supporting a parse phase is supported");
647  static_assert(
648  (boost::mp11::mp_count_if<
650  phase_finder<policy::has_missing_phase_method, ValueType>::finder::fn>::value <=
651  1),
652  "Only zero or one policies supporting a missing phase is "
653  "supported");
654 #else
655  static_assert(
656  (boost::mp11::mp_count_if_q<policies_type, typename finder_type::finder>::value <= 1),
657  "Only zero or one policies supporting a parse phase is supported");
658  static_assert(
659  (boost::mp11::mp_count_if_q<policies_type,
660  typename phase_finder<policy::has_missing_phase_method,
661  ValueType>::finder>::value <= 1),
662  "Only zero or one policies supporting a missing phase is "
663  "supported");
664 #endif
665 
666  if constexpr (std::is_void_v<policy_type>) {
667  return parser<ValueType>::parse(token);
668  } else {
669  return this->policy_type::template parse_phase<ValueType>(token, parents...);
670  }
671  }
672 
673  children_type children_;
674 };
675 } // namespace arg_router
constexpr static auto label_generator() noexcept
Definition: tree_node.hpp:266
constexpr static auto description_generator() noexcept
Definition: tree_node.hpp:298
constexpr static auto count_suffix() noexcept
Definition: tree_node.hpp:313
std::decay_t< decltype(description_generator())> description
Definition: tree_node.hpp:422
static vector< runtime_help_data > runtime_children(const OwnerNode &owner, FilterFn &&f)
Definition: tree_node.hpp:371
std::decay_t< decltype(label_generator())> label
Definition: tree_node.hpp:416
boost::mp11::mp_remove_if< boost::mp11::mp_transform< child_help, children_type >, std::is_void > all_children_help
Definition: tree_node.hpp:409
constexpr static auto value_suffix() noexcept
Definition: tree_node.hpp:177
constexpr static auto is_named
Definition: tree_node.hpp:68
boost::mp11::mp_sort< policies_type, priority_order > priority_ordered_policies_type
Definition: tree_node.hpp:54
constexpr static bool any_phases_v
Definition: tree_node.hpp:142
std::tuple< std::decay_t< Params >... > parameters_type
Definition: tree_node.hpp:48
typename phase_finder< PolicyChecker, Args... >::type phase_finder_t
Definition: tree_node.hpp:97
constexpr tree_node(Params... params) noexcept
Definition: tree_node.hpp:435
boost::mp11::mp_filter< is_tree_node, std::decay_t< decltype(list_expander(std::declval< std::decay_t< Params > >()...))> > children_type
Definition: tree_node.hpp:59
constexpr const children_type & children() const noexcept
Definition: tree_node.hpp:154
auto parse(std::string_view token, const Node &node, const Parents &... parents) const
Definition: tree_node.hpp:488
boost::mp11::mp_filter< policy::is_policy, parameters_type > policies_type
Definition: tree_node.hpp:51
children_type & children() noexcept
Definition: tree_node.hpp:148
std::optional< parsing::parse_target > pre_parse(parsing::pre_parse_data< Validator, HasTarget > pre_parse_data, const Node &node, const Parents &... parents) const
Definition: tree_node.hpp:465
constexpr auto short_prefix
Definition: config.hpp:58
constexpr auto long_prefix
Definition: config.hpp:52
token_type get_token_type(std::string_view token)
Definition: token_type.hpp:103
utility::result< pre_parse_action, multi_lang_exception > pre_parse_result
Definition: parsing.hpp:27
constexpr bool match(token_type token) noexcept
Definition: parsing.hpp:36
constexpr auto clean_node_ancestry_list(const BaseNode &base_node, const DerivedAndParents &... derived_and_parents)
Definition: parsing.hpp:105
constexpr bool has_runtime_children_method_v
Definition: traits.hpp:582
typename underlying_type< T >::type underlying_type_t
Definition: traits.hpp:106
typename convert_integral_to_cts< Value >::type convert_integral_to_cts_t
constexpr std::enable_if_t< traits::is_tuple_like_v< std::decay_t< Tuple > > > tuple_iterator(F &&f, Tuple &&tuple)
std::vector< T, config::allocator< T > > vector
Definition: basic_types.hpp:39
constexpr auto list_expander(Params... params) noexcept
Definition: list.hpp:98
boost::mp11::mp_eval_if_c< boost::mp11::mp_find_if_q< policies_type, finder >::value==std::tuple_size_v< policies_type >, void, boost::mp11::mp_at, policies_type, boost::mp11::mp_find_if_q< policies_type, finder > > type
Definition: tree_node.hpp:88