arg_router  1.4.0
C++ command line argument parsing and routing
alias.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/policy/no_result_value.hpp"
9 #include "arg_router/policy/policy.hpp"
10 #include "arg_router/utility/compile_time_optional.hpp"
11 #include "arg_router/utility/tree_recursor.hpp"
12 #include "arg_router/utility/tuple_iterator.hpp"
13 
15 {
21 template <typename... AliasedPolicies>
22 class alias_t : public no_result_value<>
23 {
24 public:
26  using aliased_policies_type = std::tuple<std::decay_t<AliasedPolicies>...>;
27 
29  constexpr static auto priority = std::size_t{100};
30 
35  constexpr explicit alias_t([[maybe_unused]] const AliasedPolicies&... policies) {}
36 
59  template <typename ProcessedTarget, typename... Parents>
62  [[maybe_unused]] utility::compile_time_optional<ProcessedTarget> processed_target,
63  parsing::parse_target& target,
64  const Parents&... parents) const
65  {
66  using node_type = boost::mp11::mp_first<std::tuple<Parents...>>;
67  static_assert(!node_type::template any_phases_v<typename node_type::value_type,
71  "Alias owning node cannot have policies that support parse, "
72  "validation, or routing phases");
73 
74  // Find the owning mode
75  using mode_data = policy::nearest_mode_like<std::tuple<Parents...>>;
76  using mode_type = typename mode_data::type;
77  static_assert(!std::is_void_v<mode_type>, "Cannot find parent mode");
78 
79  // Perform all the compile-time checking
80  constexpr auto count = node_fixed_count<node_type>();
81  {
82  using targets = typename alias_targets<aliased_policies_type, mode_type>::type;
83  static_assert(cyclic_dependency_checker<targets, mode_type>::value,
84  "Cyclic dependency detected");
85 
86  // Verify that all the targets have the same count
87  check_target_counts<count, targets>();
88  }
89 
90  // +1 because the node must be named
91  if ((count + 1) > tokens.size()) {
93  parsing::node_token_type<node_type>()};
94  }
95 
96  // Because this node's job is to create sub-targets, and therefore isn't a 'real' node in
97  // itself, it needs to return skip_node_but_use_sub_targets so the owning tree node keeps
98  // the side-effects (i.e. the sub-targets) but doesn't check the label token. We don't want
99  // label checking in the owning tree_node because we need to _replace_ the alias label
100  // token with the aliased tokens, so any label check against this node will fail. However,
101  // we do need to do a label check _here_, otherwise this aliasing will occur everytime the
102  // pre-parse is called - even on tokens that do not belong to this aliased node!
103  {
104  auto alias_label = *tokens.begin();
105  if (alias_label.prefix == parsing::prefix_type::none) {
106  alias_label =
107  parsing::get_token_type(std::get<0>(std::tuple{std::cref(parents)...}).get(),
108  alias_label.name);
109  }
110 
111  if (!parsing::match<node_type>(alias_label)) {
113  }
114  }
115 
116  // Attempt a transfer so we can guarantee that the original tokens are in the processed
117  // container (this is a no-op if they already are)
118  tokens.transfer(tokens.begin() + count);
119 
120  // Now do the runtime target building
121  const auto visitor = [&](const auto& current, [[maybe_unused]] const auto&... parents) {
122  using policies_type = typename std::decay_t<decltype(current)>::policies_type;
123 
124  using intersection =
125  boost::mp11::mp_set_intersection<aliased_policies_type, policies_type>;
126 
127  if constexpr ((std::tuple_size_v<intersection>) > 0) {
128  auto target_tokens = vector<parsing::token_type>{};
129  target_tokens.reserve(count);
130 
131  auto value_it = tokens.begin();
132  for (auto j = 0u; j < count; ++j) {
133  // Pre-increment the value iterator so we skip over the label token
134  target_tokens.push_back(*(++value_it));
135  }
136 
137  target.add_sub_target({std::move(target_tokens), //
138  current,
139  parents...});
140  }
141  };
142  const auto& mode =
143  std::get<mode_data::index::value>(std::tuple{std::cref(parents)...}).get();
144  utility::tree_recursor(visitor, mode);
145 
146  // Now remove the original tokens as they refer to the aliased node
147  tokens.processed().erase(tokens.processed().begin(),
148  tokens.processed().begin() + count + 1);
149 
150  // The owning node's name checker will fail us now (because we removed this node's label
151  // token), but we still want to keep the processed and unprocessed container changes for
152  // later processing
154  }
155 
156 private:
157  template <typename T>
158  struct policy_checker {
159  constexpr static auto value =
160  traits::has_long_name_method_v<T> || traits::has_short_name_method_v<T>;
161  };
162 
163  static_assert((sizeof...(AliasedPolicies) > 0), "At least one name needed for alias");
164  static_assert(policy::is_all_policies_v<aliased_policies_type>,
165  "All parameters must be policies");
166  static_assert(boost::mp11::mp_all_of<aliased_policies_type, policy_checker>::value,
167  "All parameters must provide a long and/or short form name");
168 
169  template <typename NodeType>
170  [[nodiscard]] constexpr static std::size_t node_fixed_count() noexcept
171  {
172  static_assert(traits::has_minimum_count_method_v<NodeType> &&
173  traits::has_maximum_count_method_v<NodeType>,
174  "Aliased nodes must have minimum and maximum count methods");
175  static_assert(NodeType::minimum_count() == NodeType::maximum_count(),
176  "Aliased nodes must have a fixed count");
177 
178  return NodeType::minimum_count();
179  }
180 
181  // Starting from ModeType, recurse down through the tree and find all the nodes referred to in
182  // AliasPolicies
183  template <typename AliasPoliciesTuple, typename ModeType>
184  struct alias_targets {
185  template <typename Current, typename... Parents>
186  struct visitor {
187  // If current is one of the AliasedPolicies and Parents is not empty, then use the first
188  // element of Parents (i.e. the owning node of the name policy). If not, then set to
189  // void
190  using type = boost::mp11::mp_eval_if_c<
191  !(boost::mp11::mp_contains<AliasPoliciesTuple, Current>::value &&
192  (sizeof...(Parents) > 0)),
193  void,
194  boost::mp11::mp_at,
195  std::tuple<Parents...>,
196  traits::integral_constant<std::size_t{0}>>;
197  };
198 
199  using type =
200  boost::mp11::mp_remove_if<utility::tree_type_recursor_collector_t<visitor, ModeType>,
201  std::is_void>;
202 
203  static_assert(std::tuple_size_v<type> == std::tuple_size_v<AliasPoliciesTuple>,
204  "Number of found modes must match alias policy count");
205  static_assert(std::tuple_size_v<boost::mp11::mp_unique<type>> == std::tuple_size_v<type>,
206  "Node alias list must be unique, do you have short and long "
207  "names from the same node?");
208  };
209 
210  template <typename AliasNodesTuple, typename ModeType>
211  struct cyclic_dependency_checker {
212  // For each alias, find all of its alias, stop when there are no more or if you hit this
213  // policy - static_assert
214  template <std::size_t I, typename Nodes>
215  [[nodiscard]] constexpr static bool check() noexcept
216  {
217  if constexpr (I >= std::tuple_size_v<Nodes>) {
218  return true;
219  } else {
220  using aliased_type = std::tuple_element_t<I, Nodes>;
221  if constexpr (algorithm::has_specialisation_v<
222  alias_t,
223  typename aliased_type::policies_type>) {
224  if constexpr (boost::mp11::mp_contains<typename aliased_type::policies_type,
225  alias_t>::value) {
226  return false;
227  } else {
228  using targets =
229  typename alias_targets<typename aliased_type::aliased_policies_type,
230  ModeType>::type;
231 
232  return check<I + 1, boost::mp11::mp_append<Nodes, targets>>();
233  }
234  }
235 
236  return check<I + 1, Nodes>();
237  }
238  }
239 
240  constexpr static bool value = check<0, AliasNodesTuple>();
241  };
242 
243  template <std::size_t Count, typename TargetsTuple>
244  constexpr static void check_target_counts() noexcept
245  {
246  utility::tuple_type_iterator<TargetsTuple>([](auto i) {
247  using target_type = std::tuple_element_t<i, TargetsTuple>;
248 
249  static_assert(node_fixed_count<target_type>() == Count,
250  "All alias targets must have a count that matches the owner");
251  });
252  }
253 };
254 
262 template <typename... AliasedPolicies>
263 [[nodiscard]] constexpr auto alias(AliasedPolicies... policies) noexcept
264 {
265  return alias_t{std::move(policies)...};
266 }
267 
268 template <typename... AliasedPolicies>
269 struct is_policy<alias_t<AliasedPolicies...>> : std::true_type {
270 };
271 } // namespace arg_router::policy
void add_sub_target(parse_target target)
parsing::pre_parse_result pre_parse_phase(parsing::dynamic_token_adapter &tokens, [[maybe_unused]] utility::compile_time_optional< ProcessedTarget > processed_target, parsing::parse_target &target, const Parents &... parents) const
Definition: alias.hpp:60
std::tuple< std::decay_t< AliasedPolicies >... > aliased_policies_type
Definition: alias.hpp:26
constexpr static auto priority
Definition: alias.hpp:29
constexpr alias_t([[maybe_unused]] const AliasedPolicies &... policies)
Definition: alias.hpp:35
constexpr auto has_specialisation_v
Definition: algorithm.hpp:124
token_type get_token_type(std::string_view token)
Definition: token_type.hpp:103
constexpr auto alias(AliasedPolicies... policies) noexcept
Definition: alias.hpp:263
std::integral_constant< decltype(Value), Value > integral_constant
Definition: traits.hpp:210
constexpr void tree_recursor(Visitor visitor, const Root &root)
std::vector< T, config::allocator< T > > vector
Definition: basic_types.hpp:39
constexpr auto mode(Params... params)
Definition: mode.hpp:570