Showing posts with label Tricks. Show all posts
Showing posts with label Tricks. Show all posts

Monday, May 7, 2012

Scrolling a Panel Left and Right

Back on November 3rd, I wrote about "cylindrical arrays" and said "ISPF facilities do not allow the shifting of panels right and left except for some very special IBM-originated panels", but Chris Lewis introduced me to a way to simulate it.  Herewith.

The first thing is to rig the panel so that it can have any column headings you desire and fit it with a variable )MODEL.  The "desc" following is the last line of the )BODY and is where the column headings would be stored.  It is followed by a )MODEL section where the actual model is a variable:

@desc
)MODEL ROWS(SCAN)
&modl

Before the panel can be TBDISPL'd, values for those must be set up...

   /* Set up values for the variable-format panel(s)                 */
   desc1   = "V -Member- Type  A/R Base  -Userid-  ---Date--- Time   Li",
             || "btype   Cleared"
   desc2   = "V -Member- Type  A/R Base  -Userid-  -Username----      Cl",
             || "eared  As"
   modl1   = "_z!mlroot  +!z   !mlbase   !mluid    !mldate    !mltime!ml",
             || "loc    !mlclrdt "
   modl2   = "_z!mlroot  +!z   !mlbase   !mluid    !mlsname           !m",
             || "lclrdt !mlclrnm "

...and loaded to the variables named on the screen:

      modl = Value('modl'style)
      desc = Value('desc'style)

Now, whenever the response from the panel is a PF10 or PF11, call the code that inches the "cylindrical array" left or right:

   select
 
      when pfkey = "PF11" then,
         style = style//stylect + 1    /* next style                 */
 
      when pfkey = "PF10" then,
         style = (style+stylect-2)//stylect + 1       /* prev style  */
 
      otherwise nop
   end

That's all there is to it.  Once STYLE is set based on the PF10 or PF11, that style-number is used to select DESC1/MODL1 or DESC2/MODL2 just before the panel is redisplayed.  It's the same panel (really), but the content displayed there will be different enough that it will appear the panel has actually been shifted.

Saturday, November 5, 2011

Masking and Matching

When you have to find things in a list that match things in another list, that's fairly easy.  You simply spin through one list (the shorter one) and for each element there, you see if "WordPos" delivers a match in the other list.  WORDPOS' processing is probably faster by orders of magnitude than anything you can code in REXX using the Bigger Hammer method.

What do you do when you have to find elements in one list that are merely like elements in another list?  Say you need to find all the membernames in a PDS that look like 'BRT**4'.  That's a different problem.  Yes, you could just go get a bigger hammer, or you can reach for the jeweler's screwdrivers.

REXX has two built-in functions, BITOR and BITAND, that can double-team that problem.  BITAND operates on two strings by ANDing them at the bit-level.  BITOR operates on two strings by ORing them at the bit-level.

BITAND returns a 1-bit in every bit position for which both strings have a 1-bit:  BITAND( '73'x , '27'x ) yields '23'x.  '0111 0011' & '0010 0111' = '0010 0011'.

BITOR returns a 1-bit in every bit position for which either string has a 1-bit:  BITOR( '15'x , '24'x ) yields '35'x.  '0001 0101' | '0010 0100' = '0011 0101'.

If there are characters for which it doesn't matter whether they match, those characters can be BITANDed with 'FF'x and BITORed with '00'x.  The result in each case is that the "I don't care" characters are returned intact by the BITAND/BITOR.  For the other characters, the ones for which it does matter whether they match, only an exact match will deliver the proper bit-pattern when they are both BITANDed and BITORed.  Like this:

      memmask = Strip(memmask,"T","*")
      maskl   = Length(memmask)
      lomask  = Translate(memmask, '00'x , "*")
      himask  = Translate(memmask, 'FF'x , "*")
      do Words(mbrlist)                /* each membername            */
         parse var mbrlist mbr mbrlist
         if BitAnd(himask,Left(mbr,maskl)) = ,
             BitOr(lomask,Left(mbr,maskl)) then,
            mbrlist = mbrlist mbr
      end                              /* words                      */

In this case, we have been given 'memmask' which may look like 'BRT**4'.  MASKL is '6'.  LOMASK gets '00'x characters in place of the asterisks.  HIMASK gets 'FF'x characters in place of the asterisks.  We spin through the list of membernames stored in MBRLIST.  For each word ("MBR") in that list, we BITAND it against the HIMASK and BITOR it against the LOMASK.  When the comparison is exactly equal, we have a match.  In this case, we merely attach the matched membername at the back of MBRLIST.  When we have iterated this loop "Words(mbrlist)" times (once for each original word), MBRLIST will contain only those membernames matching "BTR**4".

As with all 'tips and tricks', this is merely one way to 'skin the cat'.

Friday, November 4, 2011

Boolean Switch-setting

'Switches' are an important logic element in many languages.  They allow the programmer to quickly and easily direct the flow of logic.  The classic IF-THEN-ELSE is even represented on flowcharts by the diamond-shaped 'switch box'.  The result is either true or false, '1' or '0'.  In fact, that classic IF-THEN-ELSE is very often used to set switches for later use when the values may have been changed by subsequent processing.  Switches often get set like this:

if A = B then sw.0equal = 1
         else sw.0equal = 0
There is another way, I think it's a better way, and it certainly cuts down on the keystroking, but it's not immediately obvious to newbies what's happening:
sw.0equal = A = B
Got it?  When A and B are equal — when 'A = B' is TRUE — sw.0equal gets set to '1', TRUE.  sw.0equal takes on the truth-value of the proposition 'A = B'.

My old friend, Ramon Faulk, taught me that trick back in the 1970s.  You have to be writing in a terse language like REXX or PL/I to be able to use it.  Good thing we're working in REXX, huh? ;-)

Note also that the content-addressible array "sw." is here suffixed with the glyph "0equal".  "0equal" is invalid as a variable name, so no maintainer can come along behind you, use "equal" as a variable, and dynamically redefine the meaning of "sw.equal".  I learned that one from Les Koehler, the first American to write a REXX program.  Unless you intend that content-addressible array suffix to be changeable, start it with any character that will prevent its use as a variable.

Thursday, November 3, 2011

Cylindrical arrays

I use the term "cylindrical array" for any collection of things you have to process as "18, 19, 1, 2 ..." or "3, 2, 1, 22, 21, ...";  that is, when you get to either the upper end or the lower end, you continue processing at the other end as if the array were wrapped around a cylinder.  There aren't many cases in ordinary data processing where this technique is called for, but when it is, it's always an opportunity to "use a bigger hammer".  (Think of pounding a square peg into a round hole: if it doesn't fit, you get a bigger hammer.)

Luckily, it is dead-bang-easy to implement something like this, and you'll see it wherever (for example) an application calls for being able to shift a panel right or left.  Now, ISPF facilities do not allow the shifting of panels right and left except for some very special IBM-originated panels as are found in Browse and Edit.  A good friend, Chris Lewis, introduced me to a technique for simulating the effect so that it looks like the panel is shifting sideways.  The logic was a little bit "bigger-hammerish" so I smoothed it down.

You start with the knowledge of how many items there are in the list (the item-count).  To advance to the next item in the array (or list, if you prefer) you MOD the current item-number with the item-count and add one:

next_item = (item // item_count) + 1
To retreat to the prior item, sum the item-number and item-count and subtract two, then MOD the result with the item-count and add one:
previous_item = ( (item + item_count - 2) // item_count ) + 1

Here's how it breaks down for a list-of-five:

   To go to the next style:  (style//stylect) + 1:
          1   2   3   4   5
          1   2   3   4   0    (mod stylect)
          2   3   4   5   1    (add one)
   To go to the prior style:  (style+stylect-2)//stylect + 1
          1   2   3   4   5
          6   7   8   9  10    (+stylect)
          4   5   6   7   8    ( -2 )
          4   0   1   2   3    ( //stylect)
          5   1   2   3   4    ( +1 )
and this even works when the number of items is one.

At some time in the future, I'll blog about ISPF tables and table-displays and this topic will certainly come up again.  If you'd like to see a preview of how it's used, pop on over to my REXX page and take a look at PKGREQS.  It may not make much sense at first, but that's just because this blog isn't long enough... yet.

Wednesday, November 2, 2011

FB80 v. VB255, and what to do about it.

Very many installations have their SYSPROC and SYSEXEC libraries set up as FB/80 datasets, and I've seen too many with blocksizes of 800.  Why in the world would any sysprog do such a thing?  The answer is that that's the way IBM delivers SYSMODs, and it's easy to simply load them up whichever way IBM delivered them, never mind how much disk space such a thing eats.  You did remember that IBM was in the hardware business, didn't you?

So, here you are, a lowly application programmer, and your opinion as to how stupid it is to waste all that space doesn't carry an awful lot of weight.  If you want to get your CLIST or REXX code into a 'supported' dataset, it had better be amenable to living in a fixed-block-80-byte world.  Or does it?

What do you do with all those REXX execs that you wrote 124 characters wide because you happened to be using a model 5 terminal emulator?  Rework them so there aren't any lines longer than 80 bytes?  Not me.  I'm not putting all that effort into anything so unnecessary.  I load up a driver and put the real code into a VB/255 library where it belongs.  Interested?  Yeah, I'll bet you are...

/* REXX    Driver
*/ arg argline
address TSO
parse source . . exec_name .

"ALTLIB   ACT APPLICATION(EXEC)  DA('........') "
if rc > 4 then do
   say "ALTLIB failed, RC="rc
   exit
   end

(exec_name)  argline

"ALTLIB DEACT APPLICATION(EXEC)"

exit 

Here's how it works:  The 'arg' statement takes a snapshot of all the original parms for later use.  The 'parse source' identifies the name of this exec.  Issue an ALTLIB for the dataset that holds the real code, the VB/255 copy.  If you get a non-zero return, the ALTLIB didn't work for some reason.  If it did, you can now call that copy, and you do so with the original parms.  When it finishes, it will return right to this spot and a second ALTLIB releases the VB/255 dataset.

Whatever is the name of the working code in the VB/255 dataset, plant this driver in the 'supported' FB/80 dataset with the exact same member name.  Now, when someone invokes that routine, they get the driver, the driver snags the list of parms, ALTLIBs the dataset containing the working code into a preferential position, and re-issues the original call.  That re-issuance catches the working code, passes the parms to it, it executes (or not, as the case may be), and finally returns to the driver for clean-up.

There's another secondary benefit to this, too.  That 'supported' dataset doesn't hold anything except a 'stub' pointing to the real code.  If your sysprogs keep it locked, and your real code is in that locked library, fixing it in an expeditious manner may not be very easy.  Remember, they're not being measured on how fast you shoot your bugs.  If what's locked up is just a pointer to the real code... well, you don't want to fix the stub, do you?

For environments that have multiple levels — development, testing, production, and possibly others — this technique is easily adjusted to locate the proper environment and execute from it, excluding the others.  In that case, called subroutines will be located from the same environment, if they exist there, so that all processes use elements from the same stage of the software development life cycle.  Neat, huh?

Tuesday, November 1, 2011

The Zenith of Tracing

Zenith used to make TVs, and they may still; I haven't shopped for a TV in years.  Back in The Good Old Days (tm), Zenith had an advertising slogan:  "The quality is built in, not bolted on".  The implication, for those who could read between the lines, is that quality can't be either forgotten at the factory or removed at a later date.  I have the same attitude when it comes to tracing and debugging.

How many times have you seen this in a REXX program:

/* REXX   --  a program to do some stuff.   */
/*  Trace("R")   */
How many times have you written something like that?

This is an invitation to maintainers to de-comment that line when they need to trace the flow of the program.  This is "bolting quality on", not "building it in", and there are a host of problems that it generates.

First, de-commenting that line constitutes "changing the program".  Yes, it's a small change and (I admit) unlikely to have any noticeable effects beyond the fact that the program now runs in trace-mode.  The (philosophical) point here is that you are not really running the program that failed, you're running a program that was changed after it failed.

Then there are other, less philosophical and much more serious, problems:  are you changing the production copy or something else?  If you're changing the production copy, will everyone running this "in production" suddenly find themselves accidentally (and surprisingly) producing reams of output?  If it's not the production copy, are you sure it was an exact duplicate of the version that failed?  Where is your test copy stored?  Is the concatenation sequence the same for your test copy as for the failing version?  Are you sure?  What about called subroutines?  Will your version pick up true copies of all necessary subroutines?  After all, the failure might have been in a subroutine and caused by bad data being passed back to the caller.

So many problems.  One solution.  Build the trace capability in, don't bolt it on.  As with oh-so-many-things, there is more than one way to skin this cat.  All that's necessary is to make it clear, and to do it consistently, and to design it so that the program doesn't have to be changed in order to start a TRACE.  Did I just say "parm"?  Yes, I believe I did.

/* REXX    -- a program to do some stuff
*/ arg argline
address TSO
arg parms "((" opts            /* 'opts' are rightward of a double-open-paren */
parse var opts "TRACE"   tv  . /* looking for "TRACE ?r" or similar           */
parse value tv "O" with tv .   /* if tv is empty, it becomes 'O'              */
rc     = trace(tv)             /* set whatever trace-value was loaded         */
You now can turn on TRACE with any form of TRACE without any change to the program.  You will be running the program that failed, not a copy of it.  Instead of calling it as
TSO BLAH43M for Monday using custfile4 crossfoot subtotals
you issue instead
TSO BLAH43M for Monday using custfile4 crossfoot subtotals (( trace ?r

You can, of course, get much fancier than this, but it's not strictly necessary unless you have an articulable reason.  I do get fancier than this (see REXXSKEL on my REXX page) because much of the parsing code I use is pre-packaged and thus doesn't have to be coded for every routine I write.  Whether you choose 'simple' or 'elaborate', it really doesn't matter.  Just no more de-commenting TRACE commands, okay?  Please?

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.