2

I have a CMD command object that drives an RPGLE program. Because the command may be called with several different parameters, some of which are mutually exclusive, I parse the parameter passed by using a data structure in the RPGLE so I can handle the different scenarios that pass the parameters in various positions.

For example, the CMD file has:

CMD       PROMPT('Reprint Invoices and Credits')      

 PARM      KWD(ORDERNUM) TYPE(ORDER) +                 
           PROMPT('For order number:')                 

 PARM      KWD(INVDATE) TYPE(*DATE) PASSATR(*YES) +    
           PROMPT('For invoice date')                  

 PARM      KWD(DATERANGE) TYPE(DTRANGE) +              
           PROMPT('For date range:')      

 PARM      KWD(TRANSTYPE) TYPE(*CHAR) LEN(9) RSTD(*YES)      +
              DFT(*BOTH) VALUES(*INVOICES *CREDITS *BOTH)    +
              PASSATR(*YES) PROMPT('Transactions to print')   

 DTRANGE:  ELEM      TYPE(*DATE) MIN(1) PASSATR(*YES) +             
                     PROMPT('Beginning date')                       
           ELEM      TYPE(*DATE) MIN(1) PASSATR(*YES) +             
                     PROMPT('Ending date')                          

 ORDER:    ELEM      TYPE(*DEC) LEN(6) MIN(1) PASSATR(*YES) +       
                     PROMPT('Order number')                         
           ELEM      TYPE(*DEC) LEN(2) MIN(0) PASSATR(*YES) +       
                     PROMPT('Shipment number (optional)')           

           DEP       CTL(*ALWAYS) PARM(ORDERNUM INVDATE DATERANGE) +
                     NBRTRUE(*EQ 1)                                 

The user can print by various criteria: order number, date, date range. Only one of these three methods can be chosen. Depending on what the user chooses, the parameters get delivered to the called RPGLE program differently.

  ********************************************************************                           
  *      Parameters from CMD object INV_REPRNT                                                   

 D InputParms      DS                  TEMPLATE QUALIFIED                                        
 D  AllParms                    143A                                                             
 D  ParmType               2      2A                                        Can't find in manual 
 D                                                                          'Type' might be      
 D                                                                          a misnomer           
 D                                                                                               
 D  OrdDteAttr             3      3A                                        For attr's, see      
 D  OrderNum               4      7P 0                                      SEU help for         
 D  ShipAttr               8      8A                                        CMD PASSATR          
 D  Shipment               9     10P 0                                                           
 D  OrdInvCMAttr          21     21A                                                       
 D  OrdInvCM              22     30A                                        char  9        
 D                                                                                 
 D  InvDate@               4     10A                                               
 D  DteInvCMAttr          13     13A                                               
 D  DteInvCM              14     22A                                        char  9
 D                                                                                 
 D  BeginDateAttr         13     13A                                               
 D  BeginDate@            14     20A                                               
 D  EndDateAttr           21     21A                                               
 D  EndDate@              22     28A                                               
 D  RgeInvCMAttr          29     29A                                               
 D  RgeInvCM              30     38A                                        char  9

As you can see, the position of the later parameters like TRANSTYPE shift position depending on which of the earlier parameters was chosen. OrdInvCM starts at 22, DteInvCM starts at 14, RgeInvCM starts at 30. This is not a problem as this data structure and the code using it is able to pick out the right position to read from based on the mysterious little attribute that I am calling ParmType. As far as I can tell, this attribute is not documented anywhere in the CL manuals on the internet or in the help included in the SEU editor (which has information on PASSATR that is not in the online manuals). I have pieced together a little of ParmType's behavior in relation to the pass attributes, enough to use it, but not enough to fully understand it.

Some constants to make parsing the PASSATR easier (not every possibility):

 D Null            C                   CONST(X'00')                                            
 D Parm2           C                   CONST(X'02')                                            
 D NumSpecd        C                   CONST(X'A1')                         1010 0001          
 D NumUnspecd      C                   CONST(X'21')                         0010 0001          
 D CharQSpecd      C                   CONST(X'C5')                         1100 0101 Quoted   
 D CharQUnspecd    C                   CONST(X'45')                         0100 0101 Quoted   
 D CharUQSpecd     C                   CONST(X'85')                         1000 0101 Unquoted 
 D CharUQUnspecd   C                   CONST(X'05')                         0000 0101 Unquoted 
 D                                                                                             
 D IsSpecd         C                   CONST(X'80')                         >= 1000 0000       

I have found that:

 IF P.ParmType = Null;         
   IF P.OrdDteAttr >= IsSpecd; 
     // this is a single date
   ELSE;
     IF P.BeginDateAttr >= IsSpecd;
       // this is a data range
     ELSE;
       // this is error condition I have not gotten yet
     ENDIF;
   ENDIF;
 ELSE;
   IF P.OrdDteAttr >= IsSpecd;
     // this is an order number
   ELSE;
     // this is error condition I have not gotten yet
   ENDIF;
 ENDIF;

In other words the ParmType has a hex value of '00' when the parameter is either a date or a date range. The ParmType has a hex value of '02' when the parameter is a packed *DEC (6P 0) for 'Order number'.

I would like to understand how this ParmType value gets set at a given number so I can robustly write programs that can accept various parameter combinations. I also do not see a particular reason why the data range fields start over at 14 rather than at 4 like the single date does. I was able to exploit this fact to make the necessary distinction, but I do not know if the command system did this on purpose because it saw that I had two possibilities of the same data type or if this is just a lucky break that is not guaranteed to occur. A similar question occurs if I want to add a additional packed parameter as a choice, say an invoice number. The 'PASSATR' hex value of 'A1' could tell you it was packed, but not which type (order number or invoice number). It might be that the command system shifts the position similar to what it did with date range, but I have not run that particular experiment.

In short, is there documentation or at least deduced algorithms on how commands build their parameter lists so that one can predict what these fields will contain and where they will be located?

Mike
  • 1,274
  • 10
  • 24

3 Answers3

4

All parameters will be passed whether values are entered or not, and they will appear in the order provided in the command.

PASSATR should not be needed, leave it out.

CMD       PROMPT('Reprint Invoices and Credits')

  PARM      KWD(ORDERNUM) TYPE(ORDER) +
            PROMPT('For order number:')

  PARM      KWD(INVDATE) TYPE(*DATE) +
            PROMPT('For invoice date')

  PARM      KWD(DATERANGE) TYPE(DTRANGE) +
            PROMPT('For date range:')

  PARM      KWD(TRANSTYPE) TYPE(*CHAR) LEN(9) RSTD(*YES) +
            DFT(*BOTH) VALUES(*INVOICES *CREDITS *BOTH)  +
            PROMPT('Transactions to print')

  DTRANGE:  ELEM      TYPE(*DATE) MIN(1) +
                      PROMPT('Beginning date')
            ELEM      TYPE(*DATE) MIN(1) +
                      PROMPT('Ending date')

  ORDER:    ELEM      TYPE(*DEC) LEN(6) MIN(1) +
                      PROMPT('Order number')
            ELEM      TYPE(*DEC) LEN(2) MIN(0) +
                      PROMPT('Shipment number (optional)')

            DEP       CTL(*ALWAYS) PARM(ORDERNUM INVDATE DATERANGE) +
                      NBRTRUE(*EQ 1)

Mixed lists ORDERNUM and DATERANGE will appear with an Integer number of elements as the first two bytes. If a mixed list parameter is empty, or not passed, this integer will contain 0.

Here is how you could code a command processing program in CL

PGM        PARM(&ORDERNUM &INVDATE &DATERANGE &TRANSTYPE)

         DCL        VAR(&ORDERNUM) TYPE(*CHAR)
         DCL        VAR(&ONELMCNT) TYPE(*INT) STG(*DEFINED) +
                      LEN(2) DEFVAR(&ORDERNUM 1)
         DCL        VAR(&ONORDER) TYPE(*DEC) STG(*DEFINED) LEN(6 +
                      0) DEFVAR(&ORDERNUM 3)
         DCL        VAR(&ONSHIPNO) TYPE(*DEC) STG(*DEFINED) +
                      LEN(2 0) DEFVAR(&ORDERNUM 7)

         DCL        VAR(&INVDATE) TYPE(*CHAR) LEN(7)

         DCL        VAR(&DATERANGE) TYPE(*CHAR)
         DCL        VAR(&DRELMCNT) TYPE(*INT) STG(*DEFINED) +
                      LEN(2) DEFVAR(&DATERANGE 1)
         DCL        VAR(&DRBDATE) TYPE(*CHAR) STG(*DEFINED) +
                      LEN(7) DEFVAR(&DATERANGE 3)
         DCL        VAR(&DREDATE) TYPE(*CHAR) STG(*DEFINED) +
                      LEN(7) DEFVAR(&DATERANGE 10)

         DCL        VAR(&TRANSTYPE) TYPE(*CHAR) LEN(9)

         if (&onelmcnt *ne 0) do
           /* ORDERNUM parameter has been entered */
         enddo

         if (&invdate *ne '0000000') do
           /* INVDATE parameter has been entered */
         enddo

         if (&drelmcnt *ne 0) do
           /* DATERANGE parameter has been entered */
         enddo

         if (&transtype *ne ' ') do
         enddo

done:   endpgm 

Notice the structures for the mixed list (ELEM) parameters. Only the element count &xxelmcnt fields in these structures are valid if the number of elements in the list is 0. Also note that these will always contain 0, or 2 (the number of defined elements in each list). The ORDERNUM parameter will contain 2 if it is provided even if the shipper number is left blank. The value passed for shipper number in this case will be 0.

You can process this in a similar way in an RPG program:

   ctl-opt Main(testcmd);

   dcl-ds ordernum_t qualified template;
     elements      Int(5);
     order         Packed(6:0);
     shipper       Packed(2:0);
   end-ds;

   dcl-ds daterange_t qualified template;
     elements      Int(5);
     begindt       Char(7);
     enddt         Char(7);
   end-ds;

   dcl-proc testcmd;
     dcl-pi *n ExtPgm('TESTCMD');
       ordernum      LikeDs(ordernum_t) const;
       invdate       Char(7) const;
       daterange     LikeDs(daterange_t) const;
       transtype     Char(9) const;
     end-pi;

     if ordernum.elements <> 0;
       // parameter has been entered
     endif;

     if invdate <> '0000000';
       // parameter has been entered
     endif;

     if daterange.elements <> 0;
       // parameter has been entered
     endif;

     if transtype <> '';
       // parameter has been entered
     endif;

     return;
   end-proc;

Here is some documentation for how mixed list parameters are handled. surrounding it in the manual are descriptions for simple lists, and lists within lists (very complex).


Edit as Charles pointed out, you are trying to access the parameter values as a single block in your example. That is almost guaranteed to cause confusion as there is no (public) definition around how parameters are loaded into memory other than the parameter references defined in the program. Parameters are passed by reference, and it is the calling program that determines where they are in memory. Assuming that each parameter is physically adjacent to the previous parameter can be a dangerous assumption. The only safe way to access a given parameter is by using its individual parameter reference. It is a bad idea to try to access parameter 2 from parameter 1's reference. Even if works once, it will not necessarily always work. As you have seen, the command object moves things around in memory based on what the user keys.

Since we know that the command above defines 4 parameters, that is 4 PARM elements, we can be confident that each of the 4 parameters will be passed to the command processing program exactly as they are defined in the command. But, we cannot have confidence in what comes after any parameter in memory.

jmarkmurphy
  • 11,030
  • 31
  • 59
  • Very interesting, I will try this approach. Your statement "Mixed lists ORDERNUM and DATERANGE will appear with an Integer number of elements as the first two bytes. If a mixed list parameter is empty, or not passed, this integer will contain 0." explains why I saw a 2 early in the string hex dump. – Mike Aug 04 '17 at 13:51
  • The only thing in your answer that does not accord with what I have seen is "All parameters will be passed whether values are entered or not, and they will appear in the order provided in the command." Dumps of my input parameter shows that (presumably because of the "DEP NBRTRUE(*EQ 1)") parameters that are not used are not passed with empty placeholders, instead the used parameter is shifted leftward into the place that the skipped one was. I'll update my question with the three different dumps to make it clearer; it can be seen in my "InvDate@ 4 10A" and "D OrderNum 4 7P 0". – Mike Aug 04 '17 at 14:04
  • Boy, I wish this answer was correct. As you point out in your edit, it would make a better technique than I was using. Unfortunately, when I do this (get rid of PASSATR's, list parameters in order, add an additional 2B parm that you don't list to hold the listCount), the revised CMD object still only passes **used** parameters so they are misaligned. If I could force it to **pass all** like you think it should, this technique would totally work. – Mike Aug 04 '17 at 16:20
  • Did you try it, compile and run it under debug, this is exactly how it works. – jmarkmurphy Aug 04 '17 at 17:08
  • 1
    BTW, I did compile and run this code through Debug. You will never see what you want if you look only at the first parameter and what is beyond it in memory. It will never line up. It will only work as you expect it to if you use all four parameters that you have defined in the command. – jmarkmurphy Aug 04 '17 at 17:12
  • Yes, in RPG, with dumps to acertain what the parms really contain. I did not try your CL which I suppose would be the next logical step.9 – Mike Aug 04 '17 at 17:12
  • Did you need to add the 2B list count to make it line up? Did it write the date parms left of where they would be? I think there might be a keyword I need to add to the CMD to get it to leave space like you say. – Mike Aug 04 '17 at 17:18
  • It should work exactly as it stands. You need the element count in the ordernum and daterange data structures for everything to line up. It is a 2 byte integer value. Use integers rather than the older binary. Int(5) is a 2 byte integer. – jmarkmurphy Aug 04 '17 at 17:28
  • I created the CMD object with the source listed. If you create the RPGLE and the CMD with the source exactly as listed, you will see that it works. – jmarkmurphy Aug 04 '17 at 17:31
  • It works! Thank you so much. The CMD object still writes the parameters in their weird overlapped postitions, so you do get junk if you read a parameter that was not passed, but when you define it like you did, the compiler lines up the parameter positions into the correct spots. Reading the parameter pointers that the compiler creates in the dump (_QRNL_PSTR_INVDATE, _QRNL_PSTR_ORDERNUM), you can see that the later parameters are still shifted left and overlapping each other, but RPG apparently knows how to handle that right (not sure how). Awesome, thank you so much. – Mike Aug 04 '17 at 19:21
  • Mike, the parameters don't overlap either other. They just happen to follow each other in storage. The first parameter passed to your program only has the information for the first parameter of the command. Same for the second, third, and fourth parameters. – Barbara Morris Aug 05 '17 at 13:21
  • follow-on to my previous comment ... Imagine a program PGMA that defines a data structure in RPG, with subfields X, Y, Z. If you call a program PGMB passing those subfields as three parameters (X:Y:Z), the PGMB would be able to see all three of X+Y+Z if it defined just a single parameter and figured out how to find the three pieces. But if the order of the subfields in the DS was changed to Y,Z,X, PGMB would be broken if it was incorrectly trying to access all three parameters from the first one. The command analyzer is like program PGMA. Your command-processing program is like PGMB. – Barbara Morris Aug 05 '17 at 13:33
  • 1
    @Mike The *parameters* are *not* overlapping. Your RPG *definitions* are. Your RPG definitions say "There will *always* be a date value in this position..." as well as other similar assertions. But your *CMD object says otherwise, that a date value might or might not be passed. Your definitions need to account for non-passed list values. You can't use a single DS with fixed positions to handle non-passed list values. Each list needs to be specifically handled. – user2338816 Aug 17 '17 at 13:03
  • @user2338816 Thank you. That is a change of viewpoint that answers a nagging thought that was left in my mind. In short: Don't expect fixed position since you wrote lists in the CMD. – Mike Aug 17 '17 at 13:08
  • @user2338816 It is a little confusing what you are trying to say here, but the real truth is that your command processing program needs to have one parameter for each `PARM` defined in the command source, and they must be in the order defined in the command source. You can't try to access all the parameters from a single parameter. All parms will be passed in order whether they are ignored by the command prompt or not. – jmarkmurphy Aug 17 '17 at 13:12
2

PASSATR is documented here Pass attribute byte (PASSATR)

*YES
An attribute byte is passed with the parameter. The attribute byte has two fields:

  1. The leftmost bit of the attribute byte indicates whether or not a value was specified. If the leftmost bit is '0'B, the value passed to the command processing program is a default value and was not specified in the command string. If the leftmost bit is '1'B, the value passed to the command processing program was specified in the command string.
  2. The remaining seven bits describe the value passed to the command processing program when *CHAR is specified for the Type of value (TYPE) parameter.

Attribute Description ---------- -------------------------------------- '0000010'B Meets *NAME rules, like A_B '0000100'B Meets GENERIC rules, like AB '1000101'B Quoted character string, like 'A B' '0000101'B Unquoted character string, like 5A '1001000'B Logical constant, '0' or '1' '0001100'B Hexadecimal value, like X'C1C2' '0100001'B Unsigned numeric value, like 5 '0101001'B Unsigned numeric with decimal point, like 5.2 '0110001'B Signed numeric value, like -5 '0111001'B Signed numeric with decimal point, like -5.2

Also look at Value to pass if unspecified (PASSVAL), which is documented right underneath PASSATR.

Value to pass if unspecified (PASSVAL)
Specifies whether a value is passed to the command processing program for this parameter. *NULL is not valid if the parameter is a constant parameter (a parameter in which a value has been specified for the Constant value (CONSTANT) parameter, or a parameter for which *ZEROELEM or *NULL has been specified for the Type of value (TYPE) parameter, or a list/qualified name defined by all constant ELEM or QUAL statements). *NULL also is not valid if *YES has been specified on the Return value (RTNVAL) parameter, or if the value specified for the Minimum values required (MIN) parameter is greater than zero. A DEP statement or the REL and RANGE keywords of other PARM statements may not refer to the value of a parameter defined with *NULL.

If you PASSVAL unspecified parameters as *NULL, you should be able to define them in RPGLE as OPTION(*OMIT) and then check if %addr(myOptParm) <> 0;

EDIT
What you're trying to do, pass all parms as a single chunk is a bad idea. You might get it to work today, but it could break with the application of a PTF or during an OS upgrade. The system is designed to pass individual parameters.

Just pass them all to your RPG program and check to see what was actually used.

Charles
  • 21,637
  • 1
  • 20
  • 44
  • It is good that you found this documentation in the online manual, so it does not just exist in the SEU editor help, but unfortunately it does not really answer how the positioning of the parameters works. What you posted is the information that I was using as background to my question, so is still valuable. – Mike Aug 03 '17 at 14:12
0

I vaguely recalled an article by Bob Cozzi that talked about the PASSATR attribute. Maybe it will help ... https://www.mcpressonline.com/programming/rpg/retrieving-user-space-data

Barbara Morris
  • 3,195
  • 8
  • 10