Chapter 19. Use of SIGNAL ON

REXX has a SIGNAL ON event  command that allows you to change the flow of your procedure when a specific event happens.

Using SIGNAL ON can be useful in following conditions:

  1. You want your procedure to nicely clean-up, even if it abends due to a syntax error.  In that case, a SIGNAL ON SYNTAX will help.
  2. You want to test the return codes of several commands without duplicating the test code.  This code, for example,
       'EXECIO 1 DISKW MY FILE A (STRING test1'
         if rc^=0 then ....
       'EXECIO 1 DISKW MY FILE A (STRING test2'
         if rc^=0 then ....
       'EXECIO 1 DISKW MY FILE A (STRING test3'
         if rc^=0 then ....
       ...
    

    can be simplified by using a SIGNAL ON ERROR:

         Signal on error
         'EXECIO 1 DISKW MY FILE A (STRING test1'
         'EXECIO 1 DISKW MY FILE A (STRING test2'
         'EXECIO 1 DISKW MY FILE A (STRING test3'
         ...
         Signal off error
    

    Note however that for some commands, it is normal to get return codes other than zero (e.g. MAKEBUF, SENTRIES).  In that case you have temporarily to suppress the error trapping via SIGNAL OFF ERROR.

  3. You want to trap variables that are not properly initialized.  A SIGNAL ON NOVALUE will trap this condition.  It may also detect misspelled variable names in your procedure.  It is quite handy for larger procedures.
  4. You use stream I/O functions, and want to trap conditions where you can not proceed due to an error condition (e.g. reading beyond the end of a file, printer not available or ready).  A SIGNAL ON NOTREADY can trap these error conditions.
  5. You want to keep full control over the virtual machine and forbid the user to break out the procedure with an HI immediate command (or a Ctrl-Break on a Personal System).  Then, use a SIGNAL ON HALT.
Except for the SYNTAX condition, REXX provides also the CALL ON event.  Unlike the SIGNAL, a CALL allows you to return to the mainline of the procedure.

Let's study some examples.  Here is one where we intercept a syntax error:

  /* This procedure uses SIGNAL ON SYNTAX to trap syntax errors */
  address command
  signal on syntax
  a=''
  a=a+3
  exit
  /*******************************************************************/
  SYNTAX: /*  we come here when SIGNAL ON SYNTAX traps an error      */
  /*******************************************************************/
    parse upper source . how myname mytype . syn .
    call errexit rc,'REXX problem in' myname mytype 'line' sigl':' ,
         'ERRORTEXT'(rc), sigl':'!!'SOURCELINE'(sigl)
  /*******************************************************************/
  ERREXIT: /*  exit with retcode & errormsg                          */
  /*******************************************************************/
    do i=2 to arg()
       say arg(i)
    end
    exit

As we included some syntax errors intentionally, this is what will be displayed on our terminal when we execute this procedure:

   REXX problem in SGN EXEC line 5: Bad arithmetic conversion
   5:  a=a+3

Your SYNTAX: routine is better placed on top of your procedure, otherwise, it may not be found when quotes, comments or parentheses do not match somewhere in your procedure.

The sourceline() and errortext() functions, as well as the sigl special REXX variable are usefull here too. 
sigl is a special REXX variable that contains the statement number from which a branch was taken.
In our example, it returns 5 (the fifth line of the procedure contains the error).
sourceline() if no parameter is specified, it returns the number of lines in the program, otherwise, it returns the n-th line number.  If specified, n must be a positive whole number and must not exceed the number of lines in the program.  In our example, it returns the 5th statement as sigl has the value 5.
errortext() returns the REXX error message associated with error number n.  The n must be in the range 0-99, and any other value is an error.  Returns the null string if n is in the allowed range but is not a defined REXX error number.  In our example, it returns Bad arithmetic conversion.

Note: SIGNAL instructions are local to the subroutines.  This means neat things become possible, such as letting your procedure continue even if syntax errors occurred.  To illustrate this, look at this simple procedure:

   /* Procedure to calculate expressions */
   do forever
      say 'Please enter an expression to be calculated.   E.g. 1+1'
      say 'or null line to terminate.'
      parse linein express
      if express='' then exit
      call evaluate express
   end
   EVALUATE:
     signal on syntax
     interpret 'data='arg(1)
     say arg(1) '=' data
     return
   SYNTAX:
     say 'Ooops, REXX doesn''t understand' arg(1) ',retry'
     return

When the user enters for example 1+z, REXX will signal a syntax error when the interpret is executed.  The SYNTAX: subroutine gets control, and after displaying the error message, the return will go back to our do forever in the mainline.

Next example traps error return codes:

  /* This procedure uses SIGNAL ON ERROR to trap RC¬=0 */
  /* We do also a good clean-up.                       */
  address command
  parse upper source . how myname mytype . syn .
  'MAKEBUF' ; buffer=rc
  'LISTFILE * * A (STACK'
  signal on error
  'EXECIO 1 diskw' myname 'LOG (STRING' myname mytype 'test'
  exit
  /*******************************************************************/
  ERROR: /*  we come here when SIGNAL ON ERROR traps an error        */
  /*******************************************************************/
    call ERREXIT rc, 'Retcode' rc 'from' condition('d')
  /*******************************************************************/
  ERREXIT: /*  exit with retcode & errormsg                          */
  /*******************************************************************/
    if symbol('BUFFER')='VAR' then            /* if we made a buffer */
       'DROPBUF' buffer            /* drop ALL buffers our exec made */
    do i=2 to arg()
       say arg(i)
    end
    exit

Execution results in:

   DMSEIO621E Bad Plist: Invalid DEVICE argument (diskw)
   Retcode 24 from EXECIO 1 diskw SGN LOG (STRING SGN EXEC test

The function condition() is another useful REXX function: in the SIGNAL ON ERROR case, it returns the command causing the error.  For more information, issue the HELP REXX CONDITION

Next example traps variables that are not initialized:

  /* This procedure uses SIGNAL ON NOVALUE to trap uninitialized vars */
  address command
  signal on novalue name undefined
  'QUERY DISK A (LIFO'
  parse pull . . . . . . . . blk_left .
  parse pull .    /* header */
  ok=(blkleft-3)>0
  ....
  exit
  /*******************************************************************/
  UNDEFINED:  /* we come here when REXX finds unitialised variable   */
  /*******************************************************************/
    parse upper source . how myname mytype . syn .
    call errexit 99,'REXX problem in' myname mytype 'line' sigl,
         'variable "'condition('D')'" not defined'
  /*******************************************************************/
  ERREXIT: /*  exit with retcode & errormsg                          */
  /*******************************************************************/
    call diag 8,'SET EMSG' emsgsave
    do i=2 to arg()
       say arg(i)
    end
    exit

Without the novalue trap, we would get:

       9 +++ ok=(blkleft-3)>0
   DMSREX476E Error 41 running SGN EXEC, line 9: Bad arithmetic conversion
   Ready(20041);

as blkleft is misspelled.  With our subroutine, we get:

   REXX problem in SGN EXEC line 10 variable "BLKLEFT" not defined

Agree that the second message is by far more explicit than the first one.

You may be reluctant to use the signal on novalue, as it really forces you to code literals between quotes and your procedures will probably abend more if you don't apply this rule strictly.  But, as you see, you get a clear and concise error message for every misspelled variable, and you don't have to guess or trace to find out why your procedure didn't work as you expected.  If you would have coded if blkleft>3, then you would not get an errormessage at all, but the condition would always be false, even if there are really more than 3 blocks left.  Indeed, the character string BLKLEFT is the value of the uninitialized variable, and this is not larger than the character 3 (in the EBCDIC collating sequence, that is).

Note that in this example, we added the parameter 'name undefined' to the signal instruction to change the default name of the subroutine.  We also show that the exit routine is able to restore the user environment (SET EMSG) when it exits, even in case of an abend .

Chapter 20 is optional and covers CMS Multitasking techniques.

Chapter 21 gives details on different options to augment the performance of procedures, such as loading the procedures in storage and using the REXX Compiler.