Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions atos/modules/ObjectControl/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,13 @@ install(TARGETS ${OBJECT_CONTROL_TARGET}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)

add_executable(dump_state_machine
${CMAKE_CURRENT_SOURCE_DIR}/src/dump_state_machine.cpp
)
target_link_libraries(dump_state_machine
${ATOS_COMMON_LIBRARY}
)
target_include_directories(dump_state_machine PUBLIC SYSTEM
${CMAKE_CURRENT_SOURCE_DIR}/inc
)
2 changes: 2 additions & 0 deletions atos/modules/ObjectControl/inc/statemachine.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
#include <cstdint>
#include <type_traits>
#include "roschannels/objstatechangechannel.hpp"
#include "roschannels/commandchannels.hpp"
#include "roschannels/statechange.hpp"
#include "util.h"

class ObjectControl;

Expand Down
368 changes: 368 additions & 0 deletions atos/modules/ObjectControl/src/dump_state_machine.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,368 @@
//
// Copyright (c) 2016-2020 Kris Jusiak (kris at jusiak dot net)
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
//

#include "statemachine.hpp"

namespace sml = boost::sml;

inline void do_indent(std::ostream& out, unsigned int indent) { out << std::string(indent, ' '); }

// use this to track initialization for orthogonal states
bool state_initialized = false; // NOLINT(misc-definitions-in-headers)
// use this to track which submachines have already been dumped so they don't get dumped twice
std::vector<std::string> completed_submachines; // NOLINT(misc-definitions-in-headers)

/** allows for checking if the type is sml::front::seq_
* This type is used by sml when there are lists of actions.
*/
template <class... Ts>
struct is_seq_ : sml::aux::false_type {}; // NOLINT(readability-identifier-naming)
template <class... Ts>
struct is_seq_<sml::front::seq_<Ts...>> : sml::aux::true_type {}; // NOLINT(readability-identifier-naming)

/** allows for checking if the type is sml::front::not_
* This type is used by sml inside of guards, when the guard value is negated with !
*
* The partial specialization matches if the type passed in is sml::front::not_, causing the struct to
* inherit from sml::aux::true_type, which gives it a member called "value" that is set to true.
* If the type passed doesn't match sml::front::not_, it'll match the generic is_not_ which inherits
* from sml::aux::false_type, giving it a member called "value" that is set to false.
*/
template <class... Ts>
struct is_not_ : sml::aux::false_type {}; // NOLINT(readability-identifier-naming)
template <class... Ts>
struct is_not_<sml::front::not_<Ts...>> : sml::aux::true_type {}; // NOLINT(readability-identifier-naming)

/** provides access to the template parameter type of an sml::front::not_<T>
*/
template <class T>
struct strip_not_ {
using type = T;
}; // NOLINT(readability-identifier-naming)
template <class T>
struct strip_not_<sml::front::not_<T>> {
using type = T;
}; // NOLINT(readability-identifier-naming)

/** allows for checking if the type is sml::front::and_
* This type is used by sml inside of guards when two guard functions are combined with &&
*/
template <class... Ts>
struct is_and_ : sml::aux::false_type {}; // NOLINT(readability-identifier-naming)
template <class... Ts>
struct is_and_<sml::front::and_<Ts...>> : sml::aux::true_type {}; // NOLINT(readability-identifier-naming)

/** allows for checking if the type is sml::front::or_
* This type is used by sml inside of guards when two guard functions are combined with ||
*/
template <class... Ts>
struct is_or_ : sml::aux::false_type {}; // NOLINT(readability-identifier-naming)
template <class... Ts>
struct is_or_<sml::front::or_<Ts...>> : sml::aux::true_type {}; // NOLINT(readability-identifier-naming)

/** uses std::tuple_element and std::tuple to access the Nth type in a parameter pack
*/
template <int N, class... Ts>
using NthTypeOf = typename std::tuple_element<N, std::tuple<Ts...>>::type;

/** gets the size of a parameter pack
* this isn't really necessary, sizeof...(Ts) can be used directly instead
*/
template <class... Ts>
struct count { // NOLINT(readability-identifier-naming)
static const std::size_t value = sizeof...(Ts); // NOLINT(readability-identifier-naming)
};

/** allows for checking if the type is sml::aux::zero_wrapper
* sml puts this around types inside of guards and event sequences
*/
template <class T>
struct is_zero_wrapper : sml::aux::false_type {}; // NOLINT(readability-identifier-naming)
template <class T>
struct is_zero_wrapper<sml::aux::zero_wrapper<T>> : sml::aux::true_type {}; // NOLINT(readability-identifier-naming)

/** if T is a zero wrapper, ::type will be the inner type. if not, it will be T.
*/
template <class T>
struct strip_zero_wrapper {
using type = T;
}; // NOLINT(readability-identifier-naming)
template <class T>
struct strip_zero_wrapper<sml::aux::zero_wrapper<T>> {
using type = T;
}; // NOLINT(readability-identifier-naming)

/** accesses the type of a state-machine, sml::back::sm
*/
template <class T>
struct submachine_type {
using type = T;
}; // NOLINT(readability-identifier-naming)
template <class T>
struct submachine_type<sml::back::sm<T>> {
using type = typename T::sm;
}; // NOLINT(readability-identifier-naming)

/** print the types inside a sml::front::seq_
* These types came from a list of actions.
*/
template <class... Ts>
struct print_seq_types { // NOLINT(readability-identifier-naming)
template <int I>
static void func(std::ostream& out) {
constexpr auto param_pack_empty = (sizeof...(Ts) == I);
if constexpr (!param_pack_empty) { // NOLINT(readability-braces-around-statements,bugprone-suspicious-semicolon)
using current_type = NthTypeOf<I, Ts...>;
if constexpr (is_seq_<typename current_type::type>::value) { // NOLINT(readability-braces-around-statements)
// handle nested seq_ types, these happen when there are 3 or more actions
print_seq_types<typename current_type::type>::template func<0>(out);
} else { // NOLINT(readability-misleading-indentation)
// print this param directly
out << sml::aux::string<typename strip_zero_wrapper<current_type>::type>{}.c_str();
}
if constexpr (I + 1 < sizeof...(Ts)) { // NOLINT(readability-braces-around-statements,bugprone-suspicious-semicolon)
out << ",\\n ";
}
print_seq_types<Ts...>::template func<I + 1>(out);
}
}
};
template <class... Ts>
struct print_seq_types<sml::front::seq_<Ts...>> { // NOLINT(readability-identifier-naming)
template <int I>
static void func(std::ostream& out) {
print_seq_types<Ts...>::template func<0>(out);
}
};

/** print the types inside a guard
* These can be a functor, an sml::front::not_, an sml::front::and_, or an sml::front::or_ which makes
* this one more complicated. They also involve the zero_wrapper.
* The various partial specializations handle all of the possible types.
*/
template <class... Ts>
struct print_guard { // NOLINT(readability-identifier-naming)
template <int I>
static void func(std::ostream& out, const std::string& sep = "") {
constexpr auto param_pack_empty = (sizeof...(Ts) == I);
if constexpr (!param_pack_empty) { // NOLINT(readability-braces-around-statements,bugprone-suspicious-semicolon)
using current_type = NthTypeOf<I, Ts...>;
if constexpr (is_zero_wrapper<
current_type>::value) { // NOLINT(readability-braces-around-statements,bugprone-suspicious-semicolon)
// unwrap the zero_wrapper and put it back into the recursion, it could be anything
print_guard<typename strip_zero_wrapper<current_type>::type>::template func<0>(out);
} else { // NOLINT(readability-misleading-indentation)
// it's just a functor, print it
out << sml::aux::string<current_type>{}.c_str();
}

// if we're not at the end, print the separator
if constexpr (I + 1 < sizeof...(Ts)) { // NOLINT(readability-braces-around-statements,bugprone-suspicious-semicolon)
if (!sep.empty()) {
out << sep;
}
}

// keep the recursion going, call for the next type in the parameter pack
print_guard<Ts...>::template func<I + 1>(out, sep);
}
}
};
template <class T>
struct print_guard<sml::front::not_<T>> { // NOLINT(readability-identifier-naming)
template <int I>
static void func(std::ostream& out, const std::string& /*sep*/ = "") {
out << "!" << sml::aux::string<typename strip_zero_wrapper<T>::type>{}.c_str();
}
};
template <class... Ts>
struct print_guard<sml::front::and_<Ts...>> { // NOLINT(readability-identifier-naming)
template <int I>
static void func(std::ostream& out, const std::string& /*sep*/ = "") {
constexpr auto param_pack_empty = (sizeof...(Ts) == I);
if constexpr (!param_pack_empty) {
print_guard<Ts...>::template func<I>(out, " &&\\n ");
}
}
};
template <class... Ts>
struct print_guard<sml::front::or_<Ts...>> { // NOLINT(readability-identifier-naming)
template <int I>
static void func(std::ostream& out, const std::string& /*sep*/ = "") {
constexpr auto param_pack_empty = (sizeof...(Ts) == I);
if constexpr (!param_pack_empty) {
print_guard<Ts...>::template func<I>(out, " ||\\n ");
}
}
};

// forward declaration of dump_transitions
template <typename...>
struct dump_transitions;

template <int N, class T>
void dump_transition(std::ostream& out) noexcept {
constexpr auto src_is_sub_sm =
!sml::aux::is_same<sml::aux::type_list<>, sml::back::get_sub_sms<typename T::src_state>>::value;
constexpr auto dst_is_sub_sm =
!sml::aux::is_same<sml::aux::type_list<>, sml::back::get_sub_sms<typename T::dst_state>>::value;

std::string src_state, dst_state;
// NOLINTNEXTLINE(readability-braces-around-statements)
if constexpr (src_is_sub_sm) {
src_state = std::string{sml::aux::string<typename submachine_type<typename T::src_state>::type>{}.c_str()};
} else { // NOLINT(readability-misleading-indentation)
src_state = std::string{sml::aux::string<typename T::src_state>{}.c_str()};
}

// NOLINTNEXTLINE(readability-braces-around-statements)
if constexpr (dst_is_sub_sm) {
dst_state = std::string{sml::aux::string<typename submachine_type<typename T::dst_state>::type>{}.c_str()};
} else { // NOLINT(readability-misleading-indentation)
dst_state = std::string{sml::aux::string<typename T::dst_state>{}.c_str()};
}

const auto dst_internal = sml::aux::is_same<typename T::dst_state, sml::front::internal>::value;

const auto has_event = !sml::aux::is_same<typename T::event, sml::anonymous>::value;
const auto has_guard = !sml::aux::is_same<typename T::guard, sml::front::always>::value;
const auto has_action = !sml::aux::is_same<typename T::action, sml::front::none>::value;

if (has_event && has_action && sml::aux::is_same<typename T::action::type, sml::front::actions::defer>::value) {
do_indent(out, N);
out << src_state << " : " << boost::sml::aux::get_type_name<typename T::event>() << " / defer\n";
return;
}

if (dst_state == "terminate") {
dst_state = "[*]";
}

if (T::initial) {
if (state_initialized) { // create an orthogonal section
do_indent(out, N);
out << "--\n";
}

state_initialized = true;
do_indent(out, N);
out << "[*] --> " << src_state << "\n";
}

// NOLINTNEXTLINE(readability-braces-around-statements, bugprone-suspicious-semicolon)
if constexpr (src_is_sub_sm) {
auto already_in =
std::find(completed_submachines.begin(), completed_submachines.end(), src_state) != completed_submachines.end();
if (!already_in) {
completed_submachines.push_back(src_state);
constexpr int indent = N + 2;
do_indent(out, N);
out << "state " << src_state << " {\n";
bool prev_state = state_initialized;
state_initialized = false;
dump_transitions<typename T::src_state::transitions>::template func<indent>(out);
do_indent(out, N);
out << "}\n";
state_initialized = prev_state;
}
}

do_indent(out, N);
out << src_state;
if (!dst_internal) {
out << " --> " << dst_state;
}

if (has_event || has_guard || has_action) {
out << " :";
}

if (has_event) {
out << " " << std::string{sml::aux::string<typename T::event>{}.c_str()};
}

if (has_guard) {
out << "\\n [";
print_guard<typename T::guard::type>::template func<0>(out);
out << "]";
}

if (has_action) {
out << " /\\n ";

if constexpr (is_seq_<typename T::action::type>::value) { // NOLINT(readability-braces-around-statements)
out << "(";
print_seq_types<typename T::action::type>::template func<0>(out);
out << ")";
} else { // NOLINT(readability-misleading-indentation)
out << sml::aux::string<typename T::action::type>{}.c_str();
}
}

out << "\n";

// NOLINTNEXTLINE(readability-braces-around-statements, bugprone-suspicious-semicolon)
if constexpr (dst_is_sub_sm) {
auto already_in =
std::find(completed_submachines.begin(), completed_submachines.end(), dst_state) != completed_submachines.end();
if (!already_in) {
completed_submachines.push_back(dst_state);
constexpr int indent = N + 2;
do_indent(out, N);
out << "state " << dst_state << " {\n";
bool prev_state = state_initialized;
state_initialized = false;
dump_transitions<typename T::dst_state::transitions>::template func<indent>(out);
do_indent(out, N);
out << "}\n";
state_initialized = prev_state;
}
}
}

// this template allows iterating through the types in the parameter pack Ts...
// I is the counter
// INDENT is the current indentation level (for the state machine or sub-state machine)
template <int INDENT, int I, class... Ts>
void apply_dump_transition(std::ostream& out) {
// iteration is finished when I == the size of the parameter pack
constexpr auto param_pack_empty = (sizeof...(Ts) == I);
if constexpr (!param_pack_empty) { // NOLINT(readability-braces-around-statements,bugprone-suspicious-semicolon)
// run the dump_transition function to print this sml::front::transition type
dump_transition<INDENT, NthTypeOf<I, Ts...>>(out);
// iteration isn't finished, keep going
apply_dump_transition<INDENT, I + 1, Ts...>(out);
}
}

// SFINAE type
template <typename...>
struct dump_transitions { // NOLINT(readability-identifier-naming)
template <int INDENT>
static void func(std::ostream&) {}
};

// Partial specialization for sml::aux::type_list<Ts...>. This grants access to the
// types inside the type list, which are sml::front::transition types, so they can
// be passed to apply_dump_transition.
template <typename... Ts>
struct dump_transitions<typename sml::aux::type_list<Ts...>> { // NOLINT(readability-identifier-naming)
template <int INDENT>
static void func(std::ostream& out) {
apply_dump_transition<INDENT, 0, Ts...>(out);
}
};

template <class T>
void dump(std::ostream& out) noexcept {
out << "@startuml\n\n";
dump_transitions<typename sml::sm<T>::transitions>::template func<0>(out);
out << "\n@enduml\n";
}

int main() { dump<state_machine::StateMachine>(std::cout); }
Loading