Skip to content

Commit

Permalink
Update documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexandreDecan committed Mar 14, 2018
1 parent 7a5b667 commit 5db0e69
Show file tree
Hide file tree
Showing 6 changed files with 87 additions and 70 deletions.
105 changes: 56 additions & 49 deletions docs/code.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,26 @@ Include code in statecharts
===========================

A statechart can specify code that needs to be executed under some circumstances.
For example, the *preamble* property on a statechart, the *guard* or *action* on a transition or the
*on entry* and *on exit* properties for a state may all contain code.
For example, the *preamble* of a statechart, the *guard* or *action* of a transition or the
*on entry* and *on exit* of a state may all contain code.

In Sismic, these pieces of code can be evaluated and executed by :py:class:`~sismic.code.Evaluator` instances.
By default, when an interpreter is created, a :py:class:`~sismic.code.PythonEvaluator` is created and allows
the interpreter to evaluate and execute Python code contained in a statechart.

.. _python_evaluator:
Alternatively, a :py:class:`~sismic.code.DummyEvaluator` that always evaluates conditions to ``True`` and
silently ignores actions can be used, but is clearly of less interest.

Built-in Python code evaluator
------------------------------
In the following, we will implicitly assume that the code evaluator is an instance of :py:class:`~sismic.code.PythonEvaluator`.

By default, Sismic provides two built-in :py:class:`~sismic.code.Evaluator` subclasses:

- A default :py:class:`~sismic.code.PythonEvaluator` that allows the statecharts to execute Python code directly.
- A :py:class:`~sismic.code.DummyEvaluator` that always evaluates to ``True`` and silently executes nothing when
it is called. Its context is an empty dictionary.
Context of the Python code evaluator
------------------------------------

The key point to understand how an evaluator works is the concept of ``context``, which is a dictionary-like structure that contains the data
that is exposed to the code fragments contained in the statechart (ie. override ``__locals__``).
When a code evaluator is created or provided to an interpreter, its ``context`` is exposed through the ``context``
attribute of the interpreter. The context of an evaluator is a mapping between variable names and their values.
When a piece of code contained in a statechart has to be evaluated or executed, the context of the evaluator is used to
populate the local and global variables that are available for this piece of code.

As an example, consider the following partial statechart definition.

Expand All @@ -36,22 +38,37 @@ As an example, consider the following partial statechart definition.
name: s1
on entry: x += 1
When the statechart is initialized, the ``context`` of the :py:class:`~sismic.code.PythonEvaluator` is ``{'x': 1, 'y': 0}``.
When *s1* is entered, the code will be evaluated with this context.
After the execution of ``x += 1``, the context associates ``2`` to ``x``.
When an interpreter is created for this statechart, its preamble is executed and the context of the code evaluator is
populated with ``{'x': 1, 'y': 0}``. When the statechart is further executed (initialized), and its root state
*s1* is entered, the code ``x += 1`` contained in the ``on entry`` field of *s1* is then executed in this context.
After execution, the context is ``{'x': 2, 'y': 0}``.

The default code evaluator has a global context that is always exposed when a piece of code has to be evaluated
or executed, and also defines local contexts for each state. Local contexts allow one to create variables whose
scope is limited to the state and its descendants. In other word, the context of a state is composed of its local
context and the ones of its ancestors. The context of a transition is the same than the one of its source state.

More precisely, every state and every transition has a specific evaluation context.
The code associated with a state is executed in a local context which is composed of local variables and every
variable that is defined in the context of the parent state. The context of a transition is built upon the context
of its source state.

.. note:: While you have full access to an ancestor's context, the converse is not true: every variable that
is defined in a context is NOT visible by any other context, except the ones that are nested.
Dealing with local contexts should not be an issue when writing code in the statechart, as it's very close to the
way scopes are handled by Python.
It could however look tricky when manipulating the ``context`` attribute of an interpreter or of a
:py:class:`~sismic.code.PythonEvaluator` instance directly.
For convenience, the context that is exposed by those objects prepend the name of the state to the name of the
variables that were defined in that state. For instance, if ``x`` has been defined independently in both *s1* and *s2*,
and assuming that *s1* is not a descendant nor an ancestor of *s2*, then the value of ``x`` in *s1* and *s2* can be
accessed respectively using ``interpreter.context['s1.x']`` and ``interpreter.context['s2.x']``.


When a :py:class:`~sismic.code.PythonEvaluator` instance is initialized, an initial context can be specified.
For convenience, the initial context can be directly provided to the constructor of
an :py:class:`~sismic.interpreter.Interpreter`.
For convenience, the initial context can be directly provided to the constructor of an :py:class:`~sismic.interpreter.Interpreter`.

It should be noticed that the initial context is set *before* executing the preamble of a statechart.
While this should be expected, it has the direct consequence that if a variable defined in the initial context is
also defined by the preamble, the latter will override its value, as illustrated by the following example:

.. testcode:: variable_override

Expand Down Expand Up @@ -81,17 +98,12 @@ an :py:class:`~sismic.interpreter.Interpreter`.

1

The initial context is evaluated *before* any code contained in the statechart.
As a consequence, this implies that if a same variable name is used both in the initial context and
in the YAML, the value set in the initial context will be overridden by the value set in the YAML definition.

In this example, the value of ``x`` in the statechart is set to ``1`` while the initial context sets its
value to ``2``. However, as the initial context is evaluated before the statechart, the value of
``x`` is ``1``.

This is a perfectly normal, expected behavior.
If you want to define variables in your statechart that can be overridden by an initial context, you should
check this variable does not exist in ``locals()``. For example, using
In this example, the value of ``x`` is eventually set to ``1``.
While the initial context provided to the interpreter defined the value of ``x`` to ``2``, the code contained in the
preamble overrode its value.
If you want to make use of the initial context to somehow *parametrize* the execution of the statechart, while still
providing *default* values for these parameters, you should check the existence of the variables before setting
their values. This can be done as follows:

.. testcode::

Expand All @@ -112,24 +124,27 @@ or equivalently,
See `this question on Stackoverflow <http://stackoverflow.com/questions/32894942/listcomp-unable-to-access-locals-defined-in-code-called-by-exec-if-nested-in-fun>`__ for more information.


Features of the built-in Python evaluator
-----------------------------------------
Predefined variables and functions
----------------------------------

Depending on the situation (state entered, guard evaluation, etc.), the context is populated with additional
entries. These entries are covered in the docstring of a :py:class:`~sismic.code.PythonEvaluator`:
When a piece of code is evaluated or executed, the default Python code evaluator enriches its local context with
several predefined variables and functions. These predefined objects depend on the situation triggering a code
evaluation or a code execution (entry or exit actions, guard evaluation, transition action, ...).

These entries are covered in the docstring of a :py:class:`~sismic.code.PythonEvaluator`:

.. autoclass:: sismic.code.PythonEvaluator
:noindex:

.. note:: The documentation below explains how an evaluator is organized and what does the default built-in Python evaluator.
Readers that are not interested in tuning existing evaluators or creating new ones can skip this part of the documentation.


Anatomy of a code evaluator
---------------------------

An :py:class:`~sismic.code.Evaluator` must provide two main methods and an attribute:
.. note:: The documentation below explains how an evaluator is organized and what does the default built-in Python evaluator.
Readers that are not interested in tuning existing evaluators or creating new ones can skip this part of the documentation.

An :py:class:`~sismic.code.Evaluator` subclass must at lest implement the following methods and attributes:

.. automethod:: sismic.code.Evaluator._evaluate_code
:noindex:
Expand All @@ -140,23 +155,15 @@ An :py:class:`~sismic.code.Evaluator` must provide two main methods and an attri
.. autoattribute:: sismic.code.Evaluator.context
:noindex:

None of the two methods is actually called by the interpreter during the execution of a statechart.
These methods are fallback methods, meaning they are implicitly called when one of the following methods is not defined in a concrete evaluator instance:

.. note::
None of those two methods are actually called by the interpreter during the execution of a statechart.
These methods are *fallback methods* that are used by other methods that are implicitly called depending on what is
currently being processed in the statechart. The documentation of :py:class:`~sismic.code.Evaluator` covers this:

.. autoclass:: sismic.code.Evaluator
:members:
:exclude-members: _evaluate_code, _execute_code, context
:member-order: bysource
:noindex:

In order to understand how the evaluator works, the documentation of the :py:class:`~sismic.code.Evaluator` mentions the following important statements:

- Methods :py:meth:`~sismic.code.Evaluator.execute_onentry` and :py:meth:`~sismic.code.Evaluator.execute_onexit`
are called respectively when a state is entered or exited, even if this state does not define a *on_entry* or
*on_exit* attribute.
- Method :py:meth:`~sismic.code.Evaluator.execute_action` is called when a transition is processed, even if
the transition does not define any *action*.

This allows the evaluator to keep track of the states that are entered or exited, and of the transitions that are
processed.

File renamed without changes.
30 changes: 18 additions & 12 deletions docs/advancedtopics/dealingtime.rst → docs/dealingtime.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,31 @@
Dealing with time
=================

It is quite usual in a statechart to rely on some notion of time.
To cope with this, the built-in evaluator (see :py:class:`~sismic.code.PythonEvaluator`) has support for
time events ``after(x)`` and ``idle(x)``, meaning that a transition can be triggered after a certain amount of time.
It is quite usual in statecharts to find notations such as "*after 30 seconds*", often expressed as specific events
on a transition. Sismic does not support the use of these *special events*, and proposes instead to deal with time
by making use of some specifics provided by its interpreter and the default Python code evaluator.

When it comes to interpreting statecharts, Sismic deals with time using an internal clock whose value is exposed
Every interpreter has an internal clock whose value is initially set to 0. This internal clock is exposed by
by the :py:attr:`~sismic.interpreter.Interpreter.time` property of an :py:class:`~sismic.interpreter.Interpreter`.
Basically, this clock does nothing by itself except for being available for an
:py:class:`~sismic.code.Evaluator` instance.
If your statechart needs to rely on a time value, you have to set it by yourself.
This property allows one to execute a statechart using simulated time. In other word, the value of this property
won't change, unless you set it by yourself.

Below are some examples to illustrate the use of time events.
The built-in Python code evaluator allows one to make use of ``after(...)``, ``idle(...)`` in guards or contracts.
These two Boolean predicates can be used to automatically compare the current time (as exposed by the interpreter)
with a predefined value that depends on the state in which the predicate is used. For instance, ``after(x)`` will
evaluate to ``True`` if the current time of the interpreter is at least ``x`` units greater than the time when the
state using this predicate (or source state in the case of a transition) was entered.
Similarly, ``idle(x)`` evaluates to ``True`` if no transition was triggered during the last ``x`` units of time.

Note that while this property was initially designed to manage simulate time, it can also be used to synchronise
the internal clock of an interpreter with the *real* time, i.e. wall-clock time.


Simulated time
--------------

Sismic provides a discrete step-by-step interpreter for statecharts.
It seems natural in a discrete simulation to rely on simulated time.

The following example illustrates a statechart modeling the behavior of a simple *elevator*.
If the elevator is sent to the 4th floor, according to the YAML definition of this statechart,
If the elevator is sent to the 4th floor then, according to the YAML definition of this statechart,
the elevator should automatically go back to the ground floor after 10 seconds.

.. code:: yaml
Expand Down Expand Up @@ -95,6 +99,8 @@ We can check the current floor:

This displays ``0``.



Real time
---------

Expand Down
16 changes: 10 additions & 6 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -69,19 +69,23 @@ Sismic statecharts provides full support for the majority of the UML 2 statechar
format
execution
code

.. toctree::
:caption: Statechart testing
:maxdepth: 1

contract
behavior
properties

behavior

.. toctree::
:caption: Advanced topics
:maxdepth: 1

advancedtopics/dealingtime
advancedtopics/communication
advancedtopics/integrate_code
advancedtopics/semantics
dealingtime
integrate_code
communication
semantics

.. toctree::
:caption: Misc
Expand Down
File renamed without changes.
6 changes: 3 additions & 3 deletions docs/advancedtopics/semantics.rst → docs/semantics.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Implementing other statechart semantics

An :py:class:`~sismic.interpreter.Interpreter` makes use of several *private* methods for its initialization and computations.
These methods computes the transition(s) that should be processed, the resulting steps, etc.
These methods can be overridden or combined easily to define other variants of the statechart semantics.
These methods can be overridden or combined to define variants of statechart semantics.

.. automethod:: sismic.interpreter.Interpreter._select_event

Expand All @@ -23,7 +23,7 @@ These methods can be overridden or combined easily to define other variants of t
.. automethod:: sismic.interpreter.Interpreter._apply_step


These methods are called directly (or not) by :py:class:`~sismic.interpreter.Interpreter.execute_once`.
These methods are all used (even indirectly) by :py:class:`~sismic.interpreter.Interpreter.execute_once`.

.. seealso:: Consider looking at the source of :py:class:`~sismic.interpreter.Interpreter.execute_once` to understand
how these methods are related and organized.
Expand All @@ -49,7 +49,7 @@ and to invert the order in which the internal and external events queues are vis
Example: Custom way to deal with non-determinism
------------------------------------------------

If you want to change the way the Sismic semantics deals with non-determinism,
If you want to change the way the Sismic deals with non-determinism,
for example because it deviates from the semantics given by SCXML or Rhapsody
(remember :ref:`semantic`), you can implement your own variant for dealing with non-determinism.
The method :py:meth:`~sismic.interpreter.Interpreter._sort_transitions` is where the whole job is done:
Expand Down

0 comments on commit 5db0e69

Please sign in to comment.