State Chart XML is a w3c recommendation that aims to be a generic state-machine based execution environment. The core constructs part of it provides a standard way to describe state charts. State machine cat can output core constructs SCXML and read them as well. SCXML looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<scxml xmlns="http://www.w3.org/2005/07/scxml" initial="meow" version="1.0">
<state id="eat">
<transition event="belly full" target="sleep"/>
</state>
<state id="sleep">
<transition event="wake up" target="meow"/>
</state>
<state id="meow">
<transition event="no response from human" target="meow"/>
<transition event="human gives food" target="eat"/>
<transition event="human gives toy" target="play"/>
</state>
<state id="play">
<transition event="tired or bored" target="sleep"/>
</state>
</scxml>
Both the command line and the online interpreter support scxml output.
- Command line:
smcat --output-type scxml mycoolchart.smcat
- Online interpreter: pick SCXML from the hamburger menu.
On the command line tell that you're trying to parse scxml by passing it
as an input type: smcat --input-type scxml mycoolcart.scxml
. In the online
interpreter you can pick SCXML from the hamburger menu under Input type.
The atom plugin kicks in when the extension is scxml - so it will just work
when you hit the State Machine cat Preview Toggle Preview action.
All core constructs, except transitions without a target, which are not a concept in state machine cat's language. For these states it will create a self transition, which is as close to the SCXML concept as it can get.
Also, state machine cat's primary goal is to visualize state machines. With that in mind it will focus on the core constructs and not on other parts of the SCXML w3c recommendation (like executable content, data model, external communications).
I've made some choices how to convert state machine cat/ UML constructs to SCXML that seemed logical to me. Which means they might surprise you.
In UML initial pseudo state is not the real initial state. The initial state is the one the pseudo state points to.
SCXML lives by this realization as well. It does support the
initial pseudo state, but it also has an initial
attribute in both
the top (scxml node) level and at the state level. state machine cat
usually transforms initial states into those attributes. E.g. the
above state machine would look like this:
<?xml version="1.0" encoding="UTF-8"?>
<scxml xmlns="http://www.w3.org/2005/07/scxml" initial="off" version="1.0">
<state id="off">
<transition event="tap" target="on"/>
</state>
<state id="on">
<transition event="tap" target="off"/>
</state>
</scxml>
The only reason to still have an <initial>
pseudo state in an
scxml document is to express that when you go into the real initial
state for the very first time, you want an action to happen.
Hence, if state machine cat sees an action on a transition from an initial pseudo state to the real initial state, it'll put an initial pseudo state in the output.
initial => off: /regurgitate the brimstone;
<scxml xmlns="http://www.w3.org/2005/07/scxml" initial="initial" version="1.0">
<initial id="initial">
<transition target="off" action="regurgitate the brimstone">
</initial>
<state id="off">
</state>
</scxml>
There can be only one initial state per state machine, and each initial pseudo state can only have one transition out. This means there's at most one initial state on scxml level, and at most one initial state for each composite state.
State machine cat (currently) does not enforce this rule, so it has to choose. If there's more than one initial state, or more than one transition out of an initial state it picks the first one it encounters.
smcat recognizes the entry/ and exit/ keywords and treats everything after it on the same line to be the 'body' of the trigger.
So this
landing:
entry/ cabin crew take your seat
entry/ cabin crew arm the slides
exit/ cabin crew disarm the slides
;
translates into
<?xml version="1.0" encoding="UTF-8"?>
<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0">
<state id="landing">
<onentry>cabin crew take your seat</onentry>
<onentry>cabin crew arm the slides</onentry>
<onexit>cabin crew disarm the slides</onexit>
</state>
</scxml>
At the moment state machine cat makes these into full fledged SCXML states. This is not incorrect, but de-sugaring might be a better approach. I've kept that as a future feature for now.
If you use the UML way of writing events on transitions, like so:
a => b: event [condition] / action
state machine cat extracts the correct events and emits them into SCXML
<state id="a">
<transition target="b" event="event" cond="condition">
action
</transition>
</state>
State id's in SCXML have constraints the smcat language does not have. state-machine-cat transforms state name to conform to these constraints. It uses these rules:
- If the state name is a valid XML id already it leaves it as is.
- Replace all invalid state name characters with
_
's - If the state name starts with a character that (1) is valid in
an XML id, but (2) not as a start character it puts a
_
in front of it. - If the state has no name or an empty name: use
empty
as a name.
state name | valid XML ID |
---|---|
On |
On |
"media player on" |
media_player_on |
"8 ball shaking" |
_8_ball_shaking |
"" |
empty |
One of the consequences of this transformation algorithm is that in edge cases it's possible to get unintended name clashes. E.g. when you have a "yes|no" and a "yes no" state, they'll both map to "yes_no".
Especially with sparsely named pseudo states this might spell trouble;
a choice state named ^
and a fork state named ]
will both map to
_
. The obvious way to prevent this is to name the choices/ forks/ joins.
It'll make your smcat source more readable in the process:
^cool? -> "tell @sverweij": no;
^cool? -> "star state-machine-cat on github": yes;
In SCXML event descriptors follow some syntactic rules that don't exist in smcat:
- spaces separate multiple events
- dots play a role in event selection
- only a subset of utf-8 can be used as the first character of an event descriptor; a slightly different set can be used for other data types. (see EventType.datatype in scxml-datatypes xsd).
Without transformation this would a.o. mean that the event in
meow -> eat: human gives food;
would in scxml be interpreted as
three events; human
, gives
and food
.
- state-machine-cat treats new lines as event separators.
- For each of the events that remain after separation:
- If the event name is a valid id already it leaves it as is.
- It replaces all 'invalid' event characters with
_
's - If the event starts with a character that (1) is valid in
an XML id, but (2) not as a start character it puts a
_
in front of it. - If the event has no or an empty name: use
empty
as a as event descriptor.
meow -> eat:
human gives food
human leaves food on the table;
<?xml version="1.0" encoding="UTF-8"?>
<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0">
<state id="meow">
<transition event="human_gives_food human_leaves_food_on_the_table" target="eat"/>
</state>
<state id="eat">
</state>
</scxml>
Translating SCXML into state-machine-cat's schema is
more straightforward than writing to it as SCXML is generally more restricting.
The only exception is that SCXML allows transitions
to have no target. We
handle these as if they were 'self-transitions'.
At this moment state-machine-cat does not support referencing state machines with XInclude.
In SCXML a state can have invoke
s. These are not core constructs, but they're
used in practice and can be fairly straightforward to represent in a state chart.
State-machine-cat currently shows them in the same section as it shows actions. For example ...
<?xml version="1.0" encoding="UTF-8"?>
<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0">
<state id="doing">
<invoke id="doSomething()"/>
<invoke id="doSomethingElse()"/>
<invoke>does an invoke with a body make sense?</invoke>
<invoke/> <!-- likewise, does an empty invoke make sense? -->
</state>
</scxml>
... will show up as ...
- The state part of the SCXML specification, which points to
- the ID paragraph in the xmlschema specification, which points to
- the ID attribute validity constraint in the xml spec, which points to
- the Name production rule in that same spec.
- The event descriptors section of the SCXML specification and the
EventType.datatype
in the scxml-datatypes xsd.
The SCXML specification seemed useful. The only way (for me) to grasp a specification is to use it. Writing an SCXML renderer for state machine cat fit the bill.
This has had two side effects:
- State machine cat models are now a little more interoperable with the rest of the world.
- I realized state machine cat could be expanded with some
useful concepts:
- history states (implemented in v2.3.0, deep history in v2.6.0)
- initial and final states within composite states (implemented in v2.3.0)
- separate
event [condition]/ action
in the internal models (implemented in v2.3.0) - the
scjson
format - which is conceptually equal to scxml but in javascript is lot easier to read from and translate into (implemented in v2.3.0) - onentry, and onexit events on states (implemented in v2.4.0)
- parallel states (implemented in v2.5.0)
- model rules (e.g. one initial per state machine, only actions on initial pseudo to the real initial, ... - future feature)
- de-sugaring pseudo states into core constructs (!experimental! feature as of v5.2.0)