:github_url: https://github.com/UniversalRobots/Universal_Robots_Client_Library/blob/master/doc/architecture/script_reader.rst

.. _script_reader:

ScriptReader
============

Script code used by the :ref:`script_sender` is read from a file. That script code might have to be
dynamically modified based on some configuration input. For example, if the script code contains
connections to a remote PC, that PC's IP address might be configured by the user. Another example
would be to include certain parts of the script code only if the robot's software version supports
that.

For that purpose the ``ScriptReader`` class is provided. It reads the script code from a file and
performs the following substitutions:

- Replaces variables in the form of ``{{variable_name}}`` with the value of the variable
  from a provided dictionary.
- Includes other script files using the directive ``{% include file_name %}``. The included file is
  read from the same directory as the main script file. Nested includes are also possible.
- Use conditionals in order to add certain parts of the script code only if a condition matches.

The supported substitutions use a basic implementation of the `Jinja2 templating engine` syntax.

.. note::
  One special literal is defined for **version information**. Use a software version prefixed with a
  ``v`` character, e.g. ``v10.8.0`` to encode a software version. Version information entries can be
  compared with each other.

  **Do not** wrap version information into quotes, as this will be interpreted as a string.

Example
-------

Given two script files:

.. literalinclude:: ../../tests/resources/example_urscript_main.urscript
   :caption: tests/resources/example_urscript_main.urscript
   :linenos:
   :lineno-match:

.. literalinclude:: ../../tests/resources/example_urscript_feature.urscript
   :language: python
   :caption: tests/resources/example_urscript_feature.urscript
   :linenos:
   :lineno-match:

The dictionary entry for ``feature_name`` is "torque control".

Depending on the ``SOFTWARE_VERSION`` entry in the dictionary passed to the
``ScriptReader``, the script code will be read as follows:

Given ``SOFTWARE_VERSION = v5.21.0``, the script code will be:

.. code-block:: python

     popup("The cool new feature is not supported on Software version 5.23.0")


Given ``SOFTWARE_VERSION = v5.23.0``, the script code will be:

.. code-block:: python

     textmsg("torque control is a very cool feature!")

Supported Data
--------------

Data dictionary (C++ side)
~~~~~~~~~~~~~~~~~~~~~~~~~~

The data dictionary supports the following types

- ``str``: A string value, e.g. "Hello World"
- ``int``: An integer value, e.g. 42
- ``double``: A floating point value, e.g. 3.14
- ``bool``: A boolean value, e.g. ``true`` or ``false``
- ``VersionInformation``: A version information value, e.g. ``VersionInformation::fromString("10.8.0")``

Script code side
~~~~~~~~~~~~~~~~

Variable replacements
^^^^^^^^^^^^^^^^^^^^^

Variable replacements in the script code are done using the syntax ``{{ variable_name }}``. For
this to work, the variable ``variable_name`` has to be defined in the data dictionary passed to the
``ScriptReader``.

The expression ``{{ variable_name }}`` will be replaced with the string representation of the
variable's content.

- If the variable is a string, it has to be wrapped into quotes in the script
  code. e.g.

  .. code-block:: python

     textmsg("{{ log_message }}")


- Boolean variables will be replaced with the string ``True`` or ``False``.
- Numeric variables (integer and floating point) will be replaced with the string representation generated by
  `std::to_string() <https://en.cppreference.com/w/cpp/string/basic_string/to_string>`_.
- Version information variables will be replaced with the string representation similar to
  ``10.7.0.0``

Boolean expressions
^^^^^^^^^^^^^^^^^^^

Boolean expressions have to follow one of two possible syntax variations

- Direct evaluation of a boolean variable from the data dictionary:

  .. code-block::

     boolean_variable_name

- Comparison of a variable with a value using an operator.

  .. code-block::

     variable_name operator value

  The operator has to be one of ``==``, ``!=``, ``<``, ``<=``, ``>``, ``>=``. On the lefthand side
  of the operator there has to be a variable from the data dictionary. The right hand side can be
  either a variable name or a value. If the right hand side is a variable name, it has to be
  defined in the data dictionary as well. Values will be parsed as follows:

  - Strings: Wrapped in quotes, e.g. ``"Hello World"`` or ``'Universal Robots'``.
  - Numerical values such as ``42``, ``3.14``, ``-1``, ``1e-12``.
  - Boolean values: See below.
  - Version information: Prefixed with a ``v`` character, e.g. ``v10.8.0``, ``v5.23.0``.

Boolean values parsing
^^^^^^^^^^^^^^^^^^^^^^

Boolean values can be parsed from the following strings:

- ``true``, ``True``, ``TRUE``
- ``on``, ``On``, ``ON``
- ``yes``, ``Yes``, ``YES``
- ``1``
- ``false``, ``False``, ``FALSE``
- ``off``, ``Off``, ``OFF``
- ``no``, ``No``, ``NO``
- ``0``

Conditional blocks
^^^^^^^^^^^^^^^^^^

Conditional blocks have to be started with a ``{% if condition %}`` directive and closed with a
``{% endif %}`` directive. The condition can be any boolean expression as described above.

The ``{% elif condition %}`` and ``{% else %}`` directives can be used to add alternative paths.

Conditional blocks can be nested.
