PureFSM
fsm.hpp
Go to the documentation of this file.
1 /**
2  * @file fsm.hpp
3  *
4  * File that contains a Finite State Machine.
5  */
6 #ifndef PURE_FSM
7 #define PURE_FSM
8 
9 #include <cstddef>
10 
11 /**
12  * @bug Clangd can't find `<type_pack.hpp>` header, but can find it by
13  * specifying its relative path: `"../../lib/type_pack/include/type_pack.hpp"`.
14  * However, clang++ compiles it well.
15  */
16 #include "../../lib/type_pack/include/type_pack.hpp"
17 
18 #include <type_traits>
19 #include <utility>
20 #include <variant>
21 
22 /**
23  * @brief PureFSM library namespace
24  */
25 namespace pure {
26 
27  struct none {};
28 
29  template <class Source, class Event, class Target, class Action,
30  class Guard = none>
31  struct transition {
32  /** @cond undocumented */
33  using source_t = Source;
34  using event_t = Event;
35  using target_t = Target;
36  using action_t = Action;
37  using guard_t = Guard;
38  /** @endcond */
39  };
40 
41  /**
42  * @brief Typedef to transition
43  */
44  template <class Source, class Event, class Target, class Action, class Guard>
45  using tr = transition<Source, Event, Target, Action, Guard>;
46 
47  namespace __details {
48 
49  template <class Gd, class Target>
50  struct match;
51 
52  template <class Guard, class Target>
53  inline constexpr bool match_v = match<Guard, Target>::value;
54 
55  template <class Guard>
56  struct is_logical_guard;
57 
58  template <typename T>
59  struct unpack {};
60 
61  template <typename T, typename... Ts>
62  struct unpack<tp::type_pack<T, Ts...>> {
63  using type = std::variant<T, Ts...>;
64  };
65 
66  template <class GuardPack>
67  struct unpack_guards;
68 
69  template <class Source, class Event, class GuardPack>
70  struct tr_cond {};
71 
72  template <class TrPack>
73  struct unpack_trs {
74  using type = tp::empty_pack;
75  };
76 
77  template <class Tr, class... Trs>
78  struct unpack_trs<tp::type_pack<Tr, Trs...>> {
79  using source_t = typename Tr::source_t;
80  using event_t = typename Tr::event_t;
81  using guard_t = typename Tr::guard_t;
82  using tr_t = tr_cond<source_t, event_t, guard_t>;
83 
84  using type =
85  tp::concatenate_t<tp::just_type<tr_t>,
86  typename unpack_trs<tp::type_pack<Trs...>>::type>;
87  };
88  } // namespace __details
89 
90  template <typename... Ts>
92  /** @cond undocumented */
93  using transitions = tp::type_pack<Ts...>;
94 
95  using sources = tp::type_pack<typename Ts::source_t...>;
96  using events = tp::type_pack<typename Ts::event_t...>;
97  using targets = tp::type_pack<typename Ts::target_t...>;
98  using guards_raw = tp::concatenate_t<tp::type_pack<typename Ts::guard_t...>,
99  tp::just_type<none>>;
100  using guards = typename __details::unpack_guards<guards_raw>::type;
101 
102  using states = tp::concatenate_t<sources, targets>;
103 
104  using state_collection = tp::unique_t<states>;
105  using event_collection = tp::unique_t<events>;
106  using guard_collection = tp::unique_t<guards>;
107 
108  using state_v = typename __details::unpack<state_collection>::type;
109  using event_v = typename __details::unpack<event_collection>::type;
110  using guard_v = typename __details::unpack<guard_collection>::type;
111  /** @endcond */
112 
113  private:
114  using tr_conds = typename __details::unpack_trs<transitions>::type;
115  using test_t = tp::unique_t<tr_conds>;
116 
117  static_assert(tp::is_equal<tr_conds, test_t>::value,
118  "Duplicated transitions");
119  };
120 
121  namespace __details {
122 
123  template <class T, class Pack>
124  constexpr bool static_check_contains() {
125  constexpr bool contains = tp::contains<T, Pack>::value;
126  static_assert(contains);
127  return contains;
128  }
129 
130  template <class FSMLogger, class F, typename... Args>
131  inline void invoke(FSMLogger& logger, F&& f, Args&&... args) noexcept(
132  std::is_nothrow_invocable_v<F, Args...>) {
133  if constexpr (std::is_invocable<F, Args...>::value) {
134  logger.write("Calling an action...");
135  f(std::forward<Args>(args)...);
136  }
137  }
138 
139  } // namespace __details
140 
141  class empty_logger {
142  public:
143  /**
144  * @brief Log message, that inspects a type T.
145  */
146  template <typename T>
147  inline void write(const char*) noexcept {}
148 
149  /**
150  * @brief Log message
151  */
152  inline void write(const char*) noexcept {}
153  };
154 
155  template <class Table, class Logger = empty_logger>
157  private:
158  using state_v = typename Table::state_v;
159  using event_v = typename Table::event_v;
160  using guard_v = typename Table::guard_v;
161  using transition_pack = typename Table::transitions;
162  using guard_collection = typename Table::guard_collection;
163 
164  static constexpr std::size_t m_tr_count = transition_pack::size();
165 
166  state_v m_state;
167  guard_v m_guard;
168 
169  using logger_t = Logger;
170  logger_t logger;
171 
172  template <class State, class Event, class Guard, class Pack,
173  std::size_t Idx>
174  struct event_impl {
175  template <typename... Args>
176  inline void operator()(state_v&, Args&&...) noexcept {}
177  };
178 
179  template <class State, class Event, class Guard, std::size_t Idx,
180  typename T, typename... Ts>
181  struct event_impl<State, Event, Guard, tp::type_pack<T, Ts...>, Idx> {
182  template <typename... Args>
183  inline void operator()(state_v& state, logger_t& log, Args&&... args) {
184  using state_t = typename T::source_t;
185  using event_t = typename T::event_t;
186  using guard_t = typename T::guard_t;
187  using target_t = typename T::target_t;
188  using action_t = typename T::action_t;
189 
190  if constexpr (std::is_same_v<State, state_t> &&
191  std::is_same_v<Event, event_t> &&
192  __details::match_v<Guard, guard_t>) {
193  log.template write<target_t>("Change state to ");
194  state = target_t {};
195  __details::invoke(log, action_t {}, std::forward<Args>(args)...);
196  } else
197  event_impl<State, Event, Guard, tp::type_pack<Ts...>, Idx + 1> {}(
198  state, log, std::forward<Args>(args)...);
199  }
200  };
201 
202  public:
203  inline state_machine()
204  : m_state(tp::at_t<0, typename Table::sources> {}), m_guard(none {}) {}
205 
206  /**
207  * @brief Constructor that allows to initialize a logger
208  *
209  * If the user-defined logger needs some internal data initialization,
210  * user can define a template type of State Machine as a reference to a
211  * logger and pass a logger by reference; or pass it by value.
212  */
213  inline state_machine(logger_t custom_logger)
214  : m_state(tp::at_t<0, typename Table::sources> {}), m_guard(none {}),
215  logger(std::move(custom_logger)) {}
216 
217  /**
218  * @brief Pass an event to a State Machine
219  *
220  * @tparam Event event
221  * @tparam Args... variadic template type pack of arguments
222  *
223  * Input impact for State Machine. If the there is a transition, for which
224  * current state, current guard and an event are matched, the transition
225  * will be performed.
226  *
227  * If the given event causes a transition, and this transition has an
228  * action, it will be called with the arguments `Args...`.
229  */
230  template <typename Event, typename... Args>
231  void event(Args&&... args) {
232  logger.template write<Event>("New event: ");
233  state_v& ref_state = m_state;
234  guard_v& ref_guard = m_guard;
235  auto l = [&](const auto& arg) {
236  auto gl = [&](const auto& arg2) {
237  using event_t = Event;
238  using state_t = std::decay_t<decltype(arg)>;
239  using guard_t = std::decay_t<decltype(arg2)>;
240  event_impl<state_t, event_t, guard_t, transition_pack, 0> {}(
241  ref_state, logger, std::forward<Args>(args)...);
242  };
243  std::visit(gl, ref_guard);
244  };
245  std::visit(l, m_state);
246  }
247 
248  /**
249  * @brief Calls a state action
250  *
251  * @tparam Args... variadic template type pack of arguments
252  *
253  * If the current state type is a functor and it is can be called with the
254  * given arguments, it will be called.
255  */
256  template <typename... Args>
257  void action(Args&&... args) {
258  logger_t& ref_log = logger;
259  auto l = [&](const auto& arg) {
260  using state_t = std::decay_t<decltype(arg)>;
261  ref_log.template write<state_t>("Attempt to call an action for: ");
262  __details::invoke(ref_log, state_t {}, std::forward<Args>(args)...);
263  };
264  std::visit(l, m_state);
265  }
266 
267  /**
268  * @brief Change current guard
269  *
270  * @tparam Guard next guard
271  */
272  template <class Guard>
273  inline void guard() {
274  if constexpr (__details::static_check_contains<Guard,
275  guard_collection>()) {
276  logger.template write<Guard>("New guard: ");
277  m_guard = Guard {};
278  }
279  }
280  };
281 
282  /* guard definitions */
283 
284  namespace __details {
285 
286  enum class guard_class { noneof, anyof };
287 
288  struct logic_guard_base {};
289 
290  } // namespace __details
291 
292  template <class Guard, class... Guards>
293  struct guard_any_of : __details::logic_guard_base {
294  /** @cond undocumented */
295  static constexpr __details::guard_class type =
296  __details::guard_class::anyof;
297  using guard_pack = tp::type_pack<Guard, Guards...>;
298  using pack = guard_pack;
299  /** @endcond */
300  };
301 
302  template <class Guard, class... Guards>
303  struct guard_none_of : __details::logic_guard_base {
304  /** @cond undocumented */
305  static constexpr __details::guard_class type =
306  __details::guard_class::noneof;
307  using guard_pack = tp::type_pack<Guard, Guards...>;
308  using pack = guard_pack;
309  /** @endcond */
310  };
311 
312  /** @brief Typedef to guard_none_of */
313  template <class Guard>
314  using not_ = guard_none_of<Guard>;
315 
316  /** @brief Typedef to guard_any_of */
317  template <class... Guards>
318  using any_of = guard_any_of<Guards...>;
319 
320  /** @brief Typedef to guard_none_of */
321  template <class... Guards>
322  using none_of = guard_none_of<Guards...>;
323 
324  namespace __details {
325 
326  template <class Guard, class Target, typename AlwaysVoid>
327  struct match_impl {
328  static const bool value = false;
329  };
330 
331  template <class Guard>
332  struct is_none_guard {
333  static constexpr bool value = false;
334  };
335 
336  template <>
337  struct is_none_guard<none> {
338  static constexpr bool value = true;
339  };
340 
341  template <class Guard, class Target>
342  struct match_impl<Guard, Target,
343  std::enable_if_t<std::is_same_v<Guard, Target> &&
344  !is_none_guard<Target>::value>> {
345  static constexpr bool value = true;
346  };
347 
348  template <class Guard, class Target>
349  struct match_impl<
350  Guard, Target,
351  std::enable_if_t<
352  Target::type == guard_class::anyof &&
353  tp::contains<Guard, typename Target::guard_pack>::value>> {
354  static constexpr bool value = true;
355  };
356 
357  template <class Guard, class Target>
358  struct match_impl<
359  Guard, Target,
360  std::enable_if_t<
361  Target::type == guard_class::noneof &&
362  !tp::contains<Guard, typename Target::guard_pack>::value>> {
363  static constexpr bool value = true;
364  };
365 
366  template <class Guard, class Target>
367  struct match_impl<Guard, Target,
368  std::enable_if_t<is_none_guard<Target>::value>> {
369  static constexpr bool value = true;
370  };
371 
372  /*
373  * match checks if the guard Guard matches with guard Target.
374  * Provides constant member, which is true in the following cases:
375  * if Guard == Target
376  * if Target == any_of and Guard is appeared in its guard pack
377  * if Target == none_of and Guard is not appeared in its guard pack
378  *
379  * Otherwise the value of member is false.
380  */
381  template <class Gd, class Target>
382  struct match : __details::match_impl<Gd, Target, void> {};
383 
384  template <class Guard>
385  struct is_logical_guard {
386  static constexpr bool value = std::is_base_of_v<logic_guard_base, Guard>;
387  };
388 
389  template <class Guard, typename AlwaysVoid>
390  struct unpack_guard_impl {
391  using type = tp::just_type<Guard>;
392  };
393 
394  template <class Guard>
395  struct unpack_guard_impl<Guard,
396  std::enable_if_t<is_logical_guard<Guard>::value>> {
397  using type = typename Guard::pack;
398  };
399 
400  template <class Guard>
401  struct unpack_guard : unpack_guard_impl<Guard, void> {};
402 
403  template <class GuardPack>
404  struct unpack_guards {};
405 
406  template <typename T, typename... Ts>
407  struct unpack_guards<tp::type_pack<T, Ts...>> {
408  using pack = typename unpack_guard<T>::type;
409  using next = typename unpack_guards<tp::type_pack<Ts...>>::type;
410  using type = tp::concatenate_t<pack, next>;
411  };
412 
413  template <>
414  struct unpack_guards<tp::empty_pack> {
415  using type = tp::empty_pack;
416  };
417 
418  } // namespace __details
419 
420 } // namespace pure
421 
422 #endif