ibm-midrangerpglecontrol-language

How do CL commands build their exact parameter lists?


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?


Solution

  • 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.