Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
Reference.txt 77.60 KiB
SNL Reference
=============

This chapter gives a detailed reference for the SNL syntax and semantics
and for the built-in functions.

Formal syntax is given in `BNF`_. Multiple rules for the same
`nonterminal symbol`_ mean that any of the given rules may apply. `Terminal
symbols`_ are enclosed in double quotes.

.. _BNF: http://en.wikipedia.org/wiki/Backus-Naur_Form
.. _nonterminal symbol: http://en.wikipedia.org/wiki/Terminal_and_nonterminal_symbols#Nonterminal_symbols
.. _terminal symbols: http://en.wikipedia.org/wiki/Terminal_and_nonterminal_symbols#Terminal_symbols

Lexical Syntax
--------------

The lexical syntax is not specified formally in this document, since it is
almost identical to that of C. There are only two exceptions:

* `Embedded C Code`_, and

* more reserved words.

We use an informal notation with explanatory text in agle brackets standing
in for the formal definition.

Comments
^^^^^^^^

.. productionlist::
   comment: "/*" <anything> "*/"

C-style comments may be placed anywhere in the program. They are
treated as white space. As in C, comments cannot be nested.

.. todo:: allow C++ style comments

Identifiers
^^^^^^^^^^^

.. productionlist::
   identifier: <same as in C>

Identifiers follow the same rules as in C. They are used for variables
(including foreign variables and event flags), the program name,
states, state sets, and options.

Literals
^^^^^^^^

.. productionlist::
   integer_literal: <same as in C>
   floating_point_literal: <same as in C>
   string_literal: <same as in C>

The lexical syntax of identifiers, as well as numeric and string
literals is exactly as in C, including automatic string
concatenation, character literals, and octal, decimal, and
hexadecimal integer literals.

Embedded C Code
^^^^^^^^^^^^^^^

.. productionlist::
   embedded_c_code: "%{" <anything> "}%"
   embedded_c_code: "%%" <anything> "\n"

A sequence of characters enclosed between "%{" and "}%" is used
literally and without further parsing as if it were a complete
declaration or statement, depending on where it appears.

A sequence of characters enclosed between "%%" and the next line ending
is treated similarly, except that it is stripped of leading and
trailing whitespace and inserted in the output with the current
indentation.

See `Escape to C Code`_ for examples and rationale.

Embedded C code fragments are causing two of the three conflicts in the
grammar. The reason is that the parser cannot decide whether such a
fragment is a declaration or a statement.

Line Markers
^^^^^^^^^^^^

.. productionlist::
   line_marker: "#" line_number "\n"
   line_marker: "#" line_number file_name "\n"
   line_number: <non-empty sequence of decimals>
   file_name: <like string_literal, without automatic string concatenation>

Line markers are interpreted exactly as in C, i.e. they indicate
that the following symbols are really located in the given source file (if
any) at the given line.

.. note::

   :token:`line_number` may only contain decimal numbers, and
   :token:`file_name` must be a single string (no automatic string
   concatenation).

Line markers are typically generated by preprocessors, such as *cpp*.

Program
-------

.. productionlist::
   program: "program" `identifier` `program_param` `global_defns` `entry` `state_sets` `exit` `c_codes`

This is the overall structure of an SNL program. After the keyword
"program" comes the name of the program, followed by an optional program
parameter, global definitions, an optional entry block, the state sets, an
optional exit block, and finally some embedded C code.

Program Name and Parameters
^^^^^^^^^^^^^^^^^^^^^^^^^^^

The program name is an identifier. It is used as the name of the global
variable which contains or points to all the program data structures
(the address of this global variable is passed to the ``seq`` function when
creating the run-time sequencer). It is also used as the base for the state
set thread names unless overridden via the *name* parameter (see :ref:`run
time parameters`).

.. productionlist::
   program_param: "(" `string` ")"
   program_param: 

The program name may be followed by an optional string enclosed in
parentheses. The string content must be a list of comma-separated
parameters in the same form as if specified on invocation (see
:ref:`run time parameters`).

.. note::

   Parameters specified on invocation override those specified in the
   program.

.. productionlist::
   global_defns: `global_defns` `global_defn`
   global_defns: 
   global_defn: `assign`
   global_defn: `monitor`
   global_defn: `sync`
   global_defn: `syncq`
   global_defn: `declaration`
   global_defn: `option`
   global_defn: `c_code`

Global (top-level) definitions, see `Definitions`_ for details.

.. _GlobalEntryExit:

Global Entry and Exit Blocks
^^^^^^^^^^^^^^^^^^^^^^^^^^^^

.. productionlist::
   entry: "entry" `block`
   entry: 
   exit: "exit" `block`
   exit: 

An SNL program may specify optional entry code to run prior to running the
state sets, and exit code to run after all state sets have exited. Both are
run in the context of the first state set thread.

(Global entry and exit blocks should not be confused with
:ref:`state entry and exit blocks`, which have the same syntax but are
executed at each transition from/to a new state.)

The entry or exit code is a regular SNL code block and thus can contain local
variable declarations. Global entry code is always executed after initiating
connections to (named) PVs, and exit code is executed before connections are
shut down. Furthermore, if the global :option:`+c` option is in effect, the
entry code is executed only after all channels are connected and monitored
channels have received their first monitor event. All built-in PV functions
can be expected to work in global entry and exit blocks.

Note that global entry and exit blocks operate as if executed as part of the
first state set. This means that in :ref:`safe mode` changes to global
variables made from inside the entry block are not visible to state sets
other than the first unless explicitly communicated (e.g. by calling pvPut).
The fact that the first state set plays a special role is a historical
accident, rather than conscious design. I might be tempted to redesign this
aspect in a future version, for instance by giving entry and exit blocks
their own dedicated virtual state set.


Final C Code Block
^^^^^^^^^^^^^^^^^^

.. productionlist::
   c_codes: `c_codes` `c_code`
   c_codes: 
   c_code: `embedded_c_code`

Any number of embedded C code blocks may appear after state sets and the
optional exit block. See `Escape to C Code`_.

.. _Definitions:

Definitions
-----------

Variable Declarations
^^^^^^^^^^^^^^^^^^^^^

.. productionlist::
   declaration: `basetype` `init_declarators` ";"
   init_declarators: `init_declarator`
   init_declarators: `init_declarators` "," `init_declarator`
   init_declarator: `declarator`
   init_declarator: `declarator` "=" `init_expr`
   declarator: "*" `declarator`
   declarator: `direct_declarator`
   direct_declarator: `variable`
   direct_declarator: "(" `declarator` ")"
   direct_declarator: `direct_declarator` `subscript`
   init_expr: "(" `type_expr` ")" "{" `init_exprs` "}"
   init_expr: "{" `init_exprs` "}"
   init_expr: `expr`
   init_exprs: `init_exprs` "," `init_expr`
   init_exprs: `init_expr`
   init_exprs: 
   variable: `identifier`

Variable declarations are quite similar to C.

.. versionadded:: 2.1

You can declare more than one variable in a single declaration (comma
separated) and add pointer and array markers (subscripts) ad libitum as well
as initializers.

The remain some limitations:

* arrays must have a defined size (i.e. the integer in the
  subscript brackets is not optional as in C)
* you cannot declare new types or type synonyms
* you cannot declare functions
* no qualifiers ("static", "extern", "const" etc.)
* only certain types are allowed, see below

Some of these restrictions may be lifted in future versions.


.. _foreign entities:

Foreign Declarations
~~~~~~~~~~~~~~~~~~~~

.. productionlist::
   declaration: "foreign" `declarators` ";"
   declarators: `declarator`
   declarators: `declarators` "," `declarator`

.. versionadded:: 2.1

Foreign declarations are used let the SNL compiler know about the existence
of C variables or C preprocessor macros (without arguments) that have been 
defined outside the SNL program or in escaped C code. No warning will be
issued if such a variable or macro is used in the program even if warnings
are enabled.

.. versionadded:: 2.2

It is no longer needed to declare struct or union members as foreign
entities, since the parser no longer confuses them with variables. See
section `Primary Expression Operators`_ below.


.. _Types:

Basic Types
~~~~~~~~~~~

.. productionlist::
   basetype: `prim_type`
   basetype: "evflag"
   basetype: "void"
   basetype: "enum" `identifier`
   basetype: "struct" `identifier`
   basetype: "union" `identifier`
   basetype: "typename" `identifier`

These are the allowed base types, of which you can declare variables,
or pointers or arrays etc.

.. versionadded:: 2.2

You can *use* enumerations, structs, unions and typedefs in declarations
and type expressions. There is still no way to *define* such types in
SNL programs (except by escaping to C).

.. note::

   Any use of a type alias (defined using "typedef" in C) *must*
   be preceded by the "typename" keyword. This keyword has been borrowed
   from C++, but in contrast to C++ using "typename" is *not* optional.

.. todo::

   Add some type ("enumeration"?) to make it easier and more robust to
   interact with process variables of type ``DBF_ENUM`` or ``DBF_MENU``.

.. todo::

   Allow "const" for non-channel variables.

.. productionlist::
   prim_type: "char"
   prim_type: "short"
   prim_type: "int"
   prim_type: "long"
   prim_type: "unsigned" "char"
   prim_type: "unsigned" "short"
   prim_type: "unsigned" "int"
   prim_type: "unsigned" "long"
   prim_type: "int8_t"
   prim_type: "uint8_t"
   prim_type: "int16_t"
   prim_type: "uint16_t"
   prim_type: "int32_t"
   prim_type: "uint32_t"
   prim_type: "float"
   prim_type: "double"
   prim_type: "string"

Primitive types are those base types that correspond directly to some
(scalar) EPICS type. They have the same semantics as in C, except "string"
that does not exist in C. See subsection `Strings`_ below.

.. versionadded:: 2.1

Since the standard numeric types in C have implemenation defined size, fixed
size integral types that correspond to the ones in the C99 standard have
been added. However, int64_t and uint64_t are not supported because
primitive types must correspond to the primitive EPICS types, and EPICS does
not yet support 64 bit integers.

Type Expressions
~~~~~~~~~~~~~~~~

.. productionlist::
   type_expr: `basetype` `opt_abstract_declarator`
   opt_abstract_declarator: 
   opt_abstract_declarator: `abstract_declarator`
   abstract_declarator: "*"
   abstract_declarator: "*" `direct_abstract_declarator`
   abstract_declarator: `direct_abstract_declarator`
   direct_abstract_declarator: "(" `abstract_declarator` ")"
   direct_abstract_declarator: `direct_abstract_declarator` `subscript`
   direct_abstract_declarator: `subscript`

Type expressions closely follow declaration syntax just as in C. They are
used for type casts (`Unary Prefix Operators`_) and the special sizeof
operator (`Primary Expression Operators`_).

Strings
~~~~~~~

The type :token:`string` is defined in C as::

   typedef char string[MAX_STRING_SIZE];

where ``MAX_STRING_SIZE`` is a constant defined in one of the
included header files from EPICS base. I know of no EPICS version
where it is different from 40, but to be on the safe side I
recommend not to rely too much on the numeric value. (You can use
``sizeof(string)`` in SNL expressions.)

.. note::

   In contrast to C, in SNL ``string s`` is *not* a synonym for
   ``char s[MAX_STRING_SIZE]``, since variables of type :token:`string`
   are treated differently when it comes to interacting with PVs: the
   former gets requested with type DBR_STRING and a count of one, while
   the latter gets requested with type DBR_CHAR and a count of
   ``MAX_STRING_SIZE``.

Event Flags
~~~~~~~~~~~

Event flags are values of an abstract data type with four operations
defined on them: :c:func:`efSet`, :c:func:`efClear`, :c:func:`efTest`,
and :c:func:`efTestAndClear`.

An event flag ``e`` can act as a binary semaphore, allowing exactly one
state set to continue, if ``when(efTestAndClear(e))`` is used to wait
and ``efSet(e)`` to signal. Event flags can be coupled to changes of a
PV using the :token:`sync` clause, so that the flag gets set whenever
an event happens.

You cannot declare arrays of or pointers to event flags, since
event flags are not translated to C variables in your program.

See :ref:`EventFlags`.

.. _variable scope:

Variable Scope and Life Time
^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Variables are statically scoped, i.e. they are visible and accessible to the
program only inside the smallest :token:`block` enclosing the declaration;
if declared outside of any block, i.e. at the top level, they are visible
everywhere in the program.

This is quite similar to C and other statically scoped programming
languages. However, there are two differences to C:

First, global variables are always local to the program, similar to
variables that have been declared "static" in C.

Second, in C, a variable's life time is defined by its scope: when the scope
is left, the variable becomes undefined. That is, life time (also called
dynamic scope) follows static scope. This is generally the same in SNL, with
two notable *exceptions*: A variable declared local to a state set or local
to a state continues to exist *as long as the program runs*, just as if it
were declared at the top level, i.e. before any state sets. We say that such
variables have  *global life time*.

Only variables with global life time can be assigned to a process variable,
monitored, synced etc. If they have an initializer, they will be initialized
only once when the program starts.

Variables declared local to an action block or a compound statement behave
exactly as in C. They can *not* be assigned to a PV; and (if they have an
initializer) they become re-initialized each time the block is entered.

The rationale for this is that, while bringing a normal variable into and
out of scope is a very cheap operation, establishing a connection to a PV is
not.

Variable Initialization
^^^^^^^^^^^^^^^^^^^^^^^

Initializers for variables of global life time (i.e. globals as well as
state set and state local ones) must respect the usual C rules for static
variable initializers. Particularly, they must be *constant expressions* in
the C sense, i.e. calculable at compile time, which also means they must not
refer to other variables.

Variables of global life time are initialized before *any* other code is
executed.

Initializers for other variables (i.e. those with local lifetime) behave
exactly as regular local C variables. Particularly, an initializer for such
a variable may refer to other variables that have been declared (and,
possibly, initialized) in an outer scope, and also to those that have
previously been declared (and, possibly, initialized) in the same scope.

Process Variables
^^^^^^^^^^^^^^^^^

The syntactic constructs in this section allow program variables to be
connected to externally defined process variables.

assign/connect
~~~~~~~~~~~~~~

.. productionlist::
   assign: `connect` `variable` `to` `string` ";"
   assign: `connect` `variable` `subscript` `to` `string` ";"
   assign: `connect` `variable` `to` "{" `strings` "}" ";"
   assign: `connect` `variable` ";"
   connect: "assign"
   connect: "connect"
   to: "to"
   to: 
   strings: `strings` "," `string`
   strings: `string`
   strings: 
   subscript: "[" `integer_literal` "]"

This assigns or connects program variables to named or anonymous process
variables.

There are four variants of the :token:`assign` statement. The first one
assigns a (scalar or array) variable to a single process variable.
The second one assigns a single element of an array variable to a
single process variable. The third one assigns elements of an array
variable to separate process variables.

For the third variant, if the number of PV names does not match the number
of elements in the array, then the rule is that

* missing PV names default to "", and
* excess PV names are discarded.

.. versionadded:: 2.1

The fourth variant serves as an abbreviation for
the first variant, in the special case where the PV name is empty (``""``).

Assigned variables must be of global life time, see
:ref:`variable scope`. Assigned variables, or separately assigned
elements of an array, can be used as argument to built-in ``pvXXX``
procedures (see `Built-in Functions`_). This is the primary means of
interacting with process variables from within an SNL program.

Only certain types of variables can be assigned to a PV: allowed
are numeric types (char, short, int, long, and their unsigned variants)
and strings (these are sometimes referred to as *scalar types*), as well
as one or two dimensional arrays of these.

Process variable names are subject to *parameter expansion*: substrings
of the form ::

    {parametername}

(i.e. an identifier enclosed in curly braces) are expanded to the value of
the program parameter *if* a corresponding parameter is defined (either
inside the program or as extra argument on invocation); otherwise no
expansion takes place.

If the process variable name (after expansion) is an empty string, then no
actual assignment to any process variable is performed, but the variable
is marked for potential (dynamic) assignment with :c:func:`pvAssign`.

.. note:: An :token:`assign` clause using an empty string for the PV name is
   interpreted differently in :ref:`safe mode`, see :ref:`anonymous pvs`.

An array variable assigned wholesale to one process variable (using
the first syntactic variant above) or an element of a
two-dimensional variable assigned to an array process variable
(using the second syntactic variant) will use either the length of
the array (resp. sub-array) or the native count for the underlying
variable, whichever is smaller, when communicating with the
underlying process variable. The native count is determined when a
connection is established. For anonymous PVs, the length of the array is
used.

Pointer types may not be assigned to process variables.

.. versionadded:: 2.1

You can use the keyword "connect" as a synonym
for "assign". It is offered as an alternative because the traditional
term "assign" is too easily confused with the assignment
statement (``var = expression;``), even though these notions have
nothing in common. However, for compatibility and the
fact that most SNL programmers are used to it, I have refrained from
making any disruptive change here. Thus, the documentation still talks
about "assigned variables", the "assign clause" etc. This might change
in a future (major) release.


monitor
~~~~~~~

.. productionlist::
   monitor: "monitor" `variable` `opt_subscript` ";"
   opt_subscript: `subscript`
   opt_subscript: 

This sets up a monitor for an assigned variable or array element.
Monitored variables are automatically updated whenever the underlying
process variable changes its value. Note, however, that this depends on
the configuration of the underlying PV: some PVs post an update event
only if the value changes by at least a certain amount. Also, events may
be posted, even if no actual change happens, i.e. the value remains the
same. The details can be found in the `EPICS Record Reference Manual`_.

.. _EPICS Record Reference Manual: http://www.aps.anl.gov/epics/wiki/index.php/RRM_3-14


sync
~~~~

.. productionlist::
   sync: "sync" `variable` `opt_subscript` `to` `event_flag` ";"
   event_flag: `identifier`

This declares a variable to be synchronized with an event flag.

When a monitor is posted on any of the process variables associated
with the event flag (and these are :token:`monitor`\ed), or when an
asynchronous get or put operation completes, the corresponding event
flag is set.

The variable must be :token:`assign`\ed and :token:`monitor`\ed.
A variable can be mentioned in at most one sync clause, but an event
flag may appear in more than one such clause. The variable may be an
array, and as such may be associated with multiple process variables.

.. versionadded:: 2.1

*  It is now allowed to sync an event flag to more than one variable.
*  There is now a run-time equivalent to the sync clause, see the
   built-in function :c:func:`pvSync`.

syncQ
~~~~~

.. productionlist::
   syncq: "syncq" `variable` `opt_subscript` `to` `event_flag` `syncq_size` ";"
   syncq: "syncq" `variable` `opt_subscript` `syncq_size` ";"
   syncq_size: `integer_literal`
   syncq_size: 

This declares a variable to be queued.

When a monitor is posted on any of the process variables associated
with the given program variable, the new value is written to the end
of the queue. If the queue is already full, the last (youngest) entry
is overwritten. The :c:func:`pvGetQ` function reads items from the
queue.

The variable must be :token:`assign`\ed and :token:`monitor`\ed.
Specifying a size (number of elements) for the queue is optional. If
a size is given, it must be a positive decimal number, denoting the
maximum number of elements that can be stored in the queue. A
missing size means that it defaults to 100 elements. The variable can
be an array, and may be associated with multiple process variables, in
which case there is still only one queue, but any change in any of the
involved PVs will add an entry to the queue.

.. versionadded:: 2.1

You can use "syncq" (all lower case) as keyword instead of "syncQ". The
latter may be deprecated in a future version.

.. versionadded:: 2.1

Not giving a queue size (thus relying on the default of 100 elements)
is now *deprecated* and the compiler will issue a warning. The reason
for this is that queues are now statically allocated, which can result
in a large memory overhead especially if the variable is an array
associated with a single PV. (A default queue size of 1 would be much
more useful, but for compatibility I kept it at 100 as in previous
versions.)

.. versionadded:: 2.1

A queued variable no longer needs to be associated with an event flag.
The first form of the :token:`syncq` clause is now merely an
abbreviation for a :token:`sync` clause together with a :token:`syncq`
of the second form, i.e. ::

   syncq var to ef qsize;

is equivalent to ::

   sync var to ef;
   syncq var qsize;

Forcing the association with an event flag was never really necessary,
since :c:func:`pvGetQ` already checks and returns whether the queue is
empty or not; and any state set that mentions a variable in a
:token:`transition` clause automatically gets woken up whenever the variable
changes due to a monitor event. On the other hand, relying on the event
flag being set as an indication that the queue is non-empty has always
been unreliable since another :c:func:`pvGetQ` might have intervened and emptied
the queue between the two calls.

Note that :c:func:`pvGetQ` clears an event flag associated with the variable if
the queue becomes empty after removing the head element.


.. _option definition:

Option
^^^^^^

.. productionlist::
   option: "option" `option_value` `identifier` ";"
   option_value: "+"
   option_value: "-"

The option name is any combination
of option letters. Multiple options can be clobbered into a single
option clause, but only a single option value is allowed.
Option value "+" turns the given options on, a"-" turns them off.

Examples::

   option +r; /* make code reentrant */
   option -ca; /* synchronous pvGet and don't wait for channels to connect */

Unknown option letters cause a warning to be issued, but are otherwise
ignored.

The same syntax is used for global options and state options. The
interpretation, however, is different:

Global (top-level) options are interpreted as if the corresponding
compiler option had been given on the command line (see
:ref:`CompilerOptions`). Global option definitions take precedence over
options given to the compiler on the command line.

State options occur inside the state construct and affect only the
state in which they are defined, see `State Option`_.

State Set
---------
.. productionlist::
   state_sets: `state_sets` `state_set`
   state_sets: `state_set`
   state_set: "ss" `identifier` "{" `ss_defns` `states` "}"
   ss_defns: `ss_defns` `ss_defn`
   ss_defns: 

A program contains one or more state sets. Each state set is
defined by the keyword "ss", followed by the name of the state set (an
identifier). After that comes an opening brace, optionally state set
local definitions, a list of states, and then a closing brace.

State set names must be unique in the program.

State Set Local Definition
^^^^^^^^^^^^^^^^^^^^^^^^^^

.. productionlist::
   ss_defn: `assign`
   ss_defn: `monitor`
   ss_defn: `sync`
   ss_defn: `syncq`
   ss_defn: `declaration`

Inside state sets are allowed variable declarations and process variable
definitions (:token:`assign`, :token:`monitor`, :token:`sync`, and
:token:`syncq`).

See `variable scope`_ for details on what local definitions mean.

State
^^^^^

.. productionlist::
   states: `states` `state`
   states: `state`
   state: "state" `identifier` "{" `state_defns` `entry` `transitions` `exit` "}"
   state_defns: `state_defns` `state_defn`
   state_defns: 

A state set contains one or more states. Each state is defined by the
keyword "state", followed by the name of the state (and identifier),
followed by an opening brace, optionally state local definitions, an
optional entry block, a list of transitions, an optional exit block,
and finally a closing brace.

State names must be unique in the sate set to which they belong.

State Local Definition
~~~~~~~~~~~~~~~~~~~~~~

.. productionlist::
   state_defn: `assign`
   state_defn: `monitor`
   state_defn: `sync`
   state_defn: `syncq`
   state_defn: `declaration`
   state_defn: `option`

.. _StateOption:

State Option
~~~~~~~~~~~~

The syntax for a state option is the same as for global options
(see :ref:`option definition`).

The state options are:

:option:`+t`
   Reset delay timers each time the state is entered, even if entered
   from the same state. This is the default.

:option:`-t`
   Don't reset delay timers when entering from the same state. In other
   words, the :c:func:`delay` function will return whether the specified
   time has elapsed from the moment the current state was entered from a
   different state, rather than from when it was entered for the current
   iteration.

:option:`+e`
   Execute :token:`entry` blocks only if the previous state was
   not the same as the current state. This is the default.

:option:`-e`
   Execute :token:`entry` blocks even if the previous state was the same
   as the current state.

:option:`+x`
   Execute :token:`exit` blocks only if the next state is not the same as
   the current state. This is the default.

:option:`-x`
   Execute :token:`exit` blocks even if the next state is the same as
   the current state.

For example::

   state low {
      option -e; /* Do entry{} every time ... */
      option +x; /* but only do exit{} when really leaving */
      entry { ... }
      ...when ()...
      exit { ... }
   }

.. _state entry and exit blocks:

State Entry and Exit Blocks
~~~~~~~~~~~~~~~~~~~~~~~~~~~

The syntax is the same as for `Global Entry and Exit Blocks`_.

Entry blocks are executed when the state is entered, before any of the
conditions for state transitions are evaluated.

Exit blocks are executed when the state is left, after the transition
block that determines the next state.

.. note:: State options can be used to control whether entry/exit blocks
   get executed even if the new state is the same as the current one.

.. _Transitions:

Transitions
~~~~~~~~~~~

.. productionlist::
   transitions: `transitions` `transition`
   transitions: `transition`
   transition: "when" "(" `opt_expr` ")" `block` "state" `identifier`
   transition: "when" "(" `opt_expr` ")" `block` "exit"

A state transition starts with the keyword "when", followed by a condition
(in parentheses), followed by a block, and finally the keyword "state" and
then the name of the target state (which must be a state of the same state
set).

The condition must be a valid boolean expression (see `Expressions`_). If
there is no condition given, it defaults to ``TRUE`` (1).

Conditions are evaluated

#. when the state is entered (after entry block execution), and

#. when an event happens (see below).

Evaluation proceeds in the order in which the transitions appear in the
program. If one of the conditions evaluates to ``TRUE``, the corresponding
action block is executed, any exit block of the state is executed, and the
state changes to the specified new state. Otherwise, the state set waits
until an event happens. 

There are five types of event:

-  a process variable monitor is posted
-  an asynchronous :c:func:`pvGet` or :c:func:`pvPut` completes
-  a time :c:func:`delay` elapses
-  an event flag is set or cleared
-  a process variable connects or disconnects

.. versionadded:: 2.1

Instead of declaring which should be the next state, one can use the single
keyword "exit". This has the same effect as a call to the :c:func:`seqStop`
command, that is, at this point all state sets should terminate (after
completing any action block in progress) and execution proceed with the
global exit block (if any). Afterwards all channels are disconnected, all
allocated memory is freed, and the program terminates.

.. note::

   If the program has been started under an ioc shell, then only the SNL
   program is terminated, not the whole ioc. If terminating the ioc
   shell is required, you should call the ``exit()`` function from the
   standard C library. This call can conveniently be placed in the SNL
   program's global exit block.


Block
^^^^^

.. productionlist::
   block: "{" `block_defns` `statements` "}"
   block_defns: `block_defns` `block_defn`
   block_defns: 
   block_defn: `declaration`
   block_defn: `c_code`

Blocks are enclosed in matching (curly) braces. They may contain any
number of block definitions and afterwards any number of statements.

Block definitions are: declarations and embedded C code.

Statements and Expressions
--------------------------

Statements
^^^^^^^^^^

.. productionlist::
   statements: `statements` `statement`
   statements: 
   statement: "break" ";"
   statement: "continue" ";"
   statement: "state" `identifier` ";"
   statement: `c_code`
   statement: "{" `block_defns` `statements` "}"
   statement: "if" "(" `comma_expr` ")" `statement`
   statement: "if" "(" `comma_expr` ")" `statement` "else" `statement`
   statement: "while" "(" `comma_expr` ")" `statement`
   statement: `for_statement`
   statement: `opt_expr` ";"
   for_statement: "for" "(" `opt_expr` ";" `opt_expr` ";" `opt_expr` ")" `statement`

As can be seen, most C statements are supported. Not supported are
the switch/case statement and the return statement.

.. versionadded:: 2.1

The *state change statement* is not borrowed from C; it is only available
in the action block of a state :token:`transition` and has the effect
of immediately jumping out of the action block, overriding the statically
specified new state (given after the block) with its state argument.

Expressions
^^^^^^^^^^^

Formation rules for expressions are listed in groups of descending order
of precedence.

Atomic Expression
~~~~~~~~~~~~~~~~~

.. productionlist::
   expr: `integer_literal`
   expr: `floating_point_literal`
   expr: `string`
   expr: `variable`
   string: `string_literal`

These are literals and variables.

Parenthesized Expression
~~~~~~~~~~~~~~~~~~~~~~~~

.. productionlist::
   expr: "(" `comma_expr` ")"

Primary Expression Operators
~~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. productionlist::
   expr: `identifier` "(" `args` ")"
   expr: "exit" "(" `args` ")"
   expr: "sizeof" "(" `expr` ")"
   expr: "sizeof" "(" `type_expr` ")"
   expr: `expr` "[" `expr` "]"
   expr: `expr` "." `member`
   expr: `expr` "->" `member`
   expr: `expr` "++"
   expr: `expr` "--"
   member: `identifier`

These are: function call, array subscript, record selection, pointer to
record selection, and postfix operators (increment and decrement).

.. note::

   :token:`exit` is listed explicitly because it is a keyword, not an
   identifier, but can also be used as a function.

.. note::

   SNL makes no use of the semantics of structure member access and
   struct tags are treated as if they were expressions (variables, in
   fact). A side-effect is that *snc* will warn that structure tags are
   "used but not declared", which can be silenced with a :ref:`foreign
   entities` declaration, just as with variables.
Unary Prefix Operators
~~~~~~~~~~~~~~~~~~~~~~

.. productionlist::
   expr: "+" `expr`
   expr: "-" `expr`
   expr: "*" `expr`
   expr: "&" `expr`
   expr: "!" `expr`
   expr: "~" `expr`
   expr: "++" `expr`
   expr: "--" `expr`

.. versionadded:: 2.2

Type casts:

.. productionlist::
   expr: "(" `type_expr` ")" `expr`

Left-associative Binary Operators
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. productionlist::
   expr: `expr` "-" `expr`
   expr: `expr` "+" `expr`
   expr: `expr` "*" `expr`
   expr: `expr` "/" `expr`
   expr: `expr` ">" `expr`
   expr: `expr` ">=" `expr`
   expr: `expr` "==" `expr`
   expr: `expr` "!=" `expr`
   expr: `expr` "<=" `expr`
   expr: `expr` "<" `expr`
   expr: `expr` "||" `expr`
   expr: `expr` "&&" `expr`
   expr: `expr` "<<" `expr`
   expr: `expr` ">>" `expr`
   expr: `expr` "|" `expr`
   expr: `expr` "^" `expr`
   expr: `expr` "&" `expr`
   expr: `expr` "%" `expr`

.. note::

   Like in most programming languages, evaluation of conditional
   expressions using ``&&`` and ``||`` is done *lazily*: the second operand
   is not evaluated if evaluation of the first already determines the
   result. This particularly applies to the boolean expressions in
   :token:`transition` clauses. See the built-in function :c:func:`pvGetQ`
   for an extended discussion.

Ternary Operator
~~~~~~~~~~~~~~~~

.. productionlist::
   expr: `expr` "?" `expr` ":" `expr`

The ternary operator (there is only one) is right-associative.

.. _Assignment Operators:

Assignment Operators
~~~~~~~~~~~~~~~~~~~~

.. productionlist::
   expr: `expr` "=" `expr`
   expr: `expr` "+=" `expr`
   expr: `expr` "-=" `expr`
   expr: `expr` "&=" `expr`
   expr: `expr` "|=" `expr`
   expr: `expr` "/=" `expr`
   expr: `expr` "*=" `expr`
   expr: `expr` "%=" `expr`
   expr: `expr` "<<=" `expr`
   expr: `expr` ">>=" `expr`
   expr: `expr` "^=" `expr`

These operators are right-associative.

Comma Operator
~~~~~~~~~~~~~~

.. productionlist::
   comma_expr: `comma_expr` "," `expr`
   comma_expr: `expr`
   opt_expr: `comma_expr`
   opt_expr: 

The comma operator is left associative. An :token:`opt_expr` is
an optional :token:`comma_expr`; it appears, for instance, inside
a :token:`for_statement`.

Argument List
~~~~~~~~~~~~~

.. productionlist::
   args: `args` "," `expr`
   args: `expr`
   args: 

Function argument lists look exactly like chained application of the
comma operator, which is why application of the comma operator in an
argument list must be grouped by parentheses.


Built-in Constants
------------------

Some of the built-in functions use (i.e. accept or return) values of
certain enumeration types and constants. These are also available to C
code and defined in the header files pvAlarm.h and seqCom.h.

.. versionadded:: 2.1.6

The pvStat and pvSevr constants are now known to the compiler, so you
no longer have to declare them as ``foreign`` in the SNL code.


pvStat
^^^^^^

::

   typedef enum {
       /* generic OK and error statuses */
       pvStatOK           = 0,
       pvStatERROR        = -1,
       pvStatDISCONN      = -2,

       /* correspond to EPICS statuses */
       pvStatREAD         = 1,
       pvStatWRITE        = 2,
       pvStatHIHI         = 3,
       pvStatHIGH         = 4,
       pvStatLOLO         = 5,
       pvStatLOW          = 6,
       pvStatSTATE        = 7,
       pvStatCOS          = 8,
       pvStatCOMM         = 9,
       pvStatTIMEOUT      = 10,
       pvStatHW_LIMIT     = 11,
       pvStatCALC         = 12,
       pvStatSCAN         = 13,
       pvStatLINK         = 14,
       pvStatSOFT         = 15,
       pvStatBAD_SUB      = 16,
       pvStatUDF          = 17,
       pvStatDISABLE      = 18,
       pvStatSIMM         = 19,
       pvStatREAD_ACCESS  = 20,
       pvStatWRITE_ACCESS = 21
   } pvStat;


pvSevr
^^^^^^

::

   typedef enum {
       /* generic OK and error severities */
       pvSevrOK      = 0,
       pvSevrERROR   = -1,

       /* correspond to EPICS severities */
       pvSevrNONE    = 0,
       pvSevrMINOR   = 1,
       pvSevrMAJOR   = 2,
       pvSevrINVALID = 3
   } pvSevr;


compType
^^^^^^^^

::

   enum compType {
       DEFAULT,
       ASYNC,
       SYNC
   };

.. note::

   Only ``SYNC`` and ``ASYNC`` are SNL built-in constants (i.e.
   known to *snc*). The constant ``DEFAULT`` is for use in C code
   (to represent a missing optional argument).


seqBool
^^^^^^^

::

   typedef int seqBool;

   #define TRUE   1
   #define FALSE  0


NOEVFLAG
^^^^^^^^

::

   #define NOEVFLAG 0

This can be given as second argument to :c:func:`pvSync` (instead of
an event flag) to cancel the sync.


.. _BuiltinFunctions:

Built-in Functions
------------------

The following special functions are built into the SNL. In most
cases the state notation compiler performs some special
interpretation of the parameters to these functions. Therefore,
some are either not available through escaped C code or their use
in escaped C code is subject to special rules.

An argument specified as *assigned_var* refers to any SNL variable or
array element that has been assigned to a (possibly anonymous) process
variable. The compiler statically checks this. Similarly, *queued_var*
refers to a variable that has been assigned to a PV and also associated
with an event queue with the :token:`syncq` clause. This is also
statically checked by the compiler.

Several of these functions are intended to be called only from
:token:`transition` clauses or only from action code. It is safe to call
them in another context, but the effect is probably not what you want. For
instance, calling :c:func:`delay` in action code does *not* introduce a
delay at this point. Later versions of *snc* may output a warning or even
stop with an error.

.. todo:: What about changing this so that :c:func:`delay` in action code
   *does* introduce a delay? In other words, compile 'delay(<expr>)' to
   'epicsThreadSleep(<expr>)' if called in action code.


delay
^^^^^

.. c:function::
   boolean delay(double delay_in_seconds)

Returns whether the specified time has elapsed since entering the
state. It should be used only within a :token:`transition` expression.

The :option:`-t` state option (see `State Option`_) controls whether the
delay is measured from when the current state was entered from a different
state (:option:`-t`) or from any state, including itself (:option:`+t`,
the default).

pvPut
^^^^^

.. c:function::
   pvStat pvPut(assigned_var)
   pvStat pvPut(assigned_var, SYNC)
   pvStat pvPut(assigned_var, SYNC, timeout)
   pvStat pvPut(assigned_var, ASYNC)

Puts (or writes) the value of an SNL variable to the underlying process
variable. Returns the status from the PV layer (e.g. ``pvStatOK`` for
success).

If the variable is an array and associated with multiple PVs, then only
the first element of the array interacts with its underlying PV.

.. todo:: It would be much nicer to have *all* the
   elements of the array written to their respective PVs in this case.
   Would this break compatibility too much?
   Need to find out what to do.

By default, :c:func:`pvPut` is un-confirmed "fire and forget";
completion must be inferred by other means. The optional ``SYNC``
argument causes it to block on completion with a hard-coded timeout
of 10s. The optional ``ASYNC`` argument allows the program to
continue but still check for completion via a subsequent call to
:c:func:`pvPutComplete` (typically in a :token:`transition` clause).

Note that SNL allows only one pending :c:func:`pvPut` per variable and state set
to be active. As long as a ``pvPut(var,ASYNC)`` is pending completion,
further calls to ``pvPut(var,ASYNC)`` from the same state set immediately
fail and an error message is printed; whereas further calls to ``pvPut(var,SYNC)``
are *delayed* until the previous operation completes. Thus ::

   pvPut(var,ASYNC);
   pvPut(var,SYNC);

and ::

   pvPut(var,SYNC);
   pvPut(var,SYNC);

are equivalent. Whereas in ::

   pvPut(var,ASYNC);
   pvPut(var,ASYNC);

the second pvPut may fail if it is a named PV located on another IOC (since
the first one is still awaiting completion), whereas for local named PVs and
anonymous PVs it will succeed (since completion is immediate in these cases).

In :ref:`safe mode`, :c:func:`pvPut` can be used with anonymous PVs (variables assigned
to "") to communicate between state sets. This makes sense only with global
variables as only those can be referenced in more than one state set. The
behaviour for anonymous PVs exactly mirrors that of named PVs, including the fact
that the new value will not be seen by other state sets until they issue a pvGet,
or, if the variable is monitored, until they wait for an event in a :token:`transition`
clause.
Note that for anonymous PVs completion is always immediate, so the ``ASYNC`` option
is not very useful.

.. versionadded:: 2.2

A timeout value may be specified after the ``SYNC`` argument. This should be
a positive floating point number, specifying the number of seconds before
the request times out. This value overrides the default timeout of 10 seconds.


pvPutComplete
^^^^^^^^^^^^^

.. c:function::
   boolean pvPutComplete(assigned_var)
   boolean pvPutComplete(assigned_var, boolean any)
   boolean pvPutComplete(assigned_var, boolean any, boolean *pComplete)

Returns whether the last asynchronous :c:func:`pvPut` to this process
variable has completed. This call is appropriate only if
the :c:func:`pvGet` 's optional ``ASYNC`` argument was used.

The first form is appropriate when the SNL variable is a scalar.
However, it can also be an array (each of whose elements may be
assigned to a different process variable). In this case, the
single argument form returns whether *all* :c:func:`pvPut` operations
for elements of the array have completed (missing arguments are
implicitly set to ``0``). If ``any`` is ``TRUE``, then
the function returns whether *any* put has completed since the last
call. If ``pComplete`` is non-NULL, it should be an array of
at least the array length of the SNL variable and its elements indicate
whether the corresponding :c:func:`pvPut` has completed.


pvPutCancel
^^^^^^^^^^^

.. c:function::
   void pvPutCancel(assigned_var)

Cancel a pending (asynchronous) :c:func:`pvPut`. If the variable is a
multi-PV array, all pending asynchronous puts to elements of the array
are cancelled.


pvGet
^^^^^

.. c:function::
   pvStat pvGet(assigned_var)
   pvStat pvGet(assigned_var, SYNC)
   pvStat pvGet(assigned_var, SYNC, timeout)
   pvStat pvGet(assigned_var, ASYNC)

Gets (or reads) the value of an SNL variable from the underlying process
variable. Returns the status from the PV layer (e.g.
``pvStatOK`` for success). 

If the variable is an array and associated with multiple PVs, then only
the first element of the array interacts with its underlying PV.

.. todo:: Same as for pvPut, i.e. should work like 'foreach element do pvGet'.

By default, the state set will block until the
read operation is complete with a hard-coded timeout of 10s. The
asynchronous (:option:`+a`) compile option can be used to prevent this, in
which case completion can be checked via a subsequent call to
:c:func:`pvGetComplete` (typically in a :token:`transition` clause).

The optional ``SYNC`` and ``ASYNC`` arguments override the compile option.
``SYNC`` blocks and so gives default behavior if :option:`+a` was not
specified; ``ASYNC`` doesn't block and so gives default behavior if
:option:`+a` was specified.

In :ref:`safe mode`, if ``ASYNC`` is specified and the variable is not
monitored, then the state set local copy of the variable will not be updated
until a call to pvGetComplete is made and returns ``TRUE`` or, if the
variable is monitored, until the state set waits for events in a :token:`transition`
clause. Note that anonymous PVs behave exactly in the same way.

.. versionadded:: 2.2

A timeout value may be specified after the ``SYNC`` argument. This should be
a positive floating point number, specifying the number of seconds before
the request times out. This value overrides the default timeout of 10 seconds.


pvGetComplete
^^^^^^^^^^^^^

.. c:function::
   boolean pvGetComplete(assigned_var)

Returns whether the last asynchronous :c:func:`pvGet` to this process
variable has completed. This call is appropriate only if
the asynchronous (:option:`+a`) compile option is specified or
:c:func:`pvGet` 's optional ``ASYNC`` argument was used.

In :ref:`safe mode`, the the state set local copy of the variable will be
updated with the value from the PV layer as a side effect of this call
(if TRUE is returned).

.. versionadded:: 2.2

.. c:function::
   boolean pvGetComplete(assigned_var, boolean any)
   boolean pvGetComplete(assigned_var, boolean any, boolean *pComplete)

The first form is appropriate when the SNL variable is a scalar.
However, it can also be an array (each of whose elements may be
assigned to a different process variable). In this case, the
single argument form returns whether *all* :c:func:`pvGet` operations
for elements of the array have completed (missing arguments are
implicitly set to ``0``). If ``any`` is ``TRUE``, then
the function returns whether *any* get has completed since the last
call. If ``pComplete`` is non-NULL, it should be an array of
at least the array length of the SNL variable and its elements indicate
whether the corresponding :c:func:`pvGet` has completed.


pvGetCancel
^^^^^^^^^^^

.. c:function::
   void pvGetCancel(assigned_var)

Cancel a pending (asynchronous) :c:func:`pvGet`. If the variable is a
multi-PV array, all pending asynchronous gets to elements of the array
are cancelled.


pvGetQ
^^^^^^

.. c:function::
   boolean pvGetQ(queued_var)

If the queue associated with this variable is not empty, remove its first
(oldest) value and update the variable with it. Returns whether there was
an element in the queue (and the variable got updated). If an element gets
removed, and the queue becomes empty as a result, then any event flag
:token:`sync`\ed to the variable will be cleared.

It is an error to call pvGetQ
with a variable that is not associated with a queue (see the :token:`syncq`
clause and the built-in function :c:func:`pvSync`).

Note that since pvGetQ may have a *side-effect* you should be careful when
combining a call to pvGetQ with other conditions in the same
:token:`transition` clause, e.g. ::

        when (pvGetQ(msg) && other_condition) {
           printf(msg);
        } state ...

would remove the head from the queue every time the condition gets
evaluated, regardless of whether ``other_condition`` is ``TRUE``. This is
most probably not the desired effect, as you would lose an unknown number of
messages. (Of course it *could* be exactly what you want, for instance if
``other_condition`` were something like ``!suppress_output``.) Whereas ::

        when (other_condition && pvGetQ(msg)) {
           printf(msg);
        } state ...

is "safe", in the sense that no messages will be lost. BTW, If you combine
with a disjunction ("||") it is the other way around, i.e. pvGetQ should
appear as the first operand. This is all merely a result of the evaluation
order imposed by the C language (and thus by SNL); similar remarks apply
whenever you want to use an expression inside a :token:`transition` clause
that potentially has a side-effect.


pvFreeQ
^^^^^^^

.. c:function::
   void pvFreeQ(queued_var)

Deletes all entries from a queued variable's queue and
clears the associated event flag.

.. versionadded:: 2.1

Queue elements are no longer dynamically allocated, so this
is now an alias for pvFlushQ.


pvFlushQ
^^^^^^^^

.. c:function::
   void pvFlushQ(queued_var)

.. versionadded:: 2.1

Flush the queue associated with this variable, so it is empty afterwards.


pvAssign
^^^^^^^^

.. c:function::
   pvStat pvAssign(assigned_var, process_variable_name)

Assigns or re-assigns the SNL variable ``var`` to ``process_variable_name``.
If ``process_variable_name`` is an empty string then ``assigned_var`` is
de-assigned (not associated with any process variable). In :ref:`safe mode`,
it causes assignment to an anonymous PV.

As usual, ``assigned_var`` can also be an array element.

See also :token:`assign` clause.

Note that pvAsssign is *asynchronous*: it sends a request to search for and
connect to the given ``process_variable_name``, but it does not wait for a
response, similar to ``pvGet(var,ASYNC)``. Calling pvAssign *does* have one
immediate effect, namely de-assigning the variable from any PV it currently
is assigned to. In order to make sure that it has connected to the new PV,
you can use the :c:func:`pvConnected` built-in function inside a :token:`transition`
clause.

.. todo::

   Add an optional argument, so users can make pvAssign wait for the
   connection to be established (or some timeout).

.. versionadded:: 2.2

Program parameter expansion is done in the same way as
when using the :token:`assign` clause.

A better name for this function would be ``pvReassign``.

.. note::

   ``pvAssign`` can only be called on variables (or array elements)
   that have been statically marked as process variables using the
   :token:`assign` syntax. An empty string may be used for the initial
   assignment, or (from version 2.1. onward) the simplified form
   ``assign var``.

.. warning::

   If a variable gets de-assigned from a non-empty to an empty
   name, the corresponding channel is destroyed, which means that
   dynamically allocated memory gets freed. If your
   system cannot handle dynamic memory allocation without fragmentation,
   care should be taken that assignment and de-assignment do not
   alternate too often.

.. note::

   If you want to assign array elements to separate PVs, you cannot
   currently do this with a single call (in contrast to doing it in an
   :token:`assign` clause. Instead, you must call ``pvAssign``
   for each array element individually.


pvMonitor
^^^^^^^^^

.. c:function::
   pvStat pvMonitor(assigned_var)

Initiates a monitor on the underlying process variable.

See :token:`monitor` clause.

.. note:: If you want to monitor all elements of an array that have been
   assigned to separate PVs, you cannot currently do this with a single
   call (in contrast to doing it in a :token:`monitor` clause). Instead,
   you must call :c:func:`pvMonitor` for each array element individually.
   This might change in a future release.

.. todo:: Implement multiple monitor for arrays.


pvStopMonitor
^^^^^^^^^^^^^

.. c:function::
   pvStat pvStopMonitor(assigned_var)

.. note:: If you want to stop monitoring all elements of an array that have been
   assigned to separate PVs, you cannot currently do this with a single
   call. Instead,
   you must call :c:func:`pvStopMonitor` for each array element individually.
   This might change in a future release.

.. todo:: Implement multiple stop monitor for arrays.

Terminates a monitor on the underlying process variable.


pvSync
^^^^^^

.. c:function::
   void pvSync(assigned_var, event_flag)

Synchronizes a variable with an event flag, or removes such a
synchronization if ``event_flag`` is `NOEVFLAG`_.

.. note:: If you want to sync all elements of an array that have been
   assigned to separate PVs, you cannot currently do this with a single
   call. Instead,
   you must call :c:func:`pvSync` for each array element individually.
   This might change in a future release.

.. todo:: Implement multiple sync for arrays.

See :token:`sync` clause.

pvCount
^^^^^^^

.. c:function::
   unsigned pvCount(assigned_var)

Returns the element count associated with the process variable. This value
is independent of the array size (if the variable is an array), it can be
smaller or larger.


pvStatus
^^^^^^^^

.. c:function::
   pvStat pvStatus(assigned_var)

Returns the current alarm status (e.g. ``pvStatHIHI``, see the header file
pvAlarm.h) of the underlying PV
or indicates failure of a previous PV operation.

The status, severity, and message returned by :c:func:`pvStatus`,
:c:func:`pvSeverity`, and :c:func:`pvMessage` reflect either the underlying
PV's properties (if a pvPut or pvGet operation completed, or the variable is
monitored), or else indicate a failure to initiate one of these operations.


pvSeverity
^^^^^^^^^^

.. c:function::
   pvSevr pvSeverity(assigned_var)

Returns the current alarm severity (e.g. pvSevrMAJOR) of the underlying PV
or indicates failure of a previous PV operation.


pvMessage
^^^^^^^^^

.. c:function::
   const char *pvMessage(assigned_var)

Returns the current error message of the variable, or "" (the empty string)
if none is available.


pvTimeStamp
^^^^^^^^^^^

.. c:function::
   epicsTimeStamp pvTimeStamp(assigned_var)

Returns the time stamp for the last :c:func:`pvGet` completion or monitor
event for this variable. The SNL compiler does not recognize type epicsTimeStamp.
Therefore, variable declarations for this type should be in escaped
C code (see `Escape to C Code`_ and `Foreign Entities`_).

Example::

   %%epicsTimeStamp ts;
   foreign ts;

   int var;
   assign var;

   entry {
       ts = pvTimeStamp(var);
   }
.. todo:: This is crude. Types like epicsTimeStamp etc should be allowed in
   variable declarations. OTOH, this will be fixed as soon as I allow
   'struct <name>', 'enum <name>' etc in declarations, which is planned anyway.


pvAssigned
^^^^^^^^^^

.. c:function::
   boolean pvAssigned(assigned_var)

Returns whether the SNL variable is currently assigned to a process variable.
Note that this function returns ``FALSE`` for anonymous PVs.


pvConnected
^^^^^^^^^^^

.. c:function::
   boolean pvConnected(assigned_var)

Returns whether the underlying process variable is currently connected.


pvIndex
^^^^^^^

.. c:function::
   unsigned pvIndex(assigned_var)

Returns the index associated with a variable. See
`Calling pvGet etc. from C`_ for how to use this function.


pvFlush
^^^^^^^

.. c:function::
   void pvFlush()

Causes the PV layer to flush its send buffer. This is only
necessary if you need to make sure that CA operations are
started *before* the action block finishes. The buffer is
always automatically flushed before the sequencer waits
for events, that is, after a state's entry block is executed
(or on entry to the state if there is no entry block).
The buffer is also flushed after initiating a synchronous
operation that waits for a callback (i.e. ``pvPut(var,SYNC)``
and ``pvGet(var,SYNC)``).


pvChannelCount
^^^^^^^^^^^^^^

.. c:function::
   unsigned pvChannelCount()

Returns the total number of process variables
associated with the program.


pvAssignCount
^^^^^^^^^^^^^

.. c:function::
   unsigned pvAssignCount()

Returns the total number of SNL variables in this
program that are assigned to underlying process variables.
For instance, if all SNL variables are assigned then the following
expression is ``TRUE``::

   pvAssignCount() == pvChannelCount()

Each element of an SNL array counts as variable for the purposes of
:c:func:`pvAssignCount`.


pvConnectCount
^^^^^^^^^^^^^^

.. c:function::
   unsigned pvConnectCount()

Returns the total number of underlying process
variables that are connected.

For instance, if all assigned variables are connected then the following
expression is ``TRUE``::

   pvConnectCount() == pvAssignCount()


efSet
^^^^^

.. c:function::
   void efSet(event_flag)

Sets the event flag and causes evaluation of the :token:`transition`
clauses for all state sets that are pending on this event flag.


efClear
^^^^^^^

.. c:function::
   boolean efClear(event_flag)

Clears the event flag and causes evaluation of the :token:`transition`
clauses for all state sets that are pending on this event flag.


efTest
^^^^^^

.. c:function::
   boolean efTest(event_flag)

Returns whether the event flag was set.

.. note::

   In safe mode, this function is a synchronization point for all
   variables (channels) that are :token:`sync`\ed with it, i.e. the
   state set local copy of these variables will be updated from their
   current globally visible value.


efTestAndClear
^^^^^^^^^^^^^^

.. c:function::
   boolean efTestAndClear(event_flag)

Clears the event flag and returns whether the event
flag was set. It is intended for use within a :token:`transition` clause.

.. note::

   In safe mode, this function is a synchronization point for all
   variables (channels) that are :token:`sync`\ed with it, i.e. the
   state set local copy of these variables will be updated from their
   current globally visible value.


macValueGet
^^^^^^^^^^^

.. c:function::
   char* macValueGet(char *parameter_name)

Returns a pointer to the value of the specified program parameter, if
it exists, else ``NULL``. See `Program Name and Parameters`_.

.. _Shell Commands:

Shell Commands
--------------

These are commands to be issued from the IOC shell or VxWorks shell.
They can also be called from C (and therefore SNL) code.

Some of these routines behave slightly different depending on whether
run under iocsh or a VxWorks shell. This mostly concerns the
``threadID`` argument: under iocsh, this can in fact be a *thread
name* instead of a thread ID. Note, however, that this is
unreliable if you have more than one instance of the same program
running, since the thread names are identical for all instances. The
VxWorks shell version directly takes an epicsThreadID argument and
thus does not recognize thread names.

.. c:function::
   void seq(seqProgram *program, const char *paramdefs, unsigned stacksize)

Start the given program with the given set of parameter definitions and
stack size. If ```stacksize`` is zero or is omitted, then use a default
(EPICS "small" stack). If ``paramdefs`` is zero or the last two arguments
are omitted, then no parameters are defined. Otherwise ``paramdefs`` should
be a string that specifies program parameters as detailed in :ref:`run time
parameters`. See also :token:`program_param`.

.. c:function::
   void seqShow()
   void seqShow(epicsThreadId threadID)

The first form shows a table of all programs, program instances, and
state sets, e.g. ::

  epics> seqShow
  Program Name        Thread ID           Thread Name         SS Name
  ------------        ---------           -----------         -------
  demo                0x807e628           demo                light              
                      0x809fbc8           demo_1              ramp               
                      0x809fcd8           demo_2              limit              
  ------------        ---------           -----------         -------
  demo                0x807fd98           demo                light              
                      0xb7100470          demo_1              ramp               
                      0xb7100580          demo_2              limit              
  ------------        ---------           -----------         -------
  demo                0x80814e0           demo                light              
                      0x809fe68           demo_1              ramp               
                      0x809ff78           demo_2              limit              

Note that in this example we have three running instances of a single
program named 'demo', each of which consists of three state sets
running in its own thread.

The second form displays the internal state of a running program
instance. The threadID parameter must be one of the program's state
set thread IDs as listed in the above table.

For instance, for the above example we might get ::

  epics> seqShow 0x807e628
  State Program: "demo"
    thread priority = 50
    number of state sets = 3
    number of syncQ queues = 0
    number of channels = 6
    number of channels assigned = 6
    number of channels connected = 6
    number of channels monitored = 5
    options: async=0, debug=0, newef=1, reent=1, conn=1, main=0
    user variables: address = 0x807d158, length = 44

    State Set: "light"
    thread name = demo;  Thread id = 0x807e628
    First state = "START"
    Current state = "LIGHT_OFF"
    Previous state = "START"
    Elapsed time since state was entered = 3.0 seconds
    Get in progress = [000000]
    Put in progress = [000000]
    Queued time delays:

    State Set: "ramp"
    thread name = demo_1;  Thread id = 0x809fbc8
    First state = "START"
    Current state = "RAMP_UP"
    Previous state = "RAMP_UP"
    Elapsed time since state was entered = 0.1 seconds
    Get in progress = [000000]
    Put in progress = [000000]
    Queued time delays:
          delay[0]=0.100000

    State Set: "limit"
    thread name = demo_2;  Thread id = 0x809fcd8
    First state = "START"
    Current state = "START"
    Previous state = ""
    Elapsed time since state was entered = 3.0 seconds
    Get in progress = [000000]
    Put in progress = [000000]
    Queued time delays:

.. c:function::
   void seqChanShow(epicsThreadId threadID)
   void seqChanShow(epicsThreadId threadID, const char *)

Display channel information for the program instance specified
by the given threadID. If a second argument is given, it is
interpreted as part of a channel name. Only channels whose name
contains the given string as substring are displayed. The name
can be preceded by a single "-" or "+" sign, signifying that only
disconnected ("-") or connected ("+") channels should be
displayed.

The procedure displays one channel at a time, starting with the
first matching one, and then asks the user
for input. This input can be

a (signed) integer:
  increase / decrease current channel number by the given amount,
  then display the current channel

minus sign ("-"):
  same as "-1"

plus sign ("+"), empty string (return):
  same as "+1"

anything else:
  quit, i.e. back to the shell

If user interaction causes the channel number to leave the range
(i.e. less than zero, greater or equal to number of channels),
the command quits, too.

.. c:function::
   void seqQueueShow(epicsThreadId threadID)

Display information about queued channels. For example ::

  epics> seqShow
  Program Name        Thread ID           Thread Name         SS Name
  ------------        ---------           -----------         -------
  syncqTest           0x8053e60           syncqTest           get                
                      (nil)               (no thread)         get1               
                      (nil)               (no thread)         put                
                      (nil)               (no thread)         flush              
  epics> seqQueueShow 0x8053e60
  State Program: "syncqTest"
  Number of queues = 2
    Queue #0: numElems=5, used=0, elemSize=136
  Next? (+/- skip count, q=quit)

    Queue #1: numElems=5, used=0, elemSize=56
  Next? (+/- skip count, q=quit)

The command is interactive and accepts the same inputs as
:c:func:`seqChanShow`.

.. c:function::
   void seqcar(int level)

The name stands for "sequencer channel access report". It displays
channel connection information. If level <= 1, or no level argument
is given, only a summary line is displayed, for example ::

  Total programs=3, channels=18, connected=18, disconnected=0

For level > 1, connection information is displayed for all channels
of all running programs. For instance ::

  epics> seqcar 2
    Program "demo"
      Variable "light" connected to PV "demo1:light"
      Variable "lightOn" connected to PV "demo1:lightOn"
      Variable "lightOff" connected to PV "demo1:lightOff"
      Variable "voltage" connected to PV "demo1:voltage"
      Variable "loLimit" connected to PV "demo1:loLimit"
      Variable "hiLimit" connected to PV "demo1:hiLimit"
    Program "demo"
      Variable "light" connected to PV "demo2:light"
      Variable "lightOn" connected to PV "demo2:lightOn"
      Variable "lightOff" connected to PV "demo2:lightOff"
      Variable "voltage" connected to PV "demo2:voltage"
      Variable "loLimit" connected to PV "demo2:loLimit"
      Variable "hiLimit" connected to PV "demo2:hiLimit"
    Program "demo"
      Variable "light" connected to PV "demo3:light"
      Variable "lightOn" connected to PV "demo3:lightOn"
      Variable "lightOff" connected to PV "demo3:lightOff"
      Variable "voltage" connected to PV "demo3:voltage"
      Variable "loLimit" connected to PV "demo3:loLimit"
      Variable "hiLimit" connected to PV "demo3:hiLimit"
  Total programs=3, channels=18, connected=18, disconnected=0

.. c:function::
   void seqStop(epicsThreadId threadID)

Initiate a clean program exit. Running state transitions are
completed, then all state set threads exit, all channels are
disconnected, and finally allocated resources are freed.


.. _safe mode:

Safe Mode
---------

.. versionadded:: 2.1

SNL code can be interpreted in *safe mode*. This must be
enabled with the :option:`+s` option, because it changes the way variables
are handled and is thus not fully backwards compatible. It should, however,
be easy to adapt existing programs to safe mode by making communication
between state sets explicit. New programs should no longer use the
traditional unsafe mode.

In the traditional (unsafe) mode, variables are *not* protected against
access from concurrently running threads. Concurrent access to SNL
variables was introduced in version 2.0, when implementation of the PV
layer switched from the old single threaded CA mode ("preemptive
callbacks disabled") to the multi-threaded mode ("preemptive callbacks
enabled") in order to support more than one state set per program. This
could result in data corruption for variables that are not read and written
atomically, the details of which are architecture and compiler dependent
(i.e. plain ``int`` is typically atomic, whereas double is problematic on
some, string and arrays on almost all architectures/compilers). Even for
plain ``int`` variables, read-modify-write cycles (like ``v++``) cannot be
guaranteed to have any consistent result. Furthermore, conditions that have
been met inside a :token:`transition` clause cannot be relied upon to still hold
inside the associated action block.

Concurrent access to SNL variables happens when

* multiple state sets access the same variable, or
* variables are updated from the PV layer due to monitors
  and asynchronous get operations.

While it is possible to avoid the first case by careful coding (using e.g.
event flags for synchronization) it is not possible to guard against the
second case as these events can interrupt action statements at any time.

One of the reasons SNL programs have mostly worked in spite of this is
that due to the standard CA thread priorities the callback thread does not
interrupt the state set threads. Furthermore (and contrary to what many
people believe) the VxWorks scheduler does not normally serve threads with
equal priority in a round-robin (time-sliced) fashion; instead each thread
keeps running until it gets interrupted by a higher priority thread or
until it blocks on a semaphore.

However, RTEMS does time-share threads at the same priority, while Linux and
Windows honor thread priorities only if real-time mode (which needs
administrator privileges). Most importantly, priorities should only be used
to improve latency for certain operations (at the cost of others) and never
should be relied upon for program correctness.

Safe mode solves all these problems by changing the way variables,
particularly global variables, are interpreted. In safe mode, all variables
--except event flags-- are interpreted as if they were *local to the state
set*. This means that setting a variable (even a global variable) in one
state set does *not* automatically change its value as seen by other state
sets. State sets are effectively isolated against each other, and all
communication between them must be explicit. They are also isolated against
updates by callbacks from the PV layer except at those points where they
don't do anything i.e. when they wait for events in a :token:`transition` clause.
In safe mode, variable values get updated right before the conditions are
evaluated, or when explicitly calling synchronization functions like
:c:func:`pvGetComplete` or :c:func:`pvGet` (the latter only if called in
synchronous mode), as well as :c:func:`efTest` and :c:func:`efTestAndClear`.
The documentation for the built-in functions explains the details.

Safe mode implies reentrant mode, see :ref:`reentrant option`.

.. _anonymous pvs:

Anonymous PVs
^^^^^^^^^^^^^

Explicit communication between state sets can use either event flags or
variables. Event flags work exactly the same as in traditional mode.

To use a variable for communication it must have been *assigned* with an
:token:`assign` statement. You can then use :c:func:`pvPut`,
:c:func:`pvGet`, :c:func:`pvMonitor`, and most of the other built-in PV
functions, *regardless of whether the PV name is the empty string (``""``)
or not*. If it is the empty string, the PV functions behave as if the
assignment had been to an internal, anonymous 'pseudo PV' that behaves
similar to an external 'real' PV. The variant ``assign var;`` of the
:token:`assign` statement has been introduced as an abbreviation for
``assign var to "";``.


For instance, with the declaration ::

   int var;
   assign var;

the action statement ::

   pvPut(var)

makes the value of ``var`` available to other state sets. They will,
however, not see the new value until they issue either a (synchronous)
:c:func:`pvGet`, or the variable is declared as monitored and state
change conditions are evaluated.

The action ::

   pvGet(var, SYNC)

updates ``var`` immediately with whatever has been written to it
previously via :c:func:`pvPut` by some other state set. Whereas ::

   pvGet(var, ASYNC)

has no immediate effect on the variable ``var``. Instead, ``var``
will be updated only if the code calls :c:func:`pvGetComplete`
(and it returns ``TRUE``), or when state change conditions are
evaluated the next time.

.. note:: This behaviour is exactly the same as with external PVs.

.. note:: Using ``SYNC`` or ``ASYNC`` with anonymous PVs is not very
   useful since all operations complete immediately.

Event flags can be :token:`sync`\ed to anonymous PVs and will behave
as with external PVs.

Event Flags in Safe Mode
^^^^^^^^^^^^^^^^^^^^^^^^

Apart from the :token:`sync` and :token:`syncq` features, and apart
from the atomicity of :c:func:`efTestAndClear`, an event flag behaves
similar to an anonymous PV of boolean type with a monitor. In safe
mode, :c:func:`efTest` and :c:func:`efTestAndClear` have the additional
side-effect of acting as a synchronization point for all variables that
are :token:`sync`\ed with the event flag: the state set local copies of
these variable are updated from their globally visible copies.


C Compatibility Features
------------------------

.. todo::

   - extract this section into its own file/chapter

   - update to version 2.2 (see release notes for many details)
   
   - reduce verbosity, e.g. move examples and motivations to the tutorial

   - should I start to deprecate escape to C code? with the generated header
     file, is there anything that cannot be done in a separate C file?
     (%%#include comes to mind, but that could be replaced by a SNL
     directive)

Foreign Variables and Functions
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

You can use (reference, call) any exported C variable or function that
is linked to your program. For instance, you may link a C library to
the SNL program and simply call functions from it directly in SNL code.
Note, however, that this is restricted to *SNL action statements*
(:token:`statement`). It is advisable to take care that the C code
generated from your SNL program contains (directly oir via ``#include``)
a valid declaration for such entities, otherwise the C compiler will
not be able to catch type errors (the SNL compiler does not do any
type-checking by itself). For libraries, this is usually done
by adding a line ::

   %%#include "api-of-your-library.h"

This uses the one-line-escape syntax explained in the next section.

For historical reasons, *snc* complains with a warning if you use a
foreign variable or preprocessor macro in SNL code (but not for foreign
function calls). This can be suppressed by adding a foreign
declaration (see `Foreign Entities`_).


Escape to C Code
^^^^^^^^^^^^^^^^

Because the SNL does not support the full C language, C code may be
escaped in the program. The escaped code is not compiled by *snc*,
instead it is literally copied to the generated C code. There are two
escape methods:

#. Any code between ``%%`` and the next newline character is escaped.
   Example::

      %% for (i=0; i < NVAL; i++)

#. Any code between ``%{`` and ``}%`` is escaped. Example::

      %{
      extern float smooth();
      extern LOGICAL accelerator_mode;
      }%

   Note that text appearing on the same line after ``%{`` and
   before ``}%`` also belongs to the literal code block.

A variable or preprocessor macro declared in escaped C code is
foreign to the SNL, just as if it were declared in C code extern to
the SNL program and its use will give a warning if no foreign
declaration preceeds it.


Preprocessor Directives
~~~~~~~~~~~~~~~~~~~~~~~

A very common pitfall is the use of preprocessor directives, such as
``#include``,  in multi-line literal C code blocks in conjunction
with using the preprocessor on the unprocessed SNL code (which
happens by the default with the standard build rules if the name of
the source file has the ``.st`` extension).

For instance with ::

   %{
   #include <abcLib.h>
   /* ... */
   }%

the header file will be included *before* the SNL compiler parses
the program, which is most probably not what you wanted to happen
here. For instance,
if the header contains macro definitions, these will not be in effect
when the rest of the C code block gets compiled.

You can defer interpretation of a preprocessor directive until
after *snc* has compiled the code to C, by ensuring that some extra
non-blank characters appear in front of the ``#`` sign, so *cpp* does
not recognize the directive. For instance ::

   %%#include <abcLib.h>

or ::

   %{#include <abcLib.h>}%


Defining C Functions within the Program
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Escaped C code can appear in many places in the SNL program. But only
code that appears at the top level, i.e. outside any SNL code block will
be placed at the C top level. So, C *function definitions* must be
placed there; this means either inside the definitions section
(:token:`global_defns`) or at the end of the program. For example::

   program example
   ...
   /* last SNL statement */
   %{
       static float smooth (pArray, numElem)
       { ... }
   }%

It matters where the function is defined: if it appears at the end of
the program, it can see all the variable and type definitions generated
by the SNL compiler, so if your C function accesses a global SNL variable,
you must place its definition at the end. However, this means that the
generated code for the SNL program does not see the C function definition,
so you might want to place a separate prototype for the function in the
definitions section (i.e. before the state sets).

Remember that you can *call* any C function from anywhere in the SNL program
without escaping. You can also link the SNL program to C objects or
libraries, so the only reason to put C function definitions inside the
SNL program is if your function accesses global SNL variables.

Calling pvGet etc. from C
^^^^^^^^^^^^^^^^^^^^^^^^^

The built-in SNL functions such as :c:func:`pvGet` cannot be directly
used in user-supplied functions. However, most of the built-in
functions have a C language equivalent with the same name, except
that the prefix ``seq_`` is added (e.g. ``pvGet`` becomes
``seq_pvGet``). These C functions expect an additional first argument
identifying the calling state set, which is available in action code
under the name ``ssId``. If a process variable name is required, the
index of that variable must be supplied. This index is obtained via
the :c:func:`pvIndex` function (which must be called from SNL code,
not C code, to work).

If the program is compiled with the :option:`+r` option, user
functions cannot directly access SNL variables, not even global ones.
Instead, variables (or their address) should be passed to user
functions as arguments. Alternatively, you can pass the ``pVar``
variable of type ``USER_VAR*`` and access SNL variables as structure
members of ``pVar``. See next section for details and an example.

The prototypes for the C functions corresponding to the built-in SNL
functions as well as additional supporting macros and type
definitions can be found in the header file ``seqCom.h``. This header
file is always included by the generated C code.


.. _reentrant option:

Variable Modification for Reentrant Option
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

If the reentrant option (:option:`+r`) is specified to *snc* then all
variables are made part of a structure. Suppose we have the
following top-level declarations in the SNL program::

   int sw1;
   float v5;
   short wf2[1024];

The C file will contain the following declaration::

   struct UserVar {
       int sw1;
       float v5;
       short wf2[1024];
   };

The sequencer allocates the structure area at run time and passes a
pointer to this structure into the program. This structure
has the following type::

   struct UserVar *pVar;

Reference to variable ``sw1`` is made as ::

   pVar->sw1

This conversion is automatically performed by *snc* for all SNL
statements, but you will have to handle escaped C code yourself.

.. note::

   :ref:`safe mode` (enabled with the :option:`+s` option) implies
   reentrant mode. In safe mode, each state set has its own copy of the
   ``UserVar`` struct. You can operate on its members in any way you like,
   including taking the address of variables and passing them to C
   functions.

Here is a stupid example of a C function that does a pvGet, increments the
variable, and then does a pvPut::

   program userfunc

   option +r;

   %{
   static void incr(SS_ID ssId, int *pv, VAR_ID v)
   {
       seq_pvGet(ssId, v, SYNC);
       *pv += 1;
       seq_pvPut(ssId, v, SYNC);
   }
   }%

   int i;
   assign i to "counter";

   foreign ssId;

   ss myss {
       state doit {
           when (delay(1)) {
               incr(ssId, &i, pvIndex(i));
           } state doit
       }
   }