Primary Expressions¶
expr ::=integer_literal
expr ::=floating_point_literal
expr ::=string
expr ::=variable
string ::=string_literal
expr ::= "("comma_expr
")"
These are literals, variables, and parenthesized expressions.
This chapter gives a detailed reference for the SNL syntax and semantics and for the built-in functions of SNL in version 2.2. The documentation for version 2.1 is and will remain available, too.
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.
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.
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.
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 ::= "%{" <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.
Note that embedded C code fragments are not allowed to appear in expressions.
See Escape to C Code for examples and rationale.
Embedded C code fragments are the cause for one of the two conflicts in the grammar. The reason is that the parser cannot always decide whether such a fragment is a declaration or a statement. (The other conflict is due to the infamous “dangling else”.)
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
line_number
may only contain decimal numbers, and
file_name
must be a single string (no automatic string
concatenation).
Line markers are typically generated by preprocessors, such as cpp
.
program ::= "program"identifier
program_param
initial_defns
entry
state_sets
exit
final_defns
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, initial global definitions, an optional entry block, the state sets, an optional exit block, and then again global definitions.
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 Program Parameters).
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 Program Parameters).
Note
Parameters specified on invocation override those specified in the program.
initial_defns ::=initial_defns
initial_defn
initial_defns ::= initial_defn ::=assign
initial_defn ::=monitor
initial_defn ::=sync
initial_defn ::=syncq
initial_defn ::=declaration
initial_defn ::=option
initial_defn ::=funcdef
initial_defn ::=structdef
initial_defn ::=c_code
final_defns ::=final_defns
final_defn
final_defns ::= final_defn ::=funcdef
final_defn ::=structdef
final_defn ::=c_code
Global (top-level) definitions, see Definitions for details. Note that in the final definition section only function definitions and literal code blocks are allowed.
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
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 +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 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.
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.
New in version 2.2.
funcdef ::=basetype
declarator
block
Function definitions are very much like those in C.
The most important difference is that functions declared in SNL get passed certain implicit parameters. This makes it possible to call built-in functions as if the code were a normal action block. Note, however, that there is currently no way to pass channel (“assigned”) variables to SNL functions in such a way that you can call built-in functions on them. The “assigned” status of such a variable is lost and it gets passes to the function as a normal C value. This means you can call built-in functions that expect such a variable only if the variable was declared at the top-level. Lifting this limitation is planned for a future release.
Another difference from C is that SNL functions automatically scope over the whole program, no matter where they are defined. It is not necessary (but allowed) to repeat the function declaration (without the defining code block, commonly referred to as a “function prototype”) at the top of the program.
New in version 2.2.
structdef ::= "struct"identifier
members
";" members ::= "{"member_decls
"}" member_decls ::=member_decl
member_decls ::=member_decls
member_decl
member_decl ::=basetype
declarator
";" member_decl ::=c_code
Type definitions are currently limited to struct definitions and may appear only at the top level. They are translated to the equivalent struct definition in C. Differences to C are that bit fields are not supported, and that member types are limited to SNL types (built-in or user defined).
declaration ::=basetype
init_declarators
";" init_declarators ::=init_declarator
init_declarators ::=init_declarators
","init_declarator
init_declarator ::=declarator
init_declarator ::=declarator
"="init_expr
declarator ::=variable
declarator ::= "("declarator
")" declarator ::=declarator
subscript
declarator ::=declarator
"("param_decls
")" declarator ::= "*"declarator
declarator ::= "const"declarator
variable ::=identifier
init_expr ::= "{"init_exprs
"}" init_expr ::= "("type_expr
")" "{"init_exprs
"}" init_expr ::=expr
init_exprs ::=init_exprs
","init_expr
init_exprs ::=init_expr
init_exprs ::=
New in version 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.
Changed in version 2.2: Function declarations and initializers with type casts.
As in C, some combinations of type operators are not allowed, for instance because they are redundant (like declaring a function or array const). This is not specified in the SNL grammar. There are some checks that warn about obviously wrong combinations, but as with type checking, most of the work is off-loaded to the C compiler.
The also remain some limitations:
arrays must have a defined size: the expression inside the subscript brackets is not optional as in C; it must also be an integer literal, not a constant expression as in C.
you cannot declare new types or type synonyms
the “volatile” type qualifier is not supported
neither are storage specifiers (“static”, “extern”, “auto”, register”)
only certain base types are allowed, see below)
Some of these restrictions may be lifted in future versions.
New in version 2.2.
As in C, declarations may involve the “const” keyword to make parts of the thing declared constant i.e. immutable. The SNL syntax treats “const” as a prefix type operator, exactly like the pointer “*” operator.
Note
This correctly specifies a large subset of the C syntax, but there are some limitations. For instance, in C you can write
const int x; /* attention: invalid in SNL */
as an alternative notation for
int const x;
This is not allowed in SNL to keep the parser simple and orthogonal.
Otherwise, “const” behaves like in C. For instance,
char const *p;
declares a mutable pointer to constant char (possibly more than one), while
char *const p;
declares a constant pointer to one or more mutable chars, and
char *const *p;
a constant pointer to constant chars.
As in C, declarations (and similarly Type Expressions) should be read inside out, starting with the identifier and working outwards, respecting the precedences (postfix binds stronger than prefix). This works for “const” exactly as for array subscripts, pointers, and parameter lists.
New in version 2.1.
Deprecated since version 2.2.
declaration ::= "foreign"variables
";" variables ::=variable
variables ::=variables
","variable
Foreign declarations are used to let the SNL compiler know about the existence of C variables or C preprocessor macros (without parameters) 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.
Changed in version 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 Postfix Operators below.
Also, warnings for undefined entities are now disabled by default. You
can use the “extra warnings” option +W
to enable them. In
contrast to older versions, this will also warn about foreign functions
when called from SNL code. Note that enabling extra warnings is normally
not needed: if a variable or function is actually undefined in the
generated C code, the C compiler will issue a warning (or an error)
anyway.
These are the allowed base types, of which you can declare variables, or pointers or arrays etc.
basetype ::= prim_type
basetype ::= "evflag"
New in version 2.2.
basetype ::= "enum"identifier
basetype ::= "struct"identifier
basetype ::= "union"identifier
basetype ::= "typename"identifier
basetype ::= "void"
You can use enumerations, structs, unions and typedefs in declarations and type expressions. The “void” type is also allowed, subject to the same restrictions as in 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.
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.
New in version 2.1.
Since the standard numeric types in C have implementation 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.
New in version 2.2.
type_expr ::=basetype
type_expr ::=basetype
abs_decl
abs_decl ::= "("abs_decl
")" abs_decl ::=subscript
abs_decl ::=abs_decl
subscript
abs_decl ::= "("param_decls
")" abs_decl ::=abs_decl
"("param_decls
")" abs_decl ::= "*" abs_decl ::= "*"abs_decl
abs_decl ::= "const" abs_decl ::= "const"abs_decl
param_decls ::= param_decls ::=param_decl
param_decls ::=param_decls
","param_decl
param_decl ::=basetype
declarator
param_decl ::=type_expr
Type expressions closely follow declaration syntax just as in C. They are used for parameter declarations as well as type casts and the special sizeof operator (see Prefix Operators).
Using “const” in type expressions is subject to the same restrictions as in declarations (see Variable Declarations).
The type 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 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 are values of an abstract data type with four operations
defined on them: efSet
, efClear
, efTest
,
and 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 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.
Variables are statically scoped, i.e. they are visible and accessible to the
program only inside the smallest 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.
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.
The syntactic constructs in this section allow program variables to be connected to externally defined process variables.
assign ::= "assign"variable
to
string
";" assign ::= "assign"variable
subscript
to
string
";" assign ::= "assign"variable
to
"{"strings
"}" ";" assign ::= "assign"variable
";" 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 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.
New in version 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
Variable Scope and Life Time. 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 an extra argument on invocation, see seq
);
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 pvAssign
.
Note
An assign
clause using an empty string for the PV name is
interpreted differently in Safe Mode, see Anonymous Channels.
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 or the native count for the underlying process 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.
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.
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 monitor
ed), or when an
asynchronous get or put operation completes, the corresponding event
flag is set.
The variable must be assign
ed and 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.
Changed in version 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 pvSync
.
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 pvGetQ
function reads items from the
queue.
The variable must be assign
ed and 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.
New in version 2.1.
You can use “syncq” (all lower case) as keyword instead of “syncQ”. The latter may be deprecated in a future version.
Changed in version 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.)
Changed in version 2.1.
A queued variable no longer needs to be associated with an event flag.
The first form of the syncq
clause is now merely an
abbreviation for a sync
clause together with a 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 pvGetQ
already checks and returns whether the queue is
empty or not; and any state set that mentions a variable in a
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 pvGetQ
might have intervened and emptied
the queue between the two calls.
Note that pvGetQ
clears an event flag associated with the variable if
the queue becomes empty after removing the head element.
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 Compiler Options). 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_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.
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 (assign
, monitor
, sync
, and
syncq
).
See variable scope for details on what local definitions mean.
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_defn ::=assign
state_defn ::=monitor
state_defn ::=sync
state_defn ::=syncq
state_defn ::=declaration
state_defn ::=option
The syntax for a state option is the same as for global options (see Option).
The state options are:
Reset delay timers each time the state is entered, even if entered from the same state. This is the default.
Don’t reset delay timers when entering from the same state. In other
words, the 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.
Execute entry
blocks only if the previous state was
not the same as the current state. This is the default.
Execute entry
blocks even if the previous state was the same
as the current state.
Execute exit
blocks only if the next state is not the same as
the current state. This is the default.
Execute 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 { ... }
}
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
condition
s for state transitions
are evaluated.
Exit blocks are executed when the state is left, after the transition
block that determines the next state.
transitions ::=transitions
transition
transitions ::=transition
transition ::= "when" "("condition
")"block
"state"identifier
condition ::=opt_expr
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
.
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 condition
s 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
a delay
timer expires
an event flag is set or cleared
a process variable connects or disconnects
While the sequencer’s runtime system and the compiler work together to
minimize evaluation of conditions, there is no guarantee whatsoever about
the number of times conditions might be evaluated before one of them returns
true
.
Conditions are usually written so that they have no side-effects. This ensures that it does not matter how often they are evaluated or in which order.
Note
If a condition has a side-effect, care should be taken to ensure that the
effect is limited to the case when the whole(!) condition fires (returns
true
), so that it will occur at most once. Otherwise you will have no
control over when and how often the effect happens.
Built-in functions that have side-effects and are suitable for use in
conditions, notably efTestAndClear
and pvGetQ
, meet this criterion,
but only of used by themselves alone. If used as part of a larger
expression, the whole condition might no longer meet the citerion.
New in version 2.1.
transition ::= "when" "("condition
")"block
"exit"
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 seqStop
shell 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 ::= "{"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 ::=statements
statement
statements ::= statement ::= "break" ";" statement ::= "continue" ";" statement ::=c_code
statement ::=block
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 is the switch/case statement.
New in version 2.1.
statement ::= "state" identifier
";"
The state change statement is not borrowed from C; it is only available
in the action block of a state 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.
New in version 2.2.
statement ::= "return" opt_expr
";"
The return statement can be used only inside Function Definitions.
Formation rules for expressions are listed in groups of descending order of precedence. Precedence is determined by the standard C operator precedence table.
expr ::=integer_literal
expr ::=floating_point_literal
expr ::=string
expr ::=variable
string ::=string_literal
expr ::= "("comma_expr
")"
These are literals, variables, and parenthesized expressions.
expr ::=expr
"("args
")" expr ::= "exit" "("args
")" expr ::=expr
"["expr
"]" expr ::=expr
"."member
expr ::=expr
"->"member
expr ::=expr
"++" expr ::=expr
"--" member ::=identifier
These are all left associative.
Note
exit
must be listed explicitly because in SNL it is a keyword,
not an identifier. The extra rule allows calls to the standard
library function exit
.
expr ::= "+"expr
expr ::= "-"expr
expr ::= "*"expr
expr ::= "&"expr
expr ::= "!"expr
expr ::= "~"expr
expr ::= "++"expr
expr ::= "--"expr
Prefix operators are right-associative.
New in version 2.2: Type casts and sizeof operator:
expr ::= "("type_expr
")"expr
expr ::= "sizeof" "("type_expr
")" expr ::= "sizeof"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
"%"expr
All binary operators associate to the left.
Note
Like in most programming languages, including C, 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 transition
clauses. See the built-in function
pvGetQ
for an extended discussion.
expr ::=expr
"?"expr
":"expr
The ternary operator (there is only one) is right-associative.
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_expr ::=comma_expr
","expr
comma_expr ::=expr
opt_expr ::=comma_expr
opt_expr ::=
The comma operator is left associative. An opt_expr
is
an optional comma_expr
; it appears, for instance, inside
a for_statement
.
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.
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.
Changed in version 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.
An enumeration for status values.
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;
typedef enum {
/* generic OK and error severities */
pvSevrOK = 0,
pvSevrERROR = -1,
/* correspond to EPICS severities */
pvSevrNONE = 0,
pvSevrMINOR = 1,
pvSevrMAJOR = 2,
pvSevrINVALID = 3
} pvSevr;
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).
typedef int seqBool;
#define TRUE 1
#define FALSE 0
#define NOEVFLAG 0
This can be given as second argument to pvSync
(instead of
an event flag) to cancel the sync.
The following special functions are built into the language. In most cases the compiler performs some special interpretation of the arguments to these functions. Using their C equivalents in escaped C code is possible but subject to special rules.
For documentation purposes only, we assign special pseudo-types to some of the parameters of built-in functions. Note that these are not valid SNL types.
A parameter with this type means the function expects an SNL variable or array element that is assigned to a single (possibly anonymous) process variable.
A parameter with type channel[]
means the function expects a channel
array, that is, an array where the elements are assigned to separate
(possibly anonymous) process variables. By convention, functions accepting a
channel
array have a name that starts with “pvArray”.
Several of these functions are intended to be called only from
transition
clauses or only from action code. If the compiler allows
it, it is safe to call them in another context, but the effect might not be
what you expect.
Returns whether the specified time has elapsed since entering the
state. It should be used only within a transition
expression.
The “t” State Option controls whether the delay is measured from when the current state was entered from a different state (-t) or from any state, including itself (+t, the default).
Changed in version 2.2.
It is no longer allowed to call this function outside the condition
of a
transition
clause.
Puts (or writes) the value of ch
to the process variable it has been
assigned to. Returns the status from the PV layer (e.g. pvStatOK
for success).
By default, i.e. with no optional arguments, pvPut
is un-confirmed “fire
and forget”; completion must be inferred by other means. An optional second
argument can change this default:
SYNC
causes it to block the state set until completion. This
mode is called synchronous.
ASYNC
allows the state set to continue but still check for
completion via a subsequent call to pvPutComplete
(typically in a
condition
). This mode is called asnchronous.
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.
Note that SNL allows only one pending 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 Safe Mode, 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 condition
.
Note that for anonymous PVs completion is always immediate, so the ASYNC
option
is not very useful.
Changed in version 2.2.
Calling this function with a multi-PV array is no longer allowed and results in a compile-time error.
Returns whether the last asynchronous pvPut
to this process variable has
completed.
Always returns true
for anonymous PVs.
Changed in version 2.2.
Calling this function with a multi-PV array is no longer allowed and results
in a compile-time error. In previous versions this was the one built-in pv
function that correctly worked on channel arrays. The corresponding C
function still has the extra arguments for compatibility, but in SNL code
you must now use pvArrayPutComplete
for channel arrays.
New in version 2.2.
Like pvPutComplete
, but returns whether all of the length
first
elements of the array have completed their last asynchronous pvPut
. The
length
argument should not be larger than the number of elements in
the array.
If the optional any
argument is true
, then instead it returns whether
any of these channels have completed.
If the optional complete
argument is not NULL
, it must point to an
array of seqBool
s with at least length
elements; the will then
write the individual results for the array elements into this array.
New in version 2.2.
Cancel a pending (asynchronous) pvPut
.
New in version 2.2.
Like pvPutCancel
but all pending asynchronous pvPut
s to the first
length
elements of the array are cancelled.
Gets (or reads) the value of ch
from the process variable it has been
assigned to. Returns the status from the PV layer (e.g. pvStatOK
for success).
With no optional arguments, the completion type is determined by the value
of a program option: if -a
is in effect (the default), then the state set
will block until the read operation is complete or until the default timeout
of 10 seconds has expired; if +a
is in effect, the the call completes
immediately and completion can be checked with subsequent calls to
pvGetComplete
(typically in a condition
).
An optional second argument overrides the program option: SYNC
makes the call block and ASYNC
makes the call return
immediately, regardless of whether -a
or +a
is in effect.
In 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 transition
clause. Note that anonymous PVs behave exactly in the same way.
Like for pvPut
, only one pending pvGet
per channel and state set can be
active.
Changed in version 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.
Changed in version 2.2.
Calling this function with a multi-PV array is no longer allowed and results in a compile-time error.
Returns whether the last asynchronous pvGet
for ch
has completed.
In 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).
Always returns true
for anonymous PVs.
Changed in version 2.2.
Calling this function with a multi-PV array is no longer allowed and results
in a compile-time error. For use with multi-PV arrays there is now
pvArrayGetComplete
.
New in version 2.2.
Like pvGetComplete
but works on all channels of a multi-PV array. The
optional arguments have the same meaning as for pvArrayPutComplete
.
New in version 2.2.
Cancel a pending (asynchronous) pvGet
.
New in version 2.2.
Cancel pending (asynchronous) pvGet
operations for the first length
elements of the array.
The channel argument must have been associated with a queue using the
syncq
clause.
If the queue 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 sync
ed to the variable will be
cleared.
Note that since pvGetQ may have a side-effect you should be careful when
combining a call to pvGetQ with other condition
s in the same
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 transition
clause
that potentially has a side-effect.
Changed in version 2.2.
Calling this function with a multi-PV array is no longer allowed and results in a compile-time error.
Deletes all entries from a queued variable’s queue and clears the associated event flag.
Changed in version 2.1.
Queue elements are no longer dynamically allocated, so this is now an alias for pvFlushQ.
New in version 2.1.
Flush the queue associated with this variable, so it is empty afterwards.
Changed in version 2.2.
Calling this function with a multi-PV array is no longer allowed and results in a compile-time error.
Assigns or re-assigns ch
to a process variable with name pv_name
. If
pv_name
is an empty string or NULL
, then ch
is de-assigned (not
associated with any process variable); in Safe Mode it causes assignment
to an anonymous PV.
See also assign
clause.
Note that pvAsssign is asynchronous: it sends a request to search for and
connect to the given pv_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 pvConnected
built-in function inside a transition
clause.
Like for all other operations, completion is immediate for anonymous PVs.
Note
pvAssign can only be called on variables (or array elements)
that have been statically marked as process variables using the
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
assign
clause. Instead, you must call pvAssign
for each array element individually.
Changed in version 2.2.
Calling this function with a multi-PV array is no longer allowed and results in a compile-time error.
Like pvAssign
, except that it substitutes program parameters, like the
assign
clause.
Initiates a monitor on the process variable that ch
was assigned to.
See monitor
clause.
Changed in version 2.2.
Calling this function with a multi-PV array is no longer allowed and results in a compile-time error.
New in version 2.2.
Like pvMonitor
but turns on monitors for the first length
elements of
a channel array.
See monitor
clause.
Terminates a monitor on the underlying process variable.
Changed in version 2.2.
Calling this function with a multi-PV array is no longer allowed and results in a compile-time error.
New in version 2.2.
Like pvStopMonitor
but turns off monitors for the first length
elements of a channel array.
Synchronizes a variable with an event flag, or removes such a
synchronization if ef
is NOEVFLAG
.
See sync
clause.
Changed in version 2.2.
Calling this function with a multi-PV array is no longer allowed and results in a compile-time error.
New in version 2.2.
Synchronizes the first length
elements of a channel array with an event
flag, or removes such a synchronization if ef
is NOEVFLAG
.
See sync
clause.
Returns the element count associated with the (single) process variable. This value is independent of the array size given in the declaration, it can be smaller or larger.
Changed in version 2.2.
Calling this function with a multi-PV array is no longer allowed and results in a compile-time error.
Returns the current alarm status of the underlying PV or indicates failure of a previous PV operation.
The status, severity, and message returned by pvStatus
,
pvSeverity
, and pvMessage
reflect either the underlying
PV’s properties (if a pvPut
or pvGet
operation completed, or the variable is
monitor
ed), or else indicate a failure to initiate one of these operations.
Changed in version 2.2.
Calling this function with a multi-PV array is no longer allowed and results in a compile-time error.
Returns the current alarm severity (e.g. pvSevrMAJOR) of the underlying PV or indicates failure of a previous PV operation.
Changed in version 2.2.
Calling this function with a multi-PV array is no longer allowed and results in a compile-time error.
Returns the current error message of the variable, or “” (the empty string) if none is available.
Changed in version 2.2.
Calling this function with a multi-PV array is no longer allowed and results in a compile-time error.
Returns the time stamp for the last pvGet
completion or monitor
event for this variable.
New in version 2.2.
You can declare variables of type struct epicsTimeStamp
directly in SNL
code.
Changed in version 2.2.
Calling this function with a multi-PV array is no longer allowed and results in a compile-time error.
Returns whether the SNL variable is currently assigned to a process variable.
Note that this function returns FALSE
for anonymous PVs.
Changed in version 2.2.
Calling this function with a multi-PV array is no longer allowed and results in a compile-time error.
Returns whether the underlying process variable is currently connected.
Changed in version 2.2.
Always returns true
for anonymous PVs.
Calling this function with a multi-PV array is no longer allowed and results in a compile-time error.
New in version 2.2.
Returns whether elements of a channel array are currently connected.
Returns the index associated with a variable. See Calling PV Functions from C for how to use this function.
Changed in version 2.2.
Calling this function with a multi-PV array is no longer allowed and results in a compile-time error.
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)
).
Returns the total number of process variables associated with the program.
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
pvAssignCount
.
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()
Sets the event flag and causes evaluation of the transition
clauses for all state sets that are pending on this event flag.
Clears the event flag and causes evaluation of the transition
clauses for all state sets that are pending on this 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 sync
ed with it, i.e. the
state set local copy of these variables will be updated from their
current globally visible value.
Clears the event flag and returns whether the event
flag was set. It is intended for use within a transition
clause.
Note
In safe mode, this function is a synchronization point for all
variables (channels) that are sync
ed with it, i.e. the
state set local copy of these variables will be updated from their
current globally visible value.
Returns a pointer to the value of the specified program parameter, if
it exists, else NULL
. See Program Name and Parameters.
Returns whether the program option with the given name is in effect or not.
The option name is a string consisting of a single letter such as “a” for
the program option +a
/-a
.
New in version 2.1.
SNL code can be interpreted in safe mode by using the program option +s
,
see Safe Mode for a less dense introduction. Safe mode implies
reentrant mode, see Variable Modification for Reentrant Option.
In safe mode, global variables are treated as if they were local to the
state set. Changing such a variable inside an action block does not have any
immediate effect on other state sets, nor do external events, such as
monitors or pvGet completions, change the variables as seen by a state set,
except at well defined Synchronization Points. In other words, each state
set works on its own copy (“view”) of all variables. The Channel Access
layer (and the part of the run time system that implements Anonymous Channels) acts on yet another copy, the “world view”. Information is only
ever exchanged between the world view and the state set local view. All
communication between state sets must be explicit: apart from using event
flags, the only way information can flow out of a state set is using
pvPut
, the only way in is via pvGet
or monitors.
An important (and welcome) side-effect of safe mode is that in the action
block after a condition you can rely on the condition
to be true
(and
remain true
– unless, of course, the action block itself invalidates it
by modifying the variables involved).
At certain points in the program a state set’s view of the variables is updated from the world view. These are:
in each state, immediately before condition
s are evaluated (for
monitored channels), and
in calls to some of the built-in functions: efTest
, efTestAndClear
,
pvGet
(in SYNC mode), and pvGetComplete
(when it signals completion).
Note that there is no point at which variables modified by a state set are
automatically “published”. For this you have to use pvPut
explicitly,
which updates the world view as a side-effect.
It would be impractical if the programmer had to create a real PV for each
communication channel inside a program. Instead, in safe mode you can use an
anonymous channel for this purpose. Anonymous channels are created with
the usual assign
clause, but use an empty string (""
) for the PV name
(which in traditional mode is interpreted as “not assigned”). You can
imagine such channels to be connected to “virtual” PVs that are implemented
inside the sequencer’s run time system.
Anonymous channels should work exactly like channels assigned to a “real” PV
– they can be monitor
ed, sync
ed to event flags etc. – except that
they are always connected and the operations on them always complete
immediately.
Apart from the sync
and syncq
features, and apart from the atomicity of
efTestAndClear
, an event flag behaves like an anonymous PV of boolean type
with a monitor. In safe mode, efTest
and efTestAndClear
have the
additional side-effect of acting as a synchronization point for all
variables that are sync
ed with the event flag.
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. We call such entities or objects “foreign”.
It is advisable to take care that the C code generated from your SNL program
contains (directly or via #include
) a valid declaration for foreign
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"
(See also Preprocessor Directives.)
Note that when passing channel variables (i.e. ones assigned to one or more
PVs using an assign
clause) to a C function, only the value is
passed. The special assigned” status of the variable gets lost as soon as
the function is entered (same as for functions defined in SNL).
Changed in version 2.2.
The SNL compiler no longer complains about “undeclared identifiers” when
using foreign variables unless you supply the +W
(extra warnings)
option.
Comments¶
C-style comments may be placed anywhere in the program. They are treated as white space. As in C, comments cannot be nested.
New in version 2.2.
C++ style comments are allowed, too.