This command can support an extensive list of trim characters.
In the previous article, "Cut, Snip, Trim with TRMLFTCHR," we saw how the Trim Left Characters (TRMLFTCHR) command could allow the user to specify what leading character to trim when left-adjusting a character string value. Today, we will see what is required to have TRMLFTCHR trim any number of various leading characters from an input string of any length, left-adjust the remaining characters, and then pad the returned character string with blanks to its declared length.
First, we need to change the command definition to indicate that multiple TRMCHR values can be specified. The command change to enable this is shown below.
Cmd Prompt('Trim Left Characters')
Parm Kwd(Var) Type(*Char) Len(1) RtnVal(*Yes) +
Min(1) Vary(*Yes *Int4) +
Prompt('Decimal value')
Parm Kwd(TrmChr) Type(*Char) Len(1) Dft(0) +
SpcVal((0)) Max(50) +
Prompt('Character to trim')
Parm Kwd(AllTrmChr) Type(*Char) Len(1) +
Dft(*TrmChr) SpcVal((*TRMCHR X'FF')) +
Prompt('Character for all trimmed')
The changes are the addition of SPCVAL((0)) and MAX(50) to the definition of the TRMCHR parameter. The default for the MAX keyword of the PARM command is 1. By changing the keyword value to 50, we define the TRMCHR parameter as supporting a list of up to 50 discrete values. The actual number of values that the user specifies will be passed to the command's CPP as a 2-byte integer value immediately followed by a contiguous list of the specified values. This is similar to how a varying-length parameter, such as VAR of TRMLFTCHR, is passed to the CPP as an integer value, indicating the declared length of the associated variable (when the variable is defined as RTNVAL(*YES)), followed immediately by the actual variable value). In the case of a list, the CPP is passed the number of elements within the array (or list) of values and then the values. There are more complex types of lists that can be defined by a command (including lists within lists), but for the TRMCHR parameter, this simple list approach suffices. The SPCVAL keyword is necessary as the TRMCHR parameter is now a list with a default value specified for the first list entry. The special value defined is the default value of 0.
To create the new version of the TRMLFTCHR command, you can use the same CRTCMD command that was used in previous articles:
CRTCMD CMD(TRMLFTCHR) PGM(TRMLFTCHR) ALLOW(*IPGM *BPGM *IMOD *BMOD)
As we've changed the TRMCHR parameter from being a single value to potentially a list of up to 50 values, we also need to update the TRMLFTCHR CPP. The updated source for the TRMLFTCHR program is shown below.
Pgm Parm(&Char_Parm &TrmChrParm &All_TrmChr)
Dcl Var(&Char_Parm) Type(*Char) Len(5)
Dcl Var(&Char_Siz) Type(*Int) Stg(*Defined) +
DefVar(&Char_Parm 1)
Dcl Var(&First_Char) Type(*Char) Len(1) +
Stg(*Defined) DefVar(&Char_Parm 5)
Dcl Var(&TrmChrParm) Type(*Char) Len(3)
Dcl Var(&NbrTrmChr) Type(*UInt) Len(2) +
Stg(*Defined) DefVar(&TrmChrParm 1)
Dcl Var(&First_Trm) Type(*Char) Len(1) +
Stg(*Defined) DefVar(&TrmChrParm 3)
Dcl Var(&All_TrmChr) Type(*Char) Len(1)
Dcl Var(&Char_Ptr) Type(*Ptr)
Dcl Var(&Char) Type(*Char) Len(1) +
Stg(*Based) BasPtr(&Char_Ptr)
Dcl Var(&CharTgtPtr) Type(*Ptr)
Dcl Var(&Char_Tgt) Type(*Char) Len(1) +
Stg(*Based) BasPtr(&CharTgtPtr)
Dcl Var(&TrmChrPtr) Type(*Ptr)
Dcl Var(&TrmChr) Type(*Char) Len(1) +
Stg(*Based) BasPtr(&TrmChrPtr)
Dcl Var(&Char_Pos) Type(*UInt)
Dcl Var(&Char_Rem) Type(*UInt)
Dcl Var(&Trm_Pos) Type(*UInt)
Dcl Var(&XFF) Type(*Char) Len(1) Value(x'FF')
ChgVar Var(&Char_Ptr) Value(%addr(&First_Char))
ChgVar Var(&CharTgtPtr) Value(%addr(&First_Char))
Trim: DoFor Var(&Char_Pos) From(1) To(&Char_Siz)
ChgVar Var(&TrmChrPtr) Value(%addr(&First_Trm))
DoFor Var(&Trm_Pos) From(1) To(&NbrTrmChr)
If Cond(&Char *EQ &TrmChr) Then(Do)
ChgVar Var(%ofs(&Char_Ptr)) +
Value(%ofs(&Char_Ptr) + 1)
Iterate CmdLbl(Trim)
EndDo
Else Cmd(Do)
ChgVar Var(%ofs(&TrmChrPtr)) +
Value(%ofs(&TrmChrPtr) + 1)
Iterate
EndDo
EndDo
Leave
EndDo
If Cond(&Char_Pos *LE &Char_Siz) Then(Do)
DoFor Var(&Char_Pos) From(&Char_Pos) To(&Char_Siz)
ChgVar Var(&Char_Tgt) Value(&Char)
ChgVar Var(%ofs(&CharTgtPtr)) +
Value(%ofs(&CharTgtPtr) + 1)
ChgVar Var(%ofs(&Char_Ptr)) +
Value(%ofs(&Char_Ptr) + 1)
EndDo
If Cond(&Char_Ptr *NE &CharTgtPtr) Then(Do)
ChgVar Var(&Char_Rem) Value( +
(%ofs(&Char_Ptr) - %ofs(&CharTgtPtr)))
DoFor Var(&Char_Pos) From(1) To(&Char_Rem)
ChgVar Var(&Char_Tgt) Value(' ')
ChgVar Var(%ofs(&CharTgtPtr)) +
Value(%ofs(&CharTgtPtr) + 1)
EndDo
EndDo
EndDo
Else Cmd(Do)
If Cond(&All_TrmChr *EQ &XFF) Then(Do)
ChgVar Var(&TrmChrPtr) Value(%addr(&First_Trm))
ChgVar Var(&Char_Tgt) Value(&TrmChr)
EndDo
Else Cmd( +
ChgVar Var(&Char_Tgt) Value(&All_TrmChr))
ChgVar Var(%ofs(&CharTgtPtr)) +
Value(%ofs(&CharTgtPtr) + 1)
DoFor Var(&Char_Pos) From(2) To(&Char_Siz)
ChgVar Var(&Char_Tgt) Value(' ')
ChgVar Var(%ofs(&CharTgtPtr)) +
Value(%ofs(&CharTgtPtr) + 1)
EndDo
EndDo
EndPgm
Reviewing the updated program shown above, the following changes have been made:
- The name of the second parameter passed to the TRMLFTCHR CPP is changed to &TrmChrParm.
Parm(&Char_Parm &TrmChrParm &All_TrmChr)
This change is made solely for consistency with earlier versions of the CPP. As you will see shortly, this change allows us to continue to use the variable name &TrmChr to represent the trim character being tested.
- The parameter &TrmChrParm is declared with this definition:
Dcl Var(&TrmChrParm) Type(*Char) Len(3)
Dcl Var(&NbrTrmChr) Type(*UInt) Len(2) +
Stg(*Defined) DefVar(&TrmChrParm 1)
Dcl Var(&First_Trm) Type(*Char) Len(1) +
Stg(*Defined) DefVar(&TrmChrParm 3)
Using the same style previously used to define the parameter &Char_Parm, we use defined storage to redefine &TrmChrParm as a 2-byte unsigned integer (&NbrTrmChr) representing the number of trim characters specified, and a 1-byte character variable (&First_Trm) representing the first trim character in the TRMCHR list specified by the user.
- Again following the style previously used to support the varying-length parameter &Char_Parm, we define a pointer to address the trim characters (&TrmChrPtr), a 1-byte character variable (&TrmChr) based on the &TrmChrPtr pointer, and an unsigned integer (&Trm_Pos) to serve as a DOFOR control variable when looping through the user-specified trim character list.
Dcl Var(&TrmChrPtr) Type(*Ptr)
Dcl Var(&TrmChr) Type(*Char) Len(1) +
Stg(*Based) BasPtr(&TrmChrPtr)
Dcl Var(&Trm_Pos) Type(*UInt)
- Associate the command label "Trim:" to the initial DOFOR loop of the CPP:
Trim: DoFor Var(&Char_Pos) From(1) To(&Char_Siz)
This label is used as the target of an ITERATE command used to control the flow of the program when searching for leading trim characters.
- Replace the previous test for a single trim character…
If Cond(&Char *EQ &TrmChr) Then(Do)
ChgVar Var(%ofs(&Char_Ptr)) Value(%ofs(&Char_Ptr) + 1)
Iterate
EndDo
Else Cmd(Leave)
…with the following:
ChgVar Var(&TrmChrPtr) Value(%addr(&First_Trm))
DoFor Var(&Trm_Pos) From(1) To(&NbrTrmChr)
If Cond(&Char *EQ &TrmChr) Then(Do)
ChgVar Var(%ofs(&Char_Ptr)) +
Value(%ofs(&Char_Ptr) + 1)
Iterate CmdLbl(Trim)
EndDo
Else Cmd(Do)
ChgVar Var(%ofs(&TrmChrPtr)) +
Value(%ofs(&TrmChrPtr) + 1)
Iterate
EndDo
EndDo
Leave
As the program is now checking for more than one trim character, the program must, prior to testing the list of trim characters, reset the pointer variable &TrmChrPtr. This ensures that the program is consistently starting from the beginning of the list for each comparison to the current character. This "reset" is done by setting &TrmChrPtr to the address of &First_Trm. The program then enters a DOFOR loop, controlled by the number of trim characters specified (&NbrTrmChr), and tests the current character of the input character string (&Char) to the trim character currently being tested for (&TrmChr).
If they are equal, the program trims the current character by moving to the next character in the input character string and restarting the initial DOFOR loop. This is done by incrementing the pointer variable &Char_Ptr by one and using the command ITERATE CMDLBL(TRIM) to pass control back to the DOFOR loop identified by the label Trim.
If &Char and &TrmChr are not equal, the program tests the next trim character by incrementing pointer variable &TrmChrPtr by one and using the command ITERATE, with no CMDLBL keyword, to pass control back to the innermost DOFOR loop (the one associated with testing each specified trim character).
The only time this inner DOFOR loop is exited is when &Char is not equal to &TrmChr and all specified trim characters have been tested. In this situation, the program LEAVEs the DOFOR loop associated with the label "Trim," left-adjusts all characters from the current position to the end of the input character string, and blank-pads the resulting string. The logic associated with the left-adjusting and blank-padding of the character string is unchanged from the previous version of the CPP.
- Replace the logic associated with the *TRMCHR special value of keyword ALLTRMCHR…
If Cond(&All_TrmChr *EQ &XFF) Then( +
ChgVar Var(&Char_Tgt) Value(&TrmChr))
…with
If Cond(&All_TrmChr *EQ &XFF) Then(Do)
ChgVar Var(&TrmChrPtr) Value(%addr(&First_Trm))
ChgVar Var(&Char_Tgt) Value(&TrmChr)
EndDo
With this change, the use of special value *TRMCHR for the ALLTRMCHR keyword now indicates that the first trim character specified by the TRMCHR parameter list is to be used when no significant characters are found in the input character string.
To compile the new CPP, use either of the following commands:
CRTBNDCL PGM(TRMLFTCHR)
or
CRTCLPGM PGM(TRMLFTCHR)
As mentioned in the earlier article, "Going Where No Substring (%SST) Operation Can Go," if you are using the CRTBNDCL command to create an ILE program, make sure that you have the following PTFs applied to your system prior to compiling the CPP:
- V5R4 SI39398
- V6R1 SI39405
- V7R1 SI39407
To test the latest version of the TRMLFTCHR command, you can use the following CL program:
Pgm
Dcl Var(&Char10) Type(*Char) Len(10)
Dcl Var(&Char50) Type(*Char) Len(50)
ChgVar Var(&Char10) Value(12.34)
SndPgmMsg Msg('Originally' *BCat &Char10)
TrmLftChr Var(&Char10)
SndPgmMsg Msg('Now.......' *BCat &Char10)
ChgVar Var(&Char10) Value(0)
SndPgmMsg Msg('Originally' *BCat &Char10)
TrmLftChr Var(&Char10) AllTrmChr('?')
SndPgmMsg Msg('Now.......' *BCat &Char10)
ChgVar Var(&Char10) Value(0)
SndPgmMsg Msg('Originally' *BCat &Char10)
TrmLftChr Var(&Char10)
SndPgmMsg Msg('Now.......' *BCat &Char10)
ChgVar Var(&Char10) Value('***ABC EF')
SndPgmMsg Msg('Originally' *BCat &Char10)
TrmLftChr Var(&Char10)
SndPgmMsg Msg('Now.......' *BCat &Char10)
ChgVar Var(&Char10) Value('***ABC EF')
SndPgmMsg Msg('Originally' *BCat &Char10)
TrmLftChr Var(&Char10) TrmChr(*)
SndPgmMsg Msg('Now.......' *BCat &Char10)
ChgVar Var(&Char50) Value(' ABCDEF')
SndPgmMsg Msg('Originally' *BCat &Char50)
TrmLftChr Var(&Char50) TrmChr(' ')
SndPgmMsg Msg('Now.......' *BCat &Char50)
ChgVar Var(&Char10) Value(' *1.23')
SndPgmMsg Msg('Originally' *BCat &Char10)
TrmLftChr Var(&Char10) TrmChr(* ' ')
SndPgmMsg Msg('Now.......' *BCat &Char10)
ChgVar Var(&Char10) Value('* * * 1.23')
SndPgmMsg Msg('Originally' *BCat &Char10)
TrmLftChr Var(&Char10) TrmChr(* ' ')
SndPgmMsg Msg('Now.......' *BCat &Char10)
ChgVar Var(&Char50) Value(' * * *1.23')
SndPgmMsg Msg('Originally' *BCat &Char50)
TrmLftChr Var(&Char50) TrmChr(* ' ')
SndPgmMsg Msg('Now.......' *BCat &Char50)
EndPgm
This sample program provides for both regression testing, in that the first several tests are the same we used in the previous article, and also new function testing with the addition of the last few test cases. Running the test program will result in these messages being displayed:
Originally 0000012.34
Now....... 12.34
Originally 0000000000
Now....... ?
Originally 0000000000
Now....... 0
Originally ***ABC EF
Now....... ***ABC EF
Originally ***ABC EF
Now....... ABC EF
Originally ABCDEF
Now....... ABCDEF
Originally *1.23
Now....... 1.23
Originally * * * 1.23
Now....... 1.23
Originally * * *1.23
Now....... 1.23
In developing the TRMLFTCHR command, you have seen how to create user commands incorporating several features. These features included returning a value to a CL variable, supporting varying-length character variables, defining default values for parameters, supporting a list of values for a parameter, and mapping special values to a replacement value. You may also have picked up a few CL programming techniques related to defined storage, based storage, and pointers. With these latest changes, we've pretty much exhausted what a user might want to do in terms of left-adjusting the value of a character variable.
Returning briefly to the original scenario in the article "Create Reusable Code," where we wanted to left-adjust and blank-pad a decimal value, a user command could also have been created to eliminate the need for using CHGVAR to change the TYPE(*DEC) numeric variable &Number to the TYPE(*CHAR) character variable &Char prior to using the TRMLFTCHR command. A command such as Edit Decimal Value (EDTDECVXCL), provided in the no-charge base of the eXtreme CL product, could have been implemented. The EDTDECVXCL command accepts a TYPE(*DEC) CL variable of any (CL-supported) digit and decimal position length, applies either an edit code or an edit word to the decimal value, and then returns the edited value as either a left-adjusted or right-adjusted character variable of any length. In other words, rather than coding…
Pgm
Dcl Var(&Number) Type(*Dec) Len(6 2) Value(1.23)
Dcl Var(&Char) Type(*Char) Len(10)
ChgVar Var(&Char) Value(&Number)
TrmLftChr Var(&Char)
SndPgmMsg Msg('The cost is $' *Cat &Char)
EndPgm
…we could have used an implementation such as this:
Pgm
Dcl Var(&Number) Type(*Dec) Len(6 2) Value(1.23)
Dcl Var(&Char) Type(*Char) Len(10)
EdtDecVXCL Var(&Char) Value(&Number) EdtCde(3) Align(*Left)
SndPgmMsg Msg('The cost is $' *Cat &Char)
EndPgm
Both programs would display the same message:
The cost is $1.23
I elected to use the TRMLFTCHR approach in this series as I believe it provides for a more gradual learning curve when discussing the creation of user commands. User-created commands, however, can support many more features than those we've discussed, such as decimal variables of varying digits and decimal position as shown above. I encourage you to prompt the PARM command and review everything that can be done with a command parameter. Commands provide you with a very easy way to productively share common logic across your application programs. In this series of articles, we've only scratched the surface of what you can do!
One item, however, that is lacking, though not tied directly to the PARM command, is any online help for the TRMLFTCHR command. In the next article, we'll see how we can add F1 (Help) support to TRMLFTCHR.
More CL Questions?
Wondering how to accomplish a function in CL? Send your CL-related questions to me at
LATEST COMMENTS
MC Press Online