Advanced REXX Programming Techniques


Published on

Shows some advanced REXX techniques to make your programs more efficient and more readable for easier debugging. Also describes some tips for creating file and program structures not discussed in a typical REXX class.

  • Be the first to comment

No Downloads
Total Views
On Slideshare
From Embeds
Number of Embeds
Embeds 0
No embeds

No notes for slide

Advanced REXX Programming Techniques

  1. 1. REXX Programming For TSO: Advanced Concepts Dan O’Dea September 2, 2004 1
  2. 2. Table of Contents Introduction . . . . . . . . . . . . . . . . . 3 Goals of the course. . . . . . . . . . . . . 3 More on syntax 4 Comments . . . . . . . . . . . . . . . . . . 4 Handling Hexadecimals. . . . . . . . . . . . 4 More on If/Then 5 Using words to indicate success or failure . 5 Boolean operators. . . . . . . . . . . . . . 7 Passing Parameters 8 Using keyword parameters . . . . . . . . . . 8 Parsing 9 Using columns. . . . . . . . . . . . . . . . 9 Using literals . . . . . . . . . . . . . . . 10 PARSE VALUE. . . . . . . . . . . . . . . . . 10 PARSE SOURCE . . . . . . . . . . . . . . . . 10 Trace Output Line Codes 12 OutTrap Restrictions 12 Arithmetic and verifying expressions 13 Addressing Environments 14 Stem Variables 15 System Information 16 SYSVAR . . . . . . . . . . . . . . . . . . . 16 MVSVAR . . . . . . . . . . . . . . . . . . . 18 Other Performance Concerns 19 Putting it all together: real-world examples 20 Appendix A, Page 26: a few simple edit macros Appendix B, Page 28: solutions to exercises 2
  3. 3. Introduction The first part of this course provided you with basic learning needed to code REXX execs. Some topics were glossed over, or skipped entirely. REXX performance was not discussed. Finally, some REXX features can be used to make your program more readable, such as word tests (something like the COBOL 88 level variable). This document corrects those “oversights.” While the first document often used simplified examples, this document uses real-world examples wherever possible. Goals of the Course When you complete the course you will be able to do the following. • Do arithmetic on hexadecimal values. • Use nouns in IF statements rather than comparisons. • Handle both positional and keyword parameters. • Parse anything into anything. • Understand trace output. • Verify arithmetic expressions. • Use multi-dimensional stem variables (matrixes). • Code more efficient REXX execs. 3
  4. 4. More on REXX Syntax Comments are started by “/*” and ended with “*/”. They can go anywhere in the program. For example, this is a wing comment: Say “Hello” /* instruction displays “Hello” */ This is a box comment. Note each line is not closed. Only the first and last lines have slashes; the first slash starts the comment, and the last finishes it. /* multiple-line comments are more efficient than many single-line comments. When using like this, use one pair of delimiters. */ Longer comments are more efficient because the REXX interpreter opens comment processing each time it encounters a /*. By coding only one set around multiple comment lines, the interpreter does less work. This is especially important in loops. Make a box comment above a loop to explain everything the loop does. If the comments are in the loop, they are scanned each pass through the loop. To avoid unnecessary loop processing, don’t comment within the loop. Hexadecimal Values You may recall that hexadecimal numbers may be used. Here is a piece of code showing how interpreting hex characters is used to determine what LPAR you’re running on. How it works: First, convert the 4 bytes of system storage area at location x’10’ to hex. Convert that to decimal for math, then add the offset to the storage location of the system name (in this case, hex 154 / decimal 340). Convert the result back to hex. Then get the 8 bytes of storage at the resulting address and strip the spaces from it. /*------------* * What LPAR? * *------------*/ CVT = C2X(STORAGE(10,4)) CVT_154 = D2X(X2D(CVT) + X2D(154)) SID = STRIP(STORAGE(CVT_154,8)) 4
  5. 5. Conditional Operators As we know, the IF statement follows a standard format: If expression Then one instruction Else one instruction DO WHILE and DO UNTIL statements are considered IF statements. Internally, any IF statement operates by testing the outcome of a binary choice (yes or no). We’ve seen the results of IF comparisons can be displayed as “1” (true) or “0” (false). We can use this feature to our advantage. In REXX, almost any statement can contain a variable to be resolved. Consider the following: Dan = 0 Say “Enter name” Pull Name If Name = “Dan” Then Dan = 1 If Dan Then Say “Hi, Mr. O’Dea!” Else Say “Hi,” Name“.” REXX resolves the expression “If Dan” as either “If 0” or “If 1” depending on the value in the variable Dan. “Dan” is called a noun switch. It’s COBOL equivalent is the 88 level. There are two things to keep in mind when using this method: 1. You must be careful to set the switch to only zero or one. If the IF statement finds any other value, your exec terminates with a syntax error. 2. You cannot negate the switch. To test for a negative condition, it’s a good idea to code the statement positively, and use the NOP command to skip the THEN condition. For example, KSDS = 0 If <index> Then KSDS = 1 If KSDS Then NOP /* Do nothing for KSDS */ Else Say “ESDS” /* Do ESDS stuff */ 5
  6. 6. Conditional Operators Noun Switch Example Here is part of the code from the stack example above. First, set the switch to 0 (off/false). Then, check if the dataset has an index component. If the index component exists, turn the switch on. You can then check the switch in the IF. KSDS = 0 CKIX = INDSN“.INDEX” If SysDSN(CKIX) = “OK” Then KSDS = 1 :: If KSDS Then Do Queue " DATA (NAME("NewDSN".DATA)) -" Queue " INDEX (NAME("NewDSN".INDEX))" End Else Queue " DATA (NAME("NewDSN".DATA))" Here’s another piece of code used to scan a LISTC ENT() ALL output for the index CI size: If EndOfData Then NOP Else Do Do Until LCA.I = "ATTRIBUTES"; I = I + 1; End I = I + 1 ICIS = Strip(Right(Word(LCA.I,4),6),"L","-") End Notes on the above: 1. The “Do Until” scans the output from the previous CISIZE field (the one in the data component section) until the index attributes section to skip over stuff we don’t want. The index “I” must point to the next line after “ATTRIBUTES” to get the CISZ. 2. The last line gets the fourth word of the output line, takes the last 6 characters of that word, then removes leading dashes from it to give the CI size of the index. Exercise #1 Modify the exec you wrote in Exercise #28 of the first class to use a noun switch instead of expression tests when deciding what to do with the dataset. 6
  7. 7. Passing Information to the Exec The ARG statement parses values passed to the program based on spaces or commas. ARG is thus a positional operation selector. Here’s one way to code the ARG to enable you to process passed parameters as keywords. /* REXX Keyword Parms, LC accepts up to three parms. More than that are ignored. */ ARG PARM.1 PARM.2 PARM.3 . Lall = “”; LGDG = “” Do I = 1 to 3 Parse Var Parm.I Opt 4 . Select When Opt = “” Then NOP When Opt = “ENT” Then Do Parse Var Parm.I . “(” LCADSN LCADSN = Strip(LCADSN,“T”,“)”) End When Opt = “ALL” Then LAll = “ALL” When Opt = “GDG” Then LGDG = “GDG” Otherwise Say Parm.I “is invalid.” End End LCADSN = $FixDSN(LCADSN) “LISTC ENT(“LCADSN”)” LAll LGDG This is designed to perform a LISTC ENT() on without having to code ALL, GDG, or other argument in any order. All these calls give the same result: LC ENT(‘ISDODEA.TEST.DATA’) ALL GDG LC GDG ENT(‘ISDODEA.TEST.DATA’) ALL LC ALL GDG ENT(‘ISDODEA.TEST.DATA’) LC ALL ENT(‘ISDODEA.TEST.KSDS’) GDG Exercise #2 Modify the exec you wrote in Exercise #16 from the first course to accept the unit and amount values as keywords. Only accept two arguments. If one argument is bad, display the syntax and exit. 7
  8. 8. Separating Data: Parsing In the introduction we talked about parsing data by word or literal. You can also parse data in other ways. Some of them are very useful. You can parse by column number. It can get complicated because column numbers in PARSE are not intuitive, but for some things it performs very well. The PARSE command is very fast. It is faster than SUBSTR or the LEFT and RIGHT functions. In loops running many times, PARSE using columns can save you some CPU. When specifying column delimiters, the number to the left of the variable name is the start column, while the number on the right is the end column, minus one. As you can see in the example, sometimes this can be misleading: %SPLITIT ABCDEFGHIJKLMNOP PARSE ARG 1 VAR1 5 VAR2 9 VAR3 13 In this example, the variables are stored as: •VAR1 = ABCD; •VAR2 = EFGH; •VAR3 = IJKL When using both column numbers everywhere, it can be worse: %SPLITIT ABCDEFGHIJKLMNOP PARSE ARG 1 VAR1 5 7 VAR2 9 11 VAR3 13 In this example, the variables are stored as: •VAR1 = ABCD; •VAR2 = GH; •VAR3 = KL Please don’t give up on this just because it looks complicated. You’ll see why below. 8
  9. 9. PARSE using Literals When parsing using a literal, you can use any literal. Consider this: LINE1 = “ DEFINE CLUSTER(NAME(YOUR.DSN) –” PARSE VAR LINE1 JUNK “NAME(” DSN “)” . After the PARSE the variable DSN contains “YOUR.DSN”. However, you can skip the LINE1 assignment statement by using PARSE VALUE and the literal itself. PARSE VALUE PARSE VALUE needs the keyword WITH to tell where the template begins. This can be used with functions as well as variables. PARSE VALUE “This is a sample” With A B C D PARSE VALUE TIME() With HRS “:” MIN “:” SEC PARSE SOURCE PARSE SOURCE asks TSO for information about the way the exec was run. PARSE SOURCE OP_SYSTEM HOW_CALLED EXEC_NAME, DD_NAME, DATASET_NAME AS_CALLED, DEFAULT_ADDRESS, NAME_OF_ADDRESS_SPACE where: •OP_SYSTEM is TSO •HOW_CALLED is either COMMAND, SUBROUTINE, or FUNCTION •EXEC_NAME is the name of the exec in upper case •DD_NAME the exec was found in SYSEXEC or SYSPROC •DATASET_NAME is the DSN the exec was in when called implicitly •AS_CALLED is the name the exec was invoked by. May be in lower case when called implicitly •DEFAULT_ADDRESS is the initial address environment. This is usually TSO, MVS, ISPEXEC (i. e. ISPF), or ISREDIT (the ISPF Editor) •NAME_OF_ADDRESS_SPACE is one of MVS, TSO, or ISPF. 9
  10. 10. Parsing continued Here are several examples of using PARSE rather than a built-in functions or many assignments. Take the third word from the variable INVAR and call it WORD3. The PARSE instructions are much faster, and if you don’t need the previous two words use the “.”. 1. WORD3 = WORD(INVAR,3) 2. WORD3 = SUBWORD(INVAR,1,3) 3. PARSE VAR INVAR JUNK1 JUNK2 WORD3 . 4. PARSE VAR INVAR . . WORD3 . Take the left five bytes from the variable AMBI and call it LEFTY. LEFT is faster then SUBSTR. Again, the PARSE instruction are much faster than either. 1. LEFTY = Left(AMBI,5) 2. LEFTY = SUBSTR(AMBI,1,5) 3. PARSE VAR AMBI 1 LEFTY 6 . Take the right five bytes from the variable AMBI and call it RT. Note there is an extra step for the SUBSTR example, and no PARSE example. Parse could be used if you knew, when you coded, how long AMBI would be. The single function call is faster. 1. RT = Right(AMBI,5) 2. ST = Length(AMBI) – 4; RT = SUBSTR(AMBI,ST,5) Take the middle five bytes from the variable AMBI, where the middle is known before coding. The PARSE instruction is faster. 1. MID = SUBSTR(AMBI,5,5) 2. PARSE VAR AMBI . 5 MID 10 . Set A, B, C, and D to null. The PARSE instruction is faster. You can also use this method to set all variables to zero. 1. A = “”; B = “”; C = “”; D = “” 2. PARSE VALUE “” WITH A B C D 3. PARSE VALUE “0 0 0 0” WITH A B C D 10
  11. 11. Exercise #3 Write an exec to parse the date into month, day, and year. Use Parse Value. Redisplay the date in the following formats: MM.DD.YY (USA), DD.MM.YY (European), YY.DD.MM, and YY.MM.DD (ordered). The command to get the USA-style date (MM/DD/YY) is DATE(”U”). 11
  12. 12. Trace Output Line Codes When running a program in trace mode, the REXX interpreter prefixes each line of trace output with a three character symbol. These symbols tell you what is happening at each stage of REXX’s handling of the statement. The three-character codes displayed during tracing are: •*-* original program line •+++ trace message •>>> result of an expression during TRACE R •>.> value assigned to a placeholder (period) during parsing •>C> resolved name of a compound variable •>F> result of a function call •>L> a literal •>O> result of an operation on two terms •>P> result of a prefix operation •>V> contents of a variable OutTrap Restrictions Not all output from TSO commands can be trapped by REXX. Whether you can trap output depends on how the command sends its output to the screen. Outtrap cannot capture lines produced by the following. •TPUT •WTO macro •Messages issued by REXX (IRX*) •Trace output messages An example of a command you cannot capture the output for is CONCAT. Try it. 12
  13. 13. Arithmetic Operations in REXX As you know, there are certain characters you can use in any arithmetic statement, and some you can’t. In the first half of the class we saw the basic arithmetic operators. Parentheses can be used, as well as spaces to separate terms, periods for decimals, and the letters A – F for hexadecimal numbers. Here is a clever way to make sure your expression (EXPR) contains only valid characters: If Verify(EXPR,'1234567890ABCDEF+-*/% ()') = 0 The VERIFY function checks to see if all the characters in the first string are contained in the second string. An Unusual Type of DO loop On a DO from X to Y loop, the FOR clause enforces a specific count: Do I = 1.234 To 9943.2323 by .3203 For 22 Say “I is” I End The FOR tells REXX to run this loop 22 times no matter what else happens. 13
  14. 14. Talking to the Environment For each ADDRESS command, REXX opens a command processor link. To improve performance, then, it is good practice to use as few ADDRESS commands as possible. For example, the following code is a subroutine to format and submit a piece of JCL. Arg JCLSKEL Address ISPEXEC DO0JOBNM = $JobName() "FTOPEN TEMP" "VGET (ZTEMPF) SHARED" "FTINCL" JCLSKEL "FTCLOSE" ZEDSMSG = DO0JOBNM "SUBMITTED" ZEDLMSG = "Batch job” DO0JOBNM “submitted.” "SETMSG MSG(ISRZ000)" Address TSO "SUBMIT DATASET('"ZTEMPF"')" Return This is the same code, with the ADDRESS commands as part of the code. Arg JCLSKEL DO0JOBNM = $JobName() Address ISPEXEC "FTOPEN TEMP" Address ISPEXEC "VGET (ZTEMPF) SHARED" Address ISPEXEC "FTINCL" JCLSKEL Address ISPEXEC "FTCLOSE" ZEDSMSG = "JOB SUBMITTED" ZEDLMSG = "Batch job” DO0JOBNM “was submitted.” Address ISPEXEC "SETMSG MSG(ISRZ000)" Address TSO "SUBMIT DATASET('"ZTEMPF"')" Return While both produce the same results, the first one uses less CPU and runs slightly faster because there’s no address switching except for the SUBMIT command passed to TSO. For short execs the difference is so small it’s almost meaningless, but for many consecutive calls, or calls within a loop, the difference is measurable. 14
  15. 15. More on Stem Variables So far we’ve only seen stem variables with a single index that’s always a number. Stem variables are more flexible than that. For example, although a stem variable has a stem and an index, a single entry of a stem variable is just like any other, non-stem variable. We can set any single entry of a stem variable to be a stem variable using a second index. Here is a piece of code to initializes a chessboard, a two-dimensional array of eight ranks and eight files. Do R = 1 To 8 Do F = 1 To 8 ChessBoard.R.F = "." End End The stem index does not have to be a number. REXX understands the use of a letter rather than a number. For example, try this. /* REXX */ Do Forever Say “What is your name (END to stop)?” Pull NAME If NAME = END Then Leave Say “Thanks,” NAME “, how old are you?” Pull HOW_OLD If DataType(HOW_OLD) <> “NUM” Then Say “Not valid age, rejected.” Else AGE.NAME = HOW_OLD End Do Forever Say “Whose age would you like”, “(END to stop)?” Pull NAME If NAME = “END” Then Leave If SYMBOL(‘AGE.NAME’) <> “VAR” Then Say “Name not in memory.” Else Say NAME “is” AGE.NAME “years old.” End 15
  16. 16. System Variables REXX provides two sets of system variables. The first set is SYSVAR. 1. SYSPREF: the prefix assigned to not-fully-qualified dataset names. 2. SYSPROC: the logon procedure for the current session. 3. SYSUID: the user ID of the person logged on. 4. SYSLTERM and SYSWTERM: the length and width of the current terminal screen. In batch, SYSLTERM returns 0 and SYSWTERM returns 132. 5. SYSENV: returns FORE or BACK. 6. SYSICMD: returns the name of the running, implicitly called exec. If the call was explicit, SYSICMD returns null. 7. SYSISPF: returns ACTIVE or NOT ACTIVE. 8. SYSNEST: if the current program was called from another, SYSNEST returns YES; otherwise it returns NO. 9. SYSPCMD: the most recently processed TSO-E command processor. The initial value of SYSPCMD may be EXEC (if the EXEC command was used) or EDIT (if the EXEC subcommand of EDIT was used). 10. SYSSCMD: the most recently processed TSO-E subcommand processor. The initial value of SYSPCMD may be null (if the EXEC command was used) or EXEC (if the EXEC subcommand of EDIT was used). 11. SYSCPU: the number of CPU seconds used this session. 12. SYSSRV: the number of service units used this session. 13. SYSHSM: the status of DFHSM. Returns AVAILABLE or null. 14. SYSJES: the name and level of JES. 15. SYSLRACF: the level of RACF. If RACF is not installed, this is null. 16. SYSRACF: returns AVAILABLE, NOT AVAILABLE, or NOT INSTALLED. 17. SYSNODE: the JES node name. This returns either the JES node name, the string -INACTIVE-, or the string -DOWNLEVEL- if the subsystem is neither JES2 SP4.3 or later, nor JES3 SP5.1.1 or later. 18. SYSTERMID: the terminal ID, or null if batch. 19. SYSTSOE: the level of TSO installed. For OS/390 Version 2, Release 4 and later, SYSTSOE returns 2060. 20. SYSDTERM: If double-byte character set (DBCS) is enabled, returns YES. 21. SYSKTERM: if Katakana character set is enabled, returns YES. 22. SYSPLANG and SYSSLANG: returns the 3-byte primary and secondary language settings. 23. SOLDISP and UNSDISP: show solicited (operator replies) or unsolicited (operator messages) messages on the user’s terminal. YES or NO. 24. SOLNUM and UNSNUM: the size of the message table. 25. MFTIME: show a time stamp with each message. YES or NO. 26. MFOSNM: show originating system name with each message. YES or NO. 27. MFJOB: display the originating job name. YES or NO. 28. MFSNMJBX: did the user request to NOT show the originating job and system names of the message. YES means to NOT show the names; NO means to show them. 16
  17. 17. System Variables: SYSVAR notes 1. SYSVAR can only be used in TSO/E environments (i. e. not VM, OS/2, etc.). 2. SYSPROC has three different responses, depending on where it was called: o Foreground: returns the logon procedure name. o Batch: returns INIT because the job has an initiator. o Started Task: returns the name of the started task. 3. SYSPCMD and SYSSCMD are connected. For example, if you’re running the TEST TSO command (debugging a program), SYSSCMD might return EQUATE (the last TEST command run) while SYSPCMD would return TEST. 4. MFSNMJBX is meant to override MFJOB and MFOSNM. It should not be consistent with them. 5. Some CLIST control variables do not apply to REXX. REXX has “replacements” for these variables. •SYSDATE ===> DATE(“U”) •SYSJDATE ===> DATE(“J”) •SYSSDATE ===> DATE(“O”) •SYSSTIME ===> SUBSTR(TIME(),1,5) •SYSTIME ===> TIME() Examples: •Am I running in the foreground? If SYSVAR(SYSENV) = “FORE” •Am I in ISPF? If SYSVAR(SYSISPF) = “NOT ACTIVE” Then Exit •Who is using the command? SYSVAR(SYSUID) •List your JES node name. SYSVAR(SYSNODE) •How large is my display screen? TL = Strip(SysVar(SYSLTERM)) TW = Strip(SysVar(SYSWTERM)) "Your LTERM displays" TL "lines of" TW "bytes each." 17
  18. 18. System Variables: MVSVAR The second set of system variables are the MVS variables. •SYSAPPCLU: the APPC/MVS logical unit (LU) name. •SYSDFP: the level of MVS/Data Facility Product (MVS/DFP). •SYSMVS: the level of the base control program (BCP) component of z/OS. •SYSNAME: the name of the system your REXX exec is running on, as specified in the SYSNAME statement in SYS1.PARMLIB member IEASYSxx. •SYSOPSYS: the z/OS name, version, release, modification level, and FMID. •SYSSECLAB: the security label (SECLABEL) name of the TSO/E session. •SYSSMFID: identification of the system on which System Management Facilities (SMF) is active. •SYSSMS: indicator whether DFSMS/MVS is available to your REXX exec. •SYSCLONE: MVS system symbol representing its system name. •SYSPLEX: the MVS sysplex name as found in the COUPLExx or LOADxx member of SYS1.PARMLIB. •SYMDEF: symbolic variables of your MVS system. When using SYMDEF, you must supply both the word SYMDEF and the symbolic variable name. Variables must be defined in the SYS1.PARMLIB member IEASYMxx. Exercise #4 Write an exec to print a calendar month, given the name of the month and the name of the first day of the month. For example, CALPRT JANUARY MONDAY. Exercise #5 Write an exec to fill a 3X3 array with “#” by having the user enter a row name (letter) and column number. If the row and column are “equal”, fill the slot with a “$” instead. Print the array at the end. The array should look like this: 1 2 3 A $ # # B # $ # C # # $ 18
  19. 19. Other Performance Concerns Here are some other performance tips. They are not in any particular order. Remember, you get more bang for your buck in a loop than in a single instruction. Don’t sacrifice readability for speed unless you absolutely need to. o Shorter variable names are faster. Use abbreviated names if they’re clear enough. For example, Social_Security_Number is valid, but SSN is just as clear and more efficient. o Drop large stem arrays when you’re done with them. o When known ahead of time, use literals or constants rather than variables. o Don’t use external subroutines in loops. If a subroutine is necessary, code it in the exec. o Code subroutines as close to the call as possible (without sacrificing readability). Use SIGNAL to get around the code, if necessary. o When grouping subroutines, place the most often used routine first. o Consider a subroutine when a large number of statements are needed for something that is often NOT executed. o When calling subroutines or functions don’t specify the defaults, and pass literals rather than variables when possible. o Avoid using functions within functions. For example, a) is more efficient than b) despite using more lines of code: a)PT = right(strip(cmddata),3) b)PL = strip(cmddata); PT = right(PL,3) o Avoid DATE and TIME functions in loops and subroutines if possible. o Instructions are about twice as fast as built-in functions. o Instructions are about 100 times as fast as external commands. 19
  20. 20. Putting It All Together You can combine functions to manage records. Here are some examples. Suppose you want to scan records in JCL to remove a now-defunct symbolic parameter. For this example, the symbolic parameter to remove is CVOL. We don’t care what the value given is, we just want to remove it. In EDIT you can do a FIND on CVOL, then delete characters up to the next comma. Here’s a way to do it automatically in REXX. 1.MCVLOC = Pos("MODCVOL",SYMB) 2.LHLen = MCVLOC - 1 3.RHLen = 71 - MCVLOC 4.LHalf = Substr(Source.SOURCEI,1,LHLen) 5.RHalf = Substr(Source.SOURCEI,MCVLOC,RHLen)) 6.Parse Var RHalf MODCVOL "," RRHalf 7. If Right(Strip(Rhalf),1) = "," 8. Then SYMB = LHalf","RRHalf 9. Else SYMB = LHalf||RRHalf 10.FLen = Length(SYMB) 11.If Right(SYMB,2) = ",," 12. Then SYMB = Substr(SYMB,1,FLen-1) 1. Get the location of CVOL on the line. 2. The length of the line up to CVOL is the location of CVOL minus 1 3. The length of the line from CVOL to the end is 71 minus the location of CVOL because column 72 is the continuation column. 4. The left “half” of the line starts at Byte 1 and continues for the length as found in Step 2. 5. Start the right “half” of the line from CVOL through the end. 6. Break up the right half into the part containing CVOL and the rest by looking for the comma after the CVOL parameter. 7. If the last non-blank character in the right half is a comma: 8. Yes, it was, add the comma when concatenating the halves together; 9. No comma, concatenate without one. 10. Just in case CVOL was the last thing on the line, get the length of the entire line. 11. If CVOL was the last thing on the line, the added comma for the concatenation was wrong. Remove it by taking all the characters up to, but not including, the comma (see 12 also). 20
  21. 21. Combinations of Functions continued Suppose you want neater JCL. Your idea is to force the EXEC or DD to always make room for an 8-byte step name or DD name, followed by one space, followed by either “DD” or “EXEC.” All other arguments follow the “DD” or “EXEC” after one space. 1. NLine = "//"Copies(" ",70) 2. Select 3. When WordPos("EXEC",Source.SOURCEI) ¬= 0 Then Do 4. Parse Var Source.SOURCEI SNX EXECS PROCS 5. NLine = Overlay(SNX,NLine,1) 6. NLine = Overlay("EXEC",NLine,12) 7. NLine = Overlay(PROCS,NLine,17) 8. End 9. When WordPos("DD",Source.SOURCEI) ¬= 0 Then Do 10. Parse Var Source.SOURCEI DDNX DDS DDARGS 11. NLine = Overlay(DDNX,NLine,1) 12. NLine = Overlay("DD",NLine,12) 13. NLine = Overlay(DDARGS,NLine,15) 14. End 15.End 16.Source.SourceI = NLine 1. Create a default JCL line of “//” followed by 70 blanks. 2. Start a Select group. 3. If this is an EXEC statement (“EXEC” surrounded by blanks), do steps 4 – 7. 4. Parse the line into “//<stepname>”, the word EXEC, and anything following EXEC. 5. Put the “//<stepname>” on the new line beginning in Column 1. 6. Put the word “EXEC” on the line in position 12. This leaves room for the “//” plus an 8-character step name. 7. Put the remainder of the EXEC line one space after the word EXEC. 8. End the EXEC group. 9. If this is a DD statement (“DD” surrounded by blanks), do steps 10 – 13. 10. Parse the LINE variable into the “//<DD name>”, the word DD, and anything following DD. “DD name” can be all blanks. 11. Put the “//<DD name>” on the new line beginning in Column 1. 12. Put the word “DD” on the line in position 12. This leaves room for the “//” plus an 8-character DD name. 13. Put the remainder of the DD line one space after the word DD. 14. End the DD group. 15. End the Select group. 16. Set the JCL line to its new, formatted version. 21
  22. 22. Combinations of Functions continued At times, people doing data security need to scan their datasets to see what is and what is not protected, and what profiles are in WARNING mode (i.e. the profile exists but is not observed). Here is some code to do this check against any dataset level. Please note the code is split onto two pages, with the explanation on a third page. 1. /* REXX Checks datasets under LEVEL for RACF. */ 2. Arg LEVEL 3. GOODPROF = 0; WARNPROF = 0; NOPROF = 0; TotalDSN = 0 4. foo = outtrap(LCL.) 5. "LISTC LEVEL("LEVEL")" 6. foo = outtrap(Off) 7. OutLine.1 = "The profile name, its UACC, and warning mode”, “is displayed." 8. OutLine.2 = "'????' is displayed if you don't have access”, “to list the profile.” 9. OutLine.3 = "A warning message is displayed if there is no”, “profile." 10.OutLine.4 = “” 11./* Skip 5 through 13 for final tallies, 14 is header. */ 12.OutLine.14 = Copies(" ",79) 13.OutLine.14 = Overlay("DSN",OutLine.14,1) 14.OutLine.14 = Overlay("PROFILE",OutLine.14,36) 15.OutLine.14 = Overlay("UACC",OutLine.14,64) 16.OutLine.14 = Overlay("WARNING?",OutLine.14,72) 17.OutIX = 14 18.Do L = 1 to LCL.0 19. Select 20. When Word(LCL.L,1) = "NONVSAM" Then RADS = Word(LCL.L,3) 21. When Word(LCL.L,1) = "CLUSTER" Then RADS = Word(LCL.L,3) 22. When Word(LCL.L,1) = "AIX” Then RADS = Word(LCL.L,3) 23. Otherwise Iterate 24. End 25. foo = outtrap(RI.,'*') 26. "LD DA('"RADS"') GENERIC ALL" 27. If Word(RI.1,1) = "ICH35003I" Then Do /* No profile */ 28. NOPROF = NOPROF + 1 29. OutIX = OutIX + 1 30. OutLine.OutIX = “Dataset" RADS "not protected by RACF." 31. Iterate L 32. End 33. Do I = 1 To RI.0 34. Parse Var RI.1 . . . PROFILE . 35. Parse Var RI.5 . . UACC WARN . 36. End 37. If RI.0 = 1 Then Do 38. PROFILE = "????"; WARN = "????"; UACC = "????" 39. End 22
  23. 23. RIB exec Page 2 This section of the exec determines the profile type and if it is in warning. Finally, the exec creates an output report. 40. Select 41. When WARN = "NO" Then Do 42. W = "HARDCHK"; GOODPROF = GOODPROF + 1 43. End 44. When WARN = "YES" Then Do 45. W = "WARNING"; WARNPROF = WARNPROF + 1 46. End 47. Otherwise W = "UNKNOWN" 48. End 49. OutIX = OutIX + 1 50. OutLine.OutIX = Copies(" ",79) 51. OutLine.OutIX = Overlay(RACFDS,OutLine.OutIX,1) 52. OutLine.OutIX = Overlay(PROFILE,OutLine.OutIX,36) 53. OutLine.OutIX = Overlay(UACC,OutLine.OutIX,64) 54. OutLine.OutIX = Overlay(W,OutLine.OutIX,72) 55.End L /* Do group from Line 19 */ 56.UNKNOWN = TotalDSN - GOODPROF - WARNPROF - NOPROF 57.PROFILES = GOODPROF + WARNPROF 58. OutLine.5 = "RACF report for dataset level" LEVEL":" 59. OutLine.6 = "" 60. OutLine.7 = "TOTAL DATASETS: ", 61. Right(" "TotalDSN,6) 62. OutLine.8 = "DATASETS WITH PROFILES: ", 63. Right(" "PROFILES,6) 64. OutLine.9 = "DATASETS IN WARNING: ", 65. Right(" "WARNPROF,6) 66. OutLine.10 = "PROTECTED DATASETS: ", 67. Right(" "GOODPROF,6) 68. OutLine.11 = "UNPROTECTED DATASETS: ", 69. Right(" "NOPROF,6) 70. OutLine.12 = "INSUFFICIENT AUTHORITY: ", 71. Right(" "UNKNOWN,6) 72. OutLine.0 = OutIX 73."ATTRIB FB80 RECFM(F B) LRECL(80)" 74.OutDSN = LEVEL".DATA" 75. "DELETE" OUTDSN 76. "ALLOC DA("OUTDSN") F(OUTFILE) NEW SPACE(5 5) TRACKS”, 77. “REUSE USING(FB80) RELEASE" 78."EXECIO " OutLine.0 " DISKW OUTFILE (STEM OutLine. FINIS" 79. Say 80.Say "Output written to" OutDSN"." 81.“FREE F(OUTFILE) ATTR(FB80)” 82.Exit 23
  24. 24. RIB exec Page 3 A brief explanation of some of the code follows. Line 3 initializes all counter fields for the output totals. Lines 4 – 6 capture LISTC LEVEL data to get the datasets to check. Lines 7 – 16 set the headers. Note several lines are left blank at this time. This makes room for the summary lines, whose values cannot be determined until the exec ends. The OVERLAY function lets me align the headers to specific columns. Finally, Line 17 points the output stem index to the first blank line after the dataset report header line. The select group at Line 19 keeps non-VSAM and VSAM dataset names, and skips all other lines from the LISTC. Line 26 does the RACF command LISTDSD to get the profile information. Lines 27 – 32 writes a note if there is no profile to list. Lines 33 – 36 pull the useful information from the profile. Lines 37 – 39 writes a note if you don’t have the authority to list the profile. The select group at Line 40 notes whether the profile is in WARNING or not. Lines 49 – 54 format the report line for this dataset. Lines 56 – 72 format the summary section. This section uses the blank lines we left back on lines 7 – 16. The remaining lines allocate the output dataset and write the report stem to the output dataset. 24
  25. 25. This Page Intentionally Left Blank 25
  26. 26. Appendix A: A Few Simple Edit Macros These are a few, simple edit macros to show how many macros are just a string of edit commands you might use frequently. Each is explained in some detail. Initial Edit Macro When starting any edit session, ISPF can run a macro on the dataset before turning control over to you. This macro is called an initial edit macro. You can specify an initial macro on the edit screen. If you want the macro to run every time, though, it’s just as easy to tell ISPF what it is. This is done in two easy steps. STEP 1: Code the macro. Here is an example. The code is COURIER; comments are made in Arial and underscored. Address ISREDIT "MACRO" "PROFILE UNLOCK" - unlock the edit profile of the current dataset. "BOUNDS" - Reset BOUNDS to the full LRECL. "NULLS ON" - Lets me insert (ignores spaces on end of lines). "HILITE ON" - Turns on highlighting CURMBR = "" "(CURMBR) = MEMBER" Checks if editing a PDS. If so, turn on STATS. If CURMBR <> "" Then "STATS ON" "RESET" - Clear all messages. Exit STEP 2: set the initial macro. You must use the VPUT Dialog Manager service to do this. So you don’t have to remember (or know) how to do that, run this command: SETZIMAC <name of your macro> Your CLIST or REXX macro must be in the SYSPROC or SYSEXEC concatenation. If your macro is a program, it must be in the STEPLIB concatenation. Note: some ISPF dialogs run in their own application ID. Should you enter a dialog and your edit macro doesn’t work, try running SETZIMAC again. 26
  27. 27. More Simple Edit Macros These two edit macros change all upper-case characters to lower-case, or vice-versa. Note they’re just a single edit command. This could just as easily be set to a function key. Address ISREDIT "MACRO" "C P'>' P'<' ALL" Exit Address ISREDIT "MACRO" "C P'<' P'>' ALL" Exit This macro excludes all lines, then does a find on a passed string. The purpose is to see only what you want to see. Note the passed parameter may include quotes, column numbers, and other FIND operands (except ALL, of course). For sample calls, see below the macro. Address ISREDIT "MACRO (FPARM)" "EXCLUDE ALL" "FIND ALL "FPARM Exit 0 Sample calls: XFS ISDODEA shows all occurrences of this TSO user ID. XFS WORD EXEC shows all EXEC lines in a JCL dataset. XFS WORD DD 13 71 shows all DD statements not aligned at column 12. 27
  28. 28. Appendix B: Solutions to Exercises Exercise 1, page 6 Modify the exec you wrote in Exercise #28 to use a noun switch instead of expression tests when deciding what to do with the dataset. /* REXX passes args to LISTD with program stack. */ Say "Enter dataset name to pass to LISTD."; Pull DSN If Length(DSN) > 44 Then Do Say "DSN" DSN "longer than 44 bytes." Exit End If SYSDSN(DSN) = “OK” Then DSNExists = 1 Else DSNExists = 0 If DSNExists Then Do Queue DSN DUMMY = Prompt("ON") "LISTD" End Else Say DSN "does not exist." 28
  29. 29. Exercise 2, Page 7 Modify the exec you wrote in Exercise #16 from the first course to accept the unit and amount values as keywords. Only accept two arguments. If one argument is bad, display the syntax and exit. /* REXX Metric Converter */ Arg Parm.1 Parm.2 Do I = 1 to 2 Select When Left(Parm.I,1) = "U" Then Parse Var Parm.I Junk "(" UNIT ")" When DataType(Parm.I) = "NUM" Then AMT = Parm.I Otherwise Do Say "Invalid parm entered: " Parm.I"." Say "SYNTAX : CNVT UNIT(...) and a number." Say "UNIT must be one of liter, quart, mile, or", "kilometer." Exit End End End Select When UNIT = "LITER" Then Say AMT UNIT "is" AMT*1.057 "quart." When UNIT = "QUART" Then Say AMT UNIT "is" AMT*.946 "liter." When UNIT = "MILE" Then Say AMT UNIT "is" AMT*1.6 "kilometer." When UNIT = "KILOMETER" Then Say AMT UNIT "is" AMT*.625 "mile." End 29
  30. 30. Exercise 3, Page 11 Write an exec to parse the date into month, day, and year. Display them in the following formats: MM/DD/YY, DD/MM/YY, YY/DD/MM, and YY/MM/DD. The command to get the USA-style date (MM/DD/YY) is DATE(”U”). /* REXX Date formatter */ Parse Value Date("U") With Month "/" Day "/" Year "CLRSCRN" Say "Today's date is: " Date("U")", or" Say "USA: " Month"."Day"."Year";" Say "European: " Day"."Month"."Year";" Say "Year, then day: " Year"."Day"."Month";" Say "Ordered: " Year"."Month"."Day"." 30
  31. 31. Exercise 4, Page 19 Write an exec to print a calendar month, given the name of the month and the name of the first day of the month. Assume the first day of the week is Sunday. For example, CALPRT JANUARY MONDAY should start the calendar on Monday, January 1. Use the CENTER function to display the month name. /* REXX print a calendar month. */ Arg Month Day /*-------------------------------* * Init month table with blanks. * *-------------------------------*/ Do I = 1 to 6 Do J = 1 to 7 Week.I.J = " " End J End I /*------------------------------------------* * Init fields: * * WeekDay is the day of the week; * * DayNumber is the day of the month; * *------------------------------------------*/ WeekDay = 1; DayNumber = 1 /*---------------------------------* * Set the first day of the month. * *---------------------------------*/ Select When Day = "SUNDAY" Then WeekDay = 1 When Day = "MONDAY" Then WeekDay = 2 When Day = "TUESDAY" Then WeekDay = 3 When Day = "WEDNESDAY" Then WeekDay = 4 When Day = "THURSDAY" Then WeekDay = 5 When Day = "FRIDAY" Then WeekDay = 6 When Day = "SATURDAY" Then WeekDay = 7 Otherwise Do Say "Invalid day" Day"." Exit End End Continued on the next page 31
  32. 32. Exercise 4, Page 19, answer continued /*---------------------------------------* * Load the table. Start at the current * * weekday, then reset it after Week 1. * * Skip out when at end of month. * *---------------------------------------*/ Do WeekNumber = 1 to 6 Do WeekDay = WeekDay to 7 If Length(DayNumber) = 1 Then DayWord = " "DayNumber Else DayWord = DayNumber Week.WeekNumber.WeekDay = DayWord DayNumber = DayNumber + 1 If DayNumber > DayLimit Then Signal EndOfMonth End WeekDay = 1 End EndOfMonth: "CLRSCRN" Say Say Center(Month,21) Say " S M T W T F S" Do WeekNumber = 1 to 6 Say " "Week.WeekNumber.1 Week.WeekNumber.2, Week.WeekNumber.3 Week.WeekNumber.4 Week.WeekNumber.5, Week.WeekNumber.6 Week.WeekNumber.7 End 32
  33. 33. Exercise 5, Page 19 Write an exec to fill a 3X3 array with “#” by having the user enter a row name (letter) and column number. If the row and column are “equal”, fill the slot with a “$” instead. Print the array at the end. The array should look like this: 1 2 3 A $ # # B # $ # C # # $ HINT: you will need the TRANSLATE function to do this exec cleanly. Guidance is provided if desired. /* REXX advanced course, exercise 6 */ Do I = 1 to 3 Select When I = 1 Then Row = "A" When I = 2 Then Row = "B" When I = 3 Then Row = "C" End Do Col = 1 to 3 Array.Row.Col = " " End Col End I /*------------------------------------* * SLOTS is the number of full slots; * * ArrayFull says the array is full. * *------------------------------------*/ Slots = 0; ArrayFull = 0 Continued on the next page 33
  34. 34. Exercise 5, Page 19 continued /*---------------------------* * Loop until array is full. * *---------------------------*/ Do Until ArrayFull Say "Enter a row (A, B, or C) and a column (1, 2, or 3)." Pull Row Col Say If Pos(Row,"ABC") = 0 Then Do Say "Row" Row "invalid, try again." Iterate End If Pos(Col,"123") = 0 Then Do Say "Column" Col "invalid, try again." Iterate End If Array.Row.Col ¬= " " Then Do Say Row "and" Col "already used, try again." Iterate End RowNum = Translate(Row,"123","ABC") If RowNum = Col Then Array.Row.Col = "$" Else Array.Row.Col = "#" Slots = Slots + 1 If Slots = 9 Then ArrayFull = 1 End /*--------------------* * Display the array. * *--------------------*/ Row = "A" Say "A" Array.Row.1 Array.Row.2 Array.Row.3 Row = "B" Say "B" Array.Row.1 Array.Row.2 Array.Row.3 Row = "C" Say "C" Array.Row.1 Array.Row.2 Array.Row.3 34