Commands: Underused and Unappreciated
February 16, 2011 Ted Holt
As programming languages go, CL is not glamorous. Nevertheless, it is practical and effective, and it has some nice features. One of my favorite features is the ability to extend the CL language by creating my own commands. I have used this feature many times to great advantage over the years, and I am amazed that so many programmers have never written a command. In this article I discuss advantages of creating commands and give an example of how to do so.
I have noticed that many shops use the CALL command exclusively to start execution of a program. CALL works in all instances and is often sufficient. However, you can write a command as an alternative interface to a program. Running the command is the same as calling the program, but the command has certain advantages. Here are some of them.
Advantages of Commands over CALL
1. Keyword parameters are easier to read than positional parameters.
Let’s say you’re required to change a CL program. Which of the following two commands would you prefer to see as you read through the source code? Do you like the CALL version?
CALL PGM(MYPGM) PARM('QCUSTCDT QIWS ' 'CUSTOMER QTEMP ' + '*FIRST' 'CUSTOMER' '*REPLACE')
Or the keyword version?
MYCMD FROMFILE(QIWS/QCUSTCDT) TOFILE(QTEMP/CUSTOMER) + FROMMBR(*FIRST) TOMBR(CUSTOMER) MBROPT(*REPLACE)
I vote for the keyword version, because it gives me a lot of information about what’s going on.
2. When using keyword parameters, you don’t have to leave placeholders for unnecessary parameters.
Suppose you only need to provide values for parameters 1, 2, and 10 on a call to program MYPGM. Which of the following do you like better?
CALL PGM(MYPGM) PARM(&ENDDATE &COMPANY X X X X X X X CLOSE) MYCMD ENDDATE(&ENDDATE) COMPANY(&COMPANY) OPTION(CLOSE)
Again, I prefer the second.
3. Rules for parameter values are more relaxed when using a command than they are under CALL.
Suppose program MYPGM requires one parameter–a two-digit packed decimal company number. To call this program from another program requires that the company number match in type and size.
DCL VAR(&COMPANY) TYPE(*CHAR) LEN(3) DCL VAR(&NCOMPANY) TYPE(*DEC) LEN(2 0) CHGVAR VAR(&NCOMPANY) VALUE(&COMPANY) CALL PGM(MYPGM) PARM(&NCOMPANY)
But if you call MYPGM via a command, the command processor converts the data to the required type and size for you.
DCL VAR(&COMPANY) TYPE(*CHAR) LEN(2 0) MYCMD COMPANY(&COMPANY)
Say goodbye to those annoying scratch variables that clutter up your code.
4. If the command definition allows it, you may use expressions in command parameters. You may not use expressions in the PARM parameter of the CALL command.
In this example, MYPGM requires three parameters. Suppose you wish to call MYPGM using parameter values extracted from a data area. Here’s how it’s done with CALL.
DCL VAR(&DATA) TYPE(*CHAR) LEN(1024) DCL VAR(&PRTTXT) TYPE(*CHAR) LEN(30) DCL VAR(&COMPANY) TYPE(*DEC) LEN(2) DCL VAR(&CUSTOMER) TYPE(*DEC) LEN(6) CHGVAR VAR(&COMPANY) VALUE(%SST(&DATA 101 2)) CHGVAR VAR(&CUSTOMER) VALUE(%SST(&DATA 103 6)) CHGVAR VAR(&PRTTXT) VALUE(%SST(&DATA 151 200)) CALL PGM(MYPGM) PARM(&COMPANY &CUSTOMER &PRTTXT)
The same process is simpler when using a command that allows expressions.
DCL VAR(&DATA) TYPE(*CHAR) LEN(1024) MYCMD COMPANY(%SST(&DATA 101 2)) + CUSTOMER(%SST(&DATA 103 6)) + PRTTXT(%SST(&DATA 151 30))
I used the substring function in that example. You can also concatenate. Notice the COMPANY parameter in the following example.
DCL VAR(&DATA) TYPE(*CHAR) LEN(1024) DCL VAR(&COMP1) TYPE(*CHAR) LEN(1) DCL VAR(&COMP2) TYPE(*CHAR) LEN(1) MYCMD COMPANY(&COMP1 *CAT &COMP2) + CUSTOMER(%SST(&DATA 103 6)) + PRTTXT(%SST(&DATA 151 30))
5. You may specify limited validation of parameter values in a command definition. For each parameter, you may specify such things as:
You may also name a validity checking program in the Create Command (CRTCMD) command. This program runs before the command-processing program, and allows you to define even more stringent parameter validation.
6. Commands are not afflicted with the default parameter definitions inherent in CALL.
When it comes to the CMD parameter of the Submit Job (SBMJOB) command, these rules apply to variables as well. Submitting a CALL with parameters to a job queue can be a pain. But submitting a command is no big deal. The command processor straightens everything out.
7. The command processor provides a nice prompting feature, which you can use from the command line, when editing a source member, and when prompting a command parameter in another command, such as Submit Job (SBMJOB). Because of the prompter, you can do a much better job filling in the parameters properly.
Let’s say you’ve written a wonderful program that’s going to revolutionize the Accounts Receivable department. It’s really more than one program; you’ve created three objects.
Job well done!
Your super-duper application requires three values to be supplied at run time.
Here are some pieces of the CL program showing the parameter definitions.
pgm parm(&inCompany &inCustomer &inPrtTxt) dcl &inCompany *dec 2 dcl &inCustomer *dec 6 dcl &inPrtTxt *char 30 dcl &Status *char 8 ovrprtf ar100p prttxt(&inPrtTxt) call ar100r parm(&inCompany &inCustomer &Status) dltovr ar100p if (%sst(&Status 4 5) *ne '00000') do sndpgmmsg msgid(cpf9898) msgf(qcpfmsg) msgtype(*diag) + msgdta('Program' *bcat &PgmName *bcat + 'ended abnormally with status' *bcat &Status) goto Escape enddo
You’ve been asked to place several invocations of this application–one for each company–on the job scheduler to run overnight throughout the week. There’s no good way to do this using CALL, due to the lengths of the company and customer parameters.
However, if you’ll define a command to run the CL program, scheduling the job will be easy. Here’s the source for command AR100:
CMD PROMPT('Accounts Receivable Report') PARM KWD(COMPANY) TYPE(*DEC) LEN(2 0) REL(*GT 0) + MIN(1) PROMPT('Company') PARM KWD(CUSTOMER) TYPE(*DEC) LEN(6 0) DFT(*ALL) + REL(*GT 0) SPCVAL((*ALL -1)) + PROMPT('Customer') PARM KWD(PRTTXT) TYPE(*CHAR) LEN(30) EXPR(*YES) + PROMPT('Print text')
Here’s how to create the command:
CRTCMD CMD(MYLIB/AR100) PGM(*LIBL/AR100C) SRCFILE(MYLIB/QCMDSRC) SRCMBR(AR100)
And here’s how to schedule it to run:
ADDJOBSCDE JOB(AR100_3) CMD(AR100 COMPANY(3) CUSTOMER(*ALL) PRTTXT('Give to Billy Ruben')) FRQ(*WEEKLY) SCDDAY(*SUN *MON *TUE *WED *THU) SCDTIME(230000) JOBD(ARJOBD)
In My Experience. . .
The example in this article includes a command that executes a CL program. I don’t want to give the impression that the command-processing program has to be written in CL. It can be written in any language. However, as I think back on commands I’ve written over the past 25 or so years, the majority of my commands have executed CL programs.
Also, I don’t want to give the impression that all programs need command interfaces. If a program has few or no parameters, or is called only from other programs, the usual CALL mechanism is adequate. My experience is that only a very small percentage of programs can benefit from a command interface. However, in the case of a program that is called from many CL programs (i.e., a utility), I find that writing a command interface, even if none is needed, enhances readability and facilitates program modification.