A Python3 tool parsing PlantUML statecharts scripts and generating C++11 code with its unit tests.
This repository contains:
The Python script offers you:
Here is an example of PlantUML code source, generating this kind of diagram, this tool is able to parse (Click on the figure to see the PlantUML code source):
This repository also contains several more evolved examples of statecharts the tool can parse. For people not sure how state machines work, there are several links explaining them given in the last section of this document.
do / activity
and after(X ms)
are not yet managed.on event
in the
same state.CountQuarter
. In the same idea: events on output transitions shall
be mutually exclusive but the tool cannot parse C++ logic. And finally for
unit tests, to help generate good valuespython3 -m pip install networkx lark
./statecharts.py <plantuml statechart file> <langage> [name]
Where:
plantuml statechart file
is the path of the PlantUML
statecharts file as input. This repo
contains examples.langage
is either "cpp"
to force create a C++ source file or "hpp"
to
force create a C++ header file.name
is optional and allows giving prefix to the C++ class name and file.Example:
./statecharts.py foo.plantuml cpp controller
Will create a FooController.cpp
file with a class name FooController
.
cd examples
make -j8
Examples are compiled into the build
folder as well as their PNG file.
You can run binaries. For example:
./build/Gumball
This tool does not pretend to parse the whole PlantUML syntax or implement the whols UML statecharts standard. Here is the basic PlantUML statecharts syntax it can understand:
FromState --> ToState : event [ guard ] / action
FromState -> ToState : event [ guard ] / action
ToState <-- FromState : event [ guard ] / action
ToState <- FromState : event [ guard ] / action
State : entry / action
State : exit / action
State : on event [ guard ] / action
Where [ guard ]
is optional.
'
for single-line comment.
The statecharts shall have one [*]
as a source.
Optionally [*]
as a sink.
Note: [ guard ]
and / action
are optional. You can add C++ code (the less
the better, you can complete with '[code]
as depicted in this section). The
tool shall manage spaces between tokens -->
, :
, []
, and /
. The event
is optional it can be spaced but shall refer to a valid C++ syntax of a
function (so do not add logic operations).
Note: I added some sugar syntax:
State : entering / action
alias for State : entry / action
.State : leaving / action
alias for State : exit / action
.State : comment / description
to add a C++ comment for the state in the
generated code.\n--\n action
alias for / action
to follow State-Transition Diagrams used
in Structured Analysis for Real
Time
(but also to force carriage return on PlantUML diagrams).I added some syntax to help generate extra C++ code. They start with the '
keyword which is a PlantUML single-line comment so they will not produce syntax
error when PlantUML is parsing the file but, on our side, we exploit them.
'[brief]
for adding a comment for the generated state machine class.'[header]
for adding code in the header of the file, before the class of the
state machine. You can include other C++ files, and create or define functions.'[footer]
for adding code in the footer of the file, after the class of the
state machine.'[param]
are arguments to pass to the state machine C++ constructor. Commas
are added. One argument by line.'[cons]
to allow init the argument before the code of the constructor.
One argument by line.'[init]
is C++ code called by the constructor or bu the reset()
function.'[code]
to allow you to add member variables or member functions.'[test]
to allow you to add C++ code for unit tests.In the beginning, I did not understand the differences between the State/Transition diagram (STD) from the Structured Analysis for Real-Time methodology with the UML statechart. In STD actions are made by transitions, while in UML actions are made by transitions or by states. I was confused.
What I understood after: in 1956 there were two kinds of state machines: Moore in where actions were called from states and Mealy where actions were called from transitions. They describe exactly the same system and you can translate a Moore machine into a Mealy machine and vice versa, without losing any expressiveness cite. In 1984, Harel mixed the two syntaxes plus added some features (composite ...) and named it statecharts. Finally UML integrated statecharts in their standard.
Some tools like the one explained in this document simplify the statecharts graph to get a Mealy graph before generating the code. In the case of our translator, to keep the code simple to read, the state machine is not simplified and actions are made by states and by transitions.
Another point of confusion hat is the difference between action and activity.
The action is instantaneous: it does not consume time (contrary to the
activity). The activity can be seen as a thread that is preempted by any
external events the state reacts to. The thread is halted when the activity is
done or when the system switches state. Therefore, an activity shall not be
seen by a periodic external update
event since its code does not necessarily be
repeated.
My last point of confusion concerned the order of execution of actions in transitions and in states. I explain it in the next section.
Let's suppose the C++ code of the following state machine has been generated with
the C++ name class Simple
.
Simple
is entering to State1
(made active): the action7
is
called (private method of the class Simple
or any local function).event3
(public method of the class Simple
or any local
function) may occur and when this will happen, and if and only if the guard3
returns true
(boolean expression of a function returning a boolean), then the
action3
is called (method of the class Simple
or any local function).event1
is triggered and if the guard1
returns true
then the system is
leaving the State1
and the exit action8
is called followed by the
transition action1
.State2
(made active): the action9
is called.event5
may be triggered and once happening the action5
is called.event2
is triggered then the State2
exit action10
is called. Else if
event6
is triggered then the State2
exit action10
is called.event3
or event5
are triggered, the entry and exit actions of
the corresponding state is not called.If an output transition has no explicit event and no guard is given (or if the guard
is returning true) and the activity has finished then the transition is
immediately made in an atomic way. In our example, if event1
, event2
, and
guard1
were not present this would create an infinite loop.
Events shall be mutually exclusive since we are dealing in discrete time events, several events can occur during the delta time but since in this API you have to call the event and the state machine reacts immediately, the order is defined by the caller.
The translation pipeline of the Python script is the following:
How is the generated code? The state machine, like any graph structure (nodes are states and edges are transitions) can be depicted by a matrix.
For example concerning the motor controller:
can be depicted in the following table (guards and actions are not shown). In practice the table is usually sparse:
Set Speed | Halt | -- | |
---|---|---|---|
IDLE | STARTING | ||
STOPPING | IDLE | ||
STARTING | SPINNING | STOPPING | |
SPINNING | SPINNING | STOPPING |
Our implementation is the following:
std::map
for the sparse side) maps transitions (source states to destination states)
shall be defined. This table also holds pointers to private methods for the
guards and for actions. This table is used by a general private method doing
all statecharts logic to follow the UML norm.