Variable Program Calls in Free-Format RPG
March 3, 2010 Jon Paris
Many RPG programmers are familiar with the notion of using a variable, rather than a literal, to define the target of a program or procedure call. The problems usually begin when they try to utilize this technique in a /Free program because the “translation” of such calls into /Free is not an obvious one.
The PGMCALL shown below is a simple “old-style” RPG program that demonstrates the basic principle for those of you unfamiliar with the technique. As you can see, it simply requests the name of a program to call, calls the program, and issues an error message if the call fails.
H DftActgrp(*No) Option(*SrcStmt : *NoDebugIO) D pgmName s 21a Inz D message c 'Enter name of program to call' // Ask for name of first program to call C message Dsply pgmName C DoU pgmName = *Blanks // Use of "E" extender will cause %Error to be set on call failure C Call(E) pgmName C If %Error C 'Not found!' Dsply C EndIf // Set program name to blanks to clear previous name C Eval pgmName = *Blanks // Ask for next name (if left empty loop will end) C message Dsply pgmName C EndDo C Eval *InLR = *On
The code is very simple. One aspect of it that you may have not encountered previously is that the variable used for the program name (pgmName) is 21 characters long. As you might guess, this is because the name used can include the library name. That is, it can be in the format LibName/ProgName. In fixed-format RPG, the factor 2 area was too small to accommodate a qualified program name, so it never occurred to most of us that it was possible.
Now let’s see how it works in /Free. Most /Free aficionados are aware that only prototyped calls (i.e., those using the optional CALLP opcode) are permitted in /Free code. But when faced with prototyping the call, just about every example that you see will look something like this:
D MyProgram Pr ExtPgm('PGMNAME')
Since we don’t know what the program name is, how can we prototype it? The answer is simple, once you know it. Omit the quotes from the ExtPgm parameter and the compiler assumes that the name used references a variable, which will contain the name of the program to be called. Note that just as when you use a literal for the program name, the name in the variable must be in all upper case. When you test the example, key a program name in lower case and you will see what happens. Program and library names must always be in upper case.
The converted program looks like this:
H DftActgrp(*No) Option(*SrcStmt : *NoDebugIO) // Prototype for dynamic program call - Notice no quotes! D MyProgram Pr ExtPgm(pgmName) D pgmName s 21a Inz /FREE // Ask for name of first program to call Dsply ('Enter name of program to call') ' ' pgmName; DoU pgmName = *Blanks; CallP(E) MyProgram(); If %Error; Dsply ('Program ' + %TrimR(pgmName) + ' Not found!'); EndIf; // Set program name to blanks to clear previous name pgmName = *Blanks; // Ask for next name (if left empty the loop will end) Dsply ('Enter name of program to call') ' ' pgmName; EndDo; *InLR = *On; /END-FREE
As you might have noticed, I couldn’t resist tweaking the code just a bit, to take advantage of a feature of /Free that you may not be aware of. Take a look at the DSPLY used to issue the error message and you will see what I mean.
Instead of a fixed text error message, I used a string expression to build the message text dynamically. This enabled me to incorporate the program name in the message text. The key is to enclose the whole expression in parentheses so that the compiler treats it as a single factor. Of course, I could have done this in the original version by building the message text with an EVAL, but I find this approach simpler and more convenient when writing short test programs.
In the example I used CALLP with the (E) extender in order to trigger %Error should the program not be found. The full CALLP opcode is compulsory when the extender is used, but I don’t like the look of it. So in my own code I would tend to have coded the call like this:
Monitor; MyProgram(); On-Error; Dsply ('Program ' + %TrimR(pgmName) + ' Not found!'); EndMon;
One other comment, although perhaps not that significant in this instance. I much prefer the MONITOR approach to error handling because it makes the expected logic path much more obvious. In other words, any programmer who comes after me can look at the code and know what I expected to happen (the program call), and how I am going to handle things in the event of an error (display a message). MONITOR is great for handling potential situations like divide by zero, and has the added advantage that it can trap things like decimal data errors and allow you to still carry on processing.
A final note: While on the surface the prototyping approach used here with the ExtPgm keyword also works with the ExtProc keyword to allow a variable target on procedure calls, there is a lot more to the calling of procedures. I will explain that in a subsequent tip.
Jon Paris is one of the world’s most knowledgeable experts on programming on the System i platform. Paris cut his teeth on the System/38 way back when, and in 1987 he joined IBM’s Toronto software lab to work on the COBOL compilers for the System/38 and System/36. He also worked on the creation of the COBOL/400 compilers for the original AS/400s back in 1988, and was one of the key developers behind RPG IV and the CODE/400 development tool. In 1998, he left IBM to start his own education and training firm, a job he does to this day with his wife, Susan Gantner–also an expert in System i programming. Paris and Gantner, along with Paul Tuohy and Skip Marchesani, are co-founders of System i Developer, which hosts the new RPG & DB2 Summitconference. Send your questions or comments for Jon to Ted Holt via the IT Jungle Contact page.