You are on page 1of 42

Up to the computing page.

Last-modified: 01 Aug 2004


Version: 1.4q
Send Feedback.

TSO-REXX Frequently Asked Questions

Contents

1. Introduction
1. Changes made to the FAQ at this release
2. To Do
2. Using Rexx.
1. How do I Run My Rexx Exec.
2. How do I allocate Exec libraries and other datasets to my TSO or
ISPF session.
3. Where can I find Rexx manuals and documentation.
4. How do I find or access the current level of a GDG.
5. How do I access data in control blocks such as jobname.
6. How can I access a calling execs variables.
7. How do I implement job control with (and for) Rexx
8. Where are the Standard Rexx I/O functions.
9. How do I replicate CLIST's WRITENR functionality.
10. Why does Outtrap() not trap all output.
11. EXECIO, why does it require 'Enter' more than once.
12. How do I List Datasets and PDS members.
13. How do I access data held on the JES spool.
14. What do unusual return codes such as -3 and 0196 mean.
15. How do I pass parms to my ISPF Edit macro.
16. Why are my procedure arguments in upper case.
3. Rexx and Different environments.
1. OS/390 Unix Systems Services
2. ISPF Services
3. ISPF Services in Batch
4. Load Modules (I.e. Assembled programs)
5. SQL
6. Console Command Environment
7. Rexx aware editors
8. Rexx Compilers
9. Using Rexx as CGI programs with the Web Server.
4. Style.
1. What are the most effective ways to use comments?
2. EXECIO.
3. What case should I program in, upper or lower?
4. Functions and procedures
5. Logical (Boolean) variables and functions.
6. Symbolic names.
7. Internal and external calls
8. How can I improve performance of my exec?
9. How can my systems programmer improve performance?
10. What obscure coding tricks are there?
11. Using Non-Alphanumeric characters.
12. Are there any other style hints?
5. Appendices.
1. Code Examples
2. Other Sources of Information
3. Subscribing to the TSO-REXX List
4. Glossary

Introduction

This FAQ is for REXX/MVS, that is, REXX for IBM mainframes (MVS, OS/390
and VM). It is unofficial and based on questions asked in the TSO-REXX listserv
group based at LISTSERV@VM.MARIST.EDU.

As is the case with all FAQs, this document is a work in progress. Additions,
corrections, and comments are very welcome. Please send any correspondence to
webspace@neilhancock.co.uk

If this FAQ or the List Archivedoes not answer your Rexx questions, do not email
me, but instead subscribe to the TSO-REXX List where your questions will be
usually be answered promptly by many knowlegable people.

Thanks for contributions from:


Jon Alexander, Stephen Bacher, Tim Larsen, John McDonald, and all those who
posed and answered questions on the mail list.

In coding examples, screen output is indicated by ==> within a comment.


Latest edits are marked in bold in the contents section.
Changes made to the FAQ at this release.

• 1.4q - Update links and whatnot, add EXECIO tip, add PARSE ARG.
• 1.4p - New coding trick. Assorted submissions and updates (Thanks!).
• 1.4o - Assorted minor things from the list, edit macro parms.
• 1.4n - R.I.P. Deja. ReMarQ too. Code examples moved external to FAQ.
• 1.4m - Performance updated. Coding tricks added.
• 1.4l - LIBDEF/ALLOC/ALTLIB question and sample.
• 1.4k - Subscription Info added.
• 1.4j - Rexx in Batch added. More info added to. RXSQL noted. Docs
question added.
• 1.4i - More reformatting, Some answers reviewed and amended.
• 1.4h - Minor formatting.
• 1.4g - Corrections submitted by Peter Tillier. Reinstated rexx information
sources

To Do

• Is Posix (USS) info still up to date and correct?


• Inline JCL technique with IEBGENER to temp PDS.
• XMLify.
• Scare up some useful google keywords.

Using Rexx.

How do I Run My Rexx Exec.

Most of this is described in the User Guide, or in the guide for the appropriate
environment.

For the TSO environment:

• Make sure the Exec starts with the /* REXX */ comment.


• TSO EXEC 'hlq.your.dsn(member)' runs Rexx in exactly the same way
as it would a CLIST.
• TSO EX yourlib(member) E uses a suffix of EXEC and the TSO profile
prefix to form a full DSN of 'your_prefix.yourlib.EXEC(member)'
• The SYSEXEC concatenation can be used to run execs using TSO
execname. The SYSPROC concatenation will do just as well, but is really
intended for CLISTS.
• TSO %execname parameter_string allows you to pass a parameter
string to the exec, note that this string is only a single argument, not multiple
arguments as can be used when calling from within an exec. The '%'
instructs TSO to not use loadlib concatenations when constructing the search
path.

For running Rexx in batch:

• Create a batch environment for the Exec to run in with PGM=IRXJCL,


PGM=IKJEFT01 or PGM=IKJEFT1B
• IRXJCL is a straight batch environment
• IKJEFT01 and IKJEFT1B are the TSO in batch programs.
• Put the Rexx exec libraries in the SYSEXEC DD concatenation.
• Call your exec via the PARM='yourprog' or as input in the SYSTSIN DD
• See the Code Examples appendix for examples.

For more information, RTFM.

How do I allocate Exec libraries and other datasets to my TSO or ISPF


session.

• ALTLIB ACTIVATE can be used to dynamically add an Exec or Clist


library to the search path. I don't know whether these ATLIBs are stacked in
the way LIBDEFs can be.
• LIBDEF can be used to dynamically allocate other libraries, such as ISPF
panel libraries. Use the STACK option to avoid corrupting the environment
for nested calls to applications. An example LIBDEF/ALTLIB is in an
appendix.
• Your allocations at logon are controlled by the JCL procedure used to
initiate your TSO environment. This is called your TSO LOGON
PROCEDURE, and it will be under the control of your systems programmer.
• To override the LOGON proc and allocate your own SYSEXEC, ISPPLIB,
etc concatenations, the TSO ISRDDN command can create a handy CLIST
to use as your base. To generate the TSO ALLOC commands needed to
replicate your current allocations, type the CLIST command on the
command line. Edit and save the Clist created, and run it at logon (outside of
ISPF) to allocate your own environment.

Where can I find Rexx manuals and documentation.


IBM Online Library in HTML and PDF form, clisk on the 'elements and features'
links.

The Rexx Reference and Guide are in the TSO section.


ISPF interfaces ( E.g. ISPF Edit Macro commands) are documented in the ISPF
section.
There is a manual for Rexx in the Unix System Services environment.
Interfaces for other applications will be documented with that application.

How do I find or access the current level of a GDG.

Use Outtrap() on the output of TSO LISTCAT.

See RXGDGV() in the appendices.

How do I access data in control blocks such as jobname?

Use the Storage() function to extract the data from control blocks.

/* REXX Get taskname from TCB */


cvt = storage(10,4) /* FLCCVT-PSA data area */
tcbp = storage(d2x(c2d(cvt)),4) /* CVTTCBP */
tcb = storage(d2x(c2d(tcbp)+4),4)
tiot = storage(d2x(c2d(tcb)+12),4) /* TCBTIO */
say strip(storage(d2x(c2d(tiot)),8)) /* TIOCNJOB */
/* I have lost the original author of this code.
Thanks whoever you are - NWH */

How can I access a calling execs variables.

Rexx doesn't allow an exec to access the symbols used by another exec. A classic
example of this is creating an exec to sort the contents of a stemmed symbol.

Some suggested methods are:

• Use ISPF variable pools to pass values across execs


• Pass all symbols as arguments and returned values (I.e. Use PARSE ARG
args and RETURN value).
• Create your own function in assembler to pass stems across. Refer to the
IRXEXCOM macro interface in the IBM Rexx manuals.
• Generate executable code as the returned string and use INTERPRET on it
to make the variables available in the external program.
• Use the stack, see NEWSTACK, DELSTACK, PUSH, PULL, QUEUE, etc,
in the manual.

How do I implement job control with (and for) Rexx

The obvious answer is to use JCL condition code processing, possibly running a
Rexx exec at the end of each job to perform error recovery, message issuing, etc.
See the ISPF in batch discussion for more information.

Another solution if the JCL is part of a single application is to pass checkpoint


information between an application server and executing tasks in a dataset. E.g. A
server may create and prime a checkpoint dataset, and then submit a job that
updates the dataset with diagnostic information that the server will interrogate
when the job has finished.

Jon Alexander <johnalex@AOL.COM> writes:

SDSF offers a batch interface. I don't have the documentation here at home, but it
can be found as a whole chapter in the SDSF Users Guide (Chapter 15 seems to
stick out in my head). I can't swear that you can "interrogate" sysout data or
condition codes from this interface, so you will need to validate this also. If you
can, then you can build a set of REXX to call this interface from within a TSO
environment.

HOWEVER, you must still consider how you will implement this facility. Let's say
that you want to monitor the majority of your batch work. You must "schedule"
this routine to run after every single batch task, inputting the jobname and JES
number for the SDd. This is not an easy task from within CA-7...

CA's (formerly Legent's) JOBTRAC scheduling product provides a facility to run a


REXX routine at the termination of each batch event, and also provides an
interface to their "SDSF counterpart" called SYSVIEW/E which will definitely
allow you to display sysout text and conditions codes. They distribute a sample
REXX with their maintenance tape that provides most of the code for what you
would want to do, but I'm not sure you really want to change your production
scheduler just to solve this one problem.

So, to make a long answer even longer, I would recommend that you get away
from looking at the SDSF data, and possibly try to exploit one of the following:
- Implement the SHARE sample of an IEFACTRT exit that generates WTOs with
step condition code results, and use your local automation package to intercept the
WTO and perform an automation process.

- See if you can exploit CA-11 or CA-7 as far as a user exit point or extracting
their database info, and take actions based on that

- Implement your own IEFACTRT exit (there are a lot of samples out there) to
perform the actions you need (yeah, I know, I'd rather do it in REXX also)

- Get with your CA rep and see if CA-7 will be incorporating any of the REXX
technology that CA-JOBTRAC currently uses and possibly a time frame on the
availability

Where are the Standard Rexx I/O functions

Linein(), Lineout(), Stream() etc. have not been implemented in MVS/Rexx *as far
as I am aware*. I/O can be done using the functionality of EXECIO.
A function package giving I/O functions for the OE environment only is available
from IBM's Download website.

How do I replicate CLIST's WRITENR functionality?

The Rexx say command places a carriage return and line feed at the end of each
say command execution.
Some suggestions for creating a say command that behaves in the same way as
CLIST's WRITENR are:

Use a CLIST call.

Use CHAROUT(). However this I/O function is not implemented in MVS/Rexx.

The free utility XWRITENR (and also XPROC) are available from
ftp.mackinney.com and the CBT tape

Why does Outtrap() not trap all output

The Outtrap() function does not trap output written directly to the screen by the
TPUT macro, but only traps output from the PUTLINE macro. Use PUTLINE
when writing assembler programs, and your program will be of more use in a Rexx
environment.
TSO Session Manager can be used to trap and manage all output that is normally
written to the screen. Session Manager may require some setting up by your
Systems Programer, who will have to look things up in the TSO manuals.

EXECIO, why does it require 'Enter' more than once

When using "EXECIO * DISKW DDNAME", Rexx will continue pulling input
from the stack and from terminal input (depending on the users TSO PROMPT()
value) until it gets a null line. So a users will have to hit enter once to generate a
null line.

Solutions are:

• Queue a null line before writing data from the stack.


• Use the Queued() built in function instead of '*'.
• Only EXECIO the required number of lines from terminal input.
E.g.
"EXECIO 1 DISKR DDNAME (FINIS)" will only read a single line
from the stack or terminal input.
"EXECIO "queued()" DISKW DDNAME (FINIS)" will write the
entire stack without requiring a null line to terminate output.
• See also EXECIO under Style.

How do I List Datasets and PDS members.

There are various methods for listing datasets and PDS members.

• LISTDS - TSO dataset list command. Use the MEMBERS subcommand to


get the members of a PDS.
• LM utilities - ISPF library management utilities. Use LMMLIST with
LMINIT to list the members. See ISPF services manual for details.
• EXECIO of the PDS directory. A PDS directory can be allocated
(LRECL=256) and read with EXECIO. This method does not work with
PDSEs and cannot be guaranteed to work in the future.
• Create your own access routines in assembler using the BPAM access
method.
• CSI - Catalog Search Interface. A high performance catalog search routine
shipped by IBM. Use the LINKMVS environment to use it.
• FDR - Dataset management tools. Innovations FDR package supplies a tool
for reporting upon datasets (FDREPORT) that can be customised for use
with Rexx under TSO.

How do I access data held on the JES spool.

JES3 sites can extract data off of the JES3 spool using E-JES.

The TSO STATUS command gives limited information about jobs on the spool.
The TSO OUTPUT command can reteive some data from the spool. The TSO
CANCEL command can be used to stop jobs, and TSO SUBMIT to them.

Brave souls can try the JES interface macros. Try looking at the CBTTape for
examples.

JES2 users can brave the perils of SDSF in batch or other third party products

What do unusual return codes such as -3 and 0196 mean?

Basically they are either decimalised abend codes or indicate a problem with the
environment.

E.g. 0196 is decimal for hex 0C4, -3 indicates external environment not found.
This is explained further in the Rexx manuals.

How do I pass parms to my ISPF Edit macro.

From command line calls use ISPEXEC "MACRO (parms)"

From ISPEXEC EDIT call use VPUT and VGET

Why are my procedure arguments in upper case.

It may be because you are using ARG instead of PARSE ARG


PARSE ARG arg01 , arg02 /* Arguments parsed with no
uppercasing */
ARG arg01, arg02 /* arguments are uppercased */

Also note that the command line on most panels has CAPS(ON) set. Notable
execptions (where case is preserved)
are ISPF edit and ISPF 6.
Rexx and Different environments.

OS/390 Unix System Services

address SYSCALL and address SH give you the ability to run execs that use
OpenEdition MVS services. From outside of the shell use the Syscall() function to
activate the environment.

See Manual 'Using REXX to Access Unix System Services.' (or whatever its called
this week).

There are some pitfalls in this environment:

• External names are uppercased. So RXGDGV() will work whilst rxgdgv()


will fail not found.
• Newly created executables may need to be made executable with a 'chmod'
command.
• The syscall environment initialises some variables which may be the same
as those you use.
See MTM_RDWR in the mount example below.
• The method of passing variables into the environment calls is different from
'classic' MVS calls.
E.g. mount example.

/* REXX mount example */


path = /tmp/testpath/mountpoint
dsn = 'HFS.TEST.MOUNT'
address SYSCALL "mount (path) (dsn) HFS "MTM_RDWR
say 'Rc='rc 'Retval='retval 'Errno='errno

• Command return codes are not exactly passed via the usual rc symbol, but
through new symbols retval and errno.
• The syscall environment initialises __argv. and __environment. stems.

ISPF Services

address ISPEXEC is detailed in the REXX and ISPF manuals. Examples can
be found using the MODELcommand in the ISPF editor.

Some specific areas covered by ISPF services are:


• PDS interrogation and manipulation using the LM utilities E.g. LMCOPY to
copy datasets and PDS members.
• ISPF table access with the TB utilities. E.g. TBSCAN to find a table record.
• Edit and Browse with ISPF EDIT and ISPF EDIT macros.
• Panel services with DISPLAY, ADDPOP, and others.

There is a known problem in ISPF table services, where a freed table may not be
truly free (its ISPF trying to be eficient), causing the TSO FREE to fail. To avoid
the problem, open a fake table, e.g. ADDRESS ISPEXEC "TBOPEN FAKETAB
LIBRARY(FAKELIB) NOWRITE"

ISPF Services in Batch

ISPF services can be used by a batch job, although panel display and user
interaction may be a little limited. Use ISPSTART running under IKJEFT01 to
initiate the ISPF environment, see the manuals for this.

There can be some difficulties when ISPF return codes >=8 cause ISPF to
terminate. Firstly, the termination will pass a zero RC back to the batch job, most
terminations can be controlled using ISPEXEC CONTROL, see below for an
example. Secondly, the ISPF RC has to be passed back via profile variable
ZISPFRC, as the Rexx RC is ignored by ISPF.

Lastly, BDISPMAX and BREDIMAX errors, often caused by ISPF trying to


display a panel (which it can't in batch, of course) don't appear to be controlled by
CONTROL, and cause abending programs to return zero RCs to the batch job. I
suggest setting RC=4 as normal completion, and treating RC=0 as an abend in the
job control.

/* REXX - Controlling ISPF errors example */


address ISPEXEC

/* If an ISPF error occurs, then return control to the


Rexx */
say 'Setting CONTROL ERRORS RETURN'

"CONTROL ERRORS RETURN"


/* Call LMFREE for a nonexistent dataid. Returns rc =
10 ,
which is not usually terminated by ISPF anyway */

say 'Calling LMFREE'

"LMFREE DATAID(EEYORE)"

say 'LMFREE rc='rc

/* Call LMOPEN with a bad parameter. Returns rc = 20 ,

which causes ISPF to cancel and is thus influenced


by
the CONTROL ERRORS RETURN */

say 'Calling LMINIT'

"LMINIT DATAID(PIGLET) DATAFISH('HADDOCK')"

say 'LMINIT rc='rc

/* Call EDIT with a non existent edit macro. I cannot


get this to
return control to the Rexx, and it terminates here
with the
'ISPP330 BDISPMAX exceeded' message. The JCL
completion code
is an not very useful '00' */

say 'Calling EDIT'

"EDIT DATASET('NWH.GENERAL.EXEC(ISPFTEST)')
MACRO(MCGYVER)"
say 'EDIT rc='rc
/* Pass the current value of rc back to job control */

zispfrc = rc

address ISPEXEC "VPUT (ZISPFRC)"

/* This EXIT return code will not make it back past


ISPF, but it
would have if just TSO was involved. As ISPF is
active, it
is overridden with the value of ZISPFRC, and a
message appears
in the SYSTSPRT output */

exit 12

See the Appendices for an example JCL procedure.

Load Modules (I.e. Assembled programs)

address MVS, LINKMVS, ATTCHMVS, etc. are detailed in REXX manuals.


These allow the user to pass parameter lists into the load module.

Creating and accessing Rexx external functions using compilers and assembler is
also documented in the Rexx manuals. Examples of using the IRXEXCOM
interface are on the CBTTape.

It is well worth understanding APF authorisation for load modules. Read the
manuals for full details, but briefly, APF authority abends are usually a result of
the loss of an authorised environment caused by 'dirty' programs in the user
address space or non APF authorised libraries in the STEPLIB concatenation.
Some items to look out for are:

• The SETPROG console command.


• TSOCMDxx PARMLIB member for TSO command authorisation.
• Assembling programs with AC=1
• 'Problem' programs. A problem program is any non APF authorised
program.

SQL

DB2 for zOS has a Rexx interface, introduced with DB2 version 5, and described
in the latest IBM DB2 manuals. The interface supplies the DSNREXX
environment (as in ADDRESS DSNREXX "EXECSQL SELECT FROM
yadda.yadda").

Console Command Environment

Kevin McGrath writes:


MVS TSO/REXX will issue MVS commands using TSO's MCS console support.
Look in Appendix "D" and "E" of the TSO/E REXX Reference. You must run
under TSO, either interactive or batch. REXX provides a host command
environment. I've attached a simple example.

/* REXX under TSO */


Parse arg command
Address TSO "CONSOLE ACTIVATE" /* start a
console session. */
Address CONSOLE "CART(REXXMVS)" /* Establish a
CART token. */
Address CONSOLE command /* Issue
command. */
getcode = GETMSG('prtmsg.','sol','REXXMVS',,60) /* get
response */
If getcode = 0 then /* got response?
*/
Do i = 1 to prtmsg.0
Say prtmsg.i /* do something
with response */
End
Else
Say "GETMSG ERROR RETRIEVING MESSAGE. RETURN CODE
IS" GETCODE
Address TSO "CONSOLE DEACTIVATE" /* stop the
console session. */
Exit
Neil Hancock adds:
Your security setup may restrict use of the console environment. The relevant
RACF profile is listable using RLIST TSOAUTH CONSOLE ALL. There are also
user profile OPERPARM segments involved.
Note that solicited messages are not necessarily available from subsystems such as
JES, RACF, etc. You may have to issue the command, and process unsolicited
messages to retrieve information from subsystem commands.

Rexx aware editors

ISPF edit supplies a color hilite facility that is a must if it is available. ISPF edit
also supplies models for ISPEXEC services, although you may have to adjust the
style to your own. Many *nix editors understand Rexx, and Kate (part of KDE)
uses a simple XML markup that allows highlighting of any language.

Rexx Compilers

There are Rexx compilers available, including one from IBM. Ask on the
newsgroup for Information on what is currently available.

A freeware utility that converts Rexx source into a tokenised form (as is used by
LLA / VLF) is rumoured to exist.

Using Rexx as CGI programs with the Web Server.


Rexx can be used to create CGI programs that access MVS based resources for
Web browser based users.
All the Rexx program is doing is to write a dynamic html page to the POSIX
sysout stream. This can be done using the Rexx say command.
The BonusPak supplies two shell commands; cgiutils for writing http headers (for
passing html versions and cookies to the browser, etc), andcgiparse for reading
environment variables, including forms data.
For the GET CGI method, the CGI parameters are available in environment
variable QUERY_STRING.
Environment variables are available in the Rexx stemmed variable
__environment. and the parms can be extracted with the parse command. Note
that use of interpret to extract parms is insecure. A user may manage to pass raw
rexx commands into the CGI exec.
For pre OS/390 V1 R3 systems, the Rexx say command wraps at 80 characters.
Use SYSCALL "write .." instead.
Some browsers convert special characters to an escaped notation.
Access to Classic MVS resources can be done by using Rexx function shcmd to
trap the output from commands run under TSO.
Commands can be run under TSO using the tsocmd shell command.
Both shcmd and tsocmd are available from the IBM Kingston ftp site.

Style.

These style hints are mostly aimed at programmers from other MVS and TSO
languages, especially CLIST. Your site may have its own conventions and these
should take precedence over anything said here. You may also prefer to work in
your own style, and that too is perfectly acceptable.

What are the most effective ways to use comments?

• You must have REXX as part of the first line of Rexx, which must also be a
comment. It seems sensible to take the opportunity to say something about
what the program is about in this opening comment.
• Only open and close a comment that goes across many lines once. There are
no real performance benefits in this, only your code will contain fewer
extraneous characters, and this will help you see the wood for the trees.
• It makes little sense to comment every line no matter how trivial. Rexx isn't
assembler and most commands are self evident.
• Consider commenting what is being terminated at an END statement,
particularly select statements and do loops that cover more than one
screenfull of program.
• Don't squeeze comments down the right hand side of the code.
This restricts the insertion of maintenance code, and more importantly, the
restricted space causes the comments to become abbreviated past the point
of becoming cryptic.

EXECIO.

Close any brackets. It makes the code look neater.

Use address MVS "EXECIO" in preference to address TSO "EXECIO".


You then avoid unexpected occurances of rc(-3) when you run the exec under
IRXJCL instead of IKJEFT01.
Tim Larsen <DKDDBNGX@IBMMAIL.COM> writes:
With DISKW - use the queued() built-in, instead of a null-record and "*".
If you forget the null-record ("QUEUE'') you will get a prompt, depending on the
TSO PROFILE!
E.g. "EXECIO" queued() "DISKW DDNAME (FINIS)"

What case should I program in, upper or lower?


Readability can be improved by writing Rexx as if it were a human language and
not as if it were JCL or assembly code. Avoid having the entire program in upper
case, this is known as shouting and does nothing to help readability. It is common
to write commands addressed to external environments in upper case, and to use
mixed case for Rexx commands and symbols.

Functions and procedures

If a call returns a value that is not a return code, it may be better to call it as a
function rather than a procedure.

E.g. Consider: if Linein(needle,haystack) then say 'Hello'


versus: call Linein needle,haystack
if result = 1 then say 'Hello'

Make the names of functions and procedures meaningful

Logical (Boolean) variables and functions.


Symbols with a value of 1 are logically true, 0 logically false. All other values give
an error when tested for logical truth.

Functions may return logical values, but don't confuse functions returning logical
values with those that return numeric values.

E.g. if Datatype(var,'NUM') then say 'Hello' /* Is OK */


if Pos(needle,haystack) then say 'Hello' /* May abend
*/

Symbolic names.
Symbolic (or variable) names are not limited to eight characters as in some other
languages, this allows more scope for meaningful names. The names used will
depend largely on the programmers preferences and style of code writing, but here
are some suggestions to consider.

Use long variable names and separate words with an underscore.


E.g. Table_name = 'MAILTAB'
Underscores are a useful way of hiding variables from ISPF.

Use i,j,k,etc as loop control symbols. Execs that do not hide symbols using the
procedure command may suffer from reuse of loop control symbols.
E.g. do i = 1 to 10

Prefix variable names with specific characters to show the type of data the variable
contains, such as logical, numeric, flags, etc.
I often prefix variable names with '?' to stop variable substitution occurring if
stemmed variables.
E.g.
count = 2 ; year = '1945' ; stem.count.?year = year /*
STEM.2.?YEAR = '1945' */
Bboolean = 1; if Bboolean then say 'hello'
Fflag = 'A'; if Fflag = 'A' then say 'hello'
@address = STORAGE(something)
#numeric = 10 + 12

See also the section on using non-alphanumeric characters

Internal and external calls


Rexx allows calls to be made to execs that reside outside of the currently executing
exec. This allows programmers to use prewritten modules, and to split large
amounts of code up into manageable chunks. However there is a performance
overhead in doing this. Here are some other points to consider:

Some aspects of an exec may be relevant to other execs, and you may wish to
supply a routine that is usable by other execs performing similar tasks. Consider
creating external routines that supply a robust and generic service (e.g. catalogue
listing, mail sending, interfaces to data, etc.) so that other developers may benefit
from reduced development time.

Exit external routines with RETURN rather than EXIT. This facilitates the copying
of the routine into an exec for performance benefits.
Don't use an external call to a routine that is called recursively, the resulting I/O
will drop the exec performance.

The inability to share variables across external procedures may lead you to choose
to use internal procedures over external ones .

How can I improve performance of my exec?


In general, using an appropriate and well tuned algorythm/solution is better than
using specific 'hacks'.

Address external environments directly using ADDRESS. For multiple calls to the
same environment set the environment before entering into the multiple calls.
E.g. address TSO "ALLOC FI(...) DA(....) SHR"

Internalise external routines that are called repeatedly. Then they won't have to be
loaded and tokenised by the interpreter every time they are called.

Use single letter vars for loop control. TSO/Rexx has some fast-path optimisation
for code such as DO I = 1 to 1000.

Using the string functions ( POS(), LEFT(), etc ) on a single very large string,
rather than searching through many stemmed vars.

Use terse coding, avoid unnecessary commands.


E.g. Consider:
if symbol = 'FRED' then say 'Hello'
Versus:
if symbol = 'FRED' then ,
do
say 'Hello'
end

Embed function calls within each other rather than build up a variable over several
lines of code.
E.g. junk = Left(Overlay('A',Userid()||
Random(999),1,1),8)

Use a compiler. Expect 100% improvement for number maniplulation (adding and
whatnot), 30% for string manipulation (using the string functions), and no real
improvement when interfacing to external environments.
Performance test your code using the SYSVAR('SYSCPU') function and not the
TIME() function.

How can my systems programmer improve performance.

Use VLF. See IKJEXEC in Init and Tuning manual. Note that VLF is not used for
external procedure and function calls, only the EXEC interface, like what is used
when you TSO EXEC MYLIB(MYEXEC).

Use the environment tables in SAMPLIB if you have environments that are not
part of the distribution tables.

Supply well written external routines for non trivial tasks performed by user code.
This may deter users from writing inefficient code to perform the task themselves.

What obscure coding tricks are there.

These tricks are not necessarily recommended, as they can reduce the readability of
the code. They can also speed things up, particularly those tricks using PARSE.

Use a boolean check.


sign = TRANSLATE( (var<0) , '+-' , '01' )
boolvar = (test=true)

Use the TRANSLATE() function to reorder or insert chars in a string. (Thanks to


Doug Nadel)
say TRANSLATE('ab,cde,fgh,ijk,lmn,opq,rst,uvw,xyz' ,
myNumber , 'abcdefghijklmnopqrstuvwxyz')
say TRANSLATE('abcdefgh ijklmnop qrstuvwx yz123456' ,
hexChars , 'abcdefghijklmnopqrstuvwxyz123456')

Use functions such as DATE() to check if some data conforms to a type. Use ON
SYNTAX to trap the error.
junk = DATE(,testDate,'S') /* Will give a syntax error
is testDate is not a standard date */

The many variations of DO. Read the Rexx Reference and Rexx Guide for
inspiration.

Use PARSE to initialise variables.


parse value '1 2 3 6 4' with var1 var2 var3 var4 var5 .
Use PARSE instead of SUBSTR().
parse var string =(colA) var1 +(lenA) .,
=(colB) var2 +(lenB) .,
=(colC) var3 +(lenC) .
is functionally equivalent to:
var1 = SUBSTR(string,colA,lenA)
var2 = SUBSTR(string,colB,lenB)
var3 = SUBSTR(string,colC,lenC)

Use PARSE to split out bits from a string:


parse value
X2B(C2X('AA'x)) ,

with x80 2 x40 3 x20 4 x10 5 x08 6 x04 7 x02 8


x01

Use PARSE to increment a stem counter and assign a value to the new last stem
(Attributed to Les Koehler @ IBM Tampa).
stem.0 = 1 ; stem.1 = 'cheese'

parse value stem.0+1 'mice' ,


with 1 x stem.x ,
1 stem.0 . ;

say stem.0 /* ==> '2' */


say stem.1 /* ==> 'cheese' */
say stem.2 /* ==> 'mice' */
say x /* ==> '2' */

Using Non-Alphanumeric characters.

Stephen Bacher writes on the subject of using non-alphanumeric characters:


"I disagree strongly with (... the use of ...) non-alphameric characters (like ! # % )
in REXX variable names. Extensions to REXX, including object-oriented
extensions, may use these characters. Including them in variable names is asking
for eventual breakage IMHO."
Another reason to avoid using the EBCDIC special characters (these include
currency symbols, hash, at, double quotes, etc.) is because these characters may
have different display meanings in foreign EBCDIC chacacter sets.

Are there any other style hints?

• Leave space at the end of line for continuations and short 'date changed'
comments.
• Avoid ambiguous or confusing abbreviations in symbol names and
comments.
E.g. 'lst' and '1st' appear similar, and will a casual reader know whether a
symbol 'lstPointer' is referring to a 'list pointer' or a 'last pointer'
• See the Style Guide for a more detailed style discussion

Appendices.

Code Examples

This is a sample JCL procedure to run ISPF in batch.

//ISPFPROC PROC PREFU='user.lib',PREFS='SYS1',CMD=


//ISPF EXEC PGM=IKJEFT01,DYNAMNBR=30,
// PARM='ISPSTART CMD(&CMD)'
//*
//ISPPROF DD DISP=NEW,UNIT=SYSSQ,SPACE=(TRK,(1,1,1)),
// DCB=(LRECL=80,BLKSIZE=8000,RECFM=FBA)
//*
//SYSEXEC DD DISP=SHR,DSN=&PREFU..EXEC
// DD DISP=SHR,DSN=&PREFS..SISPEXEC
//SYSPROC DD DISP=SHR,DSN=&PREFS..SISPCLIB
//ISPPLIB DD DISP=SHR,DSN=&PREFU..PANELS
// DD DISP=SHR,DSN=&PREFS..SISPPENU
//ISPMLIB DD DISP=SHR,DSN=&PREFU..MSGS
// DD DISP=SHR,DSN=&PREFS..SISPMENU
//ISPSLIB DD DISP=SHR,DSN=&PREFU..SKELS
// DD DISP=SHR,DSN=&PREFS..SISPSENU
//ISPTLIB DD DISP=SHR,DSN=&PREFU..TABLES
// DD DISP=SHR,DSN=&PREFS..SISPTENU
//ISPTABL DD DISP=SHR,DSN=&PREFU..TABLES
//ISPLOG DD DUMMY,DCB=(RECFM=VA,LRECL=125,BLKSIZE=129)
//ISPLIST DD DUMMY,DCB=(RECFM=VA,LRECL=125,BLKSIZE=129)
//ISPLST1 DD DISP=NEW,UNIT=SYSSQ,SPACE=(CYL,(1,1)),
// DCB=(LRECL=121,BLKSIZE=1210,RECFM=FBA)
//ISPLST2 DD DISP=NEW,UNIT=SYSSQ,SPACE=(CYL,(1,1)),
// DCB=(LRECL=121,BLKSIZE=1210,RECFM=FBA)
//ISPCTL1 DD DISP=NEW,UNIT=SYSSQ,SPACE=(CYL,(1,1)),
// DCB=(LRECL=80,BLKSIZE=800,RECFM=FB)
//ISPCTL2 DD DISP=NEW,UNIT=SYSSQ,SPACE=(CYL,(1,1)),
// DCB=(LRECL=80,BLKSIZE=800,RECFM=FB)
//ISPWRK1 DD DISP=NEW,UNIT=SYSSQ,SPACE=(CYL,(1,1)),
// DCB=(LRECL=256,BLKSIZE=2560,RECFM=FB)
//ISPWRK2 DD DISP=NEW,UNIT=SYSSQ,SPACE=(CYL,(1,1)),
// DCB=(LRECL=256,BLKSIZE=2560,RECFM=FB)
//*
//SYSUDUMP DD DUMMY
//SYSTSPRT DD SYSOUT=Q
//SYSPRINT DD DUMMY
//SYSTSIN DD DUMMY
// PEND

And run it thus:

//TESTISPF EXEC ISPFPROC,


// PARM.ISPF='ISPSTART CMD(%REXXPROG
TESTPARM)',
// PREFU='ISP'
//* or alternativly,
//TESTISPF EXEC ISPFPROC,
// CMD='%TESTPROG TESTPARM'
// PREFU='ISP'

If ISPF is not needed, try using IRXJCL:

//IRXJCL1 EXEC PGM=IRXJCL,PARM='%execname'


//SYSEXEC DD DISP=SHR,DSN=your.exec.library
//SYSPRINT DD SYSOUT=*
//SYSTSPRT DD SYSOUT=*

or TSO in batch:
//IRXTSO EXEC PGM=IKJEFT01
//* You could use PARM='%execname testparms' on exec
statement
//SYSEXEC DD DISP=SHR,DSN=your.exec.library
//SYSTSPRT DD SYSOUT = *
//SYSTSIN DD *
%execname testparms
/*
//* You could run more than one exec sequentially from
SYSTSIN
//* You can also run straight TSO commands like TSO
PROF PREF(FRED)

This Exec shows how to temporarily LIBDEF ISPF datasets to your session,
ALTLIB an Exec library to your session, call an ISPF application (under a new
application Id to avoid name collisions with other ISPF profile var. names), and
then unstack the LIBDEFs, and deactivate the alternative Exec library.

address ISPEXEC "LIBDEF ISPPLIB DATASET


ID('user.PANELS') STACK"
address ISPEXEC "LIBDEF ISPMLIB DATASET ID('user.MSGS')
STACK"
address ISPEXEC "LIBDEF ISPTLIB DATASET
ID('user.TABLES') STACK"
address TSO "ALTLIB ACTIVATE APPLICATION(EXEC)
DATASET('user.EXEC')"
address ISPEXEC "SELECT CMD(%yourexec "your exec
options") NEWAPPL(yourappl) PASSLIB"
address ISPEXEC "LIBDEF ISPTLIB"
address ISPEXEC "LIBDEF ISPMLIB"
address ISPEXEC "LIBDEF ISPPLIB"
address TSO "ALTLIB DEACTIVATE APPLICATION(EXEC)"

Other Sources of Information.

Marist College hosts the ListServ Archives at


http://www.marist.edu/htbin/wlvindex?tso-rexx
Yahoo! has a Rexx category that lists several Rexx information sites.
Rexx Book list. Try searching Amazon.
Rexx manuals, search the IBM S390 Websiteto find them online in PDF and
HTML formats.
CbtTape.org has many useful utilities, including a list of Listserv lists.
Rexx newsgroup comp.lang.rexx can be found via Google

ISPF has its own active listserv at ISPF-L@listserv.nd.edu, Send message text
'SUBSCRIBE ISPF-L Your Name' to LISTSERV@listserv.nd.edu and await
further instructions.

Subscribing to the TSO-REXX List

Go to the TSO-Rexx list archive, scroll to the bottom of the page, and follow the
instructions there.

Glossary

Generation Data Group.


A set of datasets referred to by their GDG base, and a generation number
referring to the generation required. See MVS/Using datasets.
Built-in Function.
A standard Rexx function, e.g. SUBSTR().
External Function.
An external Rexx function, either supplied with TSO/E, e.g. SYSVAR(), or
supplied in a PDS in Rexx source or Load Module form, e.g. RXGDGV() in
appendix C.
Internal Label.
A label within an exec, either hidden (with PROCEDURE without
EXPOSE), or exposed.
Profile prefix.
A TSO environment variable. The users default HLQ, prefixed to
unqualified datasets to make a qulified DSN
HLQ.
High Level Qualifier. The first qualifier in a fully qualified dataset name.
DTL
Dialog Tag Language. An SGML markup language used to define ISPF
panels. DTL documents are converted into panels using the ISPDTLC
compile utility.
Last-modified: 01 Sept 2000Version:
Up to the computing page.
Send Feedback. 1.3cAuthor: Neil Hancock

Style Guide for Rexx

Introduction.

This is a style guide for the Rexx programming language, primarily aimed at Rexx
in the OS/390 environment, but application to Rexx on other platforms, and to
variants of Rexx such as NetRexx. Many of the points here have been raised at
some time on the TSO-REXX listserv mailing list.

Using Comments.

Correctly commenting code is one of the most useful things you can do when
writing code. Most style problems can be repaired with a formatter, but useful
comments cannot be conjured out of thin air.

Comments come in two flavours, winged and boxed:

/* This is a winged comment */

/**************************************\

* This is a *

* boxed comment *

\**************************************/

Boxed comments should be reserved to highlight major structural elements, such


as the start of procedures. See the Calculate_Factorial() example.
Winged comments are useful for describing the purpose of the statement(s)
immediatly adjacent to the comment.
It may also be worth borrowing from the Javadoc idea. Comments enclosed
by /** ... */ may be read by a suitable program, and the text within associated with
the immediately following Rexx statement, and used to automate the creation of
external documentation for the program.

The use of asterisks (*) as the box border can make the comment too heavy.
Consider using minus (-) or another lighter character instead. Also, don't worry
about closing the right hand vertical, as realigning the border after text insertion is
a tedious affair. You may have a Rexx reformatter that deals with this problem.

A common use of boxed comments is for an informative header at the start


of the exec. Here is an example:

/* Rexx -----------------------------------------
AUTOTOOL
<A one line description for use by an indexing tool>
-------------------------------------------------------
Copyright: <You may want a copyright notice>
Change History:
<yy/mmm/dd Userid VersId Description>
Description:
<A long description of the purpose of the exec.
Include invocation arguments, examples of call
syntax, returned values, etc.>
---------------------------------------------- MEMNAME
*/

It is extremely useful to have a tool to handle the creation of a standard header, and
to handle change history information. Note the marker in the top right of the box
that may be used by a tool to spot whether this is a standard header or not.

An indexing tool that reads the short description and change history is also useful,
so make sure the header format supplies information that would be useful to an
indexing tool.

In the change history, put the most recent changes at the top, so they are visible
when the exec is opened. It may also be useful to have a version id to mark
changed code in small comments later on.
It may not be worth putting some types of information in the header, particularly
data that ages poorly. Dataset names and pointers to external information are
particularly prone to becoming incorrect.

Things to avoid when creating comments:

• Stating the obvious. E.g. string = TRANSLATE(STRIP(string)) /* Strip


and uppercase string */
• Using 'content free' phrases such as 'We call this Rexx program to get a
returned value to the caller consisting of....'. This also applies to function
names, E.g. In Perform_Update_Function(), the words 'perform' and
'function' tell us nothing useful, try Update_Table() instead.
• Using acronyms and abbreviations to the point of crypticness. E.g. /* prt opt
pg len */ instead of /* print option - page length */.

Using Symbols and Variables.

The Rexx ANSI standard talks of Rexx 'symbols'. Here I shall use the more
familiar terms 'variable', 'variable name' and 'variable value'.
Compound variables, stems, and tails refer to the whole and various parts of
compound variables.

Always make your variable names useful and meaningful.

Some programmers use a form of Hungarian Notation to show when a variable is


boolean, a loop index, numeric, etc. This may make your variable names not so
easy to read, and has other dissadvantages.

Consider using i, j, k, etc as loop control variables. There are advantages and
disadvantages to this:
Faster execution speed. Single character variable names show a performance
improvment (My simple test measured nearly 10% improvement).
Compound variables names are shorter and less likely to make long
statements cross onto multiple lines.
You have to rely on the stem name to indicate the meaning of the data in the
compound variable. E.g. Consider compound var names record.i and
score.teamIndex.eventIndex . It is obvious that i is the record number, but would
it be obvious from score.i.j that i is the team and j is the event?
Global variables can be handled in the following way:
First, in the opening section of your Exec, set a variable named global whose
contents are the stem names of your global variables:

global = 'gl.' /* set a list of stems that are global


variables */
gl.testVar = 'This is a sample global variable.'

Then, at each procedure start, expose this global, and its contents:

An_Internal_Function: procedure expose (global)


say gl.testVar /* ==> This is a sample global variable.
*/
return 0

Do use the procedure keyword with every defined procedure. This hides the
procedure's internal variables from the caller, and allows the use of i,j and k as
loop control variables throughout your program without any side effects, as in this
example:

do i = 5 to 10
say 'Number:'i 'Factorial:'Calculate_Factorial(2,i)
end
exit
/* --------------------------------------
Return the multiple (factorial) of all
numbers between arg1 and arg2.
-------------------------------------- */
Calculate_Factorial: procedure
parse arg startNum,endNum
factorial = 1
do i = startNum to endNum
factorial = factorial * i
end
return factorial

This common problem occurs when a variable is assigned a value, and the
variable is then used in a compound variable:

stem.salutation = 'HELLO!' /* literally


stem.SALUTATION = 'HELLO!' */
/* Output the intended result */
say stem.salutation /* ==> HELLO! */
/* Set the tail of the compound variable to have a value
*/
salutation = 'GOODBYE'
/* Now show an often unintended result */
say stem.salutation /* ==> STEM.GOODBYE */
exit

If you don't want this effect, you must use unassigned variable names in the
compound variable. You could use non-alphanumeric characters such as ! or ? to
prefix the variable name when its used in the compund tail, or use a numeric
prefix, as symbols starting with numerics are, by definition, constants.

testVar = 'HELLO'
stem.0testVar = testVar
stem.!testVar = testVar

Numeric prefixes are better for code portability.


The ISPF editor picks special characters (such as !) up in HILTE mode.
Non-alphanumeric characters may not be portable to foreign EBCDIC character
sets, and are not portable to some Rexx extentions, such as Object-Oriented Rexx.

Large blocks of variable assignations can be split into columns, with variable
names, variable values, and winged comments:

gl.Bdebug = 0 /* Turns debugging


on/off */
gl.!pageLength = 60 /* print option -
page length */
gl.!pageWidth = 50 /* print option -
page width */

Case, Indentation and Block Structure.

Here we discuss the case we could use for syntactic elements, what indentation is
appropriate, and how we could break blocks of statements up.
But first, What are we trying to achieve, and how do we go about achieving our
aim.

We are trying to increase the readability of the code.

We can improve readability by using techniques to differentiate and highlight


elements within a statement, show relationships between statements, and lay out
the code so that these relationships and statement elements are visible to the reader.

The target audience should be anyone likely to need to change your code in the
future, including yourself. Don't assume any knowledge of the inner workings of
your code, it may be some years before you revisit it. A reasonable knowledge of
Rexx may be assumed, even though the reader may be new to the language.
Everyone has to learn something sometime.

Here are some elements that go to make up statements:

• Keywords such as IF, SELECT and CALL.


• Variable names such as USERNAME, or USERCARDNUMBER.
• Major Blocks such as DO count - END, SELECT - END and label:
PROCEDURE - RETURN.
• Contitional Blocks such as IF cond THEN DO - END and WHEN cond
THEN DO - END.
• Natural Blocks of statements that together perform a particular function.
• Builtin function names such as POS(), D2B(), and TRANSLATE().
• Implementation supplied builtin external functions such as MVSVAR() and
LISTDSI().
• User supplied external functions such as MYFUNC().
• Internal functions such as MYINTERNALFUNCTION().
• Commands to external environments such as ADDRESS TSO "LISTCAT"
• Comments.
• Literal text.

Here are some techniques to improve readability using case:

• UPPER, lower and Mixed case.


• Concatenated words with case differentiation such as ConcatenatedWords.
• Underscore separated words such as underscore_separated_words.

Other techniques to improve readability are:.


• Indenting blocks or statements within blocks.
• Aligning the start of a block with its end.
• Splitting statements across more than one line.
• Inserting blank lines between blocks.
• Using boxed comments before major structural items.

We are also concerned with breaking up the code into manageable and
reusable units. We can do this by using procedures, and by surrounding blocks of
statements that perform a task with white space.

Constraints and Influences on Style Guide Rules

There are some facts about the programming environment that influence what
techniques may be most effective.

External function names are related to file names in a file system, or member
names in partitioned datasets. MYFUNC() may refer to PDS
'user.EXEC(MYFUNC)' or file name MYFUNC.REXX, depending on your Rexx
environment. By using upper case for external function names, you avoid having to
quote function names to maintain lower case in the file name. You then, of course,
must use upper case in the file names of external functions.

Screen size influences how much code can be on the screen at one time.
Using too many blank lines or splitting statements over multiple lines can move
code out of the viewable area. Techniques that disperse statements over several
lines work well on window based systems that allow the viewable area to be
adjusted, but are restricted by the fixed terminal size of 3270 systems. The same is
true for boxed comments.

Some editors are Rexx aware and can hilite Rexx code to show keywords,
quoted strings and other elements. A Rexx aware editor such as ISPF EDIT with
the HILITE utility, can for instance turn all keywords red, and all comments blue.
There's no point in using upper case to highlight keywords, when the editor can
pick them out with colour.

Keywords appear in isolation in the text, and also tend to appear at the start
of statements, so they stand out without any help from the programmer.
Function and variable names tend to appear close together in code. We need
to be able to differentiate variable names from function names, and also
differentiate between the various flavours of function; internal, external, builtin,
etc. For example:

datasetName = STRIP(TRANSLATE(GetDsn(ddName)),,"'")
or
datasetName =
STRIP(TRANSLATE(Get_Dataset_Name(ddName)),,"'")

Literals should be quoted, unless you can be sure that the literal hasn't been
used as a symbol elsewhere.

Commands being passed to external environments often have single quotes


in them. E.g. address TSO "ALLOC FI(TEST) DA('MY.DATASET') SHR
REUSE".

NetRexx (and presumably other close relatives of Rexx) are sufficiently


different from Rexx to make consideration of styles that apply to NetRexx
irrelevant. Having said that NetRexx is a case insensitive language, and most style
rules should apply to it.

An Example Set of Style Rules.

Here is programming style that meets the above criteria in varying degrees. It tries
to keep the code dense for 3270 displays, and relies heavily on a highlighting
editor to differentiate between keywords, quoted text, variable names, and other
statement elements.

Rules specifically related to case:

• Keywords in lower case. Let the editor highlight them. E.g. select do if end
• Variable names starting in lower case with the initial letters of concatenated
words in upper case. This differentiates them from function names, which
will all start with an upper case character. E.g. myName counter fishType
• Internal function names with the initial letters of individual words in upper
case. Optionally, you can also use underscore separators E.g. MyFunction()
MyOtherFunction() Your_Function()
• External function names in upper case. This saves having to quote them. E.g.
MYFUNC() EXTFUNC()
• Builtin functions in upper case. This rule is pretty arbitrary, as the
highlighting editor will mark them in a different colour to user supplied
functions anyway. E.g. SUBSTR() D2B() MVSVAR()
• External environment names in upper case. Note that address treats the
environment name as a constant, and does not perform symbolic
substitution. E.g. address TSO "X", address ISPEXEC "FTCLOSE",
address SH "cat myfile"
• Commands for external environments preferably in upper case, unless the
environment is case sensitive. E.g. address TSO "ALLOC FI("ddName")
SHR REUSE DA("dsName")"

Rules for quoting:

• Enclose commands to external environments in double quotes, coming out


of quotes to pass variables into the command. E.g. address TSO "ALLOC
FI("ddName") SHR REUSE DA("dsName")"
• Always quote literal strings. Never rely on the string not being used as a
variable name. E.g. myVar = 'test'. Never use myVar = test
• Use variables for quotes if building strings containing both single and
double quotes to avoid the opening and closing becoming confusing: E.g.
myVar = dq||sq||'test'||sq||dq rather than myVar = '"'"'test'"'"'

Rules for procedures:

• Use the procedure keyword wherever possible, to avoid collisions between


variable names in different procedures. E.g. My_Function: procedure
expose (global).
• Use commas and not spaces to delimit your arguments. See the
Calculate_Factorial demo for an example.
• Use a comment in the style of javaDoc before each procedure, so that
possible future 'rexxDoc' implementations will be able to pick up your
comments. See the LZ78 demo for an example.

Rules for comments:

• Use a standard machine readable header to document a synopsis of the exec,


change information, and any other useful information. Create a tool to
manage and extract useful information from these headers.
• Comment logical groups of statements, indicating what they do. E.g. do
loops, select blocks.
• Aim your comments at a reasonably competent programmer who is not
familiar with your exec.
• Comment what end statements are ending. E.g. end /* select */.
• Indicate what variables are to be used for when they are first assigned. See
the example in an earlier chapter.
• Use a boxed comment to mark where procedures begin. See the LZ78 demo
for an example.

Other miscellaneous rules:

• Use i, k, j, etc. as loop control variables. This can make your code run faster,
and helps keep statements short..
• Indent do, select, and other similar structures with between 1 and 3 spaces.

An aside on following a ruleset: One important comment that must be


made, is that it is not so important that one set of rules is followed in all cases, but
it is important that a set of rules is followed consistently in a piece of work.
Leaping from one style to another mid-procedure can hinder a readers
understanding of the program.

Style Example

Here's an example program, with two similar internal functions present, in


two slightly different styles. The compress function follows the above example set
of guidelines. The uncompress function follows a second consistent set of
guidelines. Note how the uncompress function is easier to read when no colour
highlighting is used. Copy the program into a Rexx sensitive editor, and the
compress function becomes easier to read.

/* REXX
--------------------------------------------------------
--
SYNOPSIS : LZ78 compressor/decompressor demo
VERSION : 1.0
CREATED : April 1999
NOTES : From the uberFish Rexx Style Guide.
--------------------------------------------------------
------- */
/* Demo normal text compress/decompress */
say 'Normal text demo.'
inString = 'If_I_were_a_Linesman,_' ,
||
'I_would_execute_the_players_who_applauded_my_offsides.'

say 'Compressing 'LENGTH(inString)' byte string .....'


say inString

compString = Compress_LZ78(inString)
say 'Compressed as.....'
say compString

decompString = Uncompress_LZ78(compString)
say 'Decompressed as.....'
say decompString

/* Demo Highly redundant text compress/decompress */


say
say 'Highly redundant demo.'
inString = 'Oogachacka,Oogachacka,Oogachacka,'
inString = inString||inString||inString||inString
inString = inString||inString||inString||inString
say 'Compressing 'LENGTH(inString)' byte string .....'
say inString

compString = Compress_LZ78(inString)
say 'Compressed as.....'
say compString

decompString = Uncompress_LZ78(compString)
say 'Decompressed as.....'
say decompString

exit 0
/**
========================================================
========
Demo LZ78 compressor. In reality, the output would be
formatted
to take up less space then the original, E,g, in 9bit
bytes,
with the high order bit set on when the char is a
dictionary
reference, or something.
Oh, and it doesnt like spaces, cos I use WORDPOS() to
search
the dictionary.
--------------------------------------------------------
--------- */
Compress_LZ78: procedure
parse arg inString

/* Initialise the dictionary, output string, etc */


outString = '' /* output string
*/
d = '' /* Dictionary. Blank delimited 'words'
*/
w = '' /* Last used phrase
*/
outCode = '' /* Our current dictionary reference for
this phrase */

/* For every char in the input string, output the char,


or a
dictionary reference */
do i = 1 to LENGTH(inString)

/* Get next input char, and make phrase */


thisChar = SUBSTR(inString,i,1)
thisPhrase = w||thisChar

/* If the new phrase is in the dictionary, queue up this


dictionary
reference to go into the output string */
if WORDPOS(thisPhrase,d) > 0 then do
if outCode = '' then outString =
LEFT(outString,LENGTH(outString)-1)
outCode = '<'WORDPOS(thisPhrase,d)'>'
w = thisPhrase
end

/* If the new phrase wasnt in the dictionary, output it


*/
else do
outString = outString||outCode||thisChar
outCode = ''
d = d thisPhrase
w = thisChar
end
end

/* return the compressed string */


return outString||outCode
/**
========================================================
=========
Demo LZ78 uncompressor.
Here Ive switched to another style for comparison.
--------------------------------------------------------
--------- */
Uncompress_LZ78: procedure

parse arg inString

/* Initialise stuff */
outString = ''
d = ''
w = ''

/* Loop across each char in input string, building


dictionary
and output string as we go */
Do i = 1 To Length(inString)
/* Get next char from input string */
thisChar = Substr(inString,i,1)
/* If the char is a code, then look up code in
dictionary,
add new phrase to dictionary, add phrase to output */
If thisChar = '<' Then
Do
thisCode = Substr(inString,i+1,Pos('>',inString,i)-i-1)
thisOut = Word(d,thisCode)
d = d || ' ' || w || Left(thisOut,1)
w = thisOut
i = i + Length(thisCode) +1
outString = outString || thisOut
End
/* Else add phrase to dictionary and output the
character */
Else
Do
d = d || ' ' || w || thisChar
w = thisChar
outString = outString || thisChar
End
End
/* Return the uncompressed string */
Return outString

Spot the line that is longer than 80 characters. In ISPF it should be wrapped in
whatever way is consistent with your treatment if IF THEN DO constructs:

if outCode = '' then


outString = LEFT(outString,LENGTH(outString)-1)

Enforcing Programming Standards.

There's little point in setting guidelines for writing programs if they're going to be
ignored. You have to make people want to follow your guidelines.

Here are some ways of encouraging the use of programming standards:

• Allow individuals to follow the guidelines as they see fit.


• Use management control.
• Automate the process by supplying easy to use tools to manage internal
documentation and format code.
Relying on individuals can fail if lazy or naive programmers fail to follow
guidelines. It does allow creative and expert programmers the freedom to use
systems they are comfortable and productive with.

Management control is particularly suited to organizations that have a Quality


Assurance program.

A tools led approach can be combined with other approaches. There is some
development effort to get tools into place, and the tools must be flexible and fit for
their purpose. Generally, a handful of ISPF EDIT macros to perform various
formatting tasks will suffice.

Concatenating Strings.

There are two schools of thought on whether to explicitly use the string
concatenation operator ( || ) or not.

say 'Explicit use of operators'


newString = oldString || ' Some Text ' ||
MYFUNC('ham','eggs')
say Status_Char(userStatus,'TEXT') || ': ' ,
||userName || ' is ' || userStatus
say 'Omission of operators.'
newString = oldString 'Some Text' MYFUNC('ham','eggs')
say Status_Char(userStatus,'TEXT')':' userName 'is'
userStatus

There are some good reasons to use concatenation operators, and some good
reasons not to.

Points for the use of the concatenation operator:

• Programmers supporting your code often do not use Rexx regularly.


• Component elements of the string will be explicit to the reader.
• Unexpected spaces can creep in when ommitting concatenation operators.

Points for ommiting the operator whenever convenient:


• Less is more. The resulting code is tidier.
• The Hilite function of the editor makes the component elements clear
enough anyway.
• There is less chance of the statement overrunning onto the next line.
• You don't have to explicitly include spaces inserted between the strings. E.g.
var1 || ' ' || var2 versus var1 var2

Some Other Dos and Don'ts

Don't use too many levels of indirection.


For instance, there is often little use in storing ISPF panel names in variables when
address ISPEXEC "DISPLAY PANEL(MYPANEL)" pinpoints exactly where in
your exec that panel MYPANEL is actually used. Having functions call functions
that call functions can equally make debugging an exec very tiresome indeed, so
make sure that you comment what is going on if you do have to create a
complicated structure.

Do make sure abstracted procedures allow full access to the underlying data.
For instance, say you put a nice shell exec around TSO LISTCAT. Your shell
program should be able to control all arguments for LISTCAT, and return any
information returned by LISTCAT to the caller. Otherwise you may have to create
a new interface for your old shell if new LISTCAT data is needed. Adding new
functionality to old code creates backwards compatibility problems, and
maintenance problems as the code becomes 'hairy' from unnecessary revisions.

Don't hide information in variables set far away from where they are used.
Its one thing to have a nice block of commented assignations near the top of an
exec or procedure, but its another thing entirely to place assignation statements in
obscure parts of the exec, far away from where they will actually be used.

Do end all internal procedures with an unconditional RETURN.


This makes it clear to the reader when a procedure ends and may help code
formatting tools to spot the end of internal procedures.

Send Feedback

Please drop me a line, and tell me about any improvements you think could
be made to this page.
• Was the information useful?
• Was the information complete and concise?
• Did you understand the information?

You might also like