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