feat add fsm

This commit is contained in:
tqcq 2024-04-11 10:28:46 +00:00
parent b5e5d717a9
commit a1ec48b1ea
4 changed files with 539 additions and 0 deletions

View File

@ -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_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_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_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) endif(SLED_BUILD_TESTS)
if(SLED_BUILD_FUZZ) if(SLED_BUILD_FUZZ)

461
src/sled/nonstd/fsm.h Normal file
View 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

View 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);
}
}

View File

@ -7,6 +7,7 @@
#include "sled/async/async.h" #include "sled/async/async.h"
#include "sled/nonstd/cxxopts.h" #include "sled/nonstd/cxxopts.h"
#include "sled/nonstd/expected.h" #include "sled/nonstd/expected.h"
#include "sled/nonstd/fsm.h"
#include "sled/nonstd/inja.h" #include "sled/nonstd/inja.h"
#include "sled/nonstd/string_view.h" #include "sled/nonstd/string_view.h"
#include "toml.hpp" #include "toml.hpp"