The CL Corner: Trim Multiple Leading Characters with TRMLFTCHR

CL
Typography
  • Smaller Small Medium Big Bigger
  • Default Helvetica Segoe Georgia Times

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 This email address is being protected from spambots. You need JavaScript enabled to view it.. I'll try to answer your burning questions in future columns.

BLOG COMMENTS POWERED BY DISQUS

LATEST COMMENTS

Support MC Press Online

$0.00 Raised:
$