Exercise 7. Extract macro from MACLIB.

The assignment

Write a procedure that extracts a specific macro from a MACLIB.  Of course, you could do it with the aid of some CMS commands (like PUNCH or MACLIST), but this time we expect you to use REXX File I/O methods.

Your procedure can use any of the File I/O methods explained in the text.  Maybe it's good to review the advantages and disadvantages of the different methods before to choose one for the exercise.

The command format should be:

   GETMAC maclib_fn macro

where maclib_fn is the filename of the macro library, and macro is the name of the macro you want to extract.

Note
You can find plenty of MACLIB files on your S-disk.  You should 'stress-test' your procedure on one of the largest ones (DMSOM MACLIB).

Your procedure should thus perform these logical steps:

  1. Accept the parameters and verify the completeness.
  2. Verify if the MACLIB exists on any accessed disk.
  3. Read the first record of the MACLIB and extract the pointer to the index.
  4. Locate and extract the index record that contains the macro name (if not found, error message to the user)
  5. Parse the index record to extract the starting record number for the macro.
  6. Read the records of the macro till the separator is found.
  7. Put the macro in a file macro MACRO on your A-disk.

Commented solution.

   ! /**********************************************************************/
   ! /* GETMAC - Created by RUDI                                           */
   ! /* Function : get macro out of maclib                                 */
   ! /* Parameters : maclib and macro name                                 */
   ! /**********************************************************************/
 1 ! Address Command
 2 ! parse upper source . . myname .
 3 ! parse upper arg maclibname macroname .
 4 ! if maclibname='' then call error_exit 01,'Give a valid maclib name'
 5 ! if macroname='' then call error_exit 02,'Give a valid macro name'
 6 ! address '' 'ESTATE' maclibname 'MACLIB *'
 7 ! if RC^=0 then call error_exit rc,maclibname 'MACLIB * does not exists'
   !    /**************** get index pointer from first record ***********/
 8 ! 'EXECIO 1 DISKR 'maclibname ' MACLIB * (FINIS VAR' firstline
 9 ! if RC<>0 then call Error_Exit RC,'cannot read 'maclibname' MACLIB.'
10 ! parse var firstline vermac 7 13 indpoint 17 .
11 ! if vermac<>'LIBPDS' then call error_exit 03,maclibname' is not a maclib'
12 ! macroname=left(macroname,8)
   !    /**** read index from indpoint till end until macro found *******/
13 ! 'EXECIO * DISKR 'maclibname' MACLIB * 'c2d(indpoint)' (FINIS STEM LIN.'
14 ! line=''
15 ! do i=1 to lin.0 by 1
16 !    line=line''lin.i
17 ! end
18 ! parse var line (macroname) +8 macp +8 nextmacnam +8 nextmacp +8
19 ! if macp='' then call error_exit 28,macroname' not found in 'maclibname
20 ! mlins=c2d(nextmacp)-c2d(macp)-1
   ! /***************** read the macro *************************************/
21 ! 'EXECIO 'mlins' DISKR 'maclibname' MACLIB * 'c2d(macp)' (FINIS STEM LIN.'
   ! /**** write the macro to a file ***************************************/
22 ! 'EXECIO 'mlins' DISKW 'macroname' MACRO A (FINIS STEM LIN.'
23 ! call Error_Exit 00,'All done'
   ! /****************** Exit routine ********************************/
24 ! Error_Exit:
25 !  parse arg retc,errmsg
26 !  if errmsg<>'' then say myname':' errmsg
27 !  exit retc
  1. Lines 4 and 5. Good parameter checking, but the feedback messages could be a bit more explicit.  By the way, is the user supposed to know what names are "valid" ?  We suggest this:
      if maclibname='' then call error_exit 01,,
         'Parameters missing : we expect a MACLIB name and a macro name'
      if macroname='' then call error_exit 02,,
         'Parameter missing : we need a macro name as well'
    

    Or, easier, as both parameters are required anyway:

      if macroname='' then call error_exit 01,,
         'Parameter(s) missing : correct format is GETMAC maclib macro'
    

    At the same time, the message informs about the correct syntax.

  2. Line 6. We repeat ourselves... why prefixing with address '' as there is a address command at the top of the procedure ?
  3. Lines 8, 13. It's better not to close the file (FINIS), as it must still be used later on.  Opening a file requires system resources also, such as locating the file, creating buffers, and so on.  On the other hand, the file should be closed when errors occur, but this should be included in the exit routine.
  4. Line 11. Good test, it is indeed not certain that a file with filetype MACLIB is really a MACLIB that contains a valid pointer to an index record !
  5. Line 13. It would have been possible to use the LOCATE option of EXECIO to find the index record with the macroname, instead of using plain REXX.
  6. Lines 14 to 17. ...and, it is needless to concatenate all index records into variable line.  This is overhead due to storage management.  With a simple pos(macroname,line.i)>0, the record with the macro would be detected immediately.
  7. Line 19. Although this is an intelligent use of parse the procedure will not work in all cases.  Indeed, when the last macro of the library has to be extracted, there is no subsequent macro anymore, and thus, line 20 will result in a negative number, giving trouble to the subsequent EXECIO command.  A safe solution is to look for the separator record...
  8. Lines 21 and 22. Good use of EXECIO, correct FINIS option this time. 
  9. Line 23. The return code of EXECIO should be tested here too.
   ! /***********************************************************************/
   ! /* GETMAC   - Created by Valerie                                       */
   ! /***********************************************************************/
 1 ! address command
 2 ! parse upper arg maclib_fn macro . 
 3 ! select
 4 !   when maclib_fn='' then call errexit rc,'You forgot the filename',
 5 !                          'of the maclib!'
 6 !   when macro='' then call errexit rc,'You forgot the macro name!'
 7 !   when length(maclib_fn)>8 then call errexit rc,'The filename of',
 8 !                               'the maclib is too long!','Try again'
 9 !   when length(macro)>8 then call errexit rc,'The name of',
10 !               'the macro you want to extract is too long!','Try again'
11 !   otherwise nop
12 ! end
13 ! 'ESTATE' maclib_fn 'MACLIB *'
14 ! if rc<>=0 then call errexit rc,"Didn't find" maclib_fn "on any",
15 !                     "accessed disk","Try again"
16 ! 'EXECIO 1 DISKR' maclib_fn 'MACLIB * (MARGIN 13 16 VAR RECC'
17 ! r=c2d(recc)
18 ! 'EXECIO 1 DISKR 'maclib_fn 'MACLIB *' r '(VAR MACC'
19 ! parse var macc (macro) mac1 +8 macadr +8
20 ! if mac1='' then call errexit 5,'The macro' macro 'does not exist'
21 ! 'PIPE COMMAND ERASE' macro 'MACLIB A'
22 ! t=c2d(macadr)
23 ! v='61FFFF61'x
24 ! 'EXECIO * DISKR' maclib_fn 'MACLIB *' t+1 '(LOCATE &'v'& STEM Z.'
25 ! if rc¬=0 then call errexit rc,'does not exist'
26 ! parse var z.2 z .
27 ! 'EXECIO' z ' DISKR' maclib_fn 'MACLIB *' t '(FINIS STEM D.'
28 ! 'EXECIO' z 'DISKW' macro 'MACRO A (FINIS STEM' D. 
29 ! if rc<>=0 then call errexit rc,'The write to your A-disk was not',
30 !                               'succesfull'
31 ! else say 'The file' macro 'MACRO is created on your A-disk'
32 ! exit
33 ! ERREXIT:
34 ! parse source . . myname mytype . 
35 ! do n=2 to arg()
36 !   say myname mytype ':' arg(n)
37 ! end
38 ! if arg(1)<>='RC' then do
39 !   'FINIS' maclib_fn 'MACLIB' fn
40 !    exit arg(1)
41 ! end
42 ! else exit
  1. Perfect use of the general exit routine, with an extra FINIS to close the file in case of errors.
  2. Lines 7 to 10.  It is needless to test for the length of macro or library names.  When you issue CMS commands that require filenames, they are truncated to 8 characters anyway if you issue the command from the Ready; state. 
  3. Lines 17 and 18 could be combined to avoid the creation of the intermediate variable r).  Similarly, for the variables v and t at lines 22 and 23.
  4. There is a logic error in this procedure.  The first record contains a pointer to the index record(s).  There can be more than one index record, and the macro that is searched for can be on any of these records.  At line 18, the remaining records of the file should be read, followed by a search until the macroname is found.  An even easier solution is of course to use the LOCATE option of EXECIO to extract only the record containing the required macro-name.
  5. Line 21.  This is an overkill.  Using PIPE to issue just one ERASE command is heavy and should be avoided.  We know, is is to hide the File not found message, but it's still cheaper to use the SET CMSTYPE HT and SET CMSTYPE RT commands around the ERASE command than to use the PIPE command.  For the PIPE,

    This is definitely agains our rule that says to avoid switching between environments if it is not required.

Our solution

This is our solution for this exercise:

 /***********************************************************************/
 /* Procedure GETMAC                                                    */
 /* Function : Get a macro from a MACLIB file                           */
 /* Format : GETMAC maclib_name macro_name                              */
 /***********************************************************************/
 address command
 parse upper arg maclib macro . 
 if maclib='' then call errexit 8,'Missing parameters, format is',
                                 'GETMAC maclib_name macro_name'
 if macro='' then call errexit 8,'Missing macro name, format is',
                                 'GETMAC maclib_name macro_name'
 maclib=maclib 'MACLIB *'
 'STATE' maclib                                  /* does maclib exist ? */
 if rc<>0 then call errexit rc,maclib 'MACLIB not found on any disk'
 'EXECIO 1 DISKR' maclib '(MARGIN 13 16 VAR IDX'    /* get ptr to index */
 if rc<>0 then call errexit rc,'Problems reading 'maclib' MACLIB file'
                                                     /* then read index */
 'EXECIO * DISKR' maclib c2d(idx) '(LOCATE /'macro'/ STEM IDX.'
 if rc<>0 then call errexit rc,'Macro 'macro' not found in library',
                               'or problems reading 'maclib' MACLIB file'
 parse var idx.1 . (macro) . +8 macrec +8 .
                                                /* search for delimiter */
 'EXECIO * DISKR' maclib c2d(macrec) '(LOCATE &'"61FFFF61"x'& STEM EOM.'
 if rc<>0 then call errexit rc,'Problems extracting macro 'macro
                                                       /* extract macro */
 'EXECIO' word(eom.2,1)-1 'DISKR' maclib c2d(macrec) '(FINIS STEM MAC.'
 if rc<>0 then call errexit rc,'Problems extracting macro 'macro
 'ERASE' macro 'MACRO A'
 'EXECIO' mac.0 'DISKW' macro 'MACRO A (FINIS STEM MAC.'    /* write it */
 if rc<>0 then call errexit rc,'Problems creating 'macro' MACRO on disk A'
          else call exit 0,macro 'MACRO A successfully created'
 /***********************************************************************/
 ERREXIT:
 EXIT:
  parse source . . myname . 
  do n=2 to arg() ; say myname':' arg(n) ; end n
  exit arg(1)

Use the backward navigation of your browser to return to the assignments.