Steve Ferg's
Thoughts on Rexx portability
Here's my take on REXX portability, based on my own experiences.

CLASSIFICATION OF PORTABILITY ISSUES
===============================

There are three  kinds of portability problems that I have encountered:

(1) OS platform portability -- operating system commands vary (from say
DOS to UNIX to MVS)

(2) user interface mode portability -- a console-mode interpreter
interacts with the user in a different way than a GUI interpreter does

(3) interpreter portability -- different interpreters (typically from
different vendors) running on the same platform.  At one time both
Kilowatt REXX and Personal REXX were available for DOS, and they offered
different vendor extensions.  Now the same thing is true of Personal
Rexx for Windows, Enterprise REXX, and IBM's Object Rexx.

SOLUTIONS TO PORTABILITY ISSUES --
RUNNING.REX
========================================

The first task to address in writing portable code is to develop a
routine that enables you easily to figure out what platform (operating
system, user interface mode, interpreter) you are running on.  I have
developed an external REXX routine that I call RUNNING.REX.   Here it
is, in its current version.  Obviously, anybody can customize it to
handle a particular set of available platforms.  If I had it to do over
again (and I may try), I'd have it return values is UPPER case, which
would be more compatible with mainframe implementations.

***********************************************************
/* RUNNING.REX -- find out what interpreter/environment we're running */
/* 1.0 1997/09/23 Steve Ferg (sferg@acm.org) wrote it */
Running:
   if translate(arg(1)) = "TEST" then do
      parse source source
      parse version version
      say
      say "RUNNING:"
      say "   source :" source
      say "   version:" version
      "pause"
      RETURN
   end

   parse source Environment .
   parse version Interpreter .

   if left(translate(arg(1)),3) = "ENV" then return environment

   if Interpreter = "REXX/Enterprise" then RETURN "e~rexx"

   if Interpreter = "OBJREXX"       then RETURN "objrexx"

   if Interpreter = "REXX/Personal" then do
      if Environment = "PCDOS"   then RETURN "prexx"
      if Environment = "WIN"     then RETURN "prexxwin"
      RETURN "unknown"
   end

RETURN "unknown"
***********************************************************

In a program, you can invoke RUNNING() this way:

     IF RUNNING() = "prexx" then do ... end
     else ....


SOLUTIONS TO PORTABILITY ISSUES --
PLATFORM-INDEPENDENT UTILITIES
========================================

The next thing is to isolate your applications from functions that are
platform-dependent.


(1) OS-DEPENDENT
-------------------------------------

Such functions tend to be related to operations on files.
Unfortunately, there are no reliable standards for what kinds of
commands you can give to STREAM(), and some implementations can handle
wildcards on the STREAM() filespec while others can't.

I have developed some standard external routines that I use to do file
operations.  They include

F_EXISTS(f)  -- returns a boolean, tells whether a file named by "f"
exists
F_DELETE(f) -- delete a file
F_CREATE(f) -- create an empty file
F_RENAME(old_f, new_f) -- rename old_f to new_f
F_COPY(source_f, target_f) -- copy file source_f to target_f
F_MOVE(source_f, target_f) -- move  source_f  target_f
F_SORT(f) -- do a simple ascending-sequence sort on file f

GETFILES(filespec_containing_wildcards) --
returns a string of blank delimited filenames that match the filespec.
(Now that long filenames can contain blanks, I may have to revise this)

GETDIRS(...) -- like getfiles(), but operates on directories rather than
files
As long as an application program uses these functions, it can be ported
from one platform to another -- all you have to do is to recode the
internals of these routines.    It is even possible to make these
routines somewhat platform independent, by having them using the
RUNNING() function, and then to perform different OS-dependent actions
depending on which OS they're running on.

I have also developed routines to perform various functions on the NAMES
of files--

F_NAME(name_part, f) -- return a specified part of a filename

F_NAME32(function, long_filename) -- performs various functions (e.g.
CREATE, DELETE, return SHORTNAME) on files specified by a long filename,
and returns the short (DOS 8.3) filename of the file.  This is for older
REXX interpreters (Personal REXX for DOS, for instance) that cannot
handle long filenames

F_UNC() -- performs various functions on filenames specified in
Universal Naming Convention format
(\\servername\sharename\directoryname\filename).  This is for older REXX
interpreters  that cannot handle UNC filenames.


(2) UI-DEPENDENT
-------------------------------------

I have developed some standard external routines interact with the user.
They  rely on the RUNNING() function to tell them what user-interface
mode they're in, and then they behave appropriately.  They include:

MSGBOX (message, title) --
displays a message to the user

GET_KEY(string_containing_list_of_acceptable_keys, message) --
displays a message to the user, and gets a reply.  Will accept only keys
specified in arg(1).

In console mode, this routine writes a line to the screen and waits for
a key press.

In a windowed environment, if the acceptable keys are only Y, N, or
escape, it will present a "QuestionBox" or "CancelBox" window with
Yes/No or Yes/No/Cancel buttons, and the user clicks on his choice.  If
the list of acceptable keys includes other keys, then the user will get
a listbox displaying the acceptable letters, and can choose one by
clicking on it.

GET_SEL(string_containing_list_of_acceptable_choices, message) --
A "get selection" routine.  Like GetKey(), but the list of acceptable
choices consists of strings separated by "00"x characters.  It drops
down a scrollable list of choices from which the user can make a
selection or CANCEL.  (I had to write the routine that does this for
console mode, myself.)

GET_STR(message) --
Get  a string (some input) from the user.

GET_NUM(lower_bound, upper_bound, default, message) --
Like Get_Str() , but will accept only whole numbers between lower_bound
and upper_bound.

F_EDIT(f) --
edits a file using an appropriate editor (i.e. either DOS-based or
Windows-based) for the environment.


(3) Interpreter dependent
------------------------------------------------------------------------
-----

These routines rely on the RUNNING() function to tell them what
interpreter they're using, and then they behave appropriately.

Most of these routines also fit into category (2) but there are still a
few leftovers.  For example, both Personal Rexx for Windows and
Enterprise Rexx support a RexxConsole() function to control the console
window, but one of them accepts letters(e.g. "x") and the other accepts
whole words (e.g. "Maximize").  So I have a function called CONSOLE().
It accepts whole words.  It checks with RUNNING() and returns a whole
word if running under the interpreter that requires whole words,
otherwise it returns the appropriate letter.

So now, instead of coding CALL REXXCONSOLE "MAXIMIZE" or CALL
REXXCONSOLE "X", a program can do this:  CALL REXXCONSOLE
CONSOLE("MAXIMIZE") and it will run under either interpreter.

*******************************************************
I love REXX, and I love to play with it on different platforms. So far I've played with (console mode and windows mode) versions of Kilowatt Rexx (now defunct) Personal Rexx and Enterprise Rexx, as well as Regina on Unix, and IBM's Object Rexx on Win95. No two of them ever worked exactly the same.

-- Steve Ferg sferg@acm.org which (currently) goes to... ferg_s@bls.gov.


Return to the Rexx Anywhere! web page.