Monday, October 31, 2011

The Magic of TRANSLATE

TRANSLATE is typically used to clear unwanted characters from a string or to change a set of characters to some other set.  The syntactical description is that in

x = Translate( a , b , c )
each character in 'a' is searched for in 'c', and if found the corresponding character from 'b' replaces the character in 'a' on output.  Any characters in 'a' which are not found in 'c' are copied as-is to the output.  The default values for 'b' and 'c' are the uppercase alphabet and the lowercase alphabet, respectively, so that TRANSLATE, by default, will shift any string into uppercase.  This behavior can be harnessed for other purposes.

Among other things, TRANSLATE can rearrange the characters of one string to form a completely new and different arrangement.  (The technique shown here also works in PL/I where the syntax of the TRANSLATE function is exactly identical to REXX syntax.)  What's happening is often not immediately clear, but the code can be written to make it clear:

changed = Translate("CcYy/Mm/Dd" , chgdate , "CcYyMmDd")
The code surrounding this will make it fairly obvious that, going into the conversion, 'chgdate' has the form of a REXX date("S").  It becomes pretty easy to guess that the output form will have slashes inserted in the appropriate spots.  In effect,
CcYy/Mm/Dd  <--  CcYyMmDd
If that's not the format you need on output, a simple change provides whatever you want:
changed = Translate("Mm/Dd/Yy" , chgdate , "CcYyMmDd")
/*  Mm/Dd/Yy  <--  CcYyMmDd  */
comp_time = Translate("Hh:Mm:Ss" , comp_time , "HhMmSs")
/*  Hh:Mm:Ss  <--  HhMmSs   */

Just remember that when using TRANSLATE this way, the 'c' string describes the starting position of the 'b' string, and the 'a' string describes the desired result.  Naturally, the 'c' string may not have any duplicate characters.  While you can use any characters you want for 'c', it makes a lot of sense to use characters which give the reader/maintainer some clue as to what's intended.

Sunday, October 30, 2011

Manipulating SYSEXEC

SYSEXEC is a funny bird.  In most MVS-like environments, when SYSEXEC is opened, it stays open until something closes it.  Since it's OPEN, it can't be FREEd, therefore you cannot concatenate an additional dataset to it.  There is, of course, ALTLIB, but if the routine doing the ALTLIB is not already in the concatenation somewhere, calling it might be problematical.

So, here's the problem:  you have to hook another dataset into SYSEXEC and the system is not cooperating.  What now, my love?  (Hey, wouldn't that make a great title for a song?)

Let us look at how SYSEXEC got rigged to OPEN and stay open.  When it was first ALLOCated, it was marked (by EXECUTIL) like this:

"EXECUTIL EXECDD(NOCLOSE) SEARCHDD(YES)"
and that has to be thwarted.  Not a problem.
"EXECUTIL EXECDD(CLOSE)"
and follow it with a call to any routine in SYSEXEC.  As soon as the called function completes, SYSEXEC will close.  (If you just want SYSEXEC closed and you don't really want to do anything, it would be good if SYSEXEC had an IEFBR14-equivalent, a little NOOP program that starts and ends in the same gasp.  It can't, by the way, be in an ALTLIB'd dataset searched ahead of SYSEXEC.  It must be in the real SYSEXEC allocation.)

This is what ATTACH does (see my REXX page for the gory details).  Once SYSEXEC is closed, you can reallocate it as you need it.  Don't forget to

"EXECUTIL EXECDD(NOCLOSE) SEARCHDD(YES)"
when you're done ;-)

Saturday, October 29, 2011

Rapid Initialization

If you, like me, always write your REXX code beginning with a "signal on novalue" (to prevent the use of uninitialized variables), then you (like me) always want to make sure that all your variables are properly initialized.  Here's a fast way to initialize a whole load of variables WHAM!:

   parse value "0 0 0 0 0 0 0"    with ,
               rpt#   rpt. ,
               .
   parse value ""    with ,
               slist   ltoken  stoken  loadlist   ,
               tag   taglist   ,
               .
   parse value 0   "ISR00000  YES       Error-Press PF1"    with,
               sw.  zerrhm    zerralrm  zerrsm

The first PARSE uses a string of several (many, a whole lot of) zeroes because it's concerned with zeroing-out several counters, among which are 'rpt#' and 'rpt.'.  Note the '.' on the last line to flush any unused zeroes.

Any number of counters can be zeroed in a single PARSE by just adding their names to the list.  If more zeroes are needed in the value-pattern, they are easy enough to add because you never have to count.  Need a few more?  Add another twenty or thirty!

The second PARSE is concerned with variables that need to be blanked-out.  Here, you never have to worry about whether there are enough blanks.  To add another variable-to-be-blanked, just add it to the existing list.

The third PARSE initializes a collection of variables to several different values.  'sw.' is set to '0';  'zerrhm' is set to 'ISR00000';  'zerrsm' is set to 'Error-Press PF1';  &cetera.  PARSEs of this type need to be carefully constructed.

Rather than have each variable assigned its value on a separate line, this technique clusters many assignments together in a compact form that does not distract from reading the code for comprehension, it is at least as fast, execution-wise, as doing them one-by-one, and it's a heckuvalot easier to type.

Thursday, October 27, 2011

Logging

As any program (and REXX is no exception) lumbers along, it's often a good idea to keep track of what's happening, so that if something goes south later, you'll have some idea of where that happened.  A log-file is a good way to cover that.  Ideally, you want to have a separate file for each execution of the program so that if multiple users are banging away, each one has their activity logged to a unique spot.  That implies the log files will have different names, probably with a timestamp.  Here's the technique I use with great success:

   parse value Date("S")  Time("S")  Time("N")  with,
               yyyymmdd   sssss      hhmmss  .
   parse var yyyymmdd  4 yrdigit 5 mm 7 dd          /* 9 12 14 maybe */
   if Pos(yrdigit,"13579") > 0 then mm = mm + 12       /* mm=24      */
   logtag = Substr("ABCDEFGHIJKLMNOPQRSTUVWX",mm,1)    /* logtag=X   */
   subid  = logtag""dd""Right(sssss,5,0)               /* X1423722 ? */

The initial "parse" snags coherent date-and-time information.  Doing this on a single statement guarantees that the time will be proper for the date.  The second "parse" peels off the one-digit year ( '3' in 2013 ) so that we can determine whether it is odd ( if Pos(yrdigit,"13579") > 0 ).  If it is, we add 12 to the month value ( mm = mm + 12 ).  Next, we grab the mmth value from the string of the first 24 letters of the alphabet.  We are now ready to form the unique part of the log file's dataset name:

   logdsn = "LOG."exec_name"."subid".LIST"

Put the exec's name as part of the log file's name.  If a routine calls a subroutine which also generates a log file, you don't want to cause a conflict.

Now all you have to do is allocate the dataset with a proper DCB and start filling it with progress notes.

Getting Started

It's been a year and a half since I lost access to a mainframe and my ability to write a new REXX program for MVS, but I can still pass along tips, tricks, and techniques I learned in the quarter-century I spent writing REXX, and there are lots of those, let me tell you.

I'll be back after the jump with one you can put to use almost immediately.