MVS TOOLS AND TRICKS OF THE TRADE JULY 2000 Sam Golob MVS Systems Programmer P.O. Box 906 Tallman, New York 10982 Sam Golob is a Senior Systems Programmer. He also participates in library tours and book signings with his wife, author Courtney Taylor. Sam can be contacted at sbgolob@cbttape.org. Information about the CBT MVS Tapes can be found on the web, at http://www.cbttape.org. SCANNING FOR PARMS IN A BATCH PROGRAM As we all know, many programs, and especially utility programs, have options. These are switches that we can turn on or off. Each switch tells the program that it can run one way, or another way. You can invoke one feature, another feature, both, or neither of them. There are various mechanisms of telling a program how to change its execution style. We can write a program so the switch info is contained in the execution PARM field in the JCL. Or we can code the program so it reads SYSIN control statements that come from a (card-image) file. Or, in the case of TSO commands, we can code subcommands and operands, which are executed under the control of the main TSO command. Today, we'll discuss how to tell a batch program about its execution options, using the JCL EXEC card PARM field. The idea is to arrange for one or more bytes in the program to be designated as flag bytes, with a bit set on, or off. The program's code will interrogate these bits as it executes, usually using a TM (Test Under Mask) instruction, and this will tell the program how it should run. There's going to be a relation between the PARM field in the JCL EXEC statement, and the flag bytes that get set on or off in the program. Actually making this connection, in the program, is the trick. We'll talk about various techniques of getting the EXEC PARM information into the program, so that there is a proper relationship between what the PARM says, and the option switches which the program sets. These are the option switches that the program interrogates later on during its execution, to see which features it should run with. PARSING MVS (as did MVT before it) allows the programmer an opportunity of communicating execution information to a program, through the PARM keyword of the EXEC statement in the JCL. The string that can be fed to a program by means of the PARM can be up to 100 bytes long. The idea is that it's a raw string of arbitrary data. The programmer, in planning a program's structure, has to figure out how to exploit this arrangement intelligently, so a user of this program will be able to figure out how to code the characters in the PARM field and to exercise effective control of the program's execution modes. Here is our general problem with MVS execution PARMs. You have a string of character bytes which the user will code, in the PARM field of the EXEC statement in the JCL. This string must be searched, divided into recognizable keywords, and those keywords must be converted into switch settings that the program's code will later know how to use. In short, your program has to recognize user-coded character strings and convert them into bit settings in flag bytes. Making this conversion is probably the first thing that your program will have to do, since it will later need to know what choices of operation it's going to have to make. Programmers, in deciding what switches a program will need, therefore have to design a scheme of converting PARM keywords into switch settings. There are several commonly-used methods of recognizing coded keywords, an activity which is generally termed "parsing". To parse a string for keywords, means to divide it into recognizable separate fields, so that a program can read in these fields and process them. So, besides the other processing that a program will do, it might also have to include a scheme of converting PARM keywords into execution switches. The program usually does this at the beginning of execution. We'll discuss several such schemes. I'll also show you a nice table-driven PARM parser that I've just written, which performs this task very efficiently and very nicely. With my PARM parser in place in a program, it's very easy to add or change the permissible keywords, and the execution flags they set or turn off. It's also possible to set any of the keywords as defaults. Those defaults can be overridden by explicitly coding keywords in the PARM field. Now let's talk about PARM coding specifics. WHERE THE PARMS ARE When you run a batch program under MVS, the operating system makes the execution PARM field available through Register 1. Register 1 points to an area containing a 2-byte length, followed by the actual PARM bytes that the user coded. The 2-byte length is the number of characters that the user coded in the PARM field (including blanks) so it tells you that you should search this number of characters from the starting point, and no farther. It follows, that if you only want to code one PARM keyword and no more, you only need two lines of program code to recognize the keyword. Let's say that the keyword will be called "LTMSKIP". (This is a for a tape copying program, and I want to give the program the option of not copying leading tapemarks from the input tape.) The necessary lines of code are: L R1,0(,R1) GET PARM FROM JCL CLC =C'LTMSKIP',2(R1) ARE WE SKIPPING LTM'S? This will be followed by a BE (branch if equal) or BNE (branch if not equal) instruction. One of the branch paths will set the switch flag on, while the other will not. This is very simple code, but the reason for the simplicity is that there's only one PARM keyword acceptable. Suppose we'd need two PARM keywords. I'll code them as in my actual program. They are: LTMSKIP and LABELS. I want my tape copying program to have the option of printing tape label information if it finds any on the input tape, or not printing the label info. I also want my program to be able to skip leading tape marks when copying a tape, or to have the option of copying the leading tape marks. These are clearly two separate choices, and they'll need two separate switches (bits in a flag byte) to be independently set. I could force the user of my program to code LTMSKIP first, and then LABELS afterwards. Then I could simply look for LTMSKIP in the PARM field, and upon finding or not finding it, just look further for the LABELS keyword. This isn't realistic, and it may make my program hard to use. A better design would be to have the same flag bits set, if keyword LABELS is coded first, then LTMSKIP. The order shouldn't matter. So I have to start my search of the PARM string, looking either for the word LTMSKIP or for the word LABELS. Upon finding one of these, I have to search farther up in the PARM string, looking for the other keyword. To make the process easier, many programmers use the following design, and require the user to code a comma or a space in between any two keywords. This is how such a design works: First, the program looks for one keyword at the beginning of the PARM string, so it copies the first non-blank characters to a work area until it finds a comma or a space. It interrogates the characters in the work area, to see if they match a required keyword. Then it searches farther in the PARM field, until the next comma or space. The same process is continued until the length of the PARM field is exhausted. This search method is very common, especially in older programs. I feel I have designed a better method. It's a bit more complicated to set up, but once it's in place, it's far easier to change the permissible PARM keywords, and it's a dream to maintain the program later. My method also has the advantage, that you don't need a comma or a space to separate the PARM keywords, although you can separate the keywords by a comma or a space for clarity. And you can also code synonyms for the same keyword. These are two different keywords which set the same switch flags in the program. Now I'll show you how my method works. A TABLE-DRIVEN PARM PARSER When designing my PARM parser, these were the specifications I had in mind. It would be table driven, so in order to change a keyword, or the switch setting it would be associated with, all you'd have to do is to change a table entry. All the other code would remain the same. Maintaining such a program, or changing the configuration of keyword and switch settings, would be extremely easy. This is how I did it. I decided to code a fixed-length table, with the keywords eight bytes or less, in length. Of course, this can be changed if you need longer keywords. I wanted flexibility to choose many execution options, so I allowed for three contiguous flag bytes, each containing eight bits, so there are 24 independent flag bit settings possible. I also wanted the option of turning the flag bits off, so I needed an on-off switch. That occupies only one bit, but since I didn't want to use one of the flag bits, I allowed for an extra byte with option switches. One bit of this byte is for turning flag bits off. Another bit of this byte would be used for specifying that a keyword will be set as a "default" when no PARM at all, is coded in the JCL EXEC card. So in my current design (which you can adjust for your needs), one table entry takes up 13 bytes. There's one byte at the beginning, which specifies in hex, the length of the PARM keyword. Then there's an option byte, where 2 bits are used. One bit says whether to turn the flag bit(s) on or off, as a result of coding this keyword. The other bit says that this keyword will be set as a default for program execution, when no PARM keywords are coded in the JCL. Then there's 8 bytes for the keyword itself. If the keyword is shorter than 8 bytes, as it usually is, it is left-justified in the table entry. Finally there are 3 model flag bytes, which will contain the specific bit settings associated with this PARM keyword. To see a sample coded table, look at Figure 1. To see the assembler code which processes this table, look at Figure 2. In reading this table, I first make one pass through it, to see if an entry is a default, flagging its associated option bits accordingly, on or off. Then I search through the entire table again, looking also at the beginning of the PARM field. If I find a keyword entry match, I flip the associated program flag bits, go farther into the PARM field, and search again through the entire table, looking for another match. If I don't find a keyword match at this point in the PARM field, I go forward one byte, and repeat the table search again. Eventually I find all valid PARM keywords that have been coded, and all the appropriate option flag bits have been correctly set. I'll conclude with a word about setting program option bits from the table entry. Each coded PARM keyword is associated with one or more option bits in the 3 model flag bytes that are in each table entry. These model bit settings will turn the corresponding program flag bits either on or off. How is this on-off bit flipping done? To set a bit on, you use the "OR" instruction. If a bit is zero, and you OR it with a one, the resulting bit will be one. If a bit is one, and you OR it with a one, the resulting bit is still one. You see that an OR with a one will always turn a bit on, but an OR with a zero will always leave it unchanged. So if any "model bits" are on, and you want to turn on the corresponding program flag bits, you just have to OR the model bits into the program flag bits. Since in our case, 3 bytes of flag bits are involved, we use the OC (OR CHARACTERS) instruction for 3 bytes. Setting a bit off, starting from an indicator bit that's on, is more complicated. I don't have space to work you through the thinking, but this is what you do. If a keyword option bit is set on, and you want to turn the corresponding flag bit off, you move the option bit to a work area, flip it over (if it was zero, make it a one, if it was one, make it zero), and then you "AND" the result into the program flag bits. This will have the result of turning that bit off, while leaving all the other bits unchanged. Please study the code in the figures, and you'll see how clever and flexible this arrangement is. With this gizmo, PARM coding is easy. I have to say that my PARM searching code uses four work registers, but they are usually available, since this code is normally executed once, at the very beginning of the program, and the work registers could be reused later. I hope you've gotten a lot of satisfaction from reading this month's column. You can find the program that uses this code at ftp://ftp.cbttape.org/pub/cbttape/cbt/CBT229.zip Best of luck to you in all your work. I'm looking forward to seeing you again, next month. * * * * * * * * * * * * * * * * * * * * * * * Figure 1. Format of the PARM Table which sets program flag switches, based on the table entries. This table is very easy to change, and you do not have to change the code which drives it. Some constants, which the driving code uses, are included here, at the top. * --------------------------------------------------------- * PARMFLGS DS 0CL3 3 CONTIGUOUS BYTES OF PARM FLAGS PARMFLG1 DC X'00' PARMFLG2 DC X'00' PARMFLG3 DC X'00' * --------------------------------------------------------- * PARMSLEN DC F'0' LENGTH OF PARMS PARMSTRT DC F'0' START ADDR OF JCL PARMS PRMCHRCT DC F'0' CHAR COUNT FOR PRM SEARCH PARMHITS DC PL4'0' NUMBER OF PARM HITS PARMLCNT DC PL4'0' NUMBER OF PARM LOOPS PARMTENT DC CL13' ' PARM TABLE ENTRY DUMP DC CL7' ' FILLER PARMFWRK DC XL3'00' FOR TURNING OFF PARM BYTES * --------------------------------------------------------- * * FORMAT OF PARM TABLE * ONE ENTRY IS 13 BYTES (the way it's implemented here) * 1ST BYTE: LENGTH OF THIS PARM IN HEX * 2ND BYTE: OPTION BYTE * X'01' SAYS TURN FLAG OFF * X'00' SAYS TURN FLAG ON * X'10' SET ENTRY AS A DEFAULT * NEXT 8 BYTES: PARM NAME - LEFT JUSTIFIED * NEXT 3 BYTES: FLAG SETTINGS - 3 FLAGS, * WHICH ALLOWS FOR 24 PARMS * THAT DON'T HAVE TO BE * SEPARATED BY A COMMA. PBYTES EQU 3 NUMBER OF PARM SWITCH BYTES * --------------------------------------------------------- * PARMTABL DC X'07',X'00',C'LTMSKIP ',X'200000' FIRST ENTRY PTELEN EQU *-PARMTABL PTEFLG EQU PTELEN-PBYTES DC X'07',X'00',C'SKIPLTM ',X'200000' synonym for on DC X'07',X'11',C'COPYLTM ',X'200000' off (default) DC X'07',X'01',C'LTMCOPY ',X'200000' synonym for off DC X'07',X'00',C'LBLINFO ',X'DE0000' all label info DC X'06',X'00',C'PRINTL ',X'DE0000' synonym DC X'06',X'00',C'LABELS ',X'DE0000' synonym DC X'06',X'01',C'NOLABL ',X'DE0000' synonym for off DC X'07',X'11',C'NOLABEL ',X'DE0000' off (default) DC X'04',X'00',C'HDR1 ',X'020000' print HDR1 info DC X'06',X'01',C'NOHDR1 ',X'020000' no HDR1 DC X'04',X'00',C'HDR2 ',X'040000' print HDR2 info DC X'06',X'01',C'NOHDR2 ',X'040000' no HDR2 DC X'04',X'00',C'EOF1 ',X'080000' print EOF1 info DC X'06',X'01',C'NOEOF1 ',X'080000' no EOF1 DC X'04',X'00',C'EOF2 ',X'100000' print EOF2 info DC X'06',X'01',C'NOEOF2 ',X'100000' no EOF2 DC X'04',X'00',C'EOV1 ',X'400000' print EOV1 info DC X'06',X'01',C'NOEOV1 ',X'400000' no EOV1 DC X'04',X'00',C'EOV2 ',X'800000' print EOV2 info DC X'06',X'01',C'NOEOV2 ',X'800000' no EOV2 DC X'04',X'00',C'HDRS ',X'060000' HDR1 and HDR2 DC X'06',X'01',C'NOHDRS ',X'060000' neither DC X'04',X'00',C'EOFS ',X'180000' EOF1 and EOF2 DC X'06',X'01',C'NOEOFS ',X'180000' neither DC X'04',X'00',C'EOVS ',X'C00000' EOV1 and EOV2 DC X'06',X'01',C'NOEOVS ',X'C00000' neither DC X'05',X'00',C'NINTH ',X'000100' NOT USED DC X'07',X'01',C'NONINTH ',X'000100' NOT USED DC X'05',X'00',C'TENTH ',X'000200' NOT USED DC X'07',X'01',C'NOTENTH ',X'000200' NOT USED DC X'07',X'01',C'NOEXTRA ',X'010300' ALL EXTRAS OFF EFFS DC X'FFFFFFFFFFFFFFFF' * * * * * * * * * * * * * * * * * * * * * * * Figure 2. This is the code which looks at the PARM table in Figure 1, and which sets the appropriate bits in the PARMFLGS field that the program interrogates to determine its operating options. One initial pass is made through the table, to set the program defaults, and then the JCL PARM field is scanned for keywords to override these defaults. PARMCHK DS 0H CHECK PARMS AND FLAG THEIR * PRESENCE OR ABSENCE. * (PERMISSIBLE PARMS ARE CODED IN PARMTABL.) PARMINIT L R1,0(,R1) GET PARM FROM JCL LH R5,0(,R1) SAVE PARM LENGTH ST R5,PARMSLEN STORE FULLWORD VALUE LA R7,PARMTABL LOAD START OF PARM TABLE LA R4,2(,R1) POINT TO START OF JCL PARMS ST R4,PARMSTRT PUT ADDRESS INTO PGM STORAGE LA R1,0 USE R1 TO MEASURE LENGTH READ * ----------------------------------------------------------- * ONE PASS THRU PARM TABLE TO SET DEFAULT ENTRIES BEFORE * LOOKING AT JCL OVERRIDES TO THE PARMS. * ----------------------------------------------------------- PARMDFLT DS 0H SET DEFAULTS FROM PARM TABLE CLI 0(R7),X'FF' END OF PARM TABLE? BE PARMDEND YES. DEFAULTS SET. CHECK PARMS. TM 1(R7),X'10' IS THIS ENTRY A DEFAULT? BO PARMDSET YES, GO SET IT. LA R7,PTELEN(,R7) NO. BOP TO TEST NEXT ENTRY, B PARMDFLT AND LOOP UNTIL TABLE ENDS. PARMDSET TM 1(R7),X'01' DO WE TURN THIS FLAG OFF? BO PARMDOFF YES. FLAGS ARE TURNED OFF.. OC PARMFLGS(3),PTEFLG(R7) NO. FLAG IS TURNED ON. LA R7,PTELEN(,R7) NO. BOP TO TEST NEXT ENTRY, B PARMDFLT CHECK MORE TABLE ENTRIES. PARMDOFF MVC PARMFWRK(3),PTEFLG(R7) PARM BYTES TO WORKAREA XC PARMFWRK(3),EFFS FLIP PARM BYTES OVER NC PARMFLGS(3),PARMFWRK TURN PARM BYTE(S) OFF LA R7,PTELEN(,R7) BOP TO TEST NEXT ENTRY, B PARMDFLT LOOP BACK TILL ONE PASS THRU TABLE PARMDEND LA R1,0 INITIALIZE COUNTING REGISTER LA R7,PARMTABL RELOAD START OF PARM TABLE L R4,PARMSTRT RELOAD JCL PARM ADDRESS * ----------------------------------------------------------- * NOW CHECK EACH CHARACTER IN JCL, TO SEE IF IT'S A PARM. * ----------------------------------------------------------- PARMLOOP C R1,PARMSLEN PAST THE END OF PARMS? BNL PARMFIN YES - GET OUT LA R7,PARMTABL POINT BACK TO BEGINNING OF TABLE AP PARMLCNT,=P'1' COUNT PARM LOOPS FOR DEBUGGING BOPPTBL CLI 0(R7),X'FF' END OF PARM TABLE? BE PRMNFND YES. BUMP AND TRY AGAIN. SR R5,R5 CLEAR WORK REGISTER FOR REUSE IC R5,0(,R7) LOAD LENGTH TO BE COMPARED BCTR R5,0 ONE LESS FOR EXECUTE EX R5,CMPPARM COMPARE TABLE ENTRY TO PARM LOC BE PRMFOUND PARM FOUND AT THIS LOCATION LA R7,PTELEN(,R7) OTHERWISE, GO TO NEXT ENTRY B BOPPTBL INNER LOOP THROUGH THE TABLE PRMNFND LA R4,1(,R4) FORWARD IN JCL PARMS LA R1,1(,R1) MEASURE DISTANCE TRAVELED B PARMLOOP TRY SEARCHING THE TABLE AGAIN PRMFOUND MVC PARMTENT(PTELEN),0(R7) DUMP TABLE ENTRY FOUND TM 1(R7),X'01' TURN THIS SWITCH ON OR OFF? BO PARMOFF IF OFF, TURN IT OFF, OTHERWISE, OC PARMFLGS(3),PTEFLG(R7) TURN THE FLAG ON. B PARMGO AND GO ON PARMOFF MVC PARMFWRK(3),PTEFLG(R7) PARM BYTES TO WORK AREA XC PARMFWRK(3),EFFS FLIP PARM BYTES OVER, THEN NC PARMFLGS(3),PARMFWRK TURN PARM BYTE(S) OFF PARMGO AP PARMHITS,=P'1' COUNT PARM HITS IC R5,0(,R7) LENGTH OF PARM, FROM THE TABLE LA R1,0(R5,R1) INCREMENT COUNT BY LENGTH LA R4,0(R5,R4) SAME FOR SEARCH POSITION B PARMLOOP LOOK FOR MORE PARMS PARMFIN ST R1,PRMCHRCT HOW MANY CHARS WE SEARCHED -- * AT THE END, THIS COUNT SHOULD MATCH THE PARM LENGTH. PRMCHEND BR R6 CMPPARM CLC 0(0,R4),2(R7) ** EXECUTED **