Loading CMakeLists.txt +1 −0 Original line number Diff line number Diff line Loading @@ -200,6 +200,7 @@ if(SLED_BUILD_TESTS) sled_add_test(NAME sled_config_test SRCS src/sled/config_test.cc) sled_add_test(NAME sled_ioc_test SRCS src/sled/ioc/ioc_test.cc) sled_add_test(NAME sled_inja_test SRCS src/sled/nonstd/inja_test.cc) sled_add_test(NAME sled_fsm_test SRCS src/sled/nonstd/fsm_test.cc) endif(SLED_BUILD_TESTS) if(SLED_BUILD_FUZZ) Loading src/sled/nonstd/fsm.h 0 → 100644 +461 −0 Original line number Diff line number Diff line /* * Copyright (c) 2015-2021 Thomas Kemmer * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, copy, * modify, merge, publish, distribute, sublicense, and/or sell copies * of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #ifndef FSMLITE_FSM_H #define FSMLITE_FSM_H #include <cstddef> #include <type_traits> #if !defined(NDEBUG) && (!__GNUC__ || __EXCEPTIONS) #include <stdexcept> #endif namespace fsmlite { template<typename T, typename State> class Fsm; namespace detail { #if __cplusplus >= 201703L || _MSVC_LANG >= 201703L template<class F, class... Args> using invoke_result_t = std::invoke_result_t<F, Args...>; template<class F, class... Args> using is_invocable = std::is_invocable<F, Args...>; #elif __cplusplus >= 201103L || _MSVC_LANG >= 201103L template<class F, class... Args> using invoke_result_t = typename std::result_of<F && (Args && ...)>::type; struct is_invocable_test { struct no_type { int a; int b; }; template<class F, class... Args, class = invoke_result_t<F, Args...>> static char test(int); template<class, class...> static no_type test(...); }; template<class F, class... Args> using is_invocable = typename std::integral_constant<bool, sizeof(is_invocable_test::test<F, Args...>(0)) == 1>::type; #else #error "fsmlite requires C++11 support." #endif // C++11 std::forward() is in <utility>, which may not be // present on freestanding implementations template<class T> constexpr T && forward(typename std::remove_reference<T>::type &t) noexcept { return static_cast<T &&>(t); } template<class T> constexpr T && forward(typename std::remove_reference<T>::type &&t) noexcept { return static_cast<T &&>(t); } // C++17 std::invoke() is in <functional>, which may not be // present on freestanding implementations template<class F, class... Args> invoke_result_t<F, Args...> invoke(F &&f, Args &&...args) { return f(args...); } template<class M, class T, class T1, class... Args> invoke_result_t<M T::*, T1, Args...> invoke(M T::*f, T1 &&obj, Args &&...args) { return (obj.*f)(args...); } // use any of F(), F(Arg1), F(Arg2), F(Arg1, Arg2) template<class F, class Arg1, class Arg2, bool f1 = is_invocable<F>::value, bool f2 = is_invocable<F, Arg1>::value, bool f3 = is_invocable<F, Arg2>::value, bool f4 = is_invocable<F, Arg1, Arg2>::value> struct binary_fn_helper; template<class F, class Arg1, class Arg2> struct binary_fn_helper<F, Arg1, Arg2, true, false, false, false> { using result_type = invoke_result_t<F>; static result_type invoke(F &&f, Arg1 &&, Arg2 &&) { return detail::invoke(f); } }; template<class F, class Arg1, class Arg2> struct binary_fn_helper<F, Arg1, Arg2, false, true, false, false> { using result_type = invoke_result_t<F, Arg1>; static result_type invoke(F &&f, Arg1 &&a, Arg2 &&) { return detail::invoke(f, a); } }; template<class F, class Arg1, class Arg2> struct binary_fn_helper<F, Arg1, Arg2, false, false, true, false> { using result_type = invoke_result_t<F, Arg2>; static result_type invoke(F &&f, Arg1 &&, Arg2 &&b) { return detail::invoke(f, b); } }; template<class F, class Arg1, class Arg2> struct binary_fn_helper<F, Arg1, Arg2, false, false, false, true> { using result_type = invoke_result_t<F, Arg1, Arg2>; static result_type invoke(F &&f, Arg1 &&a, Arg2 &&b) { return detail::invoke(f, a, b); } }; template<class F, class Arg1, class Arg2> using invoke_as_binary_fn_result_t = typename binary_fn_helper<F, Arg1, Arg2>::result_type; template<class F, class Arg1, class Arg2> invoke_as_binary_fn_result_t<F, Arg1, Arg2> invoke_as_binary_fn(F &&f, Arg1 &&a, Arg2 &&b) { return binary_fn_helper<F, Arg1, Arg2>::invoke( detail::forward<F>(f), detail::forward<Arg1>(a), detail::forward<Arg2>(b)); } // basic template metaprogramming stuff; note that we could // use std::tuple instead of list, but <tuple> may nor be // present on freestanding implementations template<class...> struct list {}; template<class...> struct concat; template<class T, class... Types> struct concat<T, list<Types...>> { using type = list<T, Types...>; }; template<> struct concat<> { using type = list<>; }; template<template<typename> class Predicate, class...> struct filter; template<template<typename> class Predicate, class T, class... Types> struct filter<Predicate, T, Types...> { using type = typename std::conditional<Predicate<T>::value, typename concat<T, typename filter<Predicate, Types...>::type>::type, typename filter<Predicate, Types...>::type>::type; }; template<template<typename> class Predicate> struct filter<Predicate> { using type = list<>; }; }// namespace detail /** * Finite state machine (FSM) base class template. * * @tparam Derived the derived state machine class * * @tparam State the FSM's state type, defaults to `int` */ template<class Derived, class State = int> class Fsm { public: /** * The FSM's state type. */ typedef State state_type; public: /** * Create a state machine with an optional initial state. * * @param init_state the FSM's initial state */ Fsm(state_type init_state = state_type()) : m_state(init_state) {} /** * Process an event. * * @warning This member function must not be called * recursively, e.g. from another `fsm` instance. * * @tparam Event the event type * * @param event the event instance * * @throw std::logic_error if a recursive invocation is * detected */ template<class Event> void ProcessEvent(const Event &event) { using rows = typename by_event_type<Event, typename Derived::TransitionTable>::type; processing_lock lock(*this); static_assert(std::is_base_of<Fsm, Derived>::value, "must derive from fsm"); Derived &self = static_cast<Derived &>(*this); m_state = handle_event<Event, rows>::execute(self, event, m_state); } /** * Return the state machine's current state. */ state_type CurrentState() const { return m_state; } protected: /** * Called when no transition can be found for the given event * in the current state. Derived state machines may override * this to throw an exception, or change to some other (error) * state. The default is to return the current state, so no * state change occurs. * * @tparam Event the event type * * @param event the event instance * * @return the FSM's new state */ template<class Event> state_type NoTransition(const Event & /*event*/) { return m_state; } private: template<State start, class Event, State target> struct row_base { using state_type = State; using event_type = Event; static constexpr state_type start_value() { return start; } static constexpr state_type target_value() { return target; } protected: template<class Action> static void ProcessEvent(Action &&action, Derived &self, const Event &event) { detail::invoke_as_binary_fn(action, self, event); } // clang++-5.0: constexpr function's return type 'void' is not a literal type static /*constexpr*/ void ProcessEvent(std::nullptr_t, Derived & /*self*/, const Event & /*event*/) {} template<class Guard> static bool check_guard(Guard &&guard, const Derived &self, const Event &event) { return detail::invoke_as_binary_fn(guard, self, event); } static constexpr bool check_guard(std::nullptr_t, const Derived &, const Event &) { return true; } }; protected: /** * Transition table variadic class template. * * Each derived state machine class must define a nested * non-template type `transition_table` that's either derived * from or a type alias of `table`. */ template<class... Rows> using table = detail::list<Rows...>; /** * Basic transition class template. * * @tparam start the start state of the transition * * @tparam Event the event type triggering the transition * * @tparam target the target state of the transition * * @tparam Action an action function type, or `std::nullptr_t` * * @tparam action a static `Action` instance * * @tparam Guard a guard function type, or `std::nullptr_t` * * @tparam guard a static `Guard` instance */ template<State start, class Event, State target, class Action = std::nullptr_t, Action action = nullptr, class Guard = std::nullptr_t, Guard guard = nullptr> struct basic_row : public row_base<start, Event, target> { static void process_event(Derived &self, const Event &event) { row_base<start, Event, target>::ProcessEvent(action, self, event); } static bool check_guard(const Derived &self, const Event &event) { return row_base<start, Event, target>::check_guard(guard, self, event); } }; #if __cplusplus >= 201703L || _MSVC_LANG >= 201703L /** * Generic transition class template (requires C++17). * * @tparam start the start state of the transition * * @tparam Event the event type triggering the transition * * @tparam target the target state of the transition * * @tparam action a static action function pointer, or `nullptr` * * @tparam guard a static guard function pointer, or `nullptr` */ template<State start, class Event, State target, auto action = nullptr, auto guard = nullptr> struct row : public row_base<start, Event, target> { static void ProcessEvent(Derived &self, const Event &event) { row_base<start, Event, target>::process_event(action, self, event); } static bool check_guard(const Derived &self, const Event &event) { return row_base<start, Event, target>::check_guard(guard, self, event); } }; #else /** * Member function transition class template. * * @tparam start the start state of the transition * * @tparam Event the event type triggering the transition * * @tparam target the target state of the transition * * @tparam action an action member function, or `nullptr` * * @tparam guard a guard member function, or `nullptr` */ template<State start, class Event, State target, void (Derived::*action)(const Event &) = nullptr, bool (Derived::*guard)(const Event &) const = nullptr> struct row : public row_base<start, Event, target> { static void ProcessEvent(Derived &self, const Event &event) { if (action != nullptr) { row_base<start, Event, target>::ProcessEvent(action, self, event); } } static bool check_guard(const Derived &self, const Event &event) { if (guard != nullptr) { return row_base<start, Event, target>::check_guard(guard, self, event); } else { return true; } } }; #endif private: template<class Event, class...> struct by_event_type; template<class Event, class... Types> struct by_event_type<Event, detail::list<Types...>> { template<class T> using predicate = std::is_same<typename T::event_type, Event>; using type = typename detail::filter<predicate, Types...>::type; }; template<class Event> struct by_event_type<Event, detail::list<>> { using type = detail::list<>; }; template<class Event, class...> struct handle_event; template<class Event, class T, class... Types> struct handle_event<Event, detail::list<T, Types...>> { static State execute(Derived &self, const Event &event, State state) { return state == T::start_value() && T::check_guard(self, event) ? T::ProcessEvent(self, event), T::target_value() : handle_event<Event, detail::list<Types...>>::execute(self, event, state); } }; template<class Event> struct handle_event<Event, detail::list<>> { static State execute(Derived &self, const Event &event, State) { return self.NoTransition(event); } }; private: state_type m_state; private: #if !defined(NDEBUG) && (!__GNUC__ || __EXCEPTIONS) class processing_lock { public: processing_lock(Fsm &m) : processing(m.processing) { if (processing) { throw std::logic_error("process_event called recursively"); } processing = true; } ~processing_lock() { processing = false; } private: bool &processing; }; bool processing = false; #else struct processing_lock { processing_lock(fsm &) {} }; #endif }; }// namespace fsmlite namespace sled { template<typename T, typename State = int> using Fsm = fsmlite::Fsm<T, State>; } #endif src/sled/nonstd/fsm_test.cc 0 → 100644 +76 −0 Original line number Diff line number Diff line #include <sled/nonstd/fsm.h> enum class State { kIdle, kStarted, kEnd, }; struct FSMTest : public sled::Fsm<FSMTest, State> { public: FSMTest() : Fsm(State::kIdle) {} struct StartEvent { StartEvent(bool do_nothing = false) : do_nothing(do_nothing) {} bool do_nothing = false; }; struct StopEvent {}; struct ResetEvent {}; template<typename E> state_type NoTransition(const E &) { ++error_count; return CurrentState(); } int error_count = 0; private: bool StartCheck(const StartEvent &e) const { return !e.do_nothing; } void OnStart(const StartEvent &) {} void OnStop(const StopEvent &) {} void OnReset(const ResetEvent &) {} public: using TransitionTable = table<row<State::kIdle, StartEvent, State::kStarted, &FSMTest::OnStart, &FSMTest::StartCheck>, row<State::kStarted, StopEvent, State::kEnd, &FSMTest::OnStop>, row<State::kEnd, ResetEvent, State::kIdle, &FSMTest::OnReset>>; }; TEST_SUITE("fsm") { TEST_CASE("Entry Exit") { FSMTest fsm_test; CHECK_EQ(fsm_test.CurrentState(), State::kIdle); CHECK_EQ(fsm_test.error_count, 0); fsm_test.ProcessEvent(FSMTest::StartEvent(true)); CHECK_EQ(fsm_test.CurrentState(), State::kIdle); CHECK_EQ(fsm_test.error_count, 1); fsm_test.ProcessEvent(FSMTest::StartEvent()); CHECK_EQ(fsm_test.CurrentState(), State::kStarted); CHECK_EQ(fsm_test.error_count, 1); fsm_test.ProcessEvent(FSMTest::StopEvent()); CHECK_EQ(fsm_test.CurrentState(), State::kEnd); CHECK_EQ(fsm_test.error_count, 1); fsm_test.ProcessEvent(FSMTest::ResetEvent()); CHECK_EQ(fsm_test.CurrentState(), State::kIdle); CHECK_EQ(fsm_test.error_count, 1); fsm_test.ProcessEvent(FSMTest::ResetEvent()); CHECK_EQ(fsm_test.CurrentState(), State::kIdle); CHECK_EQ(fsm_test.error_count, 2); } } src/sled/sled.h +1 −0 Original line number Diff line number Diff line Loading @@ -7,6 +7,7 @@ #include "sled/async/async.h" #include "sled/nonstd/cxxopts.h" #include "sled/nonstd/expected.h" #include "sled/nonstd/fsm.h" #include "sled/nonstd/inja.h" #include "sled/nonstd/string_view.h" #include "toml.hpp" Loading Loading
CMakeLists.txt +1 −0 Original line number Diff line number Diff line Loading @@ -200,6 +200,7 @@ if(SLED_BUILD_TESTS) sled_add_test(NAME sled_config_test SRCS src/sled/config_test.cc) sled_add_test(NAME sled_ioc_test SRCS src/sled/ioc/ioc_test.cc) sled_add_test(NAME sled_inja_test SRCS src/sled/nonstd/inja_test.cc) sled_add_test(NAME sled_fsm_test SRCS src/sled/nonstd/fsm_test.cc) endif(SLED_BUILD_TESTS) if(SLED_BUILD_FUZZ) Loading
src/sled/nonstd/fsm.h 0 → 100644 +461 −0 Original line number Diff line number Diff line /* * Copyright (c) 2015-2021 Thomas Kemmer * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, copy, * modify, merge, publish, distribute, sublicense, and/or sell copies * of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #ifndef FSMLITE_FSM_H #define FSMLITE_FSM_H #include <cstddef> #include <type_traits> #if !defined(NDEBUG) && (!__GNUC__ || __EXCEPTIONS) #include <stdexcept> #endif namespace fsmlite { template<typename T, typename State> class Fsm; namespace detail { #if __cplusplus >= 201703L || _MSVC_LANG >= 201703L template<class F, class... Args> using invoke_result_t = std::invoke_result_t<F, Args...>; template<class F, class... Args> using is_invocable = std::is_invocable<F, Args...>; #elif __cplusplus >= 201103L || _MSVC_LANG >= 201103L template<class F, class... Args> using invoke_result_t = typename std::result_of<F && (Args && ...)>::type; struct is_invocable_test { struct no_type { int a; int b; }; template<class F, class... Args, class = invoke_result_t<F, Args...>> static char test(int); template<class, class...> static no_type test(...); }; template<class F, class... Args> using is_invocable = typename std::integral_constant<bool, sizeof(is_invocable_test::test<F, Args...>(0)) == 1>::type; #else #error "fsmlite requires C++11 support." #endif // C++11 std::forward() is in <utility>, which may not be // present on freestanding implementations template<class T> constexpr T && forward(typename std::remove_reference<T>::type &t) noexcept { return static_cast<T &&>(t); } template<class T> constexpr T && forward(typename std::remove_reference<T>::type &&t) noexcept { return static_cast<T &&>(t); } // C++17 std::invoke() is in <functional>, which may not be // present on freestanding implementations template<class F, class... Args> invoke_result_t<F, Args...> invoke(F &&f, Args &&...args) { return f(args...); } template<class M, class T, class T1, class... Args> invoke_result_t<M T::*, T1, Args...> invoke(M T::*f, T1 &&obj, Args &&...args) { return (obj.*f)(args...); } // use any of F(), F(Arg1), F(Arg2), F(Arg1, Arg2) template<class F, class Arg1, class Arg2, bool f1 = is_invocable<F>::value, bool f2 = is_invocable<F, Arg1>::value, bool f3 = is_invocable<F, Arg2>::value, bool f4 = is_invocable<F, Arg1, Arg2>::value> struct binary_fn_helper; template<class F, class Arg1, class Arg2> struct binary_fn_helper<F, Arg1, Arg2, true, false, false, false> { using result_type = invoke_result_t<F>; static result_type invoke(F &&f, Arg1 &&, Arg2 &&) { return detail::invoke(f); } }; template<class F, class Arg1, class Arg2> struct binary_fn_helper<F, Arg1, Arg2, false, true, false, false> { using result_type = invoke_result_t<F, Arg1>; static result_type invoke(F &&f, Arg1 &&a, Arg2 &&) { return detail::invoke(f, a); } }; template<class F, class Arg1, class Arg2> struct binary_fn_helper<F, Arg1, Arg2, false, false, true, false> { using result_type = invoke_result_t<F, Arg2>; static result_type invoke(F &&f, Arg1 &&, Arg2 &&b) { return detail::invoke(f, b); } }; template<class F, class Arg1, class Arg2> struct binary_fn_helper<F, Arg1, Arg2, false, false, false, true> { using result_type = invoke_result_t<F, Arg1, Arg2>; static result_type invoke(F &&f, Arg1 &&a, Arg2 &&b) { return detail::invoke(f, a, b); } }; template<class F, class Arg1, class Arg2> using invoke_as_binary_fn_result_t = typename binary_fn_helper<F, Arg1, Arg2>::result_type; template<class F, class Arg1, class Arg2> invoke_as_binary_fn_result_t<F, Arg1, Arg2> invoke_as_binary_fn(F &&f, Arg1 &&a, Arg2 &&b) { return binary_fn_helper<F, Arg1, Arg2>::invoke( detail::forward<F>(f), detail::forward<Arg1>(a), detail::forward<Arg2>(b)); } // basic template metaprogramming stuff; note that we could // use std::tuple instead of list, but <tuple> may nor be // present on freestanding implementations template<class...> struct list {}; template<class...> struct concat; template<class T, class... Types> struct concat<T, list<Types...>> { using type = list<T, Types...>; }; template<> struct concat<> { using type = list<>; }; template<template<typename> class Predicate, class...> struct filter; template<template<typename> class Predicate, class T, class... Types> struct filter<Predicate, T, Types...> { using type = typename std::conditional<Predicate<T>::value, typename concat<T, typename filter<Predicate, Types...>::type>::type, typename filter<Predicate, Types...>::type>::type; }; template<template<typename> class Predicate> struct filter<Predicate> { using type = list<>; }; }// namespace detail /** * Finite state machine (FSM) base class template. * * @tparam Derived the derived state machine class * * @tparam State the FSM's state type, defaults to `int` */ template<class Derived, class State = int> class Fsm { public: /** * The FSM's state type. */ typedef State state_type; public: /** * Create a state machine with an optional initial state. * * @param init_state the FSM's initial state */ Fsm(state_type init_state = state_type()) : m_state(init_state) {} /** * Process an event. * * @warning This member function must not be called * recursively, e.g. from another `fsm` instance. * * @tparam Event the event type * * @param event the event instance * * @throw std::logic_error if a recursive invocation is * detected */ template<class Event> void ProcessEvent(const Event &event) { using rows = typename by_event_type<Event, typename Derived::TransitionTable>::type; processing_lock lock(*this); static_assert(std::is_base_of<Fsm, Derived>::value, "must derive from fsm"); Derived &self = static_cast<Derived &>(*this); m_state = handle_event<Event, rows>::execute(self, event, m_state); } /** * Return the state machine's current state. */ state_type CurrentState() const { return m_state; } protected: /** * Called when no transition can be found for the given event * in the current state. Derived state machines may override * this to throw an exception, or change to some other (error) * state. The default is to return the current state, so no * state change occurs. * * @tparam Event the event type * * @param event the event instance * * @return the FSM's new state */ template<class Event> state_type NoTransition(const Event & /*event*/) { return m_state; } private: template<State start, class Event, State target> struct row_base { using state_type = State; using event_type = Event; static constexpr state_type start_value() { return start; } static constexpr state_type target_value() { return target; } protected: template<class Action> static void ProcessEvent(Action &&action, Derived &self, const Event &event) { detail::invoke_as_binary_fn(action, self, event); } // clang++-5.0: constexpr function's return type 'void' is not a literal type static /*constexpr*/ void ProcessEvent(std::nullptr_t, Derived & /*self*/, const Event & /*event*/) {} template<class Guard> static bool check_guard(Guard &&guard, const Derived &self, const Event &event) { return detail::invoke_as_binary_fn(guard, self, event); } static constexpr bool check_guard(std::nullptr_t, const Derived &, const Event &) { return true; } }; protected: /** * Transition table variadic class template. * * Each derived state machine class must define a nested * non-template type `transition_table` that's either derived * from or a type alias of `table`. */ template<class... Rows> using table = detail::list<Rows...>; /** * Basic transition class template. * * @tparam start the start state of the transition * * @tparam Event the event type triggering the transition * * @tparam target the target state of the transition * * @tparam Action an action function type, or `std::nullptr_t` * * @tparam action a static `Action` instance * * @tparam Guard a guard function type, or `std::nullptr_t` * * @tparam guard a static `Guard` instance */ template<State start, class Event, State target, class Action = std::nullptr_t, Action action = nullptr, class Guard = std::nullptr_t, Guard guard = nullptr> struct basic_row : public row_base<start, Event, target> { static void process_event(Derived &self, const Event &event) { row_base<start, Event, target>::ProcessEvent(action, self, event); } static bool check_guard(const Derived &self, const Event &event) { return row_base<start, Event, target>::check_guard(guard, self, event); } }; #if __cplusplus >= 201703L || _MSVC_LANG >= 201703L /** * Generic transition class template (requires C++17). * * @tparam start the start state of the transition * * @tparam Event the event type triggering the transition * * @tparam target the target state of the transition * * @tparam action a static action function pointer, or `nullptr` * * @tparam guard a static guard function pointer, or `nullptr` */ template<State start, class Event, State target, auto action = nullptr, auto guard = nullptr> struct row : public row_base<start, Event, target> { static void ProcessEvent(Derived &self, const Event &event) { row_base<start, Event, target>::process_event(action, self, event); } static bool check_guard(const Derived &self, const Event &event) { return row_base<start, Event, target>::check_guard(guard, self, event); } }; #else /** * Member function transition class template. * * @tparam start the start state of the transition * * @tparam Event the event type triggering the transition * * @tparam target the target state of the transition * * @tparam action an action member function, or `nullptr` * * @tparam guard a guard member function, or `nullptr` */ template<State start, class Event, State target, void (Derived::*action)(const Event &) = nullptr, bool (Derived::*guard)(const Event &) const = nullptr> struct row : public row_base<start, Event, target> { static void ProcessEvent(Derived &self, const Event &event) { if (action != nullptr) { row_base<start, Event, target>::ProcessEvent(action, self, event); } } static bool check_guard(const Derived &self, const Event &event) { if (guard != nullptr) { return row_base<start, Event, target>::check_guard(guard, self, event); } else { return true; } } }; #endif private: template<class Event, class...> struct by_event_type; template<class Event, class... Types> struct by_event_type<Event, detail::list<Types...>> { template<class T> using predicate = std::is_same<typename T::event_type, Event>; using type = typename detail::filter<predicate, Types...>::type; }; template<class Event> struct by_event_type<Event, detail::list<>> { using type = detail::list<>; }; template<class Event, class...> struct handle_event; template<class Event, class T, class... Types> struct handle_event<Event, detail::list<T, Types...>> { static State execute(Derived &self, const Event &event, State state) { return state == T::start_value() && T::check_guard(self, event) ? T::ProcessEvent(self, event), T::target_value() : handle_event<Event, detail::list<Types...>>::execute(self, event, state); } }; template<class Event> struct handle_event<Event, detail::list<>> { static State execute(Derived &self, const Event &event, State) { return self.NoTransition(event); } }; private: state_type m_state; private: #if !defined(NDEBUG) && (!__GNUC__ || __EXCEPTIONS) class processing_lock { public: processing_lock(Fsm &m) : processing(m.processing) { if (processing) { throw std::logic_error("process_event called recursively"); } processing = true; } ~processing_lock() { processing = false; } private: bool &processing; }; bool processing = false; #else struct processing_lock { processing_lock(fsm &) {} }; #endif }; }// namespace fsmlite namespace sled { template<typename T, typename State = int> using Fsm = fsmlite::Fsm<T, State>; } #endif
src/sled/nonstd/fsm_test.cc 0 → 100644 +76 −0 Original line number Diff line number Diff line #include <sled/nonstd/fsm.h> enum class State { kIdle, kStarted, kEnd, }; struct FSMTest : public sled::Fsm<FSMTest, State> { public: FSMTest() : Fsm(State::kIdle) {} struct StartEvent { StartEvent(bool do_nothing = false) : do_nothing(do_nothing) {} bool do_nothing = false; }; struct StopEvent {}; struct ResetEvent {}; template<typename E> state_type NoTransition(const E &) { ++error_count; return CurrentState(); } int error_count = 0; private: bool StartCheck(const StartEvent &e) const { return !e.do_nothing; } void OnStart(const StartEvent &) {} void OnStop(const StopEvent &) {} void OnReset(const ResetEvent &) {} public: using TransitionTable = table<row<State::kIdle, StartEvent, State::kStarted, &FSMTest::OnStart, &FSMTest::StartCheck>, row<State::kStarted, StopEvent, State::kEnd, &FSMTest::OnStop>, row<State::kEnd, ResetEvent, State::kIdle, &FSMTest::OnReset>>; }; TEST_SUITE("fsm") { TEST_CASE("Entry Exit") { FSMTest fsm_test; CHECK_EQ(fsm_test.CurrentState(), State::kIdle); CHECK_EQ(fsm_test.error_count, 0); fsm_test.ProcessEvent(FSMTest::StartEvent(true)); CHECK_EQ(fsm_test.CurrentState(), State::kIdle); CHECK_EQ(fsm_test.error_count, 1); fsm_test.ProcessEvent(FSMTest::StartEvent()); CHECK_EQ(fsm_test.CurrentState(), State::kStarted); CHECK_EQ(fsm_test.error_count, 1); fsm_test.ProcessEvent(FSMTest::StopEvent()); CHECK_EQ(fsm_test.CurrentState(), State::kEnd); CHECK_EQ(fsm_test.error_count, 1); fsm_test.ProcessEvent(FSMTest::ResetEvent()); CHECK_EQ(fsm_test.CurrentState(), State::kIdle); CHECK_EQ(fsm_test.error_count, 1); fsm_test.ProcessEvent(FSMTest::ResetEvent()); CHECK_EQ(fsm_test.CurrentState(), State::kIdle); CHECK_EQ(fsm_test.error_count, 2); } }
src/sled/sled.h +1 −0 Original line number Diff line number Diff line Loading @@ -7,6 +7,7 @@ #include "sled/async/async.h" #include "sled/nonstd/cxxopts.h" #include "sled/nonstd/expected.h" #include "sled/nonstd/fsm.h" #include "sled/nonstd/inja.h" #include "sled/nonstd/string_view.h" #include "toml.hpp" Loading