Primary Expressions¶
expr ::=integer_literalexpr ::=floating_point_literalexpr ::=stringexpr ::=variablestring ::=string_literalexpr ::= "("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"identifierprogram_paraminitial_defnsentrystate_setsexitfinal_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_defnsinitial_defninitial_defns ::= initial_defn ::=assigninitial_defn ::=monitorinitial_defn ::=syncinitial_defn ::=syncqinitial_defn ::=declarationinitial_defn ::=optioninitial_defn ::=funcdefinitial_defn ::=structdefinitial_defn ::=c_code
final_defns ::=final_defnsfinal_defnfinal_defns ::= final_defn ::=funcdeffinal_defn ::=structdeffinal_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"blockentry ::= exit ::= "exit"blockexit ::=
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 ::=basetypedeclaratorblock
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"identifiermembers";" members ::= "{"member_decls"}" member_decls ::=member_declmember_decls ::=member_declsmember_declmember_decl ::=basetypedeclarator";" 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 ::=basetypeinit_declarators";" init_declarators ::=init_declaratorinit_declarators ::=init_declarators","init_declaratorinit_declarator ::=declaratorinit_declarator ::=declarator"="init_exprdeclarator ::=variabledeclarator ::= "("declarator")" declarator ::=declaratorsubscriptdeclarator ::=declarator"("param_decls")" declarator ::= "*"declaratordeclarator ::= "const"declaratorvariable ::=identifierinit_expr ::= "{"init_exprs"}" init_expr ::= "("type_expr")" "{"init_exprs"}" init_expr ::=exprinit_exprs ::=init_exprs","init_exprinit_exprs ::=init_exprinit_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 ::=variablevariables ::=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"identifierbasetype ::= "struct"identifierbasetype ::= "union"identifierbasetype ::= "typename"identifierbasetype ::= "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 ::=basetypetype_expr ::=basetypeabs_declabs_decl ::= "("abs_decl")" abs_decl ::=subscriptabs_decl ::=abs_declsubscriptabs_decl ::= "("param_decls")" abs_decl ::=abs_decl"("param_decls")" abs_decl ::= "*" abs_decl ::= "*"abs_declabs_decl ::= "const" abs_decl ::= "const"abs_declparam_decls ::= param_decls ::=param_declparam_decls ::=param_decls","param_declparam_decl ::=basetypedeclaratorparam_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"variabletostring";" assign ::= "assign"variablesubscripttostring";" assign ::= "assign"variableto"{"strings"}" ";" assign ::= "assign"variable";" to ::= "to" to ::= strings ::=strings","stringstrings ::=stringstrings ::= 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"variableopt_subscript";" opt_subscript ::=subscriptopt_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"variableopt_subscripttoevent_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 monitored), or when an
asynchronous get or put operation completes, the corresponding event
flag is set.
The variable must be assigned and monitored.
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"variableopt_subscripttoevent_flagsyncq_size";" syncq ::= "syncq"variableopt_subscriptsyncq_size";" syncq_size ::=integer_literalsyncq_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 assigned and monitored.
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_valueidentifier";" 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_setsstate_setstate_sets ::=state_setstate_set ::= "ss"identifier"{"ss_defnsstates"}" ss_defns ::=ss_defnsss_defnss_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 ::=assignss_defn ::=monitorss_defn ::=syncss_defn ::=syncqss_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 ::=statesstatestates ::=statestate ::= "state"identifier"{"state_defnsentrytransitionsexit"}" state_defns ::=state_defnsstate_defnstate_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 ::=assignstate_defn ::=monitorstate_defn ::=syncstate_defn ::=syncqstate_defn ::=declarationstate_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
conditions for state transitions are evaluated.
Exit blocks are executed when the state is left, after the transition
block that determines the next state.
transitions ::=transitionstransitiontransitions ::=transitiontransition ::= "when" "("condition")"block"state"identifiercondition ::=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 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
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 ::=statementsstatementstatements ::= statement ::= "break" ";" statement ::= "continue" ";" statement ::=c_codestatement ::=blockstatement ::= "if" "("comma_expr")"statementstatement ::= "if" "("comma_expr")"statement"else"statementstatement ::= "while" "("comma_expr")"statementstatement ::=for_statementstatement ::=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_literalexpr ::=floating_point_literalexpr ::=stringexpr ::=variablestring ::=string_literalexpr ::= "("comma_expr")"
These are literals, variables, and parenthesized expressions.
expr ::=expr"("args")" expr ::= "exit" "("args")" expr ::=expr"["expr"]" expr ::=expr"."memberexpr ::=expr"->"memberexpr ::=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 ::= "+"exprexpr ::= "-"exprexpr ::= "*"exprexpr ::= "&"exprexpr ::= "!"exprexpr ::= "~"exprexpr ::= "++"exprexpr ::= "--"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"-"exprexpr ::=expr"+"expr
expr ::=expr"<<"exprexpr ::=expr">>"expr
expr ::=expr"*"exprexpr ::=expr"/"expr
expr ::=expr">"exprexpr ::=expr">="exprexpr ::=expr"<="exprexpr ::=expr"<"expr
expr ::=expr"=="exprexpr ::=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"="exprexpr ::=expr"+="exprexpr ::=expr"-="exprexpr ::=expr"&="exprexpr ::=expr"|="exprexpr ::=expr"/="exprexpr ::=expr"*="exprexpr ::=expr"%="exprexpr ::=expr"<<="exprexpr ::=expr">>="exprexpr ::=expr"^="expr
These operators are right-associative.
comma_expr ::=comma_expr","exprcomma_expr ::=expropt_expr ::=comma_expropt_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","exprargs ::=exprargs ::=
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 seqBools 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 pvPuts 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 synced 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 conditions 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
monitored), 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 synced 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 synced 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 conditions 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 monitored, synced 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 synced 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.