2. Writing external functions for REXX/imc

Contents

Introductory text

There are four ways to write external functions for REXX/imc:

  (a) in REXX
  (b) using the SAA application programming interface
  (c) as a ".rxfn" file, and
  (d) as a Unix program.

External functions in REXX are covered in the REXX programming reference.
The SAA application programming interface for external functions is covered
in section 1(d).  The remaining methods are described in this section.

2(c). Writing a ".rxfn" file

A function written as a ".rxfn" file will be searched for and linked in
with the interpreter at the time when it is first requested.  This gives an
advantage over using the SAA API in that SAA functions must be registered by
the application before they may be used by a REXX program - though this
is made easier by the inclusion of the RxFuncAdd REXX function in the
interpreter.  In general, the function(s) in a ".rxfn" file may either be
SAA function handlers as described above or specially written functions
for REXX/imc.  However, the latter type is the only one supported for
stand-alone functions; files containing only SAA functions must be supplied
as a library with a ".rxlib" file (see section 3).  This is because it is
the ".rxlib" file which tells the interpreter that the functions must be
called using the SAA calling sequence (either that, or a REXX/imc-style
function must use the SAA interface to register all the SAA functions).

A ".rxfn" file should be compiled and then linked in the normal way for
shared objects.  This differs between operating systems, but if the "Make"
program knows how to compile shared objects on your system then try making
rxmathfn.rxfn to see what flags it uses.  For a stand-alone function the
name of the output file should be the name by which the function is to
be called with ".rxfn" appended, all in lower case.  If this file is in
the rexx function search order (see the section on function or subroutine
invocation in the REXX/imc programming reference) then the file will be
loaded and linked in with the interprter automatically the first time that
the function is called.  Future calls to the same function will not have to
load the function from a file.

The REXX/imc-style function is described in the remainder of this section.
It is not really recommended for new functions because of portability
issues, so skip to the next section if this is not of interest.

A REXX/imc function will need access to several of the interpreter's
functions, so its source must include "functions.h".  It may also need
access to certain global variables, in which case it should include
"globals.h".  Useful functions and global variables are listed below.
In addition, the function may call any routine from the API, in which
case it should include "rexxsaa.h" as described in section 1.

A REXX/imc function should be declared as:

    int rxfunction(char *name, int argc);

(including the name rxfunction).  The name parameter gives the name by which
the function was called, excluding any path name.  That is, any part of the
name up to and including the last slash (/) will have been removed.  This
may be used to distinguish between several function calls that have all
been implemented by the same routine (this is more common in a function
library; see the section on function libraries).  The argc parameter gives
the number of arguments which were passed to the function.  These will be
placed on the calculator stack in LIFO order.  Any missing parameters will
have been stacked as strings of length -1.

The function will be expected to remove all its parameters from the
calculator stack and either

  (i) place a result on the calculator stack and return 1, or
 (ii) return 0 without placing anything on the calculator stack.  This means
      that the function has not returned a result.

However, the function may choose to return an error.  In this case, it
need not remove all arguments from the calculator stack.  A valid error
code is a negative number whose absolute value is the number of a REXX
error.  When the function returns, that error will be reported.

REXX/imc contains a function declared as follows:

   int funccall(RexxFunctionHandler *func,char *name,int argc);

This function enables a function that has a REXX/imc-style calling sequence
to call a function that has an SAA-style calling sequence.  For example,
if function_handler is an external function designed for use with the SAA
API, then it can be saved as a REXX/imc-style ".rxfn" file by appending the
following function:

   int rxfunction(char *name, int argc)
   {
      return funccall(function_handler,name,argc);
   }

This gives the SAA function the advantage of being able to be loaded on
demand instead of having to be registered.

A single function stored in a REXX/imc file should not contain a public
symbol called rxdictionary; this is reserved for function libraries.

Useful functions, including functions to retrieve arguments and stack the
answer, are the following:

char *delete(len)  int *len;

Deletes a string from the calculator stack, and returns an address where it
can be found.  On return, len will be set to the length of the argument.
The string is guaranteed to be followed by two bytes of available memory,
so that the application may teminate it with a null character if necessary,
and the string may be written to.  The string is not guaranteed to be
null-terminated when it is unstacked.  The string is not actually moved in
memory, so it will be invalidated whenever the calculator stack is changed
(that means that it may not be used as an argument to stack()).  However,
further calls to delete() will not corrupt the string.

int getint(flag)  int *flag;

Deletes a string from the calculator stack, interprets it as an integer, and
returns the result.  If the string is not a number which can be held in an
int variable, the function will die.  If the string is not an integer and
the flag is non-zero, the function will die, otherwise the number will be
converted into the nearest integer.

void stack(string,len)  char *string; int len;

Copies the given string of the given length on to the calculator stack.

void stackint(i)  int i;

Stacks a string representing the integer i on the calculator stack.

int isnull()

Returns 1 if the next value to be unstacked is null (that is, it has
length -1 indicating an omitted parameter), and zero otherwise.

void die(rc)  int rc;

Raise the error whose code is rc.  The interpreter will then either halt
with diagnostics or signal to an appropriate label, according to the current
settings of "SIGNAL ON".  This function never returns to its caller.

int num(minus,exp,zero,len)  int *minus,*exp,*zero,*len;

Attempts to extract a number from the top value on the calculator stack,
returning it as a sequence of digits.  The value is always left stacked.  If
unsuccessful, num returns a negative number (except when the top value is
null, in which case num dies).  Otherwise, num stores a sequence of digits
in the workspace (see below), and returns the offset from the start of the
workspace to the start of the sequence of digits (which always equals the
old value of eworkptr).  The value eworkptr is updated to point past the end
of the sequence of digits.  The length of the sequence is returned in len.
If the number is zero, then zero is set to 1, and otherwise it is set to 0.
If it is negative, then minus is set to 1, and otherwise 0.  The exponent
stored in exp is such that if a decimal point were placed between the first
two digits of the sequence and the result were multiplied by ten to the
power exp, then the original number would be recovered.

void stacknum(num,len,exp,minus) char *num; int exp,len,minus;

A sequence of digits starting at address num and of length len is formatted
according to the Rexx rules for numerics and stacked.  The exponent "exp" and
sign "minus" are interpreted according to the rules in "num" above.

The following functions from the variable interface should probably not
be used.  Use the SAA API instead.

char *varget(name,namelen,len) char *name; int varlen; int *len;

The variable whose name is "name" and contains "namelen" characters is
searched for in the current symbol table.  If the name is a compound symbol
or a stem, then the first character of the name must have bit 7 set.  The
name is used as-is, that is, it is not translated to upper case, and if it
is a compound variable then no substitution occurs in the tail.  A pointer
to the variable's value is returned, and "len" is set to its length.  If the
variable has not been assigned a value then "len" and the result are zero
and the null pointer, respectively.  The copy of the value which is returned
must not be changed in any way.  It may be invalidated whenever the symbol
table is changed (and may not be used as a parameter to varset()).

void varset(name,namelen,value,len) char *name,*value; int namelen,len;

The parameters "name" and "namelen" name a variable and satisfy the same
conditions as in varget above.  The parameters "value" and "len" describe
the position and length respectively of a string which is to be assigned to
the variable.  The assignment always succeeds unless there is insufficient
memory available, in which case the routine dies.

Note: the variable name and the string may contain arbitrary characters, and
neither is validated.  If you assign a value to a simple symbol called, e.g.
"blah" (in lower case) or "XYZ.123" (containing a dot), etc.  then the
symbol will not be accessible to the Rexx program, but it may be recovered
using varget(), provided it has not been hidden or destroyed by a PROCEDURE
instruction.

Any other function from the Rexx source may be used, but they are [or
perhaps are not!] described elsewhere.  Try in the source itself...

A function may use global data from Rexx.  It may either include the
header file "globals.h" (which defines all the global variables of the Rexx
interpreter) or simply include declarations for the particular variables it
uses.  Some useful variables are:

char *workptr; unsigned eworkptr,worklen;

The workspace, which may be used freely by a function.  The space starts at
workptr, and is of maximum length worklen.  The variable eworkptr may be
used to hold the length of the data currently stored in the workspace.

int precision,fuzz;

The setting of "NUMERIC DIGITS", and precision minus the setting of "NUMERIC
FUZZ", respectively.  That is, "precision" holds the precision of ordinary
calculations, and "fuzz" holds the precision of comparison operations.

char numform;

Zero if "NUMERIC FORM SCIENTIFIC" is in effect, and one if "NUMERIC FORM
ENGINEERING".

int ppc;

The number of the instruction being interpreted.

int rxstacksock;

A file descriptor which is connected to the Rexx stack process via a socket.

FILE *ttyin,*ttyout;

Streams which may be used to communicate with the terminal.


A Rexx memory structure (for example the workspace) may be extended using
the macros mtest or dtest.  These are called like functions, but note that
some arguments may be evaluated more than once or not at all.

mtest(memptr,alloc,length,extend)

Check that the desired length for the area, "length", is not greater than
the actual length, "alloc".  If it is, then attempt to reallocate the area,
pointed to by "memptr", with "extend" more bytes than its previous length.
The parameters memptr and alloc will be updated to reflect the new position
and length of the area.  The macro will die if not enough memory is
available.

dtest(memptr,alloc,length,extend)

Perform mtest on the four arguments, but return 0 if the area did not move,
and non-zero otherwise.  In order to use this macro, the current function
must contain variables:

char *mtest_old;
long mtest_diff;

If the memory pointer has moved, then mtest_diff contains the difference
(the new pointer minus the old).

2(d). Writing an external function as a Unix program

If a REXX program calls a function which is not internal or built-in and
cannot be found in any of the other categories of external function, then
REXX/imc searches for a file having the same name as the function in lower
case without a file extension, and this is assumed to be a Unix program.
The program may be written in any language supported by Unix such as C or
perl (even REXX, but note that although the programs will share the same
stack, a fresh copy of the interpreter will be loaded and some of the
parameter information will be lost as usual for a REXX program called
as a command).  If the program is interpreted then it must start with a
"#!" comment that names its interpreter, and the Unix kernel must be able
to recognise this form of comment. 

REXX/imc will execute the Unix program with argv[0] containing the name
by which the function was called, excluding any path name (that part of
the name up to and including the last slash character), and the rest of
argv[] containing the function's arguments.  The program will thus have
argc equal to one more than the number of arguments passed by REXX.  The
arguments will be null-terminated, which means that the arguments must not
contain null characters.

The program will be expected to print its result on its standard output,
followed by a newline character.  The program returns an empty string by
printing a single newline character.  The program returns no result by
printing nothing.

If the program finishes with an exit code of 255 (or -1), then REXX/imc will
raise error 50 (error in called routine).