NAME

prog_unit -- unit to execute (small) C-program

PROTOTYPE

unitptr prog_unit( int iDimX, int iDimY, int iDimZ, int iDimT, char *pcFmt, char *pcProg, unitptr uHost)

ARGUMENTS

int iDimX
dimension for X-array
int iDimY
dimension for Y-array
int iDimZ
dimension for Z-array
int iDimT
dimension for T-array
char *pcFmt
option string, allowing to predefine up to four array variables and optionally make these visible as input or output fields of dimensions specified with the first four arguments iDimX..iDimT. The first letter pcFmt[0] of pcFmt determines whether array X is input ( pcFmt[0]='i'), output ( pcFmt[0]='o') or only available for internal use ( pcFmt[0]= any other char). Similarly for the next three letters and the arrays Y, Z and T. Likewise, letters '[' and ']' map the corresponding array to input or output fields of the type string.
char *pcProg
string holding program source code
unitptr uHost
host unit

RETURN VALUE:

A pointer to the created unit or NULL in the case of an error.

SYNOPSIS:

Creates a unit that executes a C-program whose source text is given as a string pcProg. Supported syntax is a subset of C, with the only data types const, float, int, struct and one-dimensional float, int and char and struct arrays, but a facility to introduce from shared libraries additional, user-defined data types that appear as classes. All globally declared variables and all local static variables will retain their values between invocations of the prog_unit. The non-standard keywords INP or OUT in place of static front of a declaration of one or more variables makes these 'visible' as input or output fields of the created NST unit (for details, cf. below). Additionally, up to four arrays with fixed names X[], Y[], Z[] and T[] and dimensions given by iDimX..iDimT can be predefined through the interface of the prog_unit constructor function (cf. description of argument parameter pcFmt). There is some support for pointer variables and dynamically allocated memory; pointers declared after an INP or OUT keyword give rise to dynamic input or output arrays (cf. below). Supported control constructs are the usual if, for, do, while and switch constructs. As a non-standard extension, a switch may use a string value (in this case, all case values must be string constants too). Functions with return values float int or void and argument parameters float, int, struct, float*, int* or char* can be declared and defined, including automatic, static and const local variables. The usual math functions and operations, as well as printf and scanf are predefined. Additionally, a strlen() and a strdup() function on char or float arrays and a + operator that is interpreted as concatenation when occuring between char arrays facilitates string handling. The special predefined functions exec_opnds(), adapt_opnds() and exec_container_opnds(), adapt_container_opnds() allow to execute or adapt an interactively selectable number of successor units (``operands'') of the prog_unit and/or its container unit, resp. The predefined goto labels NST_INIT, NST_RESET, NST_EXEC and NST_ADAPT may occur among the global program statements and will then serve as entry points when the corresponding NST operations are applied to the prog_unit. The non-standard assignment operator ":=" allows to evaluate matrix algebra expressions (cf. below). It is also used to copy class objects (while '=' only re-references). A #import command allows to extend the set of predefined function with additional functions from shared libraries. For example, the string.so and file.so libraries provide most string and i/o function calls familiar from string.h and stdio.h (including creating and accessing files). The class keyword allows to access user-defined classes implemented in a shared library in a similar way as for a #import'ed library. See Section Classes below. The #debug and #break directives support some debugging options (see DEBUGGING). The #implicit directive allows to restrict the otherwise implicit definition of scalar variable symbols. The keyword public in front of a parameterless void function makes it an externally callable 'method' and turns the created prog_unit into a class container with a separate method subunit for each public function defined in this way. The interface of each method subunit will then be given by the subset of INP and OUT declarations within its local scope. All remaining INP/OUT declarations will define the interface of the enclosing class container. Instead of a main() function, the corresponding program statements are expected to directly follow after all definitions of global variables and functions (which must appear in this order) have been made. A throw() function is provided for error handling.

DESCRIPTION:

Expects pcProg to contain the code of a C-program that will be executed at each subsequent exec_unit - call. There is a simple way to dynamically extend the set of predefined functions by self-written functions (see below). There is the possibility to execute all or parts of the code also in response to adapt_unit and ctrl_unit calls, and to execute or adapt other units from within the C-program of the prog_unit (see below). This unit is provided to allow the interactive specification of specialized NST-units from within Neo, using a subset of C.

SUPPORTED SYNTAX:

Supported is a subset of the C-language plus a non-standard extension for evaluating matrix expressions.

Primitive Data Types:

There are the primitive data types float (which is the default and need not be declared), int (which must be declared) and one-dimensional float, int, char or byte arrays which also must be declared (and optionally initialized). Additionally, constant values can be declared with the keyword const; in this case, their type (.float or int) will be inferred from the given expression, e.g.

   const n=10/4, f=10./2;

will define n as a constant integer of value 2, and f as a constant float of value 5. Currently, int values are limited to a useful range of about -1.6E7..+1.6E7 (if larger values occur, no warning will be issued, but arithmetics becomes unreliable, since most computations on int's are done via floats, which outside the given range become spaced too far apart to represent consecutive integers). Char and byte arrays are interchangeable for most purposes; the main differences are (i) if they are made to appear as NST input or output pins, char arrays are mapped to strings, while byte arrays are mapped to byte vector pins, and (ii) byte arrays are saved fully in hexadecimal format, while char arrays are saved as strings and only up to the first null byte. Additionally, there are float*, int*, char* and byte* pointers (with an analogous minor difference between char* and byte*) and class data types (cf. below). Declarations must appear at the beginning of their respective scope (either a function body or the entire program).

Example:


   const n=2, pi=4*atan(1);
   static float a[2*n], b[n]={-0.5,0.5}, c[]={pi/2,pi,3*pi/2};
   float *p,r;

Global variable and function declarations may be interspersed, but each declaration is only visible below its first occurrence. All variables that are declared as static will become automatically saved with the prog unit, so that their values become restored when the prog unit is later loaded This feature becomes suppressed, however, if a static variable is explicitly initialized (such as b[] and c[] above). Global variables not declared static still retain their values between invocations of the prog unit (but not between load/save's). Strings are supported either by using char or byte arrays, or by using a float array to hold each letter as a float value (deprecated - only for backward compatibility with earlier versions). Corresponding initialization and elementwise char value assignments are supported, e.g.

   static char s[]="hello world", mystr[4]="abc";
   s[0] = 'H'; s[6] = '\n';

Such strings are always limited to a maximal number of chars given by the array dimension minus one. See also Section Pointers. DEPRECATED : there are up to four special predefined arrays X[], Y[], Z[] and T[] for accessing values from input or writing values to output fields of the prog_unit instance (which of X,Y,Z,T is mapped to inputs or outputs is described below). For backward compatibility, there are also four predefined macros X()..T() of the same name to access the array elements. There are the predefined constants NX, NY, NZ and NT that provide the dimensions of the four predefined arrays. Further predefined constants are M_PI for pi and NaN. Finally, __LINE__ and __NAME__ provide the current line number and the name string of the NST unit.

Struct Data Types:

The primitive data types can be used to define (non-recursive) structures:

   struct mytype {
      int i=4711, j;
      float af[] = {1,2,3};
      char *string = "hello";
   } u,v[100];

   struct mytype x, y[12];
   ...

This will define two variables u and x and two arrays v[100] and y[12] of structs. Not that, unlike classes (and in line with C usage) declared struct variables (including the elements of arrays) are always instantiated. Currently, struct instances are always static. Copy and member assignments follow the syntax

   u := x; // now u independent copy!
   u.af[2] = 1234; // access an element

Reference assignments, such as

   u = x; // will u only let reference x

are also possible, however, they require that the struct has no members that are arrays (i.e., the mytype struct would not work here). Although seldom needed, the name of the struct can be used as a (parameterless) constructor function:

   x = mytype();
   for (i=0; i<100; i++) v[i] = mytype();

Struct data types can be declared as INP and OUT parameters (giving rise to a composite field with a pin of corresponding type for each member variable)

   INP struct mytype xx;
   OUT struct mytype yy;

and as argument parameters for subroutines (again by reference access implied):

   void foo(int i, struct mytype z) {
       z.string += " world";
       z.i = i;
   }
   ...
   foo(34,u);
   // now u.i = 34 and u.string = "hello world"

However, inside a subroutine a referenced struct argument cannot be re-allocated. Currently, there are also no struct return values. Structs can also have class members (cf. section Classes below). Finally, structs can serve as type arguments for template classes (cf. below). In this way, suitable template classes permit an organization of struct instances into lists, trees, graphs etc. (this avoids the need for recursive structs with struct pointer elements and encapsulates the (often complex) handling and maintenance of such structures in the template class).

Input and Output fields:

The keyword static may be replaced by either of the special keywords INP and OUT. In this case, the declared variables again will be static, but they will additionally become accessible as input or output fields of the prog_unit. Arrays of types float[], int[] and byte[] give rise to packed float int or byte fields of corresponding dimension. Arrays of type char[] will give string field of corresponding dimension. Float or int variables that appear consecutively within the same declaration are 'glued' together into a single scalar float field (also for ints!) with one pin per variable. A float array can also be mapped into a scalar field by specifying instead of float the nonstandard keyword scalar after the INP or OUT. Composite Fields can be formed by enclosing several declarations after a INP or OUT in curly brackets, e.g.

    OUT { float x,y; char ac[5]; float af[10]; }

will make a composite output field with 4 pins: two scalar pins x,y, one text pin ac, and a float vector pin af of 10 elements (all elements are taken as static, i.e., will be saved unless initialized, cf. LOAD/SAVE below). A C++ style comment in the same line as the INP or OUT declaration will be accessible as an info text under Neo.

Synchronization of Input and Output fields:

The values of input and output fields are synchronized only when the prog_unit starts or ends execution, or when an exec_opnds() or adapt_opnds() call begins or ends (in the latter case, only the fields of the calling unit are synchronized). Sychronization means, that changes from outside the prog_unit become visible inside and vice versa. Sometimes (e.g., when the called operands access pins of the prog unit that do not belong to the calling unit (which may be a method subunit), or when an #import library function is called that itself calls external units) an extra, immediate synchronization of all pins may be required. This can be achieved with a call of the predefined parameterless function sync_ports().

Dynamic Arrays:

Pointer variables (cf. below) after an INP or OUT keyword give rise to an input or output field for a float or int vector of dynamically changeable dimension, e.g.: INP float *p; OUT int *q; Here, INP pointers cannot be initialized (they just provide access to the input vector and follow its dimension; an exception is the special empty initialization p=, which only serves to exclude p from being saved to file, cf. below), while OUT pointers can be treated as usual. Likewise, the type specifier byte* yields a dynamic array of bytes and a corresponding NST dynamic bytes output pin (currently, the char* will yield a dynamic float output field; this should not be used, since it may change in future versions).

String Inputs and String Outputs:

After a INP or OUT declarator it is also possible to use the type specifier char or byte instead of float or int. This has the effect to make the declared variables visible as NST fields of type string (for char) and of type byte (for byte) in the interface of the prog_unit. The string will be always clipped to a maximal length of array dimension minus 1 (scalars are treated as arrays of dimension 1 and are thus not useful in this context).

Functions:

Functions can be defined with return types void, float or int. The parameter list can be void, or it can be any combination of float, int, float*, int*, char* or struct arguments. Char* arguments expect a pointer or an array that must have char or byte elements (.i.e, no float array is allowed). Float* arguments admit arrays or pointers of both element types, but in the case of the char element type there will be some overhead due to allocation of a temporary buffer to pass a suitably converted copy of the char array, whose contents will be written back when the function returns. Therefore, for large arrays or time critical calls one should always pass the matching type. Local variables can be declared in the usual way, including const and static var. Static local variables can be made visible in the interface of the NST unit, using INP and OUT instead of static, as described above. To allow mutually recursive function calls, preliminary function declarations, such as

   float myfun(float x);

can be made before the full definition of the function occurs.

Implicit Variables:

If a function introduces implicitly defined variables in its body, the scope of the implicit definition is limited to the function, unless there is a global definition of the same name before the function. This is a subtlety that is unknown in standard C, since no implicit definitions are allowed there.

Classes:

The keyword class permits to define static instances of classes that are implemented in shared libraries (for a description, see Section Implementing Classes below). Classes admit linear arrays, e.g., if foo is a class with a constructor that expects two integer arguments, the following definition

   class foo x, y[10], z(3,4);

will define x to be an uninstantiated foo reference, y to be an array of 10 such references, and z to be a reference that is initialized to a foo instance created by the foo constructor called with parameters (3,4). (if there is a (void) constructor: x() will create an instance, x only a reference). Constructors in class declarations (e.g., z(3,4) above) must not have input variables among their arguments.

E.g. the following would be illegal:


   INP int iDim;
   class foo z(iDim,4);

The reason for this restriction is that the input values of a prog_unit are copied into the associated variables only much later (namely at execution time), so that the above code would look grossly misleading (the constructor would always see the value iDim=0). Membership functions are then invoked in the usual way, e.g.

   ...
   y[3] = z; // make y[3] reference instance z
   y[4] = foo(1,2); // create y[4] as new instance
   u = z.method(4.56); // invoke method of z
   u = y[3].method(4.56); // the same via y[3]
   y[3] := z; // make y[3] a copy of instance z
   ...

Accessing members of an uninstantiated class variable or using it as a function argument will cause a runtime error.

Class instances can occur as members of structs:


   struct mix {
      int i;
      class foo obj;
   } x,y;

Currently, such members are initially uninstantiated (replacing obj with the full constructor obj(args..) would not be accepted); they also cannot be accessed with the "naive" syntax

   x.obj.method(4.56); // does not work

Instead, one has to use an auxiliary variable

   class foo p;
   ...
   p = x.obj;
   // now access x.obj via p:
   p = foo(1,2); // instantiation
   p.method(4.56); // invoke foo-method on obj

To make class variables participate in load/restore, declare them with an explicit static keyword, e.g.

   static class foo x, y[10], z(3,4);

instead of the above. This requires that the class has a RESTORE method defined (the older LOAD method permits no uninstantiated variables). Class arrays can be dynamically resized when declared without dimension:

   static class foo z[]; // has initially 0 elements
   z = resize(20);
   printf("%d\n",dimof(z));

will create an array with 20 uninstantiated elements and print out the new dimension. Later resizes that make the array grow will leave already instantiated elements untouched. When an array is shrunk, unused instantiated elements will be freed if they are not referenced by other variables. The statement

   x = NULL;

puts x back into the uninstantiated state (when x was the last reference to an object, the object will become freed too, otherwise it is kept alive for the remaining references). The instantiation of a class variable w can be checked with the ! operator: !w is true (value=1), when w is uninstantiated, false (0) otherwise, and !!w is the logical complement (simply using w will not work, since it cannot be interpreted as a number).

Template Classes:

Classes can be implemented to offer member functions that work on choosable data types. Such classes are called template classes and can, e.g., offer structured storage and access of general objects. When creating instances from a template class, the necessary type parameters are passed in acute brackets:

   class foo <thistype,thattype> u(4711);
   ...

(assuming that foo denotes a template class that expects two type parameters, which here have been chosen as class thistype and class thattype (note that the class keyword is implied within the <..> brackets; currently, allowable template types are only (non-template-) classes or struct types. For details about the implementation of template classes, see the Neo Tutorial. For a template class that does not use any of its template types in the constructor function(s), the <...> can always be omitted, or some of the type names be specified as asterisks '*'. This will restrict the callable member functions to those that do not involve any of the unspecified types.

Public Functions and Class Containers:

Parameterless functions of return type void may be declared public. In the presence of one or more public functions, the created prog_unit will become a NST class container, with one method subunit for each public function. The interface of a method subunit will consist of the subset of static variables of its associated public function that were declared with INP and OUT keywords (these variables will be withdrawn from the interface of the enclosing class container). The name of the method subunit will be the name of its associated public function (truncated to a maximum length). Public functions can call other functions (public or not), and they share all global variables. Note that this offers a flexible and convenient way for a rapid prototyping of NST class containers, in particular, if the public functions are used as wrappers of compiled C-functions introduced with the #import directive. A call of a public function involves only a synchronization of the input and output pins that belong to the public function. To also synchronize the pins of the main prog unit, call the routine sync_ports().

Implicit Declarations:

By default, scalar float variables need no explicit declaration. If an undefined identifier occurs within the scope of a function, it will be implicitly declared as an automatic float variable that is local to the function. If it occurs globally, it will be implicitly declared as a global static float variable. The possibility of implicit declarations introduces some subtleties unknown in standard C:

   // start of program here!
   foo1(void) { i=5; printf("%g\n",i/2); }
   foo2(void) { i=7; printf("%g\n",i/2); }
   foo1(); foo2();

will print the values 2.5 and 3.5, since in each routine i is an implicitly defined variable of type float that is confined to the scope of its definition, i.e., the respective enclosing function. If the above is changed to

   // start of program here!
   foo1(void) { i=5; printf("%g\n",i/2); }
   int i;
   foo2(void) { i=7; printf("%g\n",i/2); }
   foo1(); foo2();

the output will now yield 2.5 and 3, since foo2 (but not foo1()!) now falls into the scope of the newly defined global int variable, so that the division now is treated differently (the prog unit's printf function is nice enough to accept also integer arguments for its %g format). If foo2() is changed to use the explicit declaration float i=7; the old behavior is restored. If such things appear too dangerous, the #noimplicit directive at the head of the program allows to re-instate the standard C policy to require declarations for all variables. A directive #implicit \"ijk" offers the compromise to allow only single-letter variable names from the given set of letters.

Operations:

Supported unary operators are ++ and -- (both as post- and prefix), supported binary operators are: = + - * / % += -= *= /= < > <= > != and == with the same rules for forming expressions and assignments as familiar from C. Not supported is the comma-operator, the ()?: - construct and any operators involving the tilde, | or &. As a result, boolean expressions are restricted to a single comparison of two operands, or to a single operand (use brackets to process more complicated boolean expressions). The non-standard assignment operator := allows to evaluate matrix expressions (cf. below). The '+', if between pointer variables or string constants, is interpreted as string concatenation, e.g. p = p + "hello".

Control Constructs:

Supported control constructs are while() for(;;) (including break and continue ) and if() - statements (including an optional else ), unsupported are switch and do.

Comments:

C-style and C++ style comments can be used. A C++-style comment that follows an INP or OUT declaration on the same line is automatically used to make for Neo an info-text for the corresponding input or output field.

Built-in Functions:

Supported functions are the usual float valued math functions (.sqrt, log, log10, exp, rint, floor, ceil) and the trigonometric functions ( sin, cos, tan, sinh, cosh, tanh ) together with their inverses. The function pow is replaced by the binary operator ^. The function abs returns the absolute value of its argument, but keeping the type of the argument in the result. In addition, the printf and sprintf functions are supported.

A few additional functions are provided:

erf and erfc (error and complementary error function), fermi(x)=1/(1+exp(-x)), step(x)=(x>0)?1:0, noneg(x)=(x>0)?x:0, rnd(x) returns random number from uniform distribution in interval [0..x], srand(x) sets seed value for rnd). grnd(x) returns gaussian random numbers with variance x and finally isnan(x) which is 1 if x is the non-value NaN, 0 otherwise. The int valued functions dimof(a), bgnof(a) and endof(a) expect an array or class array parameter a as their argument and return its dimension and the value of the start and the end (i.e., the highest allowable index value plus one) of the index range of a so that

     for (i=bgnof(a); i<endof(a); i++)

will correctly iterate over the index range of array a. This is important when iterating over elements of arrays that have been passed as parameters to functions: their index range may appear shifted, due to calls such as f(a+5) etc.) If no shift is present, bgnof(a)=0 and endof(a)=dimof(a). Additionally, the special function dim_from_mask(msk,d) where msk is a string and d a nonnegative integer dimension value computes the dimension of a subvector created according to msk from a subset of elements of a d dimensional vector (the subvector itself is not created by this routine; it provides only the dimension calculation). For the format of the mask string msk see the manual page of class extract

Predefined functions with array arguments:

A few functions admit array arguments and interprete them as strings: strlen(s) returns the length of the string, system(s) issues s as a system command (cf. section SYSTEM CALLS). printf(fmt,...) and sprintf(buf,fmt,...) are analogous to their C counterparts, but with an extension to print arrays (see Printing of Array Elements below). The call strdup(s,n) returns a string that is a copy of the first n (or all, if n is absent) characters of s. An overloaded throw() function throws an exception and is provided for exception handling (cf. section EXCEPTION HANDLING below) All remaining built-in functions take a single scalar argument. The set of built-in functions can be extended by user-defined functions with the #import directive for accessing functions in a shared library (see below). If a syntax error is detected, the returned handle will be NULL, and an error message that (approximately) locates the error will be printed.

RESERVED WORDS:

The following may not be used as variable identifiers:

abs, acos, acosh, adapt_opnd, adapt_opnds, adapt_container_opnds, adapt_unit, asin, asinh, atan, atanh, bgnof, break, byte, case, catch, char, ceil, class, continue, const, cos, cosh, ctrl_opnd, ctrl_unit, dimof, dim_from_mask, endof, else, erf, erfc, errno, errtype, exp, exec_opnds, exec_container_opnds, exec_unit, fermi, float, floor, for, int, if, init_opnd, init_unit, int, iterator, log, log10, isnan, main, M_PI, NaN, new noneg, printf, public, reset_opnd, reset_unit, resize return, scalar, sgn, sin, sinh, sprintf, sqrt, static, step, strdup, strlen, struct, system, tan, tanh, throw, while, INP, OUT, NX, NY, NZ, NT, NULL, PI, SHARED, X, Y, Z, T. Further reserved C-keywords may be added to this list in the future. It is thus recommendable to avoid C keywords for variable identifiers or functions in advance.

STRING AND ARRAY HANDLING:

Array elements and string characters can only be accessed by indexing. However, functions that expect array parameters may be passed offset "values", provided that the array identifier comes first, e.g.

    char a[] = "hello world";
    pi = 3.14;
    printf("%s\n",a+pi-1);

will print "llo world", but replacing s+pi-1 by, e.g., pi+s-1 or (s+pi-1) would not be accepted. If a is an array of dimension d, and n>=0, the usage of a+n as an array parameter will be treated as an offset array with the shifted index range -n..d-n. If such arrays are passed to #import-ed functions, their upper dimension will be passed as d-(int)n. In this case, negative shifts n should be avoided, since no information of the lower index bound is passed to #import-ed functions. Note also, when a is an array and not a pointer, a new value can not be set with s="new text". Use

    sprintf(a,"%s","new text");

instead. If a is too small, any assignments will be suitably truncated. Finally, if v is a float variable, &v will be accepted as an array with a single element of value v. Redundant combinations such as &v[0], however, are not supported. If a float array is used as a string, the first float element that, if rounded to an int, is 0, determines its end. If there is no such element, the length will array dimension. prog_unit functions need not have extra parameters for passing array dimensions: such information is always implicitly passed to their C implementation function (cf. below). The '+' between pointers (and, if not in first position, also arrays) that have been declared as char* performs concatenation, e.g, if s is a pointer variable holding a string, the statement

     s = strdup(s,n) + p + strdup(s+n);

inserts a string p after the n-th character into s. NOTE: Unlike in C, initialization of automatic variables is restricted to constant values. This restriction does not apply to pointers (cf. below).

Printing of Array Elements:

The predefined printf(fmt,...) and sprintf(buf,fmt,...) functions provide a convenient extension to print array elements: Each conversion specifier may be preceded by the non-standard length-modifier v, optionally followed by a decimal integer d. The corresponding argument is then expected to be an array whose elements will then all be printed, applying for each the current format directive. If a non-zero decimal integer d was given, after each block of d array elements an additional newline character will be inserted. If no field width was specified, an additional space character will be inserted between otherwise adjacent array elements. Example:

     printf("A vector: %5.2v3f---%vd\n",afVec,afVec);

will print

   A vector: 1.00 2.00
      3.00 4.00
      5.00 6.00
      7.00---1 2 3 4 5 6 7

when afVec is a vector holding the first seven integers. When the array argument is NULL, nothing will be contributed to the output by the associated conversion specifier.

Dimension Revision Directive:

For #import-ed functions, a dimension cast allows to pass an array (or pointer) parameter with an apparently (for the called function) revised dimension:

    static float a[]={1,2,3,4,5}, i=1;
    ...
    myfun((DIM 3) a, (DIM i+1)(a+1))

will make myfun see the arrays 1,2,3 and 2,3 of dimensions 3 and 2, resp. (there is no analogous directive for functions that are defined in the prog unit source code). Any cast with DIM specified larger than endof(a) will be without effect. A cast to dim 0 or to a negative dim will be equivalent to passing a NULL pointer.

POINTERS:

Pointers can be declared in the usual way, e.g.

    static float *p=NULL,*q="abc";

There is no pointer arithmetics; pointers are just provided to simulate arrays with dynamic dimension:

    p = new(d); // allocation of d elements
    p = resize(n); // resize to n elements
    resize p,q,r = n; // resize several pointers
    p = p + "hello"; // string concatenation
    p += "hello"; // the same effect
    p = a + i; // ref to array el a[i]
    p = (DIM 2) a+i; // dto., but make .p appear as
                      // if only 2-dimensional
    p = vecfun(42); // allocation with result
                         of vector valued function

After the first statement, p can be used like an array variable with d elements. The second statement resizes this 'array' to n elements, keeping the first min(n,d) values intact. The third statement uses a more compact notation to achieve the same for several pointer arguments in one statement. The fourth statement concatenates two strings (with a char pointer var p interpreted as a null-terminated string) and resizes the result to the required length. The fifth statement achieves the same effect, and the sixth statement requires a to be an array variable. In this case, p will have dim(p)=dim(a)-i and an allowable indexing range of bgnof(p)=0..endof(p)=dim(p). The dimension cast in the next statement allows to make p appear with a smaller dimension (this is useful, e.g., in conjunction with matrix algebra expressions). The last statement allocates p to the result of a vector-valued #import function. If the return type of vecfun is char*, it can also be used in string concatenations, such as

    p = p + vecfun(42);

There is a subtle but important difference in the interpretation of arrays and pointers after a '=': an array introduces always a reference (such as the last statement above) and may only be followed by an address offset, but a char or byte pointer always introduces a string assignment or a string concatenation and may only be followed by further char/byte pointers, arrays or string constants. After a '+=', we always have string concatenation, and there is no difference if an char/byte array or a pointer follows. (there is no analogous definition for the action of + or += between float arrays). There is no need to care about freeing memory: when the number of references to a memory block drops to 0, it is automatically freed. If p is the only reference to a block,

   p = NULL;

will enforce its freeing (.p=resize(0) or p=new(0) are equivalent). The special pointer value NULL may also be passed in place of an array argument to a function. A quick way to copy n elements in the midst of an array into a pointer variable is p=new(n); p=a+i; p=resize(n); Alternatively, p = (DIM n) a+i makes p appear as a pointer pointing to a n-dimensional array with first element a[i], with no copying involved. No other types of pointer assignments are allowed, i.e., there is no pointer arithmetics such as in C, not even assignments such as p=q+i when q is a pointer (and not an array). However, pointers can be passed as float* or char* arguments to a function (in this case, shifts, such as f(p+i) are allowed). Within the scope of the function, a passed pointer argument appears as an array (and no longer as a pointer!) of corresponding dimension. As a consequence, pointer assignments such as the above are no longer allowed inside the function.

Pointer Initialization:

While static pointers are initialized only once at the beginning (and thus require constant initializers), automatic pointers within a function are newly allocated at each invocation (and deallocated when the function returns). Example:

   float fac(float i) {
      float *p=new(10+i);
      if (i==1) return 1;
      else return i*fac(i-1);
   }

The call fac(3) will recursively allocate three memory blocks of 13, 12 and 11 elements, resp., each referenced by the instance of the pointer variable p within its current scope. Upon return of the function invocations, the three blocks will then be freed in the reverse order of their creation. Pointers defined as INP cannot be initialized (the only exception is the empty initialization INP float *p=; to suppress load/save of their data (cf. below)). Conceptually, they inherit their elements from the (dynamic!) input field that they represent. Pointers defined as OUT can be initialized as usual, making the dimension of the corresponding (dynamic!) output field vary accordingly. Initialization of a pointer also allows to control whether it participates in load/save operations (cf. LOAD AND SAVE below). Initialization of a pointer p can be checked with the value of dimof(p) or with the same syntax as for objects: !!p is true (non-zero) when p has been initialized, false (0) otherwise (and vice versa for !p).

EXCEPTION HANDLING:

The predefined function

   throw(char *pcErrType, char *pcFmt, ..)

is provided to throw an exception. The first argument pcErrType should be a string of zero or more colon-separated identifiers

   pcErrType = "ident1:ident2:...identk"

and is used to specify a type for the exception. The remaining arguments are parsed as in a printf statement to associate with the exception an optional error text. The call will deposit pcErrType and the error text in the two predefined global char arrays errtype[] and errtext[] and starts the search for an exception handler. Exception handlers can be defined within the prog_unit in the form of catch block chains

   catch(Arg1) { ... statements ... }
   catch(Arg2) { ... statements ... }
   ...
   catch(Argk) { ... statements ... }
   
In contrast to the familiar C++ construct, here each Argi is a constant string of the same syntax as pcErrType that is matched against the pcErrType of the current exception. The first catch block whose Argi matches some initial portion of the current pcErrType will be executed to handle the exception, after which processing will continue after the end of the catch chain. If no handler in the nearest following catch chain matches, the exception will be propagated one calling level up and the search will recursively continue from there, until either a matching handler is found, or the top level is reached. In this case, propagation continues into the NST execution chain (there can be handler units instead of catch blocks), and if this fails, too, the exception finally appears as a runtime error message at the top level. A catch block that has started handling can still pass control to subsequent catch block candidate by raising itself an exception (in particular, a parameterless call of throw() will pass on the current exception, with errtype and errtext unchanged), or it can be left at any point with a break statement occuring at block scope (i.e., not nested in a for or while body). For throwing exceptions from within an #import function or class, see Section PASSING EXCEPTIONS FROM EXTENSION LIBRARIES. In contrast to C++, there is no try block construct to restrict the code that can throw an exception. The prog unit builtin functions may throw exceptions of one of the following pcErrTypes:

1. exceptions signalling bad argument parameters:


    "badarg:type"
    "badarg:value"
    "badarg:array"
    "badarg:array:type"
    "badarg:value:none"
    "badarg:array:dim"
    "badarg:object"

2. exceptions signalling an attempt at a bad operation:


    "badop"
    "badop:div0"
    "badop:index"
    "badop:array"
    "badop:array:dim"
    "badop:array:type"
    "badop:object"

3. exceptions signalling a detected bad result outcome:


   "badres:array"
   "badres:singular"

PROG UNITS AS NST EXCEPTION HANDLERS:

When the entire main portion of a prog_unit consists only of catch blocks, the unit becomes a NST exception handler. A NST exception handler does no longer react to exec and adapt. Instead, it plays the role of a catch block at the NST unit level, i.e., one can arrange such units or chains of them to catch exceptions propagated from other units (the search following analogous rules as within a C++ program, with the units playing the role of (possibly nested) statement blocks). The exceptions to which a prog_unit will respond are then precisily those that are defined by its catch blocks that make up main.

MATRIX ALGEBRA EXTENSIONS:

The non-standard vector assignment operator ":=" allows to assign to an array variable a vector or matrix expression. For example, let A and B be float arrays of suitable dimension, then we may compute expressions such as

   A := 2*(A^2+2*B+1)/B;

Here, the operators '*', '/' and '\\' between arrays are interpreted as matrix multiplication, right and left matrix division, resp. (i.e., A/B=A*Inv(B) and A\\B=Inv(A)*B), in any other cases they are the usual scalar operations. Addition of a scalar and an array is elementwise, i.e., the entire array becomes shifted (therefore, 1 is not to be confused with the identity matrix!). HINT: A^0 (cf. below) yields an identity matrix of the size of A (which must be a square matrix). An assignment to a subarray can be made with the syntax

   A[first..last] := someOtherArray;

Here, first may also be larger than last. For instance,

   A[dimof(A)-1,0] := A;

reverses the array A. NOTE: don't confuse vector expressions with string addition:

   char *pc="hello", *w="world", *r1, *r2;
   r1 = pc + w; // concatenation
   r2 := pc + w; // elementwise addition

Vector or matrix expressions may also contain calls of array-valued #import library functions, e.g., if eigval() is such a function:

   A := 2*eigval(B) - 1;

(such calls include an automatic conversion of return types, e.g., from byte to float, if necessary). As a special case, instead of A there may also be a scalar variable. This is treated as if assigning to an array of dimension 1 and is handy, e.g., for computing scalar products or other matrix algebra expressions with 1-dimensional results:

   scalar := vec1*vec2;

Two further operators on arrays require a square number of array elements and assume in this case that their array operand is a square matrix: The single apostrophe "'" will form the transpose, the hat-operator "^" (followed by an integral scalar exponent) will compute an integral matrix power (this includes the matrix inverse and the identity matrix as special cases). Example ( A square matrix):

    A := (A + A')^(-2);

NOTE: applying either of these two operators to a rectangular matrix will lead to a meaningless result and will go unnoticed, if the matrix has a square number of elements (the reason for this is that the prog_unit actually knows nothing about matrix shapes, except in bilinear products, where the correct matrix shape can be inferred (cf. below)). The arrangement of matrix elements in an array is taken as row-wise, e.g., (a00,a01,a10,a11) for a 2x2 matrix. Array variables can be replaced everywhere by pointers. This admits, e.g., to cut out rows or shorter pieces of arrays. If a matrix division contains a rectangular (non-square) divisor, it will be interpreted as the corresponding (left or right) multiplication with the pseudoinverse. This includes the least-square solution of overdetermined linear equations as a special case. While all matrix algebra expressions can work with float arrays, those that involve higher-order operations on matrices (matrix division, inversion and exponentiation, except for the trivial exponents 0 and 1) will produce a run-time error when used with int or byte operands (scalar scaling, addition multiplication, transpose and the special matrix functions described below are ok for all three data types).

Syntax for Matrix Algebra Expressions:

Handling of linear algebra expressions is based on interpreting linear arrays as matrices and on deriving their matrix shape from the linear dimensions of the arrays, when they occur in matrix products or divisions with two factors. Therefore, all matrix expressions are restricted to sums of terms each of which contains at most two matrix factors (if there is only a single matrix factor, there is no way to infer its matrix shape, but we also don't need it, since matrix addition is just elementwise, regardless of matrix shape). Note also that a factor may be an arbitrary power of a square shaped matrix, i.e., products such as A^5*(A+B)^(-2) are perfectly possible. There are also two special restrictions on the use of brackets in matrix expressions:
1.:
a bracket must not contain any matrix products or divisions, except those that can be written as powers of a square matrix (e.g., (A*A+1) is forbidden, but (A^2+1) is allowed).
2.:
If a bracket contains a matrix expression, the expression must start with an array (i.e., instead of (2-3*A) we must write (A*(-3)+2) or -(A*3-2)).
Both restrictions are for reasons of simplified parsing and are not really limiting; they only enforce that more complex matrix expressions (with products or divisions of more than two factors) must be built up in several steps from simpler (at most bilinear) expressions.

Cross Product:

Any 3-dimensional product of two float vectors that are themselves 3-dimensional float vectors is interpreted as the cross product (since it cannot be the usual matrix or scalar product in this case). Thus, the right hand side of

   y := a*b + 4*c*(d-2);

is evaluated as "a x b + 4 c x (d-(2,2,2))" in this case.

Special Matrix Functions:

First of all, any one-argument float functions (such as sin, cos, but also self-defined functions or #import'ed functions) can in a vector expression be applied to a vector argument. In this case, they will be applied elementwise, yielding a result array of the same dimension as the argument array. E.g., the update of a simple neural network with weight matrix W and activity vector v is the single line

   v := fermi(W*v);

Additionally, there are three special functions to access rows, columns or subrectangles of a matrix A:
col(A,j,n):
j-th column of A; n must equal the column dimension of A
row(A,i,n):
i-th row of A; n must equal the column dimension of A
rect(A,x,y,dx,dy,n):
rectangular submatrix of dx columns and dy rows, starting at column x and row y. Again, n is A's column dimension.
These functions are only recognized within matrix assignments, and they may appear even as the left value of a ":=" assignment operator.

Example:


   row(A,i,n) := row(A,j,n);

This copies the contents of row j of matrix A into into its row i (overlaps in the case of rectangles are permitted). If part of a specified rect-angle lies outside of a matrix, the elements in this part will be returned as zero. In contrast, the col() and row() functions use wraparound.

Imported Matrix Functions:

Further vector or matrix functions can be implemented as #import library functions (however, such functions may not be used as left values). If the element type of the assigned array does not match, there will be an automatic conversion. Note the following difference between ":=" and "=" assignments (assuming a function float *foo()):

   float *vec;
   ...
   vec := foo(); // (1)
   vec = foo(); // (2)

Assignment (1) is a vector assignment that does no allocation for vec, i.e., in this case vec must already have the same (or a larger) dimension as the return value of foo() (or the ::= operator is used to enforce the assignment even in case of a dimension mismatch, cf. below). Assignment (2) is actually a redirection of the pointer vec to the array that is the return value of foo(). In this case, vec automatically assumes the correct dimension and need not be allocated before. In (1), vec could also be replaced by an array, in (2) not. Currently, calls of vector valued functions, such as foo(), cannot be directly used as function parameters (use an assignment to an auxiliary pointer for this).

Vector norm computation:

Additionally, |vec|[p] (where p is a numeric expression that evaluates to a non-negative value) returns for p>0 the p norm of vec, while for p=0 the arithmetic mean of all elements of vec is returned. Here, vec is a vector expression restricted in the same way as for a bracket in a matrix expression (such as A-2*B, i.e., vec must start with an array identifier and have no matrix products, cf. 1. and 2. above). If the part [p] is absent, the default p=2 (i.e., the euclidean norm) is assumed. For instance, to compute the standard deviation of all elements of a vector A, one may use

     stdev = | A - |A|[0] | / sqrt(dimof(A));

Handling of Dimension Mismatches:

If a sum of vectors contains vectors that are longer than the result dimension, the excess elements will be ignored. Similarly, if one or several vectors are too short, the sum will be formed, but some elements in the result may not be set by any summand(s). In both cases, run-time warnings are issued that can be suppressed by specifying the assignment with "::=" instead of the usual ":=". An exception to this treatment are scalar summands; they are considered as shorthands for arrays of the required result dimension, but with all elements equal. The result dimension of a vector assignment is the dimension of the array on the left hand side. If the left hand side is a scalar variable, it is treated as if it were an array with a single element, e.g., to compute a scalar product f of two vectors v1 and v2, one can use

    f := v1*v2;

Within a bracket, the result dimension of a vector sum is determined by the dimension of the leftmost array in the bracket. In matrix products and divisions, all dimensions must match precisely, otherwise the operation cannot be carried out and a run-time error is issued. Depending on the number of array elements of their factors, different matrix products in a sum may become interpreted as matrices of different shape (but with the same number of elements). In this case, their sum will still be computed by elementwise (vector-) addition, although the result of course cannot any longer be interpreted as a matrix sum.

EXECUTION OF CREATED UNIT:

Will carry out the statements in pcProg. Initialization for static variables (arrays and const values) will only be carried out once before any other operation). Communication with other units is achieved by making some or all of the special arrays X[], Y[], Z[] and T[] or additional static variables visible as input or output fields of the created NST unit (this requires an entry of 'i' or 'o' at the corresponding character positions of pcFmt or the use of INP and OUT instead of static ) The predefined goto label EXEC can be used to specify an entry point for execution that differs from the first statement. This is achieved by inserting the sequence `EXEC:'' before the desired entry point.

ADAPTATION OF CREATED UNIT:

An adapt_unit call has no effect on the unit, unless the program text contains the sequence `ADAPT:''. In this case, it will specify an entry point that is used for each adapt_unit call (similar as explained for execution, see above).

LOAD AND SAVE:

All explicitly defined static variables that are not explicitly initialized in their declaration are saved and loaded under the NST save/load methods. Thus, initialization in a declaration can be used to selectively exclude variables (such as, e.g., a big array) from (possibly time-consuming) save/load operations (even a void initializer list, such as for a[n]= suffices (this can also be applied to class instances)). Also, omitting the keyword static will exclude a variable from load/save, unless it is declared as INP or OUT (these always imply static). If a variable is declared as INP, it is also considered as being initialized. Further, the up to four predefined arrays X..T are never included in the saved/loaded set (if it is desired to include them, they must be declared explicitly in the program instead). The matching of loaded to existing variables is by their name and the name of the function in which they occur. Thus, introduction of new or reordering of the old variables will not do any harm (but redimensioning will!).

Load and Save for Pointers:

Dynamic memory referenced by static pointers is handled analogously: if the pointer has not been explicitly initialized in its declaration, its currently referenced memory block will be included in a save, if it does not coincide with the memory of an array (i.e., it is required to by truly dynamic). A similar rule applies for loads: if the file contains data for a pointer variable p that has not been explicitly initialized, an implicit p=new(d) operation will be carried out such that p can accomodate the data, then the load will be made. An exception are pointers that are INP variables: their dimension will not be changed under a load operation, and any surplus data will just be ignored. To benefit from loading of pointer data, of course, requires to make processing of a pointer dependent on whether it references any elements or not. While queries on the 'value' of a pointer are not permitted, both !p and !!p can be queried and are logical indicators that p references no elements or has been initialized, resp.

CONTROL MODES:

In addition to the EXEC and ADAPT goto labels, there are two further goto labels INIT and RESET that define similar entry points used when the NST_INIT or NST_RESET message is sent. Imported class objects (cf. below) can define a CTRL_classname method that will become invoked for each class instance and for each ctrl call sent to the prog_unit.

Note:

For backward compatibility, the abovementioned goto labels may be preceded by a (redundant) keyword case.

PROG_UNIT AS AN OPERATOR UNIT:

The presence of a call to the parameterless void function exec_opnds() or adapt_opnds() makes the prog_unit into an operator unit. Each call of exec_opnds() is translated into an exec call for each of the prog_unit's operands (the number of operand units can be interactively adjusted in Neo, or set with nst_operands_of() at the programming level). If the exec_opnds() call appears within the scope of a public function, the operator status will instead be given to the corresponding method subunit (or its referencing use_method or use_named unit). Note that this happens only, if the exec_opnds() occurs explicitly in the public function and not hidden inside another function call. In particular for iterators implemented in this way, there usually will be nested references to the same method subunit. To avoid side-effects, it is recommendable to use for the referencing use_method unit the BY_REDIRECTION policy for transporting of output values. Similar rules apply for the adapt_opnds() call. The only difference is that here adaptation occurs in right-to left order, i.e. proceeds from the rightmost, last operand to the first operand unit.

PROG_UNIT AS A VIRTUAL OPERATOR UNIT:

The exec_container_opnds() call behaves analogously as an exec_opnds() call, but executes as its operands the successors of the container that embeds the prog_unit (i.e., the prog_unit becomes a virtual operator). The exec_container_opnds() call has the restriction that it may not occur inside a public function. NEO note: If there are several prog_units with exec_container_opnds() calls inside the same container, Neo will automatically restrict all exec_container_opnds() calls to use the same (interactively set) number of operands.

CALLING SELECTED NST UNITS:

Additionally, there exist the older exec_opnd(i), adapt_opnd(i), init_opnd(i), reset_opnd(i) and ctrl_opnd(mode,i) functions. These allow to execute, adapt init or reset a fixed (the i -th) successor unit of the prog_unit. Instead of the the numeric position i the argument can also be a string value that specifies the referenced unit by its name. The ctrl_opnd function has an additional mode argument. This is not a normal argument; it must be a token that is either a const int value or one of the NST ctrl mode identifiers (upper or lowercase, with or without the NST_ prefix). While exec_opnds() and adapt_opnds() allows only to execute a consecutive (but interactively adjustable) number of operands, the other five functions allow to select arbitrary, but fixed (no interactive adjustments are possible) operand positions. Also, in this case there is no visual indication of the selected units in Neo. A value of i=0 results in a recursive call of the prog-unit itself (note, however, that all global variables are implicitly taken as static, i.e., no copies for each new calling level are made). Negative values of i result in calls to predecessor units. If they occur within a public function, counting of successors is with respect to the position of the calling use_method unit instance.

Note:

This unit allows also to write into its float input pins (not into its string input pins) and thus gives a means to change the outputs of units connected to these inputs.

Example:

Assume a unit u with iDimInp=.iDimOut and pcFmt="io--" has been created and for pcProg, the following code fragment

    printf("Iteration %3.f:\n",count++);
    for (j=0; j<NX; j++) {
       Y[j] += X[j];
       if (Y[j)>100) Y[j]=100;
       printf("Y(%3.f) = %7.2f\n",j,Y[j]);
    }
    return;
    INIT:
    count=0;
    RESET:
    for (j=0; j<NX; Y[j++]=0);

was specified. Then u will be a unit that at each exec_unit call increments its outputs X_out[] by the corresponding current inputs X_in[], however, imposing an upper limit of 100 on the result. It will also print some information. The output values will be reset to zero whenever a call ctrl_unit(m,u) with m=NST_RESET or m=NST_INIT is made, in the latter case, the global iteration count count (note that variable values are preserved between calls!) will also be set to zero.

DEBUGGING:

If the beginning of the program contains the token #debug, any run-time error messages will show the portion of the source code where the error occurred (setting the #debug option causes no runtime penalty; only some extra memory to keep the source code text will be allocated). Additionally, the following four function calls are provided (these can also be obtained from #importing the predefined import library debug.so, which is actually what the #debug directive does).
print_lines(int iYes)
turn on/off printing of executed programming lines
print_instructions(int iYes)
turn on/off printing of executed instructions
print_stack(int iYes)
turn on/off printing of instruction stack pointer
The token #break may be placed among the source source statements to insert a break point. When it is encountered and the #debug option has been set, program execution stops and a few commands can be used to inspect variables. For a list of available commands, enter '?', followed by return. To continue, enter 'q'. Without a prior #debug option, the #break directives have no effect.

PERFORMANCE ISSUES:

Execution of prog_unit code is about 10-12 times slower than compiled C code. On a 100 Mhz Pentium, a function call costs about 15us (each parameter adds a few us). There is a significant optimization of for loops that are of the form

    for (statement; i < expr; i++) ...

(here, i can be any variable name, and instead of '<' there can also be '<=','>' or '>=', with i++ replaced by i-- in the latter two cases). Therefore, time critical loops should preferably be written in that form. (note that a general loop can always be transformed into the specified form by a suitable choice of expr!).

SYSTEM CALLS:

The function system(char *cmd) allows to invoke system calls. However, as a safety measure, the prog_unit will carry out system commands only when the following three conditions are fulfilled: (i) the home directory contains a permission file named .nst_permit_system_calls, (ii) this file is write protected, and (iii) it contains a line that specifies a pattern that matches the desired command cmd (file globbing wildcards may be used to specify the pattern). Example: if .nst_permit_system_calls contains the two lines

   ls
   wc *

system("ls") and system("wc myfile") will be carried out, but not system("ls mydir"), since only the first two commands match a line in .nst_permit_system_calls. (currently, even trailing or interspersed blanks [but not leading blanks] are significant, thus not even system("ls ") would be carried out [but don't rely on this in the future]). Lines starting with a hash symbol are considered as comments. A .nst_permit_system_calls file with a line containing a single "*" will allow any system command. THIS CAN BE VERY DANGEROUS!

EXTENDING THE SET OF BUILT-IN FUNCTIONS:

The set of predefined functions can be dynamically extended with zero or more directives of the form

    #import sharedLibName

(for backward compatibility, there is also the older command #loadlib sharedLibName.so that requires to specify the (operating system dependent) file suffix). The token sharedLibName may optionally be enclosed in apostrophes (apostrophes become necessary when sharedLibName contains characters, such as '+' or '/', that would otherwise cause the parser to split it into multiple tokens). Each #import directive must precede the program text and each sharedLibName must specify a shared object containing definitions of the to-be-added functions (if no path is given, sharedLibName is searched in LD_LIBRARY_PATH). In case of name conflicts when importing from several shared libraries, a suffix can be specified to restrict the imported definitions from each library, e.g. (note the necessity of apostrophes in the first #import due to the '*' in the name)

    #import "string.str*"
    #import string.islower

will import from shared library string.so all definitions that start with "str" plus the single routine named "islower" (the usual Unix file globbing wildcard rules apply; note also that the file suffix ".so" of the shared library is not specified when using the #import statement). No such control is possible with the older #loadlib directive (which, however, may be useful to import from shared libraries that contain one or more wildcard characters in their base name). Directives #verbose and #silent can be used to activate/deactivate a listing of the imported function definitions. A source file for adding three functions min(x,y), fun(x), and quad_sum(x,y,z,t) (all argument and result types are restricted to the type float) would have to contain the following definitions:

    char *FUNCTIONS[] = {"min<2>","fun<1>","quad_sum<4>",(char*)0};
                       
    float min(float x, float y) { ... }
    float fun(float x) { ... }
    float quad_sum(float x, float y, float z, float t) { ... }

Here, the identifier *FUNCTIONS[] and the specification of the arities in the format "<number>" are a fixed convention, the identifiers can be freely chosen, the arities must be in the range 0..10. The FUNCTIONS array MUST be terminated with a (char*)0. For a better name space separation, the symbol "FUNCTIONS" may also be chosen as "FUNCTIONS_xxxx", where "xxxx" denotes the base name of the specified shared library (which then must be a legal C identifier). This is the now recommended practice (the prog_unit will first search for the more specific symbol FUNCTIONS_xxxx, and if not present, will try FUNCTIONS as a fallback). In order to pass more (and a possibly varying number) of parameters to a function func, and to use also arrays as parameters in order to pass back result values, and to define package-specific constants, the following additional specification formats can be used in the FUNCTIONS[] array:
"ident"
(to be distinguished from "indent<0>" for a parameterless function) introduce a constant ident in the name space of the prog_unit, taking its value to be the value of a float variable of the same name that must be exported by the library (the prog_unit will use the value of this variable at the time of its (the prog_unit's) creation. Any later changes will be ignored).
"func(D)"
define for the prog_unit a function func() with a parameter list specified by the string D. D is a usual C-style parameter list declarations without parameter names. The only allowed type identifiers are byte*, char*, int, int*, float and float* (Example: foo(int*,float,char*)). Additionally, the last parameter may be followed by three dots to indicate a variable number of parameters. If there is no intervening comma between the last type specifier and the three dots, this indicates a variable (possibly 0) number of parameters of the last specified type. If there is an intervening comma, this indicates a variable number of float or read-only char* parameters (as explained for the '-'. old-style specifier). The corresponding library function will be passed two arrays:

     piDim[0..iArg-1] dimension of each argument
     ppfArg[0..iArg-1] adress of each argument

In the case of a variable number of parameters, these two arguments will be preceded by a third argument of type int specifying the total number (fixed and variable) parameters with which the function call was made. In the case of a function with a fixed number of zero arguments, the expected prototype will be parameterless (Note that zero arguments in the variadic case are still passed as a triple (int,int*,void**) of arguments!).

Function pointer arguments:

An #import function can be passed up to 10 function pointers. Function pointer arguments are defined with the usual C-syntax, e.g.

    FUNCTIONS[] = {
      ...
    "myfun(int, float (*)(char*,float), void (*)(void))",
      ...
    }

will define an #import function with three arguments. The first argument is an int value, the next argument is a function expecting a char* and a float argument and returning a float value. The last argument is a parameterless function that also returns nothing. The implementation of myfun will receive in its void **ppvArg[] argument three pointers: one to the int adress of the first argument, and the next two to the two functions that were passed by the caller. Special Note 1: function parameters with array or float arguments (e.g., (*)(float*, float)) are passed to the implementation not with their original signature (i.e., (float*,float)), but with an "augmented signature" in which each float argument is replaced by a double argument, and each float*, int* or byte* argument is preceded by an additionally inserted int argument (i.e., the above would be passed as (int,float*,double), but at the prog unit level the expected function still remains a (*)(float*,float)). The implementation is expected to pass in the additional int parameter the actual dimension of the following array element (or 0, it it is NULL). An exception are char* parameters: unlike byte*, these are not augmented. Instead, the implementation is expected to only pass null-terminated strings in such arguments. The dimension is then always taken to be the string length+1. Special Note 2: the passed function pointers can only be used for calling the function during the lifetime of the #import function call. For implementation reasons, any other operations, e.g., storage for later use or comparisons, will not produce the expected result. For instance, even when the caller passes two identical function pointers, the implementation may receive different adress values (although calling the functions will perform the desired operation). Passed functions can only use int, float, float*, int*, char* and byte* arguments (with char* args assumed to be only used with null-terminated strings whose dimension is then inferred as length+1). The passed functions can either be #import library functions or functions defined in the source code of the prog_unit, as well as some fixed built-ins, such as sin or cos. However, element functions of class instances are currently not permitted (Hint: wrap them in a prog unit source function, this function then can be passed). When a function parameter is the name of an overloaded function, the desired signature must be specified explicitly by appending a type list, e.g.,

     foo(4711,f(int,char*)); // a call, not a declaration!
     foo2(f(float*)); // use of another version of f()

(assuming that f exists in the two indicated variants).

Old style function declarations:

For backward compatibility, there is also an old-style declaration that is somewhat more cryptic, but shorter:
"func<?>"
define for the prog_unit a function func() with a variable number of float parameters. The corresponding library function func must be declared as func(int iArg,float *pfArg), with iArg passing the number of parameters found in the prog_unit call, and pfArg[0..iArg-1] providing their values.
"func<S>"
define for the prog_unit a function func() with a parameter list specified by a string S (for details, see below). The corresponding library function will be passed two arrays.

     piDim[0..iArg-1] dimension of each argument
     ppfArg[0..iArg-1] adress of each argument

In the case of an <S> type specification, the number iArg of arguments in this case is given by the letters of S: each letter from the set '.','*' and '$' denotes a separate argument. A final letter '+' or '-' denotes a possibly varying number of further arguments (in this case, iArg will be explicitly passed to the C as an additional parameter preceding piDim[] and ppfArg[])

Argument types for an old-style declaration are:

"."
specifies a single float argument. ppfArg[iArgPos] will be the adress of a copy of its value (i.e., the original value cannot be changed), piDim[iArgPos] will be 0.
";"
specifies a single int argument. ppfArg[iArgPos] will be the adress of a copy of its value (which will be passed as an int; the original value cannot be changed), piDim[iArgPos] will be 0.
"$"
specifies a string argument. ppfArg[iArgPos] will point to the adress of a null-terminated char* string which holds a copy of the original string (i.e., again access is readonly). piDim[iArgPos] will hold the size of the string (including its null-byte) and is, therefore, non-zero.
"*"
specifies a float array argument. ppfArg[iArgPos] will point to the address of the original array, i.e., elements can be read and written. piDim[iArgPos] will hold the dimension of the array. A parameter &Ident will be treated as an array of one element.
"%"
specifies a byte array argument. ppfArg[iArgPos] will point to the address of the original byte array, i.e., elements can be read and written. piDim[iArgPos] will hold the dimension of the array.
"&"
specifies a int array argument. ppfArg[iArgPos] will point to the address of the original int array, i.e., elements can be read and written. piDim[iArgPos] will hold the dimension of the array.
"+"
may occur only as the last letter of S and indicates a variable (including zero) number of additional, trailing float array argument parameters, each with dimension and address passed as decribed for '*'.
"-"
may occur only as the last letter of S and indicates a variable (including 0) number of additional, trailing single float or string array argument parameters (.ie, types '$' or '.'). They can be distinguished by the value of piDim[iArgPos] (0 in conjunction with a non-NULL ppfArg[iArgPos] indicates that a float was provided; all other cases indicate that an array was provided. IMPORTANT NOTE: the called routine always gets passed a float array, even if the caller provided a char string).
"="
may occur only as the last letter of S and indicates a variable (including 0) number of additional, trailing int argument parameters.
"!"
may occur only as the last letter of S and indicates a variable (including 0) number of additional, trailing int array argument parameters, each with dimension and address passed as decribed for '%'.
":"
may occur only as the last letter of S and indicates a variable (including 0) number of additional, trailing byte array argument parameters, each with dimension and address passed as decribed for '%'.
For all arguments that expect an array parameter, the reserved value NULL may be given. This will be treated in the same way as if an uninitialized pointer had been passed, i.e, the dimension will appear as 0, and ppfArg[iArgPos] will be NULL. Using these definitions, it is easy to extend the prog_unit with self-written libraries. Moreover, it is often very little overhead to include a suitable FUNCTIONS[] line and a few wrapper functions to make part of the functionality in existing NST shared libraries also accessible via function calls from the prog_unit.

PASSING EXCEPTIONS FROM EXTENSION LIBRARIES:

At the implementation level of an #import function or class (cf. section .) the function

   void prog_throw(char *pcErrType, char *pcFmt, ..)

is available to throw an exception, setting the predefined prog_unit variable errtype to pcErrType and evaluating its remaining arguments like a printf statement to write errtext. The call will cause an immediate return to the prog_unit calling level to the position of the next catch block that follows the #import function call (ascending from any nested function calls as necessary).

DEFINING BUILT-IN EXTENSION LIBRARIES:

By calling the function nst_def_builtin_lib(name) one can register a number names as built-in libraries. Any #import directives for these are then resolved by looking up their symbols directly in the main program (i.e., no attempt to locate a separate shared library file is made). Neo will automatically register the names of its compiled folders in this way (therefore, "stdr" is always among the built-in libraries). To avoid name conflicts, the look-up symbols "FUNCTIONS" and "CLASSES" must be replaced for a built-in library "name" by "FUNCTIONS_name" and "CLASSES_name", resp. This is also recommended for shared libraries (the loadlib mechanism searches any loadlib name first for the specific symbols FUNCTIONS_name and CLASSES_name and only then uses the general FUNCTIONS and CLASSES symbols as a fallback).

Type Conversions:

If a float* argument of a loadlib function receives a char* parameter (or vice versa), there will be an automatic creation of a temporary conversion buffer, i.e., one need not care about element size, but a type mismatch will be punished by the additional overhead for buffer creation and copying (for writeable array parameters, copying will occur twice, first when the function starts and a second time to write any changes back when the function returns).

Avoiding Name Conflicts:

When extending the prog_unit with shared libraries, it is important to avoid name conflicts by loaded symbols, e.g., by choosing some reasonably safe prefix string for each identifier. To restrict the visibility of such prefixes to the C implementation level (and to avoid having to use it also for the call names of the functions within code processed by the prog_unit itself) include the prefix strings before the call names in the FUNCTION list (in this list, any identifier that has a colon ':' at its end (and is without a <> -part) will be interpreted as a prefix specifier (with the colon excluded) for the following symbols). Name conflicts can also occur within the prog_unit code. Currently, all predefined symbols for the prog_unit are either all lower case or all upper case letters. This convention will be kept in future extensions, so that name conflicts with future extensions can be avoided by using mixed character symbol names for private extensions. Name conflicts between shared libraries that shall be used simultaneously can be avoided by restricting the #import'ed functions suitably by specifying a pattern (cf. above).

Example:


      char *FUNCTIONS[] = {"__:", /* define name prefix */
                           "I_see",
                           "mul_vec<.*>", sum<?>, strcpy<*$>,
                           (char*)0};

      /* first entry specifies additional prefix "__" for all
         referenced library names that follow.
      */

      float __I_see = 42;
      
      float __mul_vec(int *piDim, float **ppfArg) {
          float fArg0 = ppfArg[0][0]; /* first arg is scalar */
          float *pfArg1= ppfArg[1]; /* 2nd arg is array */
          int i, iDimArg1 = piDim[1]; /* its dimension */
          for (i=0; i<iDimArg1; i++) pfArg1[i] *= fArg0;
          return 0; /* return value not used here */
      }

      float __sum(int iArgs, float *pfArgs) {
          float fSum=0;
          while (--iArgs >= 0) fSum += pfArgs[iArgs];
          return fSum;
      }

      float __strcpy(int *piDim, float **ppfArg) {
          int i, iDim = piDim[0];
          float *pfDest = ppfArg[0];
          char *pcStr = (char*) ppfArg[1];
          int iLen = strlen(pcStr);
          if (iDim>iLen-1) iDim=iLen-1; /* ensure limits */
          for (i=0; i<iDim; i++) *pfDest++ = *pcStr++;
          *pfDest = 0; /* terminating null */
          return i;
      }

implements a constant I_see, a function mul_vec(float,float*) a function sum(float,..) and a function strcpy(float*,float*) for the prog_unit. mul_vec(a,v) multiplies a vector v by a factor a, returning the result in v (note that the dimension of v is not part of the parameter list, but it is available for the called library function __mul_vec in the array piDim). sum(f1..fn) returns the sum of its (variable) number n of arguments f1..fn. strcpy(f1,f2) copies f2, interpreted as a string, into f1. Since we want f1 to become changed, we must use * for this arg, however, we can use t for the read-only string f2.

Overloading of functions:

Within the same #loadlib package, one and the same function name may be defined with several different function argument lists (."overloading"). By using different prefixes, each such definition can be connected with its own implementation function. The corresponding (single-named) prog unit function will then appear as an overloaded function, with the actual implementation function selected by the function name and the chosen argument parameters. Example:

     *FUNCTIONS[] = {
        "_0_:", "foo(void)",
        "_1_:", "foo(int)",
        "_2_:", "foo(float)",
        NULL };

     float _0_foo(void) {
        ... here implementation of foo(void) ...
     }
     float _1_foo(int *piDim, int **ppfArg) {
        ... here implementation of foo(int) ...
     }
     float _2_foo(int *piDim, float **ppfArg) {
        ... here implementation of foo(float) ...
     }



Initialization and Clean-up for Extension Libraries:

If a self-defined extension library defines the special parameterless prog unit function _init() (requiring an entry _init<> in the FUNCTIONS line), the corresponding C-function (which then will need a prefix in order not to collide with the _init() function of a shared library; the type of the _init() function has to be void (*)(void)) will be called whenever the extension library is imported with a loadlib command from a prog_unit (i.e., each new prog unit causes a separate call). This may be used to make package-specific initializations for the calling prog-unit instance. An example is the loading of further shared libraries for which the convenience function

       nst_prog_loadlib(pcName)

is provided. Its string argument specifies the same string as would come after a loadlib directive, and the integer return value is the nr of sucessfully loaded definitions, or 0 in case of failure. Likewise, if a self-defined extension library defines the special parameterless prog unit function _fini(), the corresponding C-function (which must again be declared as void (*)(void)) will be called whenever the prog unit instance is removed (NOTE: this means that the _fini() function of a loadlib may be called many times! Beware of repeated free's etc.!!). An example of the use of _fini() can be found in nst_file.c. To distinguish functions calls from different prog unit instances, the parameterless function nst_prog_unit_id() is provided. If called within the scope of a package function, it returns an integer id that uniquely identifies the prog unit that caused the call of a package function. Each of the functions _init() and _fini() may alternatively also be defined with one int parameter. If this variant is chosen, the int parameter will pass the nst_prog_unit_id() of the prog_unit that caused the call.

Shared Variables per Loadlib and Prog_unit Instance:

There is a simple way to share a user-defined data structure among all function calls that are made by the same prog_unit to a particular loadlib (NOTE: a better way to achieve the same goal may be to implement a class instead of a function, cf. IMPLEMENTING CLASSES IN IMPORT LIBRARIES below). To this end, allocate the data structure (e.g., in the _init function called once for each prog_unit instance) and then register its address pAdr and the address pFree of a suitable destructor function (.pFree=NULL will select free()) by a call of

      nst_prog_def_userobj(pAdr,pFree)

This will pass ownership of the object at pAdr to that prog_unit instance that invoked the call into the loadlib during which the above registration was made. A subsequent call

      pAdr = nst_prog_userobj()

occurring during a loadlib invocation from a prog_unit instance will then return the object address registered previously by that instance (or NULL, if no such registry was made or the call was not invoked by prog_unit). Repeated calls to nst_prog_def_userobj() to redefine the current shared data object are legal and will free the previous object instance and register the new one. Note that a registered object belongs to the calling prog_unit and may thus not be explicitly freed explicitly. However, a call of nst_prog_def_userobj(NULL,NULL) is legal way to undefine the object and thereby to enforce its destruction.

IMPLEMENTING CLASSES IN IMPORT LIBARIES:

By a slight extension of the scheme for defining new functions, a #import may also define classes that then become available as new data types in the importing prog_unit. The following lines in a #import library will define a class account with three methods to deposit or withdraw an amount and to query the current balance:

   char *Accnt[]={"account<1>","deposit<1>",
                  "withdraw<1>","balance<>",
                  "FREE_account<>",NULL};

   char **CLASSES[]={Accnt,..more classes..,NULL};

   // the implementation of the functions:
   
   typedef struct account_{
           float fAmount; ... } account_t;

   // the constructor:

   account_t *account(float fInitialAmount) {
      account_t *pData=calloc(1,sizeof(account_t));
      pData->fAmount = fInitialAmount;
      ...
      return pData;
   }
   // the methods:

   float deposit(float fVal,account_t *pD){
      pD->fAmount += fVal; return 0;
   }
   float withdraw(float fVal, account_t *pD) {..}
   float balance(account_t *pD) {return pD->fAmount;}
   void FREE_account(account_t *pD) {free(pD);}

All previously described ways to define more complicated parameter lists and to define prefixes against name collisions are available in the same way. Also, overloaded functions can be implemented as explained previously (even for the constructor function). Note that only the CLASSES identifier is fixed (note also the char**[] type), all other identifiers can be freely chosen. In the same way as for the "FUNCTIONS" symbol, the symbol "CLASSES" may also be chosen as "CLASSES_xxxx", where "xxxx" denotes the base name of the specified shared library (which then must be a legal C identifier). This is the now recommended practice (the prog_unit will first search for the more specific symbol CLASSES_xxxx, and if not present, will try CLASSES as a fallback). A major difference to functions definitions is that each function (except the constructor) gets passed an additional handle to reference a class-specific data object for each instance. The constructor function is expected to return a handle to a suitably initialized new instance of this object. The constructor is the first function that is defined in the function definition list of the class, and its name defines also the name of the class. The destructor for a constructor xxxx is expected to be named FREE_xxxx<>. (its parameter list is always taken as (void*), no matter what is actually defined). If no constructor is given, the free() function will be used.

Additional argument type tokens:

"@"
indicates a parameter of an element function that is a reference to an instance of the class to which the element function belongs (for each such argument, the corresponding C function will be passed a handle to the data object of the passed instance). The passed dimension will be 1, the passing mechanism is the same float** array as for * or $ parameters.
"#"
a variable number of @ - arguments, passing mechanism as above.
"a".."z":
another class argument, where the expected class is indicated by the letter and must be from the same loadlib as the class of the called element function. Letters 'a', 'b', ... 'z' denote the first, second, ... 26-th class definition in the CLASSES[] array of the loadlib (the number of classes useable in this way is limited to max. 26).
Functions can be defined with return types, e.g.

   ...
   "float fun0(void)",
   "float *fun1(void)",
   "void fun2(float)",
   "class fun3(int)",

Here, the second function declares an array-valued function. Such functions must pass a corresponding pointer to a newly allocated array whose ownership must be given to the caller. If a function returns an array, it must also return the allocation dimension of the array in piDim[-1] (all other functions can ignore piDim[-1]). The last definition declares a member function that is similar to a constructor. The implementation of this function is expected to be of the form

   void *fun3(int *piDim, void **ppvArg, void *pD) {..}

and should return a class handle to a newly created instance (ownership passed to the caller). It then can be used in assignments, such as

    x = y.fun3(42);

which copies the created instance into class variable x.

Special functions:

The following method names will be treated in a special way (in all cases, xxxx denotes the name and dataptr a reference to the class in consideration):
SAVE_xxxx<>
indicates that class xxxx has a data save function that must be implemented as char *SAVE_xxxx(FILE*,dataptr) and that should save the data object instance passed with its last argument to the stream passed with its first argument. A return value of NULL should indicate correct operation, everything else is interpreted as an address of an error message.
RESTORE_xxxx<>
indicates that class xxxx has a data restore function that must be implemented as char *RESTORE_xxxx(FILE *fp,dataptr *pPtr). This function is expected to be able to read the data record produced by SAVE_xxx and to initialize the data object that is passed with the last argument appropriately. Note that the last parameter is not the pointer pPtr to the data object but instead the adress &pPtr of that pointer! The reason is that the RESTORE function is expected to create an new data instance when the address pPtr of the data object is NULL (indicating that no instantiation has yet occurred). In this case, the address of the newly created instance must be returned in *pPtr. In all other cases (i.e., pPtr != NULL), the pointer value pPtr *must not be changed*, however, any elements inside the data object can be rebuilt in any way (as required by the read data). The return value of the routine is used to transmit an error status: A return value of NULL should indicate correct operation, everything else is interpreted as a char* address of an error message.
LOAD_xxxx<>
[deprecated, only for backward compatibility] a matching data load function that should be able to restore the state of the class instance from a data record previousy saved by the associated SAVE_xxxx function. Signature is char*(FILE*,dataptr). Unlike the RESTORE function, a LOAD function cannot pass back a newly allocated instance (since the last argument is a dataptr and not a dataptr*). Therefore, classes that have a LOAD function do not permit static declarations of uninstantiated class variables or assignments that involve such variables, since the resulting data files could not be read back by the load function.
DESCRIBE_xxxx<>
indicates presence of a function char *DESCRIBE_xxxx(dataptr) that is expected to return the address to a string holding some descriptive information about the data instance referenced by the passed dataptr (used, for instance, by Neo to display pin info).
CTRL_xxxx<>
specifies a control call handler for the instances of class xxxx. The handler must be implemented with the fixed signature void CTLR_xxxx(int mode, void *obj) (i.e., the first parameter will pass the NST ctrl mode, the last parameter the object handle).
COPY_xxxx<>
indicates presence of a function dataptr COPY_xxxx(dataptr,dataptr) which will copy a class instance referenced by the second argument, optionally utilizing the instance referenced by the first argument (ownership of this instance is passed to the COPY routine). The following requirements must be met by the implementation: when the first argument is is NULL, the return value must be the address of a newly allocated copy; when the second argument is NULL, the reaction must be as if a reference to a default instance had been passed as the second argument (for more details, see the nst_typedef() manpage). When a COPY_xxxx function has been defined, two important things happen: (i), instances of the class can be defined as INP or OUT variables, giving rise to NST pins of the newly defined class and allowing transport of (access to) the corresponding objects via NST wires. (ii) instances of the class can be assigned to each other, e.g., a=b; (will invoke COPY_xxxx(a,b)). For more comments on the implementation of COPY_xxxx and user-defined NST datatypes see the nst_typedef MANPAGE.
COMPATIBLE_xxxx<>
sometimes, even instances from the same class may differ sufficiently that one might want to forbid assignments such as a=b or connections of respective pins by wires. Such restrictions can be built by specifying a int COMPATIBLE_xxxx(dataptr, dataptr) function that compares two instances and returns nonzero, when they are compatible, and 0 otherwise. This will restrict wires and assignments in a corresponding way.
UPDATE_xxxx<>
this entry must be present when the class has array variables that shall be dynamically resizeable from a loadlib routine. In this case, the implementation has to define a function pointer void (*UPDATE_xxxx)(void). The corresponding function call UPDATE_xxxx() has then to be issued by any loadlib function that has resized the memory of one or several of the class array variables (the function ptr will have been set by NST to a notifier function whose call triggers a re-registration of the changed class variable addresses and dimensions).
These functions may also not be overloaded. When implementations for the SAVE/RESTORE_xxxx functions are provided, the prog_unit will automatically save and restore instances of a newly defined class (suppression is possible with a =" assignment).

Element variables:

Parameterless entries in the function definition list of a class are interpreted as identifiers for element variables. For each such identifier (e.g., val) the implementation is expected to provide a function void *val(int*,dataptr) that returns an address location where the value of the variable can be stored. Usually, this will be some location inside the data object of the class instance (to which dataptr passes a reference). Variables can either be of type float (the default) or of type int. The type can (for int must) be explicitly specified by prefixing an argument type char:
";"
specifies an int
"."
specifies a float (optional).
The int* parameter allows to return a dimension. If the return value is a positive number, the variable will appear at the prog unit level as an array variable of corresponding dimension (a few unreasonably large values may be reserved for special purposes). The type of an array variable can be specified by prepending to its name one of the following characters:
"*"
specifies an array of floats.
"&"
specifies an array of int.
"%"
specifies an array of bytes.
"$"
specifies a null terminated string. In this case, the implementation is expected to always hold a null-terminated string at the returned address, unless the returned addres was NULL. The variable will then appear like a dynamic char pointer variable of corresponding, variable length.
If you want a scalar, simply leave the int at the passed int* address unchanged (no matter what its value!). The dataptr again passes the handle of the associated data object (usually, the variable will occupy some location(s) in that object). Each associated function is called once when the class instance has been created. Element variables implemented in this way can then be used also as left values in assignments, e.g., if we extend the above definitions as

   char *Accnt[]={...,"*vec","val",...}
   ...
   typedef struct account_{
           float fAmount;
           float fVec[10];
           float fVal;
   } account_t;

   float *vec(int *piDim, account_t *pD){
         *piDim=10; return &(pD->fVec); }
   float *val(int *piDim, account_t *pD){
         return &(pD->fVal); }
   ...

we can assign and retrieve values in the prog unit by statements like

   static class account a(5);
   ...
   a.vec[3] = 3.56;
   a.val = 4 + a.vec[0];
   ...

If the constructor of a class has no parameters, the class instances may be instantiated without any parameter list, e.g.

   static class foo x,y,z;
   ...

Note that classes have no _init and _fini function. If a package needs this, you can use a FUNCTIONS[] lines to introduce a pair of such functions.

C-Style Interface Specification:

Instead of the hard-to-memorize single character letter codes, one can also provide the interface specification in a more explicit, C-style fashion, using the bsic data type keywords int, float, byte, char and class (the latter optionally followed by a class name; otherwise, the class of the calling function is implied). E.g., instead of

   ":thisarray",
   "thatfun<.&c!>",
   "SAVE_fxy<>",

one may write

   "byte* thisarray",
   "thatfun(float,int*,class foo,int* ...)",
   "SAVE_fxy()"

or also

   "thatfun(float x, int *pi,class foo c,int* ..)",

Similar to C++, it is also possible to assign default values to a number of trailing arguments (that then may be omitted when calling the function). Currently, such default assignments are limited to int and float parameters; additionally the single default value NULL or 0 is possible for float*, int*, and char* parameters. Example declaration:

   "foo(float x, int n=5, char *s=NULL, int *p=0)"

With C-Style interface definitions it is also possible to define class element functions that have as their arguments classes that are defined in other #import packages. Variable arguments may only be indicated in the last position and the following correspondences hold:

   "float" --> "."
   "float*" --> "*"
   "int" --> ";"
   "int*" --> "&"
   "byte*" --> "%"
   "char*" --> "$"
   "class" --> "@"
   "..." --> "-"
   "int ..." --> "="
   "float* ..." --> "+"
   "int* ..." --> "!"
   "byte* ..." --> ":"
   "class ..." --> "#"

Note the absence of a comma in the vararg types: the sequence "(float*, ..)" and "(float* ..)" correspond to the different interfaces "<*->" and "<+>"! Note also that the interfaces requested by <number> and <?> cannot be obtained in this way. They only can be specified in the old notation. Single character and C-style notation may also be mixed within the same FUNCTION or CLASS definition array (although not within the same definition string!).

Redimensioning of Array Element Variables:

If an element array such as vec shall be redimensioned, we must notify the prog unit about that event. To this end, the implementation has to provide a function pointer void (*UPDATE_xxxx)(void), that is to be called whenever one of its routines has resized an array variable such as vec[]. In the present example, we might now use a different data struct

   typedef struct account_{
           float fAmount;
           float *pfVec; // now dynamic!
           float fVal;
   } account_t;

initialize pfVec in the constructor and provide a resize method for subsequent changes. This could be done by extending *Accnt[] with the two further entries

   ..,"resize<1>", "UPDATE_account<>",...

implemented as

   void (*UPDATE_account)(void) = NULL;
   
   float resize(float iNewDim, account_t *pD) {
      pD->pfVec = realloc(pD->pfVec,
                          ((int)iNewDim)*sizeof(float));
      UPDATE_account();
      return 0;
   }
   ...

Note that the initially NULL UPDATE_account function pointer has become set (by the prog unit) to a suitable notifier function when the loadlib was loaded. Exception: when the RESTORE or LOAD method of a class needs to resize any array element variables, it need not call the UPDATE function. In this case, any updates are made automatically, when the RESTORE method returns.

PREDEFINED EXTENSION LIBRARIES:

The currently available prog_unit extension libraries include the following:

Loadable library on string operations:

string.so
the NST string library wraps for the prog_unit a number of string functions, mostly analogous to the familiar C functions: Available routines include

   strcmp(s1,s2), strncmp(s1,s2,n),
   strcpy(to,from), strncpy(to,from), strcat(s1,s2),
   strstr(s1,s2), strchr(s1,c), strrchr(s2,c),
   strpbrk(s1,s2), memove(s1,s2,n), memset(s1,f,n),
   tolower(c), toupper(c), isdigit(c), isalpha(c),
   islower(c), isupper(c), atof(s), atoi(s),
   strtol(s,&offset,base), strtof(s,&offset),
   strtoken(tok,s,sepset) [cf. below]

with the main difference that the counterparts of functions that in C return a pointer here instead return the corresponding index offset to the first char position of the first argument parameter, or -1, if the C result would be NULL, e.g.

       strstr("abcde","cd") = 2
       strstr("abcde","CD") = -1
   
The function memove is analogous to memmove, but the moved entities are always array elements, i.e., floats. To parse a string s with strtoken() into tokens tok separated by chars from d:

     strtoken(tok,s,d); ... use tok ...
     while (strtoken(tok,"",d)) { ... use tok ...}

Loadable library on low-level file access:

file.so
provides some routines for low-level file access:

       fopen(fileid,m), fclose(fp), feof(fp), stdin,
       stdout, stderr, EOF, ftell(fp), rewind(fp),
       fseek(fp,i,w), SEEK_SET, SEEK_CUR, SEEK_END,
       fflush(fp), fgetc(fp), ungetc(c,fp), fputc(c,fp),
       fgets(s,n,fp), fputs(s,fp), skip_comments(fp,c),
       fprintf(fp,fmt,..), fscanf(fp,fmt,..),
       fprintf_vec(fp, fmt, pfX, split)

These routines/constants are essentially wrappers of their C counterparts (except fprintf_vec) and analogously defined. FILE pointer arguments or return values in the prog_unit are just integer-valued floats, the value 0 corresponds to the file pointer NULL. When a unit is deleted, its opened files are closed. The implemented fscanf interpretes a non-standard directive %# to skip over a comment block. fprintf_vec() prints vector pfX with format ftm (e.g. "%g" or "%5.2f"). Elements are separated by one space or after each split items by a newline (if split>0). If split != 0 the last line is terminated by a newline, else no newline termination occurs. Returns the number of printed elements, EOF=-1 on error.

      fprintf_vec(stdout,"%g", Y,-1); // print Y vector elements ending w. "\n"
      fprintf_vec(stdout,"%7.3f",Y, 4); // print Y vector elements in block of 4


   

Loadable library on Open-GL solid routines:

solid.so
the NST solid-library now contains a number of wrappers and a FUNCTIONS[]-line to provide a direct interface for the prog_unit to some GL-functions, as well as to some NST-rendering routines built on top of GL. The routines have an effect only when the prog_unit is in the scope of a view3d unit, otherwise they return with no further action. Available routines and constants include

        glRotatef(a,x,y,z), glTranslatef(x,y,z), glScalef(x,y,z),
        glMultMatrixf(M[16]), glPushMatrix(), glPopMatrix(),
        glColor3f(r,g,b), glPolygonMode(w,m),
        GL_POINTS, GL_LINES, GL_FILL, GL_FRONT, GL_BACK,
        nstBox(dx,dy,dz), nstPyramid(dx,dy,dz), nstTetrahedron(h),
        nstCircleSegments(n), nstCylinder(r,h), nstSphere(r),
        nstArrowZ(len,r)
        nstMaterial(m,r,g,b,ambient,diffuse,specular,emission),
        nstLighting(on), nstPuma(th[6]),
        nstDHTrafo(distance,twist,offset,theta),
        nstPoints(V[3n]),nstLineStrip(V[3n]), nstLineLoop(V[3n]),
        nstPolygon2(V[2n]),nstLineStrip2(V[2n]),nstLineLoop2(V[2n])

and mostly wrap their similarly named GL-counterparts or follow similarly named NST-units in nst_solid.c (arguments V[n] denote n-dimensional arrays).

Loadable library on vector operation utilities:

vector.so
Contains: Routines for sorting indexing and handling sorted vector elements; Routines for creating random sequences without doubles or drop outs; Routines for histogramming; Routines for setting a vector; Routines for complex vectors; Routines for statistic analysis; Unitary vector routines; Routines for shifting vectors and time series representations; Miscellaneous More info see extra manpage vector.so

    qsort(afRes,afX) -- quicksort vector afX[].
    index(afIndex, afX) -- compute index array from data vector.
    percentindex(pfSorted, percent) -- returns interpolated value into sorted array
    findindex(pfIndex, pfX, pfBook, highIdx) -- find and return closest indeces
    remap(pfRes, pfSorted, drop0, dropEnd) -- remap sorted array by interpolation.
    checkorder(pfX) -- check ordering of vector, return bit mask
    permuteindex(afIndex) -- compute random index sequence afIndex[]
    nextindex(afIndex) -- return next index in array and mark used
    histo(pfCount, pfMidpoints, pfX) -- compute histogram of pfX[] elements.
    binning(pfCount, pfMidpoints, pfX) -- bin and count vector elements pfX[]
    histoline(pfLine, pfY, pfMidpoints) -- compute polyline for histogram.
    zero(pfX) -- set vector to zero.
    linspace(pfX, x1, x2) -- return vector pfX[n] of n equally spaced points
    logspace(pfX, x1, x2) -- return vector of n logarithmically
    rand(pfX,l,h) -- return random vector with uniform distribution between [l,h[.
    randn(pfX,std) -- return random vector with normal distribution, width std
    gauss(std) -- return single random variable with gauss distribution,
    quantile(pfRes, pfX) -- compute required quantile levels.
    quantile_i(pfRes, pfX) -- compute required quantile levels by interpolation.
    mean(pfX) -- return mean value of vector pfX[]
    adev(pfX) -- return average deviation from mean value of vector pfX[]
    var(pfX) -- return variance of vector pfX[]
    std(pfX) -- return standard deviation value of vector pfX[]
    moments(pfRes, pfX) -- return several moments and quantiles levels of
    norm0(pfX) -- return maximum norm of vector pfX[] (=max(|x_i|)).
    norm1(pfX) -- return absolute norm of vector pfX[] (=sum(|x_i|)).
    norm2(pfX) -- return Euclidean norm of vector pfX[] (== norm(pfX)).
    sum(pfX) -- return sum of all elements of vector pfX[].
    prod(pfX) -- return product of all elements of vector pfX[].
    min(pfX) -- return minimal element of vector pfX[].
    max(pfX) -- return maximal element of vector pfX[].
    mini(pfX) -- return index of minimal element of vector pfX[].
    maxi(pfX) -- return index of maximal element of vector pfX[].
    sprod(pfX1, pfX2) -- return scalar (inner) product of vectors pfX1[] and pfX2[].
    mprod(pfRik, pfMij, pfMjk, j) -- matrix product pfRik[]=pfRij[] * pfRjk[].
    multadd(pfY, a, pfX, b) -- return vector Y=a*X+b and its norm.
    multaddv(pfY, pfA, pfX, pfB) -- return vector Y=A.*X+B and its norm.
    add(pfDest, pfX1, pfX2) -- return addition vector pfDest[i] = pfX1[]+pfX2[]
    diff(pfDest, pfX1, pfX2) -- return subtraction vector pfDest[] = pfX1[]-pfX2[]
    scale(pfDest, fScale, pfX) -- returns scaled vector pfDest[i] = fScale * pfX[i]
    clip(pfDest, pfX, lowClipValue, highClipValue) -- returns vector pfDest[] cliped
    threshold(pfRes,pfX,threshold) -- threshold operation returns vector with 0 or 1s
    reverse(pfRes, pfX) -- reverses order of vector elements pfX. (pfRes==pfX ok)
    transpose(pfNM, pfMN, n) -- transpose matrix with initially n columns.
    switch_input(pfX, pfVectors, select) -- switch one select-ed section to pfX.
    switch_output(pfVectors, pfX, select) -- copy pfX to one (select-ed) section
    switch(pfDest, pfSrc, select) -- switch_input() or switch_output()
    convolve(pfRes, pfX, pfKernel) -- kernel convolution, only full operations
    convolve_0(pfRes, pfX, pfKernel, kernelRefPos) -- convolution w. zero padding
    convolve_e(pfRes, pfX, pfKernel, kernelRefPos) -- convolution w. edge extention
    convolve_c(pfRes, pfX, pfKernel, kernelRefPos) -- convolution w. cyclic extention
    convolve_(pfRes, pfX, pfKernel, kernelRefPos, mode) -- switched convolution.
    sparse_copy(pfRes,offRes,incRes, pfX,offX,incX, maxCopy) -- sparse copy.
    push(pfX, x) -- shift vector pfX[] right, return last, insert x at pos 0.
    pop(pfX, x) -- return pos 0, shift vector pfX[] left, insert x at end.
    acov(pfACV, pfX) -- compute data auto covariance function.
    acor(pfACV, pfX) -- compute data auto corelation function.
    size(pfX) -- return length of vector.
    popup(string) -- write string to a popup alert window
    "scopy", sparse sparse_copy()
    "cabs", complex absolute
    "cangle", complex angle
    "csquare", complex square
    "eprod", elementwise product
    "elog10", elementwise log base 10
    "elog", elementwise log
    "chirpwave",sin wave with drifting frequency
    "chirpwave_linWL", same with linear drifting wavelength
    "DEG2RAD", Const
    "RAD2DEG", Const
    "linreg", linear least square regresion
    "linmedreg",linear robust median regression
    "set", set
    "cmp_ge", compare greater equal
    "cmp_le", compare less equal
    "cmp_eq", compare equal
    "tic", read elapsed time/ms + reset
    "toc", read time/ms
    "ranks" mid-ranks of unordered set

Loadable library on spectral methods:

spectral.so
Contains: extremly fast real-complex FFT, window operations, power spectra. See spectral.so.

FILE

/homes/jontrup/nst5/man/../o.sol2//../nstsrc/nst_prog.c