SNL Reference ============= This chapter gives a detailed reference for the SNL syntax and semantics and for the built-in functions. Formal syntax is given in `BNF`_. Multiple rules for the same `nonterminal symbol`_ mean that any of the given rules may apply. `Terminal symbols`_ are enclosed in double quotes. .. _BNF: http://en.wikipedia.org/wiki/Backus-Naur_Form .. _nonterminal symbol: http://en.wikipedia.org/wiki/Terminal_and_nonterminal_symbols#Nonterminal_symbols .. _terminal symbols: http://en.wikipedia.org/wiki/Terminal_and_nonterminal_symbols#Terminal_symbols Lexical Syntax -------------- Comments ^^^^^^^^ .. productionlist:: comment: "/*" "*/" C-style comments may be placed anywhere in the program. They are treated as white space by *snc*. As in C, comments cannot be nested. Identifiers ^^^^^^^^^^^ .. productionlist:: identifier: Identifiers follow the same rules as in C. They are used for variables (including foreign variables and event flags), the program name, states, state sets, and options. Literals ^^^^^^^^ .. productionlist:: integer_literal: floating_point_literal: string_literal: The lexical syntax of identifiers, as well as numeric and string literals is exactly as in C, including automatic string concatenation, character literals, and octal, decimal, and hexadecimal integer literals. Embedded C Code ^^^^^^^^^^^^^^^ .. productionlist:: embedded_c_code: "%{" "}%" embedded_c_code: "%%" "\n" A sequence of characters enclosed between "%{" and "}%" is used literally and without further parsing as if it were a complete declaration or statement, depending on where it appears. A sequence of characters enclosed between "%%" and the next line ending is treated similarly, except that it is stripped of leading and trailing whitespace and inserted in the output with the current indentation. See `Escape to C Code`_ for examples and rationale. Embedded C code fragments are causing two of the three conflicts in the grammar. The reason is that the parser cannot decide whether such a fragment is a declaration or a statement. Line Markers ^^^^^^^^^^^^ .. productionlist:: line_marker: "#" line_number "\n" line_marker: "#" line_number file_name "\n" line_number: file_name: Line markers are interpreted by *snc* exactly as in C, i.e. they indicate that the following symbols are really located in the given source file (if any) at the given line. .. note:: :token:`line_number` may only contain decimal numbers, and :token:`file_name` must be a single string (no automatic string concatenation). Line markers are typically generated by preprocessors, such as CPP. .. _StateProgram: Program ------- .. productionlist:: program: "program" `identifier` `program_param` `global_defns` `entry` `state_sets` `exit` `c_codes` This is the overall structure of an SNL program. After the keyword "program" comes the name of the program, followed by an optional program parameter, global definitions, an optional entry block, the state sets, an optional exit block, and finally some embedded C code. Program Name and Parameter ^^^^^^^^^^^^^^^^^^^^^^^^^^ The program name is an identifier. It is used as the name of the global variable which contains or points to all the program data structures (the address of this global variable is passed to the ``seq`` function when creating the run-time sequencer). It is also used as the base for the state set thread names unless overridden via the *name* parameter (see :ref:`run time parameters`). .. productionlist:: program_param: "(" `string` ")" program_param: The program name may be followed by an optional string enclosed in parentheses. The string content must be a list of comma-separated parameters in the same form as they are specified on the command line (see :ref:`run time parameters`). .. note:: Command-line parameters override those specified in the program. .. productionlist:: global_defns: `global_defns` `global_defn` global_defns: global_defn: `assign` global_defn: `monitor` global_defn: `sync` global_defn: `syncq` global_defn: `declaration` global_defn: `option` global_defn: `c_code` Global (top-level) definitions, see `Definitions`_ for details. .. _GlobalEntryExit: Global Entry and Exit Blocks ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. productionlist:: entry: "entry" `block` entry: exit: "exit" `block` exit: An SNL program may specify optional entry code to run prior to running the state sets, and exit code to run after all state sets have exited. Both are run in the context of the first state set thread. (Global entry and exit blocks should not be confused with :ref:`state entry and exit blocks`, which have the same syntax but are executed at each transition from/to a new state.) The entry or exit code is a regular SNL code block and thus can contain local variable declarations. Global entry code is always executed after initiating connections to (named) PVs, and exit code is executed before connections are shut down. Furthermore, if the global :option:`+c` option is in effect, the entry code is executed only after all channels are connected and monitored channels have received their first monitor event. All built-in PV functions can be expected to work in global entry and exit blocks. Note that global entry and exit blocks operate as if executed as part of the first state set. This means that in :ref:`safe mode` changes to global variables made from inside the entry block are not visible to state sets other than the first unless explicitly communicated (e.g. by calling pvPut). The fact that the first state set plays a special role is a historical accident, rather than conscious design. I might be tempted to redesign this aspect in a future version, for instance by giving entry and exit blocks their own dedicated virtual state set. Final C Code Block ^^^^^^^^^^^^^^^^^^ .. productionlist:: c_codes: `c_codes` `c_code` c_codes: c_code: `embedded_c_code` Any number of embedded C code blocks may appear after state sets and the optional exit block. See `Escape to C Code`_. .. _Definitions: Definitions ----------- Declarations ^^^^^^^^^^^^ .. productionlist:: declaration: `type` `init_declarators` ";" init_declarators: `init_declarator` init_declarators: `init_declarators` "," `init_declarator` init_declarator: `declarator` init_declarator: `declarator` "=" `init_expr` declarator: "*" `declarator` declarator: `direct_declarator` direct_declarator: `variable` direct_declarator: "(" `declarator` ")" direct_declarator: `direct_declarator` `subscript` init_expr: "{" `init_exprs` "}" init_expr: `expr` init_exprs: `init_exprs` "," `init_expr` init_exprs: `init_expr` init_exprs: Variable declarations are quite similar to C. .. versionadded:: 2.1 You can now declare more than one variable in a single declaration (comma separated) and add pointer and array markers (subscripts) ad libitum as well as initializers. The remain some limitations: * arrays must have a defined size (i.e. the integer in the subscript brackets is not optional as in C) * you cannot declare new types or type synonyms * you cannot declare functions or use function types * only certain types are allowed, see below Some of these restrictions may be lifted in future versions. .. _Types: Types ~~~~~ .. productionlist:: type: "char" type: "short" type: "int" type: "long" type: "unsigned" "char" type: "unsigned" "short" type: "unsigned" "int" type: "unsigned" "long" type: "int8_t" type: "uint8_t" type: "int16_t" type: "uint16_t" type: "int32_t" type: "uint32_t" type: "float" type: "double" type: "string" type: "evflag" type: "foreign" These are the allowed base types, of which you can declare variables, or pointers or arrays etc. The list contains the usual numeric types, (including the fixed size integral types from the C99 standard) as well as three types that don't exist in C: "string", "evflag" and "foreign". These are explained below. Not supported are * function, struct, union, and enum types * typedefs * void * qualifiers ("static", "extern", "const" etc. other than "unsigned") An additional type (probably called "enumeration") is planned, but not yet implemented. It will make it easier and more robust to interact with process variables of type ``DBF_ENUM`` or ``DBF_MENU``. .. todo:: Allow "const" for non-channel variables. .. todo:: Allow *use* of (named) struct, union, and enum types in variable declarations (note: not struct, union, and enum type declarations). Strings ~~~~~~~ The type :token:`string` is defined in C as:: typedef char string[MAX_STRING_SIZE]; where ``MAX_STRING_SIZE`` is a constant defined in one of the included header files from EPICS base. I know of no EPICS version where it is different from 40, but to be on the safe side I recommend not to rely too much on the numeric value. (You can use ``sizeof(string)`` in SNL expressions.) .. note:: In contrast to C, in SNL ``string s`` is *not* a synonym for ``char s[MAX_STRING_SIZE]``, since variables of type :token:`string` are treated differently when it comes to interacting with PVs: the former gets requested with type DBR_STRING and a count of one, while the latter gets requested with type DBR_CHAR and a count of ``MAX_STRING_SIZE``. Event Flags ~~~~~~~~~~~ Event flags are values of an abstract data type with four operations defined on them: :c:func:`efSet`, :c:func:`efClear`, :c:func:`efTest`, and :c:func:`efTestAndClear`. An event flag ``e`` can act as a binary semaphore, allowing exactly one state set to continue, if ``when(efTestAndClear(e))`` is used to wait and ``efSet(e)`` to signal. Event flags can be coupled to changes of a PV using the :token:`sync` clause, so that the flag gets set whenever an event happens. You cannot declare arrays of or pointers to event flags, since event flags are not translated to C variables in your program. See :ref:`EventFlags`. .. _foreign entities: Foreign Entities ~~~~~~~~~~~~~~~~ The pseudo type "foreign" is used to declare any number of C variables or struct tags or CPP macros that have been defined outside the SNL program. No warning will be issued if such a variable, struct tag, or macro is used in the program even if warnings are enabled. You cannot declare arrays of or pointers to foreign entities. .. _variable scope: Variable Scope and Life Time ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Variables are statically scoped, i.e. they are visible to the program only inside the smallest enclosing :token:`block`. This is similar to C and other statically scoped programming languages. However, contrary to C, life time does not strictly follow static scope. Global variables, i.e. variables declared at the top-level (before any state sets) are visible throughout the program, and persist as long as the program is running. We say they have *global life time*. For local variables, life time depends on where they are declared: #. Variables of *global life time* are global variables and those which are local to a state set or a state clause. Only variables of this sort can be assigned to a process variable, monitored, synced etc. #. Variables declared in any other block have life time *limited to the enclosing block*; they disappear when the block exits, just as block local variables in C. They can *not* be assigned to process variables. If they have an initializer, the variable will be initialized every time the block is entered. If not, their value is undefined until they become initialized e.g. by an assignment. 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 following are definitions that allow program variables to interact with externally defined process variables. assign/connect ~~~~~~~~~~~~~~ .. productionlist:: assign: `connect` `variable` `to` `string` ";" assign: `connect` `variable` `subscript` `to` `string` ";" assign: `connect` `variable` `to` "{" `strings` "}" ";" assign: `connect` `variable` ";" connect: "assign" connect: "connect" to: "to" to: strings: `strings` "," `string` strings: `string` subscript: "[" `integer_literal` "]" variable: `identifier` This assigns or connects program variables to named or anonymous process variables. There are four variants of the :token:`assign` statement. The first one assigns a (scalar or array) variable to a single process variable. The second one assigns a single element of an array variable to a single process variable. The third one assigns elements of an array variable to separate process variables. .. versionadded:: 2.1 The fourth variant serves as an abbreviation for the first variant, in the special case where the PV name is empty (``""``). Assigned variables must be of global life time, see :ref:`variable scope`. Assigned variables, or separately assigned elements of an array, can be used as argument to built-in ``pvXXX`` procedures (see `Built-in Functions`_). This is the primary means of interacting with process variables from within an SNL program. Only certain types of variables can be assigned to a PV: allowed are numeric types (char, short, int, long, and their unsigned variants) and strings (these are sometimes referred to as *scalar types*), as well as one or two dimensional arrays of these. The process variable name may contain macro names enclosed in braces, as in ``"{sys}{sub}voltage"``. Macros are named following the same rules as C language variables. Macros are defined via command line argument, or program parameter (see `Program Name and Parameter`_). If the process variable name is an empty string, then no actual assignment to any process variable is performed, but the variable is marked for potential (dynamic) assignment with :c:func:`pvAssign`. .. note:: An :token:`assign` clause using an empty string for the PV name is interpreted differently in :ref:`safe mode`, see :ref:`anonymous pvs`. .. todo:: In safe mode it might make sense to implicitly declare every global variable as assigned (wholesale, if it is an array) by default. An explicit assign/connect statement will override this, of course. This is because un-assigned global variables are not very useful in safe mode, since the only reason to use global variables at all is to enable communication between state sets (and to avoid having to type the same declarations in every state set that uses a certain channel). An array variable assigned wholesale to one process variable (using the first syntactic variant above) or an element of a two-dimensional variable assigned to an array process variable (using the second syntactic variant) will use either the length of the array (resp. sub-array) or the native count for the underlying variable, whichever is smaller, when communicating with the underlying process variable. The native count is determined when a connection is established. For anonymous PVs, the length of the array is used. Pointer types may not be assigned to process variables. .. versionadded:: 2.1 The keyword "connect" is synonymous with "assign". It is offered as an alternative because the traditional term "assign" is too easily confused with the assignment statement (``var = expression;``), even though these notions have nothing in common. However, for compatibility and the fact that most SNL programmers are used to it, I have refrained from making any disruptive change here. Thus, the documentation still talks about "assigned variables", the "assign clause" etc. monitor ~~~~~~~ .. productionlist:: monitor: "monitor" `variable` `opt_subscript` ";" opt_subscript: `subscript` opt_subscript: This sets up a monitor for an assigned variable or array element. Monitored variables are automatically updated whenever the underlying process variable changes its value. Note, however, that this depends on the configuration of the underlying PV: some PVs post an update event only if the value changes by at least a certain amount. Also, events may be posted, even if no actual change happens, i.e. the value remains the same. The details can be found in the `EPICS Record Reference Manual`_. .. _EPICS Record Reference Manual: http://www.aps.anl.gov/epics/wiki/index.php/RRM_3-14 sync ~~~~ .. productionlist:: sync: "sync" `variable` `opt_subscript` `to` `event_flag` ";" event_flag: `identifier` This declares a variable to be synchronized with an event flag. When a monitor is posted on any of the process variables associated with the event flag (and these are :token:`monitor`\ed), or when an asynchronous get or put operation completes, the corresponding event flag is set. The variable must be :token:`assign`\ed and :token:`monitor`\ed. A variable can be mentioned in at most one sync clause, but an event flag may appear in more than one such clause. The variable may be an array, and as such may be associated with multiple process variables. .. versionadded:: 2.1 * It is now allowed to sync an event flag to more than one variable. * There is now a run-time equivalent to the sync clause, see the built-in function :c:func:`pvSync`. syncQ ~~~~~ .. productionlist:: syncq: "syncq" `variable` `opt_subscript` `to` `event_flag` `syncq_size` ";" syncq: "syncq" `variable` `opt_subscript` `syncq_size` ";" syncq_size: `integer_literal` syncq_size: This declares a variable to be queued. When a monitor is posted on any of the process variables associated with the given program variable, the new value is written to the end of the queue. If the queue is already full, the last (youngest) entry is overwritten. The :c:func:`pvGetQ` function reads items from the queue. The variable must be :token:`assign`\ed and :token:`monitor`\ed. Specifying a size (number of elements) for the queue is optional. If a size is given, it must be a positive decimal number, denoting the maximum number of elements that can be stored in the queue. A missing size means that it defaults to 100 elements. The variable can be an array, and may be associated with multiple process variables, in which case there is still only one queue, but any change in any of the involved PVs will add an entry to the queue. .. versionadded:: 2.1 You can use "syncq" (all lower case) as keyword instead of "syncQ". The latter may be deprecated in a future version. .. versionadded:: 2.1 Not giving a queue size (thus relying on the default of 100 elements) is now *deprecated* and the compiler will issue a warning. The reason for this is that queues are now statically allocated, which can result in a large memory overhead especially if the variable is an array associated with a single PV. (A default queue size of 1 would be much more useful, but for compatibility I kept it at 100 as in previous versions.) .. versionadded:: 2.1 A queued variable no longer needs to be associated with an event flag. The first form of the :token:`syncq` clause is now merely an abbreviation for a :token:`sync` clause together with a :token:`syncq` of the second form, i.e. :: syncq var to ef qsize; is equivalent to :: sync var to ef; syncq var qsize; Forcing the association with an event flag was never really necessary, since :c:func:`pvGetQ` already checks and returns whether the queue is empty or not; and any state set that mentions a variable in a :token:`when` 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 definition: Option ^^^^^^ .. productionlist:: option: "option" `option_value` `identifier` ";" option_value: "+" option_value: "-" Option values must include the "+" or "-" sign. Example:: option +r; /* make code reentrant */ The same syntax is used for global options and state options. The interpretation, however, is different: Global (top-level) options are interpreted as if the corresponding compiler option had been given on the command line (see :ref:`CompilerOptions`). Global option definitions take precedence over options given to the compiler on the command line. State options occur inside the state construct and affect only the state in which they are defined, see `State Option`_. State Set --------- .. productionlist:: state_sets: `state_sets` `state_set` state_sets: `state_set` state_set: "ss" `identifier` "{" `ss_defns` `states` "}" ss_defns: `ss_defns` `ss_defn` ss_defns: A program may contains one or more state sets. Each state set is defined by the keyword "ss", followed by the name of the state set (an identifier). After that comes an opening brace, optionally state set local definitions, a list of states, and then a closing brace. State set names must be unique in the program. State Set Local Definition ^^^^^^^^^^^^^^^^^^^^^^^^^^ .. productionlist:: ss_defn: `assign` ss_defn: `monitor` ss_defn: `sync` ss_defn: `syncq` ss_defn: `declaration` Inside state sets are allowed variable declarations and process variable definitions (:token:`assign`, :token:`monitor`, :token:`sync`, and :token:`syncq`). See `variable scope`_ for details on what local definitions mean. State ^^^^^ .. productionlist:: states: `states` `state` states: `state` state: "state" `identifier` "{" `state_defns` `entry` `whens` `exit` "}" state_defns: `state_defns` `state_defn` state_defns: A state set contains one or more states. Each state is defined by the keyword "state", followed by the name of the state (and identifier), followed by an opening brace, optionally state local definitions, an optional entry block, a list of transitions, an optional exit block, and finally a closing brace. State names must be unique in the sate set to which they belong. State Local Definition ~~~~~~~~~~~~~~~~~~~~~~ .. productionlist:: state_defn: `assign` state_defn: `monitor` state_defn: `sync` state_defn: `syncq` state_defn: `declaration` state_defn: `option` .. _StateOption: State Option ~~~~~~~~~~~~ The syntax for a state option is the same as for global options (see :ref:`option definition`). The state options are: :option:`+t` Reset delay timers each time the state is entered, even if entered from the same state. This is the default. :option:`-t` Don't reset delay timers when entering from the same state. In other words, the :c:func:`delay` function will return whether the specified time has elapsed from the moment the current state was entered from a different state, rather than from when it was entered for the current iteration. :option:`+e` Execute :token:`entry` blocks only if the previous state was not the same as the current state. This is the default. :option:`-e` Execute :token:`entry` blocks even if the previous state was the same as the current state. :option:`+x` Execute :token:`exit` blocks only if the next state is not the same as the current state. This is the default. :option:`-x` Execute :token:`exit` blocks even if the next state is the same as the current state. For example:: state low { option -e; /* Do entry{} every time ... */ option +x; /* but only do exit{} when really leaving */ entry { ... } ...when ()... exit { ... } } .. _state entry and exit blocks: State Entry and Exit Blocks ~~~~~~~~~~~~~~~~~~~~~~~~~~~ The syntax is the same as for global entry blocks: .. productionlist:: entry: "entry" `block` entry: exit: "exit" `block` exit: Entry blocks are executed when the state is entered, before any of the conditions for state transitions are evaluated. Exit blocks are executed when the state is left, after the transition block that determines the next state. .. note:: State options can be used to control whether entry/exit blocks get executed even if the new state is the same as the current one. .. _Transitions: Transitions ~~~~~~~~~~~ .. productionlist:: whens: `whens` `when` whens: `when` when: "when" "(" `opt_expr` ")" `block` "state" `identifier` when: "when" "(" `opt_expr` ")" `block` "exit" opt_expr: `expr` 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 any state of the same state set). If there is no condition given, it defaults to ``TRUE`` (1). .. versionadded:: 2.1 Instead of declaring which should be the next state, one can use the single keyword "exit" to specify that 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 disconnect all channels, close the pv system (CA), free all allocated memory, and then terminate the program. 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. See also the ioc shell command ``seqStop`` in section :ref:`Stopping the State Program Tasks`. Block ^^^^^ .. productionlist:: block: "{" `block_defns` `statements` "}" block_defns: `block_defns` `block_defn` block_defns: block_defn: `declaration` block_defn: `c_code` Blocks are enclosed in matching (curly) braces. They may contain any number of block definitions and afterwards any number of statements. Block definitions are: declarations and embedded C code. Statements and Expressions -------------------------- Statements ^^^^^^^^^^ .. productionlist:: statements: `statements` `statement` statements: statement: "break" ";" statement: "continue" ";" statement: "state" `identifier` ";" statement: `c_code` statement: `block` statement: "if" "(" `comma_expr` ")" `statement` statement: "if" "(" `comma_expr` ")" `statement` "else" `statement` statement: "while" "(" `comma_expr` ")" `statement` statement: `for_statement` statement: `opt_expr` ";" for_statement: "for" "(" `opt_expr` ";" `opt_expr` ";" `opt_expr` ")" `statement` As can be seen, most C statements are supported. Not supported are the switch/case statement and the return statement. The *state change statement* is not borrowed from C; it is only available in a state transition action block (i.e. after a when) and has the effect of immediately jumping out of the action block, overriding the statically specified new state (given after the block) with its state argument. Expressions ^^^^^^^^^^^ Formation rules for expressions are listed in groups of descending order of precedence. Atomic Expression ~~~~~~~~~~~~~~~~~ .. productionlist:: expr: `integer_literal` expr: `floating_point_literal` expr: `string` expr: `identifier` string: `string_literal` These are literals and variables. Parenthesized Expression ~~~~~~~~~~~~~~~~~~~~~~~~ .. productionlist:: expr: "(" `comma_expr` ")" Primary Expression Operators ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. productionlist:: expr: `identifier` "(" `args` ")" expr: "exit" "(" `args` ")" expr: `expr` "[" `expr` "]" expr: `expr` "." `expr` expr: `expr` "->" `expr` expr: `expr` "++" expr: `expr` "--" These are: function call, array subscript, record selection, pointer to record selection, and postfix operators (increment and decrement). .. note:: :token:`exit` is listed explicitly because it is a keyword, not an identifier, but can also be used as a function. .. note:: SNL makes no use of the semantics of structure member access and struct tags are treated as if they were expressions (variables, in fact). A side-effect is that *snc* will warn that structure tags are "used but not declared", which can be silenced with a :ref:`foreign entities` declaration, just as with variables. Unary Prefix Operators ~~~~~~~~~~~~~~~~~~~~~~ .. productionlist:: expr: "+" `expr` expr: "-" `expr` expr: "*" `expr` expr: "&" `expr` expr: "!" `expr` expr: "~" `expr` expr: "++" `expr` expr: "--" `expr` Left-associative Binary Operators ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. productionlist:: expr: `expr` "-" `expr` expr: `expr` "+" `expr` expr: `expr` "*" `expr` expr: `expr` "/" `expr` expr: `expr` ">" `expr` expr: `expr` ">=" `expr` expr: `expr` "==" `expr` expr: `expr` "!=" `expr` expr: `expr` "<=" `expr` expr: `expr` "<" `expr` expr: `expr` "||" `expr` expr: `expr` "&&" `expr` expr: `expr` "<<" `expr` expr: `expr` ">>" `expr` expr: `expr` "|" `expr` expr: `expr` "^" `expr` expr: `expr` "&" `expr` expr: `expr` "%" `expr` Ternary Operator ~~~~~~~~~~~~~~~~ .. productionlist:: expr: `expr` "?" `expr` ":" `expr` The ternary operator (there is only one) is right-associative. Assignment Operators ~~~~~~~~~~~~~~~~~~~~ .. productionlist:: expr: `expr` "=" `expr` expr: `expr` "+=" `expr` expr: `expr` "-=" `expr` expr: `expr` "&=" `expr` expr: `expr` "|=" `expr` expr: `expr` "/=" `expr` expr: `expr` "*=" `expr` expr: `expr` "%=" `expr` expr: `expr` "<<=" `expr` expr: `expr` ">>=" `expr` expr: `expr` "^=" `expr` These operators are right-associative. Comma Operator ~~~~~~~~~~~~~~ .. productionlist:: comma_expr: `comma_expr` "," `expr` comma_expr: `expr` opt_expr: `comma_expr` opt_expr: The comma operator is left associative. An :token:`opt_expr` is an optional :token:`comma_expr`; it appears, for instance, inside a :token:`for_statement`. Argument List ~~~~~~~~~~~~~ .. productionlist:: args: `args` "," `expr` args: `expr` args: Function argument lists look exactly like chained application of the comma operator, which is why application of the comma operator in an argument list must be grouped by parentheses. Built-in Constants ------------------ Some of the built-in functions use (i.e. accept or return) values of certain enumeration types and constants. These are also available to C code and defined in the header files pvAlarm.h and seqCom.h. .. versionadded:: 2.1.6 The pvStat and pvSevr constants are now known to the compiler, so you no longer have to declare them as ``foreign`` in the SNL code. pvStat ^^^^^^ :: typedef enum { /* generic OK and error statuses */ pvStatOK = 0, pvStatERROR = -1, pvStatDISCONN = -2, /* correspond to EPICS statuses */ pvStatREAD = 1, pvStatWRITE = 2, pvStatHIHI = 3, pvStatHIGH = 4, pvStatLOLO = 5, pvStatLOW = 6, pvStatSTATE = 7, pvStatCOS = 8, pvStatCOMM = 9, pvStatTIMEOUT = 10, pvStatHW_LIMIT = 11, pvStatCALC = 12, pvStatSCAN = 13, pvStatLINK = 14, pvStatSOFT = 15, pvStatBAD_SUB = 16, pvStatUDF = 17, pvStatDISABLE = 18, pvStatSIMM = 19, pvStatREAD_ACCESS = 20, pvStatWRITE_ACCESS = 21 } pvStat; pvSevr ^^^^^^ :: typedef enum { /* generic OK and error severities */ pvSevrOK = 0, pvSevrERROR = -1, /* correspond to EPICS severities */ pvSevrNONE = 0, pvSevrMINOR = 1, pvSevrMAJOR = 2, pvSevrINVALID = 3 } pvSevr; compType ^^^^^^^^ :: enum compType { DEFAULT, ASYNC, SYNC }; .. note:: Only ``SYNC`` and ``ASYNC`` are SNL built-in constants (i.e. known to *snc*). The constant ``DEFAULT`` is for use in C code (to represent a missing optional argument). seqBool ^^^^^^^ :: typedef int seqBool; #define TRUE 1 #define FALSE 0 NOEVFLAG ^^^^^^^^ :: #define NOEVFLAG 0 This can be given as second argument to :c:func:`pvSync` (instead of an event flag) to cancel the sync. .. _BuiltinFunctions: Built-in Functions ------------------ The following special functions are built into the SNL. In most cases the state notation compiler performs some special interpretation of the parameters to these functions. Therefore, some are either not available through escaped C code or their use in escaped C code is subject to special rules. An argument specified as *assigned_var* refers to any SNL variable or array element that has been assigned to a (possibly anonymous) process variable. The compiler statically checks this. Similarly, *queued_var* refers to a variable that has been assigned to a PV and also associated with an event queue with the :token:`syncq` clause. This is also statically checked by the compiler. Several of these functions are primarily intended to be called only from :token:`when` clauses or only from action code. It is safe to call them in action code, but the effect is probably not what you want. For instance, calling :c:func:`delay` in action code does *not* introduce a delay at this point. Later versions of *snc* may output a warning or even stop with an error. delay expired ^^^^^^^^^^^^^ .. c:function:: boolean delay(double delay_in_seconds) Returns whether the specified time has elapsed since entering the state. It should be used only within a :token:`when` expression. The :option:`-t` state option (see `State Option`_) controls whether the delay is measured from when the current state was entered from a different state (:option:`-t`) or from any state, including itself (:option:`+t`, the default). pvPut ^^^^^ .. c:function:: pvStat pvPut(assigned_var) pvStat pvPut(assigned_var, SYNC) pvStat pvPut(assigned_var, ASYNC) Puts (or writes) the value of an SNL variable to the underlying process variable. Returns the status from the PV layer (e.g. ``pvStatOK`` for success). If the variable is an array and associated with multiple PVs, then only the first element of the array interacts with its underlying PV. .. todo:: It would be much nicer to have *all* the elements of the array written to their respective PVs in this case. Would this break compatibility too much? Need to find out what to do. By default, :c:func:`pvPut` is un-confirmed "fire and forget"; completion must be inferred by other means. The optional ``SYNC`` argument causes it to block on completion with a hard-coded timeout of 10s. The optional ``ASYNC`` argument allows the program to continue but still check for completion via a subsequent call to :c:func:`pvPutComplete` (typically in a :token:`when` clause). Note that SNL allows only one pending :c:func:`pvPut` per variable and state set to be active. As long as a ``pvPut(var,ASYNC)`` is pending completion, further calls to ``pvPut(var,ASYNC)`` from the same state set immediately fail and an error message is printed; whereas further calls to ``pvPut(var,SYNC)`` are *delayed* until the previous operation completes. Thus :: pvPut(var,ASYNC); pvPut(var,SYNC); and :: pvPut(var,SYNC); pvPut(var,SYNC); are equivalent. Whereas in :: pvPut(var,ASYNC); pvPut(var,ASYNC); the second pvPut may fail if it is a named PV located on another IOC (since the first one is still awaiting completion), whereas for local named PVs and anonymous PVs it will succeed (since completion is immediate in these cases). In :ref:`safe mode`, :c:func:`pvPut` can be used with anonymous PVs (variables assigned to "") to communicate between state sets. This makes sense only with global variables as only those can be referenced in more than one state set. The behaviour for anonymous PVs exactly mirrors that of named PVs, including the fact that the new value will not be seen by other state sets until they issue a pvGet, or, if the variable is monitored, until they wait for an event in a :token:`when` clause. Note that for anonymous PVs completion is always immediate, so the ``ASYNC`` option is not very useful. pvPutComplete ^^^^^^^^^^^^^ .. c:function:: boolean pvPutComplete(assigned_var) boolean pvPutComplete(assigned_var, boolean any) boolean pvPutComplete(assigned_var, boolean any, boolean *pComplete) Returns whether the last asynchronous :c:func:`pvPut` to this process variable has completed. The first form is appropriate when the SNL variable is a scalar. However, it can also be an array (each of whose elements may be assigned to a different process variable). In this case, the single argument form returns whether *all* :c:func:`pvPut` operations for elements of the array have completed (missing arguments are implicitly set to ``0``). If ``any`` is ``TRUE``, then the function returns whether *any* put has completed since the last call. If ``pComplete`` is non-NULL, it should be an array of at least the array length of the SNL variable and its elements indicate whether the corresponding :c:func:`pvPut` has completed. pvGet ^^^^^ .. c:function:: pvStat pvGet(assigned_var) pvStat pvGet(assigned_var, SYNC) pvStat pvGet(assigned_var, ASYNC) Gets (or reads) the value of an SNL variable from the underlying process variable. Returns the status from the PV layer (e.g. ``pvStatOK`` for success). If the variable is an array and associated with multiple PVs, then only the first element of the array interacts with its underlying PV. .. todo:: Same as for pvPut, i.e. should work like 'foreach element do pvGet'. By default, the state set will block until the read operation is complete with a hard-coded timeout of 10s. The asynchronous (:option:`+a`) compile option can be used to prevent this, in which case completion can be checked via a subsequent call to :c:func:`pvGetComplete` (typically in a :token:`when` clause). The optional ``SYNC`` and ``ASYNC`` arguments override the compile option. ``SYNC`` blocks and so gives default behavior if :option:`+a` was not specified; ``ASYNC`` doesn't block and so gives default behavior if :option:`+a` was specified. In :ref:`safe mode`, if ``ASYNC`` is specified and the variable is not monitored, then the state set local copy of the variable will not be updated until a call to pvGetComplete is made and returns ``TRUE`` or, if the variable is monitored, until the state set waits for events in a :token:`when` clause. Note that anonymous PVs behave exactly in the same way. pvGetComplete ^^^^^^^^^^^^^ .. c:function:: boolean pvGetComplete(assigned_var) Returns whether the last get of this process variable has completed, i.e. the value in the variable is current. This call is appropriate only if the asynchronous (:option:`+a`) compile option is specified or :c:func:`pvGet` 's optional ``ASYNC`` argument was used. In :ref:`safe mode`, the the state set local copy of the variable will be updated with the value from the PV layer as a side effect of this call (if TRUE is returned). pvGetQ ^^^^^^ .. c:function:: boolean pvGetQ(queued_var) If the queue associated with this variable is not empty, remove its first (oldest) value and update the variable with it. Returns whether there was an element in the queue (and the variable got updated). If an element gets removed, and the queue becomes empty as a result, then any event flag :token:`sync`\ed to the variable will be cleared. It is an error to call pvGetQ with a variable that is not associated with a queue (see the :token:`syncq` clause and the built-in function :c:func:`pvSync`). Note that since pvGetQ may have a *side-effect* you should be careful when combining a call to pvGetQ with other conditions in the same :token:`when` clause, e.g. :: when (pvGetQ(msg) && other_condition) { printf(msg); } state ... would remove the head from the queue every time the when clause 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, especially if ``other_condition`` were in fact ``!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; similar remarks apply whenever you want to use an expression inside a :token:`when` clause that potentially has a side-effect. pvFreeQ ^^^^^^^ .. c:function:: void pvFreeQ(queued_var) Deletes all entries from a queued variable's queue and clears the associated event flag. .. versionadded:: 2.1 Queue elements are no longer dynamically allocated, so this is now an alias for pvFlushQ. pvFlushQ ^^^^^^^^ .. c:function:: void pvFlushQ(queued_var) .. versionadded:: 2.1 Flush the queue associated with this variable, so it is empty afterwards. pvAssign ^^^^^^^^ .. c:function:: pvStat pvAssign(assigned_var, process_variable_name) Assigns or re-assigns the SNL variable ``var`` to ``process_variable_name``. If ``process_variable_name`` is an empty string then ``assigned_var`` is de-assigned (not associated with any process variable). In :ref:`safe mode`, it causes assignment to an anonymous PV. As usual, ``assigned_var`` can also be an array element. See also :token:`assign` clause. A better name for this function would be ``pvReassign``. .. note:: :c:func:`pvAssign` can only be called on variables (or array elements) that have been statically marked as process variables using the :token:`assign` syntax. An empty string may be used for the initial assignment, or (from version 2.1. onward) the simplified form ``assign var``. .. note:: If a variable gets de-assigned from a non-empty to an empty name, the corresponding channel is destroyed, which means that dynamically allocated memory gets freed. If your system cannot handle dynamic memory allocation without fragmentation, care should be taken that assignment and de-assignment do not alternate too often. ..note:: If you want to assign array elements to separate PVs, you cannot currently do this with a single call (in contrast to doing it in an :token:`assign` clause. Instead, you must call :c:func:`pvAssign` for each array element individually. This might change in a future release. ..todo:: Implement multiple assign for arrays. pvMonitor ^^^^^^^^^ .. c:function:: pvStat pvMonitor(assigned_var) Initiates a monitor on the underlying process variable. See :token:`monitor` clause. ..note:: If you want to monitor all elements of an array that have been assigned to separate PVs, you cannot currently do this with a single call (in contrast to doing it in a :token:`monitor` clause). Instead, you must call :c:func:`pvMonitor` for each array element individually. This might change in a future release. ..todo:: Implement multiple monitor for arrays. pvStopMonitor ^^^^^^^^^^^^^ .. c:function:: pvStat pvStopMonitor(assigned_var) ..note:: If you want to stop monitoring all elements of an array that have been assigned to separate PVs, you cannot currently do this with a single call. Instead, you must call :c:func:`pvStopMonitor` for each array element individually. This might change in a future release. ..todo:: Implement multiple stop monitor for arrays. Terminates a monitor on the underlying process variable. pvSync ^^^^^^ .. c:function:: void pvSync(assigned_var, event_flag) Synchronizes a variable with an event flag, or removes such a synchronization if ``event_flag`` is `NOEVFLAG`_. ..note:: If you want to sync all elements of an array that have been assigned to separate PVs, you cannot currently do this with a single call. Instead, you must call :c:func:`pvSync` for each array element individually. This might change in a future release. ..todo:: Implement multiple sync for arrays. See :token:`sync` clause. pvCount ^^^^^^^ .. c:function:: unsigned pvCount(assigned_var) Returns the element count associated with the process variable. This value is independent of the array size (if the variable is an array), it can be smaller or larger. pvStatus ^^^^^^^^ .. c:function:: pvStat pvStatus(assigned_var) Returns the current alarm status (e.g. ``pvStatHIHI``, see :ref:`pvStat`) of the underlying PV or indicates failure of a previous PV operation. The status, severity, and message returned by :c:func:`pvStatus`, :c:func:`pvSeverity`, and :c:func:`pvMessage` reflect either the underlying PV's properties (if a pvPut or pvGet operation completed, or the variable is monitored), or else indicate a failure to initiate one of these operations. pvSeverity ^^^^^^^^^^ .. c:function:: pvSevr pvSeverity(assigned_var) Returns the current alarm severity (e.g. pvSevrMAJOR) of the underlying PV or indicates failure of a previous PV operation. pvMessage ^^^^^^^^^ .. c:function:: const char *pvMessage(assigned_var) Returns the current error message of the variable, or "" (the empty string) if none is available. pvTimeStamp ^^^^^^^^^^^ .. c:function:: epicsTimeStamp pvTimeStamp(assigned_var) Returns the time stamp for the last :c:func:`pvGet` completion or monitor event for this variable. The SNL compiler does not recognize type epicsTimeStamp. Therefore, variable declarations for this type should be in escaped C code. This will generate a compiler warning, which can be ignored or fixed by adding a "foreign" declaration (see `Foreign Entities`_). Example:: %%epicsTimeStamp ts; foreign ts; int var; assign var; entry { ts = pvTimeStamp(var); } .. todo:: This is crude. Types like epicsTimeStamp etc should be allowed in variable declarations. OTOH, this will be fixed as soon as I allow 'struct ', 'enum ' etc in declarations, which is planned anyway. pvAssigned ^^^^^^^^^^ .. c:function:: boolean pvAssigned(assigned_var) Returns whether the SNL variable is currently assigned to a process variable. Note that this function returns ``FALSE`` for anonymous PVs. pvConnected ^^^^^^^^^^^ .. c:function:: boolean pvConnected(assigned_var) Returns whether the underlying process variable is currently connected. pvIndex ^^^^^^^ .. c:function:: unsigned pvIndex(assigned_var) Returns the index associated with a variable. See `User Functions within the State Program`_ for how to use this function. pvFlush ^^^^^^^ .. c:function:: void pvFlush() Causes the PV layer to flush its send buffer. This is only necessary if you need to make sure that CA operations are started *before* the action block finishes. The buffer is always automatically flushed before the sequencer waits for events, that is, after a state's entry block is executed (or on entry to the state if there is no entry block). The buffer is also flushed after initiating a synchronous operation that waits for a callback (i.e. ``pvPut(var,SYNC)`` and ``pvGet(var,SYNC)``). pvChannelCount ^^^^^^^^^^^^^^ .. c:function:: unsigned pvChannelCount() Returns the total number of process variables associated with the program. pvAssignCount ^^^^^^^^^^^^^ .. c:function:: unsigned pvAssignCount() Returns the total number of SNL variables in this program that are assigned to underlying process variables. For instance, if all SNL variables are assigned then the following expression is ``TRUE``:: pvAssignCount() == pvChannelCount() Each element of an SNL array counts as variable for the purposes of :c:func:`pvAssignCount`. pvConnectCount ^^^^^^^^^^^^^^ .. c:function:: unsigned pvConnectCount() Returns the total number of underlying process variables that are connected. For instance, if all assigned variables are connected then the following expression is ``TRUE``:: pvConnectCount() == pvAssignCount() efSet ^^^^^ .. c:function:: void efSet(event_flag) Sets the event flag and causes evaluation of the :token:`when` clauses for all state sets that are pending on this event flag. efClear ^^^^^^^ .. c:function:: boolean efClear(event_flag) Clears the event flag and causes evaluation of the :token:`when` clauses for all state sets that are pending on this event flag. efTest ^^^^^^ .. c:function:: boolean efTest(event_flag) Returns whether the event flag was set. .. note:: In safe mode, this function is a synchronization point for all variables (channels) that are :token:`sync`\ed with it, i.e. the state set local copy of these variables will be updated from their current globally visible value. efTestAndClear ^^^^^^^^^^^^^^ .. c:function:: boolean efTestAndClear(event_flag) Clears the event flag and returns whether the event flag was set. It is intended for use within a :token:`when` clause. .. note:: In safe mode, this function is a synchronization point for all variables (channels) that are :token:`sync`\ed with it, i.e. the state set local copy of these variables will be updated from their current globally visible value. macValueGet ^^^^^^^^^^^ .. c:function:: char* macValueGet(char *macro_name) Returns a pointer to a string that is the value for the specified macro name. If the macro does not exist, it returns ``NULL``. .. _safe mode: Safe Mode --------- .. versionadded:: 2.1 SNL code can be interpreted in *safe mode*. This must be enabled with the :option:`+s` option, because it changes the way variables are handled and is thus not fully backwards compatible. It should, however, be easy to adapt existing programs to safe mode by making communication between state sets explicit. New programs should no longer use the traditional unsafe mode. In the traditional (unsafe) mode, variables are *not* protected against access from concurrently running threads. Concurrent access to SNL variables was introduced in version 2.0, when implementation of the PV layer switched from the old single threaded CA mode ("preemptive callbacks disabled") to the multi-threaded mode ("preemptive callbacks enabled") in order to support more than one state set per program. This could result in data corruption for variables that are not read and written atomically, the details of which are architecture and compiler dependent (i.e. plain ``int`` is typically atomic, whereas double is problematic on some, string and arrays on almost all architectures/compilers). Even for plain ``int`` variables, read-modify-write cycles (like ``v++``) cannot be guaranteed to have any consistent result. Furthermore, conditions that have been met inside a :token:`when` clause cannot be relied upon to still hold inside the associated action block. Concurrent access to SNL variables happens when * multiple state sets access the same variable, or * variables are updated from the PV layer due to monitors and asynchronous get operations. While it is possible to avoid the first case by careful coding (using e.g. event flags for synchronization) it is not possible to guard against the second case as these events can interrupt action statements at any time. One of the reasons SNL programs have mostly worked in spite of this is that due to the standard CA thread priorities the callback thread does not interrupt the state set threads. Furthermore (and contrary to what many people believe) the VxWorks scheduler does not normally serve threads with equal priority in a round-robin (time-sliced) fashion; instead each thread keeps running until it gets interrupted by a higher priority thread or until it blocks on a semaphore. However, thread priorities are not honored if programs are run under Linux or Windows in user mode; and anyway, priorities should only be used to improve latency for certain operations (at the cost of others) and *never* should they be relied upon for program correctness. Safe mode solves all these problems by changing the way variables, particularly global variables, are interpreted. In safe mode, all variables --except event flags-- are interpreted as if they were *local to the state set*. This means that setting a variable (even a global variable) in one state set does *not* automatically change its value as seen by other state sets. State sets are effectively isolated against each other, and all communication between them must be explicit. They are also isolated against updates by callbacks from the PV layer except at those points where they don't do anything i.e. when they wait for events in a :token:`when` clause. In safe mode, variable values get updated right before the conditions are evaluated, or when explicitly calling synchronization functions like :c:func:`pvGetComplete` or :c:func:`pvGet` (the latter only if called in synchronous mode), as well as :c:func:`efTest` and :c:func:`efTestAndClear`. The documentation for the built-in functions explains the details. Safe mode implies reentrant mode, see :ref:`reentrant option`. .. _anonymous pvs: Anonymous PVs ^^^^^^^^^^^^^ Explicit communication between state sets can use either event flags or variables. Event flags work exactly the same as in traditional mode. To use a variable for communication it must have been *assigned* with an :token:`assign` statement. You can then use :c:func:`pvPut`, :c:func:`pvGet`, :c:func:`pvMonitor`, and most of the other built-in PV functions, *regardless of whether the PV name is the empty string (``""``) or not*. If it is the empty string, the PV functions behave as if the assignment had been to an internal, anonymous 'pseudo PV' that behaves similar to an external 'real' PV. The variant ``assign var;`` of the :token:`assign` statement has been introduced as an abbreviation for ``assign var to "";``. For instance, with the declaration :: int var; assign var; the action statement :: pvPut(var) makes the value of ``var`` available to other state sets. They will, however, not see the new value until they issue either a (synchronous) :c:func:`pvGet`, or the variable is declared as monitored and state change conditions are evaluated. The action :: pvGet(var, SYNC) updates ``var`` immediately with whatever has been written to it previously via :c:func:`pvPut` by some other state set. Whereas :: pvGet(var, ASYNC) has no immediate effect on the variable ``var``. Instead, ``var`` will be updated only if the code calls :c:func:`pvGetComplete` (and it returns ``TRUE``), or when state change conditions are evaluated the next time. .. note:: This behaviour is exactly the same as with external PVs. .. note:: Using ``SYNC`` or ``ASYNC`` with anonymous PVs is not very useful since all operations complete immediately. Event flags can be :token:`sync`\ed to anonymous PVs and will behave as with external PVs. Event Flags in Safe Mode ^^^^^^^^^^^^^^^^^^^^^^^^ Apart from the :token:`sync` and :token:`syncq` features, and apart from the atomicity of :c:func:`efTestAndClear`, an event flag behaves similar to an anonymous PV of boolean type with a monitor. In safe mode, :c:func:`efTest` and :c:func:`efTestAndClear` have the additional side-effect of acting as a synchronization point for all variables that are :token:`sync`\ed with the event flag: the state set local copies of these variable are updated from their globally visible copies. C Compatibility Features ------------------------ Escape to C Code ^^^^^^^^^^^^^^^^ Because the SNL does not support the full C language, C code may be escaped in the program. The escaped code is not compiled by SNC, but is passed the C compiler. There are two escape methods allowed: #. Any code between ``%%`` and the next newline character is escaped. Example:: %% for (i=0; i < NVAL; i++) { #. Any code between ``%{`` and ``}%`` is escaped. Example:: %{ extern float smooth(); extern LOGICAL accelerator_mode; }% If you are using the C pre-processor prior to compiling with *snc*, and you wish to defer interpretation of a preprocessor directive, then you should use the form:: %%#include %%#include Any variable declared in escaped C code and used in SNL code will be flagged with a warning message by the SNC. However, it will be passed on to the C compiler correctly. User Functions within the State Program ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The last state set may be followed by C code, usually containing one or more user-supplied functions. For example:: program example ... /* last SNL statement */ %{ static float smooth (pArray, numElem) { ... } }% There is little reason to do this, since a program can of course be linked against C libraries. Calling pvGet etc. from C ^^^^^^^^^^^^^^^^^^^^^^^^^ The built-in SNL functions such as :c:func:`pvGet` cannot be directly used in user-supplied functions. However, most of the built-in functions have a C language equivalent with the same name, except that the prefix ``seq_`` is added (e.g. ``pvGet`` becomes ``seq_pvGet``). These C functions expect an additional first argument identifying the calling state set, which is available in action code under the name ``ssId``. If a process variable name is required, the index of that variable must be supplied. This index is obtained via the :c:func:`pvIndex` function (which must be called from SNL code, not C code, to work). If the program is compiled with the :option:`+r` option, user functions cannot directly access SNL variables, not even global ones. Instead, variables (or their address) should be passed to user functions as arguments. Alternatively, you can pass the ``pVar`` variable of type ``USER_VAR*`` and access SNL variables as structure members of ``pVar``. See next section for details and an example. The prototypes for the C functions corresponding to the built-in SNL functions as well as additional supporting macros and type definitions can be found in the header file ``seqCom.h``. This header file is always included by the generated C code. .. _reentrant option: Variable Modification for Reentrant Option ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ If the reentrant option (:option:`+r`) is specified to SNC then all variables are made part of a structure. Suppose we have the following top-level declarations in the SNL program:: int sw1; float v5; short wf2[1024]; The C file will contain the following declaration:: struct UserVar { int sw1; float v5; short wf2[1024]; }; The sequencer allocates the structure area at run time and passes a pointer to this structure into the program. This structure has the following type:: struct UserVar *pVar; Reference to variable ``sw1`` is made as :: pVar->sw1 This conversion is automatically performed by the SNC for all SNL statements, but you will have to handle escaped C code yourself. .. note:: :ref:`safe mode` (enabled with the :option:`+s` option) implies reentrant mode. In safe mode, each state set has its own copy of the ``UserVar`` struct. You can operate on its members in any way you like, including taking the address of variables and passing them to C functions. Here is a stupid example of a C function that does a pvGet, increments the variable, and then does a pvPut:: program userfunc option +r; %{ static void incr(SS_ID ssId, int *pv, VAR_ID v) { seq_pvGet(ssId, v, SYNC); *pv += 1; seq_pvPut(ssId, v, SYNC); } }% int i; assign i to "counter"; foreign ssId; ss myss { state doit { when (delay(1)) { incr(ssId, &i, pvIndex(i)); } state doit } } Syntax Summary -------------- What follows is a summary of all syntax rules. It is guaranteed to be up-to-date as it is automatically generated from the parser generator specification. .. include:: bnf.inc