// // 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) // #if __cplusplus >= 201703L #include #include #include #include #include #include #include #include #include namespace sml = boost::sml; struct e1 {}; struct e2 {}; struct e3 {}; struct e4 {}; struct e5 {}; struct guard { bool operator()() const { return true; } } guard; struct guard2 { bool operator()() const { return true; } } guard2; struct action { void operator()() {} } action; struct action2 { void operator()() {} } action2; void on_s1_entry_f() {} void on_s2_exit_f() {} struct on_s1_entry { auto operator()() { on_s1_entry_f(); } } on_s1_entry; struct on_s2_exit { auto operator()() { on_s2_exit_f(); } } on_s2_exit; struct sub_machine { auto operator()() const noexcept { using namespace sml; // clang-format off return make_transition_table( *"orth1"_s = X ,*"orth2"_s = X ); // clang-format on } }; struct plant_uml { auto operator()() const noexcept { using namespace sml; // clang-format off return make_transition_table( *"idle"_s + event = "s1"_s , "s1"_s + event [ !guard && guard2 ] / action = "s2"_s , "s1"_s + sml::on_entry<_> / on_s1_entry , "s2"_s + event [ guard || guard2 ] = "s1"_s , "s2"_s + sml::on_exit<_> / on_s2_exit , "s2"_s + event / action = state , state + event / (action, action2) = X ); // clang-format on } }; 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 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 struct is_seq_ : sml::aux::false_type {}; // NOLINT(readability-identifier-naming) template struct is_seq_> : 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 struct is_not_ : sml::aux::false_type {}; // NOLINT(readability-identifier-naming) template struct is_not_> : sml::aux::true_type {}; // NOLINT(readability-identifier-naming) /** provides access to the template parameter type of an sml::front::not_ */ template struct strip_not_ { using type = T; }; // NOLINT(readability-identifier-naming) template struct strip_not_> { 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 struct is_and_ : sml::aux::false_type {}; // NOLINT(readability-identifier-naming) template struct is_and_> : 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 struct is_or_ : sml::aux::false_type {}; // NOLINT(readability-identifier-naming) template struct is_or_> : 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 using NthTypeOf = typename std::tuple_element>::type; /** gets the size of a parameter pack * this isn't really necessary, sizeof...(Ts) can be used directly instead */ template 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 struct is_zero_wrapper : sml::aux::false_type {}; // NOLINT(readability-identifier-naming) template struct is_zero_wrapper> : 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 struct strip_zero_wrapper { using type = T; }; // NOLINT(readability-identifier-naming) template struct strip_zero_wrapper> { using type = T; }; // NOLINT(readability-identifier-naming) /** accesses the type of a state-machine, sml::back::sm */ template struct submachine_type { using type = T; }; // NOLINT(readability-identifier-naming) template struct submachine_type> { 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 struct print_seq_types { // NOLINT(readability-identifier-naming) template 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; if constexpr (is_seq_::value) { // NOLINT(readability-braces-around-statements) // handle nested seq_ types, these happen when there are 3 or more actions print_seq_types::template func<0>(out); } else { // NOLINT(readability-misleading-indentation) // print this param directly out << sml::aux::string::type>{}.c_str(); } if constexpr (I + 1 < sizeof...(Ts)) { // NOLINT(readability-braces-around-statements,bugprone-suspicious-semicolon) out << ",\\n "; } print_seq_types::template func(out); } } }; template struct print_seq_types> { // NOLINT(readability-identifier-naming) template static void func(std::ostream& out) { print_seq_types::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 struct print_guard { // NOLINT(readability-identifier-naming) template 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; 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::type>::template func<0>(out); } else { // NOLINT(readability-misleading-indentation) // it's just a functor, print it out << sml::aux::string{}.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::template func(out, sep); } } }; template struct print_guard> { // NOLINT(readability-identifier-naming) template static void func(std::ostream& out, const std::string& /*sep*/ = "") { out << "!" << sml::aux::string::type>{}.c_str(); } }; template struct print_guard> { // NOLINT(readability-identifier-naming) template 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::template func(out, " &&\\n "); } } }; template struct print_guard> { // NOLINT(readability-identifier-naming) template 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::template func(out, " ||\\n "); } } }; // forward declaration of dump_transitions template struct dump_transitions; template void dump_transition(std::ostream& out) noexcept { constexpr auto src_is_sub_sm = !sml::aux::is_same, sml::back::get_sub_sms>::value; constexpr auto dst_is_sub_sm = !sml::aux::is_same, sml::back::get_sub_sms>::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::type>{}.c_str()}; } else { // NOLINT(readability-misleading-indentation) src_state = std::string{sml::aux::string{}.c_str()}; } // NOLINTNEXTLINE(readability-braces-around-statements) if constexpr (dst_is_sub_sm) { dst_state = std::string{sml::aux::string::type>{}.c_str()}; } else { // NOLINT(readability-misleading-indentation) dst_state = std::string{sml::aux::string{}.c_str()}; } const auto dst_internal = sml::aux::is_same::value; const auto has_event = !sml::aux::is_same::value; const auto has_guard = !sml::aux::is_same::value; const auto has_action = !sml::aux::is_same::value; if (has_event && has_action && sml::aux::is_same::value) { do_indent(out, N); out << src_state << " : " << boost::sml::aux::get_type_name() << " / 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::template func(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{}.c_str()}; } if (has_guard) { out << "\\n ["; print_guard::template func<0>(out); out << "]"; } if (has_action) { out << " /\\n "; if constexpr (is_seq_::value) { // NOLINT(readability-braces-around-statements) out << "("; print_seq_types::template func<0>(out); out << ")"; } else { // NOLINT(readability-misleading-indentation) out << sml::aux::string{}.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::template func(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 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>(out); // iteration isn't finished, keep going apply_dump_transition(out); } } // SFINAE type template struct dump_transitions { // NOLINT(readability-identifier-naming) template static void func(std::ostream&) {} }; // Partial specialization for sml::aux::type_list. 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 struct dump_transitions> { // NOLINT(readability-identifier-naming) template static void func(std::ostream& out) { apply_dump_transition(out); } }; template void dump(std::ostream& out) noexcept { out << "@startuml\n\n"; dump_transitions::transitions>::template func<0>(out); out << "\n@enduml\n"; } int main() { dump(std::cout); } #elif __cplusplus == 201402L #include #include #include #include #include namespace sml = boost::sml; struct e1 {}; struct e2 {}; struct e3 {}; struct e4 {}; struct guard { bool operator()() const { return true; } } guard; struct action { void operator()() {} } action; void on_s1_entry() {} void on_s2_exit() {} struct plant_uml { auto operator()() const noexcept { using namespace sml; // clang-format off return make_transition_table( *"idle"_s + event = "s1"_s , "s1"_s + event [ guard ] / action = "s2"_s , "s1"_s + sml::on_entry<_> / [] { on_s1_entry(); } , "s2"_s + event [ guard ] = "s1"_s , "s2"_s + sml::on_exit<_> / [] { on_s2_exit(); } , "s2"_s + event / action = X ); // clang-format on } }; template void dump_transition(std::ostream& out) noexcept { auto src_state = std::string{sml::aux::string{}.c_str()}; auto dst_state = std::string{sml::aux::string{}.c_str()}; if (dst_state == "X") { dst_state = "[*]"; } if (T::initial) { out << "[*] --> " << src_state << "\n"; } const auto has_event = !sml::aux::is_same::value; const auto has_guard = !sml::aux::is_same::value; const auto has_action = !sml::aux::is_same::value; const auto is_entry = sml::aux::is_same>::value; const auto is_exit = sml::aux::is_same>::value; // entry / exit entry if (is_entry || is_exit) { out << src_state; } else { // state to state transition out << src_state << " --> " << dst_state; } if (has_event || has_guard || has_action) { out << " :"; } if (has_event) { // handle 'on_entry' and 'on_exit' per plant-uml syntax auto event = std::string(boost::sml::aux::get_type_name()); if (is_entry) { event = "entry"; } else if (is_exit) { event = "exit"; } out << " " << event; } if (has_guard) { out << " [" << boost::sml::aux::get_type_name() << "]"; } if (has_action) { out << " / " << boost::sml::aux::get_type_name(); } out << "\n"; } template