Solution to Exercise 1

This was the assignment...

Exercise : Enhance PROFILE XEDIT

When you edit a new procedure, it is nice to have it initialized with some statements that you always need and with a starting comment box containing the name, date of creation and function of the procedure.

The start of an EXEC could contain something like this :

 /*********************************************************************/
 /* PROCNAME - Created by USERID on 18 Apr 2000 at 12:12:17           */
 /* Function :                                                        */
 /* Parameters :                                                      */
 /*********************************************************************/
 address command
 parse upper source . . myname mytype . syn .
 parse arg params '(' options

followed by a series of empty records so that you can immediately start your coding. 

If you want, you can also make that our general error exit routine is included at the end of your file.

Adapt the PROFILE so that this is executed for a new procedure.  You will be faced with some problems such as how to detect if it is a new file.  Try to find the solution.

A second exercise was also suggested:

Exercise : Further enhance PROFILE XEDIT

In the previous exercise, you used your User-id in the first comment line for a new procedure.  But this can be meaningless. USER0005 doesn't say much to others.  So, it would be nice to have somehow your full name (and optionally your address) in the prologue of the procedure.

This could give something like :

 /*********************************************************************/
 /* PROCNAME - Created by Guy De Ceulaer on 18 Apr 2000 at 12:12:17   */
 /*            IBM Belgium - VM Support                               */
 /*            Square Victoria Regina, 1 - B 1210 Brussels, Belgium   */
 /* Function :                                                        */
 /* Parameters :                                                      */
 /*********************************************************************/
 ...

Where can this information come from ?  The most standard way of storing this information in VM/ESA is to put it in a NAMES file.

Steps

  1. Issue the NAMES command and complete the panel with your identifications.  Use me as your nickname.

  2. Edit the file that is created by the NAMES command and study the contents of it (that is if you don't know yet what a NAMES file looks like).

  3. Issue the command NAMEFIND :nick myself  from the CMS command line and look at the output.  You see that you can extract data from the names file based on a keyword (:nick) in this case.  But you can use any keyword tag and you can also limit your output to those fields you want to see.  Study the NAMEFIND command (via HELP) to see how you can use it in procedures to extract data from a NAMES file.

  4. Adapt your PROFILE XEDIT to make the changes as suggested above.

Our comments

As said in the preface to this course, we attach great importance on the performance aspects of procedures.  You can argue that a PROFILE XEDIT is private, but if you are a systems programmer (or simply a good programmer), your version could become promoted to a public disk and then it is executed several thousands of times a day.  This is true for any well-written or designed procedure. 

What's more, you'll see that in many cases the best performance is combined with shorter or simpler coding, so that you gain on both sides.  It's therefore just a matter of knowing the possibilities of REXX and VM/ESA in general. 

Most of our former students failed to present a near-perfect solution.  With perfect, we mean close to our REXX coding standards and recommendations, so we will review the most common errors and show how to solve the problems posed by this exercise.

This exercise forced you to find a solution for following problems:

  1. How can we determine if a file is new or not ?
  2. How do we get variable information, like date and time, userid, etc. ?
  3. How can we add the records to the file ?
  4. How can we extract information from a NAMES file ?

How to determine if a file is new

We are pretty sure that most of you tried to solve this problem by using a CMS command, especially the STATE command.

There is however a by far more elegant solution.  XEDIT knows about the size of the file, as you can see on the first line of your XEDIT screen (size=nnnn).

We can therefore ask XEDIT to return this information to our procedure via the EXTRACT /SIZE/ command.  This command allows you to ask about almost anything XEDIT knows (i.e. everything you can QUERY for in XEDIT and even more than that).  This command makes the combination of REXX and XEDIT so powerful. 

When you extract an element, you get the result back in a REXX array(footnote 1) with the same stem-name as the element you're extracting.  Element 0 of the array contains the number of returned elements.  Depending on the query, you can get one or more elements returned. 

For example, if you use EXTRACT /CASE/, then you will get following array elements:

For our exercise, we have to ask XEDIT about the SIZE of the file.  The EXTRACT /SIZE/ command will return the number of records of the file in element size.1.  If the number is zero, then we can expect it to be a new file (or an empty SFS file). 

MoreInfo

You can combine several parameters in one EXTRACT subcommand, so that you have only one call to XEDIT (e.g. COMMAND EXTRACT /CASE/SIZE/).  This is of course better for the performance and makes you procedures a bit shorter. 

Tip

The goodies provide a macro that helps you to see what values EXTRACT will return.  Often this information is enough so that you don't need to look up the HELP anymore. 

The goodie is the QUERY XEDIT macro that replaces the standard XEDIT command and can perform many enhanced functions (issue the HELP XEDIT QUERY command while you have access to the goodies disk to have a complete description).  In order to have this goodie return the EXTRACT information, you have to set up an XEDIT synonym, as follows:

   SET SYN QEXTRACT 2 MACRO QUERY

This will then allow you to issue commands like the following:

   QExtract /CASE
   QExtract /CURLine

Many of our other goodies also require synonyms to be defined.  You might therefore consider to include these synonym definitions in your PROFILE as well.  To ease this task, yet another goodie, SETSYN XEDIT is provided.  It sets all the synonyms required for the goodies.  So it suffices to include a MACRO SETSYN in your profile for XEDIT.

How to get access to variable information ?

For our exercise, we need access to the current date and time, our user-id, and so forth.

To know your user-id in a procedure, several possibilities exist:

  1. IDENTIFY.  This command returns one line in the stack:
        MYUSER AT MYNODE VIA RSCS WEDNESDAY 12/04/00 07:03:33
    

    It has the advantage of returning several variables we may need in this exercise, but on the other hand, we always try to avoid the CMS stack because:

    We will come back to stack management in a later lesson.
     

  2. A second alternative, at least for this specific problem, is to get the information from CP.  We have already seen examples of the diag() function.  CP definitely knows about our userid, and also provides date and time to the virtual machine, so we might ask it as follows:
        parse value diag(8,'QUERY USERID') with myuser . mynode . '15'x
    

    Similarly, the date and time could be parse from the result of a CP QUERY TIME command.

    We will learn more about the parse instruction in lesson 3, so, we will not extend on it here, as there is yet a by far more elegant solution...
     

  3. We can get the required information directly from REXX.  The userid as the result of the userid() function (no parameters), the date and time via the date() and time() functions respectively.  You might review the help for these functions now, as there are several formats that can be returned for the date and the time.

One could argue that the PROFILE XEDIT will never be used in a condition where there is something in the stack.  This is not true.  Consider this example:

  /* Use XEDIT to change JOBNAMEs in JCL files */
  address command
  parse upper arg jobname .
  queue 'COMMAND CHANGE /JOBNAME/'jobname'/* *'
  queue 'COMMAND FILE' jobname 'JCL A'
  'XEDIT STANDARD JCL A'
  say 'You can now submit' jobname 'JCL'
  exit

This is a typical use of XEDIT in procedures.  XEDIT is used here to change parts of records in the file.

The problem however is that the XEDIT command is used without the NOPROF option in order to bypass the users' profile (footnote 2).  So, our PROFILE XEDIT will be executed, even before the queued commands are read from the stack and thus, if we would have used IDENTIFY (STACK, our userid would become COMMAND as this is the first word of the first line in the stack... 

How can we add (empty) records.

Quite some students used the method whereby records are stacked. 

This method has the same dangers as explained above (desynchronized records, and such), and is an inefficient detour.  These students may have inherited this method from the early VM days when we had only EDIT, or, in analogy to the technique used in the procedure changing the jobname shown above. 

However, if you understand that XEDIT is already up and alive when your PROFILE XEDIT starts its execution, then it is easy to understand that you can simply issue XEDIT commands to insert the required records.  So, in this case, you can use the add or input subcommands, thereby avoiding the stack. 

input may seem strange here, as you might expect XEDIT to switch to input mode when you issue the command, thereby pausing your procedure until you enter a null line.  This is not true however.  If the input subcommand has parameters, then XEDIT will insert the record and return to the user (your procedure in this case).  If you omit parameters, then of course, you will enter the input mode.

CMS Pipelines can also be used in XEDIT macros, and could be of some help in our case here, but this is a subject of our other telecourse.

How to extract data from a NAMES file ?

NAMEFIND is the command to use in order to extract information from a NAMES file.  Review its HELP if needed.

As the NAMEFIND command allows an option to return the result in a REXX stem, as opposed to the stack, we will of course again opt for this solution.

You have however to pay attention when using the STEM option for NAMEFIND.  If you issue the command

    address COMMAND 'NAMEFIND :USERID' userid() '(STEM MYNICK.'
    parse value mynick.4 with ':name ' myname
    parse value mynick.5 with
          ':addr 'aline.1 ';' aline.2 ';' aline.3 ';' aline.4 . 

then the complete NAMES file entry is put in the REXX stem. 

The example here supposes the name of the user is in element 4, while the address is in element 5.  This is not safe, as the number of elements in a NAMES entry can change over time.  See what we may get:

  address '' 'NAMEFIND :NICK MYSELF (STEM MYNICK.'
     /* mynick.0 = 6                               */
     /* mynick.1 = :nick MYSELF                    */
     /* mynick.2 = :userid DECEULAE                */
     /* mynick.3 = :node BEVM                      */
     /* mynick.4 = :name De Ceulaer                */
     /* mynick.5 = :phone (02) 225.21.11           */
     /* mynick.6 = :addr Square Victoria Regina, 1 */

In this case, the phone number is defined too, and thus it gets returned as fifth element, while the address is in element 6 now.  To get to the correct data, you would need to parse each of the elements and control the tag element names.

A good use of the NAMEFIND command therefore requires to be explicit in the elements (tags) to be returned, like in this example:

  address '' 'NAMEFIND :NICK MYSELF :NAME :ADDR (STEM MYNICK.'
     /* mynick.0 = 2                               */
     /* mynick.1 = De Ceulaer                      */
     /* mynick.2 = Square Victoria Regina, 1       */

By specifying explicitly the elements of the entry that you want to receive, you know exactly what you will get, and the order is the same as for the specified parameters.  If an element is not defined in the NAMES entry, then you still get that element, but with a null-string as value.  Remark also that the tag names are not part of the returned result, so you don't have to strip them off. 

Other common errors

Sinn against our rule

We have seen many programs that do NOT code the (sub-)commands as literal strings (inside quotes).  Some even use a strange mix, like :

    EXTRACT '/SIZE/'

This particular example is understandable as REXX would try to divide SIZE from EXTRACT if it was not coded as a literal...

We insist and repeat our very important coding rule: Put everything that is not a REXX variable or statement inside quotes, and preferably, code (sub-)commands in uppercase to highlight them !

There are several reasons for that :

The comment box

The assignment suggested to create a closed comment box at the top of the procedure, like this one:

   /****************/
   /*  text        */
   /****************/

This looks nice (and one of your instructors prefers also to use it for aesthetic reasons), but an open comment box like this:

   /***************
       text
   ***************/

has some advantages:

Concatenation

We have also seen many solutions using REXX concatenation characters where these are not required.  Concatenation characters tend to make your code look heavy.

We repeat that concatenation characters are only needed when you have to concatenate 2 variables or functions.  Compare these:

      'COMMAND INPUT /' !! copies('*',71) !! '/'
      'COMMAND INPUT /'copies('*',71)'/'

Review these example too:

    'Yester' !! 'day' <> 'Yester' 'day'  /* Yes: Yesterday <> Yester day */
    'Yester' !! 'day' <> 'Yester''day'   /* Yes: Yesterday <> Yester'day */
    'Yester' !! 'day' == 'Yester'"day"   /* Yes: Yesterday == Yesterday  */

Short variables

If you have analyzed the provided PROFILE XEDIT you may have remarked our definitions at the start of the procedure:

     c='COMMAND ' ; cs="COMMAND SET "

This enhances the programmers' productivity as the commands are then shorter to write.  On the other hand, the performance of the procedure is a bit less as REXX has more work to interpret the variables, unless the procedure is compiled (see more details in lesson 5). 

What's a pity however, is that a many omit to use the COMMAND (or MACRO) prefix for all XEDIT subcommands.  They should review the text of the lesson to understand the consequences.

Other REXX procedure types

Remember that an XEDIT macro is also a REXX procedure, be it with a filetype XEDIT.  These can profit from your code too, although the initialization part needs to be different.  XEDIT macros do not need an address command. If you take the CMS Pipelines Telecourse, then you will learn that user-written pipeline stages use REXX too, but have a filetype REXX this time, so you might also include logic for these files.

REXX functions

If you are new to REXX, then you may not know about all REXX built-in functions.  Especially the string manipulation functions(footnote 3) such as LEFT, WORD, SUBWORD, etc. may be missed. 

Performance however always improves if you can shorten the number of statements executed, especially inside DO loops.  To give an example :

 1 |   fn=substr(fn,1,8)
 2 |   record='/*--------' fn '--------*/
 3 |   'COMMAND INPUT' record

This requires that REXX executes three statements :

  1. redefine variable fn, hence lookup fn and create a new area to store the new value ;
  2. create a new variable record incorporating variable fn, hence lookup fn again ;
  3. append variable record (lookup again) to a literal string (the command) and pass it to XEDIT. 

whereas:

  'COMMAND INPUT /*--------' left(fn,8) '---------*/'

does exactly the same in one statement (append result of function to constant, append another constant and pass it to XEDIT).  The net result is that less overhead is created due to the variable lookup (variable record is even not used) and associated storage management.  Variable fn has to be looked up only once. 

Address COMMAND

Last but not least, the missing address COMMAND statements.  We know, we may be boring, but if you'd look up the list of errors in VM due to a missing address COMMAND, you will better accept why we are stressing this point so hard. 

Any CMS command issued from within your XEDIT macros requires this to be safe and well performing.  Your solution may have commands such as STATE and NAMEFIND...

A PROFILE XEDIT can do quite a lot more for you.  Have a look at our solution below to see some more examples of what can be done. 

Our solution.

With everything we have explained above, we come to present our solution.  We produce a header box like the this:

 /***********************************************************************/
 /* Procedure TTEST    written by Guy De Ceulaer (DECEULAE at BEVM    ) */
 /* Creation date and time : 22-04-2000 18:38:22                        */
 /* Function :                                                          */
 /* Parameters :                                                        */
 /***********************************************************************/
 address command

Our PROFILE XEDIT routine for REXX procedures.

   |  when (ft = 'EXEC') ! (ft = 'XEDIT') then do
   |      cs 'PREFIX ON RIGHT'
 1 |      c 'EXTRACT /SIZE/'                       /* is the file empty ? */
   |      if size.1=0 then do                   /* if yes then initialize */
   |         /**************/
   |         /* who am I ? */
   |         /**************/
 2 |         parse value diag(8,'Q USERID') with userid . nodeid . '15'x
 3 |         address '' 'NAMEFIND :USERID 'userid ':NODE 'nodeid,
   |                    ':NAME (STEM USERNAME'
   |         if rc<>0 then address '' 'NAMEFIND :USERID 'userid,
   |                    ':NAME (STEM USERNAME'
   |         if rc<>0 then username=''                    /* if not found */
   |                  else username=username1
   |         /**************/
   |         /* init file  */
   |         /**************/
 4 |         c 'INPUT /'left('*',71,'*')'/'                      /* box header */
 5 |         str='/* Procedure 'left(fn,8)' written by 'username,
   |             '('left(userid,8)' at 'left(nodeid,8)')'
 6 |         c 'INPUT 'left(str,71)'*/'                          /* title line */
   |         str='/* Creation date and time : 'left(date('E'),2),
 7 |              translate('Dd-Mm-CcYy',date('S'),'CcYyMmDd') '-' time()
   |         c 'INPUT 'left(str,71)'*/'                       /* Creation line */
   |         c 'INPUT 'left('/* Function :',71)'*/'           /* Function line */
   |         c 'INPUT 'left('/* Parameters :',71)'*/'       /* Parameters line */
   |         c 'INPUT /'left('*',71,'*')'/'                      /* end of box */
 8 |         if ft='EXEC' then c 'INPUT address command'
   |                      else c "INPUT c=' COMMAND ';cs=' COMMAND SET'"
 9 |         c 'ADD 15'                                  /* add 15 blank lines */
10 |         c ':1'                                       /* go back on line 1 */
11 |         cs 'ALT 0 0'                         /* set alteration count to 0 */
12 |         c 'CURSOR FILE 4 15'                                /* set cursor */
   |      end;        else do                            /* if file does exist */
13 |         c ':1 COMMAND EXTRACT /CURLINE/'         /* what's in first recd? */
   |         curline=strip(curline.3)                      /* strip off spaces */
   |         if left(curline,2)<>'/*' then do           /* if not rexx comment */
   |            cs 'CASE UPPER IGNORE'       /* is EXEC or EXEC2, so UPPERCASE */
   |            c 'EMSG Case is Upper !'              /* and signal it to user */
   |         end
   |      end  /* if size */
   |  end

Note: The variables c and cs are initialized at the top of (all) our XEDIT macros as can be seen on line 7 :

    c = 'COMMAND'  ;   cs = 'COMMAND SET'

  1. Via EXTRACT /SIZE/ we get the size of the file as we have seen.
  2. We then query who we are.  This is only one of the possible ways to query for it.  As we need the node-id also, we can't use the userid() function as it returns only the user-id.
  3. Here we use the NAMEFIND command to extract our full name.  We do it in 2 steps here ; first we search for a matching userid-nodeid combination.  It is indeed possible to have the same userid on several systems.  If you define them with different nicknames, then you have for example an easy short form to send files from one system to the other. 

    If the userid-nodeid does not give a result, then we search for the userid only. 

    Note also the following.  Unlike other commands (such as EXECIO), the NAMEFIND command has not the option VAR alongside the option STEM to put a single result in a variable.  The variable name you specify as parameter to the STEM option is used to construct the exec variables that are set.  The variable name is suffixed with a '1' for the first return value, a '2' for the second return value, and so on.  The variable name suffixed with a '0' contains the total number of returned variables.  Note that if there is a "." at the end of the variable name, a REXX stem can be set up as an array.  In our case, however we have to use username1 (and not username). 

  4. We create the box header as a string of 71 asterisks surrounded by slashes (/).  The LEFT('*',71,'*') creates the asterisk string.  It says align an asterisk left in a string of length 71 and pad the rest of the string with asterisks (third parameter). 
    COPIES('*',71) would be a valid alternative. 
  5. We then create the title string.  Look carefully how the variables and constant strings are concatenated.  Note the continuation on 2 lines. 
  6. Again we use the LEFT() function to make the string 71 long (padding with spaces now, no third parameter means spaces by default).  We could have combined statements 5 and 6 but in this case it might become less readable. 
  7. Remark how we combine a translate() function with the date() function to return a notation not provided by the date() function.  This is a very useful technique.  The general format is:
           translate(output-template,date,input-template)
    

    A translate() function masks the date with the input-template, and the matching positions are put into the output-templatedate('S') returns the date in so-called sorted format (20001012 for 12 October 2000), so matching the input-template mask gives:

          20001012
          CcYyMmDd
    

    The output-template (Dd-Mm-CcYy) will then return the new format.  Note that the characters of the templates must all be different to make it work, so that's why we have a combination of uppercase and lowercase for each of the elements.

  8. Now, depending on its filetype we add different statements.  Ideally, this should become a SELECT, as it then becomes easy to add other filetype, such as the CMS Pipeline subroutines we just mentioned.
    In our production profile, we include more statements here, such as a parse for our parameters, our global error exit routine, etc. 
  9. We add 15 new empty records to the file. 
  10. We re-position on absolute record 1. 
  11. Yet another useful addition: we set the alteration count to zero, so that, if we edit a file by mistake (misspelled name or so), we can use the QUIT PFkey (without the message File is changed, use QQUIT to QUIT anyway). 
  12. Another little gadget: via the CURSOR subcommand, we instruct XEDIT to put our cursor on the Function : line so that we are ready to start typing our first text there. 
  13. If the file did exist (size not zero), we just verify if it is a REXX procedure (which should start with a /* comment), and not an older EXEC or EXEC2 procedure.  In the latter case, we set the editing to UPPERCASE and inform the user about it (mixed case is our default for most files). 

Anyway, this first coding exercise clearly showed that there is never a unique or best solution with such a powerful programming language.

Please take the time to review your solution and correct the errors.

Then, you can plan to study the chapters making up Lesson 2.


Footnotes:
Back to text

(1) REXX arrays are called compound symbols.  The part of the name before the dot is called the stem.  REXX programmers frequently use the word stem as a synonym of an array
Back to text

(2) The option NOPROF bypasses the execution of any PROFILE XEDIT, and should always be specified in procedures that use XEDIT in order to avoid any interference from the users' profile. 
Back to text

(3) REXX' ability to manipulate strings of words is one of the main characteristic that differentiates it from most other computer languages. 
Back to text