Escape to C Code

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

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

    %% for (i=0; i < NVAL; i++)
    
  2. Any code between %{ and }% is escaped. Example:

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

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

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

Preprocessor Directives

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

For instance with

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

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

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

%%#include <abcLib.h>

or

%{#include <abcLib.h>}%

Defining C Functions within the Program

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

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

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

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

Calling PV Functions from C

The built-in SNL functions such as 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 pvIndex function (which must be called from SNL code, not C code, to work).

If the program is compiled with the +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.

Variable Modification for Reentrant Option

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

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

The C file will contain the following declaration:

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

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

struct UserVar *pVar;

Reference to variable sw1 is made as

pVar->sw1

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

Note

Safe Mode (enabled with the +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
    }
}