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.

Sunday, December 4, 2011

File Tailoring for Fun and Profit

Creating JCL for running a mainframe task is the most common use for ISPF's File Tailoring — because it does it so well.  Too often, though, such tasks are approached with a 'Bigger Hammer' mentality because the process to be tailored is seen as uncomfortably complex.  For instance:  your installation uses COBOL, PL/I, and ForTran;  some programs use DB2;  some run as part of CICS.  When it comes time to compile something, you may have to run the DB2 preprocessor;  you may have to run the CICS preprocessor;  the compiler must be appropriate to the source language;  if the compile is successful, the object module is to be link-edited;  much of the compiler listing for DB2/CICS modules is crap that does little beyond making the printed version harder to lift.  Is the compiler listing to be saved as a dataset or just printed?  If 'saved', where?  Are you going to trim away all the frou-frou before you print or save?

Faced with such a collection of decisions, most installations opt to have individual PROCs for each potential combination and to name them under a convention that allows the programmer to correctly specify the name of the PROC that does a DB2/non-CICS/PLI compile-and-link.  When a new compiler is released, somebody gets to fix and test a dozen PROCs to insert the updated library information, and it's usually several weeks before anyone realizes that an error sneaked through.  Ouch.

The reason file tailoring is not considered in these instances is because of all those mays above.  The DB2 precompile hands its output to the CICS precompile, but there may not be a DB2 precompile step at all.  The compiler gets its input from the CICS precompile step...  unless there isn't a CICS precompile step.  Where does the input come from then?  There is a solution.

You always start off with an IEFBR14, the safest program in the visible universe:

//ORIGIN   EXEC PGM=IEFBR14
//SOURCE    DD DISP=SHR,DSN=&SRCDS(&SRC)
)SET LSTEP = ORIGIN
)SET LDDN  = SOURCE

Note that this piece of skeleton identifies itself by setting LSTEP and LDDN.  Later steps can use these to find the input they need to focus upon.  Let's posit that if we have to ZORK and PARFLE and FLENCH a program, they always happen in that order, although some (or none) of them may happen at all.

)SEL    &ZORK = Y
//* --------------- ZORK processor --------------     */
//ZORK     EXEC PGM=ZORK
//SYSIN     DD DSN=*.&LSTEP..&LDDN
//SYSPRINT  DD SYSOUT=*
//SHIPOUT   DD UNIT=VIO,SPACE=(CYL,(5,1)),DISP=(,PASS),
//             DCB=(RECFM=FB,LRECL=80,BLKSIZE=0)
)SET LSTEP = ZORK
)SET LDDN  = SHIPOUT
//            IF (ZORK.RC << 8) THEN
)ENDSEL &ZORK = Y
)SEL    &PARFLE = Y
//* --------------- PARFLE processor ------------     */
//PARFLE   EXEC PGM=PARFLV2
//PARFIN    DD DSN=*.&LSTEP..&LDDN
//SYSPRINT  DD SYSOUT=*
//PARFOUT   DD UNIT=VIO,SPACE=(CYL,(5,1)),DISP=(,PASS),
//             DCB=(RECFM=FB,LRECL=80,BLKSIZE=0)
)SET LSTEP = PARFLE
)SET LDDN  = PARFOUT
//            IF (PARFLE.RC << 4) THEN
)ENDSEL &PARFLE = Y
)SEL    &FLENCH = Y
//* --------------- FLENCH processor ------------     */
//FLENCH     EXEC PGM=FLENCH00
//SYSIN     DD DSN=*.&LSTEP..&LDDN
//SYSPRINT  DD SYSOUT=*
//OUTFLEN   DD UNIT=VIO,SPACE=(CYL,(5,1)),DISP=(,PASS),
//             DCB=(RECFM=FB,LRECL=80,BLKSIZE=0)
)SET LSTEP = FLENCH
)SET LDDN  = OUTFLEN
//            IF (FLENCH.RC << 8) THEN
)ENDSEL &FLENCH = Y

If the ZORK processor is activated, it gets its input from '*.ORIGIN.SOURCE' because those are the current settings for LSTEP and LDDN.  When it's complete, it indicates that any later steps should use ZORK.SHIPOUT by setting LSTEP to 'ZORK' and LDDN to 'SHIPOUT'.  If the ZORK processor is not activated, LSTEP remains set at 'ORIGIN' and LDDN at 'SOURCE'.

If only the FLENCH processor is cleared to run, neither of the other two steps have altered LSTEP or LDDN and it picks up the dataset mentioned by the IEFBR14 step as its input.  Whichever combination of steps ran or didn't, the next step downstream will always find its input via '*.&LSTEP..&LDDN'.

Of course, things could be more complicated necessitating a little more finesse, but File Tailoring is, I assure you, up to the task.

Thursday, December 1, 2011

What about you?

I'm still reviewing old code looking for techniques that might be either useful or entertaining.  It occurs to me that readers may have tips of their own they'd like to pass along.  Anybody?  Bueller?

Tuesday, November 29, 2011

INTERPRET??  WDNNS INTERPRET!

(For those who don't know what 'wdnns' means, it is a reference to a misquote of a line from Treasure of the Sierra Madre and for the rest, you can look it up on the web.  The expansion is "We don' need no steenking ..."  'Nuf said.)

It is my firmly held conviction that INTERPRET is almost never required for ordinary REXX processing.  Les Koehler has come up with one or two scenaria where it is useful but they are so bizarre that 'esoteric' is a risible understatement.  Most uses of INTERPRET run something like this:

/*  'parm' looks like  ' name=Smith '   */
parse var parm  tag "=" tagval
interpret tag "=" tagval

I hope I got that right.  Since I never use INTERPRET and I don't have a mainframe to test this out, I have to guess as to what the code would actually look like.  If I were writing this code, I would use VALUE instead.

parse var parm tag '=' tagval
rc = Value(tag,tagval)

I have also seen entire commands constructed piece-by-piece and then INTERPRETed to cause the command to be executed.  Unnecessary.

interpret "ISPEXEC TBCREATE" tblnm blah blah blah

I have never seen a case where the INTERPRET couldn't simply be converted to a straight execution:

"ISPEXEC TBCREATE" tblnm blah blah blah

Anyone with a true instance of a necessary INTERPRET is welcome to present the same here.  I'd love to be proven wrong.

Monday, November 28, 2011

When TRACE just isn't enough

Back on November 1, I exhorted all REXX programmers to always make it possible to turn TRACE on via a parameter rather than making a code change to the program itself.  There are many good reasons to support that position philosophically, but there's one that makes all others pale in comparison.  When you can turn TRACE on remotely, you can trap the entire output of your program as it runs in TRACE.

You set up an OUTTRAP, issue the command, and close the OUTTRAP when it finishes.  'Issuing the command' in this case means calling the routine in such a way that TRACE is on.  If you can't turn on TRACE with a parameter, you add an entire layer of complexity that doesn't need to be there.  If you're in tune with REXXSKEL, it's as simple as

rc = Outtrap("OUT.")
(TSOCMD) "(( TRACE R"
rc = Outtrap("OFF")

Now, the entire TRACE output is stored in stem 'out.'  (here's hoping you didn't blow out of your TSO region in the process...)  and all that's left is to allocate a dataset to hold it, then

"EXECIO" out.0 "DISKW TRAPOUT (STEM OUT. FINIS"

If you allocated the dataset large enough (I always ask for 'CYL,(5,5)') you now have a browseable dataset showing every last line of execution right up to the point of failure.  You'll thank me for this when your routine crashes trying to process record number 11,728.

Over on my REXX page you'll find 'TRAPOUT' that implements this simple, straight-forward method for anyone whose code can be coaxed into running in TRACE :-)

Sunday, November 27, 2011

ISPF Tables — Copying

For any number of reasons, you may decide you need a copy (usually partial) of some ISPF table.  Possibly, the subset of the table you wish to operate on may not be easily parsed on the basis of the table's data content.  You will have to build a temporary table and load it from the original table, perhaps row-by-row.  Careful, now...

TBQUERY will tell you what an existing table looks like.  As soon as the table is opened, TBQUERY can be invoked to give a list of the keys and names.  From this, you can TBCREATE a replica table.

    "TBOPEN SAMPLE"
    "TBQUERY SAMPLE KEYS(knames) NAMES(nnames)"
    "TBCREATE NEWCOPY KEYS"knames "NAMES"nnames 

The column names returned from TBQUERY are enclosed within parentheses.  Unless you have other uses for the lists, it isn't even necessary to trim them off.  At this point, you are ready to write a duplicate or subset table, save for one easily overlooked aspect.

Remember 'extension variables'?  Unless you ask when reading each input row you will not know whether there are extension variables attached to the row, nor will you know their names.  Not knowing their names means not being able to specify which names should be attached to each output row.

   do forever
      "TBSKIP" intbl "SAVENAME(xvars)"
      if rc <> 0 then leave
      "TBMOD " outtbl "SAVE"xvars
   end

Notice that the extension variables themselves did not have to be operated on in any way.  We did not have to assign them, for instance.  Because the row was read (the NOREAD parameter was not specified), all the row's variables: keys, names, and extension; were populated to the variable pool.  They are therefore immediately available to be loaded to the output table row.

In fact, because of this behavior: populating every variable regardless of type; it might be good practice to never read a table row without acquiring the list of extension variables.

You can get fancier than this, of course.  For an idea of just how much fancier you can get, take a look at TBCOPY over on my REXX page.  TBCOPY doesn't use TBQUERY.  It relies on an external subroutine, TBLGEN, which itself relies on a special table in which is stored the DNA of all permanent tables, sort of a data dictionary for tables.  TBLGEN is much more elaborate than is strictly necessary, because it is intended to do all of an installation's TBCREATEs for permanent tables.  TBCOPY is likewise much more elaborate than is strictly necessary because it is intended to do all TBCOPYs for permanent tables within an installation.

Saturday, November 26, 2011

ISPF Tables — Extension Variables

Sometimes we just have to deal with tables we didn't design (and would have designed differently had we had the opportunity).  Sometimes we just need a tiny little tweak to make that table perfect (reg., U.S. Patent Pending).  Sometimes we want to let everyone know that this table row is unusual either for what it has or for what it hasn't.  Enter the extension variable.

Every table row in a given table has room for every KEY and NAME field originally specified when the table was TBCREATEd.  Flexible little devils that they are, table rows also have unlimited* space for other stuff, and every table row can be unique as to what other stuff it holds.  Observe:

    "TBCREATE BILOMAT KEYS(BMITEM) NAMES(BMDESCR BMONHAND BMECOQTY)"

our Bill-of-Materials table.  It is keyed by the item number (BMITEM), and has fields for a description, the number on hand in inventory, and the economic order quantity.  Wha...?  Where are the 'materials' that make up the 'bill'?  They're in extension variables, naturally.

    "TBVCLEAR BILOMAT"
    bmitem   = 'ER4590T'
    bmdescr  = 'Whatsis, large, left-handed, tan'
    bmonhand = qty.whatsis_l_lh_tan
    bmecoqty = ecocalc.whatsis_l_lh
    xvars    = 'DD114 DF33 DF34 DF36 DF38 EM2030'
    dd114    = 6
    df33     = 1
    df34     = 1 
    df36     = 1
    df38     = 2 
    em2030   = 4
    "TBADD BILOMAT SAVE("xvars")"

The row for 'ER4590T' in table BILOMAT has all four canonical fields filled, and there are six additional extension variables, one each for the six components of every ER4590T.  When the supplier for the EM2030 component advises you there will be an unavoidable 40% increase in the price of that component, you're going to want to know which of your products (BMITEMs) is going to be affected and by how much.  The catalog entries for each of those products will have to be adjusted to correctly show the new price of everything that relies on the EM2030, right?  Of course!

    "TBVCLEAR BILOMAT"
    em2030  = 0
    "TBSCAN BILOMAT ARGLIST(EM2030) CONDLIST(GT)"
    "TBDISPL BILOMAT PANEL(SRCHPART) AUTOSEL(NO)"

TBVCLEAR zaps all the 'regular' names mentioned in the definition of table BILOMAT, variable 'EM2030' is set to zero, TBSCAN specifies that we seek all rows where an extension variable 'EM2030' has a value greater than zero.  Panel SRCHPART should, if coded correctly, display a scrollable list of all rows (all BMITEM keys) associated with subassembly EM2030.

Zounds!  This is almost DB2!  Except that it didn't take us eight months and require the approval of two vice-presidents.  And if they decide later that they really do want to implement it in DB2, you already have the proof-of-concept ready to demonstrate.

 

(*) — You didn't think when I said 'unlimited' I actually meant 'without limit', did you?  Tsk.  Of course there are limits.  It's just that you're unlikely to hit them before your table is so large your system chokes trying to open it.  I've seen that happen with complex tables as small as 6,000 rows, but I have also seen 50,000-row tables that worked just fine, and I suspect they could have grown much larger without causing a problem.