Containers (STL-compatible) StateMachines MessageBus and more for Embedded Systems. See www.etlcpp.com
Diff: fsm_generator.h
- Revision:
- 0:b47c2a7920c2
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/fsm_generator.h Fri Mar 16 16:34:18 2018 +0000 @@ -0,0 +1,539 @@ +/****************************************************************************** +The MIT License(MIT) + +Embedded Template Library. +https://github.com/ETLCPP/etl +https://www.etlcpp.com + +Copyright(c) 2017 jwellbelove + +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. +******************************************************************************/ + +/*[[[cog +import cog +cog.outl("#if 0") +]]]*/ +/*[[[end]]]*/ +#error THIS HEADER IS A GENERATOR. DO NOT INCLUDE. +/*[[[cog +import cog +cog.outl("#endif") +]]]*/ +/*[[[end]]]*/ + +/*[[[cog +import cog +cog.outl("//***************************************************************************") +cog.outl("// This file has been auto generated. Do not edit this file.") +cog.outl("//***************************************************************************") +]]]*/ +/*[[[end]]]*/ + +//*************************************************************************** +// To generate to header file, run this at the command line. +// Note: You will need Python and COG installed. +// +// python -m cogapp -d -e -ofsm.h -DHandlers=<n> fsm_generator.h +// Where <n> is the number of messages to support. +// +// e.g. +// To generate handlers for up to 16 events... +// python -m cogapp -d -e -ofsm.h -DHandlers=16 fsm_generator.h +// +// See generate.bat +//*************************************************************************** + +#ifndef __ETL_FSM__ +#define __ETL_FSM__ + +#include <stdint.h> + +#include "platform.h" +#include "array.h" +#include "nullptr.h" +#include "error_handler.h" +#include "exception.h" +#include "user_type.h" +#include "message_router.h" +#include "integral_limits.h" +#include "largest.h" + +#undef ETL_FILE +#define ETL_FILE "34" + +#ifdef ETL_COMPILER_MICROSOFT +#undef max +#endif + +namespace etl +{ + class fsm; + + /// Allow alternative type for state id. +#if !defined(ETL_FSM_STATE_ID_TYPE) + typedef uint_least8_t fsm_state_id_t; +#else + typedef ETL_FSM_STATE_ID_TYPE fsm_state_id_t; +#endif + + // For internal FSM use. + typedef typename etl::larger_type<etl::message_id_t>::type fsm_internal_id_t; + + //*************************************************************************** + /// Base exception class for FSM. + //*************************************************************************** + class fsm_exception : public etl::exception + { + public: + + fsm_exception(string_type reason_, string_type file_name_, numeric_type line_number_) + : etl::exception(reason_, file_name_, line_number_) + { + } + }; + + //*************************************************************************** + /// Exception for null state pointer. + //*************************************************************************** + class fsm_null_state_exception : public etl::fsm_exception + { + public: + + fsm_null_state_exception(string_type file_name_, numeric_type line_number_) + : etl::fsm_exception(ETL_ERROR_TEXT("fsm:null state", ETL_FILE"A"), file_name_, line_number_) + { + } + }; + + //*************************************************************************** + /// Exception for invalid state id. + //*************************************************************************** + class fsm_state_id_exception : public etl::fsm_exception + { + public: + + fsm_state_id_exception(string_type file_name_, numeric_type line_number_) + : etl::fsm_exception(ETL_ERROR_TEXT("fsm:state id", ETL_FILE"B"), file_name_, line_number_) + { + } + }; + + //*************************************************************************** + /// Exception for incompatible state list. + //*************************************************************************** + class fsm_state_list_exception : public etl::fsm_exception + { + public: + + fsm_state_list_exception(string_type file_name_, numeric_type line_number_) + : etl::fsm_exception(ETL_ERROR_TEXT("fsm:state list", ETL_FILE"C"), file_name_, line_number_) + { + } + }; + + //*************************************************************************** + /// Interface class for FSM states. + //*************************************************************************** + class ifsm_state + { + public: + + /// Allows ifsm_state functions to be private. + friend class etl::fsm; + + //******************************************* + /// Gets the id for this state. + //******************************************* + etl::fsm_state_id_t get_state_id() const + { + return state_id; + } + + protected: + + //******************************************* + /// Constructor. + //******************************************* + ifsm_state(etl::fsm_state_id_t state_id_) + : state_id(state_id_), + p_context(std::nullptr) + { + } + + //******************************************* + inline etl::fsm& get_fsm_context() const + { + return *p_context; + } + + private: + + virtual fsm_state_id_t process_event(etl::imessage_router& source, const etl::imessage& message) = 0; + + virtual fsm_state_id_t on_enter_state() { return state_id; } // By default, do nothing. + virtual void on_exit_state() {} // By default, do nothing. + + //******************************************* + void set_fsm_context(etl::fsm& context) + { + p_context = &context; + } + + // The state id. + const etl::fsm_state_id_t state_id; + + // A pointer to the FSM context. + etl::fsm* p_context; + + // Disabled. + ifsm_state(const ifsm_state&); + ifsm_state& operator =(const ifsm_state&); + }; + + //*************************************************************************** + /// The FSM class. + //*************************************************************************** + class fsm : public etl::imessage_router + { + public: + + //******************************************* + /// Constructor. + //******************************************* + fsm(etl::message_router_id_t id) + : imessage_router(id), + p_state(std::nullptr) + { + } + + //******************************************* + /// Set the states for the FSM + //******************************************* + template <typename TSize> + void set_states(etl::ifsm_state** p_states, TSize size) + { + state_list = p_states; + number_of_states = etl::fsm_state_id_t(size); + + ETL_ASSERT((number_of_states > 0), ETL_ERROR(etl::fsm_state_list_exception)); + + for (etl::fsm_state_id_t i = 0; i < size; ++i) + { + ETL_ASSERT((state_list[i] != std::nullptr), ETL_ERROR(etl::fsm_null_state_exception)); + state_list[i]->set_fsm_context(*this); + } + } + + //******************************************* + /// Starts the FSM. + /// Can only be called once. + /// Subsequent calls will do nothing. + //******************************************* + void start() + { + // Can only be started once. + if (p_state == std::nullptr) + { + p_state = state_list[0]; + ETL_ASSERT(p_state != std::nullptr, ETL_ERROR(etl::fsm_null_state_exception)); + + p_state->on_enter_state(); + } + } + + //******************************************* + /// Top level message handler for the FSM. + //******************************************* + void receive(const etl::imessage& message) + { + etl::null_message_router nmr; + receive(nmr, message); + } + + //******************************************* + /// Top level message handler for the FSM. + //******************************************* + void receive(etl::imessage_router& source, const etl::imessage& message) + { + etl::fsm_state_id_t next_state_id = p_state->process_event(source, message); + ETL_ASSERT(next_state_id < number_of_states, ETL_ERROR(etl::fsm_state_id_exception)); + + etl::ifsm_state* p_next_state = state_list[next_state_id]; + + // Have we changed state? + if (p_next_state != p_state) + { + do + { + p_state->on_exit_state(); + p_state = p_next_state; + + next_state_id = p_state->on_enter_state(); + ETL_ASSERT(next_state_id < number_of_states, ETL_ERROR(etl::fsm_state_id_exception)); + + p_next_state = state_list[next_state_id]; + + } while (p_next_state != p_state); // Have we changed state again? + } + } + + using imessage_router::accepts; + + //******************************************* + /// Does this FSM accept the message id? + /// Yes, it accepts everything! + //******************************************* + bool accepts(etl::message_id_t id) const + { + return true; + } + + //******************************************* + /// Gets the current state id. + //******************************************* + etl::fsm_state_id_t get_state_id() const + { + ETL_ASSERT(p_state != std::nullptr, ETL_ERROR(etl::fsm_null_state_exception)); + return p_state->get_state_id(); + } + + //******************************************* + /// Gets a reference to the current state interface. + //******************************************* + ifsm_state& get_state() + { + ETL_ASSERT(p_state != std::nullptr, ETL_ERROR(etl::fsm_null_state_exception)); + return *p_state; + } + + //******************************************* + /// Gets a const reference to the current state interface. + //******************************************* + const ifsm_state& get_state() const + { + ETL_ASSERT(p_state != std::nullptr, ETL_ERROR(etl::fsm_null_state_exception)); + return *p_state; + } + + //******************************************* + /// Checks if the FSM has been started. + //******************************************* + bool is_started() const + { + return p_state != std::nullptr; + } + + //******************************************* + /// Reset the FSM to pre-started state. + //******************************************* + void reset() + { + p_state = std::nullptr; + } + + private: + + etl::ifsm_state* p_state; ///< A pointer to the current state. + etl::ifsm_state** state_list; ///< The list of added states. + etl::fsm_state_id_t number_of_states; ///< The number of states. + }; + + /*[[[cog + import cog + ################################################ + # The first definition for all of the events. + ################################################ + cog.outl("//***************************************************************************") + cog.outl("// The definition for all %s message types." % Handlers) + cog.outl("//***************************************************************************") + cog.outl("template <typename TContext, typename TDerived, const etl::fsm_state_id_t STATE_ID_, ") + cog.out(" ") + for n in range(1, int(Handlers)): + cog.out("typename T%s = void, " % n) + if n % 4 == 0: + cog.outl("") + cog.out(" ") + cog.outl("typename T%s = void>" % Handlers) + cog.outl("class fsm_state : public ifsm_state") + cog.outl("{") + cog.outl("public:") + cog.outl("") + cog.outl(" enum") + cog.outl(" {") + cog.outl(" STATE_ID = STATE_ID_") + cog.outl(" };") + cog.outl("") + cog.outl(" fsm_state()") + cog.outl(" : ifsm_state(STATE_ID)") + cog.outl(" {") + cog.outl(" }") + cog.outl("") + cog.outl("protected:") + cog.outl("") + cog.outl(" inline TContext& get_fsm_context() const") + cog.outl(" {") + cog.outl(" return static_cast<TContext&>(ifsm_state::get_fsm_context());") + cog.outl(" }") + cog.outl("") + cog.outl("private:") + cog.outl("") + cog.outl(" etl::fsm_state_id_t process_event(etl::imessage_router& source, const etl::imessage& message)") + cog.outl(" {") + cog.outl(" etl::fsm_state_id_t new_state_id;") + cog.outl(" etl::message_id_t event_id = message.message_id;") + cog.outl("") + cog.outl(" switch (event_id)") + cog.outl(" {") + for n in range(1, int(Handlers) + 1): + cog.out(" case T%d::ID:" % n) + cog.out(" new_state_id = static_cast<TDerived*>(this)->on_event(source, static_cast<const T%d&>(message));" % n) + cog.outl(" break;") + cog.out(" default:") + cog.out(" new_state_id = static_cast<TDerived*>(this)->on_event_unknown(source, message);") + cog.outl(" break;") + cog.outl(" }") + cog.outl("") + cog.outl(" return new_state_id;") + cog.outl(" }") + cog.outl("};") + + #################################### + # All of the other specialisations. + #################################### + for n in range(int(Handlers) - 1, 0, -1): + cog.outl("") + cog.outl("//***************************************************************************") + if n == 1: + cog.outl("// Specialisation for %d message type." % n) + else: + cog.outl("// Specialisation for %d message types." % n) + cog.outl("//***************************************************************************") + cog.outl("template <typename TContext, typename TDerived, const etl::fsm_state_id_t STATE_ID_, ") + cog.out(" ") + for t in range(1, n): + cog.out("typename T%d, " % t) + if t % 4 == 0: + cog.outl("") + cog.out(" ") + cog.outl("typename T%d>" % n) + cog.out("class fsm_state<TContext, TDerived, STATE_ID_, ") + for t in range(1, n + 1): + cog.out("T%d, " % t) + if t % 16 == 0: + cog.outl("") + cog.out(" ") + for t in range(n + 1, int(Handlers)): + cog.out("void, ") + if t % 16 == 0: + cog.outl("") + cog.out(" ") + cog.outl("void> : public ifsm_state") + cog.outl("{") + cog.outl("public:") + cog.outl("") + cog.outl(" enum") + cog.outl(" {") + cog.outl(" STATE_ID = STATE_ID_") + cog.outl(" };") + cog.outl("") + cog.outl(" fsm_state()") + cog.outl(" : ifsm_state(STATE_ID)") + cog.outl(" {") + cog.outl(" }") + cog.outl("") + cog.outl("protected:") + cog.outl("") + cog.outl(" inline TContext& get_fsm_context() const") + cog.outl(" {") + cog.outl(" return static_cast<TContext&>(ifsm_state::get_fsm_context());") + cog.outl(" }") + cog.outl("") + cog.outl("private:") + cog.outl("") + cog.outl(" etl::fsm_state_id_t process_event(etl::imessage_router& source, const etl::imessage& message)") + cog.outl(" {") + cog.outl(" etl::fsm_state_id_t new_state_id;") + cog.outl(" etl::message_id_t event_id = message.message_id;") + cog.outl("") + cog.outl(" switch (event_id)") + cog.outl(" {") + for n in range(1, n + 1): + cog.out(" case T%d::ID:" % n) + cog.out(" new_state_id = static_cast<TDerived*>(this)->on_event(source, static_cast<const T%d&>(message));" % n) + cog.outl(" break;") + cog.out(" default:") + cog.out(" new_state_id = static_cast<TDerived*>(this)->on_event_unknown(source, message);") + cog.outl(" break;") + cog.outl(" }") + cog.outl("") + cog.outl(" return new_state_id;") + cog.outl(" }") + cog.outl("};") + #################################### + # Specialisation for zero messages. + #################################### + cog.outl("") + cog.outl("//***************************************************************************") + cog.outl("// Specialisation for 0 message types.") + cog.outl("//***************************************************************************") + cog.outl("template <typename TContext, typename TDerived, const etl::fsm_state_id_t STATE_ID_>") + cog.out("class fsm_state<TContext, TDerived, STATE_ID_, ") + for t in range(1, int(Handlers)): + cog.out("void, ") + if t % 16 == 0: + cog.outl("") + cog.out(" ") + cog.outl("void> : public ifsm_state") + cog.outl("{") + cog.outl("public:") + cog.outl("") + cog.outl(" enum") + cog.outl(" {") + cog.outl(" STATE_ID = STATE_ID_") + cog.outl(" };") + cog.outl("") + cog.outl(" fsm_state()") + cog.outl(" : ifsm_state(STATE_ID)") + cog.outl(" {") + cog.outl(" }") + cog.outl("") + cog.outl(" inline TContext& get_fsm_context() const") + cog.outl(" {") + cog.outl(" return static_cast<TContext&>(ifsm_state::get_fsm_context());") + cog.outl(" }") + cog.outl("private:") + cog.outl("") + cog.outl(" etl::fsm_state_id_t process_event(etl::imessage_router& source, const etl::imessage& message)") + cog.outl(" {") + cog.outl(" return static_cast<TDerived*>(this)->on_event_unknown(source, message);") + cog.outl(" }") + cog.outl("};") + ]]]*/ + /*[[[end]]]*/ +} + +#undef ETL_FILE + +#ifdef ETL_COMPILER_MICROSOFT +#define max(a,b) (((a) > (b)) ? (a) : (b)) +#endif + +#endif +