We continue our quest for reusable code with CL.
In the previous CL Corner column, "Create Reusable Code," we created the TRMLFTCHR command. This command accepted a 10-byte character variable whose value was a numeric representation such as returned by the CHGVAR command when converting a TYPE(*DEC) CL variable, removed any leading zeros found in the numeric value, and returned the remaining character string left-adjusted and blank-padded. In this article, we will look at how to enhance the TRMLFTCHR command to support a CL character variable of virtually any length.
The command definition change is quite straightforward and shown below.
Cmd Prompt('Trim Left Characters')
Parm Kwd(Var) Type(*Char) Len(1) RtnVal(*Yes) +
Min(1) Vary(*Yes *Int4) +
Prompt('Decimal value')
Two changes have been made from the original definition of TRMLFTCHR and the VAR variable. The first change is the addition of the VARY keyword. The VARY keyword specifies that the parameter value passed to the command processing program (CPP) is to be preceded by a length value. This length value can be either a 2- or 4-byte integer, depending on the second element of the keyword. The VARY keyword shown above indicates that this length value should be a 4-byte integer.
The value of the length passed to the CPP is dependent on the value used with the RTNVAL keyword. When RTNVAL(*NO) is specified, the length passed to the CPP represents the actual number of characters entered for the command, with trailing blanks removed. When RTNVAL(*YES) is specified, the length passed to the CPP represents the declared length of the CL variable used for the parameter. The VAR parameter of the TRMLFTCHR command is defined as RTNVAL(*YES) so the CPP will be passed the declared length of the CL variable specified for the parameter, not the blank-trimmed length of the CL variable value.
The second change is to the LEN keyword. The TRMLFTCHR command previously defined the length of the VAR parameter as being 10 bytes in order to match the definition of the &Char variable in the TRMLFTCHR CPP. The length is now changed to 1 byte. This indicates that the minimum length of the CL variable specified for the VAR parameter is 1 byte. There is no maximum other than the maximum size of a character variable that can be declared (DCL) by the CL compiler.
To accommodate these changes to the TRMLFTCHR command definition, the TRMLFTCHR CPP must also be updated. The initial changes to the program are shown below.
Pgm Parm(&Char_Parm)
Dcl Var(&Char_Parm) Type(*Char) Len(32767)
Dcl Var(&Char_Siz) Type(*Int) Stg(*Defined) +
DefVar(&Char_Parm 1)
Dcl Var(&Char) Type(*Char) Len(32763) +
Stg(*Defined) DefVar(&Char_Parm 5)
Dcl Var(&Char_Pos) Type(*UInt)
Dcl Var(&Char_Rem) Type(*UInt)
Dcl Var(&Char_Tgt) Type(*UInt) Value(1)
DoFor Var(&Char_Pos) From(1) To(&Char_Siz)
If Cond(%sst(&Char &Char_Pos 1) *EQ '0') +
Then(Iterate)
Else Cmd(Leave)
EndDo
If Cond(&Char_Pos *LE &Char_Siz) Then(Do)
DoFor Var(&Char_Pos) From(&Char_Pos) To(&Char_Siz)
ChgVar Var(%sst(&Char &Char_Tgt 1)) +
Value(%sst(&Char &Char_Pos 1))
ChgVar Var(&Char_Tgt) Value(&Char_Tgt + 1)
EndDo
If Cond(&Char_Pos *NE &Char_Tgt) Then(Do)
ChgVar Var(&Char_Rem) +
Value(&Char_Pos - &Char_Tgt)
ChgVar Var(%sst(&Char &Char_Tgt &Char_Rem)) +
Value(' ')
EndDo
EndDo
Else Cmd(ChgVar Var(%sst(&Char &Char_Tgt &Char_Siz)) +
Value('0'))
EndPgm
Note that the program shown above is utilizing V5R4 CL enhancements, such as defined storage. If your system is V5R4 or later, you can skip to the section titled "How the Program Works." If your system is on a release prior to V5R4, continue reading.
Changes for V5R3 and Earlier Releases
Due to the lack of support for features such as declaring character variables that are greater than 9999 bytes in length and utilizing defined storage, changes to the V5R4-level program provided above are necessary. There are many possible ways to rewrite the TRMLFTCHR CPP using previous release-level support, with the below code changes being just one possible implementation.
- Replace
Pgm Parm(&Char_Parm)
Dcl Var(&Char_Parm) Type(*Char) Len(32767)
Dcl Var(&Char_Siz) Type(*Int) Stg(*Defined) +
DefVar(&Char_Parm 1)
Dcl Var(&Char) Type(*Char) Len(32763) +
Stg(*Defined) DefVar(&Char_Parm 5)
with
Pgm Parm(&Char)
Dcl Var(&Char) Type(*Char) Len(9999)
Dcl Var(&Char_Siz) Type(*Int)
as the maximum size of a character variable is 9999 bytes prior to V5R4 and there is no support for defined storage.
- Change the line
Dcl Var(&Char_Tgt) Type(*UInt) Value(1)
to
Dcl Var(&Char_Tgt) Type(*UInt) Value(5)
to skip over the 4 bytes of length information passed to the CPP.
- Add
ChgVar Var(&Char_Siz) Value(%bin(&Char 1 4) + 4)
prior to the line
DoFor Var(&Char_Pos) From(1) To(&Char_Siz)
so that the &Char_Siz variable is set to the declared length of the character variable specified by the TRMLFTCHR VAR parameter plus 4 bytes for the length information passed to the CPP.
- Change the line
DoFor Var(&Char_Pos) From(1) To(&Char_Siz)
to
DoFor Var(&Char_Pos) From(5) To(&Char_Siz)
to skip over the 4 bytes of length information passed to the CPP.
- Change the line
Else Cmd(ChgVar Var(%sst(&Char &Char_Tgt &Char_Siz)) +
Value('0'))
to
Else Cmd(Do)
ChgVar Var(&Char_Siz) Value(&Char_Siz - 4)
ChgVar Var(%sst(&Char &Char_Tgt &Char_Siz)) Value('0')
EndDo
to, once again, accommodate the 4 bytes of length information passed to the CPP.
Note that many of the changes shown above are related to an implementation decision I rather arbitrarily made. The adding and subtracting of four when working with the &Char variable data can be avoided by first moving the character data associated with the &Char_Parm parameter to a temporary variable named &Char, where the first character is indeed located at position 1 (rather than position 5 as when working directly with the &Char_Parm parameter). The left-adjusted, blank-padded value of this temporary variable would then need to be moved back to the &Char_Parm parameter prior to the CPP returning. I chose, as an implementation decision, to not move the VAR data any more than I had to. You may decide otherwise—or better, just decide to upgrade to V5R4 and know that you can be much more productive!
How the Program Works
Let's look at the TRMLFTCHR CPP with this discussion assuming that you are at release level V5R4 or later. Rather than declaring a character parameter of 10 bytes, the updated TRMLFTCHR CPP defines a character parameter of the maximum length supported by the release you are using. The program then redefines the first 4 bytes of the variable &Char_Parm as the variable &Char_Siz, which, as in the previous version of the CPP, represents the length of the CL variable specified by the VAR parameter of the TRMLFTCHR command. The remaining bytes of the &Char_Parm parameter are redefined as the CL character variable &Char. While V5R4 supports a character variable size of up to 32,767 bytes, &Char can be defined only with a length up to 32,763 bytes. This smaller length is due to &Char being a subset of the &Char_Parm character variable, which is defined as 32,767 bytes but also includes the 4-byte length information. This change, redefining the parameter passed to the CPP as the two subfields &Char_Siz and &Char, is necessary because of the use of VARY(*YES *INT4) when defining the VAR parameter of the TRMLFTCHR command.
The change in the definition of the &Char variable, going from a fixed length of 10 to a fixed length of the (almost) maximum supported length for a character variable, is the cause of the other changes found in the TRMLFTCHR CPP. The initial processing to trim leading zeros remains the same, but subsequent processing related to the first non-zero value is quite a bit different. With the original CPP, the definition of the &Char variable matched the minimum required length of the CL variable identified by the TRMLFTCHR VAR variable. Because of this, the CPP was able to use the CHGVAR command as in
ChgVar Var(&Char) Value(%sst(&Char &Char_Pos &Char_Rem))
to implicitly pad the &Char variable byte positions beyond &Char_Rem bytes with blanks up to the declared length of &Char (which was 10). With the updated CPP, the &Char variable is declared with a length of 32,763 bytes. If the program using the TRMLFTCHR command were to pass in a VAR variable DCLed with, say, LEN(15) and a value to left adjust of 10 significant characters ('000001234567890', for instance), then the CPP, if it used the same CHGVAR statement as shown above, would left adjust the 10 significant characters and then write blanks to the next 32,753 bytes of the current thread's automatic storage. And who knows what program variables might be stored in the last 32,748 bytes of that storage (in case you're wondering, the first 5 bytes hold the original right-adjusted value of &Char; it's the next 32,748 that are truly unknown). Writing these extra 32,748 blanks to the thread's storage would most likely put your job solidly into a "results are unpredictable" scenario.
To avoid this, the updated CPP uses the substring built-in, %sst, for all updates to the &Char variable. In this way, the CPP can constrain the CHGVAR commands to update only those bytes of the &Char variable that are declared by the program running the TRMLFTCHR command. The variable &Char_Tgt, which is new, is used to specify what byte location within the &Char variable is to be updated (targeted for update), and most updates are now done 1 character at a time.
When the first non-zero value is encountered (that is, &Char_Pos is less than or equal to &Char_Siz upon exiting the initial DOFOR), the CPP now left-adjusts all significant characters of &Char by processing the characters one by one within a DOFOR loop. After left-adjusting all of the significant characters, the CPP calculates the number of remaining bytes in the &Char variable (&Char_Rem) and writes blanks to these remaining byte locations. This blank-padding is done with one CHGVAR, so it's not byte by byte but is using the %sst built-in with &Char_Rem to limit the number of bytes changed.
If no significant values are found in the input character string (that is, &Char_Pos is greater than &Char_Siz upon exiting the initial DOFOR), then the CPP writes the single character value '0' and blank-pads the remainder of the input character string, again using the %sst built-in to limit the blank-padding to the declared length of the VAR variable specified by the user of the TRMLFTCHR command.
To create the updated command and CPP you can use the commands
CRTCMD CMD(TRMLFTCHR) PGM(TRMLFTCHR) ALLOW(*IPGM *BPGM *IMOD *BMOD)
and either
CRTBNDCL PGM(TRMLFTCHR)
or
CRTCLPGM PGM(TRMLFTCHR)
Testing this new command, you will find that the updated version supports a much wider range of character variable lengths—anything from 1 to 32,763 bytes in fact!
Gotcha!
There are, however, a few problems with the CPP. One, fairly minor, is that our earlier design point was to support a CL character variable of any CL-supported length. Due to the need for the 4-byte integer to precede the character variable value in the &Char_Parm parameter, the current CPP supports only a character-variable length equal to or less than the maximum size of a character variable (for the release) less 4. If you attempt to use the TRMLFTCHR command with a VAR CL variable that has a declared length greater than 32,763, you will most likely receive message CPF0804 – Built-in function operands not valid.
The second, and the larger concern in my mind, is that the maximum length supported for the VAR parameter is hard-coded within the CPP. V5R3 has a hard-coded maximum of 9,999 and V5R4 a hard-coded maximum of 32,767 for the length of the parameter being passed to the CPP. As I really don't want to have to maintain this CPP if, in a future release, IBM enhances CL to support the declaration of character variables larger than 32,767 bytes, I would much rather have the command automatically support any size CL character variable that the then-current release of the operating system might support.
In the next article, we'll look at how to address both of these concerns. However, in the case of automatically supporting a CL character variable of any size, I readily admit that my solution will require some maintenance to the command definition and CPP if the operating system ever allows you to declare a CL character variable larger than 2,147,483,647 bytes in size, a CL DCL enhancement that I'm really not too worried about right now. I suspect I can leave that maintenance chore for my grandson to take care of.
More CL Questions?
Wondering how to accomplish a function in CL? Send your CL-related questions to me at
LATEST COMMENTS
MC Press Online