Chapter 2. REXX Coding Styles

A good procedure is not only bug-free, but is also readable and maintainable by others.  REXX is such a powerful language, and allows such a freedom in coding styles that each programmer can easily create his/her own 'dialect'.  This may result in procedures that can no longer be maintained by others, or even yourself after a while, especially if the procedures are large and complex.  This chapter proposes some of our standards when coding REXX procedures and is based on our long experience with this language.

We are hereby guided by the following considerations:

There are hundreds of programming languages.  Each language has its typical characteristics, pro's and con's.  REXX is a language that is not strong-typed (you don't have to define the variables), with only limited syntactical rules (almost no punctuation or other special characters), and a great freedom in coding style or variable naming (you can define a variable if or do if you like to).

Let's turn these characteristics into our advantage and not impose stringent coding styles and programming structures.  Let's only focus on efficiency for the programmer.  Another person that has to maintain the program will not get lost more when the style is compact and straightforward.

Look at some examples.

This is all of course our own opinion.  You remain free to have another one.


Use of comments.

Comments are very useful in procedures.  Although REXX is a high level language and thus its syntax self-explanatory to some extent, the functions you execute in the procedure can be unclear to others.

A REXX procedure has to start with a comment to indicate to CMS that it is not an EXEC1 or an EXEC2 procedure.

On OS/2, the first REXX comment line has to start in column 1.

This first comment should at least contain a very brief explanation of the purpose of the procedure and an indication of the environment where it has to be used.  For example, a RXSQL procedure should indicate that it needs access authority to SQL databases.

Other useful indications at the beginning of the procedure can be :

Your PROFILE XEDIT could prepare a standard initialization when you code a new procedure.

Comments have almost no influence on the execution speed of the procedure.  They only use space on disk and make the loading time a little bit longer.
When you invoke a REXX procedure for the first time (after creation or modification), the procedure is tokenized by OS/2.  It means, the procedure is kind of pre-compiled, and comments are removed.  All subsequent invocations will use the tokenized version for quicker loading.  The tokenized version is stored in the Extended Attributes.


Use mixed case.

Use of mixed case highly augments the readability of the procedure.  But here too, some consistency is useful.  We propose the following conventions :

  1. Use lowercase or mixed case characters for :
  2. and use uppercase characters for :

We already mentioned that the advantage is that the key functions of the procedure will rapidly become evident to the reader as they are highlighted by the use of uppercase characters.  Anyway, chapter 3 will give you other good reasons to code the commands in uppercase.

Remember, the variable names are not case sensitive, which means that the variable name counter is equivalent to COUNTER or Counter or any other combination of upper- and lowercase characters.  Internally, REXX uses only the uppercase form of the variable names.  It is important to remember this when accessing REXX variables from outside environments, such as EXECIO or Assembler programs.

We for our part will try to be consistent with our convention in our documents.  We have set up our XEDIT PROFILE so that it immediately sets CASE to MIXED for REXX procedures.


Variable names.

In a larger piece of code, variable names should be as explicit as possible to give the reader a clue of what it is supposed to contain.  Short, one character variable names are good for temporary use, such as the variable "a" here:

   a=wordpos(day,'MONDAY TUESDAY WEDNESDAY THURSDAY FRIDAY SATURDAY SUNDAY')
   if a=0 then say 'Bad day name'
          else DayOfWeek=word('Mon Tue Wed Thu Fri Sat Sun')

Typical is to use one character variables to DO-loop counters.

In the past, we used more explicit variable names by combining several words, separated by underscores, such as day_of_week.  It is very clear what the meaning of the variable is, but the typing of the underscores is cumbersome (at least in Europe where it is an uppercase character on the keyboard), and the reading may lead to confusion with blanks.

Since some time now, we prefer the more compact form, but with a judicious mix of uppercase and lowercase characters, such as our DayOfWeek in the previous coding example.

Don't exaggerate in lengthy variable names either.  If it is obvious from the context that you are working with day-names, then Dow may do the trick as well.


Indentation and modularity.

REXX allows structured and modular programming.  Why not using that ? The editing on screen can also be structured by using indentation.  This is most useful when you have to match the end statements with the beginning of the do or select blocks.  If everything is coded aligned at the left margin, then finding the matching end of a block is rather cumbersome.

So, indent each underlying coding block by 1 to 3 positions.

If a great number of sub-blocks are needed, indentation with 3 characters can make you end up at the right margin.  In this case, your coding is probably too complex and you'd better use calls to subroutines.

In order to limit our building blocks as much as possible to a single screen size, we personally have opted for a bit more condensed style.  In the next few paragraphs, we give you examples of these.

DO blocks.

This is a first simple case:

  do i=1 to 5
     j=j+1
  end /* i */

If the block is large, indicating the comment in the end statement can be useful when matching it with the start of the block.  It is also allowed to use the DO repetitor as parameter of the END, like here :

  do i=1 to 5
     j=j+1
  end i

Remember that XEDIT provides the very powerful ALL macro.  If you use the command (might assign this to a PF-key when editing a REXX procedure) :

     ALL /DO / | /END / | /SELECT /

you will see only those statements containing one of these three words on your screen (use set shadow off to suppress shadow lines).  Verifying matching blocks becomes very easy now.  If you want to see the whole procedure again, just enter the command ALL without parameters.

The goodies contain a highly enhanced version of the ALL command.  If you have the time, you should have a look at its possibilities (HELP ALL). 

IF blocks.

  1. Simple case.

      if i>0 then j=7
    

    This is an IF statement without ELSE clause nor DO block.  We have seen people promoting the rule that an ELSE clause is mandatory, even if it results in a nop statement.  We find this a non-sense.

  2. With ELSE clause.

      if i>0 then j=7
             else j=3
    

    Aligning THEN and ELSE clauses makes a program more readable.  Note also that the ELSE clause is a separated clause, which means that if you want to code it on the same line in the procedure, you would need to separate it from the THEN clause by a semicolon (statement separator).

  3. With DO blocks.

      if i>0 then do              |  if i>0 then do
         j=7                      |     j=7
         a='Hello'                |     a='Hello'
      end                         |  end;   else do
      else do                     |     j=3
         j=3                      |     a='Goodbye'
         a='Goodbye'              |  end  /*else*/
      end /*else*/                |
    

    In this case we prefer to code the END clauses under the IF statement instead of under the DO statement as then we can better see the blocks.  You could read it as do this if i>0 and do that in other case.  It is just a matter of a little mind switch, one that you can learn quickly.

    The right part of the figure shows that you can also win a line by coding the END of the THEN block at the same line as the beginning of the ELSE clause, but don't forget the semicolon to make a separate statement of it.

    Compare readability of previous example with the following one :

      if i>0 then do
         j=7
         a='Hello'
                  end
             else do
         j=3
         a='Goodbye'
                  end
    

    or even the classic C-programmers coding style...

      if i>0 then
         do
            j=7
            a='Hello'
         end
             else
         do
            j=3
            a='Goodbye'
         end
    

    30% more lines needed for just this trivial IF clause.

SELECT blocks.

SELECT statements need an END clause to terminate the block.  It is a common error to forget it and debugging can be cumbersome.  A good practice is to code the select and accompanying end at the same time and then to add some records in between to include the logic.

A SELECT block is scanned only once, so that, as soon as one of the selected conditions is true, the related statements are executed and the rest of the selections are skipped.  This is really better performing than coding several IF statements.  It is also much more readable, so try to use SELECTs whenever applicable.  Coding the most frequent condition as the first WHEN only slightly impacts the performance.

    select
      when i<0 then say 'Too low'
      when i=0 then do
           say 'You win'
           exit 0
      end
      when i>0 then say 'Too high'
    end /*select*/

This piece of code could be part of a little game where you have to guess for a number.  There is no OTHERWISE clause as at least one of the three tested conditions will be true.  If however, the WHEN conditions do NOT cover all cases, an OTHERWISE is needed ! Neglecting to code it will make your procedure go in abend.

Rule
A SELECT block needs at least one true condition ! 
The REXX Compiler that is available on the mainframe operating systems even requires the OTHERWISE clause.  A compiler can indeed not know beforehand if all conditions are covered by the WHEN clauses.

Again we prefer to code the END under the WHEN and not under the DO, hence we consider is to be a do...when block in analogy to our IF constructs, but that's just a matter of taste.

Next example uses an OTHERWISE clause.

 select
    when i<0 then do
         say 'Guess too low'
         say 'Try with a higher number'
    end
    when i=0 then do
         say 'Congratulations, you found it in 'step' steps !'
         exit 0
    end
    otherwise say 'Guess too high'
              say 'Try with a lower number'
 end /*select*/

Remark that the OTHERWISE implicitly defines a DO-block and that no END is necessary (in fact the END of the SELECT serves also as END of the OTHERWISE).

Now compare the SELECT solution with the following equivalent IF sequences :

 if i<0 then say 'Guess too low'
        else if i=0 then do
                    say 'You won !'
                    exit 0
            end;    else say 'Guess too high'

Readability and performance have suffered !

One could say that the next coding would also do the job :

 if i<0 then say 'Guess too low'
 if i=0 then do;say 'You won !'
                exit 0
 end
 if i>0 then say 'Guess too high'

That's true, but problems arise when the THEN statements of one of the IF blocks modify the value of the variable i ! Look at following piece of code :

  if i<0 then say 'Guess too low'
  if i=0 then do;say 'You won !'
                 answ=''
                 do while answ=''
                    say 'Do you want to play again (Y/N) ?'
                    parse upper pull 1 answ 2 .
                 end
                 if answ='Y' then i=1    /*reinitialize i*/
                             else exit 0
  end
  if i>0 then say 'Guess too high'

You should verify that the second condition would make the third one true if the player answers Yes to the question.  In that case, the message Guess too high would also appear on the screen...

In a SELECT construct, REXX will execute only one true condition and then skip to the END, so that it definitely is an advantage to use the SELECT construct, while not harming readability.


Line continuation.

Although REXX procedures can contain statements with a record length of up to 64K characters, editing becomes cumbersome as soon as the statements are longer than the width of the screen area (hence 73 characters as there are 2 attribute characters and the prefix area).

It is possible to split statements on several lines, like this :

 if length(input_line)>80 & substr(input_line,7,2)='AB'
    then do ...

There is no harm in separating the THEN clause from the IF.  Even this is possible :

 if length(input_line)>80 & ,
    (substr(input_line,7,2)='AB' | ,
    substr(input_line,7,2)='BA')
    then do ...

The comma is the continuation character.

This was an example of splitting statements, but it is not a very good coding example as in this case, the substr() function needs to be executed twice for evaluating the IF condition.  It would therefore be better for performance, to put the result of the substr() function in a variable, and to test this variable. 

Here's another example :

 say 'The filename is' left(fn,8) 'while the filetype is',
      left(ft,8) '!'

The SAY statement is continued on the second line.  The result on the screen could be something like this :

  The filename is PROFILE  while the filetype is XEDIT    !

Remark that indentation has no effect on the number of spaces in the output string.  The continuation character will be replaced by one and just one space character and be followed by the first not-blank character of the next line.  Indentation again serves readability as one can see immediately that the say instruction is continued.

A comma within a string constant will of course not have the effect of continuation, thus the following piece of code would not have the expected result :

 say 'The filename is' left(fn,8) 'while the filetype is,'
      left(ft,8) '!'

The comma is within a string constant and is not treated as a continuation character.  The line with left(ft,8) is a new statement for the procedure and will probably result in an execution error (the filetype gets executed by CMS... !).

To continue a clause without intervening blank character, use the concatenation character, such as

 say 'The filename is' left(fn,8) 'while the file'||,
     'type is' left(ft,8) '!'


Line/string composition.

When coding long strings, remember the REXX rules : concatenation can be done by :

The following illustrates the basic rules :

Concatenation examples

 type='BALL'
 stuff='MEAT'
 something='MEAT'type     /* --> "MEATBALL"  this is abuttal      */
 samething='MEAT'||type   /* --> "MEATBALL"  REXX concat operator */
 different='MEAT' type    /* --> "MEAT BALL" 2 tokens             */
 different='MEAT'   type  /* --> "MEAT BALL" only 1 blank kept    */
 samething='MEAT'||'BALL' /* --> "MEATBALL"  concatenated         */
 different='MEAT''BALL'   /* --> "MEAT'BALL" 2 quotes result in 1 */
 something=stuff||type    /* --> "MEATBALL"  concatenated vars    */

We don't like to code much || orders, we use abuttal whenever possible.  The concatenation operator is needed only when :


Literals versus variables.

We come to another very important coding rule here :

Rule

Code everything that is NOT variable as a literal string !

Let's explore this a little bit.  Each word in a statement is considered to be a variable or REXX instruction except if it is coded enclosed by single or double quotation marks (quotes) ('...' or "..."), in which case it is called a literal string.

The value of a literal string is of course exactly what's in between the quotes. 
The value of a variable is either:

  1. the current value assigned to the variable
  2. or its own name, translated to uppercase, if it was never initialized.

Now, let's have a look at next example :

  /* sample procedure */
  a=17
  ...
  copyfile profile xedit a oldprof xedit a

The result of REXXs' interpretation will be :

  COPYFILE PROFILE XEDIT 17 OLDPROF XEDIT 17

This will be passed to CMS as it is not recognized as a REXX statement, and CMS will of course have problems.

What happens is that the parameters a in the copyfile statement are recognized as a variable whose value was already set to 17.  The other words in the statement are also treated as variables because they are not coded as a literal, but as they never were initialized, their value is their own uppercased name.

So, let's correct the previous procedure :

  /* sample procedure */
  a=17
  ...
  copyfile profile xedit 'a' oldprof xedit 'a'

Now the parameters a become literals (constants) and are not changed during interpretation.  So, the result will be :

    COPYFILE PROFILE XEDIT a OLDPROF XEDIT a

This will work..., unless :

  1. An address command is coded prior to this statement (Chapter 3 will cover the details of this).  In this case, the lowercase characters are no longer translated to uppercase by CMS, and COPYFILE doesn't accept lowercase filemodes.

  2. You modify your procedure some days later, create a variable profile, and initialize it to some value.  Your procedure will again give problems.

So, we repeat : Code everything that is NOT variable as a literal string to make your procedure foolproof !  (You could enforce this rule by the use of signal on novalue as we will learn in a later lesson).


Good Use of DO Loops

The DO statement can be used in several ways (forever, while, until, ...), but typical is that this can lead to an endless loop.  Avoiding endless loops remains the responsibility of the programmer, but we will give you some hints and techniques to help you coding better DO loops.

Straightforward Programming.

DO WHILE and DO UNTIL are interesting because these allow leaving the loop conditionally. We probably teach you nothing when saying that the difference between both is :

Both ways can be used to code procedures that can be read like a novel, from top to bottom.  We call this straight forward programming, isn't it ?  The main advantage is that when you have to analyze the procedure, your mind doesn't have to jump mentally each time your program logic jumps.

A typical example follows here :

  answ=''      /* initialize answ to null string */
  do until answ='Y' | answ='N'
     say 'Do you want to play another game (Y/N) ?'
     parse upper pull 1 answ 2 .
  end
  if answ='Y' then do ....

You can use this sample when you have to accept a simple user response from the terminal.  Here, the parse instruction only accepts the first character and immediately translates it to uppercase.

Do you know what will happen if the user enters one or more blanks before the YES ?  If not, code this simple procedure, add a trace ?i before the do until and run it.

The LEAVE Statement.

WHILE or UNTIL let you terminate (leave) the loop when the condition is true.  Sometimes, you may however have several points in the loop where you want to leave it. 

The LEAVE statement can be used in that case.  It will branch to the instruction immediately following the END of the loop.

In the case where you have nested loops, the LEAVE will terminate the loop at the current level and all its lower levels.  If an inner loop has to terminate an outer loop, you can add a parameter to the LEAVE statement that refers to the iteration variable used in the outer loop.  For example:

  do i=1 to 10
     do j=1 to 20
        do k=1 to 30
           if ... then leave j
        end
     end
     say '....'
  end

The leave j will branch to the say instruction, effectively terminating both the k- and the j-loop.  If the i-loop is not finished yet, it will of course be iterated again.

The END Statement.

As you can see from previous example, matching do and end statements is never easy.

When your DO loop uses a variable (like do i=...), then you can code end i.  Otherwise, it's good practice to add a comment to the end statement (especially if your loop doesn't hold on one screen).  This is an example:

  do i=1 to 10
     do forever
        do k=1 to 30
           if ... then leave i
        end k
     end /* do forever */
     say '....'
  end i

The ITERATE Statement.

Just as the LEAVE statement can let you exit from the loop, the ITERATE statement can let you branch directly to the end statement of the loop, and thus start a new iteration  of the loop.  For example :

  do i=1 to 10
     if (i//3)=0 then iterate
     k=k+1
  end

Here, the variable k will not increment if the loop variable i is a multiple of 3.


Exits.

We want to spend one final topic on generalized exit routines.  The purpose is to have only one single point where the procedure terminates and returns to the caller.

Let's start with an often used but less elegant programming style :

 /* our REXX exec : CASE 1 */
  ...
  if SomeThing=wrong then do
     Say 'Sorry' SomeThing 'is wrong'
     exit 1
  end
  ...
  if SomeThingElse=NotWhatWeWant then do
     Say 'Sorry' SomeThingElse 'is wrong, we stop'
     exit 1
  end
  ...

Many statements are needed each time an error condition is detected that must terminate the procedure.  In this case, it is still simple, but you may have to add statements that close files, detach devices, clean log files, drop lines from the stack, etc.

It is therefore better practice to call a common exit routine.  Let's optimize our previous example a bit :

 /* our REXX exec : CASE 2 */
  ...
  if something=wrong then do
     emsg='Sorry' something 'is wrong'
     retc=1
     signal errexit
  end
  ...
  if somethingelse=notwhatwewant then do
     emsg='Sorry' somethingelse 'is wrong, we stop'
     retc=1
     signal errexit
  end
  ...
 ERREXIT: /* exit with errormessage */
  say emsg
 exit retc

We can improve even more, as follows:

 /* our REXX exec : CASE 3 */
 ...
 if SomeThing=wrong then call errexit 1,'Sorry' SomeThing 'is wrong'
 ...
 if SomeThingElse=NotWhatWeWant then call errexit 1 , 'Sorry',
    SomeThingElse 'is wrong, we stop'
 ...
 ERREXIT: /* exit with errormessage */
  parse arg erc , emsg                            /* get rc & errormsg */
  parse source . . myname mytype . mysyn .        /* what's our name ? */
  say myname':' emsg                              /* tell what's wrong */
 exit erc

What we specially like in last case is that :

We do not use the label error: for our error exit routine, as this label is reserved for use with the signal on error instruction.

We can still go one step further.  Sometimes the program may need to display more than one error message.  This is easy to solve, as you can see in the example below:

 /* our rexx exec: CASE 4 ----*/
 ...
 if thing=wrong then call errexit 1,'Sorry,' thing 'is wrong',,
    'We stop for now' , 'Enter HELP MYEXEC before retrying.'
 ...
 ERREXIT:                                 /* exit with errormessage(s) */
  parse source . . myname mytype . mysyn .             /* who are we ? */
  do n=2 to arg()                     /* get all errormessages to show */
     say myname':' arg(n)                     /* give the n'th message */
  end n
 exit arg(1)

The output on the screen may become something like this :

  CASE4: Sorry, QzRLBm is wrong
  CASE4: We stop for now
  CASE4: Enter HELP MYEXEC before retrying.
  Ready(0001);

The above techniques are based on the fact that REXX allows passing up to 20 parameters to subroutines or functions, by separating them with commas.

To terminate this discussion, we'll leave you one last, more complete example to study :
Error exits, extra security feature.

 /* our REXX exec: CASE 4 */
 ....
 if something=wrong then call errexit 1 ,,
    'Sorry' something 'is wrong',,
    'We stop for now',,
    'Enter HELP MYEXEC before retrying'
 ....
 'MAKEBUF'                              /* make ourself a STACK buffer */
 'Q DISK R/W (STACK'
 if rc¬=0 then call errexit rc 'DROP' ,,
    'You have no READ/WRITE disk',,
    'We can''t work'
 parse pull . /* skip header */
 parse pull label .
 Say 'Your first R/W disk is:' label
 'DROPBUF' /* quit our STACK buffer */
 ....
 call exit 0
 /***************************** subroutine *****************************/
 EXIT:
 ERREXIT:                                 /* exit with errormessage(s) */
  parse source . . myname mytype . mysyn .          /* what's our name */
  parse arg erc drop .             /* get some info out or parameter 1 */
  if drop='DROP' then 'DROPBUF'           /* perform DROPBUF if wanted */
  do n=2 to arg()                 /* get all the errormessages to show */
     say myname':' arg(n)                     /* give the n'th message */
  end n
 exit erc

Remark that the EXIT: and ERROR: routines actually result in the same coding, which is perfectly valid for REXX.

This concludes our lecture on coding styles.  Chapter 3 is of utmost importance, so take an appropriate amount of time to study this text.


Footnotes:

  The character used for concatenation (and for logical or) must have the value x'4F'.  On our host terminals, this is represented by an exclamation sign and not by a vertical bar, due to national language translations. On however, the vertical bar must be used.
Back to text