feat add fsm
This commit is contained in:
parent
b5e5d717a9
commit
a1ec48b1ea
@ -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)
|
||||
|
461
src/sled/nonstd/fsm.h
Normal file
461
src/sled/nonstd/fsm.h
Normal file
@ -0,0 +1,461 @@
|
||||
/*
|
||||
* 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
|
76
src/sled/nonstd/fsm_test.cc
Normal file
76
src/sled/nonstd/fsm_test.cc
Normal file
@ -0,0 +1,76 @@
|
||||
#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);
|
||||
}
|
||||
}
|
@ -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…
Reference in New Issue
Block a user