OS/2 Procedures Language - REXX
                     --------------------------------


Why this article needs to be written
------------------------------------

One of the things about OS/2 which I continually find interesting is how
similar and yet how different it is to DOS.  The advantage of being like
DOS is that things feel familiar to those migrating to OS/2; the drawback
is that the enhancements are not used because people think they already know
it all from DOS.

A good example of this is 'batch' files: first versions of OS/2 had a few
extensions to batch file handling, and long standing readers of Pointers may
recall Tony Harris's article in issue 6 (Jan/Feb '90) where he described some
of the additions OS/2 has in this area.  However, these additions were fairly
limited - for example in the article a program had to be written to get the
user's reply to a simple Yes/No question.

In more recent versions of OS/2 however, IBM have included the so-called OS/2
procedures language, or REXX (which stands for REstructured eXtended eXecutor
language if you must know...).  Despite its name it is a very useful feature
of OS/2, but suffers from the problem described in paragraph one: most users
of OS/2 don't even know it exists!

For this reason I felt a technical tip giving a 'taster' of some of the things
which REXX allows you to do might dispel some of this ignorance.  I apologise
to those of you (mostly ex IBM mainframe users!) who already write programs
in REXX - this is going to be a couple of 'starter' programs to get people
started.


Introduction to REXX
--------------------

So what is REXX?  REXX is a programming language, with some of the simplicity
of BASIC, which allows you to include OS/2 commands in your program.
Support for REXX programs is built into the standard command shell CMD.EXE as
an extension to the .CMD batch files everyone who uses OS/2 has used - if a
batch file starts with a REXX comment ( '/* ... */' ) then it is treated as
a REXX program.

REXX is an interesting programming language: it is a free-form structured
language, with constructs like if..then..else and do..end; it is typeless,
which means that variables have no type (ie unlike 'C' where variables are
'int' or 'char *' all REXX variables are just character strings of arbritary
length); and, rather unusually, uninitialised variables have a default value
of the variable name itself, in capitals.

Any commands which are not recognised by REXX will be passed on to CMD.EXE
to be processed.

REXX has a set of functions for input/output, string manipulation and also
a range of expressions, some operating on all strings and others on those
which are recognised as numbers.

A trivial REXX program is:

hello.cmd:
------------------------------------------------------------------------------
/* Comment - all REXX programs have one */
        SAY hello everyone
------------------------------------------------------------------------------

If you type this in using a text editor and type 'hello' from the command
prompt, the program displays HELLO EVERYONE on the screen.  This is because
'say' is recognised as a REXX command, with arguments 'hello' and 'everyone'.

Since neither of these variables are initialised their values are 'HELLO' and
'EVERYONE', so these two words are written out.
If you wanted to retain the case of the words, simply enclose then in quotes
to tell REXX that they are strings, not variables, and the string itself is
displayed.

A full list of REXX commands is beyond the scope of this article.  There is a
short description of REXX in the OS/2 User's Guide, and a User's Guide and
Reference specifically for REXX are also available.  A few of the commonest
commands will be used later in this article.

REXX can also use function calls as well as commands - an REXX functions can
take a variable number of arguments.  An inbuilt function 'args' allows the
called function to determine how many and what type of arguments were passed
to it.

You should be aware that external functions, callable by REXX, can be written
in REXX, C, Pascal or MASM.  This feature allows creation of a program where
the shell is written in REXX and the 'real work' is done by a compiled piece
of code for efficiency.
In addition, REXX can pass external commands to other environments than just
CMD.EXE, and so can be used as a general macro language.
However, I will not touch on either of these techniques further in a first
introduction to REXX.


Since this is a 'technical tips' article, I will now describe two REXX
programs which, although of some use by themselves, more importantly show
the sort of things which can be done with REXX.


REXX example programs
---------------------

Each program is shown containing comments of the form '/* n */' - these refer
to the notes at the end of each program.

For clarity, all REXX reserved words, commands and functions are shown in
upper case.  This is a recommended, but not obligatory, style.


Example 1: math.cmd

This program runs a very simple full screen expression evaluator.  You can
type in arithmetic expressions like '2 + 2' or '2 ** 3 + (7.6 / 1.2)' and
the value of the expression is displayed.  If invalid expressions are used
the program writes '** Unable to evaluate expression **'.

math.cmd:
------------------------------------------------------------------------------
/* Simple expression evaluator using REXX */

    /* 1 */
    esc = x2c('1b')
    title  = esc'[02;0H'esc'[K               Simple expression evaluator'
    banner = esc'[10;0H'esc'[K  Result of: '
    answer = esc'[12;0H'esc'[K  is: '
    prompt = esc'[20;0H'esc'[K  Enter expression or ''quit'' ->'

    '@cls'                                              /* 2 */
    SAY title

    DO FOREVER

       CALL CHAROUT 'screen$', prompt                   /* 3 */

       inp = LINEIN()                                   /* 4 */
       PARSE UPPER VALUE inp WITH expression            /* 5 */

       IF expression = 'QUIT' THEN
          LEAVE

       CALL evaluate                                    /* 6 */

       CALL CHAROUT 'screen$', banner||expression       /* 7 */
       CALL CHAROUT 'screen$', answer||res

       END

    RETURN res
    EXIT

    evaluate:                                           /* 8 */
       SIGNAL ON SYNTAX                                 /* 9 */
       INTERPRET 'res = ' expression                    /* 10 */
       IF \DATATYPE(res, 'num') THEN                    /* 11 */
          SIGNAL SYNTAX                                 /* 12 */
       RETURN

    syntax:                                             /* 13 */
       res = '** Unable to evaluate expression **'
       RETURN
------------------------------------------------------------------------------

Notes on math.cmd:

        (1)  Esc is set to the 'escape' character using the inbuilt hex to
             char function X2C.  This variable is then used to build up the
             ANSI escape sequences for the various display strings.

        (2)  cls is used to clear the screen in this example although it
             is easier to use the ANSI control sequence.  The initial '@'
             causes the CMD.EXE echoing to be suppressed, and the command
             is quoted to ensure REXX does not try and parse the text.

        (3)  Charout writes the string to the file named in the first
             argument.  The usual command to do this, 'say', appends a
             newline.

        (4)  The first statement reads a line of input (usually the
             'pull' command would be used but this causes a prompt
             to be written in some versions of OS/2).

        (5)  The PARSE statement 'parses' the input line, into a variable
             called 'expression'.  The UPPER modifier first coerces the text
             to upper case, and the VALUE modifier tells parse to operate on
             the value of the variable 'inp'.
             The parsing command is very general purpose and allows a range
             of parsing methods, only a small subset of which are used
             in this article.

        (6)  A function is called to evaluate the expression typed in -
             this is to allow localisation of error handling.

        (7)  The || operator concatenates strings.

        (8)  The evaluate function starts here.

        (9)  This tells REXX to jump on any syntax errors - to the default
             label which is called SYNTAX.  Other conditions can be signalled
             on such as external command errors.
             Syntax error occur if the user types a 'silly' expression like
             '/ 2' or 'a + b'.

        (10) This line does the work! It generates a string by putting the
             value of 'expression' together with 'res = ' and then interprets
             this string as a REXX statement.  Any errors will cause a jump
             to the 'syntax' label.

        (11) Since all variables in REXX are strings, 'res' may contain a
             string which is not a number.  For example if expression was
             'fred', the variable res would now be 'FRED'.  The 'datatype'
             function checks whether the variable is a valid number: other
             options for datatype include 'alphanumeric', 'binary' and 'hex'.

        (12) This is an example of generating an error condition - in this
             case to force the program to go to 'syntax'.

        (13) This is the point where execution continues on syntax errors -
             res is set to an error string and the function returned from.
             Note that 'syntax' is not itself a function - it is jumped to
             and so it is returning from the function running when the
             syntax error occurred.  An alternative construct, CALL ON xxx,
             can be used instead of SIGNAL ON xxx in cases where you do want
             to return to the statement causing the error.


Example 2: modlist.cmd

This example shows the modules used by a program.  For example
"MODLIST C:\OS2\PMSHELL.EXE" will display all the modules (DLLs) which
are required by the program and the directory the are located in.
As a demostration of recursion in REXX the program checks all dependencies
of the module tree.

Note that this program works with both OS/2 1.x and 2.0 binaries.


modlist.cmd:
------------------------------------------------------------------------------
/* REXX procedure to give a list of modules (DLLs) used by a program */

PARSE ARG filename scrap                                /* 1 */

IF filename = '' THEN DO
   SAY 'filename expected'
   EXIT
   END

IF STREAM( filename, 'C', 'QUERY EXISTS') = '' THEN DO  /* 2 */
   SAY filename 'not found'
   EXIT
   END

CALL getlibpath                                         /* 3 */

modlist. = ''                                           /* 4 */
modlist.DOSCALLS = 'OS/2 kernel'        /* Predefined */

RETURN doone( filename)                                 /* 5 */

/**************************************************/
doone: PROCEDURE EXPOSE modlist. libpath. libcount      /* 6 */

PARSE ARG filename

IF checkit( filename) \= 0 THEN
   RETURN 1

numq = 0
DO QUEUED()                                             /* 7 */
   PULL x
   numq = numq + 1
   list.numq = x                                        /* 8 */
   END

DO j = 1 TO numq
   x = list.j
   IF modlist.x = '' THEN DO                            /* 9 */
      DO i = 1 TO libcount
         test = libpath.i || '\' || x || '.DLL'
         IF STREAM( test, 'C', 'QUERY EXISTS') \= '' THEN DO
            modlist.x = libpath.i
            SAY x '-' libpath.i
            IF doone( test) \= 0 THEN                   /* 10 */
               RETURN 1
            LEAVE                                       /* 11 */
            END
         END
      IF modlist.x = '' THEN
         SAY '*** module' x 'could not be found on the LIBPATH ***'
      END
   END

RETURN 0


/**************************************************/
checkit: PROCEDURE EXPOSE filename

header = CHARIN( filename, 1, 2)                        /* 12 */
IF header \= 'MZ' THEN DO
   SAY filename 'is not executable'
   RETURN 1
   END

newoff = CHARIN( filename, 60 + 1, 4)
newoff = C2D( REVERSE( newoff)) + 1                     /* 13 */

header = CHARIN( filename, newoff, 2)

SELECT
   WHEN header = 'LE' THEN DO                           /* 14 */
      impmodtbl = CHARIN( filename, newoff + 28 * 4, 4)
      impmodtbl = C2D( REVERSE( impmodtbl))
      impmodcnt = CHARIN( filename, newoff + 29 * 4, 4)
      impmodcnt = C2D( REVERSE( impmodcnt))

      CALL STREAM filename, 'C', 'SEEK ' || impmodtbl + newoff
      DO impmodcnt
         len = C2D( CHARIN( filename, , 1))
         module = CHARIN( filename, , len)
         QUEUE module
         END
      END
   WHEN header = 'NE' THEN DO
      modtab = CHARIN( filename, 20 * 2 + newoff, 2)
      modtab = C2D( REVERSE( modtab))
      imptab = CHARIN( filename, 21 * 2 + newoff, 2)
      imptab = C2D( REVERSE( imptab))

      modlen = imptab - modtab

      CALL STREAM filename, 'C', 'SEEK ' || modtab + newoff
      DO i = 1 TO modlen / 2
         val = CHARIN( filename, , 2)
         modptr.i = C2D( REVERSE( val))
         END

      /* work through every item in the table */
      DO i = 1 TO modlen / 2
         CALL STREAM filename, 'C', 'SEEK ' || modptr.i + imptab + newoff
         len = C2D( CHARIN( filename, , 1))
         module = CHARIN( filename, , len)
         QUEUE module
         END
      END
   OTHERWISE DO                                         /* 15 */
      SAY filename 'is not an OS/2 executable program'
      RETURN 1
      END
   END

CALL STREAM filename, 'C', 'CLOSE'

RETURN 0

/**************************************************/
getlibpath: PROCEDURE EXPOSE libpath. libcount

filename= "c:\config.sys"
libcount = 0

DO count = 1 UNTIL LINES( filename) = 0                 /* 16 */

   check = LINEIN( filename)

   IF SUBSTR( check, 1, 8) = "LIBPATH=" THEN DO
      libpath = SUBSTR( check,9)
      DO WHILE libpath \= ''
         libcount = libcount + 1
         PARSE VALUE libpath WITH libpath.libcount ';' libpath  /* 17 */
         END
      LEAVE
      END
   END

CALL STREAM filename, 'C', 'CLOSE'

RETURN 0
------------------------------------------------------------------------------

Notes on modlist.cmd:

        (1)  get arguments - discard all but the first one

        (2)  The stream function allows you to perform file specific
             operations - in this case query existence of a file.

        (3)  Here CALL is used for our own procedure getlibpath - in the
             previous example it was used for intrinsic comands.

        (4)  This is an example of a 'compound' variable.  "modlist." is
             the stem (a bit like an array name in C), modlist.DOSCALLS
             is a specified item.  Here we set a default value '' for all
             variables with the stem modlist.

        (5)  Pass the filename to the recursive procedure doone.

        (6)  doone is defined with the 'procedure' keyword to indicate
             that all variables are hidden and per-instance, EXCEPT for
             the ones listed after the expose keyword.

        (7)  checkit places its results on the default REXX queue, which is
             processed by the DO loop. QUEUED() returns the number of items
             on the queue.

        (8)  'list.numq =' assigns to list.1, list.2, etc. as for a simple C
             array.

        (9)  'modlist.x' is using a non-numeric subscript - we are using this
             feature of REXX variables so the list of known modules is
             maintained for us by REXX.  'x' will be the current module, for
             example PMWIN, and modlist.PMWIN will be equal to '' (see note 4)
             unless it has been already processed.  This allows us to build
             up a tree of dependent modules.

        (10) Here we call ourselves recursively - variables like i, j and
             even 'list.' are local and so will not be affected by the call,
             but 'modlist.' is exposed and so changes will remain on exit.

        (11) The leave statement in a simplest form exits the nearest DO loop.

        (12) Here we process the executable file format of the given filename.
             CHARIN allows us to read binary data from the file, at a given
             offset and a given length.

        (13) C2D and REVERSE are used to convert Intel byte-reversed binary
             into a REXX number.
             Note that explanation of the offsets and formats used in this
             function is outside the scope of this article!

        (14) The SELECT statement allows matching on a flexible set of
             criteria although in this case we are checking the same
             variable in each WHEN clause.
             'LE' is the 'magic cookie' for OS/2 2.0 style EXE headers,
             and 'NE' for OS.2 1.x headers.

        (15) OTHERWISE should be self-explanatory...

        (16) The LINES function returns the number of lines left to
             process in the file - the first call automatically
             opens the file.

        (17) Another example of PARSE.  Note that we can use the same
             variable both as the thing to be parsed and as one of the
             variables to contain the result.  Also note that libpath
             and libpath. can be used without confusion as different
             variables.


Conclusion
----------

Use of REXX is particularly helpful for cases where a simple program is
required, for example installation programs, but the standard OS/2 batch
facility lacks the flexibility and usability of REXX.  However the range of
features and extensibility of REXX means it is by no means restricted to
these cases.

I hope that these examples of the use of REXX to perform actions which
would more usually require the writing of a compiled program will encourage
more people to experiment with REXX.