SNL Reference for Version 2.2

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.

Lexical Syntax

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

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

Comments

comment ::=  "/*" <anything> "*/"
comment ::=  "//" <anything until end of line>

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.

Identifiers

identifier ::=  <same as in C>

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

Literals

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

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 Markers

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 ::=  "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.

Program Name and Parameters

The program name is an identifier. It is used as the name of the global variable which contains or points to all the program data structures (the address of this global variable is passed to the seq function when creating the run-time sequencer). It is also used as the base for the state set thread names unless overridden via the name parameter (see 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.

Global Entry and Exit Blocks

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.

Final C Code Block

c_code ::=  embedded_c_code

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

Definitions

Function Definitions

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.

Type Definitions

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).

Variable Declarations

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.

const

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.

Foreign Declarations

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.

Basic Types

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.

Type Expressions

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).

Strings

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

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.

See Synchronizing State Sets with Event Flags.

Variable Scope and Life Time

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.

Variable Initialization

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

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

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

Process Variables

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

assign

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       ::=  "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       ::=  "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 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      ::=  "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 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" 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 Set

state_sets ::=  state_sets state_set
state_sets ::=  state_set
state_set  ::=  "ss" identifier "{" ss_defns states "}"
ss_defns   ::=  ss_defns ss_defn
ss_defns   ::= 

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

State set names must be unique in the program.

State Set Local Definition

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.

State

states      ::=  states state
states      ::=  state
state       ::=  "state" identifier "{" state_defns entry transitions exit "}"
state_defns ::=  state_defns state_defn
state_defns ::= 

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

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

State Local Definition

state_defn ::=  assign
state_defn ::=  monitor
state_defn ::=  sync
state_defn ::=  syncq
state_defn ::=  declaration
state_defn ::=  option

State Option

The syntax for a state option is the same as for global options (see Option).

The state options are:

+t

Reset delay timers each time the state is entered, even if entered from the same state. This is the default.

-t

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.

+e

Execute entry blocks only if the previous state was not the same as the current state. This is the default.

-e

Execute entry blocks even if the previous state was the same as the current state.

+x

Execute exit blocks only if the next state is not the same as the current state. This is the default.

-x

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 { ... }
}

State Entry and Exit Blocks

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

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

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

Transitions

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

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

  2. when an event happens (see below).

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

There are five types of event:

  • a process variable monitor is posted

  • an asynchronous pvGet or pvPut completes

  • 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       ::=  "{" block_defns statements "}"
block_defns ::=  block_defns block_defn
block_defns ::= 
block_defn  ::=  declaration
block_defn  ::=  c_code

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

Block definitions are: declarations and embedded C code.

Statements and Expressions

Statements

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.

Expressions

Formation rules for expressions are listed in groups of descending order of precedence. Precedence is determined by the standard C operator precedence table.

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.

Postfix Operators

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.

Prefix Operators

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

Binary Operators

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.

Ternary Operator

expr ::=  expr "?" expr ":" expr

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

Assignment Operators

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

These operators are right-associative.

Comma Operator

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.

Argument List

args ::=  args "," expr
args ::=  expr
args ::= 

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

Built-in Constants

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

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.

type pvStat

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;
type pvSevr
typedef enum {
    /* generic OK and error severities */
    pvSevrOK      = 0,
    pvSevrERROR   = -1,

    /* correspond to EPICS severities */
    pvSevrNONE    = 0,
    pvSevrMINOR   = 1,
    pvSevrMAJOR   = 2,
    pvSevrINVALID = 3
} pvSevr;
type compType
enum compType {
    DEFAULT,
    ASYNC,
    SYNC
};

Note

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

type seqBool
typedef int seqBool;

#define TRUE   1
#define FALSE  0
NOEVFLAG
#define NOEVFLAG 0

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

Built-in Functions

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.

type channel

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.

delay

seqBool delay(double delay_in_seconds)

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.

pvPut

pvStat pvPut(channel ch, compType mode = DEFAULT, double timeout = 10.0)

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.

pvPutComplete

seqBool pvPutComplete(channel ch)

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.

pvArrayPutComplete

New in version 2.2.

seqBool pvArrayPutComplete(channel ch[], unsigned int length, seqBool any = FALSE, seqBool *complete = NULL)

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.

pvPutCancel

New in version 2.2.

void pvPutCancel(channel ch)

Cancel a pending (asynchronous) pvPut.

pvArrayPutCancel

New in version 2.2.

void pvArrayPutCancel(channel ch[], unsigned int length)

Like pvPutCancel but all pending asynchronous pvPuts to the first length elements of the array are cancelled.

pvGet

pvStat pvGet(channel ch, compType ct = DEFAULT, double timeout = 10.0)

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.

pvGetComplete

seqBool pvGetComplete(channel ch)

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.

pvArrayGetComplete

New in version 2.2.

seqBool pvArrayGetComplete(channel ch[], unsigned int length, seqBool any = FALSE, seqBool *complete = NULL)

Like pvGetComplete but works on all channels of a multi-PV array. The optional arguments have the same meaning as for pvArrayPutComplete.

pvGetCancel

New in version 2.2.

void pvGetCancel(channel ch)

Cancel a pending (asynchronous) pvGet.

pvArrayGetCancel

New in version 2.2.

void pvArrayGetCancel(channel ch[], unsigned int length)

Cancel pending (asynchronous) pvGet operations for the first length elements of the array.

pvGetQ

seqBool pvGetQ(channel ch)

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.

pvFreeQ

void pvFreeQ(queued_channel)

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.

pvFlushQ

void pvFlushQ(queued_channel)

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.

pvAssign

pvStat pvAssign(channel ch, char *pv_name)

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.

pvAssignSubst

pvStat pvAssignSubst(channel ch, char *pv_name)

Like pvAssign, except that it substitutes program parameters, like the assign clause.

pvMonitor

pvStat pvMonitor(channel ch)

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.

pvArrayMonitor

New in version 2.2.

pvStat pvArrayMonitor(channel ch[], unsigned int length)

Like pvMonitor but turns on monitors for the first length elements of a channel array.

See monitor clause.

pvStopMonitor

pvStat pvStopMonitor(channel ch)

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.

pvArrayStopMonitor

New in version 2.2.

pvStat pvArrayStopMonitor(channel ch[], unsigned int length)

Like pvStopMonitor but turns off monitors for the first length elements of a channel array.

pvSync

void pvSync(channel ch, evflag ef)

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.

pvArraySync

New in version 2.2.

void pvArraySync(channel ch[], unsigned int length, evflag ef)

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.

pvCount

unsigned pvCount(channel ch)

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.

pvStatus

pvStat pvStatus(channel ch)

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.

pvSeverity

pvSevr pvSeverity(channel ch)

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.

pvMessage

const char *pvMessage(channel ch)

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.

pvTimeStamp

struct epicsTimeStamp pvTimeStamp(channel ch)

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.

pvAssigned

seqBool pvAssigned(channel ch)

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.

pvConnected

seqBool pvConnected(channel ch)

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.

pvArrayConnected

New in version 2.2.

seqBool pvArrayConnected(channel ch[], unsigned int length)

Returns whether elements of a channel array are currently connected.

pvIndex

unsigned pvIndex(channel ch)

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.

pvFlush

void pvFlush()

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

pvChannelCount

unsigned pvChannelCount()

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

pvAssignCount

unsigned pvAssignCount()

Returns the total number of SNL variables in this program that are assigned to underlying process variables.

For instance, if all SNL variables are assigned then the following expression is true:

pvAssignCount() == pvChannelCount()

Each element of an SNL array counts as variable for the purposes of pvAssignCount.

pvConnectCount

unsigned pvConnectCount()

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

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

pvConnectCount() == pvAssignCount()

efSet

void efSet(evflag ef)

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

efClear

seqBool efClear(evflag ef)

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

efTest

seqBool efTest(evflag ef)

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.

efTestAndClear

seqBool efTestAndClear(evflag ef)

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.

macValueGet

char *macValueGet(char *parameter_name)

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

optGet

seqBool optGet(char *option_name)

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.

Safe Mode

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).

Synchronization Points

At certain points in the program a state set’s view of the variables is updated from the world view. These are:

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.

Anonymous Channels

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.

Event Flags in Safe Mode

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.

Foreign Variables and Functions

You can use (reference, call) any exported C variable or function that is linked to your program. For instance, you may link a C library to the SNL program and simply call functions from it directly in SNL code. 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.