Guru: The CALL I’ve Been Waiting For
June 20, 2022 Ted Holt
Christmas came to my house early this year. May 3, to be exact. Boy! Was Santa Claus good to me! IBM gave me two presents! The first was a CL enhancement that I had desired for years. The second was an improvement that, to my delight, almost obsoletes a utility I wrote years ago. Both have to do with the CL CALL command.
CALL is arguably the most used and most important command in CL, yet for all these years it has remained untouched. I’m glad that IBM has seen fit to devote time and resources to the enhancement of this important language, and especially glad to see the enhancement of CALL.
Present #1: Expressions
Let’s begin with the present I was hoping for.
Assume a CL program that receives two parameters — a qualified object name and an object type. The qualified object name is a 20-byte character value. The first 10 characters are the object name, and the second 10 characters are a library name or a special value, such as *LIBL.
If we want to make sure the object exists, we can do it this way.
pgm parm(&inQualObj &inObjType) dcl &inQualObj *char 20 dcl &inObjType *char 10 ChkObj obj(%SST(&inQualObj 11 10)/%SST(&inQualObj 1 10)) + ObjType(&inObjType) aut(*ObjExist)
Notice the substring functions in the OBJ parameter. Two of them! The OBJ parameter allows expressions (a good idea, in my opinion), so I don’t have to copy the object name and library name into scratch variables.
Suppose we want to pass the library name to another program. For most of the history of IBM i and its predecessors, we had to declare a single-use variable, like this:
pgm parm(&inQualObj &inObjType) dcl &inQualObj *char 20 dcl &inObjType *char 10 dcl &Lib *char 10 ChgVar &Lib %SST(&inQualObj 11 10) call pgm(X) parm(&Lib)
Why? Because we weren’t allowed to use expressions in the PARM parameter of the CALL command. This has annoyed me for a long time.
Later, IBM introduced defined variables, which I consider an improvement.
pgm parm(&inQualObj &inObjType) dcl &inQualObj *char 20 dcl &inObjType *char 10 dcl &Lib *char 10 stg(*defined) DefVar(&inQualObj 11) call pgm(X) parm(&Lib)
There’s no need for the CHGVAR command because &Lib overlays &inQualObj in memory. Changing one changes the other.
But now, at last, we can use expressions in the PARM parameter!
pgm parm(&inQualObj &inObjType) dcl &inQualObj *char 20 dcl &inObjType *char 10 call pgm(X) parm(%SST(&inQualObj 11 10))
Just think, no more having to declare single-use variables just to pass a parameter to a program. Fabulous!
Here’s an example that uses a logical value.
A CL program receives a four-byte character value of *YES or *NO to indicate whether the job should create a logging file or not. It needs to convert this value to ‘1’ or ‘0’ when passing it along to another program. Here’s how we’ve had to do that until now.
pgm parm(&CrtLog) dcl &CrtLog *char 4 dcl &CrtLogLgl *lgl chgvar &CrtLogLgl (%upper(&CrtLog) *eq *YES) call pgm(X) PARM(&CrtLogLgl)
Here’s the new way.
pgm parm(&CrtLog) dcl &CrtLog *char 4 call pgm(X) PARM(((%upper(&CRTLOG) *EQ *YES) (*LGL 1)))
Here’s an example with concatenation.
call pgm(x) (%sst(&ItemType 5 1) *cat %sst(&Option 2 1))
And so it goes. I haven’t tested this exhaustively yet, but it seems safe to assume that anything you can put into the VALUE parameter of the CHGVAR command can be used in PARM.
Gift #2: Parameter Definitions
To understand just how wonderful the second present from IBM is, let’s first be sure we understand how CL has worked since its introduction to the world.
The people who created CL for the System/38 those many eons ago had to make some decisions. One question they faced concerned the use of literal parameters in the CALL command, namely, how such parameters should be stored in memory. They adopted a convention, which has been written about many times. (Here’s one.)
- Character literals of 32 bytes or less are stored in 32 bytes of memory. If the literal is less than 32 bytes long, the system pads it with trailing blanks.
- Character literals of more than 32 bytes are stored in the number of bytes needed to contain all characters up to and including the last non-blank character. To put it another way, trailing blanks are not stored in memory.
- Numeric literals are passed as 15-digit packed-decimal numbers with five assumed decimal positions.
Programmers have had to work around these limitations. Some common methods include:
- Define all parameters as character in the called program.
- Append an extra non-blank character to a character literal.
- Pass data to a program through the local data area rather than through parameters.
- Declare numeric parameters as 15,5 in the called program.
- Pass numeric values as hexadecimal literals.
- Create a command interface for the called program.
Well, not any more. Now we can tell the system how we want a literal value to be stored.
In this example, the literal 302 is passed to program X as a five-digit packed-decimal value.
CALL PGM(X) PARM((302 (*DEC 5 0)))
Bear in mind that the system may see literal values where we use variables. The system knows about variable definitions within a CL program/procedure. Otherwise, the CALL is interpreted. A good example is the Submit Job (SBMJOB) command.
dcl &CusNbr *dec 5 SbmJob cmd(CALL PGM(X) PARM((&CUSNBR))) job(Whatever)
The Submit Job command creates a request message, which the new job will receive and execute as a command. The new job will not know that &CusNbr was defined as five digits packed in the submitting program. Instead, it will treat the customer account number as if were defined as 15,5. If program X defines the customer number parameter as 5,0 packed, the program will abend with a data decimal error.
Instead, this program needs to pass the customer number to program X as a five-digit packed-decimal number. Here’s how it’s done.
dcl &CusNbr *dec 5 SbmJob cmd(CALL PGM(X) PARM((&CUSNBR (*DEC 5 0)))) + job(Whatever)
Suppose that program X defines the customer number as a seven-digit packed-decimal number. We can reformat the number appropriately.
dcl &CusNbr *dec 5 SbmJob cmd(CALL PGM(X) PARM((&CUSNBR (*DEC 7 0)))) + job(Whatever)
This new enhancement almost obsoletes a utility I wrote years ago and have been using since. I refer to my RUN command. It seems that I end up installing this utility on every system I work on. Maybe I’ll need it less often from now on.
The reason I say that this new CALL behavior almost obsoletes RUN is that I ran into one instance where CALL wouldn’t do what needed to be done. Here’s a command from the article I linked to in the previous paragraph.
RUN PGM(MYPGM) PARM(('I like cheese a whole lot!' *CHAR (64)) + (3.14 *DEC (7 2)) + (12345678901 *DEC (11)))
I converted this code to a CALL.
call pgm(MYPGM) PARM(('I like cheese a whole lot!' (*CHAR 64)) + (3.14 (*DEC 7 2)) + (12345678901 (*DEC 11 0)))
Guess what? It doesn’t compile. The compiler allows me to define the third parameter with 11 significant digits, but it won’t let me pass an 11-digit literal. It appears I won’t completely retire RUN just yet, but maybe I won’t have to use it as often as before.
Einstein Is My Model
Albert Einstein said, “Everything should be made as simple as possible, but not simpler.” I subscribe to the same philosophy. I write complicated source code when I have to, but only when nothing simpler will serve the purpose.
I’ve received the CALL I had been waiting for. Thanks to these new enhancements, my CL programs and procedures will be less cluttered with single-use variables and, I hope, easier to read and work on.