More About Commands and Variable-Length Parameters
December 6, 2006 Ted Holt
The code associated with this article is available for download.
Last week, I told you about an easy way to power programming–creating CL commands with variable-length parameters. Using variable-length parameters lets you pass variables of different sizes to commands, instead of requiring that you use variables of certain lengths. It also helps you to solve certain problems once, and be done with them. This week, I am going to show you how you can make the variable-length parameter technique even easier.
Last week’s command, ZEROSUP, invoked a CL program in order to strip leading zeros from a number stored in a character string. Instead of showing a user a confusing message, such as “00000080880 orders were loaded into the database,” you can send a clearer message, such as “80880 orders were loaded into the database.”
dcl &CustOrders *dec 11 dcl &AlphaNum *char 12 chgvar &AlphaNum &CustOrders zerosup value(&AlphaNum) tovar(&AlphaNum) zerobal(*yes) sndmsg msg(&AlphaNum *bcat 'orders were added to the database.') + tousr(Somebody)
I wrote the command-processing program (CPP) for one reason–everybody has CL. However, the CPP does not have to be written in CL, and that leads me to this week’s tip: Consider using RPG for command-processing programs when variable-length parameters are involved.
The reason is simple: RPG directly supports variable-length character data. All that’s required is to be able to spell the word varying.
Let’s create another command to illustrate RPG’s suitability to the task at hand. This command will divide a character string into two strings by searching for a separator character. I’ll call it SPLITSTR (Split String). Here’s the source code:
CMD PROMPT('Split a string') PARM KWD(STRING) TYPE(*CHAR) LEN(4096) MIN(1) + EXPR(*YES) VARY(*YES *INT2) + PROMPT('String to split') PARM KWD(TOVAR1) TYPE(*CHAR) LEN(1) RTNVAL(*YES) + MIN(1) VARY(*YES *INT2) + PROMPT('First receiving variable') PARM KWD(TOVAR2) TYPE(*CHAR) LEN(1) RTNVAL(*YES) + MIN(1) VARY(*YES *INT2) + PROMPT('Second receiving variable') PARM KWD(DELIMITER) TYPE(*CHAR) LEN(1) + DFT(*BLANK) SPCVAL((*BLANK ' ')) + EXPR(*YES) PROMPT('Delimiting character')
Notice that the first three parameters are all variable-length. The first parm, the string that is to be divided, is defined with a length of 4096. This means that the string to be divided can have a maximum of 4096 characters. The second and third parms, which contain the two resultant strings, are defined with a length of one. This means that the variables must be at least one byte long. They can be longer.
Now let’s see how the parameters are defined to RPG. Here’s the procedure interface (the *ENTRY PLIST, but defined in the D specs).
D SplitStr1 pi D inFromString 4096a varying D ouToString1 4096a varying D ouToString2 4096a varying D inDelimiter 1a
In the RPG program, I’ve defined all three parameters with their maximum lengths. The prefix on each parameter, which I can access through the %LEN built-in function, will give me their true lengths. This assignment statement tells me how long the input string was:
Length = %len(inFromString);
And these commands change the output parms without the undesired side-effect of changing other variables in memory.
%subst(ouToString1:1:%len(ouToString1)) = ToString1; %subst(ouToString2:1:%len(ouToString2)) = ToString2;
That is, if ouToString1 is 20 bytes long, the assignment only changes 20 bytes.
Here’s an example of how you might use the SPLITSTR command:
dcl &file *char 10 dcl &lib *char 10 dcl &qualfile *char 21 splitstr string(&QualFile) + tovar1(&Lib) tovar2(&File) + delimiter(/)
Here a 21-byte variable contains a qualified file name. SPLITSTR divides the qualified name into library and file names by looking for a slash. If &QUALFILE has the value MYLIB/MYFILE, then &LIB is MYLIB and &FILE is MYFILE.
Here’s a second example.
dcl &AmtPosted *dec (9 2) dcl &AmtChar *char 12 dcl &Dollars *char 9 dcl &Cents *char 2 chgvar &AmtChar &AmtPosted ZeroSup &AmtChar &AmtChar SplitStr &AmtChar + tovar1(&Dollars) tovar2(&Cents) delimiter('.')
Suppose that &AMTPOSTED has the value 395.81. CHGVAR loads &AMTCHAR with the value 0000385.81. The ZEROSUP command changes &AMTCHAR to 385.81 SPLITSTR loads 385 into &DOLLARS and 81 into &CENTS. Think about how you would accomplish the same task without the ZEROSUP and SPLITSTR commands. It’s not pretty, is it?
I hope these articles have encouraged you to dig deeper into this greatly underused feature of OS/400 and i5/OS. To those iSeries and System i developers who have never written a command, I’ll pass along some advice my dad would give me as I faced new challenges while growing up, “You aren’t going to learn any younger.”