[[puml-front-end]]

= PUML front-end (C++20, experimental)

[[plantuml-basics]]

== PlantUML basics

https://plantuml.com/[PlantUML] is a modelling tool with a nice
extension for state machine diagrams. The result can then be viewed, for
example VSCode has add-ons for previewing the result.

Our usual player example could look like this in PlantUML syntax:

[source,cpp]
----
@startuml Player
            skinparam linetype polyline
            state Player{
                [*]-> Empty
                Stopped     -> Playing   : play 
                Stopped     -> Open      : open_close    / open_drawer
                Stopped     -> Stopped   : stop

                Open        -> Empty     : open_close    / close_drawer               [can_close_drawer]
                Empty       --> Open     : open_close    / open_drawer
                Empty       ---> Stopped : cd_detected   / store_cd_info              [good_disk_format && always_true]
                Playing     --> Stopped  : stop          / stop_playback
                Playing     -> Paused    : pause
                Playing     --> Open     : open_close    / stop_playback, open_drawer
                Paused      -> Playing   : end_pause     / resume_playback
                Paused      --> Stopped  : stop          / stop_playback
                Paused      --> Open     : open_close    / stop_playback, open_drawer

                Playing : flag CDLoaded
                Playing : entry start_playback [always_true]
                Paused  : entry pause_playback
                Paused  : flag CDLoaded
                Stopped : flag CDLoaded                
            }
 @enduml
----

A few key points of the syntax:

* Initial states are marked with [*] -> State
* Terminal states are marked with Terminal -> [*]
* Transitions follow the syntax: Source State -> Target State : event /
Action1,Action2 [guard conditions]
* Varying the number of "-" will make the layouter use longer arrows for
transitions
* "--" will make orthogonal regions clearer
* Supported guard conditions are guard names &&... ||... !... (), for
example !G1 && (G2 || G3)

We also want to add these non-standard PlantUML features:

* Flags. State Name : [keyword] flag flag-name. Add a flag per line.
* entry / exit actions. State name: [keyword] entry-or-exit
comma-separated-actions-sequence [guard conditions]
* An anonymous transition has an empty event name
* An any event is defined using the Kleene syntax "*" as the event name.
* a defer function is already provided for conditional event deferring
* an internal transition is implemented using an equal source and target
state and a "-" before the event name
+
[source,cpp]
----
Open        -> Open         : -play         / defer
----
* An internal Kleene would then be:
+
[source,cpp]
----
Empty       -> Empty        : -*            / defer               [is_play_event]
----

Before PUML one had to convert this syntax in the classical MSM
transition table, states with entry/exit/flags, events, etc. which takes
long and is error-prone.

Good news: This is no more necessary. Now we can copy-paste our PlantUML
and directly use it in the code, which gives us:

[source,cpp]
----
    // front-end: define the FSM structure 
    struct player_ : public msm::front::state_machine_def<player_>
    {
        // here is the whole FSM structure defined:
        // Initial states [*]
        // Transitions with actions starting with / and separated by ,
        // and guards between [ ]. Supported are !, &&, || and ()
        // State Entry / Exit with guards
        // Flags
        // -> can have different lengths for nicer PlantUML Viewer preview

        BOOST_MSM_PUML_DECLARE_TABLE(
            R"( 
            @startuml Player
            skinparam linetype polyline
            state Player{
                [*]-> Empty
                Stopped     -> Playing   : play 
                Stopped     -> Open      : open_close    / open_drawer
                Stopped     -> Stopped   : stop

                Open        -> Empty     : open_close    / close_drawer               [can_close_drawer]
                Empty       --> Open     : open_close    / open_drawer
                Empty       ---> Stopped : cd_detected   / store_cd_info              [good_disk_format && always_true]
                Playing     --> Stopped  : stop          / stop_playback
                Playing     -> Paused    : pause
                Playing     --> Open     : open_close    / stop_playback, open_drawer
                Paused      -> Playing   : end_pause     / resume_playback
                Paused      --> Stopped  : stop          / stop_playback
                Paused      --> Open     : open_close    / stop_playback, open_drawer

                Playing : flag CDLoaded
                Playing : entry start_playback [always_true]
                Paused  : entry pause_playback
                Paused  : flag CDLoaded
                Stopped : flag CDLoaded                
            }
            @enduml
        )"
        )

        // Replaces the default no-transition response.
        template <class FSM, class Event>
        void no_transition(Event const&, FSM&, int)
        {
        }
    };
    // Pick a back-end
    typedef msm::back11::state_machine<player_> player;
----

The PlantUML string is parsed at compile-time and generates a classical
Functor front-end.

States and event do not need to be defined any more, unless one needs to
provide them with attributes, or if the states are submachines. Actions
and Guards do need to be implemented to reduce bugs because of typos:

[source,cpp]
----
namespace boost::msm::front::puml {
    template<>
    struct Action<by_name("close_drawer")>
    {
        template <class EVT, class FSM, class SourceState, class TargetState>
        void operator()(EVT const&, FSM&, SourceState&, TargetState&)
        {
        }
    };
    template<>
    struct Guard<by_name("always_true")>
    {
        template <class EVT, class FSM, class SourceState, class TargetState>
        bool operator()(EVT const&, FSM&, SourceState&, TargetState&)
        {
            return true;
        }
    };    
    }
----

The events are also referenced by name:

[source,cpp]
----
p.process_event(Event<by_name("open_close")>{});                        
----

Please note that C++20 is required. xref:attachment$SimplePuml.cpp[A
complete implementation] of the player is provided.

[[composite-state-machines]]

== Composite State Machines

At the moment, the PUML front-end does not support submachines in a
single text string, so we need to split. First we define the submachine:

[source,cpp]
----
struct playing_ : public msm::front::state_machine_def<playing_>
{
        typedef boost::fusion::vector<PlayingPaused, CDLoaded>        flag_list;
        // optional
        template <class Event, class FSM>
        void on_entry(Event const&, FSM&) { }
        template <class Event, class FSM>
        void on_exit(Event const&, FSM&) { }

        BOOST_MSM_PUML_DECLARE_TABLE(
            R"( 
            @startuml Player
            skinparam linetype polyline
            state Player{
                [*]-> Song1
                Song1     -> Song2   : NextSong         
                Song2     -> Song1   : PreviousSong   / start_prev_song [start_prev_song_guard]
                Song2     -> Song3   : NextSong       / start_next_song
                Song3     -> Song2   : PreviousSong                     [start_prev_song_guard]
            }
            @enduml
        )"
        )

        // Replaces the default no-transition response.
        template <class FSM, class Event>
        void no_transition(Event const&, FSM&, int)
        {
        }
};                    
namespace boost::msm::front::puml {
    // define submachine with desired back-end
    template<>
    struct State<by_name("PlayingFsm")> : public msm::back11::state_machine<playing_>
    {
    };
}
                
----

We can reference the submachine within the upper state machine:

[source,cpp]
----
PlayingFsm  --> Stopped       : stop          / stop_playback
----

Again, xref:attachment$OrthogonalDeferredPuml.cpp[a complete
implementation] of the player is provided. Interesting are the
orthogonal regions delimited with "--", comments and the possibility to
declare terminate or interrupt state using the standard MSM states.
